exomind 0.4.2 → 0.5.0
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 +259 -72
- 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.0",
|
|
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"
|
|
@@ -4502,7 +4502,8 @@ var path6 = __toESM(require("path"));
|
|
|
4502
4502
|
var os2 = __toESM(require("os"));
|
|
4503
4503
|
var import_node_child_process = require("child_process");
|
|
4504
4504
|
var PKG_ROOT = path6.resolve(__dirname, "..");
|
|
4505
|
-
var
|
|
4505
|
+
var CLAUDE_SKILL_SRC = path6.join(PKG_ROOT, "skill", "claude", "SKILL.md");
|
|
4506
|
+
var CODEX_SKILL_SRC = path6.join(PKG_ROOT, "skill", "codex", "SKILL.md");
|
|
4506
4507
|
function readJson2(file) {
|
|
4507
4508
|
try {
|
|
4508
4509
|
return JSON.parse(fs6.readFileSync(file, "utf-8"));
|
|
@@ -4535,19 +4536,23 @@ function pruneBackups(file, keep) {
|
|
|
4535
4536
|
} catch {
|
|
4536
4537
|
}
|
|
4537
4538
|
}
|
|
4538
|
-
function
|
|
4539
|
-
const
|
|
4540
|
-
|
|
4541
|
-
const grp = d.mcpServers;
|
|
4542
|
-
if (!grp || !grp.exomind) return false;
|
|
4543
|
-
delete grp.exomind;
|
|
4544
|
-
if (Object.keys(grp).length === 0) delete d.mcpServers;
|
|
4545
|
-
backup(file);
|
|
4546
|
-
fs6.writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
|
|
4547
|
-
return true;
|
|
4539
|
+
function resolveCodexHome(env = process.env, homeDir = os2.homedir()) {
|
|
4540
|
+
const configured = (env.CODEX_HOME ?? "").trim();
|
|
4541
|
+
return configured ? path6.resolve(configured) : path6.join(homeDir, ".codex");
|
|
4548
4542
|
}
|
|
4549
|
-
function
|
|
4550
|
-
|
|
4543
|
+
function looksLikeValidToml(text) {
|
|
4544
|
+
if (!text.trim()) return true;
|
|
4545
|
+
if (/\x00/.test(text)) return false;
|
|
4546
|
+
let brackets = 0;
|
|
4547
|
+
for (const ch of text) {
|
|
4548
|
+
if (ch === "[") brackets++;
|
|
4549
|
+
else if (ch === "]") brackets--;
|
|
4550
|
+
if (brackets < 0) return false;
|
|
4551
|
+
}
|
|
4552
|
+
return brackets === 0;
|
|
4553
|
+
}
|
|
4554
|
+
function configureCodexMcp(codexHome) {
|
|
4555
|
+
const codexConfig = path6.join(codexHome, "config.toml");
|
|
4551
4556
|
try {
|
|
4552
4557
|
fs6.mkdirSync(path6.dirname(codexConfig), { recursive: true });
|
|
4553
4558
|
} catch {
|
|
@@ -4558,8 +4563,16 @@ function configureCodexMcp() {
|
|
|
4558
4563
|
} catch {
|
|
4559
4564
|
existing = "";
|
|
4560
4565
|
}
|
|
4566
|
+
if (existing && !looksLikeValidToml(existing)) {
|
|
4567
|
+
return {
|
|
4568
|
+
ok: false,
|
|
4569
|
+
written: false,
|
|
4570
|
+
file: codexConfig,
|
|
4571
|
+
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}`
|
|
4572
|
+
};
|
|
4573
|
+
}
|
|
4561
4574
|
if (/^\[mcp_servers\.exomind\]\s*$/m.test(existing)) {
|
|
4562
|
-
return false;
|
|
4575
|
+
return { ok: true, written: false, file: codexConfig };
|
|
4563
4576
|
}
|
|
4564
4577
|
backup(codexConfig);
|
|
4565
4578
|
const prefix = existing && !existing.endsWith("\n") ? "\n\n" : existing ? "\n" : "";
|
|
@@ -4568,6 +4581,34 @@ command = "exomind"
|
|
|
4568
4581
|
args = ["mcp"]
|
|
4569
4582
|
`;
|
|
4570
4583
|
fs6.writeFileSync(codexConfig, existing + snippet);
|
|
4584
|
+
const after = fs6.readFileSync(codexConfig, "utf-8");
|
|
4585
|
+
if (!/^\[mcp_servers\.exomind\]\s*$/m.test(after) || !looksLikeValidToml(after)) {
|
|
4586
|
+
return { ok: false, written: true, file: codexConfig, reason: "\u5199\u540E\u590D\u9A8C\u5931\u8D25(\u6BB5\u672A\u5199\u5165\u6216\u6587\u4EF6\u635F\u574F)" };
|
|
4587
|
+
}
|
|
4588
|
+
return { ok: true, written: true, file: codexConfig };
|
|
4589
|
+
}
|
|
4590
|
+
function installCodexSkill(codexHome, skillSrc = CODEX_SKILL_SRC) {
|
|
4591
|
+
const destDir = path6.join(codexHome, "skills", "exomind");
|
|
4592
|
+
const dest = path6.join(destDir, "SKILL.md");
|
|
4593
|
+
if (!fs6.existsSync(skillSrc)) {
|
|
4594
|
+
return { ok: false, dest, reason: `skill \u6E90\u4E0D\u5B58\u5728: ${skillSrc}` };
|
|
4595
|
+
}
|
|
4596
|
+
fs6.mkdirSync(destDir, { recursive: true });
|
|
4597
|
+
backup(dest);
|
|
4598
|
+
const tmp = `${dest}.tmp-${Date.now()}`;
|
|
4599
|
+
fs6.writeFileSync(tmp, fs6.readFileSync(skillSrc));
|
|
4600
|
+
fs6.renameSync(tmp, dest);
|
|
4601
|
+
return { ok: true, dest };
|
|
4602
|
+
}
|
|
4603
|
+
function purgeMcpExomind(file) {
|
|
4604
|
+
const d = readJson2(file);
|
|
4605
|
+
if (!d) return false;
|
|
4606
|
+
const grp = d.mcpServers;
|
|
4607
|
+
if (!grp || !grp.exomind) return false;
|
|
4608
|
+
delete grp.exomind;
|
|
4609
|
+
if (Object.keys(grp).length === 0) delete d.mcpServers;
|
|
4610
|
+
backup(file);
|
|
4611
|
+
fs6.writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
|
|
4571
4612
|
return true;
|
|
4572
4613
|
}
|
|
4573
4614
|
function checkMcp(timeoutMs = 6e3) {
|
|
@@ -4615,16 +4656,34 @@ function checkMcp(timeoutMs = 6e3) {
|
|
|
4615
4656
|
});
|
|
4616
4657
|
}
|
|
4617
4658
|
async function install(client, opts) {
|
|
4659
|
+
if (opts.host && !["claude", "codex", "opencode"].includes(opts.host)) {
|
|
4660
|
+
throw new Error(`\u672A\u77E5 --host: ${opts.host}(\u53EF\u9009: claude | codex | opencode)`);
|
|
4661
|
+
}
|
|
4618
4662
|
const claudeDir = path6.join(os2.homedir(), ".claude");
|
|
4619
4663
|
const skillDestDir = path6.join(claudeDir, "skills", "exomind");
|
|
4620
|
-
|
|
4621
|
-
|
|
4664
|
+
const codexHome = resolveCodexHome();
|
|
4665
|
+
const want = (h) => !opts.host || opts.host === h;
|
|
4666
|
+
if (opts.skill !== false) {
|
|
4667
|
+
if (want("claude")) {
|
|
4668
|
+
if (!fs6.existsSync(CLAUDE_SKILL_SRC)) {
|
|
4669
|
+
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)`);
|
|
4670
|
+
}
|
|
4671
|
+
fs6.mkdirSync(skillDestDir, { recursive: true });
|
|
4672
|
+
fs6.copyFileSync(CLAUDE_SKILL_SRC, path6.join(skillDestDir, "SKILL.md"));
|
|
4673
|
+
console.log(ok("\u5DF2\u5B89\u88C5 Claude Code skill"));
|
|
4674
|
+
console.log(dim(` \u2192 ${skillDestDir}/SKILL.md`));
|
|
4675
|
+
}
|
|
4676
|
+
if (want("codex")) {
|
|
4677
|
+
const r = installCodexSkill(codexHome);
|
|
4678
|
+
if (r.ok) {
|
|
4679
|
+
console.log(ok("\u5DF2\u5B89\u88C5 Codex skill"));
|
|
4680
|
+
console.log(dim(` \u2192 ${r.dest}`));
|
|
4681
|
+
} else {
|
|
4682
|
+
console.log(yellow(`\u2717 Codex skill \u5B89\u88C5\u5931\u8D25: ${r.reason}`));
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4622
4685
|
}
|
|
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) {
|
|
4686
|
+
if (opts.hook !== false && want("claude")) {
|
|
4628
4687
|
const settingsFile = path6.join(claudeDir, "settings.json");
|
|
4629
4688
|
fs6.mkdirSync(claudeDir, { recursive: true });
|
|
4630
4689
|
backup(settingsFile);
|
|
@@ -4640,40 +4699,53 @@ async function install(client, opts) {
|
|
|
4640
4699
|
hooks.UserPromptSubmit = kept;
|
|
4641
4700
|
settings.hooks = hooks;
|
|
4642
4701
|
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(" \
|
|
4702
|
+
console.log(ok("\u5DF2\u914D\u7F6E UserPromptSubmit hook \u2192 exomind hook (Claude Code \u72EC\u6709)"));
|
|
4703
|
+
console.log(dim(" Codex \u5F53\u524D\u65E0\u5BF9\u5E94 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)\u3002"));
|
|
4645
4704
|
}
|
|
4646
4705
|
if (opts.mcp !== false) {
|
|
4647
|
-
const
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4706
|
+
const configured = [];
|
|
4707
|
+
if (want("claude")) {
|
|
4708
|
+
const ccJson = path6.join(os2.homedir(), ".claude.json");
|
|
4709
|
+
backup(ccJson);
|
|
4710
|
+
const cc = readJson2(ccJson) ?? {};
|
|
4711
|
+
const ccMcp = cc.mcpServers ?? {};
|
|
4712
|
+
ccMcp.exomind = { command: "exomind", args: ["mcp"] };
|
|
4713
|
+
cc.mcpServers = ccMcp;
|
|
4714
|
+
fs6.writeFileSync(ccJson, JSON.stringify(cc, null, 2) + "\n");
|
|
4715
|
+
configured.push("Claude Code (~/.claude.json)");
|
|
4716
|
+
}
|
|
4717
|
+
if (want("opencode")) {
|
|
4718
|
+
const ocJson = path6.join(os2.homedir(), ".config", "opencode", "opencode.json");
|
|
4719
|
+
fs6.mkdirSync(path6.dirname(ocJson), { recursive: true });
|
|
4720
|
+
backup(ocJson);
|
|
4721
|
+
const oc = readJson2(ocJson) ?? {};
|
|
4722
|
+
const ocMcp = oc.mcp ?? {};
|
|
4723
|
+
ocMcp.exomind = { type: "local", command: ["exomind", "mcp"] };
|
|
4724
|
+
oc.mcp = ocMcp;
|
|
4725
|
+
fs6.writeFileSync(ocJson, JSON.stringify(oc, null, 2) + "\n");
|
|
4726
|
+
configured.push("OpenCode (~/.config/opencode/opencode.json)");
|
|
4727
|
+
}
|
|
4728
|
+
if (want("codex")) {
|
|
4729
|
+
const r = configureCodexMcp(codexHome);
|
|
4730
|
+
if (r.ok) configured.push(`Codex (${r.file})`);
|
|
4731
|
+
else console.log(yellow(`\u2717 Codex MCP \u914D\u7F6E\u5931\u8D25: ${r.reason}`));
|
|
4732
|
+
}
|
|
4663
4733
|
const purged = [];
|
|
4664
|
-
if (purgeMcpExomind(path6.join(claudeDir, "settings.json")))
|
|
4734
|
+
if (want("claude") && purgeMcpExomind(path6.join(claudeDir, "settings.json"))) {
|
|
4735
|
+
purged.push("~/.claude/settings.json");
|
|
4736
|
+
}
|
|
4665
4737
|
let curDir = process.cwd();
|
|
4666
4738
|
for (let i = 0; i < 12 && curDir !== path6.dirname(curDir); i++) {
|
|
4667
4739
|
const f = path6.join(curDir, ".mcp.json");
|
|
4668
4740
|
if (fs6.existsSync(f) && purgeMcpExomind(f)) purged.push(f.replace(os2.homedir(), "~"));
|
|
4669
4741
|
curDir = path6.dirname(curDir);
|
|
4670
4742
|
}
|
|
4671
|
-
if (purged.length) console.log(dim(` \u2192 \u6E05\u7406\u6B8B\u7559 exomind MCP \u6761\u76EE\
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4743
|
+
if (purged.length) console.log(dim(` \u2192 \u6E05\u7406\u6B8B\u7559 exomind MCP \u6761\u76EE(\u907F\u514D\u8986\u76D6 stdio): ${purged.join(", ")}`));
|
|
4744
|
+
if (configured.length) {
|
|
4745
|
+
console.log(ok("\u5DF2\u914D\u7F6E MCP server \u2192 exomind mcp"));
|
|
4746
|
+
for (const c of configured) console.log(dim(` \u2192 ${c}`));
|
|
4747
|
+
console.log(dim(" \u91CD\u542F\u5BF9\u5E94 Agent \u2192 \u62FF\u5230 mcp__exomind__* \u5DE5\u5177"));
|
|
4748
|
+
}
|
|
4677
4749
|
}
|
|
4678
4750
|
const cfgFile = path6.join(os2.homedir(), ".exomind", "config.json");
|
|
4679
4751
|
const cfg = readJson2(cfgFile);
|
|
@@ -4682,7 +4754,7 @@ async function install(client, opts) {
|
|
|
4682
4754
|
cfg.base_url = DEFAULT_BASE_URL;
|
|
4683
4755
|
fs6.writeFileSync(cfgFile, JSON.stringify(cfg, null, 2) + "\n");
|
|
4684
4756
|
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
|
|
4757
|
+
console.log(dim(" (stdio MCP \u5B50\u8FDB\u7A0B\u8BFB\u6B64\u914D\u7F6E,\u91CD\u542F Agent \u540E\u7528\u65B0\u57DF\u540D)"));
|
|
4686
4758
|
}
|
|
4687
4759
|
const live = loadConfig();
|
|
4688
4760
|
const keyHint = live.api_key ? `${live.api_key.slice(0, 8)}\u2026${live.api_key.slice(-4)}` : "";
|
|
@@ -4699,25 +4771,139 @@ async function install(client, opts) {
|
|
|
4699
4771
|
}
|
|
4700
4772
|
}
|
|
4701
4773
|
const mcp = live.api_key ? await checkMcp() : { ok: false, detail: "\u672A\u914D\u7F6E API Key" };
|
|
4774
|
+
const codexSkillOk = fs6.existsSync(path6.join(codexHome, "skills", "exomind", "SKILL.md"));
|
|
4775
|
+
const codexMcfgOk = (() => {
|
|
4776
|
+
try {
|
|
4777
|
+
return /^\[mcp_servers\.exomind\]\s*$/m.test(fs6.readFileSync(path6.join(codexHome, "config.toml"), "utf-8"));
|
|
4778
|
+
} catch {
|
|
4779
|
+
return false;
|
|
4780
|
+
}
|
|
4781
|
+
})();
|
|
4702
4782
|
console.log(yellow("\n\u53EF\u7528\u6027\u81EA\u68C0:"));
|
|
4703
4783
|
if (!live.api_key) {
|
|
4704
|
-
console.log(dim(" \
|
|
4784
|
+
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
4785
|
} else {
|
|
4706
4786
|
console.log(
|
|
4707
|
-
me && me.authenticated ? ok(` \u670D\u52A1\u5668\u8FDE\u901A + \u9274\u6743\u6709\u6548
|
|
4787
|
+
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
4788
|
);
|
|
4709
|
-
console.log(mcp.ok ? ok(` MCP \u670D\u52A1\u7AEF\u53EF\u7528
|
|
4789
|
+
console.log(mcp.ok ? ok(` MCP \u670D\u52A1\u7AEF\u53EF\u7528(${mcp.detail})`) : yellow(` \u2717 MCP \u670D\u52A1\u7AEF\u5F02\u5E38: ${mcp.detail}`));
|
|
4790
|
+
}
|
|
4791
|
+
if (want("codex")) {
|
|
4792
|
+
console.log(codexSkillOk ? ok(" Codex skill \u5DF2\u5B89\u88C5") : yellow(" \u2717 Codex skill \u672A\u5B89\u88C5"));
|
|
4793
|
+
console.log(codexMcfgOk ? ok(" Codex MCP \u914D\u7F6E\u5B58\u5728") : yellow(" \u2717 Codex MCP \u914D\u7F6E\u7F3A\u5931"));
|
|
4794
|
+
console.log(dim(" \u25B3 Codex \u65E0 UserPromptSubmit hook,\u4F9D\u8D56 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)"));
|
|
4710
4795
|
}
|
|
4711
4796
|
console.log(yellow("\n\u4E0B\u4E00\u6B65:"));
|
|
4712
4797
|
if (!live.api_key) {
|
|
4713
|
-
console.log(dim(" 1. exomind login\
|
|
4714
|
-
console.log(dim(" 2. \u91CD\u542F Claude Code\
|
|
4798
|
+
console.log(dim(" 1. exomind login(\u7C98\u8D34 API Key)"));
|
|
4799
|
+
console.log(dim(" 2. \u91CD\u542F Agent(Claude Code / Codex)\u52A0\u8F7D skill/MCP \u2192 mcp__exomind__* \u751F\u6548"));
|
|
4715
4800
|
} else if (me && me.authenticated && mcp.ok) {
|
|
4716
|
-
console.log(dim(" \u81EA\u68C0\u5168\u8FC7 \u2192 \u91CD\u542F
|
|
4801
|
+
console.log(dim(" \u81EA\u68C0\u5168\u8FC7 \u2192 \u91CD\u542F Agent(skill/MCP \u542F\u52A8\u65F6\u8F7D\u5165)\u5373\u751F\u6548"));
|
|
4717
4802
|
} else {
|
|
4718
|
-
console.log(yellow("\u81EA\u68C0\u6709\u5F02\u5E38
|
|
4803
|
+
console.log(yellow(" \u81EA\u68C0\u6709\u5F02\u5E38,\u6309\u4E0A\u65B9 \u2717 \u63D0\u793A\u6838\u9A8C\u540E\u518D\u91CD\u542F\u4F7F\u7528\u3002"));
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4807
|
+
// src/commands/doctor.ts
|
|
4808
|
+
var fs7 = __toESM(require("fs"));
|
|
4809
|
+
var path7 = __toESM(require("path"));
|
|
4810
|
+
var os3 = __toESM(require("os"));
|
|
4811
|
+
function readText(p) {
|
|
4812
|
+
try {
|
|
4813
|
+
return fs7.readFileSync(p, "utf-8");
|
|
4814
|
+
} catch {
|
|
4815
|
+
return "";
|
|
4719
4816
|
}
|
|
4720
4817
|
}
|
|
4818
|
+
function fileExists(p) {
|
|
4819
|
+
try {
|
|
4820
|
+
return fs7.existsSync(p);
|
|
4821
|
+
} catch {
|
|
4822
|
+
return false;
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
function mcpServersExomind(jsonFile, key) {
|
|
4826
|
+
try {
|
|
4827
|
+
const d = JSON.parse(readText(jsonFile));
|
|
4828
|
+
const grp = key === "mcpServers" ? d.mcpServers : d.mcp;
|
|
4829
|
+
return !!(grp && grp.exomind);
|
|
4830
|
+
} catch {
|
|
4831
|
+
return false;
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
function badge(s) {
|
|
4835
|
+
if (s === "ok") return ok("ok");
|
|
4836
|
+
if (s === "unsupported") return dim("\u2014 n/a");
|
|
4837
|
+
if (s === "missing") return yellow("\u2717 \u7F3A\u5931");
|
|
4838
|
+
return red(`\u2717 ${s}`);
|
|
4839
|
+
}
|
|
4840
|
+
async function doctor(client) {
|
|
4841
|
+
const codexHome = resolveCodexHome();
|
|
4842
|
+
const checks = [];
|
|
4843
|
+
const claudeSkill = path7.join(os3.homedir(), ".claude", "skills", "exomind", "SKILL.md");
|
|
4844
|
+
const claudeSettings = path7.join(os3.homedir(), ".claude", "settings.json");
|
|
4845
|
+
const claudeJson = path7.join(os3.homedir(), ".claude.json");
|
|
4846
|
+
checks.push({
|
|
4847
|
+
host: "claude",
|
|
4848
|
+
skill: fileExists(claudeSkill) ? "ok" : "missing",
|
|
4849
|
+
hook: /exomind/.test(readText(claudeSettings)) ? "ok" : "missing",
|
|
4850
|
+
mcpConfig: mcpServersExomind(claudeJson, "mcpServers") ? "ok" : "missing",
|
|
4851
|
+
paths: [claudeSkill, claudeSettings, claudeJson]
|
|
4852
|
+
});
|
|
4853
|
+
const codexSkill = path7.join(codexHome, "skills", "exomind", "SKILL.md");
|
|
4854
|
+
const codexConfig = path7.join(codexHome, "config.toml");
|
|
4855
|
+
checks.push({
|
|
4856
|
+
host: "codex",
|
|
4857
|
+
skill: fileExists(codexSkill) ? "ok" : "missing",
|
|
4858
|
+
hook: "unsupported",
|
|
4859
|
+
mcpConfig: /^\[mcp_servers\.exomind\]\s*$/m.test(readText(codexConfig)) ? "ok" : "missing",
|
|
4860
|
+
paths: [codexSkill, codexConfig],
|
|
4861
|
+
detail: "Codex \u65E0 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)"
|
|
4862
|
+
});
|
|
4863
|
+
const ocJson = path7.join(os3.homedir(), ".config", "opencode", "opencode.json");
|
|
4864
|
+
checks.push({
|
|
4865
|
+
host: "opencode",
|
|
4866
|
+
skill: "unsupported",
|
|
4867
|
+
hook: "unsupported",
|
|
4868
|
+
mcpConfig: mcpServersExomind(ocJson, "mcp") ? "ok" : "missing",
|
|
4869
|
+
paths: [ocJson]
|
|
4870
|
+
});
|
|
4871
|
+
let authed = false;
|
|
4872
|
+
let authErr = "";
|
|
4873
|
+
try {
|
|
4874
|
+
const me = await client.get("/auth/me", { timeoutMs: 6e3 });
|
|
4875
|
+
authed = !!me.authenticated;
|
|
4876
|
+
} catch (e) {
|
|
4877
|
+
authErr = e instanceof Error ? e.message : String(e);
|
|
4878
|
+
}
|
|
4879
|
+
const mcpInit = await checkMcp();
|
|
4880
|
+
if (isJsonMode()) {
|
|
4881
|
+
console.log(
|
|
4882
|
+
JSON.stringify(
|
|
4883
|
+
{
|
|
4884
|
+
codexHome,
|
|
4885
|
+
hosts: checks,
|
|
4886
|
+
auth: { authenticated: authed, error: authErr || void 0 },
|
|
4887
|
+
mcpInitialize: { ok: mcpInit.ok, detail: mcpInit.detail }
|
|
4888
|
+
},
|
|
4889
|
+
null,
|
|
4890
|
+
2
|
|
4891
|
+
)
|
|
4892
|
+
);
|
|
4893
|
+
return;
|
|
4894
|
+
}
|
|
4895
|
+
console.log(yellow("ExoMind \u5B89\u88C5\u8BCA\u65AD:"));
|
|
4896
|
+
console.log(dim(` Codex home: ${codexHome}`));
|
|
4897
|
+
for (const c of checks) {
|
|
4898
|
+
console.log(` ${c.host}: skill ${badge(c.skill)} hook ${badge(c.hook)} mcp ${badge(c.mcpConfig)}`);
|
|
4899
|
+
if (c.detail) console.log(dim(` ${c.detail}`));
|
|
4900
|
+
}
|
|
4901
|
+
console.log(
|
|
4902
|
+
` auth: ${authed ? ok("\u2713 \u5DF2\u767B\u5F55") : yellow("\u2717 \u672A\u767B\u5F55/\u5F02\u5E38")}${authErr ? dim(" " + authErr) : ""}`
|
|
4903
|
+
);
|
|
4904
|
+
console.log(` mcp init: ${mcpInit.ok ? ok("\u2713 " + mcpInit.detail) : yellow("\u2717 " + mcpInit.detail)}`);
|
|
4905
|
+
console.log(dim("\n \u7F3A\u9879\u7528 `exomind install [--host <claude|codex|opencode>]` \u8865\u9F50;\u6539\u5B8C\u91CD\u542F\u5BF9\u5E94 Agent\u3002"));
|
|
4906
|
+
}
|
|
4721
4907
|
|
|
4722
4908
|
// src/cli.ts
|
|
4723
4909
|
var VERSION = package_default.version;
|
|
@@ -4807,7 +4993,8 @@ program2.command("mcp").description("\u542F\u52A8 stdio MCP server(\u4F9B Claude
|
|
|
4807
4993
|
const client = new ApiClient(cfg);
|
|
4808
4994
|
await runMcpServer(client);
|
|
4809
4995
|
});
|
|
4810
|
-
program2.command("install").description("\u5B89\u88C5 skill
|
|
4996
|
+
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));
|
|
4997
|
+
program2.command("doctor").description("\u8BCA\u65AD\u5404\u5BBF\u4E3B skill/hook/MCP/\u9274\u6743 \u72B6\u6001(install \u540E\u6392\u67E5)").action(run(doctor));
|
|
4811
4998
|
async function main() {
|
|
4812
4999
|
const first = process.argv[2];
|
|
4813
5000
|
if (first && /^(-v|--version)$/.test(first)) {
|