@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,120 @@
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.js";
10
+ import { readFileSync } from "node:fs";
11
+ import { join, resolve } from "node:path";
12
+ import { ProtocolError, ProtocolErrorCode, ResourceNotFoundError, Server } from "@modelcontextprotocol/server";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
14
+ import { namedAgentActor } from "./core/actors.js";
15
+ import { ORBIT_COMMAND_INSTRUCTIONS, commandTools } from "./commands/agent-tools.js";
16
+ import { readEnv } from "./node/env.js";
17
+ import { PACKAGE_ROOT, openLocalOrbit, packageVersion } from "./node/local.js";
18
+ import { readRepoBoard, repoSourceAt } from "./node/repo-board.js";
19
+ const GUIDE = {
20
+ uri: "orbit://guide",
21
+ name: "orbit-guide",
22
+ title: "Orbit agent guide",
23
+ description: "How to work Orbit cards, fields, blockers, Journal, and the HTTP API.",
24
+ mimeType: "text/markdown"
25
+ };
26
+ /** Where to read the repo's orbit.json: where `orbit mcp` runs, or the folder `--cwd <dir>` names. */
27
+ function repoDir(args) {
28
+ let dir = null;
29
+ for (let i = 0; i < args.length; i += 1) {
30
+ const arg = args[i] ?? "";
31
+ if (arg === "--cwd" && args[i + 1])
32
+ dir = args[++i] ?? null;
33
+ else if (arg.startsWith("--cwd="))
34
+ dir = arg.slice("--cwd=".length);
35
+ }
36
+ return resolve(process.cwd(), dir ?? ".");
37
+ }
38
+ async function openClient() {
39
+ const env = readEnv();
40
+ const dir = repoDir(process.argv.slice(2));
41
+ if (env.mode === "remote") {
42
+ if (!env.apiUrl)
43
+ throw new Error("ORBIT_MODE=remote requires ORBIT_API_URL.");
44
+ const { createHttpClient } = await import("./client/http.js");
45
+ const client = createHttpClient({
46
+ baseUrl: env.apiUrl,
47
+ headers: { "x-orbit-actor-type": "agent", "x-orbit-agent-name": env.agentName },
48
+ board: env.board,
49
+ repo: () => readRepoBoard(dir)
50
+ });
51
+ return { client, close: () => Promise.resolve() };
52
+ }
53
+ const local = await openLocalOrbit(env);
54
+ return { client: local.orbit.client(namedAgentActor(env.agentName), { transport: "mcp", board: env.board, repo: repoSourceAt(dir) }), close: () => local.close() };
55
+ }
56
+ export async function runMcpServer() {
57
+ const args = process.argv.slice(process.argv[2] === "mcp" ? 3 : 2);
58
+ for (let i = 0; i < args.length; i += 1) {
59
+ const arg = args[i] ?? "";
60
+ if (arg === "--cwd" && args[i + 1])
61
+ i += 1;
62
+ else if (!arg.startsWith("--cwd=")) {
63
+ console.error(`Unknown argument: ${arg}`);
64
+ console.error("Run orbit help to see what Orbit takes.");
65
+ process.exitCode = 1;
66
+ return;
67
+ }
68
+ }
69
+ const { client, close } = await openClient();
70
+ const tools = commandTools();
71
+ let guide = null;
72
+ const guideText = () => (guide ??= readFileSync(join(PACKAGE_ROOT, "docs", "AGENT_GUIDE.md"), "utf8"));
73
+ // The low-level Server keeps the tool list, descriptions, schemas and instructions exactly as Orbit writes them (the fixtures pin them).
74
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
75
+ const server = new Server({ name: "starscape-orbit", title: "Starscape Orbit", version: packageVersion() }, { capabilities: { tools: {}, resources: {} }, instructions: ORBIT_COMMAND_INSTRUCTIONS });
76
+ server.setRequestHandler("tools/list", () => ({ tools: tools.map(({ name, description, inputSchema, annotations }) => ({ name, description, inputSchema, annotations })) }));
77
+ server.setRequestHandler("tools/call", async (request) => {
78
+ const { name, arguments: args = {} } = request.params;
79
+ const tool = tools.find((candidate) => candidate.name === name);
80
+ if (!tool)
81
+ throw new ProtocolError(ProtocolErrorCode.MethodNotFound, `Unknown tool: ${name}`);
82
+ const command = args.command;
83
+ if (typeof command !== "string" || !command.trim()) {
84
+ return { content: [{ type: "text", text: `${tool.name} needs a command, e.g. ${tool.name === "orbit_read" ? "view" : "take"}. help lists them all.` }], isError: true };
85
+ }
86
+ try {
87
+ const reply = await client.command({ command, ...(typeof args.text === "string" ? { text: args.text } : {}), readOnly: tool.name === "orbit_read" });
88
+ return { content: [{ type: "text", text: reply.text }], ...(reply.ok ? {} : { isError: true }) };
89
+ }
90
+ catch (error) {
91
+ return { content: [{ type: "text", text: error instanceof Error && error.message ? error.message : "Tool call failed." }], isError: true };
92
+ }
93
+ });
94
+ server.setRequestHandler("resources/list", () => ({ resources: [GUIDE] }));
95
+ server.setRequestHandler("resources/templates/list", () => ({ resourceTemplates: [] }));
96
+ server.setRequestHandler("resources/read", (request) => {
97
+ const { uri } = request.params;
98
+ if (uri !== GUIDE.uri)
99
+ throw new ResourceNotFoundError(uri, `Resource not found: ${uri}`);
100
+ return { contents: [{ uri, mimeType: GUIDE.mimeType, text: guideText() }] };
101
+ });
102
+ let stopping = false;
103
+ const shutdown = () => {
104
+ if (stopping)
105
+ return;
106
+ stopping = true;
107
+ void server
108
+ .close()
109
+ .catch(() => undefined)
110
+ .then(close)
111
+ .finally(() => process.exit(0));
112
+ };
113
+ process.stdin.on("end", shutdown);
114
+ process.once("SIGINT", shutdown);
115
+ process.once("SIGTERM", shutdown);
116
+ await server.connect(new StdioServerTransport());
117
+ }
118
+ // Run when started directly (an MCP app's `node .../mcp-server.js`); `orbit mcp` imports runMcpServer.
119
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname))
120
+ await runMcpServer();
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The app's files from a folder (dist/full). A path that names no file
3
+ * answers with the app's page, as the app's hash routes expect; a path
4
+ * outside the folder is refused. With no build in the folder, the page says
5
+ * how to make one.
6
+ */
7
+ export declare function folderAssets(root: string): (request: Request, pathname: string) => Promise<Response | null>;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * The app's files from a folder (dist/full). A path that names no file
3
+ * answers with the app's page, as the app's hash routes expect; a path
4
+ * outside the folder is refused. With no build in the folder, the page says
5
+ * how to make one.
6
+ */
7
+ import { readFileSync } from "node:fs";
8
+ import { readFile } from "node:fs/promises";
9
+ import { extname, resolve } from "node:path";
10
+ const TYPES = {
11
+ ".html": "text/html; charset=utf-8",
12
+ ".css": "text/css; charset=utf-8",
13
+ ".js": "text/javascript; charset=utf-8",
14
+ ".json": "application/json; charset=utf-8",
15
+ ".svg": "image/svg+xml"
16
+ };
17
+ /** /app and /app/... are the same files as /. */
18
+ function assetPath(pathname) {
19
+ return pathname === "/app" || pathname.startsWith("/app/") ? pathname.slice(4) || "/" : pathname;
20
+ }
21
+ /**
22
+ * The page's Content-Security-Policy, which the build wrote beside it
23
+ * (`csp.txt`: its inline scripts allowed by hash, nothing else inline). A
24
+ * folder without one (a build from before it) still refuses framing.
25
+ */
26
+ function pagePolicy(root) {
27
+ try {
28
+ return readFileSync(resolve(root, "csp.txt"), "utf8").trim() || "frame-ancestors 'none'";
29
+ }
30
+ catch {
31
+ return "frame-ancestors 'none'";
32
+ }
33
+ }
34
+ export function folderAssets(root) {
35
+ const policy = pagePolicy(root);
36
+ async function serve(request, pathname) {
37
+ const path = assetPath(pathname);
38
+ let decoded;
39
+ try {
40
+ decoded = decodeURIComponent(path === "/" ? "/index.html" : path);
41
+ }
42
+ catch {
43
+ return new Response(null, { status: 404 });
44
+ }
45
+ const file = resolve(root, `.${decoded}`);
46
+ if (!file.startsWith(root))
47
+ return new Response(null, { status: 403 });
48
+ try {
49
+ const data = await readFile(file);
50
+ return new Response(request.method === "HEAD" ? null : data, {
51
+ status: 200,
52
+ headers: {
53
+ "Content-Type": TYPES[extname(file)] ?? "application/octet-stream",
54
+ "Cache-Control": "no-store",
55
+ // Scripts only from Orbit itself, and another site must not frame the UI and steer clicks (clickjacking).
56
+ "Content-Security-Policy": policy,
57
+ "X-Frame-Options": "DENY"
58
+ }
59
+ });
60
+ }
61
+ catch {
62
+ if (pathname !== "/")
63
+ return serve(request, "/");
64
+ return new Response("Orbit's web app isn't built here. In a checkout, run: node build.mjs\n", { status: 404, headers: { "Content-Type": "text/plain; charset=utf-8" } });
65
+ }
66
+ }
67
+ return serve;
68
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The default data folder on this computer: made for its user alone, and
3
+ * whether an older Orbit's boards wait in its old default for `orbit migrate`.
4
+ */
5
+ import type { OrbitEnv } from "./env.ts";
6
+ /**
7
+ * Makes ~/.starscape and ~/.starscape/orbit when they are missing, readable by
8
+ * this user alone (0700), because boards are private work. A folder that
9
+ * exists keeps its mode, and a DATA_DIR someone set is the store's to make.
10
+ */
11
+ export declare function makeDefaultDataDir(env: OrbitEnv): void;
12
+ /**
13
+ * True when Orbit runs on its default folder and an older Orbit's registry is
14
+ * in ~/.orbit. Only the file's presence is checked: it is never opened.
15
+ */
16
+ export declare function olderDataWaiting(env: OrbitEnv): boolean;
17
+ /**
18
+ * The numbered steps `orbit run` prints under "Next:". While the default
19
+ * folder holds no boards and the old one does, bringing them comes first.
20
+ */
21
+ export declare function nextSteps(url: string, open: string, older: boolean): string[];
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The default data folder on this computer: made for its user alone, and
3
+ * whether an older Orbit's boards wait in its old default for `orbit migrate`.
4
+ */
5
+ import { existsSync, mkdirSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ /**
8
+ * Makes ~/.starscape and ~/.starscape/orbit when they are missing, readable by
9
+ * this user alone (0700), because boards are private work. A folder that
10
+ * exists keeps its mode, and a DATA_DIR someone set is the store's to make.
11
+ */
12
+ export function makeDefaultDataDir(env) {
13
+ if (env.dataDirIsDefault)
14
+ mkdirSync(env.dataDir, { recursive: true, mode: 0o700 });
15
+ }
16
+ /**
17
+ * True when Orbit runs on its default folder and an older Orbit's registry is
18
+ * in ~/.orbit. Only the file's presence is checked: it is never opened.
19
+ */
20
+ export function olderDataWaiting(env) {
21
+ return env.dataDirIsDefault && existsSync(join(env.olderDataDir, "registry.db"));
22
+ }
23
+ /**
24
+ * The numbered steps `orbit run` prints under "Next:". While the default
25
+ * folder holds no boards and the old one does, bringing them comes first.
26
+ */
27
+ export function nextSteps(url, open, older) {
28
+ const steps = [
29
+ ...(older ? ["Bring your boards from an older Orbit (in ~/.orbit): run orbit migrate in a terminal (it only reads the old files)."] : []),
30
+ `Open ${url} in your browser${open ? ` ${open}` : ""}.`,
31
+ "Connect your agents: in the app, Help, then Connect your agents. Or run orbit help."
32
+ ];
33
+ return steps.map((step, i) => ` ${String(i + 1)}. ${step}`);
34
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * What Orbit reads from the environment, checked once where it enters. A
3
+ * value that can't be used falls back to its default,
4
+ * except ORBIT_MODE, which is refused: a typo there would quietly point an
5
+ * agent at the wrong Orbit. Plain checks, not zod: every command reads the
6
+ * environment, and loading zod costs one-shot commands like `orbit view`
7
+ * more than their whole start (60 to 100 ms).
8
+ */
9
+ export interface OrbitEnv {
10
+ dataDir: string;
11
+ /** True when Orbit uses its default, ~/.starscape/orbit, which an agent's `orbit mcp` finds without being told. */
12
+ dataDirIsDefault: boolean;
13
+ /** An older Orbit's default data folder, ~/.orbit: what `orbit migrate` reads when no folder is named. */
14
+ olderDataDir: string;
15
+ /** Null when no PORT was given (Orbit starts at 13701 and moves up past busy ports). */
16
+ port: number | null;
17
+ host: string;
18
+ /** Names besides IP addresses, localhost and tailnet names that `orbit run` answers to (ORBIT_ALLOWED_HOSTS). */
19
+ allowedHosts: string[];
20
+ mode: "local" | "remote";
21
+ apiUrl: string;
22
+ agentName: string;
23
+ board: string | null;
24
+ backups: {
25
+ enabled: boolean;
26
+ delayMs: number;
27
+ retention: number;
28
+ };
29
+ selfUpdate: boolean;
30
+ updates: {
31
+ enabled: boolean;
32
+ ttlMinutes: number;
33
+ branch: string;
34
+ token: string;
35
+ docker: boolean;
36
+ };
37
+ }
38
+ export declare function readEnv(env?: NodeJS.ProcessEnv): OrbitEnv;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * What Orbit reads from the environment, checked once where it enters. A
3
+ * value that can't be used falls back to its default,
4
+ * except ORBIT_MODE, which is refused: a typo there would quietly point an
5
+ * agent at the wrong Orbit. Plain checks, not zod: every command reads the
6
+ * environment, and loading zod costs one-shot commands like `orbit view`
7
+ * more than their whole start (60 to 100 ms).
8
+ */
9
+ import { homedir } from "node:os";
10
+ import { join, resolve } from "node:path";
11
+ import { allowedHostsFrom } from "./host-check.js";
12
+ /** A value with its spaces trimmed, or undefined when it is missing or blank. */
13
+ const text = (value) => value?.trim() || undefined;
14
+ /** A whole number of at least `min`, or the fallback. */
15
+ function whole(value, fallback, min = 0) {
16
+ const trimmed = value?.trim() ?? "";
17
+ if (!/^\d+$/.test(trimmed))
18
+ return fallback;
19
+ const number = Number(trimmed);
20
+ return Number.isSafeInteger(number) && number >= min ? number : fallback;
21
+ }
22
+ /** True when the value says off: 0, false or off. */
23
+ const isOff = (value) => ["0", "false", "off"].includes((value ?? "1").trim().toLowerCase());
24
+ export function readEnv(env = process.env) {
25
+ const home = resolve(text(env.HOME) ?? homedir());
26
+ // Each Starscape product keeps its own folder under ~/.starscape, which the Suite can link.
27
+ const fallbackDir = join(home, ".starscape", "orbit");
28
+ const dataDir = resolve(text(env.DATA_DIR) ?? fallbackDir);
29
+ const modeWord = (env.ORBIT_MODE ?? "local").trim().toLowerCase();
30
+ if (!["local", "", "remote", "http"].includes(modeWord))
31
+ throw new Error(`Unsupported ORBIT_MODE: ${env.ORBIT_MODE ?? ""}`);
32
+ const portText = text(env.PORT);
33
+ const port = portText && /^\d{1,5}$/.test(portText) ? Number(portText) : null;
34
+ return {
35
+ dataDir,
36
+ dataDirIsDefault: dataDir === resolve(fallbackDir),
37
+ olderDataDir: join(home, ".orbit"),
38
+ port,
39
+ host: text(env.HOST) ?? text(env.ORBIT_HOST) ?? "127.0.0.1",
40
+ allowedHosts: allowedHostsFrom(env.ORBIT_ALLOWED_HOSTS),
41
+ mode: modeWord === "remote" || modeWord === "http" ? "remote" : "local",
42
+ apiUrl: (text(env.ORBIT_API_URL) ?? "").replace(/\/+$/, ""),
43
+ agentName: text(env.ORBIT_AGENT_NAME) ?? "agent",
44
+ board: text(env.ORBIT_BOARD) ?? text(env.ORBIT_DEFAULT_BOARD) ?? null,
45
+ backups: { enabled: !isOff(env.ORBIT_AUTO_BACKUPS), delayMs: whole(env.ORBIT_AUTO_BACKUP_DELAY_MS, 120_000), retention: whole(env.ORBIT_BACKUP_RETENTION, 30, 1) },
46
+ selfUpdate: text(env.ORBIT_SELF_UPDATE) === "1",
47
+ updates: { enabled: text(env.ORBIT_UPDATE_CHECK) !== "0", ttlMinutes: whole(env.ORBIT_UPDATE_TTL, 60, 1), branch: text(env.ORBIT_UPDATE_BRANCH) ?? "master", token: text(env.ORBIT_GITHUB_TOKEN) ?? "", docker: text(env.ORBIT_DOCKER) === "1" }
48
+ };
49
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The names `orbit run` answers to. The API has no accounts and trusts its
3
+ * own pages, so a page of another site that a DNS rebind points at this
4
+ * computer would look like Orbit's own (same Host, same Origin). A browser
5
+ * always says which name it asked for, so a request naming anything else is
6
+ * refused (421) before it reaches the app: an IP address, `localhost` and
7
+ * `*.localhost`, a tailnet name (`*.ts.net`), the name HOST binds to, and
8
+ * the names in ORBIT_ALLOWED_HOSTS (a reverse proxy's own domain, a short
9
+ * tailnet name, a `.local` or LAN name) are answered. The same goes for X-Forwarded-Host, which a proxy sets.
10
+ */
11
+ /** A Host header's name, without its port, lowercased and without a final dot; "" for none. */
12
+ export declare function hostName(value: string): string;
13
+ export interface HostCheck {
14
+ /** The name a request was refused for, or null when every name it gives is one Orbit answers to. */
15
+ refused(headers: Headers): string | null;
16
+ }
17
+ /** Names from ORBIT_ALLOWED_HOSTS: comma-separated, each a name (a port, if given, is ignored). */
18
+ export declare function allowedHostsFrom(value: string | undefined): string[];
19
+ export declare function hostCheck(options: {
20
+ bind: string;
21
+ allowed: readonly string[];
22
+ }): HostCheck;
23
+ /** The one plain line a refused name gets. */
24
+ export declare function misdirected(name: string): Response;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The names `orbit run` answers to. The API has no accounts and trusts its
3
+ * own pages, so a page of another site that a DNS rebind points at this
4
+ * computer would look like Orbit's own (same Host, same Origin). A browser
5
+ * always says which name it asked for, so a request naming anything else is
6
+ * refused (421) before it reaches the app: an IP address, `localhost` and
7
+ * `*.localhost`, a tailnet name (`*.ts.net`), the name HOST binds to, and
8
+ * the names in ORBIT_ALLOWED_HOSTS (a reverse proxy's own domain, a short
9
+ * tailnet name, a `.local` or LAN name) are answered. The same goes for X-Forwarded-Host, which a proxy sets.
10
+ */
11
+ import { isIP } from "node:net";
12
+ /** A Host header's name, without its port, lowercased and without a final dot; "" for none. */
13
+ export function hostName(value) {
14
+ const host = value.trim().toLowerCase();
15
+ if (host.startsWith("[")) {
16
+ const end = host.indexOf("]");
17
+ return end > 0 ? host.slice(1, end) : host;
18
+ }
19
+ const colon = host.lastIndexOf(":");
20
+ // One colon is a port; more is a bare IPv6 address (not valid in Host, but an address all the same).
21
+ const name = colon > -1 && host.indexOf(":") === colon ? host.slice(0, colon) : host;
22
+ return name.replace(/\.$/, "");
23
+ }
24
+ /** Names from ORBIT_ALLOWED_HOSTS: comma-separated, each a name (a port, if given, is ignored). */
25
+ export function allowedHostsFrom(value) {
26
+ return (value ?? "")
27
+ .split(",")
28
+ .map(hostName)
29
+ .filter(Boolean);
30
+ }
31
+ export function hostCheck(options) {
32
+ const named = new Set([...options.allowed.map(hostName), hostName(options.bind)].filter(Boolean));
33
+ const answers = (name) => isIP(name) !== 0 || name === "localhost" || name.endsWith(".localhost") || name.endsWith(".ts.net") || named.has(name);
34
+ return {
35
+ refused(headers) {
36
+ const given = [headers.get("host"), ...(headers.get("x-forwarded-host") ?? "").split(",")].map((value) => (value ?? "").trim()).filter(Boolean);
37
+ for (const value of given) {
38
+ const name = hostName(value);
39
+ if (!answers(name))
40
+ return name || value;
41
+ }
42
+ return null;
43
+ }
44
+ };
45
+ }
46
+ /** The one plain line a refused name gets. */
47
+ export function misdirected(name) {
48
+ const shown = name.replace(/[^A-Za-z0-9.:[\]_-]/g, "").slice(0, 120) || "given";
49
+ return new Response(`Orbit doesn't answer to the name ${shown}. To use it, add ${shown} to ORBIT_ALLOWED_HOSTS (comma-separated) where Orbit runs, then restart it.\n`, {
50
+ status: 421,
51
+ headers: { "Content-Type": "text/plain; charset=utf-8", "X-Content-Type-Options": "nosniff", "Cache-Control": "no-store" }
52
+ });
53
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * What the computer Orbit runs on does for the app open on it: the update
3
+ * check (the version on GitHub's default branch, cached; only the server
4
+ * polls, so GitHub's limits never matter), the opt-in self-update, and a
5
+ * folder picker. The HTTP app reaches them through `HostServices`.
6
+ */
7
+ import type { HostServices } from "../client/types.ts";
8
+ import type { OrbitEnv } from "./env.ts";
9
+ export declare function hostServices(env: OrbitEnv, dataDir: string): HostServices;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * What the computer Orbit runs on does for the app open on it: the update
3
+ * check (the version on GitHub's default branch, cached; only the server
4
+ * polls, so GitHub's limits never matter), the opt-in self-update, and a
5
+ * folder picker. The HTTP app reaches them through `HostServices`.
6
+ */
7
+ import { execFile, spawn } from "node:child_process";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { join, resolve } from "node:path";
10
+ import { promisify } from "node:util";
11
+ import { PACKAGE_ROOT, packageVersion } from "./local.js";
12
+ const CHECK_TIMEOUT_MS = 5000;
13
+ function repoCoordinates() {
14
+ let url = "";
15
+ try {
16
+ const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, "package.json"), "utf8"));
17
+ url = pkg.repository?.url ?? "";
18
+ }
19
+ catch {
20
+ url = "";
21
+ }
22
+ const match = /github\.com[/:]([^/]+)\/([^/.]+)/i.exec(url);
23
+ return { owner: match?.[1] ?? "brucestarlove", repo: match?.[2] ?? "ss-orbit" };
24
+ }
25
+ /** How this Orbit runs, which decides the update instructions the app shows. */
26
+ function deployMode(env) {
27
+ if (env.updates.docker || existsSync("/.dockerenv"))
28
+ return "docker";
29
+ if (existsSync(join(PACKAGE_ROOT, ".git")))
30
+ return "source";
31
+ return "dist";
32
+ }
33
+ /** True when `latest` is strictly newer than `current` ("a.b.c", any suffix ignored). */
34
+ function isNewer(latest, current) {
35
+ const parse = (v) => v.replace(/^v/, "").split(/[.+-]/).map((n) => Number(n) || 0);
36
+ const a = parse(latest);
37
+ const b = parse(current);
38
+ for (let i = 0; i < 3; i += 1) {
39
+ if ((a[i] ?? 0) > (b[i] ?? 0))
40
+ return true;
41
+ if ((a[i] ?? 0) < (b[i] ?? 0))
42
+ return false;
43
+ }
44
+ return false;
45
+ }
46
+ async function fetchLatest(env) {
47
+ const { owner, repo } = repoCoordinates();
48
+ const headers = { Accept: "application/vnd.github.raw+json", "User-Agent": "orbit-update-check" };
49
+ if (env.updates.token)
50
+ headers.Authorization = `Bearer ${env.updates.token}`;
51
+ try {
52
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/package.json?ref=${encodeURIComponent(env.updates.branch)}`, { headers, signal: AbortSignal.timeout(CHECK_TIMEOUT_MS) });
53
+ if (res.status === 403 || res.status === 429)
54
+ return { error: "rate_limited" };
55
+ if (!res.ok)
56
+ return { error: `http_${res.status}` };
57
+ const { version } = JSON.parse(await res.text());
58
+ if (typeof version !== "string" || !version)
59
+ return { error: "no_version" };
60
+ return { latest: version };
61
+ }
62
+ catch (error) {
63
+ return { error: error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError") ? "timeout" : "unreachable" };
64
+ }
65
+ }
66
+ /** A folder picker on this computer, by platform: the commands to try in order. */
67
+ function pickerCommands(title) {
68
+ const windows = {
69
+ command: "powershell.exe",
70
+ args: [
71
+ "-NoProfile",
72
+ "-STA",
73
+ "-Command",
74
+ [
75
+ "Add-Type -AssemblyName System.Windows.Forms",
76
+ "$dialog = New-Object System.Windows.Forms.FolderBrowserDialog",
77
+ `$dialog.Description = ${JSON.stringify(title)}`,
78
+ "if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {",
79
+ " [Console]::OutputEncoding = [System.Text.Encoding]::UTF8",
80
+ " Write-Output $dialog.SelectedPath",
81
+ "}"
82
+ ].join("; ")
83
+ ]
84
+ };
85
+ if (process.platform === "darwin")
86
+ return [{ command: "osascript", args: ["-e", `POSIX path of (choose folder with prompt ${JSON.stringify(title)})`] }];
87
+ if (process.platform === "win32")
88
+ return [windows];
89
+ return [{ command: "zenity", args: ["--file-selection", "--directory", "--title", title] }, { command: "kdialog", args: ["--getexistingdirectory", ".", title] }, windows];
90
+ }
91
+ function wslPath(raw) {
92
+ const drive = /^([a-zA-Z]):[\\/](.*)$/.exec(raw.trim());
93
+ if (!drive)
94
+ return raw.trim();
95
+ return `/mnt/${(drive[1] ?? "").toLowerCase()}/${(drive[2] ?? "").replace(/\\/g, "/").replace(/^\/+/, "")}`;
96
+ }
97
+ async function pickFolder() {
98
+ const run = promisify(execFile);
99
+ const commands = pickerCommands("Choose repo folder for Orbit board");
100
+ let missing = 0;
101
+ for (const spec of commands) {
102
+ try {
103
+ const { stdout } = await run(spec.command, spec.args, { encoding: "utf8", timeout: 120_000, windowsHide: false });
104
+ const raw = stdout.trim();
105
+ const path = raw ? resolve(process.platform === "linux" && spec.command === "powershell.exe" ? wslPath(raw) : raw) : "";
106
+ return path ? { path } : { canceled: true };
107
+ }
108
+ catch (error) {
109
+ const failure = error;
110
+ if (failure.code === "ENOENT") {
111
+ missing += 1;
112
+ continue;
113
+ }
114
+ if (failure.code === 1 && /cancel/i.test(typeof failure.stderr === "string" ? failure.stderr : ""))
115
+ return { canceled: true };
116
+ throw error;
117
+ }
118
+ }
119
+ return missing === commands.length ? { unsupported: true } : { canceled: true };
120
+ }
121
+ export function hostServices(env, dataDir) {
122
+ let cache = null;
123
+ const fixed = () => {
124
+ const { owner, repo } = repoCoordinates();
125
+ return { current: packageVersion(), deployMode: deployMode(env), selfUpdate: env.selfUpdate, repoUrl: `https://github.com/${owner}/${repo}`, changesUrl: `https://github.com/${owner}/${repo}/commits/${env.updates.branch}` };
126
+ };
127
+ return {
128
+ async version({ force }) {
129
+ if (!env.updates.enabled)
130
+ return { ...fixed(), enabled: false, latest: null, updateAvailable: false, error: null, checkedAt: null };
131
+ if (!force && cache && Date.now() - cache.at < env.updates.ttlMinutes * 60_000)
132
+ return cache.payload;
133
+ const result = await fetchLatest(env);
134
+ const payload = { ...fixed(), enabled: true, latest: result.latest ?? null, updateAvailable: Boolean(result.latest) && isNewer(result.latest ?? "", packageVersion()), error: result.error ?? null, checkedAt: new Date().toISOString() };
135
+ cache = { at: Date.now(), payload };
136
+ return payload;
137
+ },
138
+ selfUpdate() {
139
+ if (!env.selfUpdate)
140
+ return null;
141
+ const script = join(PACKAGE_ROOT, "scripts", "update.sh");
142
+ if (!existsSync(script))
143
+ return { started: false, error: "script_missing" };
144
+ const child = spawn("bash", [script], { cwd: PACKAGE_ROOT, detached: true, stdio: "ignore", env: { ...process.env, ORBIT_REPO_DIR: PACKAGE_ROOT, ORBIT_SERVER_PID: String(process.pid), ORBIT_DATA_DIR: dataDir } });
145
+ child.unref();
146
+ return { started: true };
147
+ },
148
+ pickFolder
149
+ };
150
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Orbit on this computer: the SQLite store in the data folder, the
3
+ * deployment the person's own app shows, and a backup a while after the
4
+ * first write of each day. Every entry point that runs Orbit in-process opens it here.
5
+ */
6
+ import type { Orbit } from "../orbit.ts";
7
+ import type { OrbitEnv } from "./env.ts";
8
+ import type { HostServices } from "../client/types.ts";
9
+ export { PACKAGE_ROOT, packageVersion } from "./package.ts";
10
+ /** The MCP server's file beside the CLI (src/ in a checkout, lib/ in an install); the app builds its connect command from it. */
11
+ export declare const MCP_SERVER_PATH: string;
12
+ /** Where the app's files are: dist/full, which an install ships and a checkout builds (`node build.mjs`). */
13
+ export declare function publicDir(): string;
14
+ export interface LocalOrbit {
15
+ orbit: Orbit;
16
+ /** Waits for nothing: pending backups are dropped, then the store is closed. */
17
+ close(): Promise<void>;
18
+ }
19
+ /** Opens the data folder (refusing one of older data with `data_needs_migration`) and Orbit over it. */
20
+ export declare function openLocalOrbit(env: OrbitEnv, options?: {
21
+ host?: HostServices;
22
+ }): Promise<LocalOrbit>;