@aliyunrds/ctxdb 0.0.1 → 0.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
@@ -7,12 +7,13 @@ import {
7
7
  SUPPORTED_AGENTS,
8
8
  agentFromEnv,
9
9
  agentHomeDir,
10
+ configuredAgents,
10
11
  isAgent,
11
12
  isComplete,
12
13
  load,
13
14
  removeAgent,
14
15
  save
15
- } from "../chunk-L4YJ7LDI.js";
16
+ } from "../chunk-QSSNPN3M.js";
16
17
 
17
18
  // src/cli/util.ts
18
19
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -36,6 +37,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
36
37
  // kb
37
38
  "no-wait",
38
39
  // kb upload-text / upload-file
40
+ "verbose",
41
+ // memory search / kb search (three-tier projection)
39
42
  "raw"
40
43
  // kb search / memory search --knowledge (W3 compaction bypass)
41
44
  ]);
@@ -86,9 +89,64 @@ function summarize(v) {
86
89
  return String(v);
87
90
  }
88
91
  }
92
+ var GREEN = "\x1B[32m";
93
+ var RED = "\x1B[31m";
94
+ var DIM = "\x1B[2m";
95
+ var BOLD = "\x1B[1m";
96
+ var RESET = "\x1B[0m";
97
+ var CHECK = `${GREEN}\u2714${RESET}`;
98
+ var CROSS = `${RED}\u2718${RESET}`;
99
+ function isSetupResult(v) {
100
+ if (typeof v !== "object" || v === null) return false;
101
+ return "steps" in v && Array.isArray(v.steps);
102
+ }
103
+ function isStatusResult(v) {
104
+ if (typeof v !== "object" || v === null) return false;
105
+ return "agent" in v && "connected" in v;
106
+ }
107
+ function formatSetupResult(r) {
108
+ const lines = [];
109
+ lines.push(`${r.ok ? CHECK : CROSS} ${BOLD}ctxdb setup${RESET} ${r.ok ? "completed" : "failed"}
110
+ `);
111
+ for (const s of r.steps) {
112
+ const icon = s.ok ? CHECK : CROSS;
113
+ const detail = s.detail ? ` ${DIM}${s.detail}${RESET}` : "";
114
+ lines.push(` ${icon} ${s.step}${detail}`);
115
+ }
116
+ return lines.join("\n");
117
+ }
118
+ function formatStatusResult(r) {
119
+ const lines = [];
120
+ const ok = r.connected ? CHECK : CROSS;
121
+ lines.push(`${ok} ${BOLD}ctxdb status${RESET} ${DIM}(${r.agent})${RESET}
122
+ `);
123
+ const display = [
124
+ ["base_url", r.base_url],
125
+ ["user_id", r.user_id],
126
+ ["api_key", r.api_key_set ? "set" : "not set"],
127
+ ["auto_capture", r.auto_capture],
128
+ ["auto_recall", r.auto_recall],
129
+ ["top_k", r.top_k],
130
+ ["threshold", r.threshold],
131
+ ["knowledge_top_k", r.knowledge_top_k],
132
+ ["connected", r.connected],
133
+ ["version", r.version]
134
+ ];
135
+ if (r.ping_error) display.push(["ping_error", r.ping_error]);
136
+ const maxKey = Math.max(...display.map(([k]) => k.length));
137
+ for (const [k, v] of display) {
138
+ const valStr = typeof v === "boolean" ? v ? `${GREEN}yes${RESET}` : `${RED}no${RESET}` : String(v ?? "-");
139
+ lines.push(` ${DIM}${k.padEnd(maxKey)}${RESET} ${valStr}`);
140
+ }
141
+ return lines.join("\n");
142
+ }
89
143
  function printResult(value, json) {
90
144
  if (json) {
91
145
  process.stdout.write(JSON.stringify(value, null, 2) + "\n");
146
+ } else if (isSetupResult(value)) {
147
+ process.stdout.write(formatSetupResult(value) + "\n");
148
+ } else if (isStatusResult(value)) {
149
+ process.stdout.write(formatStatusResult(value) + "\n");
92
150
  } else {
93
151
  process.stdout.write(summarize(value) + "\n");
94
152
  }
@@ -120,97 +178,7 @@ function fail(message, code = 1) {
120
178
  `);
121
179
  process.exit(code);
122
180
  }
123
- var PACKAGE_VERSION = "0.0.1";
124
-
125
- // src/cli/top.ts
126
- import { homedir } from "os";
127
- import { join } from "path";
128
- async function init(args) {
129
- const agent = agentFromFlags(args.flags);
130
- const cfg = load({ agent });
131
- if (typeof args.flags["api-key"] === "string") cfg.apiKey = args.flags["api-key"];
132
- if (typeof args.flags["base-url"] === "string") {
133
- cfg.baseUrl = args.flags["base-url"].replace(/\/+$/, "");
134
- } else if (!cfg.baseUrl) {
135
- cfg.baseUrl = DEFAULT_BASE_URL;
136
- }
137
- const cliUserId = typeof args.flags["user-id"] === "string" ? args.flags["user-id"] : void 0;
138
- cfg.userId = cliUserId ?? cfg.userId ?? DEFAULT_USER_ID;
139
- save(cfg, void 0, { agent });
140
- let validated = false;
141
- let pingError = null;
142
- if (!args.flags["no-validate"] && cfg.apiKey) {
143
- try {
144
- const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
145
- await client.get("/v1/ping/");
146
- validated = true;
147
- } catch (err) {
148
- pingError = err?.message ?? String(err);
149
- }
150
- }
151
- printResult(
152
- {
153
- ok: true,
154
- config_path: join(homedir(), ".ctxdb", "ctxdb.json"),
155
- agent,
156
- api_key_set: Boolean(cfg.apiKey),
157
- base_url: cfg.baseUrl,
158
- user_id: cfg.userId,
159
- validated,
160
- ping_error: pingError
161
- },
162
- !!args.flags.json
163
- );
164
- return 0;
165
- }
166
- async function status(args) {
167
- const agent = agentFromFlags(args.flags);
168
- const cfg = load({ agent });
169
- const complete = isComplete(cfg);
170
- let connected = false;
171
- let pingError = null;
172
- if (complete) {
173
- try {
174
- const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
175
- await client.get("/v1/ping/");
176
- connected = true;
177
- } catch (err) {
178
- pingError = err?.message ?? String(err);
179
- }
180
- }
181
- printResult(
182
- {
183
- ok: complete,
184
- agent,
185
- base_url: cfg.baseUrl,
186
- user_id: cfg.userId,
187
- api_key_set: Boolean(cfg.apiKey),
188
- auto_capture: cfg.autoCapture,
189
- auto_recall: cfg.autoRecall,
190
- top_k: cfg.topK,
191
- threshold: cfg.threshold,
192
- knowledge_top_k: cfg.knowledgeTopK,
193
- connected,
194
- ping_error: pingError,
195
- version: PACKAGE_VERSION
196
- },
197
- !!args.flags.json
198
- );
199
- return complete && connected ? 0 : 1;
200
- }
201
- async function ping(args) {
202
- const agent = agentFromFlags(args.flags);
203
- const cfg = load({ agent });
204
- if (!isComplete(cfg)) {
205
- fail(
206
- `config incomplete for agent ${agent} \u2014 run \`ctxdb setup --agent ${agent}\` or set CTXDB_API_KEY / CTXDB_BASE_URL / CTXDB_USER_ID`
207
- );
208
- }
209
- const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
210
- const resp = await client.get("/v1/ping/");
211
- printResult(resp, !!args.flags.json);
212
- return 0;
213
- }
181
+ var PACKAGE_VERSION = "0.0.3";
214
182
 
