@membank/cli 0.3.0 → 0.4.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 +13 -5
- package/dist/index.mjs +143 -233
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Options:
|
|
|
33
33
|
--json Machine-readable output
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Supported harnesses: `claude-code`, `copilot`, `codex`, `opencode`
|
|
36
|
+
Supported harnesses: `claude-code`, `copilot`, `codex`, `opencode` (see `membank setup` for harness-specific setup instructions)
|
|
37
37
|
|
|
38
38
|
## Commands
|
|
39
39
|
|
|
@@ -125,6 +125,16 @@ Output session context formatted for a harness. Called automatically by session
|
|
|
125
125
|
membank inject --harness claude-code --scope <project-scope>
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
### `membank dashboard`
|
|
129
|
+
|
|
130
|
+
Start the web dashboard for browsing and managing memories.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
membank dashboard
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Opens http://localhost:3847 by default. Features: full-text search, filtering by type/scope/pin status, edit memory metadata, view dedup reviews, and storage statistics.
|
|
137
|
+
|
|
128
138
|
## Global flags
|
|
129
139
|
|
|
130
140
|
```
|
|
@@ -143,11 +153,9 @@ Starts the stdio MCP server. This is what harnesses connect to — `setup` write
|
|
|
143
153
|
|
|
144
154
|
## Session hooks
|
|
145
155
|
|
|
146
|
-
`setup` installs
|
|
147
|
-
|
|
148
|
-
**Session start** — calls `membank inject` to prepend pinned memories into the LLM context at the beginning of every session.
|
|
156
|
+
`setup` installs a hook for Claude Code that injects memories at the start of each session:
|
|
149
157
|
|
|
150
|
-
**
|
|
158
|
+
**SessionStart** — calls `membank inject` to prepend pinned memories into the LLM context at the beginning of every session.
|
|
151
159
|
|
|
152
160
|
## Requirements
|
|
153
161
|
|
package/dist/index.mjs
CHANGED
|
@@ -134,7 +134,7 @@ async function importCommand(filePath, db, formatter, prompt) {
|
|
|
134
134
|
}
|
|
135
135
|
//#endregion
|
|
136
136
|
//#region src/commands/inject.ts
|
|
137
|
-
const MEMORY_GUIDANCE = "[Memory Guidance]:
|
|
137
|
+
const MEMORY_GUIDANCE = "[Memory Guidance]: Persistent memory is available via query_memory, save_memory, update_memory, delete_memory. Skipping save_memory when the user gives a correction or preference means they have to repeat themselves next session — that is the failure mode to avoid. Skipping query_memory on topics that touch prior decisions means contradicting yourself. Default to saving (type: correction|preference|decision|learning|fact) when in doubt; rely on dedup to handle redundancy. Pin anything that should appear at every session start.";
|
|
138
138
|
function formatContext(ctx) {
|
|
139
139
|
const lines = [];
|
|
140
140
|
const statParts = Object.entries(ctx.stats).filter(([, count]) => count > 0).map(([type, count]) => `${count} ${type}${count !== 1 ? "s" : ""}`);
|
|
@@ -160,51 +160,6 @@ function outputAdditionalContext(text, harness, eventName) {
|
|
|
160
160
|
}
|
|
161
161
|
process.stdout.write(`${text}\n`);
|
|
162
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
163
|
async function handleSessionStart(opts) {
|
|
209
164
|
const projectScope = opts.scope ?? await resolveScope();
|
|
210
165
|
const db = DatabaseManager.open();
|
|
@@ -218,41 +173,8 @@ async function handleSessionStart(opts) {
|
|
|
218
173
|
const harness = opts.harness;
|
|
219
174
|
outputAdditionalContext(text, harness, "SessionStart");
|
|
220
175
|
}
|
|
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
176
|
async function injectCommand(opts) {
|
|
246
|
-
|
|
247
|
-
const event = opts.event ?? "session-start";
|
|
248
|
-
if (event === "user-prompt") {
|
|
249
|
-
await handleUserPrompt(harness);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (event === "tool-failure") {
|
|
253
|
-
await handleToolFailure(harness);
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
177
|
+
if (opts.event !== void 0 && opts.event !== "session-start") process.exit(0);
|
|
256
178
|
await handleSessionStart(opts);
|
|
257
179
|
}
|
|
258
180
|
//#endregion
|
|
@@ -491,6 +413,7 @@ function assertCliFound(result, cli) {
|
|
|
491
413
|
}
|
|
492
414
|
const MEMBANK_NPX_ARGS = [
|
|
493
415
|
"npx",
|
|
416
|
+
"-y",
|
|
494
417
|
"@membank/cli@latest",
|
|
495
418
|
"--mcp"
|
|
496
419
|
];
|
|
@@ -532,7 +455,11 @@ const writers$1 = {
|
|
|
532
455
|
...cfg.mcpServers,
|
|
533
456
|
membank: {
|
|
534
457
|
command: "npx",
|
|
535
|
-
args: [
|
|
458
|
+
args: [
|
|
459
|
+
"-y",
|
|
460
|
+
"@membank/cli@latest",
|
|
461
|
+
"--mcp"
|
|
462
|
+
]
|
|
536
463
|
}
|
|
537
464
|
}
|
|
538
465
|
});
|
|
@@ -575,6 +502,7 @@ const writers$1 = {
|
|
|
575
502
|
type: "local",
|
|
576
503
|
command: [
|
|
577
504
|
"npx",
|
|
505
|
+
"-y",
|
|
578
506
|
"@membank/cli@latest",
|
|
579
507
|
"--mcp"
|
|
580
508
|
]
|
|
@@ -632,10 +560,10 @@ function findMembankHookCommand(hooks, pattern) {
|
|
|
632
560
|
return "";
|
|
633
561
|
}
|
|
634
562
|
function containsMembankInject(hooks) {
|
|
635
|
-
return findMembankHookCommand(hooks, "@membank/cli
|
|
563
|
+
return findMembankHookCommand(hooks, "@membank/cli") !== "";
|
|
636
564
|
}
|
|
637
565
|
function extractInjectCommand(hooks) {
|
|
638
|
-
return findMembankHookCommand(hooks, "@membank/cli
|
|
566
|
+
return findMembankHookCommand(hooks, "@membank/cli");
|
|
639
567
|
}
|
|
640
568
|
function filterOutMembank(groups) {
|
|
641
569
|
return groups.filter((g) => !containsMembankInject(getHooksArray(g)));
|
|
@@ -643,154 +571,139 @@ function filterOutMembank(groups) {
|
|
|
643
571
|
function filterOutMembankFlat(hooks) {
|
|
644
572
|
return hooks.filter((h) => !containsMembankInject([h]));
|
|
645
573
|
}
|
|
574
|
+
function pruneNestedEvent(hooks, eventKey) {
|
|
575
|
+
const existing = hooks[eventKey];
|
|
576
|
+
if (!Array.isArray(existing)) return;
|
|
577
|
+
const cleaned = filterOutMembank(existing);
|
|
578
|
+
if (cleaned.length === 0) delete hooks[eventKey];
|
|
579
|
+
else hooks[eventKey] = cleaned;
|
|
580
|
+
}
|
|
581
|
+
function pruneFlatEvent(hooks, eventKey) {
|
|
582
|
+
const existing = hooks[eventKey];
|
|
583
|
+
if (!Array.isArray(existing)) return;
|
|
584
|
+
const cleaned = filterOutMembankFlat(existing);
|
|
585
|
+
if (cleaned.length === 0) delete hooks[eventKey];
|
|
586
|
+
else hooks[eventKey] = cleaned;
|
|
587
|
+
}
|
|
646
588
|
const writers = {
|
|
647
589
|
"claude-code": {
|
|
648
|
-
|
|
649
|
-
|
|
590
|
+
inspect(resolver) {
|
|
591
|
+
const hooks = readJson(join(resolver.home(), ".claude", "settings.json")).hooks ?? {};
|
|
592
|
+
return {
|
|
593
|
+
status: "ready",
|
|
594
|
+
hooks: [{
|
|
595
|
+
event: "SessionStart",
|
|
596
|
+
command: "npx -y @membank/cli@latest inject --harness claude-code",
|
|
597
|
+
existingCommand: extractInjectCommand((Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray)) || null
|
|
598
|
+
}]
|
|
599
|
+
};
|
|
600
|
+
},
|
|
601
|
+
write(resolver, events) {
|
|
650
602
|
const cfgPath = join(resolver.home(), ".claude", "settings.json");
|
|
651
603
|
const cfg = readJson(cfgPath);
|
|
652
|
-
const hooks = cfg.hooks;
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
604
|
+
const hooks = cfg.hooks ?? {};
|
|
605
|
+
const newHooks = { ...hooks };
|
|
606
|
+
pruneNestedEvent(newHooks, "UserPromptSubmit");
|
|
607
|
+
pruneNestedEvent(newHooks, "PostToolUseFailure");
|
|
608
|
+
if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
|
|
609
|
+
matcher: "",
|
|
610
|
+
hooks: [{
|
|
611
|
+
type: "command",
|
|
612
|
+
command: "npx -y @membank/cli@latest inject --harness claude-code"
|
|
613
|
+
}]
|
|
614
|
+
}];
|
|
663
615
|
writeJsonAtomic(cfgPath, {
|
|
664
616
|
...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
|
-
}
|
|
617
|
+
hooks: newHooks
|
|
689
618
|
});
|
|
690
619
|
return { status: "written" };
|
|
691
620
|
}
|
|
692
621
|
},
|
|
693
622
|
"copilot-cli": {
|
|
694
|
-
|
|
695
|
-
|
|
623
|
+
inspect(resolver) {
|
|
624
|
+
const hooks = readJson(join(resolver.home(), ".copilot", "settings.json")).hooks ?? {};
|
|
625
|
+
return {
|
|
626
|
+
status: "ready",
|
|
627
|
+
hooks: [{
|
|
628
|
+
event: "sessionStart",
|
|
629
|
+
command: "npx -y @membank/cli@latest inject --harness copilot-cli",
|
|
630
|
+
existingCommand: extractInjectCommand(Array.isArray(hooks.sessionStart) ? hooks.sessionStart : []) || null
|
|
631
|
+
}]
|
|
632
|
+
};
|
|
633
|
+
},
|
|
634
|
+
write(resolver, events) {
|
|
696
635
|
const cfgPath = join(resolver.home(), ".copilot", "settings.json");
|
|
697
636
|
const cfg = readJson(cfgPath);
|
|
698
|
-
const hooks = cfg.hooks;
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const existingToolFailure = Array.isArray(hooks?.postToolUseFailure) ? hooks.postToolUseFailure : [];
|
|
637
|
+
const hooks = cfg.hooks ?? {};
|
|
638
|
+
const newHooks = { ...hooks };
|
|
639
|
+
pruneFlatEvent(newHooks, "userPromptSubmitted");
|
|
640
|
+
pruneFlatEvent(newHooks, "postToolUseFailure");
|
|
641
|
+
if (events.includes("sessionStart")) newHooks.sessionStart = [...filterOutMembankFlat(Array.isArray(hooks.sessionStart) ? hooks.sessionStart : []), {
|
|
642
|
+
type: "command",
|
|
643
|
+
bash: "npx -y @membank/cli@latest inject --harness copilot-cli",
|
|
644
|
+
timeoutSec: 30
|
|
645
|
+
}];
|
|
708
646
|
writeJsonAtomic(cfgPath, {
|
|
709
647
|
version: cfg.version ?? 1,
|
|
710
648
|
...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
|
-
}
|
|
649
|
+
hooks: newHooks
|
|
729
650
|
});
|
|
730
651
|
return { status: "written" };
|
|
731
652
|
}
|
|
732
653
|
},
|
|
733
654
|
codex: {
|
|
734
|
-
|
|
735
|
-
|
|
655
|
+
inspect(resolver) {
|
|
656
|
+
const hooks = readJson(join(resolver.home(), ".codex", "hooks.json")).hooks ?? {};
|
|
657
|
+
return {
|
|
658
|
+
status: "ready",
|
|
659
|
+
hooks: [{
|
|
660
|
+
event: "SessionStart",
|
|
661
|
+
command: "npx -y @membank/cli@latest inject --harness codex",
|
|
662
|
+
existingCommand: extractInjectCommand((Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []).flatMap(getHooksArray)) || null
|
|
663
|
+
}]
|
|
664
|
+
};
|
|
665
|
+
},
|
|
666
|
+
write(resolver, events) {
|
|
736
667
|
const cfgPath = join(resolver.home(), ".codex", "hooks.json");
|
|
737
668
|
const cfg = readJson(cfgPath);
|
|
738
|
-
const hooks = cfg.hooks;
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
669
|
+
const hooks = cfg.hooks ?? {};
|
|
670
|
+
const newHooks = { ...hooks };
|
|
671
|
+
pruneNestedEvent(newHooks, "UserPromptSubmit");
|
|
672
|
+
pruneNestedEvent(newHooks, "PostToolUse");
|
|
673
|
+
if (events.includes("SessionStart")) newHooks.SessionStart = [...filterOutMembank(Array.isArray(hooks.SessionStart) ? hooks.SessionStart : []), {
|
|
674
|
+
matcher: "",
|
|
675
|
+
hooks: [{
|
|
676
|
+
type: "command",
|
|
677
|
+
command: "npx -y @membank/cli@latest inject --harness codex",
|
|
678
|
+
timeout: 30
|
|
679
|
+
}]
|
|
680
|
+
}];
|
|
749
681
|
writeJsonAtomic(cfgPath, {
|
|
750
682
|
...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
|
-
}
|
|
683
|
+
hooks: newHooks
|
|
778
684
|
});
|
|
779
685
|
return { status: "written" };
|
|
780
686
|
}
|
|
781
687
|
},
|
|
782
688
|
opencode: {
|
|
783
|
-
|
|
784
|
-
write(resolver, overwrite = false) {
|
|
689
|
+
inspect(resolver) {
|
|
785
690
|
const pluginPath = join(resolver.home(), ".config", "opencode", "plugins", "membank.js");
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
if (
|
|
789
|
-
status: "already-configured",
|
|
790
|
-
existing: existing.trim(),
|
|
791
|
-
replacement: newOpencodePlugin()
|
|
792
|
-
};
|
|
691
|
+
let existingCommand = null;
|
|
692
|
+
if (existsSync(pluginPath)) {
|
|
693
|
+
if (readFileSync(pluginPath, "utf8").includes("@membank/cli")) existingCommand = pluginPath;
|
|
793
694
|
}
|
|
695
|
+
return {
|
|
696
|
+
status: "ready",
|
|
697
|
+
hooks: [{
|
|
698
|
+
event: "plugin",
|
|
699
|
+
command: pluginPath,
|
|
700
|
+
existingCommand
|
|
701
|
+
}]
|
|
702
|
+
};
|
|
703
|
+
},
|
|
704
|
+
write(resolver, events) {
|
|
705
|
+
if (events.length === 0) return { status: "written" };
|
|
706
|
+
const pluginPath = join(resolver.home(), ".config", "opencode", "plugins", "membank.js");
|
|
794
707
|
mkdirSync(dirname(pluginPath), { recursive: true });
|
|
795
708
|
writeFileSync(pluginPath, `${newOpencodePlugin()}\n`, "utf8");
|
|
796
709
|
return { status: "written" };
|
|
@@ -802,19 +715,7 @@ function newOpencodePlugin() {
|
|
|
802
715
|
"export default {",
|
|
803
716
|
" hooks: {",
|
|
804
717
|
" \"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();",
|
|
718
|
+
" return await $`npx -y @membank/cli@latest inject`.text();",
|
|
818
719
|
" },",
|
|
819
720
|
" },",
|
|
820
721
|
"};"
|
|
@@ -825,10 +726,15 @@ var InjectionHookWriter = class {
|
|
|
825
726
|
constructor(resolver = defaultPathResolver) {
|
|
826
727
|
this.#resolver = resolver;
|
|
827
728
|
}
|
|
828
|
-
|
|
729
|
+
inspect(harness) {
|
|
730
|
+
const writer = writers[harness];
|
|
731
|
+
if (!writer) return { status: "not-supported" };
|
|
732
|
+
return writer.inspect(this.#resolver);
|
|
733
|
+
}
|
|
734
|
+
write(harness, events) {
|
|
829
735
|
const writer = writers[harness];
|
|
830
736
|
if (!writer) return { status: "not-supported" };
|
|
831
|
-
return writer.write(this.#resolver,
|
|
737
|
+
return writer.write(this.#resolver, events);
|
|
832
738
|
}
|
|
833
739
|
};
|
|
834
740
|
//#endregion
|
|
@@ -1062,8 +968,7 @@ var SetupOrchestrator = class {
|
|
|
1062
968
|
out("");
|
|
1063
969
|
const injectionHooksConfigured = [];
|
|
1064
970
|
if (this.#hookWriter) {
|
|
1065
|
-
|
|
1066
|
-
injectionHooksConfigured.push(...await this.#runHookLoop(detected, "injection hook", (h, ow) => w.write(h, ow), yes, out));
|
|
971
|
+
injectionHooksConfigured.push(...await this.#runHookSetup(detected, yes, out));
|
|
1067
972
|
out("");
|
|
1068
973
|
}
|
|
1069
974
|
let modelDownloaded = false;
|
|
@@ -1086,28 +991,33 @@ var SetupOrchestrator = class {
|
|
|
1086
991
|
}
|
|
1087
992
|
return results;
|
|
1088
993
|
}
|
|
1089
|
-
async #
|
|
994
|
+
async #runHookSetup(detected, yes, out) {
|
|
1090
995
|
const configured = [];
|
|
996
|
+
const w = this.#hookWriter;
|
|
1091
997
|
for (const h of detected) try {
|
|
1092
|
-
const
|
|
1093
|
-
if (
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
998
|
+
const inspected = w.inspect(h.name);
|
|
999
|
+
if (inspected.status === "not-supported") continue;
|
|
1000
|
+
const toWrite = [];
|
|
1001
|
+
for (const hook of inspected.hooks) if (hook.existingCommand === null) {
|
|
1002
|
+
out(` ${h.name}: ${hook.event} injection hook`);
|
|
1003
|
+
out(` Command: ${hook.command}`);
|
|
1004
|
+
if (yes || await this.#prompter(` Configure ${hook.event} injection hook for ${h.name}?`)) toWrite.push(hook.event);
|
|
1097
1005
|
} else {
|
|
1098
|
-
out(` ⚠ ${h.name}: ${
|
|
1099
|
-
out(` Current: ${
|
|
1100
|
-
out(` New: ${
|
|
1101
|
-
if (yes || await this.#prompter(` Replace ${
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
}
|
|
1006
|
+
out(` ⚠ ${h.name}: ${hook.event} injection hook already configured`);
|
|
1007
|
+
out(` Current: ${hook.existingCommand}`);
|
|
1008
|
+
if (hook.existingCommand !== hook.command) out(` New: ${hook.command}`);
|
|
1009
|
+
if (yes || await this.#prompter(` Replace ${hook.event} injection hook for ${h.name}?`)) toWrite.push(hook.event);
|
|
1010
|
+
}
|
|
1011
|
+
if (toWrite.length > 0) {
|
|
1012
|
+
w.write(h.name, toWrite);
|
|
1013
|
+
const skippedCount = inspected.hooks.length - toWrite.length;
|
|
1014
|
+
const label = skippedCount > 0 ? `${toWrite.length} injection hook(s) written, ${skippedCount} skipped` : `${toWrite.length} injection hook(s) written`;
|
|
1015
|
+
out(` ✓ ${h.name}: ${label}`);
|
|
1016
|
+
configured.push(h.name);
|
|
1107
1017
|
}
|
|
1108
1018
|
} catch (err) {
|
|
1109
1019
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1110
|
-
out(` ✗ ${h.name}
|
|
1020
|
+
out(` ✗ ${h.name} injection hooks: ${msg}`);
|
|
1111
1021
|
}
|
|
1112
1022
|
return configured;
|
|
1113
1023
|
}
|
|
@@ -1241,7 +1151,7 @@ program.command("import <file>").description("import memories from a JSON export
|
|
|
1241
1151
|
db.close();
|
|
1242
1152
|
}
|
|
1243
1153
|
});
|
|
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
|
|
1154
|
+
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 (only session-start is supported; other values no-op for legacy hook compatibility)", "session-start").action(async (cmdOptions) => {
|
|
1245
1155
|
try {
|
|
1246
1156
|
await injectCommand(cmdOptions);
|
|
1247
1157
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@membank/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,9 +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/
|
|
22
|
-
"@membank/
|
|
20
|
+
"@membank/core": "0.4.1",
|
|
21
|
+
"@membank/dashboard": "0.2.1",
|
|
22
|
+
"@membank/mcp": "0.4.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^25.6.0",
|