@aliyunrds/ctxdb 0.0.5 → 0.0.8-beta.1

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
@@ -4,8 +4,7 @@ import {
4
4
  DEFAULT_INGEST_TIMEOUT_MS,
5
5
  compactChunk,
6
6
  compactKbQueryResponse,
7
- findKb,
8
- findOrCreateKb,
7
+ createKb,
9
8
  getDocument,
10
9
  listDocuments,
11
10
  listKnowledgeBases,
@@ -14,7 +13,7 @@ import {
14
13
  pollIngest,
15
14
  uploadFile,
16
15
  uploadText
17
- } from "../chunk-HAUTENYD.js";
16
+ } from "../chunk-6S5RJYBC.js";
18
17
  import {
19
18
  DEFAULT_BASE_URL,
20
19
  DEFAULT_USER_ID,
@@ -23,12 +22,14 @@ import {
23
22
  agentFromEnv,
24
23
  agentHomeDir,
25
24
  configuredAgents,
26
- isAgent,
25
+ isAgentSlug,
26
+ isBuiltinAgent,
27
27
  isComplete,
28
28
  load,
29
29
  removeAgent,
30
- save
31
- } from "../chunk-QSSNPN3M.js";
30
+ save,
31
+ writeInstalledPkgVersion
32
+ } from "../chunk-U3T5O6NX.js";
32
33
 
33
34
  // src/cli/util.ts
34
35
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
@@ -192,17 +193,16 @@ function printResult(value, json, command) {
192
193
  function agentFromFlags(flags) {
193
194
  const raw = flags.agent;
194
195
  if (raw === void 0) return agentFromEnv();
195
- if (isAgent(raw)) return raw;
196
- 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);
197
198
  }
198
199
  function buildContext(args) {
199
200
  const agent = agentFromFlags(args?.flags ?? {});
200
201
  const cfg = load({ agent });
201
202
  if (!isComplete(cfg)) {
202
- process.stderr.write(
203
- `config incomplete for agent ${agent}: run \`ctxdb setup --agent ${agent}\` or set CTXDB_API_KEY / CTXDB_BASE_URL / CTXDB_USER_ID env vars
204
- `
205
- );
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
+ `);
206
206
  process.exit(2);
207
207
  }
208
208
  const client = new HttpClient({
@@ -216,7 +216,7 @@ function fail(message, code = 1) {
216
216
  `);
217
217
  process.exit(code);
218
218
  }
219
- var PACKAGE_VERSION = "0.0.4";
219
+ var PACKAGE_VERSION = "0.0.7";
220
220
 
221
221
  // src/setup/installer.ts
222
222
  import {
@@ -248,15 +248,10 @@ function skillInstallRoot(agent) {
248
248
  return join(homedir(), ".claude", "skills");
249
249
  }
250
250
  }
251
- var SKILL_DIR_NAME = "ctxdb";
252
- function skillResourceDir(agent) {
253
- return agentSupportsHooks(agent) ? "hooks-driven" : "cli-only";
254
- }
255
- function skillInstallDir(agent) {
256
- return join(skillInstallRoot(agent), SKILL_DIR_NAME);
257
- }
251
+ var SKILL_DIRS = ["contextdb-memory", "contextdb-knowledge"];
252
+ var LEGACY_SKILL_DIRS = ["ctxdb"];
258
253
  function agentSupportsHooks(agent) {
259
- return agent === "qoder" || agent === "codex" || agent === "claude";
254
+ return isBuiltinAgent(agent);
260
255
  }
