exomind 0.8.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.8.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"
@@ -3465,6 +3465,45 @@ function sessionKey() {
3465
3465
  function dedupPath() {
3466
3466
  return path2.join(CACHE_DIR, `dedup-${sessionKey()}.json`);
3467
3467
  }
3468
+ var DISCOVER_STATE = path2.join(CACHE_DIR, "discover-state.json");
3469
+ var DISCOVER_FAIL_COOLDOWN_MS = 30 * 60 * 1e3;
3470
+ function buildDiscoverInjection(cards) {
3471
+ if (!cards || !cards.length) return "";
3472
+ const labels = { recap: "\u627E\u56DE", bridge: "\u65B0\u8FDE\u63A5", stub: "\u5F85\u8865\u5168", theme: "\u672C\u5468\u4E3B\u7EBF" };
3473
+ const c = cards[0];
3474
+ return `[ExoMind \u4ECA\u65E5\u53D1\u73B0\xB7${labels[c.type] || c.type}] ${c.reason}
3475
+ \u4ECA\u65E5\u5171 ${cards.length} \u5F20\u5361:\u8FD0\u884C \`exomind entity "${c.name}"\` \u67E5\u770B\u7B2C\u4E00\u6761,\u6216\u6253\u5F00 youhuale.cn/ui/discover \u9010\u5F20\u5904\u7406\u3002\u4E0E\u5F53\u524D\u8BDD\u9898\u65E0\u5173\u5219\u5FFD\u7565\u3002`;
3476
+ }
3477
+ function readDiscoverState() {
3478
+ try {
3479
+ return JSON.parse(fs3.readFileSync(DISCOVER_STATE, "utf-8"));
3480
+ } catch {
3481
+ return {};
3482
+ }
3483
+ }
3484
+ function saveDiscoverState(st) {
3485
+ try {
3486
+ fs3.mkdirSync(CACHE_DIR, { recursive: true });
3487
+ fs3.writeFileSync(DISCOVER_STATE, JSON.stringify(st));
3488
+ } catch {
3489
+ }
3490
+ }
3491
+ async function dailyDiscoverInjection(client) {
3492
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3493
+ const st = readDiscoverState();
3494
+ if (st.date === today && st.done) return "";
3495
+ if (st.date === today && st.failed && Date.now() - st.failed < DISCOVER_FAIL_COOLDOWN_MS) {
3496
+ return "";
3497
+ }
3498
+ try {
3499
+ const data = await client.get("/daily-discovery", void 0, { timeoutMs: 8e3 });
3500
+ saveDiscoverState({ date: today, done: true, ts: Date.now() });
3501
+ return buildDiscoverInjection(data.discoveries || []);
3502
+ } catch {
3503
+ saveDiscoverState({ date: today, failed: Date.now() });
3504
+ return "";
3505
+ }
3506
+ }
3468
3507
  function safe(s) {
3469
3508
  return s.replace(/[^a-zA-Z0-9一-龥._-]/g, "_").slice(0, 64);
3470
3509
  }
@@ -3634,6 +3673,11 @@ async function runHook(client) {
3634
3673
  if (ctx) outputs.push(ctx);
3635
3674
  } catch {
3636
3675
  }
3676
+ try {
3677
+ const discover = await dailyDiscoverInjection(client);
3678
+ if (discover) outputs.push(discover);
3679
+ } catch {
3680
+ }
3637
3681
  saveDedup(dedup);
3638
3682
  if (outputs.length) {
3639
3683
  process.stdout.write(outputs.join("\n\n") + "\n");
@@ -3794,24 +3838,70 @@ async function runMcpServer(client) {
3794
3838
  });
3795
3839
  }
3796
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
+
3797
3887
  // src/commands/ingest.ts
3798
- var path5 = __toESM(require("path"));
3888
+ var path6 = __toESM(require("path"));
3799
3889
 
3800
3890
  // src/ingest_dir.ts
3801
- var fs5 = __toESM(require("fs"));
3802
- var path4 = __toESM(require("path"));
3891
+ var fs6 = __toESM(require("fs"));
3892
+ var path5 = __toESM(require("path"));
3803
3893
 
3804
3894
  // src/manifest.ts
3805
- var fs4 = __toESM(require("fs"));
3806
- var path3 = __toESM(require("path"));
3895
+ var fs5 = __toESM(require("fs"));
3896
+ var path4 = __toESM(require("path"));
3807
3897
  var crypto = __toESM(require("crypto"));
