@nxuss/lemma 1.40.0 → 1.41.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.
Files changed (231) hide show
  1. package/README.md +45 -14
  2. package/bin/brain-ingest.js +41 -0
  3. package/bin/init.js +92 -92
  4. package/bin/postinstall.js +58 -4
  5. package/dashboard/dist/assets/index-Ca05LJvs.js +453 -0
  6. package/dashboard/dist/assets/{index-DNTQXLER.css → index-DdEVyDRj.css} +1 -1
  7. package/dashboard/dist/index.html +2 -2
  8. package/dist/cjs/cli/hostHooks.d.ts +14 -1
  9. package/dist/cjs/cli/hostHooks.d.ts.map +1 -1
  10. package/dist/cjs/cli/hostHooks.js +53 -6
  11. package/dist/cjs/cli/hostHooks.js.map +1 -1
  12. package/dist/cjs/cli/lemma-proxy.d.ts.map +1 -1
  13. package/dist/cjs/cli/lemma-proxy.js +29 -10
  14. package/dist/cjs/cli/lemma-proxy.js.map +1 -1
  15. package/dist/cjs/contracts/evidence.d.ts +8 -1
  16. package/dist/cjs/contracts/evidence.d.ts.map +1 -1
  17. package/dist/cjs/mcp/index.js +19 -0
  18. package/dist/cjs/mcp/index.js.map +1 -1
  19. package/dist/cjs/mcp/prompts.d.ts +1 -1
  20. package/dist/cjs/mcp/prompts.d.ts.map +1 -1
  21. package/dist/cjs/mcp/prompts.js +6 -16
  22. package/dist/cjs/mcp/prompts.js.map +1 -1
  23. package/dist/cjs/mcp/tool-profiles.d.ts.map +1 -1
  24. package/dist/cjs/mcp/tool-profiles.js +8 -4
  25. package/dist/cjs/mcp/tool-profiles.js.map +1 -1
  26. package/dist/cjs/mcp/tools/context-tools.d.ts.map +1 -1
  27. package/dist/cjs/mcp/tools/context-tools.js +21 -17
  28. package/dist/cjs/mcp/tools/context-tools.js.map +1 -1
  29. package/dist/cjs/mcp/tools/memory.d.ts.map +1 -1
  30. package/dist/cjs/mcp/tools/memory.js +25 -4
  31. package/dist/cjs/mcp/tools/memory.js.map +1 -1
  32. package/dist/cjs/mcp/tools/workspace.d.ts.map +1 -1
  33. package/dist/cjs/mcp/tools/workspace.js +97 -54
  34. package/dist/cjs/mcp/tools/workspace.js.map +1 -1
  35. package/dist/cjs/mcp/tools.d.ts.map +1 -1
  36. package/dist/cjs/mcp/tools.js +95 -34
  37. package/dist/cjs/mcp/tools.js.map +1 -1
  38. package/dist/cjs/mcp/utils.d.ts.map +1 -1
  39. package/dist/cjs/mcp/utils.js +6 -1
  40. package/dist/cjs/mcp/utils.js.map +1 -1
  41. package/dist/cjs/mcp/workspaceShared.d.ts.map +1 -1
  42. package/dist/cjs/mcp/workspaceShared.js +14 -9
  43. package/dist/cjs/mcp/workspaceShared.js.map +1 -1
  44. package/dist/cjs/proxy/KeyManager.d.ts +10 -0
  45. package/dist/cjs/proxy/KeyManager.d.ts.map +1 -1
  46. package/dist/cjs/proxy/KeyManager.js +45 -8
  47. package/dist/cjs/proxy/KeyManager.js.map +1 -1
  48. package/dist/cjs/security/SemanticScrubber.d.ts.map +1 -1
  49. package/dist/cjs/security/SemanticScrubber.js +19 -1
  50. package/dist/cjs/security/SemanticScrubber.js.map +1 -1
  51. package/dist/cjs/subconscious/HostRecall.d.ts +7 -5
  52. package/dist/cjs/subconscious/HostRecall.d.ts.map +1 -1
  53. package/dist/cjs/subconscious/HostRecall.js +115 -13
  54. package/dist/cjs/subconscious/HostRecall.js.map +1 -1
  55. package/dist/cjs/subconscious/TheBrainV2.d.ts +6 -1
  56. package/dist/cjs/subconscious/TheBrainV2.d.ts.map +1 -1
  57. package/dist/cjs/subconscious/TheBrainV2.js +21 -9
  58. package/dist/cjs/subconscious/TheBrainV2.js.map +1 -1
  59. package/dist/cjs/subconscious/freshness/LocalFsResolver.d.ts +3 -0
  60. package/dist/cjs/subconscious/freshness/LocalFsResolver.d.ts.map +1 -1
  61. package/dist/cjs/subconscious/freshness/LocalFsResolver.js +32 -14
  62. package/dist/cjs/subconscious/freshness/LocalFsResolver.js.map +1 -1
  63. package/dist/cjs/utils/CommandOutputDistiller.d.ts +10 -0
  64. package/dist/cjs/utils/CommandOutputDistiller.d.ts.map +1 -1
  65. package/dist/cjs/utils/CommandOutputDistiller.js +40 -2
  66. package/dist/cjs/utils/CommandOutputDistiller.js.map +1 -1
  67. package/dist/cjs/utils/ContextSqueezer.d.ts +13 -1
  68. package/dist/cjs/utils/ContextSqueezer.d.ts.map +1 -1
  69. package/dist/cjs/utils/ContextSqueezer.js +176 -3
  70. package/dist/cjs/utils/ContextSqueezer.js.map +1 -1
  71. package/dist/cjs/utils/FixCapture.d.ts.map +1 -1
  72. package/dist/cjs/utils/FixCapture.js +56 -0
  73. package/dist/cjs/utils/FixCapture.js.map +1 -1
  74. package/dist/cjs/utils/LineNumberMap.d.ts.map +1 -1
  75. package/dist/cjs/utils/LineNumberMap.js +27 -0
  76. package/dist/cjs/utils/LineNumberMap.js.map +1 -1
  77. package/dist/cjs/utils/PatchMatcher.d.ts.map +1 -1
  78. package/dist/cjs/utils/PatchMatcher.js +26 -0
  79. package/dist/cjs/utils/PatchMatcher.js.map +1 -1
  80. package/dist/cjs/utils/ProLicense.d.ts +2 -0
  81. package/dist/cjs/utils/ProLicense.d.ts.map +1 -0
  82. package/dist/cjs/utils/ProLicense.js +31 -0
  83. package/dist/cjs/utils/ProLicense.js.map +1 -0
  84. package/dist/cjs/utils/RevivalThrottle.d.ts +9 -0
  85. package/dist/cjs/utils/RevivalThrottle.d.ts.map +1 -0
  86. package/dist/cjs/utils/RevivalThrottle.js +25 -0
  87. package/dist/cjs/utils/RevivalThrottle.js.map +1 -0
  88. package/dist/cjs/utils/SavingsLedger.d.ts +58 -18
  89. package/dist/cjs/utils/SavingsLedger.d.ts.map +1 -1
  90. package/dist/cjs/utils/SavingsLedger.js +332 -179
  91. package/dist/cjs/utils/SavingsLedger.js.map +1 -1
  92. package/dist/cjs/utils/SearchCache.d.ts +2 -2
  93. package/dist/cjs/utils/SearchCache.d.ts.map +1 -1
  94. package/dist/cjs/utils/SearchCache.js +8 -6
  95. package/dist/cjs/utils/SearchCache.js.map +1 -1
  96. package/dist/cjs/utils/SessionCapture.d.ts +65 -0
  97. package/dist/cjs/utils/SessionCapture.d.ts.map +1 -0
  98. package/dist/cjs/utils/SessionCapture.js +339 -0
  99. package/dist/cjs/utils/SessionCapture.js.map +1 -0
  100. package/dist/cjs/utils/StaleDelta.d.ts +2 -0
  101. package/dist/cjs/utils/StaleDelta.d.ts.map +1 -0
  102. package/dist/cjs/utils/StaleDelta.js +60 -0
  103. package/dist/cjs/utils/StaleDelta.js.map +1 -0
  104. package/dist/cjs/utils/TokenBudgetedReader.d.ts.map +1 -1
  105. package/dist/cjs/utils/TokenBudgetedReader.js +56 -15
  106. package/dist/cjs/utils/TokenBudgetedReader.js.map +1 -1
  107. package/dist/cjs/utils/WorkspaceSearch.d.ts +8 -1
  108. package/dist/cjs/utils/WorkspaceSearch.d.ts.map +1 -1
  109. package/dist/cjs/utils/WorkspaceSearch.js +75 -5
  110. package/dist/cjs/utils/WorkspaceSearch.js.map +1 -1
  111. package/dist/cjs/utils/reportSavings.d.ts +3 -0
  112. package/dist/cjs/utils/reportSavings.d.ts.map +1 -1
  113. package/dist/cjs/utils/reportSavings.js +44 -5
  114. package/dist/cjs/utils/reportSavings.js.map +1 -1
  115. package/dist/esm/cli/hostHooks.d.ts +14 -1
  116. package/dist/esm/cli/hostHooks.d.ts.map +1 -1
  117. package/dist/esm/cli/hostHooks.js +52 -5
  118. package/dist/esm/cli/hostHooks.js.map +1 -1
  119. package/dist/esm/cli/lemma-proxy.d.ts.map +1 -1
  120. package/dist/esm/cli/lemma-proxy.js +30 -11
  121. package/dist/esm/cli/lemma-proxy.js.map +1 -1
  122. package/dist/esm/contracts/evidence.d.ts +8 -1
  123. package/dist/esm/contracts/evidence.d.ts.map +1 -1
  124. package/dist/esm/mcp/index.js +20 -1
  125. package/dist/esm/mcp/index.js.map +1 -1
  126. package/dist/esm/mcp/prompts.d.ts +1 -1
  127. package/dist/esm/mcp/prompts.d.ts.map +1 -1
  128. package/dist/esm/mcp/prompts.js +6 -16
  129. package/dist/esm/mcp/prompts.js.map +1 -1
  130. package/dist/esm/mcp/tool-profiles.d.ts.map +1 -1
  131. package/dist/esm/mcp/tool-profiles.js +8 -4
  132. package/dist/esm/mcp/tool-profiles.js.map +1 -1
  133. package/dist/esm/mcp/tools/context-tools.d.ts.map +1 -1
  134. package/dist/esm/mcp/tools/context-tools.js +21 -17
  135. package/dist/esm/mcp/tools/context-tools.js.map +1 -1
  136. package/dist/esm/mcp/tools/memory.d.ts.map +1 -1
  137. package/dist/esm/mcp/tools/memory.js +26 -5
  138. package/dist/esm/mcp/tools/memory.js.map +1 -1
  139. package/dist/esm/mcp/tools/workspace.d.ts.map +1 -1
  140. package/dist/esm/mcp/tools/workspace.js +98 -55
  141. package/dist/esm/mcp/tools/workspace.js.map +1 -1
  142. package/dist/esm/mcp/tools.d.ts.map +1 -1
  143. package/dist/esm/mcp/tools.js +94 -33
  144. package/dist/esm/mcp/tools.js.map +1 -1
  145. package/dist/esm/mcp/utils.d.ts.map +1 -1
  146. package/dist/esm/mcp/utils.js +6 -1
  147. package/dist/esm/mcp/utils.js.map +1 -1
  148. package/dist/esm/mcp/workspaceShared.d.ts.map +1 -1
  149. package/dist/esm/mcp/workspaceShared.js +14 -9
  150. package/dist/esm/mcp/workspaceShared.js.map +1 -1
  151. package/dist/esm/proxy/KeyManager.d.ts +10 -0
  152. package/dist/esm/proxy/KeyManager.d.ts.map +1 -1
  153. package/dist/esm/proxy/KeyManager.js +43 -6
  154. package/dist/esm/proxy/KeyManager.js.map +1 -1
  155. package/dist/esm/security/SemanticScrubber.d.ts.map +1 -1
  156. package/dist/esm/security/SemanticScrubber.js +19 -1
  157. package/dist/esm/security/SemanticScrubber.js.map +1 -1
  158. package/dist/esm/subconscious/HostRecall.d.ts +7 -5
  159. package/dist/esm/subconscious/HostRecall.d.ts.map +1 -1
  160. package/dist/esm/subconscious/HostRecall.js +115 -14
  161. package/dist/esm/subconscious/HostRecall.js.map +1 -1
  162. package/dist/esm/subconscious/TheBrainV2.d.ts +6 -1
  163. package/dist/esm/subconscious/TheBrainV2.d.ts.map +1 -1
  164. package/dist/esm/subconscious/TheBrainV2.js +21 -9
  165. package/dist/esm/subconscious/TheBrainV2.js.map +1 -1
  166. package/dist/esm/subconscious/freshness/LocalFsResolver.d.ts +3 -0
  167. package/dist/esm/subconscious/freshness/LocalFsResolver.d.ts.map +1 -1
  168. package/dist/esm/subconscious/freshness/LocalFsResolver.js +32 -14
  169. package/dist/esm/subconscious/freshness/LocalFsResolver.js.map +1 -1
  170. package/dist/esm/utils/CommandOutputDistiller.d.ts +10 -0
  171. package/dist/esm/utils/CommandOutputDistiller.d.ts.map +1 -1
  172. package/dist/esm/utils/CommandOutputDistiller.js +39 -2
  173. package/dist/esm/utils/CommandOutputDistiller.js.map +1 -1
  174. package/dist/esm/utils/ContextSqueezer.d.ts +13 -1
  175. package/dist/esm/utils/ContextSqueezer.d.ts.map +1 -1
  176. package/dist/esm/utils/ContextSqueezer.js +175 -3
  177. package/dist/esm/utils/ContextSqueezer.js.map +1 -1
  178. package/dist/esm/utils/FixCapture.d.ts.map +1 -1
  179. package/dist/esm/utils/FixCapture.js +56 -0
  180. package/dist/esm/utils/FixCapture.js.map +1 -1
  181. package/dist/esm/utils/LineNumberMap.d.ts.map +1 -1
  182. package/dist/esm/utils/LineNumberMap.js +27 -0
  183. package/dist/esm/utils/LineNumberMap.js.map +1 -1
  184. package/dist/esm/utils/PatchMatcher.d.ts.map +1 -1
  185. package/dist/esm/utils/PatchMatcher.js +26 -0
  186. package/dist/esm/utils/PatchMatcher.js.map +1 -1
  187. package/dist/esm/utils/ProLicense.d.ts +2 -0
  188. package/dist/esm/utils/ProLicense.d.ts.map +1 -0
  189. package/dist/esm/utils/ProLicense.js +25 -0
  190. package/dist/esm/utils/ProLicense.js.map +1 -0
  191. package/dist/esm/utils/RevivalThrottle.d.ts +9 -0
  192. package/dist/esm/utils/RevivalThrottle.d.ts.map +1 -0
  193. package/dist/esm/utils/RevivalThrottle.js +22 -0
  194. package/dist/esm/utils/RevivalThrottle.js.map +1 -0
  195. package/dist/esm/utils/SavingsLedger.d.ts +58 -18
  196. package/dist/esm/utils/SavingsLedger.d.ts.map +1 -1
  197. package/dist/esm/utils/SavingsLedger.js +332 -179
  198. package/dist/esm/utils/SavingsLedger.js.map +1 -1
  199. package/dist/esm/utils/SearchCache.d.ts +2 -2
  200. package/dist/esm/utils/SearchCache.d.ts.map +1 -1
  201. package/dist/esm/utils/SearchCache.js +8 -6
  202. package/dist/esm/utils/SearchCache.js.map +1 -1
  203. package/dist/esm/utils/SessionCapture.d.ts +65 -0
  204. package/dist/esm/utils/SessionCapture.d.ts.map +1 -0
  205. package/dist/esm/utils/SessionCapture.js +328 -0
  206. package/dist/esm/utils/SessionCapture.js.map +1 -0
  207. package/dist/esm/utils/StaleDelta.d.ts +2 -0
  208. package/dist/esm/utils/StaleDelta.d.ts.map +1 -0
  209. package/dist/esm/utils/StaleDelta.js +54 -0
  210. package/dist/esm/utils/StaleDelta.js.map +1 -0
  211. package/dist/esm/utils/TokenBudgetedReader.d.ts.map +1 -1
  212. package/dist/esm/utils/TokenBudgetedReader.js +56 -15
  213. package/dist/esm/utils/TokenBudgetedReader.js.map +1 -1
  214. package/dist/esm/utils/WorkspaceSearch.d.ts +8 -1
  215. package/dist/esm/utils/WorkspaceSearch.d.ts.map +1 -1
  216. package/dist/esm/utils/WorkspaceSearch.js +74 -5
  217. package/dist/esm/utils/WorkspaceSearch.js.map +1 -1
  218. package/dist/esm/utils/reportSavings.d.ts +3 -0
  219. package/dist/esm/utils/reportSavings.d.ts.map +1 -1
  220. package/dist/esm/utils/reportSavings.js +42 -5
  221. package/dist/esm/utils/reportSavings.js.map +1 -1
  222. package/package.json +4 -4
  223. package/dashboard/dist/assets/index-DzL_Zu3m.js +0 -452
  224. package/dist/cjs/examples/basic-usage.d.ts +0 -12
  225. package/dist/cjs/examples/basic-usage.d.ts.map +0 -1
  226. package/dist/cjs/examples/basic-usage.js +0 -128
  227. package/dist/cjs/examples/basic-usage.js.map +0 -1
  228. package/dist/esm/examples/basic-usage.d.ts +0 -12
  229. package/dist/esm/examples/basic-usage.d.ts.map +0 -1
  230. package/dist/esm/examples/basic-usage.js +0 -126
  231. package/dist/esm/examples/basic-usage.js.map +0 -1
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # 🧠 Lemma
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@nxuss/lemma.svg?color=orange&style=flat-square)](https://www.npmjs.com/package/@nxuss/lemma)
4
- [![License](https://img.shields.io/npm/l/@nxuss/lemma.svg?color=blue&style=flat-square)](https://github.com/Nxusbets/lemma/blob/main/LICENSE)
5
- [![MCP](https://img.shields.io/badge/MCP-91%20tools-ff69b4?style=flat-square)](https://github.com/Nxusbets/lemma)
6
- [![Local-first](https://img.shields.io/badge/local--first-no%20cloud%20required-success?style=flat-square)](https://github.com/Nxusbets/lemma)
4
+ ![License](https://img.shields.io/npm/l/@nxuss/lemma.svg?color=blue&style=flat-square)
5
+ ![MCP](https://img.shields.io/badge/MCP-91%20tools-ff69b4?style=flat-square)
6
+ ![Local-first](https://img.shields.io/badge/local--first-no%20cloud%20required-success?style=flat-square)
7
7
 
8
8
  **A memory your AI agent can verify, and a context window that only holds what matters.**
9
9
 
@@ -19,6 +19,12 @@ lemma init # wires Claude Code, Cursor, Codex, Windsurf, Kiro, OpenCode, Mu
19
19
 
20
20
  That's it: your agent now has Lemma's tools. The dashboard opens at `http://localhost:8081/dashboard/`.
21
21
 
22
+ > **Run `lemma init` in every project, and again after each upgrade.** `npm install -g` updates Lemma's
23
+ > code, but part of what Lemma does lives in files `init` writes into *your project*: the rules your agent
24
+ > reads (`CLAUDE.md` / `AGENTS.md`), the pre-approved tool permissions, and the hooks for the other clients.
25
+ > Without it you keep the old rules and keep seeing permission prompts. Then reconnect the MCP server
26
+ > (`/mcp` in Claude Code) so the running session loads the new build. [What an upgrade does on its own](#after-an-upgrade).
27
+
22
28
  ---
23
29
 
24
30
  ## Why Lemma
@@ -28,6 +34,15 @@ in a local index (BM25, no embeddings service, no cloud). Every memory is tied t
28
34
  symbols it's about, so when the code changes it comes back **stale** instead of confidently
29
35
  wrong — and a memory with no evidence is labelled as such, never sold as a verified answer.
30
36
 
37
+ **↪ Work that survives the session (Pro).** A session that edited code leaves a hand-off built from the
38
+ transcript and git, with no model tokens: the task exactly as it was given, the files changed, the last test
39
+ run. The next session's briefing points at it, so "continue where we left off" has something to continue from.
40
+ An agent's own note tends to keep function names and drop their parameter lists; the hand-off keeps the plan
41
+ verbatim. In our benchmark (a plan said only in the first session, graded by hidden tests) agents with Lemma
42
+ finished it in 3 of 3 runs, Claude Code's built-in memory in 1 of 3 and another agent-memory tool in 0 of 3 (3-run
43
+ cells, one model, our own benchmark: a lead, not a proof). It costs more per task, not less, because the agent
44
+ does the work the others skip.
45
+
31
46
  **✍️ It fills itself.** A test, build or lint command that goes red → green is saved as
32
47
  `fix: <the error>` with the diff that fixed it. Commits become memories. Next time the same
33
48
  error shows up, the agent is pointed at the fix — through hooks in Claude Code, Codex, Cursor
@@ -60,14 +75,15 @@ pretending (`capability_matrix`).
60
75
 
61
76
  ## Free vs Pro
62
77
 
63
- 41 of the 91 tools are free, including all memory, upkeep and export tools. Pro adds writing and patching,
64
- `search_workspace`, `run_workspace_command`, static analysis, PR review and auto-heal.
78
+ Free covers what your agent can already do natively (reading, searching, running commands, patching) plus the
79
+ memory you write and read yourself, upkeep and export. Pro is what keeps memories true over time and carries work
80
+ across sessions: `verify_memory`, `refresh_memory`, `brain_team`, and the automatic end-of-session capture and hand-off.
65
81
 
66
82
  | | Free | Pro |
67
83
  | :--- | :--- | :--- |
68
- | Memory | `search_memory`, `store_memory`, `get_memory`, `update_memory`, `verify_memory`, `brain_team`, `brain_upkeep`, `brain_export`… | `get_telepathic_hints`, `semantic_dedup_guard`, `local_semantic_autofix` |
69
- | Context | `read_workspace_file`, `scrub_privacy`, `token_budget`, `token_receipt`, `auto_context_bundle` | `get_ast_hologram`, `smart_file_slice`, `get_symbol_surgical_context` |
70
- | Workspace | — | `apply_workspace_patch`, `write_workspace_file`, `run_workspace_command`, `validate_patch_sandbox`, `surgical_ast_insert` |
84
+ | Memory | `search_memory`, `store_memory`, `get_memory`, `update_memory`, `forget_memory`, `brain_upkeep`, `brain_export`… | `verify_memory`, `refresh_memory`, `brain_team`, automatic session capture and hand-off, `get_telepathic_hints`, `semantic_dedup_guard`, `local_semantic_autofix` |
85
+ | Context | `read_workspace_file`, `get_ast_hologram`, `output_region`, `scrub_privacy`, `token_budget`, `token_receipt`, `auto_context_bundle` | `smart_file_slice`, `get_symbol_surgical_context` |
86
+ | Workspace | `search_workspace`, `run_workspace_command`, `apply_workspace_patch` | `write_workspace_file`, `validate_patch_sandbox`, `surgical_ast_insert` |
71
87
  | Analysis & review | `type_coverage_report`, `git_blame_heat` | `depgraph`, `entropy_score`, `refactor`, `review_diff`, `review_pr`, `test_oracle` |
72
88
  | Monolith analysis | `infra_analyze_monolith`, `infra_suggest_split` (not advertised; via `lemma_toolbox`) | — |
73
89
 
@@ -89,9 +105,25 @@ Without a license, Pro tools aren't advertised to the model at all. Full list:
89
105
  `post-checkout`, and post-tool hooks for Claude Code, Codex, Cursor and OpenCode). In Claude Code
90
106
  it adds a `SessionStart` briefing and a prompt-time recall too, and pre-approves the read and
91
107
  memory tools so using them never costs a permission prompt. Existing hooks are kept; `--no-hooks`
92
- skips them. An install from an older version gets the missing Claude Code hooks the next time the
93
- MCP server starts; re-run `lemma init` after upgrading to refresh the agent rules. Health check:
94
- `lemma doctor`.
108
+ skips them. Health check: `lemma doctor`.
109
+
110
+ ### After an upgrade
111
+
112
+ After `npm install -g @nxuss/lemma` and reconnecting the MCP server, a project that already ran
113
+ `lemma init` gets some things by itself and needs `lemma init` again for the rest:
114
+
115
+ | | Comes with the upgrade | Needs `lemma init` again |
116
+ | :--- | :---: | :---: |
117
+ | New and fixed tool behaviour (freshness checks, the diff shown on a STALE memory, the hand-off logic) | ✅ | |
118
+ | Missing Claude Code hooks (`SessionStart`, prompt recall, the note before an edit, `SessionEnd`), added at the next MCP start | ✅ | |
119
+ | Agent rules in `CLAUDE.md` / `AGENTS.md` (when to search, store and leave a hand-off) | | ✅ |
120
+ | Pre-approved read and memory tool permissions (otherwise the agent is asked each time) | | ✅ |
121
+ | Hooks and config for Codex, Cursor, OpenCode, Windsurf and Kiro | | ✅ |
122
+ | A project that never ran `lemma init` | | ✅ |
123
+
124
+ The startup repair adds hooks but never changes what your agent may do without asking, which is why
125
+ permissions and rules wait for `lemma init`. It keeps what you wrote in those files and only replaces
126
+ Lemma's own block.
95
127
 
96
128
  ### CLI
97
129
 
@@ -119,8 +151,7 @@ MCP server starts; re-run `lemma init` after upgrading to refresh the agent rule
119
151
  }
120
152
  ```
121
153
 
122
- `LEMMA_BRAIN_ENCRYPT=1` stores the Brain AES-256-GCM encrypted at rest. Security-related
123
- environment variables are documented in [`.env.security.example`](.env.security.example).
154
+ `LEMMA_BRAIN_ENCRYPT=1` stores the Brain AES-256-GCM encrypted at rest.
124
155
 
125
156
  ### Proxy mode (optional)
126
157
 
@@ -130,4 +161,4 @@ project, never served across projects.
130
161
 
131
162
  ---
132
163
 
133
- Changelog: [`CHANGELOG.md`](CHANGELOG.md) · MIT © Nxus Studio · [Lemma Pro](https://lemma.nxus.studio/upgrade)
164
+ MIT © Nxus Studio · [lemma.nxus.studio](https://lemma.nxus.studio) · [Lemma Pro](https://lemma.nxus.studio/upgrade)
@@ -16,6 +16,8 @@
16
16
  * Usage: lemma-brain-ingest [projectRoot] [--commits [--max N]] [--changelog] [--team-pull]
17
17
  * lemma-brain-ingest --hook-result [--host=claude|codex|cursor|opencode] < payload.json
18
18
  * lemma-brain-ingest --prompt-submit < payload.json
19
+ * lemma-brain-ingest --pre-tool < payload.json
20
+ * lemma-brain-ingest --session-end < payload.json
19
21
  *
20
22
  * --team-pull Import the repo's shared memories from .lemma-brain/ (post-merge/post-checkout).
21
23
  * --hook-result Read a host PostToolUse hook payload on stdin (Claude Code shape) and:
@@ -123,6 +125,29 @@ async function promptSubmit() {
123
125
  await reportOffered(dist, payload && payload.cwd, [recall]);
124
126
  }
125
127
 
128
+ // PreToolUse hook (Claude Code), edits only: the memories about the file, before it is written.
129
+ // Prints context and nothing else — never a permission decision, so it cannot stop or prompt a tool.
130
+ async function preTool() {
131
+ const dist = path.join(__dirname, '..', 'dist', 'cjs');
132
+ let preEditRecall, formatHostOutput;
133
+ try {
134
+ ({ preEditRecall } = require(path.join(dist, 'subconscious', 'HostRecall.js')));
135
+ ({ formatHostOutput } = require(path.join(dist, 'subconscious', 'HostAdapters.js')));
136
+ } catch {
137
+ return; // no build — a hook must stay silent and harmless
138
+ }
139
+ let payload;
140
+ try {
141
+ payload = JSON.parse(readStdin());
142
+ } catch {
143
+ return;
144
+ }
145
+ const recall = preEditRecall(payload, payload && payload.cwd);
146
+ const out = formatHostOutput('claude', recall ? recall.text : '', 'PreToolUse');
147
+ if (out) process.stdout.write(out);
148
+ await reportOffered(dist, payload && payload.cwd, [recall]);
149
+ }
150
+
126
151
  // SessionStart hook (Claude Code): the Brain's briefing and the Lemma playbook, in context
127
152
  // before the first prompt — no tool call for the agent to remember or skip.
128
153
  async function sessionStart() {
@@ -155,10 +180,26 @@ async function sessionStart() {
155
180
  await reportOffered(dist, payload.cwd, [recall]);
156
181
  }
157
182
 
183
+ // SessionEnd hook (Claude Code): a session that did real work and stored nothing leaves its closing
184
+ // answer in the Brain (see SessionCapture). Silent: the host shows nothing at session end.
185
+ async function sessionEnd() {
186
+ const dist = path.join(__dirname, '..', 'dist', 'cjs');
187
+ let payload = {};
188
+ try {
189
+ payload = JSON.parse(readStdin()) || {};
190
+ } catch {
191
+ return;
192
+ }
193
+ const { captureSession } = require(path.join(dist, 'utils', 'SessionCapture.js'));
194
+ captureSession(payload, payload.cwd);
195
+ }
196
+
158
197
  async function main() {
159
198
  const args = process.argv.slice(2);
199
+ if (args.includes('--session-end')) return sessionEnd();
160
200
  if (args.includes('--session-start')) return sessionStart();
161
201
  if (args.includes('--prompt-submit')) return promptSubmit();
202
+ if (args.includes('--pre-tool')) return preTool();
162
203
  if (args.includes('--hook-result')) return hookResult(args);
163
204
  const maxIndex = args.indexOf('--max');
164
205
  const maxCommits = maxIndex >= 0 ? parseInt(args[maxIndex + 1], 10) : undefined;
package/bin/init.js CHANGED
@@ -261,94 +261,39 @@ function loadLemmaToolNames() {
261
261
 
262
262
  const LEMMA_TOOL_NAMES = loadLemmaToolNames();
263
263
  // Pre-approved in Claude Code: reading, searching and the Brain's lifecycle — local, and nothing
264
- // a native Read/Grep couldn't already do. Tools that write code (apply_workspace_patch,
265
- // surgical_ast_insert), run commands (run_workspace_command), delete (forget_memory) or
266
- // dispatch arbitrary tools (lemma_toolbox) still ask.
264
+ // a native Read/Grep couldn't already do — plus run_workspace_command. Tools that write code
265
+ // (apply_workspace_patch, surgical_ast_insert), delete (forget_memory) or dispatch arbitrary tools
266
+ // (lemma_toolbox) still ask.
267
+ //
268
+ // run_workspace_command is pre-approved on purpose, and it is a real trade-off: a tool that asks on
269
+ // every call is one agents stop using, and it is the one that turns a 70k-character test run into
270
+ // its failures (measured). What it lets through without a prompt is whatever Lemma's own allowlist
271
+ // permits (npm, git, node, python, make, docker… — deny-by-default, no redirection or substitution),
272
+ // which is more than a native Bash rule like `Bash(npx jest *)` would. To tighten it: set
273
+ // mcp.confirmActions or mcp.sandbox (docker) in lemma.config.json, or remove the entry from
274
+ // permissions.allow.
267
275
  const LEMMA_PREAPPROVED_TOOLS = [
268
276
  'search_memory', 'get_memory', 'verify_memory', 'store_memory', 'update_memory', 'refresh_memory',
269
277
  'downvote_memory', 'read_workspace_file', 'list_workspace_dir', 'search_workspace', 'smart_file_slice',
270
278
  'get_ast_hologram', 'get_symbol_surgical_context', 'output_region', 'get_project_onboarding',
271
- 'state_hash_cache', 'smarter_cache', 'token_receipt', 'get_routing_advice',
279
+ 'state_hash_cache', 'smarter_cache', 'token_receipt', 'get_routing_advice', 'run_workspace_command',
272
280
  ].map((name) => `mcp__lemma__${name}`);
273
281
 
274
282
  const CLAUDE_MD_CONTENT = `# Lemma MCP — Cómo usar las herramientas de este proyecto
275
283
 
276
- Tienes conectado el servidor MCP \`lemma\`. Lo que hace que las herramientas nativas no pueden
277
- es **recordar entre sesiones**: el Brain guarda fixes y decisiones verificadas, y avisa cuando el
278
- código del que dependen cambió. Ese valor solo existe si se consulta y se alimenta, así que estos
279
- momentos no son opcionales:
280
-
281
- ## Disparadores — haz esto siempre
282
-
283
- | Cuando… | Llama | Por qué no basta lo nativo |
284
- |---|---|---|
285
- | Vas a investigar un bug, un error o un "¿por qué X?" | \`mcp__lemma__search_memory\` con el texto del error o la pregunta | Puede que ya esté resuelto; \`grep\` no encuentra lo que se aprendió en otra sesión |
286
- | Un aviso 🧠 de Lemma nombra memorias | \`mcp__lemma__get_memory\` de la primera | El aviso trae solo el titular; el cuerpo tiene los archivos y el porqué |
287
- | Un fix quedó verificado (tests pasan, build ok, el usuario confirmó) | \`mcp__lemma__store_memory\` con \`filePaths\`: la causa raíz, la trampa o lo que no funcionó — no un resumen de qué cambió | Si no se guarda, la próxima sesión lo vuelve a investigar desde cero; un resumen de cambios ya está en git log y nadie lo vuelve a buscar |
288
- | Una memoria resultó desactualizada o incorrecta | \`mcp__lemma__update_memory\` / \`forget_memory\` | Guardar otra al lado deja dos versiones y la búsqueda mezcla ambas |
289
- | Vas a leer código que aún no ubicaste, o que puede tener secretos | \`mcp__lemma__search_workspace\` / \`read_workspace_file\` | Enmascaran API keys antes de que entren a tu contexto |
290
-
291
- Si las tools de Lemma te llegan diferidas (solo el nombre), cárgalas en **una sola** llamada a
292
- ToolSearch: \`select:mcp__lemma__search_memory,mcp__lemma__get_memory,mcp__lemma__verify_memory,mcp__lemma__store_memory,mcp__lemma__update_memory,mcp__lemma__read_workspace_file,mcp__lemma__search_workspace\`.
293
-
294
- Fuera de esos momentos usa tu criterio: para un archivo chico que ya ubicaste, \`Read\` nativo
295
- cuesta menos que el ida y vuelta, y para procesos, puertos o git Lemma no tiene nada que aportar.
296
-
297
- ## Referencia: qué tool para qué
298
-
299
- | Tarea | Tool de Lemma | Por qué |
300
- |---|---|---|
301
- | Leer/listar/buscar en el workspace | \`mcp__lemma__read_workspace_file\`, \`list_workspace_dir\`, \`search_workspace\` | Auto-scrubbing de secretos y compresión de contexto sin perder el archivo real |
302
- | Pregunta puntual, de una sola sesión, atada a archivos concretos | \`mcp__lemma__state_hash_cache\` (action="lookup" antes de razonar, "store" después) | Hit solo si el hash exacto de esos archivos no cambió — invalidación exacta, no una adivinanza por similitud. Match de texto exacto en la query: no sirve para "¿algo así ya se resolvió?", solo para "esta misma pregunta, ¿estos archivos siguen igual?" |
303
- | Conocimiento que debe sobrevivir sesiones, con seguimiento por símbolo (no todo el archivo) | \`mcp__lemma__search_memory\` / \`store_memory\` | Motor con más garantías: invalida por símbolo específico (no por archivo completo), distingue cambio de código real vs. cambio de branch, y sigue cadenas de memorias derivadas — no solo "sin dependencia de archivos", también rastrea archivos con más precisión que state_hash_cache |
304
- | Pregunta general, sin dependencia de archivos ni necesidad de sobrevivir sesiones | \`mcp__lemma__smarter_cache\` | Cache semántico difuso; si hay hit, dilo explícitamente ("esto viene de una respuesta cacheada") en vez de presentarlo como recién razonado |
305
- | Tarea mecánica/trivial y quieres saber si conviene un modelo más barato | \`mcp__lemma__get_routing_advice\` | Evita gastar presupuesto de modelo grande en algo mecánico |
306
- | Agregar un método/función a un archivo existente | \`mcp__lemma__surgical_ast_insert\` | Patch quirúrgico en vez de reescribir el archivo completo |
307
- | Antes de aplicar un patch generado | \`mcp__lemma__validate_patch_sandbox\` | Valídalo antes de escribirlo, no después |
308
- | "¿Cuánto ahorramos de verdad?" | \`mcp__lemma__token_receipt\` | Bitácora auditable real, no una cifra inventada |
309
- | Leer una memoria por su id (antes de editarla, o para un id que traes de otra sesión) | \`mcp__lemma__get_memory\` | Lectura determinista: \`search_memory\` es un ranking y puede no devolver la entrada que buscas. Editar un documento vivo sin leerlo antes es editarlo a ciegas |
310
- | Corregir o extender una memoria que ya existe | \`mcp__lemma__update_memory\` | Reescribe en su sitio conservando id, hits y las aristas \`derivedFrom\`. \`store_memory\` siempre crea un id nuevo: re-guardar una versión corregida deja las dos y la búsqueda devuelve una mezcla arbitraria de ambas |
311
- | Guardar una solución para el futuro | \`mcp__lemma__store_memory\` | Solo después de verificarla (tests pasaron, el usuario confirmó) — una solución mala guardada se vuelve un cache hit falso más adelante |
312
- | Una memoria salió STALE pero la verificaste y sigue siendo correcta | \`mcp__lemma__refresh_memory\` | Re-ancla la evidencia conservando id, hits y las aristas \`derivedFrom\`; guardar un casi-duplicado pierde las tres (y el dedup suele rechazarlo) |
313
- | Una memoria está simplemente mal, no solo mal encajada | \`mcp__lemma__forget_memory\` | Borrado real. Para "no era lo que buscaba en esta consulta" usa \`downvote_memory\`: esa memoria puede ser la correcta para otra pregunta |
314
-
315
- El ciclo de vida completo (\`search\` → \`get\` → \`update\`/\`refresh\`/\`forget\`) está anunciado:
316
- ante una memoria desactualizada, corrígela; no guardes otra al lado.
317
-
318
- ## No todas las tools de Lemma están en tu lista
319
-
320
- Cada schema que Lemma anuncia se re-envía en **cada** request de la sesión, así que solo se
321
- anuncian las que este proyecto puede usar de verdad. El resto sigue registrado y disponible:
322
-
323
- - \`mcp__lemma__lemma_toolbox\` con \`action="list"\` (y un \`query\`) para ver qué existe.
324
- - \`action="schema"\` para leer los inputs de una tool antes de llamarla.
325
- - \`action="call"\` para ejecutarla.
326
-
327
- Antes de decirle al usuario que Lemma no puede hacer algo, revisa el toolbox. Si el proyecto
328
- necesita todas las tools anunciadas de forma permanente, pon \`mcp.toolProfile: "full"\` en
329
- \`lemma.config.json\`.
330
-
331
- ## Antes de pedirle código al usuario
332
-
333
- No le pidas que pegue archivos, logs o documentación que puedes obtener tú: agota primero
334
- \`search_workspace\`, \`get_ast_hologram\` (estructura: da nombres y líneas, no firmas) y
335
- \`read_workspace_file\`. Cada ida y vuelta con el usuario cuesta más que la lectura.
336
-
337
- ## Al iniciar en este proyecto
338
-
339
- El hook \`SessionStart\` de Lemma ya te dio un resumen del Brain al arrancar (si no lo ves,
340
- \`lemma doctor\` dice qué falta). Para más contexto: \`mcp__lemma__get_project_onboarding\`.
341
- Lo que venga del Brain no es un hecho hasta verificarlo contra el estado actual del código.
342
-
343
- ## Al investigar un bug, además
284
+ El servidor MCP \`lemma\` recuerda entre sesiones y avisa cuando el código del que depende una memoria cambió. Úsalo en estos momentos:
344
285
 
345
- \`lemma_toolbox\` → \`get_telepathic_hints\` con el archivo relevante para contexto de runtime.
286
+ ## Disparadores
346
287
 
347
- ## Una sola regla dura
288
+ - Vas a investigar un bug, un error o un "¿por qué X?" → \`mcp__lemma__search_memory\` con el error o la pregunta.
289
+ - Un aviso 🧠 nombra memorias → \`mcp__lemma__get_memory\` de la primera.
290
+ - Un fix quedó verificado (tests pasan) → \`mcp__lemma__store_memory\` con \`filePaths\`: la causa raíz o la trampa, no un resumen del cambio.
291
+ - Terminas una sesión con trabajo a medias o por partes → \`store_memory\` (o \`update_memory\` si ya hay una) con lo HECHO, lo PENDIENTE y los nombres/firmas exactos de lo especificado; no resumas la especificación.
292
+ - Una memoria quedó desactualizada → \`update_memory\` (o \`forget_memory\`); no guardes otra al lado.
293
+ - Tests, build o lint con salida larga → \`mcp__lemma__run_workspace_command\`: devuelve los fallos y el resumen; el resto se abre con \`output_region\`.
294
+ - Código que aún no ubicaste o que puede tener secretos → \`search_workspace\` / \`read_workspace_file\`.
348
295
 
349
- Cualquier respuesta que dependa de un cache hit, un fix auto-sugerido, o un archivo comprimido
350
- para tu propio contexto debe entregarse con la información real y sin ocultar que vino de ahí.
351
- Ahorrar tokens nunca debe costar que el usuario reciba algo incorrecto sin saberlo.
296
+ Si las tools de Lemma llegan diferidas (solo el nombre), cárgalas en una sola llamada a ToolSearch. Las demás están en \`mcp__lemma__lemma_toolbox\` (\`list\`, \`schema\`, \`call\`). No le pidas al usuario código que puedas leer tú. Si una respuesta viene de cache o de un archivo comprimido, dilo.
352
297
 
353
298
  ---
354
299
  *Auto-generado por \`lemma init\` — @nxuss/lemma*
@@ -488,6 +433,7 @@ OpenCode).
488
433
  | Investigating a bug, an error, or a "why does X" | \`search_memory\` with the error text or question | grep can't find what was learned in another session |
489
434
  | A 🧠 Lemma note names memories | \`get_memory\` on the top one | The note carries the headline; the body has the files and the reason |
490
435
  | A fix is verified (tests pass, build ok, user confirmed) | \`store_memory\` with \`filePaths\`: the root cause, the trap or what didn't work — not a summary of what changed | Unsaved, the next session investigates it from scratch; a change summary is already in git log and nobody searches for it |
436
+ | You end a session with work half done or split in parts | \`store_memory\` (or \`update_memory\` if one exists): what is DONE, what is PENDING, and the exact names/signatures that were specified — don't summarise the spec | The next session starts with no memory of the plan; a vague note loses the details |
491
437
  | A memory turned out outdated or wrong | \`update_memory\` / \`forget_memory\` | A near-duplicate leaves two versions and search mixes them |
492
438
  | Reading code you haven't located yet, or that may hold secrets | \`search_workspace\` / \`read_workspace_file\` | They mask API keys before they reach your context |
493
439
  | Applying a generated patch | \`validate_patch_sandbox\` first | Validate before writing, not after |
@@ -990,7 +936,7 @@ function applyEnvToCurrentProcess() {
990
936
  // Upstream API keys live here (mode 0600) instead of inside the launchd/systemd
991
937
  // unit: the proxy daemon loads this file at startup (lemma-proxy.cjs loadEnvFile),
992
938
  // so a background service gets credentials without baking secrets into a unit file.
993
- function createEnvFile(envFile = ENV_FILE) {
939
+ function createEnvFile(envFile = ENV_FILE, { routing = true } = {}) {
994
940
  const proxyUrl = `http://localhost:${PORT}`;
995
941
  const keyLines = [
996
942
  process.env.ANTHROPIC_API_KEY ? `export ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}` : '',
@@ -1001,14 +947,14 @@ function createEnvFile(envFile = ENV_FILE) {
1001
947
  # source ${envFile}
1002
948
  export LEMMA_PROXY_PORT=${PORT}
1003
949
  export LEMMA_CACHE_DIR=${LEMMA_CACHE}
1004
- export OPENAI_BASE_URL=${proxyUrl}
950
+ ${routing ? `export OPENAI_BASE_URL=${proxyUrl}
1005
951
  export OPENAI_API_BASE=${proxyUrl}
1006
952
  export CODEX_BASE_URL=${proxyUrl}
1007
953
  export LITELLM_PROXY_BASE_URL=${proxyUrl}
1008
- ${keyLines ? keyLines + '\n' : ''}`;
954
+ ` : ''}${keyLines ? keyLines + '\n' : ''}`;
1009
955
  fs.writeFileSync(envFile, envContent, 'utf8');
1010
956
  try { fs.chmodSync(envFile, 0o600); } catch {}
1011
- applyEnvToCurrentProcess();
957
+ if (routing) applyEnvToCurrentProcess();
1012
958
  return envFile;
1013
959
  }
1014
960
 
@@ -1048,6 +994,44 @@ function setupShellIntegration() {
1048
994
  return configured;
1049
995
  }
1050
996
 
997
+ /**
998
+ * Undo what an earlier `lemma init` appended to the shell: the two lines that source the env file
999
+ * (only that exact block — anything the user wrote, or commented out, is left alone) and the
1000
+ * launchctl variables, only when they still hold the value we set.
1001
+ *
1002
+ * Routing OPENAI_BASE_URL & co. through localhost:8081 is only safe while the proxy is guaranteed
1003
+ * to be up, which is what --daemon installs. Without it, the first time the proxy was not running
1004
+ * every OpenAI-compatible tool in every new terminal (and GUI apps, via launchctl) failed with
1005
+ * 'connection refused'.
1006
+ */
1007
+ function removeShellIntegration(home = os.homedir()) {
1008
+ const block = /\n?# Lemma Cache Proxy — auto-configured by `lemma init`\n\[ -f "[^"\n]*lemma-cache\/env" \] && \. "[^"\n]*lemma-cache\/env"\n?/g;
1009
+ let removed = false;
1010
+ for (const file of [path.join(home, '.zshrc'), path.join(home, '.bashrc'), path.join(home, '.config', 'fish', 'config.fish')]) {
1011
+ try {
1012
+ if (!fs.existsSync(file)) continue;
1013
+ const content = fs.readFileSync(file, 'utf8');
1014
+ const next = content.replace(block, '\n');
1015
+ if (next !== content) {
1016
+ fs.writeFileSync(file, next, 'utf8');
1017
+ removed = true;
1018
+ }
1019
+ } catch {}
1020
+ }
1021
+ if (process.platform === 'darwin') {
1022
+ for (const name of ['OPENAI_BASE_URL', 'CODEX_BASE_URL']) {
1023
+ try {
1024
+ const current = String(execSync(`launchctl getenv ${name}`, { stdio: ['ignore', 'pipe', 'ignore'] }) || '').trim();
1025
+ if (current === `http://localhost:${PORT}`) {
1026
+ execSync(`launchctl unsetenv ${name}`, { stdio: 'ignore' });
1027
+ removed = true;
1028
+ }
1029
+ } catch {}
1030
+ }
1031
+ }
1032
+ return removed;
1033
+ }
1034
+
1051
1035
  async function main(argv = process.argv) {
1052
1036
  const opts = parseInitArgs(argv);
1053
1037
  if (opts.showHelp) {
@@ -1150,7 +1134,7 @@ async function main(argv = process.argv) {
1150
1134
  });
1151
1135
  if (hasCodex) {
1152
1136
  const codexConfigured = configureCodexCliMcp();
1153
- if (codexConfigured) ok('Codex CLI configured (MCP + OPENAI_BASE_URL env)');
1137
+ if (codexConfigured) ok('Codex CLI configured (MCP)');
1154
1138
  else warn('Codex CLI detected but MCP config could not be auto-updated');
1155
1139
  } else skip('Codex CLI not detected');
1156
1140
 
@@ -1176,12 +1160,20 @@ async function main(argv = process.argv) {
1176
1160
  skip('Brain hooks not installed (--no-hooks)');
1177
1161
  }
1178
1162
 
1179
- const envFile = createEnvFile();
1163
+ // Routing model traffic through the local proxy is tied to the daemon: only then is the proxy
1164
+ // guaranteed to be listening. Without it nothing in the user's shell or GUI apps is pointed at it.
1165
+ const envFile = createEnvFile(ENV_FILE, { routing: opts.wantDaemon });
1180
1166
  ok(`Env file created: ${envFile}`);
1181
1167
 
1182
- const shellOk = setupShellIntegration();
1183
- if (shellOk) ok('Shell rc files updated (source ~/.lemma-cache/env)');
1184
- else warn('Could not update shell rc — source manually: source ~/.lemma-cache/env');
1168
+ if (opts.wantDaemon) {
1169
+ const shellOk = setupShellIntegration();
1170
+ if (shellOk) ok('Shell rc files updated (source ~/.lemma-cache/env)');
1171
+ else warn('Could not update shell rc — source manually: source ~/.lemma-cache/env');
1172
+ } else if (removeShellIntegration()) {
1173
+ ok('Removed the proxy routing an earlier init added to your shell (OPENAI_BASE_URL & co. pointed at a proxy that is not installed)');
1174
+ } else {
1175
+ skip('Shell and app environment untouched — no model traffic is routed (opt-in via `lemma init --daemon`)');
1176
+ }
1185
1177
 
1186
1178
  process.stderr.write(`\n ${BOLD}${GREEN}✓ Lemma init complete${RESET}\n`);
1187
1179
  process.stderr.write(` ${DIM}──────────────────────────────────────${RESET}\n`);
@@ -1192,14 +1184,22 @@ async function main(argv = process.argv) {
1192
1184
  }
1193
1185
  process.stderr.write(` ${GREEN}●${RESET} MCP server ready ${DIM}(lemma mcp)${RESET}\n`);
1194
1186
  process.stderr.write(` ${GREEN}●${RESET} Dashboard active ${DIM}(lemma stats)${RESET}\n`);
1195
- process.stderr.write(` ${GREEN}●${RESET} New terminals: env vars auto-loaded\n`);
1187
+ if (opts.wantDaemon) {
1188
+ process.stderr.write(` ${GREEN}●${RESET} New terminals: env vars auto-loaded\n`);
1189
+ }
1196
1190
  process.stderr.write(` ${DIM}Upkeep: re-run 'lemma init' after upgrading @nxuss/lemma (refreshes skills + rules)${RESET}\n`);
1197
- process.stderr.write(` ${GREEN}●${RESET} Todos los IDE/CLI nuevos ya apuntan al proxy\n`);
1191
+ if (opts.wantDaemon) {
1192
+ process.stderr.write(` ${GREEN}●${RESET} Todos los IDE/CLI nuevos ya apuntan al proxy\n`);
1193
+ }
1198
1194
  process.stderr.write(` ${gatewayResult.activated ? GREEN + '●' : YELLOW + '○'}${RESET} Claude Code gateway: ${gatewayResult.activated ? 'ACTIVE (model traffic cached)' : 'inactive (MCP tools only)'}\n`);
1199
1195
  process.stderr.write(` ${DIM}──────────────────────────────────────${RESET}\n`);
1200
- process.stderr.write(` ${YELLOW}Para activar en ESTA terminal:${RESET}\n`);
1201
- process.stderr.write(` ${BOLD}source ${ENV_FILE}${RESET}\n`);
1202
- process.stderr.write(` ${DIM}Logs: ${LEMMA_CACHE}/proxy-daemon.log${RESET}\n\n`);
1196
+ if (opts.wantDaemon) {
1197
+ process.stderr.write(` ${YELLOW}Para activar en ESTA terminal:${RESET}\n`);
1198
+ process.stderr.write(` ${BOLD}source ${ENV_FILE}${RESET}\n`);
1199
+ process.stderr.write(` ${DIM}Logs: ${LEMMA_CACHE}/proxy-daemon.log${RESET}\n\n`);
1200
+ } else {
1201
+ process.stderr.write(` ${DIM}Open your agent (Claude Code, Cursor, Codex…) in this project and the MCP tools are there.${RESET}\n\n`);
1202
+ }
1203
1203
  }
1204
1204
 
1205
1205
  if (require.main === module) {
@@ -1209,4 +1209,4 @@ if (require.main === module) {
1209
1209
  });
1210
1210
  }
1211
1211
 
1212
- module.exports = { main, configureMuse, configureMuseUserSkill, updateAgentsMd, configureClaudeCode, MUSE_SKILL_CONTENT, LEMMA_RULES_CONTENT, CLAUDE_MD_CONTENT, installProxyDaemon, removeProxyDaemon, parseInitArgs, createEnvFile, installBrainFeeds };
1212
+ module.exports = { removeShellIntegration, setupShellIntegration, main, configureMuse, configureMuseUserSkill, updateAgentsMd, configureClaudeCode, MUSE_SKILL_CONTENT, LEMMA_RULES_CONTENT, CLAUDE_MD_CONTENT, installProxyDaemon, removeProxyDaemon, parseInitArgs, createEnvFile, installBrainFeeds };
@@ -6,9 +6,10 @@
6
6
  * catalog so it auto-loads in every `muse` session, with zero extra steps
7
7
  * after `npm i [-g] @nxuss/lemma`.
8
8
  *
9
- * Total function: never fails the install, never prints on success, finishes
10
- * in well under a second when there is nothing to do. No build output needed —
11
- * skill install only registers a markdown file, it never invokes any tool.
9
+ * It also tells the person to run `lemma init` in their projects, once, on a GLOBAL install
10
+ * to a terminal (see notify). Total function: never fails the install, finishes in well under a
11
+ * second when there is nothing to do. No build output needed — skill install only registers a
12
+ * markdown file, it never invokes any tool.
12
13
  */
13
14
 
14
15
  const { execSync, execFileSync } = require('child_process');
@@ -28,7 +29,58 @@ function museOnPath() {
28
29
  }
29
30
  }
30
31
 
32
+ const NOTICE = [
33
+ '',
34
+ ' Lemma is installed. One more step, in each project you want it in:',
35
+ '',
36
+ ' lemma init',
37
+ '',
38
+ ' Run it again after every upgrade: it updates the agent rules and the pre-approved',
39
+ ' permissions, which the package alone cannot change. Then reconnect the MCP server',
40
+ ' (/mcp in Claude Code). What an upgrade does by itself: README, "After an upgrade".',
41
+ '',
42
+ ].join('\n');
43
+
44
+ /**
45
+ * Only a global install is the moment someone is setting Lemma up. `npx -y @nxuss/lemma mcp`
46
+ * (how some clients launch the server) and installs as a dependency run this script too, and
47
+ * a message written to the terminal there would land on top of the client's own screen.
48
+ */
49
+ function shouldNotify(env) {
50
+ if (env.npm_config_global !== 'true') return false;
51
+ if (env.CI || env.LEMMA_QUIET) return false;
52
+ if (/^(silent|error)$/.test(env.npm_config_loglevel || '')) return false;
53
+ return true;
54
+ }
55
+
56
+ /**
57
+ * npm 7+ hides a postinstall script's output on a normal install, so printing to stdout would
58
+ * reach nobody. The controlling terminal is written directly instead; with no terminal (a pipe,
59
+ * a container) that fails quietly and the notice goes to stderr, which `--foreground-scripts` shows.
60
+ */
61
+ function notify() {
62
+ try {
63
+ const fd = fs.openSync(process.platform === 'win32' ? '\\\\.\\CON' : '/dev/tty', 'w');
64
+ try {
65
+ fs.writeSync(fd, `${NOTICE}\n`);
66
+ } finally {
67
+ fs.closeSync(fd);
68
+ }
69
+ } catch {
70
+ try {
71
+ process.stderr.write(`${NOTICE}\n`);
72
+ } catch {
73
+ // a notice is never worth failing an install
74
+ }
75
+ }
76
+ }
77
+
31
78
  function main() {
79
+ try {
80
+ if (shouldNotify(process.env)) notify();
81
+ } catch {
82
+ // best-effort, same contract as below
83
+ }
32
84
  try {
33
85
  const skillDir = path.join(__dirname, '..', '.agents', 'skills', 'lemma');
34
86
  if (!fs.existsSync(path.join(skillDir, 'SKILL.md'))) return;
@@ -40,4 +92,6 @@ function main() {
40
92
  }
41
93
  }
42
94
 
43
- main();
95
+ if (require.main === module) main();
96
+
97
+ module.exports = { shouldNotify, NOTICE };