exomind 0.2.6 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,19 +42,33 @@ exomind feedback "entities/Redis.md" positive
42
42
 
43
43
  加 `--json` 获取机器可读输出(脚本/管道):`exomind --json stats | jq .total_nodes`。
44
44
 
45
- ## 接入 Claude Code
45
+ ## 接入 Claude Code(一条命令,默认全装)
46
46
 
47
47
  ```bash
48
- exomind install --with-hook
48
+ exomind install # 装 skill + hook + MCP,全部自动(幂等+备份)
49
+ # 不想要某项: exomind install --no-hook 或 --no-mcp
49
50
  ```
50
51
 
51
- 一行完成:① 装 Claude Code skill(教 Agent 何时用 CLI);② 写入 `UserPromptSubmit` hook 指向 `exomind hook`(幂等,自动备份,不影响其它工具)。重启 Claude Code 后:
52
+ 一行完成三层,免手改任何 JSON:
53
+ - **① MCP 工具**(能力):写 `~/.claude.json` 的 `mcpServers.exomind` → Agent 拿到 `mcp__exomind__*` 确定性工具。
54
+ - **② skill**(指导):拷到 `~/.claude/skills/exomind/`。
55
+ - **③ hook**(闸门):写 `settings.json` 的 `UserPromptSubmit → exomind hook`。
52
56
 
53
- - **`存档`** / **`jdit`** → 自动回顾会话、运行 `exomind ingest`。
57
+ 重启 Claude Code 后:
58
+ - 说 **`存档`** / **`jdit`** → 自动回顾会话、摄入。
54
59
  - 提问涉及知识库已有实体 → 自动注入 `[ExoMind 知识库上下文]`。
55
- - 产出经验/踩坑/架构决策/性能数据/最佳实践自动提示摄入(飞轮)。
60
+ - Agent 需查询/摄入时 直接调 `mcp__exomind__*`(确定),或按 skill 跑 `exomind` CLI
56
61
 
57
- 即便不装 hook,skill 也会让 Agent 主动遵循飞轮规则。完整接入步骤见服务端仓库 `myExoMindManager/docs/new-machine-setup.md`。
62
+ 完整接入步骤见服务端仓库 `myExoMindManager/docs/new-machine-setup.md`。
63
+
64
+ ## 关于 MCP 工具层(Claude Code 已默认装;OpenCode 需手写)
65
+
66
+ `exomind install` 已默认把 MCP 写进 Claude Code 的 `~/.claude.json`(见上)。这里仅给**其它宿主/手写**场景:
67
+
68
+ - **OpenCode**(`opencode.json`):`{ "mcp": { "exomind": { "type": "local", "command": ["exomind", "mcp"] } } }`
69
+ - **手写 Claude Code**:`~/.claude.json` 或项目 `.mcp.json`:`{ "mcpServers": { "exomind": { "command": "exomind", "args": ["mcp"] } } }`
70
+
71
+ `exomind mcp` 是本地 stdio MCP server,把 ingest/query/search/entity/relations/stats 暴露为 typed tool;复用同一份凭证,三平台都能跑(本地 stdio,不涉及远程 SSE 的 Windows 坑)。详见 [三层模式说明](./docs/mcp.md)。
58
72
 
59
73
  ## 命令一览
60
74
 
