exomind 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,6 +17,8 @@ exomind login # 粘贴 youhuale.cn/ui/account 的 API Key
17
17
  exomind me # 验证登录
18
18
  ```
19
19
 
20
+ > **短命令**:`emcli` 是同一入口的别名(`exomind` 太长时的省敲方案),`emcli query "..."` 等价于 `exomind query "..."`,help/usage 会跟随实际调用名。两个名字同时可用;文档示例一律写 `exomind`。
21
+
20
22
  > 前置:Node.js 18+(`node -v`)。CI 等场景可改用环境变量 `EXOMIND_API_KEY` / `EXOMIND_BASE_URL`,免登录。
21
23
 
22
24
  ## 快速开始
package/dist/cli.js CHANGED
@@ -3119,10 +3119,11 @@ var {
3119
3119
  // package.json
3120
3120
  var package_default = {
3121
3121
  name: "exomind",
3122
- version: "0.15.0",
3122
+ version: "0.17.0",
3123
3123
  description: "ExoMind \u8DE8\u5E73\u53F0\u547D\u4EE4\u884C\u5BA2\u6237\u7AEF \u2014 \u901A\u8FC7 REST \u4E0E ExoMind \u77E5\u8BC6\u98DE\u8F6E\u4EA4\u4E92(ingest/query/search/review),\u66FF\u4EE3 Windows \u4E0D\u53EF\u7528\u7684 MCP \u5BA2\u6237\u7AEF\u3002",
3124
3124
  bin: {
3125
- exomind: "dist/cli.js"
3125
+ exomind: "dist/cli.js",
3126
+ emcli: "dist/cli.js"
3126
3127
  },
3127
3128
  files: [
3128
3129
  "dist",
@@ -3302,6 +3303,12 @@ function saveConfig(cfg) {
3302
3303
  } catch {
3303
3304
  }
3304
3305
  }
3306
+ function invalidateKeywordCache() {
3307
+ try {
3308
+ fs.unlinkSync(CACHE_KEYWORDS);
3309
+ } catch {
3310
+ }
3311
+ }
3305
3312
  function resolveConfig(overrides) {
3306
3313
  const base = loadConfig();
3307
3314
  return {
@@ -3632,13 +3639,27 @@ async function getEntityDesc(client, name, deadline = Number.POSITIVE_INFINITY)
3632
3639
  return { name, description: "" };
3633
3640
  }
3634
3641
  }
3642
+ var SHORT_ASCII = /^[a-z0-9._#+-]{2,4}$/;
3643
+ function escapeRegExp(s) {
3644
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3645
+ }
3646
+ function hitsPrompt(promptLower, candLower) {
3647
+ if (SHORT_ASCII.test(candLower)) {
3648
+ try {
3649
+ return new RegExp(`(?<![a-z0-9])${escapeRegExp(candLower)}(?![a-z0-9])`).test(promptLower);
3650
+ } catch {
3651
+ return promptLower.includes(candLower);
3652
+ }
3653
+ }
3654
+ return promptLower.includes(candLower);
3655
+ }
3635
3656
  function matchEntities(prompt2, candidates) {
3636
3657
  const lower = prompt2.toLowerCase();
3637
3658
  const hits = /* @__PURE__ */ new Set();
3638
3659
  for (const name of candidates) {
3639
3660
  const nl = name.toLowerCase().trim();
3640
3661
  if (nl.length < 2) continue;
3641
- if (lower.includes(nl)) hits.add(name);
3662
+ if (hitsPrompt(lower, nl)) hits.add(name);
3642
3663
  }
3643
3664
  return [...hits].sort((a, b) => b.length - a.length);
3644
3665
  }
@@ -3664,22 +3685,39 @@ ${desc}
3664
3685
  }
3665
3686
  async function buildContext(client, msg, dedup, now, deadline = Number.POSITIVE_INFINITY) {
3666
3687
  const index = await getKeywordIndex(client, deadline);
3667
- if (!index) return "";
3688
+ if (!index) {
3689
+ return '[ExoMind] \u77E5\u8BC6\u98DE\u8F6E\u4E0A\u4E0B\u6587\u672C\u6B21\u672A\u6CE8\u5165(\u5173\u952E\u8BCD\u8868\u62C9\u53D6\u8D85\u65F6\u6216\u5931\u8D25);\u9700\u8981\u53CD\u67E5\u53EF\u8FD0\u884C `exomind search "\u5173\u952E\u8BCD"`\u3002';
3690
+ }
3668
3691
  const candidates = [...index.names || [], ...index.aliases || []];
3669
3692
  const matched = matchEntities(msg, candidates);
3670
3693
  if (!matched.length) return "";
3671
3694
  const picked = [];
3695
+ const pickedCanonical = /* @__PURE__ */ new Set();
3696
+ const cooled = [];
3672
3697
  for (const name of matched) {
3673
3698
  if (picked.length >= 3) break;
3674
3699
  if (budgetMs(deadline) <= 200) break;
3675
3700
  const canonical = name;
3676
- if (dedup.injected[canonical] && now - dedup.injected[canonical] < COOLDOWN_MS) continue;
3701
+ if (dedup.injected[canonical] && now - dedup.injected[canonical] < COOLDOWN_MS) {
3702
+ cooled.push(name);
3703
+ continue;
3704
+ }
3677
3705
  const desc = await getEntityDesc(client, name, deadline);
3678
3706
  if (desc.description || desc.relationships?.length) {
3679
- picked.push(desc);
3680
3707
  dedup.injected[canonical] = now;
3708
+ const norm = (desc.name || name).toLowerCase();
3709
+ if (pickedCanonical.has(norm)) continue;
3710
+ pickedCanonical.add(norm);
3711
+ picked.push(desc);
3681
3712
  }
3682
3713
  }
3714
+ if (!picked.length && cooled.length) {
3715
+ const remainMin = Math.ceil(
3716
+ (COOLDOWN_MS - (now - Math.max(...cooled.map((n) => dedup.injected[n] ?? 0)))) / 6e4
3717
+ );
3718
+ const sample = cooled.slice(0, 2).map((n) => `"${n}"`).join("\u3001");
3719
+ return `[ExoMind] ${sample} \u7B49\u5173\u952E\u8BCD\u7684\u4E0A\u4E0B\u6587\u5DF2\u6CE8\u5165\u8FC7,30 \u5206\u949F\u53BB\u91CD\u51B7\u5374\u8FD8\u5269 ${remainMin} \u5206\u949F,\u672C\u6B21\u8DF3\u8FC7;\u9700\u8981\u8BE6\u60C5\u53EF\u8FD0\u884C \`exomind entity "${cooled[0]}"\`\u3002`;
3720
+ }
3683
3721
  return contextBlock(picked);
