@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,238 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
3
- import { dirname, extname, join, normalize, sep } from "node:path";
4
- import { boardById, ticketById } from "./queries.js";
5
- import { requireBoardAccess, requirePermission } from "./auth.js";
6
- import { tx } from "./db.js";
7
- import { recordEvent } from "./events.js";
8
- import { bumpTicketUpdatedAt } from "./tickets.js";
9
- import { httpError, id, now } from "./util.js";
10
-
11
- export const MAX_IMAGE_ATTACHMENT_BYTES = 10 * 1024 * 1024;
12
-
13
- const IMAGE_EXTENSIONS = new Map([
14
- ["image/png", ".png"],
15
- ["image/jpeg", ".jpg"],
16
- ["image/gif", ".gif"],
17
- ["image/webp", ".webp"],
18
- ["image/svg+xml", ".svg"],
19
- ["image/bmp", ".bmp"],
20
- ["image/avif", ".avif"]
21
- ]);
22
-
23
- export function attachmentStorageRoot(boardRow) {
24
- return join(dirname(boardRow.db_path), "attachments");
25
- }
26
-
27
- function ticketAttachmentDir(boardRow, ticketId) {
28
- return join(attachmentStorageRoot(boardRow), "tickets", ticketId);
29
- }
30
-
31
- function safeOriginalName(value) {
32
- const name = String(value || "image").replace(/[\0\r\n]/g, "").trim();
33
- return name.slice(0, 240) || "image";
34
- }
35
-
36
- function normalizeImageMime(value) {
37
- const mime = String(value || "").split(";")[0].trim().toLowerCase();
38
- if (!IMAGE_EXTENSIONS.has(mime)) throw httpError(415, "unsupported_attachment_type");
39
- return mime;
40
- }
41
-
42
- function extensionFor(mime, originalName = "") {
43
- const originalExt = extname(originalName).toLowerCase();
44
- if ([...IMAGE_EXTENSIONS.values()].includes(originalExt)) return originalExt;
45
- return IMAGE_EXTENSIONS.get(mime) || ".img";
46
- }
47
-
48
- function relativeStoredPath(ticketId, attachmentId, mime, originalName) {
49
- return normalize(join("tickets", ticketId, `${attachmentId}${extensionFor(mime, originalName)}`)).replace(/\\/g, "/");
50
- }
51
-
52
- function resolveStoredPath(boardRow, storedPath) {
53
- const root = attachmentStorageRoot(boardRow);
54
- const abs = join(root, storedPath);
55
- const normalizedRoot = normalize(root + sep);
56
- const normalizedAbs = normalize(abs);
57
- if (!normalizedAbs.startsWith(normalizedRoot)) throw httpError(400, "invalid_attachment_path");
58
- return normalizedAbs;
59
- }
60
-
61
- function assertTicket(ctx, ticketId, write = false) {
62
- const { db, board, actor } = ctx;
63
- const ticket = ticketById(db, ticketId);
64
- if (!ticket || ticket.board_id !== board.id) throw httpError(404, "ticket_not_found");
65
- requireBoardAccess(actor, boardById(db, ticket.board_id));
66
- if (write) requirePermission(actor, "write");
67
- return ticket;
68
- }
69
-
70
- function rowToAttachment(boardRow, row) {
71
- const path = resolveStoredPath(boardRow, row.stored_path);
72
- let missing = true;
73
- let size = row.size_bytes;
74
- try {
75
- const stat = statSync(path);
76
- missing = !stat.isFile();
77
- if (!missing) size = stat.size;
78
- } catch {
79
- missing = true;
80
- }
81
- return {
82
- id: row.id,
83
- ticket_id: row.ticket_id,
84
- board_id: row.board_id,
85
- original_name: row.original_name,
86
- mime_type: row.mime_type,
87
- size_bytes: row.size_bytes,
88
- sha256: row.sha256,
89
- stored_path: row.stored_path,
90
- created_by: row.created_by,
91
- created_at: row.created_at,
92
- missing,
93
- content_url: missing ? null : `/api/tickets/${encodeURIComponent(row.ticket_id)}/attachments/${encodeURIComponent(row.id)}/content`,
94
- filesystem_size_bytes: missing ? null : size
95
- };
96
- }
97
-
98
- export function listTicketAttachments(ticketId, ctx) {
99
- assertTicket(ctx, ticketId);
100
- const rows = ctx.db
101
- .prepare("SELECT * FROM ticket_attachments WHERE ticket_id = ? ORDER BY created_at ASC")
102
- .all(ticketId);
103
- return { attachments: rows.map((row) => rowToAttachment(ctx.board, row)) };
104
- }
105
-
106
- export function getTicketAttachmentContent(ticketId, attachmentId, ctx) {
107
- assertTicket(ctx, ticketId);
108
- const row = ctx.db
109
- .prepare("SELECT * FROM ticket_attachments WHERE id = ? AND ticket_id = ?")
110
- .get(attachmentId, ticketId);
111
- if (!row) throw httpError(404, "attachment_not_found");
112
- const filePath = resolveStoredPath(ctx.board, row.stored_path);
113
- if (!existsSync(filePath)) throw httpError(404, "attachment_file_missing");
114
- return { row, filePath };
115
- }
116
-
117
- export function createTicketAttachment(ticketId, input, ctx) {
118
- const ticket = assertTicket(ctx, ticketId, true);
119
- const mime = normalizeImageMime(input.mime_type);
120
- const bytes = Buffer.isBuffer(input.bytes) ? input.bytes : Buffer.from(input.bytes || []);
121
- if (bytes.length === 0) throw httpError(400, "empty_attachment");
122
- if (bytes.length > MAX_IMAGE_ATTACHMENT_BYTES) throw httpError(413, "attachment_too_large");
123
- const originalName = safeOriginalName(input.original_name);
124
- const attachmentId = id();
125
- const storedPath = relativeStoredPath(ticketId, attachmentId, mime, originalName);
126
- const filePath = resolveStoredPath(ctx.board, storedPath);
127
- const hash = createHash("sha256").update(bytes).digest("hex");
128
- const time = now();
129
-
130
- mkdirSync(ticketAttachmentDir(ctx.board, ticketId), { recursive: true });
131
- writeFileSync(filePath, bytes, { flag: "wx" });
132
-
133
- try {
134
- return tx(ctx.db, () => {
135
- ctx.db.prepare(
136
- `INSERT INTO ticket_attachments
137
- (id, ticket_id, board_id, original_name, stored_path, mime_type, size_bytes, sha256, created_by, created_at)
138
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
139
- ).run(attachmentId, ticketId, ticket.board_id, originalName, storedPath, mime, bytes.length, hash, ctx.actor.name, time);
140
- ctx.db.prepare("UPDATE tickets SET updated_at = ? WHERE id = ?").run(time, ticketId);
141
- recordEvent(ctx.db, ticket.board_id, "attachment_created", ticketId, ctx.actor.name, {
142
- attachment_id: attachmentId,
143
- original_name: originalName,
144
- mime_type: mime,
145
- size_bytes: bytes.length,
146
- sha256: hash
147
- });
148
- bumpTicketUpdatedAt(ctx.db, ticket.parent_ticket_id, time);
149
- return rowToAttachment(ctx.board, ctx.db.prepare("SELECT * FROM ticket_attachments WHERE id = ?").get(attachmentId));
150
- });
151
- } catch (error) {
152
- try { unlinkSync(filePath); } catch {}
153
- throw error;
154
- }
155
- }
156
-
157
- export function deleteTicketAttachment(ticketId, attachmentId, ctx) {
158
- const ticket = assertTicket(ctx, ticketId, true);
159
- const row = ctx.db
160
- .prepare("SELECT * FROM ticket_attachments WHERE id = ? AND ticket_id = ?")
161
- .get(attachmentId, ticketId);
162
- if (!row) throw httpError(404, "attachment_not_found");
163
- const filePath = resolveStoredPath(ctx.board, row.stored_path);
164
- return tx(ctx.db, () => {
165
- ctx.db.prepare("DELETE FROM ticket_attachments WHERE id = ?").run(attachmentId);
166
- const time = now();
167
- ctx.db.prepare("UPDATE tickets SET updated_at = ? WHERE id = ?").run(time, ticketId);
168
- recordEvent(ctx.db, ticket.board_id, "attachment_deleted", ticketId, ctx.actor.name, {
169
- attachment_id: attachmentId,
170
- original_name: row.original_name
171
- });
172
- bumpTicketUpdatedAt(ctx.db, ticket.parent_ticket_id, time);
173
- try { unlinkSync(filePath); } catch {}
174
- return { ok: true, deleted_id: attachmentId };
175
- });
176
- }
177
-
178
- export function attachmentRowsForBoard(db, boardId) {
179
- return db
180
- .prepare("SELECT * FROM ticket_attachments WHERE board_id = ? ORDER BY created_at ASC")
181
- .all(boardId);
182
- }
183
-
184
- export function serializeAttachmentForExport(boardRow, row, includeBytes = false) {
185
- const attachment = rowToAttachment(boardRow, row);
186
- if (!includeBytes || attachment.missing) {
187
- return { ...attachment, data_base64: null, included: false };
188
- }
189
- const filePath = resolveStoredPath(boardRow, row.stored_path);
190
- const data = readFileSync(filePath);
191
- return {
192
- ...attachment,
193
- data_base64: data.toString("base64"),
194
- included: true,
195
- missing: false
196
- };
197
- }
198
-
199
- export function importAttachmentRows(snapshot, ctx, targetBoardId) {
200
- const rows = Array.isArray(snapshot.attachments) ? snapshot.attachments : [];
201
- for (const attachment of rows) {
202
- const attachmentId = attachment.id || id();
203
- const ticketId = attachment.ticket_id;
204
- if (!ticketId || !ctx.db.prepare("SELECT 1 FROM tickets WHERE id = ?").get(ticketId)) continue;
205
- const mime = normalizeImageMime(attachment.mime_type || "image/png");
206
- const originalName = safeOriginalName(attachment.original_name);
207
- const storedPath = relativeStoredPath(ticketId, attachmentId, mime, originalName);
208
- let size = Number(attachment.size_bytes || 0);
209
- let sha = String(attachment.sha256 || "");
210
-
211
- if (attachment.data_base64) {
212
- const bytes = Buffer.from(String(attachment.data_base64), "base64");
213
- if (bytes.length > MAX_IMAGE_ATTACHMENT_BYTES) throw httpError(413, "attachment_too_large");
214
- size = bytes.length;
215
- sha = createHash("sha256").update(bytes).digest("hex");
216
- const filePath = resolveStoredPath(ctx.board, storedPath);
217
- mkdirSync(dirname(filePath), { recursive: true });
218
- writeFileSync(filePath, bytes);
219
- }
220
-
221
- ctx.db.prepare(
222
- `INSERT OR IGNORE INTO ticket_attachments
223
- (id, ticket_id, board_id, original_name, stored_path, mime_type, size_bytes, sha256, created_by, created_at)
224
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
225
- ).run(
226
- attachmentId,
227
- ticketId,
228
- targetBoardId,
229
- originalName,
230
- storedPath,
231
- mime,
232
- size,
233
- sha,
234
- attachment.created_by || "import",
235
- attachment.created_at || now()
236
- );
237
- }
238
- }
package/src/core/auth.js DELETED
@@ -1,85 +0,0 @@
1
- export function localOwnerActor() {
2
- return {
3
- id: "local",
4
- name: "local",
5
- type: "human",
6
- role: "owner",
7
- permissions: ["owner", "read", "write", "claim"],
8
- boards: ["*"]
9
- };
10
- }
11
-
12
- export function localAgentActor() {
13
- return {
14
- id: "local-agent",
15
- name: "agent",
16
- type: "agent",
17
- role: "owner",
18
- permissions: ["owner", "read", "write", "claim"],
19
- boards: ["*"]
20
- };
21
- }
22
-
23
- function headerValue(req, name) {
24
- const value = req?.headers?.[name.toLowerCase()];
25
- if (Array.isArray(value)) return value[0] || "";
26
- return String(value || "");
27
- }
28
-
29
- function cleanActorField(value, fallback) {
30
- const clean = String(value || "")
31
- .trim()
32
- .replace(/\s+/g, " ")
33
- .slice(0, 80);
34
- return clean || fallback;
35
- }
36
-
37
- function actorIdFromName(name) {
38
- return String(name || "agent")
39
- .toLowerCase()
40
- .replace(/[^a-z0-9]+/g, "-")
41
- .replace(/^-+|-+$/g, "")
42
- .slice(0, 80) || "agent";
43
- }
44
-
45
- export function actorFromHttpRequest(req) {
46
- const actorType = headerValue(req, "x-orbit-actor-type").trim().toLowerCase();
47
- const agentName = headerValue(req, "x-orbit-agent-name");
48
- if (actorType !== "agent" && !agentName.trim()) return localOwnerActor();
49
-
50
- const name = cleanActorField(headerValue(req, "x-orbit-actor-name") || agentName, "agent");
51
- const id = cleanActorField(headerValue(req, "x-orbit-actor-id"), actorIdFromName(name));
52
- return {
53
- ...localAgentActor(),
54
- id,
55
- name
56
- };
57
- }
58
-
59
- export function authenticate() {
60
- return localOwnerActor();
61
- }
62
-
63
- export function actorFromToken() {
64
- return localOwnerActor();
65
- }
66
-
67
- export function canAccessBoard() {
68
- return true;
69
- }
70
-
71
- export function requireBoardAccess() {}
72
-
73
- export function requireOwner() {}
74
-
75
- export function requirePermission() {}
76
-
77
- export function publicActor(actor) {
78
- return {
79
- id: actor.id,
80
- name: actor.name,
81
- type: actor.type,
82
- role: actor.role,
83
- permissions: actor.permissions
84
- };
85
- }
@@ -1,142 +0,0 @@
1
- import { existsSync, mkdirSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { closeConnection, getRegistry, hasConnection, openConnection } from "./db.js";
4
- import { BACKUP_DIR, REGISTRY_DB_PATH } from "./paths.js";
5
- import { now } from "./util.js";
6
-
7
- const DEFAULT_RETENTION = 30;
8
- const DEFAULT_AUTO_DELAY_MS = 120_000;
9
- const scheduledBoardBackups = new Map();
10
-
11
- function backupsEnabled() {
12
- return !["0", "false", "off"].includes(String(process.env.ORBIT_AUTO_BACKUPS || "1").toLowerCase());
13
- }
14
-
15
- function retentionLimit() {
16
- const parsed = Number(process.env.ORBIT_BACKUP_RETENTION || DEFAULT_RETENTION);
17
- if (!Number.isFinite(parsed) || parsed < 1) return DEFAULT_RETENTION;
18
- return Math.floor(parsed);
19
- }
20
-
21
- function cleanSegment(value) {
22
- return String(value || "unknown")
23
- .trim()
24
- .replace(/[^a-zA-Z0-9._-]+/g, "-")
25
- .replace(/^-+|-+$/g, "")
26
- .slice(0, 80) || "unknown";
27
- }
28
-
29
- function timestampForPath() {
30
- return now().replace(/[:.]/g, "-");
31
- }
32
-
33
- function sqlLiteral(value) {
34
- return `'${String(value).replace(/'/g, "''")}'`;
35
- }
36
-
37
- function pruneBackups(dir, suffix) {
38
- if (!existsSync(dir)) return;
39
- const limit = retentionLimit();
40
- const files = readdirSync(dir)
41
- .filter((name) => name.endsWith(suffix))
42
- .sort()
43
- .reverse();
44
- for (const stale of files.slice(limit)) {
45
- unlinkSync(join(dir, stale));
46
- const manifest = join(dir, `${stale}.json`);
47
- if (existsSync(manifest)) unlinkSync(manifest);
48
- }
49
- }
50
-
51
- function latestBackupStartsWith(dir, prefix, suffix) {
52
- if (!existsSync(dir)) return false;
53
- return readdirSync(dir).some((name) => name.startsWith(prefix) && name.endsWith(suffix));
54
- }
55
-
56
- function writeBackup(db, targetPath) {
57
- mkdirSync(dirname(targetPath), { recursive: true });
58
- db.exec(`VACUUM INTO ${sqlLiteral(targetPath)};`);
59
- }
60
-
61
- function writeManifest(targetPath, payload) {
62
- writeFileSync(`${targetPath}.json`, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
63
- }
64
-
65
- function automaticDelayMs() {
66
- const parsed = Number(process.env.ORBIT_AUTO_BACKUP_DELAY_MS || DEFAULT_AUTO_DELAY_MS);
67
- if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_AUTO_DELAY_MS;
68
- return Math.floor(parsed);
69
- }
70
-
71
- export function backupBoardDatabase(boardRow, db, reason = "manual", options = {}) {
72
- if (!backupsEnabled()) return null;
73
- const boardId = cleanSegment(boardRow.id);
74
- const slug = cleanSegment(boardRow.slug);
75
- const dir = join(BACKUP_DIR, "boards", boardId);
76
- const dayPrefix = `${now().slice(0, 10)}T`;
77
- if (options.oncePerDay && latestBackupStartsWith(dir, dayPrefix, ".board.db")) return null;
78
-
79
- const filename = `${timestampForPath()}-${cleanSegment(reason)}-${slug}.board.db`;
80
- const targetPath = join(dir, filename);
81
- writeBackup(db, targetPath);
82
- writeManifest(targetPath, {
83
- kind: "board",
84
- reason,
85
- created_at: now(),
86
- board_id: boardRow.id,
87
- slug: boardRow.slug,
88
- name: boardRow.name,
89
- repo_path: boardRow.repo_path,
90
- db_path: boardRow.db_path
91
- });
92
- pruneBackups(dir, ".board.db");
93
- return { path: targetPath };
94
- }
95
-
96
- export function scheduleAutomaticBoardBackup(boardRow, db) {
97
- if (!backupsEnabled() || !boardRow?.id || !db) return;
98
- const existing = scheduledBoardBackups.get(boardRow.id);
99
- if (existing) clearTimeout(existing);
100
-
101
- const timer = setTimeout(() => {
102
- scheduledBoardBackups.delete(boardRow.id);
103
- const hadBoardConnection = hasConnection(boardRow.db_path);
104
- const hadRegistryConnection = hasConnection(REGISTRY_DB_PATH);
105
- const backupDb = openConnection(boardRow.db_path);
106
- try {
107
- backupBoardDatabase(boardRow, backupDb, "auto-write");
108
- backupRegistry("auto-write");
109
- } catch (error) {
110
- console.warn(`[orbit] Automatic write backup failed for "${boardRow.slug}": ${error.message}`);
111
- } finally {
112
- if (!hadBoardConnection) closeConnection(boardRow.db_path);
113
- if (!hadRegistryConnection) closeConnection(REGISTRY_DB_PATH);
114
- }
115
- }, automaticDelayMs());
116
- timer.unref?.();
117
- scheduledBoardBackups.set(boardRow.id, timer);
118
- }
119
-
120
- export function cancelAutomaticBoardBackup(boardId) {
121
- const existing = scheduledBoardBackups.get(boardId);
122
- if (!existing) return;
123
- clearTimeout(existing);
124
- scheduledBoardBackups.delete(boardId);
125
- }
126
-
127
- export function backupRegistry(reason = "manual", options = {}) {
128
- if (!backupsEnabled()) return null;
129
- const dir = join(BACKUP_DIR, "registry");
130
- const dayPrefix = `${now().slice(0, 10)}T`;
131
- if (options.oncePerDay && latestBackupStartsWith(dir, dayPrefix, ".registry.db")) return null;
132
-
133
- const targetPath = join(dir, `${timestampForPath()}-${cleanSegment(reason)}.registry.db`);
134
- writeBackup(getRegistry(), targetPath);
135
- writeManifest(targetPath, {
136
- kind: "registry",
137
- reason,
138
- created_at: now()
139
- });
140
- pruneBackups(dir, ".registry.db");
141
- return { path: targetPath };
142
- }
package/src/core/board.js DELETED
@@ -1,148 +0,0 @@
1
- // Barrel module + startup orchestration.
2
- //
3
- // Startup contract:
4
- // 1. Ensure the registry DB and its schema exist.
5
- // 2. If the registry has zero boards, create a first board under
6
- // PROJECT_ROOT and seed it. This is the open-source one-command-startup
7
- // ergonomic — drop into a repo, run the server, get a board.
8
- // 3. Iterate every registry board, ensuring the per-board schema exists
9
- // and reindexing FTS for that board's DB. No "active board" singleton:
10
- // each iteration opens its own connection.
11
- // 4. When launched through `orbit run`, refresh Orbit-managed
12
- // SKILL-ORBIT.md copies for registered repo paths.
13
- //
14
- // Beyond startup this module just re-exports per-domain functions for
15
- // `src/server.js` and `src/mcp-server.js` to consume.
16
-
17
- import { existsSync, mkdirSync } from "node:fs";
18
- import { resolve } from "node:path";
19
- import "./paths.js";
20
- import { syncSkillOrbitMd } from "./agents-md.js";
21
- import { createBoardSchema, createRegistrySchema } from "./db.js";
22
- import { reindexAllTickets } from "./tickets.js";
23
- import { deleteBoard, listBoards, openBoardDb, syncRegistryFromBoardDb } from "./registry.js";
24
- import { provisionRepoBoard } from "./provision-repo-board.js";
25
- import { migrateInRepoBoards } from "./migrate.js";
26
- import { id, slugify } from "./util.js";
27
- import {
28
- DATA_DIR,
29
- EXPORT_DIR,
30
- BACKUP_DIR,
31
- PORT,
32
- PROJECT_ROOT,
33
- PUBLIC_DIR,
34
- REGISTRY_DB_PATH,
35
- ROOT_DIR
36
- } from "./paths.js";
37
-
38
- function ensureWorkspace() {
39
- mkdirSync(DATA_DIR, { recursive: true });
40
- createRegistrySchema();
41
- }
42
-
43
- function ensureFirstBoard() {
44
- if (listBoards().length > 0) return;
45
- // Bootstrap a first board under PROJECT_ROOT so the server is useful out of
46
- // the box. Shared logic with `orbit init` (provision-repo-board.js).
47
- provisionRepoBoard(PROJECT_ROOT);
48
- }
49
-
50
- function ensureSchemaForEveryBoard() {
51
- for (const row of listBoards()) {
52
- if (!existsSync(row.db_path)) {
53
- console.warn(
54
- `[orbit] Removing stale registry row for "${row.slug}": database file missing (${row.db_path}).`
55
- );
56
- deleteBoard(row.id);
57
- continue;
58
- }
59
- const db = openBoardDb(row);
60
- createBoardSchema(db);
61
- reindexAllTickets(db);
62
- syncRegistryFromBoardDb(row, db);
63
- }
64
- }
65
-
66
- function syncManagedSkillOrbitFiles() {
67
- const skillSrc = resolve(ROOT_DIR, "SKILL-ORBIT.md");
68
- for (const row of listBoards()) {
69
- if (!row.repo_path) continue;
70
- // Respect boards created without "Generate AI helper files" — don't
71
- // re-push SKILL-ORBIT.md into a repo the user opted out of.
72
- if (row.manage_helper_files === 0) continue;
73
- try {
74
- const result = syncSkillOrbitMd(row.repo_path, skillSrc);
75
- if (result.status === "missing_source") {
76
- console.warn(`[orbit] Missing managed SKILL-ORBIT.md source at ${skillSrc}; skipped repo guidance refresh.`);
77
- return;
78
- }
79
- if (result.status === "written") {
80
- console.log(`[orbit] Refreshed managed SKILL-ORBIT.md in ${row.repo_path}`);
81
- }
82
- } catch (error) {
83
- console.warn(`[orbit] Could not refresh SKILL-ORBIT.md in ${row.repo_path}: ${error.message}`);
84
- }
85
- }
86
- }
87
-
88
- ensureWorkspace();
89
- // Move any per-board SQLite files that still live in in-repo .orbit/ directories
90
- // into the central DATA_DIR/boards/<slug>/ store. Must run before
91
- // ensureSchemaForEveryBoard so all connections open against the canonical paths.
92
- migrateInRepoBoards();
93
- // Reconcile the registry against disk *before* deciding whether to bootstrap a
94
- // first board — otherwise a stale row from a deleted .orbit/ folder makes
95
- // listBoards() non-empty and we never re-seed.
96
- ensureSchemaForEveryBoard();
97
- ensureFirstBoard();
98
- if (process.env.ORBIT_SYNC_MANAGED_SKILL_ORBIT === "1") {
99
- syncManagedSkillOrbitFiles();
100
- }
101
-
102
- export const boardRuntime = {
103
- rootDir: ROOT_DIR,
104
- publicDir: PUBLIC_DIR,
105
- projectRoot: PROJECT_ROOT,
106
- dataDir: DATA_DIR,
107
- registryPath: REGISTRY_DB_PATH,
108
- exportDir: EXPORT_DIR,
109
- backupDir: BACKUP_DIR,
110
- port: PORT
111
- };
112
-
113
- export function startupSummary(port = PORT, host = "localhost") {
114
- const rows = listBoards();
115
- return {
116
- url: `http://${host}:${port}`,
117
- registryPath: REGISTRY_DB_PATH,
118
- boardCount: rows.length,
119
- boardPaths: rows.map((row) => row.db_path)
120
- };
121
- }
122
-
123
- export { localOwnerActor, localAgentActor } from "./auth.js";
124
- export { startSSEStream } from "./events.js";
125
- export { sendEmpty, sendJson, serveStatic } from "./http.js";
126
- export { handleApi } from "./router.js";
127
- export { getBootstrap } from "./bootstrap.js";
128
- export {
129
- createBoard,
130
- createBoardEntry,
131
- getBoardContext,
132
- insertBoardEntry,
133
- updateBoard,
134
- updateBoardEntry,
135
- boardManual
136
- } from "./boards.js";
137
- export { archiveTicket, createComment, createTicket, deleteTicket, restoreTicket, updateTicket } from "./tickets.js";
138
- export { createReviewVerdict, getReviewVerdict, listReviewVerdicts } from "./review-verdicts.js";
139
- export { archivedTicketsForBoard } from "./queries.js";
140
- export { getTicketBlockers, getTicketRelations } from "./relations.js";
141
- export { checkpointTicket, claimNext, completeTicket, getAgentDispatchPacket, getContextPack, getContextPackFull, readComments, readTicket } from "./agent.js";
142
- export { searchTickets } from "./search.js";
143
- export { getTicketMarkdownArtifact, storeTicketMarkdownArtifact } from "./artifacts.js";
144
- export { exportBoard } from "./snapshots.js";
145
-
146
- // Helpers for ad-hoc id/slug minting in callers (e.g. MCP tools that mint
147
- // per-process tokens). Intentionally kept tiny and side-effect-free.
148
- export const mint = { id, slugify };