@starlove/orbit 1.4.1 → 2.0.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 (329) hide show
  1. package/Dockerfile +10 -8
  2. package/README.md +146 -164
  3. package/dist/full/csp.txt +1 -0
  4. package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-subset.woff2 +0 -0
  5. package/dist/full/fonts/JetBrains_Mono/README.txt +5 -0
  6. package/dist/full/fonts/Mona_Sans/MonaSans-subset.woff2 +0 -0
  7. package/{public/fonts/Open_Sans → dist/full/fonts/Mona_Sans}/OFL.txt +93 -93
  8. package/dist/full/fonts/Mona_Sans/README.txt +10 -0
  9. package/{public/fonts/JetBrains_Mono → dist/full/fonts/Newsreader}/OFL.txt +93 -93
  10. package/dist/full/fonts/Newsreader/README.txt +8 -0
  11. package/dist/full/fonts/Newsreader/newsreader.woff2 +0 -0
  12. package/dist/full/index.html +36 -653
  13. package/dist/full/js/app.BmuponPJ.js +32 -0
  14. package/dist/full/js/app.BmuponPJ.js.map +1 -0
  15. package/dist/full/js/chunk.1fJGQGbW.js +2 -0
  16. package/dist/full/js/chunk.1fJGQGbW.js.map +1 -0
  17. package/dist/full/js/chunk.5D7JO2zp.js +2 -0
  18. package/dist/full/js/chunk.5D7JO2zp.js.map +1 -0
  19. package/dist/full/js/chunk.BrNn_D79.js +2 -0
  20. package/dist/full/js/chunk.BrNn_D79.js.map +1 -0
  21. package/dist/full/js/chunk.CokJXIqM.js +2 -0
  22. package/dist/full/js/chunk.CokJXIqM.js.map +1 -0
  23. package/dist/full/js/chunk.D_Haqy6R.js +2 -0
  24. package/dist/full/js/chunk.D_Haqy6R.js.map +1 -0
  25. package/dist/full/js/chunk.RO0jsH6I.js +15 -0
  26. package/dist/full/js/chunk.RO0jsH6I.js.map +1 -0
  27. package/dist/full/js/chunk.dqWOEtBM.js +2 -0
  28. package/dist/full/js/chunk.dqWOEtBM.js.map +1 -0
  29. package/dist/full/js/warm.f260bbbc.js +15 -0
  30. package/dist/full/orbits-favicon.png +0 -0
  31. package/dist/full/orbits-logo-bg-wide.png +0 -0
  32. package/dist/full/orbits-logo-bg.png +0 -0
  33. package/dist/full/orbits-logo.png +0 -0
  34. package/dist/full/styles.2b154ffe.css +1 -0
  35. package/dist/full/vendor/starlove-ui/theme-init.js +66 -0
  36. package/dist/full/version.json +2 -2
  37. package/docs/AGENT_GUIDE.md +114 -0
  38. package/docs/UPDATING.md +249 -64
  39. package/lib/adapters/indexeddb/blobs.d.ts +3 -0
  40. package/lib/adapters/indexeddb/blobs.js +54 -0
  41. package/lib/adapters/indexeddb/database.d.ts +60 -0
  42. package/lib/adapters/indexeddb/database.js +126 -0
  43. package/lib/adapters/indexeddb/index.d.ts +5 -0
  44. package/lib/adapters/indexeddb/index.js +3 -0
  45. package/lib/adapters/indexeddb/locks.d.ts +14 -0
  46. package/lib/adapters/indexeddb/locks.js +40 -0
  47. package/lib/adapters/indexeddb/store.d.ts +40 -0
  48. package/lib/adapters/indexeddb/store.js +424 -0
  49. package/lib/adapters/indexeddb/tables.d.ts +43 -0
  50. package/lib/adapters/indexeddb/tables.js +127 -0
  51. package/lib/adapters/memory/blobs.d.ts +3 -0
  52. package/lib/adapters/memory/blobs.js +44 -0
  53. package/lib/adapters/memory/board.d.ts +41 -0
  54. package/lib/adapters/memory/board.js +521 -0
  55. package/lib/adapters/memory/index.d.ts +2 -0
  56. package/lib/adapters/memory/index.js +2 -0
  57. package/lib/adapters/memory/search.d.ts +6 -0
  58. package/lib/adapters/memory/search.js +30 -0
  59. package/lib/adapters/memory/store.d.ts +34 -0
  60. package/lib/adapters/memory/store.js +266 -0
  61. package/lib/adapters/sqlite/backups.d.ts +53 -0
  62. package/lib/adapters/sqlite/backups.js +232 -0
  63. package/lib/adapters/sqlite/blobs.d.ts +14 -0
  64. package/lib/adapters/sqlite/blobs.js +109 -0
  65. package/lib/adapters/sqlite/board.d.ts +77 -0
  66. package/lib/adapters/sqlite/board.js +328 -0
  67. package/lib/adapters/sqlite/database.d.ts +67 -0
  68. package/lib/adapters/sqlite/database.js +285 -0
  69. package/lib/adapters/sqlite/index.d.ts +3 -0
  70. package/lib/adapters/sqlite/index.js +2 -0
  71. package/lib/adapters/sqlite/lock.d.ts +11 -0
  72. package/lib/adapters/sqlite/lock.js +34 -0
  73. package/lib/adapters/sqlite/records.d.ts +54 -0
  74. package/lib/adapters/sqlite/records.js +546 -0
  75. package/lib/adapters/sqlite/schema.d.ts +54 -0
  76. package/lib/adapters/sqlite/schema.js +276 -0
  77. package/lib/adapters/sqlite/store.d.ts +39 -0
  78. package/lib/adapters/sqlite/store.js +385 -0
  79. package/lib/browser/in-page.d.ts +31 -0
  80. package/lib/browser/in-page.js +46 -0
  81. package/lib/browser/old-preview.d.ts +13 -0
  82. package/lib/browser/old-preview.js +80 -0
  83. package/lib/browser/preview-board.d.ts +8 -0
  84. package/lib/browser/preview-board.js +69 -0
  85. package/lib/cli/main.d.ts +7 -0
  86. package/lib/cli/main.js +343 -0
  87. package/lib/cli/orbit.d.ts +8 -0
  88. package/lib/cli/orbit.js +25 -0
  89. package/lib/client/http.d.ts +38 -0
  90. package/lib/client/http.js +334 -0
  91. package/lib/client/local.d.ts +26 -0
  92. package/lib/client/local.js +190 -0
  93. package/lib/client/object-url.d.ts +8 -0
  94. package/lib/client/object-url.js +21 -0
  95. package/lib/client/types.d.ts +268 -0
  96. package/lib/client/types.js +8 -0
  97. package/lib/commands/agent-tools.d.ts +31 -0
  98. package/lib/commands/agent-tools.js +100 -0
  99. package/lib/commands/index.d.ts +56 -0
  100. package/lib/commands/index.js +298 -0
  101. package/lib/commands/parse.d.ts +73 -0
  102. package/lib/commands/parse.js +231 -0
  103. package/lib/commands/present.d.ts +134 -0
  104. package/lib/commands/present.js +198 -0
  105. package/lib/commands/session.d.ts +85 -0
  106. package/lib/commands/session.js +138 -0
  107. package/lib/commands/verbs.d.ts +35 -0
  108. package/lib/commands/verbs.js +842 -0
  109. package/lib/core/actors.d.ts +18 -0
  110. package/lib/core/actors.js +45 -0
  111. package/lib/core/agent.d.ts +128 -0
  112. package/lib/core/agent.js +323 -0
  113. package/lib/core/boards.d.ts +113 -0
  114. package/lib/core/boards.js +288 -0
  115. package/lib/core/bootstrap.d.ts +47 -0
  116. package/lib/core/bootstrap.js +100 -0
  117. package/lib/core/bytes.d.ts +10 -0
  118. package/lib/core/bytes.js +52 -0
  119. package/lib/core/card-paths.d.ts +25 -0
  120. package/lib/core/card-paths.js +44 -0
  121. package/lib/core/context.d.ts +60 -0
  122. package/lib/core/context.js +42 -0
  123. package/lib/core/cross-board.d.ts +81 -0
  124. package/lib/core/cross-board.js +228 -0
  125. package/lib/core/describe.d.ts +21 -0
  126. package/lib/core/describe.js +81 -0
  127. package/lib/core/errors.d.ts +159 -0
  128. package/lib/core/errors.js +160 -0
  129. package/lib/core/files.d.ts +79 -0
  130. package/lib/core/files.js +262 -0
  131. package/lib/core/history.d.ts +20 -0
  132. package/lib/core/history.js +53 -0
  133. package/lib/core/image-types.d.ts +16 -0
  134. package/lib/core/image-types.js +36 -0
  135. package/lib/core/model.d.ts +263 -0
  136. package/lib/core/model.js +34 -0
  137. package/lib/core/nebula-link.d.ts +14 -0
  138. package/lib/core/nebula-link.js +39 -0
  139. package/lib/core/relations.d.ts +58 -0
  140. package/lib/core/relations.js +112 -0
  141. package/lib/core/search.d.ts +38 -0
  142. package/lib/core/search.js +92 -0
  143. package/lib/core/sessions.d.ts +44 -0
  144. package/lib/core/sessions.js +158 -0
  145. package/lib/core/snapshot-schema.d.ts +295 -0
  146. package/lib/core/snapshot-schema.js +82 -0
  147. package/lib/core/snapshots.d.ts +75 -0
  148. package/lib/core/snapshots.js +409 -0
  149. package/lib/core/states.d.ts +25 -0
  150. package/lib/core/states.js +126 -0
  151. package/lib/core/text.d.ts +40 -0
  152. package/lib/core/text.js +113 -0
  153. package/lib/core/tickets.d.ts +69 -0
  154. package/lib/core/tickets.js +311 -0
  155. package/lib/core/trello.d.ts +17 -0
  156. package/lib/core/trello.js +269 -0
  157. package/lib/core/verdicts.d.ts +28 -0
  158. package/lib/core/verdicts.js +92 -0
  159. package/lib/core/views.d.ts +95 -0
  160. package/lib/core/views.js +186 -0
  161. package/lib/http/app.d.ts +46 -0
  162. package/lib/http/app.js +491 -0
  163. package/lib/mcp-server.d.ts +10 -0
  164. package/lib/mcp-server.js +120 -0
  165. package/lib/node/assets.d.ts +7 -0
  166. package/lib/node/assets.js +68 -0
  167. package/lib/node/data-folder.d.ts +21 -0
  168. package/lib/node/data-folder.js +34 -0
  169. package/lib/node/env.d.ts +38 -0
  170. package/lib/node/env.js +49 -0
  171. package/lib/node/host-check.d.ts +24 -0
  172. package/lib/node/host-check.js +53 -0
  173. package/lib/node/host-services.d.ts +9 -0
  174. package/lib/node/host-services.js +150 -0
  175. package/lib/node/local.d.ts +22 -0
  176. package/lib/node/local.js +61 -0
  177. package/lib/node/migrate.d.ts +53 -0
  178. package/lib/node/migrate.js +635 -0
  179. package/lib/node/package.d.ts +7 -0
  180. package/lib/node/package.js +14 -0
  181. package/lib/node/repo-board.d.ts +49 -0
  182. package/lib/node/repo-board.js +245 -0
  183. package/lib/node/serve.d.ts +8 -0
  184. package/lib/node/serve.js +106 -0
  185. package/lib/orbit.d.ts +47 -0
  186. package/lib/orbit.js +44 -0
  187. package/lib/quiet-sqlite-warning.d.ts +1 -0
  188. package/lib/quiet-sqlite-warning.js +18 -0
  189. package/lib/store/blob-path.d.ts +6 -0
  190. package/lib/store/blob-path.js +12 -0
  191. package/lib/store/conformance.d.ts +52 -0
  192. package/lib/store/conformance.js +1449 -0
  193. package/lib/store/search-words.d.ts +10 -0
  194. package/lib/store/search-words.js +19 -0
  195. package/lib/store/types.d.ts +452 -0
  196. package/lib/store/types.js +34 -0
  197. package/llms.txt +63 -0
  198. package/package.json +109 -25
  199. package/src/cli/orbit.js +11 -658
  200. package/src/mcp-server.js +16 -876
  201. package/SKILL-ORBIT.md +0 -151
  202. package/build.mjs +0 -210
  203. package/dist/full/js/app.4e6fbe20.js +0 -952
  204. package/dist/full/js/app.4e6fbe20.js.map +0 -7
  205. package/dist/full/styles.4e60d824.css +0 -1
  206. package/docs/AGENT_PROTOCOL.md +0 -424
  207. package/docs/MCP_SETUP.md +0 -54
  208. package/docs/ORBIT_DISPATCH.md +0 -280
  209. package/docs/SS-Orbit-Dark.png +0 -0
  210. package/docs/SS-Orbit-Light.png +0 -0
  211. package/docs/mcp-client.example.json +0 -13
  212. package/docs/setup/orbit-init-wizard.md +0 -174
  213. package/public/apple-touch-icon.png +0 -0
  214. package/public/cursors/live_arrow.cur +0 -0
  215. package/public/cursors/live_link.cur +0 -0
  216. package/public/cursors/live_move.cur +0 -0
  217. package/public/cursors/live_select.cur +0 -0
  218. package/public/cursors/live_unavail.cur +0 -0
  219. package/public/favicon-32x32.png +0 -0
  220. package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
  221. package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
  222. package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
  223. package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
  224. package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
  225. package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
  226. package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
  227. package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
  228. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
  229. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
  230. package/public/fonts/Chakra_Petch/OFL.txt +0 -93
  231. package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
  232. package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
  233. package/public/fonts/Inter/OFL.txt +0 -93
  234. package/public/fonts/Inter/README.txt +0 -118
  235. package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
  236. package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
  237. package/public/fonts/JetBrains_Mono/README.txt +0 -79
  238. package/public/fonts/OpenRing-Regular.ttf +0 -0
  239. package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  240. package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  241. package/public/fonts/Open_Sans/README.txt +0 -100
  242. package/public/fonts/Orbitron/OFL.txt +0 -93
  243. package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
  244. package/public/fonts/Orbitron/README.txt +0 -68
  245. package/public/fonts/Rajdhani/OFL.txt +0 -93
  246. package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
  247. package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
  248. package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
  249. package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
  250. package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
  251. package/public/fonts/runescape-uf.ttf +0 -0
  252. package/public/index.html +0 -769
  253. package/public/js/actor-labels.js +0 -65
  254. package/public/js/api.js +0 -39
  255. package/public/js/app.js +0 -45
  256. package/public/js/artifact-drawer.js +0 -79
  257. package/public/js/board-export.js +0 -161
  258. package/public/js/board-menu.js +0 -374
  259. package/public/js/card-actions.js +0 -545
  260. package/public/js/config.js +0 -20
  261. package/public/js/create-card.js +0 -190
  262. package/public/js/dom.js +0 -19
  263. package/public/js/drawer.js +0 -273
  264. package/public/js/font-preference.js +0 -230
  265. package/public/js/format.js +0 -433
  266. package/public/js/help-menu.js +0 -31
  267. package/public/js/icons.js +0 -16
  268. package/public/js/kanban.js +0 -694
  269. package/public/js/local-backend.js +0 -1015
  270. package/public/js/main.js +0 -161
  271. package/public/js/modal.js +0 -33
  272. package/public/js/motion-preference.js +0 -64
  273. package/public/js/panel.js +0 -58
  274. package/public/js/router.js +0 -134
  275. package/public/js/search.js +0 -56
  276. package/public/js/settings-tabs.js +0 -7
  277. package/public/js/settings.js +0 -1634
  278. package/public/js/sse.js +0 -159
  279. package/public/js/starscape.js +0 -332
  280. package/public/js/state.js +0 -129
  281. package/public/js/text-editing.js +0 -128
  282. package/public/js/theme-preference.js +0 -95
  283. package/public/js/ticket-detail.js +0 -1516
  284. package/public/js/ticket-typeahead.js +0 -49
  285. package/public/js/toast.js +0 -89
  286. package/public/js/topbar-notes.js +0 -155
  287. package/public/js/transport.js +0 -22
  288. package/public/js/typeahead.js +0 -206
  289. package/public/js/unread.js +0 -121
  290. package/public/js/update-check.js +0 -58
  291. package/public/js/url-routes.js +0 -94
  292. package/public/orbits-favicon.png +0 -0
  293. package/public/orbits-logo-bg-wide.png +0 -0
  294. package/public/orbits-logo-bg.png +0 -0
  295. package/public/orbits-logo.png +0 -0
  296. package/public/styles.css +0 -7709
  297. package/src/core/agent.js +0 -387
  298. package/src/core/agents-md.js +0 -109
  299. package/src/core/artifacts.js +0 -149
  300. package/src/core/attachments.js +0 -238
  301. package/src/core/auth.js +0 -85
  302. package/src/core/backups.js +0 -142
  303. package/src/core/board.js +0 -148
  304. package/src/core/boards.js +0 -409
  305. package/src/core/bootstrap.js +0 -170
  306. package/src/core/css-bundle.js +0 -30
  307. package/src/core/db.js +0 -315
  308. package/src/core/delete-board.js +0 -163
  309. package/src/core/dispatch.js +0 -1007
  310. package/src/core/events.js +0 -314
  311. package/src/core/http.js +0 -115
  312. package/src/core/migrate.js +0 -198
  313. package/src/core/paths.js +0 -55
  314. package/src/core/provision-repo-board.js +0 -119
  315. package/src/core/queries.js +0 -246
  316. package/src/core/registry.js +0 -211
  317. package/src/core/relations.js +0 -163
  318. package/src/core/review-verdicts.js +0 -138
  319. package/src/core/router.js +0 -556
  320. package/src/core/search.js +0 -183
  321. package/src/core/seed.js +0 -234
  322. package/src/core/snapshots.js +0 -692
  323. package/src/core/states.js +0 -167
  324. package/src/core/system-picker.js +0 -109
  325. package/src/core/tickets.js +0 -400
  326. package/src/core/util.js +0 -106
  327. package/src/core/version-check.js +0 -154
  328. package/src/mcp/orbit-client.js +0 -287
  329. package/src/server.js +0 -115
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Orbit's JSON API and its pages as one `fetch` handler, Hono inside:
3
+ * `createOrbitApp(orbit, { resolveActor, assets })`. `orbit run` serves it
4
+ * through @hono/node-server; a host can mount it anywhere `fetch` runs.
5
+ *
6
+ * Every request finds its own actor and board from the request itself; no
7
+ * "active board" is shared between requests. The API has no accounts, so
8
+ * whoever reaches it acts as the owner unless the headers say an agent: the
9
+ * guard refuses what a browser marks as coming from another site, and a page
10
+ * is never framed by another.
11
+ */
12
+ import type { Actor } from "../core/model.ts";
13
+ import type { Orbit } from "../orbit.ts";
14
+ export declare const MAX_JSON_BODY_BYTES: number;
15
+ export interface OrbitAppOptions {
16
+ /** Who a request acts as. Default: the owner, or the agent its headers name. */
17
+ resolveActor?: (request: Request) => Actor;
18
+ /** The app's files: a response for a page or asset, or null for none. */
19
+ assets?: (request: Request, pathname: string) => Promise<Response | null>;
20
+ /** The package's version, for /llms.txt. */
21
+ version?: string;
22
+ /** One line per expected refusal (a 4xx) and per fault. */
23
+ log?: (line: string) => void;
24
+ /** How often a stream says it's alive, in ms. */
25
+ heartbeatMs?: number;
26
+ }
27
+ /**
28
+ * True for a request a browser marks as coming from another site: a
29
+ * Sec-Fetch-Site other than same-origin or none (same-site too: on a
30
+ * tailnet, other services share the site), or an Origin naming another host.
31
+ * Non-browser clients send neither, so they keep working.
32
+ */
33
+ export declare function isCrossSiteRequest(headers: Headers): boolean;
34
+ /** A Web-standard handler: Request in, Response out, so any host can mount it. */
35
+ export interface OrbitApp {
36
+ fetch: (request: Request) => Promise<Response>;
37
+ }
38
+ export declare function createOrbitApp(orbit: Orbit, options?: OrbitAppOptions): OrbitApp;
39
+ /**
40
+ * An attachment's content may be anything a person or an import put on a
41
+ * card, so it is never a page of Orbit's origin: the browser may not sniff
42
+ * it into another type, and opened as a document it runs nothing (`sandbox`
43
+ * gives it an origin of its own). An `<img>` ignores both, so images still
44
+ * show. A type Orbit won't show (an SVG) comes as a download.
45
+ */
46
+ export declare const ATTACHMENT_CSP = "default-src 'none'; style-src 'unsafe-inline'; sandbox";
@@ -0,0 +1,491 @@
1
+ /**
2
+ * Orbit's JSON API and its pages as one `fetch` handler, Hono inside:
3
+ * `createOrbitApp(orbit, { resolveActor, assets })`. `orbit run` serves it
4
+ * through @hono/node-server; a host can mount it anywhere `fetch` runs.
5
+ *
6
+ * Every request finds its own actor and board from the request itself; no
7
+ * "active board" is shared between requests. The API has no accounts, so
8
+ * whoever reaches it acts as the owner unless the headers say an agent: the
9
+ * guard refuses what a browser marks as coming from another site, and a page
10
+ * is never framed by another.
11
+ */
12
+ import { Hono } from "hono";
13
+ import { actorFromHeaders, localOwnerActor } from "../core/actors.js";
14
+ import { HTTP_STATUS, OrbitError, isOrbitError } from "../core/errors.js";
15
+ import { DOWNLOAD_TYPE, MAX_IMAGE_BYTES } from "../core/files.js";
16
+ import { changeOf } from "../core/model.js";
17
+ import { asText } from "../core/text.js";
18
+ export const MAX_JSON_BODY_BYTES = 100 * 1024 * 1024;
19
+ const JSON_TYPE = "application/json; charset=utf-8";
20
+ /** JSON as Orbit has always written it: two-space indented. */
21
+ function json(status, body, headers = {}) {
22
+ return new Response(JSON.stringify(body, null, 2), { status, headers: { "Content-Type": JSON_TYPE, ...headers } });
23
+ }
24
+ const OWN_SITES = new Set(["same-origin", "none"]);
25
+ const firstHost = (value) => (value.split(",")[0] ?? "").trim().toLowerCase();
26
+ /**
27
+ * True for a request a browser marks as coming from another site: a
28
+ * Sec-Fetch-Site other than same-origin or none (same-site too: on a
29
+ * tailnet, other services share the site), or an Origin naming another host.
30
+ * Non-browser clients send neither, so they keep working.
31
+ */
32
+ export function isCrossSiteRequest(headers) {
33
+ const site = (headers.get("sec-fetch-site") ?? "").trim().toLowerCase();
34
+ if (site)
35
+ return !OWN_SITES.has(site);
36
+ const origin = (headers.get("origin") ?? "").trim();
37
+ if (!origin)
38
+ return false;
39
+ let host;
40
+ try {
41
+ host = new URL(origin).host.toLowerCase();
42
+ }
43
+ catch {
44
+ return true; // "null" (sandboxed frames, file://) or garbage
45
+ }
46
+ // Behind a TLS proxy (tailscale serve) the page's origin names the proxy; a cross-site page can't add X-Forwarded-Host without a preflight, which Orbit never grants.
47
+ return ![headers.get("host"), headers.get("x-forwarded-host")]
48
+ .map((value) => (value ?? "").trim())
49
+ .filter(Boolean)
50
+ .map(firstHost)
51
+ .includes(host);
52
+ }
53
+ /** The URL a request reached Orbit at, for the commands /llms.txt suggests. A forwarded scheme or host counts only when it looks like one. */
54
+ function requestOrigin(headers) {
55
+ const first = (value) => (value ?? "").split(",")[0]?.trim() ?? "";
56
+ const hostLike = (value) => /^[A-Za-z0-9.-]+(:\d{1,5})?$|^\[[0-9A-Fa-f:.]+\](:\d{1,5})?$/.test(value);
57
+ const forwarded = first(headers.get("x-forwarded-proto")).toLowerCase();
58
+ const proto = forwarded === "https" || forwarded === "http" ? forwarded : "http";
59
+ const host = [first(headers.get("x-forwarded-host")), first(headers.get("host"))].find(hostLike) ?? "localhost";
60
+ return `${proto}://${host}`;
61
+ }
62
+ /** The body's bytes, refused past `max` with `payload_too_large` as they arrive. */
63
+ async function readBytes(request, max) {
64
+ if (!request.body)
65
+ return new Uint8Array(0);
66
+ const reader = request.body.getReader();
67
+ const chunks = [];
68
+ let size = 0;
69
+ for (;;) {
70
+ const { done, value } = await reader.read();
71
+ if (done)
72
+ break;
73
+ size += value.byteLength;
74
+ if (size > max) {
75
+ await reader.cancel().catch(() => undefined);
76
+ throw new OrbitError("payload_too_large");
77
+ }
78
+ chunks.push(value);
79
+ }
80
+ const bytes = new Uint8Array(size);
81
+ let at = 0;
82
+ for (const chunk of chunks) {
83
+ bytes.set(chunk, at);
84
+ at += chunk.byteLength;
85
+ }
86
+ return bytes;
87
+ }
88
+ /** The request's JSON: {} when empty, `invalid_json` when unreadable. An array or a value passes through, for the routes that check it. */
89
+ async function readJson(request) {
90
+ const text = new TextDecoder().decode(await readBytes(request, MAX_JSON_BODY_BYTES));
91
+ if (!text.trim())
92
+ return {};
93
+ try {
94
+ return JSON.parse(text);
95
+ }
96
+ catch {
97
+ throw new OrbitError("invalid_json");
98
+ }
99
+ }
100
+ async function readObject(request) {
101
+ const body = await readJson(request);
102
+ return body && typeof body === "object" && !Array.isArray(body) ? body : {};
103
+ }
104
+ const str = (value) => {
105
+ const text = asText(value);
106
+ return text === "" ? null : text;
107
+ };
108
+ export function createOrbitApp(orbit, options = {}) {
109
+ const app = new Hono();
110
+ const log = options.log ?? (() => undefined);
111
+ const resolveActor = options.resolveActor ?? ((request) => actorFromHeaders((name) => request.headers.get(name) ?? undefined));
112
+ const heartbeatMs = options.heartbeatMs ?? 30_000;
113
+ const clientFor = (c) => orbit.client(resolveActor(c.req.raw), { transport: "http", shared: true });
114
+ const query = (c, name) => c.req.query(name) ?? null;
115
+ const queryHint = (c) => ({ board_id: query(c, "board_id"), board: query(c, "board") });
116
+ /** A body's board, else the query's. */
117
+ const bodyHint = (c, body) => ({ board_id: str(body.board_id) ?? query(c, "board_id"), board: str(body.board) ?? query(c, "board") });
118
+ /** The board holding a record (the hinted board first), or the error that says it's missing. */
119
+ async function located(c, kind, id, missing) {
120
+ const store = orbit.context(resolveActor(c.req.raw)).store;
121
+ const hint = queryHint(c);
122
+ const hinted = hint.board_id ? await store.boards.get(hint.board_id) : hint.board ? await store.boards.bySlug(hint.board) : null;
123
+ if ((hint.board_id || hint.board) && !hinted)
124
+ throw new OrbitError("board_not_found");
125
+ if (!(await store.locate(kind, id, hinted?.id)))
126
+ throw new OrbitError(missing);
127
+ }
128
+ const contextOptions = (c) => ({
129
+ max_chars_per_field: query(c, "max_chars_per_field") ?? undefined,
130
+ comment_limit: query(c, "comment_limit") ?? undefined,
131
+ include_parent_full: query(c, "include_parent_full") === "true",
132
+ include_related_full: query(c, "include_related_full") === "true"
133
+ });
134
+ app.use("*", async (c, next) => {
135
+ const path = new URL(c.req.url).pathname;
136
+ if (path.startsWith("/api/") && isCrossSiteRequest(c.req.raw.headers)) {
137
+ log(`Orbit: 403 cross_site_request on ${c.req.method} ${path}`);
138
+ return json(403, { error: "cross_site_request", message: "Orbit's API answers only its own pages and non-browser clients." });
139
+ }
140
+ if (c.req.method === "OPTIONS")
141
+ return new Response(null, { status: 204 });
142
+ await next();
143
+ return undefined;
144
+ });
145
+ app.onError((error, c) => {
146
+ const path = new URL(c.req.url).pathname;
147
+ if (isOrbitError(error)) {
148
+ const status = HTTP_STATUS[error.kind];
149
+ if (status < 500)
150
+ log(`Orbit: ${status} ${error.code} on ${c.req.method} ${path}`);
151
+ else
152
+ log(`Orbit: ${status} ${error.code} on ${c.req.method} ${path}${error.cause instanceof Error ? `: ${error.cause.message}` : ""}`);
153
+ // An internal fault never carries a database's own words.
154
+ return json(status, { error: error.code, message: error.kind === "internal" ? error.code : error.message });
155
+ }
156
+ log(`Orbit: 500 on ${c.req.method} ${path}: ${error.stack ?? error.message}`);
157
+ return json(500, { error: "internal_error", message: "internal_error" });
158
+ });
159
+ app.get("/api/health", () => json(200, { ok: true }));
160
+ // What the computer can do comes from the host given to createOrbit (HostServices); without it, 404.
161
+ app.get("/api/version", async (c) => json(200, await clientFor(c).version({ refresh: query(c, "refresh") === "1" })));
162
+ // Replacing Orbit's own code is the person's call, never an agent's, as with the folder picker.
163
+ app.post("/api/update", async (c) => {
164
+ if (resolveActor(c.req.raw).type === "agent")
165
+ throw new OrbitError("forbidden");
166
+ return json(202, await clientFor(c).update());
167
+ });
168
+ app.get("/api/bootstrap", async (c) => json(200, await clientFor(c).bootstrap(queryHint(c))));
169
+ app.get("/api/boards", async (c) => json(200, await clientFor(c).boards.list()));
170
+ app.get("/api/sessions", async (c) => json(200, await clientFor(c).sessions({ since: query(c, "since") ?? undefined })));
171
+ app.post("/api/cmd", async (c) => {
172
+ const raw = await readJson(c.req.raw);
173
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
174
+ throw new OrbitError("invalid_body", { message: 'Send a JSON object: { "command": "view", "board": "<slug>", "text": "..." }.' });
175
+ const body = raw;
176
+ const transport = body.transport === "cli" || body.transport === "mcp" ? body.transport : "http";
177
+ // A remote client's repo board (its orbit.json), checked again here; callers that send none are untouched.
178
+ const { repoBoardFromRequest } = await import("../commands/index.js");
179
+ const repo = repoBoardFromRequest(body.repo_board, body.repo_board_problem);
180
+ const board = typeof body.board === "string" && body.board.trim() ? body.board.trim() : null;
181
+ const client = orbit.client(resolveActor(c.req.raw), { transport, shared: true, board, repo: repo ? { read: () => repo } : null });
182
+ const input = { json: Boolean(body.json), readOnly: Boolean(body.read_only) };
183
+ if (Array.isArray(body.argv))
184
+ input.argv = body.argv.map((word) => asText(word));
185
+ else
186
+ input.command = asText(body.command);
187
+ if (typeof body.text === "string")
188
+ input.text = body.text;
189
+ const result = await client.command(input);
190
+ const reply = { ok: result.ok, text: result.text, board: result.board, kind: result.kind };
191
+ if (body.json && result.ok)
192
+ reply.data = result.data;
193
+ return json(result.ok ? 200 : 422, reply);
194
+ });
195
+ // Opens Finder or Explorer on the server's machine, so the person can choose a folder there.
196
+ // Its answer is a path on that machine, so an agent is refused.
197
+ app.post("/api/system/pick-folder", async (c) => {
198
+ if (resolveActor(c.req.raw).type === "agent")
199
+ throw new OrbitError("forbidden");
200
+ const result = await orbit.host.pickFolder?.();
201
+ if (!result || result.unsupported)
202
+ throw new OrbitError("folder_picker_unavailable");
203
+ return json(200, result);
204
+ });
205
+ app.post("/api/boards", async (c) => json(201, await clientFor(c).boards.create(await readObject(c.req.raw))));
206
+ const boardRoute = async (c) => {
207
+ const ident = c.req.param("ident") ?? "";
208
+ const sub = c.req.param("sub") ?? "";
209
+ const method = c.req.method;
210
+ const client = clientFor(c);
211
+ if (sub === "" && method === "DELETE")
212
+ return json(200, await client.boards.remove(ident, await readObject(c.req.raw)));
213
+ if (sub === "" && method === "PATCH")
214
+ return json(200, await client.boards.update(ident, await readObject(c.req.raw)));
215
+ if (sub === "context" && method === "GET")
216
+ return json(200, await client.boards.context(ident, { include_struck: query(c, "include_struck") === "true" }));
217
+ if (sub === "archive" && method === "GET")
218
+ return json(200, await client.boards.archive(ident));
219
+ if (sub === "entries" && method === "POST")
220
+ return json(201, await client.entries.create(ident, await readObject(c.req.raw)));
221
+ if (sub === "export" && method === "GET")
222
+ return json(200, await client.boards.export(ident, { include_attachments: query(c, "include_attachments") === "true" || query(c, "include_images") === "true" }));
223
+ if (sub === "states" && method === "POST")
224
+ return json(201, await client.states.create(ident, await readObject(c.req.raw)));
225
+ if (sub === "states" && method === "PATCH")
226
+ return json(200, await client.states.reorder(ident, await readObject(c.req.raw)));
227
+ // No such route: the board is named first, as every route here does.
228
+ await client.boards.context(ident);
229
+ return json(404, { error: "not_found" });
230
+ };
231
+ app.all("/api/boards/:ident", boardRoute);
232
+ app.all("/api/boards/:ident/:sub{.+}", boardRoute);
233
+ app.post("/api/tickets", async (c) => {
234
+ const body = await readObject(c.req.raw);
235
+ return json(201, await clientFor(c).tickets.create({ ...body, ...bodyHint(c, body) }));
236
+ });
237
+ app.get("/api/tickets/lookup", async (c) => {
238
+ const by = {};
239
+ if (query(c, "number") !== null)
240
+ by.number = query(c, "number");
241
+ if (query(c, "title") !== null)
242
+ by.title = query(c, "title");
243
+ return json(200, await clientFor(c).tickets.lookup(queryHint(c), by));
244
+ });
245
+ const ticketRoute = async (c) => {
246
+ const id = c.req.param("id") ?? "";
247
+ const sub = c.req.param("sub") ?? "";
248
+ const method = c.req.method;
249
+ const client = clientFor(c);
250
+ const hint = queryHint(c);
251
+ const comment = /^comments\/([^/]+)$/.exec(sub);
252
+ const content = /^attachments\/([^/]+)\/content$/.exec(sub);
253
+ const attachment = /^attachments\/([^/]+)$/.exec(sub);
254
+ if (sub === "" && method === "GET")
255
+ return json(200, await client.tickets.read(id, hint));
256
+ if (sub === "" && method === "PATCH")
257
+ return json(200, await client.tickets.update(id, await readObject(c.req.raw), hint));
258
+ if (sub === "" && method === "DELETE")
259
+ return json(200, await client.tickets.remove(id, hint));
260
+ if (sub === "archive" && method === "POST")
261
+ return json(200, await client.tickets.archive(id, hint));
262
+ if (sub === "restore" && method === "POST")
263
+ return json(200, await client.tickets.restore(id, hint));
264
+ if (sub === "context" && method === "GET")
265
+ return json(200, await client.tickets.context(id, contextOptions(c), hint));
266
+ if (sub === "context/full" && method === "GET")
267
+ return json(200, await client.tickets.contextFull(id, contextOptions(c), hint));
268
+ if ((sub === "dispatch-packet" || sub === "agent-dispatch-packet") && method === "GET")
269
+ return json(200, await client.tickets.dispatchPacket(id, contextOptions(c), hint));
270
+ if (sub === "history" && method === "GET")
271
+ return json(200, await client.tickets.history(id, hint));
272
+ if (sub === "relations" && method === "GET")
273
+ return json(200, await client.tickets.relations(id, hint));
274
+ if (sub === "blockers" && method === "GET")
275
+ return json(200, await client.tickets.blockers(id, hint));
276
+ if (sub === "comments" && method === "GET")
277
+ return json(200, await client.comments.list(id, hint));
278
+ if (sub === "artifacts/markdown" && method === "GET")
279
+ return json(200, await client.artifacts.read(id, query(c, "path") ?? "", hint));
280
+ if (sub === "artifacts/markdown" && method === "POST")
281
+ return json(201, await client.artifacts.write(id, await readObject(c.req.raw), hint));
282
+ if (sub === "comments" && method === "POST")
283
+ return json(201, await client.comments.create(id, await readObject(c.req.raw), hint));
284
+ if (comment?.[1] && method === "PATCH")
285
+ return json(200, await client.comments.update(id, decodeURIComponent(comment[1]), await readObject(c.req.raw), hint));
286
+ if (sub === "review-verdicts" && method === "GET")
287
+ return json(200, await client.verdicts.list(id, { limit: query(c, "limit") }, hint));
288
+ if (sub === "review-verdicts" && method === "POST")
289
+ return json(201, await client.verdicts.create(id, await readObject(c.req.raw), hint));
290
+ if (sub === "attachments" && method === "GET")
291
+ return json(200, await client.attachments.list(id, hint));
292
+ if (sub === "attachments" && method === "POST") {
293
+ const bytes = await readBytes(c.req.raw, MAX_IMAGE_BYTES);
294
+ const name = c.req.header("x-file-name") || query(c, "filename") || "image";
295
+ return json(201, await client.attachments.create(id, bytes, { mime_type: c.req.header("content-type") ?? "", original_name: name }, hint));
296
+ }
297
+ if (content?.[1] && method === "GET") {
298
+ const file = await client.attachments.content(id, decodeURIComponent(content[1]), hint);
299
+ return new Response(file.bytes, { status: 200, headers: attachmentHeaders(file) });
300
+ }
301
+ if (attachment?.[1] && method === "DELETE")
302
+ return json(200, await client.attachments.remove(id, decodeURIComponent(attachment[1]), hint));
303
+ // No such route: the card is found first, as every route here does.
304
+ await located(c, "ticket", id, "ticket_not_found");
305
+ return json(404, { error: "not_found" });
306
+ };
307
+ app.all("/api/tickets/:id", ticketRoute);
308
+ app.all("/api/tickets/:id/:sub{.+}", ticketRoute);
309
+ app.get("/api/review-verdicts/:id", async (c) => json(200, await clientFor(c).verdicts.get(c.req.param("id"), requiredHint(queryHint(c)))));
310
+ app.all("/api/states/:id", async (c) => {
311
+ const client = clientFor(c);
312
+ const id = c.req.param("id");
313
+ if (c.req.method === "PATCH")
314
+ return json(200, await client.states.update(id, await readObject(c.req.raw), queryHint(c)));
315
+ if (c.req.method === "DELETE")
316
+ return json(200, await client.states.remove(id, queryHint(c)));
317
+ // The lane must exist before the method is refused.
318
+ await located(c, "state", id, "state_not_found");
319
+ return json(405, { error: "method_not_allowed" });
320
+ });
321
+ app.patch("/api/board-entries/:id", async (c) => {
322
+ const body = await readObject(c.req.raw);
323
+ return json(200, await clientFor(c).entries.update(c.req.param("id"), body, queryHint(c)));
324
+ });
325
+ app.delete("/api/board-entries/:id", async (c) => json(200, await clientFor(c).entries.remove(c.req.param("id"), queryHint(c))));
326
+ app.post("/api/relations", async (c) => {
327
+ const body = await readObject(c.req.raw);
328
+ return json(201, await clientFor(c).relations.create({ ...body, ...bodyHint(c, body) }));
329
+ });
330
+ app.delete("/api/relations/:id", async (c) => json(200, await clientFor(c).relations.remove(c.req.param("id"), queryHint(c))));
331
+ app.get("/api/search", async (c) => json(200, await clientFor(c).search(queryHint(c), {
332
+ q: query(c, "q") ?? "",
333
+ limit: query(c, "limit"),
334
+ mode: query(c, "mode"),
335
+ include_full: query(c, "include_full") === "true" ? true : undefined,
336
+ fields: query(c, "fields"),
337
+ max_chars_per_field: query(c, "max_chars_per_field") ?? undefined
338
+ })));
339
+ app.post("/api/agent/claim-next", async (c) => {
340
+ const body = await readObject(c.req.raw);
341
+ return json(200, await clientFor(c).agent.claimNext(bodyHint(c, body), body));
342
+ });
343
+ app.post("/api/agent/claim", async (c) => {
344
+ const body = await readObject(c.req.raw);
345
+ return json(200, await clientFor(c).agent.claim({ ...body, ...bodyHint(c, body) }));
346
+ });
347
+ app.post("/api/agent/checkpoint", async (c) => {
348
+ const body = await readObject(c.req.raw);
349
+ return json(200, await clientFor(c).agent.checkpoint({ ...body, ...bodyHint(c, body) }));
350
+ });
351
+ app.post("/api/agent/complete", async (c) => {
352
+ const body = await readObject(c.req.raw);
353
+ return json(200, await clientFor(c).agent.complete({ ...body, ...bodyHint(c, body) }));
354
+ });
355
+ // By default an import makes a new board; replacing one names it, and never an agent's (the core
356
+ // refuses it: refuseAgentReplace).
357
+ app.post("/api/admin/import", async (c) => {
358
+ const body = await readObject(c.req.raw);
359
+ return json(201, await clientFor(c).boards.import({ ...body, ...bodyHint(c, body) }));
360
+ });
361
+ app.get("/api/events/stream", async (c) => {
362
+ const boardId = query(c, "board_id");
363
+ const rows = await orbit.context(resolveActor(c.req.raw)).store.boards.list();
364
+ if (boardId && !rows.some((row) => row.id === boardId))
365
+ return json(404, { error: "board_not_found" });
366
+ const after = c.req.header("last-event-id") || query(c, "last_event_id");
367
+ return eventStream(orbit, boardId ? [boardId] : rows.map((row) => row.id), after ?? null, heartbeatMs, c.req.raw.signal);
368
+ });
369
+ app.all("/api/*", () => json(404, { error: "not_found" }));
370
+ app.on(["GET", "HEAD"], "/llms.txt", async (c) => {
371
+ const { instanceLlmsTxt } = await import("../commands/agent-tools.js");
372
+ const text = instanceLlmsTxt({ origin: requestOrigin(c.req.raw.headers), version: options.version ?? "" });
373
+ return new Response(c.req.method === "HEAD" ? null : text, { status: 200, headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "no-cache" } });
374
+ });
375
+ app.all("*", async (c) => {
376
+ if (c.req.method !== "GET" && c.req.method !== "HEAD")
377
+ return new Response(null, { status: 405 });
378
+ const found = await options.assets?.(c.req.raw, new URL(c.req.url).pathname);
379
+ return found ?? new Response(null, { status: 404 });
380
+ });
381
+ return { fetch: (request) => Promise.resolve(app.fetch(request)) };
382
+ }
383
+ /**
384
+ * An attachment's content may be anything a person or an import put on a
385
+ * card, so it is never a page of Orbit's origin: the browser may not sniff
386
+ * it into another type, and opened as a document it runs nothing (`sandbox`
387
+ * gives it an origin of its own). An `<img>` ignores both, so images still
388
+ * show. A type Orbit won't show (an SVG) comes as a download.
389
+ */
390
+ export const ATTACHMENT_CSP = "default-src 'none'; style-src 'unsafe-inline'; sandbox";
391
+ function attachmentHeaders(file) {
392
+ const headers = {
393
+ "Content-Type": file.download_only ? DOWNLOAD_TYPE : file.mime_type,
394
+ "Cache-Control": "private, max-age=3600",
395
+ "X-Content-Type-Options": "nosniff",
396
+ "Content-Security-Policy": ATTACHMENT_CSP
397
+ };
398
+ if (file.download_only)
399
+ headers["Content-Disposition"] = contentDisposition(file.original_name || "file");
400
+ return headers;
401
+ }
402
+ /** `attachment`, with the name in ASCII for old readers and in UTF-8 for the rest (RFC 6266). */
403
+ function contentDisposition(name) {
404
+ const ascii = name.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
405
+ return `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(name)}`;
406
+ }
407
+ /** A route that needs a board, given none: `board_id_required` before anything is looked up. */
408
+ function requiredHint(hint) {
409
+ if (!hint.board_id && !hint.board)
410
+ throw new OrbitError("board_id_required");
411
+ return hint;
412
+ }
413
+ const encoder = new TextEncoder();
414
+ const frame = (change) => encoder.encode(`id: ${change.id}\nevent: board\ndata: ${JSON.stringify(change)}\n\n`);
415
+ /**
416
+ * The live stream: every committed change on these boards, from any writer
417
+ * (the app, MCP, the CLI), as server-sent events. With an event id to
418
+ * resume from, the events after it come first (up to 200).
419
+ */
420
+ function eventStream(orbit, boardIds, after, heartbeatMs, signal) {
421
+ const stops = [];
422
+ let timer = null;
423
+ const stop = () => {
424
+ if (timer)
425
+ clearInterval(timer);
426
+ timer = null;
427
+ for (const fn of stops.splice(0))
428
+ fn();
429
+ };
430
+ const body = new ReadableStream({
431
+ start: async (controller) => {
432
+ const sent = new Set();
433
+ let live = false;
434
+ const queued = [];
435
+ const send = (change) => {
436
+ if (sent.has(change.id))
437
+ return;
438
+ sent.add(change.id);
439
+ try {
440
+ controller.enqueue(frame(change));
441
+ }
442
+ catch {
443
+ stop();
444
+ }
445
+ };
446
+ controller.enqueue(encoder.encode(":ok\n\n"));
447
+ // Listen first, so nothing committed during the replay is missed.
448
+ for (const id of boardIds)
449
+ stops.push(orbit.watch(id, (change) => {
450
+ if (live)
451
+ send(change);
452
+ else
453
+ queued.push(change);
454
+ }));
455
+ if (after && boardIds.length === 1 && boardIds[0]) {
456
+ const boardId = boardIds[0];
457
+ try {
458
+ const data = await orbit.context(localOwnerActor()).store.board(boardId);
459
+ const missed = await data.read((r) => r.events.after(after, 200));
460
+ for (const event of missed)
461
+ send(changeOf(boardId, event));
462
+ }
463
+ catch {
464
+ // A board that can't be read now still streams what comes next.
465
+ }
466
+ }
467
+ live = true;
468
+ for (const change of queued.splice(0))
469
+ send(change);
470
+ timer = setInterval(() => {
471
+ try {
472
+ controller.enqueue(encoder.encode(":heartbeat\n\n"));
473
+ }
474
+ catch {
475
+ stop();
476
+ }
477
+ }, heartbeatMs);
478
+ signal.addEventListener("abort", () => {
479
+ stop();
480
+ try {
481
+ controller.close();
482
+ }
483
+ catch {
484
+ // Already closed.
485
+ }
486
+ });
487
+ },
488
+ cancel: stop
489
+ });
490
+ return new Response(body, { status: 200, headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", "X-Accel-Buffering": "no" } });
491
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * `orbit mcp`: the board commands for an MCP app over stdio, on the MCP SDK's
3
+ * low-level Server. Two tools take a command line (orbit_read refuses
4
+ * anything that writes), and the guide is a resource. Local mode runs Orbit
5
+ * in this process on the data folder; remote mode (ORBIT_MODE=remote) sends
6
+ * each command to ORBIT_API_URL. Either way the repo's orbit.json is read
7
+ * where the server runs, or in the folder `--cwd` names.
8
+ */
9
+ import "./quiet-sqlite-warning.ts";
10
+ export declare function runMcpServer(): Promise<void>;