exomind 0.4.2 → 0.5.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 +17 -8
- package/dist/cli.js +270 -76
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/skill/codex/SKILL.md +49 -0
- /package/skill/{SKILL.md → claude/SKILL.md} +0 -0
package/README.md
CHANGED
|
@@ -56,13 +56,18 @@ exomind feedback "entities/Redis.md" positive
|
|
|
56
56
|
|
|
57
57
|
> 把 API Key 当密码对待:不提交进仓库、不贴进聊天。脚本化登录用 `exomind login --api-key ...`(注意别让 key 进 shell 历史或进程列表);交互式 `exomind login` 的 prompt 读取不走 shell 历史。
|
|
58
58
|
|
|
59
|
-
## 接入 Claude Code(一条命令,默认全装)
|
|
59
|
+
## 接入 Claude Code / Codex(一条命令,默认全装)
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
exomind install # 装 skill
|
|
63
|
-
#
|
|
62
|
+
exomind install # 装 skill(Claude+Codex)+ hook(Claude)+ MCP(各宿主),全部自动(幂等+备份)
|
|
63
|
+
# 只装某个宿主: exomind install --host codex (claude | codex | opencode)
|
|
64
|
+
# 跳过某项: --no-skill / --no-hook / --no-mcp
|
|
64
65
|
```
|
|
65
66
|
|
|
67
|
+
**Codex**(skill + MCP,无 hook):装到 `$CODEX_HOME/skills/exomind/`(缺省 `~/.codex/`),MCP 写入 `$CODEX_HOME/config.toml`。**Codex 没有 Claude 的 UserPromptSubmit hook**,靠 skill 的触发词(jdit/存档/查询)驱动;MCP `ingest` 只收文本,目录导入走 `exomind ingest --dir`。装完**重启 Codex**,skill 列表应出现 `exomind`。
|
|
68
|
+
|
|
69
|
+
排查各宿主装没装上:`exomind doctor`(或 `exomind --json doctor`)。
|
|
70
|
+
|
|
66
71
|
一行完成三层,免手改任何 JSON:
|
|
67
72
|
- **① MCP 工具**(能力):写 `~/.claude.json` 的 `mcpServers.exomind` → Agent 拿到 `mcp__exomind__*` 确定性工具。
|
|
68
73
|
- **② skill**(指导):拷到 `~/.claude/skills/exomind/`。
|
|
@@ -77,13 +82,14 @@ exomind install # 装 skill + hook + MCP,全部自动(幂等+备份)
|
|
|
77
82
|
|
|
78
83
|
**升级**:`npm i -g exomind@latest && exomind install`(幂等,刷新 skill/hook/mcp;`~/.exomind/` 的 config 与 manifest 保留)。注意:`npm i -g` 只换二进制(CLI+MCP 自动用新),**skill 是拷贝的,需 `exomind install` 才刷新**。
|
|
79
84
|
|
|
80
|
-
## 关于 MCP 工具层(Claude Code
|
|
85
|
+
## 关于 MCP 工具层(Claude Code / OpenCode / Codex 都已默认装)
|
|
81
86
|
|
|
82
|
-
`exomind install`
|
|
87
|
+
`exomind install` 一次写**三个宿主**的 MCP 配置(都幂等+备份,互不干扰,各读各的):
|
|
83
88
|
- **Claude Code**:`~/.claude.json` → `mcpServers.exomind`
|
|
84
89
|
- **OpenCode**:`~/.config/opencode/opencode.json` → `mcp.exomind`
|
|
90
|
+
- **Codex**:`$CODEX_HOME/config.toml` → `[mcp_servers.exomind]`
|
|
85
91
|
|
|
86
|
-
只关 MCP:`exomind install --no-mcp`(
|
|
92
|
+
只关 MCP:`exomind install --no-mcp`(三个宿主都不写)。手写/其它宿主(如 Cursor)参考 [docs/mcp.md](./docs/mcp.md)。
|
|
87
93
|
|
|
88
94
|
`exomind mcp` 是本地 stdio MCP server,把 ingest/query/search/entity/relations/stats 暴露为 typed tool;复用同一份凭证,三平台都能跑(本地 stdio,不涉及远程 SSE 的 Windows 坑)。
|
|
89
95
|
|
|
@@ -100,7 +106,9 @@ exomind install # 装 skill + hook + MCP,全部自动(幂等+备份)
|
|
|
100
106
|
| `review` / `review mark` | FSRS-5 复习队列与评分 |
|
|
101
107
|
| `synthesize` / `topics` / `gaps` / `daily` | 主题综合、选题、缺口、每日摘要 |
|
|
102
108
|
| `feedback` | 质量反馈(影响搜索排名) |
|
|
103
|
-
| `
|
|
109
|
+
| `install` | 装 skill(Claude+Codex)+ hook(Claude)+ MCP;`--host`/`--no-skill`/`--no-hook`/`--no-mcp` |
|
|
110
|
+
| `doctor` | 诊断各宿主 skill/hook/MCP/鉴权 状态(`--json`) |
|
|
111
|
+
| `hook` | UserPromptSubmit 钩子(由 install 配置) |
|
|
104
112
|
|
|
105
113
|
完整命令参考与排错见 **[CLI 命令指南](./docs/cli-guide.md)**。
|
|
106
114
|
|
|
@@ -143,7 +151,8 @@ src/
|
|
|
143
151
|
io.ts stdin / 文件读取
|
|
144
152
|
hook.ts UserPromptSubmit 钩子(替代 bash hook)
|
|
145
153
|
commands/ 每个命令一个文件
|
|
146
|
-
skill/SKILL.md Claude Code skill 源(install 时拷贝)
|
|
154
|
+
skill/claude/SKILL.md Claude Code skill 源(install 时拷贝)
|
|
155
|
+
skill/codex/SKILL.md Codex skill 源(install 时拷到 $CODEX_HOME/skills/exomind/)
|
|
147
156
|
test/ node:test 单元 + 协议级 e2e
|
|
148
157
|
```
|
|
149
158
|
|
package/dist/cli.js
CHANGED
|
@@ -965,8 +965,8 @@ var require_command = __commonJS({
|
|
|
965
965
|
"use strict";
|
|
966
966
|
var EventEmitter = require("events").EventEmitter;
|
|
967
967
|
var childProcess = require("child_process");
|
|
968
|
-
var
|
|
969
|
-
var
|
|
968
|
+
var path8 = require("path");
|
|
969
|
+
var fs8 = require("fs");
|
|
970
970
|
var process2 = require("process");
|
|
971
971
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
972
972
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1898,11 +1898,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1898
1898
|
let launchWithNode = false;
|
|
1899
1899
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1900
1900
|
function findFile(baseDir, baseName) {
|
|
1901
|
-
const localBin =
|
|
1902
|
-
if (
|
|
1903
|
-
if (sourceExt.includes(
|
|
1901
|
+
const localBin = path8.resolve(baseDir, baseName);
|
|
1902
|
+
if (fs8.existsSync(localBin)) return localBin;
|
|
1903
|
+
if (sourceExt.includes(path8.extname(baseName))) return void 0;
|
|
1904
1904
|
const foundExt = sourceExt.find(
|
|
1905
|
-
(ext) =>
|
|
1905
|
+
(ext) => fs8.existsSync(`${localBin}${ext}`)
|
|
1906
1906
|
);
|
|
1907
1907
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1908
1908
|
return void 0;
|
|
@@ -1914,21 +1914,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1914
1914
|
if (this._scriptPath) {
|
|
1915
1915
|
let resolvedScriptPath;
|
|
1916
1916
|
try {
|
|
1917
|
-
resolvedScriptPath =
|
|
1917
|
+
resolvedScriptPath = fs8.realpathSync(this._scriptPath);
|
|
1918
1918
|
} catch (err) {
|
|
1919
1919
|
resolvedScriptPath = this._scriptPath;
|
|
1920
1920
|
}
|
|
1921
|
-
executableDir =
|
|
1922
|
-
|
|
1921
|
+
executableDir = path8.resolve(
|
|
1922
|
+
path8.dirname(resolvedScriptPath),
|
|
1923
1923
|
executableDir
|
|
1924
1924
|
);
|
|
1925
1925
|
}
|
|
1926
1926
|
if (executableDir) {
|
|
1927
1927
|
let localFile = findFile(executableDir, executableFile);
|
|
1928
1928
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1929
|
-
const legacyName =
|
|
1929
|
+
const legacyName = path8.basename(
|
|
1930
1930
|
this._scriptPath,
|
|
1931
|
-
|
|
1931
|
+
path8.extname(this._scriptPath)
|
|
1932
1932
|
);
|
|
1933
1933
|
if (legacyName !== this._name) {
|
|
1934
1934
|
localFile = findFile(
|
|
@@ -1939,7 +1939,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1939
1939
|
}
|
|
1940
1940
|
executableFile = localFile || executableFile;
|
|
1941
1941
|
}
|
|
1942
|
-
launchWithNode = sourceExt.includes(
|
|
1942
|
+
launchWithNode = sourceExt.includes(path8.extname(executableFile));
|
|
1943
1943
|
let proc;
|
|
1944
1944
|
if (process2.platform !== "win32") {
|
|
1945
1945
|
if (launchWithNode) {
|
|
@@ -2779,7 +2779,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2779
2779
|
* @return {Command}
|
|
2780
2780
|
*/
|
|
2781
2781
|
nameFromFilename(filename) {
|
|
2782
|
-
this._name =
|
|
2782
|
+
this._name = path8.basename(filename, path8.extname(filename));
|
|
2783
2783
|
return this;
|
|
2784
2784
|
}
|
|
2785
2785
|
/**
|
|
@@ -2793,9 +2793,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2793
2793
|
* @param {string} [path]
|
|
2794
2794
|
* @return {(string|null|Command)}
|
|
2795
2795
|
*/
|
|
2796
|
-
executableDir(
|
|
2797
|
-
if (
|
|
2798
|
-
this._executableDir =
|
|
2796
|
+
executableDir(path9) {
|
|
2797
|
+
if (path9 === void 0) return this._executableDir;
|
|
2798
|
+
this._executableDir = path9;
|
|
2799
2799
|
return this;
|
|
2800
2800
|
}
|
|
2801
2801
|
/**
|
|
@@ -3119,7 +3119,7 @@ var {
|
|
|
3119
3119
|
// package.json
|
|
3120
3120
|
var package_default = {
|
|
3121
3121
|
name: "exomind",
|
|
3122
|
-
version: "0.
|
|
3122
|
+
version: "0.5.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"
|
|
@@ -3487,7 +3487,7 @@ function hasTechTerm(msg) {
|
|
|
3487
3487
|
}
|
|
3488
3488
|
function isSecretWord(msg) {
|
|
3489
3489
|
const m = msg.trim();
|
|
3490
|
-
return /^(存档|jdit)[\s
|
|
3490
|
+
return /^(存档|jdit)[\s!.,;:!?。?、~…]*$/i.test(m);
|
|
3491
3491
|
}
|
|
3492
3492
|
function matchesExperience(msg) {
|
|
3493
3493
|
if (msg.length < 50) return false;
|
|
@@ -3615,9 +3615,16 @@ async function runHook(client) {
|
|
|
3615
3615
|
if (msg.length < 8) return;
|
|
3616
3616
|
if (CONFIRM_WORDS.has(msg.toLowerCase())) return;
|
|
3617
3617
|
}
|
|
3618
|
-
if (isSecret && msg.length < 20
|
|
3619
|
-
|
|
3620
|
-
|
|
3618
|
+
if (isSecret && msg.length < 20) {
|
|
3619
|
+
if (now - dedup.lastArchive > COOLDOWN_MS) {
|
|
3620
|
+
outputs.push(ARCHIVE_INSTRUCTION);
|
|
3621
|
+
dedup.lastArchive = now;
|
|
3622
|
+
} else {
|
|
3623
|
+
const remain = Math.ceil((COOLDOWN_MS - (now - dedup.lastArchive)) / 1e3);
|
|
3624
|
+
outputs.push(
|
|
3625
|
+
`[ExoMind] \u5B58\u6863\u51B7\u5374\u4E2D,${remain} \u79D2\u540E\u53EF\u518D\u6B21\u89E6\u53D1(30 \u5206\u949F\u9632\u91CD\u590D\u6444\u5165)\u3002\u6697\u53F7\u5DF2\u8BC6\u522B\u3002\u5982\u9700\u7ACB\u5373\u5B58\u6863,\u76F4\u63A5\u8FD0\u884C \`exomind ingest\`\u3002`
|
|
3626
|
+
);
|
|
3627
|
+
}
|
|
3621
3628
|
} else if (!isSecret) {
|
|
3622
3629
|
if (matchesExperience(msg)) outputs.push(EXPERIENCE_INSTRUCTION);
|
|
3623
3630
|
else if (matchesResearch(msg)) outputs.push(RESEARCH_INSTRUCTION);
|
|
@@ -4502,7 +4509,8 @@ var path6 = __toESM(require("path"));
|
|
|
4502
4509
|
var os2 = __toESM(require("os"));
|
|
4503
4510
|
var import_node_child_process = require("child_process");
|
|
4504
4511
|
var PKG_ROOT = path6.resolve(__dirname, "..");
|
|
4505
|
-
var
|
|
4512
|
+
var CLAUDE_SKILL_SRC = path6.join(PKG_ROOT, "skill", "claude", "SKILL.md");
|
|
4513
|
+
var CODEX_SKILL_SRC = path6.join(PKG_ROOT, "skill", "codex", "SKILL.md");
|
|
4506
4514
|
function readJson2(file) {
|
|
4507
4515
|
try {
|
|
4508
4516
|
return JSON.parse(fs6.readFileSync(file, "utf-8"));
|
|
@@ -4535,19 +4543,23 @@ function pruneBackups(file, keep) {
|
|
|
4535
4543
|
} catch {
|
|
4536
4544
|
}
|
|
4537
4545
|
}
|
|
4538
|
-
function
|
|
4539
|
-
const
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
if (
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4546
|
+
function resolveCodexHome(env = process.env, homeDir = os2.homedir()) {
|
|
4547
|
+
const configured = (env.CODEX_HOME ?? "").trim();
|
|
4548
|
+
return configured ? path6.resolve(configured) : path6.join(homeDir, ".codex");
|
|
4549
|
+
}
|
|
4550
|
+
function looksLikeValidToml(text) {
|
|
4551
|
+
if (!text.trim()) return true;
|
|
4552
|
+
if (/\x00/.test(text)) return false;
|
|
4553
|
+
let brackets = 0;
|
|
4554
|
+
for (const ch of text) {
|
|
4555
|
+
if (ch === "[") brackets++;
|
|
4556
|
+
else if (ch === "]") brackets--;
|
|
4557
|
+
if (brackets < 0) return false;
|
|
4558
|
+
}
|
|
4559
|
+
return brackets === 0;
|
|
4548
4560
|
}
|
|
4549
|
-
function configureCodexMcp() {
|
|
4550
|
-
const codexConfig = path6.join(
|
|
4561
|
+
function configureCodexMcp(codexHome) {
|
|
4562
|
+
const codexConfig = path6.join(codexHome, "config.toml");
|
|
4551
4563
|
try {
|
|
4552
4564
|
fs6.mkdirSync(path6.dirname(codexConfig), { recursive: true });
|
|
4553
4565
|
} catch {
|
|
@@ -4558,8 +4570,16 @@ function configureCodexMcp() {
|
|
|
4558
4570
|
} catch {
|
|
4559
4571
|
existing = "";
|
|
4560
4572
|
}
|
|
4573
|
+
if (existing && !looksLikeValidToml(existing)) {
|
|
4574
|
+
return {
|
|
4575
|
+
ok: false,
|
|
4576
|
+
written: false,
|
|
4577
|
+
file: codexConfig,
|
|
4578
|
+
reason: `config.toml \u7591\u4F3C\u635F\u574F(\u62EC\u53F7\u4E0D\u914D\u5E73/\u542B\u4E8C\u8FDB\u5236),\u5DF2\u4FDD\u7559\u539F\u6587\u4EF6\u672A\u6539: ${codexConfig}`
|
|
4579
|
+
};
|
|
4580
|
+
}
|
|
4561
4581
|
if (/^\[mcp_servers\.exomind\]\s*$/m.test(existing)) {
|
|
4562
|
-
return false;
|
|
4582
|
+
return { ok: true, written: false, file: codexConfig };
|
|
4563
4583
|
}
|
|
4564
4584
|
backup(codexConfig);
|
|
4565
4585
|
const prefix = existing && !existing.endsWith("\n") ? "\n\n" : existing ? "\n" : "";
|
|
@@ -4568,6 +4588,34 @@ command = "exomind"
|
|
|
4568
4588
|
args = ["mcp"]
|
|
4569
4589
|
`;
|
|
4570
4590
|
fs6.writeFileSync(codexConfig, existing + snippet);
|
|
4591
|
+
const after = fs6.readFileSync(codexConfig, "utf-8");
|
|
4592
|
+
if (!/^\[mcp_servers\.exomind\]\s*$/m.test(after) || !looksLikeValidToml(after)) {
|
|
4593
|
+
return { ok: false, written: true, file: codexConfig, reason: "\u5199\u540E\u590D\u9A8C\u5931\u8D25(\u6BB5\u672A\u5199\u5165\u6216\u6587\u4EF6\u635F\u574F)" };
|
|
4594
|
+
}
|
|
4595
|
+
return { ok: true, written: true, file: codexConfig };
|
|
4596
|
+
}
|
|
4597
|
+
function installCodexSkill(codexHome, skillSrc = CODEX_SKILL_SRC) {
|
|
4598
|
+
const destDir = path6.join(codexHome, "skills", "exomind");
|
|
4599
|
+
const dest = path6.join(destDir, "SKILL.md");
|
|
4600
|
+
if (!fs6.existsSync(skillSrc)) {
|
|
4601
|
+
return { ok: false, dest, reason: `skill \u6E90\u4E0D\u5B58\u5728: ${skillSrc}` };
|
|
4602
|
+
}
|
|
4603
|
+
fs6.mkdirSync(destDir, { recursive: true });
|
|
4604
|
+
backup(dest);
|
|
4605
|
+
const tmp = `${dest}.tmp-${Date.now()}`;
|
|
4606
|
+
fs6.writeFileSync(tmp, fs6.readFileSync(skillSrc));
|
|
4607
|
+
fs6.renameSync(tmp, dest);
|
|
4608
|
+
return { ok: true, dest };
|
|
4609
|
+
}
|
|
4610
|
+
function purgeMcpExomind(file) {
|
|
4611
|
+
const d = readJson2(file);
|
|
4612
|
+
if (!d) return false;
|
|
4613
|
+
const grp = d.mcpServers;
|
|
4614
|
+
if (!grp || !grp.exomind) return false;
|
|
4615
|
+
delete grp.exomind;
|
|
4616
|
+
if (Object.keys(grp).length === 0) delete d.mcpServers;
|
|
4617
|
+
backup(file);
|
|
4618
|
+
fs6.writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
|
|
4571
4619
|
return true;
|
|
4572
4620
|
}
|
|
4573
4621
|
function checkMcp(timeoutMs = 6e3) {
|
|
@@ -4615,16 +4663,34 @@ function checkMcp(timeoutMs = 6e3) {
|
|
|
4615
4663
|
});
|
|
4616
4664
|
}
|
|
4617
4665
|
async function install(client, opts) {
|
|
4666
|
+
if (opts.host && !["claude", "codex", "opencode"].includes(opts.host)) {
|
|
4667
|
+
throw new Error(`\u672A\u77E5 --host: ${opts.host}(\u53EF\u9009: claude | codex | opencode)`);
|
|
4668
|
+
}
|
|
4618
4669
|
const claudeDir = path6.join(os2.homedir(), ".claude");
|
|
4619
4670
|
const skillDestDir = path6.join(claudeDir, "skills", "exomind");
|
|
4620
|
-
|
|
4621
|
-
|
|
4671
|
+
const codexHome = resolveCodexHome();
|
|
4672
|
+
const want = (h) => !opts.host || opts.host === h;
|
|
4673
|
+
if (opts.skill !== false) {
|
|
4674
|
+
if (want("claude")) {
|
|
4675
|
+
if (!fs6.existsSync(CLAUDE_SKILL_SRC)) {
|
|
4676
|
+
throw new Error(`Claude skill \u6E90\u4E0D\u5B58\u5728: ${CLAUDE_SKILL_SRC}(npm \u5305\u53EF\u80FD\u635F\u574F,\u6216\u5F00\u53D1\u6A21\u5F0F\u4E0B\u672A\u6784\u5EFA)`);
|
|
4677
|
+
}
|
|
4678
|
+
fs6.mkdirSync(skillDestDir, { recursive: true });
|
|
4679
|
+
fs6.copyFileSync(CLAUDE_SKILL_SRC, path6.join(skillDestDir, "SKILL.md"));
|
|
4680
|
+
console.log(ok("\u5DF2\u5B89\u88C5 Claude Code skill"));
|
|
4681
|
+
console.log(dim(` \u2192 ${skillDestDir}/SKILL.md`));
|
|
4682
|
+
}
|
|
4683
|
+
if (want("codex")) {
|
|
4684
|
+
const r = installCodexSkill(codexHome);
|
|
4685
|
+
if (r.ok) {
|
|
4686
|
+
console.log(ok("\u5DF2\u5B89\u88C5 Codex skill"));
|
|
4687
|
+
console.log(dim(` \u2192 ${r.dest}`));
|
|
4688
|
+
} else {
|
|
4689
|
+
console.log(yellow(`\u2717 Codex skill \u5B89\u88C5\u5931\u8D25: ${r.reason}`));
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4622
4692
|
}
|
|
4623
|
-
|
|
4624
|
-
fs6.copyFileSync(SKILL_SRC, path6.join(skillDestDir, "SKILL.md"));
|
|
4625
|
-
console.log(ok("\u5DF2\u5B89\u88C5 Claude Code skill"));
|
|
4626
|
-
console.log(dim(` \u2192 ${skillDestDir}/SKILL.md`));
|
|
4627
|
-
if (opts.hook !== false) {
|
|
4693
|
+
if (opts.hook !== false && want("claude")) {
|
|
4628
4694
|
const settingsFile = path6.join(claudeDir, "settings.json");
|
|
4629
4695
|
fs6.mkdirSync(claudeDir, { recursive: true });
|
|
4630
4696
|
backup(settingsFile);
|
|
@@ -4640,40 +4706,53 @@ async function install(client, opts) {
|
|
|
4640
4706
|
hooks.UserPromptSubmit = kept;
|
|
4641
4707
|
settings.hooks = hooks;
|
|
4642
4708
|
fs6.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
|
|
4643
|
-
console.log(ok("\u5DF2\u914D\u7F6E UserPromptSubmit hook \u2192 exomind hook"));
|
|
4644
|
-
console.log(dim(" \
|
|
4709
|
+
console.log(ok("\u5DF2\u914D\u7F6E UserPromptSubmit hook \u2192 exomind hook (Claude Code \u72EC\u6709)"));
|
|
4710
|
+
console.log(dim(" Codex \u5F53\u524D\u65E0\u5BF9\u5E94 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)\u3002"));
|
|
4645
4711
|
}
|
|
4646
4712
|
if (opts.mcp !== false) {
|
|
4647
|
-
const
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4713
|
+
const configured = [];
|
|
4714
|
+
if (want("claude")) {
|
|
4715
|
+
const ccJson = path6.join(os2.homedir(), ".claude.json");
|
|
4716
|
+
backup(ccJson);
|
|
4717
|
+
const cc = readJson2(ccJson) ?? {};
|
|
4718
|
+
const ccMcp = cc.mcpServers ?? {};
|
|
4719
|
+
ccMcp.exomind = { command: "exomind", args: ["mcp"] };
|
|
4720
|
+
cc.mcpServers = ccMcp;
|
|
4721
|
+
fs6.writeFileSync(ccJson, JSON.stringify(cc, null, 2) + "\n");
|
|
4722
|
+
configured.push("Claude Code (~/.claude.json)");
|
|
4723
|
+
}
|
|
4724
|
+
if (want("opencode")) {
|
|
4725
|
+
const ocJson = path6.join(os2.homedir(), ".config", "opencode", "opencode.json");
|
|
4726
|
+
fs6.mkdirSync(path6.dirname(ocJson), { recursive: true });
|
|
4727
|
+
backup(ocJson);
|
|
4728
|
+
const oc = readJson2(ocJson) ?? {};
|
|
4729
|
+
const ocMcp = oc.mcp ?? {};
|
|
4730
|
+
ocMcp.exomind = { type: "local", command: ["exomind", "mcp"] };
|
|
4731
|
+
oc.mcp = ocMcp;
|
|
4732
|
+
fs6.writeFileSync(ocJson, JSON.stringify(oc, null, 2) + "\n");
|
|
4733
|
+
configured.push("OpenCode (~/.config/opencode/opencode.json)");
|
|
4734
|
+
}
|
|
4735
|
+
if (want("codex")) {
|
|
4736
|
+
const r = configureCodexMcp(codexHome);
|
|
4737
|
+
if (r.ok) configured.push(`Codex (${r.file})`);
|
|
4738
|
+
else console.log(yellow(`\u2717 Codex MCP \u914D\u7F6E\u5931\u8D25: ${r.reason}`));
|
|
4739
|
+
}
|
|
4663
4740
|
const purged = [];
|
|
4664
|
-
if (purgeMcpExomind(path6.join(claudeDir, "settings.json")))
|
|
4741
|
+
if (want("claude") && purgeMcpExomind(path6.join(claudeDir, "settings.json"))) {
|
|
4742
|
+
purged.push("~/.claude/settings.json");
|
|
4743
|
+
}
|
|
4665
4744
|
let curDir = process.cwd();
|
|
4666
4745
|
for (let i = 0; i < 12 && curDir !== path6.dirname(curDir); i++) {
|
|
4667
4746
|
const f = path6.join(curDir, ".mcp.json");
|
|
4668
4747
|
if (fs6.existsSync(f) && purgeMcpExomind(f)) purged.push(f.replace(os2.homedir(), "~"));
|
|
4669
4748
|
curDir = path6.dirname(curDir);
|
|
4670
4749
|
}
|
|
4671
|
-
if (purged.length) console.log(dim(` \u2192 \u6E05\u7406\u6B8B\u7559 exomind MCP \u6761\u76EE\
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4750
|
+
if (purged.length) console.log(dim(` \u2192 \u6E05\u7406\u6B8B\u7559 exomind MCP \u6761\u76EE(\u907F\u514D\u8986\u76D6 stdio): ${purged.join(", ")}`));
|
|
4751
|
+
if (configured.length) {
|
|
4752
|
+
console.log(ok("\u5DF2\u914D\u7F6E MCP server \u2192 exomind mcp"));
|
|
4753
|
+
for (const c of configured) console.log(dim(` \u2192 ${c}`));
|
|
4754
|
+
console.log(dim(" \u91CD\u542F\u5BF9\u5E94 Agent \u2192 \u62FF\u5230 mcp__exomind__* \u5DE5\u5177"));
|
|
4755
|
+
}
|
|
4677
4756
|
}
|
|
4678
4757
|
const cfgFile = path6.join(os2.homedir(), ".exomind", "config.json");
|
|
4679
4758
|
const cfg = readJson2(cfgFile);
|
|
@@ -4682,7 +4761,7 @@ async function install(client, opts) {
|
|
|
4682
4761
|
cfg.base_url = DEFAULT_BASE_URL;
|
|
4683
4762
|
fs6.writeFileSync(cfgFile, JSON.stringify(cfg, null, 2) + "\n");
|
|
4684
4763
|
console.log(ok(`\u5DF2\u8FC1\u79FB base_url: d.youhuale.cn \u2192 ${DEFAULT_BASE_URL}`));
|
|
4685
|
-
console.log(dim(" (stdio MCP \u5B50\u8FDB\u7A0B\u8BFB\u6B64\u914D\u7F6E
|
|
4764
|
+
console.log(dim(" (stdio MCP \u5B50\u8FDB\u7A0B\u8BFB\u6B64\u914D\u7F6E,\u91CD\u542F Agent \u540E\u7528\u65B0\u57DF\u540D)"));
|
|
4686
4765
|
}
|
|
4687
4766
|
const live = loadConfig();
|
|
4688
4767
|
const keyHint = live.api_key ? `${live.api_key.slice(0, 8)}\u2026${live.api_key.slice(-4)}` : "";
|
|
@@ -4699,25 +4778,139 @@ async function install(client, opts) {
|
|
|
4699
4778
|
}
|
|
4700
4779
|
}
|
|
4701
4780
|
const mcp = live.api_key ? await checkMcp() : { ok: false, detail: "\u672A\u914D\u7F6E API Key" };
|
|
4781
|
+
const codexSkillOk = fs6.existsSync(path6.join(codexHome, "skills", "exomind", "SKILL.md"));
|
|
4782
|
+
const codexMcfgOk = (() => {
|
|
4783
|
+
try {
|
|
4784
|
+
return /^\[mcp_servers\.exomind\]\s*$/m.test(fs6.readFileSync(path6.join(codexHome, "config.toml"), "utf-8"));
|
|
4785
|
+
} catch {
|
|
4786
|
+
return false;
|
|
4787
|
+
}
|
|
4788
|
+
})();
|
|
4702
4789
|
console.log(yellow("\n\u53EF\u7528\u6027\u81EA\u68C0:"));
|
|
4703
4790
|
if (!live.api_key) {
|
|
4704
|
-
console.log(dim(" \
|
|
4791
|
+
console.log(dim(" (\u672A\u914D\u7F6E API Key,\u8DF3\u8FC7\u670D\u52A1\u7AEF\u81EA\u68C0;exomind login \u540E\u91CD\u8DD1 install \u53EF\u81EA\u68C0)"));
|
|
4705
4792
|
} else {
|
|
4706
4793
|
console.log(
|
|
4707
|
-
me && me.authenticated ? ok(` \u670D\u52A1\u5668\u8FDE\u901A + \u9274\u6743\u6709\u6548
|
|
4794
|
+
me && me.authenticated ? ok(` \u670D\u52A1\u5668\u8FDE\u901A + \u9274\u6743\u6709\u6548(tenant ${me.tenant_id} \xB7 ${keyHint})`) : yellow(" \u2717 \u670D\u52A1\u5668\u8FDE\u901A/\u9274\u6743\u5F02\u5E38 \u2192 exomind me \u6838\u9A8C")
|
|
4708
4795
|
);
|
|
4709
|
-
console.log(mcp.ok ? ok(` MCP \u670D\u52A1\u7AEF\u53EF\u7528
|
|
4796
|
+
console.log(mcp.ok ? ok(` MCP \u670D\u52A1\u7AEF\u53EF\u7528(${mcp.detail})`) : yellow(` \u2717 MCP \u670D\u52A1\u7AEF\u5F02\u5E38: ${mcp.detail}`));
|
|
4797
|
+
}
|
|
4798
|
+
if (want("codex")) {
|
|
4799
|
+
console.log(codexSkillOk ? ok(" Codex skill \u5DF2\u5B89\u88C5") : yellow(" \u2717 Codex skill \u672A\u5B89\u88C5"));
|
|
4800
|
+
console.log(codexMcfgOk ? ok(" Codex MCP \u914D\u7F6E\u5B58\u5728") : yellow(" \u2717 Codex MCP \u914D\u7F6E\u7F3A\u5931"));
|
|
4801
|
+
console.log(dim(" \u25B3 Codex \u65E0 UserPromptSubmit hook,\u4F9D\u8D56 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)"));
|
|
4710
4802
|
}
|
|
4711
4803
|
console.log(yellow("\n\u4E0B\u4E00\u6B65:"));
|
|
4712
4804
|
if (!live.api_key) {
|
|
4713
|
-
console.log(dim(" 1. exomind login\
|
|
4714
|
-
console.log(dim(" 2. \u91CD\u542F Claude Code\
|
|
4805
|
+
console.log(dim(" 1. exomind login(\u7C98\u8D34 API Key)"));
|
|
4806
|
+
console.log(dim(" 2. \u91CD\u542F Agent(Claude Code / Codex)\u52A0\u8F7D skill/MCP \u2192 mcp__exomind__* \u751F\u6548"));
|
|
4715
4807
|
} else if (me && me.authenticated && mcp.ok) {
|
|
4716
|
-
console.log(dim(" \u81EA\u68C0\u5168\u8FC7 \u2192 \u91CD\u542F
|
|
4808
|
+
console.log(dim(" \u81EA\u68C0\u5168\u8FC7 \u2192 \u91CD\u542F Agent(skill/MCP \u542F\u52A8\u65F6\u8F7D\u5165)\u5373\u751F\u6548"));
|
|
4717
4809
|
} else {
|
|
4718
|
-
console.log(yellow("\u81EA\u68C0\u6709\u5F02\u5E38
|
|
4810
|
+
console.log(yellow(" \u81EA\u68C0\u6709\u5F02\u5E38,\u6309\u4E0A\u65B9 \u2717 \u63D0\u793A\u6838\u9A8C\u540E\u518D\u91CD\u542F\u4F7F\u7528\u3002"));
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
|
|
4814
|
+
// src/commands/doctor.ts
|
|
4815
|
+
var fs7 = __toESM(require("fs"));
|
|
4816
|
+
var path7 = __toESM(require("path"));
|
|
4817
|
+
var os3 = __toESM(require("os"));
|
|
4818
|
+
function readText(p) {
|
|
4819
|
+
try {
|
|
4820
|
+
return fs7.readFileSync(p, "utf-8");
|
|
4821
|
+
} catch {
|
|
4822
|
+
return "";
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
function fileExists(p) {
|
|
4826
|
+
try {
|
|
4827
|
+
return fs7.existsSync(p);
|
|
4828
|
+
} catch {
|
|
4829
|
+
return false;
|
|
4719
4830
|
}
|
|
4720
4831
|
}
|
|
4832
|
+
function mcpServersExomind(jsonFile, key) {
|
|
4833
|
+
try {
|
|
4834
|
+
const d = JSON.parse(readText(jsonFile));
|
|
4835
|
+
const grp = key === "mcpServers" ? d.mcpServers : d.mcp;
|
|
4836
|
+
return !!(grp && grp.exomind);
|
|
4837
|
+
} catch {
|
|
4838
|
+
return false;
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
function badge(s) {
|
|
4842
|
+
if (s === "ok") return ok("ok");
|
|
4843
|
+
if (s === "unsupported") return dim("\u2014 n/a");
|
|
4844
|
+
if (s === "missing") return yellow("\u2717 \u7F3A\u5931");
|
|
4845
|
+
return red(`\u2717 ${s}`);
|
|
4846
|
+
}
|
|
4847
|
+
async function doctor(client) {
|
|
4848
|
+
const codexHome = resolveCodexHome();
|
|
4849
|
+
const checks = [];
|
|
4850
|
+
const claudeSkill = path7.join(os3.homedir(), ".claude", "skills", "exomind", "SKILL.md");
|
|
4851
|
+
const claudeSettings = path7.join(os3.homedir(), ".claude", "settings.json");
|
|
4852
|
+
const claudeJson = path7.join(os3.homedir(), ".claude.json");
|
|
4853
|
+
checks.push({
|
|
4854
|
+
host: "claude",
|
|
4855
|
+
skill: fileExists(claudeSkill) ? "ok" : "missing",
|
|
4856
|
+
hook: /exomind/.test(readText(claudeSettings)) ? "ok" : "missing",
|
|
4857
|
+
mcpConfig: mcpServersExomind(claudeJson, "mcpServers") ? "ok" : "missing",
|
|
4858
|
+
paths: [claudeSkill, claudeSettings, claudeJson]
|
|
4859
|
+
});
|
|
4860
|
+
const codexSkill = path7.join(codexHome, "skills", "exomind", "SKILL.md");
|
|
4861
|
+
const codexConfig = path7.join(codexHome, "config.toml");
|
|
4862
|
+
checks.push({
|
|
4863
|
+
host: "codex",
|
|
4864
|
+
skill: fileExists(codexSkill) ? "ok" : "missing",
|
|
4865
|
+
hook: "unsupported",
|
|
4866
|
+
mcpConfig: /^\[mcp_servers\.exomind\]\s*$/m.test(readText(codexConfig)) ? "ok" : "missing",
|
|
4867
|
+
paths: [codexSkill, codexConfig],
|
|
4868
|
+
detail: "Codex \u65E0 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)"
|
|
4869
|
+
});
|
|
4870
|
+
const ocJson = path7.join(os3.homedir(), ".config", "opencode", "opencode.json");
|
|
4871
|
+
checks.push({
|
|
4872
|
+
host: "opencode",
|
|
4873
|
+
skill: "unsupported",
|
|
4874
|
+
hook: "unsupported",
|
|
4875
|
+
mcpConfig: mcpServersExomind(ocJson, "mcp") ? "ok" : "missing",
|
|
4876
|
+
paths: [ocJson]
|
|
4877
|
+
});
|
|
4878
|
+
let authed = false;
|
|
4879
|
+
let authErr = "";
|
|
4880
|
+
try {
|
|
4881
|
+
const me = await client.get("/auth/me", { timeoutMs: 6e3 });
|
|
4882
|
+
authed = !!me.authenticated;
|
|
4883
|
+
} catch (e) {
|
|
4884
|
+
authErr = e instanceof Error ? e.message : String(e);
|
|
4885
|
+
}
|
|
4886
|
+
const mcpInit = await checkMcp();
|
|
4887
|
+
if (isJsonMode()) {
|
|
4888
|
+
console.log(
|
|
4889
|
+
JSON.stringify(
|
|
4890
|
+
{
|
|
4891
|
+
codexHome,
|
|
4892
|
+
hosts: checks,
|
|
4893
|
+
auth: { authenticated: authed, error: authErr || void 0 },
|
|
4894
|
+
mcpInitialize: { ok: mcpInit.ok, detail: mcpInit.detail }
|
|
4895
|
+
},
|
|
4896
|
+
null,
|
|
4897
|
+
2
|
|
4898
|
+
)
|
|
4899
|
+
);
|
|
4900
|
+
return;
|
|
4901
|
+
}
|
|
4902
|
+
console.log(yellow("ExoMind \u5B89\u88C5\u8BCA\u65AD:"));
|
|
4903
|
+
console.log(dim(` Codex home: ${codexHome}`));
|
|
4904
|
+
for (const c of checks) {
|
|
4905
|
+
console.log(` ${c.host}: skill ${badge(c.skill)} hook ${badge(c.hook)} mcp ${badge(c.mcpConfig)}`);
|
|
4906
|
+
if (c.detail) console.log(dim(` ${c.detail}`));
|
|
4907
|
+
}
|
|
4908
|
+
console.log(
|
|
4909
|
+
` auth: ${authed ? ok("\u2713 \u5DF2\u767B\u5F55") : yellow("\u2717 \u672A\u767B\u5F55/\u5F02\u5E38")}${authErr ? dim(" " + authErr) : ""}`
|
|
4910
|
+
);
|
|
4911
|
+
console.log(` mcp init: ${mcpInit.ok ? ok("\u2713 " + mcpInit.detail) : yellow("\u2717 " + mcpInit.detail)}`);
|
|
4912
|
+
console.log(dim("\n \u7F3A\u9879\u7528 `exomind install [--host <claude|codex|opencode>]` \u8865\u9F50;\u6539\u5B8C\u91CD\u542F\u5BF9\u5E94 Agent\u3002"));
|
|
4913
|
+
}
|
|
4721
4914
|
|
|
4722
4915
|
// src/cli.ts
|
|
4723
4916
|
var VERSION = package_default.version;
|
|
@@ -4807,7 +5000,8 @@ program2.command("mcp").description("\u542F\u52A8 stdio MCP server(\u4F9B Claude
|
|
|
4807
5000
|
const client = new ApiClient(cfg);
|
|
4808
5001
|
await runMcpServer(client);
|
|
4809
5002
|
});
|
|
4810
|
-
program2.command("install").description("\u5B89\u88C5 skill
|
|
5003
|
+
program2.command("install").description("\u5B89\u88C5 skill(Claude+Codex)+ hook(Claude)+ MCP(\u5404\u5BBF\u4E3B);\u9ED8\u8BA4\u5168\u88C5,--host \u9009\u5355\u5BBF\u4E3B").option("--host <h>", "\u53EA\u88C5\u67D0\u4E2A\u5BBF\u4E3B: claude | codex | opencode(\u7F3A\u7701\u5168\u88C5)").option("--no-skill", "\u4E0D\u5237\u65B0 skill(\u4E0D\u5220\u5DF2\u6709)").option("--no-hook", "\u4E0D\u5199 UserPromptSubmit hook(\u4EC5 Claude)").option("--no-mcp", "\u4E0D\u5199 MCP server \u914D\u7F6E").action(run(install));
|
|
5004
|
+
program2.command("doctor").description("\u8BCA\u65AD\u5404\u5BBF\u4E3B skill/hook/MCP/\u9274\u6743 \u72B6\u6001(install \u540E\u6392\u67E5)").action(run(doctor));
|
|
4811
5005
|
async function main() {
|
|
4812
5006
|
const first = process.argv[2];
|
|
4813
5007
|
if (first && /^(-v|--version)$/.test(first)) {
|