package/dist/cli.js CHANGED
@@ -3119,7 +3119,7 @@ var {
3119
3119
  // package.json
3120
3120
  var package_default = {
3121
3121
  name: "exomind",
3122
- version: "0.2.6",
3122
+ version: "0.3.1",
3123
3123
  description: "ExoMind \u8DE8\u5E73\u53F0\u547D\u4EE4\u884C\u5BA2\u6237\u7AEF \u2014 \u901A\u8FC7 REST \u4E0E ExoMind \u77E5\u8BC6\u5E93\u4EA4\u4E92(ingest/query/search/review),\u66FF\u4EE3 Windows \u4E0D\u53EF\u7528\u7684 MCP \u5BA2\u6237\u7AEF\u3002",
3124
3124
  bin: {
3125
3125
  exomind: "dist/cli.js"
@@ -3624,6 +3624,160 @@ async function runHook(client) {
3624
3624
  }
3625
3625
  }
3626
3626
 
3627
+ // src/mcp.ts
3628
+ var readline = __toESM(require("readline"));
3629
+ var PROTOCOL_VERSION = "2024-11-05";
3630
+ var TOOLS = [
3631
+ {
3632
+ name: "ingest",
3633
+ description: "\u5411 ExoMind \u77E5\u8BC6\u5E93\u5BFC\u5165\u77E5\u8BC6(\u6587\u672C),\u81EA\u52A8\u62BD\u53D6\u5B9E\u4F53/\u5173\u7CFB\u3002",
3634
+ inputSchema: {
3635
+ type: "object",
3636
+ properties: {
3637
+ content: { type: "string", description: "\u8981\u5BFC\u5165\u7684\u6587\u672C\u5185\u5BB9" },
3638
+ title: { type: "string", description: "\u6807\u9898(\u53EF\u9009)" },
3639
+ tags: { type: "array", items: { type: "string" }, description: "\u6807\u7B7E(\u53EF\u9009)" }
3640
+ },
3641
+ required: ["content"]
3642
+ }
3643
+ },
3644
+ {
3645
+ name: "query",
3646
+ description: "LLM \u95EE\u7B54:\u57FA\u4E8E\u77E5\u8BC6\u5E93\u56DE\u7B54\u95EE\u9898\u3002",
3647
+ inputSchema: {
3648
+ type: "object",
3649
+ properties: {
3650
+ question: { type: "string", description: "\u95EE\u9898" },
3651
+ tags: { type: "array", items: { type: "string" }, description: "\u6807\u7B7E\u8FC7\u6EE4(\u53EF\u9009)" }
3652
+ },
3653
+ required: ["question"]
3654
+ }
3655
+ },
3656
+ {
3657
+ name: "search",
3658
+ description: "\u5168\u6587\u641C\u7D22\u77E5\u8BC6\u5E93\u3002",
3659
+ inputSchema: {
3660
+ type: "object",
3661
+ properties: {
3662
+ keyword: { type: "string", description: "\u5173\u952E\u8BCD" },
3663
+ limit: { type: "integer", description: "\u8FD4\u56DE\u6570\u91CF(\u9ED8\u8BA4 10)" }
3664
+ },
3665
+ required: ["keyword"]
3666
+ }
3667
+ },
3668
+ {
3669
+ name: "entity",
3670
+ description: "\u83B7\u53D6\u5B9E\u4F53\u8BE6\u60C5(\u63CF\u8FF0\u3001\u522B\u540D\u3001\u5173\u7CFB)\u3002",
3671
+ inputSchema: {
3672
+ type: "object",
3673
+ properties: { name: { type: "string", description: "\u5B9E\u4F53\u540D\u79F0" } },
3674
+ required: ["name"]
3675
+ }
3676
+ },
3677
+ {
3678
+ name: "relations",
3679
+ description: "\u83B7\u53D6\u5B9E\u4F53\u7684\u5173\u8054\u5B9E\u4F53(\u53EF\u8FBE\u6027)\u3002",
3680
+ inputSchema: {
3681
+ type: "object",
3682
+ properties: {
3683
+ name: { type: "string", description: "\u5B9E\u4F53\u540D\u79F0" },
3684
+ depth: { type: "integer", description: "\u6DF1\u5EA6 1-3(\u9ED8\u8BA4 1)" }
3685
+ },
3686
+ required: ["name"]
3687
+ }
3688
+ },
3689
+ {
3690
+ name: "stats",
3691
+ description: "\u77E5\u8BC6\u5E93\u7EDF\u8BA1(\u8282\u70B9/\u5173\u7CFB\u6570)\u3002",
3692
+ inputSchema: { type: "object", properties: {} }
3693
+ }
3694
+ ];
3695
+ async function handleTool(client, name, args) {
3696
+ switch (name) {
3697
+ case "ingest":
3698
+ return client.post("/ingest", args, { timeoutMs: opTimeout(3e5) });
3699
+ case "query":
3700
+ return client.post("/query", args, { timeoutMs: opTimeout(18e4) });
3701
+ case "search":
3702
+ return client.get("/search", { q: args.keyword ?? args.q, limit: args.limit ?? 10 });
3703
+ case "entity":
3704
+ return client.get(`/entities/${encodeURIComponent(String(args.name))}`);
3705
+ case "relations":
3706
+ return client.get(`/relations/${encodeURIComponent(String(args.name))}`, { depth: args.depth ?? 1 });
3707
+ case "stats":
3708
+ return client.get("/stats");
3709
+ default:
3710
+ throw new Error(`\u672A\u77E5\u5DE5\u5177: ${name}`);
3711
+ }
3712
+ }
3713
+ async function handleMessage(client, msg) {
3714
+ const id = msg.id;
3715
+ const method = msg.method;
3716
+ if (method === "initialize") {
3717
+ return {
3718
+ jsonrpc: "2.0",
3719
+ id,
3720
+ result: {
3721
+ protocolVersion: PROTOCOL_VERSION,
3722
+ capabilities: { tools: {} },
3723
+ serverInfo: { name: "exomind", version: package_default.version }
3724
+ }
3725
+ };
3726
+ }
3727
+ if (method === "notifications/initialized" || method === "initialized") return null;
3728
+ if (method === "ping") return { jsonrpc: "2.0", id, result: {} };
3729
+ if (method === "tools/list") return { jsonrpc: "2.0", id, result: { tools: TOOLS } };
3730
+ if (method === "tools/call") {
3731
+ const params = msg.params ?? {};
3732
+ try {
3733
+ const data = await handleTool(client, params.name ?? "", params.arguments ?? {});
3734
+ return {
3735
+ jsonrpc: "2.0",
3736
+ id,
3737
+ result: {
3738
+ content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }]
3739
+ }
3740
+ };
3741
+ } catch (e) {
3742
+ const message = e instanceof Error ? e.message : String(e);
3743
+ return {
3744
+ jsonrpc: "2.0",
3745
+ id,
3746
+ result: { content: [{ type: "text", text: `\u9519\u8BEF: ${message}` }], isError: true }
3747
+ };
3748
+ }
3749
+ }
3750
+ return { jsonrpc: "2.0", id, error: { code: -32601, message: `Unknown method: ${method}` } };
3751
+ }
3752
+ async function runMcpServer(client) {
3753
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
3754
+ rl.on("line", async (line) => {
3755
+ const trimmed = line.trim();
3756
+ if (!trimmed) return;
3757
+ let msg;
3758
+ try {
3759
+ msg = JSON.parse(trimmed);
3760
+ } catch {
3761
+ return;
3762
+ }
3763
+ try {
3764
+ const resp = await handleMessage(client, msg);
3765
+ if (resp) process.stdout.write(JSON.stringify(resp) + "\n");
3766
+ } catch (e) {
3767
+ process.stdout.write(
3768
+ JSON.stringify({
3769
+ jsonrpc: "2.0",
3770
+ id: msg?.id ?? null,
3771
+ error: { code: -32603, message: e instanceof Error ? e.message : String(e) }
3772
+ }) + "\n"
3773
+ );
3774
+ }
3775
+ });
3776
+ return new Promise((resolve6) => {
3777
+ rl.on("close", () => resolve6());
3778
+ });
3779
+ }
3780
+
3627
3781
  // src/commands/ingest.ts
