@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
@@ -1,409 +0,0 @@
1
- import { getRegistry, tx } from "./db.js";
2
- import { boardById, compactStatePositions } from "./queries.js";
3
- import {
4
- computeNewBoardDbPath,
5
- ensureBoardDbFileAndSchema,
6
- insertBoard,
7
- updateBoardMeta
8
- } from "./registry.js";
9
- import { requireBoardAccess, requirePermission } from "./auth.js";
10
- import { recordEvent } from "./events.js";
11
- import { buildBoardFromRepo } from "./seed.js";
12
- import { generateHelperFiles } from "./agents-md.js";
13
- import { httpError, id, now, normalizePath, normalizeProjectEntryType, requiredString, slugify } from "./util.js";
14
- import { DATA_DIR, MCP_SERVER_PATH, ROOT_DIR } from "./paths.js";
15
- import { resolve } from "node:path";
16
-
17
- /** Create a brand-new board on disk + register it. Does not require an
18
- * existing ctx.board / ctx.db: this is registry-only. */
19
- export function createBoard(body, ctx) {
20
- const { actor } = ctx;
21
- requirePermission(actor, "owner");
22
-
23
- const name = requiredString(body.name, "name");
24
- const slug = slugify(body.slug || name);
25
- // Folder is optional: a board without a repo_path is a pure kanban board (no
26
- // agent dispatch, no managed helper files). When present it is the coding
27
- // project where SKILL-ORBIT.md / AGENTS.md live for the AI agent's reference.
28
- const repoPath = normalizePath(body.repo_path || body.system_path || "");
29
- // Only meaningful with a folder; default on so the common "link my repo" path
30
- // keeps writing the docs. Stored so the serve-time refresh respects opt-out.
31
- const manageHelperFiles = repoPath ? body.manage_helper_files !== false : false;
32
- const reg = getRegistry();
33
- if (reg.prepare("SELECT id FROM boards WHERE slug = ?").get(slug)) {
34
- throw httpError(409, "board_slug_taken");
35
- }
36
-
37
- const dbPath = computeNewBoardDbPath(repoPath, slug);
38
- const db = ensureBoardDbFileAndSchema(dbPath);
39
-
40
- const boardId = id();
41
- const time = now();
42
- const sniff = repoPath ? buildBoardFromRepo(repoPath) : { repoUrl: "", defaultBranch: "main" };
43
-
44
- const aiEnabled = body.ai_enabled === undefined ? true : Boolean(body.ai_enabled);
45
- const defaultStates = body.states || (aiEnabled ? [
46
- ["Backlog", null],
47
- ["Todo", null],
48
- ["AI Ready", "ai_ready"],
49
- ["In Progress", "in_progress"],
50
- ["Review", "review"],
51
- ["Done", "done"],
52
- ["Cancelled", null]
53
- ] : [
54
- ["Backlog", null],
55
- ["Todo", null],
56
- ["In Progress", "in_progress"],
57
- ["Review", "review"],
58
- ["Done", "done"],
59
- ["Cancelled", null]
60
- ]);
61
-
62
- // Board row + lanes + creation event land atomically in the board DB. The
63
- // registry insert below is a different database (can't share this
64
- // transaction); syncRegistryFromBoardDb repairs it idempotently if that
65
- // second write is ever lost.
66
- tx(db, () => {
67
- db.prepare(
68
- `INSERT INTO boards
69
- (id, slug, name, repo_url, system_path, default_branch, ai_enabled, created_at, updated_at)
70
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
71
- ).run(
72
- boardId,
73
- slug,
74
- name,
75
- body.repo_url || sniff.repoUrl || "",
76
- repoPath,
77
- body.default_branch || sniff.defaultBranch || "main",
78
- aiEnabled ? 1 : 0,
79
- time,
80
- time
81
- );
82
-
83
- defaultStates.forEach(([stateName, role], position) => {
84
- db.prepare(
85
- `INSERT INTO states
86
- (id, board_id, name, position, is_default, role, created_at)
87
- VALUES (?, ?, ?, ?, ?, ?, ?)`
88
- ).run(id(), boardId, stateName, position, stateName === "Todo" ? 1 : 0, role || null, time);
89
- });
90
-
91
- if (aiEnabled) ensureAgentLanes(db, boardId);
92
- recordEvent(db, boardId, "board_created", null, actor.name, { board_id: boardId, slug });
93
- });
94
-
95
- insertBoard({
96
- id: boardId,
97
- slug,
98
- name,
99
- repo_path: repoPath,
100
- db_path: dbPath,
101
- repo_url: body.repo_url || sniff.repoUrl || "",
102
- default_branch: body.default_branch || sniff.defaultBranch || "main",
103
- manage_helper_files: manageHelperFiles ? 1 : 0,
104
- last_active_at: time,
105
- created_at: time,
106
- updated_at: time
107
- });
108
-
109
- // Mirror `orbit init`: write SKILL-ORBIT.md + the AGENTS.md Orbit section into
110
- // the project so the browser flow leaves the same files on disk. Best-effort —
111
- // a board is still created if the files can't be written.
112
- if (manageHelperFiles) {
113
- try {
114
- generateHelperFiles(repoPath, resolve(ROOT_DIR, "SKILL-ORBIT.md"));
115
- } catch (error) {
116
- console.warn(`[orbit] Could not write helper files in ${repoPath}: ${error.message}`);
117
- }
118
- }
119
-
120
- return boardById(db, boardId);
121
- }
122
-
123
- const AGENT_LANES = [
124
- { name: "AI Ready", role: "ai_ready" },
125
- { name: "In Progress", role: "in_progress" },
126
- { name: "Review", role: "review" }
127
- ];
128
-
129
- function desiredAgentLanePosition(db, boardId, role) {
130
- if (role === "ai_ready") {
131
- const todo = db.prepare("SELECT position FROM states WHERE board_id = ? AND name = 'Todo'").get(boardId);
132
- if (todo) return Number(todo.position) + 1;
133
- const inProgress = db.prepare("SELECT position FROM states WHERE board_id = ? AND role = 'in_progress'").get(boardId);
134
- if (inProgress) return Number(inProgress.position);
135
- }
136
- const maxPosition = db
137
- .prepare("SELECT MAX(position) AS value FROM states WHERE board_id = ?")
138
- .get(boardId).value;
139
- return Number(maxPosition ?? -1) + 1;
140
- }
141
-
142
- function openStateSlot(db, boardId, position, exceptStateId = null) {
143
- db.prepare(
144
- `UPDATE states
145
- SET position = position + 1
146
- WHERE board_id = ?
147
- AND position >= ?
148
- AND (? IS NULL OR id != ?)`
149
- ).run(boardId, position, exceptStateId, exceptStateId);
150
- }
151
-
152
- /** Ensure the role-bearing lanes that agent flow depends on exist on the
153
- * board. AI Ready is kept directly after Todo so claim-next has a predictable
154
- * handoff column. Idempotent. */
155
- export function ensureAgentLanes(db, boardId) {
156
- const time = now();
157
- for (const lane of AGENT_LANES) {
158
- const existing = db
159
- .prepare("SELECT id, position FROM states WHERE board_id = ? AND role = ?")
160
- .get(boardId, lane.role);
161
- if (existing) {
162
- if (lane.role === "ai_ready") {
163
- let desiredPosition = desiredAgentLanePosition(db, boardId, lane.role);
164
- if (Number(existing.position) !== desiredPosition) {
165
- db.prepare("UPDATE states SET position = -1 WHERE id = ?").run(existing.id);
166
- compactStatePositions(db, boardId);
167
- desiredPosition = desiredAgentLanePosition(db, boardId, lane.role);
168
- openStateSlot(db, boardId, desiredPosition, existing.id);
169
- db.prepare("UPDATE states SET position = ? WHERE id = ?").run(desiredPosition, existing.id);
170
- }
171
- }
172
- continue;
173
- }
174
- const position = desiredAgentLanePosition(db, boardId, lane.role);
175
- openStateSlot(db, boardId, position);
176
- db.prepare(
177
- `INSERT INTO states
178
- (id, board_id, name, position, is_default, role, created_at)
179
- VALUES (?, ?, ?, ?, 0, ?, ?)`
180
- ).run(id(), boardId, lane.name, position, lane.role, time);
181
- }
182
- }
183
-
184
- export function enableAiCollaboration(db, boardId, options = {}) {
185
- const innerBoard = boardById(db, boardId);
186
- if (!innerBoard) throw httpError(404, "board_not_found");
187
-
188
- const time = now();
189
- tx(db, () => {
190
- db.prepare("UPDATE boards SET ai_enabled = 1, updated_at = ? WHERE id = ?").run(time, boardId);
191
- ensureAgentLanes(db, boardId);
192
-
193
- if (options.stageOnboardingTicket) {
194
- const aiReadyLane = db.prepare("SELECT id FROM states WHERE board_id = ? AND role = 'ai_ready'").get(boardId);
195
- if (aiReadyLane) {
196
- db.prepare(
197
- `UPDATE tickets
198
- SET state_id = ?, updated_at = ?
199
- WHERE board_id = ?
200
- AND number = 12
201
- AND title = 'Try Orbit MCP on this ticket'`
202
- ).run(aiReadyLane.id, time, boardId);
203
- }
204
- }
205
-
206
- recordEvent(db, boardId, "ai_enabled", null, options.actor || "system", { board_id: boardId });
207
- });
208
- return boardById(db, boardId);
209
- }
210
-
211
- export function disableAiCollaboration(db, boardId, options = {}) {
212
- const innerBoard = boardById(db, boardId);
213
- if (!innerBoard) throw httpError(404, "board_not_found");
214
-
215
- const time = now();
216
- return tx(db, () => {
217
- db.prepare("UPDATE boards SET ai_enabled = 0, updated_at = ? WHERE id = ?").run(time, boardId);
218
- recordEvent(db, boardId, "ai_disabled", null, options.actor || "system", { board_id: boardId });
219
- return boardById(db, boardId);
220
- });
221
- }
222
-
223
- export function updateBoard(boardId, body, ctx) {
224
- const { db, board, actor } = ctx;
225
- if (boardId !== board.id) throw httpError(404, "board_not_found");
226
- const innerBoard = boardById(db, boardId);
227
- if (!innerBoard) throw httpError(404, "board_not_found");
228
- requireBoardAccess(actor, innerBoard);
229
- requirePermission(actor, "write");
230
-
231
- const allowed = [
232
- "name",
233
- "repo_url",
234
- "system_path",
235
- "default_branch",
236
- "project_notes",
237
- "agent_instructions"
238
- ];
239
- const sets = [];
240
- const values = [];
241
-
242
- for (const field of allowed) {
243
- if (body[field] !== undefined) {
244
- sets.push(`${field} = ?`);
245
- values.push(field === "name" ? requiredString(body[field], "name") : String(body[field] ?? ""));
246
- }
247
- }
248
-
249
- const aiTurningOn = body.ai_enabled !== undefined && Boolean(body.ai_enabled);
250
- if (body.ai_enabled !== undefined) {
251
- sets.push("ai_enabled = ?");
252
- values.push(body.ai_enabled ? 1 : 0);
253
- }
254
-
255
- // manage_helper_files lives on the registry row (not the per-board DB), so it
256
- // is handled separately from `sets`. A request that only toggles it must
257
- // still be processed even when no per-board columns change.
258
- const manageHelperFiles =
259
- body.manage_helper_files === undefined ? undefined : Boolean(body.manage_helper_files);
260
-
261
- if (sets.length === 0 && manageHelperFiles === undefined) return innerBoard;
262
-
263
- if (sets.length > 0) {
264
- sets.push("updated_at = ?");
265
- values.push(now(), boardId);
266
- tx(db, () => {
267
- db.prepare(`UPDATE boards SET ${sets.join(", ")} WHERE id = ?`).run(...values);
268
- if (aiTurningOn) ensureAgentLanes(db, boardId);
269
- recordEvent(db, boardId, "board_updated", null, actor.name, { board_id: boardId, fields: Object.keys(body) });
270
- });
271
- }
272
-
273
- // Mirror the changed fields into the registry row so cross-board listings
274
- // stay coherent without needing a second source of truth.
275
- const after = boardById(db, boardId);
276
- const repoPath = normalizePath(after.system_path || "");
277
- updateBoardMeta(boardId, {
278
- name: after.name,
279
- repo_url: after.repo_url,
280
- default_branch: after.default_branch,
281
- repo_path: repoPath,
282
- ...(manageHelperFiles === undefined ? {} : { manage_helper_files: manageHelperFiles ? 1 : 0 })
283
- });
284
-
285
- // Generating the helper files is the active half of turning the toggle on:
286
- // write them now rather than waiting for the next `orbit run` refresh.
287
- // Turning it off just stops the managed refresh; existing files are left.
288
- if (manageHelperFiles && repoPath) {
289
- try {
290
- generateHelperFiles(repoPath, resolve(ROOT_DIR, "SKILL-ORBIT.md"));
291
- } catch (error) {
292
- console.warn(`[orbit] Could not write helper files in ${repoPath}: ${error.message}`);
293
- }
294
- }
295
-
296
- return after;
297
- }
298
-
299
- export function getBoardContext(boardId, ctx, options = {}) {
300
- const { db, board, actor } = ctx;
301
- if (boardId !== board.id) throw httpError(404, "board_not_found");
302
- const innerBoard = boardById(db, boardId);
303
- if (!innerBoard) throw httpError(404, "board_not_found");
304
- requireBoardAccess(actor, innerBoard);
305
- return boardManual(boardId, ctx, options);
306
- }
307
-
308
- export function boardManual(boardId, ctx, options = {}) {
309
- const { db } = ctx;
310
- const innerBoard = boardById(db, boardId);
311
- if (!innerBoard) throw httpError(404, "board_not_found");
312
- const includeStruck = Boolean(options.includeStruck);
313
- const allEntries = db
314
- .prepare("SELECT * FROM board_entries WHERE board_id = ? ORDER BY created_at DESC")
315
- .all(boardId);
316
- const activeEntries = allEntries.filter((entry) => !entry.struck_at);
317
- const entries = includeStruck ? allEntries : activeEntries;
318
- return {
319
- board: {
320
- id: innerBoard.id,
321
- slug: innerBoard.slug,
322
- name: innerBoard.name,
323
- repo_url: innerBoard.repo_url,
324
- system_path: innerBoard.system_path || ctx.board.repo_path || "",
325
- default_branch: innerBoard.default_branch,
326
- agent_instructions: innerBoard.agent_instructions,
327
- ai_enabled: innerBoard.ai_enabled,
328
- manage_helper_files: ctx.board.manage_helper_files,
329
- created_at: innerBoard.created_at,
330
- updated_at: innerBoard.updated_at
331
- },
332
- entries,
333
- deployment: {
334
- app_root: ROOT_DIR,
335
- system_path: innerBoard.system_path || ctx.board.repo_path || "",
336
- data_dir: DATA_DIR,
337
- mcp_server_path: MCP_SERVER_PATH,
338
- db_path: ctx.board.db_path
339
- }
340
- };
341
- }
342
-
343
- export function createBoardEntry(boardId, body, ctx) {
344
- const { db, board, actor } = ctx;
345
- if (boardId !== board.id) throw httpError(404, "board_not_found");
346
- const innerBoard = boardById(db, boardId);
347
- if (!innerBoard) throw httpError(404, "board_not_found");
348
- requireBoardAccess(actor, innerBoard);
349
- requirePermission(actor, "write");
350
-
351
- const entryType = normalizeProjectEntryType(body.type);
352
- const entryTitle = requiredString(body.title, "title");
353
- return tx(db, () => {
354
- const entry = insertBoardEntry(
355
- db,
356
- boardId,
357
- entryType,
358
- entryTitle,
359
- body.body || "",
360
- actor.name,
361
- body.ticket_id || null
362
- );
363
- recordEvent(db, boardId, "board_entry_created", body.ticket_id || null, actor.name, {
364
- board_id: boardId,
365
- entry_id: entry.id,
366
- type: entry.type
367
- });
368
- return entry;
369
- });
370
- }
371
-
372
- export function updateBoardEntry(entryId, body, ctx) {
373
- const { db, board, actor } = ctx;
374
- const entry = db.prepare("SELECT * FROM board_entries WHERE id = ?").get(entryId);
375
- if (!entry || entry.board_id !== board.id) throw httpError(404, "board_entry_not_found");
376
-
377
- const innerBoard = boardById(db, entry.board_id);
378
- if (!innerBoard) throw httpError(404, "board_not_found");
379
- requireBoardAccess(actor, innerBoard);
380
- requirePermission(actor, "write");
381
-
382
- const shouldStrike = Boolean(body.struck);
383
- const struckAt = shouldStrike ? entry.struck_at || now() : null;
384
- const updatedAt = now();
385
- return tx(db, () => {
386
- db.prepare("UPDATE board_entries SET struck_at = ?, updated_at = ? WHERE id = ?").run(struckAt, updatedAt, entryId);
387
- const updated = db.prepare("SELECT * FROM board_entries WHERE id = ?").get(entryId);
388
- recordEvent(
389
- db,
390
- entry.board_id,
391
- shouldStrike ? "board_entry_struck" : "board_entry_restored",
392
- entry.ticket_id || null,
393
- actor.name,
394
- { board_id: entry.board_id, entry_id: entry.id, type: entry.type }
395
- );
396
- return updated;
397
- });
398
- }
399
-
400
- export function insertBoardEntry(db, boardId, type, title, body, createdBy, ticketId = null) {
401
- const entryId = id();
402
- const time = now();
403
- db.prepare(
404
- `INSERT INTO board_entries
405
- (id, board_id, type, title, body, ticket_id, created_by, created_at, updated_at)
406
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
407
- ).run(entryId, boardId, normalizeProjectEntryType(type), title, body || "", ticketId, createdBy, time, time);
408
- return db.prepare("SELECT * FROM board_entries WHERE id = ?").get(entryId);
409
- }
@@ -1,170 +0,0 @@
1
- import { compactTicket, ticketById } from "./queries.js";
2
- import { canAccessBoard, publicActor } from "./auth.js";
3
- import { EXPORT_DIR } from "./paths.js";
4
- import {
5
- getBoardByRegistryId,
6
- listBoards,
7
- openBoardDb,
8
- pickDefaultBoard,
9
- touchBoardActive
10
- } from "./registry.js";
11
-
12
- function boardSummaryFromRegistry(r) {
13
- return {
14
- id: r.id,
15
- slug: r.slug,
16
- name: r.name,
17
- repo_url: r.repo_url,
18
- default_branch: r.default_branch,
19
- repo_path: r.repo_path,
20
- system_path: r.repo_path,
21
- project_notes: "",
22
- agent_instructions: "",
23
- ai_enabled: 0,
24
- created_at: r.created_at,
25
- updated_at: r.updated_at
26
- };
27
- }
28
-
29
- /**
30
- * Build the bootstrap payload for one board (or just registry metadata if the
31
- * actor has no accessible boards). No global state is mutated; the chosen
32
- * board's last_active_at is bumped so subsequent default-pick prefers it.
33
- *
34
- * @param actor authenticated actor
35
- * @param requestedBoardId optional board id from `?board_id=` (or null)
36
- * @param requestedBoardSlug optional board slug from `?board=` (or null)
37
- */
38
- export function getBootstrap(actor, requestedBoardId = null, requestedBoardSlug = null) {
39
- const registryRows = listBoards();
40
- const accessible = registryRows.filter((row) => canAccessBoard(actor, { slug: row.slug }));
41
-
42
- if (accessible.length === 0) {
43
- return {
44
- actor: publicActor(actor),
45
- meta: {
46
- db_path: null,
47
- export_dir: EXPORT_DIR
48
- },
49
- boards: [],
50
- board_entries: [],
51
- states: [],
52
- labels: [],
53
- tickets: []
54
- };
55
- }
56
-
57
- let chosen = null;
58
- if (requestedBoardId) {
59
- chosen = accessible.find((row) => row.id === requestedBoardId) || null;
60
- }
61
- if (!chosen && requestedBoardSlug) {
62
- chosen = accessible.find((row) => row.slug === requestedBoardSlug) || null;
63
- }
64
- if (!chosen) {
65
- const defaultRow = pickDefaultBoard();
66
- if (defaultRow && accessible.some((row) => row.id === defaultRow.id)) {
67
- chosen = defaultRow;
68
- }
69
- }
70
- if (!chosen) {
71
- const sorted = [...accessible].sort(
72
- (a, b) =>
73
- String(b.last_active_at || "").localeCompare(String(a.last_active_at || "")) ||
74
- String(a.name).localeCompare(String(b.name))
75
- );
76
- chosen = sorted[0];
77
- }
78
-
79
- // Re-fetch in case the chosen row came from pickDefaultBoard before the
80
- // accessible filter. Either way, this is the row we open.
81
- const chosenRow = getBoardByRegistryId(chosen.id);
82
- touchBoardActive(chosenRow.id);
83
- const db = openBoardDb(chosenRow);
84
- const innerBoard = db.prepare("SELECT * FROM boards LIMIT 1").get();
85
-
86
- const boards = accessible.map((r) => {
87
- if (innerBoard && r.id === innerBoard.id) {
88
- return {
89
- ...innerBoard,
90
- repo_path: r.repo_path,
91
- system_path: innerBoard.system_path || r.repo_path
92
- };
93
- }
94
- return boardSummaryFromRegistry(r);
95
- });
96
-
97
- const boardIds = new Set(innerBoard ? [innerBoard.id] : []);
98
-
99
- const states = db
100
- .prepare("SELECT * FROM states ORDER BY position")
101
- .all()
102
- .filter((state) => boardIds.has(state.board_id));
103
-
104
- const tickets = db
105
- .prepare(
106
- `SELECT t.*, s.name AS state_name, s.role AS state_role,
107
- b.slug AS board_slug, b.name AS board_name
108
- FROM tickets t
109
- JOIN states s ON s.id = t.state_id
110
- JOIN boards b ON b.id = t.board_id
111
- WHERE t.archived_at IS NULL
112
- ORDER BY t.updated_at DESC, t.id ASC`
113
- )
114
- .all()
115
- .filter((ticket) => boardIds.has(ticket.board_id));
116
-
117
- const labels = db
118
- .prepare("SELECT * FROM labels ORDER BY name")
119
- .all()
120
- .filter((label) => boardIds.has(label.board_id));
121
-
122
- const ticketLabels = db
123
- .prepare(
124
- `SELECT tl.ticket_id, l.id, l.name, l.color
125
- FROM ticket_labels tl
126
- JOIN labels l ON l.id = tl.label_id`
127
- )
128
- .all()
129
- .filter((label) => tickets.some((ticket) => ticket.id === label.ticket_id));
130
-
131
- const commentCounts = db
132
- .prepare("SELECT ticket_id, COUNT(*) AS count FROM comments GROUP BY ticket_id")
133
- .all()
134
- .filter((row) => tickets.some((ticket) => ticket.id === row.ticket_id));
135
-
136
- const childCounts = db
137
- .prepare(
138
- "SELECT parent_ticket_id AS ticket_id, COUNT(*) AS count FROM tickets WHERE parent_ticket_id IS NOT NULL AND archived_at IS NULL GROUP BY parent_ticket_id"
139
- )
140
- .all()
141
- .filter((row) => tickets.some((ticket) => ticket.id === row.ticket_id));
142
-
143
- const boardEntries = db
144
- .prepare("SELECT * FROM board_entries ORDER BY created_at DESC")
145
- .all()
146
- .filter((entry) => boardIds.has(entry.board_id));
147
-
148
- return {
149
- actor: publicActor(actor),
150
- // The server may choose a default board that is not first in the registry's
151
- // alphabetic board list. Expose that choice explicitly so first-page load
152
- // selects the same board whose lanes/tickets are included below.
153
- active_board_id: chosenRow.id,
154
- meta: {
155
- db_path: chosenRow.db_path,
156
- export_dir: EXPORT_DIR
157
- },
158
- boards,
159
- board_entries: boardEntries,
160
- states,
161
- labels,
162
- tickets: tickets.map((ticket) => ({
163
- ...ticket,
164
- labels: ticketLabels.filter((label) => label.ticket_id === ticket.id),
165
- comment_count: commentCounts.find((row) => row.ticket_id === ticket.id)?.count || 0,
166
- child_count: childCounts.find((row) => row.ticket_id === ticket.id)?.count || 0,
167
- parent_ticket: ticket.parent_ticket_id ? compactTicket(ticketById(db, ticket.parent_ticket_id)) : null
168
- }))
169
- };
170
- }
@@ -1,30 +0,0 @@
1
- // CSS bundling seam for Starscape UI package imports.
2
- //
3
- // public/styles.css remains Orbit's editable source of truth, but it can now
4
- // import @starlove/ui component CSS. Static builds and source dev serving both
5
- // pass that source through esbuild so browsers never have to resolve package
6
- // specifiers from raw CSS.
7
-
8
- const CURSOR_ASSET_LOADERS = {
9
- ".cur": "file"
10
- };
11
-
12
- export async function bundleCss({ entryPoint, outfile, minify = false }) {
13
- const { build } = await import("esbuild");
14
- const result = await build({
15
- entryPoints: [entryPoint],
16
- bundle: true,
17
- target: "es2022",
18
- minify,
19
- sourcemap: false,
20
- write: false,
21
- outfile,
22
- assetNames: "cursors/[name]",
23
- loader: CURSOR_ASSET_LOADERS,
24
- external: ["./fonts/*", "fonts/*"],
25
- legalComments: "none"
26
- });
27
- const cssFile = result.outputFiles.find((file) => file.path.endsWith(".css"));
28
- if (!cssFile) throw new Error("CSS bundle did not produce a stylesheet");
29
- return { cssFile, outputFiles: result.outputFiles };
30
- }