exomind 0.9.0 → 0.11.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.11.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++;
@@ -4172,15 +4215,22 @@ async function runDirIngestest(client, opts, dir) {
4172
4215
  }
4173
4216
 
4174
4217
  // src/commands/ingest.ts
4218
+ function resolveIngestDir(dir, args) {
4219
+ const dirPath = typeof dir === "string" ? dir : args[0];
4220
+ if (!dirPath || dirPath === "-") {
4221
+ throw new Error("--dir \u9700\u8981\u76EE\u5F55\u8DEF\u5F84\u3002\u4E24\u79CD\u5199\u6CD5: exomind ingest --dir <\u76EE\u5F55> \u6216 exomind ingest <\u76EE\u5F55> --dir");
4222
+ }
4223
+ return dirPath;
4224
+ }
4175
4225
  async function ingest(client, opts, args) {
4176
- if (opts.dir) return runDirIngestest(client, opts, opts.dir);
4226
+ if (opts.dir) return runDirIngestest(client, opts, resolveIngestDir(opts.dir, args));
4177
4227
  let content = "";
4178
4228
  let fileAbs = null;
4179
4229
  let fileRaw = null;
4180
4230
  if (opts.file) {
4181
4231
  fileRaw = readFileText(opts.file);
4182
4232
  content = fileRaw;
4183
- fileAbs = path5.resolve(opts.file);
4233
+ fileAbs = path6.resolve(opts.file);
4184
4234
  } else if (args.length && args[0] === "-") {
4185
4235
  content = await readStdinForced();
4186
4236
  } else if (args.length) {
@@ -4202,7 +4252,7 @@ async function ingest(client, opts, args) {
4202
4252
  const result = await ingestWithRetry(client, body, opTimeout(3e5));
4203
4253
  if (fileAbs && fileRaw !== null) {
4204
4254
  const man = loadManifest();
4205
- recordFile(man, fileAbs, fileRaw, opts.title || path5.basename(fileAbs));
4255
+ recordFile(man, fileAbs, fileRaw, opts.title || path6.basename(fileAbs));
4206
4256
  saveManifest(man);
4207
4257
  }
4208
4258
  output(result, () => {
@@ -4227,7 +4277,7 @@ async function query(client, opts, args) {
4227
4277
  if (opts.tag?.length) body.tags = opts.tag;
4228
4278
  if (opts.model) body.model = opts.model;
4229
4279
  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) });
4280
+ const result = await retryWith429(() => client.post("/query", body, { timeoutMs: opTimeout(18e4) }));
4231
4281
  output(result, () => {
4232
4282
  console.log(result.answer || dim("(\u65E0\u56DE\u7B54)"));
4233
4283
  if (result.pages?.length) {
@@ -4242,12 +4292,12 @@ async function query(client, opts, args) {
4242
4292
  async function search(client, opts, args) {
4243
4293
  const keyword = args.join(" ").trim();
4244
4294
  if (!keyword) throw new Error('\u8BF7\u63D0\u4F9B\u5173\u952E\u8BCD: exomind search "\u5173\u952E\u8BCD"');
4245
- const result = await client.get("/search", {
4295
+ const result = await retryWith429(() => client.get("/search", {
4246
4296
  q: keyword,
4247
4297
  limit: opts.limit ?? 10,
4248
4298
  rerank: opts.rerank ?? false,
4249
4299
  hybrid: opts.hybrid ?? false
4250
- });
4300
+ }));
4251
4301
  const results = result.results || [];
4252
4302
  output(result, () => {
4253
4303
  if (!results.length) {
@@ -4260,7 +4310,8 @@ async function search(client, opts, args) {
4260
4310
  console.log(`
4261
4311
  ${i + 1}. ${cyan(title)}`);
4262
4312
  if (r.path) console.log(dim(` ${r.path}`));
4263
- if (r.snippet) console.log(dim(` ${truncate(String(r.snippet), 100)}`));
4313
+ const snip = r.snippet ?? r.content;
4314
+ if (snip) console.log(dim(` ${truncate(String(snip), 100)}`));
4264
4315
  if (r.score != null) console.log(dim(` score: ${r.score}`));
4265
4316
  });
4266
4317
  });
@@ -4638,26 +4689,26 @@ async function whoami(client) {
4638
4689
  }
4639
4690
 
4640
4691
  // src/commands/install.ts
4641
- var fs6 = __toESM(require("fs"));
4642
- var path6 = __toESM(require("path"));
4692
+ var fs7 = __toESM(require("fs"));
4693
+ var path7 = __toESM(require("path"));
4643
4694
  var os2 = __toESM(require("os"));
4644
4695
  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");
4696
+ var PKG_ROOT = path7.resolve(__dirname, "..");
4697
+ var CLAUDE_SKILL_SRC = path7.join(PKG_ROOT, "skill", "claude", "SKILL.md");
4698
+ var CODEX_SKILL_SRC = path7.join(PKG_ROOT, "skill", "codex", "SKILL.md");
4648
4699
  function readJson2(file) {
4649
4700
  try {
4650
- return JSON.parse(fs6.readFileSync(file, "utf-8"));
4701
+ return JSON.parse(fs7.readFileSync(file, "utf-8"));
4651
4702
  } catch {
4652
4703
  return null;
4653
4704
  }
4654
4705
  }
4655
4706
  function backup(file) {
4656
- if (!fs6.existsSync(file)) return;
4707
+ if (!fs7.existsSync(file)) return;
4657
4708
  const bak = `${file}.bak-${Date.now()}`;
4658
4709
  try {
4659
- fs6.copyFileSync(file, bak);
4660
- console.log(dim(` (\u5DF2\u5907\u4EFD \u2192 ${path6.basename(bak)})`));
4710
+ fs7.copyFileSync(file, bak);
4711
+ console.log(dim(` (\u5DF2\u5907\u4EFD \u2192 ${path7.basename(bak)})`));
4661
4712
  } catch {
4662
4713
  return;
4663
4714
  }
@@ -4665,12 +4716,12 @@ function backup(file) {
4665
4716
  }
4666
4717
  function pruneBackups(file, keep) {
4667
4718
  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);
4719
+ const dir = path7.dirname(file);
4720
+ const prefix = path7.basename(file) + ".bak-";
4721
+ 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
4722
  for (const o of olds.slice(keep)) {
4672
4723
  try {
4673
- fs6.unlinkSync(path6.join(dir, o.f));
4724
+ fs7.unlinkSync(path7.join(dir, o.f));
4674
4725
  } catch {
4675
4726
  }
4676
4727
  }
@@ -4679,7 +4730,7 @@ function pruneBackups(file, keep) {
4679
4730
  }
4680
4731
  function resolveCodexHome(env = process.env, homeDir = os2.homedir()) {
4681
4732
  const configured = (env.CODEX_HOME ?? "").trim();
4682
- return configured ? path6.resolve(configured) : path6.join(homeDir, ".codex");
4733
+ return configured ? path7.resolve(configured) : path7.join(homeDir, ".codex");
4683
4734
  }
4684
4735
  function looksLikeValidToml(text) {
4685
4736
  if (!text.trim()) return true;
@@ -4693,14 +4744,14 @@ function looksLikeValidToml(text) {
4693
4744
  return brackets === 0;
4694
4745
  }
4695
4746
  function configureCodexMcp(codexHome) {
4696
- const codexConfig = path6.join(codexHome, "config.toml");
4747
+ const codexConfig = path7.join(codexHome, "config.toml");
4697
4748
  try {
4698
- fs6.mkdirSync(path6.dirname(codexConfig), { recursive: true });
4749
+ fs7.mkdirSync(path7.dirname(codexConfig), { recursive: true });
4699
4750
  } catch {
4700
4751
  }
4701
4752
  let existing = "";
4702
4753
  try {
4703
- existing = fs6.readFileSync(codexConfig, "utf-8");
4754
+ existing = fs7.readFileSync(codexConfig, "utf-8");
4704
4755
  } catch {
4705
4756
  existing = "";
4706
4757
  }
@@ -4721,24 +4772,24 @@ function configureCodexMcp(codexHome) {
4721
4772
  command = "exomind"
4722
4773
  args = ["mcp"]
4723
4774
  `;
4724
- fs6.writeFileSync(codexConfig, existing + snippet);
4725
- const after = fs6.readFileSync(codexConfig, "utf-8");
4775
+ fs7.writeFileSync(codexConfig, existing + snippet);
4776
+ const after = fs7.readFileSync(codexConfig, "utf-8");
4726
4777
  if (!/^\[mcp_servers\.exomind\]\s*$/m.test(after) || !looksLikeValidToml(after)) {
4727
4778
  return { ok: false, written: true, file: codexConfig, reason: "\u5199\u540E\u590D\u9A8C\u5931\u8D25(\u6BB5\u672A\u5199\u5165\u6216\u6587\u4EF6\u635F\u574F)" };
4728
4779
  }
4729
4780
  return { ok: true, written: true, file: codexConfig };
4730
4781
  }
4731
4782
  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)) {
4783
+ const destDir = path7.join(codexHome, "skills", "exomind");
4784
+ const dest = path7.join(destDir, "SKILL.md");
4785
+ if (!fs7.existsSync(skillSrc)) {
4735
4786
  return { ok: false, dest, reason: `skill \u6E90\u4E0D\u5B58\u5728: ${skillSrc}` };
4736
4787
  }
4737
- fs6.mkdirSync(destDir, { recursive: true });
4788
+ fs7.mkdirSync(destDir, { recursive: true });
4738
4789
  backup(dest);
4739
4790
  const tmp = `${dest}.tmp-${Date.now()}`;
4740
- fs6.writeFileSync(tmp, fs6.readFileSync(skillSrc));
4741
- fs6.renameSync(tmp, dest);
4791
+ fs7.writeFileSync(tmp, fs7.readFileSync(skillSrc));
4792
+ fs7.renameSync(tmp, dest);
4742
4793
  return { ok: true, dest };
4743
4794
  }
4744
4795
  function purgeMcpExomind(file) {
@@ -4749,7 +4800,7 @@ function purgeMcpExomind(file) {
4749
4800
  delete grp.exomind;
4750
4801
  if (Object.keys(grp).length === 0) delete d.mcpServers;
4751
4802
  backup(file);
4752
- fs6.writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
4803
+ fs7.writeFileSync(file, JSON.stringify(d, null, 2) + "\n");
4753
4804
  return true;
4754
4805
  }
4755
4806
  function checkMcp(timeoutMs = 6e3) {
@@ -4801,17 +4852,17 @@ async function install(client, opts) {
4801
4852
  if (opts.host && !["claude", "codex", "opencode"].includes(opts.host)) {
4802
4853
  throw new Error(`\u672A\u77E5 --host: ${opts.host}(\u53EF\u9009: claude | codex | opencode)`);
4803
4854
  }
4804
- const claudeDir = path6.join(os2.homedir(), ".claude");
4805
- const skillDestDir = path6.join(claudeDir, "skills", "exomind");
4855
+ const claudeDir = path7.join(os2.homedir(), ".claude");
4856
+ const skillDestDir = path7.join(claudeDir, "skills", "exomind");
4806
4857
  const codexHome = resolveCodexHome();
4807
4858
  const want = (h) => !opts.host || opts.host === h;
4808
4859
  if (opts.skill !== false) {
4809
4860
  if (want("claude")) {
4810
- if (!fs6.existsSync(CLAUDE_SKILL_SRC)) {
4861
+ if (!fs7.existsSync(CLAUDE_SKILL_SRC)) {
4811
4862
  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
4863
  }
4813
- fs6.mkdirSync(skillDestDir, { recursive: true });
4814
- fs6.copyFileSync(CLAUDE_SKILL_SRC, path6.join(skillDestDir, "SKILL.md"));
4864
+ fs7.mkdirSync(skillDestDir, { recursive: true });
4865
+ fs7.copyFileSync(CLAUDE_SKILL_SRC, path7.join(skillDestDir, "SKILL.md"));
4815
4866
  console.log(ok("\u5DF2\u5B89\u88C5 Claude Code skill"));
4816
4867
  console.log(dim(` \u2192 ${skillDestDir}/SKILL.md`));
4817
4868
  }
@@ -4826,8 +4877,8 @@ async function install(client, opts) {
4826
4877
  }
4827
4878
  }
4828
4879
  if (opts.hook !== false && want("claude")) {
4829
- const settingsFile = path6.join(claudeDir, "settings.json");
4830
- fs6.mkdirSync(claudeDir, { recursive: true });
4880
+ const settingsFile = path7.join(claudeDir, "settings.json");
4881
+ fs7.mkdirSync(claudeDir, { recursive: true });
4831
4882
  backup(settingsFile);
4832
4883
  const settings = readJson2(settingsFile) ?? {};
4833
4884
  const hooks = settings.hooks ?? {};
@@ -4840,31 +4891,31 @@ async function install(client, opts) {
4840
4891
  });
4841
4892
  hooks.UserPromptSubmit = kept;
4842
4893
  settings.hooks = hooks;
4843
- fs6.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
4894
+ fs7.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
4844
4895
  console.log(ok("\u5DF2\u914D\u7F6E UserPromptSubmit hook \u2192 exomind hook (Claude Code \u72EC\u6709)"));
4845
4896
  console.log(dim(" Codex \u5F53\u524D\u65E0\u5BF9\u5E94 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)\u3002"));
4846
4897
  }
4847
4898
  if (opts.mcp !== false) {
4848
4899
  const configured = [];
4849
4900
  if (want("claude")) {
4850
- const ccJson = path6.join(os2.homedir(), ".claude.json");
4901
+ const ccJson = path7.join(os2.homedir(), ".claude.json");
4851
4902
  backup(ccJson);
4852
4903
  const cc = readJson2(ccJson) ?? {};
4853
4904
  const ccMcp = cc.mcpServers ?? {};
4854
4905
  ccMcp.exomind = { command: "exomind", args: ["mcp"] };
4855
4906
  cc.mcpServers = ccMcp;
4856
- fs6.writeFileSync(ccJson, JSON.stringify(cc, null, 2) + "\n");
4907
+ fs7.writeFileSync(ccJson, JSON.stringify(cc, null, 2) + "\n");
4857
4908
  configured.push("Claude Code (~/.claude.json)");
4858
4909
  }
4859
4910
  if (want("opencode")) {
4860
- const ocJson = path6.join(os2.homedir(), ".config", "opencode", "opencode.json");
4861
- fs6.mkdirSync(path6.dirname(ocJson), { recursive: true });
4911
+ const ocJson = path7.join(os2.homedir(), ".config", "opencode", "opencode.json");
4912
+ fs7.mkdirSync(path7.dirname(ocJson), { recursive: true });
4862
4913
  backup(ocJson);
4863
4914
  const oc = readJson2(ocJson) ?? {};
4864
4915
  const ocMcp = oc.mcp ?? {};
4865
4916
  ocMcp.exomind = { type: "local", command: ["exomind", "mcp"] };
4866
4917
  oc.mcp = ocMcp;
4867
- fs6.writeFileSync(ocJson, JSON.stringify(oc, null, 2) + "\n");
4918
+ fs7.writeFileSync(ocJson, JSON.stringify(oc, null, 2) + "\n");
4868
4919
  configured.push("OpenCode (~/.config/opencode/opencode.json)");
4869
4920
  }
4870
4921
  if (want("codex")) {
@@ -4873,14 +4924,14 @@ async function install(client, opts) {
4873
4924
  else console.log(yellow(`\u2717 Codex MCP \u914D\u7F6E\u5931\u8D25: ${r.reason}`));
4874
4925
  }
4875
4926
  const purged = [];
4876
- if (want("claude") && purgeMcpExomind(path6.join(claudeDir, "settings.json"))) {
4927
+ if (want("claude") && purgeMcpExomind(path7.join(claudeDir, "settings.json"))) {
4877
4928
  purged.push("~/.claude/settings.json");
4878
4929
  }
4879
4930
  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);
4931
+ for (let i = 0; i < 12 && curDir !== path7.dirname(curDir); i++) {
4932
+ const f = path7.join(curDir, ".mcp.json");
4933
+ if (fs7.existsSync(f) && purgeMcpExomind(f)) purged.push(f.replace(os2.homedir(), "~"));
4934
+ curDir = path7.dirname(curDir);
4884
4935
  }
4885
4936
  if (purged.length) console.log(dim(` \u2192 \u6E05\u7406\u6B8B\u7559 exomind MCP \u6761\u76EE(\u907F\u514D\u8986\u76D6 stdio): ${purged.join(", ")}`));
4886
4937
  if (configured.length) {
@@ -4889,12 +4940,12 @@ async function install(client, opts) {
4889
4940
  console.log(dim(" \u91CD\u542F\u5BF9\u5E94 Agent \u2192 \u62FF\u5230 mcp__exomind__* \u5DE5\u5177"));
4890
4941
  }
4891
4942
  }
4892
- const cfgFile = path6.join(os2.homedir(), ".exomind", "config.json");
4943
+ const cfgFile = path7.join(os2.homedir(), ".exomind", "config.json");
4893
4944
  const cfg = readJson2(cfgFile);
4894
4945
  if (cfg && typeof cfg.base_url === "string" && cfg.base_url.includes("d.youhuale.cn")) {
4895
4946
  backup(cfgFile);
4896
4947
  cfg.base_url = DEFAULT_BASE_URL;
4897
- fs6.writeFileSync(cfgFile, JSON.stringify(cfg, null, 2) + "\n");
4948
+ fs7.writeFileSync(cfgFile, JSON.stringify(cfg, null, 2) + "\n");
4898
4949
  console.log(ok(`\u5DF2\u8FC1\u79FB base_url: d.youhuale.cn \u2192 ${DEFAULT_BASE_URL}`));
4899
4950
  console.log(dim(" (stdio MCP \u5B50\u8FDB\u7A0B\u8BFB\u6B64\u914D\u7F6E,\u91CD\u542F Agent \u540E\u7528\u65B0\u57DF\u540D)"));
4900
4951
  }
@@ -4913,10 +4964,10 @@ async function install(client, opts) {
4913
4964
  }
4914
4965
  }
4915
4966
  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"));
4967
+ const codexSkillOk = fs7.existsSync(path7.join(codexHome, "skills", "exomind", "SKILL.md"));
4917
4968
  const codexMcfgOk = (() => {
4918
4969
  try {
4919
- return /^\[mcp_servers\.exomind\]\s*$/m.test(fs6.readFileSync(path6.join(codexHome, "config.toml"), "utf-8"));
4970
+ return /^\[mcp_servers\.exomind\]\s*$/m.test(fs7.readFileSync(path7.join(codexHome, "config.toml"), "utf-8"));
4920
4971
  } catch {
4921
4972
  return false;
4922
4973
  }
@@ -4947,19 +4998,19 @@ async function install(client, opts) {
4947
4998
  }
4948
4999
 
4949
5000
  // src/commands/doctor.ts
4950
- var fs7 = __toESM(require("fs"));
4951
- var path7 = __toESM(require("path"));
5001
+ var fs8 = __toESM(require("fs"));
5002
+ var path8 = __toESM(require("path"));
4952
5003
  var os3 = __toESM(require("os"));
4953
5004
  function readText(p) {
4954
5005
  try {
4955
- return fs7.readFileSync(p, "utf-8");
5006
+ return fs8.readFileSync(p, "utf-8");
4956
5007
  } catch {
4957
5008
  return "";
4958
5009
  }
4959
5010
  }
4960
5011
  function fileExists(p) {
4961
5012
  try {
4962
- return fs7.existsSync(p);
5013
+ return fs8.existsSync(p);
4963
5014
  } catch {
4964
5015
  return false;
4965
5016
  }
@@ -4982,9 +5033,9 @@ function badge(s) {
4982
5033
  async function doctor(client) {
4983
5034
  const codexHome = resolveCodexHome();
4984
5035
  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");
5036
+ const claudeSkill = path8.join(os3.homedir(), ".claude", "skills", "exomind", "SKILL.md");
5037
+ const claudeSettings = path8.join(os3.homedir(), ".claude", "settings.json");
5038
+ const claudeJson = path8.join(os3.homedir(), ".claude.json");
4988
5039
  checks.push({
4989
5040
  host: "claude",
4990
5041
  skill: fileExists(claudeSkill) ? "ok" : "missing",
@@ -4992,8 +5043,8 @@ async function doctor(client) {
4992
5043
  mcpConfig: mcpServersExomind(claudeJson, "mcpServers") ? "ok" : "missing",
4993
5044
  paths: [claudeSkill, claudeSettings, claudeJson]
4994
5045
  });
4995
- const codexSkill = path7.join(codexHome, "skills", "exomind", "SKILL.md");
4996
- const codexConfig = path7.join(codexHome, "config.toml");
5046
+ const codexSkill = path8.join(codexHome, "skills", "exomind", "SKILL.md");
5047
+ const codexConfig = path8.join(codexHome, "config.toml");
4997
5048
  checks.push({
4998
5049
  host: "codex",
4999
5050
  skill: fileExists(codexSkill) ? "ok" : "missing",
@@ -5002,7 +5053,7 @@ async function doctor(client) {
5002
5053
  paths: [codexSkill, codexConfig],
5003
5054
  detail: "Codex \u65E0 prompt-submit hook,\u9760 skill \u89E6\u53D1(jdit/\u5B58\u6863/\u67E5\u8BE2)"
5004
5055
  });
5005
- const ocJson = path7.join(os3.homedir(), ".config", "opencode", "opencode.json");
5056
+ const ocJson = path8.join(os3.homedir(), ".config", "opencode", "opencode.json");
5006
5057
  checks.push({
5007
5058
  host: "opencode",
5008
5059
  skill: "unsupported",
@@ -5080,6 +5131,7 @@ function run(fn) {
5080
5131
  const client = new ApiClient(cfg);
5081
5132
  try {
5082
5133
  await fn(client, command.opts(), command.args);
5134
+ if (!root.json) await maybeNotifyUpdate(VERSION);
5083
5135
  } catch (e) {
5084
5136
  handleError(e);
5085
5137
  }
@@ -5090,7 +5142,7 @@ program2.name("exomind").description("ExoMind \u8DE8\u5E73\u53F0\u77E5\u8BC6\u5E
5090
5142
  program2.command("login").description("\u914D\u7F6E\u670D\u52A1\u5668\u5730\u5740\u4E0E\u51ED\u8BC1,\u5199\u5165 ~/.exomind/config.json").option("--base-url <url>", "\u670D\u52A1\u5668\u5730\u5740").option("--api-key <key>", "API Key \u6216\u767B\u5F55 token(\u4E0D\u586B\u5219\u4EA4\u4E92\u8F93\u5165)").action(run(login));
5091
5143
  program2.command("me").description("\u663E\u793A\u5F53\u524D\u767B\u5F55\u6001\u4E0E\u670D\u52A1\u5668").action(run(whoami));
5092
5144
  program2.command("whoami", { hidden: true }).description("\u663E\u793A\u5F53\u524D\u767B\u5F55\u6001\u4E0E\u670D\u52A1\u5668(me \u7684\u65E7\u540D)").action(run(whoami));
5093
- program2.command("ingest [content...]").description("\u5BFC\u5165\u77E5\u8BC6: \u53C2\u6570\u6587\u672C / --file / stdin / --dir \u76EE\u5F55\u6279\u91CF(\u589E\u91CF)").option("-t, --title <title>", "\u6807\u9898(\u5355\u6587\u4EF6\u6A21\u5F0F)").option("--tag <tag>", "\u6807\u7B7E(\u53EF\u91CD\u590D)", collect, []).option("--file <path>", "\u4ECE\u6587\u4EF6\u8BFB\u53D6\u5185\u5BB9").option("--dir <path>", "\u76EE\u5F55\u6279\u91CF\u6444\u5165(\u589E\u91CF: \u5185\u5BB9\u54C8\u5E0C\u8DF3\u8FC7\u672A\u53D8\u6587\u4EF6)").option("-r, --recursive", "\u9012\u5F52\u5B50\u76EE\u5F55(\u914D\u5408 --dir)").option("--pattern <glob>", "\u6587\u4EF6\u540D\u5339\u914D,\u9ED8\u8BA4 *.md", "*.md").option("--force", "\u5FFD\u7565 manifest,\u5F3A\u5236\u5168\u91CF\u91CD\u6444(\u914D\u5408 --dir)").option(
5145
+ program2.command("ingest [content...]").description("\u5BFC\u5165\u77E5\u8BC6: \u53C2\u6570\u6587\u672C / --file / stdin / --dir \u76EE\u5F55\u6279\u91CF(\u589E\u91CF)").option("-t, --title <title>", "\u6807\u9898(\u5355\u6587\u4EF6\u6A21\u5F0F)").option("--tag <tag>", "\u6807\u7B7E(\u53EF\u91CD\u590D)", collect, []).option("--file <path>", "\u4ECE\u6587\u4EF6\u8BFB\u53D6\u5185\u5BB9").option("--dir [path]", "\u76EE\u5F55\u6279\u91CF\u6444\u5165(\u589E\u91CF: \u5185\u5BB9\u54C8\u5E0C\u8DF3\u8FC7\u672A\u53D8\u6587\u4EF6;\u76EE\u5F55\u53EF\u5199\u5728\u524D: ingest <\u76EE\u5F55> --dir)").option("-r, --recursive", "\u9012\u5F52\u5B50\u76EE\u5F55(\u914D\u5408 --dir)").option("--pattern <glob>", "\u6587\u4EF6\u540D\u5339\u914D,\u9ED8\u8BA4 *.md", "*.md").option("--force", "\u5FFD\u7565 manifest,\u5F3A\u5236\u5168\u91CF\u91CD\u6444(\u914D\u5408 --dir)").option(
5094
5146
  "-c, --concurrency <n>",
5095
5147
  "\u5E76\u53D1\u6444\u5165\u6570(\u914D\u5408 --dir,\u9ED8\u8BA4 3,\u5F31\u670D\u52A1\u5668\u53CB\u597D)",
5096
5148
  (v) => parseInt(v, 10) || 3,