brane 0.1.1 → 0.3.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.
Files changed (352) hide show
  1. package/AGENTS.md +367 -0
  2. package/CHANGELOG.md +66 -0
  3. package/CONTRIBUTING.md +101 -0
  4. package/DESIGN.md +405 -0
  5. package/Dockerfile +61 -0
  6. package/LICENSE +656 -17
  7. package/README.md +174 -2
  8. package/SECURITY.md +213 -0
  9. package/dist/absorb/brane-id.js +11 -0
  10. package/dist/absorb/chatgpt.js +161 -0
  11. package/dist/absorb/claude-code.js +504 -0
  12. package/dist/absorb/index.js +543 -0
  13. package/dist/absorb/organelle.js +345 -0
  14. package/dist/absorb/run.js +520 -0
  15. package/dist/absorb/sources.js +168 -0
  16. package/dist/absorb/types.js +3 -0
  17. package/dist/agent-env.js +45 -0
  18. package/dist/agents.js +1168 -0
  19. package/dist/claude-live-session.js +124 -0
  20. package/dist/cli.js +50 -0
  21. package/dist/codex-tool-log.js +144 -0
  22. package/dist/commands/absorb.js +291 -0
  23. package/dist/commands/attach.js +31 -0
  24. package/dist/commands/certify.js +62 -0
  25. package/dist/commands/config.js +133 -0
  26. package/dist/commands/cost.js +126 -0
  27. package/dist/commands/daemon.js +350 -0
  28. package/dist/commands/doctor.js +262 -0
  29. package/dist/commands/fold.js +95 -0
  30. package/dist/commands/list.js +89 -0
  31. package/dist/commands/mcp.js +84 -0
  32. package/dist/commands/meld.js +360 -0
  33. package/dist/commands/memory.js +215 -0
  34. package/dist/commands/revive.js +74 -0
  35. package/dist/commands/run.js +85 -0
  36. package/dist/commands/spawn.js +1101 -0
  37. package/dist/commands/token.js +154 -0
  38. package/dist/copilot-tool-log.js +145 -0
  39. package/dist/cost/pricing.js +116 -0
  40. package/dist/cost/reprice.js +136 -0
  41. package/dist/crush-tool-log.js +293 -0
  42. package/dist/daemon/agent-scope.js +116 -0
  43. package/dist/daemon/awaiting.js +54 -0
  44. package/dist/daemon/cert-manager.js +349 -0
  45. package/dist/daemon/client.js +262 -0
  46. package/dist/daemon/gated-routes.js +112 -0
  47. package/dist/daemon/keep-awake.js +103 -0
  48. package/dist/daemon/local-socket.js +145 -0
  49. package/dist/daemon/presence-gate.js +206 -0
  50. package/dist/daemon/server.js +6353 -0
  51. package/dist/daemon/signal-source.js +35 -0
  52. package/dist/daemon/tailscale-rebind.js +58 -0
  53. package/dist/daemon/token-ring.js +263 -0
  54. package/dist/daemon/token-routes.js +190 -0
  55. package/dist/db/events.js +152 -0
  56. package/dist/db/mentions.js +179 -0
  57. package/dist/db/schema.js +643 -0
  58. package/dist/db/tokens.js +624 -0
  59. package/dist/db/tool-uses.js +647 -0
  60. package/dist/diagram/index.js +85 -0
  61. package/dist/diagram/label.js +455 -0
  62. package/dist/diagram/layout.js +476 -0
  63. package/dist/diagram/links.js +50 -0
  64. package/dist/diagram/metrics.js +152 -0
  65. package/dist/diagram/parse.js +487 -0
  66. package/dist/diagram/svg.js +389 -0
  67. package/dist/diagram/types.js +9 -0
  68. package/dist/export.js +629 -0
  69. package/dist/files/archive-types.js +2 -0
  70. package/dist/files/archive.js +182 -0
  71. package/dist/files/browse.js +327 -0
  72. package/dist/files/bytes.js +162 -0
  73. package/dist/files/codecs.js +33 -0
  74. package/dist/files/find.js +212 -0
  75. package/dist/files/tar.js +223 -0
  76. package/dist/files/zip.js +209 -0
  77. package/dist/folds.js +72 -0
  78. package/dist/grammars/ebnf.js +92 -0
  79. package/dist/highlight.js +250 -0
  80. package/dist/ids.js +24 -0
  81. package/dist/markdown-diagnostics.js +233 -0
  82. package/dist/math.js +168 -0
  83. package/dist/mcp/server.js +886 -0
  84. package/dist/memory/embedder.js +145 -0
  85. package/dist/memory/embedding-worker.js +71 -0
  86. package/dist/memory/embeddings.js +91 -0
  87. package/dist/memory/index.js +232 -0
  88. package/dist/memory/storage.js +587 -0
  89. package/dist/memory/wiki.js +688 -0
  90. package/dist/memory/wordpiece.js +247 -0
  91. package/dist/mentions-scan.js +40 -0
  92. package/dist/presence.js +259 -0
  93. package/dist/session-affinity.js +185 -0
  94. package/dist/shared/api-request.js +124 -0
  95. package/dist/shared/app-origin.js +26 -0
  96. package/dist/shared/block-glyphs.js +114 -0
  97. package/dist/shared/diagram-css.js +17 -0
  98. package/dist/shared/export-selection.js +101 -0
  99. package/dist/shared/find-in-text.js +110 -0
  100. package/dist/shared/glow-hues.js +225 -0
  101. package/dist/shared/hex-dump.js +62 -0
  102. package/dist/shared/hex-pages.js +52 -0
  103. package/dist/shared/hit-drum.js +79 -0
  104. package/dist/shared/known-nids.js +58 -0
  105. package/dist/shared/list-scroll-keys.js +56 -0
  106. package/dist/shared/markdown-lists.js +72 -0
  107. package/dist/shared/markdown-math.js +485 -0
  108. package/dist/shared/markdown-mentions.js +79 -0
  109. package/dist/shared/markdown-nids.js +41 -0
  110. package/dist/shared/markdown-protocols.js +53 -0
  111. package/dist/shared/markdown-refs.js +123 -0
  112. package/dist/shared/membrane-invasion.js +342 -0
  113. package/dist/shared/membrane-motion.js +83 -0
  114. package/dist/shared/membrane-palette.js +253 -0
  115. package/dist/shared/membrane-terrain.js +212 -0
  116. package/dist/shared/message-selection.js +123 -0
  117. package/dist/shared/mo-glyph.js +57 -0
  118. package/dist/shared/narrative-format.js +237 -0
  119. package/dist/shared/narrative-menu.js +84 -0
  120. package/dist/shared/narrative-sort.js +47 -0
  121. package/dist/shared/narrative-status.js +52 -0
  122. package/dist/shared/no-pan.js +85 -0
  123. package/dist/shared/pane-layout.js +50 -0
  124. package/dist/shared/payload-inline.js +83 -0
  125. package/dist/shared/speaker-color.js +61 -0
  126. package/dist/shared/token-rotation.js +14 -0
  127. package/dist/shared/view-url.js +216 -0
  128. package/dist/shared/wiki-links.js +70 -0
  129. package/dist/shared/wiki-window.js +4 -0
  130. package/dist/tailscale.js +55 -0
  131. package/dist/test/absorb-claude.test.js +892 -0
  132. package/dist/test/agent-scope.test.js +193 -0
  133. package/dist/test/agents.test.js +55 -0
  134. package/dist/test/api-request.test.js +122 -0
  135. package/dist/test/app-host-literal.test.js +53 -0
  136. package/dist/test/awaiting-glow.test.js +54 -0
  137. package/dist/test/awaiting.test.js +122 -0
  138. package/dist/test/bind-token.test.js +88 -0
  139. package/dist/test/block-glyphs.test.js +68 -0
  140. package/dist/test/cert-manager.test.js +262 -0
  141. package/dist/test/claude-live-session.test.js +110 -0
  142. package/dist/test/codex-tool-log.test.js +568 -0
  143. package/dist/test/config.test.js +71 -0
  144. package/dist/test/copilot-tool-log.test.js +93 -0
  145. package/dist/test/cors-preflight.test.js +56 -0
  146. package/dist/test/cost-linger.test.js +31 -0
  147. package/dist/test/cost-pricing.test.js +72 -0
  148. package/dist/test/cost-reprice.test.js +139 -0
  149. package/dist/test/cost-sessions.test.js +301 -0
  150. package/dist/test/crush-tool-log.test.js +289 -0
  151. package/dist/test/daemon-client.test.js +109 -0
  152. package/dist/test/daemon-socket.test.js +293 -0
  153. package/dist/test/diagram-arrows.test.js +205 -0
  154. package/dist/test/diagram-export.test.js +210 -0
  155. package/dist/test/diagram-labels.test.js +313 -0
  156. package/dist/test/diagram-parse.test.js +191 -0
  157. package/dist/test/diagram-render.test.js +175 -0
  158. package/dist/test/diagram-review.test.js +288 -0
  159. package/dist/test/diagram-self-loop-labels.test.js +303 -0
  160. package/dist/test/embedder.test.js +84 -0
  161. package/dist/test/events.test.js +123 -0
  162. package/dist/test/export-selection.test.js +102 -0
  163. package/dist/test/export.test.js +505 -0
  164. package/dist/test/files-browse.test.js +393 -0
  165. package/dist/test/files-find.test.js +272 -0
  166. package/dist/test/find-in-text.test.js +86 -0
  167. package/dist/test/first-paint-viewport.test.js +59 -0
  168. package/dist/test/fixtures/embedding-golden.js +23 -0
  169. package/dist/test/fixtures/wordpiece-golden.js +167 -0
  170. package/dist/test/glow-hues.test.js +202 -0
  171. package/dist/test/harness-cost.test.js +423 -0
  172. package/dist/test/helpers.js +21 -0
  173. package/dist/test/hex-pages.test.js +68 -0
  174. package/dist/test/highlight.test.js +69 -0
  175. package/dist/test/hit-drum.test.js +71 -0
  176. package/dist/test/hook-config.test.js +299 -0
  177. package/dist/test/json-gzip.test.js +70 -0
  178. package/dist/test/key-routing.test.js +58 -0
  179. package/dist/test/known-nids.test.js +106 -0
  180. package/dist/test/link-preview.test.js +67 -0
  181. package/dist/test/list-scroll-keys.test.js +66 -0
  182. package/dist/test/listen-own-words.test.js +147 -0
  183. package/dist/test/listen-timeout.test.js +33 -0
  184. package/dist/test/markdown-diagnostics.test.js +121 -0
  185. package/dist/test/markdown-lists.test.js +182 -0
  186. package/dist/test/markdown-math.test.js +256 -0
  187. package/dist/test/markdown-nids.test.js +146 -0
  188. package/dist/test/markdown-protocols.test.js +139 -0
  189. package/dist/test/mcp-bridge-cancel.test.js +98 -0
  190. package/dist/test/mcp-bridge-reconnect.test.js +121 -0
  191. package/dist/test/mcp-narrative.test.js +193 -0
  192. package/dist/test/mcp.test.js +548 -0
  193. package/dist/test/meld.test.js +143 -0
  194. package/dist/test/membrane-invasion.test.js +482 -0
  195. package/dist/test/membrane-motion.test.js +101 -0
  196. package/dist/test/membrane-palette.test.js +166 -0
  197. package/dist/test/membrane-terrain.test.js +170 -0
  198. package/dist/test/memory-log.test.js +202 -0
  199. package/dist/test/mentions-e2e.test.js +261 -0
  200. package/dist/test/mentions-ledger.test.js +265 -0
  201. package/dist/test/mentions-listen.test.js +265 -0
  202. package/dist/test/mentions.test.js +238 -0
  203. package/dist/test/message-selection.test.js +114 -0
  204. package/dist/test/mo-glyph.test.js +22 -0
  205. package/dist/test/model-resolution.test.js +166 -0
  206. package/dist/test/narrate-validate.test.js +77 -0
  207. package/dist/test/narrate-wait.test.js +160 -0
  208. package/dist/test/narrative-format.test.js +219 -0
  209. package/dist/test/narrative-menu.test.js +150 -0
  210. package/dist/test/narrative-sort.test.js +75 -0
  211. package/dist/test/no-pan.test.js +60 -0
  212. package/dist/test/pane-layout.test.js +153 -0
  213. package/dist/test/paths.test.js +37 -0
  214. package/dist/test/payload-inline.test.js +91 -0
  215. package/dist/test/precache-fonts.test.js +72 -0
  216. package/dist/test/presence-gate.test.js +440 -0
  217. package/dist/test/presence.test.js +160 -0
  218. package/dist/test/read-events.test.js +220 -0
  219. package/dist/test/remote-meld.test.js +128 -0
  220. package/dist/test/revive-cli.test.js +93 -0
  221. package/dist/test/search-floor.test.js +206 -0
  222. package/dist/test/search-fusion.test.js +168 -0
  223. package/dist/test/search.test.js +205 -0
  224. package/dist/test/security.test.js +48 -0
  225. package/dist/test/selection-wiring.test.js +61 -0
  226. package/dist/test/session-affinity.test.js +295 -0
  227. package/dist/test/signal-source.test.js +32 -0
  228. package/dist/test/socket-account.test.js +268 -0
  229. package/dist/test/spawn-toml.test.js +72 -0
  230. package/dist/test/speaker-color.test.js +90 -0
  231. package/dist/test/tailscale-rebind.test.js +79 -0
  232. package/dist/test/terminal-cursor.test.js +61 -0
  233. package/dist/test/terminal-resync.test.js +23 -0
  234. package/dist/test/terminal-size.test.js +36 -0
  235. package/dist/test/thin-cli.test.js +382 -0
  236. package/dist/test/token-bootstrap.test.js +272 -0
  237. package/dist/test/token-devices.test.js +646 -0
  238. package/dist/test/token-lapse.test.js +94 -0
  239. package/dist/test/token-ring.test.js +188 -0
  240. package/dist/test/token-rotate-cli.test.js +58 -0
  241. package/dist/test/token-routes.test.js +95 -0
  242. package/dist/test/tool-uses.test.js +1006 -0
  243. package/dist/test/usage-ledger.test.js +126 -0
  244. package/dist/test/view-url.test.js +627 -0
  245. package/dist/test/wiki-links.test.js +57 -0
  246. package/dist/test/wiki-sources.test.js +85 -0
  247. package/dist/test/wiki-window.test.js +31 -0
  248. package/dist/test/wiki.test.js +399 -0
  249. package/dist/test/wordpiece.test.js +68 -0
  250. package/dist/test/wrangler-route.test.js +27 -0
  251. package/dist/tls.js +148 -0
  252. package/dist/tmux.js +213 -0
  253. package/dist/user-agent.js +46 -0
  254. package/docker-entrypoint.sh +154 -0
  255. package/docs/README.md +3 -0
  256. package/docs/absorb.md +231 -0
  257. package/docs/cost-ledger.md +125 -0
  258. package/docs/daemon-api.md +189 -0
  259. package/docs/diagrams.md +163 -0
  260. package/docs/doctor.md +36 -0
  261. package/docs/export-scope.md +69 -0
  262. package/docs/files-view.md +133 -0
  263. package/docs/link-previews.md +38 -0
  264. package/docs/math.md +32 -0
  265. package/docs/mcp.md +69 -0
  266. package/docs/memory-search.md +75 -0
  267. package/docs/mentions.md +101 -0
  268. package/docs/multi-brane.md +14 -0
  269. package/docs/passkeys.md +165 -0
  270. package/docs/presence.md +202 -0
  271. package/docs/releasing.md +104 -0
  272. package/docs/rendering-warnings.md +22 -0
  273. package/docs/request-layer.md +72 -0
  274. package/docs/selection-store.md +50 -0
  275. package/docs/session-affinity.md +61 -0
  276. package/docs/terminal-mirroring.md +243 -0
  277. package/docs/token-rotation.md +431 -0
  278. package/docs/view-urls.md +134 -0
  279. package/docs/web-app.md +23 -0
  280. package/docs/wiki.md +43 -0
  281. package/expo/dist/_expo/static/js/web/entry-e94247f119fdcd3b424a1764233a41d2.js +1099 -0
  282. package/expo/dist/apple-touch-icon.png +0 -0
  283. package/expo/dist/favicon.svg +3 -0
  284. package/expo/dist/index.html +177 -0
  285. package/expo/dist/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  286. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  287. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  288. package/expo/dist/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  289. package/expo/dist/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  290. package/expo/dist/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  291. package/expo/dist/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  292. package/expo/dist/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  293. package/expo/dist/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  294. package/expo/dist/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  295. package/expo/dist/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  296. package/expo/dist/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  297. package/expo/dist/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  298. package/expo/dist/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  299. package/expo/dist/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  300. package/expo/dist/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  301. package/expo/dist/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  302. package/expo/dist/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  303. package/expo/dist/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  304. package/expo/dist/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  305. package/expo/dist/katex/katex.min.css +1 -0
  306. package/expo/dist/manifest.json +16 -0
  307. package/expo/dist/metadata.json +1 -0
  308. package/expo/dist/og/membrane.v1.png +0 -0
  309. package/expo/dist/pdf.min.mjs +28 -0
  310. package/expo/dist/pdf.worker.min.mjs +28 -0
  311. package/expo/dist/sw.js +154 -0
  312. package/package.json +79 -27
  313. package/protocols/LIVE_loop.md +407 -0
  314. package/protocols/bebrief.md +41 -0
  315. package/protocols/digest.md +231 -0
  316. package/protocols/fly.md +266 -0
  317. package/protocols/idle.md +81 -0
  318. package/protocols/keeper.md +829 -0
  319. package/protocols/markdown.md +80 -0
  320. package/protocols/memory.md +419 -0
  321. package/protocols/mentions.md +93 -0
  322. package/protocols/payloads.md +53 -0
  323. package/protocols/prelude.md +91 -0
  324. package/protocols/ramify.md +105 -0
  325. package/protocols/review.md +356 -0
  326. package/protocols/rrr.md +232 -0
  327. package/protocols/scribe.md +429 -0
  328. package/protocols/summarize.md +203 -0
  329. package/protocols/twin.md +174 -0
  330. package/protocols/wiki.md +438 -0
  331. package/protocols/worker.md +38 -0
  332. package/wiki/shared/append-only-event-log.md +148 -0
  333. package/wiki/shared/index.md +53 -0
  334. package/wiki/shared/owning-your-narratives.md +161 -0
  335. package/wiki/shared/premature-ontologies.md +435 -0
  336. package/wiki/shared/sedimentary-superintelligence.md +128 -0
  337. package/wiki/shared/sequestering-the-narrative.md +143 -0
  338. package/wiki/shared/why-wikis-work.md +216 -0
  339. package/.travis.yml +0 -7
  340. package/lib/brane.cjs.js +0 -12
  341. package/lib/brane.cjs.js.map +0 -1
  342. package/lib/brane.d.ts +0 -11
  343. package/lib/brane.js +0 -6
  344. package/lib/brane.js.map +0 -1
  345. package/lib/tests.d.ts +0 -1
  346. package/lib/tests.js +0 -8
  347. package/lib/tests.js.map +0 -1
  348. package/rollup.config.js +0 -21
  349. package/scripts/test.sh +0 -8
  350. package/src/brane.ts +0 -21
  351. package/src/tests.ts +0 -8
  352. package/tsconfig.json +0 -16
