@100xprompt/brain 0.0.0-stage → 0.1.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 (213) hide show
  1. package/LICENSE +48 -0
  2. package/README.md +172 -2
  3. package/package.json +48 -4
  4. package/src/adapters/aider/index.ts +8 -0
  5. package/src/adapters/aider/reader.ts +129 -0
  6. package/src/adapters/aider/records.ts +220 -0
  7. package/src/adapters/aider/writer.ts +269 -0
  8. package/src/adapters/amazon-q-cli/index.ts +8 -0
  9. package/src/adapters/amazon-q-cli/reader.ts +84 -0
  10. package/src/adapters/amazon-q-cli/records.ts +191 -0
  11. package/src/adapters/amazon-q-cli/writer.ts +370 -0
  12. package/src/adapters/amazon-q-ide/index.ts +9 -0
  13. package/src/adapters/amazon-q-ide/reader.ts +131 -0
  14. package/src/adapters/amazon-q-ide/records.ts +141 -0
  15. package/src/adapters/amazon-q-ide/writer.ts +325 -0
  16. package/src/adapters/antigravity/index.ts +9 -0
  17. package/src/adapters/antigravity/proto.ts +194 -0
  18. package/src/adapters/antigravity/reader.ts +424 -0
  19. package/src/adapters/antigravity/records.ts +236 -0
  20. package/src/adapters/antigravity/writer.ts +463 -0
  21. package/src/adapters/auggie/index.ts +36 -0
  22. package/src/adapters/claude-code/index.ts +8 -0
  23. package/src/adapters/claude-code/reader.ts +639 -0
  24. package/src/adapters/claude-code/records.ts +88 -0
  25. package/src/adapters/claude-code/writer.ts +329 -0
  26. package/src/adapters/cline/index.ts +12 -0
  27. package/src/adapters/cline/reader.ts +432 -0
  28. package/src/adapters/cline/records.ts +151 -0
  29. package/src/adapters/cline/vscdb.ts +56 -0
  30. package/src/adapters/cline/writer.ts +656 -0
  31. package/src/adapters/cline/xml.ts +177 -0
  32. package/src/adapters/cline4/index.ts +9 -0
  33. package/src/adapters/cline4/reader.ts +179 -0
  34. package/src/adapters/cline4/records.ts +92 -0
  35. package/src/adapters/cline4/writer.ts +361 -0
  36. package/src/adapters/codebuff/index.ts +8 -0
  37. package/src/adapters/codebuff/project.ts +207 -0
  38. package/src/adapters/codebuff/reader.ts +215 -0
  39. package/src/adapters/codebuff/records.ts +173 -0
  40. package/src/adapters/codebuff/writer.ts +383 -0
  41. package/src/adapters/codex/index.ts +8 -0
  42. package/src/adapters/codex/native.ts +239 -0
  43. package/src/adapters/codex/reader.ts +747 -0
  44. package/src/adapters/codex/records.ts +114 -0
  45. package/src/adapters/codex/writer.ts +479 -0
  46. package/src/adapters/continue/index.ts +9 -0
  47. package/src/adapters/continue/reader.ts +68 -0
  48. package/src/adapters/continue/records.ts +418 -0
  49. package/src/adapters/continue/writer.ts +162 -0
  50. package/src/adapters/copilot-cli/index.ts +9 -0
  51. package/src/adapters/copilot-cli/reader.ts +186 -0
  52. package/src/adapters/copilot-cli/records.ts +241 -0
  53. package/src/adapters/copilot-cli/writer.ts +353 -0
  54. package/src/adapters/crush/index.ts +8 -0
  55. package/src/adapters/crush/reader.ts +180 -0
  56. package/src/adapters/crush/records.ts +169 -0
  57. package/src/adapters/crush/writer.ts +333 -0
  58. package/src/adapters/cursor-cli/index.ts +405 -0
  59. package/src/adapters/cursor-ide/agentkv.ts +95 -0
  60. package/src/adapters/cursor-ide/index.ts +501 -0
  61. package/src/adapters/cursor-ide/transcripts.ts +122 -0
  62. package/src/adapters/cursor-ide/workspaces.ts +119 -0
  63. package/src/adapters/cursor-ide/writer.ts +170 -0
  64. package/src/adapters/devin/index.ts +8 -0
  65. package/src/adapters/devin/reader.ts +305 -0
  66. package/src/adapters/devin/records.ts +277 -0
  67. package/src/adapters/devin/writer.ts +523 -0
  68. package/src/adapters/factory-droid/index.ts +12 -0
  69. package/src/adapters/factory-droid/reader.ts +184 -0
  70. package/src/adapters/factory-droid/records.ts +79 -0
  71. package/src/adapters/factory-droid/writer.ts +350 -0
  72. package/src/adapters/gemini-cli/exchanges.ts +228 -0
  73. package/src/adapters/gemini-cli/index.ts +396 -0
  74. package/src/adapters/gemini-cli/records.ts +521 -0
  75. package/src/adapters/gemini-cli/writer.ts +354 -0
  76. package/src/adapters/goose/index.ts +8 -0
  77. package/src/adapters/goose/reader.ts +138 -0
  78. package/src/adapters/goose/records.ts +199 -0
  79. package/src/adapters/goose/writer.ts +340 -0
  80. package/src/adapters/grok-cli/image.ts +44 -0
  81. package/src/adapters/grok-cli/index.ts +8 -0
  82. package/src/adapters/grok-cli/reader.ts +302 -0
  83. package/src/adapters/grok-cli/records.ts +172 -0
  84. package/src/adapters/grok-cli/writer.ts +329 -0
  85. package/src/adapters/hermes/index.ts +8 -0
  86. package/src/adapters/hermes/reader.ts +249 -0
  87. package/src/adapters/hermes/records.ts +285 -0
  88. package/src/adapters/hermes/writer.ts +452 -0
  89. package/src/adapters/iflow-cli/index.ts +72 -0
  90. package/src/adapters/iflow-cli/records.ts +215 -0
  91. package/src/adapters/iflow-cli/writer.ts +204 -0
  92. package/src/adapters/index.ts +112 -0
  93. package/src/adapters/kilo-cli/index.ts +21 -0
  94. package/src/adapters/kilo-code/index.ts +11 -0
  95. package/src/adapters/kilo-code/writer.ts +92 -0
  96. package/src/adapters/kimi/index.ts +9 -0
  97. package/src/adapters/kimi/reader.ts +92 -0
  98. package/src/adapters/kimi/records.ts +221 -0
  99. package/src/adapters/kimi/writer.ts +367 -0
  100. package/src/adapters/kiro-cli/index.ts +8 -0
  101. package/src/adapters/kiro-cli/reader.ts +87 -0
  102. package/src/adapters/kiro-cli/records.ts +260 -0
  103. package/src/adapters/kiro-cli/writer.ts +305 -0
  104. package/src/adapters/kiro-ide/index.ts +8 -0
  105. package/src/adapters/kiro-ide/writer.ts +52 -0
  106. package/src/adapters/letta/index.ts +8 -0
  107. package/src/adapters/letta/reader.ts +177 -0
  108. package/src/adapters/letta/records.ts +119 -0
  109. package/src/adapters/letta/writer.ts +413 -0
  110. package/src/adapters/mastracode/index.ts +8 -0
  111. package/src/adapters/mastracode/reader.ts +323 -0
  112. package/src/adapters/mastracode/records.ts +336 -0
  113. package/src/adapters/mastracode/writer.ts +385 -0
  114. package/src/adapters/mistral-vibe/index.ts +9 -0
  115. package/src/adapters/mistral-vibe/reader.ts +62 -0
  116. package/src/adapters/mistral-vibe/records.ts +104 -0
  117. package/src/adapters/mistral-vibe/writer.ts +359 -0
  118. package/src/adapters/muse/index.ts +10 -0
  119. package/src/adapters/muse/reader.ts +332 -0
  120. package/src/adapters/muse/records.ts +104 -0
  121. package/src/adapters/muse/writer.ts +387 -0
  122. package/src/adapters/omp/index.ts +13 -0
  123. package/src/adapters/omp/reader.ts +314 -0
  124. package/src/adapters/omp/records.ts +153 -0
  125. package/src/adapters/omp/writer.ts +216 -0
  126. package/src/adapters/opencode/ids.ts +52 -0
  127. package/src/adapters/opencode/index.ts +31 -0
  128. package/src/adapters/opencode/note.ts +8 -0
  129. package/src/adapters/opencode/reader.ts +452 -0
  130. package/src/adapters/opencode/tools.ts +174 -0
  131. package/src/adapters/opencode/v2.ts +367 -0
  132. package/src/adapters/opencode/writer.ts +379 -0
  133. package/src/adapters/openhands/index.ts +8 -0
  134. package/src/adapters/openhands/reader.ts +222 -0
  135. package/src/adapters/openhands/records.ts +136 -0
  136. package/src/adapters/openhands/writer.ts +404 -0
  137. package/src/adapters/pearai/index.ts +10 -0
  138. package/src/adapters/pi/index.ts +28 -0
  139. package/src/adapters/pi/reader.ts +261 -0
  140. package/src/adapters/pi/records.ts +99 -0
  141. package/src/adapters/pi/writer.ts +362 -0
  142. package/src/adapters/qoder-cli/index.ts +8 -0
  143. package/src/adapters/qoder-cli/reader.ts +83 -0
  144. package/src/adapters/qoder-cli/records.ts +45 -0
  145. package/src/adapters/qoder-cli/writer.ts +72 -0
  146. package/src/adapters/qwen-code/index.ts +84 -0
  147. package/src/adapters/qwen-code/records.ts +194 -0
  148. package/src/adapters/qwen-code/writer.ts +224 -0
  149. package/src/adapters/tabnine-cli/index.ts +8 -0
  150. package/src/adapters/tabnine-cli/reader.ts +106 -0
  151. package/src/adapters/tabnine-cli/records.ts +33 -0
  152. package/src/adapters/tabnine-cli/writer.ts +156 -0
  153. package/src/adapters/types.ts +73 -0
  154. package/src/adapters/void/index.ts +9 -0
  155. package/src/adapters/void/reader.ts +48 -0
  156. package/src/adapters/void/records.ts +285 -0
  157. package/src/adapters/void/writer.ts +93 -0
  158. package/src/adapters/vscode-copilot/index.ts +9 -0
  159. package/src/adapters/vscode-copilot/reader.ts +212 -0
  160. package/src/adapters/vscode-copilot/records.ts +388 -0
  161. package/src/adapters/vscode-copilot/shared.ts +68 -0
  162. package/src/adapters/vscode-copilot/writer.ts +546 -0
  163. package/src/adapters/zed/index.ts +8 -0
  164. package/src/adapters/zed/reader.ts +198 -0
  165. package/src/adapters/zed/records.ts +87 -0
  166. package/src/adapters/zed/writer.ts +341 -0
  167. package/src/brief-writers.ts +71 -0
  168. package/src/brief.ts +238 -0
  169. package/src/cli.ts +326 -0
  170. package/src/fsutil.ts +133 -0
  171. package/src/gateway/connect.ts +845 -0
  172. package/src/gateway/signin.ts +323 -0
  173. package/src/handoff.ts +104 -0
  174. package/src/hooks.ts +455 -0
  175. package/src/memory/audit.ts +514 -0
  176. package/src/memory/blobs.ts +94 -0
  177. package/src/memory/cli.ts +117 -0
  178. package/src/memory/distill.ts +381 -0
  179. package/src/memory/externalize.ts +435 -0
  180. package/src/memory/lookups.ts +70 -0
  181. package/src/memory/mcp-install.ts +401 -0
  182. package/src/memory/mcp-server.ts +296 -0
  183. package/src/memory/prepare.ts +179 -0
  184. package/src/memory/preview.ts +154 -0
  185. package/src/memory/project.ts +35 -0
  186. package/src/memory/redact.ts +72 -0
  187. package/src/memory/relevance.ts +187 -0
  188. package/src/memory/repomap.ts +516 -0
  189. package/src/memory/store.ts +270 -0
  190. package/src/memory/sync.ts +166 -0
  191. package/src/memory/tidy.ts +193 -0
  192. package/src/memory/types.ts +118 -0
  193. package/src/org/admin-ops.ts +96 -0
  194. package/src/org/auth.ts +112 -0
  195. package/src/org/backup.ts +115 -0
  196. package/src/org/cli.ts +438 -0
  197. package/src/org/client.ts +404 -0
  198. package/src/org/config.ts +44 -0
  199. package/src/org/crypto.ts +537 -0
  200. package/src/org/db.ts +435 -0
  201. package/src/org/graph.ts +318 -0
  202. package/src/org/oidc.ts +435 -0
  203. package/src/org/policy.ts +398 -0
  204. package/src/org/server.ts +920 -0
  205. package/src/org/sso.ts +316 -0
  206. package/src/org/tls.ts +66 -0
  207. package/src/org/types.ts +193 -0
  208. package/src/self.ts +68 -0
  209. package/src/session/attachments.ts +53 -0
  210. package/src/session/peekcache.ts +50 -0
  211. package/src/session/types.ts +104 -0
  212. package/src/session/util.ts +130 -0
  213. package/src/sqlite.ts +48 -0
