exomind 0.3.3 → 0.3.4

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
@@ -3119,7 +3119,7 @@ var {
3119
3119
  // package.json
3120
3120
  var package_default = {
3121
3121
  name: "exomind",
3122
- version: "0.3.3",
3122
+ version: "0.3.4",
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"
@@ -3172,10 +3172,14 @@ function opTimeout(defaultMs) {
3172
3172
  var ApiError = class extends Error {
3173
3173
  status;
3174
3174
  detail;
3175
- constructor(status, detail) {
3175
+ headers;
3176
+ body;
3177
+ constructor(status, detail, headers = {}, body = null) {
3176
3178
  super(`HTTP ${status}: ${detail}`);
3177
3179
  this.status = status;
3178
3180
  this.detail = detail;
3181
+ this.headers = headers;
3182
+ this.body = body;
3179
3183
  }
3180
3184
  };
3181
3185
  var ApiClient = class {
@@ -3219,13 +3223,18 @@ var ApiClient = class {
3219
3223
  });
3220
3224
  const text = await res.text();
3221
3225
  if (!res.ok) {
3226
+ const headers = {};
3227
+ res.headers?.forEach((v, k) => {
3228
+ headers[k.toLowerCase()] = v;
3229
+ });
3222
3230
  let detail = text || res.statusText;
3231
+ let body = null;
3223
3232
  try {
3224
- const j = JSON.parse(text);
3225
- detail = j.detail || j.message || JSON.stringify(j);
3233
+ body = JSON.parse(text);
3234
+ detail = body.detail || body.message || JSON.stringify(body);
3226
3235
  } catch {
3227
3236
  }
3228
- throw new ApiError(res.status, String(detail).slice(0, 800));
3237
+ throw new ApiError(res.status, String(detail).slice(0, 800), headers, body);
3229
3238
  }
3230
3239
  if (opts.text) return text;
3231
3240
  try {
@@ -3881,6 +3890,55 @@ function planIngestest(files, manifest, force) {
3881
3890
  }
3882
3891
  return { toIngest, toSkip };
3883
3892
  }
3893
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
3894
+ var suspended = false;
3895
+ async function ingestWithRetry(client, payload, timeoutMs) {
3896
+ let rateAttempts = 0;
3897
+ let quotaWaits = 0;
3898
+ while (true) {
3899
+ try {
3900
+ return await client.post("/ingest", payload, { timeoutMs });
3901
+ } catch (e) {
3902
+ if (!(e instanceof ApiError) || e.status !== 429) throw e;
3903
+ const type = e.body?.type;
3904
+ if (type === "daily_quota") {
3905
+ if (++quotaWaits > 3) throw new ApiError(429, "\u914D\u989D\u8FDE\u7EED 3 \u4E2A\u81EA\u7136\u65E5\u672A\u6062\u590D,\u653E\u5F03");
3906
+ await suspendUntilMidnight(Number(e.body?.reset ?? 0));
3907
+ continue;
3908
+ }
3909
+ if (type === "rate_limit") {
3910
+ if (++rateAttempts >= 5) throw new ApiError(429, "\u5E76\u53D1\u9650\u6D41,\u91CD\u8BD5 5 \u6B21\u4ECD\u5931\u8D25");
3911
+ const retry = Number(e.headers["retry-after"] ?? e.body?.retry_after ?? 5);
3912
+ process.stderr.write(dim(` \u23F8 \u5E76\u53D1\u9650\u6D41,${retry}s \u540E\u91CD\u8BD5
3913
+ `));
3914
+ await sleep(retry * 1e3);
3915
+ continue;
3916
+ }
3917
+ throw e;
3918
+ }
3919
+ }
3920
+ }
3921
+ async function suspendUntilMidnight(resetEpoch) {
3922
+ suspended = true;
3923
+ const now = Date.now();
3924
+ const waitMs = resetEpoch > 0 ? Math.max(1e3, resetEpoch * 1e3 - now) : 60 * 1e3;
3925
+ process.stderr.write(
3926
+ dim(` \u23F8 \u4ECA\u65E5\u914D\u989D\u5DF2\u6EE1,\u6302\u8D77\u5230\u6B21\u65E5 0 \u70B9\u7EED\u8DD1(\u7EA6 ${Math.ceil(waitMs / 6e4)} \u5206\u949F)
3927
+ `)
3928
+ );
3929
+ process.stderr.write(dim(" Ctrl+C \u53EF\u5B89\u5168\u9000\u51FA(\u5DF2\u4FDD\u5B58\u8FDB\u5EA6),\u6B21\u65E5\u91CD\u8DD1\u540C\u547D\u4EE4\u5373\u53EF\u7EED\u8DD1\n"));
3930
+ const start = Date.now();
3931
+ while (Date.now() - start < waitMs) {
3932
+ const remaining = waitMs - (Date.now() - start);
3933
+ await sleep(Math.min(6e4, remaining));
3934
+ const remainMin = Math.ceil((waitMs - (Date.now() - start)) / 6e4);
3935
+ if (remainMin > 0) process.stderr.write(`\r ${dim(`\u5269\u4F59\u7EA6 ${remainMin} \u5206\u949F`)} `);
3936
+ }
3937
+ process.stderr.write(`
3938
+ ${dim("\u5230\u8FBE 0 \u70B9,\u7EE7\u7EED\u6444\u5165\u2026")}
3939
+ `);
3940
+ suspended = false;
3941
+ }
3884
3942
  async function runDirIngestest(client, opts, dir) {
3885
3943
  const files = walkDir(dir, !!opts.recursive, opts.pattern || "*.md");
3886
3944
  if (!files.length) {
@@ -3894,39 +3952,51 @@ async function runDirIngestest(client, opts, dir) {
3894
3952
  `\u76EE\u5F55 ${dir}: ${total} \u6587\u4EF6 \u2014 \u5F85\u6444\u5165 ${plan.toIngest.length},\u8DF3\u8FC7 ${plan.toSkip.length}
3895
3953
  `
3896
3954
  );
3955
+ const onSigInt = () => {
3956
+ if (suspended) {
3957
+ saveManifest(manifest);
3958
+ process.stderr.write("\n\u5DF2\u4FDD\u5B58\u8FDB\u5EA6,\u9000\u51FA\u3002\u6B21\u65E5\u91CD\u8DD1\u540C\u547D\u4EE4\u5373\u53EF\u7EED\u8DD1\u3002\n");
3959
+ }
3960
+ process.exit(130);
3961
+ };
3962
+ process.on("SIGINT", onSigInt);
3897
3963
  let added = 0;
3898
3964
  let updated = 0;
3899
3965
  let failed = 0;
3900
- for (let i = 0; i < plan.toIngest.length; i++) {
3901
- const f = plan.toIngest[i];
3902
- const prev = manifest[f.path];
3903
- process.stderr.write(`\u23F3 [${i + 1}/${plan.toIngest.length}] ${path4.basename(f.path)}\u2026
3966
+ try {
3967
+ for (let i = 0; i < plan.toIngest.length; i++) {
3968
+ const f = plan.toIngest[i];
3969
+ const prev = manifest[f.path];
3970
+ process.stderr.write(`\u23F3 [${i + 1}/${plan.toIngest.length}] ${path4.basename(f.path)}\u2026
3904
3971
  `);
3905
- try {
3906
- const res = await client.post(
3907
- "/ingest",
3908
- { content: f.content, title: f.title, tags: opts.tag },
3909
- { timeoutMs: opTimeout(3e5) }
3910
- );
3911
- recordFile(manifest, f.path, f.content, f.title);
3912
- saveManifest(manifest);
3913
- if (prev) {
3914
- updated++;
3915
- process.stderr.write(` ${green("\u2713")} \u66F4\u65B0 \u2014 \u5B9E\u4F53 ${res.entities ?? 0}/\u6982\u5FF5 ${res.concepts ?? 0}
3972
+ try {
3973
+ const res = await ingestWithRetry(
3974
+ client,
3975
+ { content: f.content, title: f.title, tags: opts.tag },
3976
+ opTimeout(3e5)
3977
+ );
3978
+ recordFile(manifest, f.path, f.content, f.title);
3979
+ saveManifest(manifest);
3980
+ if (prev) {
3981
+ updated++;
3982
+ process.stderr.write(` ${green("\u2713")} \u66F4\u65B0 \u2014 \u5B9E\u4F53 ${res.entities ?? 0}/\u6982\u5FF5 ${res.concepts ?? 0}
3916
3983
  `);
3917
- } else {
3918
- added++;
3919
- process.stderr.write(` ${green("\u2713")} \u65B0\u589E \u2014 \u5B9E\u4F53 ${res.entities ?? 0}/\u6982\u5FF5 ${res.concepts ?? 0}
3984
+ } else {
3985
+ added++;
3986
+ process.stderr.write(` ${green("\u2713")} \u65B0\u589E \u2014 \u5B9E\u4F53 ${res.entities ?? 0}/\u6982\u5FF5 ${res.concepts ?? 0}
3920
3987
  `);
3921
- }
3922
- } catch (e) {
3923
- failed++;
3924
- process.stderr.write(` ${red("\u2717")} ${e.message}
3988
+ }
3989
+ } catch (e) {
3990
+ failed++;
3991
+ process.stderr.write(` ${red("\u2717")} ${e.message}
3925
3992
  `);
3993
+ }
3926
3994
  }
3995
+ cleanupStale(manifest, dir, files);
3996
+ saveManifest(manifest);
3997
+ } finally {
3998
+ process.removeListener("SIGINT", onSigInt);
3927
3999
  }
3928
- cleanupStale(manifest, dir, files);
3929
- saveManifest(manifest);
3930
4000
  const allUpToDate = added + updated === 0 && plan.toSkip.length > 0 && failed === 0;
3931
4001
  output(
3932
4002
  { added, updated, skipped: plan.toSkip.length, failed, total, dir, allUpToDate },