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
@@ -0,0 +1,62 @@
1
+ import { defineCommand } from "citty";
2
+ import { mkdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { ensureBraneDir } from "../db/schema.js";
5
+ import { getTailscaleHost } from "../tailscale.js";
6
+ import { provisionTlsCert } from "../tls.js";
7
+ function getCertsDir() {
8
+ const dir = join(ensureBraneDir(), "certs");
9
+ mkdirSync(dir, { recursive: true });
10
+ return dir;
11
+ }
12
+ export function getTlsCertPath() {
13
+ return join(getCertsDir(), "tls.crt");
14
+ }
15
+ export function getTlsKeyPath() {
16
+ return join(getCertsDir(), "tls.key");
17
+ }
18
+ export const certifyCommand = defineCommand({
19
+ meta: {
20
+ name: "certify",
21
+ description: "Provision TLS certificates for Tailscale HTTPS access",
22
+ },
23
+ args: {
24
+ host: {
25
+ type: "string",
26
+ description: "Tailscale hostname to certify (default: auto-detect)",
27
+ },
28
+ },
29
+ async run({ args }) {
30
+ const host = args.host ?? getTailscaleHost();
31
+ if (!host) {
32
+ console.error("Could not detect Tailscale hostname. Is Tailscale running?\n" +
33
+ "Use --host to specify the hostname manually.");
34
+ process.exit(1);
35
+ }
36
+ const certPath = getTlsCertPath();
37
+ const keyPath = getTlsKeyPath();
38
+ console.log(`Provisioning TLS certificate for ${host}...`);
39
+ const result = await provisionTlsCert({
40
+ host,
41
+ certPath,
42
+ keyPath,
43
+ });
44
+ if (!result.ok) {
45
+ console.error(`\nFailed to provision certificate (${result.reason}): ${result.message}`);
46
+ if (result.stderr)
47
+ console.error(result.stderr);
48
+ if (result.reason === "https_not_enabled") {
49
+ console.error("\nEnable HTTPS for your tailnet at https://login.tailscale.com/admin/dns");
50
+ }
51
+ else if (result.reason === "tailscale_missing") {
52
+ console.error("\nInstall Tailscale: https://tailscale.com/download");
53
+ }
54
+ process.exit(1);
55
+ }
56
+ console.log(`\nCertificate written to ${result.certPath}`);
57
+ console.log(`Key written to ${result.keyPath}`);
58
+ console.log(`Expires: ${result.notAfter.toISOString()}`);
59
+ console.log(`\nA running daemon will pick up the new cert automatically via fs.watch — no restart needed.`);
60
+ },
61
+ });
62
+ //# sourceMappingURL=certify.js.map
@@ -0,0 +1,133 @@
1
+ import { defineCommand } from "citty";
2
+ import { openDatabase } from "../db/schema.js";
3
+ import { cleanAppHost } from "../shared/app-origin.js";
4
+ // Well-known config keys and their defaults
5
+ export const CONFIG_DEFAULTS = {
6
+ // The hostname that serves the shared app: where `brane meld` points a QR
7
+ // unless `--app` says otherwise, and what `GET /api/status` reports so a
8
+ // daemon's own bundle knows where Isolate hops out to. Never a trust
9
+ // decision: the daemon accepts any origin (SECURITY.md, Authentication).
10
+ // A fork changes this one line, and its daemons point at its own shell.
11
+ app_domain: "brane.organelle.dev",
12
+ "session-affinity.enabled": "true",
13
+ };
14
+ /** The shared app's host from a configured or given `app_domain` value,
15
+ * reduced to a host (`cleanAppHost`); a blank one means the default, so
16
+ * `brane config app_domain ""` or `brane meld --app ""` can never make a QR
17
+ * for `https:///`. Every reader applies this one rule — `brane meld`
18
+ * (`buildMeldUrl`) and `GET /api/status` (`statusAppDomain`) — so a daemon's
19
+ * bundle and the QR always agree on where the shared app is. Lives here,
20
+ * beside the default it falls back to, so the daemon need not import the
21
+ * meld command to read it. */
22
+ export function resolveAppHost(value) {
23
+ return cleanAppHost(value ?? "") || cleanAppHost(CONFIG_DEFAULTS.app_domain);
24
+ }
25
+ /** Reads a config key. Pass the caller's `db` whenever it has one: the daemon
26
+ * and every test do. Without it this opens `~/.brane/brane.db` itself — right
27
+ * for a CLI command, and exactly wrong anywhere else. That fallback is how a
28
+ * test that drove a mention receipt used to open the operator's live database
29
+ * and run the migration ladder on it, and how the daemon used to open (and
30
+ * never close) a fresh connection on every `listen` reply. */
31
+ export function getConfig(key, db) {
32
+ const conn = db ?? openDatabase();
33
+ try {
34
+ const row = conn.prepare("SELECT value FROM meta WHERE key = ?").get(`config.${key}`);
35
+ return row?.value ?? CONFIG_DEFAULTS[key];
36
+ }
37
+ finally {
38
+ if (!db)
39
+ conn.close();
40
+ }
41
+ }
42
+ export const configCommand = defineCommand({
43
+ meta: {
44
+ name: "config",
45
+ description: "Get or set brane configuration",
46
+ },
47
+ args: {
48
+ key: {
49
+ type: "positional",
50
+ description: "Configuration key",
51
+ required: false,
52
+ },
53
+ value: {
54
+ type: "positional",
55
+ description: "Value to set (omit to get current value)",
56
+ required: false,
57
+ },
58
+ list: {
59
+ type: "boolean",
60
+ description: "List all configuration values",
61
+ },
62
+ unset: {
63
+ type: "boolean",
64
+ description: "Remove a configuration key",
65
+ },
66
+ },
67
+ run({ args }) {
68
+ const db = openDatabase();
69
+ // --list: show all config
70
+ if (args.list) {
71
+ const rows = db
72
+ .prepare("SELECT key, value FROM meta WHERE key LIKE 'config.%' ORDER BY key")
73
+ .all();
74
+ if (rows.length === 0 && Object.keys(CONFIG_DEFAULTS).length === 0) {
75
+ console.log("No configuration set.");
76
+ return;
77
+ }
78
+ // Show defaults that haven't been overridden
79
+ const setKeys = new Set(rows.map((r) => r.key.replace(/^config\./, "")));
80
+ for (const [key, value] of Object.entries(CONFIG_DEFAULTS)) {
81
+ if (!setKeys.has(key)) {
82
+ console.log(`${key}=${value} (default)`);
83
+ }
84
+ }
85
+ // Show explicitly set values
86
+ for (const row of rows) {
87
+ const key = row.key.replace(/^config\./, "");
88
+ console.log(`${key}=${row.value}`);
89
+ }
90
+ return;
91
+ }
92
+ const key = args.key;
93
+ if (!key) {
94
+ // No key provided and no --list flag: show usage
95
+ console.log("Usage: brane config <key> [value]");
96
+ console.log(" brane config --list");
97
+ console.log(" brane config --unset <key>");
98
+ return;
99
+ }
100
+ // --unset: remove a key
101
+ if (args.unset) {
102
+ const deleted = db.prepare("DELETE FROM meta WHERE key = ?").run(`config.${key}`);
103
+ if (deleted.changes > 0) {
104
+ console.log(`Unset ${key}`);
105
+ }
106
+ else {
107
+ console.log(`${key} is not set`);
108
+ }
109
+ return;
110
+ }
111
+ const value = args.value;
112
+ if (value === undefined) {
113
+ // Get
114
+ const row = db.prepare("SELECT value FROM meta WHERE key = ?").get(`config.${key}`);
115
+ if (row) {
116
+ console.log(row.value);
117
+ }
118
+ else if (CONFIG_DEFAULTS[key] !== undefined) {
119
+ console.log(`${CONFIG_DEFAULTS[key]} (default)`);
120
+ }
121
+ else {
122
+ // Key not found, exit silently like git config
123
+ process.exit(1);
124
+ }
125
+ }
126
+ else {
127
+ // Set
128
+ db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(`config.${key}`, value);
129
+ console.log(`${key}=${value}`);
130
+ }
131
+ },
132
+ });
133
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,126 @@
1
+ import { defineCommand } from "citty";
2
+ import { resolveDaemonEndpoint, daemonRequest, warnIfVersionMismatch } from "../daemon/client.js";
3
+ import { formatCost } from "../shared/narrative-format.js";
4
+ const backfillCommand = defineCommand({
5
+ meta: {
6
+ name: "backfill",
7
+ description: "Sweep the usage ledger over every narrative whose transcripts still exist",
8
+ },
9
+ args: {
10
+ "dry-run": {
11
+ type: "boolean",
12
+ description: "Report what would be recorded without writing anything",
13
+ },
14
+ all: {
15
+ type: "boolean",
16
+ description: "List every readable narrative, not just the ones that would change",
17
+ },
18
+ },
19
+ async run({ args }) {
20
+ try {
21
+ // The sweep runs in the daemon — readers resolve transcripts against its
22
+ // HOME and memoize in its process — so this is a client of
23
+ // POST /api/cost/backfill, in the same shape as `brane revive`.
24
+ const endpoint = await resolveDaemonEndpoint();
25
+ warnIfVersionMismatch(endpoint);
26
+ const result = await daemonRequest(endpoint, "/api/cost/backfill", {
27
+ method: "POST",
28
+ body: { dry_run: !!args["dry-run"] },
29
+ });
30
+ console.log(formatBackfillResult(result, { all: !!args.all }));
31
+ }
32
+ catch (err) {
33
+ console.error(err instanceof Error ? err.message : String(err));
34
+ process.exit(1);
35
+ }
36
+ },
37
+ });
38
+ export function formatBackfillResult(result, opts = {}) {
39
+ const changed = result.samples.filter((s) => s.new_rows > 0 || s.new_amount > 0 || s.after !== s.before);
40
+ const shown = opts.all ? result.samples : changed;
41
+ const verb = result.dry_run ? "would record" : "recorded";
42
+ const lines = [];
43
+ for (const s of shown) {
44
+ const delta = formatCost(s.new_amount, s.unit) ?? "0";
45
+ const before = formatCost(s.before, s.unit) ?? "—";
46
+ const after = formatCost(s.after, s.unit) ?? "—";
47
+ lines.push(`${s.nid.padEnd(18)} ${s.agent_type.padEnd(8)} ${s.readable}/${s.sessions} sessions +${s.new_rows} rows (${delta}) ${before} → ${after}`);
48
+ }
49
+ // Per-unit totals of what this pass adds, never summed across units.
50
+ const byUnit = new Map();
51
+ for (const s of result.samples)
52
+ byUnit.set(s.unit, (byUnit.get(s.unit) ?? 0) + s.new_amount);
53
+ const totals = [...byUnit].map(([u, a]) => formatCost(a, u)).filter(Boolean).join(" · ") || "nothing";
54
+ lines.push(`${result.dry_run ? "[dry run] " : ""}${result.scanned} narratives scanned, ${result.samples.length} readable, ${changed.length} changed; ${verb} ${totals}`);
55
+ return lines.join("\n");
56
+ }
57
+ const repriceCommand = defineCommand({
58
+ meta: {
59
+ name: "reprice",
60
+ description: "Re-price recorded ledger rows under the current price table",
61
+ },
62
+ args: {
63
+ "dry-run": {
64
+ type: "boolean",
65
+ description: "Report what would change without writing anything",
66
+ },
67
+ all: {
68
+ type: "boolean",
69
+ description: "List every narrative that changes, not just the ten largest",
70
+ },
71
+ },
72
+ async run({ args }) {
73
+ try {
74
+ // Rewrites the daemon's ledger, so it goes through the daemon like
75
+ // backfill does — one writer, and the app hears about the new totals.
76
+ const endpoint = await resolveDaemonEndpoint();
77
+ warnIfVersionMismatch(endpoint);
78
+ const result = await daemonRequest(endpoint, "/api/cost/reprice", {
79
+ method: "POST",
80
+ body: { dry_run: !!args["dry-run"] },
81
+ });
82
+ console.log(formatRepriceResult(result, { all: !!args.all }));
83
+ }
84
+ catch (err) {
85
+ console.error(err instanceof Error ? err.message : String(err));
86
+ process.exit(1);
87
+ }
88
+ },
89
+ });
90
+ const usd = (n) => (n === null ? "—" : `$${n.toFixed(2)}`);
91
+ export function formatRepriceResult(result, opts = {}) {
92
+ const lines = [];
93
+ for (const m of result.models) {
94
+ const mark = m.changed ? "" : " (unchanged)";
95
+ lines.push(`${m.model.padEnd(20)} ${String(m.rows).padStart(6)} rows ${usd(m.before)} → ${usd(m.after)}${mark}`);
96
+ }
97
+ // Largest corrections first; the full list only on request.
98
+ const shown = opts.all ? result.narratives : result.narratives.slice(0, 10);
99
+ if (shown.length)
100
+ lines.push("");
101
+ for (const n of shown) {
102
+ lines.push(`${n.nid.padEnd(18)} ledger ${usd(n.ledger_before)} → ${usd(n.ledger_after)} card ${usd(n.column_before)} → ${usd(n.column_after)}`);
103
+ }
104
+ if (shown.length < result.narratives.length) {
105
+ lines.push(`… ${result.narratives.length - shown.length} more (--all to list them)`);
106
+ }
107
+ const before = result.models.reduce((a, m) => a + m.before, 0);
108
+ const after = result.models.reduce((a, m) => a + m.after, 0);
109
+ const verb = result.dry_run ? "would change" : "changed";
110
+ lines.push(`${result.dry_run ? "[dry run] " : ""}${result.scanned} priced rows, ${verb} ${result.changed} ` +
111
+ `across ${result.narratives.length} narratives: ${usd(before)} → ${usd(after)}` +
112
+ `; ${result.recovered} cache splits recovered` +
113
+ (result.unrecoverable ? `, ${result.unrecoverable} unrecoverable (left as recorded)` : ""));
114
+ return lines.join("\n");
115
+ }
116
+ export const costCommand = defineCommand({
117
+ meta: {
118
+ name: "cost",
119
+ description: "Cost ledger maintenance",
120
+ },
121
+ subCommands: {
122
+ backfill: backfillCommand,
123
+ reprice: repriceCommand,
124
+ },
125
+ });
126
+ //# sourceMappingURL=cost.js.map
@@ -0,0 +1,350 @@
1
+ import { defineCommand } from "citty";
2
+ import { spawn, execFileSync } from "node:child_process";
3
+ import { openSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { openDatabase, ensureBraneDir, getBraneDir, getDaemonAddress } from "../db/schema.js";
6
+ import { startServer } from "../daemon/server.js";
7
+ import { legacyTokenWarning } from "../db/tokens.js";
8
+ import { withoutAgentEnv } from "../agent-env.js";
9
+ import { getTlsCertPath } from "./certify.js";
10
+ import { inspectCert } from "../tls.js";
11
+ function getLogPath() {
12
+ return join(getBraneDir(), "daemon.log");
13
+ }
14
+ // During `daemon restart` the running daemon's `caffeinate -i` child is
15
+ // killed when the old daemon shuts down, and a fresh one isn't established
16
+ // until the new daemon boots far enough to call `keepAwake.start()`. That
17
+ // leaves a brief window (death-poll granularity + new-daemon startup) with
18
+ // no PreventUserIdleSystemSleep assertion held — and because releasing the
19
+ // assertion doesn't reset macOS's idle timer, a machine already idle past
20
+ // its sleep timeout becomes immediately sleep-eligible during that gap.
21
+ //
22
+ // To make the handoff break-free, the restart command itself spawns a
23
+ // short-lived bridging assertion before tearing down the old daemon. It is
24
+ // detached + unref'd so it outlives this CLI process, and self-expires via
25
+ // `-t` so it never lingers as a duplicate of the new daemon's own child.
26
+ // Darwin-only, matching KeepAwake; a no-op elsewhere.
27
+ const RESTART_BRIDGE_SECONDS = 30;
28
+ function spawnKeepAwakeBridge() {
29
+ if (process.platform !== "darwin")
30
+ return;
31
+ try {
32
+ const bridge = spawn("caffeinate", ["-i", "-t", String(RESTART_BRIDGE_SECONDS)], { stdio: "ignore", detached: true });
33
+ bridge.on("error", () => { }); // caffeinate missing — best effort, ignore
34
+ bridge.unref();
35
+ console.log(`Holding idle-sleep bridge for ${RESTART_BRIDGE_SECONDS}s across restart (caffeinate pid ${bridge.pid})`);
36
+ }
37
+ catch {
38
+ // Best effort: if the bridge can't spawn, restart still proceeds.
39
+ }
40
+ }
41
+ // Ask V8 to dump a heap snapshot when it's about to OOM and have Node
42
+ // write a diagnostic report on any fatal error, both into ~/.brane so
43
+ // they're easy to find after a crash.
44
+ export function buildDaemonEnv() {
45
+ const diagnosticFlags = [
46
+ "--heapsnapshot-near-heap-limit=1",
47
+ "--report-on-fatalerror",
48
+ `--report-directory=${getBraneDir()}`,
49
+ ].join(" ");
50
+ const existing = process.env.NODE_OPTIONS;
51
+ // A daemon restarted from an agent's shell must not carry that agent's
52
+ // session: its token and its harness's session id would ride into every
53
+ // tmux server it starts, and from there into sessions that are not that
54
+ // agent (claude-jrjh1e8w:10, claude-eiuzl79r:33). See src/agent-env.ts.
55
+ const env = withoutAgentEnv(process.env);
56
+ return {
57
+ ...env,
58
+ BRANE_DAEMON_DETACHED: "1",
59
+ NODE_OPTIONS: existing ? `${existing} ${diagnosticFlags}` : diagnosticFlags,
60
+ };
61
+ }
62
+ /** Resolve the daemon port: explicit flag > previous port > 0 (OS-assigned) */
63
+ function resolvePort(explicit, db) {
64
+ if (explicit)
65
+ return parseInt(explicit, 10);
66
+ // Try to reuse previous port
67
+ const prevRow = db.prepare("SELECT value FROM meta WHERE key = 'daemon_port'").get();
68
+ if (prevRow) {
69
+ return parseInt(prevRow.value, 10);
70
+ }
71
+ // Fall back to OS-assigned
72
+ return 0;
73
+ }
74
+ const daemonStart = defineCommand({
75
+ meta: {
76
+ name: "start",
77
+ description: "Start the brane daemon",
78
+ },
79
+ args: {
80
+ port: {
81
+ type: "string",
82
+ description: "Port to listen on (default: previous port, then random)",
83
+ },
84
+ token: {
85
+ type: "string",
86
+ description: "Also accept this token, stored as a hashed device token. " +
87
+ "Adopted once: after a rotation revokes it, passing it again does nothing",
88
+ },
89
+ bind: {
90
+ type: "string",
91
+ description: "Bind address (default: auto-detect localhost + Tailscale)",
92
+ },
93
+ foreground: {
94
+ type: "boolean",
95
+ description: "Run in foreground (default: detach)",
96
+ },
97
+ },
98
+ async run({ args }) {
99
+ const db = openDatabase();
100
+ const port = resolvePort(args.port, db);
101
+ // Check if daemon is already running (cleans up stale entries). The
102
+ // address is enough: a running daemon this shell cannot reach is still
103
+ // running, and a second one would only lose the claim.
104
+ const existing = getDaemonAddress(db);
105
+ if (existing) {
106
+ console.log(`Daemon already running. Exiting.`);
107
+ db.close();
108
+ return;
109
+ }
110
+ if (!args.foreground) {
111
+ db.close();
112
+ // Detach: re-spawn ourselves with --foreground in a detached child
113
+ ensureBraneDir();
114
+ const logFd = openSync(getLogPath(), "a");
115
+ const child = spawn(process.execPath, [...process.execArgv, ...process.argv.slice(1), "--foreground"], {
116
+ detached: true,
117
+ stdio: ["ignore", logFd, logFd],
118
+ env: buildDaemonEnv(),
119
+ });
120
+ child.unref();
121
+ console.log(`Daemon started in background (pid ${child.pid})`);
122
+ return;
123
+ }
124
+ await startServer({ port, db, token: args.token, bind: args.bind });
125
+ },
126
+ });
127
+ const daemonStop = defineCommand({
128
+ meta: {
129
+ name: "stop",
130
+ description: "Stop the brane daemon",
131
+ },
132
+ run() {
133
+ const db = openDatabase();
134
+ const pidRow = db
135
+ .prepare("SELECT value FROM meta WHERE key = 'daemon_pid'")
136
+ .get();
137
+ if (!pidRow) {
138
+ console.log("No daemon running.");
139
+ return;
140
+ }
141
+ const pid = parseInt(pidRow.value, 10);
142
+ try {
143
+ process.kill(pid, "SIGTERM");
144
+ console.log(`Sent SIGTERM to daemon (pid ${pid})`);
145
+ }
146
+ catch {
147
+ console.log(`Daemon process ${pid} not found. Cleaning up stale entry.`);
148
+ db.prepare("DELETE FROM meta WHERE key IN ('daemon_token', 'daemon_pid', 'daemon_version', 'daemon_tls', 'daemon_socket')").run();
149
+ }
150
+ },
151
+ });
152
+ const daemonStatus = defineCommand({
153
+ meta: {
154
+ name: "status",
155
+ description: "Show daemon status",
156
+ },
157
+ run() {
158
+ const db = openDatabase();
159
+ const pidRow = db.prepare("SELECT value FROM meta WHERE key = 'daemon_pid'").get();
160
+ const portRow = db.prepare("SELECT value FROM meta WHERE key = 'daemon_port'").get();
161
+ if (!pidRow) {
162
+ console.log("Daemon is not running.");
163
+ return;
164
+ }
165
+ const pid = parseInt(pidRow.value, 10);
166
+ let alive = false;
167
+ try {
168
+ process.kill(pid, 0);
169
+ alive = true;
170
+ }
171
+ catch {
172
+ alive = false;
173
+ }
174
+ console.log(`Daemon status: ${alive ? "running" : "dead (stale entry)"}`);
175
+ console.log(` pid: ${pid}`);
176
+ console.log(` port: ${portRow?.value ?? "unknown"}`);
177
+ // This machine's CLI holds no token: it reaches the daemon over this
178
+ // socket, which only this account can open.
179
+ const socketRow = db.prepare("SELECT value FROM meta WHERE key = 'daemon_socket'").get();
180
+ console.log(` sock: ${socketRow?.value ?? "none (this shell cannot reach it; see `brane daemon logs`)"}`);
181
+ const legacyWarning = legacyTokenWarning(db);
182
+ if (legacyWarning)
183
+ console.log(`\nNote: ${legacyWarning}`);
184
+ // TLS cert status — read directly from disk so we can report even if the
185
+ // daemon isn't responsive. The daemon's own CertManager is the source of
186
+ // truth for renewal scheduling; here we just surface the expiry.
187
+ const cert = inspectCert(getTlsCertPath());
188
+ if (cert) {
189
+ const daysLeft = Math.floor((cert.notAfter.getTime() - Date.now()) / 86_400_000);
190
+ const expiryLabel = daysLeft < 0 ? `EXPIRED ${-daysLeft}d ago`
191
+ : daysLeft === 0 ? "expires today"
192
+ : `${daysLeft}d remaining`;
193
+ console.log(` tls: ${cert.notAfter.toISOString()} (${expiryLabel})`);
194
+ }
195
+ else {
196
+ console.log(` tls: none (daemon will provision one if Tailscale is running)`);
197
+ }
198
+ if (!alive) {
199
+ db.prepare("DELETE FROM meta WHERE key IN ('daemon_token', 'daemon_pid', 'daemon_version', 'daemon_tls', 'daemon_socket')").run();
200
+ console.log("\nCleaned up stale daemon entry.");
201
+ }
202
+ },
203
+ });
204
+ const daemonLogs = defineCommand({
205
+ meta: {
206
+ name: "logs",
207
+ description: "Show daemon logs",
208
+ },
209
+ args: {
210
+ follow: {
211
+ type: "boolean",
212
+ alias: "f",
213
+ description: "Follow log output (like tail -f)",
214
+ },
215
+ lines: {
216
+ type: "string",
217
+ alias: "n",
218
+ description: "Number of lines to show (default: 50)",
219
+ },
220
+ },
221
+ run({ args }) {
222
+ const logPath = getLogPath();
223
+ const lines = args.lines ?? "50";
224
+ const tailArgs = ["-n", lines];
225
+ if (args.follow)
226
+ tailArgs.push("-f");
227
+ tailArgs.push(logPath);
228
+ try {
229
+ execFileSync("tail", tailArgs, { stdio: "inherit" });
230
+ }
231
+ catch {
232
+ console.error(`No log file found at ${logPath}`);
233
+ }
234
+ },
235
+ });
236
+ const daemonRestart = defineCommand({
237
+ meta: {
238
+ name: "restart",
239
+ description: "Restart the brane daemon",
240
+ },
241
+ args: {
242
+ port: {
243
+ type: "string",
244
+ description: "Port to listen on (default: previous port, then random)",
245
+ },
246
+ },
247
+ async run({ args }) {
248
+ const db = openDatabase();
249
+ // Capture the previous port before stopping. No token needs carrying:
250
+ // this machine's CLI holds none, and the new daemon finds the device
251
+ // tokens' hashes in the database.
252
+ const prevPortRow = db
253
+ .prepare("SELECT value FROM meta WHERE key = 'daemon_port'")
254
+ .get();
255
+ const prevPort = args.port ?? prevPortRow?.value;
256
+ // Hold an idle-sleep assertion across the handoff *before* killing the
257
+ // old daemon, so there's no window where nothing prevents idle sleep.
258
+ spawnKeepAwakeBridge();
259
+ // Stop existing daemon if running
260
+ const pidRow = db
261
+ .prepare("SELECT value FROM meta WHERE key = 'daemon_pid'")
262
+ .get();
263
+ if (pidRow) {
264
+ const pid = parseInt(pidRow.value, 10);
265
+ try {
266
+ process.kill(pid, "SIGTERM");
267
+ console.log(`Sent SIGTERM to daemon (pid ${pid})`);
268
+ // Wait for it to die
269
+ for (let i = 0; i < 20; i++) {
270
+ await new Promise((r) => setTimeout(r, 100));
271
+ try {
272
+ process.kill(pid, 0);
273
+ }
274
+ catch {
275
+ break;
276
+ }
277
+ }
278
+ }
279
+ catch {
280
+ // Already dead
281
+ }
282
+ }
283
+ db.close();
284
+ // Start fresh daemon (detached)
285
+ ensureBraneDir();
286
+ const logFd = openSync(getLogPath(), "a");
287
+ const startArgs = [...process.argv.slice(1)].filter((a) => a !== "restart" && !a.startsWith("--port") && !a.startsWith("--token"));
288
+ // Replace "restart" with "start" in the args
289
+ const idx = startArgs.indexOf("daemon");
290
+ const childArgs = [
291
+ ...process.execArgv,
292
+ ...startArgs.slice(0, idx + 1),
293
+ "start",
294
+ "--foreground",
295
+ ...(prevPort ? ["--port", prevPort] : []),
296
+ ];
297
+ const child = spawn(process.execPath, childArgs, {
298
+ detached: true,
299
+ stdio: ["ignore", logFd, logFd],
300
+ env: buildDaemonEnv(),
301
+ });
302
+ child.unref();
303
+ console.log(`Daemon restarted (pid ${child.pid})`);
304
+ },
305
+ });
306
+ /** Start the daemon in the background, unless one is already running, and
307
+ * wait for it to be ready. Returns its address only: what a caller presents
308
+ * is its own business (`resolveCredential` in daemon/client.ts). */
309
+ export async function ensureDaemonRunning() {
310
+ ensureBraneDir();
311
+ const check = openDatabase();
312
+ const running = getDaemonAddress(check);
313
+ check.close();
314
+ if (running)
315
+ return running;
316
+ const logFd = openSync(getLogPath(), "a");
317
+ const cliArgs = process.argv.slice(0, 2);
318
+ const child = spawn(process.execPath, [...process.execArgv, cliArgs[1], "daemon", "start", "--foreground"], {
319
+ detached: true,
320
+ stdio: ["ignore", logFd, logFd],
321
+ env: buildDaemonEnv(),
322
+ });
323
+ child.unref();
324
+ // Poll for daemon to be ready (up to 5s)
325
+ for (let i = 0; i < 50; i++) {
326
+ await new Promise((r) => setTimeout(r, 100));
327
+ // The daemon writes its port only after bootstrapping tokens and binding
328
+ // its socket, so once the address is up the socket is too.
329
+ const db = openDatabase();
330
+ const address = getDaemonAddress(db);
331
+ db.close();
332
+ if (address)
333
+ return address;
334
+ }
335
+ throw new Error("Daemon failed to start within 5s");
336
+ }
337
+ export const daemonCommand = defineCommand({
338
+ meta: {
339
+ name: "daemon",
340
+ description: "Manage the brane daemon",
341
+ },
342
+ subCommands: {
343
+ start: daemonStart,
344
+ stop: daemonStop,
345
+ restart: daemonRestart,
346
+ status: daemonStatus,
347
+ logs: daemonLogs,
348
+ },
349
+ });
350
+ //# sourceMappingURL=daemon.js.map