261
256
  function hookConfigPath(agent) {
262
257
  switch (agent) {
@@ -276,54 +271,48 @@ var LEGACY_QODER_SKILL_DIRS = [
276
271
  var HOOK_EVENTS = ["UserPromptSubmit", "Stop", "SessionStart"];
277
272
  async function runSetup(options) {
278
273
  const steps = [];
279
- if (!isAgent(options.agent)) {
274
+ if (!isAgentSlug(options.agent)) {
280
275
  steps.push({
281
276
  step: "validate-agent",
282
277
  ok: false,
283
- 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)`
284
279
  });
285
280
  return { ok: false, steps };
286
281
  }
287
282
  const agent = options.agent;
288
283
  const installSkill = options.installSkill !== false;
289
284
  const validate = options.validate !== false;
290
- const homeCheck = checkAgentHome(agent);
291
- steps.push(homeCheck);
292
- 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
+ }
293
290
  const cfg = load({ agent });
294
291
  if (options.apiKey) cfg.apiKey = options.apiKey;
295
292
  if (options.baseUrl) cfg.baseUrl = options.baseUrl.replace(/\/+$/, "");
296
293
  else if (!cfg.baseUrl) cfg.baseUrl = DEFAULT_BASE_URL;
297
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
+ }
298
303
  save(cfg, void 0, { agent });
299
304
  steps.push({
300
305
  step: "write-config",
301
306
  ok: true,
302
307
  detail: `${agent}: ${cfg.baseUrl} (user_id=${cfg.userId})`
303
308
  });
304
- if (installSkill) {
305
- try {
306
- const srcDir = locateSkillDir(skillResourceDir(agent));
307
- if (srcDir) {
308
- const dest = skillInstallDir(agent);
309
- copySkillDir(srcDir, dest, agent);
310
- steps.push({ step: "install-skill", ok: true, detail: dest });
311
- } else {
312
- steps.push({
313
- step: "install-skill",
314
- ok: false,
315
- detail: `skill source directory not found (${skillResourceDir(agent)})`
316
- });
317
- }
318
- } catch (err) {
319
- steps.push({
320
- step: "install-skill",
321
- ok: false,
322
- detail: err?.message ?? String(err)
323
- });
324
- }
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 };
325
314
  }
326
- if (agentSupportsHooks(agent)) {
315
+ if (isBuiltinAgent(agent) && agentSupportsHooks(agent)) {
327
316
  const hookPaths = locateHookPaths();
328
317
  steps.push({
329
318
  step: "locate-hooks",
@@ -406,15 +395,15 @@ async function runSetup(options) {
406
395
  }
407
396
  function runRemove(agent, options = {}) {
408
397
  const steps = [];
409
- if (!isAgent(agent)) {
398
+ if (!isAgentSlug(agent)) {
410
399
  steps.push({
411
400
  step: "validate-agent",
412
401
  ok: false,
413
- detail: `unknown --agent: ${String(agent)} (expected one of ${SUPPORTED_AGENTS.join(" / ")})`
402
+ detail: `unknown --agent: ${String(agent)} (expected one of ${SUPPORTED_AGENTS.join(" / ")} / default)`
414
403
  });
415
404
  return { ok: false, steps };
416
405
  }
417
- if (agentSupportsHooks(agent)) {
406
+ if (isBuiltinAgent(agent) && agentSupportsHooks(agent)) {
418
407
  const path = hookConfigPath(agent);
419
408
  if (!existsSync(path)) {
420
409
  steps.push({
@@ -451,12 +440,12 @@ function runRemove(agent, options = {}) {
451
440
  return { ok: false, steps };
452
441
  }
453
442
  }
454
- const dirs = agent === "qoder" ? [
455
- skillInstallDir("qoder"),
456
- ...LEGACY_QODER_SKILL_DIRS.map(
457
- (d) => join(homedir(), ".qoder", "skills", d)
458
- )
459
- ] : [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
+ ];
460
449
  for (const p of dirs) {
461
450
  if (!existsSync(p)) continue;
462
451
  try {
@@ -470,26 +459,6 @@ function runRemove(agent, options = {}) {
470
459
  });
471
460
  }
472
461
  }
473
- } else {
474
- const dir = skillInstallDir(agent);
475
- if (!existsSync(dir)) {
476
- steps.push({
477
- step: "skill-missing",
478
- ok: true,
479
- detail: `nothing to remove (${dir})`
480
- });
481
- } else {
482
- try {
483
- rmSync(dir, { recursive: true, force: true });
484
- steps.push({ step: "remove-skill-dir", ok: true, detail: dir });
485
- } catch (err) {
486
- steps.push({
487
- step: "remove-skill-dir",
488
- ok: false,
489
- detail: `${dir}: ${err?.message ?? String(err)}`
490
- });
491
- }
492
- }
493
462
  }
494
463
  try {
495
464
  const r = removeAgent(agent, void 0, {
@@ -546,6 +515,12 @@ function runTeardown(options = {}) {
546
515
  steps.push({ ...s, step: `${agent}:${s.step}` });
547
516
  }
548
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
+ }
549
524
  if (purgeConfig) {
550
525
  const cfgPath = join(homedir(), ".ctxdb", "ctxdb.json");
551
526
  if (existsSync(cfgPath)) {
@@ -649,11 +624,15 @@ function checkHookNodePaths(agent) {
649
624
  if (!entryIsCtxdb(entry)) continue;
650
625
  for (const h of entry.hooks ?? []) {
651
626
  if (h?.type !== "command" || typeof h.command !== "string") continue;
652
- const cmd = h.command;
653
- if (cmd.includes("@aliyunrds/ctxdb")) {
654
- const parts = cmd.split(" ");
655
- if (parts.length >= 2 && !parts[0].endsWith(".js") && !parts[0].endsWith(".ts")) {
656
- nodePaths.add(parts[0]);
627
+ if (Array.isArray(h.args) && h.args.length > 0) {
628
+ nodePaths.add(h.command);
629
+ } else {
630
+ const cmd = h.command;
631
+ if (cmd.includes("@aliyunrds/ctxdb")) {
632
+ const parts = cmd.split(" ");
633
+ if (parts.length >= 2 && !parts[0].endsWith(".js") && !parts[0].endsWith(".ts")) {
634
+ nodePaths.add(parts[0]);
635
+ }
657
636
  }
658
637
  }
659
638
  }
@@ -827,6 +806,44 @@ function copySkillDir(src, dest, agent) {
827
806
  }
828
807
  }
829
808
  }
809
+ var SKILL_INSTALL_TARGETS = {
810
+ qoder: join(homedir(), ".qoder", "skills"),
811
+ codex: join(homedir(), ".codex", "skills"),
812
+ claude: join(homedir(), ".claude", "skills"),
813
+ openclaw: join(homedir(), ".openclaw", "skills"),
814
+ hermes: join(homedir(), ".hermes", "skills")
815
+ };
816
+ function skillInstallTargetPath(target) {
817
+ return SKILL_INSTALL_TARGETS[target] ?? null;
818
+ }
819
+ function installSkillsTo(targetRoot, agent = "default") {
820
+ const steps = [];
821
+ for (const skillName of SKILL_DIRS) {
822
+ try {
823
+ const srcDir = locateSkillDir(skillName);
824
+ if (srcDir) {
825
+ const dest = join(targetRoot, skillName);
826
+ copySkillDir(srcDir, dest, agent);
827
+ steps.push({ step: "install-skill", ok: true, detail: dest });
828
+ } else {
829
+ steps.push({
830
+ step: "install-skill",
831
+ ok: false,
832
+ detail: `skill source directory not found (${skillName})`
833
+ });
834
+ return { ok: false, steps };
835
+ }
836
+ } catch (err) {
837
+ steps.push({
838
+ step: "install-skill",
839
+ ok: false,
840
+ detail: err?.message ?? String(err)
841
+ });
842
+ return { ok: false, steps };
843
+ }
844
+ }
845
+ return { ok: true, steps };
846
+ }
830
847
  function appendHooks(agent, hookPaths) {
831
848
  const path = hookConfigPath(agent);
832
849
  if (!path) return;
@@ -852,16 +869,15 @@ var LEGACY_MARKER_KEYS = ["_ctxdbQoder", "_ctxdbPackage"];
852
869
  var LEGACY_MARKER_VALUES = ["@aliyunrds/ctxdb-qoder"];
853
870
  var TOOL_SCOPED_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse"]);
854
871
  function appendOne(hooks, event, command, agent) {
855
- const commandWithAgent = `${process.execPath} ${command} --agent=${agent}`;
856
872
  if (!Array.isArray(hooks[event])) hooks[event] = [];
857
873
  const dup = hooks[event].some(
858
874
  (entry2) => Array.isArray(entry2?.hooks) && entry2.hooks.some(
859
- (h) => h?.type === "command" && typeof h.command === "string" && h.command === commandWithAgent
875
+ (h) => h?.type === "command" && Array.isArray(h.args) && h.args[0] === command
860
876
  )
861
877
  );
862
878
  if (dup) return;
863
879
  const entry = {
864
- hooks: [{ type: "command", command: commandWithAgent, timeout: 60 }],
880
+ hooks: [{ type: "command", command: process.execPath, args: [command, `--agent=${agent}`], timeout: 60 }],
865
881
  [ENTRY_MARKER_KEY]: ENTRY_MARKER_VALUE,
866
882
  [ENTRY_AGENT_KEY]: agent
867
883
  };
@@ -1081,7 +1097,7 @@ function describeCodexFeatureMutation(status2, createdNew) {
1081
1097
  }
1082
1098
  }
1083
1099
  function runUpgrade(options = {}) {
1084
- const agents = options.agent ? [options.agent] : configuredAgents(options.configPath);
1100
+ const agents = options.agent ? [options.agent] : configuredAgents(options.configPath).filter(isBuiltinAgent);
1085
1101
  if (agents.length === 0) {
1086
1102
  return {
1087
1103
  ok: false,
@@ -1113,24 +1129,19 @@ function upgradeOneAgent(agent) {
1113
1129
  if (!homeCheck.ok) {
1114
1130
  return { ok: true, steps, hints: [`${agent}: skipped \u2014 agent home not found`] };
1115
1131
  }
1116
- try {
1117
- const srcDir = locateSkillDir(skillResourceDir(agent));
1118
- if (srcDir) {
1119
- const dest = skillInstallDir(agent);
1120
- copySkillDir(srcDir, dest, agent);
1121
- steps.push({ step: "install-skill", ok: true, detail: dest });
1122
- } else {
1123
- steps.push({
1124
- step: "install-skill",
1125
- ok: false,
1126
- detail: "skill source directory not found"
1127
- });
1128
- return { ok: false, steps };
1132
+ const root = skillInstallRoot(agent);
1133
+ for (const legacy of LEGACY_SKILL_DIRS) {
1134
+ const legacyDir = join(root, legacy);
1135
+ if (!existsSync(legacyDir)) continue;
1136
+ try {
1137
+ rmSync(legacyDir, { recursive: true, force: true });
1138
+ steps.push({ step: "remove-legacy-skill", ok: true, detail: legacyDir });
1139
+ } catch {
1129
1140
  }
1130
- } catch (err) {
1131
- steps.push({ step: "install-skill", ok: false, detail: err?.message ?? String(err) });
1132
- return { ok: false, steps };
1133
1141
  }
1142
+ const skillResult = installSkillsTo(root, agent);
1143
+ steps.push(...skillResult.steps);
1144
+ if (!skillResult.ok) return { ok: false, steps };
1134
1145
  if (agentSupportsHooks(agent)) {
1135
1146
  const hookPaths = locateHookPaths();
1136
1147
  if (!hookPaths) {
@@ -1188,7 +1199,7 @@ async function status(args) {
1188
1199
  pingError = err?.message ?? String(err);
1189
1200
  }
1190
1201
  }
1191
- const hookHealth = checkHookNodePaths(agent);
1202
+ const hookHealth = isBuiltinAgent(agent) ? checkHookNodePaths(agent) : { installed: false, nodePaths: [], nodeOk: true, detail: "no hooks (default agent)" };
1192
1203
  printResult(
1193
1204
  {
1194
1205
  ok: complete,
@@ -1226,25 +1237,51 @@ async function ping(args) {
1226
1237
  }
1227
1238
 
1228
1239
  // src/cli/setup-cli.ts
1240
+ var HELP = `ctxdb setup \u2014 configure an agent
1241
+
1242
+ USAGE
1243
+ ctxdb setup [--agent <qoder|codex|claude>]
1244
+ [--api-key=K] [--base-url=URL] [--user-id=ID]
1245
+ [--no-install-skill] [--no-validate] [--json]
1246
+
1247
+ BEHAVIOR
1248
+ Without --agent Writes agents.default in ~/.ctxdb/ctxdb.json (CLI-only,
1249
+ no hooks or skills installed).
1250
+ With --agent Writes agent config + installs hooks + skills for the
1251
+ specified agent harness.
1252
+
1253
+ FLAGS
1254
+ --agent <name> Target agent (qoder|codex|claude)
1255
+ --api-key <key> API key (required on first setup)
1256
+ --base-url <url> Server URL (default: https://context-database.aliyuncs.com)
1257
+ --user-id <id> User bucket (default: "default")
1258
+ --no-install-skill Skip skill directory installation
1259
+ --no-validate Skip connectivity ping after setup
1260
+ --json Machine-readable JSON output
1261
+ `;
1229
1262
  function parseAgent(args) {
1230
1263
  const raw = args.flags.agent;
1231
- if (typeof raw !== "string") return null;
1232
- return SUPPORTED_AGENTS.includes(raw) ? raw : null;
1233
- }
1234
- function agentError() {
1264
+ if (raw === void 0) return "default";
1265
+ if (typeof raw === "string" && isAgentSlug(raw)) return raw;
1235
1266
  process.stderr.write(
1236
- `ctxdb setup: --agent <${SUPPORTED_AGENTS.join("|")}> is required
1237
- e.g. ctxdb setup --agent qoder --api-key=<key>
1267
+ `unknown --agent: ${String(raw)} (expected one of ${SUPPORTED_AGENTS.join(" / ")} / default)
1238
1268
  `
1239
1269
  );
1270
+ process.exit(2);
1240
1271
  }
1241
1272
  async function setup(args) {
1242
- const agent = parseAgent(args);
1243
- if (!agent) {
1244
- agentError();
1245
- return 2;
1273
+ if (args.flags.help || args.flags.h) {
1274
+ process.stdout.write(HELP);
1275
+ return 0;
1246
1276
  }
1277
+ const agent = parseAgent(args);
1247
1278
  const json = !!args.flags.json;
1279
+ if (agent === "default" && !json && !configuredAgents().includes("default")) {
1280
+ process.stderr.write(
1281
+ `hint: creating CLI-only "default" agent (no hooks/skills). Use --agent <claude|qoder|codex> for full setup.
1282
+ `
1283
+ );
1284
+ }
1248
1285
  const result = await runSetup({
1249
1286
  agent,
1250
1287
  apiKey: typeof args.flags["api-key"] === "string" ? args.flags["api-key"] : void 0,
@@ -1263,7 +1300,7 @@ ${h}
1263
1300
  }
1264
1301
 
1265
1302
  // src/cli/teardown.ts
1266
- var HELP = `ctxdb uninstall \u2014 remove hooks + skills
1303
+ var HELP2 = `ctxdb uninstall \u2014 remove hooks + skills
1267
1304
 
1268
1305
  USAGE
1269
1306
  ctxdb uninstall [--agent <qoder|codex|claude>] [--purge-config] [--purge-logs] [--purge-all] [--json]
@@ -1290,15 +1327,15 @@ NOTE
1290
1327
  `;
1291
1328
  function uninstall(args) {
1292
1329
  if (args.flags.help || args.flags.h) {
1293
- process.stdout.write(HELP);
1330
+ process.stdout.write(HELP2);
1294
1331
  return 0;
1295
1332
  }
1296
1333
  const json = !!args.flags.json;
1297
1334
  const agentFlag = args.flags.agent;
1298
1335
  if (agentFlag !== void 0) {
1299
- if (!isAgent(agentFlag)) {
1336
+ if (!isAgentSlug(agentFlag)) {
1300
1337
  process.stderr.write(
1301
- `unknown --agent: ${String(agentFlag)} (expected one of ${SUPPORTED_AGENTS.join(" / ")})
1338
+ `unknown --agent: ${String(agentFlag)} (expected one of ${SUPPORTED_AGENTS.join(" / ")} / default)
1302
1339
  `
1303
1340
  );
1304
1341
  return 2;
@@ -1326,10 +1363,145 @@ ${h}
1326
1363
  return result.ok ? 0 : 1;
1327
1364
  }
1328
1365
 
1366
+ // src/lib/self-update.ts
1367
+ import { spawnSync } from "child_process";
1368
+ import { fileURLToPath as fileURLToPath2 } from "url";
1369
+ import { dirname as dirname2 } from "path";
1370
+ var PACKAGE_NAME = "@aliyunrds/ctxdb";
1371
+ var NPM_VIEW_TIMEOUT_MS = 1e4;
1372
+ function detectInstallMethod() {
1373
+ const dir = dirname2(fileURLToPath2(import.meta.url));
1374
+ return dir.includes("/node_modules/") ? "npm" : "unknown";
1375
+ }
1376
+ function checkLatestVersion(currentVersion) {
1377
+ const result = spawnSync("npm", ["view", PACKAGE_NAME, "version"], {
1378
+ encoding: "utf-8",
1379
+ timeout: NPM_VIEW_TIMEOUT_MS,
1380
+ stdio: ["ignore", "pipe", "pipe"]
1381
+ });
1382
+ if (result.status !== 0 || !result.stdout?.trim()) {
1383
+ return {
1384
+ latest: null,
1385
+ current: currentVersion,
1386
+ isNewer: false,
1387
+ error: result.stderr?.trim() || `npm view exited with code ${result.status}`
1388
+ };
1389
+ }
1390
+ const latest = result.stdout.trim();
1391
+ return {
1392
+ latest,
1393
+ current: currentVersion,
1394
+ isNewer: isNewerVersion(latest, currentVersion)
1395
+ };
1396
+ }
1397
+ function isNewerVersion(candidate, current) {
1398
+ const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
1399
+ const [cMaj = 0, cMin = 0, cPat = 0] = parse(candidate);
1400
+ const [uMaj = 0, uMin = 0, uPat = 0] = parse(current);
1401
+ if (cMaj !== uMaj) return cMaj > uMaj;
1402
+ if (cMin !== uMin) return cMin > uMin;
1403
+ return cPat > uPat;
1404
+ }
1405
+ function runSelfUpdate(currentVersion, passthroughArgs = []) {
1406
+ const method = detectInstallMethod();
1407
+ if (method !== "npm") {
1408
+ return {
1409
+ ok: false,
1410
+ updated: false,
1411
+ fromVersion: currentVersion,
1412
+ error: "ctxdb was not installed via npm. Update manually with your package manager."
1413
+ };
1414
+ }
1415
+ const check = checkLatestVersion(currentVersion);
1416
+ if (check.error) {
1417
+ process.stderr.write(`ctxdb: npm view failed (${check.error}), skipping self-update
1418
+ `);
1419
+ return { ok: true, updated: false, fromVersion: currentVersion };
1420
+ }
1421
+ if (!check.isNewer) {
1422
+ process.stderr.write(`ctxdb: package already up to date (v${currentVersion})
1423
+ `);
1424
+ return { ok: true, updated: false, fromVersion: currentVersion };
1425
+ }
1426
+ process.stderr.write(`ctxdb: updating ${PACKAGE_NAME} v${currentVersion} \u2192 v${check.latest}...
1427
+ `);
1428
+ const install = spawnSync("npm", ["install", "-g", `${PACKAGE_NAME}@${check.latest}`], {
1429
+ stdio: "inherit",
1430
+ encoding: "utf-8"
1431
+ });
1432
+ if (install.status !== 0) {
1433
+ return {
1434
+ ok: false,
1435
+ updated: false,
1436
+ fromVersion: currentVersion,
1437
+ error: `npm install -g failed with exit code ${install.status}`
1438
+ };
1439
+ }
1440
+ process.stderr.write(`ctxdb: package updated to v${check.latest}, refreshing skills/hooks...
1441
+ `);
1442
+ const upgradeArgs = ["upgrade", ...passthroughArgs];
1443
+ const refresh = spawnSync("ctxdb", upgradeArgs, {
1444
+ stdio: "inherit",
1445
+ encoding: "utf-8"
1446
+ });
1447
+ return {
1448
+ ok: refresh.status === 0,
1449
+ updated: true,
1450
+ fromVersion: currentVersion,
1451
+ toVersion: check.latest,
1452
+ error: refresh.status !== 0 ? `ctxdb upgrade exited with code ${refresh.status}` : void 0
1453
+ };
1454
+ }
1455
+
1329
1456
  // src/cli/upgrade.ts
1457
+ var HELP3 = `ctxdb upgrade \u2014 refresh skills + hooks
1458
+
1459
+ USAGE
1460
+ ctxdb upgrade [--agent <qoder|codex|claude>] [--self-update] [--json]
1461
+
1462
+ BEHAVIOR
1463
+ Refreshes skill files and hook entries for configured agents without
1464
+ touching credentials. Designed for the post-npm-update path.
1465
+
1466
+ Without --agent: upgrades all configured built-in agents.
1467
+ With --agent: upgrades only the specified agent.
1468
+
1469
+ FLAGS
1470
+ --agent <name> Target a single agent (qoder|codex|claude)
1471
+ --self-update Also pull latest package from npm before upgrading
1472
+ --json Machine-readable JSON output
1473
+ `;
1330
1474
  function upgrade(args) {
1475
+ if (args.flags.help || args.flags.h) {
1476
+ process.stdout.write(HELP3);
1477
+ return 0;
1478
+ }
1479
+ const selfUpdate = !!args.flags["self-update"];
1480
+ if (selfUpdate) {
1481
+ const passthrough = [];
1482
+ const agentFlag2 = args.flags.agent;
1483
+ if (agentFlag2) passthrough.push("--agent", agentFlag2);
1484
+ if (args.flags.json) passthrough.push("--json");
1485
+ const result2 = runSelfUpdate(PACKAGE_VERSION, passthrough);
1486
+ if (!result2.ok) {
1487
+ if (args.flags.json) {
1488
+ process.stdout.write(JSON.stringify({ ok: false, error: result2.error }) + "\n");
1489
+ } else {
1490
+ process.stderr.write(`ctxdb: self-update failed: ${result2.error}
1491
+ `);
1492
+ }
1493
+ return 1;
1494
+ }
1495
+ if (result2.updated) {
1496
+ return 0;
1497
+ }
1498
+ }
1331
1499
  const agentFlag = args.flags.agent;
1332
- if (agentFlag && !isAgent(agentFlag)) {
1500
+ if (agentFlag === "default") {
1501
+ process.stderr.write("nothing to upgrade for default agent (no hooks or skills)\n");
1502
+ return 0;
1503
+ }
1504
+ if (agentFlag && !isBuiltinAgent(agentFlag)) {
1333
1505
  process.stderr.write(
1334
1506
  `unknown --agent: ${agentFlag} (expected one of ${SUPPORTED_AGENTS.join(" / ")})
1335
1507
  `
@@ -1337,6 +1509,12 @@ function upgrade(args) {
1337
1509
  return 2;
1338
1510
  }
1339
1511
  const result = runUpgrade({ agent: agentFlag });
1512
+ if (result.ok) {
1513
+ try {
1514
+ writeInstalledPkgVersion(PACKAGE_VERSION);
1515
+ } catch {
1516
+ }
1517
+ }
1340
1518
  const json = !!args.flags.json;
1341
1519
  printResult(result, json);
1342
1520
  if (!json && result.hints) {
@@ -1347,6 +1525,71 @@ ${h}
1347
1525
  return result.ok ? 0 : 1;
1348
1526
  }
1349
1527
 
1528
+ // src/cli/skill-cli.ts
1529
+ var SUPPORTED_TARGETS = Object.keys(SKILL_INSTALL_TARGETS);
1530
+ var HELP4 = `ctxdb skill install \u2014 install skill files to an agent's skill directory
1531
+
1532
+ USAGE
1533
+ ctxdb skill install --target <name>
1534
+ ctxdb skill install --path <dir>
1535
+
1536
+ TARGETS
1537
+ ${SUPPORTED_TARGETS.join(", ")}
1538
+
1539
+ FLAGS
1540
+ --target <name> Install to a built-in agent's skill directory
1541
+ --path <dir> Install to a custom directory
1542
+ --json Machine-readable JSON output
1543
+
1544
+ Note: This command only installs skill files (contextdb, contextdb-memory,
1545
+ contextdb-knowledge). It does not write config or install hooks.
1546
+ To configure server connection, run: ctxdb setup --api-key=<key> --base-url=<url>
1547
+ `;
1548
+ function skillInstall(args) {
1549
+ if (args.flags.help || args.flags.h) {
1550
+ process.stdout.write(HELP4);
1551
+ return 0;
1552
+ }
1553
+ const json = !!args.flags.json;
1554
+ const target = args.flags.target;
1555
+ const customPath = args.flags.path;
1556
+ if (target && customPath) {
1557
+ fail("--target and --path are mutually exclusive", 2);
1558
+ }
1559
+ if (!target && !customPath) {
1560
+ process.stderr.write(HELP4);
1561
+ return 2;
1562
+ }
1563
+ let targetRoot;
1564
+ if (customPath) {
1565
+ targetRoot = customPath;
1566
+ } else {
1567
+ const resolved = skillInstallTargetPath(target);
1568
+ if (!resolved) {
1569
+ fail(
1570
+ `unknown --target: ${target} (supported: ${SUPPORTED_TARGETS.join(", ")})`,
1571
+ 2
1572
+ );
1573
+ }
1574
+ targetRoot = resolved;
1575
+ }
1576
+ const result = installSkillsTo(targetRoot);
1577
+ const ok = result.ok;
1578
+ const output = {
1579
+ ok,
1580
+ target: target ?? null,
1581
+ path: targetRoot,
1582
+ steps: result.steps
1583
+ };
1584
+ printResult(output, json);
1585
+ if (ok && !json) {
1586
+ process.stderr.write(
1587
+ "\nNote: This command only installs skill files.\nTo configure server connection, run:\n ctxdb setup --api-key=<key> --base-url=<url>\n"
1588
+ );
1589
+ }
1590
+ return ok ? 0 : 1;
1591
+ }
1592
+
1350
1593
  // src/cli/memory.ts
1351
1594
  async function memoryAdd(args) {
1352
1595
  const text = args.positional[0];
@@ -1477,25 +1720,20 @@ async function kbUploadText(args) {
1477
1720
  const text = args.flags["text"];
1478
1721
  if (!kbName || !docName || typeof text !== "string") {
1479
1722
  fail(
1480
- "usage: ctxdb kb upload-text <kb-name> <doc-name> --text=<body> [--kb-description=...] [--file-path=<server-logical-path>] [--no-wait]"
1723
+ "usage: ctxdb kb upload-text <kb-name> <doc-name> --text=<body> [--file-path=<server-logical-path>] [--no-wait]"
1481
1724
  );
1482
1725
  }
1483
1726
  const ctx = buildContext(args);
1484
- const description = args.flags["kb-description"] ?? "";
1485
1727
  const filePath = typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0;
1486
- const { kb, created } = await findOrCreateKb(ctx.client, kbName, description);
1487
- const doc = await uploadText(ctx.client, kb.id, docName, text, "text/plain", filePath);
1728
+ const doc = await uploadText(ctx.client, kbName, docName, text, "text/plain", filePath);
1488
1729
  if (args.flags["no-wait"]) {
1489
- printResult({ kb, kb_created: created, document: doc }, !!args.flags.json);
1730
+ printResult({ document: doc }, !!args.flags.json);
1490
1731
  return 0;
1491
1732
  }
1492
- const final = await pollIngest(ctx.client, kb.id, doc.id, {
1733
+ const final = await pollIngest(ctx.client, kbName, doc.id, {
1493
1734
  timeoutMs: DEFAULT_INGEST_TIMEOUT_MS
1494
1735
  });
1495
- printResult(
1496
- { kb, kb_created: created, document: final },
1497
- !!args.flags.json
1498
- );
1736
+ printResult({ document: final }, !!args.flags.json);
1499
1737
  return 0;
1500
1738
  }
1501
1739
  async function kbUploadFile(args) {
@@ -1506,23 +1744,28 @@ async function kbUploadFile(args) {
1506
1744
  );
1507
1745
  }
1508
1746
  const ctx = buildContext(args);
1509
- const { kb, created } = await findOrCreateKb(ctx.client, kbName);
1510
- const doc = await uploadFile(ctx.client, kb.id, localPath, {
1747
+ const doc = await uploadFile(ctx.client, kbName, localPath, {
1511
1748
  docName: typeof args.flags["doc-name"] === "string" ? args.flags["doc-name"] : void 0,
1512
1749
  filePath: typeof args.flags["file-path"] === "string" ? args.flags["file-path"] : void 0,
1513
1750
  timeoutMs: DEFAULT_FILE_INGEST_TIMEOUT_MS
1514
1751
  });
1515
1752
  if (args.flags["no-wait"]) {
1516
- printResult({ kb, kb_created: created, document: doc }, !!args.flags.json);
1753
+ printResult({ document: doc }, !!args.flags.json);
1517
1754
  return 0;
1518
1755
  }
1519
- const final = await pollIngest(ctx.client, kb.id, doc.id, {
1756
+ const final = await pollIngest(ctx.client, kbName, doc.id, {
1520
1757
  timeoutMs: DEFAULT_FILE_INGEST_TIMEOUT_MS
1521
1758
  });
1522
- printResult(
1523
- { kb, kb_created: created, document: final },
1524
- !!args.flags.json
1525
- );
1759
+ printResult({ document: final }, !!args.flags.json);
1760
+ return 0;
1761
+ }
1762
+ async function kbCreate(args) {
1763
+ const kbName = args.positional[0];
1764
+ if (!kbName) fail("usage: ctxdb kb create <kb-name> [--description=<desc>]");
1765
+ const ctx = buildContext(args);
1766
+ const description = typeof args.flags["description"] === "string" ? args.flags["description"] : "";
1767
+ const kb = await createKb(ctx.client, kbName, description);
1768
+ printResult(kb, !!args.flags.json);
1526
1769
  return 0;
1527
1770
  }
1528
1771
  async function kbList(args) {
@@ -1532,28 +1775,24 @@ async function kbList(args) {
1532
1775
  return 0;
1533
1776
  }
1534
1777
  async function kbDocumentsList(args) {
1535
- const kbNameOrId = args.positional[0];
1536
- if (!kbNameOrId) fail("usage: ctxdb kb documents-list <kb-name-or-id>");
1778
+ const kbName = args.positional[0];
1779
+ if (!kbName) fail("usage: ctxdb kb documents-list <kb-name>");
1537
1780
  const ctx = buildContext(args);
1538
- const kb = await findKb(ctx.client, kbNameOrId);
1539
- if (!kb) fail(`KB not found: ${kbNameOrId}`);
1540
- const docs = await listDocuments(ctx.client, kb.id);
1781
+ const docs = await listDocuments(ctx.client, kbName);
1541
1782
  printResult(
1542
- { kb, documents: docs, count: docs.length },
1783
+ { documents: docs, count: docs.length },
1543
1784
  !!args.flags.json
1544
1785
  );
1545
1786
  return 0;
1546
1787
  }
1547
1788
  async function kbDocumentGet(args) {
1548
- const [kbNameOrId, docId] = args.positional;
1549
- if (!kbNameOrId || !docId) {
1550
- fail("usage: ctxdb kb document-get <kb-name-or-id> <doc-id>");
1789
+ const [kbName, docId] = args.positional;
1790
+ if (!kbName || !docId) {
1791
+ fail("usage: ctxdb kb document-get <kb-name> <doc-id>");
1551
1792
  }
1552
1793
  const ctx = buildContext(args);
1553
- const kb = await findKb(ctx.client, kbNameOrId);
1554
- if (!kb) fail(`KB not found: ${kbNameOrId}`);
1555
- const doc = await getDocument(ctx.client, kb.id, docId);
1556
- printResult({ kb, document: doc }, !!args.flags.json);
1794
+ const doc = await getDocument(ctx.client, kbName, docId);
1795
+ printResult({ document: doc }, !!args.flags.json);
1557
1796
  return 0;
1558
1797
  }
1559
1798
  async function kbSearch(args) {
@@ -1564,16 +1803,10 @@ async function kbSearch(args) {
1564
1803
  );
1565
1804
  }
1566
1805
  const ctx = buildContext(args);
1567
- const kbList2 = (typeof args.flags.kb === "string" ? args.flags.kb : "").split(",").map((s) => s.trim()).filter(Boolean);
1568
- const datasetIds = [];
1569
- for (const name of kbList2) {
1570
- const kb = await findKb(ctx.client, name);
1571
- if (!kb) fail(`KB not found: ${name}`);
1572
- datasetIds.push(kb.id);
1573
- }
1806
+ const kbNames = (typeof args.flags.kb === "string" ? args.flags.kb : "").split(",").map((s) => s.trim()).filter(Boolean);
1574
1807
  const body = {
1575
1808
  question: query,
1576
- dataset_ids: datasetIds
1809
+ knowledge_base_names: kbNames
1577
1810
  };
1578
1811
  if (typeof args.flags["top-k"] === "string") {
1579
1812
  const n = Number(args.flags["top-k"]);
@@ -1590,21 +1823,22 @@ async function kbSearch(args) {
1590
1823
  }
1591
1824
 
1592
1825
  // src/cli/main.ts
1593
- var HELP2 = `ctxdb v${PACKAGE_VERSION} \u2014 RDS ContextDatabase CLI (multi-agent)
1826
+ var HELP5 = `ctxdb v${PACKAGE_VERSION} \u2014 RDS ContextDatabase CLI (multi-agent)
1594
1827
 
1595
1828
  USAGE
1596
1829
  ctxdb <command> [args] [--flags]
1597
1830
 
1598
1831
  COMMANDS
1599
- setup --agent <qoder|codex|claude>
1832
+ setup [--agent <qoder|codex|claude>]
1600
1833
  [--api-key=K] [--base-url=URL] [--user-id=ID]
1601
1834
  [--no-install-skill] [--no-validate] [--json]
1835
+ Without --agent \u2192 writes agents.default (CLI-only, no hooks/skills)
1602
1836
  Qoder \u2192 writes agents.qoder config + ~/.qoder/settings.json hooks + skill
1603
1837
  Codex \u2192 writes agents.codex config + ~/.codex/hooks.json hooks + skill
1604
1838
  Claude \u2192 writes agents.claude config + ~/.claude/settings.json hooks + skill
1605
- status [--agent <qoder|codex|claude>] [--json]
1606
- ping [--agent <qoder|codex|claude>] [--json]
1607
- uninstall [--agent <qoder|codex|claude>] [--purge-config] [--purge-logs] [--purge-all] [--json]
1839
+ status [--agent <name>] [--json]
1840
+ ping [--agent <name>] [--json]
1841
+ uninstall [--agent <name>] [--purge-config] [--purge-logs] [--purge-all] [--json]
1608
1842
  teardown (alias for uninstall)
1609
1843
  Without --agent: removes hooks + skills for every agent.
1610
1844
  With --agent: removes only that agent's hooks + skill + config section.
@@ -1612,10 +1846,9 @@ COMMANDS
1612
1846
  --purge-logs also deletes ~/.ctxdb/logs/.
1613
1847
  --purge-all deletes everything under ~/.ctxdb/
1614
1848
  (does not run npm uninstall).
1615
- upgrade [--agent <name>] [--json]
1616
- Refresh skills + hooks for configured agents.
1617
- Run after npm update -g @aliyunrds/ctxdb.
1618
- Does not modify credentials.
1849
+ upgrade|update [--agent <name>] [--self-update] [--json]
1850
+ Refresh skills + hooks. --self-update also
1851
+ pulls latest package from npm.
1619
1852
 
1620
1853
  memory add <text> [--agent=<name>] [--user-id=...] [--metadata=K1=V1,K2=V2] [--no-infer]
1621
1854
  memory search <query> [--agent=<name>] [--top-k=10] [--threshold=0.4] [--knowledge]
@@ -1629,13 +1862,14 @@ COMMANDS
1629
1862
  memory update <memory-id> --text=<new-text> [--agent=<name>]
1630
1863
  memory delete <memory-id> | --all [--agent=<name>]
1631
1864
 
1865
+ kb create <kb-name> [--description=<desc>] [--agent=<name>]
1632
1866
  kb upload-text <kb-name> <doc-name> --text=<body> [--agent=<name>]
1633
- [--kb-description=...] [--file-path=<server-logical-path>] [--no-wait]
1867
+ [--file-path=<server-logical-path>] [--no-wait]
1634
1868
  kb upload-file <kb-name> <local-path> [--agent=<name>] [--doc-name=...]
1635
1869
  [--file-path=<server-logical-path>] [--no-wait]
1636
1870
  kb list [--agent=<name>]
1637
- kb documents-list <kb-name-or-id> [--agent=<name>]
1638
- kb document-get <kb-name-or-id> <doc-id> [--agent=<name>]
1871
+ kb documents-list <kb-name> [--agent=<name>]
1872
+ kb document-get <kb-name> <doc-id> [--agent=<name>]
1639
1873
  kb search <query> [--agent=<name>] [--kb=name1,name2] [--top-k=N] [--threshold=F]
1640
1874
  [--verbose] [--raw]
1641
1875
  Standalone KB recall (independent of memory).
@@ -1644,10 +1878,20 @@ COMMANDS
1644
1878
  doc_id?/tags? for citation.
1645
1879
  --raw: server response verbatim (curl parity).
1646
1880
 
1881
+ skill install --target <qoder|codex|claude|openclaw|hermes> [--json]
1882
+ skill install --path <dir> [--json]
1883
+ Install skill files (contextdb-memory,
1884
+ contextdb-knowledge) to an agent's skill directory.
1885
+ Does not write config or install hooks.
1886
+
1887
+ AGENT RESOLUTION (when --agent is omitted):
1888
+ 1. CTXDB_AGENT env var
1889
+ 2. agents.default section in ~/.ctxdb/ctxdb.json
1890
+
1647
1891
  ENV VARS (override selected ~/.ctxdb/ctxdb.json agent config):
1648
1892
  CTXDB_AGENT CTXDB_API_KEY CTXDB_BASE_URL CTXDB_USER_ID
1649
1893
 
1650
- CONFIG: ~/.ctxdb/ctxdb.json (agents.qoder / agents.codex / agents.claude)
1894
+ CONFIG: ~/.ctxdb/ctxdb.json (agents.default / agents.qoder / agents.codex / agents.claude)
1651
1895
  LOGS: ~/.ctxdb/logs/ctxdb.log
1652
1896
  `;
1653
1897
  var ROUTES = {
@@ -1657,6 +1901,10 @@ var ROUTES = {
1657
1901
  uninstall,
1658
1902
  teardown: uninstall,
1659
1903
  upgrade,
1904
+ update: upgrade,
1905
+ skill: {
1906
+ install: skillInstall
1907
+ },
1660
1908
  memory: {
1661
1909
  add: memoryAdd,
1662
1910
  search: memorySearch,
@@ -1666,6 +1914,7 @@ var ROUTES = {
1666
1914
  delete: memoryDelete
1667
1915
  },
1668
1916
  kb: {
1917
+ create: kbCreate,
1669
1918
  "upload-text": kbUploadText,
1670
1919
  "upload-file": kbUploadFile,
1671
1920
  list: kbList,
@@ -1676,7 +1925,7 @@ var ROUTES = {
1676
1925
  };
1677
1926
  async function main(argv = process.argv.slice(2)) {
1678
1927
  if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
1679
- process.stdout.write(HELP2);
1928
+ process.stdout.write(HELP5);
1680
1929
  return 0;
1681
1930
  }
1682
1931
  if (argv[0] === "-v" || argv[0] === "--version") {
@@ -1689,7 +1938,7 @@ async function main(argv = process.argv.slice(2)) {
1689
1938
  if (!route) {
1690
1939
  process.stderr.write(`unknown command: ${first}
1691
1940
  `);
1692
- process.stderr.write(HELP2);
1941
+ process.stderr.write(HELP5);
1693
1942
  return 2;
1694
1943
  }
1695
1944
  if (typeof route === "function") {
@@ -1702,6 +1951,13 @@ async function main(argv = process.argv.slice(2)) {
1702
1951
  );
1703
1952
  return 2;
1704
1953
  }
1954
+ if (rest[0] === "-h" || rest[0] === "--help") {
1955
+ process.stdout.write(
1956
+ `usage: ctxdb ${first} <${Object.keys(route).join("|")}> ...
1957
+ `
1958
+ );
1959
+ return 0;
1960
+ }
1705
1961
  const [sub, ...subRest] = rest;
1706
1962
  const handler = route[sub];
1707
1963
  if (!handler) {