@starlove/orbit 1.4.0 → 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.62905fcb.js +0 -952
  204. package/dist/full/js/app.62905fcb.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 -229
  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/core/util.js DELETED
@@ -1,106 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { resolve } from "node:path";
3
-
4
- /** Normalize repo paths for registry lookup (trailing slashes, slashes, Windows case). */
5
- export function normalizePath(raw) {
6
- if (raw === null || raw === undefined) return "";
7
- let s = String(raw).trim();
8
- if (!s) return "";
9
- try {
10
- s = resolve(s);
11
- } catch {
12
- return "";
13
- }
14
- s = s.replace(/\\/g, "/").replace(/\/+$/, "");
15
- if (process.platform === "win32") s = s.toLowerCase();
16
- return s;
17
- }
18
-
19
- export function id() {
20
- return randomUUID();
21
- }
22
-
23
- export function now() {
24
- return new Date().toISOString();
25
- }
26
-
27
- export function slugify(value) {
28
- return String(value)
29
- .toLowerCase()
30
- .trim()
31
- .replace(/[^a-z0-9]+/g, "-")
32
- .replace(/^-+|-+$/g, "")
33
- .slice(0, 80);
34
- }
35
-
36
- export function titleize(value) {
37
- return String(value)
38
- .replace(/[-_]+/g, " ")
39
- .replace(/\s+/g, " ")
40
- .trim()
41
- .replace(/\b\w/g, (letter) => letter.toUpperCase());
42
- }
43
-
44
- export function httpError(status, code) {
45
- const error = new Error(code);
46
- error.status = status;
47
- error.code = code;
48
- return error;
49
- }
50
-
51
- export function requiredString(value, field) {
52
- if (typeof value !== "string" || value.trim() === "") {
53
- throw httpError(400, `missing_${field}`);
54
- }
55
- return value.trim();
56
- }
57
-
58
- export function normalizePriority(value) {
59
- const number = Number(value ?? 2);
60
- if (!Number.isFinite(number)) return 2;
61
- return Math.max(0, Math.min(4, Math.round(number)));
62
- }
63
-
64
- export function normalizeTicketType(value) {
65
- const type = String(value || "task").toLowerCase().trim();
66
- const allowed = new Set(["epic", "feature", "task", "bug"]);
67
- if (!allowed.has(type)) throw httpError(400, "invalid_ticket_type");
68
- return type;
69
- }
70
-
71
- export function normalizeProjectEntryType(value) {
72
- const type = String(value || "").toLowerCase().trim();
73
- const allowed = new Set(["decision", "lesson"]);
74
- if (!allowed.has(type)) throw httpError(400, "invalid_project_entry_type");
75
- return type;
76
- }
77
-
78
- export function normalizeStateRole(value) {
79
- if (value === null || value === undefined || value === "") return null;
80
- const role = String(value).toLowerCase().trim();
81
- // `done` marks the terminal lane that auto-resolves blockers (relation-row
82
- // SSOT — see updateTicket / unresolvedBlockers). The agent-flow roles
83
- // (ai_ready/in_progress/review) are anchors the claim-next pipeline keys off.
84
- const allowed = new Set(["ai_ready", "in_progress", "review", "done"]);
85
- if (!allowed.has(role)) throw httpError(400, "invalid_state_role");
86
- return role;
87
- }
88
-
89
- export function appendFieldNote(existing, note, actorName) {
90
- void actorName;
91
- const text = String(note || "").trim();
92
- if (!text) return existing || "";
93
- const prior = String(existing || "").trim();
94
- return [prior, text].filter(Boolean).join("\n\n");
95
- }
96
-
97
- export function toFtsQuery(q) {
98
- return q
99
- .toLowerCase()
100
- .replace(/[^a-z0-9_ -]/g, " ")
101
- .split(/\s+/)
102
- .map((part) => part.trim())
103
- .filter(Boolean)
104
- .map((part) => `${part}*`)
105
- .join(" AND ");
106
- }
@@ -1,154 +0,0 @@
1
- // Update checking. The server reads its own version from package.json and
2
- // compares it against the `version` field of package.json on the project's
3
- // GitHub default branch. We hit the GitHub *contents API* (not the raw CDN):
4
- // it honours conditional requests / no-cache so the answer is authoritative,
5
- // and because only the server polls — not every browser — GitHub's 60 req/hr
6
- // unauthenticated limit is a non-issue. Results are cached in-memory for
7
- // ORBIT_UPDATE_TTL minutes; `force` bypasses the cache for a "Check now".
8
- //
9
- // Env:
10
- // ORBIT_UPDATE_CHECK=0 disable the check entirely (returns enabled:false)
11
- // ORBIT_UPDATE_TTL=<min> cache lifetime, default 60
12
- // ORBIT_UPDATE_BRANCH branch to read, default the repo default (master)
13
- // ORBIT_GITHUB_TOKEN optional PAT (private repos / higher rate limit)
14
- // ORBIT_SELF_UPDATE=1 enable the gated POST /api/update self-update
15
- // ORBIT_DOCKER=1 force "docker" deploy mode
16
-
17
- import { existsSync, readFileSync } from "node:fs";
18
- import { join } from "node:path";
19
- import { spawn } from "node:child_process";
20
- import { ROOT_DIR, DATA_DIR } from "./paths.js";
21
-
22
- const DEFAULT_OWNER = "brucestarlove";
23
- const DEFAULT_REPO = "ss-orbit";
24
- const CHECK_TIMEOUT_MS = 5000;
25
-
26
- let pkgCache = null;
27
- function readPackage() {
28
- if (pkgCache) return pkgCache;
29
- try {
30
- pkgCache = JSON.parse(readFileSync(join(ROOT_DIR, "package.json"), "utf8"));
31
- } catch {
32
- pkgCache = {};
33
- }
34
- return pkgCache;
35
- }
36
-
37
- /** Parse owner/repo out of package.json repository.url, falling back to the
38
- * known Orbit repo so the check still works on odd checkouts. */
39
- function repoCoordinates() {
40
- const url = readPackage()?.repository?.url || "";
41
- const match = url.match(/github\.com[/:]([^/]+)\/([^/.]+)/i);
42
- return { owner: match?.[1] || DEFAULT_OWNER, repo: match?.[2] || DEFAULT_REPO };
43
- }
44
-
45
- function localVersion() {
46
- return readPackage()?.version || "0.0.0";
47
- }
48
-
49
- function updateBranch() {
50
- return process.env.ORBIT_UPDATE_BRANCH || "master";
51
- }
52
-
53
- /** How this instance is running, which decides the update instructions the
54
- * client shows. Docker images carry no git tree, so they're rebuild-only. */
55
- function detectDeployMode() {
56
- if (process.env.ORBIT_DOCKER === "1" || existsSync("/.dockerenv")) return "docker";
57
- if (existsSync(join(ROOT_DIR, ".git"))) return "source";
58
- return "dist";
59
- }
60
-
61
- /** Numeric "a.b.c" comparison (ignores any prerelease suffix). Returns true
62
- * when `latest` is strictly newer than `current`. */
63
- function isNewer(latest, current) {
64
- const parse = (v) => String(v).replace(/^v/, "").split(/[.+-]/).map((n) => Number(n) || 0);
65
- const a = parse(latest);
66
- const b = parse(current);
67
- for (let i = 0; i < 3; i += 1) {
68
- if ((a[i] || 0) > (b[i] || 0)) return true;
69
- if ((a[i] || 0) < (b[i] || 0)) return false;
70
- }
71
- return false;
72
- }
73
-
74
- async function fetchLatestVersion() {
75
- const { owner, repo } = repoCoordinates();
76
- const branch = updateBranch();
77
- const headers = {
78
- Accept: "application/vnd.github.raw+json",
79
- "User-Agent": "orbit-update-check"
80
- };
81
- if (process.env.ORBIT_GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.ORBIT_GITHUB_TOKEN}`;
82
-
83
- const controller = new AbortController();
84
- const timer = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS);
85
- try {
86
- const res = await fetch(
87
- `https://api.github.com/repos/${owner}/${repo}/contents/package.json?ref=${encodeURIComponent(branch)}`,
88
- { headers, signal: controller.signal, cache: "no-store" }
89
- );
90
- if (res.status === 403 || res.status === 429) return { error: "rate_limited" };
91
- if (!res.ok) return { error: `http_${res.status}` };
92
- const pkg = JSON.parse(await res.text());
93
- if (!pkg?.version) return { error: "no_version" };
94
- return { latest: String(pkg.version) };
95
- } catch (err) {
96
- return { error: err?.name === "AbortError" ? "timeout" : "unreachable" };
97
- } finally {
98
- clearTimeout(timer);
99
- }
100
- }
101
-
102
- let cache = null; // { at: epochMs, payload }
103
-
104
- function staticPayload(extra) {
105
- const { owner, repo } = repoCoordinates();
106
- const branch = updateBranch();
107
- return {
108
- current: localVersion(),
109
- deployMode: detectDeployMode(),
110
- selfUpdate: process.env.ORBIT_SELF_UPDATE === "1",
111
- repoUrl: `https://github.com/${owner}/${repo}`,
112
- changesUrl: `https://github.com/${owner}/${repo}/commits/${branch}`,
113
- ...extra
114
- };
115
- }
116
-
117
- /** Get current/latest version info, cached for ORBIT_UPDATE_TTL minutes.
118
- * Never throws — network problems surface as `{ error, updateAvailable:false }`. */
119
- export async function getVersionInfo({ force = false } = {}) {
120
- if (process.env.ORBIT_UPDATE_CHECK === "0") {
121
- return staticPayload({ enabled: false, latest: null, updateAvailable: false, error: null, checkedAt: null });
122
- }
123
-
124
- const ttlMs = (Number(process.env.ORBIT_UPDATE_TTL) || 60) * 60 * 1000;
125
- if (!force && cache && Date.now() - cache.at < ttlMs) return cache.payload;
126
-
127
- const result = await fetchLatestVersion();
128
- const current = localVersion();
129
- const payload = staticPayload({
130
- enabled: true,
131
- latest: result.latest ?? null,
132
- updateAvailable: Boolean(result.latest) && isNewer(result.latest, current),
133
- error: result.error ?? null,
134
- checkedAt: new Date().toISOString()
135
- });
136
- cache = { at: Date.now(), payload };
137
- return payload;
138
- }
139
-
140
- /** Kick off scripts/update.sh detached and hand back immediately. Only ever
141
- * reached when ORBIT_SELF_UPDATE=1 (the router gates the route). The script
142
- * rebuilds and then signals this process so a supervisor restarts it. */
143
- export function startSelfUpdate() {
144
- const script = join(ROOT_DIR, "scripts", "update.sh");
145
- if (!existsSync(script)) return { started: false, error: "script_missing" };
146
- const child = spawn("bash", [script], {
147
- cwd: ROOT_DIR,
148
- detached: true,
149
- stdio: "ignore",
150
- env: { ...process.env, ORBIT_REPO_DIR: ROOT_DIR, ORBIT_SERVER_PID: String(process.pid), ORBIT_DATA_DIR: DATA_DIR }
151
- });
152
- child.unref();
153
- return { started: true };
154
- }
@@ -1,287 +0,0 @@
1
- import { existsSync, mkdirSync } from "node:fs";
2
- import { dirname, join, resolve } from "node:path";
3
-
4
- function rpcError(code, message) {
5
- const error = new Error(message);
6
- error.code = code;
7
- return error;
8
- }
9
-
10
- export function orbitMode(env = process.env) {
11
- const mode = String(env.ORBIT_MODE || "local").trim().toLowerCase();
12
- if (mode === "remote" || mode === "http") return "remote";
13
- if (mode === "local" || mode === "") return "local";
14
- throw rpcError(-32602, `Unsupported ORBIT_MODE: ${env.ORBIT_MODE}`);
15
- }
16
-
17
- export async function createOrbitClient(env = process.env) {
18
- return orbitMode(env) === "remote" ? createHttpOrbitClient(env) : createLocalOrbitClient(env);
19
- }
20
-
21
- export function createHttpOrbitClient(env = process.env, fetchImpl = globalThis.fetch) {
22
- const apiUrl = String(env.ORBIT_API_URL || "").trim().replace(/\/+$/, "");
23
- let defaultBoard = String(env.ORBIT_DEFAULT_BOARD || "").trim();
24
- if (!apiUrl) throw rpcError(-32602, "ORBIT_MODE=remote requires ORBIT_API_URL.");
25
- if (typeof fetchImpl !== "function") throw rpcError(-32603, "ORBIT_MODE=remote requires global fetch support.");
26
-
27
- async function request(method, path, { query = {}, body, ok = [200] } = {}) {
28
- const url = new URL(`${apiUrl}${path}`);
29
- for (const [key, value] of Object.entries(query)) {
30
- if (value !== undefined && value !== null && value !== "") url.searchParams.set(key, String(value));
31
- }
32
- const response = await fetchImpl(url, {
33
- method,
34
- headers: body === undefined ? undefined : { "Content-Type": "application/json" },
35
- body: body === undefined ? undefined : JSON.stringify(body)
36
- });
37
- let payload = null;
38
- const text = await response.text();
39
- if (text) {
40
- try { payload = JSON.parse(text); } catch { payload = { message: text }; }
41
- }
42
- if (!ok.includes(response.status)) {
43
- const message = payload?.message || payload?.error || `Orbit API ${method} ${url.pathname} failed with HTTP ${response.status}`;
44
- throw rpcError(-32000, message);
45
- }
46
- return payload ?? {};
47
- }
48
-
49
- function boardArg(args = {}) {
50
- return args.board_id || args.board_slug || args.board || defaultBoard;
51
- }
52
-
53
- function requireBoard(args = {}) {
54
- const board = boardArg(args);
55
- if (!board) throw rpcError(-32602, "Remote Orbit MCP requires board_id/board_slug/board or ORBIT_DEFAULT_BOARD.");
56
- return board;
57
- }
58
-
59
- const ticketQuery = (args = {}) => ({ board_id: args.board_id, board: args.board_slug || args.board || defaultBoard });
60
- const stripBoardSelectors = (args = {}) => {
61
- const { board_id, board_slug, board, ...rest } = args;
62
- return rest;
63
- };
64
- const settingsPatch = (args = {}) => {
65
- const patch = {};
66
- for (const field of ["name", "repo_url", "system_path", "default_branch", "agent_instructions"]) {
67
- if (args[field] !== undefined) patch[field] = args[field];
68
- }
69
- return patch;
70
- };
71
- const searchQuery = (args = {}) => ({
72
- q: args.q,
73
- limit: args.limit,
74
- board_id: args.board_id,
75
- board: args.board_slug || args.board || defaultBoard,
76
- mode: args.mode,
77
- include_full: args.include_full || args.includeFull ? "true" : undefined,
78
- fields: Array.isArray(args.fields) ? args.fields.join(",") : args.fields,
79
- max_chars_per_field: args.max_chars_per_field ?? args.maxCharsPerField
80
- });
81
- const contextQuery = (args = {}) => ({
82
- ...ticketQuery(args),
83
- depth: args.depth,
84
- max_chars_per_field: args.max_chars_per_field ?? args.maxCharsPerField,
85
- comment_limit: args.comment_limit ?? args.commentLimit,
86
- include_parent_full: args.include_parent_full || args.includeParentFull ? "true" : undefined,
87
- include_related_full: args.include_related_full || args.includeRelatedFull ? "true" : undefined
88
- });
89
-
90
- return {
91
- mode: "remote",
92
- sessionLabel: apiUrl,
93
- close() {},
94
- async boardContext(args = {}) {
95
- return request("GET", `/api/boards/${encodeURIComponent(requireBoard(args))}/context`, { query: { include_struck: args.include_struck ? "true" : undefined } });
96
- },
97
- async boardList() { return request("GET", "/api/boards"); },
98
- async boardSetActive(args = {}) { if (!args.slug) throw rpcError(-32602, "slug is required."); defaultBoard = String(args.slug); env.ORBIT_DEFAULT_BOARD = defaultBoard; return { ok: true, slug: defaultBoard, mode: "remote" }; },
99
- async claimNext(args = {}) { return request("POST", "/api/agent/claim-next", { body: { ...args, board: requireBoard(args) } }); },
100
- async getTicketContext(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/context`, { query: { ...contextQuery(args), depth: args.depth || 1 } }); },
101
- async getTicketContextFull(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/context/full`, { query: { ...contextQuery(args), depth: args.depth || 1 } }); },
102
- async getAgentDispatchPacket(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/dispatch-packet`, { query: contextQuery(args) }); },
103
- async readTicket(args = {}) {
104
- if (args.ticket_id) {
105
- return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}`, { query: ticketQuery(args) });
106
- }
107
- if (args.number !== undefined || args.title) {
108
- return request("GET", "/api/tickets/lookup", { query: { ...ticketQuery(args), number: args.number, title: args.title } });
109
- }
110
- throw rpcError(-32602, "ticket_id_or_number_or_title_required");
111
- },
112
- async readComments(args = {}) {
113
- if (args.ticket_id) {
114
- return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/comments`, { query: ticketQuery(args) });
115
- }
116
- const found = await this.readTicket(args);
117
- const ticket = found.ticket || found.results?.find((row) =>
118
- (args.number !== undefined && Number(row.number) === Number(args.number)) ||
119
- (args.title && String(row.title || "").toLowerCase() === String(args.title).toLowerCase())
120
- );
121
- if (!ticket?.id) throw rpcError(-32004, "ticket_not_found");
122
- return request("GET", `/api/tickets/${encodeURIComponent(ticket.id)}/comments`, { query: ticketQuery(args) });
123
- },
124
- async getTicketRelations(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/relations`, { query: ticketQuery(args) }); },
125
- async getTicketBlockers(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/blockers`, { query: ticketQuery(args) }); },
126
- async search(args = {}) { return request("GET", "/api/search", { query: searchQuery(args) }); },
127
- async createTicket(args = {}) { return request("POST", "/api/tickets", { body: { ...args, board: requireBoard(args) }, ok: [201] }); },
128
- async updateTicket({ ticket_id, ...patch }) { return request("PATCH", `/api/tickets/${encodeURIComponent(ticket_id)}`, { query: ticketQuery(patch), body: stripBoardSelectors(patch) }); },
129
- async uploadMarkdownArtifact({ ticket_id, ...body }) { return request("POST", `/api/tickets/${encodeURIComponent(ticket_id)}/artifacts/markdown`, { query: ticketQuery(body), body: stripBoardSelectors(body), ok: [201] }); },
130
- async addComment({ ticket_id, ...body }) { return request("POST", `/api/tickets/${encodeURIComponent(ticket_id)}/comments`, { query: ticketQuery(body), body: stripBoardSelectors(body), ok: [201] }); },
131
- async createReviewVerdict({ ticket_id, ...body }) { return request("POST", `/api/tickets/${encodeURIComponent(ticket_id)}/review-verdicts`, { query: ticketQuery(body), body: stripBoardSelectors(body), ok: [201] }); },
132
- async listReviewVerdicts(args = {}) { return request("GET", `/api/tickets/${encodeURIComponent(args.ticket_id)}/review-verdicts`, { query: { ...ticketQuery(args), limit: args.limit } }); },
133
- async getReviewVerdict(args = {}) { return request("GET", `/api/review-verdicts/${encodeURIComponent(args.review_id)}`, { query: ticketQuery(args) }); },
134
- async addBoardEntry(args = {}) { return request("POST", `/api/boards/${encodeURIComponent(requireBoard(args))}/entries`, { body: stripBoardSelectors(args), ok: [201] }); },
135
- async checkpoint(args = {}) { const fallback = boardArg(args); return request("POST", "/api/agent/checkpoint", { body: fallback ? { ...args, board: args.board || args.board_slug || fallback } : args }); },
136
- async complete(args = {}) { const fallback = boardArg(args); return request("POST", "/api/agent/complete", { body: fallback ? { ...args, board: args.board || args.board_slug || fallback } : args }); },
137
- async archiveTicket(args = {}) { return request("POST", `/api/tickets/${encodeURIComponent(args.ticket_id)}/archive`, { query: ticketQuery(args) }); },
138
- async restoreTicket(args = {}) { return request("POST", `/api/tickets/${encodeURIComponent(args.ticket_id)}/restore`, { query: ticketQuery(args) }); },
139
- async deleteTicket(args = {}) { return request("DELETE", `/api/tickets/${encodeURIComponent(args.ticket_id)}`, { query: ticketQuery(args) }); },
140
- async listArchive(args = {}) { return request("GET", `/api/boards/${encodeURIComponent(requireBoard(args))}/archive`); },
141
- async exportBoard(args = {}) {
142
- return request("GET", `/api/boards/${encodeURIComponent(requireBoard(args))}/export`, {
143
- query: { include_attachments: args.include_attachments || args.include_images ? "true" : undefined }
144
- });
145
- },
146
- async updateSettings(args = {}) { return request("PATCH", `/api/boards/${encodeURIComponent(requireBoard(args))}`, { body: settingsPatch(args) }); }
147
- };
148
- }
149
-
150
- export async function createLocalOrbitClient(env = process.env) {
151
- const board = await import("../core/board.js");
152
- const dbMod = await import("../core/db.js");
153
- const registry = await import("../core/registry.js");
154
- const backups = await import("../core/backups.js");
155
- const seed = await import("../core/seed.js");
156
- const util = await import("../core/util.js");
157
-
158
- const provision = await import("../core/provision-repo-board.js");
159
- let sessionBoardRow = null;
160
- const setSessionBoard = (row) => { sessionBoardRow = row; if (row) registry.touchBoardActive(row.id); };
161
- const getSessionBoard = () => sessionBoardRow;
162
- const walkUp = (startDir, predicate) => { let dir = startDir; while (true) { if (predicate(dir)) return dir; const parent = dirname(dir); if (parent === dir) return null; dir = parent; } };
163
-
164
- const start = util.normalizePath(env.PROJECT_ROOT ? resolve(env.PROJECT_ROOT) : process.cwd());
165
- const displayStart = resolve(env.PROJECT_ROOT ? env.PROJECT_ROOT : process.cwd());
166
-
167
- // Find repo root (walk up for .git, fall back to start).
168
- const gitRoot = walkUp(start, (dir) => existsSync(join(dir, ".git")));
169
- const repoRoot = util.normalizePath(gitRoot || start);
170
-
171
- // 1. Registry-first lookup: walk upward from start so we find boards regardless
172
- // of whether they were registered at the git root, at PROJECT_ROOT, or at any
173
- // ancestor (handles boards originally created from a subdirectory).
174
- let foundByRegistry = false;
175
- {
176
- let dir = start;
177
- while (true) {
178
- const row = registry.getBoardByRepoPath(dir);
179
- if (row && existsSync(row.db_path)) { setSessionBoard(row); foundByRegistry = true; break; }
180
- const parent = util.normalizePath(dirname(dir));
181
- if (parent === dir) break; // filesystem root
182
- dir = parent;
183
- }
184
- }
185
- if (!foundByRegistry) {
186
- // 2. Legacy fallback: walk up for an existing in-repo .orbit/board.db.
187
- const boardRoot = walkUp(start, (dir) => existsSync(join(dir, ".orbit", "board.db")));
188
- if (boardRoot) {
189
- const root = util.normalizePath(boardRoot);
190
- const existing = registry.getBoardByRepoPath(root);
191
- if (existing) {
192
- setSessionBoard(existing);
193
- } else {
194
- const dbPath = join(boardRoot, ".orbit", "board.db");
195
- const db = dbMod.openConnection(dbPath);
196
- dbMod.createBoardSchema(db);
197
- const seeded = seed.seedIfEmpty(db, root);
198
- if (seeded) {
199
- const t = util.now();
200
- registry.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 });
201
- }
202
- const fresh = registry.getBoardByRepoPath(root);
203
- if (fresh) { registry.syncRegistryFromBoardDb(fresh, db); setSessionBoard(fresh); }
204
- }
205
- } else {
206
- // 3. No board found — auto-create one (goes to central DATA_DIR/boards/).
207
- provision.provisionRepoBoard(repoRoot, { enableAi: true });
208
- const fresh = registry.getBoardByRepoPath(repoRoot);
209
- if (fresh) setSessionBoard(fresh);
210
- }
211
- }
212
-
213
- const ctxFor = (row, actor) => {
214
- if (!row) throw rpcError(-32602, "No board in session; pass board_id or board_slug, or call board_set_active first.");
215
- return { actor, board: row, db: registry.openBoardDb(row) };
216
- };
217
- const backup = (ctx, result) => { backups.scheduleAutomaticBoardBackup(ctx.board, ctx.db); return result; };
218
- const explicit = (args = {}) => {
219
- if (args.board_id) {
220
- const row = registry.getBoardByRegistryId(args.board_id);
221
- if (!row) throw rpcError(-32004, `Board id not found: ${args.board_id}`);
222
- return row;
223
- }
224
- if (args.board_slug || args.board) {
225
- const slug = args.board_slug || args.board;
226
- const row = registry.getBoardBySlug(slug);
227
- if (!row) throw rpcError(-32004, `Board slug not found: ${slug}`);
228
- return row;
229
- }
230
- return null;
231
- };
232
- const rowOrSession = (args = {}) => explicit(args) || getSessionBoard();
233
- const stripBoardSelectors = (args = {}) => {
234
- const { board_id, board_slug, board: _board, ...rest } = args;
235
- return rest;
236
- };
237
- const settingsPatch = (args = {}) => {
238
- const patch = {};
239
- for (const field of ["name", "repo_url", "system_path", "default_branch", "agent_instructions"]) {
240
- if (args[field] !== undefined) patch[field] = args[field];
241
- }
242
- return patch;
243
- };
244
- const actor = () => board.localAgentActor();
245
-
246
- return {
247
- mode: "local",
248
- sessionLabel: () => {
249
- const b = getSessionBoard();
250
- return b ? `${displayStart} target: ${b.repo_path} db: ${b.db_path}` : "(none)";
251
- },
252
- close: () => dbMod.closeAllConnections(),
253
- boardContext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getBoardContext(ctx.board.id, ctx, { includeStruck: Boolean(args.include_struck) }); },
254
- boardList: () => ({ boards: registry.listBoards().map((row) => ({ id: row.id, slug: row.slug, name: row.name, repo_path: row.repo_path })) }),
255
- boardSetActive: (args = {}) => { const slug = String(args.slug || "").trim(); if (!slug) throw rpcError(-32602, "slug is required."); const row = registry.getBoardBySlug(slug); if (!row) throw rpcError(-32004, `Board slug not found: ${slug}`); setSessionBoard(row); return { ok: true, board_id: row.id, slug: row.slug, name: row.name, db_path: row.db_path }; },
256
- claimNext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.claimNext(args, ctx)); },
257
- getTicketContext: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getContextPack(args.ticket_id, ctx, Number(args.depth || 1), args); },
258
- getTicketContextFull: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getContextPackFull(args.ticket_id, ctx, Number(args.depth || 1), args); },
259
- getAgentDispatchPacket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getAgentDispatchPacket(args.ticket_id, ctx, args); },
260
- readTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.readTicket(args, ctx); },
261
- readComments: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.readComments(args, ctx); },
262
- getTicketRelations: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getTicketRelations(args.ticket_id, ctx); },
263
- getTicketBlockers: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getTicketBlockers(args.ticket_id, ctx); },
264
- search: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.searchTickets(args, ctx); },
265
- createTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); const body = stripBoardSelectors(args); body.board_id = ctx.board.id; return backup(ctx, board.createTicket(body, ctx)); },
266
- updateTicket: ({ ticket_id, ...patch }) => { const ctx = ctxFor(rowOrSession(patch), actor()); return backup(ctx, board.updateTicket(ticket_id, stripBoardSelectors(patch), ctx)); },
267
- uploadMarkdownArtifact: ({ ticket_id, ...body }) => { const ctx = ctxFor(rowOrSession(body), actor()); return board.storeTicketMarkdownArtifact(ticket_id, stripBoardSelectors(body), ctx); },
268
- addComment: ({ ticket_id, ...body }) => { const ctx = ctxFor(rowOrSession(body), actor()); return backup(ctx, board.createComment(ticket_id, stripBoardSelectors(body), ctx)); },
269
- createReviewVerdict: ({ ticket_id, ...body }) => { const ctx = ctxFor(rowOrSession(body), actor()); return backup(ctx, board.createReviewVerdict(ticket_id, stripBoardSelectors(body), ctx)); },
270
- listReviewVerdicts: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return { review_verdicts: board.listReviewVerdicts(args.ticket_id, ctx, args) }; },
271
- getReviewVerdict: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return board.getReviewVerdict(args.review_id, ctx); },
272
- addBoardEntry: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.createBoardEntry(ctx.board.id, stripBoardSelectors(args), ctx)); },
273
- checkpoint: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.checkpointTicket(stripBoardSelectors(args), ctx)); },
274
- complete: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.completeTicket(stripBoardSelectors(args), ctx)); },
275
- archiveTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.archiveTicket(args.ticket_id, ctx)); },
276
- restoreTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.restoreTicket(args.ticket_id, ctx)); },
277
- deleteTicket: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.deleteTicket(args.ticket_id, ctx)); },
278
- listArchive: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return { tickets: board.archivedTicketsForBoard(ctx.db, ctx.board.id) }; },
279
- exportBoard: (args = {}) => {
280
- const ctx = ctxFor(rowOrSession(args), actor());
281
- return board.exportBoard(ctx.board.id, ctx, {
282
- includeAttachments: Boolean(args.include_attachments || args.include_images)
283
- });
284
- },
285
- updateSettings: (args = {}) => { const ctx = ctxFor(rowOrSession(args), actor()); return backup(ctx, board.updateBoard(ctx.board.id, settingsPatch(args), ctx)); }
286
- };
287
- }
package/src/server.js DELETED
@@ -1,115 +0,0 @@
1
- import { createServer } from "node:http";
2
- import { networkInterfaces } from "node:os";
3
- import {
4
- boardRuntime,
5
- handleApi,
6
- serveStatic,
7
- sendEmpty,
8
- sendJson,
9
- startSSEStream,
10
- startupSummary
11
- } from "./core/board.js";
12
- import { closeAllConnections } from "./core/db.js";
13
-
14
- const server = createServer(async (req, res) => {
15
- try {
16
- const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
17
-
18
- if (req.method === "OPTIONS") {
19
- sendEmpty(res, 204);
20
- return;
21
- }
22
-
23
- if (url.pathname === "/api/events/stream" && req.method === "GET") {
24
- startSSEStream(req, res, url);
25
- return;
26
- }
27
-
28
- if (url.pathname.startsWith("/api/")) {
29
- await handleApi(req, res, url);
30
- return;
31
- }
32
-
33
- await serveStatic(req, res, url.pathname);
34
- } catch (error) {
35
- console.error(error);
36
- if (error.status) {
37
- sendJson(res, error.status, { error: error.code || "request_error", message: error.message });
38
- return;
39
- }
40
- sendJson(res, 500, { error: "internal_error", message: error.message });
41
- }
42
- });
43
-
44
- const requestedPort = boardRuntime.port;
45
- const requestedHost = process.env.HOST || process.env.ORBIT_HOST || "";
46
- const hasExplicitPort = Boolean(process.env.PORT);
47
- const maxAutoPort = requestedPort + 99;
48
- let currentPort = requestedPort;
49
-
50
- function listen(port) {
51
- if (requestedHost) {
52
- server.listen(port, requestedHost);
53
- return;
54
- }
55
- server.listen(port);
56
- }
57
-
58
- function displayHost(host) {
59
- if (!host || host === "::" || host === "0.0.0.0") return "localhost";
60
- return host;
61
- }
62
-
63
- function networkUrls(port) {
64
- return Object.values(networkInterfaces())
65
- .flat()
66
- .filter((item) => item && item.family === "IPv4" && !item.internal)
67
- .map((item) => `http://${item.address}:${port}`);
68
- }
69
-
70
- server.on("error", (error) => {
71
- if (!hasExplicitPort && error?.code === "EADDRINUSE" && currentPort < maxAutoPort) {
72
- currentPort += 1;
73
- console.warn(`[orbit] Port ${currentPort - 1} is in use; trying ${currentPort}.`);
74
- listen(currentPort);
75
- return;
76
- }
77
-
78
- console.error(error);
79
- closeAllConnections();
80
- process.exit(1);
81
- });
82
-
83
- listen(currentPort);
84
-
85
- server.on("listening", () => {
86
- const address = server.address();
87
- const port = typeof address === "object" && address ? address.port : currentPort;
88
- const host = typeof address === "object" && address ? displayHost(address.address) : displayHost(requestedHost);
89
- const summary = startupSummary(port, host);
90
- console.log(`Starscape Orbit listening on ${summary.url}`);
91
- const urls = networkUrls(port).filter((url) => url !== summary.url);
92
- if (urls.length) console.log(`Network: ${urls.join(", ")}`);
93
- console.log(`Registry: ${summary.registryPath}`);
94
- console.log(`Boards (${summary.boardCount}):`);
95
- for (const path of summary.boardPaths) console.log(` - ${path}`);
96
- });
97
-
98
- let shuttingDown = false;
99
-
100
- function shutdown() {
101
- if (shuttingDown) return;
102
- shuttingDown = true;
103
- server.close(() => {
104
- closeAllConnections();
105
- process.exit(0);
106
- });
107
- setTimeout(() => {
108
- closeAllConnections();
109
- process.exit(0);
110
- }, 1500).unref();
111
- }
112
-
113
- process.once("SIGINT", shutdown);
114
- process.once("SIGTERM", shutdown);
115
- process.once("exit", () => closeAllConnections());