3808
- var MANIFEST_FILE = path3.join(CONFIG_DIR, "manifest.json");
3898
+ var MANIFEST_FILE = path4.join(CONFIG_DIR, "manifest.json");
3809
3899
  function sha256(s) {
3810
3900
  return crypto.createHash("sha256").update(s, "utf-8").digest("hex");
3811
3901
  }
3812
3902
  function loadManifest() {
3813
3903
  try {
3814
- const d = JSON.parse(fs4.readFileSync(MANIFEST_FILE, "utf-8"));
3904
+ const d = JSON.parse(fs5.readFileSync(MANIFEST_FILE, "utf-8"));
3815
3905
  return d && typeof d === "object" ? d : {};
3816
3906
  } catch {
3817
3907
  return {};
@@ -3819,8 +3909,8 @@ function loadManifest() {
3819
3909
  }
3820
3910
  function saveManifest(m) {
3821
3911
  try {
3822
- fs4.mkdirSync(CONFIG_DIR, { recursive: true });
3823
- 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));
3824
3914
  } catch {
3825
3915
  }
3826
3916
  }
@@ -3833,7 +3923,7 @@ function recordFile(manifest, absPath, rawContent, title) {
3833
3923
  };
3834
3924
  }
3835
3925
  function cleanupStale(m, dir, currentFiles) {
3836
- const prefix = path3.resolve(dir) + path3.sep;
3926
+ const prefix = path4.resolve(dir) + path4.sep;
3837
3927
  const current = new Set(currentFiles);
3838
3928
  for (const key of Object.keys(m)) {
3839
3929
  if (key.startsWith(prefix) && !current.has(key)) {
@@ -3853,13 +3943,13 @@ function walkDir(dir, recursive, pattern) {
3853
3943
  const walk = (d) => {
3854
3944
  let entries;
3855
3945
  try {
3856
- entries = fs5.readdirSync(d, { withFileTypes: true });
3946
+ entries = fs6.readdirSync(d, { withFileTypes: true });
3857
3947
  } catch {
3858
3948
  return;
3859
3949
  }
3860
3950
  for (const e of entries) {
3861
3951
  if (e.name.startsWith(".")) continue;
3862
- const full = path4.join(d, e.name);
3952
+ const full = path5.join(d, e.name);
3863
3953
  if (e.isDirectory()) {
3864
3954
  if (recursive) walk(full);
3865
3955
  } else if (e.isFile() && rx.test(e.name)) {
@@ -3867,7 +3957,7 @@ function walkDir(dir, recursive, pattern) {
3867
3957
  }
3868
3958
  }
3869
3959
  };
3870
- walk(path4.resolve(dir));
3960
+ walk(path5.resolve(dir));
3871
3961
  return out.sort();
3872
3962
  }
3873
3963
  function deriveTitle(file, content) {
@@ -3875,7 +3965,7 @@ function deriveTitle(file, content) {
3875
3965
  const m = line.match(/^#\s+(.+?)\s*$/);
3876
3966
  if (m) return m[1].trim();
3877
3967
  }
3878
- return path4.basename(file).replace(/\.[^.]+$/, "");
3968
+ return path5.basename(file).replace(/\.[^.]+$/, "");
3879
3969
  }
3880
3970
  function planIngestest(files, manifest, force) {
3881
3971
  const toIngest = [];
@@ -3965,22 +4055,19 @@ async function ingestWithRetry(client, payload, timeoutMs, url = "/ingest") {
3965
4055
  }
3966
4056
  }
3967
4057
  }
3968
- async function retryTransient(fn, maxAttempts = 3) {
3969
- let lastErr;
3970
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4058
+ async function retryWith429(fn, maxAttempts = 3) {
4059
+ for (let attempt = 1; ; attempt++) {
3971
4060
  try {
3972
4061
  return await fn();
3973
4062
  } catch (e) {
3974
- lastErr = e;
3975
- const transient = e instanceof ApiError && ([502, 503, 504].includes(e.status) || e.status === 0);
3976
- if (transient && attempt < maxAttempts) {
3977
- await sleep(Math.pow(2, attempt - 1) * 1e3);
3978
- continue;
3979
- }
3980
- 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);
3981
4069
  }
3982
4070
  }
3983
- throw lastErr;
3984
4071
  }
3985
4072
  async function suspendUntilMidnight(resetEpoch) {
3986
4073
  suspended = true;
@@ -4042,7 +4129,7 @@ async function runDirIngestest(client, opts, dir) {
4042
4129
  pending.push({ file: f, jobId: res.job_id });
4043
4130
  } catch (e) {
4044
4131
  failed++;
4045
- 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)}
4046
4133
  `);
4047
4134
  }
4048
4135
  });
@@ -4056,7 +4143,7 @@ async function runDirIngestest(client, opts, dir) {
4056
4143
  if (Date.now() > pollDeadline) {
4057
4144
  for (const [, f] of inFlight) {
4058
4145
  failed++;
4059
- 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
4060
4147
  `);
4061
4148
  }
4062
4149
  inFlight.clear();
@@ -4065,13 +4152,13 @@ async function runDirIngestest(client, opts, dir) {
4065
4152
  await Promise.all(
4066
4153
  [...inFlight.entries()].map(async ([jobId, f]) => {
4067
4154
  try {
4068
- const s = await retryTransient(() => client.get("/ingest/status", { job_id: jobId }));
4155
+ const s = await retryWith429(() => client.get("/ingest/status", { job_id: jobId }));
4069
4156
  if (s.status !== "done" && s.status !== "failed") return;
4070
4157
  inFlight.delete(jobId);
4071
4158
  done++;
4072
4159
  if (s.status === "failed") {
4073
4160
  failed++;
4074
- 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"}
4075
4162
  `);
4076
4163
  return;
4077
4164
  }
@@ -4080,7 +4167,7 @@ async function runDirIngestest(client, opts, dir) {
4080
4167
  saveManifest(manifest);
4081
4168
  if (s.extracted === false) {
4082
4169
  degraded++;
4083
- 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
4084
4171
  `);
4085
4172
  } else if (prev) {
4086
4173
  updated++;
@@ -4136,7 +4223,7 @@ async function ingest(client, opts, args) {
4136
4223
  if (opts.file) {
4137
4224
  fileRaw = readFileText(opts.file);
4138
4225
  content = fileRaw;
4139
- fileAbs = path5.resolve(opts.file);
4226
+ fileAbs = path6.resolve(opts.file);
4140
4227
  } else if (args.length && args[0] === "-") {
4141
4228
  content = await readStdinForced();
4142
4229
  } else if (args.length) {
@@ -4158,7 +4245,7 @@ async function ingest(client, opts, args) {
4158
4245
  const result = await ingestWithRetry(client, body, opTimeout(3e5));
4159
4246
  if (fileAbs && fileRaw !== null) {
4160
4247
  const man = loadManifest();
4161
- recordFile(man, fileAbs, fileRaw, opts.title || path5.basename(fileAbs));
4248
+ recordFile(man, fileAbs, fileRaw, opts.title || path6.basename(fileAbs));
4162
4249
  saveManifest(man);
4163
4250
  }
4164
4251
  output(result, () => {
@@ -4183,7 +4270,7 @@ async function query(client, opts, args) {
4183
4270
  if (opts.tag?.length) body.tags = opts.tag;
4184
4271
  if (opts.model) body.model = opts.model;
4185
4272
  hint("\u23F3 \u67E5\u8BE2\u4E2D: LLM \u68C0\u7D22 + \u751F\u6210,\u53EF\u80FD 1-2 \u5206\u949F\u2026");
4186
- const result = await client.post("/query", body, { timeoutMs: opTimeout(18e4) });
4273
+ const result = await retryWith429(() => client.post("/query", body, { timeoutMs: opTimeout(18e4) }));
4187
4274
  output(result, () => {
4188
4275
  console.log(result.answer || dim("(\u65E0\u56DE\u7B54)"));
4189
4276
  if (result.pages?.length) {
@@ -4198,12 +4285,12 @@ async function query(client, opts, args) {
4198
4285
  async function search(client, opts, args) {
4199
4286
  const keyword = args.join(" ").trim();
4200
4287
  if (!keyword) throw new Error('\u8BF7\u63D0\u4F9B\u5173\u952E\u8BCD: exomind search "\u5173\u952E\u8BCD"');
4201
- const result = await client.get("/search", {
4288
+ const result = await retryWith429(() => client.get("/search", {
4202
4289
  q: keyword,
4203
4290
  limit: opts.limit ?? 10,
4204
4291
  rerank: opts.rerank ?? false,
4205
4292
  hybrid: opts.hybrid ?? false
4206
- });
4293
+ }));
4207
4294
  const results = result.results || [];
4208
4295
  output(result, () => {
4209
4296
  if (!results.length) {
@@ -4594,26 +4681,26 @@ async function whoami(client) {
4594
4681
  }
4595
4682
 
4596
4683
  // src/commands/install.ts
4597
- var fs6 = __toESM(require("fs"));
4598
- var path6 = __toESM(require("path"));
4684
+ var fs7 = __toESM(require("fs"));
4685
+ var path7 = __toESM(require("path"));
4599
4686
  var os2 = __toESM(require("os"));
4600
4687
  var import_node_child_process = require("child_process");
4601
- var PKG_ROOT = path6.resolve(__dirname, "..");
4602
- var CLAUDE_SKILL_SRC = path6.join(PKG_ROOT, "skill", "claude", "SKILL.md");
4603
- 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");
4604
4691
  function readJson2(file) {
4605
4692
  try {
4606
- return JSON.parse(fs6.readFileSync(file, "utf-8"));
4693
+ return JSON.parse(fs7.readFileSync(file, "utf-8"));
4607
4694
  } catch {
4608
4695
  return null;
4609
4696
  }
4610
4697
  }
4611
4698
  function backup(file) {
4612
- if (!fs6.existsSync(file)) return;
4699
+ if (!fs7.existsSync(file)) return;
4613
4700
  const bak = `${file}.bak-${Date.now()}`;
4614
4701
  try {
4615
- fs6.copyFileSync(file, bak);
4616
- 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)})`));
4617
4704
  } catch {
4618
4705
  return;
4619
4706
  }
@@ -4621,12 +4708,12 @@ function backup(file) {
4621
4708
  }
4622
4709
  function pruneBackups(file, keep) {
4623
4710
  try {
4624
- const dir = path6.dirname(file);
4625
- const prefix = path6.basename(file) + ".bak-";
4626
- 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);
4627
4714
  for (const o of olds.slice(keep)) {
4628
4715
  try {
4629
- fs6.unlinkSync(path6.join(dir, o.f));
4716
+ fs7.unlinkSync(path7.join(dir, o.f));
4630
4717
  } catch {
4631
4718
  }
4632
4719
  }
@@ -4635,7 +4722,7 @@ function pruneBackups(file, keep) {
4635
4722
  }
4636
4723
  function resolveCodexHome(env = process.env, homeDir = os2.homedir()) {
4637
4724
  const configured = (env.CODEX_HOME ?? "").trim();
4638
- return configured ? path6.resolve(configured) : path6.join(homeDir, ".codex");
4725
+ return configured ? path7.resolve(configured) : path7.join(homeDir, ".codex");
4639
4726
  }
4640
4727
  function looksLikeValidToml(text) {
4641
4728
  if (!text.trim()) return true;
@@ -4649,14 +4736,14 @@ function looksLikeValidToml(text) {
4649
4736
  return brackets === 0;
4650
4737
  }
4651
4738
  function configureCodexMcp(codexHome) {
4652
- const codexConfig = path6.join(codexHome, "config.toml");
4739
+ const codexConfig = path7.join(codexHome, "config.toml");
4653
4740
  try {
4654
- fs6.mkdirSync(path6.dirname(codexConfig), { recursive: true });
4741
+ fs7.mkdirSync(path7.dirname(codexConfig), { recursive: true });
4655
4742
  } catch {
4656
4743
  }
4657
4744
  let existing = "";
4658
4745
  try {
4659
- existing = fs6.readFileSync(codexConfig, "utf-8");
4746
+ existing = fs7.readFileSync(codexConfig, "utf-8");
4660
4747
  } catch {
4661
4748
  existing = "";
4662
4749
  }
@@ -4677,24 +4764,24 @@ function configureCodexMcp(codexHome) {
4677
4764
  command = "exomind"
4678
4765
  args = ["mcp"]
4679
4766
  `;
4680
- fs6.writeFileSync(codexConfig, existing + snippet);
4681
- const after = fs6.readFileSync(codexConfig, "utf-8");
4767
+ fs7.writeFileSync(codexConfig, existing + snippet);
4768
+ const after = fs7.readFileSync(codexConfig, "utf-8");
4682
4769
  if (!/^\[mcp_servers\.exomind\]\s*$/m.test(after) || !looksLikeValidToml(after)) {
4683
4770
  return { ok: false, written: true, file: codexConfig, reason: "\u5199\u540E\u590D\u9A8C\u5931\u8D25(\u6BB5\u672A\u5199\u5165\u6216\u6587\u4EF6\u635F\u574F)" };
4684
4771
  }
4685
4772
  return { ok: true, written: true, file: codexConfig };
4686
4773
  }
4687
4774
  function installCodexSkill(codexHome, skillSrc = CODEX_SKILL_SRC) {
4688
- const destDir = path6.join(codexHome, "skills", "exomind");
4689
- const dest = path6.join(destDir, "SKILL.md");
4690
- 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)) {
4691
4778
  return { ok: false, dest, reason: `skill \u6E90\u4E0D\u5B58\u5728: ${skillSrc}` };
4692
4779
  }
4693
- fs6.mkdirSync(destDir, { recursive: true });
4780
+ fs7.mkdirSync(destDir, { recursive: true });
4694
4781
  backup(dest);
4695
4782
  const tmp = `${dest}.tmp-${Date.now()}`;
4696
- fs6.writeFileSync(tmp, fs6.readFileSync(skillSrc));
4697
- fs6.renameSync(tmp, dest);
4783
+ fs7.writeFileSync(tmp, fs7.readFileSync(skillSrc));
4784
+ fs7.renameSync(tmp, dest);
4698
4785
  return { ok: true, dest };
4699
4786
  }
4700
4787
  function purgeMcpExomind(file) {
@@ -4705,7 +4792,7 @@ function purgeMcpExomind(file) {
4705
4792
  delete grp.exomind;
4706
4793
  if (Object.keys(grp).length === 0) delete d.mcpServers;
4707
4794
  backup(file);
4708
- fs6.writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
4795
+ fs7.writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
4709
4796
  return true;
4710
4797
  }
4711
4798
  function checkMcp(timeoutMs = 6e3) {
@@ -4757,17 +4844,17 @@ async function install(client, opts) {
4757
4844
  if (opts.host && !["claude", "codex", "opencode"].includes(opts.host)) {
4758
4845
  throw new Error(`\u672A\u77E5 --host: ${opts.host}(\u53EF\u9009: claude | codex | opencode)`);
4759
4846
  }
4760
- const claudeDir = path6.join(os2.homedir(), ".claude");
4761
- const skillDestDir = path6.join(claudeDir, "skills", "exomind");
4847
+ const claudeDir = path7.join(os2.homedir(), ".claude");
4848
+ const skillDestDir = path7.join(claudeDir, "skills", "exomind");
4762
4849
  const codexHome = resolveCodexHome();
4763
4850
  const want = (h) => !opts.host || opts.host === h;
4764
4851
  if (opts.skill !== false) {
4765
4852
  if (want("claude")) {
4766
- if (!fs6.existsSync(CLAUDE_SKILL_SRC)) {
4853
+ if (!fs7.existsSync(CLAUDE_SKILL_SRC)) {
4767
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)`);
4768
4855
  }
4769
- fs6.mkdirSync(skillDestDir, { recursive: true });
4770
- 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"));
4771
4858
  console.log(ok("\u5DF2\u5B89\u88C5 Claude Code skill"));
4772
4859
  console.log(dim(` \u2192 ${skillDestDir}/SKILL.md`));
4773
4860
  }
@@ -4782,8 +4869,8 @@ async function install(client, opts) {
4782
4869
  }
4783
4870
  }
4784
4871
  if (opts.hook !== false && want("claude")) {
4785
- const settingsFile = path6.join(claudeDir, "settings.json");
4786
- fs6.mkdirSync(claudeDir, { recursive: true });
4872
+ const settingsFile = path7.join(claudeDir, "settings.json");
4873
+ fs7.mkdirSync(claudeDir, { recursive: true });
4787
4874
  backup(settingsFile);
4788
4875
  const settings = readJson2(settingsFile) ?? {};
4789
4876
  const hooks = settings.hooks ?? {};
@@ -4796,31 +4883,31 @@ async function install(client, opts) {
4796
4883
  });
4797
4884
  hooks.UserPromptSubmit = kept;
4798
4885
  settings.hooks = hooks;
4799
- fs6.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
4886
+ fs7.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
4800
4887
  console.log(ok("\u5DF2\u914D\u7F6E UserPromptSubmit hook \u2192 exomind hook (Claude Code \u72EC\u6709)"));
4801
4888
  console.log(dim(" Codex \u5F53\u524D\u65E0\u5BF9\u5E94 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)\u3002"));
4802
4889
  }
4803
4890
  if (opts.mcp !== false) {
4804
4891
  const configured = [];
4805
4892
  if (want("claude")) {
4806
- const ccJson = path6.join(os2.homedir(), ".claude.json");
4893
+ const ccJson = path7.join(os2.homedir(), ".claude.json");
4807
4894
  backup(ccJson);
4808
4895
  const cc = readJson2(ccJson) ?? {};
4809
4896
  const ccMcp = cc.mcpServers ?? {};
4810
4897
  ccMcp.exomind = { command: "exomind", args: ["mcp"] };
4811
4898
  cc.mcpServers = ccMcp;
4812
- fs6.writeFileSync(ccJson, JSON.stringify(cc, null, 2) + "\n");
4899
+ fs7.writeFileSync(ccJson, JSON.stringify(cc, null, 2) + "\n");
4813
4900
  configured.push("Claude Code (~/.claude.json)");
4814
4901
  }
4815
4902
  if (want("opencode")) {
4816
- const ocJson = path6.join(os2.homedir(), ".config", "opencode", "opencode.json");
4817
- 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 });
4818
4905
  backup(ocJson);
4819
4906
  const oc = readJson2(ocJson) ?? {};
4820
4907
  const ocMcp = oc.mcp ?? {};
4821
4908
  ocMcp.exomind = { type: "local", command: ["exomind", "mcp"] };
4822
4909
  oc.mcp = ocMcp;
4823
- fs6.writeFileSync(ocJson, JSON.stringify(oc, null, 2) + "\n");
4910
+ fs7.writeFileSync(ocJson, JSON.stringify(oc, null, 2) + "\n");
4824
4911
  configured.push("OpenCode (~/.config/opencode/opencode.json)");
4825
4912
  }
4826
4913
  if (want("codex")) {
@@ -4829,14 +4916,14 @@ async function install(client, opts) {
4829
4916
  else console.log(yellow(`\u2717 Codex MCP \u914D\u7F6E\u5931\u8D25: ${r.reason}`));
4830
4917
  }
4831
4918
  const purged = [];
4832
- if (want("claude") && purgeMcpExomind(path6.join(claudeDir, "settings.json"))) {
4919
+ if (want("claude") && purgeMcpExomind(path7.join(claudeDir, "settings.json"))) {
4833
4920
  purged.push("~/.claude/settings.json");
4834
4921
  }
4835
4922
  let curDir = process.cwd();
4836
- for (let i = 0; i < 12 && curDir !== path6.dirname(curDir); i++) {
4837
- const f = path6.join(curDir, ".mcp.json");
4838
- if (fs6.existsSync(f) && purgeMcpExomind(f)) purged.push(f.replace(os2.homedir(), "~"));
4839
- 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);
4840
4927
  }
4841
4928
  if (purged.length) console.log(dim(` \u2192 \u6E05\u7406\u6B8B\u7559 exomind MCP \u6761\u76EE(\u907F\u514D\u8986\u76D6 stdio): ${purged.join(", ")}`));
4842
4929
  if (configured.length) {
@@ -4845,12 +4932,12 @@ async function install(client, opts) {
4845
4932
  console.log(dim(" \u91CD\u542F\u5BF9\u5E94 Agent \u2192 \u62FF\u5230 mcp__exomind__* \u5DE5\u5177"));
4846
4933
  }
4847
4934
  }
4848
- const cfgFile = path6.join(os2.homedir(), ".exomind", "config.json");
4935
+ const cfgFile = path7.join(os2.homedir(), ".exomind", "config.json");
4849
4936
  const cfg = readJson2(cfgFile);
4850
4937
  if (cfg && typeof cfg.base_url === "string" && cfg.base_url.includes("d.youhuale.cn")) {
4851
4938
  backup(cfgFile);
4852
4939
  cfg.base_url = DEFAULT_BASE_URL;
4853
- fs6.writeFileSync(cfgFile, JSON.stringify(cfg, null, 2) + "\n");
4940
+ fs7.writeFileSync(cfgFile, JSON.stringify(cfg, null, 2) + "\n");
4854
4941
  console.log(ok(`\u5DF2\u8FC1\u79FB base_url: d.youhuale.cn \u2192 ${DEFAULT_BASE_URL}`));
4855
4942
  console.log(dim(" (stdio MCP \u5B50\u8FDB\u7A0B\u8BFB\u6B64\u914D\u7F6E,\u91CD\u542F Agent \u540E\u7528\u65B0\u57DF\u540D)"));
4856
4943
  }
@@ -4869,10 +4956,10 @@ async function install(client, opts) {
4869
4956
  }
4870
4957
  }
4871
4958
  const mcp = live.api_key ? await checkMcp() : { ok: false, detail: "\u672A\u914D\u7F6E API Key" };
4872
- const codexSkillOk = fs6.existsSync(path6.join(codexHome, "skills", "exomind", "SKILL.md"));
4959
+ const codexSkillOk = fs7.existsSync(path7.join(codexHome, "skills", "exomind", "SKILL.md"));
4873
4960
  const codexMcfgOk = (() => {
4874
4961
  try {
4875
- 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"));
4876
4963
  } catch {
4877
4964
  return false;
4878
4965
  }
@@ -4903,19 +4990,19 @@ async function install(client, opts) {
4903
4990
  }
4904
4991
 
4905
4992
  // src/commands/doctor.ts
4906
- var fs7 = __toESM(require("fs"));
4907
- var path7 = __toESM(require("path"));
4993
+ var fs8 = __toESM(require("fs"));
4994
+ var path8 = __toESM(require("path"));
4908
4995
  var os3 = __toESM(require("os"));
4909
4996
  function readText(p) {
4910
4997
  try {
4911
- return fs7.readFileSync(p, "utf-8");
4998
+ return fs8.readFileSync(p, "utf-8");
4912
4999
  } catch {
4913
5000
  return "";
4914
5001
  }
4915
5002
  }
4916
5003
  function fileExists(p) {
4917
5004
  try {
4918
- return fs7.existsSync(p);
5005
+ return fs8.existsSync(p);
4919
5006
  } catch {
4920
5007
  return false;
4921
5008
  }
@@ -4938,9 +5025,9 @@ function badge(s) {
4938
5025
  async function doctor(client) {
4939
5026
  const codexHome = resolveCodexHome();
4940
5027
  const checks = [];
4941
- const claudeSkill = path7.join(os3.homedir(), ".claude", "skills", "exomind", "SKILL.md");
4942
- const claudeSettings = path7.join(os3.homedir(), ".claude", "settings.json");
4943
- 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");
4944
5031
  checks.push({
4945
5032
  host: "claude",
4946
5033
  skill: fileExists(claudeSkill) ? "ok" : "missing",
@@ -4948,8 +5035,8 @@ async function doctor(client) {
4948
5035
  mcpConfig: mcpServersExomind(claudeJson, "mcpServers") ? "ok" : "missing",
4949
5036
  paths: [claudeSkill, claudeSettings, claudeJson]
4950
5037
  });
4951
- const codexSkill = path7.join(codexHome, "skills", "exomind", "SKILL.md");
4952
- 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");
4953
5040
  checks.push({
4954
5041
  host: "codex",
4955
5042
  skill: fileExists(codexSkill) ? "ok" : "missing",
@@ -4958,7 +5045,7 @@ async function doctor(client) {
4958
5045
  paths: [codexSkill, codexConfig],
4959
5046
  detail: "Codex \u65E0 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)"
4960
5047
  });
4961
- const ocJson = path7.join(os3.homedir(), ".config", "opencode", "opencode.json");
5048
+ const ocJson = path8.join(os3.homedir(), ".config", "opencode", "opencode.json");
4962
5049
  checks.push({
4963
5050
  host: "opencode",
4964
5051
  skill: "unsupported",
@@ -5036,6 +5123,7 @@ function run(fn) {
5036
5123
  const client = new ApiClient(cfg);
5037
5124
  try {
5038
5125
  await fn(client, command.opts(), command.args);
5126
+ if (!root.json) await maybeNotifyUpdate(VERSION);
5039
5127
  } catch (e) {
5040
5128
  handleError(e);
5041
5129
  }