215
183
  // src/setup/installer.ts
216
184
  import {
@@ -226,19 +194,20 @@ import {
226
194
  unlinkSync,
227
195
  statSync
228
196
  } from "fs";
229
- import { homedir as homedir2 } from "os";
230
- import { join as join2, dirname } from "path";
197
+ import { homedir } from "os";
198
+ import { join, dirname } from "path";
231
199
  import { fileURLToPath } from "url";
200
+ import { execFileSync } from "child_process";
232
201
  var UNINSTALL_NPM_HINT = "To also remove the npm binaries: `npm uninstall -g @aliyunrds/ctxdb @aliyunrds/ctxdb-shared`";
233
202
  var UNINSTALL_ORDER_HINT = "When you eventually want to uninstall: run `ctxdb teardown --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.)";
234
203
  function skillInstallRoot(agent) {
235
204
  switch (agent) {
236
205
  case "qoder":
237
- return join2(homedir2(), ".qoder", "skills");
206
+ return join(homedir(), ".qoder", "skills");
238
207
  case "codex":
239
- return join2(homedir2(), ".codex", "skills");
208
+ return join(homedir(), ".codex", "skills");
240
209
  case "claude":
241
- return join2(homedir2(), ".claude", "skills");
210
+ return join(homedir(), ".claude", "skills");
242
211
  }
243
212
  }
244
213
  var SKILL_DIR_NAME = "ctxdb";
@@ -246,7 +215,7 @@ function skillResourceDir(agent) {
246
215
  return agentSupportsHooks(agent) ? "hooks-driven" : "cli-only";
247
216
  }
248
217
  function skillInstallDir(agent) {
249
- return join2(skillInstallRoot(agent), SKILL_DIR_NAME);
218
+ return join(skillInstallRoot(agent), SKILL_DIR_NAME);
250
219
  }
251
220
  function agentSupportsHooks(agent) {
252
221
  return agent === "qoder" || agent === "codex" || agent === "claude";
@@ -254,11 +223,11 @@ function agentSupportsHooks(agent) {
254
223
  function hookConfigPath(agent) {
255
224
  switch (agent) {
256
225
  case "qoder":
257
- return join2(homedir2(), ".qoder", "settings.json");
226
+ return join(homedir(), ".qoder", "settings.json");
258
227
  case "codex":
259
- return join2(homedir2(), ".codex", "hooks.json");
228
+ return join(homedir(), ".codex", "hooks.json");
260
229
  case "claude":
261
- return join2(homedir2(), ".claude", "settings.json");
230
+ return join(homedir(), ".claude", "settings.json");
262
231
  }
263
232
  }
264
233
  var LEGACY_QODER_SKILL_DIRS = [
@@ -324,6 +293,14 @@ async function runSetup(options) {
324
293
  detail: hookPaths ? `${hookPaths.userPromptSubmit} + ${hookPaths.stop}` : "could not locate hook scripts in dist/ or src/"
325
294
  });
326
295
  if (!hookPaths) return { ok: false, steps };
296
+ const nodePath = process.execPath;
297
+ const nodeCheck = verifyNodeBinary(nodePath);
298
+ steps.push({
299
+ step: "resolve-node",
300
+ ok: nodeCheck.ok,
301
+ detail: nodeCheck.ok ? `${nodePath} (${nodeCheck.version})` : `node not executable: ${nodeCheck.error}`
302
+ });
303
+ if (!nodeCheck.ok) return { ok: false, steps };
327
304
  try {
328
305
  backupSettingsJson(agent);
329
306
  steps.push({ step: "backup-settings", ok: true });
@@ -439,7 +416,7 @@ function runRemove(agent, options = {}) {
439
416
  const dirs = agent === "qoder" ? [
440
417
  skillInstallDir("qoder"),
441
418
  ...LEGACY_QODER_SKILL_DIRS.map(
442
- (d) => join2(homedir2(), ".qoder", "skills", d)
419
+ (d) => join(homedir(), ".qoder", "skills", d)
443
420
  )
444
421
  ] : [skillInstallDir(agent)];
445
422
  for (const p of dirs) {
@@ -532,7 +509,7 @@ function runTeardown(options = {}) {
532
509
  }
533
510
  }
534
511
  if (purgeConfig) {
535
- const cfgPath = join2(homedir2(), ".ctxdb", "ctxdb.json");
512
+ const cfgPath = join(homedir(), ".ctxdb", "ctxdb.json");
536
513
  if (existsSync(cfgPath)) {
537
514
  try {
538
515
  unlinkSync(cfgPath);
@@ -553,7 +530,7 @@ function runTeardown(options = {}) {
553
530
  }
554
531
  }
555
532
  if (purgeLogs) {
556
- const logsDir = join2(homedir2(), ".ctxdb", "logs");
533
+ const logsDir = join(homedir(), ".ctxdb", "logs");
557
534
  if (existsSync(logsDir)) {
558
535
  try {
559
536
  rmSync(logsDir, { recursive: true, force: true });
@@ -574,7 +551,7 @@ function runTeardown(options = {}) {
574
551
  }
575
552
  }
576
553
  if (purgeRoot) {
577
- const root = join2(homedir2(), ".ctxdb");
554
+ const root = join(homedir(), ".ctxdb");
578
555
  if (existsSync(root)) {
579
556
  try {
580
557
  const remaining = readdirSync(root);
@@ -601,6 +578,70 @@ function runTeardown(options = {}) {
601
578
  const hints = ok ? [UNINSTALL_NPM_HINT] : void 0;
602
579
  return { ok, steps, hints };
603
580
  }
581
+ function verifyNodeBinary(nodePath) {
582
+ if (!existsSync(nodePath)) return { ok: false, error: `not found: ${nodePath}` };
583
+ try {
584
+ const out = execFileSync(nodePath, ["--version"], { timeout: 5e3, encoding: "utf-8" }).trim();
585
+ return { ok: true, version: out };
586
+ } catch (err) {
587
+ return { ok: false, error: err?.message ?? String(err) };
588
+ }
589
+ }
590
+ function checkHookNodePaths(agent) {
591
+ const empty = {
592
+ installed: false,
593
+ nodePaths: [],
594
+ nodeOk: true,
595
+ detail: "no hooks installed"
596
+ };
597
+ const path = hookConfigPath(agent);
598
+ if (!path || !existsSync(path)) return empty;
599
+ let data;
600
+ try {
601
+ data = JSON.parse(readFileSync(path, "utf-8"));
602
+ } catch {
603
+ return empty;
604
+ }
605
+ if (!data?.hooks || typeof data.hooks !== "object") return empty;
606
+ const nodePaths = /* @__PURE__ */ new Set();
607
+ for (const event of HOOK_EVENTS) {
608
+ const entries = data.hooks[event];
609
+ if (!Array.isArray(entries)) continue;
610
+ for (const entry of entries) {
611
+ if (!entryIsCtxdb(entry)) continue;
612
+ for (const h of entry.hooks ?? []) {
613
+ if (h?.type !== "command" || typeof h.command !== "string") continue;
614
+ const cmd = h.command;
615
+ if (cmd.includes("@aliyunrds/ctxdb")) {
616
+ const parts = cmd.split(" ");
617
+ if (parts.length >= 2 && !parts[0].endsWith(".js") && !parts[0].endsWith(".ts")) {
618
+ nodePaths.add(parts[0]);
619
+ }
620
+ }
621
+ }
622
+ }
623
+ }
624
+ if (nodePaths.size === 0) {
625
+ return { installed: true, nodePaths: [], nodeOk: true, detail: "hooks use shebang (no explicit node path)" };
626
+ }
627
+ const paths = [...nodePaths];
628
+ const bad = [];
629
+ let goodVersion = "";
630
+ for (const p of paths) {
631
+ const v = verifyNodeBinary(p);
632
+ if (!v.ok) bad.push(`${p}: ${v.error}`);
633
+ else if (!goodVersion) goodVersion = v.version ?? "";
634
+ }
635
+ if (bad.length > 0) {
636
+ return {
637
+ installed: true,
638
+ nodePaths: paths,
639
+ nodeOk: false,
640
+ detail: `node not executable: ${bad.join("; ")} \u2014 re-run \`ctxdb setup --agent ${agent}\``
641
+ };
642
+ }
643
+ return { installed: true, nodePaths: paths, nodeOk: true, detail: `${paths[0]} (${goodVersion})` };
644
+ }
604
645
  var SETTINGS_BACKUP_KEEP = 5;
605
646
  function backupSettingsJson(agent) {
606
647
  const path = hookConfigPath(agent);
@@ -622,7 +663,7 @@ function rotateBackups(prefix, keep) {
622
663
  const candidates = [];
623
664
  for (const name of entries) {
624
665
  if (!name.startsWith(baseName)) continue;
625
- const full = join2(dir, name);
666
+ const full = join(dir, name);
626
667
  try {
627
668
  const st = statSync(full);
628
669
  if (st.isFile()) candidates.push({ path: full, mtime: st.mtimeMs });
@@ -674,20 +715,20 @@ function locateHookPaths() {
674
715
  if (!pkgRoot) return null;
675
716
  const preferSrc = here.includes("/src/") || here.includes("\\src\\");
676
717
  if (preferSrc) {
677
- const src = hookPathsFromDir(join2(pkgRoot, "src", "hooks"), "ts");
718
+ const src = hookPathsFromDir(join(pkgRoot, "src", "hooks"), "ts");
678
719
  if (src) return src;
679
720
  }
680
- const distDir = join2(pkgRoot, "dist", "hooks");
721
+ const distDir = join(pkgRoot, "dist", "hooks");
681
722
  const dist = hookPathsFromDir(distDir, "js");
682
723
  if (dist) return dist;
683
- return hookPathsFromDir(join2(pkgRoot, "src", "hooks"), "ts");
724
+ return hookPathsFromDir(join(pkgRoot, "src", "hooks"), "ts");
684
725
  }
685
726
  function hookPathsFromDir(dir, ext) {
686
727
  if (!existsSync(dir)) return null;
687
- const ups = join2(dir, `user-prompt-submit.${ext}`);
688
- const stp = join2(dir, `stop.${ext}`);
689
- const ss = join2(dir, `session-start.${ext}`);
690
- const ptu = join2(dir, `pre-tool-use.${ext}`);
728
+ const ups = join(dir, `user-prompt-submit.${ext}`);
729
+ const stp = join(dir, `stop.${ext}`);
730
+ const ss = join(dir, `session-start.${ext}`);
731
+ const ptu = join(dir, `pre-tool-use.${ext}`);
691
732
  if (existsSync(ups) && existsSync(stp) && existsSync(ss)) {
692
733
  return {
693
734
  userPromptSubmit: ups,
@@ -704,19 +745,19 @@ function locateSkillDir(variant) {
704
745
  if (!pkgRoot) return null;
705
746
  const preferSrc = here.includes("/src/") || here.includes("\\src\\");
706
747
  if (preferSrc) {
707
- const srcSkill2 = join2(pkgRoot, "src", "setup", "skills", variant);
748
+ const srcSkill2 = join(pkgRoot, "src", "setup", "skills", variant);
708
749
  if (existsSync(srcSkill2)) return srcSkill2;
709
750
  }
710
- const distSkill = join2(pkgRoot, "dist", "setup", "skills", variant);
751
+ const distSkill = join(pkgRoot, "dist", "setup", "skills", variant);
711
752
  if (existsSync(distSkill)) return distSkill;
712
- const srcSkill = join2(pkgRoot, "src", "setup", "skills", variant);
753
+ const srcSkill = join(pkgRoot, "src", "setup", "skills", variant);
713
754
  if (existsSync(srcSkill)) return srcSkill;
714
755
  return null;
715
756
  }
716
757
  function walkUpToPackageJson(start, expectedName) {
717
758
  let cur = start;
718
759
  for (let i = 0; i < 8; i++) {
719
- const p = join2(cur, "package.json");
760
+ const p = join(cur, "package.json");
720
761
  if (existsSync(p)) {
721
762
  try {
722
763
  const pkg = JSON.parse(readFileSync(p, "utf-8"));
@@ -733,8 +774,8 @@ function walkUpToPackageJson(start, expectedName) {
733
774
  function copySkillDir(src, dest, agent) {
734
775
  mkdirSync(dest, { recursive: true });
735
776
  for (const entry of readdirSync(src)) {
736
- const srcPath = join2(src, entry);
737
- const destPath = join2(dest, entry);
777
+ const srcPath = join(src, entry);
778
+ const destPath = join(dest, entry);
738
779
  const st = statSync(srcPath);
739
780
  if (st.isDirectory()) {
740
781
  copySkillDir(srcPath, destPath, agent);
@@ -773,7 +814,7 @@ var LEGACY_MARKER_KEYS = ["_ctxdbQoder", "_ctxdbPackage"];
773
814
  var LEGACY_MARKER_VALUES = ["@aliyunrds/ctxdb-qoder"];
774
815
  var TOOL_SCOPED_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse"]);
775
816
  function appendOne(hooks, event, command, agent) {
776
- const commandWithAgent = `${command} --agent=${agent}`;
817
+ const commandWithAgent = `${process.execPath} ${command} --agent=${agent}`;
777
818
  if (!Array.isArray(hooks[event])) hooks[event] = [];
778
819
  const dup = hooks[event].some(
779
820
  (entry2) => Array.isArray(entry2?.hooks) && entry2.hooks.some(
@@ -806,7 +847,7 @@ function entryIsCtxdb(entry) {
806
847
  );
807
848
  }
808
849
  function codexConfigTomlPath() {
809
- return join2(homedir2(), ".codex", "config.toml");
850
+ return join(homedir(), ".codex", "config.toml");
810
851
  }
811
852
  function inspectCodexHooksFeature(rawContent) {
812
853
  const content = rawContent.replace(/^/, "");
@@ -1001,6 +1042,190 @@ function describeCodexFeatureMutation(status2, createdNew) {
1001
1042
  return "updated ~/.codex/config.toml";
1002
1043
  }
1003
1044
  }
1045
+ function runUpgrade(options = {}) {
1046
+ const agents = options.agent ? [options.agent] : configuredAgents(options.configPath);
1047
+ if (agents.length === 0) {
1048
+ return {
1049
+ ok: false,
1050
+ agents: {},
1051
+ hints: options.agent ? [`agent "${options.agent}" not configured \u2014 run \`ctxdb setup --agent ${options.agent}\` first`] : ["no agents configured \u2014 run `ctxdb setup --agent <name>` first"]
1052
+ };
1053
+ }
1054
+ if (options.agent) {
1055
+ const configured = configuredAgents(options.configPath);
1056
+ if (!configured.includes(options.agent)) {
1057
+ return {
1058
+ ok: false,
1059
+ agents: {},
1060
+ hints: [`agent "${options.agent}" not configured \u2014 run \`ctxdb setup --agent ${options.agent}\` first`]
1061
+ };
1062
+ }
1063
+ }
1064
+ const results = {};
1065
+ for (const agent of agents) {
1066
+ results[agent] = upgradeOneAgent(agent);
1067
+ }
1068
+ const anyOk = Object.values(results).some((r) => r.ok);
1069
+ return { ok: anyOk, agents: results };
1070
+ }
1071
+ function upgradeOneAgent(agent) {
1072
+ const steps = [];
1073
+ const homeCheck = checkAgentHome(agent);
1074
+ steps.push(homeCheck);
1075
+ if (!homeCheck.ok) {
1076
+ return { ok: true, steps, hints: [`${agent}: skipped \u2014 agent home not found`] };
1077
+ }
1078
+ try {
1079
+ const srcDir = locateSkillDir(skillResourceDir(agent));
1080
+ if (srcDir) {
1081
+ const dest = skillInstallDir(agent);
1082
+ copySkillDir(srcDir, dest, agent);
1083
+ steps.push({ step: "install-skill", ok: true, detail: dest });
1084
+ } else {
1085
+ steps.push({
1086
+ step: "install-skill",
1087
+ ok: false,
1088
+ detail: "skill source directory not found"
1089
+ });
1090
+ return { ok: false, steps };
1091
+ }
1092
+ } catch (err) {
1093
+ steps.push({ step: "install-skill", ok: false, detail: err?.message ?? String(err) });
1094
+ return { ok: false, steps };
1095
+ }
1096
+ if (agentSupportsHooks(agent)) {
1097
+ const hookPaths = locateHookPaths();
1098
+ if (!hookPaths) {
1099
+ steps.push({ step: "locate-hooks", ok: false, detail: "hook scripts not found in dist/ or src/" });
1100
+ return { ok: false, steps };
1101
+ }
1102
+ const nodePath = process.execPath;
1103
+ const nodeCheck = verifyNodeBinary(nodePath);
1104
+ if (!nodeCheck.ok) {
1105
+ steps.push({ step: "resolve-node", ok: false, detail: `node not executable: ${nodeCheck.error}` });
1106
+ return { ok: false, steps };
1107
+ }
1108
+ try {
1109
+ backupSettingsJson(agent);
1110
+ const removed = stripCtxdbHooks(agent);
1111
+ appendHooks(agent, hookPaths);
1112
+ steps.push({
1113
+ step: "refresh-hooks",
1114
+ ok: true,
1115
+ detail: `${HOOK_EVENTS.length} hooks written (node: ${nodePath})${removed > 0 ? `, replaced ${removed} stale` : ""}`
1116
+ });
1117
+ } catch (err) {
1118
+ steps.push({ step: "refresh-hooks", ok: false, detail: err?.message ?? String(err) });
1119
+ return { ok: false, steps };
1120
+ }
1121
+ try {
1122
+ chmodSync(hookPaths.userPromptSubmit, 493);
1123
+ chmodSync(hookPaths.stop, 493);
1124
+ chmodSync(hookPaths.sessionStart, 493);
1125
+ if (hookPaths.preToolUse && existsSync(hookPaths.preToolUse)) {
1126
+ chmodSync(hookPaths.preToolUse, 493);
1127
+ }
1128
+ } catch {
1129
+ }
1130
+ if (agent === "codex") {
1131
+ steps.push(enableCodexHooksFeature());
1132
+ }
1133
+ }
1134
+ return { ok: steps.every((s) => s.ok), steps };
1135
+ }
1136
+
1137
+ // src/cli/top.ts
1138
+ import { homedir as homedir2 } from "os";
1139
+ import { join as join2 } from "path";
1140
+ async function init(args) {
1141
+ const agent = agentFromFlags(args.flags);
1142
+ const cfg = load({ agent });
1143
+ if (typeof args.flags["api-key"] === "string") cfg.apiKey = args.flags["api-key"];
1144
+ if (typeof args.flags["base-url"] === "string") {
1145
+ cfg.baseUrl = args.flags["base-url"].replace(/\/+$/, "");
1146
+ } else if (!cfg.baseUrl) {
1147
+ cfg.baseUrl = DEFAULT_BASE_URL;
1148
+ }
1149
+ const cliUserId = typeof args.flags["user-id"] === "string" ? args.flags["user-id"] : void 0;
1150
+ cfg.userId = cliUserId ?? cfg.userId ?? DEFAULT_USER_ID;
1151
+ save(cfg, void 0, { agent });
1152
+ let validated = false;
1153
+ let pingError = null;
1154
+ if (!args.flags["no-validate"] && cfg.apiKey) {
1155
+ try {
1156
+ const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
1157
+ await client.get("/v1/ping/");
1158
+ validated = true;
1159
+ } catch (err) {
1160
+ pingError = err?.message ?? String(err);
1161
+ }
1162
+ }
1163
+ printResult(
1164
+ {
1165
+ ok: true,
1166
+ config_path: join2(homedir2(), ".ctxdb", "ctxdb.json"),
1167
+ agent,
1168
+ api_key_set: Boolean(cfg.apiKey),
1169
+ base_url: cfg.baseUrl,
1170
+ user_id: cfg.userId,
1171
+ validated,
1172
+ ping_error: pingError
1173
+ },
1174
+ !!args.flags.json
1175
+ );
1176
+ return 0;
1177
+ }
1178
+ async function status(args) {
1179
+ const agent = agentFromFlags(args.flags);
1180
+ const cfg = load({ agent });
1181
+ const complete = isComplete(cfg);
1182
+ let connected = false;
1183
+ let pingError = null;
1184
+ if (complete) {
1185
+ try {
1186
+ const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
1187
+ await client.get("/v1/ping/");
1188
+ connected = true;
1189
+ } catch (err) {
1190
+ pingError = err?.message ?? String(err);
1191
+ }
1192
+ }
1193
+ const hookHealth = checkHookNodePaths(agent);
1194
+ printResult(
1195
+ {
1196
+ ok: complete,
1197
+ agent,
1198
+ base_url: cfg.baseUrl,
1199
+ user_id: cfg.userId,
1200
+ api_key_set: Boolean(cfg.apiKey),
1201
+ auto_capture: cfg.autoCapture,
1202
+ auto_recall: cfg.autoRecall,
1203
+ top_k: cfg.topK,
1204
+ threshold: cfg.threshold,
1205
+ knowledge_top_k: cfg.knowledgeTopK,
1206
+ connected,
1207
+ ping_error: pingError,
1208
+ hooks_node: hookHealth.detail,
1209
+ hooks_node_ok: hookHealth.nodeOk,
1210
+ version: PACKAGE_VERSION
1211
+ },
1212
+ !!args.flags.json
1213
+ );
1214
+ return complete && connected ? 0 : 1;
1215
+ }
1216
+ async function ping(args) {
1217
+ const agent = agentFromFlags(args.flags);
1218
+ const cfg = load({ agent });
1219
+ if (!isComplete(cfg)) {
1220
+ fail(
1221
+ `config incomplete for agent ${agent} \u2014 run \`ctxdb setup --agent ${agent}\` or set CTXDB_API_KEY / CTXDB_BASE_URL / CTXDB_USER_ID`
1222
+ );
1223
+ }
1224
+ const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
1225
+ const resp = await client.get("/v1/ping/");
1226
+ printResult(resp, !!args.flags.json);
1227
+ return 0;
1228
+ }
1004
1229
 
1005
1230
  // src/cli/setup-cli.ts
1006
1231
  function parseAgent(args) {
@@ -1066,6 +1291,27 @@ ${h}
1066
1291
  return result.ok ? 0 : 1;
1067
1292
  }
1068
1293
 
1294
+ // src/cli/upgrade.ts
1295
+ function upgrade(args) {
1296
+ const agentFlag = args.flags.agent;
1297
+ if (agentFlag && !isAgent(agentFlag)) {
1298
+ process.stderr.write(
1299
+ `unknown --agent: ${agentFlag} (expected one of ${SUPPORTED_AGENTS.join(" / ")})
1300
+ `
1301
+ );
1302
+ return 2;
1303
+ }
1304
+ const result = runUpgrade({ agent: agentFlag });
1305
+ const json = !!args.flags.json;
1306
+ printResult(result, json);
1307
+ if (!json && result.hints) {
1308
+ for (const h of result.hints) process.stderr.write(`
1309
+ ${h}
1310
+ `);
1311
+ }
1312
+ return result.ok ? 0 : 1;
1313
+ }
1314
+
1069
1315
  // src/lib/kb.ts
1070
1316
  import { readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
1071
1317
  import { basename, extname } from "path";
@@ -1145,10 +1391,16 @@ async function uploadText(client, kbId, docName, text, mimeType = "text/plain",
1145
1391
  );
1146
1392
  }
1147
1393
  async function uploadFile(client, kbId, localPath, options = {}) {
1394
+ const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
1148
1395
  const expanded = expandHome(localPath);
1149
1396
  if (!existsSync2(expanded)) throw new Error(`file not found: ${expanded}`);
1150
1397
  const stat = statSync2(expanded);
1151
1398
  if (!stat.isFile()) throw new Error(`not a file: ${expanded}`);
1399
+ if (stat.size > MAX_UPLOAD_BYTES) {
1400
+ throw new Error(
1401
+ `file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB), maximum is 100 MB`
1402
+ );
1403
+ }
1152
1404
  const filename = basename(expanded);
1153
1405
  const docName = options.docName ?? filename;
1154
1406
  const content = readFileSync2(expanded);
@@ -1160,19 +1412,22 @@ async function uploadFile(client, kbId, localPath, options = {}) {
1160
1412
  if (options.filePath !== void 0 && options.filePath !== "") {
1161
1413
  fields.file_path = options.filePath;
1162
1414
  }
1415
+ const uploadOpts = { timeoutMs: options.timeoutMs };
1163
1416
  return tryNewThenLegacy(
1164
1417
  FILES,
1165
1418
  () => client.postMultipart(
1166
1419
  FILES,
1167
1420
  fields,
1168
- { file: { filename, content, mimeType: mime } }
1421
+ { file: { filename, content, mimeType: mime } },
1422
+ uploadOpts
1169
1423
  ),
1170
1424
  () => {
1171
1425
  const { knowledge_base_id: _drop, ...legacyFields } = fields;
1172
1426
  return client.postMultipart(
1173
1427
  `${KB_COLLECTION}/${encodeURIComponent(kbId)}/files`,
1174
1428
  legacyFields,
1175
- { file: { filename, content, mimeType: mime } }
1429
+ { file: { filename, content, mimeType: mime } },
1430
+ uploadOpts
1176
1431
  );
1177
1432
  }
1178
1433
  );
@@ -1453,7 +1708,8 @@ async function kbUploadFile(args) {
1453
1708
  const { kb, created } = await findOrCreateKb(ctx.client, kbName);
1454
1709
  const doc = await uploadFile(ctx.client, kb.id, localPath, {
1455
1710
  docName: typeof args.flags["doc-name"] === "string" ? args.flags["doc-name"] : void 0,
1456
- filePath: typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0
1711
+ filePath: typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0,
1712
+ timeoutMs: DEFAULT_FILE_INGEST_TIMEOUT_MS
1457
1713
  });
1458
1714
  if (args.flags["no-wait"]) {
1459
1715
  printResult({ kb, kb_created: created, document: doc }, !!args.flags.json);
@@ -1554,6 +1810,10 @@ COMMANDS
1554
1810
  --purge-logs also deletes ~/.ctxdb/logs/.
1555
1811
  --purge-all deletes everything under ~/.ctxdb/
1556
1812
  (does not run npm uninstall).
1813
+ upgrade [--agent <name>] [--json]
1814
+ Refresh skills + hooks for configured agents.
1815
+ Run after npm update -g @aliyunrds/ctxdb.
1816
+ Does not modify credentials.
1557
1817
 
1558
1818
  memory add <text> [--agent=<name>] [--user-id=...] [--metadata=K1=V1,K2=V2] [--no-infer]
1559
1819
  memory search <query> [--agent=<name>] [--top-k=10] [--threshold=0.4] [--knowledge]
@@ -1568,8 +1828,9 @@ COMMANDS
1568
1828
  memory delete <memory-id> | --all [--agent=<name>]
1569
1829
 
1570
1830
  kb upload-text <kb-name> <doc-name> --text=<body> [--agent=<name>]
1571
- [--kb-description=...] [--no-wait]
1572
- kb upload-file <kb-name> <file-path> [--agent=<name>] [--doc-name=...] [--no-wait]
1831
+ [--kb-description=...] [--file-path=<server-logical-path>] [--no-wait]
1832
+ kb upload-file <kb-name> <local-path> [--agent=<name>] [--doc-name=...]
1833
+ [--file-path=<server-logical-path>] [--no-wait]
1573
1834
  kb list [--agent=<name>]
1574
1835
  kb documents-list <kb-name-or-id> [--agent=<name>]
1575
1836
  kb document-get <kb-name-or-id> <doc-id> [--agent=<name>]
@@ -1593,6 +1854,7 @@ var ROUTES = {
1593
1854
  ping,
1594
1855
  setup,
1595
1856
  teardown,
1857
+ upgrade,
1596
1858
  memory: {
1597
1859
  add: memoryAdd,
1598
1860
  search: memorySearch,