@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
package/src/mcp-server.js CHANGED
@@ -1,876 +1,16 @@
1
- // MCP server. One process = one session = (usually) one board, but the
2
- // per-process board lives in a closure variable that every tool reads
3
- // explicitly — never via a hidden global. Each tool builds its own ctx for
4
- // the call it's about to make, so adding new tools never has to think about
5
- // "active board" state.
6
-
7
- import { createOrbitClient } from "./mcp/orbit-client.js";
8
- import { existsSync, mkdirSync } from "node:fs";
9
- import { dirname, join, resolve } from "node:path";
10
- import {
11
- archivedTicketsForBoard,
12
- archiveTicket,
13
- checkpointTicket,
14
- claimNext,
15
- completeTicket,
16
- createBoardEntry,
17
- createComment,
18
- createTicket,
19
- deleteTicket,
20
- exportBoard,
21
- getBoardContext,
22
- getAgentDispatchPacket,
23
- getContextPack,
24
- readComments,
25
- readTicket,
26
- getTicketBlockers,
27
- getTicketRelations,
28
- localAgentActor,
29
- restoreTicket,
30
- searchTickets,
31
- updateBoard,
32
- updateTicket
33
- } from "./core/board.js";
34
- import { closeAllConnections, createBoardSchema, openConnection } from "./core/db.js";
35
- import {
36
- getBoardByRegistryId,
37
- getBoardBySlug,
38
- getBoardByRepoPath,
39
- insertBoard,
40
- listBoards,
41
- openBoardDb,
42
- syncRegistryFromBoardDb,
43
- touchBoardActive
44
- } from "./core/registry.js";
45
- import { scheduleAutomaticBoardBackup } from "./core/backups.js";
46
- import { provisionRepoBoard } from "./core/provision-repo-board.js";
47
- import { seedIfEmpty } from "./core/seed.js";
48
- import { now, normalizePath } from "./core/util.js";
49
-
50
- const SERVER_INFO = {
51
- name: "minimal-agent-board",
52
- version: "0.1.0"
53
- };
54
-
55
- const orbitClient = await createOrbitClient();
56
- // Per-process session board. Set on init by walking up from cwd, mutable via
57
- // `board_set_active`. NEVER read by anything outside this file — every
58
- // downstream call receives an explicit ctx.
59
- let sessionBoardRow = null;
60
-
61
- function setSessionBoard(row) {
62
- sessionBoardRow = row;
63
- if (row) touchBoardActive(row.id);
64
- }
65
-
66
- function getSessionBoard() {
67
- return sessionBoardRow;
68
- }
69
-
70
- /**
71
- * Walk up from `startDir` toward the filesystem root, returning the first
72
- * directory that satisfies `predicate`. Returns null if none found.
73
- */
74
- function walkUp(startDir, predicate) {
75
- let dir = startDir;
76
- while (true) {
77
- if (predicate(dir)) return dir;
78
- const parent = dirname(dir);
79
- if (parent === dir) return null;
80
- dir = parent;
81
- }
82
- }
83
-
84
- /**
85
- * Initialise the MCP session board. Resolution order:
86
- * 1. Walk up from PROJECT_ROOT (cwd unless `orbit mcp --cwd` or env overrides it)
87
- * for an existing `.orbit/board.db` — finds the board even when the agent
88
- * is launched from a subdirectory or MCP config uses an absolute command.
89
- * 2. Walk up from PROJECT_ROOT for a `.git/` directory — auto-creates a board at
90
- * the git root on first launch.
91
- * 3. Fall back to PROJECT_ROOT itself — supports non-git projects and global installs.
92
- */
93
- function initMcpSessionBoard() {
94
- const start = normalizePath(process.env.PROJECT_ROOT ? resolve(process.env.PROJECT_ROOT) : process.cwd());
95
-
96
- // 1. Prefer the registry row for PROJECT_ROOT (or one of its ancestors). New
97
- // Orbit boards live in central DATA_DIR storage, so there may be no in-repo
98
- // .orbit/board.db to discover.
99
- const registeredRoot = walkUp(start, (dir) => Boolean(getBoardByRepoPath(normalizePath(dir))));
100
- if (registeredRoot) {
101
- setSessionBoard(getBoardByRepoPath(normalizePath(registeredRoot)));
102
- return;
103
- }
104
-
105
- // 2. Walk up for an existing legacy board db.
106
- const boardRoot = walkUp(start, (dir) => existsSync(join(dir, ".orbit", "board.db")));
107
- if (boardRoot) {
108
- const root = normalizePath(boardRoot);
109
- const existing = getBoardByRepoPath(root);
110
- if (existing) { setSessionBoard(existing); return; }
111
- // db exists on disk but isn't in the registry yet — register it.
112
- const dbPath = join(boardRoot, ".orbit", "board.db");
113
- const db = openConnection(dbPath);
114
- createBoardSchema(db);
115
- const seeded = seedIfEmpty(db, root);
116
- if (seeded) {
117
- const t = now();
118
- insertBoard({ id: seeded.id, slug: seeded.slug, name: seeded.name, repo_path: root, db_path: dbPath, repo_url: seeded.repo_url || "", default_branch: seeded.default_branch || "main", last_active_at: t, created_at: t, updated_at: t });
119
- }
120
- const fresh = getBoardByRepoPath(root);
121
- if (fresh) { syncRegistryFromBoardDb(fresh, db); setSessionBoard(fresh); }
122
- return;
123
- }
124
-
125
- // 3. Walk up for a git root; fall back to cwd for non-git projects.
126
- const gitRoot = walkUp(start, (dir) => existsSync(join(dir, ".git")));
127
- const result = provisionRepoBoard(normalizePath(gitRoot || start));
128
- if (result.registryRow) setSessionBoard(result.registryRow);
129
- }
130
-
131
- function createBoardAtRoot(root) {
132
- const dbPath = join(root, ".orbit", "board.db");
133
- mkdirSync(dirname(dbPath), { recursive: true });
134
- const db = openConnection(dbPath);
135
- createBoardSchema(db);
136
- const seeded = seedIfEmpty(db, root);
137
- if (seeded) {
138
- const t = now();
139
- insertBoard({ id: seeded.id, slug: seeded.slug, name: seeded.name, repo_path: root, db_path: dbPath, repo_url: seeded.repo_url || "", default_branch: seeded.default_branch || "main", last_active_at: t, created_at: t, updated_at: t });
140
- }
141
- const fresh = getBoardByRepoPath(root);
142
- if (fresh) { syncRegistryFromBoardDb(fresh, db); setSessionBoard(fresh); }
143
- }
144
-
145
- if (orbitClient.mode !== "remote") {
146
- initMcpSessionBoard();
147
- closeAllConnections();
148
- }
149
-
150
- /** Build a ctx for the session board (or a board the tool argument selected). */
151
- function ctxFor(boardRow, actor) {
152
- if (!boardRow) throw rpcError(-32602, "No board in session; pass board_id or board_slug, or call board_set_active first.");
153
- return { actor, board: boardRow, db: openBoardDb(boardRow) };
154
- }
155
-
156
- function sessionCtx(actor) {
157
- return ctxFor(getSessionBoard(), actor);
158
- }
159
-
160
- function withAutomaticBackup(ctx, result) {
161
- scheduleAutomaticBoardBackup(ctx.board, ctx.db);
162
- return result;
163
- }
164
-
165
- /** Resolve a registry row from explicit args (no session fallback). */
166
- function resolveExplicitBoard(args) {
167
- if (args.board_id) {
168
- const row = getBoardByRegistryId(args.board_id);
169
- if (!row) throw rpcError(-32004, "Board id not found.");
170
- return row;
171
- }
172
- if (args.board_slug || args.board) {
173
- const row = getBoardBySlug(args.board_slug || args.board);
174
- if (!row) throw rpcError(-32004, "Board slug not found.");
175
- return row;
176
- }
177
- return null;
178
- }
179
-
180
- /** Resolve a board for a tool that accepts board_id/board_slug AND can fall
181
- * back to the session board. */
182
- function resolveBoardOrSession(args) {
183
- return resolveExplicitBoard(args) || getSessionBoard();
184
- }
185
-
186
- const TOOL_DEFS = [
187
- {
188
- name: "board_context",
189
- description:
190
- "Board/project context pack: agent-safe board metadata, agent_instructions, journal entries (board_entries), and deployment paths. Human-only project_notes are intentionally excluded. Complements the lean board_get_ticket_context. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience. Set include_struck true to include struck journal rows (default false). Same payload as GET /api/boards/:id/context.",
191
- inputSchema: {
192
- type: "object",
193
- properties: {
194
- board_id: { type: "string" },
195
- board_slug: { type: "string" },
196
- board: { type: "string" },
197
- include_struck: { type: "boolean", description: "When true, journal entries include struck items." }
198
- },
199
- additionalProperties: false
200
- },
201
- handler: (args) => orbitClient.boardContext(args)
202
- },
203
- {
204
- name: "board_list",
205
- description:
206
- "Tiny registry index: id, slug, name, repo_path per board. Does not open board databases or load tickets. Pair with board_set_active and board_context / ticket tools; the web UI loads full rows via GET /api/bootstrap.",
207
- inputSchema: {
208
- type: "object",
209
- properties: {},
210
- additionalProperties: false
211
- },
212
- handler: () => orbitClient.boardList()
213
- },
214
- {
215
- name: "board_set_active",
216
- description:
217
- "Switch the MCP session fallback to another board by slug (updates last_active_at for multi-board repos). Tools that accept board_id / board_slug / board use those explicit selectors first; this session board is only the convenience fallback when selectors are omitted.",
218
- inputSchema: {
219
- type: "object",
220
- properties: {
221
- slug: { type: "string" }
222
- },
223
- required: ["slug"],
224
- additionalProperties: false
225
- },
226
- handler: (args) => orbitClient.boardSetActive(args)
227
- },
228
- {
229
- name: "board_claim_next",
230
- description:
231
- "Claim the next schedulable AI-ready ticket on a specific board. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience when selectors are omitted, and omitted selectors never scan other boards.",
232
- inputSchema: {
233
- type: "object",
234
- properties: {
235
- board: { type: "string" },
236
- board_slug: { type: "string" },
237
- board_id: { type: "string" },
238
- type: { type: "string" },
239
- include_epics: { type: "boolean" }
240
- },
241
- additionalProperties: false
242
- },
243
- handler: (args) => orbitClient.claimNext(args)
244
- },
245
- {
246
- name: "board_get_ticket_context",
247
- description:
248
- "Return the default lean context pack for a ticket: ticket fields, board identity only, relations, blockers, parent/children, and related ticket summaries. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience. Project manual/journal and comments are intentionally omitted; use board_context for board manual/journal and board_read_comments for comment threads.",
249
- inputSchema: {
250
- type: "object",
251
- properties: {
252
- ticket_id: { type: "string" },
253
- board_id: { type: "string" },
254
- board_slug: { type: "string" },
255
- board: { type: "string" },
256
- depth: { type: "integer", minimum: 1, maximum: 3 },
257
- max_chars_per_field: { type: "integer", minimum: 1 },
258
- include_parent_full: { type: "boolean" },
259
- include_related_full: { type: "boolean" }
260
- },
261
- required: ["ticket_id"],
262
- additionalProperties: false
263
- },
264
- handler: (args) => orbitClient.getTicketContext(args)
265
- },
266
- {
267
- name: "board_get_ticket_context_full",
268
- description:
269
- "Return the explicit heavy ticket context pack, preserving the historical board_get_ticket_context shape: ticket, full board row, board_manual/journal, relations, blockers, parent/children, and related tickets. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience. Comments are still intentionally omitted; use board_read_comments for comment threads.",
270
- inputSchema: {
271
- type: "object",
272
- properties: {
273
- ticket_id: { type: "string" },
274
- board_id: { type: "string" },
275
- board_slug: { type: "string" },
276
- board: { type: "string" },
277
- depth: { type: "integer", minimum: 1, maximum: 3 },
278
- max_chars_per_field: { type: "integer", minimum: 1 },
279
- include_parent_full: { type: "boolean" },
280
- include_related_full: { type: "boolean" }
281
- },
282
- required: ["ticket_id"],
283
- additionalProperties: false
284
- },
285
- handler: (args) => orbitClient.getTicketContextFull(args)
286
- },
287
- {
288
- name: "board_get_agent_dispatch_packet",
289
- description:
290
- "Return a lean agent dispatch packet for one ticket: board identity/instructions, capped description/acceptance, human-visible card fields and linked artifact paths, blockers, shallow parent, relevant workflow state IDs, repo path/default branch, recent capped comments, and label names. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience. Full parent/related bodies and full comments are intentionally omitted.",
291
- inputSchema: {
292
- type: "object",
293
- properties: {
294
- ticket_id: { type: "string" },
295
- board_id: { type: "string" },
296
- board_slug: { type: "string" },
297
- board: { type: "string" },
298
- max_chars_per_field: { type: "integer", minimum: 1 },
299
- comment_limit: { type: "integer", minimum: 0, maximum: 20 }
300
- },
301
- required: ["ticket_id"],
302
- additionalProperties: false
303
- },
304
- handler: (args) => orbitClient.getAgentDispatchPacket(args)
305
- },
306
- {
307
- name: "board_read_ticket",
308
- description:
309
- "Look up a ticket by ticket_id, number, or exact title (case-insensitive), and return its title, description, labels, state, and the board manual (agent_instructions + journal + deployment paths). Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience. Ticket comments are intentionally omitted; use board_read_comments for explicit comment retrieval.",
310
- inputSchema: {
311
- type: "object",
312
- properties: {
313
- ticket_id: { type: "string" },
314
- number: { type: "integer" },
315
- title: { type: "string" },
316
- board_id: { type: "string" },
317
- board_slug: { type: "string" },
318
- board: { type: "string" }
319
- },
320
- additionalProperties: false
321
- },
322
- handler: (args) => orbitClient.readTicket(args)
323
- },
324
- {
325
- name: "board_read_comments",
326
- description:
327
- "Return every comment on a ticket (oldest → newest). Look up by ticket_id, number, or exact title (case-insensitive). Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience. Comment kinds include human_comment, agent_note, checkpoint, completion, note.",
328
- inputSchema: {
329
- type: "object",
330
- properties: {
331
- ticket_id: { type: "string" },
332
- number: { type: "integer" },
333
- title: { type: "string" },
334
- board_id: { type: "string" },
335
- board_slug: { type: "string" },
336
- board: { type: "string" }
337
- },
338
- additionalProperties: false
339
- },
340
- handler: (args) => orbitClient.readComments(args)
341
- },
342
- {
343
- name: "board_get_ticket_relations",
344
- description: "List every link touching a ticket. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience. Each entry has `type`, `direction`, `other_ticket`, and `source`. `source='relation'` rows come from the relations table (`relates_to` / `blocks` / `blocked_by`) and are the SSOT for peer dependencies — write via `POST /api/relations`, delete via `DELETE /api/relations/:id`. `source='hierarchy'` rows are read-only synthetic entries surfacing epic ownership (`child_of` toward the parent epic, `parent_of` toward each child); they have `id: null` and are mutated by patching the ticket's `parent_ticket_id`, not by calling the relations endpoints.",
345
- inputSchema: {
346
- type: "object",
347
- properties: {
348
- ticket_id: { type: "string" },
349
- board_id: { type: "string" },
350
- board_slug: { type: "string" },
351
- board: { type: "string" }
352
- },
353
- required: ["ticket_id"],
354
- additionalProperties: false
355
- },
356
- handler: (args) => orbitClient.getTicketRelations(args)
357
- },
358
- {
359
- name: "board_get_ticket_blockers",
360
- description: "Return unresolved blockers for a ticket and a can_start boolean. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience. The single source of truth for dependencies is the per-card `blocked_by` row in the relations table; if a row exists the blocker is unresolved. Rows are removed two ways: (1) automatically when the blocking ticket moves into the Done lane (state role='done'), or (2) manually by the user in the UI. A ticket with can_start=false must not be worked on until its blockers resolve. Epic blocker targets expand to that epic's open children (children in any lane whose role is not 'done', not archived); each expanded entry carries via_epic_id.",
361
- inputSchema: {
362
- type: "object",
363
- properties: {
364
- ticket_id: { type: "string" },
365
- board_id: { type: "string" },
366
- board_slug: { type: "string" },
367
- board: { type: "string" }
368
- },
369
- required: ["ticket_id"],
370
- additionalProperties: false
371
- },
372
- handler: (args) => orbitClient.getTicketBlockers(args)
373
- },
374
- {
375
- name: "board_search",
376
- description:
377
- "Search tickets, comments, and implementation records by text on a specific board. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience when selectors are omitted, and omitted selectors never merge results from other boards.",
378
- inputSchema: {
379
- type: "object",
380
- properties: {
381
- q: { type: "string" },
382
- board: { type: "string" },
383
- board_slug: { type: "string" },
384
- board_id: { type: "string" },
385
- limit: { type: "integer", minimum: 1, maximum: 50 },
386
- mode: { type: "string", enum: ["ids", "summary", "full"] },
387
- include_full: { type: "boolean" },
388
- fields: { type: "array", items: { type: "string" } },
389
- max_chars_per_field: { type: "integer", minimum: 1 }
390
- },
391
- required: ["q"],
392
- additionalProperties: false
393
- },
394
- handler: (args) => orbitClient.search(args)
395
- },
396
- {
397
- name: "board_create_ticket",
398
- description:
399
- "Create a new Orbit ticket/card. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience. Use this instead of editing .orbit/board.db directly. Same operation as POST /api/tickets.",
400
- inputSchema: {
401
- type: "object",
402
- properties: {
403
- board_id: { type: "string" },
404
- board_slug: { type: "string" },
405
- board: { type: "string" },
406
- title: { type: "string" },
407
- description: { type: "string" },
408
- type: { type: "string", enum: ["epic", "feature", "task", "bug"] },
409
- parent_ticket_id: { type: ["string", "null"] },
410
- ai_plan: { type: "string", description: "Existing AI Plan field: terse human-visible plan summary; link full markdown handoffs with plan_artifact_path." },
411
- plan_artifact_path: { type: "string", description: "Human-editable link/path to a full markdown/text plan artifact, e.g. docs/plan.md or ./docs/plan.md. Keep AI Plan terse; do not paste full handoffs into ai_plan." },
412
- implementation_summary: { type: "string", description: "Existing Work Summary field: terse human-visible result summary; link full reports with implementation_artifact_path." },
413
- implementation_artifact_path: { type: "string", description: "Human-editable link/path to a full markdown/text implementation report or evidence artifact, e.g. docs/report.md or ./docs/report.md. Keep Work Summary terse." },
414
- implementation_updates: { type: "string", description: "Revisions field: terse later changes/corrections after initial implementation; omit logs, chronology, and normal completion notes." },
415
- state_id: { type: "string" },
416
- priority: { type: "integer" },
417
- labels: { type: "array", items: { type: "string" } }
418
- },
419
- required: ["title"],
420
- additionalProperties: false
421
- },
422
- handler: (args) => orbitClient.createTicket(args)
423
- },
424
- {
425
- name: "board_update_ticket",
426
- description:
427
- "Update ticket fields such as terse AI Plan / Work Summary text, linked artifact paths, Revisions, state, type, priority, or labels (full replace: array of label names). Resolves board from board_id / board_slug / board; the MCP session board is only a fallback convenience.",
428
- inputSchema: {
429
- type: "object",
430
- properties: {
431
- ticket_id: { type: "string" },
432
- board_id: { type: "string" },
433
- board_slug: { type: "string" },
434
- board: { type: "string" },
435
- title: { type: "string" },
436
- description: { type: "string" },
437
- type: { type: "string" },
438
- parent_ticket_id: { type: ["string", "null"] },
439
- ai_plan: { type: "string", description: "Existing AI Plan field: terse human-visible plan summary; link full markdown handoffs with plan_artifact_path." },
440
- plan_artifact_path: { type: "string", description: "Human-editable link/path to a full markdown/text plan artifact, e.g. docs/plan.md or ./docs/plan.md. Keep AI Plan terse; do not paste full handoffs into ai_plan." },
441
- implementation_summary: { type: "string", description: "Existing Work Summary field: terse human-visible result summary; link full reports with implementation_artifact_path." },
442
- implementation_artifact_path: { type: "string", description: "Human-editable link/path to a full markdown/text implementation report or evidence artifact, e.g. docs/report.md or ./docs/report.md. Keep Work Summary terse." },
443
- implementation_updates: { type: "string", description: "Revisions field: terse later changes/corrections after initial implementation; omit logs, chronology, and normal completion notes." },
444
- state_id: { type: "string" },
445
- priority: { type: "integer" },
446
- labels: { type: "array", items: { type: "string" }, description: "Replaces all ticket labels when provided." }
447
- },
448
- required: ["ticket_id"],
449
- additionalProperties: false
450
- },
451
- handler: ({ ticket_id, ...patch }) => orbitClient.updateTicket({ ticket_id, ...patch })
452
- },
453
- {
454
- name: "board_add_comment",
455
- description: "Add a ticket comment or agent breadcrumb. Resolves board from board_id / board_slug / board; the MCP session board is only a fallback convenience.",
456
- inputSchema: {
457
- type: "object",
458
- properties: {
459
- ticket_id: { type: "string" },
460
- board_id: { type: "string" },
461
- board_slug: { type: "string" },
462
- board: { type: "string" },
463
- body: { type: "string" },
464
- kind: { type: "string" }
465
- },
466
- required: ["ticket_id", "body"],
467
- additionalProperties: false
468
- },
469
- handler: ({ ticket_id, ...body }) => orbitClient.addComment({ ticket_id, ...body })
470
- },
471
- {
472
- name: "board_create_review_verdict",
473
- description: "Create a durable Sentinel/agent review verdict for a ticket. Verdict must be PASS, BLOCK, or QUESTION. Findings and evidence commands are stored as structured arrays; comments may mirror the data for humans but are not the source of truth. Resolves board from board_id / board_slug / board; the active session board is only a fallback convenience.",
474
- inputSchema: {
475
- type: "object",
476
- properties: {
477
- ticket_id: { type: "string" },
478
- board_id: { type: "string" },
479
- board_slug: { type: "string" },
480
- board: { type: "string" },
481
- verdict: { type: "string", enum: ["PASS", "BLOCK", "QUESTION"] },
482
- blocking_findings: { type: "array" },
483
- optional_findings: { type: "array" },
484
- evidence_commands: { type: "array" },
485
- reviewer_profile: { type: "string" },
486
- reviewer_session_id: { type: "string" },
487
- reviewed_commit_sha: { type: "string" },
488
- dispatch_run_id: { type: "string" },
489
- supersedes_prior_review_id: { type: ["string", "null"] }
490
- },
491
- required: ["ticket_id", "verdict"],
492
- additionalProperties: false
493
- },
494
- handler: ({ ticket_id, ...body }) => orbitClient.createReviewVerdict({ ticket_id, ...body })
495
- },
496
- {
497
- name: "board_list_review_verdicts",
498
- description: "List durable review verdict records for a ticket, newest first. Use this for repair/re-review tooling instead of parsing prose comments.",
499
- inputSchema: {
500
- type: "object",
501
- properties: {
502
- ticket_id: { type: "string" },
503
- board_id: { type: "string" },
504
- board_slug: { type: "string" },
505
- board: { type: "string" },
506
- limit: { type: "integer", minimum: 1, maximum: 200 }
507
- },
508
- required: ["ticket_id"],
509
- additionalProperties: false
510
- },
511
- handler: (args) => orbitClient.listReviewVerdicts(args)
512
- },
513
- {
514
- name: "board_get_review_verdict",
515
- description: "Read one durable review verdict record by id.",
516
- inputSchema: {
517
- type: "object",
518
- properties: {
519
- review_id: { type: "string" },
520
- board_id: { type: "string" },
521
- board_slug: { type: "string" },
522
- board: { type: "string" }
523
- },
524
- required: ["review_id"],
525
- additionalProperties: false
526
- },
527
- handler: (args) => orbitClient.getReviewVerdict(args)
528
- },
529
- {
530
- name: "board_add_board_entry",
531
- description: "Add a durable board-level decision or lesson for future agents. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience.",
532
- inputSchema: {
533
- type: "object",
534
- properties: {
535
- board_id: { type: "string" },
536
- board_slug: { type: "string" },
537
- board: { type: "string" },
538
- type: { type: "string", enum: ["decision", "lesson"] },
539
- title: { type: "string" },
540
- body: { type: "string" },
541
- ticket_id: { type: "string" }
542
- },
543
- required: ["type", "title"],
544
- additionalProperties: false
545
- },
546
- handler: (args) => orbitClient.addBoardEntry(args)
547
- },
548
- {
549
- name: "board_checkpoint",
550
- description:
551
- "Pause mid-flight for a human: moves the ticket to Review (same lane as board_complete; checkpoint distinguishes a blocking question) and posts a checkpoint-kind comment with your message. Use when you cannot proceed without the user—not for normal 'work is done' handoff (use board_complete). Resolves board from board_id / board_slug / board; the MCP session board is only a fallback convenience.",
552
- inputSchema: {
553
- type: "object",
554
- properties: {
555
- ticket_id: { type: "string" },
556
- board_id: { type: "string" },
557
- board_slug: { type: "string" },
558
- board: { type: "string" },
559
- message: { type: "string" }
560
- },
561
- required: ["ticket_id", "message"],
562
- additionalProperties: false
563
- },
564
- handler: (args) => orbitClient.checkpoint(args)
565
- },
566
- {
567
- name: "board_complete",
568
- description:
569
- "Hand off finished work for human review: moves the ticket to the Review lane (by role or name), writes a compact completion comment, stores summary as the terse Work Summary (`implementation_summary`), and accepts optional linked implementation_artifact_path. Pass updates only for Revisions: terse later changes/corrections after initial implementation, not normal completion notes. This is the usual 'ready for you to look' path; use board_checkpoint only when blocked mid-flight. Resolves board from board_id / board_slug / board; the MCP session board is only a fallback convenience.",
570
- inputSchema: {
571
- type: "object",
572
- properties: {
573
- ticket_id: { type: "string" },
574
- board_id: { type: "string" },
575
- board_slug: { type: "string" },
576
- board: { type: "string" },
577
- summary: { type: "string", description: "Terse human-visible Work Summary stored in implementation_summary." },
578
- updates: { type: "string", description: "Optional Revisions text only: terse later changes/corrections after initial implementation." },
579
- implementation_artifact_path: { type: "string", description: "Human-editable link/path to a markdown/text implementation report artifact; can point at a report generated outside Orbit." },
580
- summary_artifact_path: { type: "string", description: "Compatibility alias for implementation_artifact_path." },
581
- pr_url: { type: "string" },
582
- next_state: { type: "string" }
583
- },
584
- required: ["ticket_id", "summary"],
585
- additionalProperties: false
586
- },
587
- handler: (args) => orbitClient.complete(args)
588
- },
589
- {
590
- name: "board_archive_ticket",
591
- description:
592
- "Archive (soft-delete) a ticket. The ticket is moved off the board into the Archive: it is excluded from bootstrap, search, claim-next, blockers, and relations, but its data and comments are preserved. Reverse with board_restore_ticket; permanently remove with board_delete_ticket. Resolves board from board_id / board_slug / board; the MCP session board is only a fallback convenience.",
593
- inputSchema: {
594
- type: "object",
595
- properties: { ticket_id: { type: "string" }, board_id: { type: "string" }, board_slug: { type: "string" }, board: { type: "string" } },
596
- required: ["ticket_id"],
597
- additionalProperties: false
598
- },
599
- handler: (args) => orbitClient.archiveTicket(args)
600
- },
601
- {
602
- name: "board_restore_ticket",
603
- description: "Restore an archived ticket back to the board in its previous lane (state). Resolves board from board_id / board_slug / board; the MCP session board is only a fallback convenience.",
604
- inputSchema: {
605
- type: "object",
606
- properties: { ticket_id: { type: "string" }, board_id: { type: "string" }, board_slug: { type: "string" }, board: { type: "string" } },
607
- required: ["ticket_id"],
608
- additionalProperties: false
609
- },
610
- handler: (args) => orbitClient.restoreTicket(args)
611
- },
612
- {
613
- name: "board_delete_ticket",
614
- description:
615
- "Permanently delete a ticket. The ticket MUST already be archived (call board_archive_ticket first) — otherwise this returns 409 ticket_not_archived. This cannot be undone: comments, labels, and relations are removed via cascade. Events for this ticket are kept but their ticket_id becomes NULL. Resolves board from board_id / board_slug / board; the MCP session board is only a fallback convenience.",
616
- inputSchema: {
617
- type: "object",
618
- properties: { ticket_id: { type: "string" }, board_id: { type: "string" }, board_slug: { type: "string" }, board: { type: "string" } },
619
- required: ["ticket_id"],
620
- additionalProperties: false
621
- },
622
- handler: (args) => orbitClient.deleteTicket(args)
623
- },
624
- {
625
- name: "board_list_archive",
626
- description: "List archived tickets for a board, ordered by archive time (most recent first). Identify the board with board_id or board_slug.",
627
- inputSchema: {
628
- type: "object",
629
- properties: {
630
- board_id: { type: "string" },
631
- board_slug: { type: "string" }
632
- },
633
- additionalProperties: false
634
- },
635
- handler: (args) => orbitClient.listArchive(args)
636
- },
637
- {
638
- name: "board_export_board",
639
- description: "Export a board snapshot as JSON for backup or transfer. Resolves board from board_id / board_slug / board first; the MCP session board is only a fallback convenience.",
640
- inputSchema: {
641
- type: "object",
642
- properties: {
643
- board_id: { type: "string" },
644
- board_slug: { type: "string" },
645
- board: { type: "string" },
646
- include_attachments: { type: "boolean", description: "Embed attached image bytes in the JSON snapshot." },
647
- include_images: { type: "boolean", description: "Alias for include_attachments." }
648
- },
649
- additionalProperties: false
650
- },
651
- handler: (args) => orbitClient.exportBoard(args)
652
- },
653
- {
654
- name: "board_update_settings",
655
- description:
656
- "PATCH-equivalent for board settings: name (display rename only; slug/canonical URLs stay unchanged), repo_url, system_path, default_branch, and agent_instructions (project-level agent context). Resolves board from board_id / board_slug / board; the MCP session board is only a fallback convenience.",
657
- inputSchema: {
658
- type: "object",
659
- properties: {
660
- board_id: { type: "string" },
661
- board_slug: { type: "string" },
662
- board: { type: "string" },
663
- name: { type: "string" },
664
- repo_url: { type: "string" },
665
- system_path: { type: "string" },
666
- default_branch: { type: "string" },
667
- agent_instructions: { type: "string" }
668
- },
669
- additionalProperties: false
670
- },
671
- handler: (args) => orbitClient.updateSettings(args)
672
- }
673
- ];
674
-
675
- const tools = new Map(TOOL_DEFS.map((tool) => [tool.name, tool]));
676
-
677
- let inputBuffer = Buffer.alloc(0);
678
- let expectedBodyLength = -1;
679
- let shuttingDown = false;
680
-
681
- function shutdown(code = 0) {
682
- if (shuttingDown) return;
683
- shuttingDown = true;
684
- orbitClient.close?.();
685
- process.exit(code);
686
- }
687
-
688
- process.stdin.on("data", (chunk) => {
689
- inputBuffer = Buffer.concat([inputBuffer, chunk]);
690
- drainInput();
691
- });
692
-
693
- process.stdin.on("end", () => shutdown(0));
694
- process.stdin.resume();
695
-
696
- function drainInput() {
697
- while (true) {
698
- if (expectedBodyLength >= 0) {
699
- if (inputBuffer.byteLength < expectedBodyLength) return;
700
- const body = inputBuffer.slice(0, expectedBodyLength);
701
- inputBuffer = inputBuffer.slice(expectedBodyLength);
702
- expectedBodyLength = -1;
703
- handleMessage(body.toString("utf8"), "content-length");
704
- continue;
705
- }
706
-
707
- const preview = inputBuffer.slice(0, Math.min(inputBuffer.byteLength, 32)).toString("utf8").toLowerCase();
708
- if (preview.startsWith("content-length:")) {
709
- const headerEnd = inputBuffer.indexOf(Buffer.from("\r\n\r\n"));
710
- if (headerEnd === -1) return;
711
-
712
- const headerText = inputBuffer.slice(0, headerEnd).toString("utf8");
713
- inputBuffer = inputBuffer.slice(headerEnd + 4);
714
- const headers = parseHeaders(headerText);
715
- expectedBodyLength = Number(headers["content-length"] || -1);
716
- if (!Number.isFinite(expectedBodyLength) || expectedBodyLength < 0) {
717
- writeMessage(errorResponse(null, -32700, "Missing Content-Length header."), "content-length");
718
- expectedBodyLength = -1;
719
- }
720
- continue;
721
- }
722
-
723
- const lineEnd = inputBuffer.indexOf(Buffer.from("\n"));
724
- if (lineEnd === -1) return;
725
- const line = inputBuffer.slice(0, lineEnd).toString("utf8").replace(/\r$/, "");
726
- inputBuffer = inputBuffer.slice(lineEnd + 1);
727
- if (!line.trim()) continue;
728
- handleMessage(line, "line");
729
- }
730
- }
731
-
732
- function parseHeaders(text) {
733
- const headers = {};
734
- for (const line of text.split("\r\n")) {
735
- const index = line.indexOf(":");
736
- if (index === -1) continue;
737
- const key = line.slice(0, index).trim().toLowerCase();
738
- const value = line.slice(index + 1).trim();
739
- headers[key] = value;
740
- }
741
- return headers;
742
- }
743
-
744
- async function handleMessage(body, transport = "content-length") {
745
- let message;
746
- try {
747
- message = JSON.parse(body);
748
- } catch {
749
- writeMessage(errorResponse(null, -32700, "Invalid JSON."), transport);
750
- return;
751
- }
752
-
753
- if (!message || typeof message !== "object") {
754
- writeMessage(errorResponse(null, -32600, "Invalid request."), transport);
755
- return;
756
- }
757
-
758
- if (!("id" in message)) {
759
- if (message.method === "notifications/initialized") return;
760
- return;
761
- }
762
-
763
- try {
764
- const result = await dispatch(message);
765
- writeMessage({
766
- jsonrpc: "2.0",
767
- id: message.id,
768
- result
769
- }, transport);
770
- } catch (error) {
771
- writeMessage(errorResponse(message.id, error.code || -32000, error.message || "Internal error."), transport);
772
- } finally {
773
- orbitClient.close?.();
774
- }
775
- }
776
-
777
- async function dispatch(message) {
778
- switch (message.method) {
779
- case "initialize":
780
- return {
781
- protocolVersion: message.params?.protocolVersion || "2024-11-05",
782
- capabilities: {
783
- tools: {}
784
- },
785
- serverInfo: SERVER_INFO
786
- };
787
- case "ping":
788
- return {};
789
- case "tools/list":
790
- return {
791
- tools: TOOL_DEFS.map(({ name, description, inputSchema }) => ({
792
- name,
793
- description,
794
- inputSchema
795
- }))
796
- };
797
- case "tools/call":
798
- return callTool(message.params || {});
799
- default:
800
- throw rpcError(-32601, `Method not found: ${message.method}`);
801
- }
802
- }
803
-
804
- async function callTool(params) {
805
- const name = params.name;
806
- const args = params.arguments || {};
807
- const tool = tools.get(name);
808
- if (!tool) throw rpcError(-32601, `Unknown tool: ${name}`);
809
-
810
- let result;
811
- try {
812
- result = await tool.handler(args);
813
- } catch (error) {
814
- return {
815
- content: [
816
- {
817
- type: "text",
818
- text: error.message || "Tool call failed."
819
- }
820
- ],
821
- isError: true
822
- };
823
- }
824
-
825
- return {
826
- content: [
827
- {
828
- type: "text",
829
- text: JSON.stringify(result, null, 2)
830
- }
831
- ]
832
- };
833
- }
834
-
835
- function writeMessage(message, transport = "content-length") {
836
- const json = JSON.stringify(message);
837
- if (transport === "line") {
838
- process.stdout.write(`${json}\n`);
839
- return;
840
- }
841
- process.stdout.write(`Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n${json}`);
842
- }
843
-
844
- function errorResponse(id, code, message) {
845
- return {
846
- jsonrpc: "2.0",
847
- id,
848
- error: {
849
- code,
850
- message
851
- }
852
- };
853
- }
854
-
855
- function rpcError(code, message) {
856
- const error = new Error(message);
857
- error.code = code;
858
- return error;
859
- }
860
-
861
- process.on("uncaughtException", (error) => {
862
- writeMessage(errorResponse(null, -32000, error.message || "Uncaught exception."));
863
- });
864
-
865
- process.on("unhandledRejection", (error) => {
866
- writeMessage(errorResponse(null, -32000, error?.message || "Unhandled rejection."));
867
- });
868
-
869
- process.once("SIGINT", () => shutdown(0));
870
- process.once("SIGTERM", () => shutdown(0));
871
- process.once("exit", () => orbitClient.close?.());
872
-
873
- if (process.env.MAB_MCP_STDERR_LOG === "1") {
874
- const target = typeof orbitClient.sessionLabel === "function" ? orbitClient.sessionLabel() : orbitClient.sessionLabel;
875
- console.error(`Starscape Orbit MCP ready. Mode: ${orbitClient.mode}. Target: ${target}`);
876
- }
1
+ #!/usr/bin/env node
2
+ // Orbit 1.x's MCP server lived here, and 1.x's Settings, AI told people to register it as
3
+ // `node <this file>`, naming the project in PROJECT_ROOT (or by the working folder). Orbit 2.0
4
+ // installs over 1.x in place, so this file keeps those registrations starting: it runs 2.0's
5
+ // `orbit mcp`, with PROJECT_ROOT as its --cwd. 1.x's remote settings (ORBIT_MODE, ORBIT_API_URL,
6
+ // ORBIT_DEFAULT_BOARD) mean the same in 2.0 and pass through. It says so on stderr only, because
7
+ // stdout is the MCP protocol. It stays until 3.0. Plain JavaScript, outside tsc and lint: npm
8
+ // ships it as it is. Its twin for 1.x's bin is src/cli/orbit.js.
9
+
10
+ const args = ["mcp"];
11
+ if (process.env.PROJECT_ROOT) args.push("--cwd", process.env.PROJECT_ROOT);
12
+ process.argv.splice(2, process.argv.length - 2, ...args);
13
+
14
+ process.stderr.write("Orbit 2.0 starts as `orbit mcp`: this server was added the 1.x way, so re-add it as `orbit mcp` when convenient.\n");
15
+
16
+ await import("../lib/cli/orbit.js");