exomind 0.9.0 → 0.10.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/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 path8 = require("path");
969
- var fs8 = require("fs");
968
+ var path9 = require("path");
969
+ var fs9 = 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 = path8.resolve(baseDir, baseName);
1902
- if (fs8.existsSync(localBin)) return localBin;
1903
- if (sourceExt.includes(path8.extname(baseName))) return void 0;
1901
+ const localBin = path9.resolve(baseDir, baseName);
1902
+ if (fs9.existsSync(localBin)) return localBin;
1903
+ if (sourceExt.includes(path9.extname(baseName))) return void 0;
1904
1904
  const foundExt = sourceExt.find(
1905
- (ext) => fs8.existsSync(`${localBin}${ext}`)
1905
+ (ext) => fs9.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 = fs8.realpathSync(this._scriptPath);
1917
+ resolvedScriptPath = fs9.realpathSync(this._scriptPath);
1918
1918
  } catch (err) {
1919
1919
  resolvedScriptPath = this._scriptPath;
1920
1920
  }
1921
- executableDir = path8.resolve(
1922
- path8.dirname(resolvedScriptPath),
1921
+ executableDir = path9.resolve(
1922
+ path9.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 = path8.basename(
1929
+ const legacyName = path9.basename(
1930
1930
  this._scriptPath,
1931
- path8.extname(this._scriptPath)
1931
+ path9.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(path8.extname(executableFile));
1942
+ launchWithNode = sourceExt.includes(path9.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 = path8.basename(filename, path8.extname(filename));
2782
+ this._name = path9.basename(filename, path9.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(path9) {
2797
- if (path9 === void 0) return this._executableDir;
2798
- this._executableDir = path9;
2796
+ executableDir(path10) {
2797
+ if (path10 === void 0) return this._executableDir;
2798
+ this._executableDir = path10;
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.9.0",
3122
+ version: "0.10.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"
@@ -3838,24 +3838,70 @@ async function runMcpServer(client) {
3838
3838
  });
3839
3839
  }
3840
3840
 
3841
+ // src/update-check.ts
3842
+ var fs4 = __toESM(require("fs"));
3843
+ var path3 = __toESM(require("path"));
3844
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
3845
+ var FETCH_TIMEOUT_MS = 3e3;
3846
+ function compareVersions(current, latest) {
3847
+ const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
3848
+ const a = parse(current);
3849
+ const b = parse(latest);
3850
+ if (a.length !== 3 || b.length !== 3 || [...a, ...b].some(Number.isNaN)) return 0;
3851
+ for (let i = 0; i < 3; i++) {
3852
+ if (a[i] !== b[i]) return a[i] - b[i];
3853
+ }
3854
+ return 0;
3855
+ }
3856
+ function statePath() {
3857
+ return path3.join(CACHE_DIR, "update-check.json");
3858
+ }
3859
+ async function maybeNotifyUpdate(current, registry = "https://registry.npmjs.org") {
3860
+ if (process.env.EXOMIND_SKIP_UPDATE_CHECK === "1") return;
3861
+ const file = statePath();
3862
+ try {
3863
+ const st = JSON.parse(fs4.readFileSync(file, "utf-8"));
3864
+ if (st.version === current && Date.now() - (st.ts ?? 0) < CHECK_INTERVAL_MS) return;
3865
+ } catch {
3866
+ }
3867
+ let latest = "";
3868
+ try {
3869
+ const ctrl = new AbortController();
3870
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
3871
+ const r = await fetch(`${registry}/exomind/latest`, { signal: ctrl.signal });
3872
+ clearTimeout(timer);
3873
+ if (r.ok) latest = String((await r.json()).version || "");
3874
+ } catch {
3875
+ }
3876
+ try {
3877
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
3878
+ fs4.writeFileSync(file, JSON.stringify({ version: current, ts: Date.now(), latest }));
3879
+ } catch {
3880
+ }
3881
+ if (latest && compareVersions(current, latest) < 0) {
3882
+ process.stderr.write(dim(`\u2197 \u53D1\u73B0\u65B0\u7248 v${latest}(\u5F53\u524D v${current}):npm i -g exomind \u5347\u7EA7
3883
+ `));
3884
+ }
3885
+ }
3886
+
3841
3887
  // src/commands/ingest.ts
3842
- var path5 = __toESM(require("path"));
3888
+ var path6 = __toESM(require("path"));
3843
3889
 
3844
3890
  // src/ingest_dir.ts
3845
- var fs5 = __toESM(require("fs"));
3846
- var path4 = __toESM(require("path"));
3891
+ var fs6 = __toESM(require("fs"));
3892
+ var path5 = __toESM(require("path"));
3847
3893
 
3848
3894
  // src/manifest.ts
3849
- var fs4 = __toESM(require("fs"));
3850
- var path3 = __toESM(require("path"));
3895
+ var fs5 = __toESM(require("fs"));
3896
+ var path4 = __toESM(require("path"));
3851
3897
  var crypto = __toESM(require("crypto"));
3852
- var MANIFEST_FILE = path3.join(CONFIG_DIR, "manifest.json");
3898
+ var MANIFEST_FILE = path4.join(CONFIG_DIR, "manifest.json");
3853
3899
  function sha256(s) {
3854
3900
  return crypto.createHash("sha256").update(s, "utf-8").digest("hex");
3855
3901
  }
3856
3902
  function loadManifest() {
3857
3903
  try {
3858
- const d = JSON.parse(fs4.readFileSync(MANIFEST_FILE, "utf-8"));
3904
+ const d = JSON.parse(fs5.readFileSync(MANIFEST_FILE, "utf-8"));
3859
3905
  return d && typeof d === "object" ? d : {};
3860
3906
  } catch {
3861
3907
  return {};
@@ -3863,8 +3909,8 @@ function loadManifest() {
3863
3909
  }
3864
3910
  function saveManifest(m) {
3865
3911
  try {
3866
- fs4.mkdirSync(CONFIG_DIR, { recursive: true });
3867
- fs4.writeFileSync(MANIFEST_FILE, JSON.stringify(m, null, 2));
3912
+ fs5.mkdirSync(CONFIG_DIR, { recursive: true });
3913
+ fs5.writeFileSync(MANIFEST_FILE, JSON.stringify(m, null, 2));
3868
3914
  } catch {
3869
3915
  }
3870
3916
  }
@@ -3877,7 +3923,7 @@ function recordFile(manifest, absPath, rawContent, title) {
3877
3923
  };
3878
3924
  }
3879
3925
  function cleanupStale(m, dir, currentFiles) {
3880
- const prefix = path3.resolve(dir) + path3.sep;
3926
+ const prefix = path4.resolve(dir) + path4.sep;
3881
3927
  const current = new Set(currentFiles);
3882
3928
  for (const key of Object.keys(m)) {
3883
3929
  if (key.startsWith(prefix) && !current.has(key)) {
@@ -3897,13 +3943,13 @@ function walkDir(dir, recursive, pattern) {
3897
3943
  const walk = (d) => {
3898
3944
  let entries;
3899
3945
  try {
3900
- entries = fs5.readdirSync(d, { withFileTypes: true });
3946
+ entries = fs6.readdirSync(d, { withFileTypes: true });
3901
3947
  } catch {
3902
3948
  return;
3903
3949
  }
3904
3950
  for (const e of entries) {
3905
3951
  if (e.name.startsWith(".")) continue;
3906
- const full = path4.join(d, e.name);
3952
+ const full = path5.join(d, e.name);
3907
3953
  if (e.isDirectory()) {
3908
3954
  if (recursive) walk(full);
3909
3955
  } else if (e.isFile() && rx.test(e.name)) {
@@ -3911,7 +3957,7 @@ function walkDir(dir, recursive, pattern) {
3911
3957
  }
3912
3958
  }
3913
3959
  };
3914
- walk(path4.resolve(dir));
3960
+ walk(path5.resolve(dir));
3915
3961
  return out.sort();
3916
3962
  }
3917
3963
  function deriveTitle(file, content) {
@@ -3919,7 +3965,7 @@ function deriveTitle(file, content) {
3919
3965
  const m = line.match(/^#\s+(.+?)\s*$/);
3920
3966
  if (m) return m[1].trim();
3921
3967
  }
3922
- return path4.basename(file).replace(/\.[^.]+$/, "");
3968
+ return path5.basename(file).replace(/\.[^.]+$/, "");
3923
3969
  }
3924
3970
  function planIngestest(files, manifest, force) {
3925
3971
  const toIngest = [];
@@ -4009,22 +4055,19 @@ async function ingestWithRetry(client, payload, timeoutMs, url = "/ingest") {
4009
4055
  }
4010
4056
  }
4011
4057
  }
4012
- async function retryTransient(fn, maxAttempts = 3) {
4013
- let lastErr;
4014
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4058
+ async function retryWith429(fn, maxAttempts = 3) {
4059
+ for (let attempt = 1; ; attempt++) {
4015
4060
  try {
4016
4061
  return await fn();
4017
4062
  } catch (e) {
4018
- lastErr = e;
4019
- const transient = e instanceof ApiError && ([502, 503, 504].includes(e.status) || e.status === 0);
4020
- if (transient && attempt < maxAttempts) {
4021
- await sleep(Math.pow(2, attempt - 1) * 1e3);
4022
- continue;
4023
- }
4024
- throw e;
4063
+ if (!(e instanceof ApiError) || e.status !== 429 || attempt >= maxAttempts) throw e;
4064
+ const raw = Number(e.headers["retry-after"] ?? e.body?.retry_after ?? 5);
4065
+ const wait = Math.min(Number.isFinite(raw) && raw > 0 ? raw : 5, 60);
4066
+ process.stderr.write(dim(` \u23F8 \u8BF7\u6C42\u9891\u7387\u8D85\u9650,${wait}s \u540E\u91CD\u8BD5 (${attempt}/${maxAttempts})
4067
+ `));
4068
+ await sleep(wait * 1e3);
4025
4069
  }
4026
4070
  }
4027
- throw lastErr;
4028
4071
  }
4029
4072
  async function suspendUntilMidnight(resetEpoch) {
4030
4073
  suspended = true;
@@ -4086,7 +4129,7 @@ async function runDirIngestest(client, opts, dir) {
4086
4129
  pending.push({ file: f, jobId: res.job_id });
4087
4130
  } catch (e) {
4088
4131
  failed++;
4089
- process.stderr.write(` ${red("\u2717")} ${path4.basename(f.path)} \u63D0\u4EA4\u5931\u8D25 \u2014 ${friendlyMsg(e)}
4132
+ process.stderr.write(` ${red("\u2717")} ${path5.basename(f.path)} \u63D0\u4EA4\u5931\u8D25 \u2014 ${friendlyMsg(e)}
4090
4133
  `);
4091
4134
  }
4092
4135
  });
@@ -4100,7 +4143,7 @@ async function runDirIngestest(client, opts, dir) {
4100
4143
  if (Date.now() > pollDeadline) {
4101
4144
  for (const [, f] of inFlight) {
4102
4145
  failed++;
4103
- process.stderr.write(` ${red("\u2717")} ${path4.basename(f.path)} \u8F6E\u8BE2\u8D85\u65F6
4146
+ process.stderr.write(` ${red("\u2717")} ${path5.basename(f.path)} \u8F6E\u8BE2\u8D85\u65F6
4104
4147
  `);
4105
4148
  }
4106
4149
  inFlight.clear();
@@ -4109,13 +4152,13 @@ async function runDirIngestest(client, opts, dir) {
4109
4152
  await Promise.all(
4110
4153
  [...inFlight.entries()].map(async ([jobId, f]) => {
4111
4154
  try {
4112
- const s = await retryTransient(() => client.get("/ingest/status", { job_id: jobId }));
4155
+ const s = await retryWith429(() => client.get("/ingest/status", { job_id: jobId }));
4113
4156
  if (s.status !== "done" && s.status !== "failed") return;
4114
4157
  inFlight.delete(jobId);
4115
4158
  done++;
4116
4159
  if (s.status === "failed") {
4117
4160
  failed++;
4118
- process.stderr.write(` ${red("\u2717")} ${path4.basename(f.path)} \u5904\u7406\u5931\u8D25 \u2014 ${s.error || "\u672A\u77E5\u9519\u8BEF"}
4161
+ process.stderr.write(` ${red("\u2717")} ${path5.basename(f.path)} \u5904\u7406\u5931\u8D25 \u2014 ${s.error || "\u672A\u77E5\u9519\u8BEF"}
4119
4162
  `);
4120
4163
  return;
4121
4164
  }
@@ -4124,7 +4167,7 @@ async function runDirIngestest(client, opts, dir) {
4124
4167
  saveManifest(manifest);
4125
4168
  if (s.extracted === false) {
4126
4169
  degraded++;
4127
- process.stderr.write(` \u26A0 ${path4.basename(f.path)} \u964D\u7EA7 \u2014 \u539F\u6587\u5DF2\u5B58,\u5B9E\u4F53\u62BD\u53D6\u5F85\u8865\u8DD1
4170
+ process.stderr.write(` \u26A0 ${path5.basename(f.path)} \u964D\u7EA7 \u2014 \u539F\u6587\u5DF2\u5B58,\u5B9E\u4F53\u62BD\u53D6\u5F85\u8865\u8DD1
4128
4171
  `);
4129
4172
  } else if (prev) {
4130
4173
  updated++;
@@ -4180,7 +4223,7 @@ async function ingest(client, opts, args) {
4180
4223
  if (opts.file) {
4181
4224
  fileRaw = readFileText(opts.file);
4182
4225
  content = fileRaw;
4183
- fileAbs = path5.resolve(opts.file);
4226
+ fileAbs = path6.resolve(opts.file);
4184
4227
  } else if (args.length && args[0] === "-") {
4185
4228
  content = await readStdinForced();
4186
4229
  } else if (args.length) {
@@ -4202,7 +4245,7 @@ async function ingest(client, opts, args) {
4202
4245
  const result = await ingestWithRetry(client, body, opTimeout(3e5));
4203
4246
  if (fileAbs && fileRaw !== null) {
4204
4247
  const man = loadManifest();
4205
- recordFile(man, fileAbs, fileRaw, opts.title || path5.basename(fileAbs));
4248
+ recordFile(man, fileAbs, fileRaw, opts.title || path6.basename(fileAbs));
4206
4249
  saveManifest(man);
4207
4250
  }
4208
4251
  output(result, () => {
@@ -4227,7 +4270,7 @@ async function query(client, opts, args) {
4227
4270
  if (opts.tag?.length) body.tags = opts.tag;
4228
4271
  if (opts.model) body.model = opts.model;
4229
4272
  hint("\u23F3 \u67E5\u8BE2\u4E2D: LLM \u68C0\u7D22 + \u751F\u6210,\u53EF\u80FD 1-2 \u5206\u949F\u2026");
4230
- const result = await client.post("/query", body, { timeoutMs: opTimeout(18e4) });
4273
+ const result = await retryWith429(() => client.post("/query", body, { timeoutMs: opTimeout(18e4) }));
4231
4274
  output(result, () => {
4232
4275
  console.log(result.answer || dim("(\u65E0\u56DE\u7B54)"));
4233
4276
  if (result.pages?.length) {
@@ -4242,12 +4285,12 @@ async function query(client, opts, args) {
4242
4285
  async function search(client, opts, args) {
4243
4286
  const keyword = args.join(" ").trim();
4244
4287
  if (!keyword) throw new Error('\u8BF7\u63D0\u4F9B\u5173\u952E\u8BCD: exomind search "\u5173\u952E\u8BCD"');
4245
- const result = await client.get("/search", {
4288
+ const result = await retryWith429(() => client.get("/search", {
4246
4289
  q: keyword,
4247
4290
  limit: opts.limit ?? 10,
4248
4291
  rerank: opts.rerank ?? false,
4249
4292
  hybrid: opts.hybrid ?? false
4250
- });
4293
+ }));
4251
4294
  const results = result.results || [];
4252
4295
  output(result, () => {
4253
4296
  if (!results.length) {
@@ -4638,26 +4681,26 @@ async function whoami(client) {
4638
4681
  }
4639
4682
 
4640
4683
  // src/commands/install.ts
4641
- var fs6 = __toESM(require("fs"));
4642
- var path6 = __toESM(require("path"));
4684
+ var fs7 = __toESM(require("fs"));
4685
+ var path7 = __toESM(require("path"));
4643
4686
  var os2 = __toESM(require("os"));
4644
4687
  var import_node_child_process = require("child_process");
4645
- var PKG_ROOT = path6.resolve(__dirname, "..");
4646
- var CLAUDE_SKILL_SRC = path6.join(PKG_ROOT, "skill", "claude", "SKILL.md");
4647
- var CODEX_SKILL_SRC = path6.join(PKG_ROOT, "skill", "codex", "SKILL.md");
4688
+ var PKG_ROOT = path7.resolve(__dirname, "..");
4689
+ var CLAUDE_SKILL_SRC = path7.join(PKG_ROOT, "skill", "claude", "SKILL.md");
4690
+ var CODEX_SKILL_SRC = path7.join(PKG_ROOT, "skill", "codex", "SKILL.md");
4648
4691
  function readJson2(file) {
4649
4692
  try {
4650
- return JSON.parse(fs6.readFileSync(file, "utf-8"));
4693
+ return JSON.parse(fs7.readFileSync(file, "utf-8"));
4651
4694
  } catch {
4652
4695
  return null;
4653
4696
  }
4654
4697
  }
4655
4698
  function backup(file) {
4656
- if (!fs6.existsSync(file)) return;
4699
+ if (!fs7.existsSync(file)) return;
4657
4700
  const bak = `${file}.bak-${Date.now()}`;
4658
4701
  try {
4659
- fs6.copyFileSync(file, bak);
4660
- console.log(dim(` (\u5DF2\u5907\u4EFD \u2192 ${path6.basename(bak)})`));
4702
+ fs7.copyFileSync(file, bak);
4703
+ console.log(dim(` (\u5DF2\u5907\u4EFD \u2192 ${path7.basename(bak)})`));
4661
4704
  } catch {
4662
4705
  return;
4663
4706
  }
@@ -4665,12 +4708,12 @@ function backup(file) {
4665
4708
  }
4666
4709
  function pruneBackups(file, keep) {
4667
4710
  try {
4668
- const dir = path6.dirname(file);
4669
- const prefix = path6.basename(file) + ".bak-";
4670
- const olds = fs6.readdirSync(dir).filter((f) => f.startsWith(prefix)).map((f) => ({ f, mt: fs6.statSync(path6.join(dir, f)).mtimeMs })).sort((a, b) => b.mt - a.mt);
4711
+ const dir = path7.dirname(file);
4712
+ const prefix = path7.basename(file) + ".bak-";
4713
+ const olds = fs7.readdirSync(dir).filter((f) => f.startsWith(prefix)).map((f) => ({ f, mt: fs7.statSync(path7.join(dir, f)).mtimeMs })).sort((a, b) => b.mt - a.mt);
4671
4714
  for (const o of olds.slice(keep)) {
4672
4715
  try {
4673
- fs6.unlinkSync(path6.join(dir, o.f));
4716
+ fs7.unlinkSync(path7.join(dir, o.f));
4674
4717
  } catch {
4675
4718
  }
4676
4719
  }
@@ -4679,7 +4722,7 @@ function pruneBackups(file, keep) {
4679
4722
  }
4680
4723
  function resolveCodexHome(env = process.env, homeDir = os2.homedir()) {
4681
4724
  const configured = (env.CODEX_HOME ?? "").trim();
4682
- return configured ? path6.resolve(configured) : path6.join(homeDir, ".codex");
4725
+ return configured ? path7.resolve(configured) : path7.join(homeDir, ".codex");
4683
4726
  }
4684
4727
  function looksLikeValidToml(text) {
4685
4728
  if (!text.trim()) return true;
@@ -4693,14 +4736,14 @@ function looksLikeValidToml(text) {
4693
4736
  return brackets === 0;
4694
4737
  }
4695
4738
  function configureCodexMcp(codexHome) {
4696
- const codexConfig = path6.join(codexHome, "config.toml");
4739
+ const codexConfig = path7.join(codexHome, "config.toml");
4697
4740
  try {
4698
- fs6.mkdirSync(path6.dirname(codexConfig), { recursive: true });
4741
+ fs7.mkdirSync(path7.dirname(codexConfig), { recursive: true });
4699
4742
  } catch {
4700
4743
  }
4701
4744
  let existing = "";
4702
4745
  try {
4703
- existing = fs6.readFileSync(codexConfig, "utf-8");
4746
+ existing = fs7.readFileSync(codexConfig, "utf-8");
4704
4747
  } catch {
4705
4748
  existing = "";
4706
4749
  }
@@ -4721,24 +4764,24 @@ function configureCodexMcp(codexHome) {
4721
4764
  command = "exomind"
4722
4765
  args = ["mcp"]
4723
4766
  `;
4724
- fs6.writeFileSync(codexConfig, existing + snippet);
4725
- const after = fs6.readFileSync(codexConfig, "utf-8");
4767
+ fs7.writeFileSync(codexConfig, existing + snippet);
4768
+ const after = fs7.readFileSync(codexConfig, "utf-8");
4726
4769
  if (!/^\[mcp_servers\.exomind\]\s*$/m.test(after) || !looksLikeValidToml(after)) {
4727
4770
  return { ok: false, written: true, file: codexConfig, reason: "\u5199\u540E\u590D\u9A8C\u5931\u8D25(\u6BB5\u672A\u5199\u5165\u6216\u6587\u4EF6\u635F\u574F)" };
4728
4771
  }
4729
4772
  return { ok: true, written: true, file: codexConfig };
4730
4773
  }
4731
4774
  function installCodexSkill(codexHome, skillSrc = CODEX_SKILL_SRC) {
4732
- const destDir = path6.join(codexHome, "skills", "exomind");
4733
- const dest = path6.join(destDir, "SKILL.md");
4734
- if (!fs6.existsSync(skillSrc)) {
4775
+ const destDir = path7.join(codexHome, "skills", "exomind");
4776
+ const dest = path7.join(destDir, "SKILL.md");
4777
+ if (!fs7.existsSync(skillSrc)) {
4735
4778
  return { ok: false, dest, reason: `skill \u6E90\u4E0D\u5B58\u5728: ${skillSrc}` };
4736
4779
  }
4737
- fs6.mkdirSync(destDir, { recursive: true });
4780
+ fs7.mkdirSync(destDir, { recursive: true });
4738
4781
  backup(dest);
4739
4782
  const tmp = `${dest}.tmp-${Date.now()}`;
4740
- fs6.writeFileSync(tmp, fs6.readFileSync(skillSrc));
4741
- fs6.renameSync(tmp, dest);
4783
+ fs7.writeFileSync(tmp, fs7.readFileSync(skillSrc));
4784
+ fs7.renameSync(tmp, dest);
4742
4785
  return { ok: true, dest };
4743
4786
  }
4744
4787
  function purgeMcpExomind(file) {
@@ -4749,7 +4792,7 @@ function purgeMcpExomind(file) {
4749
4792
  delete grp.exomind;
4750
4793
  if (Object.keys(grp).length === 0) delete d.mcpServers;
4751
4794
  backup(file);
4752
- fs6.writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
4795
+ fs7.writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
4753
4796
  return true;
4754
4797
  }
4755
4798
  function checkMcp(timeoutMs = 6e3) {
@@ -4801,17 +4844,17 @@ async function install(client, opts) {
4801
4844
  if (opts.host && !["claude", "codex", "opencode"].includes(opts.host)) {
4802
4845
  throw new Error(`\u672A\u77E5 --host: ${opts.host}(\u53EF\u9009: claude | codex | opencode)`);
4803
4846
  }
4804
- const claudeDir = path6.join(os2.homedir(), ".claude");
4805
- const skillDestDir = path6.join(claudeDir, "skills", "exomind");
4847
+ const claudeDir = path7.join(os2.homedir(), ".claude");
4848
+ const skillDestDir = path7.join(claudeDir, "skills", "exomind");
4806
4849
  const codexHome = resolveCodexHome();
4807
4850
  const want = (h) => !opts.host || opts.host === h;
4808
4851
  if (opts.skill !== false) {
4809
4852
  if (want("claude")) {
4810
- if (!fs6.existsSync(CLAUDE_SKILL_SRC)) {
4853
+ if (!fs7.existsSync(CLAUDE_SKILL_SRC)) {
4811
4854
  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)`);
4812
4855
  }
4813
- fs6.mkdirSync(skillDestDir, { recursive: true });
4814
- fs6.copyFileSync(CLAUDE_SKILL_SRC, path6.join(skillDestDir, "SKILL.md"));
4856
+ fs7.mkdirSync(skillDestDir, { recursive: true });
4857
+ fs7.copyFileSync(CLAUDE_SKILL_SRC, path7.join(skillDestDir, "SKILL.md"));
4815
4858
  console.log(ok("\u5DF2\u5B89\u88C5 Claude Code skill"));
4816
4859
  console.log(dim(` \u2192 ${skillDestDir}/SKILL.md`));
4817
4860
  }
@@ -4826,8 +4869,8 @@ async function install(client, opts) {
4826
4869
  }
4827
4870
  }
4828
4871
  if (opts.hook !== false && want("claude")) {
4829
- const settingsFile = path6.join(claudeDir, "settings.json");
4830
- fs6.mkdirSync(claudeDir, { recursive: true });
4872
+ const settingsFile = path7.join(claudeDir, "settings.json");
4873
+ fs7.mkdirSync(claudeDir, { recursive: true });
4831
4874
  backup(settingsFile);
4832
4875
  const settings = readJson2(settingsFile) ?? {};
4833
4876
  const hooks = settings.hooks ?? {};
@@ -4840,31 +4883,31 @@ async function install(client, opts) {
4840
4883
  });
4841
4884
  hooks.UserPromptSubmit = kept;
4842
4885
  settings.hooks = hooks;
4843
- fs6.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
4886
+ fs7.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
4844
4887
  console.log(ok("\u5DF2\u914D\u7F6E UserPromptSubmit hook \u2192 exomind hook (Claude Code \u72EC\u6709)"));
4845
4888
  console.log(dim(" Codex \u5F53\u524D\u65E0\u5BF9\u5E94 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)\u3002"));
4846
4889
  }
4847
4890
  if (opts.mcp !== false) {
4848
4891
  const configured = [];
4849
4892
  if (want("claude")) {
4850
- const ccJson = path6.join(os2.homedir(), ".claude.json");
4893
+ const ccJson = path7.join(os2.homedir(), ".claude.json");
4851
4894
  backup(ccJson);
4852
4895
  const cc = readJson2(ccJson) ?? {};
4853
4896
  const ccMcp = cc.mcpServers ?? {};
4854
4897
  ccMcp.exomind = { command: "exomind", args: ["mcp"] };
4855
4898
  cc.mcpServers = ccMcp;
4856
- fs6.writeFileSync(ccJson, JSON.stringify(cc, null, 2) + "\n");
4899
+ fs7.writeFileSync(ccJson, JSON.stringify(cc, null, 2) + "\n");
4857
4900
  configured.push("Claude Code (~/.claude.json)");
4858
4901
  }
4859
4902
  if (want("opencode")) {
4860
- const ocJson = path6.join(os2.homedir(), ".config", "opencode", "opencode.json");
4861
- fs6.mkdirSync(path6.dirname(ocJson), { recursive: true });
4903
+ const ocJson = path7.join(os2.homedir(), ".config", "opencode", "opencode.json");
4904
+ fs7.mkdirSync(path7.dirname(ocJson), { recursive: true });
4862
4905
  backup(ocJson);
4863
4906
  const oc = readJson2(ocJson) ?? {};
4864
4907
  const ocMcp = oc.mcp ?? {};
4865
4908
  ocMcp.exomind = { type: "local", command: ["exomind", "mcp"] };
4866
4909
  oc.mcp = ocMcp;
4867
- fs6.writeFileSync(ocJson, JSON.stringify(oc, null, 2) + "\n");
4910
+ fs7.writeFileSync(ocJson, JSON.stringify(oc, null, 2) + "\n");
4868
4911
  configured.push("OpenCode (~/.config/opencode/opencode.json)");
4869
4912
  }
4870
4913
  if (want("codex")) {
@@ -4873,14 +4916,14 @@ async function install(client, opts) {
4873
4916
  else console.log(yellow(`\u2717 Codex MCP \u914D\u7F6E\u5931\u8D25: ${r.reason}`));
4874
4917
  }
4875
4918
  const purged = [];
4876
- if (want("claude") && purgeMcpExomind(path6.join(claudeDir, "settings.json"))) {
4919
+ if (want("claude") && purgeMcpExomind(path7.join(claudeDir, "settings.json"))) {
4877
4920
  purged.push("~/.claude/settings.json");
4878
4921
  }
4879
4922
  let curDir = process.cwd();
4880
- for (let i = 0; i < 12 && curDir !== path6.dirname(curDir); i++) {
4881
- const f = path6.join(curDir, ".mcp.json");
4882
- if (fs6.existsSync(f) && purgeMcpExomind(f)) purged.push(f.replace(os2.homedir(), "~"));
4883
- curDir = path6.dirname(curDir);
4923
+ for (let i = 0; i < 12 && curDir !== path7.dirname(curDir); i++) {
4924
+ const f = path7.join(curDir, ".mcp.json");
4925
+ if (fs7.existsSync(f) && purgeMcpExomind(f)) purged.push(f.replace(os2.homedir(), "~"));
4926
+ curDir = path7.dirname(curDir);
4884
4927
  }
4885
4928
  if (purged.length) console.log(dim(` \u2192 \u6E05\u7406\u6B8B\u7559 exomind MCP \u6761\u76EE(\u907F\u514D\u8986\u76D6 stdio): ${purged.join(", ")}`));
4886
4929
  if (configured.length) {
@@ -4889,12 +4932,12 @@ async function install(client, opts) {
4889
4932
  console.log(dim(" \u91CD\u542F\u5BF9\u5E94 Agent \u2192 \u62FF\u5230 mcp__exomind__* \u5DE5\u5177"));
4890
4933
  }
4891
4934
  }
4892
- const cfgFile = path6.join(os2.homedir(), ".exomind", "config.json");
4935
+ const cfgFile = path7.join(os2.homedir(), ".exomind", "config.json");
4893
4936
  const cfg = readJson2(cfgFile);
4894
4937
  if (cfg && typeof cfg.base_url === "string" && cfg.base_url.includes("d.youhuale.cn")) {
4895
4938
  backup(cfgFile);
4896
4939
  cfg.base_url = DEFAULT_BASE_URL;
4897
- fs6.writeFileSync(cfgFile, JSON.stringify(cfg, null, 2) + "\n");
4940
+ fs7.writeFileSync(cfgFile, JSON.stringify(cfg, null, 2) + "\n");
4898
4941
  console.log(ok(`\u5DF2\u8FC1\u79FB base_url: d.youhuale.cn \u2192 ${DEFAULT_BASE_URL}`));
4899
4942
  console.log(dim(" (stdio MCP \u5B50\u8FDB\u7A0B\u8BFB\u6B64\u914D\u7F6E,\u91CD\u542F Agent \u540E\u7528\u65B0\u57DF\u540D)"));
4900
4943
  }
@@ -4913,10 +4956,10 @@ async function install(client, opts) {
4913
4956
  }
4914
4957
  }
4915
4958
  const mcp = live.api_key ? await checkMcp() : { ok: false, detail: "\u672A\u914D\u7F6E API Key" };
4916
- const codexSkillOk = fs6.existsSync(path6.join(codexHome, "skills", "exomind", "SKILL.md"));
4959
+ const codexSkillOk = fs7.existsSync(path7.join(codexHome, "skills", "exomind", "SKILL.md"));
4917
4960
  const codexMcfgOk = (() => {
4918
4961
  try {
4919
- return /^\[mcp_servers\.exomind\]\s*$/m.test(fs6.readFileSync(path6.join(codexHome, "config.toml"), "utf-8"));
4962
+ return /^\[mcp_servers\.exomind\]\s*$/m.test(fs7.readFileSync(path7.join(codexHome, "config.toml"), "utf-8"));
4920
4963
  } catch {
4921
4964
  return false;
4922
4965
  }
@@ -4947,19 +4990,19 @@ async function install(client, opts) {
4947
4990
  }
4948
4991
 
4949
4992
  // src/commands/doctor.ts
4950
- var fs7 = __toESM(require("fs"));
4951
- var path7 = __toESM(require("path"));
4993
+ var fs8 = __toESM(require("fs"));
4994
+ var path8 = __toESM(require("path"));
4952
4995
  var os3 = __toESM(require("os"));
4953
4996
  function readText(p) {
4954
4997
  try {
4955
- return fs7.readFileSync(p, "utf-8");
4998
+ return fs8.readFileSync(p, "utf-8");
4956
4999
  } catch {
4957
5000
  return "";
4958
5001
  }
4959
5002
  }
4960
5003
  function fileExists(p) {
4961
5004
  try {
4962
- return fs7.existsSync(p);
5005
+ return fs8.existsSync(p);
4963
5006
  } catch {
4964
5007
  return false;
4965
5008
  }
@@ -4982,9 +5025,9 @@ function badge(s) {
4982
5025
  async function doctor(client) {
4983
5026
  const codexHome = resolveCodexHome();
4984
5027
  const checks = [];
4985
- const claudeSkill = path7.join(os3.homedir(), ".claude", "skills", "exomind", "SKILL.md");
4986
- const claudeSettings = path7.join(os3.homedir(), ".claude", "settings.json");
4987
- const claudeJson = path7.join(os3.homedir(), ".claude.json");
5028
+ const claudeSkill = path8.join(os3.homedir(), ".claude", "skills", "exomind", "SKILL.md");
5029
+ const claudeSettings = path8.join(os3.homedir(), ".claude", "settings.json");
5030
+ const claudeJson = path8.join(os3.homedir(), ".claude.json");
4988
5031
  checks.push({
4989
5032
  host: "claude",
4990
5033
  skill: fileExists(claudeSkill) ? "ok" : "missing",
@@ -4992,8 +5035,8 @@ async function doctor(client) {
4992
5035
  mcpConfig: mcpServersExomind(claudeJson, "mcpServers") ? "ok" : "missing",
4993
5036
  paths: [claudeSkill, claudeSettings, claudeJson]
4994
5037
  });
4995
- const codexSkill = path7.join(codexHome, "skills", "exomind", "SKILL.md");
4996
- const codexConfig = path7.join(codexHome, "config.toml");
5038
+ const codexSkill = path8.join(codexHome, "skills", "exomind", "SKILL.md");
5039
+ const codexConfig = path8.join(codexHome, "config.toml");
4997
5040
  checks.push({
4998
5041
  host: "codex",
4999
5042
  skill: fileExists(codexSkill) ? "ok" : "missing",
@@ -5002,7 +5045,7 @@ async function doctor(client) {
5002
5045
  paths: [codexSkill, codexConfig],
5003
5046
  detail: "Codex \u65E0 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)"
5004
5047
  });
5005
- const ocJson = path7.join(os3.homedir(), ".config", "opencode", "opencode.json");
5048
+ const ocJson = path8.join(os3.homedir(), ".config", "opencode", "opencode.json");
5006
5049
  checks.push({
5007
5050
  host: "opencode",
5008
5051
  skill: "unsupported",
@@ -5080,6 +5123,7 @@ function run(fn) {
5080
5123
  const client = new ApiClient(cfg);
5081
5124
  try {
5082
5125
  await fn(client, command.opts(), command.args);
5126
+ if (!root.json) await maybeNotifyUpdate(VERSION);
5083
5127
  } catch (e) {
5084
5128
  handleError(e);
5085
5129
  }