@loompad/cli 0.1.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 (177) hide show
  1. package/ARCHITECTURE.md +211 -0
  2. package/CHANGELOG.md +470 -0
  3. package/LICENSE +21 -0
  4. package/README.md +630 -0
  5. package/dist/adapters/base.d.ts +71 -0
  6. package/dist/adapters/base.js +187 -0
  7. package/dist/adapters/base.js.map +1 -0
  8. package/dist/adapters/bridges/antigravity.d.ts +53 -0
  9. package/dist/adapters/bridges/antigravity.js +106 -0
  10. package/dist/adapters/bridges/antigravity.js.map +1 -0
  11. package/dist/adapters/bridges/cdp.d.ts +82 -0
  12. package/dist/adapters/bridges/cdp.js +222 -0
  13. package/dist/adapters/bridges/cdp.js.map +1 -0
  14. package/dist/adapters/bridges/gui-chat.d.ts +113 -0
  15. package/dist/adapters/bridges/gui-chat.js +498 -0
  16. package/dist/adapters/bridges/gui-chat.js.map +1 -0
  17. package/dist/adapters/bridges/kiro.d.ts +19 -0
  18. package/dist/adapters/bridges/kiro.js +22 -0
  19. package/dist/adapters/bridges/kiro.js.map +1 -0
  20. package/dist/adapters/bridges/profiles.d.ts +82 -0
  21. package/dist/adapters/bridges/profiles.js +125 -0
  22. package/dist/adapters/bridges/profiles.js.map +1 -0
  23. package/dist/adapters/claude-code.d.ts +26 -0
  24. package/dist/adapters/claude-code.js +209 -0
  25. package/dist/adapters/claude-code.js.map +1 -0
  26. package/dist/adapters/codex.d.ts +51 -0
  27. package/dist/adapters/codex.js +291 -0
  28. package/dist/adapters/codex.js.map +1 -0
  29. package/dist/adapters/echo.d.ts +20 -0
  30. package/dist/adapters/echo.js +79 -0
  31. package/dist/adapters/echo.js.map +1 -0
  32. package/dist/adapters/grok.d.ts +61 -0
  33. package/dist/adapters/grok.js +217 -0
  34. package/dist/adapters/grok.js.map +1 -0
  35. package/dist/adapters/index.d.ts +9 -0
  36. package/dist/adapters/index.js +33 -0
  37. package/dist/adapters/index.js.map +1 -0
  38. package/dist/adapters/opencode.d.ts +64 -0
  39. package/dist/adapters/opencode.js +426 -0
  40. package/dist/adapters/opencode.js.map +1 -0
  41. package/dist/cli/common.d.ts +11 -0
  42. package/dist/cli/common.js +40 -0
  43. package/dist/cli/common.js.map +1 -0
  44. package/dist/cli/doctor.d.ts +12 -0
  45. package/dist/cli/doctor.js +181 -0
  46. package/dist/cli/doctor.js.map +1 -0
  47. package/dist/cli/index.d.ts +8 -0
  48. package/dist/cli/index.js +641 -0
  49. package/dist/cli/index.js.map +1 -0
  50. package/dist/cli/tui-model.d.ts +73 -0
  51. package/dist/cli/tui-model.js +312 -0
  52. package/dist/cli/tui-model.js.map +1 -0
  53. package/dist/cli/tui.d.ts +15 -0
  54. package/dist/cli/tui.js +451 -0
  55. package/dist/cli/tui.js.map +1 -0
  56. package/dist/cli/ui.d.ts +10 -0
  57. package/dist/cli/ui.js +135 -0
  58. package/dist/cli/ui.js.map +1 -0
  59. package/dist/core/ades.d.ts +79 -0
  60. package/dist/core/ades.js +130 -0
  61. package/dist/core/ades.js.map +1 -0
  62. package/dist/core/baton.d.ts +37 -0
  63. package/dist/core/baton.js +92 -0
  64. package/dist/core/baton.js.map +1 -0
  65. package/dist/core/brain-extract.d.ts +111 -0
  66. package/dist/core/brain-extract.js +248 -0
  67. package/dist/core/brain-extract.js.map +1 -0
  68. package/dist/core/brain-index.d.ts +128 -0
  69. package/dist/core/brain-index.js +338 -0
  70. package/dist/core/brain-index.js.map +1 -0
  71. package/dist/core/brain.d.ts +214 -0
  72. package/dist/core/brain.js +425 -0
  73. package/dist/core/brain.js.map +1 -0
  74. package/dist/core/claude-cli.d.ts +12 -0
  75. package/dist/core/claude-cli.js +47 -0
  76. package/dist/core/claude-cli.js.map +1 -0
  77. package/dist/core/distill.d.ts +19 -0
  78. package/dist/core/distill.js +88 -0
  79. package/dist/core/distill.js.map +1 -0
  80. package/dist/core/eventlog.d.ts +43 -0
  81. package/dist/core/eventlog.js +211 -0
  82. package/dist/core/eventlog.js.map +1 -0
  83. package/dist/core/git.d.ts +203 -0
  84. package/dist/core/git.js +460 -0
  85. package/dist/core/git.js.map +1 -0
  86. package/dist/core/logbook.d.ts +59 -0
  87. package/dist/core/logbook.js +118 -0
  88. package/dist/core/logbook.js.map +1 -0
  89. package/dist/core/memory.d.ts +34 -0
  90. package/dist/core/memory.js +124 -0
  91. package/dist/core/memory.js.map +1 -0
  92. package/dist/core/notify.d.ts +11 -0
  93. package/dist/core/notify.js +30 -0
  94. package/dist/core/notify.js.map +1 -0
  95. package/dist/core/projection.d.ts +20 -0
  96. package/dist/core/projection.js +96 -0
  97. package/dist/core/projection.js.map +1 -0
  98. package/dist/core/registry.d.ts +76 -0
  99. package/dist/core/registry.js +147 -0
  100. package/dist/core/registry.js.map +1 -0
  101. package/dist/core/router.d.ts +42 -0
  102. package/dist/core/router.js +99 -0
  103. package/dist/core/router.js.map +1 -0
  104. package/dist/core/routes.d.ts +93 -0
  105. package/dist/core/routes.js +399 -0
  106. package/dist/core/routes.js.map +1 -0
  107. package/dist/core/search.d.ts +63 -0
  108. package/dist/core/search.js +0 -0
  109. package/dist/core/search.js.map +1 -0
  110. package/dist/core/setup.d.ts +70 -0
  111. package/dist/core/setup.js +250 -0
  112. package/dist/core/setup.js.map +1 -0
  113. package/dist/core/suggestions.d.ts +15 -0
  114. package/dist/core/suggestions.js +38 -0
  115. package/dist/core/suggestions.js.map +1 -0
  116. package/dist/core/worktree.d.ts +32 -0
  117. package/dist/core/worktree.js +83 -0
  118. package/dist/core/worktree.js.map +1 -0
  119. package/dist/daemon/app-page.d.ts +32 -0
  120. package/dist/daemon/app-page.js +6363 -0
  121. package/dist/daemon/app-page.js.map +1 -0
  122. package/dist/daemon/auth.d.ts +51 -0
  123. package/dist/daemon/auth.js +135 -0
  124. package/dist/daemon/auth.js.map +1 -0
  125. package/dist/daemon/board.d.ts +117 -0
  126. package/dist/daemon/board.js +202 -0
  127. package/dist/daemon/board.js.map +1 -0
  128. package/dist/daemon/brand-icons.d.ts +29 -0
  129. package/dist/daemon/brand-icons.js +44 -0
  130. package/dist/daemon/brand-icons.js.map +1 -0
  131. package/dist/daemon/client.d.ts +127 -0
  132. package/dist/daemon/client.js +237 -0
  133. package/dist/daemon/client.js.map +1 -0
  134. package/dist/daemon/geist-font.d.ts +10 -0
  135. package/dist/daemon/geist-font.js +783 -0
  136. package/dist/daemon/geist-font.js.map +1 -0
  137. package/dist/daemon/guards.d.ts +18 -0
  138. package/dist/daemon/guards.js +40 -0
  139. package/dist/daemon/guards.js.map +1 -0
  140. package/dist/daemon/linear.d.ts +59 -0
  141. package/dist/daemon/linear.js +117 -0
  142. package/dist/daemon/linear.js.map +1 -0
  143. package/dist/daemon/push.d.ts +22 -0
  144. package/dist/daemon/push.js +58 -0
  145. package/dist/daemon/push.js.map +1 -0
  146. package/dist/daemon/runtime.d.ts +299 -0
  147. package/dist/daemon/runtime.js +1016 -0
  148. package/dist/daemon/runtime.js.map +1 -0
  149. package/dist/daemon/server.d.ts +125 -0
  150. package/dist/daemon/server.js +1728 -0
  151. package/dist/daemon/server.js.map +1 -0
  152. package/dist/daemon/tasks.d.ts +153 -0
  153. package/dist/daemon/tasks.js +264 -0
  154. package/dist/daemon/tasks.js.map +1 -0
  155. package/dist/daemon/terminals.d.ts +82 -0
  156. package/dist/daemon/terminals.js +372 -0
  157. package/dist/daemon/terminals.js.map +1 -0
  158. package/dist/index.d.ts +20 -0
  159. package/dist/index.js +20 -0
  160. package/dist/index.js.map +1 -0
  161. package/dist/sdk.d.ts +17 -0
  162. package/dist/sdk.js +17 -0
  163. package/dist/sdk.js.map +1 -0
  164. package/dist/types.d.ts +236 -0
  165. package/dist/types.js +18 -0
  166. package/dist/types.js.map +1 -0
  167. package/docs/SETUP.md +265 -0
  168. package/docs/adapters.md +132 -0
  169. package/docs/design-system.md +245 -0
  170. package/docs/img/board-linear.png +0 -0
  171. package/docs/img/board-projects.png +0 -0
  172. package/docs/img/brain.png +0 -0
  173. package/docs/img/settings.png +0 -0
  174. package/docs/img/workspace.png +0 -0
  175. package/docs/integration-notes.md +76 -0
  176. package/docs/proposals/brain.md +262 -0
  177. package/package.json +84 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,470 @@
