@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.
- package/ARCHITECTURE.md +211 -0
- package/CHANGELOG.md +470 -0
- package/LICENSE +21 -0
- package/README.md +630 -0
- package/dist/adapters/base.d.ts +71 -0
- package/dist/adapters/base.js +187 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/bridges/antigravity.d.ts +53 -0
- package/dist/adapters/bridges/antigravity.js +106 -0
- package/dist/adapters/bridges/antigravity.js.map +1 -0
- package/dist/adapters/bridges/cdp.d.ts +82 -0
- package/dist/adapters/bridges/cdp.js +222 -0
- package/dist/adapters/bridges/cdp.js.map +1 -0
- package/dist/adapters/bridges/gui-chat.d.ts +113 -0
- package/dist/adapters/bridges/gui-chat.js +498 -0
- package/dist/adapters/bridges/gui-chat.js.map +1 -0
- package/dist/adapters/bridges/kiro.d.ts +19 -0
- package/dist/adapters/bridges/kiro.js +22 -0
- package/dist/adapters/bridges/kiro.js.map +1 -0
- package/dist/adapters/bridges/profiles.d.ts +82 -0
- package/dist/adapters/bridges/profiles.js +125 -0
- package/dist/adapters/bridges/profiles.js.map +1 -0
- package/dist/adapters/claude-code.d.ts +26 -0
- package/dist/adapters/claude-code.js +209 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.d.ts +51 -0
- package/dist/adapters/codex.js +291 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/echo.d.ts +20 -0
- package/dist/adapters/echo.js +79 -0
- package/dist/adapters/echo.js.map +1 -0
- package/dist/adapters/grok.d.ts +61 -0
- package/dist/adapters/grok.js +217 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +33 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.d.ts +64 -0
- package/dist/adapters/opencode.js +426 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/cli/common.d.ts +11 -0
- package/dist/cli/common.js +40 -0
- package/dist/cli/common.js.map +1 -0
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +181 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +641 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/tui-model.d.ts +73 -0
- package/dist/cli/tui-model.js +312 -0
- package/dist/cli/tui-model.js.map +1 -0
- package/dist/cli/tui.d.ts +15 -0
- package/dist/cli/tui.js +451 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/cli/ui.d.ts +10 -0
- package/dist/cli/ui.js +135 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/core/ades.d.ts +79 -0
- package/dist/core/ades.js +130 -0
- package/dist/core/ades.js.map +1 -0
- package/dist/core/baton.d.ts +37 -0
- package/dist/core/baton.js +92 -0
- package/dist/core/baton.js.map +1 -0
- package/dist/core/brain-extract.d.ts +111 -0
- package/dist/core/brain-extract.js +248 -0
- package/dist/core/brain-extract.js.map +1 -0
- package/dist/core/brain-index.d.ts +128 -0
- package/dist/core/brain-index.js +338 -0
- package/dist/core/brain-index.js.map +1 -0
- package/dist/core/brain.d.ts +214 -0
- package/dist/core/brain.js +425 -0
- package/dist/core/brain.js.map +1 -0
- package/dist/core/claude-cli.d.ts +12 -0
- package/dist/core/claude-cli.js +47 -0
- package/dist/core/claude-cli.js.map +1 -0
- package/dist/core/distill.d.ts +19 -0
- package/dist/core/distill.js +88 -0
- package/dist/core/distill.js.map +1 -0
- package/dist/core/eventlog.d.ts +43 -0
- package/dist/core/eventlog.js +211 -0
- package/dist/core/eventlog.js.map +1 -0
- package/dist/core/git.d.ts +203 -0
- package/dist/core/git.js +460 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/logbook.d.ts +59 -0
- package/dist/core/logbook.js +118 -0
- package/dist/core/logbook.js.map +1 -0
- package/dist/core/memory.d.ts +34 -0
- package/dist/core/memory.js +124 -0
- package/dist/core/memory.js.map +1 -0
- package/dist/core/notify.d.ts +11 -0
- package/dist/core/notify.js +30 -0
- package/dist/core/notify.js.map +1 -0
- package/dist/core/projection.d.ts +20 -0
- package/dist/core/projection.js +96 -0
- package/dist/core/projection.js.map +1 -0
- package/dist/core/registry.d.ts +76 -0
- package/dist/core/registry.js +147 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/router.d.ts +42 -0
- package/dist/core/router.js +99 -0
- package/dist/core/router.js.map +1 -0
- package/dist/core/routes.d.ts +93 -0
- package/dist/core/routes.js +399 -0
- package/dist/core/routes.js.map +1 -0
- package/dist/core/search.d.ts +63 -0
- package/dist/core/search.js +0 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/setup.d.ts +70 -0
- package/dist/core/setup.js +250 -0
- package/dist/core/setup.js.map +1 -0
- package/dist/core/suggestions.d.ts +15 -0
- package/dist/core/suggestions.js +38 -0
- package/dist/core/suggestions.js.map +1 -0
- package/dist/core/worktree.d.ts +32 -0
- package/dist/core/worktree.js +83 -0
- package/dist/core/worktree.js.map +1 -0
- package/dist/daemon/app-page.d.ts +32 -0
- package/dist/daemon/app-page.js +6363 -0
- package/dist/daemon/app-page.js.map +1 -0
- package/dist/daemon/auth.d.ts +51 -0
- package/dist/daemon/auth.js +135 -0
- package/dist/daemon/auth.js.map +1 -0
- package/dist/daemon/board.d.ts +117 -0
- package/dist/daemon/board.js +202 -0
- package/dist/daemon/board.js.map +1 -0
- package/dist/daemon/brand-icons.d.ts +29 -0
- package/dist/daemon/brand-icons.js +44 -0
- package/dist/daemon/brand-icons.js.map +1 -0
- package/dist/daemon/client.d.ts +127 -0
- package/dist/daemon/client.js +237 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/geist-font.d.ts +10 -0
- package/dist/daemon/geist-font.js +783 -0
- package/dist/daemon/geist-font.js.map +1 -0
- package/dist/daemon/guards.d.ts +18 -0
- package/dist/daemon/guards.js +40 -0
- package/dist/daemon/guards.js.map +1 -0
- package/dist/daemon/linear.d.ts +59 -0
- package/dist/daemon/linear.js +117 -0
- package/dist/daemon/linear.js.map +1 -0
- package/dist/daemon/push.d.ts +22 -0
- package/dist/daemon/push.js +58 -0
- package/dist/daemon/push.js.map +1 -0
- package/dist/daemon/runtime.d.ts +299 -0
- package/dist/daemon/runtime.js +1016 -0
- package/dist/daemon/runtime.js.map +1 -0
- package/dist/daemon/server.d.ts +125 -0
- package/dist/daemon/server.js +1728 -0
- package/dist/daemon/server.js.map +1 -0
- package/dist/daemon/tasks.d.ts +153 -0
- package/dist/daemon/tasks.js +264 -0
- package/dist/daemon/tasks.js.map +1 -0
- package/dist/daemon/terminals.d.ts +82 -0
- package/dist/daemon/terminals.js +372 -0
- package/dist/daemon/terminals.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/sdk.d.ts +17 -0
- package/dist/sdk.js +17 -0
- package/dist/sdk.js.map +1 -0
- package/dist/types.d.ts +236 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/docs/SETUP.md +265 -0
- package/docs/adapters.md +132 -0
- package/docs/design-system.md +245 -0
- package/docs/img/board-linear.png +0 -0
- package/docs/img/board-projects.png +0 -0
- package/docs/img/brain.png +0 -0
- package/docs/img/settings.png +0 -0
- package/docs/img/workspace.png +0 -0
- package/docs/integration-notes.md +76 -0
- package/docs/proposals/brain.md +262 -0
- package/package.json +84 -0
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Loom — Architecture & v1 Design
|
|
2
|
+
|
|
3
|
+
> **The name stuck: Loom.** A loom weaves many threads into one fabric — the agents are
|
|
4
|
+
> threads, shared memory is the weave, the tailnet is a literal mesh. It ships on npm as
|
|
5
|
+
> [`threadloom`](https://www.npmjs.com/package/threadloom) (`loom` was taken).
|
|
6
|
+
|
|
7
|
+
> **Status — read this as the design record, not the current map.** It's the reasoning the
|
|
8
|
+
> project was built from, and the *decisions* below all held. Some of the plan didn't
|
|
9
|
+
> survive contact: the surfaces grew from two to four (a web app and an Electron shell
|
|
10
|
+
> arrived alongside the CLI and phone), the phone app is Android-first over Expo rather
|
|
11
|
+
> than iOS-first over APNs, and the "pre-build spikes" have all been run — see
|
|
12
|
+
> [docs/integration-notes.md](docs/integration-notes.md) for what the agents actually do.
|
|
13
|
+
> Corrections are marked **[shipped]** inline. For what exists today, start at the
|
|
14
|
+
> [README](README.md).
|
|
15
|
+
|
|
16
|
+
## What it is
|
|
17
|
+
|
|
18
|
+
Loom is a **local-first control plane for coding agents**. One daemon runs on your
|
|
19
|
+
machine. It boots agent backends as background servers, exposes them behind a single
|
|
20
|
+
chat surface, and gives them a **shared brain** so context flows between them. You reach
|
|
21
|
+
it from your laptop CLI or your phone over a private Tailscale mesh, QR-paired.
|
|
22
|
+
|
|
23
|
+
Two headline workflows:
|
|
24
|
+
- **Handoff** — finish with Claude, slide the same context into another agent.
|
|
25
|
+
- **Routing** — Claude plans, another agent executes, Claude reviews.
|
|
26
|
+
|
|
27
|
+
The hard problem Loom solves: these agents have *incompatible* memory, control
|
|
28
|
+
interfaces, and session models, and Loom makes them behave like one organism.
|
|
29
|
+
|
|
30
|
+
## Glossary
|
|
31
|
+
|
|
32
|
+
| Term | Meaning |
|
|
33
|
+
|---|---|
|
|
34
|
+
| **Daemon** | Long-lived local process. Manages all projects, adapters, the log, networking. |
|
|
35
|
+
| **Project** | A working directory + `.loom/` config + its own event log, baton, and agent fleet. |
|
|
36
|
+
| **Event log** | Per-project append-only source of truth (messages, tool calls, edits, decisions). |
|
|
37
|
+
| **Adapter** | Full-duplex integration with a controllable agent (OpenCode, Claude Code). |
|
|
38
|
+
| **Bridge** | Read-mostly integration with a GUI agent (Antigravity). Never holds the baton. |
|
|
39
|
+
| **Baton** | The write lock. Exactly one agent per project holds it and may edit the tree. |
|
|
40
|
+
| **Projection** | Rendering the log into a target agent's native memory format on handoff. |
|
|
41
|
+
| **Role** | An agent instance's declared function: planner / executor / reviewer / general. |
|
|
42
|
+
| **Surface** | Where you interact. **[shipped]** four of them: TUI/CLI, web app, Electron desktop shell, phone app — every one a paired client of the same daemon. |
|
|
43
|
+
|
|
44
|
+
## Decisions (locked)
|
|
45
|
+
|
|
46
|
+
| Branch | Decision | Why |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| Topology | Local-first, per-user daemon; tailnet + QR pairing token | Smallest trust surface; matches BYO-agents ethos |
|
|
49
|
+
| v1 wedge | Unified thread + **manual** routing | Prove "one place for all agents" before auto-orchestration |
|
|
50
|
+
| Integration | Full-duplex **Adapters** + read-only **Bridges** | Keeps the core contract pure without abandoning GUI agents |
|
|
51
|
+
| Memory | Append-only event log = source of truth; **namespaced** projections | One schema to reconcile through; never clobber user config |
|
|
52
|
+
| Surface | One shared thread; agents take turns; baton = write lock | Directly models the handoff story |
|
|
53
|
+
| Capture | **Eager** live streaming into the log | Enables phone mirroring + mid-flight handoff |
|
|
54
|
+
| Files | One shared working tree; active agent holds write lock; interruptible | Simplest correct model for sequential routing |
|
|
55
|
+
| Roles | Declared roles + **suggested** (confirmed) handoffs | Scaffolds plan→execute→review; rails toward auto-routing |
|
|
56
|
+
| Concurrency | Many concurrent projects, **per-project** baton | Async agents across projects is half the value |
|
|
57
|
+
| Async | **Core**: fire-and-notify | The "check from my phone" magic; justifies native push |
|
|
58
|
+
| Product | OSS core + public adapter/bridge SDK | Community writes adapters (like MCP servers proliferated) |
|
|
59
|
+
| Runtime | TypeScript / Node (daemon, CLI, web); React Native (phone app) | One language end-to-end, biggest contributor pool |
|
|
60
|
+
|
|
61
|
+
**[shipped]** The phone app is **Android-first on Expo**, not iOS-first: push goes through
|
|
62
|
+
Expo's service, so Loom manages no APNs/FCM credentials — the same "no accounts, no keys"
|
|
63
|
+
bet the rest of the project makes. The v1 wedge row also under-sold itself: manual routing
|
|
64
|
+
shipped *and* so did LLM auto-routing (`src/core/router.ts`).
|
|
65
|
+
|
|
66
|
+
## Architecture
|
|
67
|
+
|
|
68
|
+
**[shipped]** — four surfaces, not two; every one a paired client of the same daemon:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
phone app ── tailnet ──┐
|
|
72
|
+
web app ──────────────┤ browser, or the Electron shell around it
|
|
73
|
+
laptop CLI/TUI ────────┤
|
|
74
|
+
▼
|
|
75
|
+
loom daemon
|
|
76
|
+
│
|
|
77
|
+
┌───────────────┼────────────────┐
|
|
78
|
+
project A project B project C
|
|
79
|
+
┌────────┐ ┌────────┐ ┌────────┐
|
|
80
|
+
│ log(SoT)│ │ log │ │ log │ ← eager stream capture
|
|
81
|
+
└───┬────┘ └────────┘ └────────┘
|
|
82
|
+
projections (on handoff)
|
|
83
|
+
┌─────┴──────┬─────────────┐
|
|
84
|
+
[adapter] [adapter] [bridge]
|
|
85
|
+
OpenCode Claude Code Antigravity
|
|
86
|
+
serve/HTTP headless/SDK debug port (read-mostly)
|
|
87
|
+
└── all edit ONE working tree; baton = write lock ──┘
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Data model — the event log
|
|
91
|
+
|
|
92
|
+
Per-project, append-only, ordered. Suggested store: **SQLite** (one file per project under
|
|
93
|
+
`.loom/`), events as rows + JSON payloads. Event kinds (initial set):
|
|
94
|
+
|
|
95
|
+
- `message` — human or agent turn (role, author agent id, text, ts)
|
|
96
|
+
- `tool_call` / `tool_result` — what an agent did
|
|
97
|
+
- `file_edit` — path, diff/hash (stream messages live; snapshot large diffs)
|
|
98
|
+
- `decision` — a distilled fact worth projecting (drives memory)
|
|
99
|
+
- `handoff` — baton moved from X to Y, with the briefing that was injected
|
|
100
|
+
- `role_change`, `agent_join`, `agent_leave`
|
|
101
|
+
|
|
102
|
+
The log is the **only** source of truth. Everything an agent "knows" that Loom manages is
|
|
103
|
+
a *projection* of this log — never the other way around.
|
|
104
|
+
|
|
105
|
+
## Adapter contract (full-duplex — mandatory for Adapters)
|
|
106
|
+
|
|
107
|
+
An Adapter MUST implement all of:
|
|
108
|
+
|
|
109
|
+
1. `send(message)` — deliver a user/handoff turn to the agent
|
|
110
|
+
2. `stream()` — live event stream (messages, tool calls, edits) → written to the log
|
|
111
|
+
3. `injectMemory(projection)` — write Loom's namespaced memory block into the agent's
|
|
112
|
+
native store **without touching user-authored files**
|
|
113
|
+
4. `acquireLock()` / `releaseLock()` — baton / write-lock coordination
|
|
114
|
+
5. `diff()` — current working-tree changes attributable to this agent
|
|
115
|
+
6. `interrupt()` — stop/pause the active agent
|
|
116
|
+
|
|
117
|
+
v1 Adapters: **OpenCode** (`serve` HTTP + event API), **Claude Code** (headless / SDK,
|
|
118
|
+
streaming JSON, memory files).
|
|
119
|
+
|
|
120
|
+
## Bridge contract (read-mostly — for GUI agents)
|
|
121
|
+
|
|
122
|
+
A Bridge implements only a subset and is **explicitly second-class**:
|
|
123
|
+
|
|
124
|
+
- `stream()` (best-effort capture) and `injectMemory()` / briefing receipt.
|
|
125
|
+
- **Never** acquires the baton; **never** edits the shared tree under Loom's lock.
|
|
126
|
+
- Antigravity (VS Code/Windsurf-class fork) attaches here via its debug port until/unless
|
|
127
|
+
it exposes a real API — then it can graduate to an Adapter.
|
|
128
|
+
|
|
129
|
+
## Handoff & projection
|
|
130
|
+
|
|
131
|
+
1. You (or a suggested-handoff prompt) pass the baton from X to Y.
|
|
132
|
+
2. Loom distills the log into Y's **namespaced** memory format (a Loom-managed block —
|
|
133
|
+
e.g. an imported memory file), so it **persists** for Y's later autonomous use.
|
|
134
|
+
3. Loom also prepends a short "you're picking up ___, current state is ___" briefing for
|
|
135
|
+
Y's immediate turn (cheap, focused).
|
|
136
|
+
4. Your own `CLAUDE.md` / `AGENTS.md` are never edited.
|
|
137
|
+
|
|
138
|
+
## Networking & security
|
|
139
|
+
|
|
140
|
+
- Daemon binds **only** to the Tailscale interface. Tailscale provides device auth + E2E
|
|
141
|
+
encryption — that is the trust boundary.
|
|
142
|
+
- **QR pairing**: encodes a short-lived, one-time pairing **token** bound to the node's
|
|
143
|
+
identity — not raw secrets, never in a URL query string. Enrolls the phone as a known
|
|
144
|
+
client.
|
|
145
|
+
- No hosted backend in v1. (Optional relay is a *later, opt-in* product decision, not core.)
|
|
146
|
+
|
|
147
|
+
## Concurrency & async
|
|
148
|
+
|
|
149
|
+
- Daemon runs **many projects at once**; each has an independent baton and log.
|
|
150
|
+
- Agents are **long-running/background**. When one **needs input** or **finishes**, Loom
|
|
151
|
+
notifies you: a local CLI/desktop notification, and **push to the phone** — **[shipped]**
|
|
152
|
+
through Expo's push service (`src/daemon/push.ts`), not APNs.
|
|
153
|
+
- The primary surface is a **board of projects**, each opening into a shared thread.
|
|
154
|
+
|
|
155
|
+
## Surfaces
|
|
156
|
+
|
|
157
|
+
**[shipped]** All four exist; none is "later".
|
|
158
|
+
|
|
159
|
+
- **TUI / CLI.** Full experience: create projects, add agents, chat, pass the baton, watch
|
|
160
|
+
streams, get notified.
|
|
161
|
+
- **Web app.** Served by the daemon itself at `/app` (`src/daemon/app-page.ts`) — no build
|
|
162
|
+
step, no CDN. On a wide screen it's the full workspace (thread, tasks, explorer, diffs,
|
|
163
|
+
a real pty terminal); on a phone-sized screen it's the board.
|
|
164
|
+
- **Desktop shell.** A thin first-party Electron window around that same `/app`
|
|
165
|
+
([`desktop/`](desktop/README.md)) — not an IDE, no editor.
|
|
166
|
+
- **Phone app (React Native / Expo).** Thin client over the tailnet to the daemon's
|
|
167
|
+
WebSocket API. Push for "needs input" / "done". Android-first; iOS needs only a build.
|
|
168
|
+
|
|
169
|
+
## Open risks / pre-build spikes
|
|
170
|
+
|
|
171
|
+
**[shipped]** These were assumptions to verify before building — all have since been run
|
|
172
|
+
against real, pinned versions; the findings live in
|
|
173
|
+
[docs/integration-notes.md](docs/integration-notes.md). Kept here as the record of what
|
|
174
|
+
was uncertain at the start:
|
|
175
|
+
|
|
176
|
+
1. **OpenCode `serve`** — confirm it exposes a live event/SSE stream and per-session
|
|
177
|
+
control sufficient for eager capture + interrupt.
|
|
178
|
+
2. **Claude Code headless** — confirm streaming-JSON event shape, memory-file locations,
|
|
179
|
+
and clean interrupt/cancel.
|
|
180
|
+
3. **Antigravity debug port** — determine what the port actually exposes (CDP? extension
|
|
181
|
+
host?) and whether even read-only capture is stable. Highest-uncertainty item.
|
|
182
|
+
4. **Baton correctness** — the write-lock must be airtight; a leaked lock strands a project.
|
|
183
|
+
5. **Suggested-handoff detection** — start with a simple heuristic (agent signals "plan
|
|
184
|
+
complete"); a model-based classifier is a later refinement.
|
|
185
|
+
|
|
186
|
+
## v1 build sequence (proposed)
|
|
187
|
+
|
|
188
|
+
**[shipped]** Every step below is built, in roughly this order — with step 10 landing as
|
|
189
|
+
an Expo/Android app rather than iOS/APNs, and a web app plus desktop shell arriving that
|
|
190
|
+
this list never anticipated. See the [CHANGELOG](CHANGELOG.md) for what actually happened.
|
|
191
|
+
|
|
192
|
+
1. Daemon skeleton + project model + SQLite event log.
|
|
193
|
+
2. First Adapter (Claude Code headless) — send, stream→log, injectMemory, lock, interrupt.
|
|
194
|
+
3. CLI: create project, chat one agent, watch stream.
|
|
195
|
+
4. Second Adapter (OpenCode serve). Now two agents exist.
|
|
196
|
+
5. Baton + handoff + namespaced projection → the core "shift from A to B" demo.
|
|
197
|
+
6. Roles + suggested handoffs.
|
|
198
|
+
7. Multi-project concurrency + background/notify (CLI notifications).
|
|
199
|
+
8. Tailnet binding + QR pairing token.
|
|
200
|
+
9. Antigravity **Bridge** (read-only) — the first GUI participant.
|
|
201
|
+
10. iOS app (v1.5) hitting the same daemon API + APNs push.
|
|
202
|
+
11. Publish the adapter/bridge SDK + docs (OSS).
|
|
203
|
+
|
|
204
|
+
## Naming
|
|
205
|
+
|
|
206
|
+
Working name **Loom**. Decide once we see which of {routing, memory, surface} feels like
|
|
207
|
+
the soul in practice — Baton if handoff dominates, Choir if it's the shared-context feel,
|
|
208
|
+
Switchboard if it stays a router. Loom is the safe, brandable default.
|
|
209
|
+
|
|
210
|
+
**[shipped]** Loom it is — the shared-context feel won, which is why the tagline settled on
|
|
211
|
+
*the shared-memory layer*. The npm package is `threadloom`; the command stayed `loom`.
|