package/LICENSE ADDED
@@ -0,0 +1,48 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nipurn Agarwal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Parts of src/adapters/{antigravity,devin,grok-cli,hermes,kilo-cli,mastracode,muse,omp} are ported from session-migrate
26
+ (https://github.com/xhluca/session-migrate), used under its MIT license:
27
+
28
+ MIT License
29
+
30
+ Copyright (c) 2026 xhluca
31
+
32
+ Permission is hereby granted, free of charge, to any person obtaining a copy
33
+ of this software and associated documentation files (the "Software"), to deal
34
+ in the Software without restriction, including without limitation the rights
35
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
36
+ copies of the Software, and to permit persons to whom the Software is
37
+ furnished to do so, subject to the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be included in all
40
+ copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
43
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
44
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
45
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
46
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
47
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
48
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,173 @@
1
- # Temporary Holding Version
1
+ # brain
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ One working session, any AI coding tool. Start in Claude Code, carry on in Codex, Cursor or another tool, and switch
4
+ back, with the same session. brain also keeps what each project has learned across tools, trims what a long session
5
+ sends back to the model, and can route your tools through the 100xprompt gateway.
6
+
7
+ Nothing calls a model to do any of this, and nothing changes in your tools until you run a command that says so.
8
+
9
+ Runs on [Bun](https://bun.sh) 1.3 or later. Built and tested on macOS.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ bun add -g @100xprompt/brain
15
+ brain --version
16
+ ```
17
+
18
+ Or run it without installing:
19
+
20
+ ```bash
21
+ bunx @100xprompt/brain session list --here
22
+ ```
23
+
24
+ Hooks and MCP entries installed from a `bunx` run start that same version later, so they keep working after Bun's
25
+ temporary copy is gone. No Bun yet? `curl -fsSL https://bun.sh/install | bash`.
26
+
27
+ ## Quick start
28
+
29
+ ```bash
30
+ brain session list --here # sessions from every tool, for this project, newest first
31
+ brain session continue <id> --in codex --dry-run # what would be written, and what would not carry over
32
+ brain session continue <id> --in codex # a new Codex session that picks up where that one stopped
33
+ brain session handoff --in claude-code # the newest session from another tool in this folder
34
+ ```
35
+
36
+ `<id>` is `<tool>:<session id>` as `list` prints it, or a unique start of the session id.
37
+
38
+ ## Moving a session
39
+
40
+ `continue` writes the session into the target the best way that target allows:
41
+
42
+ - **Native** (a new session in the tool's own storage, opened like any other): Claude Code, Codex, Gemini CLI, Copilot
43
+ Chat in VS Code, the Copilot CLI, Cline (in VS Code, Cursor, Windsurf, and its CLI), Roo Code (in VS Code, Cursor,
44
+ Windsurf), Antigravity, Aider, Amazon Q (CLI and IDE), Auggie, Codebuff, Continue, Crush, Devin CLI, Factory Droid,
45
+ Grok CLI, Hermes Agent, iFlow CLI, Kimi Code, Kiro (CLI and IDE), Letta Code, Mastra Code, Muse Code, Oh My Pi,
46
+ OpenHands CLI, PearAI, Pi, Qoder CLI, Qwen Code, Tabnine CLI, Void and Zed.
47
+ - **Import** (through the tool's own import): 100xprompt, OpenCode, Cursor, Goose, Kilo Code, Kilo CLI and Mistral Vibe.
48
+ - **Brief**: the Cursor CLI, and any other tool (`--in any`: a Markdown file to paste).
49
+
50
+ A native or import writer writes only for the tool versions its format was checked against, and some need the app
51
+ closed (VS Code); otherwise, and whenever the tool is missing, the session goes as a brief. `brain session targets`
52
+ shows what each tool on this machine gets right now, and `brain session list` shows every session brain can read.
53
+
54
+ A **brief** is built without a model call: where the session came from, the goal, where it stands, files changed, the
55
+ last commands, the latest requests, and as many recent turns as fit the target. It is saved as a pending handoff, copied
56
+ to the clipboard where the system has one, and, where the session-start hook is installed (`brain session hooks
57
+ install`, for Claude Code, Codex, Cursor and Gemini CLI), given to the model by the target's next session in the same
58
+ project.
59
+
60
+ ## Saving tokens
61
+
62
+ On real sessions, tool calls and their output are about four fifths of the tokens; what was said is about a tenth.
63
+
64
+ - **Lean sessions.** A big tool call or result becomes a one-line digest with a short preview, and the full text stays on
65
+ disk (`~/.brain/blobs/`). `continue` does this by itself on long sessions (`--lean` / `--full` override; `--budget
66
+ <tokens>` trims in steps until the session fits). The model gets the text back only when it asks, through the
67
+ `brain_expand` tool (or `brain expand <id>`).
68
+ - **Project memory.** What a session settled (decisions, constraints, preferences, files, failed approaches, commands) is
69
+ distilled into small items kept per project, so a session in any tool can recall them with `brain_recall`. Memory needs
70
+ [Chitta](https://www.npmjs.com/package/@100xprompt/chitta) (`bun add -g @100xprompt/chitta`, found next to brain, in a
71
+ global install, or at `$BRAIN_CHITTA`); without it everything else works, except a Teams server.
72
+ - **What every request reloads.** `brain tokens audit` checks, without changing anything, what a project sends with every
73
+ request: instruction files, MCP servers, ignore files.
74
+
75
+ ```bash
76
+ brain mcp install # brain_recall and brain_expand in Claude Code, Codex, Cursor, Gemini CLI, OpenCode, VS Code
77
+ brain memory sync --here # distill this project's earlier sessions (a few per run; nothing is processed twice)
78
+ brain memory search "how do we run the tests"
79
+ brain tokens audit
80
+ ```
81
+
82
+ ## The 100xprompt gateway
83
+
84
+ Point a coding tool at the 100xprompt gateway, and back. Signing in works like `100xprompt auth login`: your browser opens,
85
+ you sign in to 100xprompt and confirm, and the key comes back to brain by itself.
86
+
87
+ ```bash
88
+ brain gateway login # sign in in the browser
89
+ brain gateway connect --tool codex --dry-run # show what would change
90
+ brain gateway connect --tool codex # Claude Code, Codex, OpenCode, Gemini CLI (signs in first if needed)
91
+ brain gateway status
92
+ brain gateway disconnect --tool codex # put back exactly what was there
93
+ brain gateway logout
94
+ ```
95
+
96
+ - Connecting changes which model the tool uses: it runs on 100xprompt Pro by default (`--model flash` for 100xprompt
97
+ Flash), billed to your 100xprompt account. Context editing is on by default; `--no-context-edit` turns it off and is
98
+ remembered for later connects until `--context-edit`.
99
+ - Before changing anything, `connect` checks that the gateway accepts your key the way that tool sends it (a free check
100
+ that generates nothing). If your account's quota is used up, it still connects and tells you, in the gateway's words.
101
+ - Only the settings brain owns are written; the value each had before is recorded and put back by `disconnect`. The key
102
+ lives in `~/.brain/gateway.key`, readable only by you, and is never printed.
103
+ - On a machine without a browser (SSH): `brain gateway login --no-browser` prints the address. Open it anywhere, then
104
+ paste the key from the page's Copy key button (input is hidden). For scripts and CI: `--key-env VAR`. To reuse the
105
+ 100xprompt CLI's sign-in: `--from-cli`.
106
+ - Each sign-in makes a new key on your account. Signing in again also updates the copy Codex and Gemini CLI keep in their
107
+ own config.
108
+
109
+ ## Teams
110
+
111
+ Memory can live on one shared server instead of each person's machine, with who may see what enforced there.
112
+
113
+ ```bash
114
+ # on the server (it needs Chitta: bun add -g @100xprompt/chitta)
115
+ brain org admin init --db org.db --org-id acme --org-name Acme --admin-id ana --admin-name Ana # once; prints the admin token
116
+ brain org admin serve --db org.db --host 0.0.0.0 --allow-remote --tls-cert cert.pem --tls-key key.pem
117
+
118
+ # on each person's machine
119
+ brain org login --server https://<server>:4500 --token <token>
120
+ brain org promote --source <session> --to team --team backend
121
+ ```
122
+
123
+ Every item has a scope: `private` (the default; only its owner, not even an admin), `project`, `team` or `org`. Sharing
124
+ is an explicit, logged act. Roles are `viewer`, `editor` and `admin`; admins set policy (widest scope, paths and terms that
125
+ are never shared, retention) and read the audit log. Only distilled, secret-filtered memory items cross the wire: never
126
+ full tool output, raw turns or attachments. With no login, brain stays entirely local.
127
+
128
+ `admin serve` listens on 127.0.0.1 unless told otherwise: to reach it from other machines, use `--allow-remote` with TLS
129
+ (`--tls-cert`/`--tls-key`), or keep it local behind a TLS proxy (`--trust-proxy`). An encrypted database (`init
130
+ --encrypt`) also needs `libsql` installed next to Chitta. `brain org help` lists every option.
131
+
132
+ ## Safety
133
+
134
+ - Reading a session never changes a tool's files: SQLite stores are copied (with their write-ahead logs) and the copy is
135
+ read. To see which version is installed, `targets` and `--dry-run` start each installed tool with `--version`.
136
+ - Continuing always writes a **new** session; the source is never changed, so switching back is continuing the original.
137
+ - Writers check the installed tool version against the one their format was verified on, and fall back to the brief otherwise.
138
+ - `--dry-run` shows what would be written. Hooks, MCP entries and gateway settings are merged with your own, backed up
139
+ first, and removed exactly by the matching `uninstall` or `disconnect`.
140
+
141
+ ## Commands
142
+
143
+ ```
144
+ brain session list | show | export | continue | handoff | targets | hooks
145
+ brain memory ingest | search | status | forget | sync
146
+ brain mcp serve | install | uninstall | status
147
+ brain tokens audit | lookups
148
+ brain gateway login | logout | connect | disconnect | status | tools
149
+ brain org login | logout | whoami | status | promote | demote | forget | admin ...
150
+ brain expand <id>
151
+ brain --version
152
+ ```
153
+
154
+ `brain --help` lists every command with its main options; `brain gateway`, `brain mcp` and `brain org help` show the rest
155
+ of theirs.
156
+
157
+ ## Known limits
158
+
159
+ - Built and tested on macOS. Linux and Windows are not tested yet.
160
+ - Each writer is checked against specific versions, for example Claude Code 2.1.296 to 2.1.319 (a newer 2.1.x is
161
+ accepted with a note), Codex 0.147.0, Gemini CLI 0.33.1 and 0.67.0, Cursor 3.27 and 3.29, VS Code 1.144 with Copilot
162
+ Chat 0.72, OpenCode 2.2.6, Zed 1.26.2 and 1.27.0, Aider 0.86.2. A tool at another version gets the brief instead.
163
+ - Into Codex: tool names from Claude Code (`Read`, `Grep`, `Agent`, MCP calls) stay as they are, and subagent transcripts
164
+ are not carried. Pasted images are.
165
+ - Into Claude Code: reasoning and subagent transcripts are not written.
166
+ - Cursor is reached through its own import of Claude Code sessions (for the folder it has open), not by writing to
167
+ Cursor's database; the brief opens in Cursor's chat when that is not possible.
168
+ - Session-start hooks were checked against each tool's installed code, not yet inside every live tool. Codex asks you to
169
+ trust a new hook once.
170
+
171
+ ## License
172
+
173
+ MIT
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@100xprompt/brain",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "One working session, any AI coding tool: move sessions between Claude Code, Codex, Cursor, Gemini CLI, OpenCode and more, keep what projects learned, spend fewer tokens. Runs on Bun. By 100xprompt.",
5
+ "license": "MIT",
6
+ "author": "Nipurn Agarwal",
7
+ "homepage": "https://www.npmjs.com/package/@100xprompt/brain",
8
+ "keywords": [
9
+ "ai-coding",
10
+ "coding-agent",
11
+ "claude-code",
12
+ "codex",
13
+ "cursor",
14
+ "gemini-cli",
15
+ "opencode",
16
+ "session",
17
+ "handoff",
18
+ "context",
19
+ "memory",
20
+ "mcp",
21
+ "tokens",
22
+ "100xprompt"
23
+ ],
24
+ "type": "module",
25
+ "engines": {
26
+ "bun": ">=1.3.0"
27
+ },
28
+ "bin": {
29
+ "brain": "./src/cli.ts"
30
+ },
31
+ "files": [
32
+ "src",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "brain": "bun run src/cli.ts",
41
+ "test": "bun test --timeout 60000",
42
+ "typecheck": "tsc --noEmit -p .",
43
+ "wire": "bun wire/run.ts"
44
+ },
45
+ "devDependencies": {
46
+ "@types/bun": "^1.4.4",
47
+ "oidc-provider": "9.13.3",
48
+ "typescript": "5.8.2"
49
+ }
50
+ }
@@ -0,0 +1,8 @@
1
+ import type { Reader, Writer } from "../types.ts"
2
+ import { aiderReader } from "./reader.ts"
3
+ import { aiderNativeWriter } from "./writer.ts"
4
+
5
+ // Aider: reads each run of a project's .aider.chat.history.md as a session, and continues any session as a
6
+ // new run in that history, which `aider --restore-chat-history` hands to the model.
7
+ export const readers: Reader[] = [aiderReader]
8
+ export const writers: Writer[] = [aiderNativeWriter]
@@ -0,0 +1,129 @@
1
+ import { createHash } from "node:crypto"
2
+ import { readFileSync, statSync } from "node:fs"
3
+ import { dirname } from "node:path"
4
+ import type { Session, Turn } from "../../session/types.ts"
5
+ import { header, lossCounter, noteModel } from "../../session/util.ts"
6
+ import type { Reader, SessionRef } from "../types.ts"
7
+ import { CONTINUED_NOTE, LABEL, SUMMARY_PREFIX, TOOL, type Run, assistantText, findHistories, parseRuns, userText } from "./records.ts"
8
+
9
+ // Aider sessions. Aider has no session list or ids: every project keeps one history file and each launch
10
+ // appends a run to it, so a run ("# aider chat started at ...") is read as one session. That is also what
11
+ // a launch without --restore-chat-history sees: a fresh chat. Ids are made from the file and the run's start.
12
+
13
+ /** "2026-11-08 10:00:00" (local time, as Aider writes it) → ISO. */
14
+ const runTime = (stamp: string | undefined) => {
15
+ if (!stamp) return undefined
16
+ const t = new Date(stamp.replace(" ", "T"))
17
+ return Number.isFinite(t.getTime()) ? t.toISOString() : undefined
18
+ }
19
+
20
+ const fileKey = (path: string) => createHash("sha1").update(path).digest("hex").slice(0, 8)
21
+
22
+ /** The id of a run: the file's key and the run's start (YYYYMMDDhhmmss), with its index when two share a second. */
23
+ export function runId(path: string, run: Run, runs: Run[]): string {
24
+ const stamp = (run.startedAt ?? "00000000000000").replace(/\D/g, "")
25
+ const twin = runs.filter((r) => r.startedAt === run.startedAt).length > 1
26
+ return `${fileKey(path)}-${stamp}${twin || !run.startedAt ? `-${run.index}` : ""}`
27
+ }
28
+
29
+ /** Runs that hold conversation, not only Aider's startup notices. */
30
+ const talked = (run: Run) => run.messages.some((m) => m.role !== "tool")
31
+
32
+ function readText(path: string): string {
33
+ try {
34
+ return readFileSync(path, "utf8")
35
+ } catch (e) {
36
+ throw new Error(`cannot read the Aider history ${path}: ${(e as Error).message}`)
37
+ }
38
+ }
39
+
40
+ /** One run as a portable session. */
41
+ export function runSession(path: string, run: Run, runs: Run[], updatedAt?: string): Session {
42
+ const id = runId(path, run, runs)
43
+ const h = header(TOOL, id, { sourcePath: path, project: { cwd: dirname(path) }, startedAt: runTime(run.startedAt), updatedAt })
44
+ const lost = lossCounter()
45
+ const turns: Turn[] = []
46
+ for (const [i, m] of run.messages.entries()) {
47
+ if (m.role === "tool") {
48
+ // Aider's own notices: never sent to the model. Kept: the model it ran, its version, and the files it edited.
49
+ for (const line of m.content.split("\n")) {
50
+ const model = line.match(/^(?:Main model|Model): (\S+)/)?.[1]
51
+ if (model) noteModel(h, model)
52
+ const version = line.match(/^Aider v(\d+\.\d+\.\d+\S*)/)?.[1]
53
+ if (version) h.toolVersion ??= version
54
+ const edited = line.match(/^Applied edit to (.+?)\s*$/)?.[1]
55
+ if (edited) turns.push({ kind: "file_edit", path: edited })
56
+ }
57
+ continue
58
+ }
59
+ if (m.role === "user") {
60
+ const text = userText(m.content)
61
+ if (!text) continue
62
+ const note = i === run.messages.findIndex((x) => x.role === "user") ? text.match(CONTINUED_NOTE) : null
63
+ if (note) {
64
+ h.parent = { id: note[1]!.replace(/[.,]$/, ""), kind: "continued" }
65
+ continue
66
+ }
67
+ if (text.startsWith(SUMMARY_PREFIX.trim())) {
68
+ turns.push({ kind: "compaction", summary: text.slice(SUMMARY_PREFIX.trim().length).trim() })
69
+ continue
70
+ }
71
+ turns.push({ kind: "user", text })
72
+ continue
73
+ }
74
+ const text = assistantText(m.content)
75
+ if (text) turns.push({ kind: "assistant", text, ...(h.models.length ? { model: h.models[h.models.length - 1] } : {}) })
76
+ }
77
+ if (!turns.some((t) => t.kind === "user" || t.kind === "assistant")) lost.add("runs with only Aider's own notices")
78
+ lost.flush(h)
79
+ const first = turns.find((t) => t.kind === "user") as Extract<Turn, { kind: "user" }> | undefined
80
+ if (first) h.title = first.text.replace(/\s+/g, " ").trim().slice(0, 80)
81
+ return { header: h, turns }
82
+ }
83
+
84
+ export const aiderReader: Reader = {
85
+ tool: TOOL,
86
+ label: LABEL,
87
+
88
+ async list(home: string): Promise<SessionRef[]> {
89
+ const refs: SessionRef[] = []
90
+ for (const path of findHistories(home)) {
91
+ let text: string
92
+ let mtime: string | undefined
93
+ try {
94
+ text = readFileSync(path, "utf8")
95
+ mtime = statSync(path).mtime.toISOString()
96
+ } catch {
97
+ continue
98
+ }
99
+ const runs = parseRuns(text)
100
+ runs.forEach((run, i) => {
101
+ if (!talked(run)) return
102
+ const next = runs.slice(i + 1).find((r) => r.startedAt)
103
+ const first = run.messages.find((m) => m.role === "user" && !CONTINUED_NOTE.test(userText(m.content)))
104
+ refs.push({
105
+ tool: TOOL,
106
+ sourceId: runId(path, run, runs),
107
+ path,
108
+ cwd: dirname(path),
109
+ updatedAt: (next ? runTime(next.startedAt) : mtime) ?? runTime(run.startedAt),
110
+ ...(first ? { title: userText(first.content).replace(/\s+/g, " ").trim().slice(0, 80) } : {}),
111
+ extra: { run: run.index },
112
+ })
113
+ })
114
+ }
115
+ return refs.sort((a, b) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""))
116
+ },
117
+
118
+ async read(ref: SessionRef): Promise<Session> {
119
+ const runs = parseRuns(readText(ref.path))
120
+ const run = runs.find((r) => runId(ref.path, r, runs) === ref.sourceId) ?? runs.find((r) => r.index === ref.extra?.run)
121
+ if (!run) throw new Error(`no run ${ref.sourceId} in the Aider history ${ref.path}`)
122
+ let updatedAt: string | undefined
123
+ try {
124
+ updatedAt = statSync(ref.path).mtime.toISOString()
125
+ } catch {}
126
+ const next = runs.slice(run.index + 1).find((r) => r.startedAt)
127
+ return runSession(ref.path, run, runs, next ? runTime(next.startedAt) : updatedAt)
128
+ },
129
+ }
@@ -0,0 +1,220 @@
1
+ import { mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs"
2
+ import { dirname, join } from "node:path"
3
+ import { exists, isDir, versionOf } from "../../fsutil.ts"
4
+
5
+ // What Aider's chat history looks like, as far as this adapter relies on it. Checked against aider-chat
6
+ // 0.86.2 (PyPI) and its source at Aider-AI/aider 5dc9490 (aider/io.py, aider/utils.py,
7
+ // aider/coders/base_coder.py, aider/args.py, aider/history.py). Catalogue entry 13.
8
+ //
9
+ // Aider keeps one Markdown file per project, `.aider.chat.history.md` at the git root (else the folder it
10
+ // runs in), and appends to it for ever: each launch writes "# aider chat started at <local time>", the
11
+ // person's input as "#### " lines, Aider's own notices as "> " lines and the model's replies as plain
12
+ // text. `aider --restore-chat-history` reads the whole file back with utils.split_chat_history_markdown:
13
+ // "# " lines are skipped, "> " lines are dropped (they are its own output, never sent to the model), runs
14
+ // of "#### " lines are one user message, and everything else is the assistant's. There are no ids, no tool
15
+ // calls and no reasoning.
16
+
17
+ export const TOOL = "aider"
18
+ export const LABEL = "Aider"
19
+
20
+ /** Aider versions whose history format the writer was checked against. */
21
+ export const TESTED_VERSIONS = ["0.86.2"]
22
+
23
+ export const HISTORY_FILE = ".aider.chat.history.md"
24
+ export const INPUT_HISTORY_FILE = ".aider.input.history"
25
+
26
+ /** The line each Aider launch starts with (io.py: datetime.now().strftime("%Y-%m-%d %H:%M:%S")). */
27
+ export const RUN_HEADER = /^# aider chat started at (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s*$/
28
+
29
+ /** What Aider's own summariser puts before a summary that replaced earlier messages (prompts.summary_prefix). */
30
+ export const SUMMARY_PREFIX = "I spoke to you previously about a number of things.\n"
31
+
32
+ /** The note a continued session opens with; the reader recognises it and records where the session came from. */
33
+ export const continuedNote = (label: string, id: string, when: string) =>
34
+ `[This session continues ${label} session ${id}, carried over by brain session on ${when}. The messages below are its history; tool calls are shown as text, and reasoning and anything listed as lost did not carry over. Check files before relying on details, since they may have changed.]`
35
+ export const CONTINUED_NOTE = /^\[This session continues .+? session (\S+), carried over by brain session/
36
+
37
+ /** The installed Aider version ("aider 0.86.2" → "0.86.2"), or undefined when it is missing. */
38
+ export const aiderVersion = (bin = "aider") => versionOf(bin)?.match(/(\d+\.\d+\.\d+(?:\.dev\d*)?)/)?.[1]
39
+
40
+ /**
41
+ * The folder Aider keeps its history in for `cwd`: the enclosing git working tree (args.py defaults the
42
+ * history files to the git root; main.get_git_root searches parent folders), else `cwd` itself.
43
+ */
44
+ export function historyRoot(cwd: string): string {
45
+ let dir = cwd.replace(/\/+$/, "") || "/"
46
+ for (let i = 0; i < 40; i++) {
47
+ if (exists(join(dir, ".git"))) return dir
48
+ const up = dir.slice(0, dir.lastIndexOf("/")) || "/"
49
+ if (up === dir) break
50
+ dir = up
51
+ }
52
+ return cwd
53
+ }
54
+
55
+ /** Line breaks Python's str.splitlines() splits on; the parser sees each as a new line. */
56
+ const PY_LINE_BREAKS = /\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]/
57
+
58
+ /** Text split into lines the way split_chat_history_markdown splits it, without the line endings. */
59
+ export const pyLines = (text: string) => text.split(PY_LINE_BREAKS)
60
+
61
+ /** One message as Aider's parser yields it. */
62
+ export interface MdMessage {
63
+ role: "user" | "assistant" | "tool"
64
+ content: string
65
+ }
66
+
67
+ /** One launch of Aider: its header time and its messages, tool lines included. */
68
+ export interface Run {
69
+ startedAt?: string
70
+ /** 0-based index of the run in the file. */
71
+ index: number
72
+ messages: MdMessage[]
73
+ }
74
+
75
+ /**
76
+ * The file split into runs, each parsed exactly as utils.split_chat_history_markdown parses (with
77
+ * include_tool=True, so the reader can see Aider's notices; the model never gets the "tool" ones).
78
+ */
79
+ export function parseRuns(text: string): Run[] {
80
+ const runs: Run[] = []
81
+ let run: Run = { index: 0, messages: [] }
82
+ let user: string[] = []
83
+ let assistant: string[] = []
84
+ let tool: string[] = []
85
+ const append = (role: MdMessage["role"], lines: string[]) => {
86
+ const content = lines.join("")
87
+ if (content.trim()) run.messages.push({ role, content })
88
+ }
89
+ const flushAll = () => {
90
+ append("assistant", assistant)
91
+ append("user", user)
92
+ append("tool", tool)
93
+ assistant = []
94
+ user = []
95
+ tool = []
96
+ }
97
+ const parts = text.split(/(\r\n|[\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029])/)
98
+ for (let i = 0; i < parts.length; i += 2) {
99
+ const body = parts[i]!
100
+ const line = body + (parts[i + 1] ?? "")
101
+ if (!line) continue
102
+ const header = body.match(RUN_HEADER)
103
+ if (header) {
104
+ // A new launch: Aider's parser would carry on across it, but the reader keeps each launch apart.
105
+ flushAll()
106
+ if (run.messages.length || run.startedAt) runs.push(run)
107
+ run = { index: runs.length, startedAt: header[1]!, messages: [] }
108
+ continue
109
+ }
110
+ if (line.startsWith("# ")) continue
111
+ if (line.startsWith("> ")) {
112
+ append("assistant", assistant)
113
+ assistant = []
114
+ append("user", user)
115
+ user = []
116
+ tool.push(line.slice(2))
117
+ continue
118
+ }
119
+ if (line.startsWith("#### ")) {
120
+ append("assistant", assistant)
121
+ assistant = []
122
+ append("tool", tool)
123
+ tool = []
124
+ user.push(line.slice(5))
125
+ continue
126
+ }
127
+ append("user", user)
128
+ user = []
129
+ append("tool", tool)
130
+ tool = []
131
+ assistant.push(line)
132
+ }
133
+ flushAll()
134
+ if (run.messages.length || run.startedAt) runs.push(run)
135
+ return runs
136
+ }
137
+
138
+ /** A user message as the person typed it: Aider ends each "#### " line with a Markdown line break (" "). */
139
+ export const userText = (content: string) =>
140
+ pyLines(content)
141
+ .map((l) => l.replace(/ {2}$/, ""))
142
+ .join("\n")
143
+ .replace(/^\n+|\s+$/g, "")
144
+ .replace(/^<blank>$/, "")
145
+
146
+ /**
147
+ * Lines the parser would take for something else get one more leading space from the writer, and lose one
148
+ * in the reader; lines that already start with spaces before such a marker get one too, so the round trip
149
+ * is exact.
150
+ */
151
+ const CLASHES = /^ *(# |#### |> )/
152
+ const ESCAPED = /^ +(# |#### |> )/
153
+ export const escapeLine = (line: string) => (CLASHES.test(line) ? ` ${line}` : line)
154
+ export const unescapeLine = (line: string) => (ESCAPED.test(line) ? line.slice(1) : line)
155
+
156
+ /** An assistant message as written: the escape spaces taken out, surrounding blank lines trimmed. */
157
+ export const assistantText = (content: string) => pyLines(content).map(unescapeLine).join("\n").trim()
158
+
159
+ /** Folders the history search never enters: system and package folders that hold no projects. */
160
+ const SKIP_DIRS = new Set(["node_modules", "Library", "Applications", "Pictures", "Music", "Movies", "venv", "__pycache__", "dist", "build", "target", "vendor"])
161
+
162
+ /**
163
+ * Every Aider history file in the folders under `home`, at most `depth` levels down. Aider keeps no index
164
+ * of its projects, so they are found by name; hidden and package folders are skipped and the walk stops
165
+ * after `budget` folders so listing stays cheap.
166
+ */
167
+ export function findHistories(home: string, depth = 5, budget = 20_000): string[] {
168
+ const cache = join(home, ".brain", "cache", "aider-histories.json")
169
+ const fresh = process.env.BRAIN_NO_CACHE !== "1" ? readCache(cache) : undefined
170
+ const found = new Set(fresh ?? walkHistories(home, depth, budget))
171
+ if (!fresh && process.env.BRAIN_NO_CACHE !== "1") writeCache(cache, [...found])
172
+ // The folder the person is in, and the folders above it, are always checked: a project started since the cache was made is found.
173
+ for (let dir = process.cwd(), i = 0; i < depth && dir.startsWith(home); dir = dirname(dir), i++) {
174
+ const p = join(dir, HISTORY_FILE)
175
+ if (exists(p)) found.add(p)
176
+ }
177
+ return [...found]
178
+ }
179
+
180
+ /** How long a walk of the home folder is trusted. Aider keeps no index, so the walk is the only way to find its projects. */
181
+ const CACHE_TTL_MS = 10 * 60_000
182
+
183
+ function readCache(file: string): string[] | undefined {
184
+ try {
185
+ if (Date.now() - statSync(file).mtimeMs > CACHE_TTL_MS) return undefined
186
+ const j = JSON.parse(readFileSync(file, "utf8"))
187
+ return Array.isArray(j?.paths) ? j.paths.filter((p: unknown): p is string => typeof p === "string" && exists(p)) : undefined
188
+ } catch {
189
+ return undefined
190
+ }
191
+ }
192
+
193
+ function writeCache(file: string, paths: string[]) {
194
+ try {
195
+ mkdirSync(dirname(file), { recursive: true })
196
+ writeFileSync(file, JSON.stringify({ paths }))
197
+ } catch {}
198
+ }
199
+
200
+ function walkHistories(home: string, depth: number, budget: number): string[] {
201
+ const out: string[] = []
202
+ let left = budget
203
+ const walk = (dir: string, level: number) => {
204
+ if (left-- <= 0) return
205
+ let entries: import("node:fs").Dirent[]
206
+ try {
207
+ entries = readdirSync(dir, { withFileTypes: true })
208
+ } catch {
209
+ return
210
+ }
211
+ for (const e of entries) if (e.name === HISTORY_FILE && (e.isFile() || e.isSymbolicLink())) out.push(join(dir, e.name))
212
+ if (level >= depth) return
213
+ for (const e of entries) {
214
+ if (!e.isDirectory() || e.name.startsWith(".") || SKIP_DIRS.has(e.name) || e.name.endsWith(".app")) continue
215
+ walk(join(dir, e.name), level + 1)
216
+ }
217
+ }
218
+ if (isDir(home)) walk(home, 0)
219
+ return out
220
+ }