@nxuss/lemma 1.29.0 ā 1.30.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 +43 -7
- package/bin/brain-ingest.js +34 -14
- package/bin/init.js +46 -3
- package/dist/cjs/cli/hostHooks.d.ts +59 -0
- package/dist/cjs/cli/hostHooks.d.ts.map +1 -0
- package/dist/cjs/cli/hostHooks.js +235 -0
- package/dist/cjs/cli/hostHooks.js.map +1 -0
- package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/cjs/cli/lemma-proxy.js +17 -1
- package/dist/cjs/cli/lemma-proxy.js.map +1 -1
- package/dist/cjs/mcp/tools/depgraph.js +10 -10
- package/dist/cjs/mcp/tools/depgraph.js.map +1 -1
- package/dist/cjs/mcp/tools/memory.js +26 -26
- package/dist/cjs/mcp/tools/memory.js.map +1 -1
- package/dist/cjs/mcp/tools/squeeze-cache.js +15 -15
- package/dist/cjs/mcp/tools/squeeze-cache.js.map +1 -1
- package/dist/cjs/subconscious/HostAdapters.d.ts +45 -0
- package/dist/cjs/subconscious/HostAdapters.d.ts.map +1 -0
- package/dist/cjs/subconscious/HostAdapters.js +219 -0
- package/dist/cjs/subconscious/HostAdapters.js.map +1 -0
- package/dist/cjs/subconscious/HostRecall.d.ts +44 -0
- package/dist/cjs/subconscious/HostRecall.d.ts.map +1 -0
- package/dist/cjs/subconscious/HostRecall.js +186 -0
- package/dist/cjs/subconscious/HostRecall.js.map +1 -0
- package/dist/cjs/subconscious/TheBrainV2.d.ts +6 -0
- package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/cjs/subconscious/TheBrainV2.js +2 -2
- package/dist/cjs/subconscious/TheBrainV2.js.map +1 -1
- package/dist/cjs/utils/FixCapture.d.ts +8 -0
- package/dist/cjs/utils/FixCapture.d.ts.map +1 -1
- package/dist/cjs/utils/FixCapture.js +14 -3
- package/dist/cjs/utils/FixCapture.js.map +1 -1
- package/dist/cjs/utils/WorkspaceSearch.d.ts.map +1 -1
- package/dist/cjs/utils/WorkspaceSearch.js +0 -0
- package/dist/cjs/utils/WorkspaceSearch.js.map +1 -1
- package/dist/esm/cli/hostHooks.d.ts +59 -0
- package/dist/esm/cli/hostHooks.d.ts.map +1 -0
- package/dist/esm/cli/hostHooks.js +223 -0
- package/dist/esm/cli/hostHooks.js.map +1 -0
- package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
- package/dist/esm/cli/lemma-proxy.js +17 -1
- package/dist/esm/cli/lemma-proxy.js.map +1 -1
- package/dist/esm/mcp/tools/depgraph.js +10 -10
- package/dist/esm/mcp/tools/depgraph.js.map +1 -1
- package/dist/esm/mcp/tools/memory.js +26 -26
- package/dist/esm/mcp/tools/memory.js.map +1 -1
- package/dist/esm/mcp/tools/squeeze-cache.js +15 -15
- package/dist/esm/mcp/tools/squeeze-cache.js.map +1 -1
- package/dist/esm/subconscious/HostAdapters.d.ts +45 -0
- package/dist/esm/subconscious/HostAdapters.d.ts.map +1 -0
- package/dist/esm/subconscious/HostAdapters.js +208 -0
- package/dist/esm/subconscious/HostAdapters.js.map +1 -0
- package/dist/esm/subconscious/HostRecall.d.ts +44 -0
- package/dist/esm/subconscious/HostRecall.d.ts.map +1 -0
- package/dist/esm/subconscious/HostRecall.js +178 -0
- package/dist/esm/subconscious/HostRecall.js.map +1 -0
- package/dist/esm/subconscious/TheBrainV2.d.ts +6 -0
- package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -1
- package/dist/esm/subconscious/TheBrainV2.js +2 -2
- package/dist/esm/subconscious/TheBrainV2.js.map +1 -1
- package/dist/esm/utils/FixCapture.d.ts +8 -0
- package/dist/esm/utils/FixCapture.d.ts.map +1 -1
- package/dist/esm/utils/FixCapture.js +13 -3
- package/dist/esm/utils/FixCapture.js.map +1 -1
- package/dist/esm/utils/WorkspaceSearch.d.ts.map +1 -1
- package/dist/esm/utils/WorkspaceSearch.js +0 -0
- package/dist/esm/utils/WorkspaceSearch.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/Nxusbets/lemma)
|
|
6
6
|
[](https://github.com/Nxusbets/lemma)
|
|
7
7
|
[](https://github.com/Nxusbets/lemma)
|
|
8
|
-
[](https://github.com/Nxusbets/lemma)
|
|
9
9
|
|
|
10
10
|
```text
|
|
11
11
|
__
|
|
@@ -35,6 +35,26 @@ lemma start # gateway + dashboard on http://localhost:8081
|
|
|
35
35
|
>
|
|
36
36
|
> **š v1.22.0 ā Lemma auto-activates in every LLM client.** `npm i -g @nxuss/lemma` registers the skill in your **user** catalog (auto-loads in every `muse` session, any project) and `lemma init` wires each project for Muse, Claude Code, Cursor, Codex, Windsurf, Kiro and OpenCode. Health check: `lemma doctor`.
|
|
37
37
|
>
|
|
38
|
+
> **š v1.30.1 ā `lemma init` now really installs the Brain's hooks, and big search results stop eating
|
|
39
|
+
> your context.** Dogfooding found that `lemma init` finished "successfully" without installing the
|
|
40
|
+
> git hooks (`post-commit`, `post-merge`, `post-checkout`) or the Claude Code / Codex / Cursor /
|
|
41
|
+
> OpenCode tool hooks that v1.28āv1.30 advertised ā so commits never became memories. Fixed
|
|
42
|
+
> (idempotent, merge-only, `--no-hooks` to skip); re-run `lemma init` after updating.
|
|
43
|
+
> `search_workspace` also had a match cap but no size cap: one hit inside a minified bundle returned
|
|
44
|
+
> the whole line (~75k tokens reproduced). Lines are now a 240-char window around the hit and the
|
|
45
|
+
> result is capped at ~8k tokens, with what was dropped disclosed. Plus 10% smaller tool schemas
|
|
46
|
+
> (ā938 tokens per session).
|
|
47
|
+
>
|
|
48
|
+
> **š v1.30.0 ā the Brain speaks through your agent's own tools, for zero tokens.** Agents read,
|
|
49
|
+
> edit and test with their native tools, so the Brain used to stay silent unless the model
|
|
50
|
+
> remembered `search_memory`. Now a post-tool hook answers at the three moments a memory is worth
|
|
51
|
+
> the most: reading a file the Brain has notes on (their ids), editing code where an approach
|
|
52
|
+
> already **failed**, and a test/build/lint failing with an error the Brain has a recorded **fix**
|
|
53
|
+
> for. Local BM25 + hashes, no model tokens; ids and one line only (~35 tokens), and silent by
|
|
54
|
+
> default ā no match, a stale fix, or a note already given this session says nothing. `lemma init`
|
|
55
|
+
> wires it for **Claude Code, Codex, Cursor and OpenCode**. Also fixes `store()`'s dedup leaking
|
|
56
|
+
> across projects (the same fix in a second repo was dropped as a "duplicate").
|
|
57
|
+
>
|
|
38
58
|
> **š v1.29.0 ā the dashboard shows what the Brain knows.** `lemma start` now opens with a
|
|
39
59
|
> š§ Brain panel: how many files of your codebase it carries evidence about, how many times a
|
|
40
60
|
> memory reached an agent's context, growth this week, how much of it is verifiable against the
|
|
@@ -91,6 +111,16 @@ lemma start # gateway + dashboard on http://localhost:8081
|
|
|
91
111
|
it, never one without the other). `store_memory` derives `filePaths`/`symbols` by default from
|
|
92
112
|
whatever the session already read, wrote, or patched when the caller doesn't pass them ā
|
|
93
113
|
flagged as auto-derived in the response, correctable, but no longer opt-in.
|
|
114
|
+
- **A Brain that speaks up on its own, through the host's tools** ā a post-tool hook
|
|
115
|
+
(`lemma-brain-ingest --hook-result --host=claude|codex|cursor|opencode`) points at memories when
|
|
116
|
+
the agent reads a file they're about to matter for, warns when an edit touches a documented
|
|
117
|
+
failed attempt, and names the recorded fix when a verdict command fails with an error already
|
|
118
|
+
fixed in *this* project (term overlap between error headlines decides "the same failure", not
|
|
119
|
+
BM25 rank). No model tokens to compute, ids instead of bodies, deduped per host session, and a
|
|
120
|
+
pointer never counts as reuse. Codex's shell reads (`cat`, `sed -n`, ā¦) and `apply_patch` edits
|
|
121
|
+
are recovered from the command; OpenCode gets a plugin, since it has no hook protocol.
|
|
122
|
+
Windsurf/Devin post-hooks can't inject context, and Kiro's hooks spend tokens by design, so
|
|
123
|
+
neither is wired.
|
|
94
124
|
- **A team Brain, shared through git** ā `brain_team push` writes this project's fresh, tracked
|
|
95
125
|
memories to `.lemma-brain/memories/<id>.json`: one sorted-key JSON file per memory, so two people
|
|
96
126
|
sharing at once never conflict, every shared memory goes through code review, and deleting one is
|
|
@@ -201,7 +231,7 @@ lemma_toolbox { "action": "call", "tool": "terraform_drift", "args": {} }
|
|
|
201
231
|
|
|
202
232
|
| Command | Action |
|
|
203
233
|
| :--- | :--- |
|
|
204
|
-
| `lemma init` | Bootstraps config, auto-configures MCP for OpenCode, Claude Desktop, Cursor, Windsurf, Kiro, VS Code, and installs the
|
|
234
|
+
| `lemma init` | Bootstraps config, auto-configures MCP for OpenCode, Claude Desktop, Cursor, Windsurf, Kiro, VS Code, and installs the hooks that feed the Brain: git hooks, plus the tool hooks for Claude Code, Codex, Cursor and OpenCode (`--no-hooks` to skip). |
|
|
205
235
|
| `lemma start` | Launches proxy, cache engine, and dashboard at `http://localhost:8081` (gateway savings + Brain knowledge panel). |
|
|
206
236
|
| `lemma stop` / `lemma status` / `lemma stats` | Stop, health check, and net token/cost report (reports `NET COST` when a session is underwater). |
|
|
207
237
|
| `lemma mcp` | Starts the stdin/stdout MCP server for IDE integration. |
|
|
@@ -210,7 +240,7 @@ lemma_toolbox { "action": "call", "tool": "terraform_drift", "args": {} }
|
|
|
210
240
|
| `lemma activate <key>` | Activate a Pro license. |
|
|
211
241
|
| `lemma-checkpoint [projectRoot]` | Derive a session checkpoint from repo state (branch, changed files, commits) with no assistant call. |
|
|
212
242
|
| `lemma-brain-ingest [projectRoot] [--changelog] [--team-pull]` | Feed The Brain from git commits and `CHANGELOG.md`, incrementally; `--team-pull` imports the repo's `.lemma-brain/`. |
|
|
213
|
-
| `lemma-brain-ingest --hook-result` | Read a host
|
|
243
|
+
| `lemma-brain-ingest --hook-result [--host=claude\|codex\|cursor\|opencode]` | Read a host post-tool payload on stdin: capture red ā green fixes from the agent's own shell, and reply with a Brain note (memory ids, failed attempts, known fixes) in that host's format ā or nothing. |
|
|
214
244
|
|
|
215
245
|
Wire the standalone bins into hooks so memory doesn't depend on the assistant remembering to call a tool:
|
|
216
246
|
|
|
@@ -221,12 +251,18 @@ Wire the standalone bins into hooks so memory doesn't depend on the assistant re
|
|
|
221
251
|
```
|
|
222
252
|
|
|
223
253
|
```jsonc
|
|
224
|
-
// .claude/settings.json ā
|
|
225
|
-
|
|
226
|
-
|
|
254
|
+
// .claude/settings.local.json ā what `lemma init` writes: fixes from Claude Code's own Bash
|
|
255
|
+
// become memories, and native Read/Edit/Bash get Brain notes back
|
|
256
|
+
{ "hooks": { "PostToolUse": [{ "matcher": "Bash|Read|Edit|MultiEdit|Write|NotebookEdit",
|
|
257
|
+
"hooks": [{ "type": "command", "command": "lemma-brain-ingest --hook-result" }] }] } }
|
|
227
258
|
```
|
|
228
259
|
|
|
229
|
-
|
|
260
|
+
Codex (`~/.codex/hooks.json`, `--host=codex`), Cursor (`~/.cursor/hooks.json`, `postToolUse` +
|
|
261
|
+
`postToolUseFailure`, `--host=cursor`) and OpenCode (a plugin in `~/.config/opencode/plugins/`) are
|
|
262
|
+
wired the same way by `lemma init` when their config directory exists. Existing hooks are kept;
|
|
263
|
+
a file that isn't valid JSON is left alone and the snippet printed instead.
|
|
264
|
+
|
|
265
|
+
The git side needs nothing by hand either: `lemma init` installs `post-commit` (commit ā memory) and
|
|
230
266
|
`post-merge`/`post-checkout` (pull the team's `.lemma-brain/`). With a versioned hooks dir
|
|
231
267
|
(`core.hooksPath`, e.g. husky) it prints the lines to add instead of editing team files.
|
|
232
268
|
|
package/bin/brain-ingest.js
CHANGED
|
@@ -14,13 +14,20 @@
|
|
|
14
14
|
* and never double-store. `--max N` only matters the first time there is no marker yet.
|
|
15
15
|
*
|
|
16
16
|
* Usage: lemma-brain-ingest [projectRoot] [--max N] [--changelog] [--no-commits] [--team-pull]
|
|
17
|
-
* lemma-brain-ingest --hook-result < payload.json
|
|
17
|
+
* lemma-brain-ingest --hook-result [--host=claude|codex|cursor|opencode] < payload.json
|
|
18
18
|
*
|
|
19
19
|
* --team-pull Import the repo's shared memories from .lemma-brain/ (post-merge/post-checkout).
|
|
20
|
-
* --hook-result Read a host PostToolUse hook payload on stdin (Claude Code
|
|
21
|
-
* feed
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* --hook-result Read a host PostToolUse hook payload on stdin (Claude Code shape) and:
|
|
21
|
+
* - feed a Bash run to FixCapture, so a test that goes red ā green through the
|
|
22
|
+
* agent's own shell becomes a memory too;
|
|
23
|
+
* - answer with a HostRecall note (as `additionalContext`, the only PostToolUse
|
|
24
|
+
* output the host puts in the model's context) when a Read touches a file the
|
|
25
|
+
* Brain has memories about, an edit touches a documented failed attempt, or a
|
|
26
|
+
* failing verdict command matches a recorded fix. Silent otherwise.
|
|
27
|
+
* --host=claude|codex|cursor|opencode picks the payload/reply dialect
|
|
28
|
+
* (default claude; see src/subconscious/HostAdapters.ts).
|
|
29
|
+
* `lemma init` wires it for each host it finds, e.g. .claude/settings.local.json:
|
|
30
|
+
* "hooks": { "PostToolUse": [{ "matcher": "Bash|Read|Edit|MultiEdit|Write|NotebookEdit",
|
|
24
31
|
* "hooks": [{ "type": "command", "command": "lemma-brain-ingest --hook-result" }] }] }
|
|
25
32
|
*/
|
|
26
33
|
|
|
@@ -34,27 +41,40 @@ function readStdin() {
|
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
function hookResult() {
|
|
38
|
-
const
|
|
39
|
-
let recordHookPayload;
|
|
44
|
+
function hookResult(args) {
|
|
45
|
+
const dist = path.join(__dirname, '..', 'dist', 'cjs');
|
|
46
|
+
let recordHookPayload, recallForHookPayload, normalizeHostPayload, formatHostOutput, parseHost;
|
|
40
47
|
try {
|
|
41
|
-
({ recordHookPayload } = require(
|
|
48
|
+
({ recordHookPayload } = require(path.join(dist, 'utils', 'FixCapture.js')));
|
|
49
|
+
({ recallForHookPayload } = require(path.join(dist, 'subconscious', 'HostRecall.js')));
|
|
50
|
+
({ normalizeHostPayload, formatHostOutput, parseHost } = require(path.join(dist, 'subconscious', 'HostAdapters.js')));
|
|
42
51
|
} catch {
|
|
43
52
|
return; // no build ā a hook must stay silent and harmless
|
|
44
53
|
}
|
|
45
|
-
|
|
54
|
+
const hostArg = args.find((a) => a.startsWith('--host='));
|
|
55
|
+
const host = parseHost(hostArg ? hostArg.slice('--host='.length) : undefined);
|
|
56
|
+
let raw;
|
|
46
57
|
try {
|
|
47
|
-
|
|
58
|
+
raw = JSON.parse(readStdin());
|
|
48
59
|
} catch {
|
|
49
60
|
return;
|
|
50
61
|
}
|
|
51
|
-
const
|
|
52
|
-
|
|
62
|
+
const notes = [];
|
|
63
|
+
for (const payload of normalizeHostPayload(host, raw)) {
|
|
64
|
+
try {
|
|
65
|
+
const res = recordHookPayload(payload, payload.cwd);
|
|
66
|
+
if (res && res.captured) console.error(res.note.trim());
|
|
67
|
+
} catch { /* capture failing must not cost the recall below */ }
|
|
68
|
+
const note = recallForHookPayload(payload, payload.cwd);
|
|
69
|
+
if (note && !notes.includes(note)) notes.push(note);
|
|
70
|
+
}
|
|
71
|
+
const out = formatHostOutput(host, notes.join('\n'));
|
|
72
|
+
if (out) process.stdout.write(out);
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
function main() {
|
|
56
76
|
const args = process.argv.slice(2);
|
|
57
|
-
if (args.includes('--hook-result')) return hookResult();
|
|
77
|
+
if (args.includes('--hook-result')) return hookResult(args);
|
|
58
78
|
const maxIndex = args.indexOf('--max');
|
|
59
79
|
const maxCommits = maxIndex >= 0 ? parseInt(args[maxIndex + 1], 10) : undefined;
|
|
60
80
|
const includeChangelog = args.includes('--changelog');
|
package/bin/init.js
CHANGED
|
@@ -904,9 +904,41 @@ function parseInitArgs(argv = process.argv) {
|
|
|
904
904
|
showHelp: args.includes('--help') || args.includes('-h'),
|
|
905
905
|
wantDaemon: args.includes('--daemon'),
|
|
906
906
|
wantGateway: args.includes('--gateway'),
|
|
907
|
+
// Hooks that feed the Brain are project/host config, not a background service: on by default.
|
|
908
|
+
wantHooks: !args.includes('--no-hooks'),
|
|
907
909
|
};
|
|
908
910
|
}
|
|
909
911
|
|
|
912
|
+
// The hooks that feed the Brain (git post-commit/post-merge/post-checkout, and the PostToolUse
|
|
913
|
+
// hook of each agent host) live in src/cli/{gitHooks,hostHooks}.ts. They were only wired into the
|
|
914
|
+
// commander `init` in src/cli/lemma-proxy.ts, which the `lemma` binary (lemma-proxy.cjs ā this
|
|
915
|
+
// file) never reaches ā so `lemma init` reported success and installed none of them.
|
|
916
|
+
function installBrainFeeds(cwd = process.cwd(), home = os.homedir(), mods = null) {
|
|
917
|
+
const lines = [];
|
|
918
|
+
let git, host;
|
|
919
|
+
try {
|
|
920
|
+
const dir = path.join(__dirname, '..', 'dist', 'cjs', 'cli');
|
|
921
|
+
git = (mods && mods.gitHooks) || require(path.join(dir, 'gitHooks.js'));
|
|
922
|
+
host = (mods && mods.hostHooks) || require(path.join(dir, 'hostHooks.js'));
|
|
923
|
+
} catch {
|
|
924
|
+
return { ok: false, lines: ['Brain hooks skipped: dist/ not built (run `npm run build:cjs`, then `lemma init` again)'] };
|
|
925
|
+
}
|
|
926
|
+
try {
|
|
927
|
+
const hooks = git.installBrainHooks(cwd);
|
|
928
|
+
const changed = [...hooks.installed, ...hooks.updated];
|
|
929
|
+
if (hooks.manual) lines.push(`Git hooks live in versioned ${hooks.manual.hooksDir}/ ā not touching them. To feed the Brain, add:\n${hooks.manual.snippet}`);
|
|
930
|
+
else if (changed.length > 0) lines.push(`Git hooks feed the Brain (${changed.join(', ')})`);
|
|
931
|
+
else if (hooks.skippedReason) lines.push(`Git hooks skipped: ${hooks.skippedReason}`);
|
|
932
|
+
for (const { host: name, outcome } of host.installHostHooks(cwd, home)) {
|
|
933
|
+
if (outcome.status === 'manual') lines.push(`${outcome.file} couldn't be merged safely ā not touching it. For ${name}, add:\n${outcome.snippet}`);
|
|
934
|
+
else if (outcome.status !== 'unchanged') lines.push(`${name} hook ${outcome.status} (${outcome.file})`);
|
|
935
|
+
}
|
|
936
|
+
} catch (e) {
|
|
937
|
+
return { ok: false, lines: [`Could not install the Brain hooks: ${e.message}`] };
|
|
938
|
+
}
|
|
939
|
+
return { ok: true, lines };
|
|
940
|
+
}
|
|
941
|
+
|
|
910
942
|
function applyEnvToCurrentProcess() {
|
|
911
943
|
const proxyUrl = `http://localhost:${PORT}`;
|
|
912
944
|
process.env.LEMMA_PROXY_PORT = PORT;
|
|
@@ -984,7 +1016,7 @@ async function main(argv = process.argv) {
|
|
|
984
1016
|
process.stderr.write(`
|
|
985
1017
|
Lemma Init ā project setup (project-local files + MCP registrations only)
|
|
986
1018
|
|
|
987
|
-
Usage: lemma init [--daemon] [--gateway]
|
|
1019
|
+
Usage: lemma init [--daemon] [--gateway] [--no-hooks]
|
|
988
1020
|
|
|
989
1021
|
--daemon Install + start the background cache-proxy daemon
|
|
990
1022
|
(launchd KeepAlive on macOS, systemd user unit on Linux).
|
|
@@ -993,6 +1025,8 @@ async function main(argv = process.argv) {
|
|
|
993
1025
|
--gateway Reroute this user's Claude Code model traffic through the proxy
|
|
994
1026
|
(~/.claude/settings.json). Requires ANTHROPIC_API_KEY in env.
|
|
995
1027
|
Without it, Claude Code keeps its own auth untouched.
|
|
1028
|
+
--no-hooks Skip the hooks that feed the Brain (git post-commit/post-merge/
|
|
1029
|
+
post-checkout; Claude Code, Codex, Cursor and OpenCode tool hooks).
|
|
996
1030
|
|
|
997
1031
|
`);
|
|
998
1032
|
return;
|
|
@@ -1018,11 +1052,12 @@ async function main(argv = process.argv) {
|
|
|
1018
1052
|
|
|
1019
1053
|
// --gateway is opt-in: flipping the user's global Claude Code auth without being
|
|
1020
1054
|
// asked is exactly how Pro subscribers end up billed as API usage.
|
|
1055
|
+
let gatewayResult = { activated: false };
|
|
1021
1056
|
if (!opts.wantGateway) {
|
|
1022
1057
|
skip('Claude Code gateway not requested ā model traffic + auth untouched (MCP tools only). Re-run with --gateway to route it via the proxy.');
|
|
1023
1058
|
} else {
|
|
1024
1059
|
const anthropicApiKey = process.env.ANTHROPIC_API_KEY || '';
|
|
1025
|
-
|
|
1060
|
+
gatewayResult = configureClaudeCodeGateway(anthropicApiKey);
|
|
1026
1061
|
if (gatewayResult.activated) {
|
|
1027
1062
|
ok(`Claude Code gateway activated (~/.claude/settings.json ā ANTHROPIC_BASE_URL=http://localhost:${PORT})`);
|
|
1028
1063
|
} else {
|
|
@@ -1095,6 +1130,14 @@ async function main(argv = process.argv) {
|
|
|
1095
1130
|
skip('Proxy daemon not installed (opt-in via `lemma init --daemon`)');
|
|
1096
1131
|
}
|
|
1097
1132
|
|
|
1133
|
+
if (opts.wantHooks) {
|
|
1134
|
+
const feeds = installBrainFeeds(process.cwd());
|
|
1135
|
+
for (const line of feeds.lines) (feeds.ok ? ok : warn)(line);
|
|
1136
|
+
if (feeds.ok && feeds.lines.length === 0) skip('Brain hooks already installed');
|
|
1137
|
+
} else {
|
|
1138
|
+
skip('Brain hooks not installed (--no-hooks)');
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1098
1141
|
const envFile = createEnvFile();
|
|
1099
1142
|
ok(`Env file created: ${envFile}`);
|
|
1100
1143
|
|
|
@@ -1128,4 +1171,4 @@ if (require.main === module) {
|
|
|
1128
1171
|
});
|
|
1129
1172
|
}
|
|
1130
1173
|
|
|
1131
|
-
module.exports = { main, configureMuse, configureMuseUserSkill, updateAgentsMd, MUSE_SKILL_CONTENT, LEMMA_RULES_CONTENT, installProxyDaemon, removeProxyDaemon, parseInitArgs, createEnvFile };
|
|
1174
|
+
module.exports = { main, configureMuse, configureMuseUserSkill, updateAgentsMd, MUSE_SKILL_CONTENT, LEMMA_RULES_CONTENT, installProxyDaemon, removeProxyDaemon, parseInitArgs, createEnvFile, installBrainFeeds };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installs the hooks that let the Brain speak through each agent host's own tools (see
|
|
3
|
+
* HostRecall / HostAdapters) and capture red ā green fixes run through the host's shell (see
|
|
4
|
+
* FixCapture). Without them the Brain only ever saw Lemma's MCP tools ā and agents mostly read,
|
|
5
|
+
* edit and test with their native ones.
|
|
6
|
+
*
|
|
7
|
+
* - Claude Code ā `.claude/settings.local.json` PostToolUse (personal, never the team's
|
|
8
|
+
* versioned settings.json; gitignored if the repo doesn't ignore it yet).
|
|
9
|
+
* - Codex ā `~/.codex/hooks.json` PostToolUse, same format as Claude Code.
|
|
10
|
+
* - Cursor ā `~/.cursor/hooks.json` postToolUse + postToolUseFailure.
|
|
11
|
+
* - OpenCode ā a plugin in `~/.config/opencode/plugins/` (it has no hook protocol).
|
|
12
|
+
*
|
|
13
|
+
* Codex/Cursor/OpenCode go in user-level config: their project-level files are the team's and
|
|
14
|
+
* versioned, and the hook is repo-agnostic anyway (silent wherever the Brain has nothing).
|
|
15
|
+
*
|
|
16
|
+
* Same constraints as the git hook installer:
|
|
17
|
+
* - Merge, never overwrite. Other hooks and settings are kept exactly as they were; Lemma's
|
|
18
|
+
* entry is found by its command and updated in place (idempotent).
|
|
19
|
+
* - A config file that isn't valid JSON ā or a plugin file Lemma didn't write ā is left alone,
|
|
20
|
+
* and the caller gets what to add by hand.
|
|
21
|
+
* - Never break a tool call: the command is guarded so a missing binary is a silent no-op.
|
|
22
|
+
*/
|
|
23
|
+
export declare function hookCommand(host: 'claude' | 'codex' | 'cursor'): string;
|
|
24
|
+
export declare const CLAUDE_HOOK_COMMAND: string;
|
|
25
|
+
export declare const CLAUDE_HOOK_MATCHER = "Bash|Read|Edit|MultiEdit|Write|NotebookEdit";
|
|
26
|
+
export declare const CODEX_HOOK_MATCHER = "Bash|apply_patch|Edit|Write";
|
|
27
|
+
export declare const CURSOR_POST_MATCHER = "Read|Shell|Write";
|
|
28
|
+
export declare const CURSOR_FAILURE_MATCHER = "Shell";
|
|
29
|
+
export interface HookInstallOutcome {
|
|
30
|
+
status: 'installed' | 'updated' | 'unchanged' | 'manual';
|
|
31
|
+
file: string;
|
|
32
|
+
/** Set when the file couldn't be merged safely; holds what to add by hand. */
|
|
33
|
+
snippet?: string;
|
|
34
|
+
/** True when `.claude/settings.local.json` was appended to .gitignore (Claude Code only). */
|
|
35
|
+
gitignored?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare function installClaudeHooks(cwd?: string): HookInstallOutcome;
|
|
38
|
+
export declare function installCodexHooks(home?: string): HookInstallOutcome;
|
|
39
|
+
/** Cursor format: `{ version: 1, hooks: { <event>: [{ command, matcher? }] } }`. */
|
|
40
|
+
export declare function installCursorHooks(home?: string): HookInstallOutcome;
|
|
41
|
+
/**
|
|
42
|
+
* OpenCode has no hook protocol, so the hook is a plugin: it forwards each finished tool call
|
|
43
|
+
* to `lemma-brain-ingest --host=opencode` and appends the note it prints (if any) to the tool's
|
|
44
|
+
* output ā the only place a plugin can put text the model reads. Arguments are taken from
|
|
45
|
+
* tool.execute.before, keyed by call id, because the after-hook's input doesn't carry them.
|
|
46
|
+
*/
|
|
47
|
+
export declare const OPENCODE_PLUGIN = "// managed by `lemma init` \u2014 delete this file to disable Lemma Brain recall in OpenCode\nimport { spawnSync } from \"node:child_process\";\n\nexport const LemmaBrainRecall = async ({ directory }) => {\n const argsByCall = new Map();\n return {\n \"tool.execute.before\": async (input, output) => {\n if (!input || !input.callID) return;\n argsByCall.set(input.callID, (output && output.args) || {});\n if (argsByCall.size > 200) argsByCall.delete(argsByCall.keys().next().value);\n },\n \"tool.execute.after\": async (input, output) => {\n try {\n const args = argsByCall.get(input.callID) || input.args || {};\n argsByCall.delete(input.callID);\n if (!output || typeof output.output !== \"string\") return;\n const payload = { tool: input.tool, sessionID: input.sessionID, args, output: output.output, metadata: output.metadata || {}, directory };\n const r = spawnSync(\"lemma-brain-ingest\", [\"--hook-result\", \"--host=opencode\"], { input: JSON.stringify(payload), encoding: \"utf8\", timeout: 5000 });\n const note = typeof r.stdout === \"string\" ? r.stdout.trim() : \"\";\n if (note) output.output += \"\\n\\n\" + note;\n } catch {\n // A recall hint must never be the reason a tool call fails.\n }\n },\n };\n};\n";
|
|
48
|
+
export declare function installOpenCodePlugin(home?: string): HookInstallOutcome;
|
|
49
|
+
export interface HostHookReport {
|
|
50
|
+
host: 'Claude Code' | 'Codex' | 'Cursor' | 'OpenCode';
|
|
51
|
+
outcome: HookInstallOutcome;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Claude Code is always wired (project-local file). The other hosts only when their config
|
|
55
|
+
* directory already exists ā Lemma doesn't create a Cursor or Codex setup on a machine that has
|
|
56
|
+
* neither.
|
|
57
|
+
*/
|
|
58
|
+
export declare function installHostHooks(cwd?: string, home?: string): HostHookReport[];
|
|
59
|
+
//# sourceMappingURL=hostHooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostHooks.d.ts","sourceRoot":"","sources":["../../../src/cli/hostHooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AASH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAGvE;AAED,eAAO,MAAM,mBAAmB,QAAwB,CAAC;AACzD,eAAO,MAAM,mBAAmB,gDAAgD,CAAC;AACjF,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAChE,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AACtD,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAE9C,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6FAA6F;IAC7F,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAuED,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAsB,GAAG,kBAAkB,CAKlF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAqB,GAAG,kBAAkB,CAEjF;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,MAAqB,GAAG,kBAAkB,CAkClF;AAID;;;;;GAKG;AACH,eAAO,MAAM,eAAe,szCA0B3B,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,IAAI,GAAE,MAAqB,GAAG,kBAAkB,CAYrF;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;IACtD,OAAO,EAAE,kBAAkB,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAsB,EAAE,IAAI,GAAE,MAAqB,GAAG,cAAc,EAAE,CAO3G"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Installs the hooks that let the Brain speak through each agent host's own tools (see
|
|
4
|
+
* HostRecall / HostAdapters) and capture red ā green fixes run through the host's shell (see
|
|
5
|
+
* FixCapture). Without them the Brain only ever saw Lemma's MCP tools ā and agents mostly read,
|
|
6
|
+
* edit and test with their native ones.
|
|
7
|
+
*
|
|
8
|
+
* - Claude Code ā `.claude/settings.local.json` PostToolUse (personal, never the team's
|
|
9
|
+
* versioned settings.json; gitignored if the repo doesn't ignore it yet).
|
|
10
|
+
* - Codex ā `~/.codex/hooks.json` PostToolUse, same format as Claude Code.
|
|
11
|
+
* - Cursor ā `~/.cursor/hooks.json` postToolUse + postToolUseFailure.
|
|
12
|
+
* - OpenCode ā a plugin in `~/.config/opencode/plugins/` (it has no hook protocol).
|
|
13
|
+
*
|
|
14
|
+
* Codex/Cursor/OpenCode go in user-level config: their project-level files are the team's and
|
|
15
|
+
* versioned, and the hook is repo-agnostic anyway (silent wherever the Brain has nothing).
|
|
16
|
+
*
|
|
17
|
+
* Same constraints as the git hook installer:
|
|
18
|
+
* - Merge, never overwrite. Other hooks and settings are kept exactly as they were; Lemma's
|
|
19
|
+
* entry is found by its command and updated in place (idempotent).
|
|
20
|
+
* - A config file that isn't valid JSON ā or a plugin file Lemma didn't write ā is left alone,
|
|
21
|
+
* and the caller gets what to add by hand.
|
|
22
|
+
* - Never break a tool call: the command is guarded so a missing binary is a silent no-op.
|
|
23
|
+
*/
|
|
24
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
25
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
26
|
+
};
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.OPENCODE_PLUGIN = exports.CURSOR_FAILURE_MATCHER = exports.CURSOR_POST_MATCHER = exports.CODEX_HOOK_MATCHER = exports.CLAUDE_HOOK_MATCHER = exports.CLAUDE_HOOK_COMMAND = void 0;
|
|
29
|
+
exports.hookCommand = hookCommand;
|
|
30
|
+
exports.installClaudeHooks = installClaudeHooks;
|
|
31
|
+
exports.installCodexHooks = installCodexHooks;
|
|
32
|
+
exports.installCursorHooks = installCursorHooks;
|
|
33
|
+
exports.installOpenCodePlugin = installOpenCodePlugin;
|
|
34
|
+
exports.installHostHooks = installHostHooks;
|
|
35
|
+
const fs_1 = __importDefault(require("fs"));
|
|
36
|
+
const os_1 = __importDefault(require("os"));
|
|
37
|
+
const path_1 = __importDefault(require("path"));
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
const HOOK_MARK = 'lemma-brain-ingest --hook-result';
|
|
40
|
+
function hookCommand(host) {
|
|
41
|
+
const flag = host === 'claude' ? '' : ` --host=${host}`;
|
|
42
|
+
return `command -v lemma-brain-ingest >/dev/null 2>&1 && ${HOOK_MARK}${flag} || true`;
|
|
43
|
+
}
|
|
44
|
+
exports.CLAUDE_HOOK_COMMAND = hookCommand('claude');
|
|
45
|
+
exports.CLAUDE_HOOK_MATCHER = 'Bash|Read|Edit|MultiEdit|Write|NotebookEdit';
|
|
46
|
+
exports.CODEX_HOOK_MATCHER = 'Bash|apply_patch|Edit|Write';
|
|
47
|
+
exports.CURSOR_POST_MATCHER = 'Read|Shell|Write';
|
|
48
|
+
exports.CURSOR_FAILURE_MATCHER = 'Shell';
|
|
49
|
+
const isOurs = (h) => typeof h?.command === 'string' && h.command.includes(HOOK_MARK);
|
|
50
|
+
function readJsonObject(file) {
|
|
51
|
+
if (!fs_1.default.existsSync(file))
|
|
52
|
+
return {};
|
|
53
|
+
try {
|
|
54
|
+
const v = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
55
|
+
return v && typeof v === 'object' && !Array.isArray(v) ? v : null;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function writeJson(file, value) {
|
|
62
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
63
|
+
fs_1.default.writeFileSync(file, JSON.stringify(value, null, 2) + '\n');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Claude Code / Codex format: `hooks.<Event>[] = { matcher, hooks: [{ type, command }] }`.
|
|
67
|
+
* Lemma's hook object is identified by its command; when a user put their own hooks in the same
|
|
68
|
+
* entry, Lemma's moves to an entry of its own instead of widening their matcher.
|
|
69
|
+
*/
|
|
70
|
+
function mergeMatcherHooks(file, event, matcher, command) {
|
|
71
|
+
const wanted = { matcher, hooks: [{ type: 'command', command }] };
|
|
72
|
+
const settings = readJsonObject(file);
|
|
73
|
+
if (!settings) {
|
|
74
|
+
return { status: 'manual', file, snippet: JSON.stringify({ hooks: { [event]: [wanted] } }, null, 2) };
|
|
75
|
+
}
|
|
76
|
+
settings.hooks = settings.hooks && typeof settings.hooks === 'object' ? settings.hooks : {};
|
|
77
|
+
const list = Array.isArray(settings.hooks[event]) ? settings.hooks[event] : [];
|
|
78
|
+
const ours = list.find((e) => Array.isArray(e?.hooks) && e.hooks.some(isOurs));
|
|
79
|
+
let status;
|
|
80
|
+
if (!ours) {
|
|
81
|
+
list.push(wanted);
|
|
82
|
+
status = 'installed';
|
|
83
|
+
}
|
|
84
|
+
else if (JSON.stringify({ matcher: ours.matcher, hooks: ours.hooks }) === JSON.stringify(wanted)) {
|
|
85
|
+
status = 'unchanged';
|
|
86
|
+
}
|
|
87
|
+
else if (ours.hooks.every(isOurs)) {
|
|
88
|
+
ours.matcher = matcher;
|
|
89
|
+
ours.hooks = wanted.hooks;
|
|
90
|
+
status = 'updated';
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
ours.hooks = ours.hooks.filter((h) => !isOurs(h));
|
|
94
|
+
list.push(wanted);
|
|
95
|
+
status = 'updated';
|
|
96
|
+
}
|
|
97
|
+
settings.hooks[event] = list;
|
|
98
|
+
if (status !== 'unchanged')
|
|
99
|
+
writeJson(file, settings);
|
|
100
|
+
return { status, file };
|
|
101
|
+
}
|
|
102
|
+
function ensureClaudeLocalIgnored(cwd) {
|
|
103
|
+
const rel = path_1.default.join('.claude', 'settings.local.json');
|
|
104
|
+
try {
|
|
105
|
+
(0, child_process_1.execFileSync)('git', ['-C', cwd, 'check-ignore', '-q', rel], { stdio: 'ignore' });
|
|
106
|
+
return false; // already ignored
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
// Exit 1 = not ignored; anything else (not a repo, no git) = nothing to protect.
|
|
110
|
+
if (e?.status !== 1)
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const gitignore = path_1.default.join(cwd, '.gitignore');
|
|
114
|
+
const rule = '.claude/settings.local.json';
|
|
115
|
+
const existing = fs_1.default.existsSync(gitignore) ? fs_1.default.readFileSync(gitignore, 'utf8') : '';
|
|
116
|
+
if (existing.split('\n').some((l) => l.trim() === rule))
|
|
117
|
+
return false;
|
|
118
|
+
fs_1.default.appendFileSync(gitignore, `${existing && !existing.endsWith('\n') ? '\n' : ''}${rule}\n`);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
function installClaudeHooks(cwd = process.cwd()) {
|
|
122
|
+
const res = mergeMatcherHooks(path_1.default.join(cwd, '.claude', 'settings.local.json'), 'PostToolUse', exports.CLAUDE_HOOK_MATCHER, exports.CLAUDE_HOOK_COMMAND);
|
|
123
|
+
if (res.status === 'manual')
|
|
124
|
+
return res;
|
|
125
|
+
const gitignored = ensureClaudeLocalIgnored(cwd);
|
|
126
|
+
return { ...res, ...(gitignored ? { gitignored } : {}) };
|
|
127
|
+
}
|
|
128
|
+
function installCodexHooks(home = os_1.default.homedir()) {
|
|
129
|
+
return mergeMatcherHooks(path_1.default.join(home, '.codex', 'hooks.json'), 'PostToolUse', exports.CODEX_HOOK_MATCHER, hookCommand('codex'));
|
|
130
|
+
}
|
|
131
|
+
/** Cursor format: `{ version: 1, hooks: { <event>: [{ command, matcher? }] } }`. */
|
|
132
|
+
function installCursorHooks(home = os_1.default.homedir()) {
|
|
133
|
+
const file = path_1.default.join(home, '.cursor', 'hooks.json');
|
|
134
|
+
const command = hookCommand('cursor');
|
|
135
|
+
const wanted = {
|
|
136
|
+
postToolUse: { command, matcher: exports.CURSOR_POST_MATCHER },
|
|
137
|
+
postToolUseFailure: { command, matcher: exports.CURSOR_FAILURE_MATCHER },
|
|
138
|
+
};
|
|
139
|
+
const config = readJsonObject(file);
|
|
140
|
+
if (!config) {
|
|
141
|
+
return {
|
|
142
|
+
status: 'manual',
|
|
143
|
+
file,
|
|
144
|
+
snippet: JSON.stringify({ version: 1, hooks: Object.fromEntries(Object.entries(wanted).map(([k, v]) => [k, [v]])) }, null, 2),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const fresh = !fs_1.default.existsSync(file);
|
|
148
|
+
if (typeof config.version !== 'number')
|
|
149
|
+
config.version = 1;
|
|
150
|
+
config.hooks = config.hooks && typeof config.hooks === 'object' ? config.hooks : {};
|
|
151
|
+
let changed = false;
|
|
152
|
+
let added = false;
|
|
153
|
+
for (const [event, entry] of Object.entries(wanted)) {
|
|
154
|
+
const list = Array.isArray(config.hooks[event]) ? config.hooks[event] : [];
|
|
155
|
+
const i = list.findIndex(isOurs);
|
|
156
|
+
if (i < 0) {
|
|
157
|
+
list.push(entry);
|
|
158
|
+
changed = added = true;
|
|
159
|
+
}
|
|
160
|
+
else if (JSON.stringify(list[i]) !== JSON.stringify(entry)) {
|
|
161
|
+
list[i] = entry;
|
|
162
|
+
changed = true;
|
|
163
|
+
}
|
|
164
|
+
config.hooks[event] = list;
|
|
165
|
+
}
|
|
166
|
+
if (changed)
|
|
167
|
+
writeJson(file, config);
|
|
168
|
+
return { status: !changed ? 'unchanged' : fresh || added ? 'installed' : 'updated', file };
|
|
169
|
+
}
|
|
170
|
+
const OPENCODE_MARK = '// managed by `lemma init` ā delete this file to disable Lemma Brain recall in OpenCode';
|
|
171
|
+
/**
|
|
172
|
+
* OpenCode has no hook protocol, so the hook is a plugin: it forwards each finished tool call
|
|
173
|
+
* to `lemma-brain-ingest --host=opencode` and appends the note it prints (if any) to the tool's
|
|
174
|
+
* output ā the only place a plugin can put text the model reads. Arguments are taken from
|
|
175
|
+
* tool.execute.before, keyed by call id, because the after-hook's input doesn't carry them.
|
|
176
|
+
*/
|
|
177
|
+
exports.OPENCODE_PLUGIN = `${OPENCODE_MARK}
|
|
178
|
+
import { spawnSync } from "node:child_process";
|
|
179
|
+
|
|
180
|
+
export const LemmaBrainRecall = async ({ directory }) => {
|
|
181
|
+
const argsByCall = new Map();
|
|
182
|
+
return {
|
|
183
|
+
"tool.execute.before": async (input, output) => {
|
|
184
|
+
if (!input || !input.callID) return;
|
|
185
|
+
argsByCall.set(input.callID, (output && output.args) || {});
|
|
186
|
+
if (argsByCall.size > 200) argsByCall.delete(argsByCall.keys().next().value);
|
|
187
|
+
},
|
|
188
|
+
"tool.execute.after": async (input, output) => {
|
|
189
|
+
try {
|
|
190
|
+
const args = argsByCall.get(input.callID) || input.args || {};
|
|
191
|
+
argsByCall.delete(input.callID);
|
|
192
|
+
if (!output || typeof output.output !== "string") return;
|
|
193
|
+
const payload = { tool: input.tool, sessionID: input.sessionID, args, output: output.output, metadata: output.metadata || {}, directory };
|
|
194
|
+
const r = spawnSync("lemma-brain-ingest", ["--hook-result", "--host=opencode"], { input: JSON.stringify(payload), encoding: "utf8", timeout: 5000 });
|
|
195
|
+
const note = typeof r.stdout === "string" ? r.stdout.trim() : "";
|
|
196
|
+
if (note) output.output += "\\n\\n" + note;
|
|
197
|
+
} catch {
|
|
198
|
+
// A recall hint must never be the reason a tool call fails.
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
`;
|
|
204
|
+
function installOpenCodePlugin(home = os_1.default.homedir()) {
|
|
205
|
+
const file = path_1.default.join(home, '.config', 'opencode', 'plugins', 'lemma-brain-recall.js');
|
|
206
|
+
if (fs_1.default.existsSync(file)) {
|
|
207
|
+
const current = fs_1.default.readFileSync(file, 'utf8');
|
|
208
|
+
if (!current.startsWith(OPENCODE_MARK))
|
|
209
|
+
return { status: 'manual', file, snippet: exports.OPENCODE_PLUGIN };
|
|
210
|
+
if (current === exports.OPENCODE_PLUGIN)
|
|
211
|
+
return { status: 'unchanged', file };
|
|
212
|
+
fs_1.default.writeFileSync(file, exports.OPENCODE_PLUGIN);
|
|
213
|
+
return { status: 'updated', file };
|
|
214
|
+
}
|
|
215
|
+
fs_1.default.mkdirSync(path_1.default.dirname(file), { recursive: true });
|
|
216
|
+
fs_1.default.writeFileSync(file, exports.OPENCODE_PLUGIN);
|
|
217
|
+
return { status: 'installed', file };
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Claude Code is always wired (project-local file). The other hosts only when their config
|
|
221
|
+
* directory already exists ā Lemma doesn't create a Cursor or Codex setup on a machine that has
|
|
222
|
+
* neither.
|
|
223
|
+
*/
|
|
224
|
+
function installHostHooks(cwd = process.cwd(), home = os_1.default.homedir()) {
|
|
225
|
+
const out = [{ host: 'Claude Code', outcome: installClaudeHooks(cwd) }];
|
|
226
|
+
const has = (...p) => fs_1.default.existsSync(path_1.default.join(home, ...p));
|
|
227
|
+
if (has('.codex'))
|
|
228
|
+
out.push({ host: 'Codex', outcome: installCodexHooks(home) });
|
|
229
|
+
if (has('.cursor'))
|
|
230
|
+
out.push({ host: 'Cursor', outcome: installCursorHooks(home) });
|
|
231
|
+
if (has('.config', 'opencode') || has('.opencode'))
|
|
232
|
+
out.push({ host: 'OpenCode', outcome: installOpenCodePlugin(home) });
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=hostHooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hostHooks.js","sourceRoot":"","sources":["../../../src/cli/hostHooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AAidA,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA8DvF;AAqfD,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGtD;AA4tDD,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8KhL"}
|
|
@@ -58,6 +58,7 @@ const ContextInjector_1 = require("../proxy/ContextInjector");
|
|
|
58
58
|
const SavingsLedger_1 = require("../utils/SavingsLedger");
|
|
59
59
|
const ProjectStore_1 = require("../proxy/ProjectStore");
|
|
60
60
|
const gitHooks_1 = require("./gitHooks");
|
|
61
|
+
const hostHooks_1 = require("./hostHooks");
|
|
61
62
|
const TheBrainV2_1 = require("../subconscious/TheBrainV2");
|
|
62
63
|
const chroma = new chromadb_1.ChromaClient({ host: 'localhost', port: 8000 });
|
|
63
64
|
const dummyEmbeddingFunction = { generate: (texts) => Promise.resolve([]) };
|
|
@@ -2518,7 +2519,7 @@ commander_1.program.command('init')
|
|
|
2518
2519
|
.description('Initialize Lemma in the current project (auto-discovery)')
|
|
2519
2520
|
.option('--no-configure', 'Skip automatic configuration of editors, shell overrides, and Claude Desktop')
|
|
2520
2521
|
.option('--yes', 'Skip prompts and accept defaults')
|
|
2521
|
-
.option('--no-hooks', 'Skip installing the
|
|
2522
|
+
.option('--no-hooks', 'Skip installing the hooks that feed the Brain (git post-commit/post-merge/post-checkout; Claude Code, Codex, Cursor and OpenCode tool hooks)')
|
|
2522
2523
|
.action(async (opts) => {
|
|
2523
2524
|
const project = detectProject();
|
|
2524
2525
|
console.log(`\nš ļø Initializing Lemma for [${project}]...`);
|
|
@@ -2541,6 +2542,21 @@ commander_1.program.command('init')
|
|
|
2541
2542
|
else if (changed.length > 0) {
|
|
2542
2543
|
console.log(`ā
Git hooks feed the Brain (${changed.join(', ')}): commits become memories, merges pull .lemma-brain/`);
|
|
2543
2544
|
}
|
|
2545
|
+
try {
|
|
2546
|
+
for (const { host, outcome } of (0, hostHooks_1.installHostHooks)(process.cwd())) {
|
|
2547
|
+
if (outcome.status === 'manual') {
|
|
2548
|
+
console.log(`ā ļø ${outcome.file} couldn't be merged safely ā not touching it. So the Brain speaks through ${host}'s own tools, add:\n${outcome.snippet}`);
|
|
2549
|
+
}
|
|
2550
|
+
else if (outcome.status !== 'unchanged') {
|
|
2551
|
+
console.log(`ā
${host} hook ${outcome.status} (${outcome.file}): the Brain flags memories on native reads/edits and known fixes on failing tests`);
|
|
2552
|
+
}
|
|
2553
|
+
if (outcome.gitignored)
|
|
2554
|
+
console.log('ā
Added .claude/settings.local.json to .gitignore');
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
catch (e) {
|
|
2558
|
+
console.log(`ā ļø Could not install the agent host hooks: ${e.message}`);
|
|
2559
|
+
}
|
|
2544
2560
|
}
|
|
2545
2561
|
const envFile = path_1.default.join(process.cwd(), '.env');
|
|
2546
2562
|
const lemmaConfig = `\n# Lemma AI Gateway Configuration\nOPENAI_BASE_URL=http://localhost:8081/v1\nLEMMA_PROJECT=${project}\n`;
|