1
+ # Changelog
2
+
3
+ All notable changes to Loom are documented here.
4
+
5
+ The format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Hardening (production-readiness pass)
11
+
12
+ - **Closed a DNS-rebinding path to the admin token.** `GET /api/bootstrap` handed
13
+ the admin token to any loopback socket; it now also requires a loopback `Host`
14
+ header, so a malicious page (whose hostname rebinds to 127.0.0.1 but is still
15
+ sent as the `Host`) is refused — while the genuine local console still boots.
16
+ - **WebSocket token out of the URL.** The durable bearer token rode in `/ws?token=`
17
+ (browser history, proxy logs); it now travels in a `loom.bearer.<token>`
18
+ subprotocol header, with `?token=` kept as a fallback for the CLI/native clients.
19
+ - **git flag-injection guard.** A ref/branch beginning with `-` (e.g. `-f`, which
20
+ would force-discard the working tree) is rejected in `checkout` and worktree add.
21
+ - **No phantom daemon on an occupied port.** `loom daemon` on a taken port now
22
+ reports "port already in use" and exits non-zero, instead of Express firing the
23
+ listen callback on EADDRINUSE and printing a false "listening".
24
+ - The **security model** section of the README is rewritten to match: the local
25
+ admin bootstrap and its rebinding check, the shared-host caveat, and the honest
26
+ note that a paired client can open a real shell (arbitrary code as the daemon
27
+ user) — bearer + tailnet is the boundary, so pair only devices you control.
28
+
29
+ ### `loom tui` — a tabbed workspace, not just a thread
30
+
31
+ - The TUI is now four views: **Thread** (the streamed conversation), **Board**
32
+ (agents, your cards, issues and PRs in the four flow columns), **Brain** (the
33
+ memory the project has learned, grouped by kind with failures first and who
34
+ learned each), and **Diff** (the working tree — changed files and a colourised
35
+ patch). **`shift+tab`** cycles them, `/board /brain /diff /thread` and the
36
+ `ctrl+p` palette jump straight to one, `pgup/pgdn` scrolls, and the viewport
37
+ tracks a terminal resize. Everything the single pane did — routes, handoff,
38
+ `/pair` QR, interrupt, the palette — stays. Backed by real data (new
39
+ `brain`/`board` daemon-client methods) with pure, unit-tested formatters.
40
+
41
+ ### Connect a phone — a QR from inside the app
42
+
43
+ - A **Connect a phone** button beside the terminal opens a modal with a QR (or a
44
+ copy link) and a **Local network / Tailnet** toggle. When the daemon is still
45
+ localhost-only, **Enable phone access** adds a *second listener* on the chosen
46
+ LAN/tailnet IP — localhost is never torn down, so there's no dropped-socket
47
+ window and none of the EADDRINUSE races a live rebind to `0.0.0.0` hits. The
48
+ local (loopback) window bootstraps the admin token, so it can pair phones with
49
+ no pairing dance of its own; a paired phone is never admin.
50
+ - **Client errors reach the Console.** `window.onerror` and unhandled rejections
51
+ now stream into the same Console tab as the daemon's own logs, with the error
52
+ dot — nothing dies silently in the browser.
53
+
54
+ ### Brain — stronger injection, and it's genuinely shared
55
+
56
+ - The learned memory reaches every agent, not just Claude: the brief rides in
57
+ Grok's `--rules` (a real system channel), and for Codex/OpenCode it's framed as
58
+ an unmissable `LOOM SESSION MEMORY — authoritative, read first` block instead of
59
+ loose preamble. The brain is the **project's** — a fact one agent learns reaches
60
+ whoever takes the baton next ([`brain-shared` test](test/brain-shared.test.ts):
61
+ five agents, five prompts, one shared memory). An opt-in eval
62
+ (`LOOM_TEST_REAL=1`) checks a real model actually *uses* an injected brief.
63
+
64
+ ### Native search — one palette over everything (⌘K)
65
+
66
+ - **⌘K / Ctrl+K** opens a command palette from anywhere, even mid-type. One box
67
+ searches across **commands** (go to Thread/Board/Brain, open a panel, New task,
68
+ Settings, toggle terminal/theme…), **agents** (talk to one), **files** and
69
+ **code** (the project's own `/find` + `/grep`), **conversations**, and
70
+ **worktrees** (jump to one — it `cd`s there in the terminal). Commands, agents
71
+ and worktrees filter instantly; the daemon-backed sections stream in as you
72
+ type. ↑↓ navigate a flat list, ↵ acts, esc closes. A ⌘K affordance sits in the
73
+ sidebar search.
74
+
75
+ ### Connect GitHub from the status bar
76
+
77
+ - The bottom bar shows whether `gh` is signed in and as whom. When it isn't, a
78
+ **Connect GitHub** button runs the interactive `gh auth login --web` in the
79
+ real terminal (Loom never touches the token — gh stores it) and polls until
80
+ you're in, then lights the board up. The whole GitHub half (PRs, Projects,
81
+ review) depends on this being true, so it lives where you can see it.
82
+
83
+ ### GitHub & Linear, native
84
+
85
+ - **One board, three sources.** A segmented control switches the Board between
86
+ **GitHub** (the live kanban), **Projects** (browse the owner's GitHub Project v2
87
+ boards, items laid out by their Status column), and **Linear** (recent issues).
88
+ - **Review and approve PRs in place.** Open a PR's diff in-app and post the three
89
+ reviewer verbs — comment, request changes, approve — through your own `gh`, signed
90
+ as you. Approve asks first, because it publishes.
91
+ - **Open a worktree from any task.** One click cuts a checked-out branch in its own
92
+ sibling directory: a PR worktree checks the branch out (forks included, via
93
+ `gh pr checkout` into a detached worktree); an issue worktree cuts a fresh branch.
94
+ - **File a Linear issue with a team selector.** Loom reads `LINEAR_API_KEY` from the
95
+ daemon's own environment and never stores it — the same bet it makes with `gh`. No
96
+ key → an honest "not connected", never a dead form.
97
+
98
+ ### Settings — one sectioned screen
99
+
100
+ - The lone Setup modal is now **Settings**, with a nav rail: **Setup** (what the
101
+ machine still needs), **Diagnostics** (`loom doctor`, run live), **Updates** (build
102
+ rev + how far the checkout is behind its remote), **Preferences** (theme, brain
103
+ extractor, handoff brief style, default agent — read live, no restart), **Devices**
104
+ (paired clients, revoke, pair), and **About**.
105
+ - `/api/setup`, `/api/doctor`, and `/api/updates` moved behind the auth wall — they
106
+ inventory the machine, which matters the moment the daemon binds past localhost.
107
+
108
+ ### The Brain learns on its own (mem0-style)
109
+
110
+ - After each turn a small Claude reads what changed and files typed memory **units**
111
+ — constraint, decision, convention, fact, failure — reconciled on write
112
+ (add / update / forget, never a growing blob), the approach mem0 pioneered, adapted
113
+ to Loom's event log. Every unit's evidence is verified against the turn before it's
114
+ kept. Retrieval unions an entity index with BM25.
115
+ - The **Brain tab** shows the learned units by kind; the extractor toggles off per
116
+ project in Settings.
117
+
118
+ ### Composer
119
+
120
+ - The agent chip is a real **picker** now — click it to switch which agent the
121
+ composer talks to, instead of hunting the sidebar. Each control (attach, switch
122
+ agent, model, send) is its own labelled button, and the message box starts at two
123
+ lines, grows, then scrolls.
124
+
125
+ ### Real agent output
126
+
127
+ - Agent **thinking** is shown in a collapsible block, kept apart from the reply, and
128
+ messages render as **rich markdown** (headings, lists, code, inline code) instead of
129
+ raw text.
130
+
131
+ ### Board — everything in flight, and Tasks folded into it
132
+
133
+ - **The Tasks tab is gone.** The Board covers it: search issues and pull
134
+ requests from the board itself, in GitHub's own query language
135
+ (`is:issue is:open label:bug`), and **Start** still hands an issue to an
136
+ agent. Tabs are Thread | Board | Brain.
137
+ - **Add your own cards.** `+ Task`, or the `+` in any column — including Ready
138
+ to merge. These are yours, so the column *is* the state: dragging one really
139
+ moves it and it persists. That's the difference from a PR card, whose truth
140
+ belongs to GitHub and can only be pinned. Click a card's title to retitle it.
141
+ - Issues only appear when you search for them — a repo's whole backlog would
142
+ bury the work actually in flight. And asking for issues no longer also hands
143
+ back unrelated PRs: `gh pr list --search "is:issue …"` ignores the qualifier
144
+ and returns open PRs, so the board now asks for what you actually asked for.
145
+
146
+ ### Board — everything in flight, in one place
147
+
148
+ - A **Board** tab replaces Routes: four columns — working → needs you → in
149
+ review → ready to merge — holding every piece of live work in the project.
150
+ - Cards are derived from real state, never stored. Loom supplies the ones with
151
+ no PR yet (which agent is running, which is blocked on a question); the
152
+ project's GitHub remote supplies the rest through your own `gh`: draft,
153
+ review pending, CI failed, changes requested, approved. A card shows the
154
+ agent's own logo, the branch, and links to the PR.
155
+ - **Drag a card** and it stays where you put it (per project, across reloads).
156
+ A pin only moves a card — the badge keeps reporting what GitHub and the daemon
157
+ actually say, because a drag can't approve a review or turn a red build green.
158
+ Drop it back where its state says it belongs, or click its `pinned` mark, and
159
+ it goes back to being placed by reality.
160
+ - No remote, or no `gh`? The agent half of the board is ours and still real —
161
+ it renders, with a line saying why the pull requests are missing.
162
+ - **Routes lost its tab, not its home**: named pipelines and custom step lists
163
+ both live in the New task modal (several agents *is* a pipeline), and live
164
+ route state plus abort live in the Source Control rail. The mobile route sheet
165
+ is untouched.
166
+
167
+ ### Chats, and roles you name yourself
168
+
169
+ - **A project holds conversations.** The sidebar nests chats under each
170
+ project; create, rename (double-click), and forget them. Everything else
171
+ stays shared — one brain, one baton, one working tree. Only the talking is
172
+ split. Forgetting a chat unlists it and nothing more: the log is append-only
173
+ and the brain is built from all of it, so deleting a conversation shouldn't
174
+ quietly rewrite what the project decided.
175
+ - Agents moved to the rail's roster, which is where they belong — an agent
176
+ works the whole project, not one conversation.
177
+ - **Roles are free text.** Not planner|executor|reviewer|general any more: call
178
+ an agent "architect" or "the one that writes docs". Click the role and type.
179
+ Those three names still mean something if you use them (they seed the default
180
+ ship pipeline, the rules router prefers a reviewer last, a route step matches
181
+ by role) and nothing if you don't.
182
+ - The event log gained a `chat` column. **The first cut of that migration
183
+ destroyed logs**: the chat index was created before the column was added,
184
+ sqlite threw "no such column", and `EventLog.open` caught it and quietly
185
+ started an *empty* JSONL log beside a database full of history. Migrate
186
+ first, then index — and that catch now only covers the one case it was for
187
+ (a runtime with no `node:sqlite`). If the module is there and the log won't
188
+ open, it throws: losing the thread is worse than failing loudly.
189
+
190
+ ### ADE brand marks
191
+
192
+ - Claude Code, Antigravity, opencode, Kiro and Codex now appear as their own
193
+ logos wherever an agent does: the sidebar, the thread, agent chips, the New
194
+ task modal, and board cards. Rendered from
195
+ [@lobehub/icons](https://github.com/lobehub/lobe-icons) (MIT) by
196
+ `scripts/gen-brand-icons.mjs` and frozen into an SVG sprite — the web app has
197
+ no build step and no CDN, so it can't import React components.
198
+ - Keyed by adapter *kind*, not by name: you can call an agent anything, but its
199
+ kind is what it is. A kind with no mark (a custom adapter, `echo`) keeps the
200
+ hue monogram rather than borrowing someone else's brand.
201
+ - **Bridges are visible at last.** Antigravity is a bridge, and every view
202
+ filtered bridges out — so a configured bridge rendered nowhere and Loom looked
203
+ like it had ignored your config. Bridges now show in the sidebar, marked as
204
+ bridges and not clickable, because they never hold the baton.
205
+
206
+ ### Tasks — start work from a real GitHub issue
207
+
208
+ - New **Tasks** tab per project: the repo's open issues and pull requests in a
209
+ sortable table (id, title, author, labels, assignees, status, updated), with
210
+ an `Issues`/`PRs` switch, `Open` / `Assigned to me` filters, a query box that
211
+ accepts GitHub's own search syntax (`assignee:@me is:issue is:open`), and
212
+ pagination. Labels wear the colours GitHub reports for them.
213
+ - **Start** on any row opens Create task with the issue number, title, and URL
214
+ already drafted, so a GitHub issue becomes an agent task in two clicks.
215
+ - Data comes from **your own `gh` CLI**, which already holds your auth — Loom
216
+ needs no token, no OAuth app, no PAT of its own. It shells out the same way
217
+ the adapters shell out to the coding agents you have installed.
218
+ - When it can't list, it says why (`gh` not installed, signed out, or no GitHub
219
+ remote) instead of showing an empty table that reads as "no issues". The
220
+ fetch is capped at 60, and a capped list says so rather than letting the last
221
+ page imply it's the last issue. GitLab and Linear appear disabled — the row
222
+ shows which providers exist and which one Loom can actually read.
223
+
224
+ - **The phone gets Tasks too**, which is where the feature always wanted to
225
+ live: see an issue on the train, tap it, confirm, and an agent is working
226
+ before you look up. Same daemon endpoint, same honest unavailable states. It
227
+ asks for confirmation first — the desktop shows the brief in an editable field
228
+ before it goes, and a tap on a scrolling list has no such beat.
229
+
230
+ ### New project
231
+
232
+ - A **New project** button in the sidebar (shortcut <kbd>P</kbd>) with a proper
233
+ modal, replacing the inline path field. It reports which ADEs were detected
234
+ on the host after registering, and refuses a bad path out loud.
235
+ - In the desktop app the folder comes from a **native macOS picker**; in a
236
+ browser the path is typed, since the daemon may be on another host. The
237
+ preload exposes only that one call — no `require`, no ipc passthrough.
238
+
239
+ ### Fixed
240
+
241
+ - The right rail showed the project you just navigated away from: it renders
242
+ from `state.project`, which only a fetch filled in, so every switch left it
243
+ one project behind until you touched it.
244
+ - **Enter did nothing on the pairing screen.** Paste a token, press Enter — the
245
+ entire gesture that screen exists for — and nothing happened; only the button
246
+ worked. Same in the route form. Both now submit on Enter, like every other
247
+ field in the app.
248
+ - The Tasks `Issues`/`PRs` toggle was dead while the first `gh` fetch was in
249
+ flight — the loading branch rendered the buttons but returned before wiring
250
+ them, so they were inert for exactly as long as anyone would be looking at
251
+ them.
252
+ - The GitHub mark in the Tasks provider row was an enabled button with hover and
253
+ a pointer cursor, and no handler. It's the only provider Loom reads, so it's
254
+ now the indicator it always was, not a control that does nothing.
255
+ - Dead code and CSS removed: an unused `dockShowing`, two write-only state hooks
256
+ no consumer ever read, and five rules for classes nothing renders. `route()`
257
+ now also clears `state.retheme`, which alone survived a view teardown holding
258
+ the previous render's terminals.
259
+ - **Tasks blamed a missing remote for every failure it didn't recognise.** A
260
+ timeout, a 500, a rate limit, or a private repo you lack scope for all told
261
+ you to "add a GitHub remote and reload" — with the remote right there. gh's
262
+ failures are now sorted by what gh actually prints, and the panel shows gh's
263
+ own words. Fixes a live mis-read too: gh's message for a *non-GitHub* remote
264
+ mentions `gh auth login`, so a GitLab remote was reported as "signed out".
265
+ - The terminal routes answered **429 "too many requests" for any failure to
266
+ start a shell**, and `/term/input` could hand a JSON client Express's HTML
267
+ error page. Only the session cap is a 429 now, and it says what the cap is.
268
+ - `cliAvailable()` had no timeout. It runs in front of HTTP handlers (Tasks
269
+ probes `gh` on every request), so one wedged `gh --version` hung that request
270
+ forever with no reply. Bounded at 5s, and the child is reaped.
271
+
272
+ ### Documentation
273
+
274
+ - **Every SDK example told you to import from `loom-agents/sdk` — a package that
275
+ has never existed.** It's `threadloom/sdk`. `test/docs.test.ts` now checks that
276
+ the docs only import packages that resolve, and only names the SDK exports.
277
+ - `ARCHITECTURE.md` is the pre-build design record, and read as though it were
278
+ the current map: two surfaces instead of four, an iOS-first app over APNs
279
+ (it's Android-first over Expo), and settled questions still framed as risks.
280
+ It now says what it is, and the corrections are marked inline.
281
+ - `desktop/README.md` claimed the bootstrap was unit-tested (it wasn't — now it
282
+ is) and that `main.js` "only creates the window" (it owns the menu and the
283
+ folder-picker IPC).
284
+ - The `LOOM_*` environment variables are documented, and Loom's own
285
+ `LOOM_TERMINAL=1` marker is written down.
286
+
287
+ ### Terminal — real PTYs
288
+
289
+ - Terminals now run on a real pseudo-terminal via **node-pty**, rendered with
290
+ **xterm.js**: the shell is on a tty, so it draws its own prompt, echoes, and
291
+ job control works — `^C`/`^Z`, `less`, `vim`, `htop`, window size. Verified
292
+ end to end in the desktop app: `$(tty)` resolves to a real device, `stty`
293
+ reports the fitted window, and `vi` repaints the alternate screen.
294
+ - node-pty is an **optionalDependency** with a probing loader, so a machine
295
+ that can't build it still installs Loom and quietly gets the previous
296
+ pipe-backed shell (`cd`/vars persist, `^C` works). `npm i -g threadloom`
297
+ never breaks. `LOOM_NO_PTY=1` forces the fallback, and CI runs the suite
298
+ both ways so it can't rot.
299
+ - Fixes a node-pty packaging bug: its prebuilt `spawn-helper` ships without the
300
+ executable bit, so every spawn died with a bare "posix_spawnp failed". The
301
+ loader repairs it, and proves it can spawn rather than trusting the require.
302
+ - Terminal input now travels over the project WebSocket (a tty needs a
303
+ round-trip per keystroke); sessions keep scrollback and replay it, so a
304
+ reload rejoins the session it left. Adds `/term/resize`, tab titles from OSC,
305
+ clipboard keys, and reports the active mode from `/api/health`.
306
+ - Terminal logic moved out of `server.ts` into `src/daemon/terminals.ts`.
307
+
308
+ ### Security
309
+
310
+ - **A symlink inside a project could read files anywhere on disk.** The
311
+ Explorer endpoints resolved paths with `path.resolve`, which resolves
312
+ straight *through* symlinks, so the sandbox only stopped lexical `../`
313
+ traversal. Containment is now verified twice — lexically and again against
314
+ `fs.realpathSync`. Found by the new workspace tests.
315
+
316
+ ### Testing
317
+
318
+ - `test/workspace.test.ts` (34 tests) covers the surfaces the desktop UI is
319
+ built on and previously had none: Explorer listing/reading/find, the sandbox
320
+ (traversal, absolute paths, symlink escape, unauthenticated access), and the
321
+ terminal end-to-end (streams output, exit codes, no sentinel leakage, `cd`
322
+ and variables persisting, terminal isolation, and Ctrl+C giving exit 130
323
+ while the shell survives).
324
+ - `test/desktop-app.test.ts` covers the Electron bootstrap, which had claimed to
325
+ be unit-tested without being it: the build-rev fingerprint (including that a
326
+ UI-only rebuild moves it), the stale-daemon decision (and that an
327
+ un-fingerprintable daemon is never killed), and the pairing handshake —
328
+ against a fake daemon, so no test can spawn or kill a real one. The
329
+ rev-mirrors-the-daemon check imports the built daemon's own `BUILD_REV`
330
+ rather than re-deriving it, so the two can't drift in agreement.
331
+ - **Two tests couldn't fail**, which is worse than not having them. The
332
+ interrupt test passed in pty mode with `PtySession.interrupt()` gutted: a pty
333
+ echoes what you type, so it matched `/alive/` on the echo of `echo alive`
334
+ while `sleep 30` still held the shell. And `LOOM_EXPECT_PTY` was referenced
335
+ once, set nowhere, and inverted — no value of it could fail the pty suite
336
+ when node-pty was absent, so a failed build (it's optional, and has no Linux
337
+ prebuild) would have downgraded every pty test to a no-op with CI green.
338
+ `LOOM_EXPECT_PTY=1` now asserts the backend and CI sets it; that run confirms
339
+ the runner really does build node-pty.
340
+ - `test/tasks.test.ts` locks gh's failure classification against the strings gh
341
+ actually prints, and `test/docs.test.ts` checks the docs only import things
342
+ that resolve. Every new test above was checked by breaking the code and
343
+ watching it go red.
344
+ - CI now typechecks `app/`, which nothing was compiling.
345
+ - Suite: 96 → 179.
346
+
347
+ ### Accessibility
348
+
349
+ - Every icon-only control now carries an `aria-label`, mirrored from its
350
+ tooltip by an observer so string-rendered UI can't miss one. Verified: 17
351
+ icon-only controls on desktop, 6 on mobile, none unnamed.
352
+
353
+ ### Design — quiet graphite (2026-07-16)
354
+
355
+ - Every surface redesigned on one system adapted from
356
+ [Orca](https://github.com/stablyai/orca) (MIT): neutral monochrome tokens,
357
+ 1px hairlines, three elevation tiers with a glass floating layer, and color
358
+ reserved for state — thread cyan (live), shuttle magenta (baton), selvage
359
+ edges per agent. Spec in `docs/design-system.md`.
360
+ - Web app: light + dark themes with a persisted in-app toggle, Geist variable
361
+ type served by the daemon at `/app/fonts/geist.woff2` (SIL OFL 1.1, embedded
362
+ — no CDN), SVG icon set replacing emoji, Orca button/input/card/chip
363
+ variants, sleek scrollbars, and a readable centered thread column.
364
+ - Desktop web shell (≥900px) is the full Orca workspace: project groups with
365
+ nested agent rows in the sidebar (status dots, baton badge, click-to-target),
366
+ a tab strip over the pane (Thread | Changes | Brain | Routes), per-file diff
367
+ cards with add/delete washes, a Source-control right rail (≥1200px) whose
368
+ file rows jump to their diff, and a status bar (websocket liveness, host,
369
+ baton, working count, total spend).
370
+ - Desktop shell: Orca window chrome — canvas-colored background, macOS
371
+ traffic lights centered in the app's own top strip, 600×400 minimums,
372
+ restyled failure page.
373
+ - Phone app: graphite surfaces, near-white primary CTA, accessory-key agent
374
+ chips, session top bar + neutral-underline tabs, command-dock composer with
375
+ an arming send button, diff washes, numbered pairing steps.
376
+ - Desktop chrome tightened to Orca's: tabs live in the 40px top strip beside
377
+ the project context; the sidebar gains a Search row (filters projects and
378
+ agents), an add-project action, and a bottom utility rail.
379
+ - Split workspace: the Changes pane docks beside the Thread (persisted
380
+ toggle, default on ≥1280px) under a project/branch breadcrumb; diff cards
381
+ gain old/new line-number gutters; `turn_diff` events render as expandable
382
+ terminal-style `Update(n files)` cards; the thread tops with an agent
383
+ header block (monogram, role, baton, project dir); sidebar projects wear
384
+ hash-hued repo glyphs and the status bar a real spend meter. `BUILD_REV`
385
+ now also hashes the served app so UI-only rebuilds bump the rev.
386
+ - Right rail is a 4-view panel (Explorer / Search / Source Control / Tasks)
387
+ with a top icon switcher, open by default on the Explorer. Explorer is a
388
+ lazy project file tree (folders expand on click; files open in the dock —
389
+ changed files as a diff, others as a read-only preview). Search finds files
390
+ by name; Source Control lists the branch + changed files; Tasks holds a
391
+ per-project New-task button and the agent roster. Backed by new sandboxed
392
+ daemon endpoints: `GET /files`, `/file` (400KB cap), `/find` (200 results).
393
+ - The Changes/diff view is no longer an always-on split — it's a dock to the
394
+ right of the chat that opens only when you click a change (an `Update(…)`
395
+ card in the thread, or a file in Explorer / Search / Source Control) and
396
+ closes with an X. The rail itself collapses from the tab strip (PanelRight).
397
+ - Real terminals: each tab owns a long-lived shell in the project directory
398
+ (`POST /term/open|input|signal|close`, streamed over the project
399
+ WebSocket), so `cd` and exported vars persist between commands. A sentinel
400
+ printed after each command carries the exit code and cwd — the daemon
401
+ strips it from the stream, so the prompt tracks the live directory and
402
+ non-zero exits surface. Ctrl+C signals the shell's process group and the
403
+ shell survives via a no-op INT trap, giving real `^C → exit 130 → prompt`
404
+ behaviour. Includes ANSI colour rendering, ArrowUp/Down history, Ctrl+L,
405
+ click-to-focus, multiple tabs, drag-resize, and a Ctrl+backtick toggle.
406
+ - Every column is drag-resizable with persisted widths and double-click to
407
+ reset — sidebar, diff dock, and right rail, each clamped so the chat can't
408
+ be squeezed out.
409
+ - New Task flow (Orca's Create Worktree): a sidebar action, the Tasks rail
410
+ view, and the `n` shortcut open a modal to pick a project, a task, and
411
+ **one ADE or several** — one agent messages it directly; several run it as
412
+ a pipeline hop to hop (e.g. claude → codex).
413
+ - Phone home rebuilt to the Orca mobile layout: Welcome-back hero, stat
414
+ tiles (Projects / Agents / Spend), a Daemon card, a Resume card, hue-glyph
415
+ project cards, and quick-action pills.
416
+ - Fixed: the desktop web shell now renders the hash-addressed project on
417
+ first load; live websocket frames buffer until history hydrates (an early
418
+ event could previously wipe the rendered backlog).
419
+ - Fixed: the desktop shell could open yesterday's UI. `BUILD_REV` is now a
420
+ content hash (mtimes skew across runtimes on exFAT), the shell restarts a
421
+ stale daemon before loading, spawns the daemon under a real Node runtime
422
+ (Electron's bundled Node predates `node:sqlite`, silently degrading the
423
+ event store), and `/app` is served `Cache-Control: no-store`.
424
+
425
+ ## [0.1.0] — 2026-07-15
426
+
427
+ ### Core engine
428
+
429
+ - Scaffold TypeScript project with verified integration notes.
430
+ - Event log (SQLite + JSONL), agent registry, baton, projections, suggestions, and notify subsystem.
431
+ - Shared claude-cli helper and LLM-synthesized projections (opt-in, template fallback).
432
+ - Echo, claude-code, and opencode adapters with antigravity bridge and factory.
433
+ - Auto-capture `Decision:` lines into shared memory; handoff events snapshot outgoing tree state.
434
+ - Cost telemetry — per-agent ledger rehydrated from the log, route cost attribution, `loom costs`, $ on board/TUI/app.
435
+ - Adapter SDK guide, README, MIT license, and architecture design spec.
436
+
437
+ ### Routing
438
+
439
+ - Multi-hop route engine — role/id pipelines, pause-on-question, auto-resume, abort, timeouts, bridge refresh every hop.
440
+ - Dynamic mode — LLM router picks each hop (claude headless, rules fallback), hop budget, reasons in `route_step`; `loom route auto --router --max-hops`.
441
+ - Per-step instructions in named routes (`{step, instruction}`); rich pipeline docs and LLM projections.
442
+ - `loom route` / `loom routes` CLI — live follow, `--status`, `--abort`, `--detach`, route events in chat and board.
443
+ - Route picker sheet on the phone app — auto/named/custom dropdown, start+abort, hop reasons in banner.
444
+ - Routing guide covering pipelines, pause/resume, and precedence rules.
445
+
446
+ ### Surfaces (TUI / CLI / phone app)
447
+
448
+ - Full-screen TUI as the default command — tab-based agent switching, slash commands, live stream, route progress, in-TUI QR pairing.
449
+ - Ctrl+P command palette — fuzzy filter over shifts/routes/commands, template insertion, arrow+enter.
450
+ - Complete CLI command set — `init`, `up`/`down`, chat REPL, handoff confirm, pair QR, board, log follow.
451
+ - `loom doctor` — environment, daemon, and project diagnostics with actionable fixes.
452
+ - Phone web app served at `/app` — QR deep-link pairing, board, live thread over WebSocket, agent chips with shift-on-send, route banner, interrupt, install-to-home-screen manifest.
453
+ - Form-submit composer for mobile keyboards; phone-over-Tailscale walkthrough.
454
+
455
+ ### Security & operations
456
+
457
+ - Multi-project daemon with REST + WebSocket server, bearer auth, QR pairing tokens, and tailnet binding.
458
+ - Paired-device management — `loom clients`, admin-only revoke, immediate 401 for revoked tokens.
459
+ - Read-modify-write daemon config on claim/revoke so PID/host/port survive pairing.
460
+ - Stale-build auto-restart via health endpoint; `loom up --restart`; pair refuses unreachable localhost QR with exact fix steps.
461
+ - Hot-reload edited `.loom/config.json` when project is quiet.
462
+ - Auto-clear ghost baton holders after agent removal.
463
+ - Sanitize inherited Claude-session env vars (`CLAUDECODE`, `CLAUDE_CODE_*`, `ANTHROPIC_BASE_URL`) when spawning agent CLIs.
464
+
465
+ ### Testing & CI
466
+
467
+ - Unit + end-to-end suite (76 tests) covering eventlog, baton, projection, suggestions, auth, costs, and daemon e2e.
468
+ - Routing e2e tests — completion, role/named resolution, pause-on-question + auto-resume, abort, manual-handoff cancel, 409 double-start.
469
+ - GitHub Actions CI (Node 22 and 24, build + test).
470
+ - OpenCode adapter hardening from dogfooding Loom on itself — poll-based turn completion, error surfacing, model validation, orphan serve reaping.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nivesh Gajengi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.