package/DESIGN.md ADDED
@@ -0,0 +1,405 @@
1
+ # Brane Design
2
+
3
+ Brane is a TypeScript npm package providing a CLI tool (`npx brane`) and
4
+ userspace daemon for managing narrative agent sessions, with a clean
5
+ SQLite-first architecture.
6
+
7
+ "Brane" is short for membrane — the biological metaphor carries over from
8
+ organelle, enabling reuse of terms like nucleus (daemon), narrative, and
9
+ organelle (agent).
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm i --save-dev brane
15
+ ```
16
+
17
+ ## CLI Subcommands
18
+
19
+ ```
20
+ brane fold # Register cwd as a fold (spawn point)
21
+ brane fold --list # List all registered folds
22
+ brane daemon start|stop|status # Manage the daemon
23
+ brane daemon restart # Restart, preserving port
24
+ brane daemon logs [-f] [-n N] # View daemon logs
25
+ brane spawn <agent> [-- <args>] # Spawn agent in tmux session
26
+ --role <role> # worker | coordinator | image-analyst
27
+ --attach # Attach to session after spawning
28
+ --model <model> # Model hint (e.g., haiku, sonnet)
29
+ brane list # List active sessions
30
+ brane attach <id> # Attach to tmux session
31
+ brane revive <id> # Restart a dead session
32
+ brane meld # Show QR + tappable URL for mobile access
33
+ brane config <key> [<value>] # Get/set configuration (git-config style)
34
+ brane memory search <query> # Semantic search across narratives
35
+ brane memory index # Re-index narrative content
36
+ brane mcp # Stdio MCP server (for agents)
37
+ ```
38
+
39
+ ### Folds
40
+
41
+ A "fold" is a registered directory where agents can be spawned. The term
42
+ draws from brain folds (gyri/sulci), membrane folding, and folder as
43
+ prefix. `brane fold` registers the current directory in the central
44
+ SQLite database.
45
+
46
+ ### Agent flexibility
47
+
48
+ `brane spawn` handles:
49
+ - Scrolling TUI apps (Claude, aider) that use the LIVE loop via MCP
50
+ - Static/paned TUI apps (lazygit) that need tmux + terminal streaming
51
+ - Plain commands (caffeinate) with no UI at all
52
+
53
+ MCP injection only happens for agents that support it. The tmux-loop
54
+ supervisor treats all spawned processes uniformly.
55
+
56
+ ### Meld
57
+
58
+ `brane meld` auto-detects Tailscale hostname (if available) and prints
59
+ a tappable URL + QR code for mobile access. The URL includes the daemon
60
+ auth token as a query parameter. Defaults to `brane.organelle.dev` with
61
+ the daemon as backend; falls back to local mode (`--local`) automatically
62
+ when the daemon lacks TLS certificates.
63
+
64
+ ## Storage
65
+
66
+ All state lives in `~/.brane/brane.db` (SQLite, WAL mode). No scattered
67
+ JSON files. The only files written to fold directories are agent-agnostic
68
+ MCP config stubs (`.mcp.json`) pointing back to `brane mcp`.
69
+
70
+ ### Identity
71
+
72
+ Each brane instance has a stable ID in the format `brane-<slug>` (short
73
+ random hex/base36), generated once and stored in SQLite.
74
+
75
+ ### Schema
76
+
77
+ ```sql
78
+ CREATE TABLE meta (
79
+ key TEXT PRIMARY KEY,
80
+ value TEXT NOT NULL
81
+ );
82
+ -- Keys: brane_id, daemon_port, daemon_pid, daemon_version,
83
+ -- daemon_tls, daemon_http_port
84
+ -- (No token: tokens are stored as hashes in `tokens`.)
85
+
86
+ CREATE TABLE tokens (
87
+ id TEXT PRIMARY KEY,
88
+ hash TEXT NOT NULL UNIQUE, -- SHA-256 of the token; the token is never stored
89
+ kind TEXT NOT NULL, -- 'local' (this machine's CLI) | 'device'
90
+ label TEXT NOT NULL,
91
+ origin TEXT NOT NULL, -- 'minted' | 'adopted' (plaintext from before hashing)
92
+ created_at INTEGER NOT NULL,
93
+ last_used_at INTEGER,
94
+ revoked_at INTEGER -- revoked, never deleted
95
+ );
96
+
97
+ CREATE TABLE folds (
98
+ path TEXT PRIMARY KEY,
99
+ -- ~/relative for portability
100
+ brane_id TEXT,
101
+ created_at INTEGER NOT NULL,
102
+ last_spawned INTEGER
103
+ );
104
+
105
+ CREATE TABLE narratives (
106
+ id TEXT PRIMARY KEY,
107
+ -- e.g. claude-4c7289d1
108
+ brane_id TEXT NOT NULL,
109
+ fold_path TEXT,
110
+ agent_type TEXT,
111
+ -- claude, lazygit, caffeinate
112
+ role TEXT,
113
+ -- worker, coordinator
114
+ created_at INTEGER NOT NULL,
115
+ status TEXT DEFAULT 'active',
116
+ agent_session_id TEXT,
117
+ title TEXT,
118
+ summary TEXT,
119
+ archived INTEGER DEFAULT 0
120
+ );
121
+
122
+ -- Append-only event log
123
+ CREATE TABLE events (
124
+ narrative_id TEXT NOT NULL,
125
+ seq INTEGER NOT NULL,
126
+ time INTEGER NOT NULL,
127
+ -- unix ms
128
+ type TEXT NOT NULL,
129
+ -- narration, human_signal, agent_signal
130
+ content TEXT,
131
+ content_type TEXT,
132
+ source TEXT,
133
+ -- who produced this event (narrative ID, device name, etc.)
134
+ re TEXT,
135
+ -- JSON array of back-references
136
+ payloads TEXT,
137
+ -- JSON array of attachment paths
138
+ meta TEXT,
139
+ -- JSON object for per-event metadata (e.g. {"model": "gpt-4o"})
140
+ PRIMARY KEY (narrative_id, seq)
141
+ );
142
+ CREATE INDEX idx_events_narrative_seq ON events(narrative_id, seq);
143
+
144
+ ```
145
+
146
+ ## ID Scheme
147
+
148
+ Globally unique event addressing: `<brane-id>:<narrative-id>:<seq>`
149
+
150
+ - `brane-a7f3c2:claude-4c7289d1:42` — full global address
151
+ - `claude-4c7289d1:42` — local address (within a brane)
152
+ - `claude-4c7289d1` — narrative address (no specific event)
153
+ - `42` — seq within current narrative
154
+
155
+ ## MCP Architecture
156
+
157
+ The MCP server (`brane mcp`, defined in `src/mcp/server.ts`) is a **thin
158
+ stdio↔WebSocket bridge** (~230 lines). It has zero SQLite dependency.
159
+ All tool logic lives in the daemon (`src/daemon/server.ts`), which the
160
+ MCP bridge connects to via `/ws/mcp?nid=<id>`.
161
+
162
+ When `brane spawn` creates an agent session, it generates `.mcp.json`
163
+ in the fold directory with `BRANE_NARRATIVE_ID` as an environment
164
+ variable.
165
+
166
+ ### MCP Tools
167
+
168
+ #### narrate
169
+
170
+ Post a narration and receive pending signals in one call.
171
+
172
+ ```jsonc
173
+ {
174
+ "say": "Working on the auth module",
175
+ // Cursor: last seq you've processed
176
+ // Number for own narrative, or map for multi-narrative
177
+ "heard": 42,
178
+ // OR: "heard": { "self": 42, "claude-def": 10 }
179
+
180
+ // Optional: target narrative (default: self)
181
+ "narrative": "claude-def",
182
+ // Optional: reply-to refs
183
+ "re": [42, "claude-def:10"]
184
+ }
185
+ ```
186
+
187
+ - Writes to one narrative at a time: self (default) or explicit
188
+ `narrative` target
189
+ - Foreign narration (narrative != self) is always explicit
190
+ - `re` is metadata only — does not affect routing
191
+ - `heard` polls for signals newer than the cursor
192
+
193
+ #### listen
194
+
195
+ Block and wait for signals. Stateless cursor-based long-polling.
196
+
197
+ ```jsonc
198
+ {
199
+ // Number for own narrative
200
+ "heard": 42
201
+ // OR map for multi-narrative polling:
202
+ // "heard": { "self": 42, "claude-def": 10 }
203
+ }
204
+ ```
205
+
206
+ #### whoami
207
+
208
+ Returns the agent's narrative ID and brane ID.
209
+
210
+ #### revive
211
+
212
+ Restart a dead tmux session. Idempotent. Optional prompt injection.
213
+
214
+ #### narrative_search
215
+
216
+ Semantic search across narrative history. Auto-indexes unindexed events.
217
+ Takes `query` (required) and `limit` (optional). Returns ranked results
218
+ with narrative_id, seq, content, and score.
219
+
220
+ #### narrative_info
221
+
222
+ Get info about narratives. Without `nid`: browse recently active
223
+ narratives (takes optional `since`, `search`, `status`, `fold`, `limit`).
224
+ With `nid` (string or array): get detailed info on specific narratives
225
+ including event count, metadata, timestamps, mcp_connected, and archived
226
+ status.
227
+
228
+ #### list_protocols / read_protocol
229
+
230
+ List and read protocol documents (LIVE_loop, memory, worker, etc.).
231
+
232
+ ## Daemon
233
+
234
+ The daemon (`src/daemon/server.ts`) serves:
235
+
236
+ - **Static files**: the built Expo web app from `expo/dist/`
237
+ - **HTTP API**: narratives, events, signals
238
+ - **WebSocket endpoints**:
239
+ - `/ws/control` — event hints for live UI updates
240
+ - `/ws/terminal/:nid` — tmux terminal streaming (capture-pane at 10fps)
241
+ - `/ws/mcp/:nid` — MCP tool bridge (daemon owns all tool logic)
242
+
243
+ All endpoints require `Authorization: Bearer <token>`.
244
+
245
+ Port reuse: the daemon automatically reuses its previous port across
246
+ restarts. The restart command captures the port before sending SIGTERM
247
+ (since the shutdown handler deletes it from SQLite).
248
+
249
+ ### TLS
250
+
251
+ The daemon owns its TLS cert lifecycle end-to-end via
252
+ `src/daemon/cert-manager.ts`:
253
+
254
+ - **Auto-provision on first start.** If `tailscale` is available and no
255
+ cert exists at `~/.brane/certs/tls.{crt,key}`, the daemon shells out
256
+ to `tailscale cert <host>` (same call `brane certify` makes) and
257
+ binds HTTPS with the result. First-run users never need to run
258
+ `brane certify`.
259
+ - **Auto-renewal.** An hourly tick re-checks the cert's `notAfter`. When
260
+ fewer than 30 days remain (Tailscale issues 90-day Let's Encrypt
261
+ certs), the daemon re-provisions in the background. Failures back
262
+ off `1h → 2h → 4h → 8h → 24h`.
263
+ - **Hot-reload.** `fs.watch` on the certs directory catches both
264
+ internal renewals and operator-driven `brane certify` runs, then
265
+ calls `https.Server#setSecureContext` with the new bytes — no
266
+ daemon restart required.
267
+ - **Subject-CN check.** A cert whose CN doesn't match the current
268
+ Tailscale host is treated as invalid and forces re-provisioning,
269
+ catching tailnet/node renames.
270
+
271
+ When TLS is active the daemon also starts an HTTP fallback on a
272
+ separate ephemeral port (restricted to private/local IPs only). This
273
+ allows local and LAN clients to connect without TLS certificate
274
+ hostname mismatches. The HTTP fallback port is stored as
275
+ `daemon_http_port` in the meta table and cleaned up on shutdown.
276
+
277
+ The current state (`expires_at`, `state`, `last_error_*`,
278
+ `next_attempt_at`, `host`) is exposed on `GET /api/status` and
279
+ broadcast over `/ws/control` as `{ type: "tls", ... }` on every
280
+ transition for the UI to surface a banner. `brane certify` remains as
281
+ a manual escape hatch for forcing an immediate re-provision.
282
+
283
+ ### CORS
284
+
285
+ The daemon answers any origin with `Access-Control-Allow-Origin: *` and no
286
+ credentials. There is no allowlist: every API request and WebSocket upgrade
287
+ must present a token, so a page holding none gets a 401 whatever its origin,
288
+ and the hostname the app is served from (`brane.organelle.dev`, a staging
289
+ host, a fork's deployment, the daemon's own bundle, `localhost` in dev) is
290
+ not a trust decision. The origin each device runs the app from is recorded
291
+ on its token for the Devices list instead (`SECURITY.md`, Authentication).
292
+
293
+ ## Web App (Expo)
294
+
295
+ The Expo app at `expo/` is a React Native Web PWA served by the daemon.
296
+
297
+ ### Views
298
+
299
+ - **Narrative list** (`app/index.tsx`) — shows all active narratives
300
+ - **Narrative detail** (`app/[nid].tsx`) — chat + terminal tabs
301
+
302
+ ### Chat Tab
303
+
304
+ - Operator messages: blue bubbles (#1a4a7a), right-aligned
305
+ - Agent narrations: dark gray (#2a2a2a), left-aligned
306
+ - Foreign narrations: centered, hash-based pastel backgrounds
307
+ - Source labels on agent/foreign messages
308
+
309
+ ### Terminal Tab
310
+
311
+ - Live tmux mirroring via `ansi_up` ANSI→HTML conversion
312
+ - ResizeObserver calculates cols/rows from monospace metrics
313
+ - Physical keyboard capture on desktop (tabIndex + keydown)
314
+ - Quick bar: macros (live loop, go) + quick keys (1, 2, 3, ↑, ↓)
315
+ - Full software keyboard with multitouch modifier support
316
+ - Swipe gestures for tab switching
317
+
318
+ ### Building
319
+
320
+ ```sh
321
+ cd expo && npx expo export --platform web
322
+ ```
323
+
324
+ Output goes to `expo/dist/`, served by the daemon as static files.
325
+
326
+ ## Threading Model
327
+
328
+ Invariants:
329
+
330
+ 1. Every event belongs to exactly one narrative (stored in `narrative_id`)
331
+ 2. Every event has a monotonic `seq` within its narrative
332
+ 3. Every event has a `re` field — an array of back-references (can be
333
+ empty)
334
+ 4. Long-polling a narrative returns all new events with `seq > cursor`,
335
+ regardless of their `re` values
336
+ 5. Threading is metadata, not routing — `re` affects display, not
337
+ delivery
338
+
339
+ ### re: references
340
+
341
+ The `re` field is a JSON array. Each element is either:
342
+ - A bare seq number — refers to that seq in the same narrative
343
+ - A qualified string `"narrative-id:seq"` — cross-narrative reference
344
+ - A narrative ID without seq `"narrative-id"` — refers to the narrative
345
+ as a whole
346
+
347
+ Examples:
348
+ - `re` omitted or `[]` → new topic / continuation by adjacency
349
+ - `[42]` → replying to one specific event in the same narrative
350
+ - `[42, "claude-def:10"]` → synthesizing multiple sources
351
+
352
+ ### UI threading
353
+
354
+ The web UI displays a linear timeline (all events in seq order). `re:`
355
+ links render as clickable back-references. Click to scroll/highlight
356
+ the referenced event. No visual nesting — threading is navigational.
357
+
358
+ Foreign agent messages are stored in the timeline but collapsed by
359
+ default to avoid clutter from side conversations. Expandable on demand.
360
+
361
+ ### Prefix memory (copy-on-write)
362
+
363
+ When spawning with `--re <narrative-id>`, the new narrative logically
364
+ shares prefix memory with the parent. Events are not duplicated — the
365
+ parent's history is resolved on demand by walking the ref chain.
366
+
367
+ ## Payloads (Attachments)
368
+
369
+ Payloads are copied into a stable location on receipt:
370
+ `~/.brane/attachments/<narrative_id>/<seq>_<index>.<ext>`
371
+
372
+ Referenced in the events table as a JSON array in the `payloads` column:
373
+ ```json
374
+ ["attachments/claude-abc/42_0.png"]
375
+ ```
376
+
377
+ Paths stored relative to `~/.brane/` for portability. The MCP server
378
+ resolves to absolute paths when returning events to agents.
379
+
380
+ ## Identity and Security
381
+
382
+ - All IDs are server-assigned (UUIDs / random slugs) — unspoofable
383
+ - The MCP server fixes a narration's narrative and type server-side: an
384
+ agent's bridge binds only as its own narrative, and narration into
385
+ another arrives as an `agent_signal`
386
+ - A `human_signal`'s `source` is stamped from the authenticated token
387
+ (`web` for a device, `local` for this account), never taken from the
388
+ request, and an agent's token cannot send one
389
+ - **Not built yet:** telling two humans apart. Signals are not tied to
390
+ the device token that sent them. See SECURITY.md, "Who a signal is from"
391
+
392
+ ## Dependencies
393
+
394
+ - **citty** — TypeScript-native CLI framework (subcommand definitions)
395
+ - **better-sqlite3** — SQLite driver (synchronous, WAL mode)
396
+ - **ws** — WebSocket library
397
+ - **qrcode-terminal** — QR code generation for `brane meld`
398
+
399
+ Optional (for semantic search):
400
+ - **onnxruntime-node** — runs the local embedding model (`bge-small-en-v1.5`); tokenizer and pooling are in `src/memory/`
401
+ - **sqlite-vec** — vector search extension for SQLite
402
+
403
+ ## Code style
404
+
405
+ - Comments above what they annotate, not trailing
package/Dockerfile ADDED
@@ -0,0 +1,61 @@
1
+ FROM node:24-bookworm
2
+
3
+ # System dependencies (tmux: the daemon fetches a verified static build into
4
+ # ~/.brane/bin when it starts)
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ git \
7
+ curl \
8
+ ca-certificates \
9
+ gnupg \
10
+ gosu \
11
+ sqlite3 \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Tailscale (sole network access mechanism)
15
+ RUN curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.noarmor.gpg | \
16
+ gpg --dearmor -o /usr/share/keyrings/tailscale-archive-keyring.gpg && \
17
+ curl -fsSL https://pkgs.tailscale.com/stable/debian/bookworm.tailscale-keyring.list | \
18
+ tee /etc/apt/sources.list.d/tailscale.list && \
19
+ apt-get update && apt-get install -y --no-install-recommends tailscale && \
20
+ rm -rf /var/lib/apt/lists/*
21
+
22
+ # Claude Code CLI. Its postinstall copies the native binary over the
23
+ # `claude` placeholder, and npm 11 skips a global package's install scripts
24
+ # unless they're allowed, so run it explicitly; `claude --version` fails the
25
+ # build if the placeholder is still there.
26
+ RUN npm i -g @anthropic-ai/claude-code && \
27
+ node "$(npm root -g)/@anthropic-ai/claude-code/install.cjs" && \
28
+ claude --version
29
+
30
+ # Brane (from pre-built tarball in the build context)
31
+ COPY brane-*.tgz /tmp/
32
+ RUN npm i -g /tmp/brane-*.tgz
33
+
34
+ # Non-root user for running the daemon and spawned agents
35
+ RUN useradd -m -s /bin/bash brane && \
36
+ mkdir -p /home/brane/.brane/bin /home/brane/workspace && \
37
+ cp /root/.brane/bin/tmux /home/brane/.brane/bin/tmux 2>/dev/null || true && \
38
+ chown -R brane:brane /home/brane/.brane /home/brane/workspace
39
+
40
+ # Pre-download embedding model into image layer (outside ~/.brane volume),
41
+ # with brane's own loader: the files land exactly where the daemon looks
42
+ # (BRANE_MODEL_CACHE), and one embedding is run, so a broken download fails
43
+ # the build rather than the first search.
44
+ ENV BRANE_MODEL_CACHE=/opt/brane/models
45
+ RUN mkdir -p /opt/brane/models && \
46
+ node --input-type=module \
47
+ -e 'const { createEmbedder } = await import(process.argv[1]); const e = await createEmbedder(); const [v] = await e.embed(["warm"]); await e.dispose(); if (v.length !== 384) throw new Error("unexpected vector"); console.log("Model cached.")' \
48
+ "$(npm root -g)/brane/dist/memory/embedder.js" && \
49
+ chown -R brane:brane /opt/brane
50
+
51
+ # Entrypoint (starts as root for tailscaled, drops to brane user)
52
+ COPY docker-entrypoint.sh /usr/local/bin/
53
+ RUN chmod +x /usr/local/bin/docker-entrypoint.sh
54
+
55
+ # Persist home directory across container restarts (brane data, cargo, etc.)
56
+ VOLUME /home/brane
57
+
58
+ HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
59
+ CMD gosu brane brane daemon status || exit 1
60
+
61
+ ENTRYPOINT ["docker-entrypoint.sh"]