@aliyunrds/ctxdb 1.0.1-beta.2 → 1.0.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/README.md +79 -11
- package/dist/{chunk-TKMIWM6Q.js → chunk-6FZL67GH.js} +3 -1
- package/dist/{chunk-CYCD234A.js → chunk-EUQ3OFCQ.js} +37 -32
- package/dist/chunk-FD3AMXVU.js +111 -0
- package/dist/chunk-IMYLU5C2.js +514 -0
- package/dist/chunk-RZONPKY4.js +185 -0
- package/dist/{chunk-UULWJJT4.js → chunk-TGVURF54.js} +1 -1
- package/dist/{chunk-6S5RJYBC.js → chunk-UH7AJF6F.js} +25 -19
- package/dist/cli/main.js +484 -165
- package/dist/hooks/hermes-post-llm-call.js +220 -0
- package/dist/hooks/hermes-pre-llm-call.js +145 -0
- package/dist/hooks/session-start.js +9 -178
- package/dist/hooks/stop.js +4 -488
- package/dist/hooks/user-prompt-submit.js +8 -105
- package/dist/opencode/index.js +142 -11
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +24 -7
- package/dist/setup/skills/contextdb-memory/SKILL.md +6 -6
- package/package.json +5 -3
package/dist/cli/main.js
CHANGED
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_FILE_INGEST_TIMEOUT_MS,
|
|
4
4
|
DEFAULT_INGEST_TIMEOUT_MS,
|
|
5
|
-
compactChunk,
|
|
6
5
|
compactKbQueryResponse,
|
|
7
6
|
createKb,
|
|
8
7
|
getDocument,
|
|
9
8
|
listDocuments,
|
|
10
9
|
listKnowledgeBases,
|
|
11
|
-
minimalChunk,
|
|
12
10
|
minimalKbQueryResponse,
|
|
13
11
|
pollIngest,
|
|
14
12
|
uploadFile,
|
|
15
13
|
uploadText
|
|
16
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-UH7AJF6F.js";
|
|
17
15
|
import {
|
|
18
16
|
DEFAULT_BASE_URL,
|
|
19
17
|
DEFAULT_USER_ID,
|
|
@@ -31,7 +29,7 @@ import {
|
|
|
31
29
|
removeAgent,
|
|
32
30
|
save,
|
|
33
31
|
writeInstalledPkgVersion
|
|
34
|
-
} from "../chunk-
|
|
32
|
+
} from "../chunk-6FZL67GH.js";
|
|
35
33
|
|
|
36
34
|
// src/cli/util.ts
|
|
37
35
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -42,6 +40,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
42
40
|
// setup / init
|
|
43
41
|
"no-install-skill",
|
|
44
42
|
"no-validate",
|
|
43
|
+
// update / upgrade
|
|
44
|
+
"self-update",
|
|
45
|
+
"no-self-update",
|
|
45
46
|
// uninstall
|
|
46
47
|
"purge-config",
|
|
47
48
|
"purge-logs",
|
|
@@ -174,6 +175,10 @@ function formatStatusResult(r) {
|
|
|
174
175
|
["connected", r.connected],
|
|
175
176
|
["version", r.version]
|
|
176
177
|
];
|
|
178
|
+
if (r.hooks_node !== void 0) {
|
|
179
|
+
display.push(["hooks_node", r.hooks_node]);
|
|
180
|
+
display.push(["hooks_node_ok", r.hooks_node_ok]);
|
|
181
|
+
}
|
|
177
182
|
if (r.ping_error) display.push(["ping_error", r.ping_error]);
|
|
178
183
|
const maxKey = Math.max(...display.map(([k]) => k.length));
|
|
179
184
|
for (const [k, v] of display) {
|
|
@@ -240,6 +245,7 @@ import { homedir } from "os";
|
|
|
240
245
|
import { join, dirname, resolve } from "path";
|
|
241
246
|
import { fileURLToPath } from "url";
|
|
242
247
|
import { execFileSync } from "child_process";
|
|
248
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
243
249
|
var UNINSTALL_NPM_HINT = "To also remove the npm binaries: `npm uninstall -g @aliyunrds/ctxdb @aliyunrds/ctxdb-shared`";
|
|
244
250
|
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.)";
|
|
245
251
|
function skillInstallRoot(agent) {
|
|
@@ -254,12 +260,14 @@ function skillInstallRoot(agent) {
|
|
|
254
260
|
return join(homedir(), ".claude", "skills");
|
|
255
261
|
case "opencode":
|
|
256
262
|
return join(homedir(), ".config", "opencode", "skills");
|
|
263
|
+
case "hermes":
|
|
264
|
+
return join(homedir(), ".hermes", "skills");
|
|
257
265
|
}
|
|
258
266
|
}
|
|
259
267
|
var SKILL_DIRS = ["contextdb-memory", "contextdb-knowledge"];
|
|
260
268
|
var LEGACY_SKILL_DIRS = ["ctxdb"];
|
|
261
269
|
function agentSupportsHooks(agent) {
|
|
262
|
-
if (agent === "opencode") return false;
|
|
270
|
+
if (agent === "opencode" || agent === "hermes") return false;
|
|
263
271
|
return isBuiltinAgent(agent);
|
|
264
272
|
}
|
|
265
273
|
function hookConfigPath(agent) {
|
|
@@ -273,6 +281,7 @@ function hookConfigPath(agent) {
|
|
|
273
281
|
case "claude":
|
|
274
282
|
return join(homedir(), ".claude", "settings.json");
|
|
275
283
|
case "opencode":
|
|
284
|
+
case "hermes":
|
|
276
285
|
return null;
|
|
277
286
|
}
|
|
278
287
|
}
|
|
@@ -352,6 +361,21 @@ async function runSetup(options) {
|
|
|
352
361
|
}
|
|
353
362
|
if (installSkill && isBuiltinAgent(agent)) {
|
|
354
363
|
const installRoot = skillInstallRoot(agent);
|
|
364
|
+
for (const legacy of LEGACY_SKILL_DIRS) {
|
|
365
|
+
const legacyDir = join(installRoot, legacy);
|
|
366
|
+
if (!existsSync(legacyDir)) continue;
|
|
367
|
+
try {
|
|
368
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
369
|
+
steps.push({ step: "remove-legacy-skill", ok: true, detail: legacyDir });
|
|
370
|
+
} catch (err) {
|
|
371
|
+
steps.push({
|
|
372
|
+
step: "remove-legacy-skill",
|
|
373
|
+
ok: false,
|
|
374
|
+
detail: `${legacyDir}: ${err?.message ?? String(err)}`
|
|
375
|
+
});
|
|
376
|
+
return { ok: false, steps };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
355
379
|
const skillResult = installSkillsTo(installRoot, agent);
|
|
356
380
|
steps.push(...skillResult.steps);
|
|
357
381
|
if (!skillResult.ok) return { ok: false, steps };
|
|
@@ -438,6 +462,76 @@ async function runSetup(options) {
|
|
|
438
462
|
}
|
|
439
463
|
}
|
|
440
464
|
}
|
|
465
|
+
if (agent === "hermes") {
|
|
466
|
+
const hookPaths = locateHookPaths();
|
|
467
|
+
const hermesPreHook = hookPaths?.hermesPreLlmCall ?? null;
|
|
468
|
+
const hermesPostHook = hookPaths?.hermesPostLlmCall ?? null;
|
|
469
|
+
steps.push({
|
|
470
|
+
step: "locate-hermes-pre-hook",
|
|
471
|
+
ok: hermesPreHook !== null,
|
|
472
|
+
detail: hermesPreHook ?? "could not locate hermes-pre-llm-call in dist/ or src/"
|
|
473
|
+
});
|
|
474
|
+
steps.push({
|
|
475
|
+
step: "locate-hermes-post-hook",
|
|
476
|
+
ok: hermesPostHook !== null,
|
|
477
|
+
detail: hermesPostHook ?? "could not locate hermes-post-llm-call in dist/ or src/"
|
|
478
|
+
});
|
|
479
|
+
if (!hermesPreHook || !hermesPostHook) return { ok: false, steps };
|
|
480
|
+
const nodePath = process.execPath;
|
|
481
|
+
const nodeCheck = verifyNodeBinary(nodePath);
|
|
482
|
+
steps.push({
|
|
483
|
+
step: "resolve-node",
|
|
484
|
+
ok: nodeCheck.ok,
|
|
485
|
+
detail: nodeCheck.ok ? `${nodePath} (${nodeCheck.version})` : `node not executable: ${nodeCheck.error}`
|
|
486
|
+
});
|
|
487
|
+
if (!nodeCheck.ok) return { ok: false, steps };
|
|
488
|
+
try {
|
|
489
|
+
backupHermesConfig();
|
|
490
|
+
steps.push({ step: "backup-hermes-config", ok: true });
|
|
491
|
+
const removed = writeHermesHooksConfig(hermesPreHook, hermesPostHook, nodePath);
|
|
492
|
+
steps.push({
|
|
493
|
+
step: "append-hermes-hook",
|
|
494
|
+
ok: true,
|
|
495
|
+
detail: `pre_llm_call + post_llm_call written${removed > 0 ? `, replaced ${removed} stale` : ""}`
|
|
496
|
+
});
|
|
497
|
+
} catch (err) {
|
|
498
|
+
steps.push({
|
|
499
|
+
step: "hermes-config-update",
|
|
500
|
+
ok: false,
|
|
501
|
+
detail: err?.message ?? String(err)
|
|
502
|
+
});
|
|
503
|
+
return { ok: false, steps };
|
|
504
|
+
}
|
|
505
|
+
const chmodErrors = [];
|
|
506
|
+
for (const hookPath of [hermesPreHook, hermesPostHook]) {
|
|
507
|
+
try {
|
|
508
|
+
chmodSync(hookPath, 493);
|
|
509
|
+
} catch (err) {
|
|
510
|
+
chmodErrors.push(`${hookPath}: ${err?.message ?? String(err)}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
steps.push({
|
|
514
|
+
step: "chmod-hermes-hook",
|
|
515
|
+
ok: true,
|
|
516
|
+
detail: chmodErrors.length > 0 ? `skipped: ${chmodErrors.join("; ")}` : void 0
|
|
517
|
+
});
|
|
518
|
+
try {
|
|
519
|
+
const allowlist = checkHermesHookAllowlist(
|
|
520
|
+
hermesCtxdbEntriesByEvent(readHermesConfig())
|
|
521
|
+
);
|
|
522
|
+
steps.push({
|
|
523
|
+
step: "check-hermes-hook-allowlist",
|
|
524
|
+
ok: allowlist.ok,
|
|
525
|
+
detail: allowlist.detail
|
|
526
|
+
});
|
|
527
|
+
} catch (err) {
|
|
528
|
+
steps.push({
|
|
529
|
+
step: "check-hermes-hook-allowlist",
|
|
530
|
+
ok: false,
|
|
531
|
+
detail: `${err?.message ?? String(err)}. ${hermesHookApprovalHint()}`
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
441
535
|
if (agent === "opencode") {
|
|
442
536
|
const absEntry = resolveOpencodePluginEntry();
|
|
443
537
|
if (!absEntry) {
|
|
@@ -493,6 +587,11 @@ async function runSetup(options) {
|
|
|
493
587
|
"Restart OpenCode to load the plugin. `ctxdb upgrade` auto-refreshes the shim after npm updates; only re-run setup if you move ctxdb's install dir."
|
|
494
588
|
);
|
|
495
589
|
}
|
|
590
|
+
if (ok && agent === "hermes") {
|
|
591
|
+
hints.unshift(
|
|
592
|
+
`Hermes shell hooks: ${hermesConfigPath()} \u2192 pre_llm_call + post_llm_call`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
496
595
|
return { ok, steps, hints: hints.length > 0 ? hints : void 0 };
|
|
497
596
|
}
|
|
498
597
|
function runRemove(agent, options = {}) {
|
|
@@ -626,6 +725,42 @@ function runRemove(agent, options = {}) {
|
|
|
626
725
|
}
|
|
627
726
|
}
|
|
628
727
|
}
|
|
728
|
+
if (agent === "hermes") {
|
|
729
|
+
try {
|
|
730
|
+
backupHermesConfig();
|
|
731
|
+
const removed = removeHermesHooksConfig();
|
|
732
|
+
steps.push({
|
|
733
|
+
step: "remove-hermes-hooks",
|
|
734
|
+
ok: true,
|
|
735
|
+
detail: removed > 0 ? `removed ${removed} entries` : "nothing to remove"
|
|
736
|
+
});
|
|
737
|
+
} catch (err) {
|
|
738
|
+
steps.push({
|
|
739
|
+
step: "remove-hermes-hooks",
|
|
740
|
+
ok: false,
|
|
741
|
+
detail: err?.message ?? String(err)
|
|
742
|
+
});
|
|
743
|
+
return { ok: false, steps };
|
|
744
|
+
}
|
|
745
|
+
const root = skillInstallRoot(agent);
|
|
746
|
+
const dirs = [
|
|
747
|
+
...SKILL_DIRS.map((d) => join(root, d)),
|
|
748
|
+
...LEGACY_SKILL_DIRS.map((d) => join(root, d))
|
|
749
|
+
];
|
|
750
|
+
for (const p of dirs) {
|
|
751
|
+
if (!existsSync(p)) continue;
|
|
752
|
+
try {
|
|
753
|
+
rmSync(p, { recursive: true, force: true });
|
|
754
|
+
steps.push({ step: "remove-skill-dir", ok: true, detail: p });
|
|
755
|
+
} catch (err) {
|
|
756
|
+
steps.push({
|
|
757
|
+
step: "remove-skill-dir",
|
|
758
|
+
ok: false,
|
|
759
|
+
detail: `${p}: ${err?.message ?? String(err)}`
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
629
764
|
try {
|
|
630
765
|
const r = removeAgent(agent, void 0, {
|
|
631
766
|
keepEmptyShell: !!options.keepConfigShell
|
|
@@ -659,7 +794,14 @@ function runRemove(agent, options = {}) {
|
|
|
659
794
|
return { ok: steps.every((s) => s.ok), steps };
|
|
660
795
|
}
|
|
661
796
|
function checkAgentHome(agent) {
|
|
662
|
-
if (agent === "
|
|
797
|
+
if (agent === "hermes" && process.platform === "win32") {
|
|
798
|
+
return {
|
|
799
|
+
step: "check-agent-home",
|
|
800
|
+
ok: false,
|
|
801
|
+
detail: "hermes setup currently supports macOS/Linux only; Windows Hermes will be handled separately"
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
if (agent === "opencode" || agent === "hermes") {
|
|
663
805
|
const primary = agentHomeDir(agent);
|
|
664
806
|
try {
|
|
665
807
|
mkdirSync(primary, { recursive: true });
|
|
@@ -782,6 +924,7 @@ function verifyNodeBinary(nodePath) {
|
|
|
782
924
|
}
|
|
783
925
|
function checkHookNodePaths(agent) {
|
|
784
926
|
if (agent === "opencode") return checkOpencodeShimHealth();
|
|
927
|
+
if (agent === "hermes") return checkHermesHookHealth();
|
|
785
928
|
const empty = {
|
|
786
929
|
installed: false,
|
|
787
930
|
nodePaths: [],
|
|
@@ -986,12 +1129,16 @@ function hookPathsFromDir(dir, ext) {
|
|
|
986
1129
|
const stp = join(dir, `stop.${ext}`);
|
|
987
1130
|
const ss = join(dir, `session-start.${ext}`);
|
|
988
1131
|
const ptu = join(dir, `pre-tool-use.${ext}`);
|
|
1132
|
+
const hpl = join(dir, `hermes-pre-llm-call.${ext}`);
|
|
1133
|
+
const hpost = join(dir, `hermes-post-llm-call.${ext}`);
|
|
989
1134
|
if (existsSync(ups) && existsSync(stp) && existsSync(ss)) {
|
|
990
1135
|
return {
|
|
991
1136
|
userPromptSubmit: ups,
|
|
992
1137
|
stop: stp,
|
|
993
1138
|
sessionStart: ss,
|
|
994
|
-
preToolUse: existsSync(ptu) ? ptu : void 0
|
|
1139
|
+
preToolUse: existsSync(ptu) ? ptu : void 0,
|
|
1140
|
+
hermesPreLlmCall: existsSync(hpl) ? hpl : void 0,
|
|
1141
|
+
hermesPostLlmCall: existsSync(hpost) ? hpost : void 0
|
|
995
1142
|
};
|
|
996
1143
|
}
|
|
997
1144
|
return null;
|
|
@@ -1194,6 +1341,242 @@ function entryIsCtxdb(entry) {
|
|
|
1194
1341
|
(h) => h?.type === "command" && typeof h.command === "string" && (h.command.includes("@aliyunrds/ctxdb-qoder") || h.command.includes("@aliyunrds/ctxdb") || h.command.includes("ctxdb-qoder") || h.command.includes("rds-ctxdb") || h.command.includes("qoder-ctxdb"))
|
|
1195
1342
|
);
|
|
1196
1343
|
}
|
|
1344
|
+
var HERMES_PRE_LLM_CALL_EVENT = "pre_llm_call";
|
|
1345
|
+
var HERMES_POST_LLM_CALL_EVENT = "post_llm_call";
|
|
1346
|
+
var HERMES_HOOK_EVENTS = [
|
|
1347
|
+
HERMES_PRE_LLM_CALL_EVENT,
|
|
1348
|
+
HERMES_POST_LLM_CALL_EVENT
|
|
1349
|
+
];
|
|
1350
|
+
var HERMES_HOOK_TIMEOUT_SECONDS = 60;
|
|
1351
|
+
var HERMES_ALLOWLIST_FILENAME = "shell-hooks-allowlist.json";
|
|
1352
|
+
function hermesConfigPath() {
|
|
1353
|
+
return join(homedir(), ".hermes", "config.yaml");
|
|
1354
|
+
}
|
|
1355
|
+
function hermesAllowlistPath() {
|
|
1356
|
+
return join(dirname(hermesConfigPath()), HERMES_ALLOWLIST_FILENAME);
|
|
1357
|
+
}
|
|
1358
|
+
function hermesHookApprovalHint(events = HERMES_HOOK_EVENTS) {
|
|
1359
|
+
const hookNames = events.map((event) => `\`${event}\``).join(" and ");
|
|
1360
|
+
return `Approve the Hermes ctxdb hooks ${hookNames}: run \`hermes --accept-hooks chat\` once. This command opens an interactive chat; after Hermes starts, exit the chat, then rerun \`ctxdb setup --agent hermes\`.`;
|
|
1361
|
+
}
|
|
1362
|
+
function backupHermesConfig() {
|
|
1363
|
+
backupSettingsFile(hermesConfigPath());
|
|
1364
|
+
}
|
|
1365
|
+
function readHermesConfig() {
|
|
1366
|
+
const path = hermesConfigPath();
|
|
1367
|
+
if (!existsSync(path)) return {};
|
|
1368
|
+
const raw = readFileSync(path, "utf-8");
|
|
1369
|
+
if (!raw.trim()) return {};
|
|
1370
|
+
const parsed = parseYaml(raw);
|
|
1371
|
+
if (parsed === null || parsed === void 0) return {};
|
|
1372
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1373
|
+
throw new Error(`${path}: expected a YAML mapping at the top level`);
|
|
1374
|
+
}
|
|
1375
|
+
return parsed;
|
|
1376
|
+
}
|
|
1377
|
+
function writeHermesConfig(data) {
|
|
1378
|
+
const path = hermesConfigPath();
|
|
1379
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1380
|
+
writeFileSync(path, stringifyYaml(data, { lineWidth: 0 }), "utf-8");
|
|
1381
|
+
}
|
|
1382
|
+
function hermesHooksMap(data) {
|
|
1383
|
+
if (!data.hooks || typeof data.hooks !== "object" || Array.isArray(data.hooks)) {
|
|
1384
|
+
data.hooks = {};
|
|
1385
|
+
}
|
|
1386
|
+
return data.hooks;
|
|
1387
|
+
}
|
|
1388
|
+
function quoteHermesCommandPart(value) {
|
|
1389
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1390
|
+
}
|
|
1391
|
+
function hermesHookCommand(scriptPath, nodePath) {
|
|
1392
|
+
return `${quoteHermesCommandPart(nodePath)} ${quoteHermesCommandPart(scriptPath)} --agent=hermes`;
|
|
1393
|
+
}
|
|
1394
|
+
function hermesEntryIsCtxdb(entry) {
|
|
1395
|
+
if (!entry || typeof entry !== "object") return false;
|
|
1396
|
+
if (entry[ENTRY_MARKER_KEY] === ENTRY_MARKER_VALUE) return true;
|
|
1397
|
+
if (typeof entry.command === "string" && (entry.command.includes("hermes-pre-llm-call") || entry.command.includes("hermes-post-llm-call")) && entry.command.includes("--agent=hermes")) {
|
|
1398
|
+
return true;
|
|
1399
|
+
}
|
|
1400
|
+
return false;
|
|
1401
|
+
}
|
|
1402
|
+
function hermesCtxdbEntriesByEvent(data) {
|
|
1403
|
+
const hooks = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks) ? data.hooks : {};
|
|
1404
|
+
const entriesByEvent = /* @__PURE__ */ new Map();
|
|
1405
|
+
for (const event of HERMES_HOOK_EVENTS) {
|
|
1406
|
+
const entries = Array.isArray(hooks[event]) ? hooks[event].filter(hermesEntryIsCtxdb) : [];
|
|
1407
|
+
entriesByEvent.set(event, entries);
|
|
1408
|
+
}
|
|
1409
|
+
return entriesByEvent;
|
|
1410
|
+
}
|
|
1411
|
+
function checkHermesHookAllowlist(entriesByEvent) {
|
|
1412
|
+
const path = hermesAllowlistPath();
|
|
1413
|
+
let approvals = [];
|
|
1414
|
+
let readError = null;
|
|
1415
|
+
if (existsSync(path)) {
|
|
1416
|
+
try {
|
|
1417
|
+
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
1418
|
+
if (!Array.isArray(data?.approvals)) {
|
|
1419
|
+
readError = "expected an approvals array";
|
|
1420
|
+
} else {
|
|
1421
|
+
approvals = data.approvals;
|
|
1422
|
+
}
|
|
1423
|
+
} catch (err) {
|
|
1424
|
+
readError = err?.message ?? String(err);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
const missingEvents = HERMES_HOOK_EVENTS.filter((event) => {
|
|
1428
|
+
const entries = entriesByEvent.get(event) ?? [];
|
|
1429
|
+
return entries.length === 0 || entries.some((entry) => {
|
|
1430
|
+
if (typeof entry?.command !== "string") return true;
|
|
1431
|
+
return !approvals.some(
|
|
1432
|
+
(approval) => approval && typeof approval === "object" && approval.event === event && approval.command === entry.command
|
|
1433
|
+
);
|
|
1434
|
+
});
|
|
1435
|
+
});
|
|
1436
|
+
if (missingEvents.length === 0) {
|
|
1437
|
+
return {
|
|
1438
|
+
ok: true,
|
|
1439
|
+
detail: `pre_llm_call + post_llm_call approved in ${path}`
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
const reason = readError ? `${path} is invalid (${readError})` : `${missingEvents.join(", ")} not allowlisted in ${path}`;
|
|
1443
|
+
return {
|
|
1444
|
+
ok: false,
|
|
1445
|
+
detail: `${reason}. ${hermesHookApprovalHint(missingEvents)}`
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function stripHermesHooks(data) {
|
|
1449
|
+
const hooks = hermesHooksMap(data);
|
|
1450
|
+
let removed = 0;
|
|
1451
|
+
for (const event of HERMES_HOOK_EVENTS) {
|
|
1452
|
+
const arr = hooks[event];
|
|
1453
|
+
if (!Array.isArray(arr)) continue;
|
|
1454
|
+
const before = arr.length;
|
|
1455
|
+
hooks[event] = arr.filter((entry) => !hermesEntryIsCtxdb(entry));
|
|
1456
|
+
removed += before - hooks[event].length;
|
|
1457
|
+
}
|
|
1458
|
+
return removed;
|
|
1459
|
+
}
|
|
1460
|
+
function writeHermesHooksConfig(preScriptPath, postScriptPath, nodePath) {
|
|
1461
|
+
const data = readHermesConfig();
|
|
1462
|
+
const removed = stripHermesHooks(data);
|
|
1463
|
+
const hooks = hermesHooksMap(data);
|
|
1464
|
+
const scripts = /* @__PURE__ */ new Map([
|
|
1465
|
+
[HERMES_PRE_LLM_CALL_EVENT, preScriptPath],
|
|
1466
|
+
[HERMES_POST_LLM_CALL_EVENT, postScriptPath]
|
|
1467
|
+
]);
|
|
1468
|
+
for (const event of HERMES_HOOK_EVENTS) {
|
|
1469
|
+
if (!Array.isArray(hooks[event])) hooks[event] = [];
|
|
1470
|
+
hooks[event].push({
|
|
1471
|
+
command: hermesHookCommand(scripts.get(event), nodePath),
|
|
1472
|
+
timeout: HERMES_HOOK_TIMEOUT_SECONDS,
|
|
1473
|
+
[ENTRY_MARKER_KEY]: ENTRY_MARKER_VALUE,
|
|
1474
|
+
[ENTRY_AGENT_KEY]: "hermes"
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
writeHermesConfig(data);
|
|
1478
|
+
return removed;
|
|
1479
|
+
}
|
|
1480
|
+
function removeHermesHooksConfig() {
|
|
1481
|
+
const path = hermesConfigPath();
|
|
1482
|
+
if (!existsSync(path)) return 0;
|
|
1483
|
+
const data = readHermesConfig();
|
|
1484
|
+
const removed = stripHermesHooks(data);
|
|
1485
|
+
if (removed > 0) writeHermesConfig(data);
|
|
1486
|
+
return removed;
|
|
1487
|
+
}
|
|
1488
|
+
function firstCommandToken(command) {
|
|
1489
|
+
const s = command.trim();
|
|
1490
|
+
if (!s) return null;
|
|
1491
|
+
const quote = s[0];
|
|
1492
|
+
if (quote === "'" || quote === '"') {
|
|
1493
|
+
const end = s.indexOf(quote, 1);
|
|
1494
|
+
if (end > 1) return s.slice(1, end);
|
|
1495
|
+
}
|
|
1496
|
+
return s.split(/\s+/, 1)[0] ?? null;
|
|
1497
|
+
}
|
|
1498
|
+
function checkHermesHookHealth() {
|
|
1499
|
+
const path = hermesConfigPath();
|
|
1500
|
+
const empty = {
|
|
1501
|
+
installed: false,
|
|
1502
|
+
nodePaths: [],
|
|
1503
|
+
nodeOk: true,
|
|
1504
|
+
detail: "no hermes hooks installed"
|
|
1505
|
+
};
|
|
1506
|
+
if (!existsSync(path)) return empty;
|
|
1507
|
+
let data;
|
|
1508
|
+
try {
|
|
1509
|
+
data = readHermesConfig();
|
|
1510
|
+
} catch {
|
|
1511
|
+
return empty;
|
|
1512
|
+
}
|
|
1513
|
+
const entriesByEvent = hermesCtxdbEntriesByEvent(data);
|
|
1514
|
+
const installedEvents = HERMES_HOOK_EVENTS.filter(
|
|
1515
|
+
(event) => (entriesByEvent.get(event)?.length ?? 0) > 0
|
|
1516
|
+
);
|
|
1517
|
+
if (installedEvents.length === 0) return empty;
|
|
1518
|
+
const missingEvents = HERMES_HOOK_EVENTS.filter(
|
|
1519
|
+
(event) => (entriesByEvent.get(event)?.length ?? 0) === 0
|
|
1520
|
+
);
|
|
1521
|
+
if (missingEvents.length > 0) {
|
|
1522
|
+
return {
|
|
1523
|
+
installed: true,
|
|
1524
|
+
nodePaths: [],
|
|
1525
|
+
nodeOk: false,
|
|
1526
|
+
detail: `missing ctxdb Hermes hooks: ${missingEvents.join(", ")} \u2014 re-run \`ctxdb setup --agent hermes\``
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
const nodePaths = /* @__PURE__ */ new Set();
|
|
1530
|
+
for (const event of HERMES_HOOK_EVENTS) {
|
|
1531
|
+
for (const entry of entriesByEvent.get(event) ?? []) {
|
|
1532
|
+
if (typeof entry.command !== "string") continue;
|
|
1533
|
+
const nodePath = firstCommandToken(entry.command);
|
|
1534
|
+
if (nodePath && (nodePath.includes("/") || nodePath.includes("\\"))) {
|
|
1535
|
+
nodePaths.add(nodePath);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
if (nodePaths.size === 0) {
|
|
1540
|
+
const allowlist2 = checkHermesHookAllowlist(entriesByEvent);
|
|
1541
|
+
return {
|
|
1542
|
+
installed: true,
|
|
1543
|
+
nodePaths: [],
|
|
1544
|
+
nodeOk: allowlist2.ok,
|
|
1545
|
+
detail: allowlist2.ok ? "hermes pre_llm_call + post_llm_call hooks installed; allowlisted" : allowlist2.detail
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
const paths = [...nodePaths];
|
|
1549
|
+
const bad = [];
|
|
1550
|
+
let goodVersion = "";
|
|
1551
|
+
for (const p of paths) {
|
|
1552
|
+
const v = verifyNodeBinary(p);
|
|
1553
|
+
if (!v.ok) bad.push(`${p}: ${v.error}`);
|
|
1554
|
+
else if (!goodVersion) goodVersion = v.version ?? "";
|
|
1555
|
+
}
|
|
1556
|
+
if (bad.length > 0) {
|
|
1557
|
+
return {
|
|
1558
|
+
installed: true,
|
|
1559
|
+
nodePaths: paths,
|
|
1560
|
+
nodeOk: false,
|
|
1561
|
+
detail: `node not executable: ${bad.join("; ")} \u2014 re-run \`ctxdb setup --agent hermes\``
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
const allowlist = checkHermesHookAllowlist(entriesByEvent);
|
|
1565
|
+
if (!allowlist.ok) {
|
|
1566
|
+
return {
|
|
1567
|
+
installed: true,
|
|
1568
|
+
nodePaths: paths,
|
|
1569
|
+
nodeOk: false,
|
|
1570
|
+
detail: allowlist.detail
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
return {
|
|
1574
|
+
installed: true,
|
|
1575
|
+
nodePaths: paths,
|
|
1576
|
+
nodeOk: true,
|
|
1577
|
+
detail: `hermes pre_llm_call + post_llm_call hooks: ${paths[0]} (${goodVersion}); allowlisted`
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1197
1580
|
function codexConfigTomlPath() {
|
|
1198
1581
|
return join(homedir(), ".codex", "config.toml");
|
|
1199
1582
|
}
|
|
@@ -1390,7 +1773,7 @@ function describeCodexFeatureMutation(status2, createdNew) {
|
|
|
1390
1773
|
return "updated ~/.codex/config.toml";
|
|
1391
1774
|
}
|
|
1392
1775
|
}
|
|
1393
|
-
function runUpgrade(options = {}) {
|
|
1776
|
+
async function runUpgrade(options = {}) {
|
|
1394
1777
|
const agents = options.agent ? [options.agent] : configuredAgents(options.configPath).filter(isBuiltinAgent);
|
|
1395
1778
|
if (agents.length === 0) {
|
|
1396
1779
|
return {
|
|
@@ -1411,113 +1794,15 @@ function runUpgrade(options = {}) {
|
|
|
1411
1794
|
}
|
|
1412
1795
|
const results = {};
|
|
1413
1796
|
for (const agent of agents) {
|
|
1414
|
-
results[agent] =
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
const steps = [];
|
|
1421
|
-
const homeCheck = checkAgentHome(agent);
|
|
1422
|
-
steps.push(homeCheck);
|
|
1423
|
-
if (!homeCheck.ok) {
|
|
1424
|
-
return { ok: true, steps, hints: [`${agent}: skipped \u2014 agent home not found`] };
|
|
1425
|
-
}
|
|
1426
|
-
const root = skillInstallRoot(agent);
|
|
1427
|
-
for (const legacy of LEGACY_SKILL_DIRS) {
|
|
1428
|
-
const legacyDir = join(root, legacy);
|
|
1429
|
-
if (!existsSync(legacyDir)) continue;
|
|
1430
|
-
try {
|
|
1431
|
-
rmSync(legacyDir, { recursive: true, force: true });
|
|
1432
|
-
steps.push({ step: "remove-legacy-skill", ok: true, detail: legacyDir });
|
|
1433
|
-
} catch {
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
const skillResult = installSkillsTo(root, agent);
|
|
1437
|
-
steps.push(...skillResult.steps);
|
|
1438
|
-
if (!skillResult.ok) return { ok: false, steps };
|
|
1439
|
-
if (agentSupportsHooks(agent)) {
|
|
1440
|
-
const hookPaths = locateHookPaths();
|
|
1441
|
-
if (!hookPaths) {
|
|
1442
|
-
steps.push({ step: "locate-hooks", ok: false, detail: "hook scripts not found in dist/ or src/" });
|
|
1443
|
-
return { ok: false, steps };
|
|
1444
|
-
}
|
|
1445
|
-
const nodePath = process.execPath;
|
|
1446
|
-
const nodeCheck = verifyNodeBinary(nodePath);
|
|
1447
|
-
if (!nodeCheck.ok) {
|
|
1448
|
-
steps.push({ step: "resolve-node", ok: false, detail: `node not executable: ${nodeCheck.error}` });
|
|
1449
|
-
return { ok: false, steps };
|
|
1450
|
-
}
|
|
1451
|
-
try {
|
|
1452
|
-
backupSettingsJson(agent);
|
|
1453
|
-
const removed = stripCtxdbHooks(agent);
|
|
1454
|
-
appendHooks(agent, hookPaths);
|
|
1455
|
-
steps.push({
|
|
1456
|
-
step: "refresh-hooks",
|
|
1457
|
-
ok: true,
|
|
1458
|
-
detail: `${HOOK_EVENTS.length} hooks written (node: ${nodePath})${removed > 0 ? `, replaced ${removed} stale` : ""}`
|
|
1459
|
-
});
|
|
1460
|
-
} catch (err) {
|
|
1461
|
-
steps.push({ step: "refresh-hooks", ok: false, detail: err?.message ?? String(err) });
|
|
1462
|
-
return { ok: false, steps };
|
|
1463
|
-
}
|
|
1464
|
-
try {
|
|
1465
|
-
chmodSync(hookPaths.userPromptSubmit, 493);
|
|
1466
|
-
chmodSync(hookPaths.stop, 493);
|
|
1467
|
-
chmodSync(hookPaths.sessionStart, 493);
|
|
1468
|
-
if (hookPaths.preToolUse && existsSync(hookPaths.preToolUse)) {
|
|
1469
|
-
chmodSync(hookPaths.preToolUse, 493);
|
|
1470
|
-
}
|
|
1471
|
-
} catch {
|
|
1472
|
-
}
|
|
1473
|
-
if (agent === "codex") {
|
|
1474
|
-
steps.push(enableCodexHooksFeature());
|
|
1475
|
-
}
|
|
1476
|
-
const primaryHome = agentHomeDir(agent);
|
|
1477
|
-
const variantHomes = agentHomeDirs(agent).filter((d) => d !== primaryHome && existsSync(d));
|
|
1478
|
-
for (const vHome of variantHomes) {
|
|
1479
|
-
const tag = vHome.split("/").pop() ?? vHome;
|
|
1480
|
-
const vSkillRoot = join(vHome, "skills");
|
|
1481
|
-
const vr = installSkillsTo(vSkillRoot, agent);
|
|
1482
|
-
steps.push(...vr.steps.map((s) => ({ ...s, step: `${tag}:${s.step}` })));
|
|
1483
|
-
try {
|
|
1484
|
-
const vPath = join(vHome, "settings.json");
|
|
1485
|
-
backupSettingsFile(vPath);
|
|
1486
|
-
const vRemoved = stripCtxdbHooksFromFile(vPath);
|
|
1487
|
-
appendHooksToFile(vPath, agent, hookPaths);
|
|
1488
|
-
steps.push({
|
|
1489
|
-
step: `${tag}:refresh-hooks`,
|
|
1490
|
-
ok: true,
|
|
1491
|
-
detail: vRemoved > 0 ? `replaced ${vRemoved} stale` : void 0
|
|
1492
|
-
});
|
|
1493
|
-
} catch (err) {
|
|
1494
|
-
steps.push({ step: `${tag}:refresh-hooks`, ok: false, detail: err?.message ?? String(err) });
|
|
1495
|
-
}
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
if (agent === "opencode") {
|
|
1499
|
-
const absEntry = resolveOpencodePluginEntry();
|
|
1500
|
-
if (!absEntry) {
|
|
1501
|
-
steps.push({
|
|
1502
|
-
step: "refresh-opencode-shim",
|
|
1503
|
-
ok: false,
|
|
1504
|
-
detail: "could not resolve plugin entry under @aliyunrds/ctxdb/dist/opencode"
|
|
1505
|
-
});
|
|
1506
|
-
return { ok: false, steps };
|
|
1507
|
-
}
|
|
1508
|
-
try {
|
|
1509
|
-
writeOpencodeShim(absEntry);
|
|
1510
|
-
steps.push({
|
|
1511
|
-
step: "refresh-opencode-shim",
|
|
1512
|
-
ok: true,
|
|
1513
|
-
detail: `${opencodeShimPath()} \u2192 ${absEntry}`
|
|
1514
|
-
});
|
|
1515
|
-
} catch (err) {
|
|
1516
|
-
steps.push({ step: "refresh-opencode-shim", ok: false, detail: err?.message ?? String(err) });
|
|
1517
|
-
return { ok: false, steps };
|
|
1518
|
-
}
|
|
1797
|
+
results[agent] = await runSetup({
|
|
1798
|
+
agent,
|
|
1799
|
+
// Updating local package artifacts must not depend on service
|
|
1800
|
+
// connectivity. Users can run `ctxdb ping` separately.
|
|
1801
|
+
validate: false
|
|
1802
|
+
});
|
|
1519
1803
|
}
|
|
1520
|
-
|
|
1804
|
+
const allOk = Object.values(results).every((r) => r.ok);
|
|
1805
|
+
return { ok: allOk, agents: results };
|
|
1521
1806
|
}
|
|
1522
1807
|
|
|
1523
1808
|
// src/cli/top.ts
|
|
@@ -1577,7 +1862,7 @@ async function ping(args) {
|
|
|
1577
1862
|
var HELP = `ctxdb setup \u2014 configure an agent
|
|
1578
1863
|
|
|
1579
1864
|
USAGE
|
|
1580
|
-
ctxdb setup [--agent <qoder|qoderwork|codex|claude|opencode>]
|
|
1865
|
+
ctxdb setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes>]
|
|
1581
1866
|
[--api-key=K] [--base-url=URL] [--user-id=ID]
|
|
1582
1867
|
[--no-install-skill] [--no-validate] [--json]
|
|
1583
1868
|
|
|
@@ -1586,9 +1871,13 @@ BEHAVIOR
|
|
|
1586
1871
|
no hooks or skills installed).
|
|
1587
1872
|
With --agent Writes agent config + installs hooks + skills for the
|
|
1588
1873
|
specified agent harness.
|
|
1874
|
+
Hermes Also verifies ~/.hermes/shell-hooks-allowlist.json.
|
|
1875
|
+
Approve pre_llm_call + post_llm_call with
|
|
1876
|
+
\`hermes --accept-hooks chat\`. It opens interactive chat;
|
|
1877
|
+
exit after Hermes starts, then rerun ctxdb setup.
|
|
1589
1878
|
|
|
1590
1879
|
FLAGS
|
|
1591
|
-
--agent <name> Target agent (qoder|qoderwork|codex|claude|opencode)
|
|
1880
|
+
--agent <name> Target agent (qoder|qoderwork|codex|claude|opencode|hermes)
|
|
1592
1881
|
--api-key <key> API key (required on first setup)
|
|
1593
1882
|
--base-url <url> Server URL (default: https://context-database.aliyuncs.com)
|
|
1594
1883
|
--user-id <id> User bucket (default: "default")
|
|
@@ -1615,7 +1904,7 @@ async function setup(args) {
|
|
|
1615
1904
|
const json = !!args.flags.json;
|
|
1616
1905
|
if (agent === "default" && !json && !configuredAgents().includes("default")) {
|
|
1617
1906
|
process.stderr.write(
|
|
1618
|
-
`hint: creating CLI-only "default" agent (no hooks/skills). Use --agent <claude|qoder|qoderwork|codex|opencode> for full setup.
|
|
1907
|
+
`hint: creating CLI-only "default" agent (no hooks/skills). Use --agent <claude|qoder|qoderwork|codex|opencode|hermes> for full setup.
|
|
1619
1908
|
`
|
|
1620
1909
|
);
|
|
1621
1910
|
}
|
|
@@ -1647,11 +1936,11 @@ ${dim("codex hooks \u914D\u7F6E\u540E\uFF0C\u9700\u8981\u91CD\u542F Codex \u5BF9
|
|
|
1647
1936
|
var HELP2 = `ctxdb uninstall \u2014 remove hooks + skills
|
|
1648
1937
|
|
|
1649
1938
|
USAGE
|
|
1650
|
-
ctxdb uninstall [--agent <qoder|qoderwork|codex|claude|opencode>] [--purge-config] [--purge-logs] [--purge-all] [--json]
|
|
1939
|
+
ctxdb uninstall [--agent <qoder|qoderwork|codex|claude|opencode|hermes>] [--purge-config] [--purge-logs] [--purge-all] [--json]
|
|
1651
1940
|
|
|
1652
1941
|
DEFAULT BEHAVIOR (no --agent, no purge flags)
|
|
1653
1942
|
Strips ctxdb hook entries and skill directories for ALL agents (qoder,
|
|
1654
|
-
qoderwork, codex, claude, opencode). Keeps ~/.ctxdb/ctxdb.json (credentials) and ~/.ctxdb/logs/
|
|
1943
|
+
qoderwork, codex, claude, opencode, hermes). Keeps ~/.ctxdb/ctxdb.json (credentials) and ~/.ctxdb/logs/
|
|
1655
1944
|
so you can re-run \`ctxdb setup\` later without losing configuration.
|
|
1656
1945
|
|
|
1657
1946
|
WITH --agent <name>
|
|
@@ -1659,7 +1948,7 @@ WITH --agent <name>
|
|
|
1659
1948
|
Other agents remain untouched.
|
|
1660
1949
|
|
|
1661
1950
|
FLAGS
|
|
1662
|
-
--agent <name> Target a single agent (qoder|qoderwork|codex|claude|opencode)
|
|
1951
|
+
--agent <name> Target a single agent (qoder|qoderwork|codex|claude|opencode|hermes)
|
|
1663
1952
|
--purge-config Also delete ~/.ctxdb/ctxdb.json
|
|
1664
1953
|
--purge-logs Also delete ~/.ctxdb/logs/
|
|
1665
1954
|
--purge-all Delete everything under ~/.ctxdb/
|
|
@@ -1760,8 +2049,8 @@ function isNewerVersion(candidate, current) {
|
|
|
1760
2049
|
if (cMin !== uMin) return cMin > uMin;
|
|
1761
2050
|
return cPat > uPat;
|
|
1762
2051
|
}
|
|
1763
|
-
function runSelfUpdate(currentVersion, passthroughArgs = []) {
|
|
1764
|
-
const method = detectInstallMethod();
|
|
2052
|
+
function runSelfUpdate(currentVersion, passthroughArgs = [], options = {}) {
|
|
2053
|
+
const method = options.installMethod ?? detectInstallMethod();
|
|
1765
2054
|
if (method !== "npm") {
|
|
1766
2055
|
return {
|
|
1767
2056
|
ok: false,
|
|
@@ -1772,9 +2061,12 @@ function runSelfUpdate(currentVersion, passthroughArgs = []) {
|
|
|
1772
2061
|
}
|
|
1773
2062
|
const check = checkLatestVersion(currentVersion);
|
|
1774
2063
|
if (check.error) {
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
2064
|
+
return {
|
|
2065
|
+
ok: false,
|
|
2066
|
+
updated: false,
|
|
2067
|
+
fromVersion: currentVersion,
|
|
2068
|
+
error: `npm view failed: ${check.error}`
|
|
2069
|
+
};
|
|
1778
2070
|
}
|
|
1779
2071
|
if (!check.isNewer) {
|
|
1780
2072
|
process.stderr.write(`ctxdb: package already up to date (v${currentVersion})
|
|
@@ -1796,10 +2088,10 @@ function runSelfUpdate(currentVersion, passthroughArgs = []) {
|
|
|
1796
2088
|
error: spawnFailureMessage(install, "npm install -g")
|
|
1797
2089
|
};
|
|
1798
2090
|
}
|
|
1799
|
-
process.stderr.write(`ctxdb: package updated to v${check.latest},
|
|
2091
|
+
process.stderr.write(`ctxdb: package updated to v${check.latest}, running setup...
|
|
1800
2092
|
`);
|
|
1801
|
-
const
|
|
1802
|
-
const refresh = spawnSync(ctxdbCommand(),
|
|
2093
|
+
const setupArgs = ["update", "--no-self-update", ...passthroughArgs];
|
|
2094
|
+
const refresh = spawnSync(ctxdbCommand(), setupArgs, {
|
|
1803
2095
|
stdio: "inherit",
|
|
1804
2096
|
encoding: "utf-8",
|
|
1805
2097
|
shell: process.platform === "win32"
|
|
@@ -1809,7 +2101,8 @@ function runSelfUpdate(currentVersion, passthroughArgs = []) {
|
|
|
1809
2101
|
updated: true,
|
|
1810
2102
|
fromVersion: currentVersion,
|
|
1811
2103
|
toVersion: check.latest,
|
|
1812
|
-
error: refresh.status !== 0 ? spawnFailureMessage(refresh, "ctxdb
|
|
2104
|
+
error: refresh.status !== 0 ? spawnFailureMessage(refresh, "ctxdb setup") : void 0,
|
|
2105
|
+
failureHandled: refresh.status !== null && refresh.status !== 0
|
|
1813
2106
|
};
|
|
1814
2107
|
}
|
|
1815
2108
|
|
|
@@ -1817,58 +2110,68 @@ function runSelfUpdate(currentVersion, passthroughArgs = []) {
|
|
|
1817
2110
|
var HELP3 = `ctxdb upgrade \u2014 refresh skills + hooks
|
|
1818
2111
|
|
|
1819
2112
|
USAGE
|
|
1820
|
-
ctxdb upgrade [--agent <qoder|qoderwork|codex|claude|opencode>] [--self-update] [--json]
|
|
2113
|
+
ctxdb upgrade [--agent <qoder|qoderwork|codex|claude|opencode|hermes>] [--no-self-update] [--json]
|
|
1821
2114
|
|
|
1822
2115
|
BEHAVIOR
|
|
1823
|
-
|
|
1824
|
-
|
|
2116
|
+
Pulls the latest @aliyunrds/ctxdb package from npm, then re-runs setup
|
|
2117
|
+
for configured agents using their existing credentials.
|
|
1825
2118
|
|
|
1826
|
-
Without --agent:
|
|
1827
|
-
With --agent:
|
|
2119
|
+
Without --agent: sets up all configured built-in agents.
|
|
2120
|
+
With --agent: sets up only the specified agent.
|
|
1828
2121
|
|
|
1829
2122
|
FLAGS
|
|
1830
|
-
--agent <name> Target a single agent (qoder|qoderwork|codex|claude|opencode)
|
|
1831
|
-
--self-update
|
|
2123
|
+
--agent <name> Target a single agent (qoder|qoderwork|codex|claude|opencode|hermes)
|
|
2124
|
+
--self-update Pull latest package from npm (enabled by default)
|
|
2125
|
+
--no-self-update Skip the npm package update and only re-run setup
|
|
1832
2126
|
--json Machine-readable JSON output
|
|
1833
2127
|
`;
|
|
1834
|
-
|
|
2128
|
+
var MANUAL_UPDATE_HINT = "Automatic update failed. Please run manually:\n npm install -g @aliyunrds/ctxdb\n ctxdb setup";
|
|
2129
|
+
function printUpdateFailure(error, json) {
|
|
2130
|
+
if (json) {
|
|
2131
|
+
process.stdout.write(JSON.stringify({ ok: false, error, hint: MANUAL_UPDATE_HINT }) + "\n");
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
process.stderr.write(`ctxdb: automatic update/setup failed: ${error}
|
|
2135
|
+
${MANUAL_UPDATE_HINT}
|
|
2136
|
+
`);
|
|
2137
|
+
}
|
|
2138
|
+
async function upgrade(args) {
|
|
1835
2139
|
if (args.flags.help || args.flags.h) {
|
|
1836
2140
|
process.stdout.write(HELP3);
|
|
1837
2141
|
return 0;
|
|
1838
2142
|
}
|
|
1839
|
-
|
|
2143
|
+
if (args.flags["self-update"] && args.flags["no-self-update"]) {
|
|
2144
|
+
process.stderr.write("--self-update and --no-self-update cannot be used together\n");
|
|
2145
|
+
return 2;
|
|
2146
|
+
}
|
|
2147
|
+
const agentFlag = args.flags.agent;
|
|
2148
|
+
if (agentFlag && agentFlag !== "default" && !isBuiltinAgent(agentFlag)) {
|
|
2149
|
+
process.stderr.write(
|
|
2150
|
+
`unknown --agent: ${agentFlag} (expected one of ${SUPPORTED_AGENTS.join(" / ")})
|
|
2151
|
+
`
|
|
2152
|
+
);
|
|
2153
|
+
return 2;
|
|
2154
|
+
}
|
|
2155
|
+
const selfUpdate = !args.flags["no-self-update"];
|
|
1840
2156
|
if (selfUpdate) {
|
|
1841
2157
|
const passthrough = [];
|
|
1842
|
-
|
|
1843
|
-
if (agentFlag2) passthrough.push("--agent", agentFlag2);
|
|
2158
|
+
if (agentFlag) passthrough.push("--agent", agentFlag);
|
|
1844
2159
|
if (args.flags.json) passthrough.push("--json");
|
|
1845
2160
|
const result2 = runSelfUpdate(PACKAGE_VERSION, passthrough);
|
|
1846
2161
|
if (!result2.ok) {
|
|
1847
|
-
if (
|
|
1848
|
-
|
|
1849
|
-
} else {
|
|
1850
|
-
process.stderr.write(`ctxdb: self-update failed: ${result2.error}
|
|
1851
|
-
`);
|
|
1852
|
-
}
|
|
2162
|
+
if (result2.failureHandled) return 1;
|
|
2163
|
+
printUpdateFailure(result2.error ?? "unknown error", !!args.flags.json);
|
|
1853
2164
|
return 1;
|
|
1854
2165
|
}
|
|
1855
2166
|
if (result2.updated) {
|
|
1856
2167
|
return 0;
|
|
1857
2168
|
}
|
|
1858
2169
|
}
|
|
1859
|
-
const agentFlag = args.flags.agent;
|
|
1860
2170
|
if (agentFlag === "default") {
|
|
1861
2171
|
process.stderr.write("nothing to upgrade for default agent (no hooks or skills)\n");
|
|
1862
2172
|
return 0;
|
|
1863
2173
|
}
|
|
1864
|
-
|
|
1865
|
-
process.stderr.write(
|
|
1866
|
-
`unknown --agent: ${agentFlag} (expected one of ${SUPPORTED_AGENTS.join(" / ")})
|
|
1867
|
-
`
|
|
1868
|
-
);
|
|
1869
|
-
return 2;
|
|
1870
|
-
}
|
|
1871
|
-
const result = runUpgrade({ agent: agentFlag });
|
|
2174
|
+
const result = await runUpgrade({ agent: agentFlag });
|
|
1872
2175
|
if (result.ok) {
|
|
1873
2176
|
try {
|
|
1874
2177
|
writeInstalledPkgVersion(PACKAGE_VERSION);
|
|
@@ -1876,12 +2179,22 @@ function upgrade(args) {
|
|
|
1876
2179
|
}
|
|
1877
2180
|
}
|
|
1878
2181
|
const json = !!args.flags.json;
|
|
2182
|
+
const detail = result.hints?.[0] ?? "ctxdb setup failed";
|
|
2183
|
+
if (!result.ok && json) {
|
|
2184
|
+
process.stdout.write(
|
|
2185
|
+
JSON.stringify({ ...result, error: detail, hint: MANUAL_UPDATE_HINT }, null, 2) + "\n"
|
|
2186
|
+
);
|
|
2187
|
+
return 1;
|
|
2188
|
+
}
|
|
1879
2189
|
printResult(result, json);
|
|
1880
2190
|
if (!json && result.hints) {
|
|
1881
2191
|
for (const h of result.hints) process.stderr.write(`
|
|
1882
2192
|
${h}
|
|
1883
2193
|
`);
|
|
1884
2194
|
}
|
|
2195
|
+
if (!result.ok) {
|
|
2196
|
+
printUpdateFailure(detail, false);
|
|
2197
|
+
}
|
|
1885
2198
|
return result.ok ? 0 : 1;
|
|
1886
2199
|
}
|
|
1887
2200
|
|
|
@@ -2000,12 +2313,13 @@ function projectKnowledgeChunks(resp, flags) {
|
|
|
2000
2313
|
const r = resp;
|
|
2001
2314
|
const k = r.knowledge;
|
|
2002
2315
|
if (!k || typeof k !== "object") return resp;
|
|
2003
|
-
const
|
|
2316
|
+
const knowledge = k;
|
|
2317
|
+
const chunks = knowledge.chunks;
|
|
2004
2318
|
if (!Array.isArray(chunks)) return resp;
|
|
2005
|
-
const
|
|
2319
|
+
const normalized = flags.verbose ? compactKbQueryResponse(knowledge) : minimalKbQueryResponse(knowledge);
|
|
2006
2320
|
return {
|
|
2007
2321
|
...r,
|
|
2008
|
-
knowledge: { ...
|
|
2322
|
+
knowledge: { ...knowledge, ...normalized }
|
|
2009
2323
|
};
|
|
2010
2324
|
}
|
|
2011
2325
|
async function memoryList(args) {
|
|
@@ -2194,7 +2508,7 @@ USAGE
|
|
|
2194
2508
|
ctxdb <command> [args] [--flags]
|
|
2195
2509
|
|
|
2196
2510
|
COMMANDS
|
|
2197
|
-
setup [--agent <qoder|qoderwork|codex|claude|opencode>]
|
|
2511
|
+
setup [--agent <qoder|qoderwork|codex|claude|opencode|hermes>]
|
|
2198
2512
|
[--api-key=K] [--base-url=URL] [--user-id=ID]
|
|
2199
2513
|
[--no-install-skill] [--no-validate] [--json]
|
|
2200
2514
|
Without --agent \u2192 writes agents.default (CLI-only, no hooks/skills)
|
|
@@ -2203,6 +2517,9 @@ COMMANDS
|
|
|
2203
2517
|
Codex \u2192 writes agents.codex config + ~/.codex/hooks.json hooks + skill
|
|
2204
2518
|
Claude \u2192 writes agents.claude config + ~/.claude/settings.json hooks + skill
|
|
2205
2519
|
OpenCode \u2192 writes agents.opencode config + ~/.config/opencode/plugins/ctxdb.ts shim + skill
|
|
2520
|
+
Hermes \u2192 writes agents.hermes config + ~/.hermes/config.yaml pre/post_llm_call hooks + skill
|
|
2521
|
+
Approve pre_llm_call + post_llm_call with \`hermes --accept-hooks chat\`
|
|
2522
|
+
(opens interactive chat; exit after Hermes starts)
|
|
2206
2523
|
status [--agent <name>] [--json]
|
|
2207
2524
|
ping [--agent <name>] [--json]
|
|
2208
2525
|
uninstall [--agent <name>] [--purge-config] [--purge-logs] [--purge-all] [--json]
|
|
@@ -2213,15 +2530,16 @@ COMMANDS
|
|
|
2213
2530
|
--purge-logs also deletes ~/.ctxdb/logs/.
|
|
2214
2531
|
--purge-all deletes everything under ~/.ctxdb/
|
|
2215
2532
|
(does not run npm uninstall).
|
|
2216
|
-
upgrade|update [--agent <name>] [--self-update] [--json]
|
|
2217
|
-
|
|
2218
|
-
|
|
2533
|
+
upgrade|update [--agent <name>] [--no-self-update] [--json]
|
|
2534
|
+
Pull latest package from npm, then re-run
|
|
2535
|
+
setup. Self-update is enabled by default.
|
|
2219
2536
|
|
|
2220
2537
|
memory add <text> [--agent=<name>] [--user-id=...] [--metadata=K1=V1,K2=V2] [--no-infer]
|
|
2221
2538
|
memory search <query> [--agent=<name>] [--top-k=10] [--threshold=0.4] [--knowledge]
|
|
2222
2539
|
[--verbose] [--raw]
|
|
2223
2540
|
--knowledge enables KB chunks alongside memory.
|
|
2224
2541
|
Default chunk view: {content, score} only.
|
|
2542
|
+
Graph synthesis moves to non-citable graph_context.
|
|
2225
2543
|
--verbose adds doc_name/kb_id/doc_id/tags.
|
|
2226
2544
|
--raw ships server response verbatim.
|
|
2227
2545
|
memory list [--agent=<name>] [--page-size=100] [--category=...]
|
|
@@ -2241,6 +2559,7 @@ COMMANDS
|
|
|
2241
2559
|
[--verbose] [--raw]
|
|
2242
2560
|
Standalone KB recall (independent of memory).
|
|
2243
2561
|
Default: minimal {content, score} per chunk.
|
|
2562
|
+
Graph synthesis moves to non-citable graph_context.
|
|
2244
2563
|
--verbose: compact view adds doc_name/kb_id/
|
|
2245
2564
|
doc_id?/tags? for citation.
|
|
2246
2565
|
--raw: server response verbatim (curl parity).
|
|
@@ -2263,7 +2582,7 @@ HOOK ENV VARS:
|
|
|
2263
2582
|
CTXDB_SKIP_HOOKS=TRUE Hook/plugin entrypoints exit immediately.
|
|
2264
2583
|
Direct ctxdb CLI commands are unchanged.
|
|
2265
2584
|
|
|
2266
|
-
CONFIG: ~/.ctxdb/ctxdb.json (agents.default / agents.qoder / agents.qoderwork / agents.codex / agents.claude / agents.opencode)
|
|
2585
|
+
CONFIG: ~/.ctxdb/ctxdb.json (agents.default / agents.qoder / agents.qoderwork / agents.codex / agents.claude / agents.opencode / agents.hermes)
|
|
2267
2586
|
LOGS: ~/.ctxdb/logs/ctxdb.log
|
|
2268
2587
|
`;
|
|
2269
2588
|
var ROUTES = {
|
|
@@ -2341,7 +2660,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2341
2660
|
}
|
|
2342
2661
|
main().then(
|
|
2343
2662
|
(code) => {
|
|
2344
|
-
if (typeof code === "number") process.
|
|
2663
|
+
if (typeof code === "number") process.exitCode = code;
|
|
2345
2664
|
},
|
|
2346
2665
|
(err) => {
|
|
2347
2666
|
process.stderr.write(`error: ${err?.message ?? err}
|
|
@@ -2349,7 +2668,7 @@ main().then(
|
|
|
2349
2668
|
if (process.env.RDS_CTXDB_DEBUG === "1" && err?.stack) {
|
|
2350
2669
|
process.stderr.write(err.stack + "\n");
|
|
2351
2670
|
}
|
|
2352
|
-
process.
|
|
2671
|
+
process.exitCode = 1;
|
|
2353
2672
|
}
|
|
2354
2673
|
);
|
|
2355
2674
|
export {
|