@aliyunrds/ctxdb 1.0.2 → 1.0.3

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/main.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_FILE_INGEST_TIMEOUT_MS,
4
+ DEFAULT_FILE_UPLOAD_TIMEOUT_MS,
4
5
  DEFAULT_INGEST_TIMEOUT_MS,
5
6
  compactKbQueryResponse,
6
7
  createKb,
@@ -11,7 +12,7 @@ import {
11
12
  pollIngest,
12
13
  uploadFile,
13
14
  uploadText
14
- } from "../chunk-UH7AJF6F.js";
15
+ } from "../chunk-3PFMHU3C.js";
15
16
  import {
16
17
  DEFAULT_BASE_URL,
17
18
  DEFAULT_USER_ID,
@@ -21,7 +22,10 @@ import {
21
22
  agentFromEnv,
22
23
  agentHomeDir,
23
24
  agentHomeDirs,
25
+ agentPlatformSupport,
24
26
  configuredAgents,
27
+ detectInstalledAgents,
28
+ inspectAgentHomes,
25
29
  isAgentSlug,
26
30
  isBuiltinAgent,
27
31
  isComplete,
@@ -29,7 +33,12 @@ import {
29
33
  removeAgent,
30
34
  save,
31
35
  writeInstalledPkgVersion
32
- } from "../chunk-6FZL67GH.js";
36
+ } from "../chunk-JFTKYEVN.js";
37
+ import {
38
+ beginUpdateNotification,
39
+ completeUpdateNotification,
40
+ runSelfUpdate
41
+ } from "../chunk-BQA7YSXT.js";
33
42
 
34
43
  // src/cli/util.ts
35
44
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -55,6 +64,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
55
64
  "all",
56
65
  // memory delete
57
66
  // kb
67
+ "wait",
68
+ // kb upload-text / upload-file
58
69
  "no-wait",
59
70
  // kb upload-text / upload-file
60
71
  "verbose",
@@ -125,7 +136,12 @@ function isSetupResult(v) {
125
136
  }
126
137
  function isUpgradeResult(v) {
127
138
  if (typeof v !== "object" || v === null) return false;
128
- return "agents" in v && typeof v.agents === "object" && !("steps" in v);
139
+ return "agents" in v && typeof v.agents === "object" && !("steps" in v) && !("mode" in v);
140
+ }
141
+ function isMultiAgentResult(v) {
142
+ if (typeof v !== "object" || v === null) return false;
143
+ const value = v;
144
+ return value.mode === "all" && (value.operation === "setup" || value.operation === "status") && typeof value.agents === "object" && value.agents !== null && Array.isArray(value.skipped) && typeof value.summary === "object" && value.summary !== null;
129
145
  }
130
146
  function isStatusResult(v) {
131
147
  if (typeof v !== "object" || v === null) return false;
@@ -158,6 +174,45 @@ function formatUpgradeResult(r) {
158
174
  }
159
175
  return lines.join("\n");
160
176
  }
177
+ function formatMultiAgentResult(r) {
178
+ const lines = [];
179
+ const { selected, succeeded, failed, skipped } = r.summary;
180
+ lines.push(
181
+ `${r.ok ? CHECK : CROSS} ${BOLD}ctxdb ${r.operation} --all${RESET} ${r.ok ? "completed" : "failed"} ${DIM}(${selected} selected, ${succeeded} succeeded, ${failed} failed, ${skipped} skipped)${RESET}
182
+ `
183
+ );
184
+ for (const [name, result] of Object.entries(r.agents)) {
185
+ const agentOk = r.operation === "setup" ? Boolean(result.ok) : Boolean(result.ok && result.connected);
186
+ lines.push(` ${agentOk ? CHECK : CROSS} ${BOLD}${name}${RESET}`);
187
+ if (r.operation === "setup") {
188
+ for (const step of result.steps ?? []) {
189
+ const detail = step.detail ? ` ${DIM}${step.detail}${RESET}` : "";
190
+ lines.push(` ${step.ok ? CHECK : CROSS} ${step.step}${detail}`);
191
+ }
192
+ for (const hint of result.hints ?? []) {
193
+ lines.push(` ${DIM}hint${RESET} ${hint}`);
194
+ }
195
+ continue;
196
+ }
197
+ lines.push(` ${DIM}config${RESET} ${result.ok ? "complete" : "incomplete"}`);
198
+ lines.push(` ${DIM}connected${RESET} ${result.connected ? "yes" : "no"}`);
199
+ if (result.hooks_node !== void 0) {
200
+ lines.push(` ${DIM}hooks${RESET} ${result.hooks_node}`);
201
+ }
202
+ if (result.ping_error) {
203
+ lines.push(` ${DIM}ping_error${RESET} ${result.ping_error}`);
204
+ }
205
+ }
206
+ if (r.skipped.length > 0) {
207
+ lines.push("");
208
+ lines.push(` ${DIM}skipped${RESET}`);
209
+ for (const item of r.skipped) {
210
+ const detail = item.detail ? ` \u2014 ${item.detail}` : "";
211
+ lines.push(` ${item.agent}: ${item.reason}${detail}`);
212
+ }
213
+ }
214
+ return lines.join("\n");
215
+ }
161
216
  function formatStatusResult(r) {
162
217
  const lines = [];
163
218
  const ok = r.connected ? CHECK : CROSS;
@@ -190,6 +245,8 @@ function formatStatusResult(r) {
190
245
  function printResult(value, json, command) {
191
246
  if (json) {
192
247
  process.stdout.write(JSON.stringify(value, null, 2) + "\n");
248
+ } else if (isMultiAgentResult(value)) {
249
+ process.stdout.write(formatMultiAgentResult(value) + "\n");
193
250
  } else if (isUpgradeResult(value)) {
194
251
  process.stdout.write(formatUpgradeResult(value) + "\n");
195
252
  } else if (isSetupResult(value)) {
@@ -227,6 +284,9 @@ function fail(message, code = 1) {
227
284
  process.exit(code);
228
285
  }
229
286
 
287
+ // src/cli/main.ts
288
+ import { fileURLToPath as fileURLToPath2 } from "url";
289
+
230
290
  // src/setup/installer.ts
231
291
  import {
232
292
  readFileSync,
@@ -248,6 +308,10 @@ import { execFileSync } from "child_process";
248
308
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
249
309
  var UNINSTALL_NPM_HINT = "To also remove the npm binaries: `npm uninstall -g @aliyunrds/ctxdb @aliyunrds/ctxdb-shared`";
250
310
  var UNINSTALL_ORDER_HINT = "When you eventually want to uninstall: run `ctxdb uninstall --purge-all` FIRST, then `npm uninstall -g @aliyunrds/ctxdb @aliyunrds/ctxdb-shared`. (npm 7+ removed uninstall lifecycle hooks, so the order matters \u2014 otherwise agent settings.json hooks + ~/.ctxdb/ residue stay behind.)";
311
+ var HERMES_RESTART_HINT = "Restart any running Hermes CLI or gateway process to load the registered hooks; Hermes registers shell hooks only at process startup.";
312
+ var HERMES_ALLOWLIST_ACTION_HINT = "Next action [hermes-hook-allowlist]: run `hermes --accept-hooks chat`; after Hermes starts, exit the chat and rerun `ctxdb setup --agent hermes`, then restart any running Hermes CLI or gateway process.";
313
+ var HERMES_WEBUI_GATEWAY_HINT = "Using hermes-webui? Its default legacy backend does not load Hermes shell hooks. Set `HERMES_WEBUI_CHAT_BACKEND=gateway` to route chat through Hermes Gateway; if the gateway requires authentication, also set `HERMES_WEBUI_GATEWAY_API_KEY`, then restart hermes-webui. Reference: https://github.com/nesquena/hermes-webui/blob/master/docs/advanced-chat-setup.md#gateway-backed-browser-chat";
314
+ var NO_AGENTS_DETECTED_HINT = "No setup-compatible Agent detected. Install or start a supported Agent once, then rerun `ctxdb setup --all`; use `ctxdb setup --agent <name>` to target one explicitly.";
251
315
  function skillInstallRoot(agent) {
252
316
  switch (agent) {
253
317
  case "qoder":
@@ -579,8 +643,13 @@ async function runSetup(options) {
579
643
  }
580
644
  }
581
645
  }
582
- const ok = steps.every((s) => s.ok);
646
+ const failedSteps = steps.filter((s) => !s.ok);
647
+ const ok = failedSteps.length === 0;
648
+ const actionRequired = agent === "hermes" && failedSteps.length === 1 && failedSteps[0]?.step === "check-hermes-hook-allowlist" ? "hermes-hook-allowlist" : void 0;
583
649
  const hints = ok ? [UNINSTALL_ORDER_HINT] : [];
650
+ if (actionRequired) {
651
+ hints.unshift(HERMES_ALLOWLIST_ACTION_HINT, HERMES_WEBUI_GATEWAY_HINT);
652
+ }
584
653
  if (ok && agent === "opencode") {
585
654
  hints.unshift(
586
655
  `OpenCode plugin shim: ${opencodeShimPath()}`,
@@ -589,10 +658,98 @@ async function runSetup(options) {
589
658
  }
590
659
  if (ok && agent === "hermes") {
591
660
  hints.unshift(
592
- `Hermes shell hooks: ${hermesConfigPath()} \u2192 pre_llm_call + post_llm_call`
661
+ `Hermes shell hooks: ${hermesConfigPath()} \u2192 pre_llm_call + post_llm_call`,
662
+ HERMES_RESTART_HINT,
663
+ HERMES_WEBUI_GATEWAY_HINT
593
664
  );
594
665
  }
595
- return { ok, steps, hints: hints.length > 0 ? hints : void 0 };
666
+ return {
667
+ ok,
668
+ steps,
669
+ actionRequired,
670
+ hints: hints.length > 0 ? hints : void 0
671
+ };
672
+ }
673
+ function skippedFromDetection(detection) {
674
+ return detection.observations.filter((item) => !item.selected).map((item) => ({
675
+ agent: item.agent,
676
+ reason: item.reason,
677
+ evidence: item.evidence,
678
+ ...item.detail ? { detail: item.detail } : {}
679
+ }));
680
+ }
681
+ function withoutSharedUninstallHint(result) {
682
+ const hints = result.hints?.filter((hint) => hint !== UNINSTALL_ORDER_HINT);
683
+ return {
684
+ ...result,
685
+ ...hints && hints.length > 0 ? { hints } : { hints: void 0 }
686
+ };
687
+ }
688
+ function missingApiKeyResult(agent) {
689
+ return {
690
+ ok: false,
691
+ steps: [{
692
+ step: "write-config",
693
+ ok: false,
694
+ detail: `${agent}: --api-key is required (won't write config without a valid API key)`
695
+ }]
696
+ };
697
+ }
698
+ async function runSetupAll(options = {}, dependencies = {}) {
699
+ const detect = dependencies.detect ?? detectInstalledAgents;
700
+ const loadConfig = dependencies.loadConfig ?? ((agent) => load({ agent }));
701
+ const setup2 = dependencies.setup ?? runSetup;
702
+ const detection = detect(options.detection);
703
+ const agents = {};
704
+ if (detection.selected.length === 0) {
705
+ return {
706
+ ok: false,
707
+ operation: "setup",
708
+ mode: "all",
709
+ agents,
710
+ skipped: skippedFromDetection(detection),
711
+ summary: { selected: 0, succeeded: 0, failed: 0, skipped: detection.observations.length },
712
+ hints: [NO_AGENTS_DETECTED_HINT]
713
+ };
714
+ }
715
+ const configSnapshot = /* @__PURE__ */ new Map();
716
+ for (const agent of detection.selected) {
717
+ configSnapshot.set(agent, loadConfig(agent));
718
+ }
719
+ for (const agent of detection.selected) {
720
+ const existing = configSnapshot.get(agent);
721
+ const apiKey = options.apiKey ?? existing.apiKey;
722
+ if (!apiKey) {
723
+ agents[agent] = missingApiKeyResult(agent);
724
+ continue;
725
+ }
726
+ agents[agent] = withoutSharedUninstallHint(await setup2({
727
+ agent,
728
+ apiKey,
729
+ baseUrl: options.baseUrl ?? existing.baseUrl,
730
+ userId: options.userId ?? existing.userId,
731
+ installSkill: options.installSkill,
732
+ validate: options.validate
733
+ }));
734
+ }
735
+ const results = Object.values(agents);
736
+ const succeeded = results.filter((result) => result.ok).length;
737
+ const failed = results.length - succeeded;
738
+ const skipped = skippedFromDetection(detection);
739
+ return {
740
+ ok: failed === 0,
741
+ operation: "setup",
742
+ mode: "all",
743
+ agents,
744
+ skipped,
745
+ summary: {
746
+ selected: results.length,
747
+ succeeded,
748
+ failed,
749
+ skipped: skipped.length
750
+ },
751
+ hints: [UNINSTALL_ORDER_HINT]
752
+ };
596
753
  }
597
754
  function runRemove(agent, options = {}) {
598
755
  const steps = [];
@@ -794,11 +951,12 @@ function runRemove(agent, options = {}) {
794
951
  return { ok: steps.every((s) => s.ok), steps };
795
952
  }
796
953
  function checkAgentHome(agent) {
797
- if (agent === "hermes" && process.platform === "win32") {
954
+ const platform = agentPlatformSupport(agent);
955
+ if (!platform.supported) {
798
956
  return {
799
957
  step: "check-agent-home",
800
958
  ok: false,
801
- detail: "hermes setup currently supports macOS/Linux only; Windows Hermes will be handled separately"
959
+ detail: platform.detail
802
960
  };
803
961
  }
804
962
  if (agent === "opencode" || agent === "hermes") {
@@ -814,7 +972,7 @@ function checkAgentHome(agent) {
814
972
  };
815
973
  }
816
974
  }
817
- const dirs = agentHomeDirs(agent).filter((d) => existsSync(d));
975
+ const dirs = inspectAgentHomes(agent).existing;
818
976
  if (dirs.length === 0) {
819
977
  const primary = agentHomeDir(agent);
820
978
  return {
@@ -845,23 +1003,27 @@ function runTeardown(options = {}) {
845
1003
  }
846
1004
  if (purgeConfig) {
847
1005
  const cfgPath = join(homedir(), ".ctxdb", "ctxdb.json");
848
- if (existsSync(cfgPath)) {
849
- try {
850
- unlinkSync(cfgPath);
851
- steps.push({ step: "purge-config", ok: true, detail: cfgPath });
852
- } catch (err) {
853
- steps.push({
854
- step: "purge-config",
855
- ok: false,
856
- detail: `${cfgPath}: ${err?.message ?? String(err)}`
857
- });
858
- }
859
- } else {
1006
+ const ownedConfigFiles = [cfgPath, `${cfgPath}.bak`];
1007
+ const existing = ownedConfigFiles.filter((path) => existsSync(path));
1008
+ if (existing.length === 0) {
860
1009
  steps.push({
861
1010
  step: "purge-config",
862
1011
  ok: true,
863
1012
  detail: `nothing to remove (${cfgPath})`
864
1013
  });
1014
+ } else {
1015
+ for (const path of existing) {
1016
+ try {
1017
+ unlinkSync(path);
1018
+ steps.push({ step: "purge-config", ok: true, detail: path });
1019
+ } catch (err) {
1020
+ steps.push({
1021
+ step: "purge-config",
1022
+ ok: false,
1023
+ detail: `${path}: ${err?.message ?? String(err)}`
1024
+ });
1025
+ }
1026
+ }
865
1027
  }
866
1028
  }
867
1029
  if (purgeLogs) {
@@ -1357,7 +1519,7 @@ function hermesAllowlistPath() {
1357
1519
  }
1358
1520
  function hermesHookApprovalHint(events = HERMES_HOOK_EVENTS) {
1359
1521
  const hookNames = events.map((event) => `\`${event}\``).join(" and ");
1360
- return `Approve the Hermes ctxdb hooks ${hookNames}: run \`hermes --accept-hooks chat\` once. This command opens an interactive chat; after Hermes starts, exit the chat, then rerun \`ctxdb setup --agent hermes\`.`;
1522
+ return `Approve the Hermes ctxdb hooks ${hookNames}: run \`hermes --accept-hooks chat\` once. This command opens an interactive chat; after Hermes starts, exit the chat, rerun \`ctxdb setup --agent hermes\`, then restart any running Hermes CLI or gateway process.`;
1361
1523
  }
1362
1524
  function backupHermesConfig() {
1363
1525
  backupSettingsFile(hermesConfigPath());
@@ -1806,8 +1968,17 @@ async function runUpgrade(options = {}) {
1806
1968
  }
1807
1969
 
1808
1970
  // src/cli/top.ts
1809
- async function status(args) {
1810
- const agent = agentFromFlags(args.flags);
1971
+ var STATUS_HELP = `ctxdb status \u2014 inspect configuration and connectivity
1972
+
1973
+ USAGE
1974
+ ctxdb status [--agent <qoder|qoderwork|codex|claude|opencode|hermes|default> | --all] [--json]
1975
+
1976
+ FLAGS
1977
+ --agent <name> Inspect exactly one Agent profile
1978
+ --all Inspect every setup-compatible detected built-in Agent
1979
+ --json Machine-readable output
1980
+ `;
1981
+ async function inspectStatus(agent) {
1811
1982
  const cfg = load({ agent });
1812
1983
  const complete = isComplete(cfg);
1813
1984
  let connected = false;
@@ -1822,8 +1993,8 @@ async function status(args) {
1822
1993
  }
1823
1994
  }
1824
1995
  const hookHealth = isBuiltinAgent(agent) ? checkHookNodePaths(agent) : { installed: false, nodePaths: [], nodeOk: true, detail: "no hooks (default agent)" };
1825
- printResult(
1826
- {
1996
+ return {
1997
+ result: {
1827
1998
  ok: complete,
1828
1999
  agent,
1829
2000
  base_url: cfg.baseUrl,
@@ -1840,9 +2011,69 @@ async function status(args) {
1840
2011
  hooks_node_ok: hookHealth.nodeOk,
1841
2012
  version: PACKAGE_VERSION
1842
2013
  },
1843
- !!args.flags.json
1844
- );
1845
- return complete && connected ? 0 : 1;
2014
+ success: complete && connected
2015
+ };
2016
+ }
2017
+ function statusSkips(detection) {
2018
+ return {
2019
+ selected: detection.selected,
2020
+ skipped: detection.observations.filter((item) => !item.selected).map((item) => ({
2021
+ agent: item.agent,
2022
+ reason: item.reason,
2023
+ evidence: item.evidence,
2024
+ ...item.detail ? { detail: item.detail } : {}
2025
+ }))
2026
+ };
2027
+ }
2028
+ async function statusAll(dependencies = {}) {
2029
+ const detection = statusSkips((dependencies.detect ?? detectInstalledAgents)());
2030
+ const inspect = dependencies.inspect ?? inspectStatus;
2031
+ const agents = {};
2032
+ let succeeded = 0;
2033
+ for (const agent of detection.selected) {
2034
+ const inspection = await inspect(agent);
2035
+ agents[agent] = inspection.result;
2036
+ if (inspection.success) succeeded += 1;
2037
+ }
2038
+ const failed = detection.selected.length - succeeded;
2039
+ return {
2040
+ ok: detection.selected.length > 0 && failed === 0,
2041
+ operation: "status",
2042
+ mode: "all",
2043
+ agents,
2044
+ skipped: detection.skipped,
2045
+ summary: {
2046
+ selected: detection.selected.length,
2047
+ succeeded,
2048
+ failed,
2049
+ skipped: detection.skipped.length
2050
+ },
2051
+ ...detection.selected.length === 0 ? { hints: ["No setup-compatible Agent detected. Install or start one, then rerun `ctxdb status --all`."] } : {}
2052
+ };
2053
+ }
2054
+ async function status(args) {
2055
+ if (args.flags.help || args.flags.h) {
2056
+ process.stdout.write(STATUS_HELP);
2057
+ return 0;
2058
+ }
2059
+ if (args.flags.all === true) {
2060
+ if (args.flags.agent !== void 0) {
2061
+ process.stderr.write("cannot use --all and --agent together\n");
2062
+ return 2;
2063
+ }
2064
+ const result = await statusAll();
2065
+ printResult(result, !!args.flags.json);
2066
+ if (!args.flags.json && result.hints) {
2067
+ for (const hint of result.hints) process.stderr.write(`
2068
+ ${hint}
2069
+ `);
2070
+ }
2071
+ return result.ok ? 0 : 1;
2072
+ }
2073
+ const agent = agentFromFlags(args.flags);
2074
+ const inspection = await inspectStatus(agent);
2075
+ printResult(inspection.result, !!args.flags.json);
2076
+ return inspection.success ? 0 : 1;
1846
2077
  }
1847
2078
  async function ping(args) {
1848
2079
  const agent = agentFromFlags(args.flags);
@@ -1862,7 +2093,7 @@ async function ping(args) {
1862
2093
  var HELP = `ctxdb setup \u2014 configure an agent
1863
2094
 
1864
2095
  USAGE
1865
- ctxdb setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes>]
2096
+ ctxdb setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes> | --all]
1866
2097
  [--api-key=K] [--base-url=URL] [--user-id=ID]
1867
2098
  [--no-install-skill] [--no-validate] [--json]
1868
2099
 
@@ -1871,13 +2102,20 @@ BEHAVIOR
1871
2102
  no hooks or skills installed).
1872
2103
  With --agent Writes agent config + installs hooks + skills for the
1873
2104
  specified agent harness.
2105
+ Rerun for a new Agent, to repair integration files, or
2106
+ with --api-key <new-key> to replace that Agent's key.
2107
+ With --all Detects setup-compatible Agents on this host and runs the
2108
+ same setup lifecycle sequentially for each one. Successful
2109
+ Agents are preserved when another Agent fails.
1874
2110
  Hermes Also verifies ~/.hermes/shell-hooks-allowlist.json.
1875
2111
  Approve pre_llm_call + post_llm_call with
1876
2112
  \`hermes --accept-hooks chat\`. It opens interactive chat;
1877
- exit after Hermes starts, then rerun ctxdb setup.
2113
+ exit after Hermes starts, rerun ctxdb setup, then restart
2114
+ any running Hermes CLI/gateway process to load the hooks.
1878
2115
 
1879
2116
  FLAGS
1880
2117
  --agent <name> Target agent (qoder|qoderwork|codex|claude|opencode|hermes)
2118
+ --all Target every setup-compatible detected Agent
1881
2119
  --api-key <key> API key (required on first setup)
1882
2120
  --base-url <url> Server URL (default: https://context-database.aliyuncs.com)
1883
2121
  --user-id <id> User bucket (default: "default")
@@ -1885,6 +2123,10 @@ FLAGS
1885
2123
  --no-validate Skip connectivity ping after setup
1886
2124
  --json Machine-readable JSON output
1887
2125
  `;
2126
+ var CODEX_HOOKS_TRUST_REMINDER = [
2127
+ "codex hooks \u914D\u7F6E\u540E\uFF0C\u9700\u8981\u91CD\u542F Codex \u5BF9\u8BDD\uFF0C\u5728\u5F39\u51FA\u7684 hooks \u786E\u8BA4\u754C\u9762\u9009\u62E9\u63A5\u6536\u6240\u6709 hooks(\u94A9\u5B50)\u9009\u9879\u3002",
2128
+ "\u5982\u679C\u4F7F\u7528\u7684\u662F Codex App \u684C\u9762\u7248\uFF0C\u64CD\u4F5C\u8DEF\u5F84\u662F\uFF1A\u5DE6\u4E0B\u89D2\u70B9\u51FB\u7528\u6237\u540D \u2192 \u70B9\u51FB\u8BBE\u7F6E \u2192 \u70B9\u51FB\u4FA7\u8FB9\u680F\u7684 Hooks\uFF08\u94A9\u5B50\uFF09\u2192 \u70B9\u51FB\u7528\u6237\u914D\u7F6E\uFF0C\u4FE1\u4EFB UserPromptSubmit \u548C SessionStart \u91CC\u7684 aliyunrds/ctxdb \u94A9\u5B50\u3002"
2129
+ ].join("\n");
1888
2130
  function parseAgent(args) {
1889
2131
  const raw = args.flags.agent;
1890
2132
  if (raw === void 0) return "default";
@@ -1900,8 +2142,36 @@ async function setup(args) {
1900
2142
  process.stdout.write(HELP);
1901
2143
  return 0;
1902
2144
  }
1903
- const agent = parseAgent(args);
1904
2145
  const json = !!args.flags.json;
2146
+ const all = args.flags.all === true;
2147
+ if (all && args.flags.agent !== void 0) {
2148
+ process.stderr.write("cannot use --all and --agent together\n");
2149
+ return 2;
2150
+ }
2151
+ if (all) {
2152
+ const result2 = await runSetupAll({
2153
+ apiKey: typeof args.flags["api-key"] === "string" ? args.flags["api-key"] : void 0,
2154
+ baseUrl: typeof args.flags["base-url"] === "string" ? args.flags["base-url"] : void 0,
2155
+ userId: typeof args.flags["user-id"] === "string" ? args.flags["user-id"] : void 0,
2156
+ installSkill: !args.flags["no-install-skill"],
2157
+ validate: !args.flags["no-validate"]
2158
+ });
2159
+ printResult(result2, json);
2160
+ if (!json && result2.hints) {
2161
+ for (const hint of result2.hints) process.stderr.write(`
2162
+ ${hint}
2163
+ `);
2164
+ }
2165
+ if (!json && result2.agents.codex?.ok) {
2166
+ process.stderr.write(
2167
+ `
2168
+ ${dim(CODEX_HOOKS_TRUST_REMINDER)}
2169
+ `
2170
+ );
2171
+ }
2172
+ return result2.ok ? 0 : 1;
2173
+ }
2174
+ const agent = parseAgent(args);
1905
2175
  if (agent === "default" && !json && !configuredAgents().includes("default")) {
1906
2176
  process.stderr.write(
1907
2177
  `hint: creating CLI-only "default" agent (no hooks/skills). Use --agent <claude|qoder|qoderwork|codex|opencode|hermes> for full setup.
@@ -1925,7 +2195,7 @@ ${h}
1925
2195
  if (!json && agent === "codex" && result.ok) {
1926
2196
  process.stderr.write(
1927
2197
  `
1928
- ${dim("codex hooks \u914D\u7F6E\u540E\uFF0C\u9700\u8981\u91CD\u542F Codex \u5BF9\u8BDD\uFF0C\u5728\u5F39\u51FA\u7684 hooks \u786E\u8BA4\u754C\u9762\u9009\u62E9\u63A5\u6536\u6240\u6709 hooks(\u94A9\u5B50)\u9009\u9879")}
2198
+ ${dim(CODEX_HOOKS_TRUST_REMINDER)}
1929
2199
  `
1930
2200
  );
1931
2201
  }
@@ -1996,118 +2266,6 @@ ${h}
1996
2266
  return result.ok ? 0 : 1;
1997
2267
  }
1998
2268
 
1999
- // src/lib/self-update.ts
2000
- import { spawnSync } from "child_process";
2001
- import { fileURLToPath as fileURLToPath2 } from "url";
2002
- import { dirname as dirname2 } from "path";
2003
- import semver from "semver";
2004
- var PACKAGE_NAME = "@aliyunrds/ctxdb";
2005
- var NPM_VIEW_TIMEOUT_MS = 1e4;
2006
- function npmCommand(platform = process.platform) {
2007
- return platform === "win32" ? "npm.cmd" : "npm";
2008
- }
2009
- function ctxdbCommand(platform = process.platform) {
2010
- return platform === "win32" ? "ctxdb.cmd" : "ctxdb";
2011
- }
2012
- function detectInstallMethod() {
2013
- const dir = dirname2(fileURLToPath2(import.meta.url));
2014
- return dir.includes("/node_modules/") || dir.includes("\\node_modules\\") ? "npm" : "unknown";
2015
- }
2016
- function checkLatestVersion(currentVersion) {
2017
- const result = spawnSync(npmCommand(), ["view", PACKAGE_NAME, "version"], {
2018
- encoding: "utf-8",
2019
- timeout: NPM_VIEW_TIMEOUT_MS,
2020
- stdio: ["ignore", "pipe", "pipe"],
2021
- shell: process.platform === "win32"
2022
- });
2023
- if (result.status !== 0 || !result.stdout?.trim()) {
2024
- return {
2025
- latest: null,
2026
- current: currentVersion,
2027
- isNewer: false,
2028
- error: spawnFailureMessage(result, "npm view")
2029
- };
2030
- }
2031
- const latest = result.stdout.trim();
2032
- return {
2033
- latest,
2034
- current: currentVersion,
2035
- isNewer: isNewerVersion(latest, currentVersion)
2036
- };
2037
- }
2038
- function spawnFailureMessage(result, commandLabel) {
2039
- const stderr = result.stderr?.toString().trim();
2040
- if (stderr) return stderr;
2041
- if (result.error) return `${commandLabel} failed: ${result.error.message}`;
2042
- if (result.signal) return `${commandLabel} terminated by signal ${result.signal}`;
2043
- return `${commandLabel} exited with code ${result.status}`;
2044
- }
2045
- function isNewerVersion(candidate, current) {
2046
- const candidateVersion = semver.valid(candidate);
2047
- const currentVersion = semver.valid(current);
2048
- return Boolean(
2049
- candidateVersion && currentVersion && semver.gt(candidateVersion, currentVersion)
2050
- );
2051
- }
2052
- function runSelfUpdate(currentVersion, passthroughArgs = [], options = {}) {
2053
- const method = options.installMethod ?? detectInstallMethod();
2054
- if (method !== "npm") {
2055
- return {
2056
- ok: false,
2057
- updated: false,
2058
- fromVersion: currentVersion,
2059
- error: "ctxdb was not installed via npm. Update manually with your package manager."
2060
- };
2061
- }
2062
- const check = checkLatestVersion(currentVersion);
2063
- if (check.error) {
2064
- return {
2065
- ok: false,
2066
- updated: false,
2067
- fromVersion: currentVersion,
2068
- error: `npm view failed: ${check.error}`
2069
- };
2070
- }
2071
- if (!check.isNewer) {
2072
- process.stderr.write(
2073
- `ctxdb: package already up to date (current v${currentVersion}, latest v${check.latest})
2074
- `
2075
- );
2076
- return { ok: true, updated: false, fromVersion: currentVersion };
2077
- }
2078
- process.stderr.write(`ctxdb: updating ${PACKAGE_NAME} v${currentVersion} \u2192 v${check.latest}...
2079
- `);
2080
- const install = spawnSync(npmCommand(), ["install", "-g", `${PACKAGE_NAME}@${check.latest}`], {
2081
- stdio: "inherit",
2082
- encoding: "utf-8",
2083
- shell: process.platform === "win32"
2084
- });
2085
- if (install.status !== 0) {
2086
- return {
2087
- ok: false,
2088
- updated: false,
2089
- fromVersion: currentVersion,
2090
- error: spawnFailureMessage(install, "npm install -g")
2091
- };
2092
- }
2093
- process.stderr.write(`ctxdb: package updated to v${check.latest}, running setup...
2094
- `);
2095
- const setupArgs = ["update", "--no-self-update", ...passthroughArgs];
2096
- const refresh = spawnSync(ctxdbCommand(), setupArgs, {
2097
- stdio: "inherit",
2098
- encoding: "utf-8",
2099
- shell: process.platform === "win32"
2100
- });
2101
- return {
2102
- ok: refresh.status === 0,
2103
- updated: true,
2104
- fromVersion: currentVersion,
2105
- toVersion: check.latest,
2106
- error: refresh.status !== 0 ? spawnFailureMessage(refresh, "ctxdb setup") : void 0,
2107
- failureHandled: refresh.status !== null && refresh.status !== 0
2108
- };
2109
- }
2110
-
2111
2269
  // src/cli/upgrade.ts
2112
2270
  var HELP3 = `ctxdb upgrade \u2014 refresh skills + hooks
2113
2271
 
@@ -2396,18 +2554,28 @@ function parseMetadataFlags(raw) {
2396
2554
  }
2397
2555
 
2398
2556
  // src/cli/kb-cli.ts
2557
+ var UPLOAD_TEXT_USAGE = "usage: ctxdb kb upload-text <kb-name> <doc-name> --text=<body> [--file-path=<server-logical-path>] [--wait|--no-wait]";
2558
+ var UPLOAD_FILE_USAGE = "usage: ctxdb kb upload-file <kb-name> <local-path> [--doc-name=...] [--file-path=<server-logical-path>] [--wait|--no-wait]";
2559
+ function shouldWaitForIngest(args, usage) {
2560
+ const waitRequested = args.flags.wait !== void 0;
2561
+ const noWaitRequested = args.flags["no-wait"] !== void 0;
2562
+ if (waitRequested && noWaitRequested) {
2563
+ fail(`cannot use --wait and --no-wait together
2564
+ ${usage}`, 2);
2565
+ }
2566
+ return waitRequested;
2567
+ }
2399
2568
  async function kbUploadText(args) {
2569
+ const shouldWait = shouldWaitForIngest(args, UPLOAD_TEXT_USAGE);
2400
2570
  const [kbName, docName] = args.positional;
2401
2571
  const text = args.flags["text"];
2402
2572
  if (!kbName || !docName || typeof text !== "string") {
2403
- fail(
2404
- "usage: ctxdb kb upload-text <kb-name> <doc-name> --text=<body> [--file-path=<server-logical-path>] [--no-wait]"
2405
- );
2573
+ fail(UPLOAD_TEXT_USAGE);
2406
2574
  }
2407
2575
  const ctx = buildContext(args);
2408
2576
  const filePath = typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0;
2409
2577
  const doc = await uploadText(ctx.client, kbName, docName, text, "text/plain", filePath);
2410
- if (args.flags["no-wait"]) {
2578
+ if (!shouldWait) {
2411
2579
  printResult({ document: doc }, !!args.flags.json);
2412
2580
  return 0;
2413
2581
  }
@@ -2418,19 +2586,18 @@ async function kbUploadText(args) {
2418
2586
  return 0;
2419
2587
  }
2420
2588
  async function kbUploadFile(args) {
2589
+ const shouldWait = shouldWaitForIngest(args, UPLOAD_FILE_USAGE);
2421
2590
  const [kbName, localPath] = args.positional;
2422
2591
  if (!kbName || !localPath) {
2423
- fail(
2424
- "usage: ctxdb kb upload-file <kb-name> <local-path> [--doc-name=...] [--file-path=<server-logical-path>] [--no-wait]"
2425
- );
2592
+ fail(UPLOAD_FILE_USAGE);
2426
2593
  }
2427
2594
  const ctx = buildContext(args);
2428
2595
  const doc = await uploadFile(ctx.client, kbName, localPath, {
2429
2596
  docName: typeof args.flags["doc-name"] === "string" ? args.flags["doc-name"] : void 0,
2430
2597
  filePath: typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0,
2431
- timeoutMs: DEFAULT_FILE_INGEST_TIMEOUT_MS
2598
+ timeoutMs: DEFAULT_FILE_UPLOAD_TIMEOUT_MS
2432
2599
  });
2433
- if (args.flags["no-wait"]) {
2600
+ if (!shouldWait) {
2434
2601
  printResult({ document: doc }, !!args.flags.json);
2435
2602
  return 0;
2436
2603
  }
@@ -2510,7 +2677,7 @@ USAGE
2510
2677
  ctxdb <command> [args] [--flags]
2511
2678
 
2512
2679
  COMMANDS
2513
- setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes>]
2680
+ setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes> | --all]
2514
2681
  [--api-key=K] [--base-url=URL] [--user-id=ID]
2515
2682
  [--no-install-skill] [--no-validate] [--json]
2516
2683
  Without --agent \u2192 writes agents.default (CLI-only, no hooks/skills)
@@ -2521,8 +2688,12 @@ COMMANDS
2521
2688
  OpenCode \u2192 writes agents.opencode config + ~/.config/opencode/plugins/ctxdb.ts shim + skill
2522
2689
  Hermes \u2192 writes agents.hermes config + ~/.hermes/config.yaml pre/post_llm_call hooks + skill
2523
2690
  Approve pre_llm_call + post_llm_call with \`hermes --accept-hooks chat\`
2524
- (opens interactive chat; exit after Hermes starts)
2525
- status [--agent <name>] [--json]
2691
+ (exit chat, rerun setup, then restart any running Hermes CLI/gateway)
2692
+ --all \u2192 detects and configures every setup-compatible built-in Agent
2693
+ status [--agent <name> | --all] [--json]
2694
+ Inspect configuration, connectivity, and integration health.
2695
+ --all inspects the same detected Agent set as setup --all.
2696
+ Rerun setup to add/repair an Agent or replace its API key.
2526
2697
  ping [--agent <name>] [--json]
2527
2698
  uninstall [--agent <name>] [--purge-config] [--purge-logs] [--purge-all] [--json]
2528
2699
  teardown (alias for uninstall)
@@ -2551,9 +2722,12 @@ COMMANDS
2551
2722
 
2552
2723
  kb create <kb-name> [--description=<desc>] [--agent=<name>]
2553
2724
  kb upload-text <kb-name> <doc-name> --text=<body> [--agent=<name>]
2554
- [--file-path=<server-logical-path>] [--no-wait]
2725
+ [--file-path=<server-logical-path>] [--wait|--no-wait]
2555
2726
  kb upload-file <kb-name> <local-path> [--agent=<name>] [--doc-name=...]
2556
- [--file-path=<server-logical-path>] [--no-wait]
2727
+ [--file-path=<server-logical-path>] [--wait|--no-wait]
2728
+ Uploads return immediately by default.
2729
+ --wait polls for terminal ingest status.
2730
+ --no-wait is a compatibility alias for the default.
2557
2731
  kb list [--agent=<name>]
2558
2732
  kb documents-list <kb-name> [--agent=<name>]
2559
2733
  kb document-get <kb-name> <doc-id> [--agent=<name>]
@@ -2580,6 +2754,10 @@ ENV VARS (override selected ~/.ctxdb/ctxdb.json agent config):
2580
2754
  CTXDB_AGENT CTXDB_API_KEY CTXDB_BASE_URL CTXDB_USER_ID
2581
2755
  CTXDB_AGENT_ID CTXDB_APP_ID
2582
2756
 
2757
+ UPDATE CHECK ENV VARS:
2758
+ CTXDB_DISABLE_UPDATE_CHECK=1
2759
+ Disable background version checks and hints.
2760
+
2583
2761
  HOOK ENV VARS:
2584
2762
  CTXDB_SKIP_HOOKS=TRUE Hook/plugin entrypoints exit immediately.
2585
2763
  Direct ctxdb CLI commands are unchanged.
@@ -2616,7 +2794,7 @@ var ROUTES = {
2616
2794
  search: kbSearch
2617
2795
  }
2618
2796
  };
2619
- async function main(argv = process.argv.slice(2)) {
2797
+ async function dispatch(argv) {
2620
2798
  if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
2621
2799
  process.stdout.write(HELP5);
2622
2800
  return 0;
@@ -2660,6 +2838,15 @@ async function main(argv = process.argv.slice(2)) {
2660
2838
  }
2661
2839
  return Promise.resolve(handler(parseArgs(subRest)));
2662
2840
  }
2841
+ async function main(argv = process.argv.slice(2)) {
2842
+ const notification = beginUpdateNotification(argv, {
2843
+ currentVersion: PACKAGE_VERSION,
2844
+ workerPath: fileURLToPath2(new URL("../workers/version-check.js", import.meta.url))
2845
+ });
2846
+ const code = await dispatch(argv);
2847
+ completeUpdateNotification(notification, code);
2848
+ return code;
2849
+ }
2663
2850
  main().then(
2664
2851
  (code) => {
2665
2852
  if (typeof code === "number") process.exitCode = code;