#!/usr/bin/env python3 """IMA 上传单篇 Markdown 知识笔记到 daily 知识库。 用法: python3 ima_upload_one.py "<绝对路径/summary.md>" "<完整文章标题>" 依赖环境变量: IMA_OPENAPI_CLIENTID / IMA_OPENAPI_APIKEY / IMA_DAILY_KNOWLEDGE_BASE_ID 流程: preflight -> check_repeated_names -> create_media -> cos-upload -> add_knowledge 说明: 用 Python 而非 bash 编排,避免中文文件名/引号转义问题。 退出码 2 = 文件名重复(需与用户确认保留双方或取消),非 0 均为失败。 """ import json, os, subprocess, sys SKILL_DIR = "/root/.hermes/skills/openclaw-imports/ima-skill" IMA_API = os.path.join(SKILL_DIR, "ima_api.cjs") COS_UPLOAD = os.path.join(SKILL_DIR, "knowledge-base/scripts/cos-upload.cjs") PREFLIGHT = os.path.join(SKILL_DIR, "knowledge-base/scripts/preflight-check.cjs") def run_node(script, args): r = subprocess.run(["node", script] + args, capture_output=True, text=True, timeout=120) if r.returncode != 0: raise RuntimeError(f"{script} exit={r.returncode} stderr={r.stderr[:500]}") return json.loads(r.stdout) def ima_api(api_path, body): r = subprocess.run(["node", IMA_API, api_path, json.dumps(body, ensure_ascii=False)], capture_output=True, text=True, timeout=120) if r.returncode != 0: raise RuntimeError(f"ima_api {api_path} exit={r.returncode} stderr={r.stderr[:500]}") resp = json.loads(r.stdout) if resp.get("code") != 0: raise RuntimeError(f"ima_api {api_path} code={resp.get('code')} msg={resp.get('msg')}") return resp.get("data", {}) def main(): kb_id = os.environ["IMA_DAILY_KNOWLEDGE_BASE_ID"] file_path = sys.argv[1] title = sys.argv[2] # 完整文章标题(不含 .md) pf = run_node(PREFLIGHT, ["--file", file_path]) if not pf.get("pass"): raise RuntimeError(f"preflight failed: {pf}") file_name = pf["file_name"]; media_type = pf["media_type"] content_type = pf["content_type"]; file_size = pf["file_size"]; file_ext = pf["file_ext"] print(f"[preflight] ok file={file_name} ext={file_ext} size={file_size} media_type={media_type}") dup = ima_api("openapi/wiki/v1/check_repeated_names", { "params": [{"name": file_name, "media_type": media_type}], "knowledge_base_id": kb_id }) is_rep = dup.get("results", [{}])[0].get("is_repeated", False) if dup.get("results") else False if is_rep: print(f"[check_repeated_names] REPEATED: {file_name} — 需要处理") sys.exit(2) print("[check_repeated_names] no duplicate") cm = ima_api("openapi/wiki/v1/create_media", { "file_name": file_name, "file_size": file_size, "content_type": content_type, "knowledge_base_id": kb_id, "file_ext": file_ext }) media_id = cm["media_id"] cos = cm["cos_credential"] print(f"[create_media] media_id={media_id} cos_bucket={cos.get('bucket_name')} cos_key={cos.get('cos_key','')[:40]}") r = subprocess.run(["node", COS_UPLOAD, "--file", file_path, "--secret-id", cos["secret_id"], "--secret-key", cos["secret_key"], "--token", cos["token"], "--bucket", cos["bucket_name"], "--region", cos["region"], "--cos-key", cos["cos_key"], "--content-type", content_type, "--start-time", str(cos.get("start_time", "")), "--expired-time", str(cos.get("expired_time", "")), "--timeout", "300000" ], capture_output=True, text=True, timeout=360) if r.returncode != 0: raise RuntimeError(f"cos-upload exit={r.returncode} stderr={r.stderr[:800]}") print(f"[cos-upload] ok rc=0 stdout={r.stdout.strip()[:200]}") ak = ima_api("openapi/wiki/v1/add_knowledge", { "media_type": media_type, "media_id": media_id, "title": title, "knowledge_base_id": kb_id, "file_info": { "cos_key": cos["cos_key"], "file_size": file_size, "file_name": file_name } }) print(f"[add_knowledge] ok media_id={ak.get('media_id') or media_id}") if __name__ == "__main__": main()