myagentmemory 0.4.12 → 0.4.14
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 +118 -49
- package/dist/cli-spec.d.ts +25 -0
- package/dist/cli-spec.js +211 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +435 -71
- package/dist/completions.d.ts +13 -0
- package/dist/completions.js +429 -0
- package/dist/core.d.ts +22 -1
- package/dist/core.js +299 -62
- package/dist/hooks.d.ts +42 -0
- package/dist/hooks.js +444 -0
- package/dist/plugin-bootstrap.d.ts +190 -0
- package/dist/plugin-bootstrap.js +628 -0
- package/dist/plugin-host.d.ts +136 -0
- package/dist/plugin-host.js +98 -0
- package/dist/plugin-runtime.d.ts +21 -0
- package/dist/plugin-runtime.js +208 -0
- package/dist/plugin-service.d.ts +45 -0
- package/dist/plugin-service.js +395 -0
- package/docs/official-plugin-bootstrap.md +335 -0
- package/package.json +62 -11
- package/scripts/install-skills.sh +4 -1
- package/scripts/postinstall.cjs +23 -4
- package/src/cli-spec.ts +236 -0
- package/src/cli.ts +455 -82
- package/src/completions.ts +501 -0
- package/src/core.ts +314 -62
- package/src/hooks.ts +485 -0
- package/src/plugin-bootstrap.ts +931 -0
- package/src/plugin-host.ts +255 -0
- package/src/plugin-runtime.ts +296 -0
- package/src/plugin-service.ts +451 -0
- package/dist/agent-memory +0 -0
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
let homeDirOverride = null;
|
|
5
|
+
/** Override the detected home directory in deterministic tests. */
|
|
6
|
+
export function _setHookHomeDirForTest(directory) {
|
|
7
|
+
homeDirOverride = directory;
|
|
8
|
+
}
|
|
9
|
+
function resolveHomeDir() {
|
|
10
|
+
if (homeDirOverride !== null)
|
|
11
|
+
return homeDirOverride;
|
|
12
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();
|
|
13
|
+
return home && home !== "~" ? home : null;
|
|
14
|
+
}
|
|
15
|
+
function commandExists(command) {
|
|
16
|
+
const envPath = process.env.PATH ?? "";
|
|
17
|
+
if (!envPath)
|
|
18
|
+
return false;
|
|
19
|
+
const extensions = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean) : [""];
|
|
20
|
+
for (const directory of envPath.split(path.delimiter).filter(Boolean)) {
|
|
21
|
+
for (const extension of extensions) {
|
|
22
|
+
const filename = process.platform === "win32" ? `${command}${extension}` : command;
|
|
23
|
+
try {
|
|
24
|
+
fs.accessSync(path.join(directory, filename), fs.constants.X_OK);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const HOOK_MARKER_JSON = "_agentMemory";
|
|
33
|
+
const HOOK_MARKER_BEGIN = "# BEGIN agent-memory hook";
|
|
34
|
+
const HOOK_MARKER_END = "# END agent-memory hook";
|
|
35
|
+
function sessionStartHookCommand(agent) {
|
|
36
|
+
return `agent-memory hook session-start --agent ${agent}`;
|
|
37
|
+
}
|
|
38
|
+
function hookTargets(homeDir) {
|
|
39
|
+
return [
|
|
40
|
+
{
|
|
41
|
+
key: "claude",
|
|
42
|
+
label: "Claude Code",
|
|
43
|
+
homeMarker: path.join(homeDir, ".claude"),
|
|
44
|
+
detectFiles: [
|
|
45
|
+
path.join(homeDir, ".claude", "settings.json"),
|
|
46
|
+
path.join(homeDir, ".claude", "settings.local.json"),
|
|
47
|
+
],
|
|
48
|
+
detectCommand: "claude",
|
|
49
|
+
supported: true,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: "codex",
|
|
53
|
+
label: "Codex",
|
|
54
|
+
homeMarker: path.join(homeDir, ".codex"),
|
|
55
|
+
detectFiles: [path.join(homeDir, ".codex", "config.toml")],
|
|
56
|
+
detectCommand: "codex",
|
|
57
|
+
supported: true,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: "cursor",
|
|
61
|
+
label: "Cursor",
|
|
62
|
+
homeMarker: path.join(homeDir, ".cursor"),
|
|
63
|
+
detectFiles: [],
|
|
64
|
+
supported: true,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: "opencode",
|
|
68
|
+
label: "opencode",
|
|
69
|
+
homeMarker: path.join(homeDir, ".config", "opencode"),
|
|
70
|
+
detectFiles: [path.join(homeDir, ".config", "opencode", "opencode.json")],
|
|
71
|
+
detectCommand: "opencode",
|
|
72
|
+
supported: true,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: "pi",
|
|
76
|
+
label: "pi",
|
|
77
|
+
homeMarker: path.join(homeDir, ".pi"),
|
|
78
|
+
detectFiles: [],
|
|
79
|
+
detectCommand: "pi",
|
|
80
|
+
supported: false,
|
|
81
|
+
unsupportedReason: "no documented SessionStart hook mechanism",
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
export function detectHookAgents() {
|
|
86
|
+
const homeDir = resolveHomeDir();
|
|
87
|
+
if (!homeDir)
|
|
88
|
+
return { homeDir: null, targets: [] };
|
|
89
|
+
const targets = hookTargets(homeDir).map((target) => {
|
|
90
|
+
if (!fs.existsSync(target.homeMarker)) {
|
|
91
|
+
return { ...target, detected: false, detectReason: `${target.homeMarker} not found` };
|
|
92
|
+
}
|
|
93
|
+
const byFile = target.detectFiles.some((f) => fs.existsSync(f));
|
|
94
|
+
const byCommand = target.detectCommand ? commandExists(target.detectCommand) : false;
|
|
95
|
+
const requires = target.detectFiles.length > 0 || !!target.detectCommand;
|
|
96
|
+
if (requires && !byFile && !byCommand) {
|
|
97
|
+
return { ...target, detected: false, detectReason: "not detected" };
|
|
98
|
+
}
|
|
99
|
+
return { ...target, detected: true };
|
|
100
|
+
});
|
|
101
|
+
return { homeDir, targets };
|
|
102
|
+
}
|
|
103
|
+
function backupOnce(filePath) {
|
|
104
|
+
if (!fs.existsSync(filePath))
|
|
105
|
+
return undefined;
|
|
106
|
+
const backupPath = `${filePath}.agent-memory.bak`;
|
|
107
|
+
if (!fs.existsSync(backupPath)) {
|
|
108
|
+
fs.copyFileSync(filePath, backupPath);
|
|
109
|
+
}
|
|
110
|
+
return backupPath;
|
|
111
|
+
}
|
|
112
|
+
function readJsonConfig(filePath) {
|
|
113
|
+
if (!fs.existsSync(filePath))
|
|
114
|
+
return {};
|
|
115
|
+
let parsed;
|
|
116
|
+
try {
|
|
117
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
118
|
+
parsed = raw.trim() ? JSON.parse(raw) : {};
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
122
|
+
throw new Error(`cannot modify invalid JSON config ${filePath}: ${detail}`);
|
|
123
|
+
}
|
|
124
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
125
|
+
throw new Error(`cannot modify JSON config ${filePath}: root value must be an object`);
|
|
126
|
+
}
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
function writeJson(filePath, data) {
|
|
130
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
131
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
132
|
+
}
|
|
133
|
+
function installClaudeCodeHook(homeDir) {
|
|
134
|
+
const settingsPath = path.join(homeDir, ".claude", "settings.json");
|
|
135
|
+
const backup = backupOnce(settingsPath);
|
|
136
|
+
const settings = readJsonConfig(settingsPath);
|
|
137
|
+
const hooks = settings.hooks ?? {};
|
|
138
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? [...hooks.SessionStart] : [];
|
|
139
|
+
// Idempotency: look for any existing entry tagged with our marker.
|
|
140
|
+
const command = sessionStartHookCommand("claude");
|
|
141
|
+
let managed = 0;
|
|
142
|
+
let updated = 0;
|
|
143
|
+
for (const group of sessionStart) {
|
|
144
|
+
if (!group || typeof group !== "object")
|
|
145
|
+
continue;
|
|
146
|
+
const g = group;
|
|
147
|
+
const list = Array.isArray(g.hooks) ? g.hooks : [];
|
|
148
|
+
for (const hook of list) {
|
|
149
|
+
if (!hook || typeof hook !== "object")
|
|
150
|
+
continue;
|
|
151
|
+
const managedHook = hook;
|
|
152
|
+
if (managedHook[HOOK_MARKER_JSON] !== true)
|
|
153
|
+
continue;
|
|
154
|
+
managed++;
|
|
155
|
+
if (managedHook.command !== command) {
|
|
156
|
+
managedHook.command = command;
|
|
157
|
+
updated++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (managed && !updated) {
|
|
162
|
+
return { key: "claude", label: "Claude Code", installed: false, path: settingsPath, reason: "already installed" };
|
|
163
|
+
}
|
|
164
|
+
if (updated) {
|
|
165
|
+
hooks.SessionStart = sessionStart;
|
|
166
|
+
settings.hooks = hooks;
|
|
167
|
+
writeJson(settingsPath, settings);
|
|
168
|
+
return { key: "claude", label: "Claude Code", installed: true, path: settingsPath, backup, reason: "updated" };
|
|
169
|
+
}
|
|
170
|
+
sessionStart.push({
|
|
171
|
+
matcher: "startup|resume",
|
|
172
|
+
hooks: [{ type: "command", command, [HOOK_MARKER_JSON]: true }],
|
|
173
|
+
});
|
|
174
|
+
hooks.SessionStart = sessionStart;
|
|
175
|
+
settings.hooks = hooks;
|
|
176
|
+
writeJson(settingsPath, settings);
|
|
177
|
+
return { key: "claude", label: "Claude Code", installed: true, path: settingsPath, backup };
|
|
178
|
+
}
|
|
179
|
+
function installCodexHook(homeDir) {
|
|
180
|
+
const configPath = path.join(homeDir, ".codex", "config.toml");
|
|
181
|
+
const backup = backupOnce(configPath);
|
|
182
|
+
const existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf-8") : "";
|
|
183
|
+
const command = sessionStartHookCommand("codex");
|
|
184
|
+
const block = [
|
|
185
|
+
HOOK_MARKER_BEGIN,
|
|
186
|
+
"[[hooks.SessionStart]]",
|
|
187
|
+
'matcher = "startup|resume"',
|
|
188
|
+
"",
|
|
189
|
+
"[[hooks.SessionStart.hooks]]",
|
|
190
|
+
'type = "command"',
|
|
191
|
+
`command = "${command}"`,
|
|
192
|
+
HOOK_MARKER_END,
|
|
193
|
+
].join("\n");
|
|
194
|
+
if (existing.includes(HOOK_MARKER_BEGIN)) {
|
|
195
|
+
const escapeRe = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
196
|
+
const pattern = new RegExp(`${escapeRe(HOOK_MARKER_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_MARKER_END)}`);
|
|
197
|
+
const current = existing.match(pattern)?.[0] ?? "";
|
|
198
|
+
if (current.includes(`command = "${command}"`)) {
|
|
199
|
+
return { key: "codex", label: "Codex", installed: false, path: configPath, reason: "already installed" };
|
|
200
|
+
}
|
|
201
|
+
fs.writeFileSync(configPath, existing.replace(pattern, block), "utf-8");
|
|
202
|
+
return { key: "codex", label: "Codex", installed: true, path: configPath, backup, reason: "updated" };
|
|
203
|
+
}
|
|
204
|
+
const separator = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
205
|
+
const next = `${existing}${separator}${block}\n`;
|
|
206
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
207
|
+
fs.writeFileSync(configPath, next, "utf-8");
|
|
208
|
+
return { key: "codex", label: "Codex", installed: true, path: configPath, backup };
|
|
209
|
+
}
|
|
210
|
+
const CURSOR_RULE_BODY = `---
|
|
211
|
+
description: Load persistent memory context from agent-memory
|
|
212
|
+
alwaysApply: true
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
At the start of every conversation, and whenever the user references prior
|
|
216
|
+
context, run:
|
|
217
|
+
|
|
218
|
+
agent-memory context
|
|
219
|
+
|
|
220
|
+
Treat its stdout as authoritative context about the user, prior sessions,
|
|
221
|
+
scratchpad items, and long-term memory. Prefer it over guessing.
|
|
222
|
+
`;
|
|
223
|
+
function installCursorRule(homeDir) {
|
|
224
|
+
const rulesDir = path.join(homeDir, ".cursor", "rules");
|
|
225
|
+
const rulePath = path.join(rulesDir, "agent-memory.mdc");
|
|
226
|
+
if (fs.existsSync(rulePath)) {
|
|
227
|
+
return { key: "cursor", label: "Cursor", installed: false, path: rulePath, reason: "already installed" };
|
|
228
|
+
}
|
|
229
|
+
fs.mkdirSync(rulesDir, { recursive: true });
|
|
230
|
+
fs.writeFileSync(rulePath, CURSOR_RULE_BODY, "utf-8");
|
|
231
|
+
return { key: "cursor", label: "Cursor", installed: true, path: rulePath };
|
|
232
|
+
}
|
|
233
|
+
const OPENCODE_INSTRUCTIONS_BODY = `# agent-memory
|
|
234
|
+
|
|
235
|
+
At the start of every session and before answering context-dependent
|
|
236
|
+
questions, run:
|
|
237
|
+
|
|
238
|
+
agent-memory context
|
|
239
|
+
|
|
240
|
+
Treat its stdout as authoritative context about the user, prior sessions,
|
|
241
|
+
scratchpad items, and long-term memory.
|
|
242
|
+
`;
|
|
243
|
+
function installOpencodeInstructions(homeDir) {
|
|
244
|
+
const configPath = path.join(homeDir, ".config", "opencode", "opencode.json");
|
|
245
|
+
const instructionsPath = path.join(homeDir, ".agent-memory", "hooks", "opencode.md");
|
|
246
|
+
const backup = backupOnce(configPath);
|
|
247
|
+
const config = readJsonConfig(configPath);
|
|
248
|
+
const raw = config.instructions;
|
|
249
|
+
const list = Array.isArray(raw) ? [...raw] : [];
|
|
250
|
+
if (list.includes(instructionsPath)) {
|
|
251
|
+
return { key: "opencode", label: "opencode", installed: false, path: configPath, reason: "already installed" };
|
|
252
|
+
}
|
|
253
|
+
list.push(instructionsPath);
|
|
254
|
+
config.instructions = list;
|
|
255
|
+
fs.mkdirSync(path.dirname(instructionsPath), { recursive: true });
|
|
256
|
+
fs.writeFileSync(instructionsPath, OPENCODE_INSTRUCTIONS_BODY, "utf-8");
|
|
257
|
+
writeJson(configPath, config);
|
|
258
|
+
return { key: "opencode", label: "opencode", installed: true, path: configPath, backup };
|
|
259
|
+
}
|
|
260
|
+
export function installHooks(agents) {
|
|
261
|
+
const { homeDir, targets } = detectHookAgents();
|
|
262
|
+
if (!homeDir) {
|
|
263
|
+
return {
|
|
264
|
+
ok: false,
|
|
265
|
+
results: [],
|
|
266
|
+
error: "Home directory not found. Set HOME (or USERPROFILE on Windows) and retry.",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const results = [];
|
|
270
|
+
for (const target of targets) {
|
|
271
|
+
if (!agents.has(target.key))
|
|
272
|
+
continue;
|
|
273
|
+
if (!target.supported) {
|
|
274
|
+
results.push({
|
|
275
|
+
key: target.key,
|
|
276
|
+
label: target.label,
|
|
277
|
+
installed: false,
|
|
278
|
+
reason: target.unsupportedReason ?? "not supported",
|
|
279
|
+
});
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (!target.detected) {
|
|
283
|
+
results.push({
|
|
284
|
+
key: target.key,
|
|
285
|
+
label: target.label,
|
|
286
|
+
installed: false,
|
|
287
|
+
reason: target.detectReason ?? "not detected",
|
|
288
|
+
});
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
if (target.key === "claude")
|
|
293
|
+
results.push(installClaudeCodeHook(homeDir));
|
|
294
|
+
else if (target.key === "codex")
|
|
295
|
+
results.push(installCodexHook(homeDir));
|
|
296
|
+
else if (target.key === "cursor")
|
|
297
|
+
results.push(installCursorRule(homeDir));
|
|
298
|
+
else if (target.key === "opencode")
|
|
299
|
+
results.push(installOpencodeInstructions(homeDir));
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
results.push({
|
|
303
|
+
key: target.key,
|
|
304
|
+
label: target.label,
|
|
305
|
+
installed: false,
|
|
306
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return { ok: true, homeDir, results };
|
|
311
|
+
}
|
|
312
|
+
function uninstallClaudeCodeHook(homeDir) {
|
|
313
|
+
const settingsPath = path.join(homeDir, ".claude", "settings.json");
|
|
314
|
+
if (!fs.existsSync(settingsPath)) {
|
|
315
|
+
return { key: "claude", label: "Claude Code", installed: false, reason: "not installed" };
|
|
316
|
+
}
|
|
317
|
+
const settings = readJsonConfig(settingsPath);
|
|
318
|
+
const hooks = settings.hooks ?? {};
|
|
319
|
+
const sessionStart = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
320
|
+
let removed = 0;
|
|
321
|
+
const filtered = sessionStart
|
|
322
|
+
.map((group) => {
|
|
323
|
+
if (!group || typeof group !== "object")
|
|
324
|
+
return group;
|
|
325
|
+
const g = { ...group };
|
|
326
|
+
const list = Array.isArray(g.hooks) ? g.hooks : [];
|
|
327
|
+
const kept = list.filter((h) => {
|
|
328
|
+
const isOurs = h && typeof h === "object" && h[HOOK_MARKER_JSON] === true;
|
|
329
|
+
if (isOurs)
|
|
330
|
+
removed++;
|
|
331
|
+
return !isOurs;
|
|
332
|
+
});
|
|
333
|
+
g.hooks = kept;
|
|
334
|
+
return g;
|
|
335
|
+
})
|
|
336
|
+
.filter((group) => {
|
|
337
|
+
if (!group || typeof group !== "object")
|
|
338
|
+
return true;
|
|
339
|
+
const g = group;
|
|
340
|
+
return Array.isArray(g.hooks) && g.hooks.length > 0;
|
|
341
|
+
});
|
|
342
|
+
if (removed === 0) {
|
|
343
|
+
return { key: "claude", label: "Claude Code", installed: false, reason: "not installed" };
|
|
344
|
+
}
|
|
345
|
+
hooks.SessionStart = filtered;
|
|
346
|
+
if (filtered.length === 0)
|
|
347
|
+
delete hooks.SessionStart;
|
|
348
|
+
if (Object.keys(hooks).length === 0)
|
|
349
|
+
delete settings.hooks;
|
|
350
|
+
else
|
|
351
|
+
settings.hooks = hooks;
|
|
352
|
+
writeJson(settingsPath, settings);
|
|
353
|
+
return { key: "claude", label: "Claude Code", installed: true, path: settingsPath };
|
|
354
|
+
}
|
|
355
|
+
function uninstallCodexHook(homeDir) {
|
|
356
|
+
const configPath = path.join(homeDir, ".codex", "config.toml");
|
|
357
|
+
if (!fs.existsSync(configPath)) {
|
|
358
|
+
return { key: "codex", label: "Codex", installed: false, reason: "not installed" };
|
|
359
|
+
}
|
|
360
|
+
const existing = fs.readFileSync(configPath, "utf-8");
|
|
361
|
+
if (!existing.includes(HOOK_MARKER_BEGIN)) {
|
|
362
|
+
return { key: "codex", label: "Codex", installed: false, reason: "not installed" };
|
|
363
|
+
}
|
|
364
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
365
|
+
const pattern = new RegExp(`\\n?${escapeRe(HOOK_MARKER_BEGIN)}[\\s\\S]*?${escapeRe(HOOK_MARKER_END)}\\n?`, "g");
|
|
366
|
+
const next = existing.replace(pattern, "");
|
|
367
|
+
fs.writeFileSync(configPath, next, "utf-8");
|
|
368
|
+
return { key: "codex", label: "Codex", installed: true, path: configPath };
|
|
369
|
+
}
|
|
370
|
+
function uninstallCursorRule(homeDir) {
|
|
371
|
+
const rulePath = path.join(homeDir, ".cursor", "rules", "agent-memory.mdc");
|
|
372
|
+
if (!fs.existsSync(rulePath)) {
|
|
373
|
+
return { key: "cursor", label: "Cursor", installed: false, reason: "not installed" };
|
|
374
|
+
}
|
|
375
|
+
fs.unlinkSync(rulePath);
|
|
376
|
+
try {
|
|
377
|
+
fs.rmdirSync(path.dirname(rulePath));
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
// non-empty; fine
|
|
381
|
+
}
|
|
382
|
+
return { key: "cursor", label: "Cursor", installed: true, path: rulePath };
|
|
383
|
+
}
|
|
384
|
+
function uninstallOpencodeInstructions(homeDir) {
|
|
385
|
+
const configPath = path.join(homeDir, ".config", "opencode", "opencode.json");
|
|
386
|
+
const instructionsPath = path.join(homeDir, ".agent-memory", "hooks", "opencode.md");
|
|
387
|
+
let touched = false;
|
|
388
|
+
if (fs.existsSync(configPath)) {
|
|
389
|
+
const config = readJsonConfig(configPath);
|
|
390
|
+
const list = Array.isArray(config.instructions) ? config.instructions : [];
|
|
391
|
+
const filtered = list.filter((entry) => entry !== instructionsPath);
|
|
392
|
+
if (filtered.length !== list.length) {
|
|
393
|
+
touched = true;
|
|
394
|
+
if (filtered.length === 0)
|
|
395
|
+
delete config.instructions;
|
|
396
|
+
else
|
|
397
|
+
config.instructions = filtered;
|
|
398
|
+
writeJson(configPath, config);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (fs.existsSync(instructionsPath)) {
|
|
402
|
+
fs.unlinkSync(instructionsPath);
|
|
403
|
+
touched = true;
|
|
404
|
+
}
|
|
405
|
+
if (!touched) {
|
|
406
|
+
return { key: "opencode", label: "opencode", installed: false, reason: "not installed" };
|
|
407
|
+
}
|
|
408
|
+
return { key: "opencode", label: "opencode", installed: true, path: configPath };
|
|
409
|
+
}
|
|
410
|
+
export function uninstallHooks(agents) {
|
|
411
|
+
const homeDir = resolveHomeDir();
|
|
412
|
+
if (!homeDir) {
|
|
413
|
+
return {
|
|
414
|
+
ok: false,
|
|
415
|
+
results: [],
|
|
416
|
+
error: "Home directory not found. Set HOME (or USERPROFILE on Windows) and retry.",
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const keys = ["claude", "codex", "cursor", "opencode"];
|
|
420
|
+
const results = [];
|
|
421
|
+
for (const key of keys) {
|
|
422
|
+
if (agents && !agents.has(key))
|
|
423
|
+
continue;
|
|
424
|
+
try {
|
|
425
|
+
if (key === "claude")
|
|
426
|
+
results.push(uninstallClaudeCodeHook(homeDir));
|
|
427
|
+
else if (key === "codex")
|
|
428
|
+
results.push(uninstallCodexHook(homeDir));
|
|
429
|
+
else if (key === "cursor")
|
|
430
|
+
results.push(uninstallCursorRule(homeDir));
|
|
431
|
+
else if (key === "opencode")
|
|
432
|
+
results.push(uninstallOpencodeInstructions(homeDir));
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
results.push({
|
|
436
|
+
key,
|
|
437
|
+
label: key,
|
|
438
|
+
installed: false,
|
|
439
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return { ok: true, homeDir, results };
|
|
444
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { type AgentMemoryBundleManifestV1, type PluginEntitlementStatusV1 } from "./plugin-host.js";
|
|
2
|
+
export declare const OFFICIAL_BUNDLE_ID = "agentmemory.pro";
|
|
3
|
+
export declare const OFFICIAL_PLUGIN_IDS: readonly ["agentmemory.session-intelligence", "agentmemory.web-console"];
|
|
4
|
+
export type PluginBootstrapResultKindV1 = "not_installed" | "installed" | "upgraded" | "current" | "update_available" | "uninstalled" | "auth_required" | "renewal_required" | "unavailable";
|
|
5
|
+
export interface PluginBootstrapErrorV1 {
|
|
6
|
+
code: string;
|
|
7
|
+
message: string;
|
|
8
|
+
retryable?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface PluginNextActionV1 {
|
|
11
|
+
kind: "authenticate" | "renew" | "manage";
|
|
12
|
+
url: string;
|
|
13
|
+
userCode?: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PluginInstallReceiptV1 {
|
|
17
|
+
schemaVersion: 1;
|
|
18
|
+
bundleId: string;
|
|
19
|
+
version: string;
|
|
20
|
+
channel: string;
|
|
21
|
+
pluginApi: 1;
|
|
22
|
+
entrypoint: string;
|
|
23
|
+
packageSha256: string;
|
|
24
|
+
installedAt: string;
|
|
25
|
+
previousVersion?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface PluginSummaryV1 {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
installed: boolean;
|
|
31
|
+
available: boolean;
|
|
32
|
+
entitlement: PluginEntitlementStatusV1["state"];
|
|
33
|
+
}
|
|
34
|
+
export interface PluginBootstrapResultV1 {
|
|
35
|
+
schemaVersion: 1;
|
|
36
|
+
command: `plugin.${string}`;
|
|
37
|
+
ok: boolean;
|
|
38
|
+
result: PluginBootstrapResultKindV1;
|
|
39
|
+
bundle: {
|
|
40
|
+
id: string;
|
|
41
|
+
previousVersion: string | null;
|
|
42
|
+
version: string | null;
|
|
43
|
+
channel: string;
|
|
44
|
+
} | null;
|
|
45
|
+
entitlement: PluginEntitlementStatusV1;
|
|
46
|
+
plugins?: PluginSummaryV1[];
|
|
47
|
+
nextAction: PluginNextActionV1 | null;
|
|
48
|
+
error?: PluginBootstrapErrorV1;
|
|
49
|
+
}
|
|
50
|
+
export interface SignedPluginReleaseV1 {
|
|
51
|
+
schemaVersion: 1;
|
|
52
|
+
manifest: AgentMemoryBundleManifestV1;
|
|
53
|
+
platform: string;
|
|
54
|
+
architecture: string;
|
|
55
|
+
packageSha256: string;
|
|
56
|
+
size: number;
|
|
57
|
+
signature: {
|
|
58
|
+
algorithm: "ed25519";
|
|
59
|
+
keyId: string;
|
|
60
|
+
value: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export interface AgentMemoryPackageFileV1 {
|
|
64
|
+
path: string;
|
|
65
|
+
sha256: string;
|
|
66
|
+
contentBase64: string;
|
|
67
|
+
executable?: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface AgentMemoryPackageV1 {
|
|
70
|
+
schemaVersion: 1;
|
|
71
|
+
manifest: AgentMemoryBundleManifestV1;
|
|
72
|
+
files: AgentMemoryPackageFileV1[];
|
|
73
|
+
}
|
|
74
|
+
export type PluginAccessDecisionV1 = {
|
|
75
|
+
kind: "granted";
|
|
76
|
+
entitlement: PluginEntitlementStatusV1;
|
|
77
|
+
artifactGrant: string;
|
|
78
|
+
} | {
|
|
79
|
+
kind: "auth_required";
|
|
80
|
+
entitlement: PluginEntitlementStatusV1;
|
|
81
|
+
nextAction: PluginNextActionV1;
|
|
82
|
+
} | {
|
|
83
|
+
kind: "renewal_required";
|
|
84
|
+
entitlement: PluginEntitlementStatusV1;
|
|
85
|
+
nextAction: PluginNextActionV1;
|
|
86
|
+
} | {
|
|
87
|
+
kind: "unavailable";
|
|
88
|
+
entitlement: PluginEntitlementStatusV1;
|
|
89
|
+
error: PluginBootstrapErrorV1;
|
|
90
|
+
};
|
|
91
|
+
export interface PluginBootstrapBackendV1 {
|
|
92
|
+
getLocalEntitlement(): Promise<PluginEntitlementStatusV1>;
|
|
93
|
+
resolveAccess(request: {
|
|
94
|
+
bundleId: string;
|
|
95
|
+
installedVersion?: string;
|
|
96
|
+
channel: string;
|
|
97
|
+
allowAuthentication: boolean;
|
|
98
|
+
}): Promise<PluginAccessDecisionV1>;
|
|
99
|
+
listReleases(request: {
|
|
100
|
+
bundleId: string;
|
|
101
|
+
channel: string;
|
|
102
|
+
artifactGrant: string;
|
|
103
|
+
}): Promise<SignedPluginReleaseV1[]>;
|
|
104
|
+
downloadArtifact(request: {
|
|
105
|
+
release: SignedPluginReleaseV1;
|
|
106
|
+
artifactGrant: string;
|
|
107
|
+
}): Promise<Uint8Array>;
|
|
108
|
+
getManagementAction(): Promise<PluginNextActionV1 | null>;
|
|
109
|
+
}
|
|
110
|
+
export interface PluginReleaseVerifierV1 {
|
|
111
|
+
verifyRelease(release: SignedPluginReleaseV1): void;
|
|
112
|
+
}
|
|
113
|
+
export interface PluginInstallStoreV1 {
|
|
114
|
+
readonly root: string;
|
|
115
|
+
readReceipt(bundleId: string): PluginInstallReceiptV1 | null;
|
|
116
|
+
hasInstalledBundle(receipt: PluginInstallReceiptV1): boolean;
|
|
117
|
+
install(packageBytes: Uint8Array, release: SignedPluginReleaseV1, healthCheck?: (directory: string, release: SignedPluginReleaseV1) => Promise<void>): Promise<PluginInstallReceiptV1>;
|
|
118
|
+
uninstall(bundleId: string): PluginInstallReceiptV1 | null;
|
|
119
|
+
}
|
|
120
|
+
export interface PluginBootstrapOptionsV1 {
|
|
121
|
+
coreVersion: string;
|
|
122
|
+
backend: PluginBootstrapBackendV1;
|
|
123
|
+
verifier: PluginReleaseVerifierV1;
|
|
124
|
+
store: PluginInstallStoreV1;
|
|
125
|
+
platform?: string;
|
|
126
|
+
architecture?: string;
|
|
127
|
+
healthCheck?: (directory: string, release: SignedPluginReleaseV1) => Promise<void>;
|
|
128
|
+
}
|
|
129
|
+
export interface PluginReconcileOptionsV1 {
|
|
130
|
+
channel?: string;
|
|
131
|
+
allowAuthentication?: boolean;
|
|
132
|
+
}
|
|
133
|
+
export declare class PluginBootstrapFailure extends Error {
|
|
134
|
+
readonly code: string;
|
|
135
|
+
readonly retryable: boolean;
|
|
136
|
+
constructor(code: string, message: string, retryable?: boolean);
|
|
137
|
+
}
|
|
138
|
+
export declare class UnavailablePluginBackend implements PluginBootstrapBackendV1 {
|
|
139
|
+
getLocalEntitlement(): Promise<PluginEntitlementStatusV1>;
|
|
140
|
+
resolveAccess(): Promise<PluginAccessDecisionV1>;
|
|
141
|
+
listReleases(): Promise<SignedPluginReleaseV1[]>;
|
|
142
|
+
downloadArtifact(): Promise<Uint8Array>;
|
|
143
|
+
getManagementAction(): Promise<PluginNextActionV1 | null>;
|
|
144
|
+
}
|
|
145
|
+
export declare class RejectingReleaseVerifier implements PluginReleaseVerifierV1 {
|
|
146
|
+
verifyRelease(): void;
|
|
147
|
+
}
|
|
148
|
+
export declare class Ed25519ReleaseVerifier implements PluginReleaseVerifierV1 {
|
|
149
|
+
private readonly keys;
|
|
150
|
+
constructor(keys: Record<string, string | Buffer>);
|
|
151
|
+
verifyRelease(release: SignedPluginReleaseV1): void;
|
|
152
|
+
}
|
|
153
|
+
export declare class FilePluginInstallStore implements PluginInstallStoreV1 {
|
|
154
|
+
readonly root: string;
|
|
155
|
+
constructor(root?: string);
|
|
156
|
+
readReceipt(bundleId: string): PluginInstallReceiptV1 | null;
|
|
157
|
+
hasInstalledBundle(receipt: PluginInstallReceiptV1): boolean;
|
|
158
|
+
install(packageBytes: Uint8Array, release: SignedPluginReleaseV1, healthCheck?: (directory: string, release: SignedPluginReleaseV1) => Promise<void>): Promise<PluginInstallReceiptV1>;
|
|
159
|
+
uninstall(bundleId: string): PluginInstallReceiptV1 | null;
|
|
160
|
+
private ensureRoot;
|
|
161
|
+
private receiptPath;
|
|
162
|
+
private versionPath;
|
|
163
|
+
private writeReceipt;
|
|
164
|
+
private acquireLock;
|
|
165
|
+
private releaseLock;
|
|
166
|
+
}
|
|
167
|
+
export declare class PluginBootstrapV1 {
|
|
168
|
+
private readonly options;
|
|
169
|
+
private readonly platform;
|
|
170
|
+
private readonly architecture;
|
|
171
|
+
constructor(options: PluginBootstrapOptionsV1);
|
|
172
|
+
list(): Promise<PluginBootstrapResultV1>;
|
|
173
|
+
status(channel?: string): Promise<PluginBootstrapResultV1>;
|
|
174
|
+
install(options?: PluginReconcileOptionsV1): Promise<PluginBootstrapResultV1>;
|
|
175
|
+
update(options?: PluginReconcileOptionsV1): Promise<PluginBootstrapResultV1>;
|
|
176
|
+
uninstall(): Promise<PluginBootstrapResultV1>;
|
|
177
|
+
manage(): Promise<PluginBootstrapResultV1>;
|
|
178
|
+
private reconcile;
|
|
179
|
+
private selectRelease;
|
|
180
|
+
private validReceipt;
|
|
181
|
+
private result;
|
|
182
|
+
private failure;
|
|
183
|
+
}
|
|
184
|
+
export declare function createDefaultPluginBootstrap(coreVersion: string): PluginBootstrapV1;
|
|
185
|
+
export declare function getDefaultPluginInstallRoot(): string;
|
|
186
|
+
export declare function encodePluginPackage(packageValue: AgentMemoryPackageV1): Uint8Array;
|
|
187
|
+
export declare function releaseSigningPayload(release: SignedPluginReleaseV1): Uint8Array;
|
|
188
|
+
export declare function sha256(value: Uint8Array): string;
|
|
189
|
+
export declare function compareVersions(left: string, right: string): number;
|
|
190
|
+
export declare function supportsVersionRange(range: string, version: string): boolean;
|