@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.
- package/Dockerfile +10 -8
- package/README.md +146 -164
- package/dist/full/csp.txt +1 -0
- package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-subset.woff2 +0 -0
- package/dist/full/fonts/JetBrains_Mono/README.txt +5 -0
- package/dist/full/fonts/Mona_Sans/MonaSans-subset.woff2 +0 -0
- package/{public/fonts/Open_Sans → dist/full/fonts/Mona_Sans}/OFL.txt +93 -93
- package/dist/full/fonts/Mona_Sans/README.txt +10 -0
- package/{public/fonts/JetBrains_Mono → dist/full/fonts/Newsreader}/OFL.txt +93 -93
- package/dist/full/fonts/Newsreader/README.txt +8 -0
- package/dist/full/fonts/Newsreader/newsreader.woff2 +0 -0
- package/dist/full/index.html +36 -653
- package/dist/full/js/app.BmuponPJ.js +32 -0
- package/dist/full/js/app.BmuponPJ.js.map +1 -0
- package/dist/full/js/chunk.1fJGQGbW.js +2 -0
- package/dist/full/js/chunk.1fJGQGbW.js.map +1 -0
- package/dist/full/js/chunk.5D7JO2zp.js +2 -0
- package/dist/full/js/chunk.5D7JO2zp.js.map +1 -0
- package/dist/full/js/chunk.BrNn_D79.js +2 -0
- package/dist/full/js/chunk.BrNn_D79.js.map +1 -0
- package/dist/full/js/chunk.CokJXIqM.js +2 -0
- package/dist/full/js/chunk.CokJXIqM.js.map +1 -0
- package/dist/full/js/chunk.D_Haqy6R.js +2 -0
- package/dist/full/js/chunk.D_Haqy6R.js.map +1 -0
- package/dist/full/js/chunk.RO0jsH6I.js +15 -0
- package/dist/full/js/chunk.RO0jsH6I.js.map +1 -0
- package/dist/full/js/chunk.dqWOEtBM.js +2 -0
- package/dist/full/js/chunk.dqWOEtBM.js.map +1 -0
- package/dist/full/js/warm.f260bbbc.js +15 -0
- package/dist/full/orbits-favicon.png +0 -0
- package/dist/full/orbits-logo-bg-wide.png +0 -0
- package/dist/full/orbits-logo-bg.png +0 -0
- package/dist/full/orbits-logo.png +0 -0
- package/dist/full/styles.2b154ffe.css +1 -0
- package/dist/full/vendor/starlove-ui/theme-init.js +66 -0
- package/dist/full/version.json +2 -2
- package/docs/AGENT_GUIDE.md +114 -0
- package/docs/UPDATING.md +249 -64
- package/lib/adapters/indexeddb/blobs.d.ts +3 -0
- package/lib/adapters/indexeddb/blobs.js +54 -0
- package/lib/adapters/indexeddb/database.d.ts +60 -0
- package/lib/adapters/indexeddb/database.js +126 -0
- package/lib/adapters/indexeddb/index.d.ts +5 -0
- package/lib/adapters/indexeddb/index.js +3 -0
- package/lib/adapters/indexeddb/locks.d.ts +14 -0
- package/lib/adapters/indexeddb/locks.js +40 -0
- package/lib/adapters/indexeddb/store.d.ts +40 -0
- package/lib/adapters/indexeddb/store.js +424 -0
- package/lib/adapters/indexeddb/tables.d.ts +43 -0
- package/lib/adapters/indexeddb/tables.js +127 -0
- package/lib/adapters/memory/blobs.d.ts +3 -0
- package/lib/adapters/memory/blobs.js +44 -0
- package/lib/adapters/memory/board.d.ts +41 -0
- package/lib/adapters/memory/board.js +521 -0
- package/lib/adapters/memory/index.d.ts +2 -0
- package/lib/adapters/memory/index.js +2 -0
- package/lib/adapters/memory/search.d.ts +6 -0
- package/lib/adapters/memory/search.js +30 -0
- package/lib/adapters/memory/store.d.ts +34 -0
- package/lib/adapters/memory/store.js +266 -0
- package/lib/adapters/sqlite/backups.d.ts +53 -0
- package/lib/adapters/sqlite/backups.js +232 -0
- package/lib/adapters/sqlite/blobs.d.ts +14 -0
- package/lib/adapters/sqlite/blobs.js +109 -0
- package/lib/adapters/sqlite/board.d.ts +77 -0
- package/lib/adapters/sqlite/board.js +328 -0
- package/lib/adapters/sqlite/database.d.ts +67 -0
- package/lib/adapters/sqlite/database.js +285 -0
- package/lib/adapters/sqlite/index.d.ts +3 -0
- package/lib/adapters/sqlite/index.js +2 -0
- package/lib/adapters/sqlite/lock.d.ts +11 -0
- package/lib/adapters/sqlite/lock.js +34 -0
- package/lib/adapters/sqlite/records.d.ts +54 -0
- package/lib/adapters/sqlite/records.js +546 -0
- package/lib/adapters/sqlite/schema.d.ts +54 -0
- package/lib/adapters/sqlite/schema.js +276 -0
- package/lib/adapters/sqlite/store.d.ts +39 -0
- package/lib/adapters/sqlite/store.js +385 -0
- package/lib/browser/in-page.d.ts +31 -0
- package/lib/browser/in-page.js +46 -0
- package/lib/browser/old-preview.d.ts +13 -0
- package/lib/browser/old-preview.js +80 -0
- package/lib/browser/preview-board.d.ts +8 -0
- package/lib/browser/preview-board.js +69 -0
- package/lib/cli/main.d.ts +7 -0
- package/lib/cli/main.js +343 -0
- package/lib/cli/orbit.d.ts +8 -0
- package/lib/cli/orbit.js +25 -0
- package/lib/client/http.d.ts +38 -0
- package/lib/client/http.js +334 -0
- package/lib/client/local.d.ts +26 -0
- package/lib/client/local.js +190 -0
- package/lib/client/object-url.d.ts +8 -0
- package/lib/client/object-url.js +21 -0
- package/lib/client/types.d.ts +268 -0
- package/lib/client/types.js +8 -0
- package/lib/commands/agent-tools.d.ts +31 -0
- package/lib/commands/agent-tools.js +100 -0
- package/lib/commands/index.d.ts +56 -0
- package/lib/commands/index.js +298 -0
- package/lib/commands/parse.d.ts +73 -0
- package/lib/commands/parse.js +231 -0
- package/lib/commands/present.d.ts +134 -0
- package/lib/commands/present.js +198 -0
- package/lib/commands/session.d.ts +85 -0
- package/lib/commands/session.js +138 -0
- package/lib/commands/verbs.d.ts +35 -0
- package/lib/commands/verbs.js +842 -0
- package/lib/core/actors.d.ts +18 -0
- package/lib/core/actors.js +45 -0
- package/lib/core/agent.d.ts +128 -0
- package/lib/core/agent.js +323 -0
- package/lib/core/boards.d.ts +113 -0
- package/lib/core/boards.js +288 -0
- package/lib/core/bootstrap.d.ts +47 -0
- package/lib/core/bootstrap.js +100 -0
- package/lib/core/bytes.d.ts +10 -0
- package/lib/core/bytes.js +52 -0
- package/lib/core/card-paths.d.ts +25 -0
- package/lib/core/card-paths.js +44 -0
- package/lib/core/context.d.ts +60 -0
- package/lib/core/context.js +42 -0
- package/lib/core/cross-board.d.ts +81 -0
- package/lib/core/cross-board.js +228 -0
- package/lib/core/describe.d.ts +21 -0
- package/lib/core/describe.js +81 -0
- package/lib/core/errors.d.ts +159 -0
- package/lib/core/errors.js +160 -0
- package/lib/core/files.d.ts +79 -0
- package/lib/core/files.js +262 -0
- package/lib/core/history.d.ts +20 -0
- package/lib/core/history.js +53 -0
- package/lib/core/image-types.d.ts +16 -0
- package/lib/core/image-types.js +36 -0
- package/lib/core/model.d.ts +263 -0
- package/lib/core/model.js +34 -0
- package/lib/core/nebula-link.d.ts +14 -0
- package/lib/core/nebula-link.js +39 -0
- package/lib/core/relations.d.ts +58 -0
- package/lib/core/relations.js +112 -0
- package/lib/core/search.d.ts +38 -0
- package/lib/core/search.js +92 -0
- package/lib/core/sessions.d.ts +44 -0
- package/lib/core/sessions.js +158 -0
- package/lib/core/snapshot-schema.d.ts +295 -0
- package/lib/core/snapshot-schema.js +82 -0
- package/lib/core/snapshots.d.ts +75 -0
- package/lib/core/snapshots.js +409 -0
- package/lib/core/states.d.ts +25 -0
- package/lib/core/states.js +126 -0
- package/lib/core/text.d.ts +40 -0
- package/lib/core/text.js +113 -0
- package/lib/core/tickets.d.ts +69 -0
- package/lib/core/tickets.js +311 -0
- package/lib/core/trello.d.ts +17 -0
- package/lib/core/trello.js +269 -0
- package/lib/core/verdicts.d.ts +28 -0
- package/lib/core/verdicts.js +92 -0
- package/lib/core/views.d.ts +95 -0
- package/lib/core/views.js +186 -0
- package/lib/http/app.d.ts +46 -0
- package/lib/http/app.js +491 -0
- package/lib/mcp-server.d.ts +10 -0
- package/lib/mcp-server.js +120 -0
- package/lib/node/assets.d.ts +7 -0
- package/lib/node/assets.js +68 -0
- package/lib/node/data-folder.d.ts +21 -0
- package/lib/node/data-folder.js +34 -0
- package/lib/node/env.d.ts +38 -0
- package/lib/node/env.js +49 -0
- package/lib/node/host-check.d.ts +24 -0
- package/lib/node/host-check.js +53 -0
- package/lib/node/host-services.d.ts +9 -0
- package/lib/node/host-services.js +150 -0
- package/lib/node/local.d.ts +22 -0
- package/lib/node/local.js +61 -0
- package/lib/node/migrate.d.ts +53 -0
- package/lib/node/migrate.js +635 -0
- package/lib/node/package.d.ts +7 -0
- package/lib/node/package.js +14 -0
- package/lib/node/repo-board.d.ts +49 -0
- package/lib/node/repo-board.js +245 -0
- package/lib/node/serve.d.ts +8 -0
- package/lib/node/serve.js +106 -0
- package/lib/orbit.d.ts +47 -0
- package/lib/orbit.js +44 -0
- package/lib/quiet-sqlite-warning.d.ts +1 -0
- package/lib/quiet-sqlite-warning.js +18 -0
- package/lib/store/blob-path.d.ts +6 -0
- package/lib/store/blob-path.js +12 -0
- package/lib/store/conformance.d.ts +52 -0
- package/lib/store/conformance.js +1449 -0
- package/lib/store/search-words.d.ts +10 -0
- package/lib/store/search-words.js +19 -0
- package/lib/store/types.d.ts +452 -0
- package/lib/store/types.js +34 -0
- package/llms.txt +63 -0
- package/package.json +109 -25
- package/src/cli/orbit.js +11 -658
- package/src/mcp-server.js +16 -876
- package/SKILL-ORBIT.md +0 -151
- package/build.mjs +0 -210
- package/dist/full/js/app.4e6fbe20.js +0 -952
- package/dist/full/js/app.4e6fbe20.js.map +0 -7
- package/dist/full/styles.4e60d824.css +0 -1
- package/docs/AGENT_PROTOCOL.md +0 -424
- package/docs/MCP_SETUP.md +0 -54
- package/docs/ORBIT_DISPATCH.md +0 -280
- package/docs/SS-Orbit-Dark.png +0 -0
- package/docs/SS-Orbit-Light.png +0 -0
- package/docs/mcp-client.example.json +0 -13
- package/docs/setup/orbit-init-wizard.md +0 -174
- package/public/apple-touch-icon.png +0 -0
- package/public/cursors/live_arrow.cur +0 -0
- package/public/cursors/live_link.cur +0 -0
- package/public/cursors/live_move.cur +0 -0
- package/public/cursors/live_select.cur +0 -0
- package/public/cursors/live_unavail.cur +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/OFL.txt +0 -93
- package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/OFL.txt +0 -93
- package/public/fonts/Inter/README.txt +0 -118
- package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/README.txt +0 -79
- package/public/fonts/OpenRing-Regular.ttf +0 -0
- package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/README.txt +0 -100
- package/public/fonts/Orbitron/OFL.txt +0 -93
- package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
- package/public/fonts/Orbitron/README.txt +0 -68
- package/public/fonts/Rajdhani/OFL.txt +0 -93
- package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
- package/public/fonts/runescape-uf.ttf +0 -0
- package/public/index.html +0 -769
- package/public/js/actor-labels.js +0 -65
- package/public/js/api.js +0 -39
- package/public/js/app.js +0 -45
- package/public/js/artifact-drawer.js +0 -79
- package/public/js/board-export.js +0 -161
- package/public/js/board-menu.js +0 -374
- package/public/js/card-actions.js +0 -545
- package/public/js/config.js +0 -20
- package/public/js/create-card.js +0 -190
- package/public/js/dom.js +0 -19
- package/public/js/drawer.js +0 -273
- package/public/js/font-preference.js +0 -230
- package/public/js/format.js +0 -433
- package/public/js/help-menu.js +0 -31
- package/public/js/icons.js +0 -16
- package/public/js/kanban.js +0 -694
- package/public/js/local-backend.js +0 -1015
- package/public/js/main.js +0 -161
- package/public/js/modal.js +0 -33
- package/public/js/motion-preference.js +0 -64
- package/public/js/panel.js +0 -58
- package/public/js/router.js +0 -134
- package/public/js/search.js +0 -56
- package/public/js/settings-tabs.js +0 -7
- package/public/js/settings.js +0 -1634
- package/public/js/sse.js +0 -159
- package/public/js/starscape.js +0 -332
- package/public/js/state.js +0 -129
- package/public/js/text-editing.js +0 -128
- package/public/js/theme-preference.js +0 -95
- package/public/js/ticket-detail.js +0 -1516
- package/public/js/ticket-typeahead.js +0 -49
- package/public/js/toast.js +0 -89
- package/public/js/topbar-notes.js +0 -155
- package/public/js/transport.js +0 -22
- package/public/js/typeahead.js +0 -206
- package/public/js/unread.js +0 -121
- package/public/js/update-check.js +0 -58
- package/public/js/url-routes.js +0 -94
- package/public/orbits-favicon.png +0 -0
- package/public/orbits-logo-bg-wide.png +0 -0
- package/public/orbits-logo-bg.png +0 -0
- package/public/orbits-logo.png +0 -0
- package/public/styles.css +0 -7709
- package/src/core/agent.js +0 -387
- package/src/core/agents-md.js +0 -109
- package/src/core/artifacts.js +0 -149
- package/src/core/attachments.js +0 -238
- package/src/core/auth.js +0 -85
- package/src/core/backups.js +0 -142
- package/src/core/board.js +0 -148
- package/src/core/boards.js +0 -409
- package/src/core/bootstrap.js +0 -170
- package/src/core/css-bundle.js +0 -30
- package/src/core/db.js +0 -315
- package/src/core/delete-board.js +0 -163
- package/src/core/dispatch.js +0 -1007
- package/src/core/events.js +0 -314
- package/src/core/http.js +0 -115
- package/src/core/migrate.js +0 -198
- package/src/core/paths.js +0 -55
- package/src/core/provision-repo-board.js +0 -119
- package/src/core/queries.js +0 -246
- package/src/core/registry.js +0 -211
- package/src/core/relations.js +0 -163
- package/src/core/review-verdicts.js +0 -138
- package/src/core/router.js +0 -556
- package/src/core/search.js +0 -183
- package/src/core/seed.js +0 -234
- package/src/core/snapshots.js +0 -692
- package/src/core/states.js +0 -167
- package/src/core/system-picker.js +0 -109
- package/src/core/tickets.js +0 -400
- package/src/core/util.js +0 -106
- package/src/core/version-check.js +0 -154
- package/src/mcp/orbit-client.js +0 -287
- package/src/server.js +0 -115
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the port reads a search query (`SearchReader.query`), shared so every
|
|
3
|
+
* adapter reads it the same way. Words are runs of ASCII letters, digits and
|
|
4
|
+
* `_` holding at least one letter or digit, lowercased; each must start a word
|
|
5
|
+
* of the document. A query with no words is matched as literal text.
|
|
6
|
+
*/
|
|
7
|
+
/** The query's words, lowercased, in order. */
|
|
8
|
+
export declare function queryWords(query: string): string[];
|
|
9
|
+
/** The text a query with no words is matched by: the query trimmed, or null when blank. */
|
|
10
|
+
export declare function literalQuery(query: string): string | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the port reads a search query (`SearchReader.query`), shared so every
|
|
3
|
+
* adapter reads it the same way. Words are runs of ASCII letters, digits and
|
|
4
|
+
* `_` holding at least one letter or digit, lowercased; each must start a word
|
|
5
|
+
* of the document. A query with no words is matched as literal text.
|
|
6
|
+
*/
|
|
7
|
+
/** The query's words, lowercased, in order. */
|
|
8
|
+
export function queryWords(query) {
|
|
9
|
+
return query
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9_]+/g, " ")
|
|
12
|
+
.split(" ")
|
|
13
|
+
.filter((word) => /[a-z0-9]/.test(word));
|
|
14
|
+
}
|
|
15
|
+
/** The text a query with no words is matched by: the query trimmed, or null when blank. */
|
|
16
|
+
export function literalQuery(query) {
|
|
17
|
+
const text = query.trim();
|
|
18
|
+
return text === "" ? null : text;
|
|
19
|
+
}
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orbit's storage port: everything the core asks of storage, and nothing about
|
|
3
|
+
* how a database does it. An adapter implements it (SQLite on a computer or
|
|
4
|
+
* server, IndexedDB in the browser, memory for tests, or yours) and proves
|
|
5
|
+
* itself with the conformance suite in ./conformance.ts. docs/ARCHITECTURE.md
|
|
6
|
+
* shows where it sits and how to write one.
|
|
7
|
+
*
|
|
8
|
+
* The shape, top down:
|
|
9
|
+
*
|
|
10
|
+
* OrbitStore one Orbit's data
|
|
11
|
+
* .boards the list of boards (BoardDirectory)
|
|
12
|
+
* .board(id) one board's data (BoardStore)
|
|
13
|
+
* .read(r => ...) a consistent view of what is committed
|
|
14
|
+
* .write(w => ...) one all-or-nothing change, events included
|
|
15
|
+
* .watch(listener) every committed change, from any process or tab
|
|
16
|
+
* BlobStore bytes (images, markdown), apart from records
|
|
17
|
+
*
|
|
18
|
+
* What every adapter promises, beyond each method's own words:
|
|
19
|
+
*
|
|
20
|
+
* - Records go in and come out as plain data, field for field (the types in
|
|
21
|
+
* src/core/model.ts). A record handed out is the caller's copy; changing it
|
|
22
|
+
* changes nothing stored, and neither does changing an object after it was
|
|
23
|
+
* written.
|
|
24
|
+
* - Lists come back in the order each method states, so every adapter answers
|
|
25
|
+
* alike. "Then by when it was written" means the order the records were
|
|
26
|
+
* inserted.
|
|
27
|
+
* - Failures are OrbitErrors (src/core/errors.ts) with the codes named here.
|
|
28
|
+
* A database's own message never reaches an answer: wrap it as
|
|
29
|
+
* `storage_failed`, `storage_busy` or `storage_full`, with the original as
|
|
30
|
+
* its `cause`.
|
|
31
|
+
* - Nothing here makes an id or reads a clock. The core gives every id and
|
|
32
|
+
* time, so the same input gives the same records on any adapter.
|
|
33
|
+
*/
|
|
34
|
+
import type { Attachment, Board, BoardChange, BoardEntry, Comment, CrossBoardRelation, Id, Label, OrbitEvent, RegisteredBoard, Relation, ReviewVerdict, State, Ticket, TicketLabel, TicketType, Timestamp } from "../core/model.ts";
|
|
35
|
+
export type { BoardChange } from "../core/model.ts";
|
|
36
|
+
/** One Orbit's data: its boards and its settings. */
|
|
37
|
+
export interface OrbitStore {
|
|
38
|
+
readonly boards: BoardDirectory;
|
|
39
|
+
/** One board's data. Rejects with `board_not_found` when the directory doesn't list it. */
|
|
40
|
+
board(id: Id): Promise<BoardStore>;
|
|
41
|
+
/**
|
|
42
|
+
* The board holding a lane, card, journal entry or relation with this id,
|
|
43
|
+
* or null. A `hint` board is looked at first, so the common case costs one
|
|
44
|
+
* lookup; then board by board in `list()` order.
|
|
45
|
+
*/
|
|
46
|
+
locate(kind: "state" | "ticket" | "entry" | "relation", id: Id, hint?: Id): Promise<RegisteredBoard | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Every cross-board relation, on any board, whose target card is on
|
|
49
|
+
* `boardId`, with the board it lives on: board by board in `list()` order,
|
|
50
|
+
* each board's by when each was made, then written. A board that can't be
|
|
51
|
+
* read now (its file is gone) is skipped, never an error: one board's
|
|
52
|
+
* trouble must not stop every other board's cards from opening.
|
|
53
|
+
*/
|
|
54
|
+
relationsInto(boardId: Id): Promise<{
|
|
55
|
+
board_id: Id;
|
|
56
|
+
relation: CrossBoardRelation;
|
|
57
|
+
}[]>;
|
|
58
|
+
/** Small values this Orbit keeps for itself (the computer's default board). */
|
|
59
|
+
readonly settings: Settings;
|
|
60
|
+
/** Point-in-time copies of the data, where the adapter can make them; null where it can't. */
|
|
61
|
+
readonly backups: Backups | null;
|
|
62
|
+
/** Lets go of files and connections. The store is unusable afterwards. */
|
|
63
|
+
close(): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
/** The list of boards. */
|
|
66
|
+
export interface BoardDirectory {
|
|
67
|
+
/** Every board, by name in code-point order (as SQLite's default collation), then by when it was registered. */
|
|
68
|
+
list(): Promise<RegisteredBoard[]>;
|
|
69
|
+
get(id: Id): Promise<RegisteredBoard | null>;
|
|
70
|
+
/** The slug as stored: callers normalise it first. */
|
|
71
|
+
bySlug(slug: string): Promise<RegisteredBoard | null>;
|
|
72
|
+
/** The board used last (`last_active_at`); among equals, the one registered last. Null when there are none. */
|
|
73
|
+
latest(): Promise<RegisteredBoard | null>;
|
|
74
|
+
/**
|
|
75
|
+
* Makes a board: its storage, its own record (`board`) and whatever `seed`
|
|
76
|
+
* writes, in its first write; then lists it, with `last_active_at` set to
|
|
77
|
+
* `board.created_at`. All or nothing: if `seed` throws, no board, no data
|
|
78
|
+
* and no listing remain, and the error is rethrown. Rejects with
|
|
79
|
+
* `board_slug_taken` when another board has the slug, checked as one step
|
|
80
|
+
* with the listing, and with `duplicate_id` when one has the id.
|
|
81
|
+
*/
|
|
82
|
+
create(board: Board, seed: (w: BoardWriter) => Promise<void>): Promise<RegisteredBoard>;
|
|
83
|
+
/** Sets `last_active_at` and `updated_at`. Rejects with `board_not_found` for an unknown id. */
|
|
84
|
+
touch(id: Id, at: Timestamp): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Takes a board off the list and deletes its records, unless `keepData`.
|
|
87
|
+
* Its blobs are the BlobStore's to remove (`removeBoard`). Returns the
|
|
88
|
+
* places it deleted, for people to read (files, for SQLite). Rejects with
|
|
89
|
+
* `board_not_found` for an unknown id.
|
|
90
|
+
*/
|
|
91
|
+
remove(id: Id, options?: {
|
|
92
|
+
keepData?: boolean;
|
|
93
|
+
}): Promise<{
|
|
94
|
+
removed: string[];
|
|
95
|
+
}>;
|
|
96
|
+
}
|
|
97
|
+
export interface Settings {
|
|
98
|
+
get(key: string): Promise<string | null>;
|
|
99
|
+
set(key: string, value: string): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
/** Copies an adapter makes on request: before a destructive change, or after writes. */
|
|
102
|
+
export interface Backups {
|
|
103
|
+
/**
|
|
104
|
+
* Copies one board's data as it is committed now, labelled with `reason`.
|
|
105
|
+
* With `oncePerDay`, nothing is copied when today (UTC) already has one.
|
|
106
|
+
* Returns where the copy went, or null when none was made.
|
|
107
|
+
*/
|
|
108
|
+
board(id: Id, reason: string, options?: {
|
|
109
|
+
oncePerDay?: boolean;
|
|
110
|
+
}): Promise<string | null>;
|
|
111
|
+
/** The same for the list of boards and the settings. */
|
|
112
|
+
directory(reason: string, options?: {
|
|
113
|
+
oncePerDay?: boolean;
|
|
114
|
+
}): Promise<string | null>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* One board's data. Reads and writes go through `read()` and `write()`, so
|
|
118
|
+
* every adapter answers the same questions about concurrency the same way.
|
|
119
|
+
*/
|
|
120
|
+
export interface BoardStore {
|
|
121
|
+
readonly id: Id;
|
|
122
|
+
/**
|
|
123
|
+
* Runs `body` against what is committed. Everything one read sees is one
|
|
124
|
+
* committed state: a write that commits while `body` runs shows in full or
|
|
125
|
+
* not at all, and an open write's changes never show. Resolves with what
|
|
126
|
+
* `body` returns. A write may wait for reads in progress to finish, so a
|
|
127
|
+
* read's body must not wait for a write of the same board.
|
|
128
|
+
*/
|
|
129
|
+
read<T>(body: (r: BoardReader) => Promise<T>): Promise<T>;
|
|
130
|
+
/**
|
|
131
|
+
* Runs `body` as one change to the board. Writes to one board run one at a
|
|
132
|
+
* time across every store over the same data, in this process or another,
|
|
133
|
+
* so nothing a body reads can change under it until it finishes. Its own
|
|
134
|
+
* reads see its own changes. If `body` throws, nothing it did remains (no
|
|
135
|
+
* record, no card number used, no event, no index entry) and the same error
|
|
136
|
+
* is rethrown. When it returns, its changes commit, its events go to every
|
|
137
|
+
* watcher, and `write()` resolves with what it returned.
|
|
138
|
+
*
|
|
139
|
+
* Never call `read()` or `write()` of the same board inside `body`: the
|
|
140
|
+
* inner call waits for the outer one, which is waiting for it. Use `w`.
|
|
141
|
+
* The writer refuses every call (`write_closed`) once `write()` settles.
|
|
142
|
+
*/
|
|
143
|
+
write<T>(body: (w: BoardWriter) => Promise<T>): Promise<T>;
|
|
144
|
+
/**
|
|
145
|
+
* Hears every event the board's writes commit, once, in the order they were
|
|
146
|
+
* appended: this store's writes and those of any other store over the same
|
|
147
|
+
* data (another process, another tab). Never an event of a write that
|
|
148
|
+
* failed. Returns the function that stops listening.
|
|
149
|
+
*/
|
|
150
|
+
watch(listener: (change: BoardChange) => void): () => void;
|
|
151
|
+
}
|
|
152
|
+
/** What `read()` sees: every kind of record on the board. */
|
|
153
|
+
export interface BoardReader {
|
|
154
|
+
readonly board: BoardRecordReader;
|
|
155
|
+
readonly states: StateReader;
|
|
156
|
+
readonly labels: LabelReader;
|
|
157
|
+
readonly tickets: TicketReader;
|
|
158
|
+
readonly ticketLabels: TicketLabelReader;
|
|
159
|
+
readonly comments: CommentReader;
|
|
160
|
+
readonly relations: RelationReader;
|
|
161
|
+
readonly crossBoardRelations: CrossBoardRelationReader;
|
|
162
|
+
readonly entries: EntryReader;
|
|
163
|
+
readonly verdicts: VerdictReader;
|
|
164
|
+
readonly attachments: AttachmentReader;
|
|
165
|
+
readonly events: EventReader;
|
|
166
|
+
readonly search: SearchReader;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* What `write()` changes. It reads too, and sees its own changes. Writes
|
|
170
|
+
* that would break the board's rules of storage are refused with the codes
|
|
171
|
+
* below, and the refusal throws inside `body`, so the write as a whole fails
|
|
172
|
+
* unless `body` catches it.
|
|
173
|
+
*
|
|
174
|
+
* - A record whose id the board already has: `duplicate_id`.
|
|
175
|
+
* - A record naming what the board doesn't have: `invalid_reference`. That is
|
|
176
|
+
* a `board_id` other than this board's, and a card, lane, label or verdict
|
|
177
|
+
* that isn't on the board (a card's lane, a comment's card, a label link's
|
|
178
|
+
* card and label, a relation's two cards, an event's or a journal entry's
|
|
179
|
+
* card when set, a verdict's earlier verdict when set). Two names are not
|
|
180
|
+
* checked: a card's epic (`parent_ticket_id`) and a cross-board relation's
|
|
181
|
+
* target.
|
|
182
|
+
* - Updating or removing a record that isn't there: its kind's not-found
|
|
183
|
+
* code (`state_not_found`, `ticket_not_found`, `comment_not_found`,
|
|
184
|
+
* `relation_not_found`, `board_entry_not_found`, `attachment_not_found`).
|
|
185
|
+
*/
|
|
186
|
+
export interface BoardWriter extends BoardReader {
|
|
187
|
+
readonly board: BoardRecordWriter;
|
|
188
|
+
readonly states: StateWriter;
|
|
189
|
+
readonly labels: LabelWriter;
|
|
190
|
+
readonly tickets: TicketWriter;
|
|
191
|
+
readonly ticketLabels: TicketLabelWriter;
|
|
192
|
+
readonly comments: CommentWriter;
|
|
193
|
+
readonly relations: RelationWriter;
|
|
194
|
+
readonly crossBoardRelations: CrossBoardRelationWriter;
|
|
195
|
+
readonly entries: EntryWriter;
|
|
196
|
+
readonly verdicts: VerdictWriter;
|
|
197
|
+
readonly attachments: AttachmentWriter;
|
|
198
|
+
readonly events: EventWriter;
|
|
199
|
+
readonly search: SearchWriter;
|
|
200
|
+
/** Deletes every record on the board, its own record, events and index included, as a replacing import does first. */
|
|
201
|
+
clear(): Promise<void>;
|
|
202
|
+
}
|
|
203
|
+
export interface BoardRecordReader {
|
|
204
|
+
/** Rejects with `board_not_found` only between `clear()` and `set()` inside one write. */
|
|
205
|
+
get(): Promise<Board>;
|
|
206
|
+
}
|
|
207
|
+
export interface BoardRecordWriter extends BoardRecordReader {
|
|
208
|
+
/**
|
|
209
|
+
* Writes the board's own record whole; its id must be this board's
|
|
210
|
+
* (`invalid_reference`). The directory follows its slug, name, repo_url,
|
|
211
|
+
* default_branch and updated_at once the write commits. A slug another
|
|
212
|
+
* board has is refused with `board_slug_taken`.
|
|
213
|
+
*/
|
|
214
|
+
set(board: Board): Promise<void>;
|
|
215
|
+
/** Changes the fields given, as `set()` does. */
|
|
216
|
+
update(patch: BoardPatch): Promise<Board>;
|
|
217
|
+
}
|
|
218
|
+
export type BoardPatch = Partial<Omit<Board, "id" | "created_at">>;
|
|
219
|
+
export interface StateReader {
|
|
220
|
+
/** By position, then by when each was written. */
|
|
221
|
+
list(): Promise<State[]>;
|
|
222
|
+
get(id: Id): Promise<State | null>;
|
|
223
|
+
}
|
|
224
|
+
export interface StateWriter extends StateReader {
|
|
225
|
+
/** Refuses a name another lane on the board has with `state_name_taken`. */
|
|
226
|
+
insert(state: State): Promise<void>;
|
|
227
|
+
/** Refuses as insert() does; `state_not_found` for an unknown id. */
|
|
228
|
+
update(id: Id, patch: StatePatch): Promise<State>;
|
|
229
|
+
/** Refuses with `state_has_tickets` while any card, archived or not, is in the lane. */
|
|
230
|
+
remove(id: Id): Promise<void>;
|
|
231
|
+
}
|
|
232
|
+
export type StatePatch = Partial<Pick<State, "name" | "position" | "is_default" | "role">>;
|
|
233
|
+
export interface LabelReader {
|
|
234
|
+
/** By name in code-point order. */
|
|
235
|
+
list(): Promise<Label[]>;
|
|
236
|
+
}
|
|
237
|
+
export interface LabelWriter extends LabelReader {
|
|
238
|
+
/** Refuses a name another label on the board has with `label_name_taken`. */
|
|
239
|
+
insert(label: Label): Promise<void>;
|
|
240
|
+
}
|
|
241
|
+
export interface TicketFilter {
|
|
242
|
+
/** true: archived cards only; false: open cards only; absent: both. */
|
|
243
|
+
archived?: boolean;
|
|
244
|
+
/** Only the sub-cards of this epic. */
|
|
245
|
+
parent?: Id;
|
|
246
|
+
/** Only the cards in this lane. */
|
|
247
|
+
state?: Id;
|
|
248
|
+
/** Only these cards (unknown ids are skipped). */
|
|
249
|
+
ids?: readonly Id[];
|
|
250
|
+
}
|
|
251
|
+
export interface TicketReader {
|
|
252
|
+
get(id: Id): Promise<Ticket | null>;
|
|
253
|
+
byNumber(number: number): Promise<Ticket | null>;
|
|
254
|
+
/**
|
|
255
|
+
* The card with this exact title, ignoring the case of ASCII letters (as
|
|
256
|
+
* SQLite's lower() does): an open card before an archived one, then the
|
|
257
|
+
* highest number.
|
|
258
|
+
*/
|
|
259
|
+
byTitle(title: string): Promise<Ticket | null>;
|
|
260
|
+
/** The cards matching every filter given, by number. */
|
|
261
|
+
list(filter?: TicketFilter): Promise<Ticket[]>;
|
|
262
|
+
/** The highest number on the board, archived cards included; 0 when it has none. */
|
|
263
|
+
lastNumber(): Promise<number>;
|
|
264
|
+
}
|
|
265
|
+
/** A new card: everything but its number, which storage assigns. */
|
|
266
|
+
export type NewTicket = Omit<Ticket, "number">;
|
|
267
|
+
export interface TicketWriter extends TicketReader {
|
|
268
|
+
/**
|
|
269
|
+
* Makes a card numbered one more than the highest number on the board now
|
|
270
|
+
* (archived cards count; the first card is 1), and returns it. Because
|
|
271
|
+
* writes run one at a time, two callers never get the same number, and a
|
|
272
|
+
* write that fails uses none.
|
|
273
|
+
*/
|
|
274
|
+
create(ticket: NewTicket): Promise<Ticket>;
|
|
275
|
+
/** Writes a card with its number as given (imports). Refuses a number in use with `ticket_number_taken`. */
|
|
276
|
+
insert(ticket: Ticket): Promise<void>;
|
|
277
|
+
/** Changes the fields given. Refuses an unknown lane with `invalid_reference`. */
|
|
278
|
+
update(id: Id, patch: TicketPatch): Promise<Ticket>;
|
|
279
|
+
/**
|
|
280
|
+
* Deletes a card and what belongs to it: its comments, label links,
|
|
281
|
+
* relations either way, cross-board relations from it, review verdicts,
|
|
282
|
+
* images (records; the bytes are the BlobStore's) and index entry. Events
|
|
283
|
+
* and journal entries about it stay, their `ticket_id` set to null. Its
|
|
284
|
+
* sub-cards keep their `parent_ticket_id`.
|
|
285
|
+
*/
|
|
286
|
+
remove(id: Id): Promise<void>;
|
|
287
|
+
}
|
|
288
|
+
export type TicketPatch = Partial<Omit<Ticket, "id" | "board_id" | "number" | "created_by" | "created_at">>;
|
|
289
|
+
export interface TicketLabelReader {
|
|
290
|
+
/** The links of these cards (every card's when absent), in the order they were written. */
|
|
291
|
+
list(ticketIds?: readonly Id[]): Promise<TicketLabel[]>;
|
|
292
|
+
}
|
|
293
|
+
export interface TicketLabelWriter extends TicketLabelReader {
|
|
294
|
+
/** Replaces the card's labels with these. */
|
|
295
|
+
set(ticketId: Id, labelIds: readonly Id[]): Promise<void>;
|
|
296
|
+
/** Adds one; adding a link that exists changes nothing. */
|
|
297
|
+
add(ticketId: Id, labelId: Id): Promise<void>;
|
|
298
|
+
}
|
|
299
|
+
export interface CommentReader {
|
|
300
|
+
get(id: Id): Promise<Comment | null>;
|
|
301
|
+
/** A card's comments (every card's when absent), oldest first, then by when each was written. */
|
|
302
|
+
list(ticketId?: Id): Promise<Comment[]>;
|
|
303
|
+
/**
|
|
304
|
+
* How many comments each card has; cards with none are left out. With
|
|
305
|
+
* `forAgents`, comments hidden from agents don't count; with `exceptBody`,
|
|
306
|
+
* neither do comments whose body is exactly that text.
|
|
307
|
+
*/
|
|
308
|
+
countByTicket(options?: {
|
|
309
|
+
forAgents?: boolean;
|
|
310
|
+
exceptBody?: string;
|
|
311
|
+
}): Promise<{
|
|
312
|
+
ticket_id: Id;
|
|
313
|
+
count: number;
|
|
314
|
+
}[]>;
|
|
315
|
+
}
|
|
316
|
+
export interface CommentWriter extends CommentReader {
|
|
317
|
+
insert(comment: Comment): Promise<void>;
|
|
318
|
+
update(id: Id, patch: Pick<Comment, "omitted_for_ai">): Promise<Comment>;
|
|
319
|
+
}
|
|
320
|
+
export interface RelationReader {
|
|
321
|
+
get(id: Id): Promise<Relation | null>;
|
|
322
|
+
/** The relations with this card on either side (every relation when absent), by when each was made, then written. */
|
|
323
|
+
list(ticketId?: Id): Promise<Relation[]>;
|
|
324
|
+
}
|
|
325
|
+
export interface RelationWriter extends RelationReader {
|
|
326
|
+
/** Refuses a second relation of one type between the same two cards, in the same direction, with `relation_exists`. */
|
|
327
|
+
insert(relation: Relation): Promise<void>;
|
|
328
|
+
remove(id: Id): Promise<void>;
|
|
329
|
+
}
|
|
330
|
+
export interface CrossBoardRelationReader {
|
|
331
|
+
/** The cross-board relations from this card (all of the board's when absent), by when each was made, then written. */
|
|
332
|
+
list(ticketId?: Id): Promise<CrossBoardRelation[]>;
|
|
333
|
+
}
|
|
334
|
+
export interface CrossBoardRelationWriter extends CrossBoardRelationReader {
|
|
335
|
+
/** The source card must be on this board; the target is not checked here. Refuses a repeat with `relation_exists`. */
|
|
336
|
+
insert(relation: CrossBoardRelation): Promise<void>;
|
|
337
|
+
remove(id: Id): Promise<void>;
|
|
338
|
+
}
|
|
339
|
+
export interface EntryReader {
|
|
340
|
+
get(id: Id): Promise<BoardEntry | null>;
|
|
341
|
+
/** Oldest first, then by when each was written. */
|
|
342
|
+
list(): Promise<BoardEntry[]>;
|
|
343
|
+
}
|
|
344
|
+
export interface EntryWriter extends EntryReader {
|
|
345
|
+
insert(entry: BoardEntry): Promise<void>;
|
|
346
|
+
update(id: Id, patch: Partial<Pick<BoardEntry, "struck_at" | "updated_at">>): Promise<BoardEntry>;
|
|
347
|
+
/** Refuses an id that isn't there with `board_entry_not_found`. */
|
|
348
|
+
remove(id: Id): Promise<void>;
|
|
349
|
+
}
|
|
350
|
+
export interface VerdictReader {
|
|
351
|
+
get(id: Id): Promise<ReviewVerdict | null>;
|
|
352
|
+
/** A card's verdicts (every card's when absent), oldest first, then by when each was written. */
|
|
353
|
+
list(ticketId?: Id): Promise<ReviewVerdict[]>;
|
|
354
|
+
}
|
|
355
|
+
export interface VerdictWriter extends VerdictReader {
|
|
356
|
+
insert(verdict: ReviewVerdict): Promise<void>;
|
|
357
|
+
}
|
|
358
|
+
export interface AttachmentReader {
|
|
359
|
+
get(id: Id): Promise<Attachment | null>;
|
|
360
|
+
/** A card's images (every card's when absent), oldest first, then by when each was written. */
|
|
361
|
+
list(ticketId?: Id): Promise<Attachment[]>;
|
|
362
|
+
}
|
|
363
|
+
export interface AttachmentWriter extends AttachmentReader {
|
|
364
|
+
insert(attachment: Attachment): Promise<void>;
|
|
365
|
+
remove(id: Id): Promise<void>;
|
|
366
|
+
}
|
|
367
|
+
export interface EventFilter {
|
|
368
|
+
ticketId?: Id;
|
|
369
|
+
/** Only events of these types. */
|
|
370
|
+
types?: readonly string[];
|
|
371
|
+
/** Only events made at or after this time. */
|
|
372
|
+
since?: Timestamp;
|
|
373
|
+
}
|
|
374
|
+
export interface EventReader {
|
|
375
|
+
/** The events matching every filter given, oldest first, then in the order they were appended. */
|
|
376
|
+
list(filter?: EventFilter): Promise<OrbitEvent[]>;
|
|
377
|
+
/** For each of these cards, its newest event of these types (oldest-first order's last); cards with none are left out. */
|
|
378
|
+
latest(ticketIds: readonly Id[], types: readonly string[]): Promise<OrbitEvent[]>;
|
|
379
|
+
/** Up to `limit` events appended after the event with this id, in the order they were appended; none for an id the board doesn't have. */
|
|
380
|
+
after(id: Id, limit: number): Promise<OrbitEvent[]>;
|
|
381
|
+
/** The event appended last, or null. */
|
|
382
|
+
last(): Promise<OrbitEvent | null>;
|
|
383
|
+
}
|
|
384
|
+
export interface EventWriter extends EventReader {
|
|
385
|
+
/** Records an event; watchers hear it once the write commits. */
|
|
386
|
+
append(event: OrbitEvent): Promise<void>;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* What the index holds for one card. The core builds it, so whatever must
|
|
390
|
+
* never be found (comments hidden from agents) is simply never given.
|
|
391
|
+
*/
|
|
392
|
+
export interface SearchDocument {
|
|
393
|
+
title: string;
|
|
394
|
+
description: string;
|
|
395
|
+
/** Matched by word queries only. */
|
|
396
|
+
type: TicketType;
|
|
397
|
+
/** The plan, the summary and the revisions, one per line. */
|
|
398
|
+
details: string;
|
|
399
|
+
/** The comments agents may read, one per line. */
|
|
400
|
+
comments: string;
|
|
401
|
+
}
|
|
402
|
+
export interface SearchHit {
|
|
403
|
+
ticket_id: Id;
|
|
404
|
+
/** The adapter's own score; lower is better. */
|
|
405
|
+
rank: number;
|
|
406
|
+
}
|
|
407
|
+
export interface SearchReader {
|
|
408
|
+
/**
|
|
409
|
+
* The open (unarchived) cards whose documents match, best first, at most
|
|
410
|
+
* `limit`. The query's words are its runs of ASCII letters, digits and `_`
|
|
411
|
+
* that hold a letter or digit, lowercased (`src/store/search-words.ts`). A
|
|
412
|
+
* document's words are its runs of letters, digits and `_`, so `snake_case`
|
|
413
|
+
* is one word. A card matches when, for every query word, its document has
|
|
414
|
+
* a word starting with it, in any case (an adapter may also fold accents).
|
|
415
|
+
* A query with no such word matches documents holding the query's text as
|
|
416
|
+
* it is (trimmed), ignoring the case of ASCII letters; `%`, `_` and `\` in
|
|
417
|
+
* it are only themselves. A blank query matches nothing.
|
|
418
|
+
*/
|
|
419
|
+
query(text: string, options: {
|
|
420
|
+
limit: number;
|
|
421
|
+
}): Promise<SearchHit[]>;
|
|
422
|
+
}
|
|
423
|
+
export interface SearchWriter extends SearchReader {
|
|
424
|
+
/** Replaces the card's document. */
|
|
425
|
+
index(ticketId: Id, document: SearchDocument): Promise<void>;
|
|
426
|
+
remove(ticketId: Id): Promise<void>;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Bytes Orbit keeps beside a board's records: images under `attachments/`,
|
|
430
|
+
* markdown artifacts under `artifacts/`. Paths are POSIX-style and relative
|
|
431
|
+
* to the board (`attachments/tickets/<card id>/<image id>.png`); one that is
|
|
432
|
+
* absolute or climbs out with `..` is refused with `invalid_blob_path`.
|
|
433
|
+
* Blobs aren't part of a board's writes: the core writes the bytes before
|
|
434
|
+
* the record that names them and removes them after the record is gone, so a
|
|
435
|
+
* failure leaves an unused blob, never a record without its bytes.
|
|
436
|
+
*/
|
|
437
|
+
export interface BlobStore {
|
|
438
|
+
/** Stores the bytes, replacing what is there; with `exclusive`, refuses a path in use with `blob_exists`. */
|
|
439
|
+
put(boardId: Id, path: string, bytes: Uint8Array, options?: {
|
|
440
|
+
exclusive?: boolean;
|
|
441
|
+
}): Promise<void>;
|
|
442
|
+
/** The bytes, or null when nothing is stored at the path. */
|
|
443
|
+
get(boardId: Id, path: string): Promise<Uint8Array | null>;
|
|
444
|
+
/** The size in bytes, or null when nothing is stored at the path. */
|
|
445
|
+
size(boardId: Id, path: string): Promise<number | null>;
|
|
446
|
+
/** Removing what isn't there changes nothing. */
|
|
447
|
+
remove(boardId: Id, path: string): Promise<void>;
|
|
448
|
+
/** Removes every blob of the board. */
|
|
449
|
+
removeBoard(boardId: Id): Promise<void>;
|
|
450
|
+
/** Where the bytes are on this machine, for answers that show it; absent where blobs aren't files. */
|
|
451
|
+
location?(boardId: Id, path: string): string;
|
|
452
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orbit's storage port: everything the core asks of storage, and nothing about
|
|
3
|
+
* how a database does it. An adapter implements it (SQLite on a computer or
|
|
4
|
+
* server, IndexedDB in the browser, memory for tests, or yours) and proves
|
|
5
|
+
* itself with the conformance suite in ./conformance.ts. docs/ARCHITECTURE.md
|
|
6
|
+
* shows where it sits and how to write one.
|
|
7
|
+
*
|
|
8
|
+
* The shape, top down:
|
|
9
|
+
*
|
|
10
|
+
* OrbitStore one Orbit's data
|
|
11
|
+
* .boards the list of boards (BoardDirectory)
|
|
12
|
+
* .board(id) one board's data (BoardStore)
|
|
13
|
+
* .read(r => ...) a consistent view of what is committed
|
|
14
|
+
* .write(w => ...) one all-or-nothing change, events included
|
|
15
|
+
* .watch(listener) every committed change, from any process or tab
|
|
16
|
+
* BlobStore bytes (images, markdown), apart from records
|
|
17
|
+
*
|
|
18
|
+
* What every adapter promises, beyond each method's own words:
|
|
19
|
+
*
|
|
20
|
+
* - Records go in and come out as plain data, field for field (the types in
|
|
21
|
+
* src/core/model.ts). A record handed out is the caller's copy; changing it
|
|
22
|
+
* changes nothing stored, and neither does changing an object after it was
|
|
23
|
+
* written.
|
|
24
|
+
* - Lists come back in the order each method states, so every adapter answers
|
|
25
|
+
* alike. "Then by when it was written" means the order the records were
|
|
26
|
+
* inserted.
|
|
27
|
+
* - Failures are OrbitErrors (src/core/errors.ts) with the codes named here.
|
|
28
|
+
* A database's own message never reaches an answer: wrap it as
|
|
29
|
+
* `storage_failed`, `storage_busy` or `storage_full`, with the original as
|
|
30
|
+
* its `cause`.
|
|
31
|
+
* - Nothing here makes an id or reads a clock. The core gives every id and
|
|
32
|
+
* time, so the same input gives the same records on any adapter.
|
|
33
|
+
*/
|
|
34
|
+
export {};
|
package/llms.txt
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Starscape Orbit
|
|
2
|
+
|
|
3
|
+
> Orbit is a local-first kanban board that AI agents use for project management: tasks, tickets, plans, decisions, and lessons. The human watches the work move on a calm, live board. It's free and open source (AGPL-3.0), published on npm as `@starlove/orbit`, and needs Node.js 22.16+ (or 24).
|
|
4
|
+
|
|
5
|
+
If your user said something like "install and use Starscape Orbit", this page is for you, the agent. Follow the setup below, then use Orbit for this user's project tracking from now on.
|
|
6
|
+
|
|
7
|
+
## Set it up
|
|
8
|
+
|
|
9
|
+
1. **Check Node.js.** `node -v` must print 22.16 or newer (24 is best; 23 won't do). If it doesn't, ask the user before installing or upgrading Node.
|
|
10
|
+
2. **Install Orbit.** Run `npm install -g @starlove/orbit` (skip if `orbit --version` already prints 2 or later). Orbit keeps every board in its data folder, `~/.starscape/orbit`. If the user had Orbit 1.x (boards in `~/.orbit`), Orbit says their old boards are waiting: run `orbit migrate`, which copies them over and only reads the old folder.
|
|
11
|
+
3. **Connect yourself to Orbit's MCP server.** It's a stdio server started with `orbit mcp`. Use the entry for your harness:
|
|
12
|
+
- Claude Code: `claude mcp add --scope user orbit -- orbit mcp`
|
|
13
|
+
- Codex CLI: `codex mcp add orbit -- orbit mcp`
|
|
14
|
+
- Hermes Agent, in `~/.hermes/config.yaml`:
|
|
15
|
+
```yaml
|
|
16
|
+
mcp_servers:
|
|
17
|
+
orbit:
|
|
18
|
+
command: orbit
|
|
19
|
+
args: [mcp]
|
|
20
|
+
```
|
|
21
|
+
- Cursor (`~/.cursor/mcp.json`), Claude Desktop (`claude_desktop_config.json`), Gemini CLI (`~/.gemini/settings.json`), and most other clients:
|
|
22
|
+
```json
|
|
23
|
+
{ "mcpServers": { "orbit": { "command": "orbit", "args": ["mcp"] } } }
|
|
24
|
+
```
|
|
25
|
+
- VS Code's GitHub Copilot and Copilot CLI share `~/.copilot/mcp-config.json`: `{ "mcpServers": { "orbit": { "type": "stdio", "command": "orbit", "args": ["mcp"], "tools": ["*"] } } }`
|
|
26
|
+
|
|
27
|
+
Add `-e ORBIT_AGENT_NAME=<your name>` (or an `env` entry) so the board shows who did what.
|
|
28
|
+
|
|
29
|
+
Desktop apps often can't see the shell's PATH. If `orbit` isn't found, use absolute paths: set the command to the output of `command -v node`, and set the args to `["<output of npm root -g>/@starlove/orbit/lib/cli/orbit.js", "mcp"]`. Most clients load MCP servers at startup, so tell the user if they need to restart the app or open a new session.
|
|
30
|
+
4. **Start the board app** so the user can see it. If `curl -s http://127.0.0.1:13701/api/health` fails, run `orbit run -d`, which starts Orbit in the background. Give the user the URL, normally http://localhost:13701.
|
|
31
|
+
5. **Create a board for the project.** Send `boards` (orbit_read) to see what exists. If no board fits, send `boards new <project name>` (orbit_write). From a shell it's `orbit boards` and `orbit boards new <name>`.
|
|
32
|
+
6. **Remember it.** Save a durable note wherever you keep standing instructions (your memory, `AGENTS.md`, `CLAUDE.md`, rules files): *"Use Starscape Orbit for project management: tasks, tickets, kanban, plans, decisions. Board: `<slug>`. Start with `orbit view <slug>` (or orbit_read `view <slug>`); `help` lists the commands."* So every agent in a repo lands on its board, run `orbit boards link <slug>` in the repo and commit the `orbit.json` it writes (one line: `{ "board": "<slug>" }`); commands that name no board then use it. Otherwise, with several boards, name the board in each command (`view <slug>`, `<slug>#12`, `--board <slug>`), or make it the default for every folder on this computer with `orbit boards use <slug>`. Ask before editing files other people share.
|
|
33
|
+
7. **Tell the user it's ready.** Give them the board URL and the board name, and explain how to hand you work: "Move a card to AI Ready, or say 'work on ticket 12'."
|
|
34
|
+
|
|
35
|
+
## Run it on a server
|
|
36
|
+
|
|
37
|
+
Orbit has no login yet, so keep it on a private network.
|
|
38
|
+
|
|
39
|
+
- On the server: `npm install -g @starlove/orbit && DATA_DIR=/var/lib/orbit orbit run -d`. For restarts after a reboot, run it under systemd, or build the `Dockerfile` that ships with Orbit and run the container with a restart policy.
|
|
40
|
+
- Reach it privately with `tailscale serve --bg 13701` (only your tailnet can reach it) or an SSH tunnel: `ssh -N -L 13701:127.0.0.1:13701 user@server`.
|
|
41
|
+
- To connect an agent on another machine, use remote mode: the same MCP entry, plus the environment variables `ORBIT_MODE=remote`, `ORBIT_API_URL=<private URL>`, and optionally `ORBIT_BOARD=<slug>`. For example:
|
|
42
|
+
`claude mcp add orbit --scope user -e ORBIT_MODE=remote -e ORBIT_API_URL=https://orbit.your-tailnet.ts.net -e ORBIT_AGENT_NAME=claude -- orbit mcp`
|
|
43
|
+
- The `orbit` CLI takes the same variables, and every Orbit serves its own `/llms.txt` with the commands for that version.
|
|
44
|
+
- Never put Orbit on a public address.
|
|
45
|
+
|
|
46
|
+
## Day to day
|
|
47
|
+
|
|
48
|
+
Orbit speaks one small command language. Over MCP you get two tools, orbit_read and orbit_write, each taking one command line, with long text in `text`. In a shell the same commands are `orbit view`, `orbit take`, `orbit finish 12 -m "..."`. Cards are #n (in a shell write `orbit card 12`, because a word starting with `#` is a comment), lanes are named, priorities are words. AI Ready holds cards an agent may take: each says its goal and when it's done. The core loop:
|
|
49
|
+
|
|
50
|
+
1. `view` shows the board, one line per card.
|
|
51
|
+
2. `take` claims the next card in AI Ready, or `take #12` the card the user names. It returns the card with the board's rules and next steps.
|
|
52
|
+
3. If the goal or "done" is unclear, `ask #12` with what's missing, then stop.
|
|
53
|
+
4. `finish #12` with what changed and how you checked it. The card moves to Review for the human.
|
|
54
|
+
|
|
55
|
+
When the user mentions new work, `new <title>` makes a card instead of a private list. `help` lists every command; the guide linked below has the whole workflow. Never edit Orbit's SQLite files directly.
|
|
56
|
+
|
|
57
|
+
## Links
|
|
58
|
+
|
|
59
|
+
- [Agent guide](https://orbit.starscape.app/guide.md): the full workflow, every command, and the HTTP API
|
|
60
|
+
- [README](https://orbit.starscape.app/README.md): install, storage, backups, and self-hosting
|
|
61
|
+
- [npm package](https://www.npmjs.com/package/@starlove/orbit)
|
|
62
|
+
- [Source](https://github.com/brucestarlove/ss-orbit)
|
|
63
|
+
- [Live demo](https://orbit.starscape.app/app/): runs in the browser only, so agents can't connect to it
|