@thedevlabs-io/labd 0.3.1 → 0.6.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/AGENTS.md CHANGED
@@ -35,7 +35,7 @@ executes tools.
35
35
 
36
36
  ```
37
37
  packages/core/ the agent loop, tools, permission checks, sessions, checkpoints
38
- packages/tui/ terminal rendering - differential redraw, markdown, cards
38
+ packages/tui/ terminal rendering - the full screen, differential redraw, markdown, cards
39
39
  packages/cli/ the binary: config, auth, extensions, and the four run modes
40
40
  vendor/labd/ submodule - the labd repo, for @labd/protocol
41
41
  test/ vitest; `e2e.test.ts` runs the built binary against a fake server
@@ -176,52 +176,111 @@ Do not "simplify" this into a list. The list version cannot do any of the three.
176
176
 
177
177
  ## Terminal rules
178
178
 
179
+ Two kinds of program live in this repo, and the rules differ by which one you are in.
180
+
181
+ **The interactive session owns the screen.** `labd` on its own runs
182
+ `modes/screen.ts`: the alternate screen buffer, a scrollable transcript, a
183
+ multi-line composer, popups for `/` and `@`, dialogs for lists, approvals inline.
184
+ This replaced the "print into the scrollback, keep one live box at the bottom"
185
+ model in 0.4.0. That model was principled - the terminal owned the transcript, so
186
+ search and copy came free - and learners did not want it: they wanted an app, not
187
+ a log. `--plain` still runs the old loop for one release and is deprecated. Rules
188
+ for the screen:
189
+
190
+ - **Full-screen only when stdin AND stdout are TTYs.** Anything else falls
191
+ through to the printing modes below; a learner must never type blind.
192
+ - **Give the terminal back, whatever happens.** `Screen.leave` runs from the
193
+ normal exit, the `finally`, the signal handlers and the process `exit` hook, in
194
+ the reverse order of `enter`, and is idempotent. Raw mode or the alternate
195
+ screen left on looks like a broken machine.
196
+ - **Frames are whole and drawn differentially with absolute positions.** No
197
+ relative cursor moves: there is no scrollback to keep in step with.
198
+ - **Pure logic is separate from I/O.** `keys`, `layout`, `transcript`, `editor`,
199
+ `popup`, `dialog` know nothing about a stream and are tested without one.
200
+ `Screen` owns the terminal; `modes/screen.ts` is the state machine between them.
201
+ - **The mouse is taken by default, for wheel scrolling, and `--no-mouse` gives
202
+ it back** - native text selection needs the terminal to have it.
203
+
204
+ **The non-interactive modes own nothing and print.** `labd -p`, `--json`,
205
+ `--mode rpc`, and anything with a piped stdout use `Renderer` and plain writes:
206
+
179
207
  - **Never assume a TTY.** Piped, redirected, or in CI, colour is off and the live
180
208
  region is a no-op. Escape codes in a log file are worse than plain text.
181
209
  - **The scrollback is not ours.** Finished output is printed and belongs to the
182
- terminal; only the status line is a live region. That is what keeps search,
183
- selection, and copy-paste working.
210
+ terminal; only the status line is a live region.
184
211
  - **stdout is the answer. Everything else is stderr.** A pipeline must receive
185
- only what labd meant as the answer.
212
+ only what labd meant as the answer. The interactive session keeps this for
213
+ what it prints on the way out - the "session saved" line goes to stderr.
214
+
215
+ **Everywhere:**
216
+
186
217
  - **Escape sequences are written as `\u001b` escapes**, never as literal bytes, so
187
218
  source files stay greppable ASCII.
219
+ - **Zero runtime dependencies.** No ink, no blessed, no opentui: the TUI is the
220
+ repo's own `Screen` and differential drawing, so the bundle stays one file and
221
+ still works as a Node single-executable.
188
222
 
189
223
  ## Distribution
190
224
 
191
- Three artefacts, one source:
225
+ **The GitHub organisation is private, and public docs must not link to it.**
226
+ Learners cannot reach `github.com/thedevlabs-io/*`, raw.githubusercontent.com,
227
+ or GitHub Release assets. The public channels are npm (`@thedevlabs-io/labd`, bin
228
+ `labd`) and the VS Code Marketplace (`thedevlabs-io.labd`); README.md and every
229
+ user-facing string point at those and nowhere else. This file is for
230
+ contributors with access, so it may name the repositories.
231
+
232
+ Two bundles, one source, one channel:
192
233
 
193
234
  - **`dist/labd.js`** - ESM, zero runtime dependencies, what npm's `bin` points at.
194
- - **`dist/labd.cjs`** - the same code as CommonJS. It exists because Node's
195
- single-executable support runs its entry as CJS and refuses an ESM one outright.
196
- This is why `bin/labd.ts` must never use top-level await.
197
- - **`dist/bin/labd-<os>-<arch>`** - a standalone executable, built by injecting the
198
- CJS bundle into a copy of the `node` binary. Not cross-compilable, so the release
199
- workflow builds one per runner. ~115 MB, ~40 MB compressed: the honest price of
200
- "works on a machine with nothing installed".
201
-
202
- macOS needs the signing dance in `build-binaries.mjs`, and it is not optional -
203
- injecting invalidates the signature, and macOS kills an invalidly-signed binary
204
- rather than running it. The build also RUNS the binary before calling it done,
205
- because every one of those steps fails by producing something that does not start
206
- rather than by erroring.
235
+ `npm install -g @thedevlabs-io/labd` is the install; `npm update -g` the upgrade.
236
+ - **`dist/labd.cjs`** - the same code as CommonJS. Kept because Node's
237
+ single-executable support runs its entry as CJS and refuses an ESM one, so a
238
+ standalone build stays possible without a code change. This is why `bin/labd.ts`
239
+ must never use top-level await; CI runs the CJS bundle to prove it loads.
240
+
241
+ Standalone binaries, the install scripts and the GitHub Release step were removed
242
+ in 0.4.0: they were only reachable through GitHub, which learners cannot see.
207
243
 
208
244
  `npm install -g git+ssh://...` cannot work: `dist/` is not committed and npm does
209
245
  not fetch submodules for a git dependency. `scripts/prepare.mjs` says so rather
210
246
  than letting the install appear to succeed with no binary - which is what it did
211
247
  before, reporting "added 1 package" and installing nothing.
212
248
 
213
- ## Sub-agents - deliberately outside
214
-
215
- There is no `spawn_agent` tool, and adding one would be a permission mistake. Such
216
- a tool has to decide what mode the children run in: inherit the parent's and one
217
- approval becomes N unattended writes; give children their own and the model has
218
- been handed the setting it is specifically not allowed to influence.
219
-
220
- `--mode rpc` already makes every labd process a driveable agent, so orchestration
221
- lives in a script the operator writes - where the children's mode is set by a
222
- person, each child asks that script for approval over the wire, and there is no RPC
223
- method for changing a mode. `examples/orchestrate.mjs` is the reference, and
224
- `test/rpc-e2e.test.ts` runs two children at once to prove it.
249
+ ## Helpers - inside the permission model, not beside it
250
+
251
+ This section used to be "Sub-agents - deliberately outside", and its argument was
252
+ right about the design it rejected: a `spawn_agent` tool whose children ran in the
253
+ parent's mode would turn one approval into N unattended writes, and children with a
254
+ mode of their own would hand the model the one setting it must not touch. The
255
+ helpers labd has now avoid both, because **there is no approval to spawn and no
256
+ mode for a helper to have**:
257
+
258
+ - Spawning changes nothing, so it asks nothing. What a helper then DOES is checked
259
+ call by call: every write and command goes through `executeCall`
260
+ (`packages/core/src/calls.ts`) - the same code the turn's own calls go through -
261
+ and `needsApproval` against the learner's mode. So `auto` still asks about every
262
+ command a helper wants to run, and `ask` about every write.
263
+ - Its cards join the turn's line: one at a time, marked "from a helper", and once
264
+ the turn is stopped nothing still waiting is asked.
265
+ - **Unattended means declined, for helpers too.** `labd -p` has no one to ask, so a
266
+ helper's write is refused and the run exits 2. `e2e.test.ts` proves it.
267
+ - One level: a helper is offered every tool but `spawn_agent` and refuses it if
268
+ called. At most `MAX_SUBAGENTS` at once; the rest are refused with a reason.
269
+ - It shares the turn's ToolContext, so its writes are in the turn's checkpoint
270
+ and `/undo` puts them back with the rest.
271
+ - It writes nothing to the session but the parent's call and its report, and is
272
+ never handed to memory on its own.
273
+ - The helper's brief, the report bound and when to use one are the protocol's
274
+ (`subAgentBrief`, `SUBAGENT_REPORT_CHARS`, the prompt's "Helpers" section), not
275
+ this repo's.
276
+
277
+ `test/helpers.test.ts` pins these against the real tools and checkpoint store.
278
+
279
+ **Orchestration across processes is still outside, and still the right tool for
280
+ it.** `--mode rpc` makes every labd process a driveable agent, where each child's
281
+ mode is set by the person who wrote the script and each child asks that script for
282
+ approval. `examples/orchestrate.mjs` is the reference; `test/rpc-e2e.test.ts` runs
283
+ two children at once. An RPC host is told which helper is asking on `labd/approve`.
225
284
 
226
285
  ## Branches - promotion is by pull request, always
227
286
 
package/README.md CHANGED
@@ -3,80 +3,49 @@
3
3
  labd in your terminal — the Dev Labs assistant, in the project you're standing in.
4
4
 
5
5
  ```
6
- $ labd
7
- [o_o] labd in labd-cli on develop
8
- labd changes files on its own - every answer can be put back in one go - and still asks before running a command.
9
-
10
- > why is session.test.ts failing?
11
-
12
- ● Read test/session.test.ts
13
- ? Run a command
14
- to see which of the eleven cases fails
15
-
16
- npx vitest run test/session.test.ts
17
-
18
- On auto, commands still ask - a command can do anything your shell can, and put back isn't one of them.
19
- run this command? [y/N]
6
+ [o_o] labd · labd-cli develop · session 3f9a12c0 auto
7
+ ─────────────────────────────────────────────────────────────────────────────────
8
+ > why is session.test.ts failing?
9
+
10
+ ● Read test/session.test.ts
11
+ ? Run a command
12
+ to see which of the eleven cases fails
13
+
14
+ npx vitest run test/session.test.ts
15
+
16
+ On auto, commands still ask - a command can do anything your shell can, and
17
+ put back isn't one of them.
18
+ y to allow · n or esc to decline
19
+ ╭───────────────────────────────────────────────────────────────────────────────╮
20
+ │ > ask labd something - / for commands - @ to tag a file │
21
+ ╰───────────────────────────────────────────────────────────────────────────────╯
22
+ y allow · n decline · ctrl+c stop context 12% · credits 82%
20
23
  ```
21
24
 
22
- This is the same assistant as the [VS Code extension](https://github.com/thedevlabs-io/labd)
23
- and the one docked in the learning portal — same server, same prompt, same memory,
24
- same permission model. Ask something in your editor, follow up in your terminal.
25
+ This is the same assistant as the labd VS Code extension (Marketplace:
26
+ `thedevlabs-io.labd`) and the one docked in the learning portal at
27
+ [learn.thedevlabs.io](https://learn.thedevlabs.io) — same server, same prompt,
28
+ same memory, same permission model. Ask something in your editor, follow up in
29
+ your terminal.
25
30
 
26
31
  ## Install
27
32
 
28
- **macOS / Linux** — a standalone executable. Nothing else needs to be installed:
29
-
30
- ```bash
31
- curl -fsSL https://raw.githubusercontent.com/thedevlabs-io/labd-cli/production/install.sh | sh
32
- ```
33
-
34
- **Windows** — same, in PowerShell:
35
-
36
- ```powershell
37
- irm https://raw.githubusercontent.com/thedevlabs-io/labd-cli/production/install.ps1 | iex
38
- ```
39
-
40
- **With npm**, if you already have Node 22+:
33
+ You need Node 22 or newer. Then:
41
34
 
42
35
  ```bash
43
36
  npm install -g @thedevlabs-io/labd
44
- ```
45
-
46
- Then connect:
47
-
48
- ```bash
49
37
  labd login # approve a short code in your browser
50
38
  ```
51
39
 
52
- <details>
53
- <summary>Other ways, and what each is for</summary>
54
-
55
- | how | when |
56
- |---|---|
57
- | `install.sh` / `install.ps1` | a machine with nothing on it. Downloads a ~40 MB archive containing a single executable with the Node runtime inside it. |
58
- | `npm install -g @thedevlabs-io/labd` | you have Node. Smaller download, and `npm update -g @thedevlabs-io/labd` upgrades it. |
59
- | `npm pack` → share the `.tgz` | an air-gapped machine, or pinning an exact build. `npm install -g thedevlabs-io-labd-0.3.1.tgz`. |
60
- | copy `dist/labd.js` | you built from source. It has **zero runtime dependencies**, so that one file plus Node 22+ is a working install anywhere. |
61
- | clone + `npm link` | working on labd itself. |
62
-
63
- `npm install -g git+ssh://…` does **not** work, and says so rather than half-installing: `dist/` is not committed, and npm does not fetch the git submodule that the build needs.
64
-
65
- While this repository is private, the install scripts need a credential to read release assets. They use the GitHub CLI when it is signed in, or `GH_TOKEN` if you set one, and explain that rather than failing with a 404.
66
-
67
- **Building from source:**
40
+ To upgrade:
68
41
 
69
42
  ```bash
70
- git clone git@github.com:thedevlabs-io/labd-cli.git
71
- cd labd-cli
72
- git submodule update --init # the protocol lives in the labd repo
73
- npm install
74
- npm run build # dist/labd.js (and dist/labd.cjs, for binaries)
75
- npm link # puts `labd` on your PATH
76
- npm run build:binary # optional: a standalone executable for this platform
43
+ npm update -g @thedevlabs-io/labd
77
44
  ```
78
45
 
79
- </details>
46
+ The package is one bundled file with zero runtime dependencies, so that is the
47
+ whole install. There are no standalone binaries and no `curl | sh` installer: npm
48
+ is the one public channel.
80
49
 
81
50
  ## Using it
82
51
 
@@ -101,14 +70,53 @@ labd -c # pick up the most recent session here
101
70
  | `-e`, `--extension <path>` | load an extension file (repeatable; `/reload` keeps it) |
102
71
  | `--no-extensions` | load none, now and on `/reload` |
103
72
  | `--no-color` / `--color` | force colour off or on |
73
+ | `--no-mouse` | leave the mouse to the terminal, for native text selection |
74
+ | `--plain` | the older print-style session (deprecated; removed in 0.5) |
104
75
  | `-v`, `--version` | |
105
76
 
106
77
  Subcommands: `login`, `logout` (revokes the token server-side too), `sessions`
107
78
  (earlier sessions here), `trust` (let this project load its own extensions and
108
79
  name its own server).
109
80
 
110
- Inside a session: `@path` tags a file into your question, Tab completes both those
111
- and `/commands`, ctrl-c stops an answer without leaving.
81
+ ### Inside a session
82
+
83
+ `labd` on its own opens a full-screen session: a header, a scrollable transcript,
84
+ a box to type in at the bottom, and a footer with the keys that apply right now
85
+ and how full the conversation is. The transcript follows the answer as it
86
+ streams; scroll up and it stops following and tells you how many lines have
87
+ arrived since (`End`, or scrolling back to the bottom, follows again).
88
+
89
+ | key | |
90
+ |---|---|
91
+ | `enter` | send |
92
+ | `shift+enter`, `ctrl+j`, or a line ending in `\` | new line in the box (it grows to 8 rows) |
93
+ | `/` | a list of commands appears above the box; `↑↓` moves, `tab`/`enter` accepts, `esc` closes |
94
+ | `@` | the same, for files in the project — `@src/index.ts` tags the file into the question |
95
+ | `tab` | complete the command or path being typed |
96
+ | `shift+tab` | next mode: `ask`, `auto`, `agent` - the same as `/mode`, and it says what the new mode does |
97
+ | `pgup` / `pgdn`, mouse wheel | scroll the transcript |
98
+ | `home` / `end` | top / bottom of the transcript (with an empty box; otherwise they move within the line) |
99
+ | `↑` / `↓` | earlier questions, when the caret is on the first / last line |
100
+ | `esc` | close a list or dialog; otherwise jump back to the bottom |
101
+ | `y` / `n` | answer an approval card. Nothing else answers it, and `enter` never does |
102
+ | `ctrl+c` | stop the answer; else clear the box; else press it again within 2 s to quit |
103
+ | `ctrl+d` | quit |
104
+ | `ctrl+n`, `ctrl+o` | new session, earlier sessions |
105
+ | `ctrl+l` | redraw |
106
+
107
+ `/help`, `/sessions` and `/tree` open a dialog you can type into to narrow the
108
+ list; `enter` picks — a session to resume, or a point in this session to continue
109
+ from. Approvals are never dialogs: the card is drawn inline in the transcript
110
+ with its context, and `y` is the only key that allows it.
111
+
112
+ Two flags for the screen. `--no-mouse` leaves the mouse to the terminal (the
113
+ session takes it for wheel scrolling, and native text selection needs it
114
+ released); `mouse: false` in a settings file does the same. `--plain` runs the
115
+ older print-style session, where answers land in the terminal's scrollback and
116
+ only the box is redrawn — kept for one release for terminals that cannot cope
117
+ with the alternate screen, and deprecated. `sidebar: true`, or `/sidebar`, adds
118
+ a panel with the project, branch, mode and recent steps when there are 120 or
119
+ more columns.
112
120
 
113
121
  | command | |
114
122
  |---|---|
@@ -118,7 +126,9 @@ and `/commands`, ctrl-c stops an answer without leaving.
118
126
  | `/redo` | reapply what `/undo` put back |
119
127
  | `/tree` | every point in this session; branch from any of them |
120
128
  | `/resume <id>` | continue from a point in `/tree` |
121
- | `/sessions`, `/session` | earlier sessions here; where this one is stored |
129
+ | `/sessions`, `/session` | earlier sessions here (a dialog; `enter` resumes); where this one is stored |
130
+ | `/new` | start a fresh session; the current one stays saved |
131
+ | `/sidebar`, `/clear` | show or hide the side panel; clear the screen |
122
132
  | `/usage` | what's left of this month's allowance |
123
133
  | `/trust` | let this project load its own extensions |
124
134
  | `/reload` | reload extensions and the file list |
@@ -173,20 +183,33 @@ Two more rules worth knowing before you point this at a repository you didn't wr
173
183
  | `.labd/extensions/` | per-project extensions, loaded once trusted |
174
184
 
175
185
  `settings.json` keys, global or per project: `timeout` as `commandTimeoutSeconds`
176
- (5–600), `effort` (`low`/`medium`/`high`), `checkpoints` (`true` by default), and
177
- `serverUrl` — honoured from a project file only once that project is trusted.
178
- `mode` is read from the global file only.
186
+ (5–600), `effort` (`low`/`medium`/`high`), `checkpoints` (`true` by default),
187
+ `mouse` (`true`; the full-screen session takes the mouse for scrolling), `sidebar`
188
+ (`false`), and `serverUrl` — honoured from a project file only once that project
189
+ is trusted. `mode` is read from the global file only.
179
190
 
180
191
  `LABD_HOME`, `LABD_SERVER_URL`, and `LABD_TOKEN` override the corresponding thing;
181
192
  the last is how you run labd in CI without pairing. `NO_COLOR` turns colour off,
182
193
  `FORCE_COLOR` turns it on (`FORCE_COLOR=0` off) and wins over everything else, and
183
194
  `LABD_NO_MOTION` skips the opening animation.
184
195
 
185
- ## Sub-agents and orchestration
196
+ ## Helpers and orchestration
186
197
 
187
- labd has no `spawn_agent` tool, and that is a decision rather than a gap. It does
188
- have `--mode rpc`, which makes every labd process a driveable agent on
189
- stdin/stdout — so an orchestrator is a script that spawns several of them:
198
+ **Helpers.** labd can hand a self-contained piece of a job to a helper - another
199
+ labd with the same tools, working from a brief labd writes, whose report comes
200
+ back into the answer. Several can run at once, up to three. A helper is not a way
201
+ around your settings:
202
+
203
+ - a helper has no mode of its own. Its writes and commands ask you exactly when
204
+ labd's would, one card at a time, marked **from a helper**;
205
+ - under `labd -p` nothing can be approved, so a helper's changes are declined and
206
+ the run exits 2;
207
+ - what a helper changed is part of the answer, so `/undo` puts it back too;
208
+ - a helper cannot start helpers of its own.
209
+
210
+ **Orchestration.** `--mode rpc` makes every labd process a driveable agent on
211
+ stdin/stdout, so an orchestrator is a script that spawns several of them, each
212
+ with a mode you choose:
190
213
 
191
214
  ```bash
192
215
  node examples/orchestrate.mjs "what does this file do?" src/a.ts src/b.ts src/c.ts
@@ -195,14 +218,6 @@ node examples/orchestrate.mjs "what does this file do?" src/a.ts src/b.ts src/c.
195
218
  [`examples/orchestrate.mjs`](examples/orchestrate.mjs) is ~140 lines: spawn, send
196
219
  `labd/prompt`, read the notification stream, run three at a time.
197
220
 
198
- **Why not a tool inside labd.** A `spawn_agent` tool has to decide what mode the
199
- children run in, and every answer is bad. Inherit the parent's mode and one
200
- approval becomes N unattended writes. Give children their own mode and the model
201
- has been handed the one setting it is specifically not allowed to influence.
202
- Outside, the mode of each child is set by whoever wrote the script, each child
203
- asks *that script* for approval over the wire, and the orchestrator is code you
204
- can read. (Pi reaches the same conclusion and suggests tmux.)
205
-
206
221
  The protocol is small:
207
222
 
208
223
  | you send | |
@@ -213,9 +228,9 @@ The protocol is small:
213
228
  | labd sends | |
214
229
  | `labd/ready` | first, on start: protocol, framing, session id, mode, root |
215
230
  | `labd/text`, `labd/status`, `labd/done`, `labd/error` | notifications as the turn runs |
216
- | `labd/tool` `{name, status, input, detail, preview, output}` | each tool step; `preview` is the diff or the command, `output` what came back |
217
- | `labd/command_output` `{line}` | a running command's own output, as it happens |
218
- | `labd/approve` `{call, preview}` | **a request to you** — answer `{approved: true\|false}` |
231
+ | `labd/tool` `{name, status, input, detail, preview, output, helper?}` | each tool step; `preview` is the diff or the command, `output` what came back, `helper` `{id, label}` when a helper made the call |
232
+ | `labd/command_output` `{line, key?}` | a running command's own output, as it happens; `key` names a helper's call |
233
+ | `labd/approve` `{call, preview, helper?}` | **a request to you** - answer `{approved: true\|false}`; `helper` names the helper asking |
219
234
 
220
235
  Closing stdin ends the session: any turn in flight finishes, the conversation is
221
236
  handed over for memory, and the process exits 0.
@@ -256,7 +271,7 @@ in `~/.labd/extensions`.
256
271
  ## Development
257
272
 
258
273
  ```bash
259
- npm run build # bundle to dist/labd.js
274
+ npm run build # bundle to dist/labd.js (and dist/labd.cjs)
260
275
  npm test # vitest (build first — the e2e suite spawns the binary)
261
276
  npm run check # typecheck + tests
262
277
  ```