3628
3782
  var path5 = __toESM(require("path"));
3629
3783
 
@@ -4056,10 +4210,10 @@ async function daily(client, opts) {
4056
4210
  }
4057
4211
 
4058
4212
  // src/commands/login.ts
4059
- var readline = __toESM(require("readline/promises"));
4213
+ var readline2 = __toESM(require("readline/promises"));
4060
4214
  var import_node_process = require("process");
4061
4215
  async function prompt(question) {
4062
- const rl = readline.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
4216
+ const rl = readline2.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
4063
4217
  try {
4064
4218
  return (await rl.question(question)).trim();
4065
4219
  } finally {
@@ -4154,7 +4308,7 @@ async function install(_client, opts) {
4154
4308
  fs6.copyFileSync(SKILL_SRC, path6.join(skillDestDir, "SKILL.md"));
4155
4309
  console.log(ok("\u5DF2\u5B89\u88C5 Claude Code skill"));
4156
4310
  console.log(dim(` \u2192 ${skillDestDir}/SKILL.md`));
4157
- if (opts.withHook) {
4311
+ if (opts.hook !== false) {
4158
4312
  const settingsFile = path6.join(claudeDir, "settings.json");
4159
4313
  fs6.mkdirSync(claudeDir, { recursive: true });
4160
4314
  backup(settingsFile);
@@ -4173,9 +4327,20 @@ async function install(_client, opts) {
4173
4327
  console.log(ok("\u5DF2\u914D\u7F6E UserPromptSubmit hook \u2192 exomind hook"));
4174
4328
  console.log(dim(" \u91CD\u542F Claude Code \u751F\u6548\u3002hook \u51FA\u9519\u4E0D\u4F1A\u963B\u585E\u4F60\u7684\u8F93\u5165(\u9519\u8BEF\u53EA\u8FDB stderr)\u3002"));
4175
4329
  }
4330
+ if (opts.mcp !== false) {
4331
+ const claudeJson = path6.join(os2.homedir(), ".claude.json");
4332
+ backup(claudeJson);
4333
+ const cc = readJson2(claudeJson) ?? {};
4334
+ const mcpServers = cc.mcpServers ?? {};
4335
+ mcpServers.exomind = { command: "exomind", args: ["mcp"] };
4336
+ cc.mcpServers = mcpServers;
4337
+ fs6.writeFileSync(claudeJson, JSON.stringify(cc, null, 2) + "\n");
4338
+ console.log(ok("\u5DF2\u914D\u7F6E MCP server \u2192 exomind mcp"));
4339
+ console.log(dim(" \u2192 ~/.claude.json (mcpServers.exomind);\u91CD\u542F Claude Code \u540E Agent \u62FF\u5230 mcp__exomind__* \u5DE5\u5177"));
4340
+ }
4176
4341
  console.log(yellow("\n\u4E0B\u4E00\u6B65:"));
4177
4342
  console.log(dim(" 1. \u82E5\u672A\u914D\u7F6E\u51ED\u8BC1: exomind login"));
4178
- console.log(dim(" 2. \u91CD\u542F Claude Code(\u82E5\u88C5\u4E86 hook)"));
4343
+ console.log(dim(" 2. \u91CD\u542F Claude Code \u2192 skill + hook + mcp__exomind__* \u5168\u90E8\u751F\u6548"));
4179
4344
  }
4180
4345
 
4181
4346
  // src/cli.ts
@@ -4248,7 +4413,18 @@ program2.command("hook").description("UserPromptSubmit \u94A9\u5B50(\u7531 Claud
4248
4413
  }
4249
4414
  process.exit(0);
4250
4415
  });
4251
- program2.command("install").description("\u5B89\u88C5 Claude Code skill(\u53EF\u9009 --with-hook \u5199\u5165 UserPromptSubmit)").option("--with-hook", "\u540C\u65F6\u5199\u5165 ~/.claude/settings.json \u7684 hook").action(run(install));
4416
+ program2.command("mcp").description("\u542F\u52A8 stdio MCP server(\u4F9B Claude Code/OpenCode/Cursor \u4F5C\u4E3A MCP \u5DE5\u5177\u63A5\u5165)").action(async (...a) => {
4417
+ const command = a[a.length - 1];
4418
+ const root = rootOptions(command);
4419
+ setJsonMode(false);
4420
+ const cfg = resolveConfig({
4421
+ baseUrl: root.baseUrl,
4422
+ apiKey: root.apiKey
4423
+ });
4424
+ const client = new ApiClient(cfg);
4425
+ await runMcpServer(client);
4426
+ });
4427
+ program2.command("install").description("\u5B89\u88C5 skill + hook + MCP(\u9ED8\u8BA4\u5168\u88C5;--no-hook / --no-mcp \u5173\u95ED\u67D0\u9879)").option("--no-hook", "\u4E0D\u5199 UserPromptSubmit hook").option("--no-mcp", "\u4E0D\u5199 MCP server \u914D\u7F6E").action(run(install));
4252
4428
  async function main() {
4253
4429
  const first = process.argv[2];
4254
4430
  if (first && /^(-v|--version)$/.test(first)) {