@aliyunrds/ctxdb 0.0.9 → 1.0.0-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/README.md +23 -9
- package/dist/{chunk-PZJZ5O62.js → chunk-PLWEIEFH.js} +1 -1
- package/dist/{chunk-TOXXGL5L.js → chunk-QHYJ7OXC.js} +4 -1
- package/dist/{chunk-RHWGDY6S.js → chunk-QPFFMW52.js} +3 -3
- package/dist/cli/main.js +235 -17
- package/dist/hooks/session-start.js +3 -3
- package/dist/hooks/stop.js +2 -2
- package/dist/hooks/user-prompt-submit.js +3 -3
- package/dist/opencode/index.js +630 -0
- package/dist/setup/skills/contextdb-knowledge/SKILL.md +4 -4
- package/dist/setup/skills/contextdb-memory/SKILL.md +4 -4
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -5,10 +5,12 @@ Unified access layer for RDS ContextDatabase. One `ctxdb` CLI (memory + KB ops),
|
|
|
5
5
|
| Agent | Hooks | Skill install path | Skill style |
|
|
6
6
|
|---|---|---|---|
|
|
7
7
|
| **qoder** | ✅ UserPromptSubmit + Stop + SessionStart | `~/.qoder/skills/ctxdb/` | hooks-driven (auto capture/recall) |
|
|
8
|
+
| **qoderwork** | ✅ UserPromptSubmit + Stop + SessionStart | `~/.qoderwork/skills/ctxdb/` | hooks-driven (auto capture/recall) |
|
|
8
9
|
| **codex** | ✅ UserPromptSubmit + Stop + SessionStart | `~/.codex/skills/ctxdb/` | hooks-driven (auto capture/recall) |
|
|
9
10
|
| **claude** | ✅ UserPromptSubmit + Stop + SessionStart | `~/.claude/skills/ctxdb/` | hooks-driven (auto capture/recall) |
|
|
11
|
+
| **opencode** | Plugin shim (`~/.config/opencode/plugins/ctxdb.ts`) | `~/.config/opencode/skills/` | in-process plugin (auto capture/recall + KB catalog injection) |
|
|
10
12
|
|
|
11
|
-
Config and logs live under `~/.ctxdb/`. `~/.ctxdb/ctxdb.json` is one file, but runtime config is isolated per agent under `agents.qoder`, `agents.codex`, and `agents.
|
|
13
|
+
Config and logs live under `~/.ctxdb/`. `~/.ctxdb/ctxdb.json` is one file, but runtime config is isolated per agent under `agents.qoder`, `agents.qoderwork`, `agents.codex`, `agents.claude`, and `agents.opencode`. (Pre-2026-05-23 installs used `~/.ctxdb.json` at the home root; running `ctxdb setup --agent <name>` migrates the file into the selected agent section.)
|
|
12
14
|
|
|
13
15
|
## Install
|
|
14
16
|
|
|
@@ -17,10 +19,14 @@ npm install -g @aliyunrds/ctxdb
|
|
|
17
19
|
|
|
18
20
|
# Qoder (hooks + skill)
|
|
19
21
|
ctxdb setup --agent qoder --api-key ctxdb-...
|
|
22
|
+
# QoderWork (hooks + skill)
|
|
23
|
+
ctxdb setup --agent qoderwork --api-key ctxdb-...
|
|
20
24
|
# Codex (hooks + skill)
|
|
21
25
|
ctxdb setup --agent codex --api-key ctxdb-...
|
|
22
26
|
# Claude Code (hooks + skill)
|
|
23
27
|
ctxdb setup --agent claude --api-key ctxdb-...
|
|
28
|
+
# OpenCode (plugin shim + skill)
|
|
29
|
+
ctxdb setup --agent opencode --api-key ctxdb-...
|
|
24
30
|
|
|
25
31
|
# --base-url defaults to https://context-database.aliyuncs.com (public prod).
|
|
26
32
|
# Pass --base-url <host> only if you target a different deployment
|
|
@@ -33,13 +39,13 @@ ctxdb setup --agent claude --api-key ctxdb-...
|
|
|
33
39
|
ctxdb status --json
|
|
34
40
|
```
|
|
35
41
|
|
|
36
|
-
Setup first checks that the target agent home exists (`~/.qoder`, `~/.codex`, or `~/.claude`). If the directory is missing, install or start that agent once before running `ctxdb setup --agent <name>`.
|
|
42
|
+
Setup first checks that the target agent home exists (`~/.qoder`, `~/.qoderwork`, `~/.codex`, or `~/.claude`). If the directory is missing, install or start that agent once before running `ctxdb setup --agent <name>`. OpenCode is XDG-style: setup creates `~/.config/opencode` and `~/.config/opencode/plugins` when they are missing.
|
|
37
43
|
|
|
38
|
-
For **qoder**, **codex**, and **claude**, restart the harness (CLI: just exit + restart; app: Cmd+R or quit/relaunch) so it picks up the new hooks/skill. For **codex**, setup also writes `[features].hooks = true` into `~/.codex/config.toml` (creating the file if missing) — Codex won't fire any hook entries without it. On first use Codex may prompt you to trust the new hook commands.
|
|
44
|
+
For **qoder**, **qoderwork**, **codex**, and **claude**, restart the harness (CLI: just exit + restart; app: Cmd+R or quit/relaunch) so it picks up the new hooks/skill. For **codex**, setup also writes `[features].hooks = true` into `~/.codex/config.toml` (creating the file if missing) — Codex won't fire any hook entries without it. On first use Codex may prompt you to trust the new hook commands. For **opencode**, restart OpenCode so it loads `~/.config/opencode/plugins/ctxdb.ts`.
|
|
39
45
|
|
|
40
46
|
## What it does
|
|
41
47
|
|
|
42
|
-
For **qoder**, **codex**, and **claude**, hooks fire automatically:
|
|
48
|
+
For **qoder**, **qoderwork**, **codex**, and **claude**, hooks fire automatically:
|
|
43
49
|
|
|
44
50
|
| Trigger | Hook | What runs |
|
|
45
51
|
|---|---|---|
|
|
@@ -49,6 +55,8 @@ For **qoder**, **codex**, and **claude**, hooks fire automatically:
|
|
|
49
55
|
|
|
50
56
|
Implementation note: qoder/Claude consume the JSON `hookSpecificOutput.additionalContext` shape; Codex has been verified on this machine through `~/.codex/hooks.json`, `[features].hooks = true`, and hook audit logs, where non-empty stdout from `UserPromptSubmit`/`SessionStart` is treated as injected context. The same hook core is shared; setup passes `--agent=<name>` so each hook reads its own config section.
|
|
51
57
|
|
|
58
|
+
For **opencode**, `ctxdb setup --agent opencode` writes a small re-export shim at `~/.config/opencode/plugins/ctxdb.ts`. The shim points into this package's bundled `dist/opencode/index.js`, so `npm update -g @aliyunrds/ctxdb` updates the plugin implementation without copying source into the user config directory. `ctxdb upgrade --agent opencode` refreshes the absolute shim target after package moves.
|
|
59
|
+
|
|
52
60
|
## CLI
|
|
53
61
|
|
|
54
62
|
`ctxdb` ships 18 subcommands:
|
|
@@ -59,7 +67,7 @@ Implementation note: qoder/Claude consume the JSON `hookSpecificOutput.additiona
|
|
|
59
67
|
|
|
60
68
|
See `ctxdb --help`.
|
|
61
69
|
|
|
62
|
-
`setup` without `--agent` writes `agents.default` in `~/.ctxdb/ctxdb.json` (CLI-only, no hooks or skills); with `--agent <qoder|codex|claude>` it writes agent config and installs hooks + skills for that harness. Memory and KB commands also accept `--agent <name>` so agents use their own config section; when omitted, `CTXDB_AGENT` env wins, otherwise `agents.default` is used. `uninstall` without `--agent` loops every supported agent; with `--agent <name>` it targets just that one. `teardown` is an alias for `uninstall`.
|
|
70
|
+
`setup` without `--agent` writes `agents.default` in `~/.ctxdb/ctxdb.json` (CLI-only, no hooks or skills); with `--agent <qoder|qoderwork|codex|claude|opencode>` it writes agent config and installs hooks/plugins + skills for that harness. Memory and KB commands also accept `--agent <name>` so agents use their own config section; when omitted, `CTXDB_AGENT` env wins, otherwise `agents.default` is used. `uninstall` without `--agent` loops every supported agent; with `--agent <name>` it targets just that one. `teardown` is an alias for `uninstall`.
|
|
63
71
|
|
|
64
72
|
`memory add … --no-infer` stores the text verbatim (skips server-side LLM
|
|
65
73
|
fact-extraction). Use it when the user explicitly asks for a verbatim
|
|
@@ -87,20 +95,22 @@ Lives at `~/.ctxdb/ctxdb.json` (co-located with logs at `~/.ctxdb/logs/`). Schem
|
|
|
87
95
|
"user_id": "default",
|
|
88
96
|
"auto_capture": true,
|
|
89
97
|
"auto_recall": true,
|
|
90
|
-
"warmup_recall":
|
|
98
|
+
"warmup_recall": false,
|
|
91
99
|
"recall_knowledge": false,
|
|
92
100
|
"top_k": 5,
|
|
93
101
|
"threshold": 0.4,
|
|
94
102
|
"knowledge_top_k": 6,
|
|
95
103
|
"debug": false
|
|
96
104
|
},
|
|
105
|
+
"qoderwork": { "...": "same schema, independent values" },
|
|
97
106
|
"codex": { "...": "same schema, independent values" },
|
|
98
|
-
"claude": { "...": "same schema, independent values" }
|
|
107
|
+
"claude": { "...": "same schema, independent values" },
|
|
108
|
+
"opencode": { "...": "same schema, independent values" }
|
|
99
109
|
}
|
|
100
110
|
}
|
|
101
111
|
```
|
|
102
112
|
|
|
103
|
-
Agent selection is driven by `--agent <qoder|codex|claude|default>` on every CLI invocation, falling back to `CTXDB_AGENT` env, then to `default`. There is no `default_agent` field — older installs that have one written get it dropped on the next save.
|
|
113
|
+
Agent selection is driven by `--agent <qoder|qoderwork|codex|claude|opencode|default>` on every CLI invocation, falling back to `CTXDB_AGENT` env, then to `default`. There is no `default_agent` field — older installs that have one written get it dropped on the next save.
|
|
104
114
|
|
|
105
115
|
Field reference:
|
|
106
116
|
|
|
@@ -109,7 +119,7 @@ Field reference:
|
|
|
109
119
|
| `user_id` | str | `"default"` | mem0-layer bucket key on every capture/recall call. The server's tenant isolation runs on `member_id` (injected from `X-API-Key`), so this is an optional per-user/agent slice. Override with `ctxdb setup --agent <a> --user-id <bucket>` or `CTXDB_USER_ID=<bucket>` when you need to isolate this install from another access layer or machine sharing the same workspace |
|
|
110
120
|
| `auto_capture` | bool | `true` | Stop hook captures each turn into long-term memory for the selected agent |
|
|
111
121
|
| `auto_recall` | bool | `true` | UserPromptSubmit hook recalls memory on each user prompt for the selected agent |
|
|
112
|
-
| `warmup_recall` | bool | `
|
|
122
|
+
| `warmup_recall` | bool | `false` | SessionStart hook recalls cwd/git-related memories for the selected agent. **Default is off** — open it per-agent when you want session-start warmup (adds one bounded, circuit-protected recall call on session start) |
|
|
113
123
|
| `recall_knowledge` | bool | `false` | When `auto_recall` is on, also pull KB chunks alongside memory. Default is **off** — the recommended KB-recall path is the agent calling `kb search --agent <name>` explicitly when the user asks |
|
|
114
124
|
| `top_k` / `threshold` | int / float | `5` / `0.4` | Memory recall pagination + similarity floor. `5` keeps the codex TUI `hook context: ...` line short enough to scan and bounds token spend on the modal recall path; raise it per-agent if you want more candidates |
|
|
115
125
|
| `knowledge_top_k` | int | `6` | KB chunks pulled per recall (only effective when `recall_knowledge: true`) |
|
|
@@ -121,6 +131,8 @@ Env-var overrides apply to the selected agent config (env wins): `CTXDB_AGENT` /
|
|
|
121
131
|
|
|
122
132
|
```
|
|
123
133
|
@aliyunrds/ctxdb ← this package (CLI + setup + hooks + skills)
|
|
134
|
+
│
|
|
135
|
+
├─ bundles OpenCode plugin in dist/opencode/index.js
|
|
124
136
|
│
|
|
125
137
|
└─ depends on @aliyunrds/ctxdb-shared ← input sanitation,
|
|
126
138
|
prompt-injection defenses,
|
|
@@ -155,8 +167,10 @@ After step 1, `ctxdb uninstall` prints the exact `npm uninstall` command to run
|
|
|
155
167
|
# itself is unlinked so a future `ctxdb setup --agent <X>` runs through the
|
|
156
168
|
# first-time install path cleanly.
|
|
157
169
|
ctxdb uninstall --agent qoder
|
|
170
|
+
ctxdb uninstall --agent qoderwork
|
|
158
171
|
ctxdb uninstall --agent codex
|
|
159
172
|
ctxdb uninstall --agent claude
|
|
173
|
+
ctxdb uninstall --agent opencode
|
|
160
174
|
|
|
161
175
|
# Wholesale across all agents, but keep config + logs for re-setup later:
|
|
162
176
|
ctxdb uninstall # all agents, keeps ~/.ctxdb/{ctxdb.json,logs/}
|
|
@@ -262,7 +262,7 @@ function extractErrorDetail(text, fallback) {
|
|
|
262
262
|
// src/lib/agents.ts
|
|
263
263
|
import { homedir as homedir2 } from "os";
|
|
264
264
|
import { join as join3 } from "path";
|
|
265
|
-
var SUPPORTED_AGENTS = ["qoder", "qoderwork", "codex", "claude"];
|
|
265
|
+
var SUPPORTED_AGENTS = ["qoder", "qoderwork", "codex", "claude", "opencode"];
|
|
266
266
|
function isBuiltinAgent(v) {
|
|
267
267
|
return typeof v === "string" && SUPPORTED_AGENTS.includes(v);
|
|
268
268
|
}
|
|
@@ -280,9 +280,12 @@ function agentHomeDir(agent) {
|
|
|
280
280
|
return join3(homedir2(), ".codex");
|
|
281
281
|
case "claude":
|
|
282
282
|
return join3(homedir2(), ".claude");
|
|
283
|
+
case "opencode":
|
|
284
|
+
return join3(homedir2(), ".config", "opencode");
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
287
|
var AGENT_VARIANT_HOMES = {
|
|
288
|
+
qoder: [".qoder-cn"],
|
|
286
289
|
qoderwork: [".qoderworkcn"]
|
|
287
290
|
};
|
|
288
291
|
function agentHomeDirs(agent) {
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
isConnectionError,
|
|
7
7
|
resetCircuit,
|
|
8
8
|
tripCircuit
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-PLWEIEFH.js";
|
|
10
10
|
import {
|
|
11
11
|
CtxdbError
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-QHYJ7OXC.js";
|
|
13
13
|
|
|
14
14
|
// src/lib/recall-orchestrator.ts
|
|
15
15
|
import {
|
|
@@ -165,7 +165,7 @@ async function fetchKbCatalogBlock(client, agent) {
|
|
|
165
165
|
});
|
|
166
166
|
return [
|
|
167
167
|
"<available-knowledge-bases>",
|
|
168
|
-
`When you identify that relevant information may exist in the knowledge bases below, you MUST run \`ctxdb kb search "<query>" --kb=<name> --agent=${agent}\` with targeted keywords after initial analysis to supplement and correct your approach. Knowledge bases:`,
|
|
168
|
+
`When you identify that relevant information may exist in the knowledge bases below, you MUST run \`ctxdb kb search "<query>" --kb=<name> --agent=${agent}\` with targeted keywords after initial analysis to supplement and correct your approach. For keyword-based KB search, keep each single query focused: use at most 5 keywords or short phrases; run multiple targeted searches if more are needed. Knowledge bases:`,
|
|
169
169
|
...lines,
|
|
170
170
|
"</available-knowledge-bases>"
|
|
171
171
|
].join("\n");
|
package/dist/cli/main.js
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
removeAgent,
|
|
32
32
|
save,
|
|
33
33
|
writeInstalledPkgVersion
|
|
34
|
-
} from "../chunk-
|
|
34
|
+
} from "../chunk-QHYJ7OXC.js";
|
|
35
35
|
|
|
36
36
|
// src/cli/util.ts
|
|
37
37
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -115,6 +115,9 @@ var BOLD = "\x1B[1m";
|
|
|
115
115
|
var RESET = "\x1B[0m";
|
|
116
116
|
var CHECK = `${GREEN}\u2714${RESET}`;
|
|
117
117
|
var CROSS = `${RED}\u2718${RESET}`;
|
|
118
|
+
function dim(text) {
|
|
119
|
+
return `${DIM}${text}${RESET}`;
|
|
120
|
+
}
|
|
118
121
|
function isSetupResult(v) {
|
|
119
122
|
if (typeof v !== "object" || v === null) return false;
|
|
120
123
|
return "steps" in v && Array.isArray(v.steps);
|
|
@@ -249,11 +252,14 @@ function skillInstallRoot(agent) {
|
|
|
249
252
|
return join(homedir(), ".codex", "skills");
|
|
250
253
|
case "claude":
|
|
251
254
|
return join(homedir(), ".claude", "skills");
|
|
255
|
+
case "opencode":
|
|
256
|
+
return join(homedir(), ".config", "opencode", "skills");
|
|
252
257
|
}
|
|
253
258
|
}
|
|
254
259
|
var SKILL_DIRS = ["contextdb-memory", "contextdb-knowledge"];
|
|
255
260
|
var LEGACY_SKILL_DIRS = ["ctxdb"];
|
|
256
261
|
function agentSupportsHooks(agent) {
|
|
262
|
+
if (agent === "opencode") return false;
|
|
257
263
|
return isBuiltinAgent(agent);
|
|
258
264
|
}
|
|
259
265
|
function hookConfigPath(agent) {
|
|
@@ -266,6 +272,8 @@ function hookConfigPath(agent) {
|
|
|
266
272
|
return join(homedir(), ".codex", "hooks.json");
|
|
267
273
|
case "claude":
|
|
268
274
|
return join(homedir(), ".claude", "settings.json");
|
|
275
|
+
case "opencode":
|
|
276
|
+
return null;
|
|
269
277
|
}
|
|
270
278
|
}
|
|
271
279
|
var LEGACY_QODER_SKILL_DIRS = [
|
|
@@ -274,6 +282,24 @@ var LEGACY_QODER_SKILL_DIRS = [
|
|
|
274
282
|
"qoder-ctxdb"
|
|
275
283
|
];
|
|
276
284
|
var HOOK_EVENTS = ["UserPromptSubmit", "Stop", "SessionStart"];
|
|
285
|
+
var MIN_NODE_MAJOR = 20;
|
|
286
|
+
function parseNodeMajor(version) {
|
|
287
|
+
const match = /^v?(\d+)/.exec(version.trim());
|
|
288
|
+
if (!match) return null;
|
|
289
|
+
const major = Number.parseInt(match[1], 10);
|
|
290
|
+
return Number.isFinite(major) ? major : null;
|
|
291
|
+
}
|
|
292
|
+
function checkCurrentNodeVersion() {
|
|
293
|
+
const version = process.versions.node;
|
|
294
|
+
const major = parseNodeMajor(version);
|
|
295
|
+
if (major !== null && major >= MIN_NODE_MAJOR) return null;
|
|
296
|
+
const current = version.startsWith("v") ? version : `v${version}`;
|
|
297
|
+
return {
|
|
298
|
+
step: "check-node-version",
|
|
299
|
+
ok: false,
|
|
300
|
+
detail: `Node.js >=${MIN_NODE_MAJOR} is required by ctxdb; current ${current} at ${process.execPath}. Switch to Node 20+ (for example: \`nvm install 20 && nvm use 20\`), reinstall @aliyunrds/ctxdb, then rerun \`ctxdb setup\`.`
|
|
301
|
+
};
|
|
302
|
+
}
|
|
277
303
|
async function runSetup(options) {
|
|
278
304
|
const steps = [];
|
|
279
305
|
if (!isAgentSlug(options.agent)) {
|
|
@@ -287,6 +313,11 @@ async function runSetup(options) {
|
|
|
287
313
|
const agent = options.agent;
|
|
288
314
|
const installSkill = options.installSkill !== false;
|
|
289
315
|
const validate = options.validate !== false;
|
|
316
|
+
const nodeVersionCheck = checkCurrentNodeVersion();
|
|
317
|
+
if (nodeVersionCheck) {
|
|
318
|
+
steps.push(nodeVersionCheck);
|
|
319
|
+
return { ok: false, steps };
|
|
320
|
+
}
|
|
290
321
|
if (isBuiltinAgent(agent)) {
|
|
291
322
|
const homeCheck = checkAgentHome(agent);
|
|
292
323
|
steps.push(homeCheck);
|
|
@@ -407,6 +438,32 @@ async function runSetup(options) {
|
|
|
407
438
|
}
|
|
408
439
|
}
|
|
409
440
|
}
|
|
441
|
+
if (agent === "opencode") {
|
|
442
|
+
const absEntry = resolveOpencodePluginEntry();
|
|
443
|
+
if (!absEntry) {
|
|
444
|
+
steps.push({
|
|
445
|
+
step: "install-opencode-shim",
|
|
446
|
+
ok: false,
|
|
447
|
+
detail: "could not resolve plugin entry under @aliyunrds/ctxdb/dist/opencode"
|
|
448
|
+
});
|
|
449
|
+
return { ok: false, steps };
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
writeOpencodeShim(absEntry);
|
|
453
|
+
steps.push({
|
|
454
|
+
step: "install-opencode-shim",
|
|
455
|
+
ok: true,
|
|
456
|
+
detail: `${opencodeShimPath()} \u2192 ${absEntry}`
|
|
457
|
+
});
|
|
458
|
+
} catch (err) {
|
|
459
|
+
steps.push({
|
|
460
|
+
step: "install-opencode-shim",
|
|
461
|
+
ok: false,
|
|
462
|
+
detail: err?.message ?? String(err)
|
|
463
|
+
});
|
|
464
|
+
return { ok: false, steps };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
410
467
|
if (validate) {
|
|
411
468
|
if (!cfg.apiKey || !cfg.baseUrl) {
|
|
412
469
|
steps.push({
|
|
@@ -429,8 +486,14 @@ async function runSetup(options) {
|
|
|
429
486
|
}
|
|
430
487
|
}
|
|
431
488
|
const ok = steps.every((s) => s.ok);
|
|
432
|
-
const hints = ok ? [UNINSTALL_ORDER_HINT] :
|
|
433
|
-
|
|
489
|
+
const hints = ok ? [UNINSTALL_ORDER_HINT] : [];
|
|
490
|
+
if (ok && agent === "opencode") {
|
|
491
|
+
hints.unshift(
|
|
492
|
+
`OpenCode plugin shim: ${opencodeShimPath()}`,
|
|
493
|
+
"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
|
+
);
|
|
495
|
+
}
|
|
496
|
+
return { ok, steps, hints: hints.length > 0 ? hints : void 0 };
|
|
434
497
|
}
|
|
435
498
|
function runRemove(agent, options = {}) {
|
|
436
499
|
const steps = [];
|
|
@@ -537,6 +600,32 @@ function runRemove(agent, options = {}) {
|
|
|
537
600
|
}
|
|
538
601
|
}
|
|
539
602
|
}
|
|
603
|
+
if (agent === "opencode") {
|
|
604
|
+
const removed = removeOpencodeShim();
|
|
605
|
+
steps.push({
|
|
606
|
+
step: "remove-opencode-shim",
|
|
607
|
+
ok: true,
|
|
608
|
+
detail: removed ? opencodeShimPath() : "nothing to remove"
|
|
609
|
+
});
|
|
610
|
+
const root = skillInstallRoot(agent);
|
|
611
|
+
const dirs = [
|
|
612
|
+
...SKILL_DIRS.map((d) => join(root, d)),
|
|
613
|
+
...LEGACY_SKILL_DIRS.map((d) => join(root, d))
|
|
614
|
+
];
|
|
615
|
+
for (const p of dirs) {
|
|
616
|
+
if (!existsSync(p)) continue;
|
|
617
|
+
try {
|
|
618
|
+
rmSync(p, { recursive: true, force: true });
|
|
619
|
+
steps.push({ step: "remove-skill-dir", ok: true, detail: p });
|
|
620
|
+
} catch (err) {
|
|
621
|
+
steps.push({
|
|
622
|
+
step: "remove-skill-dir",
|
|
623
|
+
ok: false,
|
|
624
|
+
detail: `${p}: ${err?.message ?? String(err)}`
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
540
629
|
try {
|
|
541
630
|
const r = removeAgent(agent, void 0, {
|
|
542
631
|
keepEmptyShell: !!options.keepConfigShell
|
|
@@ -570,6 +659,19 @@ function runRemove(agent, options = {}) {
|
|
|
570
659
|
return { ok: steps.every((s) => s.ok), steps };
|
|
571
660
|
}
|
|
572
661
|
function checkAgentHome(agent) {
|
|
662
|
+
if (agent === "opencode") {
|
|
663
|
+
const primary = agentHomeDir(agent);
|
|
664
|
+
try {
|
|
665
|
+
mkdirSync(primary, { recursive: true });
|
|
666
|
+
return { step: "check-agent-home", ok: true, detail: primary };
|
|
667
|
+
} catch (err) {
|
|
668
|
+
return {
|
|
669
|
+
step: "check-agent-home",
|
|
670
|
+
ok: false,
|
|
671
|
+
detail: `${primary}: ${err?.message ?? String(err)}`
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}
|
|
573
675
|
const dirs = agentHomeDirs(agent).filter((d) => existsSync(d));
|
|
574
676
|
if (dirs.length === 0) {
|
|
575
677
|
const primary = agentHomeDir(agent);
|
|
@@ -679,6 +781,7 @@ function verifyNodeBinary(nodePath) {
|
|
|
679
781
|
}
|
|
680
782
|
}
|
|
681
783
|
function checkHookNodePaths(agent) {
|
|
784
|
+
if (agent === "opencode") return checkOpencodeShimHealth();
|
|
682
785
|
const empty = {
|
|
683
786
|
installed: false,
|
|
684
787
|
nodePaths: [],
|
|
@@ -712,7 +815,7 @@ function checkHookNodePaths(agent) {
|
|
|
712
815
|
if (closeQuote > 1) nodePath = cmd.slice(1, closeQuote);
|
|
713
816
|
} else {
|
|
714
817
|
const parts = cmd.split(" ");
|
|
715
|
-
if (parts.length >= 2 && !parts[0].endsWith(".js") && !parts[0].endsWith(".ts")) {
|
|
818
|
+
if (parts.length >= 2 && !parts[0].endsWith(".js") && !parts[0].endsWith(".ts") && (parts[0].includes("/") || parts[0].includes("\\"))) {
|
|
716
819
|
nodePath = parts[0];
|
|
717
820
|
}
|
|
718
821
|
}
|
|
@@ -742,6 +845,51 @@ function checkHookNodePaths(agent) {
|
|
|
742
845
|
}
|
|
743
846
|
return { installed: true, nodePaths: paths, nodeOk: true, detail: `${paths[0]} (${goodVersion})` };
|
|
744
847
|
}
|
|
848
|
+
function checkOpencodeShimHealth() {
|
|
849
|
+
const path = opencodeShimPath();
|
|
850
|
+
if (!existsSync(path)) {
|
|
851
|
+
return {
|
|
852
|
+
installed: false,
|
|
853
|
+
nodePaths: [],
|
|
854
|
+
nodeOk: false,
|
|
855
|
+
detail: `opencode shim not installed \u2014 run \`ctxdb setup --agent opencode\``
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
let text = "";
|
|
859
|
+
try {
|
|
860
|
+
text = readFileSync(path, "utf-8");
|
|
861
|
+
} catch (err) {
|
|
862
|
+
return {
|
|
863
|
+
installed: true,
|
|
864
|
+
nodePaths: [],
|
|
865
|
+
nodeOk: false,
|
|
866
|
+
detail: `opencode shim unreadable: ${err?.message ?? String(err)}`
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
const target = text.match(/export\s+\{\s*default\s*\}\s+from\s+["']([^"']+)["'];/)?.[1];
|
|
870
|
+
if (!target) {
|
|
871
|
+
return {
|
|
872
|
+
installed: true,
|
|
873
|
+
nodePaths: [],
|
|
874
|
+
nodeOk: false,
|
|
875
|
+
detail: `opencode shim malformed: ${path} \u2014 re-run \`ctxdb setup --agent opencode\``
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
if (!existsSync(target)) {
|
|
879
|
+
return {
|
|
880
|
+
installed: true,
|
|
881
|
+
nodePaths: [target],
|
|
882
|
+
nodeOk: false,
|
|
883
|
+
detail: `opencode shim target missing: ${target} \u2014 re-run \`ctxdb setup --agent opencode\``
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
installed: true,
|
|
888
|
+
nodePaths: [target],
|
|
889
|
+
nodeOk: true,
|
|
890
|
+
detail: `opencode shim: ${path} \u2192 ${target}`
|
|
891
|
+
};
|
|
892
|
+
}
|
|
745
893
|
var SETTINGS_BACKUP_KEEP = 5;
|
|
746
894
|
function backupSettingsFile(path) {
|
|
747
895
|
if (!existsSync(path)) return;
|
|
@@ -899,10 +1047,12 @@ function copySkillDir(src, dest, agent) {
|
|
|
899
1047
|
}
|
|
900
1048
|
var SKILL_INSTALL_TARGETS = {
|
|
901
1049
|
qoder: join(homedir(), ".qoder", "skills"),
|
|
1050
|
+
qodercn: join(homedir(), ".qoder-cn", "skills"),
|
|
902
1051
|
qoderwork: join(homedir(), ".qoderwork", "skills"),
|
|
903
1052
|
qoderworkcn: join(homedir(), ".qoderworkcn", "skills"),
|
|
904
1053
|
codex: join(homedir(), ".codex", "skills"),
|
|
905
1054
|
claude: join(homedir(), ".claude", "skills"),
|
|
1055
|
+
opencode: join(homedir(), ".config", "opencode", "skills"),
|
|
906
1056
|
openclaw: join(homedir(), ".openclaw", "skills"),
|
|
907
1057
|
hermes: join(homedir(), ".hermes", "skills")
|
|
908
1058
|
};
|
|
@@ -958,6 +1108,39 @@ function appendHooks(agent, hookPaths) {
|
|
|
958
1108
|
if (!path) return;
|
|
959
1109
|
appendHooksToFile(path, agent, hookPaths);
|
|
960
1110
|
}
|
|
1111
|
+
var OPENCODE_SHIM_FILENAME = "ctxdb.ts";
|
|
1112
|
+
function opencodeShimPath() {
|
|
1113
|
+
return join(homedir(), ".config", "opencode", "plugins", OPENCODE_SHIM_FILENAME);
|
|
1114
|
+
}
|
|
1115
|
+
function resolveOpencodePluginEntry() {
|
|
1116
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1117
|
+
const pkgRoot = walkUpToPackageJson(here, "@aliyunrds/ctxdb");
|
|
1118
|
+
if (!pkgRoot) return null;
|
|
1119
|
+
const candidates = [
|
|
1120
|
+
join(pkgRoot, "dist", "opencode", "index.js"),
|
|
1121
|
+
join(pkgRoot, "..", "opencode", "src", "index.ts")
|
|
1122
|
+
];
|
|
1123
|
+
return candidates.find((candidate) => existsSync(candidate)) ?? null;
|
|
1124
|
+
}
|
|
1125
|
+
var OPENCODE_SHIM_HEADER = "// Generated by ctxdb setup --agent opencode. Do not edit by hand.\n// To refresh after moving ctxdb's install directory, run ctxdb upgrade --agent opencode or re-run setup.\n// To remove, run: ctxdb setup --remove --agent opencode\n";
|
|
1126
|
+
function writeOpencodeShim(absEntry) {
|
|
1127
|
+
const path = opencodeShimPath();
|
|
1128
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1129
|
+
const importSpec = absEntry.replace(/\\/g, "/");
|
|
1130
|
+
const body = `${OPENCODE_SHIM_HEADER}export { default } from ${JSON.stringify(importSpec)};
|
|
1131
|
+
`;
|
|
1132
|
+
writeFileSync(path, body, "utf-8");
|
|
1133
|
+
}
|
|
1134
|
+
function removeOpencodeShim() {
|
|
1135
|
+
const path = opencodeShimPath();
|
|
1136
|
+
if (!existsSync(path)) return false;
|
|
1137
|
+
try {
|
|
1138
|
+
unlinkSync(path);
|
|
1139
|
+
return true;
|
|
1140
|
+
} catch {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
961
1144
|
var ENTRY_MARKER_KEY = "_ctxdb";
|
|
962
1145
|
var ENTRY_MARKER_VALUE = "@aliyunrds/ctxdb";
|
|
963
1146
|
var ENTRY_AGENT_KEY = "_ctxdbAgent";
|
|
@@ -967,7 +1150,8 @@ var TOOL_SCOPED_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse"]);
|
|
|
967
1150
|
function appendOne(hooks, event, command, agent) {
|
|
968
1151
|
if (!Array.isArray(hooks[event])) hooks[event] = [];
|
|
969
1152
|
const useExecForm = agent === "claude";
|
|
970
|
-
const
|
|
1153
|
+
const cmdRunner = agent === "qoder";
|
|
1154
|
+
const shellCmd = process.platform === "win32" ? cmdRunner ? `node ${command.replace(/\\/g, "/")} --agent=${agent}` : `"${process.execPath.replace(/\\/g, "/")}" "${command.replace(/\\/g, "/")}" --agent=${agent}` : `${process.execPath} ${command} --agent=${agent}`;
|
|
971
1155
|
const dup = hooks[event].some(
|
|
972
1156
|
(entry2) => Array.isArray(entry2?.hooks) && entry2.hooks.some(
|
|
973
1157
|
(h) => h?.type === "command" && (Array.isArray(h.args) && h.args[0] === command || typeof h.command === "string" && h.command.includes(command))
|
|
@@ -1300,6 +1484,28 @@ function upgradeOneAgent(agent) {
|
|
|
1300
1484
|
}
|
|
1301
1485
|
}
|
|
1302
1486
|
}
|
|
1487
|
+
if (agent === "opencode") {
|
|
1488
|
+
const absEntry = resolveOpencodePluginEntry();
|
|
1489
|
+
if (!absEntry) {
|
|
1490
|
+
steps.push({
|
|
1491
|
+
step: "refresh-opencode-shim",
|
|
1492
|
+
ok: false,
|
|
1493
|
+
detail: "could not resolve plugin entry under @aliyunrds/ctxdb/dist/opencode"
|
|
1494
|
+
});
|
|
1495
|
+
return { ok: false, steps };
|
|
1496
|
+
}
|
|
1497
|
+
try {
|
|
1498
|
+
writeOpencodeShim(absEntry);
|
|
1499
|
+
steps.push({
|
|
1500
|
+
step: "refresh-opencode-shim",
|
|
1501
|
+
ok: true,
|
|
1502
|
+
detail: `${opencodeShimPath()} \u2192 ${absEntry}`
|
|
1503
|
+
});
|
|
1504
|
+
} catch (err) {
|
|
1505
|
+
steps.push({ step: "refresh-opencode-shim", ok: false, detail: err?.message ?? String(err) });
|
|
1506
|
+
return { ok: false, steps };
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1303
1509
|
return { ok: steps.every((s) => s.ok), steps };
|
|
1304
1510
|
}
|
|
1305
1511
|
|
|
@@ -1360,7 +1566,7 @@ async function ping(args) {
|
|
|
1360
1566
|
var HELP = `ctxdb setup \u2014 configure an agent
|
|
1361
1567
|
|
|
1362
1568
|
USAGE
|
|
1363
|
-
ctxdb setup [--agent <qoder|qoderwork|codex|claude>]
|
|
1569
|
+
ctxdb setup [--agent <qoder|qoderwork|codex|claude|opencode>]
|
|
1364
1570
|
[--api-key=K] [--base-url=URL] [--user-id=ID]
|
|
1365
1571
|
[--no-install-skill] [--no-validate] [--json]
|
|
1366
1572
|
|
|
@@ -1371,7 +1577,7 @@ BEHAVIOR
|
|
|
1371
1577
|
specified agent harness.
|
|
1372
1578
|
|
|
1373
1579
|
FLAGS
|
|
1374
|
-
--agent <name> Target agent (qoder|qoderwork|codex|claude)
|
|
1580
|
+
--agent <name> Target agent (qoder|qoderwork|codex|claude|opencode)
|
|
1375
1581
|
--api-key <key> API key (required on first setup)
|
|
1376
1582
|
--base-url <url> Server URL (default: https://context-database.aliyuncs.com)
|
|
1377
1583
|
--user-id <id> User bucket (default: "default")
|
|
@@ -1398,7 +1604,7 @@ async function setup(args) {
|
|
|
1398
1604
|
const json = !!args.flags.json;
|
|
1399
1605
|
if (agent === "default" && !json && !configuredAgents().includes("default")) {
|
|
1400
1606
|
process.stderr.write(
|
|
1401
|
-
`hint: creating CLI-only "default" agent (no hooks/skills). Use --agent <claude|qoder|qoderwork|codex> for full setup.
|
|
1607
|
+
`hint: creating CLI-only "default" agent (no hooks/skills). Use --agent <claude|qoder|qoderwork|codex|opencode> for full setup.
|
|
1402
1608
|
`
|
|
1403
1609
|
);
|
|
1404
1610
|
}
|
|
@@ -1416,6 +1622,13 @@ async function setup(args) {
|
|
|
1416
1622
|
${h}
|
|
1417
1623
|
`);
|
|
1418
1624
|
}
|
|
1625
|
+
if (!json && agent === "codex" && result.ok) {
|
|
1626
|
+
process.stderr.write(
|
|
1627
|
+
`
|
|
1628
|
+
${dim("codex hooks \u914D\u7F6E\u540E\uFF0C\u9700\u8981\u91CD\u542F Codex \u5BF9\u8BDD\uFF0C\u5728\u5F39\u51FA\u7684 hooks \u786E\u8BA4\u754C\u9762\u9009\u62E9\u63A5\u6536\u6240\u6709 hooks(\u94A9\u5B50)\u9009\u9879")}
|
|
1629
|
+
`
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1419
1632
|
return result.ok ? 0 : 1;
|
|
1420
1633
|
}
|
|
1421
1634
|
|
|
@@ -1423,11 +1636,11 @@ ${h}
|
|
|
1423
1636
|
var HELP2 = `ctxdb uninstall \u2014 remove hooks + skills
|
|
1424
1637
|
|
|
1425
1638
|
USAGE
|
|
1426
|
-
ctxdb uninstall [--agent <qoder|qoderwork|codex|claude>] [--purge-config] [--purge-logs] [--purge-all] [--json]
|
|
1639
|
+
ctxdb uninstall [--agent <qoder|qoderwork|codex|claude|opencode>] [--purge-config] [--purge-logs] [--purge-all] [--json]
|
|
1427
1640
|
|
|
1428
1641
|
DEFAULT BEHAVIOR (no --agent, no purge flags)
|
|
1429
1642
|
Strips ctxdb hook entries and skill directories for ALL agents (qoder,
|
|
1430
|
-
qoderwork, codex, claude). Keeps ~/.ctxdb/ctxdb.json (credentials) and ~/.ctxdb/logs/
|
|
1643
|
+
qoderwork, codex, claude, opencode). Keeps ~/.ctxdb/ctxdb.json (credentials) and ~/.ctxdb/logs/
|
|
1431
1644
|
so you can re-run \`ctxdb setup\` later without losing configuration.
|
|
1432
1645
|
|
|
1433
1646
|
WITH --agent <name>
|
|
@@ -1435,7 +1648,7 @@ WITH --agent <name>
|
|
|
1435
1648
|
Other agents remain untouched.
|
|
1436
1649
|
|
|
1437
1650
|
FLAGS
|
|
1438
|
-
--agent <name> Target a single agent (qoder|qoderwork|codex|claude)
|
|
1651
|
+
--agent <name> Target a single agent (qoder|qoderwork|codex|claude|opencode)
|
|
1439
1652
|
--purge-config Also delete ~/.ctxdb/ctxdb.json
|
|
1440
1653
|
--purge-logs Also delete ~/.ctxdb/logs/
|
|
1441
1654
|
--purge-all Delete everything under ~/.ctxdb/
|
|
@@ -1593,7 +1806,7 @@ function runSelfUpdate(currentVersion, passthroughArgs = []) {
|
|
|
1593
1806
|
var HELP3 = `ctxdb upgrade \u2014 refresh skills + hooks
|
|
1594
1807
|
|
|
1595
1808
|
USAGE
|
|
1596
|
-
ctxdb upgrade [--agent <qoder|qoderwork|codex|claude>] [--self-update] [--json]
|
|
1809
|
+
ctxdb upgrade [--agent <qoder|qoderwork|codex|claude|opencode>] [--self-update] [--json]
|
|
1597
1810
|
|
|
1598
1811
|
BEHAVIOR
|
|
1599
1812
|
Refreshes skill files and hook entries for configured agents without
|
|
@@ -1603,7 +1816,7 @@ BEHAVIOR
|
|
|
1603
1816
|
With --agent: upgrades only the specified agent.
|
|
1604
1817
|
|
|
1605
1818
|
FLAGS
|
|
1606
|
-
--agent <name> Target a single agent (qoder|qoderwork|codex|claude)
|
|
1819
|
+
--agent <name> Target a single agent (qoder|qoderwork|codex|claude|opencode)
|
|
1607
1820
|
--self-update Also pull latest package from npm before upgrading
|
|
1608
1821
|
--json Machine-readable JSON output
|
|
1609
1822
|
`;
|
|
@@ -1709,7 +1922,11 @@ function skillInstall(args) {
|
|
|
1709
1922
|
}
|
|
1710
1923
|
targetRoot = resolved;
|
|
1711
1924
|
}
|
|
1712
|
-
const
|
|
1925
|
+
const TARGET_TEMPLATE_AGENT = {
|
|
1926
|
+
qodercn: "qoder",
|
|
1927
|
+
qoderworkcn: "qoderwork"
|
|
1928
|
+
};
|
|
1929
|
+
const templateAgent = TARGET_TEMPLATE_AGENT[target] ?? (target && isBuiltinAgent(target) ? target : "default");
|
|
1713
1930
|
const result = installSkillsTo(targetRoot, templateAgent);
|
|
1714
1931
|
const ok = result.ok;
|
|
1715
1932
|
const output = {
|
|
@@ -1966,7 +2183,7 @@ USAGE
|
|
|
1966
2183
|
ctxdb <command> [args] [--flags]
|
|
1967
2184
|
|
|
1968
2185
|
COMMANDS
|
|
1969
|
-
setup [--agent <qoder|qoderwork|codex|claude>]
|
|
2186
|
+
setup [--agent <qoder|qoderwork|codex|claude|opencode>]
|
|
1970
2187
|
[--api-key=K] [--base-url=URL] [--user-id=ID]
|
|
1971
2188
|
[--no-install-skill] [--no-validate] [--json]
|
|
1972
2189
|
Without --agent \u2192 writes agents.default (CLI-only, no hooks/skills)
|
|
@@ -1974,6 +2191,7 @@ COMMANDS
|
|
|
1974
2191
|
QoderWork \u2192 writes agents.qoderwork config + ~/.qoderwork/settings.json hooks + skill
|
|
1975
2192
|
Codex \u2192 writes agents.codex config + ~/.codex/hooks.json hooks + skill
|
|
1976
2193
|
Claude \u2192 writes agents.claude config + ~/.claude/settings.json hooks + skill
|
|
2194
|
+
OpenCode \u2192 writes agents.opencode config + ~/.config/opencode/plugins/ctxdb.ts shim + skill
|
|
1977
2195
|
status [--agent <name>] [--json]
|
|
1978
2196
|
ping [--agent <name>] [--json]
|
|
1979
2197
|
uninstall [--agent <name>] [--purge-config] [--purge-logs] [--purge-all] [--json]
|
|
@@ -2016,7 +2234,7 @@ COMMANDS
|
|
|
2016
2234
|
doc_id?/tags? for citation.
|
|
2017
2235
|
--raw: server response verbatim (curl parity).
|
|
2018
2236
|
|
|
2019
|
-
skill install --target <qoder|qoderwork|codex|claude|openclaw|hermes> [--json]
|
|
2237
|
+
skill install --target <qoder|qoderwork|codex|claude|opencode|openclaw|hermes> [--json]
|
|
2020
2238
|
skill install --path <dir> [--json]
|
|
2021
2239
|
Install skill files (contextdb-memory,
|
|
2022
2240
|
contextdb-knowledge) to an agent's skill directory.
|
|
@@ -2029,7 +2247,7 @@ AGENT RESOLUTION (when --agent is omitted):
|
|
|
2029
2247
|
ENV VARS (override selected ~/.ctxdb/ctxdb.json agent config):
|
|
2030
2248
|
CTXDB_AGENT CTXDB_API_KEY CTXDB_BASE_URL CTXDB_USER_ID
|
|
2031
2249
|
|
|
2032
|
-
CONFIG: ~/.ctxdb/ctxdb.json (agents.default / agents.qoder / agents.qoderwork / agents.codex / agents.claude)
|
|
2250
|
+
CONFIG: ~/.ctxdb/ctxdb.json (agents.default / agents.qoder / agents.qoderwork / agents.codex / agents.claude / agents.opencode)
|
|
2033
2251
|
LOGS: ~/.ctxdb/logs/ctxdb.log
|
|
2034
2252
|
`;
|
|
2035
2253
|
var ROUTES = {
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
fetchKbCatalogBlock,
|
|
4
4
|
recallTurn
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-QPFFMW52.js";
|
|
6
6
|
import "../chunk-6S5RJYBC.js";
|
|
7
7
|
import {
|
|
8
8
|
isCircuitOpen
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-PLWEIEFH.js";
|
|
10
10
|
import {
|
|
11
11
|
HttpClient,
|
|
12
12
|
agentFromArgvWithFallback,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
isComplete,
|
|
15
15
|
load,
|
|
16
16
|
setDebug
|
|
17
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-QHYJ7OXC.js";
|
|
18
18
|
|
|
19
19
|
// src/hooks/session-start.ts
|
|
20
20
|
import { pathToFileURL } from "url";
|