@aliyunrds/ctxdb 0.0.4 → 0.0.7

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,29 +1,46 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_FILE_INGEST_TIMEOUT_MS,
4
+ DEFAULT_INGEST_TIMEOUT_MS,
5
+ compactChunk,
6
+ compactKbQueryResponse,
7
+ createKb,
8
+ getDocument,
9
+ listDocuments,
10
+ listKnowledgeBases,
11
+ minimalChunk,
12
+ minimalKbQueryResponse,
13
+ pollIngest,
14
+ uploadFile,
15
+ uploadText
16
+ } from "../chunk-6S5RJYBC.js";
2
17
  import {
3
18
  DEFAULT_BASE_URL,
4
19
  DEFAULT_USER_ID,
5
20
  HttpClient,
6
- NotFoundError,
7
21
  SUPPORTED_AGENTS,
8
22
  agentFromEnv,
9
23
  agentHomeDir,
10
24
  configuredAgents,
11
- isAgent,
25
+ isAgentSlug,
26
+ isBuiltinAgent,
12
27
  isComplete,
13
28
  load,
14
29
  removeAgent,
15
- save
16
- } from "../chunk-QSSNPN3M.js";
30
+ save,
31
+ writeInstalledPkgVersion
32
+ } from "../chunk-S45GOYUU.js";
17
33
 
18
34
  // src/cli/util.ts
19
35
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
20
36
  // universal
21
37
  "json",
38
+ "help",
39
+ "h",
22
40
  // setup / init
23
41
  "no-install-skill",
24
42
  "no-validate",
25
- "remove",
26
- // teardown
43
+ // uninstall
27
44
  "purge-config",
28
45
  "purge-logs",
29
46
  "purge-all",
@@ -108,9 +125,9 @@ function isStatusResult(v) {
108
125
  if (typeof v !== "object" || v === null) return false;
109
126
  return "agent" in v && "connected" in v;
110
127
  }
