exomind 0.1.3 → 0.2.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
@@ -26,6 +26,7 @@ exomind whoami # 验证登录
26
26
  exomind ingest "Redis 持久化:RDB 快照 + AOF 日志,混合模式推荐" -t "Redis 持久化" --tag redis
27
27
  echo "管道内容" | exomind ingest -t "标题"
28
28
  exomind ingest --file ./notes.md -t "标题"
29
+ exomind ingest --dir ./notes --recursive # 目录批量(增量: SHA-256 跳过未变文件)
29
30
 
30
31
  # 查询与搜索
31
32
  exomind query "Redis RDB 和 AOF 的区别?"
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 path4 = require("path");
969
- var fs5 = require("fs");
968
+ var path6 = require("path");
969
+ var fs7 = 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 = path4.resolve(baseDir, baseName);
1902
- if (fs5.existsSync(localBin)) return localBin;
1903
- if (sourceExt.includes(path4.extname(baseName))) return void 0;
1901
+ const localBin = path6.resolve(baseDir, baseName);
1902
+ if (fs7.existsSync(localBin)) return localBin;
1903
+ if (sourceExt.includes(path6.extname(baseName))) return void 0;
1904
1904
  const foundExt = sourceExt.find(
1905
- (ext) => fs5.existsSync(`${localBin}${ext}`)
1905
+ (ext) => fs7.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 = fs5.realpathSync(this._scriptPath);
1917
+ resolvedScriptPath = fs7.realpathSync(this._scriptPath);
1918
1918
  } catch (err) {
1919
1919
  resolvedScriptPath = this._scriptPath;
1920
1920
  }
