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