111
- function formatSetupResult(r) {
128
+ function formatSetupResult(r, command = "setup") {
112
129
  const lines = [];
113
- lines.push(`${r.ok ? CHECK : CROSS} ${BOLD}ctxdb setup${RESET} ${r.ok ? "completed" : "failed"}
130
+ lines.push(`${r.ok ? CHECK : CROSS} ${BOLD}ctxdb ${command}${RESET} ${r.ok ? "completed" : "failed"}
114
131
  `);
115
132
  for (const s of r.steps) {
116
133
  const icon = s.ok ? CHECK : CROSS;
@@ -160,13 +177,13 @@ function formatStatusResult(r) {
160
177
  }
161
178
  return lines.join("\n");
162
179
  }
163
- function printResult(value, json) {
180
+ function printResult(value, json, command) {
164
181
  if (json) {
165
182
  process.stdout.write(JSON.stringify(value, null, 2) + "\n");
166
183
  } else if (isUpgradeResult(value)) {
167
184
  process.stdout.write(formatUpgradeResult(value) + "\n");
168
185
  } else if (isSetupResult(value)) {
169
- process.stdout.write(formatSetupResult(value) + "\n");
186
+ process.stdout.write(formatSetupResult(value, command ?? "setup") + "\n");
170
187
  } else if (isStatusResult(value)) {
171
188
  process.stdout.write(formatStatusResult(value) + "\n");
172
189
  } else {
@@ -176,17 +193,16 @@ function printResult(value, json) {
176
193
  function agentFromFlags(flags) {
177
194
  const raw = flags.agent;
178
195
  if (raw === void 0) return agentFromEnv();
179
- if (isAgent(raw)) return raw;
180
- fail(`unknown --agent: ${String(raw)} (expected one of ${SUPPORTED_AGENTS.join(" / ")})`, 2);
196
+ if (isAgentSlug(raw)) return raw;
197
+ fail(`unknown --agent: ${String(raw)} (expected one of ${SUPPORTED_AGENTS.join(" / ")} / default)`, 2);
181
198
  }
182
199
  function buildContext(args) {
183
200
  const agent = agentFromFlags(args?.flags ?? {});
184
201
  const cfg = load({ agent });
185
202
  if (!isComplete(cfg)) {
186
- process.stderr.write(
187
- `config incomplete for agent ${agent}: run \`ctxdb setup --agent ${agent}\` or set CTXDB_API_KEY / CTXDB_BASE_URL / CTXDB_USER_ID env vars
188
- `
189
- );
203
+ const hint = agent === "default" ? "run `ctxdb setup --api-key=<key> --base-url=<url>` or set CTXDB_API_KEY / CTXDB_BASE_URL env vars" : `run \`ctxdb setup --agent ${agent}\` or set CTXDB_API_KEY / CTXDB_BASE_URL / CTXDB_USER_ID env vars`;
204
+ process.stderr.write(`config incomplete for agent ${agent}: ${hint}
205
+ `);
190
206
  process.exit(2);
191
207
  }
192
208
  const client = new HttpClient({
@@ -200,7 +216,7 @@ function fail(message, code = 1) {
200
216
  `);
201
217
  process.exit(code);
202
218
  }
203
- var PACKAGE_VERSION = "0.0.4";
219
+ var PACKAGE_VERSION = "0.0.7";
204
220
 
205
221
  // src/setup/installer.ts
206
222
  import {
@@ -221,7 +237,7 @@ import { join, dirname } from "path";
221
237
  import { fileURLToPath } from "url";
222
238
  import { execFileSync } from "child_process";
223
239
  var UNINSTALL_NPM_HINT = "To also remove the npm binaries: `npm uninstall -g @aliyunrds/ctxdb @aliyunrds/ctxdb-shared`";
224
- 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.)";
240
+ 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.)";
225
241
  function skillInstallRoot(agent) {
226
242
  switch (agent) {
227
243
  case "qoder":
@@ -232,15 +248,10 @@ function skillInstallRoot(agent) {
232
248
  return join(homedir(), ".claude", "skills");
233
249
  }
234
250
  }
235
- var SKILL_DIR_NAME = "ctxdb";
236
- function skillResourceDir(agent) {
237
- return agentSupportsHooks(agent) ? "hooks-driven" : "cli-only";
238
- }
239
- function skillInstallDir(agent) {
240
- return join(skillInstallRoot(agent), SKILL_DIR_NAME);
241
- }
251
+ var SKILL_DIRS = ["contextdb-memory", "contextdb-knowledge"];
252
+ var LEGACY_SKILL_DIRS = ["ctxdb"];
242
253
  function agentSupportsHooks(agent) {
243
- return agent === "qoder" || agent === "codex" || agent === "claude";
254
+ return isBuiltinAgent(agent);
244
255
  }
245
256
  function hookConfigPath(agent) {
246
257
  switch (agent) {
@@ -260,54 +271,48 @@ var LEGACY_QODER_SKILL_DIRS = [
260
271
  var HOOK_EVENTS = ["UserPromptSubmit", "Stop", "SessionStart"];
261
272
  async function runSetup(options) {
262
273
  const steps = [];
263
- if (!isAgent(options.agent)) {
274
+ if (!isAgentSlug(options.agent)) {
264
275
  steps.push({
265
276
  step: "validate-agent",
266
277
  ok: false,
267
- detail: `unknown --agent: ${String(options.agent)} (expected one of ${SUPPORTED_AGENTS.join(" / ")})`
278
+ detail: `unknown --agent: ${String(options.agent)} (expected one of ${SUPPORTED_AGENTS.join(" / ")} / default)`
268
279
  });
269
280
  return { ok: false, steps };
270
281
  }
271
282
  const agent = options.agent;
272
283
  const installSkill = options.installSkill !== false;
273
284
  const validate = options.validate !== false;
274
- const homeCheck = checkAgentHome(agent);
275
- steps.push(homeCheck);
276
- if (!homeCheck.ok) return { ok: false, steps };
285
+ if (isBuiltinAgent(agent)) {
286
+ const homeCheck = checkAgentHome(agent);
287
+ steps.push(homeCheck);
288
+ if (!homeCheck.ok) return { ok: false, steps };
289
+ }
277
290
  const cfg = load({ agent });
278
291
  if (options.apiKey) cfg.apiKey = options.apiKey;
279
292
  if (options.baseUrl) cfg.baseUrl = options.baseUrl.replace(/\/+$/, "");
280
293
  else if (!cfg.baseUrl) cfg.baseUrl = DEFAULT_BASE_URL;
281
294
  cfg.userId = options.userId ?? cfg.userId ?? DEFAULT_USER_ID;
295
+ if (!cfg.apiKey) {
296
+ steps.push({
297
+ step: "write-config",
298
+ ok: false,
299
+ detail: `${agent}: --api-key is required (won't write config without a valid API key)`
300
+ });
301
+ return { ok: false, steps };
302
+ }
282
303
  save(cfg, void 0, { agent });
283
304
  steps.push({
284
305
  step: "write-config",
285
306
  ok: true,
286
307
  detail: `${agent}: ${cfg.baseUrl} (user_id=${cfg.userId})`
287
308
  });
288
- if (installSkill) {
289
- try {
290
- const srcDir = locateSkillDir(skillResourceDir(agent));
291
- if (srcDir) {
292
- const dest = skillInstallDir(agent);
293
- copySkillDir(srcDir, dest, agent);
294
- steps.push({ step: "install-skill", ok: true, detail: dest });
295
- } else {
296
- steps.push({
297
- step: "install-skill",
298
- ok: false,
299
- detail: `skill source directory not found (${skillResourceDir(agent)})`
300
- });
301
- }
302
- } catch (err) {
303
- steps.push({
304
- step: "install-skill",
305
- ok: false,
306
- detail: err?.message ?? String(err)
307
- });
308
- }
309
+ if (installSkill && isBuiltinAgent(agent)) {
310
+ const installRoot = skillInstallRoot(agent);
311
+ const skillResult = installSkillsTo(installRoot, agent);
312
+ steps.push(...skillResult.steps);
313
+ if (!skillResult.ok) return { ok: false, steps };
309
314
  }
310
- if (agentSupportsHooks(agent)) {
315
+ if (isBuiltinAgent(agent) && agentSupportsHooks(agent)) {
311
316
  const hookPaths = locateHookPaths();
312
317
  steps.push({
313
318
  step: "locate-hooks",
@@ -390,15 +395,15 @@ async function runSetup(options) {
390
395
  }
391
396
  function runRemove(agent, options = {}) {
392
397
  const steps = [];
393
- if (!isAgent(agent)) {
398
+ if (!isAgentSlug(agent)) {
394
399
  steps.push({
395
400
  step: "validate-agent",
396
401
  ok: false,
397
- detail: `unknown --agent: ${String(agent)} (expected one of ${SUPPORTED_AGENTS.join(" / ")})`
402
+ detail: `unknown --agent: ${String(agent)} (expected one of ${SUPPORTED_AGENTS.join(" / ")} / default)`
398
403
  });
399
404
  return { ok: false, steps };
400
405
  }
401
- if (agentSupportsHooks(agent)) {
406
+ if (isBuiltinAgent(agent) && agentSupportsHooks(agent)) {
402
407
  const path = hookConfigPath(agent);
403
408
  if (!existsSync(path)) {
404
409
  steps.push({
@@ -435,12 +440,12 @@ function runRemove(agent, options = {}) {
435
440
  return { ok: false, steps };
436
441
  }
437
442
  }
438
- const dirs = agent === "qoder" ? [
439
- skillInstallDir("qoder"),
440
- ...LEGACY_QODER_SKILL_DIRS.map(
441
- (d) => join(homedir(), ".qoder", "skills", d)
442
- )
443
- ] : [skillInstallDir(agent)];
443
+ const root = skillInstallRoot(agent);
444
+ const dirs = [
445
+ ...SKILL_DIRS.map((d) => join(root, d)),
446
+ ...LEGACY_SKILL_DIRS.map((d) => join(root, d)),
447
+ ...agent === "qoder" ? LEGACY_QODER_SKILL_DIRS.map((d) => join(root, d)) : []
448
+ ];
444
449
  for (const p of dirs) {
445
450
  if (!existsSync(p)) continue;
446
451
  try {
@@ -454,26 +459,6 @@ function runRemove(agent, options = {}) {
454
459
  });
455
460
  }
456
461
  }
457
- } else {
458
- const dir = skillInstallDir(agent);
459
- if (!existsSync(dir)) {
460
- steps.push({
461
- step: "skill-missing",
462
- ok: true,
463
- detail: `nothing to remove (${dir})`
464
- });
465
- } else {
466
- try {
467
- rmSync(dir, { recursive: true, force: true });
468
- steps.push({ step: "remove-skill-dir", ok: true, detail: dir });
469
- } catch (err) {
470
- steps.push({
471
- step: "remove-skill-dir",
472
- ok: false,
473
- detail: `${dir}: ${err?.message ?? String(err)}`
474
- });
475
- }
476
- }
477
462
  }
478
463
  try {
479
464
  const r = removeAgent(agent, void 0, {
@@ -530,6 +515,12 @@ function runTeardown(options = {}) {
530
515
  steps.push({ ...s, step: `${agent}:${s.step}` });
531
516
  }
532
517
  }
518
+ const defaultSub = runRemove("default", { keepConfigShell });
519
+ if (defaultSub.steps.some((s) => s.step === "remove-config-section" && s.detail && !s.detail.includes("nothing to remove"))) {
520
+ for (const s of defaultSub.steps) {
521
+ steps.push({ ...s, step: `default:${s.step}` });
522
+ }
523
+ }
533
524
  if (purgeConfig) {
534
525
  const cfgPath = join(homedir(), ".ctxdb", "ctxdb.json");
535
526
  if (existsSync(cfgPath)) {
@@ -811,6 +802,44 @@ function copySkillDir(src, dest, agent) {
811
802
  }
812
803
  }
813
804
  }
805
+ var SKILL_INSTALL_TARGETS = {
806
+ qoder: join(homedir(), ".qoder", "skills"),
807
+ codex: join(homedir(), ".codex", "skills"),
808
+ claude: join(homedir(), ".claude", "skills"),
809
+ openclaw: join(homedir(), ".openclaw", "skills"),
810
+ hermes: join(homedir(), ".hermes", "skills")
811
+ };
812
+ function skillInstallTargetPath(target) {
813
+ return SKILL_INSTALL_TARGETS[target] ?? null;
814
+ }
815
+ function installSkillsTo(targetRoot, agent = "default") {
816
+ const steps = [];
817
+ for (const skillName of SKILL_DIRS) {
818
+ try {
819
+ const srcDir = locateSkillDir(skillName);
820
+ if (srcDir) {
821
+ const dest = join(targetRoot, skillName);
822
+ copySkillDir(srcDir, dest, agent);
823
+ steps.push({ step: "install-skill", ok: true, detail: dest });
824
+ } else {
825
+ steps.push({
826
+ step: "install-skill",
827
+ ok: false,
828
+ detail: `skill source directory not found (${skillName})`
829
+ });
830
+ return { ok: false, steps };
831
+ }
832
+ } catch (err) {
833
+ steps.push({
834
+ step: "install-skill",
835
+ ok: false,
836
+ detail: err?.message ?? String(err)
837
+ });
838
+ return { ok: false, steps };
839
+ }
840
+ }
841
+ return { ok: true, steps };
842
+ }
814
843
  function appendHooks(agent, hookPaths) {
815
844
  const path = hookConfigPath(agent);
816
845
  if (!path) return;
@@ -1065,7 +1094,7 @@ function describeCodexFeatureMutation(status2, createdNew) {
1065
1094
  }
1066
1095
  }
1067
1096
  function runUpgrade(options = {}) {
1068
- const agents = options.agent ? [options.agent] : configuredAgents(options.configPath);
1097
+ const agents = options.agent ? [options.agent] : configuredAgents(options.configPath).filter(isBuiltinAgent);
1069
1098
  if (agents.length === 0) {
1070
1099
  return {
1071
1100
  ok: false,
@@ -1097,24 +1126,19 @@ function upgradeOneAgent(agent) {
1097
1126
  if (!homeCheck.ok) {
1098
1127
  return { ok: true, steps, hints: [`${agent}: skipped \u2014 agent home not found`] };
1099
1128
  }
1100
- try {
1101
- const srcDir = locateSkillDir(skillResourceDir(agent));
1102
- if (srcDir) {
1103
- const dest = skillInstallDir(agent);
1104
- copySkillDir(srcDir, dest, agent);
1105
- steps.push({ step: "install-skill", ok: true, detail: dest });
1106
- } else {
1107
- steps.push({
1108
- step: "install-skill",
1109
- ok: false,
1110
- detail: "skill source directory not found"
1111
- });
1112
- return { ok: false, steps };
1129
+ const root = skillInstallRoot(agent);
1130
+ for (const legacy of LEGACY_SKILL_DIRS) {
1131
+ const legacyDir = join(root, legacy);
1132
+ if (!existsSync(legacyDir)) continue;
1133
+ try {
1134
+ rmSync(legacyDir, { recursive: true, force: true });
1135
+ steps.push({ step: "remove-legacy-skill", ok: true, detail: legacyDir });
1136
+ } catch {
1113
1137
  }
1114
- } catch (err) {
1115
- steps.push({ step: "install-skill", ok: false, detail: err?.message ?? String(err) });
1116
- return { ok: false, steps };
1117
1138
  }
1139
+ const skillResult = installSkillsTo(root, agent);
1140
+ steps.push(...skillResult.steps);
1141
+ if (!skillResult.ok) return { ok: false, steps };
1118
1142
  if (agentSupportsHooks(agent)) {
1119
1143
  const hookPaths = locateHookPaths();
1120
1144
  if (!hookPaths) {
@@ -1157,46 +1181,6 @@ function upgradeOneAgent(agent) {
1157
1181
  }
1158
1182
 
1159
1183
  // src/cli/top.ts
1160
- import { homedir as homedir2 } from "os";
1161
- import { join as join2 } from "path";
1162
- async function init(args) {
1163
- const agent = agentFromFlags(args.flags);
1164
- const cfg = load({ agent });
1165
- if (typeof args.flags["api-key"] === "string") cfg.apiKey = args.flags["api-key"];
1166
- if (typeof args.flags["base-url"] === "string") {
1167
- cfg.baseUrl = args.flags["base-url"].replace(/\/+$/, "");
1168
- } else if (!cfg.baseUrl) {
1169
- cfg.baseUrl = DEFAULT_BASE_URL;
1170
- }
1171
- const cliUserId = typeof args.flags["user-id"] === "string" ? args.flags["user-id"] : void 0;
1172
- cfg.userId = cliUserId ?? cfg.userId ?? DEFAULT_USER_ID;
1173
- save(cfg, void 0, { agent });
1174
- let validated = false;
1175
- let pingError = null;
1176
- if (!args.flags["no-validate"] && cfg.apiKey) {
1177
- try {
1178
- const client = new HttpClient({ baseUrl: cfg.baseUrl, apiKey: cfg.apiKey });
1179
- await client.get("/v1/ping/");
1180
- validated = true;
1181
- } catch (err) {
1182
- pingError = err?.message ?? String(err);
1183
- }
1184
- }
1185
- printResult(
1186
- {
1187
- ok: true,
1188
- config_path: join2(homedir2(), ".ctxdb", "ctxdb.json"),
1189
- agent,
1190
- api_key_set: Boolean(cfg.apiKey),
1191
- base_url: cfg.baseUrl,
1192
- user_id: cfg.userId,
1193
- validated,
1194
- ping_error: pingError
1195
- },
1196
- !!args.flags.json
1197
- );
1198
- return 0;
1199
- }
1200
1184
  async function status(args) {
1201
1185
  const agent = agentFromFlags(args.flags);
1202
1186
  const cfg = load({ agent });
@@ -1212,7 +1196,7 @@ async function status(args) {
1212
1196
  pingError = err?.message ?? String(err);
1213
1197
  }
1214
1198
  }
1215
- const hookHealth = checkHookNodePaths(agent);
1199
+ const hookHealth = isBuiltinAgent(agent) ? checkHookNodePaths(agent) : { installed: false, nodePaths: [], nodeOk: true, detail: "no hooks (default agent)" };
1216
1200
  printResult(
1217
1201
  {
1218
1202
  ok: complete,
@@ -1250,34 +1234,50 @@ async function ping(args) {
1250
1234
  }
1251
1235
 
1252
1236
  // src/cli/setup-cli.ts
1237
+ var HELP = `ctxdb setup \u2014 configure an agent
1238
+
1239
+ USAGE
1240
+ ctxdb setup [--agent <qoder|codex|claude>]
1241
+ [--api-key=K] [--base-url=URL] [--user-id=ID]
1242
+ [--no-install-skill] [--no-validate] [--json]
1243
+
1244
+ BEHAVIOR
1245
+ Without --agent Writes agents.default in ~/.ctxdb/ctxdb.json (CLI-only,
1246
+ no hooks or skills installed).
1247
+ With --agent Writes agent config + installs hooks + skills for the
1248
+ specified agent harness.
1249
+
1250
+ FLAGS
1251
+ --agent <name> Target agent (qoder|codex|claude)
1252
+ --api-key <key> API key (required on first setup)
1253
+ --base-url <url> Server URL (default: https://context-database.aliyuncs.com)
1254
+ --user-id <id> User bucket (default: "default")
1255
+ --no-install-skill Skip skill directory installation
1256
+ --no-validate Skip connectivity ping after setup
1257
+ --json Machine-readable JSON output
1258
+ `;
1253
1259
  function parseAgent(args) {
1254
1260
  const raw = args.flags.agent;
1255
- if (typeof raw !== "string") return null;
1256
- return SUPPORTED_AGENTS.includes(raw) ? raw : null;
1257
- }
1258
- function agentError() {
1261
+ if (raw === void 0) return "default";
1262
+ if (typeof raw === "string" && isAgentSlug(raw)) return raw;
1259
1263
  process.stderr.write(
1260
- `ctxdb setup: --agent <${SUPPORTED_AGENTS.join("|")}> is required
1261
- e.g. ctxdb setup --agent qoder --api-key=<key>
1264
+ `unknown --agent: ${String(raw)} (expected one of ${SUPPORTED_AGENTS.join(" / ")} / default)
1262
1265
  `
1263
1266
  );
1267
+ process.exit(2);
1264
1268
  }
1265
1269
  async function setup(args) {
1266
- const agent = parseAgent(args);
1267
- if (!agent) {
1268
- agentError();
1269
- return 2;
1270
+ if (args.flags.help || args.flags.h) {
1271
+ process.stdout.write(HELP);
1272
+ return 0;
1270
1273
  }
1274
+ const agent = parseAgent(args);
1271
1275
  const json = !!args.flags.json;
1272
- if (args.flags.remove) {
1273
- const result2 = runRemove(agent);
1274
- printResult(result2, json);
1275
- if (!json && result2.hints) {
1276
- for (const h of result2.hints) process.stderr.write(`
1277
- ${h}
1278
- `);
1279
- }
1280
- return result2.ok ? 0 : 1;
1276
+ if (agent === "default" && !json && !configuredAgents().includes("default")) {
1277
+ process.stderr.write(
1278
+ `hint: creating CLI-only "default" agent (no hooks/skills). Use --agent <claude|qoder|codex> for full setup.
1279
+ `
1280
+ );
1281
1281
  }
1282
1282
  const result = await runSetup({
1283
1283
  agent,
@@ -1297,14 +1297,61 @@ ${h}
1297
1297
  }
1298
1298
 
1299
1299
  // src/cli/teardown.ts
1300
- function teardown(args) {
1300
+ var HELP2 = `ctxdb uninstall \u2014 remove hooks + skills
1301
+
1302
+ USAGE
1303
+ ctxdb uninstall [--agent <qoder|codex|claude>] [--purge-config] [--purge-logs] [--purge-all] [--json]
1304
+
1305
+ DEFAULT BEHAVIOR (no --agent, no purge flags)
1306
+ Strips ctxdb hook entries and skill directories for ALL agents (qoder,
1307
+ codex, claude). Keeps ~/.ctxdb/ctxdb.json (credentials) and ~/.ctxdb/logs/
1308
+ so you can re-run \`ctxdb setup\` later without losing configuration.
1309
+
1310
+ WITH --agent <name>
1311
+ Only removes hooks + skill + config section for the specified agent.
1312
+ Other agents remain untouched.
1313
+
1314
+ FLAGS
1315
+ --agent <name> Target a single agent (qoder|codex|claude)
1316
+ --purge-config Also delete ~/.ctxdb/ctxdb.json
1317
+ --purge-logs Also delete ~/.ctxdb/logs/
1318
+ --purge-all Delete everything under ~/.ctxdb/
1319
+ --json Machine-readable JSON output
1320
+
1321
+ NOTE
1322
+ Does not run \`npm uninstall\`. After uninstall completes, remove the
1323
+ npm package separately: npm uninstall -g @aliyunrds/ctxdb @aliyunrds/ctxdb-shared
1324
+ `;
1325
+ function uninstall(args) {
1326
+ if (args.flags.help || args.flags.h) {
1327
+ process.stdout.write(HELP2);
1328
+ return 0;
1329
+ }
1330
+ const json = !!args.flags.json;
1331
+ const agentFlag = args.flags.agent;
1332
+ if (agentFlag !== void 0) {
1333
+ if (!isAgentSlug(agentFlag)) {
1334
+ process.stderr.write(
1335
+ `unknown --agent: ${String(agentFlag)} (expected one of ${SUPPORTED_AGENTS.join(" / ")} / default)
1336
+ `
1337
+ );
1338
+ return 2;
1339
+ }
1340
+ const result2 = runRemove(agentFlag);
1341
+ printResult(result2, json, `uninstall --agent ${agentFlag}`);
1342
+ if (!json && result2.hints) {
1343
+ for (const h of result2.hints) process.stderr.write(`
1344
+ ${h}
1345
+ `);
1346
+ }
1347
+ return result2.ok ? 0 : 1;
1348
+ }
1301
1349
  const result = runTeardown({
1302
1350
  purgeConfig: !!args.flags["purge-config"],
1303
1351
  purgeLogs: !!args.flags["purge-logs"],
1304
1352
  purgeAll: !!args.flags["purge-all"]
1305
1353
  });
1306
- const json = !!args.flags.json;
1307
- printResult(result, json);
1354
+ printResult(result, json, "uninstall");
1308
1355
  if (!json && result.hints) {
1309
1356
  for (const h of result.hints) process.stderr.write(`
1310
1357
  ${h}
@@ -1313,10 +1360,145 @@ ${h}
1313
1360
  return result.ok ? 0 : 1;
1314
1361
  }
1315
1362
 
1363
+ // src/lib/self-update.ts
1364
+ import { spawnSync } from "child_process";
1365
+ import { fileURLToPath as fileURLToPath2 } from "url";
1366
+ import { dirname as dirname2 } from "path";
1367
+ var PACKAGE_NAME = "@aliyunrds/ctxdb";
1368
+ var NPM_VIEW_TIMEOUT_MS = 1e4;
1369
+ function detectInstallMethod() {
1370
+ const dir = dirname2(fileURLToPath2(import.meta.url));
1371
+ return dir.includes("/node_modules/") ? "npm" : "unknown";
1372
+ }
1373
+ function checkLatestVersion(currentVersion) {
1374
+ const result = spawnSync("npm", ["view", PACKAGE_NAME, "version"], {
1375
+ encoding: "utf-8",
1376
+ timeout: NPM_VIEW_TIMEOUT_MS,
1377
+ stdio: ["ignore", "pipe", "pipe"]
1378
+ });
1379
+ if (result.status !== 0 || !result.stdout?.trim()) {
1380
+ return {
1381
+ latest: null,
1382
+ current: currentVersion,
1383
+ isNewer: false,
1384
+ error: result.stderr?.trim() || `npm view exited with code ${result.status}`
1385
+ };
1386
+ }
1387
+ const latest = result.stdout.trim();
1388
+ return {
1389
+ latest,
1390
+ current: currentVersion,
1391
+ isNewer: isNewerVersion(latest, currentVersion)
1392
+ };
1393
+ }
1394
+ function isNewerVersion(candidate, current) {
1395
+ const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
1396
+ const [cMaj = 0, cMin = 0, cPat = 0] = parse(candidate);
1397
+ const [uMaj = 0, uMin = 0, uPat = 0] = parse(current);
1398
+ if (cMaj !== uMaj) return cMaj > uMaj;
1399
+ if (cMin !== uMin) return cMin > uMin;
1400
+ return cPat > uPat;
1401
+ }
1402
+ function runSelfUpdate(currentVersion, passthroughArgs = []) {
1403
+ const method = detectInstallMethod();
1404
+ if (method !== "npm") {
1405
+ return {
1406
+ ok: false,
1407
+ updated: false,
1408
+ fromVersion: currentVersion,
1409
+ error: "ctxdb was not installed via npm. Update manually with your package manager."
1410
+ };
1411
+ }
1412
+ const check = checkLatestVersion(currentVersion);
1413
+ if (check.error) {
1414
+ process.stderr.write(`ctxdb: npm view failed (${check.error}), skipping self-update
1415
+ `);
1416
+ return { ok: true, updated: false, fromVersion: currentVersion };
1417
+ }
1418
+ if (!check.isNewer) {
1419
+ process.stderr.write(`ctxdb: package already up to date (v${currentVersion})
1420
+ `);
1421
+ return { ok: true, updated: false, fromVersion: currentVersion };
1422
+ }
1423
+ process.stderr.write(`ctxdb: updating ${PACKAGE_NAME} v${currentVersion} \u2192 v${check.latest}...
1424
+ `);
1425
+ const install = spawnSync("npm", ["install", "-g", `${PACKAGE_NAME}@${check.latest}`], {
1426
+ stdio: "inherit",
1427
+ encoding: "utf-8"
1428
+ });
1429
+ if (install.status !== 0) {
1430
+ return {
1431
+ ok: false,
1432
+ updated: false,
1433
+ fromVersion: currentVersion,
1434
+ error: `npm install -g failed with exit code ${install.status}`
1435
+ };
1436
+ }
1437
+ process.stderr.write(`ctxdb: package updated to v${check.latest}, refreshing skills/hooks...
1438
+ `);
1439
+ const upgradeArgs = ["upgrade", ...passthroughArgs];
1440
+ const refresh = spawnSync("ctxdb", upgradeArgs, {
1441
+ stdio: "inherit",
1442
+ encoding: "utf-8"
1443
+ });
1444
+ return {
1445
+ ok: refresh.status === 0,
1446
+ updated: true,
1447
+ fromVersion: currentVersion,
1448
+ toVersion: check.latest,
1449
+ error: refresh.status !== 0 ? `ctxdb upgrade exited with code ${refresh.status}` : void 0
1450
+ };
1451
+ }
1452
+
1316
1453
  // src/cli/upgrade.ts
1454
+ var HELP3 = `ctxdb upgrade \u2014 refresh skills + hooks
1455
+
1456
+ USAGE
1457
+ ctxdb upgrade [--agent <qoder|codex|claude>] [--self-update] [--json]
1458
+
1459
+ BEHAVIOR
1460
+ Refreshes skill files and hook entries for configured agents without
1461
+ touching credentials. Designed for the post-npm-update path.
1462
+
1463
+ Without --agent: upgrades all configured built-in agents.
1464
+ With --agent: upgrades only the specified agent.
1465
+
1466
+ FLAGS
1467
+ --agent <name> Target a single agent (qoder|codex|claude)
1468
+ --self-update Also pull latest package from npm before upgrading
1469
+ --json Machine-readable JSON output
1470
+ `;
1317
1471
  function upgrade(args) {
1472
+ if (args.flags.help || args.flags.h) {
1473
+ process.stdout.write(HELP3);
1474
+ return 0;
1475
+ }
1476
+ const selfUpdate = !!args.flags["self-update"];
1477
+ if (selfUpdate) {
1478
+ const passthrough = [];
1479
+ const agentFlag2 = args.flags.agent;
1480
+ if (agentFlag2) passthrough.push("--agent", agentFlag2);
1481
+ if (args.flags.json) passthrough.push("--json");
1482
+ const result2 = runSelfUpdate(PACKAGE_VERSION, passthrough);
1483
+ if (!result2.ok) {
1484
+ if (args.flags.json) {
1485
+ process.stdout.write(JSON.stringify({ ok: false, error: result2.error }) + "\n");
1486
+ } else {
1487
+ process.stderr.write(`ctxdb: self-update failed: ${result2.error}
1488
+ `);
1489
+ }
1490
+ return 1;
1491
+ }
1492
+ if (result2.updated) {
1493
+ return 0;
1494
+ }
1495
+ }
1318
1496
  const agentFlag = args.flags.agent;
1319
- if (agentFlag && !isAgent(agentFlag)) {
1497
+ if (agentFlag === "default") {
1498
+ process.stderr.write("nothing to upgrade for default agent (no hooks or skills)\n");
1499
+ return 0;
1500
+ }
1501
+ if (agentFlag && !isBuiltinAgent(agentFlag)) {
1320
1502
  process.stderr.write(
1321
1503
  `unknown --agent: ${agentFlag} (expected one of ${SUPPORTED_AGENTS.join(" / ")})
1322
1504
  `
@@ -1324,6 +1506,12 @@ function upgrade(args) {
1324
1506
  return 2;
1325
1507
  }
1326
1508
  const result = runUpgrade({ agent: agentFlag });
1509
+ if (result.ok) {
1510
+ try {
1511
+ writeInstalledPkgVersion(PACKAGE_VERSION);
1512
+ } catch {
1513
+ }
1514
+ }
1327
1515
  const json = !!args.flags.json;
1328
1516
  printResult(result, json);
1329
1517
  if (!json && result.hints) {
@@ -1334,238 +1522,69 @@ ${h}
1334
1522
  return result.ok ? 0 : 1;
1335
1523
  }
1336
1524
 
1337
- // src/lib/kb.ts
1338
- import { readFileSync as readFileSync2, existsSync as existsSync2, statSync as statSync2 } from "fs";
1339
- import { basename, extname } from "path";
1340
- import { homedir as homedir3 } from "os";
1341
- import { resolve } from "path";
1342
- var KB_COLLECTION = "/v1/knowledge/knowledge_bases";
1343
- var DOCUMENTS = "/v1/knowledge/documents";
1344
- var FILES = "/v1/knowledge/files";
1345
- var DOCUMENT_DETAIL = "/v1/knowledge/documents/detail";
1346
- async function tryNewThenLegacy(newPath, newCall, legacyCall) {
1347
- try {
1348
- return await newCall();
1349
- } catch (err) {
1350
- if (err instanceof NotFoundError && err.path === newPath) {
1351
- return legacyCall();
1352
- }
1353
- throw err;
1354
- }
1355
- }
1356
- var DEFAULT_POLL_INTERVAL_MS = 1500;
1357
- var DEFAULT_INGEST_TIMEOUT_MS = 3e4;
1358
- var DEFAULT_FILE_INGEST_TIMEOUT_MS = 6e4;
1359
- var MIME_BY_EXT = {
1360
- ".pdf": "application/pdf",
1361
- ".txt": "text/plain",
1362
- ".md": "text/markdown",
1363
- ".markdown": "text/markdown",
1364
- ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1365
- ".doc": "application/msword",
1366
- ".html": "text/html",
1367
- ".htm": "text/html",
1368
- ".json": "application/json",
1369
- ".csv": "text/csv",
1370
- ".xml": "application/xml",
1371
- ".rtf": "application/rtf"
1372
- };
1373
- async function listKnowledgeBases(client) {
1374
- const resp = await client.get(KB_COLLECTION);
1375
- if (Array.isArray(resp)) return resp;
1376
- if (resp && typeof resp === "object") {
1377
- const o = resp;
1378
- return o.knowledge_bases ?? o.results ?? [];
1525
+ // src/cli/skill-cli.ts
1526
+ var SUPPORTED_TARGETS = Object.keys(SKILL_INSTALL_TARGETS);
1527
+ var HELP4 = `ctxdb skill install \u2014 install skill files to an agent's skill directory
1528
+
1529
+ USAGE
1530
+ ctxdb skill install --target <name>
1531
+ ctxdb skill install --path <dir>
1532
+
1533
+ TARGETS
1534
+ ${SUPPORTED_TARGETS.join(", ")}
1535
+
1536
+ FLAGS
1537
+ --target <name> Install to a built-in agent's skill directory
1538
+ --path <dir> Install to a custom directory
1539
+ --json Machine-readable JSON output
1540
+
1541
+ Note: This command only installs skill files (contextdb, contextdb-memory,
1542
+ contextdb-knowledge). It does not write config or install hooks.
1543
+ To configure server connection, run: ctxdb setup --api-key=<key> --base-url=<url>
1544
+ `;
1545
+ function skillInstall(args) {
1546
+ if (args.flags.help || args.flags.h) {
1547
+ process.stdout.write(HELP4);
1548
+ return 0;
1379
1549
  }
1380
- return [];
1381
- }
1382
- async function findKb(client, kbNameOrId) {
1383
- for (const kb of await listKnowledgeBases(client)) {
1384
- if (!kb || typeof kb !== "object") continue;
1385
- if (kb.name === kbNameOrId || kb.id === kbNameOrId) return kb;
1550
+ const json = !!args.flags.json;
1551
+ const target = args.flags.target;
1552
+ const customPath = args.flags.path;
1553
+ if (target && customPath) {
1554
+ fail("--target and --path are mutually exclusive", 2);
1386
1555
  }
1387
- return null;
1388
- }
1389
- async function createKb(client, kbName, description = "") {
1390
- return client.postJson(KB_COLLECTION, { name: kbName, description: description || "" });
1391
- }
1392
- async function findOrCreateKb(client, kbName, description = "") {
1393
- const existing = await findKb(client, kbName);
1394
- if (existing) return { kb: existing, created: false };
1395
- const created = await createKb(client, kbName, description);
1396
- return { kb: created, created: true };
1397
- }
1398
- async function uploadText(client, kbId, docName, text, mimeType = "text/plain", filePath) {
1399
- const body = {
1400
- knowledge_base_id: kbId,
1401
- name: docName,
1402
- text,
1403
- mime_type: mimeType
1404
- };
1405
- if (filePath !== void 0 && filePath !== "") body.file_path = filePath;
1406
- return tryNewThenLegacy(
1407
- DOCUMENTS,
1408
- () => client.postJson(DOCUMENTS, body),
1409
- () => {
1410
- const { knowledge_base_id: _drop, ...legacyBody } = body;
1411
- return client.postJson(`${KB_COLLECTION}/${encodeURIComponent(kbId)}/documents`, legacyBody);
1412
- }
1413
- );
1414
- }
1415
- async function uploadFile(client, kbId, localPath, options = {}) {
1416
- const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
1417
- const expanded = expandHome(localPath);
1418
- if (!existsSync2(expanded)) throw new Error(`file not found: ${expanded}`);
1419
- const stat = statSync2(expanded);
1420
- if (!stat.isFile()) throw new Error(`not a file: ${expanded}`);
1421
- if (stat.size > MAX_UPLOAD_BYTES) {
1422
- throw new Error(
1423
- `file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB), maximum is 100 MB`
1424
- );
1556
+ if (!target && !customPath) {
1557
+ process.stderr.write(HELP4);
1558
+ return 2;
1425
1559
  }
1426
- const filename = basename(expanded);
1427
- const docName = options.docName ?? filename;
1428
- const content = readFileSync2(expanded);
1429
- const mime = guessMime(expanded);
1430
- const fields = {
1431
- knowledge_base_id: kbId,
1432
- name: docName
1433
- };
1434
- if (options.filePath !== void 0 && options.filePath !== "") {
1435
- fields.file_path = options.filePath;
1436
- }
1437
- const uploadOpts = { timeoutMs: options.timeoutMs };
1438
- return tryNewThenLegacy(
1439
- FILES,
1440
- () => client.postMultipart(
1441
- FILES,
1442
- fields,
1443
- { file: { filename, content, mimeType: mime } },
1444
- uploadOpts
1445
- ),
1446
- () => {
1447
- const { knowledge_base_id: _drop, ...legacyFields } = fields;
1448
- return client.postMultipart(
1449
- `${KB_COLLECTION}/${encodeURIComponent(kbId)}/files`,
1450
- legacyFields,
1451
- { file: { filename, content, mimeType: mime } },
1452
- uploadOpts
1560
+ let targetRoot;
1561
+ if (customPath) {
1562
+ targetRoot = customPath;
1563
+ } else {
1564
+ const resolved = skillInstallTargetPath(target);
1565
+ if (!resolved) {
1566
+ fail(
1567
+ `unknown --target: ${target} (supported: ${SUPPORTED_TARGETS.join(", ")})`,
1568
+ 2
1453
1569
  );
1454
1570
  }
1455
- );
1456
- }
1457
- function guessMime(path) {
1458
- const ext = extname(path).toLowerCase();
1459
- if (ext in MIME_BY_EXT) return MIME_BY_EXT[ext];
1460
- return "application/octet-stream";
1461
- }
1462
- async function getDocument(client, kbId, docId) {
1463
- return tryNewThenLegacy(
1464
- DOCUMENT_DETAIL,
1465
- () => client.get(DOCUMENT_DETAIL, {
1466
- knowledge_base_id: kbId,
1467
- document_id: docId
1468
- }),
1469
- () => client.get(
1470
- `${KB_COLLECTION}/${encodeURIComponent(kbId)}/documents/${encodeURIComponent(docId)}`
1471
- )
1472
- );
1473
- }
1474
- async function listDocuments(client, kbId) {
1475
- const resp = await tryNewThenLegacy(
1476
- DOCUMENTS,
1477
- () => client.get(DOCUMENTS, { knowledge_base_id: kbId }),
1478
- () => client.get(`${KB_COLLECTION}/${encodeURIComponent(kbId)}/documents`)
1479
- );
1480
- if (Array.isArray(resp)) return resp;
1481
- if (resp && typeof resp === "object") {
1482
- const o = resp;
1483
- return o.documents ?? o.results ?? [];
1484
- }
1485
- return [];
1486
- }
1487
- async function pollIngest(client, kbId, docId, options = {}) {
1488
- const timeoutMs = options.timeoutMs ?? DEFAULT_INGEST_TIMEOUT_MS;
1489
- const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
1490
- const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
1491
- const now = options.now ?? (() => performance.now());
1492
- const deadline = now() + timeoutMs;
1493
- let doc = await getDocument(client, kbId, docId);
1494
- let timedOut = false;
1495
- while (ingestInFlight(doc)) {
1496
- if (now() >= deadline) {
1497
- timedOut = true;
1498
- break;
1499
- }
1500
- await sleep(intervalMs);
1501
- try {
1502
- doc = await getDocument(client, kbId, docId);
1503
- } catch {
1504
- break;
1505
- }
1506
- }
1507
- if (doc && typeof doc === "object") {
1508
- return { ...doc, _pollingTimedOut: timedOut };
1509
- }
1510
- return { _pollingTimedOut: timedOut };
1511
- }
1512
- function ingestInFlight(doc) {
1513
- if (!doc || typeof doc !== "object") return false;
1514
- const status2 = doc.ingest_status;
1515
- return status2 === "processing" || status2 === "pending" || status2 === "in_progress";
1516
- }
1517
- function expandHome(p) {
1518
- if (p.startsWith("~/") || p === "~") {
1519
- return resolve(homedir3(), p.slice(2));
1520
- }
1521
- return resolve(p);
1522
- }
1523
- function compactChunk(raw) {
1524
- const c = raw ?? {};
1525
- const content = c.content_with_weight ?? c.content ?? c.chunk ?? c.text ?? "";
1526
- const doc_name = c.docnm_kwd ?? c.doc_name ?? c.docnm ?? "";
1527
- const kbRaw = c.kb_id ?? c.dataset_id;
1528
- const kb_id = Array.isArray(kbRaw) ? typeof kbRaw[0] === "string" ? kbRaw[0] : "" : typeof kbRaw === "string" ? kbRaw : "";
1529
- const docIdRaw = c.doc_id;
1530
- const scoreRaw = c.similarity ?? c.rerank_score;
1531
- const tagsRaw = c.tag_kwd;
1532
- const out = {
1533
- content,
1534
- doc_name,
1535
- kb_id,
1536
- score: typeof scoreRaw === "number" ? scoreRaw : 0
1571
+ targetRoot = resolved;
1572
+ }
1573
+ const result = installSkillsTo(targetRoot);
1574
+ const ok = result.ok;
1575
+ const output = {
1576
+ ok,
1577
+ target: target ?? null,
1578
+ path: targetRoot,
1579
+ steps: result.steps
1537
1580
  };
1538
- if (typeof docIdRaw === "string" && docIdRaw.length > 0) {
1539
- out.doc_id = docIdRaw;
1540
- }
1541
- if (Array.isArray(tagsRaw) && tagsRaw.length > 0) {
1542
- out.tags = tagsRaw;
1581
+ printResult(output, json);
1582
+ if (ok && !json) {
1583
+ process.stderr.write(
1584
+ "\nNote: This command only installs skill files.\nTo configure server connection, run:\n ctxdb setup --api-key=<key> --base-url=<url>\n"
1585
+ );
1543
1586
  }
1544
- return out;
1545
- }
1546
- function compactKbQueryResponse(raw) {
1547
- if (!raw || typeof raw !== "object") return { chunks: [] };
1548
- const r = raw;
1549
- const chunksIn = Array.isArray(r.chunks) ? r.chunks : [];
1550
- const out = {
1551
- chunks: chunksIn.map(compactChunk)
1552
- };
1553
- if (typeof r.total === "number") out.total = r.total;
1554
- return out;
1555
- }
1556
- function minimalChunk(raw) {
1557
- const c = compactChunk(raw);
1558
- return { content: c.content, score: c.score };
1559
- }
1560
- function minimalKbQueryResponse(raw) {
1561
- if (!raw || typeof raw !== "object") return { chunks: [] };
1562
- const r = raw;
1563
- const chunksIn = Array.isArray(r.chunks) ? r.chunks : [];
1564
- const out = {
1565
- chunks: chunksIn.map(minimalChunk)
1566
- };
1567
- if (typeof r.total === "number") out.total = r.total;
1568
- return out;
1587
+ return ok ? 0 : 1;
1569
1588
  }
1570
1589
 
1571
1590
  // src/cli/memory.ts
@@ -1698,25 +1717,20 @@ async function kbUploadText(args) {
1698
1717
  const text = args.flags["text"];
1699
1718
  if (!kbName || !docName || typeof text !== "string") {
1700
1719
  fail(
1701
- "usage: ctxdb kb upload-text <kb-name> <doc-name> --text=<body> [--kb-description=...] [--file-path=<server-logical-path>] [--no-wait]"
1720
+ "usage: ctxdb kb upload-text <kb-name> <doc-name> --text=<body> [--file-path=<server-logical-path>] [--no-wait]"
1702
1721
  );
1703
1722
  }
1704
1723
  const ctx = buildContext(args);
1705
- const description = args.flags["kb-description"] ?? "";
1706
1724
  const filePath = typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0;
1707
- const { kb, created } = await findOrCreateKb(ctx.client, kbName, description);
1708
- const doc = await uploadText(ctx.client, kb.id, docName, text, "text/plain", filePath);
1725
+ const doc = await uploadText(ctx.client, kbName, docName, text, "text/plain", filePath);
1709
1726
  if (args.flags["no-wait"]) {
1710
- printResult({ kb, kb_created: created, document: doc }, !!args.flags.json);
1727
+ printResult({ document: doc }, !!args.flags.json);
1711
1728
  return 0;
1712
1729
  }
1713
- const final = await pollIngest(ctx.client, kb.id, doc.id, {
1730
+ const final = await pollIngest(ctx.client, kbName, doc.id, {
1714
1731
  timeoutMs: DEFAULT_INGEST_TIMEOUT_MS
1715
1732
  });
1716
- printResult(
1717
- { kb, kb_created: created, document: final },
1718
- !!args.flags.json
1719
- );
1733
+ printResult({ document: final }, !!args.flags.json);
1720
1734
  return 0;
1721
1735
  }
1722
1736
  async function kbUploadFile(args) {
@@ -1727,23 +1741,28 @@ async function kbUploadFile(args) {
1727
1741
  );
1728
1742
  }
1729
1743
  const ctx = buildContext(args);
1730
- const { kb, created } = await findOrCreateKb(ctx.client, kbName);
1731
- const doc = await uploadFile(ctx.client, kb.id, localPath, {
1744
+ const doc = await uploadFile(ctx.client, kbName, localPath, {
1732
1745
  docName: typeof args.flags["doc-name"] === "string" ? args.flags["doc-name"] : void 0,
1733
1746
  filePath: typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0,
1734
1747
  timeoutMs: DEFAULT_FILE_INGEST_TIMEOUT_MS
1735
1748
  });
1736
1749
  if (args.flags["no-wait"]) {
1737
- printResult({ kb, kb_created: created, document: doc }, !!args.flags.json);
1750
+ printResult({ document: doc }, !!args.flags.json);
1738
1751
  return 0;
1739
1752
  }
1740
- const final = await pollIngest(ctx.client, kb.id, doc.id, {
1753
+ const final = await pollIngest(ctx.client, kbName, doc.id, {
1741
1754
  timeoutMs: DEFAULT_FILE_INGEST_TIMEOUT_MS
1742
1755
  });
1743
- printResult(
1744
- { kb, kb_created: created, document: final },
1745
- !!args.flags.json
1746
- );
1756
+ printResult({ document: final }, !!args.flags.json);
1757
+ return 0;
1758
+ }
1759
+ async function kbCreate(args) {
1760
+ const kbName = args.positional[0];
1761
+ if (!kbName) fail("usage: ctxdb kb create <kb-name> [--description=<desc>]");
1762
+ const ctx = buildContext(args);
1763
+ const description = typeof args.flags["description"] === "string" ? args.flags["description"] : "";
1764
+ const kb = await createKb(ctx.client, kbName, description);
1765
+ printResult(kb, !!args.flags.json);
1747
1766
  return 0;
1748
1767
  }
1749
1768
  async function kbList(args) {
@@ -1753,28 +1772,24 @@ async function kbList(args) {
1753
1772
  return 0;
1754
1773
  }
1755
1774
  async function kbDocumentsList(args) {
1756
- const kbNameOrId = args.positional[0];
1757
- if (!kbNameOrId) fail("usage: ctxdb kb documents-list <kb-name-or-id>");
1775
+ const kbName = args.positional[0];
1776
+ if (!kbName) fail("usage: ctxdb kb documents-list <kb-name>");
1758
1777
  const ctx = buildContext(args);
1759
- const kb = await findKb(ctx.client, kbNameOrId);
1760
- if (!kb) fail(`KB not found: ${kbNameOrId}`);
1761
- const docs = await listDocuments(ctx.client, kb.id);
1778
+ const docs = await listDocuments(ctx.client, kbName);
1762
1779
  printResult(
1763
- { kb, documents: docs, count: docs.length },
1780
+ { documents: docs, count: docs.length },
1764
1781
  !!args.flags.json
1765
1782
  );
1766
1783
  return 0;
1767
1784
  }
1768
1785
  async function kbDocumentGet(args) {
1769
- const [kbNameOrId, docId] = args.positional;
1770
- if (!kbNameOrId || !docId) {
1771
- fail("usage: ctxdb kb document-get <kb-name-or-id> <doc-id>");
1786
+ const [kbName, docId] = args.positional;
1787
+ if (!kbName || !docId) {
1788
+ fail("usage: ctxdb kb document-get <kb-name> <doc-id>");
1772
1789
  }
1773
1790
  const ctx = buildContext(args);
1774
- const kb = await findKb(ctx.client, kbNameOrId);
1775
- if (!kb) fail(`KB not found: ${kbNameOrId}`);
1776
- const doc = await getDocument(ctx.client, kb.id, docId);
1777
- printResult({ kb, document: doc }, !!args.flags.json);
1791
+ const doc = await getDocument(ctx.client, kbName, docId);
1792
+ printResult({ document: doc }, !!args.flags.json);
1778
1793
  return 0;
1779
1794
  }
1780
1795
  async function kbSearch(args) {
@@ -1785,16 +1800,10 @@ async function kbSearch(args) {
1785
1800
  );
1786
1801
  }
1787
1802
  const ctx = buildContext(args);
1788
- const kbList2 = (typeof args.flags.kb === "string" ? args.flags.kb : "").split(",").map((s) => s.trim()).filter(Boolean);
1789
- const datasetIds = [];
1790
- for (const name of kbList2) {
1791
- const kb = await findKb(ctx.client, name);
1792
- if (!kb) fail(`KB not found: ${name}`);
1793
- datasetIds.push(kb.id);
1794
- }
1803
+ const kbNames = (typeof args.flags.kb === "string" ? args.flags.kb : "").split(",").map((s) => s.trim()).filter(Boolean);
1795
1804
  const body = {
1796
1805
  question: query,
1797
- dataset_ids: datasetIds
1806
+ knowledge_base_names: kbNames
1798
1807
  };
1799
1808
  if (typeof args.flags["top-k"] === "string") {
1800
1809
  const n = Number(args.flags["top-k"]);
@@ -1811,31 +1820,32 @@ async function kbSearch(args) {
1811
1820
  }
1812
1821
 
1813
1822
  // src/cli/main.ts
1814
- var HELP = `ctxdb v${PACKAGE_VERSION} \u2014 RDS ContextDatabase CLI (multi-agent)
1823
+ var HELP5 = `ctxdb v${PACKAGE_VERSION} \u2014 RDS ContextDatabase CLI (multi-agent)
1815
1824
 
1816
1825
  USAGE
1817
1826
  ctxdb <command> [args] [--flags]
1818
1827
 
1819
1828
  COMMANDS
1820
- init [--agent <qoder|codex|claude>] [--api-key=K] [--base-url=URL] [--user-id=ID] [--no-validate]
1821
- status [--agent <qoder|codex|claude>] [--json]
1822
- ping [--agent <qoder|codex|claude>] [--json]
1823
- setup --agent <qoder|codex|claude>
1829
+ setup [--agent <qoder|codex|claude>]
1824
1830
  [--api-key=K] [--base-url=URL] [--user-id=ID]
1825
- [--no-install-skill] [--no-validate] [--json] [--remove]
1831
+ [--no-install-skill] [--no-validate] [--json]
1832
+ Without --agent \u2192 writes agents.default (CLI-only, no hooks/skills)
1826
1833
  Qoder \u2192 writes agents.qoder config + ~/.qoder/settings.json hooks + skill
1827
1834
  Codex \u2192 writes agents.codex config + ~/.codex/hooks.json hooks + skill
1828
1835
  Claude \u2192 writes agents.claude config + ~/.claude/settings.json hooks + skill
1829
- teardown [--purge-config] [--purge-logs] [--purge-all] [--json]
1830
- Removes hooks + skills for every agent.
1836
+ status [--agent <name>] [--json]
1837
+ ping [--agent <name>] [--json]
1838
+ uninstall [--agent <name>] [--purge-config] [--purge-logs] [--purge-all] [--json]
1839
+ teardown (alias for uninstall)
1840
+ Without --agent: removes hooks + skills for every agent.
1841
+ With --agent: removes only that agent's hooks + skill + config section.
1831
1842
  --purge-config also deletes ~/.ctxdb/ctxdb.json.
1832
1843
  --purge-logs also deletes ~/.ctxdb/logs/.
1833
1844
  --purge-all deletes everything under ~/.ctxdb/
1834
1845
  (does not run npm uninstall).
1835
- upgrade [--agent <name>] [--json]
1836
- Refresh skills + hooks for configured agents.
1837
- Run after npm update -g @aliyunrds/ctxdb.
1838
- Does not modify credentials.
1846
+ upgrade|update [--agent <name>] [--self-update] [--json]
1847
+ Refresh skills + hooks. --self-update also
1848
+ pulls latest package from npm.
1839
1849
 
1840
1850
  memory add <text> [--agent=<name>] [--user-id=...] [--metadata=K1=V1,K2=V2] [--no-infer]
1841
1851
  memory search <query> [--agent=<name>] [--top-k=10] [--threshold=0.4] [--knowledge]
@@ -1849,13 +1859,14 @@ COMMANDS
1849
1859
  memory update <memory-id> --text=<new-text> [--agent=<name>]
1850
1860
  memory delete <memory-id> | --all [--agent=<name>]
1851
1861
 
1862
+ kb create <kb-name> [--description=<desc>] [--agent=<name>]
1852
1863
  kb upload-text <kb-name> <doc-name> --text=<body> [--agent=<name>]
1853
- [--kb-description=...] [--file-path=<server-logical-path>] [--no-wait]
1864
+ [--file-path=<server-logical-path>] [--no-wait]
1854
1865
  kb upload-file <kb-name> <local-path> [--agent=<name>] [--doc-name=...]
1855
1866
  [--file-path=<server-logical-path>] [--no-wait]
1856
1867
  kb list [--agent=<name>]
1857
- kb documents-list <kb-name-or-id> [--agent=<name>]
1858
- kb document-get <kb-name-or-id> <doc-id> [--agent=<name>]
1868
+ kb documents-list <kb-name> [--agent=<name>]
1869
+ kb document-get <kb-name> <doc-id> [--agent=<name>]
1859
1870
  kb search <query> [--agent=<name>] [--kb=name1,name2] [--top-k=N] [--threshold=F]
1860
1871
  [--verbose] [--raw]
1861
1872
  Standalone KB recall (independent of memory).
@@ -1864,19 +1875,33 @@ COMMANDS
1864
1875
  doc_id?/tags? for citation.
1865
1876
  --raw: server response verbatim (curl parity).
1866
1877
 
1878
+ skill install --target <qoder|codex|claude|openclaw|hermes> [--json]
1879
+ skill install --path <dir> [--json]
1880
+ Install skill files (contextdb-memory,
1881
+ contextdb-knowledge) to an agent's skill directory.
1882
+ Does not write config or install hooks.
1883
+
1884
+ AGENT RESOLUTION (when --agent is omitted):
1885
+ 1. CTXDB_AGENT env var
1886
+ 2. agents.default section in ~/.ctxdb/ctxdb.json
1887
+
1867
1888
  ENV VARS (override selected ~/.ctxdb/ctxdb.json agent config):
1868
1889
  CTXDB_AGENT CTXDB_API_KEY CTXDB_BASE_URL CTXDB_USER_ID
1869
1890
 
1870
- CONFIG: ~/.ctxdb/ctxdb.json (agents.qoder / agents.codex / agents.claude)
1891
+ CONFIG: ~/.ctxdb/ctxdb.json (agents.default / agents.qoder / agents.codex / agents.claude)
1871
1892
  LOGS: ~/.ctxdb/logs/ctxdb.log
1872
1893
  `;
1873
1894
  var ROUTES = {
1874
- init,
1895
+ setup,
1875
1896
  status,
1876
1897
  ping,
1877
- setup,
1878
- teardown,
1898
+ uninstall,
1899
+ teardown: uninstall,
1879
1900
  upgrade,
1901
+ update: upgrade,
1902
+ skill: {
1903
+ install: skillInstall
1904
+ },
1880
1905
  memory: {
1881
1906
  add: memoryAdd,
1882
1907
  search: memorySearch,
@@ -1886,6 +1911,7 @@ var ROUTES = {
1886
1911
  delete: memoryDelete
1887
1912
  },
1888
1913
  kb: {
1914
+ create: kbCreate,
1889
1915
  "upload-text": kbUploadText,
1890
1916
  "upload-file": kbUploadFile,
1891
1917
  list: kbList,
@@ -1896,7 +1922,7 @@ var ROUTES = {
1896
1922
  };
1897
1923
  async function main(argv = process.argv.slice(2)) {
1898
1924
  if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
1899
- process.stdout.write(HELP);
1925
+ process.stdout.write(HELP5);
1900
1926
  return 0;
1901
1927
  }
1902
1928
  if (argv[0] === "-v" || argv[0] === "--version") {
@@ -1909,7 +1935,7 @@ async function main(argv = process.argv.slice(2)) {
1909
1935
  if (!route) {
1910
1936
  process.stderr.write(`unknown command: ${first}
1911
1937
  `);
1912
- process.stderr.write(HELP);
1938
+ process.stderr.write(HELP5);
1913
1939
  return 2;
1914
1940
  }
1915
1941
  if (typeof route === "function") {
@@ -1922,6 +1948,13 @@ async function main(argv = process.argv.slice(2)) {
1922
1948
  );
1923
1949
  return 2;
1924
1950
  }
1951
+ if (rest[0] === "-h" || rest[0] === "--help") {
1952
+ process.stdout.write(
1953
+ `usage: ctxdb ${first} <${Object.keys(route).join("|")}> ...
1954
+ `
1955
+ );
1956
+ return 0;
1957
+ }
1925
1958
  const [sub, ...subRest] = rest;
1926
1959
  const handler = route[sub];
1927
1960
  if (!handler) {