1921
- executableDir = path4.resolve(
1922
- path4.dirname(resolvedScriptPath),
1921
+ executableDir = path6.resolve(
1922
+ path6.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 = path4.basename(
1929
+ const legacyName = path6.basename(
1930
1930
  this._scriptPath,
1931
- path4.extname(this._scriptPath)
1931
+ path6.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(path4.extname(executableFile));
1942
+ launchWithNode = sourceExt.includes(path6.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 = path4.basename(filename, path4.extname(filename));
2782
+ this._name = path6.basename(filename, path6.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(path5) {
2797
- if (path5 === void 0) return this._executableDir;
2798
- this._executableDir = path5;
2796
+ executableDir(path7) {
2797
+ if (path7 === void 0) return this._executableDir;
2798
+ this._executableDir = path7;
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.1.3",
3122
+ version: "0.2.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"
@@ -3624,8 +3624,157 @@ async function runHook(client) {
3624
3624
  }
3625
3625
  }
3626
3626
 
3627
+ // src/ingest_dir.ts
3628
+ var fs5 = __toESM(require("fs"));
3629
+ var path4 = __toESM(require("path"));
3630
+
3631
+ // src/manifest.ts
3632
+ var fs4 = __toESM(require("fs"));
3633
+ var path3 = __toESM(require("path"));
3634
+ var crypto = __toESM(require("crypto"));
3635
+ var MANIFEST_FILE = path3.join(CONFIG_DIR, "manifest.json");
3636
+ function sha256(s) {
3637
+ return crypto.createHash("sha256").update(s, "utf-8").digest("hex");
3638
+ }
3639
+ function loadManifest() {
3640
+ try {
3641
+ const d = JSON.parse(fs4.readFileSync(MANIFEST_FILE, "utf-8"));
3642
+ return d && typeof d === "object" ? d : {};
3643
+ } catch {
3644
+ return {};
3645
+ }
3646
+ }
3647
+ function saveManifest(m) {
3648
+ try {
3649
+ fs4.mkdirSync(CONFIG_DIR, { recursive: true });
3650
+ fs4.writeFileSync(MANIFEST_FILE, JSON.stringify(m, null, 2));
3651
+ } catch {
3652
+ }
3653
+ }
3654
+ function cleanupStale(m, dir, currentFiles) {
3655
+ const prefix = path3.resolve(dir) + path3.sep;
3656
+ const current = new Set(currentFiles);
3657
+ for (const key of Object.keys(m)) {
3658
+ if (key.startsWith(prefix) && !current.has(key)) {
3659
+ delete m[key];
3660
+ }
3661
+ }
3662
+ }
3663
+
3664
+ // src/ingest_dir.ts
3665
+ function globToRegex(pattern) {
3666
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
3667
+ return new RegExp(`^${escaped}$`);
3668
+ }
3669
+ function walkDir(dir, recursive, pattern) {
3670
+ const rx = globToRegex(pattern);
3671
+ const out = [];
3672
+ const walk = (d) => {
3673
+ let entries;
3674
+ try {
3675
+ entries = fs5.readdirSync(d, { withFileTypes: true });
3676
+ } catch {
3677
+ return;
3678
+ }
3679
+ for (const e of entries) {
3680
+ if (e.name.startsWith(".")) continue;
3681
+ const full = path4.join(d, e.name);
3682
+ if (e.isDirectory()) {
3683
+ if (recursive) walk(full);
3684
+ } else if (e.isFile() && rx.test(e.name)) {
3685
+ out.push(full);
3686
+ }
3687
+ }
3688
+ };
3689
+ walk(path4.resolve(dir));
3690
+ return out.sort();
3691
+ }
3692
+ function deriveTitle(file, content) {
3693
+ for (const line of content.split("\n")) {
3694
+ const m = line.match(/^#\s+(.+?)\s*$/);
3695
+ if (m) return m[1].trim();
3696
+ }
3697
+ return path4.basename(file).replace(/\.[^.]+$/, "");
3698
+ }
3699
+ function planIngestest(files, manifest, force) {
3700
+ const toIngest = [];
3701
+ const toSkip = [];
3702
+ for (const f of files) {
3703
+ let content;
3704
+ try {
3705
+ content = readFileText(f);
3706
+ } catch {
3707
+ continue;
3708
+ }
3709
+ const hash = sha256(content);
3710
+ const prev = manifest[f];
3711
+ if (!force && prev && prev.hash === hash) {
3712
+ toSkip.push(f);
3713
+ } else {
3714
+ toIngest.push({ path: f, content, title: deriveTitle(f, content), hash });
3715
+ }
3716
+ }
3717
+ return { toIngest, toSkip };
3718
+ }
3719
+ async function runDirIngestest(client, opts, dir) {
3720
+ const files = walkDir(dir, !!opts.recursive, opts.pattern || "*.md");
3721
+ if (!files.length) {
3722
+ console.log(dim(`\u76EE\u5F55 ${dir} \u4E0B\u65E0\u5339\u914D\u6587\u4EF6\u3002`));
3723
+ return;
3724
+ }
3725
+ const manifest = loadManifest();
3726
+ const plan = planIngestest(files, manifest, !!opts.force);
3727
+ const total = files.length;
3728
+ process.stderr.write(
3729
+ `\u76EE\u5F55 ${dir}: ${total} \u6587\u4EF6 \u2192 \u65B0\u589E/\u66F4\u65B0 ${plan.toIngest.length},\u8DF3\u8FC7 ${plan.toSkip.length}
3730
+ `
3731
+ );
3732
+ let added = 0;
3733
+ let updated = 0;
3734
+ let failed = 0;
3735
+ for (let i = 0; i < plan.toIngest.length; i++) {
3736
+ const f = plan.toIngest[i];
3737
+ const prev = manifest[f.path];
3738
+ process.stderr.write(
3739
+ `\u23F3 [${i + 1}/${plan.toIngest.length}] ${path4.basename(f.path)} ${prev ? "(\u66F4\u65B0)" : "(\u65B0\u589E)"}\u2026
3740
+ `
3741
+ );
3742
+ try {
3743
+ await client.post(
3744
+ "/ingest",
3745
+ { content: f.content, title: f.title, tags: opts.tag },
3746
+ { timeoutMs: opTimeout(3e5) }
3747
+ );
3748
+ manifest[f.path] = {
3749
+ hash: f.hash,
3750
+ ingested_at: (/* @__PURE__ */ new Date()).toISOString(),
3751
+ title: f.title,
3752
+ size: f.content.length
3753
+ };
3754
+ if (prev) updated++;
3755
+ else added++;
3756
+ saveManifest(manifest);
3757
+ } catch (e) {
3758
+ failed++;
3759
+ process.stderr.write(` ${red("\u2717")} ${path4.basename(f.path)}: ${e.message}
3760
+ `);
3761
+ }
3762
+ }
3763
+ cleanupStale(manifest, dir, files);
3764
+ saveManifest(manifest);
3765
+ output(
3766
+ { added, updated, skipped: plan.toSkip.length, failed, total, dir },
3767
+ () => console.log(
3768
+ green("\u2713 \u76EE\u5F55\u6444\u5165\u5B8C\u6210") + dim(
3769
+ `: \u65B0\u589E ${added} / \u66F4\u65B0 ${updated} / \u8DF3\u8FC7 ${plan.toSkip.length} / \u5931\u8D25 ${failed} (\u5171 ${total})`
3770
+ )
3771
+ )
3772
+ );
3773
+ }
3774
+
3627
3775
  // src/commands/ingest.ts
3628
3776
  async function ingest(client, opts, args) {
3777
+ if (opts.dir) return runDirIngestest(client, opts, opts.dir);
3629
3778
  let content = "";
3630
3779
  if (opts.file) {
3631
3780
  content = readFileText(opts.file);
@@ -3946,40 +4095,40 @@ async function whoami(client) {
3946
4095
  }
3947
4096
 
3948
4097
  // src/commands/install.ts
3949
- var fs4 = __toESM(require("fs"));
3950
- var path3 = __toESM(require("path"));
4098
+ var fs6 = __toESM(require("fs"));
4099
+ var path5 = __toESM(require("path"));
3951
4100
  var os2 = __toESM(require("os"));
3952
- var PKG_ROOT = path3.resolve(__dirname, "..");
3953
- var SKILL_SRC = path3.join(PKG_ROOT, "skill", "SKILL.md");
4101
+ var PKG_ROOT = path5.resolve(__dirname, "..");
4102
+ var SKILL_SRC = path5.join(PKG_ROOT, "skill", "SKILL.md");
3954
4103
  function readJson2(file) {
3955
4104
  try {
3956
- return JSON.parse(fs4.readFileSync(file, "utf-8"));
4105
+ return JSON.parse(fs6.readFileSync(file, "utf-8"));
3957
4106
  } catch {
3958
4107
  return null;
3959
4108
  }
3960
4109
  }
3961
4110
  function backup(file) {
3962
- if (!fs4.existsSync(file)) return;
4111
+ if (!fs6.existsSync(file)) return;
3963
4112
  const bak = `${file}.bak-${Date.now()}`;
3964
4113
  try {
3965
- fs4.copyFileSync(file, bak);
3966
- console.log(dim(` (\u5DF2\u5907\u4EFD\u539F\u914D\u7F6E \u2192 ${path3.basename(bak)})`));
4114
+ fs6.copyFileSync(file, bak);
4115
+ console.log(dim(` (\u5DF2\u5907\u4EFD\u539F\u914D\u7F6E \u2192 ${path5.basename(bak)})`));
3967
4116
  } catch {
3968
4117
  }
3969
4118
  }
3970
4119
  async function install(_client, opts) {
3971
- const claudeDir = path3.join(os2.homedir(), ".claude");
3972
- const skillDestDir = path3.join(claudeDir, "skills", "exomind");
3973
- if (!fs4.existsSync(SKILL_SRC)) {
4120
+ const claudeDir = path5.join(os2.homedir(), ".claude");
4121
+ const skillDestDir = path5.join(claudeDir, "skills", "exomind");
4122
+ if (!fs6.existsSync(SKILL_SRC)) {
3974
4123
  throw new Error(`skill \u6E90\u4E0D\u5B58\u5728: ${SKILL_SRC}(npm \u5305\u53EF\u80FD\u635F\u574F,\u6216\u5F00\u53D1\u6A21\u5F0F\u4E0B\u672A\u6784\u5EFA)`);
3975
4124
  }
3976
- fs4.mkdirSync(skillDestDir, { recursive: true });
3977
- fs4.copyFileSync(SKILL_SRC, path3.join(skillDestDir, "SKILL.md"));
4125
+ fs6.mkdirSync(skillDestDir, { recursive: true });
4126
+ fs6.copyFileSync(SKILL_SRC, path5.join(skillDestDir, "SKILL.md"));
3978
4127
  console.log(ok("\u5DF2\u5B89\u88C5 Claude Code skill"));
3979
4128
  console.log(dim(` \u2192 ${skillDestDir}/SKILL.md`));
3980
4129
  if (opts.withHook) {
3981
- const settingsFile = path3.join(claudeDir, "settings.json");
3982
- fs4.mkdirSync(claudeDir, { recursive: true });
4130
+ const settingsFile = path5.join(claudeDir, "settings.json");
4131
+ fs6.mkdirSync(claudeDir, { recursive: true });
3983
4132
  backup(settingsFile);
3984
4133
  const settings = readJson2(settingsFile) ?? {};
3985
4134
  const hooks = settings.hooks ?? {};
@@ -3992,7 +4141,7 @@ async function install(_client, opts) {
3992
4141
  });
3993
4142
  hooks.UserPromptSubmit = kept;
3994
4143
  settings.hooks = hooks;
3995
- fs4.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
4144
+ fs6.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
3996
4145
  console.log(ok("\u5DF2\u914D\u7F6E UserPromptSubmit hook \u2192 exomind hook"));
3997
4146
  console.log(dim(" \u91CD\u542F Claude Code \u751F\u6548\u3002hook \u51FA\u9519\u4E0D\u4F1A\u963B\u585E\u4F60\u7684\u8F93\u5165(\u9519\u8BEF\u53EA\u8FDB stderr)\u3002"));
3998
4147
  }
@@ -4043,7 +4192,7 @@ var program2 = new Command();
4043
4192
  program2.name("exomind").description("ExoMind \u8DE8\u5E73\u53F0\u77E5\u8BC6\u5E93\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");
4044
4193
  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));
4045
4194
  program2.command("whoami").description("\u663E\u793A\u5F53\u524D\u767B\u5F55\u6001\u4E0E\u670D\u52A1\u5668").action(run(whoami));
4046
- program2.command("ingest [content...]").description("\u5BFC\u5165\u77E5\u8BC6: \u53C2\u6570\u6587\u672C / --file / stdin (echo ... | exomind ingest)").option("-t, --title <title>", "\u6807\u9898").option("--tag <tag>", "\u6807\u7B7E(\u53EF\u91CD\u590D)", collect, []).option("--file <path>", "\u4ECE\u6587\u4EF6\u8BFB\u53D6\u5185\u5BB9").action(run(ingest));
4195
+ 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)").action(run(ingest));
4047
4196
  program2.command("query [question...]").description("LLM \u95EE\u7B54").option("--tag <tag>", "\u6807\u7B7E\u8FC7\u6EE4(\u53EF\u91CD\u590D)", collect, []).option("--model <name>", "\u6A21\u578B").action(run(query));
4048
4197
  program2.command("search [keyword...]").description("\u5168\u6587/\u6DF7\u5408/\u7CBE\u6392\u641C\u7D22").option("-l, --limit <n>", "\u8FD4\u56DE\u6570\u91CF", "10").option("--rerank", "LLM \u7CBE\u6392(\u66F4\u51C6\u4F46\u66F4\u6162)").option("--hybrid", "\u6DF7\u5408\u641C\u7D22(BM25+\u8BED\u4E49)").action(run(search));
4049
4198
  program2.command("entity [name...]").description("\u5B9E\u4F53\u8BE6\u60C5 + \u5173\u7CFB").action(run(entity));