3684
3722
  }
3685
3723
  async function runHook(client) {
@@ -4070,7 +4108,9 @@ async function ingestWithRetry(client, payload, timeoutMs, url = "/ingest") {
4070
4108
  let transientAttempts = 0;
4071
4109
  while (true) {
4072
4110
  try {
4073
- return await client.post(url, payload, { timeoutMs });
4111
+ const res = await client.post(url, payload, { timeoutMs });
4112
+ invalidateKeywordCache();
4113
+ return res;
4074
4114
  } catch (e) {
4075
4115
  if (!(e instanceof ApiError)) throw e;
4076
4116
  if (e.status === 429) {
@@ -5334,7 +5374,8 @@ function run(fn) {
5334
5374
  };
5335
5375
  }
5336
5376
  var program2 = new Command();
5337
- program2.name("exomind").description("ExoMind \u8DE8\u5E73\u53F0\u77E5\u8BC6\u98DE\u8F6E\u5BA2\u6237\u7AEF \u2014 \u901A\u8FC7 REST \u4EA4\u4E92(\u66FF\u4EE3 Windows MCP \u5BA2\u6237\u7AEF)\u3002").version(VERSION).option("--json", "\u8F93\u51FA\u539F\u59CB JSON(\u673A\u5668\u53EF\u8BFB)").option("--base-url <url>", "\u8986\u76D6\u670D\u52A1\u5668\u5730\u5740").option("--api-key <key>", "\u8986\u76D6 API Key / \u51ED\u8BC1");
5377
+ var binName = (process.argv[1] ?? "").endsWith("/emcli") ? "emcli" : "exomind";
5378
+ program2.name(binName).description("ExoMind \u8DE8\u5E73\u53F0\u77E5\u8BC6\u98DE\u8F6E\u5BA2\u6237\u7AEF \u2014 \u901A\u8FC7 REST \u4EA4\u4E92(\u66FF\u4EE3 Windows MCP \u5BA2\u6237\u7AEF)\u3002").version(VERSION).option("--json", "\u8F93\u51FA\u539F\u59CB JSON(\u673A\u5668\u53EF\u8BFB)").option("--base-url <url>", "\u8986\u76D6\u670D\u52A1\u5668\u5730\u5740").option("--api-key <key>", "\u8986\u76D6 API Key / \u51ED\u8BC1");
5338
5379
  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));
5339
5380
  program2.command("me").description("\u663E\u793A\u5F53\u524D\u767B\u5F55\u6001\u4E0E\u670D\u52A1\u5668").action(run(whoami));
5340
5381
  program2.command("whoami", { hidden: true }).description("\u663E\u793A\u5F53\u524D\u767B\u5F55\u6001\u4E0E\u670D\u52A1\u5668(me \u7684\u65E7\u540D)").action(run(whoami));