@aliyunrds/ctxdb 1.0.2 → 1.0.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/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",
@@ -116,8 +127,14 @@ var BOLD = "\x1B[1m";
116
127
  var RESET = "\x1B[0m";
117
128
  var CHECK = `${GREEN}\u2714${RESET}`;
118
129
  var CROSS = `${RED}\u2718${RESET}`;
130
+ function highlightInlineCode(text, surroundingStyle = "") {
131
+ return text.replace(
132
+ /`([^`\n]+)`/g,
133
+ (_match, content) => `${RESET}${BOLD}${content}${RESET}${surroundingStyle}`
134
+ );
135
+ }
119
136
  function dim(text) {
120
- return `${DIM}${text}${RESET}`;
137
+ return `${DIM}${highlightInlineCode(text, DIM)}${RESET}`;
121
138
  }
122
139
  function isSetupResult(v) {
123
140
  if (typeof v !== "object" || v === null) return false;
@@ -125,7 +142,12 @@ function isSetupResult(v) {
125
142
  }
126
143
  function isUpgradeResult(v) {
127
144
  if (typeof v !== "object" || v === null) return false;
128
- return "agents" in v && typeof v.agents === "object" && !("steps" in v);
145
+ return "agents" in v && typeof v.agents === "object" && !("steps" in v) && !("mode" in v);
146
+ }
147
+ function isMultiAgentResult(v) {
148
+ if (typeof v !== "object" || v === null) return false;
149
+ const value = v;
150
+ 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
151
  }
130
152
  function isStatusResult(v) {
131
153
  if (typeof v !== "object" || v === null) return false;
@@ -137,7 +159,7 @@ function formatSetupResult(r, command = "setup") {
137
159
  `);
138
160
  for (const s of r.steps) {
139
161
  const icon = s.ok ? CHECK : CROSS;
140
- const detail = s.detail ? ` ${DIM}${s.detail}${RESET}` : "";
162
+ const detail = s.detail ? ` ${dim(s.detail)}` : "";
141
163
  lines.push(` ${icon} ${s.step}${detail}`);
142
164
  }
143
165
  return lines.join("\n");
@@ -152,12 +174,51 @@ function formatUpgradeResult(r) {
152
174
  lines.push(` ${icon} ${BOLD}${name}${RESET}`);
153
175
  for (const s of result.steps) {
154
176
  const sIcon = s.ok ? CHECK : CROSS;
155
- const detail = s.detail ? ` ${DIM}${s.detail}${RESET}` : "";
177
+ const detail = s.detail ? ` ${dim(s.detail)}` : "";
156
178
  lines.push(` ${sIcon} ${s.step}${detail}`);
157
179
  }
158
180
  }
159
181
  return lines.join("\n");
160
182
  }
