@aliyunrds/ctxdb 0.0.9 → 0.0.10
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 +21 -7
- package/dist/{chunk-TOXXGL5L.js → chunk-F25Q3WM4.js} +3 -1
- package/dist/{chunk-RHWGDY6S.js → chunk-I5HRGFZO.js} +3 -3
- package/dist/{chunk-PZJZ5O62.js → chunk-XWPTIFUK.js} +1 -1
- package/dist/cli/main.js +193 -14
- 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/src/capture.ts +187 -0
- package/dist/opencode/src/config.ts +160 -0
- package/dist/opencode/src/hooks.ts +252 -0
- package/dist/opencode/src/http-client.ts +147 -0
- package/dist/opencode/src/index.ts +9 -0
- package/dist/opencode/src/kb-catalog.ts +64 -0
- package/dist/opencode/src/recall.ts +87 -0
- package/dist/opencode/src/warmup.ts +51 -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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Minimal HTTP client for the ctxdb server (in-process plugin variant).
|
|
2
|
+
//
|
|
3
|
+
// Stripped down from `@aliyunrds/ctxdb`'s HttpClient: no multipart, no
|
|
4
|
+
// PUT/DELETE, no debug logging — the plugin only ever does:
|
|
5
|
+
//
|
|
6
|
+
// GET /v1/knowledge/knowledge_bases
|
|
7
|
+
// POST /v3/memories/search/
|
|
8
|
+
// POST /v3/memories/add/
|
|
9
|
+
//
|
|
10
|
+
// Per-call timeouts are mandatory (callers pass 5s for recall/KB,
|
|
11
|
+
// 8s for capture). Errors are caught at the hook layer; this client
|
|
12
|
+
// throws on non-2xx but never logs to stderr itself.
|
|
13
|
+
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
15
|
+
|
|
16
|
+
export class CtxdbHttpError extends Error {
|
|
17
|
+
readonly status: number | null;
|
|
18
|
+
readonly path: string;
|
|
19
|
+
constructor(path: string, status: number | null, message: string) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "CtxdbHttpError";
|
|
22
|
+
this.path = path;
|
|
23
|
+
this.status = status;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface HttpClientOptions {
|
|
28
|
+
baseUrl: string;
|
|
29
|
+
apiKey: string | null;
|
|
30
|
+
userAgent?: string;
|
|
31
|
+
fetchImpl?: typeof fetch;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RequestOptions {
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class HttpClient {
|
|
39
|
+
readonly baseUrl: string;
|
|
40
|
+
readonly apiKey: string | null;
|
|
41
|
+
readonly userAgent: string;
|
|
42
|
+
private readonly fetchImpl: typeof fetch;
|
|
43
|
+
|
|
44
|
+
constructor(opts: HttpClientOptions) {
|
|
45
|
+
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
46
|
+
this.apiKey = opts.apiKey;
|
|
47
|
+
this.userAgent = opts.userAgent ?? "ctxdb-opencode-plugin/0.0.0";
|
|
48
|
+
this.fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private headers(contentType?: string): Record<string, string> {
|
|
52
|
+
const h: Record<string, string> = {
|
|
53
|
+
"User-Agent": this.userAgent,
|
|
54
|
+
Connection: "close",
|
|
55
|
+
};
|
|
56
|
+
if (this.apiKey) h.Authorization = `Token ${this.apiKey}`;
|
|
57
|
+
if (contentType) h["Content-Type"] = contentType;
|
|
58
|
+
return h;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async get(path: string, params?: Record<string, unknown>, options: RequestOptions = {}): Promise<unknown> {
|
|
62
|
+
let url = `${this.baseUrl}${path}`;
|
|
63
|
+
if (params) {
|
|
64
|
+
const qs = new URLSearchParams();
|
|
65
|
+
for (const [k, v] of Object.entries(params)) {
|
|
66
|
+
if (v !== undefined && v !== null) qs.append(k, String(v));
|
|
67
|
+
}
|
|
68
|
+
const s = qs.toString();
|
|
69
|
+
if (s) url = `${url}?${s}`;
|
|
70
|
+
}
|
|
71
|
+
return this.request("GET", url, path, undefined, undefined, options.timeoutMs);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async postJson(
|
|
75
|
+
path: string,
|
|
76
|
+
body: Record<string, unknown> | unknown[],
|
|
77
|
+
options: RequestOptions = {},
|
|
78
|
+
): Promise<unknown> {
|
|
79
|
+
const url = `${this.baseUrl}${path}`;
|
|
80
|
+
return this.request("POST", url, path, JSON.stringify(body), "application/json", options.timeoutMs);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private async request(
|
|
84
|
+
method: string,
|
|
85
|
+
url: string,
|
|
86
|
+
path: string,
|
|
87
|
+
body: string | undefined,
|
|
88
|
+
contentType: string | undefined,
|
|
89
|
+
timeoutMs: number | undefined,
|
|
90
|
+
): Promise<unknown> {
|
|
91
|
+
const effectiveTimeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
92
|
+
const controller = new AbortController();
|
|
93
|
+
const timer = setTimeout(() => controller.abort(), effectiveTimeout);
|
|
94
|
+
try {
|
|
95
|
+
let resp: Response;
|
|
96
|
+
try {
|
|
97
|
+
resp = await this.fetchImpl(url, {
|
|
98
|
+
method,
|
|
99
|
+
headers: this.headers(contentType),
|
|
100
|
+
body,
|
|
101
|
+
signal: controller.signal,
|
|
102
|
+
});
|
|
103
|
+
} catch (err: any) {
|
|
104
|
+
if (err?.name === "AbortError") {
|
|
105
|
+
throw new CtxdbHttpError(path, null, `timeout after ${effectiveTimeout}ms`);
|
|
106
|
+
}
|
|
107
|
+
throw new CtxdbHttpError(path, null, `network error: ${err?.message ?? err}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (resp.status === 204) return {};
|
|
111
|
+
let text: string;
|
|
112
|
+
try {
|
|
113
|
+
text = await resp.text();
|
|
114
|
+
} catch (err: any) {
|
|
115
|
+
throw new CtxdbHttpError(path, resp.status, `body read failed: ${err?.message ?? err}`);
|
|
116
|
+
}
|
|
117
|
+
if (!resp.ok) {
|
|
118
|
+
throw new CtxdbHttpError(path, resp.status, extractDetail(text) || `HTTP ${resp.status}`);
|
|
119
|
+
}
|
|
120
|
+
if (!text) return {};
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(text);
|
|
123
|
+
} catch {
|
|
124
|
+
return text;
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
clearTimeout(timer);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function extractDetail(text: string): string {
|
|
133
|
+
if (!text) return "";
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(text);
|
|
136
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
137
|
+
for (const k of ["detail", "message", "error"]) {
|
|
138
|
+
const v = (parsed as Record<string, unknown>)[k];
|
|
139
|
+
if (typeof v === "string" && v) return v;
|
|
140
|
+
}
|
|
141
|
+
return JSON.stringify(parsed);
|
|
142
|
+
}
|
|
143
|
+
return String(parsed);
|
|
144
|
+
} catch {
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Fetch /v1/knowledge/knowledge_bases (the existing endpoint used by
|
|
2
|
+
// `@aliyunrds/ctxdb`) and render the `<available-knowledge-bases>` block.
|
|
3
|
+
//
|
|
4
|
+
// The agent slug in the embedded `--agent=<X>` hint is hard-coded to
|
|
5
|
+
// "opencode" so the SKILL.md template substitution is consistent with
|
|
6
|
+
// what `ctxdb setup --agent opencode` writes to ~/.config/opencode/skills/.
|
|
7
|
+
|
|
8
|
+
import type { HttpClient } from "./http-client.ts";
|
|
9
|
+
|
|
10
|
+
export const KB_LIST_PATH = "/v1/knowledge/knowledge_bases";
|
|
11
|
+
|
|
12
|
+
interface KnowledgeBase {
|
|
13
|
+
name?: unknown;
|
|
14
|
+
status?: unknown;
|
|
15
|
+
key_entities?: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sanitizeKeyEntities(raw: unknown): string[] {
|
|
19
|
+
if (!Array.isArray(raw)) return [];
|
|
20
|
+
const out: string[] = [];
|
|
21
|
+
for (const e of raw) {
|
|
22
|
+
if (typeof e !== "string") continue;
|
|
23
|
+
const cleaned = e.replace(/\s+/g, " ").trim();
|
|
24
|
+
if (cleaned) out.push(cleaned);
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function fetchKbCatalogBlock(
|
|
30
|
+
client: HttpClient,
|
|
31
|
+
timeoutMs: number,
|
|
32
|
+
agent = "opencode",
|
|
33
|
+
): Promise<string> {
|
|
34
|
+
let resp: unknown;
|
|
35
|
+
try {
|
|
36
|
+
resp = await client.get(KB_LIST_PATH, undefined, { timeoutMs });
|
|
37
|
+
} catch {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let kbs: KnowledgeBase[] = [];
|
|
42
|
+
if (Array.isArray(resp)) {
|
|
43
|
+
kbs = resp as KnowledgeBase[];
|
|
44
|
+
} else if (resp && typeof resp === "object") {
|
|
45
|
+
const o = resp as Record<string, unknown>;
|
|
46
|
+
const list = (o.knowledge_bases ?? o.results) as unknown;
|
|
47
|
+
if (Array.isArray(list)) kbs = list as KnowledgeBase[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const active = kbs.filter((kb) => kb.status === "active" && typeof kb.name === "string" && kb.name);
|
|
51
|
+
if (active.length === 0) return "";
|
|
52
|
+
|
|
53
|
+
const lines = active.map((kb) => {
|
|
54
|
+
const ents = sanitizeKeyEntities(kb.key_entities);
|
|
55
|
+
return ents.length > 0 ? `· ${kb.name}: ${ents.join(", ")}` : `· ${kb.name}`;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return [
|
|
59
|
+
"<available-knowledge-bases>",
|
|
60
|
+
`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:`,
|
|
61
|
+
...lines,
|
|
62
|
+
"</available-knowledge-bases>",
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Recall path: search /v3/memories/search/ → format via shared
|
|
2
|
+
// formatRecalledMemoriesBlock. Mirrors recall-orchestrator.ts in
|
|
3
|
+
// `@aliyunrds/ctxdb` but stripped to the bare minimum the plugin needs.
|
|
4
|
+
//
|
|
5
|
+
// All callers wrap this in their own try/catch + timeout (5s recall).
|
|
6
|
+
// This module never throws — all failures resolve to an empty result.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
formatRecalledMemoriesBlock,
|
|
10
|
+
buildExternalKnowledgeBlock,
|
|
11
|
+
type MemoryItem,
|
|
12
|
+
type ExternalContextChunk,
|
|
13
|
+
} from "@aliyunrds/ctxdb-shared";
|
|
14
|
+
|
|
15
|
+
import type { OpencodeConfig } from "./config.ts";
|
|
16
|
+
import type { HttpClient } from "./http-client.ts";
|
|
17
|
+
|
|
18
|
+
export interface RecallResult {
|
|
19
|
+
/** Combined `<recalled-memories>` + `<external-knowledge>` block, or
|
|
20
|
+
* empty string when nothing to inject. */
|
|
21
|
+
block: string;
|
|
22
|
+
memoryCount: number;
|
|
23
|
+
knowledgeChunkCount: number;
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const EMPTY: RecallResult = { block: "", memoryCount: 0, knowledgeChunkCount: 0, reason: "" };
|
|
28
|
+
|
|
29
|
+
function stripSystemReminders(raw: string): string {
|
|
30
|
+
const cleaned = raw.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, "").trim();
|
|
31
|
+
return cleaned || raw;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function searchAndFormatRecall(
|
|
35
|
+
prompt: string,
|
|
36
|
+
cfg: OpencodeConfig,
|
|
37
|
+
client: HttpClient,
|
|
38
|
+
timeoutMs: number,
|
|
39
|
+
): Promise<RecallResult> {
|
|
40
|
+
if (!prompt.trim()) return { ...EMPTY, reason: "empty_prompt" };
|
|
41
|
+
if (!cfg.baseUrl) return { ...EMPTY, reason: "config_incomplete" };
|
|
42
|
+
|
|
43
|
+
const body: Record<string, unknown> = {
|
|
44
|
+
query: stripSystemReminders(prompt),
|
|
45
|
+
user_id: cfg.userId,
|
|
46
|
+
top_k: cfg.topK,
|
|
47
|
+
threshold: cfg.threshold,
|
|
48
|
+
};
|
|
49
|
+
if (cfg.recallKnowledge) {
|
|
50
|
+
body.knowledge = { enable: true, top_k: cfg.knowledgeTopK };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let resp: unknown;
|
|
54
|
+
try {
|
|
55
|
+
resp = await client.postJson("/v3/memories/search/", body, { timeoutMs });
|
|
56
|
+
} catch (err) {
|
|
57
|
+
return { ...EMPTY, reason: `http_error: ${(err as Error).message}` };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!resp || typeof resp !== "object") {
|
|
61
|
+
return { ...EMPTY, reason: "bad_response_shape" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const r = resp as Record<string, unknown>;
|
|
65
|
+
const memories: MemoryItem[] = Array.isArray(r.results)
|
|
66
|
+
? (r.results as MemoryItem[])
|
|
67
|
+
: Array.isArray(r.memories)
|
|
68
|
+
? (r.memories as MemoryItem[])
|
|
69
|
+
: [];
|
|
70
|
+
const knowledge =
|
|
71
|
+
r.knowledge && typeof r.knowledge === "object"
|
|
72
|
+
? (r.knowledge as { chunks?: ExternalContextChunk[] })
|
|
73
|
+
: null;
|
|
74
|
+
const chunks: ExternalContextChunk[] = Array.isArray(knowledge?.chunks) ? knowledge!.chunks! : [];
|
|
75
|
+
|
|
76
|
+
const blocks: string[] = [];
|
|
77
|
+
if (memories.length > 0) blocks.push(formatRecalledMemoriesBlock(memories, cfg.userId));
|
|
78
|
+
if (chunks.length > 0) blocks.push(buildExternalKnowledgeBlock(chunks, cfg.userId));
|
|
79
|
+
|
|
80
|
+
if (blocks.length === 0) return { ...EMPTY, reason: "nothing_to_inject" };
|
|
81
|
+
return {
|
|
82
|
+
block: blocks.join("\n\n"),
|
|
83
|
+
memoryCount: memories.length,
|
|
84
|
+
knowledgeChunkCount: chunks.length,
|
|
85
|
+
reason: "ok",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Warmup query builder — collects cwd basename + git branch + last 3
|
|
2
|
+
// commit subjects, joins into a single query string. Search is run via
|
|
3
|
+
// the regular recall path (searchAndFormatRecall); only the query
|
|
4
|
+
// shape differs.
|
|
5
|
+
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
import { basename } from "node:path";
|
|
8
|
+
|
|
9
|
+
export interface GitSignals {
|
|
10
|
+
branch: string;
|
|
11
|
+
recentCommits: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function collectGitSignals(cwd: string): GitSignals {
|
|
15
|
+
const result: GitSignals = { branch: "", recentCommits: [] };
|
|
16
|
+
try {
|
|
17
|
+
result.branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
18
|
+
cwd,
|
|
19
|
+
timeout: 500,
|
|
20
|
+
encoding: "utf-8",
|
|
21
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
22
|
+
}).trim();
|
|
23
|
+
} catch {
|
|
24
|
+
// not a git repo / no git
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const log = execSync("git log --oneline -3 --no-decorate", {
|
|
28
|
+
cwd,
|
|
29
|
+
timeout: 500,
|
|
30
|
+
encoding: "utf-8",
|
|
31
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
32
|
+
}).trim();
|
|
33
|
+
if (log) {
|
|
34
|
+
result.recentCommits = log.split("\n").map((l) => {
|
|
35
|
+
const idx = l.indexOf(" ");
|
|
36
|
+
return idx > 0 ? l.slice(idx + 1) : l;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// same
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildWarmupQuery(cwd: string, git: GitSignals): string {
|
|
46
|
+
const project = basename(cwd) || "unknown";
|
|
47
|
+
const parts: string[] = [`project: ${project}`];
|
|
48
|
+
if (git.branch) parts.push(`branch: ${git.branch}`);
|
|
49
|
+
if (git.recentCommits.length > 0) parts.push(`recent work: ${git.recentCommits.join("; ")}`);
|
|
50
|
+
return parts.join(", ");
|
|
51
|
+
}
|
|
@@ -18,7 +18,7 @@ description: contextdb 知识库高频操作(查询 / 浏览 KB / 上传文档
|
|
|
18
18
|
|
|
19
19
|
## 2. 高频 recipes
|
|
20
20
|
|
|
21
|
-
> 命令里的 `--agent={{agent}}` 是当前 skill 安装目标。不要把示例改成 `qoder`;如果读到未替换的双花括号 agent 模板,先按 §6 判定真实 agent,再替换成 `qoder` / `qoderwork` / `codex` / `claude` / `default`。
|
|
21
|
+
> 命令里的 `--agent={{agent}}` 是当前 skill 安装目标。不要把示例改成 `qoder`;如果读到未替换的双花括号 agent 模板,先按 §6 判定真实 agent,再替换成 `qoder` / `qoderwork` / `codex` / `claude` / `opencode` / `default`。
|
|
22
22
|
|
|
23
23
|
### Recipe 1:在已知 KB 中查内容(最常见)
|
|
24
24
|
|
|
@@ -172,18 +172,18 @@ ctxdb kb upload-file <kb-name> <local-path> --agent={{agent}}
|
|
|
172
172
|
|
|
173
173
|
| 参数 | 说明 |
|
|
174
174
|
|------|------|
|
|
175
|
-
| `--agent=<name>` | **必填**。指定操作哪个 agent 的配置桶(`qoder` / `qoderwork` / `codex` / `claude` / `default`) |
|
|
175
|
+
| `--agent=<name>` | **必填**。指定操作哪个 agent 的配置桶(`qoder` / `qoderwork` / `codex` / `claude` / `opencode` / `default`) |
|
|
176
176
|
|
|
177
177
|
`--agent` 解析顺序:
|
|
178
178
|
1. 命令行显式 `--agent=<name>` → 用它
|
|
179
179
|
2. 缺省 → 读 `CTXDB_AGENT` 环境变量
|
|
180
180
|
3. env 也没有 → 落到 `"default"` 桶
|
|
181
|
-
4. 新版本 `ctxdb setup --agent <qoder|qoderwork|codex|claude>` 会在 `agents.default` 缺失时复制当前 agent 配置作为兜底
|
|
181
|
+
4. 新版本 `ctxdb setup --agent <qoder|qoderwork|codex|claude|opencode>` 会在 `agents.default` 缺失时复制当前 agent 配置作为兜底
|
|
182
182
|
5. 若 `"default"` 桶仍不存在或未配置完整 → exit 2 "config incomplete for agent default"
|
|
183
183
|
|
|
184
184
|
## 7. 配置
|
|
185
185
|
|
|
186
|
-
- 配置文件:`~/.ctxdb/ctxdb.json`,分 `agents.<name>` 段(`qoder` / `qoderwork` / `codex` / `claude` / `default`),互不复用
|
|
186
|
+
- 配置文件:`~/.ctxdb/ctxdb.json`,分 `agents.<name>` 段(`qoder` / `qoderwork` / `codex` / `claude` / `opencode` / `default`),互不复用
|
|
187
187
|
- 配置命令:`ctxdb setup --agent <name> --api-key=<key> --base-url=<url> [--user-id=<id>]`
|
|
188
188
|
- 带 `--agent` 时还会装 hooks + skill;若 `agents.default` 缺失,会复制当前 agent 配置作为 CLI 兜底
|
|
189
189
|
- `--agent default` 只写 `agents.default` 段(仅 CLI 用,不装 hooks / skill)
|
|
@@ -18,7 +18,7 @@ description: contextdb 长期记忆高频操作(主动记忆 / 搜索过往 /
|
|
|
18
18
|
|
|
19
19
|
## 2. 高频 recipes
|
|
20
20
|
|
|
21
|
-
> 命令里的 `--agent={{agent}}` 是当前 skill 安装目标。不要把示例改成 `qoder`;如果读到未替换的双花括号 agent 模板,先按 §6 判定真实 agent,再替换成 `qoder` / `qoderwork` / `codex` / `claude` / `default`。
|
|
21
|
+
> 命令里的 `--agent={{agent}}` 是当前 skill 安装目标。不要把示例改成 `qoder`;如果读到未替换的双花括号 agent 模板,先按 §6 判定真实 agent,再替换成 `qoder` / `qoderwork` / `codex` / `claude` / `opencode` / `default`。
|
|
22
22
|
|
|
23
23
|
### Recipe 1:主动记忆("记住 X")
|
|
24
24
|
|
|
@@ -165,18 +165,18 @@ ctxdb memory delete <memory-id> --agent={{agent}}
|
|
|
165
165
|
|
|
166
166
|
| 参数 | 说明 |
|
|
167
167
|
|------|------|
|
|
168
|
-
| `--agent=<name>` | **必填**。指定操作哪个 agent 的配置桶(`qoder` / `qoderwork` / `codex` / `claude` / `default`) |
|
|
168
|
+
| `--agent=<name>` | **必填**。指定操作哪个 agent 的配置桶(`qoder` / `qoderwork` / `codex` / `claude` / `opencode` / `default`) |
|
|
169
169
|
|
|
170
170
|
`--agent` 解析顺序:
|
|
171
171
|
1. 命令行显式 `--agent=<name>` → 用它
|
|
172
172
|
2. 缺省 → 读 `CTXDB_AGENT` 环境变量
|
|
173
173
|
3. env 也没有 → 落到 `"default"` 桶
|
|
174
|
-
4. 新版本 `ctxdb setup --agent <qoder|qoderwork|codex|claude>` 会在 `agents.default` 缺失时复制当前 agent 配置作为兜底
|
|
174
|
+
4. 新版本 `ctxdb setup --agent <qoder|qoderwork|codex|claude|opencode>` 会在 `agents.default` 缺失时复制当前 agent 配置作为兜底
|
|
175
175
|
5. 若 `"default"` 桶仍不存在或未配置完整 → exit 2 "config incomplete for agent default"
|
|
176
176
|
|
|
177
177
|
## 7. 配置
|
|
178
178
|
|
|
179
|
-
- 配置文件:`~/.ctxdb/ctxdb.json`,分 `agents.<name>` 段(`qoder` / `qoderwork` / `codex` / `claude` / `default`),互不复用
|
|
179
|
+
- 配置文件:`~/.ctxdb/ctxdb.json`,分 `agents.<name>` 段(`qoder` / `qoderwork` / `codex` / `claude` / `opencode` / `default`),互不复用
|
|
180
180
|
- 配置命令:`ctxdb setup --agent <name> --api-key=<key> --base-url=<url> [--user-id=<id>]`
|
|
181
181
|
- 带 `--agent` 时还会装 hooks + skill;若 `agents.default` 缺失,会复制当前 agent 配置作为 CLI 兜底
|
|
182
182
|
- `--agent default` 只写 `agents.default` 段(仅 CLI 用,不装 hooks / skill)
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aliyunrds/ctxdb",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Unified access layer for RDS ContextDatabase: `ctxdb` CLI (memory + KB ops), one-shot `setup --agent <qoder|codex|claude>` installer, per-agent config, hooks, and SKILL.md.",
|
|
5
|
+
"description": "Unified access layer for RDS ContextDatabase: `ctxdb` CLI (memory + KB ops), one-shot `setup --agent <qoder|qoderwork|codex|claude|opencode>` installer, per-agent config, hooks/plugins, and SKILL.md.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"rds-context-database",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"qoder",
|
|
13
13
|
"codex",
|
|
14
14
|
"claude-code",
|
|
15
|
+
"opencode",
|
|
15
16
|
"hooks"
|
|
16
17
|
],
|
|
17
18
|
"main": "./dist/cli/main.js",
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"vitest": "^4.0.18"
|
|
39
40
|
},
|
|
40
41
|
"scripts": {
|
|
41
|
-
"build": "tsup && mkdir -p dist/setup && cp -r src/setup/skills dist/setup/ && chmod +x dist/hooks/*.js dist/cli/main.js",
|
|
42
|
+
"build": "tsup && mkdir -p dist/setup dist/opencode && cp -r src/setup/skills dist/setup/ && cp -r ../opencode/src dist/opencode/ && chmod +x dist/hooks/*.js dist/cli/main.js",
|
|
42
43
|
"test": "vitest run"
|
|
43
44
|
}
|
|
44
45
|
}
|