@membank/cli 0.2.0 → 0.3.0
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/index.mjs +356 -331
- package/package.json +4 -3
package/dist/index.mjs
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { DatabaseManager, EmbeddingService, MEMORY_TYPE_VALUES, MemoryRepository, QueryEngine, SessionContextBuilder, resolveScope } from "@membank/core";
|
|
3
3
|
import { startServer } from "@membank/mcp";
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import { startDashboard } from "@membank/dashboard";
|
|
5
6
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { dirname, join } from "node:path";
|
|
7
|
-
import { homedir, tmpdir } from "node:os";
|
|
8
8
|
import * as readline from "node:readline";
|
|
9
9
|
import { createInterface } from "node:readline";
|
|
10
|
+
import { homedir, tmpdir } from "node:os";
|
|
10
11
|
import { execFile } from "node:child_process";
|
|
11
12
|
import { promisify } from "node:util";
|
|
12
13
|
import { EventEmitter } from "node:events";
|
|
@@ -30,6 +31,11 @@ async function addCommand(content, options, formatter, db, embeddingService) {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
//#endregion
|
|
34
|
+
//#region src/commands/dashboard.ts
|
|
35
|
+
async function dashboardCommand(opts) {
|
|
36
|
+
await startDashboard({ port: opts.port !== void 0 ? parseInt(opts.port, 10) : void 0 });
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
33
39
|
//#region src/commands/delete.ts
|
|
34
40
|
async function deleteCommand(id, db, formatter, prompt) {
|
|
35
41
|
if (db.db.prepare(`SELECT id FROM memories WHERE id = ?`).get(id) === void 0) {
|
|
@@ -140,7 +146,66 @@ function formatContext(ctx) {
|
|
|
140
146
|
lines.push(MEMORY_GUIDANCE);
|
|
141
147
|
return lines.join("\n");
|
|
142
148
|
}
|
|
143
|
-
|
|
149
|
+
function outputAdditionalContext(text, harness, eventName) {
|
|
150
|
+
if (harness === "claude-code") {
|
|
151
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: {
|
|
152
|
+
hookEventName: eventName,
|
|
153
|
+
additionalContext: text
|
|
154
|
+
} }));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (harness === "copilot-cli") {
|
|
158
|
+
process.stdout.write(JSON.stringify({ additionalContext: text }));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
process.stdout.write(`${text}\n`);
|
|
162
|
+
}
|
|
163
|
+
async function readStdin() {
|
|
164
|
+
if (process.stdin.isTTY) return "";
|
|
165
|
+
return new Promise((resolve) => {
|
|
166
|
+
const chunks = [];
|
|
167
|
+
const timeout = setTimeout(() => resolve(""), 1e3);
|
|
168
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
169
|
+
process.stdin.on("end", () => {
|
|
170
|
+
clearTimeout(timeout);
|
|
171
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
172
|
+
});
|
|
173
|
+
process.stdin.on("error", () => {
|
|
174
|
+
clearTimeout(timeout);
|
|
175
|
+
resolve("");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
const FEEDBACK_PATTERNS = [
|
|
180
|
+
/\bdon'?t\b/i,
|
|
181
|
+
/\bstop\b/i,
|
|
182
|
+
/\bnever\b/i,
|
|
183
|
+
/\balways\b/i,
|
|
184
|
+
/\bremember\b/i,
|
|
185
|
+
/\bprefer\b/i,
|
|
186
|
+
/\bi (like|want|hate|dislike)\b/i,
|
|
187
|
+
/\bfrom now on\b/i,
|
|
188
|
+
/\bkeep in mind\b/i,
|
|
189
|
+
/\bnote that\b/i,
|
|
190
|
+
/\bstop doing\b/i,
|
|
191
|
+
/\bstop using\b/i,
|
|
192
|
+
/\bthat'?s wrong\b/i,
|
|
193
|
+
/\bno,?\s+(actually|that'?s)\b/i,
|
|
194
|
+
/\bplease (don'?t|stop|always|never)\b/i
|
|
195
|
+
];
|
|
196
|
+
function looksLikeFeedback(prompt) {
|
|
197
|
+
return FEEDBACK_PATTERNS.some((p) => p.test(prompt));
|
|
198
|
+
}
|
|
199
|
+
function isToolFailure(data) {
|
|
200
|
+
if (data.hook_event_name === "PostToolUseFailure") return true;
|
|
201
|
+
if (typeof data.error_message === "string" && data.error_message.length > 0) return true;
|
|
202
|
+
const response = data.tool_result ?? data.tool_response;
|
|
203
|
+
if (typeof response === "object" && response !== null) {
|
|
204
|
+
if (response.is_error === true) return true;
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
async function handleSessionStart(opts) {
|
|
144
209
|
const projectScope = opts.scope ?? await resolveScope();
|
|
145
210
|
const db = DatabaseManager.open();
|
|
146
211
|
let text;
|
|
@@ -151,18 +216,44 @@ async function injectCommand(opts) {
|
|
|
151
216
|
}
|
|
152
217
|
if (!text) process.exit(0);
|
|
153
218
|
const harness = opts.harness;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
219
|
+
outputAdditionalContext(text, harness, "SessionStart");
|
|
220
|
+
}
|
|
221
|
+
async function handleUserPrompt(harness) {
|
|
222
|
+
const raw = await readStdin();
|
|
223
|
+
if (!raw) process.exit(0);
|
|
224
|
+
let data;
|
|
225
|
+
try {
|
|
226
|
+
data = JSON.parse(raw);
|
|
227
|
+
} catch {
|
|
228
|
+
process.exit(0);
|
|
229
|
+
}
|
|
230
|
+
if (!looksLikeFeedback(typeof data.prompt === "string" ? data.prompt : "")) process.exit(0);
|
|
231
|
+
outputAdditionalContext("User prompt may contain a correction, preference, or decision worth saving. After responding, evaluate: should this be saved as a memory? If yes, call save_memory with the appropriate type (correction/preference/decision/learning) and scope (global or project).", harness, "UserPromptSubmit");
|
|
232
|
+
}
|
|
233
|
+
async function handleToolFailure(harness) {
|
|
234
|
+
const raw = await readStdin();
|
|
235
|
+
if (!raw) process.exit(0);
|
|
236
|
+
let data;
|
|
237
|
+
try {
|
|
238
|
+
data = JSON.parse(raw);
|
|
239
|
+
} catch {
|
|
240
|
+
process.exit(0);
|
|
241
|
+
}
|
|
242
|
+
if (!isToolFailure(data)) process.exit(0);
|
|
243
|
+
outputAdditionalContext(`Tool "${typeof data.tool_name === "string" ? data.tool_name : "unknown"}" failed. If this reveals a non-obvious constraint, environment issue, or repeatable failure pattern, call save_memory with type "learning" to prevent repeating it.`, harness, "PostToolUseFailure");
|
|
244
|
+
}
|
|
245
|
+
async function injectCommand(opts) {
|
|
246
|
+
const harness = opts.harness;
|
|
247
|
+
const event = opts.event ?? "session-start";
|
|
248
|
+
if (event === "user-prompt") {
|
|
249
|
+
await handleUserPrompt(harness);
|
|
159
250
|
return;
|
|
160
251
|
}
|
|
161
|
-
if (
|
|
162
|
-
|
|
252
|
+
if (event === "tool-failure") {
|
|
253
|
+
await handleToolFailure(harness);
|
|
163
254
|
return;
|
|
164
255
|
}
|
|
165
|
-
|
|
256
|
+
await handleSessionStart(opts);
|
|
166
257
|
}
|
|
167
258
|
//#endregion
|
|
168
259
|
//#region src/commands/list.ts
|
|
@@ -225,303 +316,6 @@ async function statsCommand(formatter) {
|
|
|
225
316
|
}
|
|
226
317
|
}
|
|
227
318
|
//#endregion
|
|
228
|
-
//#region src/setup/injection-hook-writer.ts
|
|
229
|
-
const defaultPathResolver$1 = { home: () => {
|
|
230
|
-
const h = process.env.HOME ?? process.env.USERPROFILE;
|
|
231
|
-
if (!h) throw new Error("Cannot determine home directory");
|
|
232
|
-
return h;
|
|
233
|
-
} };
|
|
234
|
-
function readJson$1(path) {
|
|
235
|
-
try {
|
|
236
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
237
|
-
} catch {
|
|
238
|
-
return {};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
function writeJsonAtomic$1(path, data) {
|
|
242
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
243
|
-
const tmp = join(mkdtempSync(join(tmpdir(), "membank-hook-")), "cfg.json");
|
|
244
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
245
|
-
renameSync(tmp, path);
|
|
246
|
-
}
|
|
247
|
-
function getHooksArray(group) {
|
|
248
|
-
if (typeof group !== "object" || group === null) return [];
|
|
249
|
-
const h = group.hooks;
|
|
250
|
-
return Array.isArray(h) ? h : [];
|
|
251
|
-
}
|
|
252
|
-
function findMembankHookCommand(hooks, pattern) {
|
|
253
|
-
for (const h of hooks) {
|
|
254
|
-
if (typeof h !== "object" || h === null) continue;
|
|
255
|
-
if ("command" in h && typeof h.command === "string" && h.command.includes(pattern)) return h.command;
|
|
256
|
-
if ("bash" in h && typeof h.bash === "string" && h.bash.includes(pattern)) return h.bash;
|
|
257
|
-
}
|
|
258
|
-
return "";
|
|
259
|
-
}
|
|
260
|
-
function containsMembankInject(hooks) {
|
|
261
|
-
return findMembankHookCommand(hooks, "@membank/cli inject") !== "";
|
|
262
|
-
}
|
|
263
|
-
function extractInjectCommand(hooks) {
|
|
264
|
-
return findMembankHookCommand(hooks, "@membank/cli inject");
|
|
265
|
-
}
|
|
266
|
-
const writers$1 = {
|
|
267
|
-
"claude-code": {
|
|
268
|
-
replacement: "npx @membank/cli inject --harness claude-code",
|
|
269
|
-
write(resolver, overwrite = false) {
|
|
270
|
-
const cfgPath = join(resolver.home(), ".claude", "settings.json");
|
|
271
|
-
const cfg = readJson$1(cfgPath);
|
|
272
|
-
const hooks = cfg.hooks;
|
|
273
|
-
const existingGroups = Array.isArray(hooks?.SessionStart) ? hooks.SessionStart : [];
|
|
274
|
-
const innerHooks = existingGroups.flatMap(getHooksArray);
|
|
275
|
-
if (!overwrite && containsMembankInject(innerHooks)) return {
|
|
276
|
-
status: "already-configured",
|
|
277
|
-
existing: extractInjectCommand(innerHooks),
|
|
278
|
-
replacement: this.replacement
|
|
279
|
-
};
|
|
280
|
-
const filteredGroups = overwrite ? existingGroups.filter((g) => !containsMembankInject(getHooksArray(g))) : existingGroups;
|
|
281
|
-
writeJsonAtomic$1(cfgPath, {
|
|
282
|
-
...cfg,
|
|
283
|
-
hooks: {
|
|
284
|
-
...hooks ?? {},
|
|
285
|
-
SessionStart: [...filteredGroups, {
|
|
286
|
-
matcher: "",
|
|
287
|
-
hooks: [{
|
|
288
|
-
type: "command",
|
|
289
|
-
command: this.replacement
|
|
290
|
-
}]
|
|
291
|
-
}]
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
return { status: "written" };
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
"copilot-cli": {
|
|
298
|
-
replacement: "npx @membank/cli inject --harness copilot-cli",
|
|
299
|
-
write(resolver, overwrite = false) {
|
|
300
|
-
const cfgPath = join(resolver.home(), ".copilot", "settings.json");
|
|
301
|
-
const cfg = readJson$1(cfgPath);
|
|
302
|
-
const hooks = cfg.hooks;
|
|
303
|
-
const existingHooks = Array.isArray(hooks?.sessionStart) ? hooks.sessionStart : [];
|
|
304
|
-
if (!overwrite && containsMembankInject(existingHooks)) return {
|
|
305
|
-
status: "already-configured",
|
|
306
|
-
existing: extractInjectCommand(existingHooks),
|
|
307
|
-
replacement: this.replacement
|
|
308
|
-
};
|
|
309
|
-
const filteredHooks = overwrite ? existingHooks.filter((h) => !containsMembankInject([h])) : existingHooks;
|
|
310
|
-
writeJsonAtomic$1(cfgPath, {
|
|
311
|
-
version: cfg.version ?? 1,
|
|
312
|
-
...cfg,
|
|
313
|
-
hooks: {
|
|
314
|
-
...hooks ?? {},
|
|
315
|
-
sessionStart: [...filteredHooks, {
|
|
316
|
-
type: "command",
|
|
317
|
-
bash: this.replacement,
|
|
318
|
-
timeoutSec: 30
|
|
319
|
-
}]
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
return { status: "written" };
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
codex: {
|
|
326
|
-
replacement: "npx @membank/cli inject --harness codex",
|
|
327
|
-
write(resolver, overwrite = false) {
|
|
328
|
-
const cfgPath = join(resolver.home(), ".codex", "hooks.json");
|
|
329
|
-
const cfg = readJson$1(cfgPath);
|
|
330
|
-
const hooks = cfg.hooks;
|
|
331
|
-
const existingGroups = Array.isArray(hooks?.SessionStart) ? hooks.SessionStart : [];
|
|
332
|
-
const innerHooks = existingGroups.flatMap(getHooksArray);
|
|
333
|
-
if (!overwrite && containsMembankInject(innerHooks)) return {
|
|
334
|
-
status: "already-configured",
|
|
335
|
-
existing: extractInjectCommand(innerHooks),
|
|
336
|
-
replacement: this.replacement
|
|
337
|
-
};
|
|
338
|
-
const filteredGroups = overwrite ? existingGroups.filter((g) => !containsMembankInject(getHooksArray(g))) : existingGroups;
|
|
339
|
-
writeJsonAtomic$1(cfgPath, {
|
|
340
|
-
...cfg,
|
|
341
|
-
hooks: {
|
|
342
|
-
...hooks ?? {},
|
|
343
|
-
SessionStart: [...filteredGroups, {
|
|
344
|
-
matcher: "",
|
|
345
|
-
hooks: [{
|
|
346
|
-
type: "command",
|
|
347
|
-
command: this.replacement,
|
|
348
|
-
timeout: 30
|
|
349
|
-
}]
|
|
350
|
-
}]
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
return { status: "written" };
|
|
354
|
-
}
|
|
355
|
-
},
|
|
356
|
-
opencode: {
|
|
357
|
-
replacement: "npx @membank/cli inject",
|
|
358
|
-
write(resolver, overwrite = false) {
|
|
359
|
-
const pluginPath = join(resolver.home(), ".config", "opencode", "plugins", "membank.js");
|
|
360
|
-
if (!overwrite && existsSync(pluginPath)) {
|
|
361
|
-
const existing = readFileSync(pluginPath, "utf8");
|
|
362
|
-
if (existing.includes("@membank/cli inject")) return {
|
|
363
|
-
status: "already-configured",
|
|
364
|
-
existing: existing.trim(),
|
|
365
|
-
replacement: newOpencodePlugin()
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
mkdirSync(dirname(pluginPath), { recursive: true });
|
|
369
|
-
writeFileSync(pluginPath, `${newOpencodePlugin()}\n`, "utf8");
|
|
370
|
-
return { status: "written" };
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
function newOpencodePlugin(includeIdle = false) {
|
|
375
|
-
return [
|
|
376
|
-
"export default {",
|
|
377
|
-
" hooks: {",
|
|
378
|
-
" \"session.start\": async ({ $ }) => {",
|
|
379
|
-
" return await $`npx @membank/cli inject`.text();",
|
|
380
|
-
" },",
|
|
381
|
-
...includeIdle ? [
|
|
382
|
-
" \"session.idle\": async ({ $ }) => {",
|
|
383
|
-
" return await $`npx @membank/cli stop-hook --harness opencode`.text();",
|
|
384
|
-
" },"
|
|
385
|
-
] : [],
|
|
386
|
-
" },",
|
|
387
|
-
"};"
|
|
388
|
-
].join("\n");
|
|
389
|
-
}
|
|
390
|
-
const STOP_HOOK_PROMPT = "Review this session and consider whether any user preferences, corrections, decisions, or learnings are worth saving for future sessions. If so, use the save_memory MCP tool to store them. Be selective — only save what would genuinely help in a future conversation. Skip ephemeral task details.";
|
|
391
|
-
function containsMembankStopHookCmd(hooks) {
|
|
392
|
-
return findMembankHookCommand(hooks, "@membank/cli stop-hook") !== "";
|
|
393
|
-
}
|
|
394
|
-
function extractStopHookCmd(hooks) {
|
|
395
|
-
return findMembankHookCommand(hooks, "@membank/cli stop-hook");
|
|
396
|
-
}
|
|
397
|
-
function containsMembankStopPrompt(stopGroups) {
|
|
398
|
-
return stopGroups.some((g) => getHooksArray(g).some((h) => typeof h === "object" && h !== null && "type" in h && h.type === "prompt" && "prompt" in h && typeof h.prompt === "string" && h.prompt.includes("save_memory")));
|
|
399
|
-
}
|
|
400
|
-
function extractStopPrompt(stopGroups) {
|
|
401
|
-
for (const g of stopGroups) for (const h of getHooksArray(g)) if (typeof h === "object" && h !== null && "type" in h && h.type === "prompt" && "prompt" in h && typeof h.prompt === "string" && h.prompt.includes("save_memory")) return h.prompt;
|
|
402
|
-
return "";
|
|
403
|
-
}
|
|
404
|
-
const stopHookWriters = {
|
|
405
|
-
"claude-code": { write(resolver, overwrite = false) {
|
|
406
|
-
const cfgPath = join(resolver.home(), ".claude", "settings.json");
|
|
407
|
-
const cfg = readJson$1(cfgPath);
|
|
408
|
-
const hooks = cfg.hooks;
|
|
409
|
-
const existingStop = Array.isArray(hooks?.Stop) ? hooks.Stop : [];
|
|
410
|
-
if (!overwrite && containsMembankStopPrompt(existingStop)) return {
|
|
411
|
-
status: "already-configured",
|
|
412
|
-
existing: extractStopPrompt(existingStop),
|
|
413
|
-
replacement: STOP_HOOK_PROMPT
|
|
414
|
-
};
|
|
415
|
-
const filteredStop = overwrite ? existingStop.filter((g) => !containsMembankStopPrompt([g])) : existingStop;
|
|
416
|
-
writeJsonAtomic$1(cfgPath, {
|
|
417
|
-
...cfg,
|
|
418
|
-
hooks: {
|
|
419
|
-
...hooks ?? {},
|
|
420
|
-
Stop: [...filteredStop, { hooks: [{
|
|
421
|
-
type: "prompt",
|
|
422
|
-
prompt: STOP_HOOK_PROMPT
|
|
423
|
-
}] }]
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
return { status: "written" };
|
|
427
|
-
} },
|
|
428
|
-
"copilot-cli": { write(resolver, overwrite = false) {
|
|
429
|
-
const cfgPath = join(resolver.home(), ".copilot", "settings.json");
|
|
430
|
-
const cfg = readJson$1(cfgPath);
|
|
431
|
-
const replacement = "npx @membank/cli stop-hook --harness copilot-cli";
|
|
432
|
-
const hooks = cfg.hooks;
|
|
433
|
-
const existingStop = Array.isArray(hooks?.stop) ? hooks.stop : [];
|
|
434
|
-
if (!overwrite && containsMembankStopHookCmd(existingStop)) return {
|
|
435
|
-
status: "already-configured",
|
|
436
|
-
existing: extractStopHookCmd(existingStop),
|
|
437
|
-
replacement
|
|
438
|
-
};
|
|
439
|
-
const filteredStop = overwrite ? existingStop.filter((h) => !containsMembankStopHookCmd([h])) : existingStop;
|
|
440
|
-
writeJsonAtomic$1(cfgPath, {
|
|
441
|
-
version: cfg.version ?? 1,
|
|
442
|
-
...cfg,
|
|
443
|
-
hooks: {
|
|
444
|
-
...hooks ?? {},
|
|
445
|
-
stop: [...filteredStop, {
|
|
446
|
-
type: "command",
|
|
447
|
-
bash: replacement,
|
|
448
|
-
timeoutSec: 30
|
|
449
|
-
}]
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
return { status: "written" };
|
|
453
|
-
} },
|
|
454
|
-
codex: { write(resolver, overwrite = false) {
|
|
455
|
-
const cfgPath = join(resolver.home(), ".codex", "hooks.json");
|
|
456
|
-
const cfg = readJson$1(cfgPath);
|
|
457
|
-
const replacement = "npx @membank/cli stop-hook --harness codex";
|
|
458
|
-
const hooks = cfg.hooks;
|
|
459
|
-
const existingGroups = Array.isArray(hooks?.Stop) ? hooks.Stop : [];
|
|
460
|
-
const innerHooks = existingGroups.flatMap(getHooksArray);
|
|
461
|
-
if (!overwrite && containsMembankStopHookCmd(innerHooks)) return {
|
|
462
|
-
status: "already-configured",
|
|
463
|
-
existing: extractStopHookCmd(innerHooks),
|
|
464
|
-
replacement
|
|
465
|
-
};
|
|
466
|
-
const filteredGroups = overwrite ? existingGroups.filter((g) => !containsMembankStopHookCmd(getHooksArray(g))) : existingGroups;
|
|
467
|
-
writeJsonAtomic$1(cfgPath, {
|
|
468
|
-
...cfg,
|
|
469
|
-
hooks: {
|
|
470
|
-
...hooks ?? {},
|
|
471
|
-
Stop: [...filteredGroups, {
|
|
472
|
-
matcher: "",
|
|
473
|
-
hooks: [{
|
|
474
|
-
type: "command",
|
|
475
|
-
command: replacement,
|
|
476
|
-
timeout: 30
|
|
477
|
-
}]
|
|
478
|
-
}]
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
return { status: "written" };
|
|
482
|
-
} },
|
|
483
|
-
opencode: { write(resolver, overwrite = false) {
|
|
484
|
-
const pluginPath = join(resolver.home(), ".config", "opencode", "plugins", "membank.js");
|
|
485
|
-
if (!overwrite && existsSync(pluginPath)) {
|
|
486
|
-
const existing = readFileSync(pluginPath, "utf8");
|
|
487
|
-
if (existing.includes("@membank/cli stop-hook")) return {
|
|
488
|
-
status: "already-configured",
|
|
489
|
-
existing: existing.trim(),
|
|
490
|
-
replacement: newOpencodePlugin(true)
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
mkdirSync(dirname(pluginPath), { recursive: true });
|
|
494
|
-
writeFileSync(pluginPath, `${newOpencodePlugin(true)}\n`, "utf8");
|
|
495
|
-
return { status: "written" };
|
|
496
|
-
} }
|
|
497
|
-
};
|
|
498
|
-
var InjectionHookWriter = class {
|
|
499
|
-
#resolver;
|
|
500
|
-
constructor(resolver = defaultPathResolver$1) {
|
|
501
|
-
this.#resolver = resolver;
|
|
502
|
-
}
|
|
503
|
-
write(harness, overwrite) {
|
|
504
|
-
const writer = writers$1[harness];
|
|
505
|
-
if (!writer) return { status: "not-supported" };
|
|
506
|
-
return writer.write(this.#resolver, overwrite);
|
|
507
|
-
}
|
|
508
|
-
writeStopHook(harness, overwrite) {
|
|
509
|
-
const writer = stopHookWriters[harness];
|
|
510
|
-
if (!writer) return { status: "not-supported" };
|
|
511
|
-
return writer.write(this.#resolver, overwrite);
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
//#endregion
|
|
515
|
-
//#region src/commands/stop-hook.ts
|
|
516
|
-
function stopHookCommand(opts) {
|
|
517
|
-
const { harness } = opts;
|
|
518
|
-
if (harness === "copilot-cli" || harness === "codex") {
|
|
519
|
-
process.stdout.write(JSON.stringify({ systemMessage: STOP_HOOK_PROMPT }));
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
process.stdout.write(`${STOP_HOOK_PROMPT}\n`);
|
|
523
|
-
}
|
|
524
|
-
//#endregion
|
|
525
319
|
//#region src/commands/unpin.ts
|
|
526
320
|
function unpinCommand(id, formatter, db) {
|
|
527
321
|
const ownDb = db === void 0;
|
|
@@ -668,7 +462,7 @@ async function execFileNoThrow(cmd, args) {
|
|
|
668
462
|
}
|
|
669
463
|
//#endregion
|
|
670
464
|
//#region src/setup/harness-config-writer.ts
|
|
671
|
-
const defaultPathResolver = {
|
|
465
|
+
const defaultPathResolver$1 = {
|
|
672
466
|
home: () => {
|
|
673
467
|
const h = process.env.HOME ?? process.env.USERPROFILE;
|
|
674
468
|
if (!h) throw new Error("Cannot determine home directory");
|
|
@@ -676,14 +470,14 @@ const defaultPathResolver = {
|
|
|
676
470
|
},
|
|
677
471
|
cwd: () => process.cwd()
|
|
678
472
|
};
|
|
679
|
-
function readJson(path) {
|
|
473
|
+
function readJson$1(path) {
|
|
680
474
|
try {
|
|
681
475
|
return JSON.parse(readFileSync(path, "utf8"));
|
|
682
476
|
} catch {
|
|
683
477
|
return {};
|
|
684
478
|
}
|
|
685
479
|
}
|
|
686
|
-
function writeJsonAtomic(path, data) {
|
|
480
|
+
function writeJsonAtomic$1(path, data) {
|
|
687
481
|
mkdirSync(dirname(path), { recursive: true });
|
|
688
482
|
const tmp = join(mkdtempSync(join(tmpdir(), "membank-")), "cfg.json");
|
|
689
483
|
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
@@ -700,9 +494,9 @@ const MEMBANK_NPX_ARGS = [
|
|
|
700
494
|
"@membank/cli@latest",
|
|
701
495
|
"--mcp"
|
|
702
496
|
];
|
|
703
|
-
const writers = {
|
|
497
|
+
const writers$1 = {
|
|
704
498
|
"claude-code": { async write(resolver, run, { overwrite = false } = {}) {
|
|
705
|
-
const configured = hasKey(readJson(join(resolver.home(), ".claude.json")).mcpServers, "membank");
|
|
499
|
+
const configured = hasKey(readJson$1(join(resolver.home(), ".claude.json")).mcpServers, "membank");
|
|
706
500
|
if (configured && !overwrite) return { status: "already-configured" };
|
|
707
501
|
if (configured) {
|
|
708
502
|
const remove = await run("claude", [
|
|
@@ -730,9 +524,9 @@ const writers = {
|
|
|
730
524
|
} },
|
|
731
525
|
copilot: { async write(resolver, _run, { overwrite = false } = {}) {
|
|
732
526
|
const cfgPath = join(resolver.home(), ".copilot", "mcp-config.json");
|
|
733
|
-
const cfg = readJson(cfgPath);
|
|
527
|
+
const cfg = readJson$1(cfgPath);
|
|
734
528
|
if (hasKey(cfg.mcpServers, "membank") && !overwrite) return { status: "already-configured" };
|
|
735
|
-
writeJsonAtomic(cfgPath, {
|
|
529
|
+
writeJsonAtomic$1(cfgPath, {
|
|
736
530
|
...cfg,
|
|
737
531
|
mcpServers: {
|
|
738
532
|
...cfg.mcpServers,
|
|
@@ -771,9 +565,9 @@ const writers = {
|
|
|
771
565
|
} },
|
|
772
566
|
opencode: { async write(resolver, _run, { overwrite = false } = {}) {
|
|
773
567
|
const cfgPath = join(resolver.home(), ".config", "opencode", "opencode.json");
|
|
774
|
-
const cfg = readJson(cfgPath);
|
|
568
|
+
const cfg = readJson$1(cfgPath);
|
|
775
569
|
if (hasKey(cfg.mcp, "membank") && !overwrite) return { status: "already-configured" };
|
|
776
|
-
writeJsonAtomic(cfgPath, {
|
|
570
|
+
writeJsonAtomic$1(cfgPath, {
|
|
777
571
|
...cfg,
|
|
778
572
|
mcp: {
|
|
779
573
|
...cfg.mcp,
|
|
@@ -790,21 +584,254 @@ const writers = {
|
|
|
790
584
|
return { status: "written" };
|
|
791
585
|
} }
|
|
792
586
|
};
|
|
793
|
-
const SUPPORTED_HARNESSES = Object.keys(writers);
|
|
587
|
+
const SUPPORTED_HARNESSES = Object.keys(writers$1);
|
|
794
588
|
var HarnessConfigWriter = class {
|
|
795
589
|
#resolver;
|
|
796
590
|
#run;
|
|
797
|
-
constructor(resolver = defaultPathResolver, run = execFileNoThrow) {
|
|
591
|
+
constructor(resolver = defaultPathResolver$1, run = execFileNoThrow) {
|
|
798
592
|
this.#resolver = resolver;
|
|
799
593
|
this.#run = run;
|
|
800
594
|
}
|
|
801
595
|
async write(harness, { overwrite = false } = {}) {
|
|
802
|
-
const writer = writers[harness];
|
|
596
|
+
const writer = writers$1[harness];
|
|
803
597
|
if (!writer) throw new Error(`Unknown harness: ${harness}`);
|
|
804
598
|
return writer.write(this.#resolver, this.#run, { overwrite });
|
|
805
599
|
}
|
|
806
600
|
};
|
|
807
601
|
//#endregion
|
|
602
|
+
//#region src/setup/injection-hook-writer.ts
|
|
603
|
+
const defaultPathResolver = { home: () => {
|
|
604
|
+
const h = process.env.HOME ?? process.env.USERPROFILE;
|
|
605
|
+
if (!h) throw new Error("Cannot determine home directory");
|
|
606
|
+
return h;
|
|
607
|
+
} };
|
|
608
|
+
function readJson(path) {
|
|
609
|
+
try {
|
|
610
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
611
|
+
} catch {
|
|
612
|
+
return {};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function writeJsonAtomic(path, data) {
|
|
616
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
617
|
+
const tmp = join(mkdtempSync(join(tmpdir(), "membank-hook-")), "cfg.json");
|
|
618
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
619
|
+
renameSync(tmp, path);
|
|
620
|
+
}
|
|
621
|
+
function getHooksArray(group) {
|
|
622
|
+
if (typeof group !== "object" || group === null) return [];
|
|
623
|
+
const h = group.hooks;
|
|
624
|
+
return Array.isArray(h) ? h : [];
|
|
625
|
+
}
|
|
626
|
+
function findMembankHookCommand(hooks, pattern) {
|
|
627
|
+
for (const h of hooks) {
|
|
628
|
+
if (typeof h !== "object" || h === null) continue;
|
|
629
|
+
if ("command" in h && typeof h.command === "string" && h.command.includes(pattern)) return h.command;
|
|
630
|
+
if ("bash" in h && typeof h.bash === "string" && h.bash.includes(pattern)) return h.bash;
|
|
631
|
+
}
|
|
632
|
+
return "";
|
|
633
|
+
}
|
|
634
|
+
function containsMembankInject(hooks) {
|
|
635
|
+
return findMembankHookCommand(hooks, "@membank/cli inject") !== "";
|
|
636
|
+
}
|
|
637
|
+
function extractInjectCommand(hooks) {
|
|
638
|
+
return findMembankHookCommand(hooks, "@membank/cli inject");
|
|
639
|
+
}
|
|
640
|
+
function filterOutMembank(groups) {
|
|
641
|
+
return groups.filter((g) => !containsMembankInject(getHooksArray(g)));
|
|
642
|
+
}
|
|
643
|
+
function filterOutMembankFlat(hooks) {
|
|
644
|
+
return hooks.filter((h) => !containsMembankInject([h]));
|
|
645
|
+
}
|
|
646
|
+
const writers = {
|
|
647
|
+
"claude-code": {
|
|
648
|
+
replacement: "npx @membank/cli inject --harness claude-code",
|
|
649
|
+
write(resolver, overwrite = false) {
|
|
650
|
+
const cfgPath = join(resolver.home(), ".claude", "settings.json");
|
|
651
|
+
const cfg = readJson(cfgPath);
|
|
652
|
+
const hooks = cfg.hooks;
|
|
653
|
+
const existingSessionStart = Array.isArray(hooks?.SessionStart) ? hooks.SessionStart : [];
|
|
654
|
+
const innerHooks = existingSessionStart.flatMap(getHooksArray);
|
|
655
|
+
if (!overwrite && containsMembankInject(innerHooks)) return {
|
|
656
|
+
status: "already-configured",
|
|
657
|
+
existing: extractInjectCommand(innerHooks),
|
|
658
|
+
replacement: this.replacement
|
|
659
|
+
};
|
|
660
|
+
const filteredSessionStart = overwrite ? filterOutMembank(existingSessionStart) : existingSessionStart;
|
|
661
|
+
const existingUserPrompt = Array.isArray(hooks?.UserPromptSubmit) ? hooks.UserPromptSubmit : [];
|
|
662
|
+
const existingToolFailure = Array.isArray(hooks?.PostToolUseFailure) ? hooks.PostToolUseFailure : [];
|
|
663
|
+
writeJsonAtomic(cfgPath, {
|
|
664
|
+
...cfg,
|
|
665
|
+
hooks: {
|
|
666
|
+
...hooks ?? {},
|
|
667
|
+
SessionStart: [...filteredSessionStart, {
|
|
668
|
+
matcher: "",
|
|
669
|
+
hooks: [{
|
|
670
|
+
type: "command",
|
|
671
|
+
command: "npx @membank/cli inject --harness claude-code"
|
|
672
|
+
}]
|
|
673
|
+
}],
|
|
674
|
+
UserPromptSubmit: [...filterOutMembank(existingUserPrompt), {
|
|
675
|
+
matcher: "",
|
|
676
|
+
hooks: [{
|
|
677
|
+
type: "command",
|
|
678
|
+
command: "npx @membank/cli inject --event user-prompt --harness claude-code"
|
|
679
|
+
}]
|
|
680
|
+
}],
|
|
681
|
+
PostToolUseFailure: [...filterOutMembank(existingToolFailure), {
|
|
682
|
+
matcher: "",
|
|
683
|
+
hooks: [{
|
|
684
|
+
type: "command",
|
|
685
|
+
command: "npx @membank/cli inject --event tool-failure --harness claude-code"
|
|
686
|
+
}]
|
|
687
|
+
}]
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
return { status: "written" };
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
"copilot-cli": {
|
|
694
|
+
replacement: "npx @membank/cli inject --harness copilot-cli",
|
|
695
|
+
write(resolver, overwrite = false) {
|
|
696
|
+
const cfgPath = join(resolver.home(), ".copilot", "settings.json");
|
|
697
|
+
const cfg = readJson(cfgPath);
|
|
698
|
+
const hooks = cfg.hooks;
|
|
699
|
+
const existingSessionStart = Array.isArray(hooks?.sessionStart) ? hooks.sessionStart : [];
|
|
700
|
+
if (!overwrite && containsMembankInject(existingSessionStart)) return {
|
|
701
|
+
status: "already-configured",
|
|
702
|
+
existing: extractInjectCommand(existingSessionStart),
|
|
703
|
+
replacement: this.replacement
|
|
704
|
+
};
|
|
705
|
+
const filteredSessionStart = overwrite ? filterOutMembankFlat(existingSessionStart) : existingSessionStart;
|
|
706
|
+
const existingUserPrompt = Array.isArray(hooks?.userPromptSubmitted) ? hooks.userPromptSubmitted : [];
|
|
707
|
+
const existingToolFailure = Array.isArray(hooks?.postToolUseFailure) ? hooks.postToolUseFailure : [];
|
|
708
|
+
writeJsonAtomic(cfgPath, {
|
|
709
|
+
version: cfg.version ?? 1,
|
|
710
|
+
...cfg,
|
|
711
|
+
hooks: {
|
|
712
|
+
...hooks ?? {},
|
|
713
|
+
sessionStart: [...filteredSessionStart, {
|
|
714
|
+
type: "command",
|
|
715
|
+
bash: "npx @membank/cli inject --harness copilot-cli",
|
|
716
|
+
timeoutSec: 30
|
|
717
|
+
}],
|
|
718
|
+
userPromptSubmitted: [...filterOutMembankFlat(existingUserPrompt), {
|
|
719
|
+
type: "command",
|
|
720
|
+
bash: "npx @membank/cli inject --event user-prompt --harness copilot-cli",
|
|
721
|
+
timeoutSec: 30
|
|
722
|
+
}],
|
|
723
|
+
postToolUseFailure: [...filterOutMembankFlat(existingToolFailure), {
|
|
724
|
+
type: "command",
|
|
725
|
+
bash: "npx @membank/cli inject --event tool-failure --harness copilot-cli",
|
|
726
|
+
timeoutSec: 30
|
|
727
|
+
}]
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
return { status: "written" };
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
codex: {
|
|
734
|
+
replacement: "npx @membank/cli inject --harness codex",
|
|
735
|
+
write(resolver, overwrite = false) {
|
|
736
|
+
const cfgPath = join(resolver.home(), ".codex", "hooks.json");
|
|
737
|
+
const cfg = readJson(cfgPath);
|
|
738
|
+
const hooks = cfg.hooks;
|
|
739
|
+
const existingSessionStart = Array.isArray(hooks?.SessionStart) ? hooks.SessionStart : [];
|
|
740
|
+
const innerHooks = existingSessionStart.flatMap(getHooksArray);
|
|
741
|
+
if (!overwrite && containsMembankInject(innerHooks)) return {
|
|
742
|
+
status: "already-configured",
|
|
743
|
+
existing: extractInjectCommand(innerHooks),
|
|
744
|
+
replacement: this.replacement
|
|
745
|
+
};
|
|
746
|
+
const filteredSessionStart = overwrite ? filterOutMembank(existingSessionStart) : existingSessionStart;
|
|
747
|
+
const existingUserPrompt = Array.isArray(hooks?.UserPromptSubmit) ? hooks.UserPromptSubmit : [];
|
|
748
|
+
const existingToolFailure = Array.isArray(hooks?.PostToolUse) ? hooks.PostToolUse : [];
|
|
749
|
+
writeJsonAtomic(cfgPath, {
|
|
750
|
+
...cfg,
|
|
751
|
+
hooks: {
|
|
752
|
+
...hooks ?? {},
|
|
753
|
+
SessionStart: [...filteredSessionStart, {
|
|
754
|
+
matcher: "",
|
|
755
|
+
hooks: [{
|
|
756
|
+
type: "command",
|
|
757
|
+
command: "npx @membank/cli inject --harness codex",
|
|
758
|
+
timeout: 30
|
|
759
|
+
}]
|
|
760
|
+
}],
|
|
761
|
+
UserPromptSubmit: [...filterOutMembank(existingUserPrompt), {
|
|
762
|
+
matcher: "",
|
|
763
|
+
hooks: [{
|
|
764
|
+
type: "command",
|
|
765
|
+
command: "npx @membank/cli inject --event user-prompt --harness codex",
|
|
766
|
+
timeout: 30
|
|
767
|
+
}]
|
|
768
|
+
}],
|
|
769
|
+
PostToolUse: [...filterOutMembank(existingToolFailure), {
|
|
770
|
+
matcher: "",
|
|
771
|
+
hooks: [{
|
|
772
|
+
type: "command",
|
|
773
|
+
command: "npx @membank/cli inject --event tool-failure --harness codex",
|
|
774
|
+
timeout: 30
|
|
775
|
+
}]
|
|
776
|
+
}]
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
return { status: "written" };
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
opencode: {
|
|
783
|
+
replacement: "npx @membank/cli inject",
|
|
784
|
+
write(resolver, overwrite = false) {
|
|
785
|
+
const pluginPath = join(resolver.home(), ".config", "opencode", "plugins", "membank.js");
|
|
786
|
+
if (!overwrite && existsSync(pluginPath)) {
|
|
787
|
+
const existing = readFileSync(pluginPath, "utf8");
|
|
788
|
+
if (existing.includes("@membank/cli inject")) return {
|
|
789
|
+
status: "already-configured",
|
|
790
|
+
existing: existing.trim(),
|
|
791
|
+
replacement: newOpencodePlugin()
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
mkdirSync(dirname(pluginPath), { recursive: true });
|
|
795
|
+
writeFileSync(pluginPath, `${newOpencodePlugin()}\n`, "utf8");
|
|
796
|
+
return { status: "written" };
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
function newOpencodePlugin() {
|
|
801
|
+
return [
|
|
802
|
+
"export default {",
|
|
803
|
+
" hooks: {",
|
|
804
|
+
" \"session.start\": async ({ $ }) => {",
|
|
805
|
+
" return await $`npx @membank/cli inject`.text();",
|
|
806
|
+
" },",
|
|
807
|
+
" \"chat.message\": async ({ $, message }) => {",
|
|
808
|
+
" const input = JSON.stringify({ prompt: message?.content ?? \"\" });",
|
|
809
|
+
" return await $`npx @membank/cli inject --event user-prompt`.stdin(input).text();",
|
|
810
|
+
" },",
|
|
811
|
+
" \"tool.execute.after\": async ({ $, result }) => {",
|
|
812
|
+
" if (!result?.exitCode && !result?.error) return;",
|
|
813
|
+
" const payload = JSON.stringify({",
|
|
814
|
+
" tool_name: result.tool ?? \"unknown\",",
|
|
815
|
+
" error_message: result.error ?? (\"exit code \" + result.exitCode),",
|
|
816
|
+
" });",
|
|
817
|
+
" return await $`npx @membank/cli inject --event tool-failure`.stdin(payload).text();",
|
|
818
|
+
" },",
|
|
819
|
+
" },",
|
|
820
|
+
"};"
|
|
821
|
+
].join("\n");
|
|
822
|
+
}
|
|
823
|
+
var InjectionHookWriter = class {
|
|
824
|
+
#resolver;
|
|
825
|
+
constructor(resolver = defaultPathResolver) {
|
|
826
|
+
this.#resolver = resolver;
|
|
827
|
+
}
|
|
828
|
+
write(harness, overwrite) {
|
|
829
|
+
const writer = writers[harness];
|
|
830
|
+
if (!writer) return { status: "not-supported" };
|
|
831
|
+
return writer.write(this.#resolver, overwrite);
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
//#endregion
|
|
808
835
|
//#region src/setup/model-downloader.ts
|
|
809
836
|
const MODEL_NAME = "Xenova/bge-small-en-v1.5";
|
|
810
837
|
var ModelDownloadError = class extends Error {
|
|
@@ -954,7 +981,6 @@ var SetupOrchestrator = class {
|
|
|
954
981
|
detectedHarnesses: [],
|
|
955
982
|
configuredHarnesses: [],
|
|
956
983
|
injectionHooksConfigured: [],
|
|
957
|
-
stopHooksConfigured: [],
|
|
958
984
|
modelDownloaded: false
|
|
959
985
|
}));
|
|
960
986
|
return [];
|
|
@@ -968,10 +994,7 @@ var SetupOrchestrator = class {
|
|
|
968
994
|
out("Planned changes (dry-run — no files written):");
|
|
969
995
|
for (const h of detected) {
|
|
970
996
|
out(` ⚠ ${h.name}: would write MCP config`);
|
|
971
|
-
if (this.#hookWriter) {
|
|
972
|
-
out(` ⚠ ${h.name}: would write injection hook config`);
|
|
973
|
-
out(` ⚠ ${h.name}: would write stop hook config`);
|
|
974
|
-
}
|
|
997
|
+
if (this.#hookWriter) out(` ⚠ ${h.name}: would write injection hook config`);
|
|
975
998
|
}
|
|
976
999
|
out("");
|
|
977
1000
|
out(" ⚠ Model download: skipped (dry-run)");
|
|
@@ -1038,11 +1061,9 @@ var SetupOrchestrator = class {
|
|
|
1038
1061
|
}
|
|
1039
1062
|
out("");
|
|
1040
1063
|
const injectionHooksConfigured = [];
|
|
1041
|
-
const stopHooksConfigured = [];
|
|
1042
1064
|
if (this.#hookWriter) {
|
|
1043
1065
|
const w = this.#hookWriter;
|
|
1044
1066
|
injectionHooksConfigured.push(...await this.#runHookLoop(detected, "injection hook", (h, ow) => w.write(h, ow), yes, out));
|
|
1045
|
-
stopHooksConfigured.push(...await this.#runHookLoop(detected, "stop hook", (h, ow) => w.writeStopHook(h, ow), yes, out));
|
|
1046
1067
|
out("");
|
|
1047
1068
|
}
|
|
1048
1069
|
let modelDownloaded = false;
|
|
@@ -1056,7 +1077,6 @@ var SetupOrchestrator = class {
|
|
|
1056
1077
|
detectedHarnesses: detected.map((h) => h.name),
|
|
1057
1078
|
configuredHarnesses: results.filter((r) => r.status === "written").map((r) => r.harness),
|
|
1058
1079
|
injectionHooksConfigured,
|
|
1059
|
-
stopHooksConfigured,
|
|
1060
1080
|
modelDownloaded
|
|
1061
1081
|
};
|
|
1062
1082
|
this.#out(JSON.stringify(output));
|
|
@@ -1221,7 +1241,7 @@ program.command("import <file>").description("import memories from a JSON export
|
|
|
1221
1241
|
db.close();
|
|
1222
1242
|
}
|
|
1223
1243
|
});
|
|
1224
|
-
program.command("inject").description("output session context for harness injection (used by setup hooks)").option("--harness <name>", "format output for a specific harness (claude-code|copilot-cli|codex|opencode)").option("--scope <scope>", "project scope override (default: auto-detect from git remote)").action(async (cmdOptions) => {
|
|
1244
|
+
program.command("inject").description("output session context for harness injection (used by setup hooks)").option("--harness <name>", "format output for a specific harness (claude-code|copilot-cli|codex|opencode)").option("--scope <scope>", "project scope override (default: auto-detect from git remote)").option("--event <event>", "hook event type (session-start|user-prompt|tool-failure)", "session-start").action(async (cmdOptions) => {
|
|
1225
1245
|
try {
|
|
1226
1246
|
await injectCommand(cmdOptions);
|
|
1227
1247
|
} catch (err) {
|
|
@@ -1229,9 +1249,6 @@ program.command("inject").description("output session context for harness inject
|
|
|
1229
1249
|
process.exit(2);
|
|
1230
1250
|
}
|
|
1231
1251
|
});
|
|
1232
|
-
program.command("stop-hook").description("output session-end prompt for harness stop hooks (called by hooks, not users)").option("--harness <name>", "harness name (copilot-cli, codex, opencode)").action((cmdOptions) => {
|
|
1233
|
-
stopHookCommand(cmdOptions);
|
|
1234
|
-
});
|
|
1235
1252
|
program.command("setup").description("detect installed harnesses and write MCP config for each").option("--yes", "skip all confirmation prompts").option("--dry-run", "print planned changes without writing any file").option("--harness <name>", "target only the named harness (skip detection)").action(async (cmdOptions) => {
|
|
1236
1253
|
const globalOpts = program.opts();
|
|
1237
1254
|
const autoYes = cmdOptions.yes === true || globalOpts.yes === true;
|
|
@@ -1263,6 +1280,14 @@ program.command("setup").description("detect installed harnesses and write MCP c
|
|
|
1263
1280
|
process.exit(2);
|
|
1264
1281
|
}
|
|
1265
1282
|
});
|
|
1283
|
+
program.command("dashboard").description("open the memory management dashboard in the browser").option("--port <port>", "port to listen on (default: 3847, fallback to random)").action(async (cmdOptions) => {
|
|
1284
|
+
try {
|
|
1285
|
+
await dashboardCommand(cmdOptions);
|
|
1286
|
+
} catch (err) {
|
|
1287
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
1288
|
+
process.exit(2);
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1266
1291
|
program.on("command:*", () => {
|
|
1267
1292
|
program.outputHelp();
|
|
1268
1293
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
"@huggingface/transformers": "^4.2.0",
|
|
18
18
|
"commander": "^14.0.3",
|
|
19
19
|
"ora": "^9.4.0",
|
|
20
|
-
"@membank/
|
|
21
|
-
"@membank/mcp": "0.
|
|
20
|
+
"@membank/dashboard": "0.1.0",
|
|
21
|
+
"@membank/mcp": "0.3.0",
|
|
22
|
+
"@membank/core": "0.3.0"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"@types/node": "^25.6.0",
|