183
+ function formatMultiAgentResult(r) {
184
+ const lines = [];
185
+ const { selected, succeeded, failed, skipped } = r.summary;
186
+ lines.push(
187
+ `${r.ok ? CHECK : CROSS} ${BOLD}ctxdb ${r.operation} --all${RESET} ${r.ok ? "completed" : "failed"} ${DIM}(${selected} selected, ${succeeded} succeeded, ${failed} failed, ${skipped} skipped)${RESET}
188
+ `
189
+ );
190
+ for (const [name, result] of Object.entries(r.agents)) {
191
+ const agentOk = r.operation === "setup" ? Boolean(result.ok) : Boolean(result.ok && result.connected);
192
+ lines.push(` ${agentOk ? CHECK : CROSS} ${BOLD}${name}${RESET}`);
193
+ if (r.operation === "setup") {
194
+ for (const step of result.steps ?? []) {
195
+ const detail = step.detail ? ` ${dim(step.detail)}` : "";
196
+ lines.push(` ${step.ok ? CHECK : CROSS} ${step.step}${detail}`);
197
+ }
198
+ for (const hint of result.hints ?? []) {
199
+ lines.push(` ${DIM}hint${RESET} ${highlightInlineCode(hint)}`);
200
+ }
201
+ continue;
202
+ }
203
+ lines.push(` ${DIM}config${RESET} ${result.ok ? "complete" : "incomplete"}`);
204
+ lines.push(` ${DIM}connected${RESET} ${result.connected ? "yes" : "no"}`);
205
+ if (result.hooks_node !== void 0) {
206
+ lines.push(` ${DIM}hooks${RESET} ${result.hooks_node}`);
207
+ }
208
+ if (result.ping_error) {
209
+ lines.push(` ${DIM}ping_error${RESET} ${result.ping_error}`);
210
+ }
211
+ }
212
+ if (r.skipped.length > 0) {
213
+ lines.push("");
214
+ lines.push(` ${DIM}skipped${RESET}`);
215
+ for (const item of r.skipped) {
216
+ const detail = item.detail ? ` \u2014 ${item.detail}` : "";
217
+ lines.push(` ${item.agent}: ${item.reason}${detail}`);
218
+ }
219
+ }
220
+ return lines.join("\n");
221
+ }
161
222
  function formatStatusResult(r) {
162
223
  const lines = [];
163
224
  const ok = r.connected ? CHECK : CROSS;
@@ -190,6 +251,8 @@ function formatStatusResult(r) {
190
251
  function printResult(value, json, command) {
191
252
  if (json) {
192
253
  process.stdout.write(JSON.stringify(value, null, 2) + "\n");
254
+ } else if (isMultiAgentResult(value)) {
255
+ process.stdout.write(formatMultiAgentResult(value) + "\n");
193
256
  } else if (isUpgradeResult(value)) {
194
257
  process.stdout.write(formatUpgradeResult(value) + "\n");
195
258
  } else if (isSetupResult(value)) {
@@ -227,6 +290,9 @@ function fail(message, code = 1) {
227
290
  process.exit(code);
228
291
  }
229
292
 
293
+ // src/cli/main.ts
294
+ import { fileURLToPath as fileURLToPath2 } from "url";
295
+
230
296
  // src/setup/installer.ts
231
297
  import {
232
298
  readFileSync,
@@ -248,6 +314,10 @@ import { execFileSync } from "child_process";
248
314
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
249
315
  var UNINSTALL_NPM_HINT = "To also remove the npm binaries: `npm uninstall -g @aliyunrds/ctxdb @aliyunrds/ctxdb-shared`";
250
316
  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.)";
317
+ 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.";
318
+ 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.";
319
+ 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";
320
+ 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
321
  function skillInstallRoot(agent) {
252
322
  switch (agent) {
253
323
  case "qoder":
@@ -579,8 +649,13 @@ async function runSetup(options) {
579
649
  }
580
650
  }
581
651
  }
582
- const ok = steps.every((s) => s.ok);
652
+ const failedSteps = steps.filter((s) => !s.ok);
653
+ const ok = failedSteps.length === 0;
654
+ const actionRequired = agent === "hermes" && failedSteps.length === 1 && failedSteps[0]?.step === "check-hermes-hook-allowlist" ? "hermes-hook-allowlist" : void 0;
583
655
  const hints = ok ? [UNINSTALL_ORDER_HINT] : [];
656
+ if (actionRequired) {
657
+ hints.unshift(HERMES_ALLOWLIST_ACTION_HINT, HERMES_WEBUI_GATEWAY_HINT);
658
+ }
584
659
  if (ok && agent === "opencode") {
585
660
  hints.unshift(
586
661
  `OpenCode plugin shim: ${opencodeShimPath()}`,
@@ -589,10 +664,98 @@ async function runSetup(options) {
589
664
  }
590
665
  if (ok && agent === "hermes") {
591
666
  hints.unshift(
592
- `Hermes shell hooks: ${hermesConfigPath()} \u2192 pre_llm_call + post_llm_call`
667
+ `Hermes shell hooks: ${hermesConfigPath()} \u2192 pre_llm_call + post_llm_call`,
668
+ HERMES_RESTART_HINT,
669
+ HERMES_WEBUI_GATEWAY_HINT
593
670
  );
594
671
  }
595
- return { ok, steps, hints: hints.length > 0 ? hints : void 0 };
672
+ return {
673
+ ok,
674
+ steps,
675
+ actionRequired,
676
+ hints: hints.length > 0 ? hints : void 0
677
+ };
678
+ }
679
+ function skippedFromDetection(detection) {
680
+ return detection.observations.filter((item) => !item.selected).map((item) => ({
681
+ agent: item.agent,
682
+ reason: item.reason,
683
+ evidence: item.evidence,
684
+ ...item.detail ? { detail: item.detail } : {}
685
+ }));
686
+ }
687
+ function withoutSharedUninstallHint(result) {
688
+ const hints = result.hints?.filter((hint) => hint !== UNINSTALL_ORDER_HINT);
689
+ return {
690
+ ...result,
691
+ ...hints && hints.length > 0 ? { hints } : { hints: void 0 }
692
+ };
693
+ }
694
+ function missingApiKeyResult(agent) {
695
+ return {
696
+ ok: false,
697
+ steps: [{
698
+ step: "write-config",
699
+ ok: false,
700
+ detail: `${agent}: --api-key is required (won't write config without a valid API key)`
701
+ }]
702
+ };
703
+ }
704
+ async function runSetupAll(options = {}, dependencies = {}) {
705
+ const detect = dependencies.detect ?? detectInstalledAgents;
706
+ const loadConfig = dependencies.loadConfig ?? ((agent) => load({ agent }));
707
+ const setup2 = dependencies.setup ?? runSetup;
708
+ const detection = detect(options.detection);
709
+ const agents = {};
710
+ if (detection.selected.length === 0) {
711
+ return {
712
+ ok: false,
713
+ operation: "setup",
714
+ mode: "all",
715
+ agents,
716
+ skipped: skippedFromDetection(detection),
717
+ summary: { selected: 0, succeeded: 0, failed: 0, skipped: detection.observations.length },
718
+ hints: [NO_AGENTS_DETECTED_HINT]
719
+ };
720
+ }
721
+ const configSnapshot = /* @__PURE__ */ new Map();
722
+ for (const agent of detection.selected) {
723
+ configSnapshot.set(agent, loadConfig(agent));
724
+ }
725
+ for (const agent of detection.selected) {
726
+ const existing = configSnapshot.get(agent);
727
+ const apiKey = options.apiKey ?? existing.apiKey;
728
+ if (!apiKey) {
729
+ agents[agent] = missingApiKeyResult(agent);
730
+ continue;
731
+ }
732
+ agents[agent] = withoutSharedUninstallHint(await setup2({
733
+ agent,
734
+ apiKey,
735
+ baseUrl: options.baseUrl ?? existing.baseUrl,
736
+ userId: options.userId ?? existing.userId,
737
+ installSkill: options.installSkill,
738
+ validate: options.validate
739
+ }));
740
+ }
741
+ const results = Object.values(agents);
742
+ const succeeded = results.filter((result) => result.ok).length;
743
+ const failed = results.length - succeeded;
744
+ const skipped = skippedFromDetection(detection);
745
+ return {
746
+ ok: failed === 0,
747
+ operation: "setup",
748
+ mode: "all",
749
+ agents,
750
+ skipped,
751
+ summary: {
752
+ selected: results.length,
753
+ succeeded,
754
+ failed,
755
+ skipped: skipped.length
756
+ },
757
+ hints: [UNINSTALL_ORDER_HINT]
758
+ };
596
759
  }
597
760
  function runRemove(agent, options = {}) {
598
761
  const steps = [];
@@ -794,11 +957,12 @@ function runRemove(agent, options = {}) {
794
957
  return { ok: steps.every((s) => s.ok), steps };
795
958
  }
796
959
  function checkAgentHome(agent) {
797
- if (agent === "hermes" && process.platform === "win32") {
960
+ const platform = agentPlatformSupport(agent);
961
+ if (!platform.supported) {
798
962
  return {
799
963
  step: "check-agent-home",
800
964
  ok: false,
801
- detail: "hermes setup currently supports macOS/Linux only; Windows Hermes will be handled separately"
965
+ detail: platform.detail
802
966
  };
803
967
  }
804
968
  if (agent === "opencode" || agent === "hermes") {
@@ -814,7 +978,7 @@ function checkAgentHome(agent) {
814
978
  };
815
979
  }
816
980
  }
817
- const dirs = agentHomeDirs(agent).filter((d) => existsSync(d));
981
+ const dirs = inspectAgentHomes(agent).existing;
818
982
  if (dirs.length === 0) {
819
983
  const primary = agentHomeDir(agent);
820
984
  return {
@@ -845,23 +1009,27 @@ function runTeardown(options = {}) {
845
1009
  }
846
1010
  if (purgeConfig) {
847
1011
  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 {
1012
+ const ownedConfigFiles = [cfgPath, `${cfgPath}.bak`];
1013
+ const existing = ownedConfigFiles.filter((path) => existsSync(path));
1014
+ if (existing.length === 0) {
860
1015
  steps.push({
861
1016
  step: "purge-config",
862
1017
  ok: true,
863
1018
  detail: `nothing to remove (${cfgPath})`
864
1019
  });
1020
+ } else {
1021
+ for (const path of existing) {
1022
+ try {
1023
+ unlinkSync(path);
1024
+ steps.push({ step: "purge-config", ok: true, detail: path });
1025
+ } catch (err) {
1026
+ steps.push({
1027
+ step: "purge-config",
1028
+ ok: false,
1029
+ detail: `${path}: ${err?.message ?? String(err)}`
1030
+ });
1031
+ }
1032
+ }
865
1033
  }
866
1034
  }
867
1035
  if (purgeLogs) {
@@ -1356,8 +1524,8 @@ function hermesAllowlistPath() {
1356
1524
  return join(dirname(hermesConfigPath()), HERMES_ALLOWLIST_FILENAME);
1357
1525
  }
1358
1526
  function hermesHookApprovalHint(events = HERMES_HOOK_EVENTS) {
1359
- 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\`.`;
1527
+ const hookNames = events.join(" and ");
1528
+ 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
1529
  }
1362
1530
  function backupHermesConfig() {
1363
1531
  backupSettingsFile(hermesConfigPath());
@@ -1806,8 +1974,17 @@ async function runUpgrade(options = {}) {
1806
1974
  }
1807
1975
 
1808
1976
  // src/cli/top.ts
1809
- async function status(args) {
1810
- const agent = agentFromFlags(args.flags);
1977
+ var STATUS_HELP = `ctxdb status \u2014 inspect configuration and connectivity
1978
+
1979
+ USAGE
1980
+ ctxdb status [--agent <qoder|qoderwork|codex|claude|opencode|hermes|default> | --all] [--json]
1981
+
1982
+ FLAGS
1983
+ --agent <name> Inspect exactly one Agent profile
1984
+ --all Inspect every setup-compatible detected built-in Agent
1985
+ --json Machine-readable output
1986
+ `;
1987
+ async function inspectStatus(agent) {
1811
1988
  const cfg = load({ agent });
1812
1989
  const complete = isComplete(cfg);
1813
1990
  let connected = false;
@@ -1822,8 +1999,8 @@ async function status(args) {
1822
1999
  }
1823
2000
  }
1824
2001
  const hookHealth = isBuiltinAgent(agent) ? checkHookNodePaths(agent) : { installed: false, nodePaths: [], nodeOk: true, detail: "no hooks (default agent)" };
1825
- printResult(
1826
- {
2002
+ return {
2003
+ result: {
1827
2004
  ok: complete,
1828
2005
  agent,
1829
2006
  base_url: cfg.baseUrl,
@@ -1840,9 +2017,69 @@ async function status(args) {
1840
2017
  hooks_node_ok: hookHealth.nodeOk,
1841
2018
  version: PACKAGE_VERSION
1842
2019
  },
1843
- !!args.flags.json
1844
- );
1845
- return complete && connected ? 0 : 1;
2020
+ success: complete && connected
2021
+ };
2022
+ }
2023
+ function statusSkips(detection) {
2024
+ return {
2025
+ selected: detection.selected,
2026
+ skipped: detection.observations.filter((item) => !item.selected).map((item) => ({
2027
+ agent: item.agent,
2028
+ reason: item.reason,
2029
+ evidence: item.evidence,
2030
+ ...item.detail ? { detail: item.detail } : {}
2031
+ }))
2032
+ };
2033
+ }
2034
+ async function statusAll(dependencies = {}) {
2035
+ const detection = statusSkips((dependencies.detect ?? detectInstalledAgents)());
2036
+ const inspect = dependencies.inspect ?? inspectStatus;
2037
+ const agents = {};
2038
+ let succeeded = 0;
2039
+ for (const agent of detection.selected) {
2040
+ const inspection = await inspect(agent);
2041
+ agents[agent] = inspection.result;
2042
+ if (inspection.success) succeeded += 1;
2043
+ }
2044
+ const failed = detection.selected.length - succeeded;
2045
+ return {
2046
+ ok: detection.selected.length > 0 && failed === 0,
2047
+ operation: "status",
2048
+ mode: "all",
2049
+ agents,
2050
+ skipped: detection.skipped,
2051
+ summary: {
2052
+ selected: detection.selected.length,
2053
+ succeeded,
2054
+ failed,
2055
+ skipped: detection.skipped.length
2056
+ },
2057
+ ...detection.selected.length === 0 ? { hints: ["No setup-compatible Agent detected. Install or start one, then rerun `ctxdb status --all`."] } : {}
2058
+ };
2059
+ }
2060
+ async function status(args) {
2061
+ if (args.flags.help || args.flags.h) {
2062
+ process.stdout.write(STATUS_HELP);
2063
+ return 0;
2064
+ }
2065
+ if (args.flags.all === true) {
2066
+ if (args.flags.agent !== void 0) {
2067
+ process.stderr.write("cannot use --all and --agent together\n");
2068
+ return 2;
2069
+ }
2070
+ const result = await statusAll();
2071
+ printResult(result, !!args.flags.json);
2072
+ if (!args.flags.json && result.hints) {
2073
+ for (const hint of result.hints) process.stderr.write(`
2074
+ ${hint}
2075
+ `);
2076
+ }
2077
+ return result.ok ? 0 : 1;
2078
+ }
2079
+ const agent = agentFromFlags(args.flags);
2080
+ const inspection = await inspectStatus(agent);
2081
+ printResult(inspection.result, !!args.flags.json);
2082
+ return inspection.success ? 0 : 1;
1846
2083
  }
1847
2084
  async function ping(args) {
1848
2085
  const agent = agentFromFlags(args.flags);
@@ -1862,7 +2099,7 @@ async function ping(args) {
1862
2099
  var HELP = `ctxdb setup \u2014 configure an agent
1863
2100
 
1864
2101
  USAGE
1865
- ctxdb setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes>]
2102
+ ctxdb setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes> | --all]
1866
2103
  [--api-key=K] [--base-url=URL] [--user-id=ID]
1867
2104
  [--no-install-skill] [--no-validate] [--json]
1868
2105
 
@@ -1871,13 +2108,20 @@ BEHAVIOR
1871
2108
  no hooks or skills installed).
1872
2109
  With --agent Writes agent config + installs hooks + skills for the
1873
2110
  specified agent harness.
2111
+ Rerun for a new Agent, to repair integration files, or
2112
+ with --api-key <new-key> to replace that Agent's key.
2113
+ With --all Detects setup-compatible Agents on this host and runs the
2114
+ same setup lifecycle sequentially for each one. Successful
2115
+ Agents are preserved when another Agent fails.
1874
2116
  Hermes Also verifies ~/.hermes/shell-hooks-allowlist.json.
1875
2117
  Approve pre_llm_call + post_llm_call with
1876
2118
  \`hermes --accept-hooks chat\`. It opens interactive chat;
1877
- exit after Hermes starts, then rerun ctxdb setup.
2119
+ exit after Hermes starts, rerun ctxdb setup, then restart
2120
+ any running Hermes CLI/gateway process to load the hooks.
1878
2121
 
1879
2122
  FLAGS
1880
2123
  --agent <name> Target agent (qoder|qoderwork|codex|claude|opencode|hermes)
2124
+ --all Target every setup-compatible detected Agent
1881
2125
  --api-key <key> API key (required on first setup)
1882
2126
  --base-url <url> Server URL (default: https://context-database.aliyuncs.com)
1883
2127
  --user-id <id> User bucket (default: "default")
@@ -1885,6 +2129,10 @@ FLAGS
1885
2129
  --no-validate Skip connectivity ping after setup
1886
2130
  --json Machine-readable JSON output
1887
2131
  `;
2132
+ var CODEX_HOOKS_TRUST_REMINDER = [
2133
+ "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",
2134
+ "\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`\u3001`SessionStart` \u548C `Stop` \u91CC\u7684 `aliyunrds/ctxdb` \u94A9\u5B50\u3002"
2135
+ ].join("\n");
1888
2136
  function parseAgent(args) {
1889
2137
  const raw = args.flags.agent;
1890
2138
  if (raw === void 0) return "default";
@@ -1900,8 +2148,38 @@ async function setup(args) {
1900
2148
  process.stdout.write(HELP);
1901
2149
  return 0;
1902
2150
  }
1903
- const agent = parseAgent(args);
1904
2151
  const json = !!args.flags.json;
2152
+ const all = args.flags.all === true;
2153
+ if (all && args.flags.agent !== void 0) {
2154
+ process.stderr.write("cannot use --all and --agent together\n");
2155
+ return 2;
2156
+ }
2157
+ if (all) {
2158
+ const result2 = await runSetupAll({
2159
+ apiKey: typeof args.flags["api-key"] === "string" ? args.flags["api-key"] : void 0,
2160
+ baseUrl: typeof args.flags["base-url"] === "string" ? args.flags["base-url"] : void 0,
2161
+ userId: typeof args.flags["user-id"] === "string" ? args.flags["user-id"] : void 0,
2162
+ installSkill: !args.flags["no-install-skill"],
2163
+ validate: !args.flags["no-validate"]
2164
+ });
2165
+ printResult(result2, json);
2166
+ if (!json && result2.hints) {
2167
+ for (const hint of result2.hints) {
2168
+ process.stderr.write(`
2169
+ ${highlightInlineCode(hint)}
2170
+ `);
2171
+ }
2172
+ }
2173
+ if (!json && result2.agents.codex?.ok) {
2174
+ process.stderr.write(
2175
+ `
2176
+ ${dim(CODEX_HOOKS_TRUST_REMINDER)}
2177
+ `
2178
+ );
2179
+ }
2180
+ return result2.ok ? 0 : 1;
2181
+ }
2182
+ const agent = parseAgent(args);
1905
2183
  if (agent === "default" && !json && !configuredAgents().includes("default")) {
1906
2184
  process.stderr.write(
1907
2185
  `hint: creating CLI-only "default" agent (no hooks/skills). Use --agent <claude|qoder|qoderwork|codex|opencode|hermes> for full setup.
@@ -1918,14 +2196,16 @@ async function setup(args) {
1918
2196
  });
1919
2197
  printResult(result, json);
1920
2198
  if (!json && result.hints) {
1921
- for (const h of result.hints) process.stderr.write(`
1922
- ${h}
2199
+ for (const h of result.hints) {
2200
+ process.stderr.write(`
2201
+ ${highlightInlineCode(h)}
1923
2202
  `);
2203
+ }
1924
2204
  }
1925
2205
  if (!json && agent === "codex" && result.ok) {
1926
2206
  process.stderr.write(
1927
2207
  `
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")}
2208
+ ${dim(CODEX_HOOKS_TRUST_REMINDER)}
1929
2209
  `
1930
2210
  );
1931
2211
  }
@@ -1996,118 +2276,6 @@ ${h}
1996
2276
  return result.ok ? 0 : 1;
1997
2277
  }
1998
2278
 
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
2279
  // src/cli/upgrade.ts
2112
2280
  var HELP3 = `ctxdb upgrade \u2014 refresh skills + hooks
2113
2281
 
@@ -2396,18 +2564,28 @@ function parseMetadataFlags(raw) {
2396
2564
  }
2397
2565
 
2398
2566
  // src/cli/kb-cli.ts
2567
+ var UPLOAD_TEXT_USAGE = "usage: ctxdb kb upload-text <kb-name> <doc-name> --text=<body> [--file-path=<server-logical-path>] [--wait|--no-wait]";
2568
+ var UPLOAD_FILE_USAGE = "usage: ctxdb kb upload-file <kb-name> <local-path> [--doc-name=...] [--file-path=<server-logical-path>] [--wait|--no-wait]";
2569
+ function shouldWaitForIngest(args, usage) {
2570
+ const waitRequested = args.flags.wait !== void 0;
2571
+ const noWaitRequested = args.flags["no-wait"] !== void 0;
2572
+ if (waitRequested && noWaitRequested) {
2573
+ fail(`cannot use --wait and --no-wait together
2574
+ ${usage}`, 2);
2575
+ }
2576
+ return waitRequested;
2577
+ }
2399
2578
  async function kbUploadText(args) {
2579
+ const shouldWait = shouldWaitForIngest(args, UPLOAD_TEXT_USAGE);
2400
2580
  const [kbName, docName] = args.positional;
2401
2581
  const text = args.flags["text"];
2402
2582
  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
- );
2583
+ fail(UPLOAD_TEXT_USAGE);
2406
2584
  }
2407
2585
  const ctx = buildContext(args);
2408
2586
  const filePath = typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0;
2409
2587
  const doc = await uploadText(ctx.client, kbName, docName, text, "text/plain", filePath);
2410
- if (args.flags["no-wait"]) {
2588
+ if (!shouldWait) {
2411
2589
  printResult({ document: doc }, !!args.flags.json);
2412
2590
  return 0;
2413
2591
  }
@@ -2418,19 +2596,18 @@ async function kbUploadText(args) {
2418
2596
  return 0;
2419
2597
  }
2420
2598
  async function kbUploadFile(args) {
2599
+ const shouldWait = shouldWaitForIngest(args, UPLOAD_FILE_USAGE);
2421
2600
  const [kbName, localPath] = args.positional;
2422
2601
  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
- );
2602
+ fail(UPLOAD_FILE_USAGE);
2426
2603
  }
2427
2604
  const ctx = buildContext(args);
2428
2605
  const doc = await uploadFile(ctx.client, kbName, localPath, {
2429
2606
  docName: typeof args.flags["doc-name"] === "string" ? args.flags["doc-name"] : void 0,
2430
2607
  filePath: typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0,
2431
- timeoutMs: DEFAULT_FILE_INGEST_TIMEOUT_MS
2608
+ timeoutMs: DEFAULT_FILE_UPLOAD_TIMEOUT_MS
2432
2609
  });
2433
- if (args.flags["no-wait"]) {
2610
+ if (!shouldWait) {
2434
2611
  printResult({ document: doc }, !!args.flags.json);
2435
2612
  return 0;
2436
2613
  }
@@ -2510,7 +2687,7 @@ USAGE
2510
2687
  ctxdb <command> [args] [--flags]
2511
2688
 
2512
2689
  COMMANDS
2513
- setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes>]
2690
+ setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes> | --all]
2514
2691
  [--api-key=K] [--base-url=URL] [--user-id=ID]
2515
2692
  [--no-install-skill] [--no-validate] [--json]
2516
2693
  Without --agent \u2192 writes agents.default (CLI-only, no hooks/skills)
@@ -2521,8 +2698,12 @@ COMMANDS
2521
2698
  OpenCode \u2192 writes agents.opencode config + ~/.config/opencode/plugins/ctxdb.ts shim + skill
2522
2699
  Hermes \u2192 writes agents.hermes config + ~/.hermes/config.yaml pre/post_llm_call hooks + skill
2523
2700
  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]
2701
+ (exit chat, rerun setup, then restart any running Hermes CLI/gateway)
2702
+ --all \u2192 detects and configures every setup-compatible built-in Agent
2703
+ status [--agent <name> | --all] [--json]
2704
+ Inspect configuration, connectivity, and integration health.
2705
+ --all inspects the same detected Agent set as setup --all.
2706
+ Rerun setup to add/repair an Agent or replace its API key.
2526
2707
  ping [--agent <name>] [--json]
2527
2708
  uninstall [--agent <name>] [--purge-config] [--purge-logs] [--purge-all] [--json]
2528
2709
  teardown (alias for uninstall)
@@ -2551,9 +2732,12 @@ COMMANDS
2551
2732
 
2552
2733
  kb create <kb-name> [--description=<desc>] [--agent=<name>]
2553
2734
  kb upload-text <kb-name> <doc-name> --text=<body> [--agent=<name>]
2554
- [--file-path=<server-logical-path>] [--no-wait]
2735
+ [--file-path=<server-logical-path>] [--wait|--no-wait]
2555
2736
  kb upload-file <kb-name> <local-path> [--agent=<name>] [--doc-name=...]
2556
- [--file-path=<server-logical-path>] [--no-wait]
2737
+ [--file-path=<server-logical-path>] [--wait|--no-wait]
2738
+ Uploads return immediately by default.
2739
+ --wait polls for terminal ingest status.
2740
+ --no-wait is a compatibility alias for the default.
2557
2741
  kb list [--agent=<name>]
2558
2742
  kb documents-list <kb-name> [--agent=<name>]
2559
2743
  kb document-get <kb-name> <doc-id> [--agent=<name>]
@@ -2580,6 +2764,10 @@ ENV VARS (override selected ~/.ctxdb/ctxdb.json agent config):
2580
2764
  CTXDB_AGENT CTXDB_API_KEY CTXDB_BASE_URL CTXDB_USER_ID
2581
2765
  CTXDB_AGENT_ID CTXDB_APP_ID
2582
2766
 
2767
+ UPDATE CHECK ENV VARS:
2768
+ CTXDB_DISABLE_UPDATE_CHECK=1
2769
+ Disable background version checks and hints.
2770
+
2583
2771
  HOOK ENV VARS:
2584
2772
  CTXDB_SKIP_HOOKS=TRUE Hook/plugin entrypoints exit immediately.
2585
2773
  Direct ctxdb CLI commands are unchanged.
@@ -2616,7 +2804,7 @@ var ROUTES = {
2616
2804
  search: kbSearch
2617
2805
  }
2618
2806
  };
2619
- async function main(argv = process.argv.slice(2)) {
2807
+ async function dispatch(argv) {
2620
2808
  if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
2621
2809
  process.stdout.write(HELP5);
2622
2810
  return 0;
@@ -2660,6 +2848,15 @@ async function main(argv = process.argv.slice(2)) {
2660
2848
  }
2661
2849
  return Promise.resolve(handler(parseArgs(subRest)));
2662
2850
  }
2851
+ async function main(argv = process.argv.slice(2)) {
2852
+ const notification = beginUpdateNotification(argv, {
2853
+ currentVersion: PACKAGE_VERSION,
2854
+ workerPath: fileURLToPath2(new URL("../workers/version-check.js", import.meta.url))
2855
+ });
2856
+ const code = await dispatch(argv);
2857
+ completeUpdateNotification(notification, code);
2858
+ return code;
2859
+ }
2663
2860
  main().then(
2664
2861
  (code) => {
2665
2862
  if (typeof code === "number") process.exitCode = code;