@vibelord/lanes 1.1.1 → 1.2.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/README.md +36 -5
- package/bin/lanes-install.mjs +166 -13
- package/lib/plan.mjs +113 -3
- package/package.json +1 -1
- package/payload/lanes/README.md +45 -22
- package/payload/lanes/VERSION +1 -1
- package/payload/lanes/bin/lanes +2459 -164
- package/payload/lanes/client/ghostty/lanes-palette-light.conf +19 -19
- package/payload/lanes/client/ghostty/lanes-palette.conf +19 -19
- package/payload/lanes/client/lanes-launch +289 -40
- package/payload/lanes/client/lanes-window +15 -3
- package/payload/lanes/container/hooks/claude-settings.json +1 -0
- package/payload/lanes/container/lane +106 -92
- package/payload/lanes/container/lane-edit +100 -0
- package/payload/lanes/container/lane-mcp +1091 -0
- package/payload/lanes/container/lane-signal +109 -29
- package/payload/lanes/container/lane_core.py +1140 -0
- package/payload/lanes/container/vimrc +31 -0
- package/payload/lanes/docs/ARCHITECTURE.md +618 -13
- package/payload/lanes/docs/DESIGN.md +122 -44
- package/payload/lanes/docs/INSTALL.md +157 -17
- package/payload/lanes/docs/RUNBOOK.md +603 -72
- package/payload/lanes/docs/SHARING.md +3 -2
- package/payload/lanes/docs/USERGUIDE.md +777 -136
- package/payload/lanes/install/enable-web.sh +2 -0
- package/payload/lanes/install/host-install.sh +163 -7
- package/payload/lanes/install/services.py +148 -18
- package/payload/lanes/install.sh +68 -7
- package/payload/lanes/libexec/agent_state.py +74 -1
- package/payload/lanes/libexec/cursor_sync.py +6 -0
- package/payload/lanes/libexec/device_sync.py +47 -10
- package/payload/lanes/libexec/economy_poll.py +1 -0
- package/payload/lanes/libexec/enrold.py +41 -22
- package/payload/lanes/libexec/keys.py +54 -23
- package/payload/lanes/libexec/lanes_lib.py +620 -22
- package/payload/lanes/libexec/members.py +16 -2
- package/payload/lanes/libexec/notify.py +104 -24
- package/payload/lanes/libexec/registrations.py +216 -18
- package/payload/lanes/libexec/session_actions.py +182 -14
- package/payload/lanes/libexec/sidebar +11 -3
- package/payload/lanes/libexec/sidebar.py +2135 -361
- package/payload/lanes/libexec/statusd.py +137 -10
- package/payload/lanes/libexec/studio_host.py +8 -1
- package/payload/lanes/libexec/tokens.py +121 -30
- package/payload/lanes/libexec/tokens.sh +86 -2
- package/payload/lanes/libexec/update.py +972 -0
- package/payload/lanes/libexec/users.py +614 -0
- package/payload/lanes/libexec/view.sh +34 -6
- package/payload/lanes/systemd/lanes-ensure.service +3 -0
- package/payload/lanes/systemd/lanes-gc.service +1 -1
- package/payload/lanes/systemd/lanes-update.service +17 -0
- package/payload/lanes/systemd/lanes-update.timer +17 -0
- package/payload/lanes/web/README.md +83 -24
- package/payload/lanes/web/browser.js +3 -5
- package/payload/lanes/web/mobile.css +17 -17
- package/payload/lanes/web/mobile.js +44 -32
- package/payload/lanes/workspaces.example/README.md +3 -1
- package/payload/lanes/zellij/config.kdl +106 -56
- package/payload/manifest.json +5 -3
|
@@ -4,7 +4,7 @@ Lanes keeps long-running agent terminals on one Linux workspace host and lets ev
|
|
|
4
4
|
same terminals: a terminal window on a Mac or Linux desktop, Safari on a phone, a browser on a laptop or tablet, and
|
|
5
5
|
the Cursor / VS Code terminals inside the workspace containers. This guide is the day-to-day reference for operators
|
|
6
6
|
and teammates, and the install and update path for the administrator. It describes the tree it ships in; run
|
|
7
|
-
`cat lanes/VERSION` (or `lanes
|
|
7
|
+
`cat lanes/VERSION` (or `lanes version` on the server) for the version.
|
|
8
8
|
|
|
9
9
|
Companion documents: [README](../README.md) (overview and quick start) · [INSTALL](INSTALL.md) (full setup) ·
|
|
10
10
|
[RUNBOOK](RUNBOOK.md) (operations and incidents) · [ARCHITECTURE](ARCHITECTURE.md) (how the parts fit) ·
|
|
@@ -69,7 +69,7 @@ The server's catalog orders the tabs.
|
|
|
69
69
|
| Kind | Comes from | In the sidebar |
|
|
70
70
|
| --- | --- | --- |
|
|
71
71
|
| Repository lane | A terminal declared in a repository's `.vscode/terminals.json` | The workspace's list; no close control |
|
|
72
|
-
| Registered session | An observed session adopted with **Add to workspace** or `lanes register` (saved in `registered-sessions.json`) | The workspace's list; no close control |
|
|
72
|
+
| Registered session | An observed session adopted with **Add to workspace** or `lanes register` (saved in `registered-sessions.json`) | The workspace's list; no close control; `u` (or `lanes unregister`) moves it back to Unregistered |
|
|
73
73
|
| Unregistered session | Any other tmux session observed in the workspace's container: a **New shell**, a session you started by hand, a Throttle Studio `st_…` session | **Unregistered (N)**; can be opened, renamed, added, promoted or closed |
|
|
74
74
|
|
|
75
75
|
**The terminal status** (sidebar column 33) is one of six words `lanes-statusd` publishes, plus `helper` for a tool
|
|
@@ -97,11 +97,17 @@ attention sections:
|
|
|
97
97
|
| Needs you | `✗` | `X` | errored | The turn failed |
|
|
98
98
|
| Needs you | `◇` | `?` | needs_input | The turn finished; waiting for your prompt |
|
|
99
99
|
| Working | `▸` | `>` | working | The agent is working |
|
|
100
|
-
| Done | `✓` | `v` | done | The turn finished; stays until you open the lane from
|
|
100
|
+
| Done | `✓` | `v` | done | The turn finished; stays until you open the lane — from any device of yours when the device has an owner, else from this one |
|
|
101
101
|
| (rest) | `_` | `_` | shell | The harness ended into a shell |
|
|
102
102
|
| (rest) | `·` | `.` | idle | The harness is up, no turn |
|
|
103
103
|
| (rest) | blank | blank | — | A plain shell, no agent signal |
|
|
104
104
|
|
|
105
|
+
A lane moves up into a section (Needs you above Working above Done) at once, and down only after the agent has
|
|
106
|
+
been doing the less urgent thing for 8 s (leaving Needs you) or 5 s (leaving Working or Done), so a lane whose
|
|
107
|
+
agent flickers between two states stays where it is. A lane whose agent signal disappears keeps its glyph for 5 s.
|
|
108
|
+
Inside a section lanes keep the order in which they entered it, and the list does not scroll under your cursor
|
|
109
|
+
(section 3, "The sidebar").
|
|
110
|
+
|
|
105
111
|
**What Throttle adds.** Lanes runs without Throttle. With the opt-in economy poller (section 8) the sidebar shows a
|
|
106
112
|
fleet economy strip (overflow spend against the daily cap, new 429s, model tiers, spill, free accounts) and one
|
|
107
113
|
economy cell per lane (`⚠` starving, `↓` downgraded, `$` mostly paid). Throttle's **Shared lanes** console and the
|
|
@@ -148,6 +154,7 @@ A member of the site sends you an invite: five lines from `lanes invite` ("Invit
|
|
|
148
154
|
| Step | The installer prints |
|
|
149
155
|
| --- | --- |
|
|
150
156
|
| Site | `site beyondwork, inferred from the invite code's prefix LN` (npm only; an explicit `--site` wins) |
|
|
157
|
+
| Release | Only when the server runs a newer release than the package you ran (npm only; see below): `server runs 1.2.0: fetching that installer first (this one is 1.1.0; nothing is installed yet, and the invite is kept for it)`, then `lanes-install: the server runs 1.2.0: npx --yes @vibelord/lanes@1.2.0, with the same options` — the server's release then prints every row below and redeems the code |
|
|
151
158
|
| Tailscale | `tailscale: connected as <login> (<address>)`, after opening the login when needed ("Tailscale" below) |
|
|
152
159
|
| SSH alias | `ssh: host alias 'lanes' -> dev@<MagicDNS name> (fallback lanes-ip -> <tailnet address>), key …` |
|
|
153
160
|
| Invite | `invite: redeemed — this machine's key is now authorized for <login> (device <device>)` |
|
|
@@ -158,6 +165,19 @@ request body (never on a command line) and never prints or stores it, and a code
|
|
|
158
165
|
with echo off. A code whose prefix belongs to another site is refused before anything is installed. Phone and
|
|
159
166
|
browser views: section 4. Uploads: section 5.
|
|
160
167
|
|
|
168
|
+
The code is redeemed by the server's own release (W9 K3). With an invite, the npm installer first asks the site's
|
|
169
|
+
enrolment service which release the server runs (`GET /enrol/site`, one call of at most 15 s; on a tailnet site only
|
|
170
|
+
when this machine is already connected, so join the tailnet first, step 1 — it never opens the Tailscale login for
|
|
171
|
+
this). When the server runs a newer release than the package, that run ends before anything is installed, sent or
|
|
172
|
+
redeemed, and the wrapper runs `npx --yes @vibelord/lanes@<release>` with your options instead — the code passes
|
|
173
|
+
through the environment, not a command line — once: that installer never asks again. When it cannot, the wrapper says
|
|
174
|
+
why in one line and the package you ran installs and redeems the code as before: no npx with its npm on the PATH, npm
|
|
175
|
+
not answering, or a release npm does not have yet (`lanes-install: the server runs 1.2.0, which npm does not have
|
|
176
|
+
(yet): installing 1.1.0 from this package instead` — a server run from a checkout names its `VERSION` before the
|
|
177
|
+
release is published). A window then updates that client by itself once npm has the release ([Versions](#versions)).
|
|
178
|
+
`LANES_NO_SELF_UPDATE=1` (or `LANES_NO_AUTOUPDATE=1`) installs exactly the package you ran. A checkout's `./install.sh`
|
|
179
|
+
only says `server runs 1.2.0; this installer is 1.1.0 — going on with 1.1.0`.
|
|
180
|
+
|
|
161
181
|
When the server already has a device of this machine's name — two laptops with one hostname, or a replaced laptop
|
|
162
182
|
whose old enrolment is still there — it answers `409 (device-taken)` and uses nothing up: the installer says so and
|
|
163
183
|
names the re-run, `npx @vibelord/lanes --invite <code> --device <another name>` (it remembers the name). If the old
|
|
@@ -215,12 +235,15 @@ lanes-window # open the desktop view
|
|
|
215
235
|
| `--site NAME` | A bundled profile (`beyondwork`); a value containing `/` or ending in `.conf` is your own file |
|
|
216
236
|
| `--stage-only` | Copy the bundled tree to `~/.local/share/lanes/pkg/<version>/` and print the path; run nothing |
|
|
217
237
|
| `--version` | Print the package version |
|
|
218
|
-
| `--invite CODE` | Hand the code to the installer; without `--site`, the code's prefix picks the bundled profile ("Join with an invite" above) |
|
|
238
|
+
| `--invite CODE` | Hand the code to the installer; without `--site`, the code's prefix picks the bundled profile ("Join with an invite" above). When the server runs a newer release, its installer runs instead and redeems the code (`LANES_NO_SELF_UPDATE=1`: this package) |
|
|
219
239
|
| `--wrapper-help` | Wrapper help only |
|
|
220
240
|
|
|
221
241
|
Every other option goes to `install.sh`. The wrapper stages the tree under `~/.local/share/lanes/pkg/<version>/`
|
|
222
|
-
(`$LANES_PKG_ROOT` overrides the parent
|
|
223
|
-
|
|
242
|
+
(`$LANES_PKG_ROOT` overrides the parent, under any name: the wrapper's `.lanes-package.json` stamp in the tree is what
|
|
243
|
+
marks it as staged, so its views follow `~/.local/share/lanes/current` like any other), reuses an identical tree, and
|
|
244
|
+
on a client (no `~/.local/share/lanes/current`) keeps the newest three versions. A server's trees are pruned by `lanes
|
|
245
|
+
gc` instead, which also keeps every older tree still in use ([systemd user units](#systemd-user-units)). See
|
|
246
|
+
[`npm/lanes/README.md`](../../npm/lanes/README.md).
|
|
224
247
|
|
|
225
248
|
### Client from a checkout
|
|
226
249
|
|
|
@@ -305,7 +328,9 @@ the server (a profile without such a name skips this):
|
|
|
305
328
|
|
|
306
329
|
The same for `./install.sh` and `npx @vibelord/lanes`: `0` ready · `3` installed, but this machine's key is not yet
|
|
307
330
|
authorized on the server · `4` installed, but this machine is not in the tailnet, or the server is unreachable or
|
|
308
|
-
did not answer in time · `1` error.
|
|
331
|
+
did not answer in time · `1` error. `install.sh` also ends with `5` — nothing installed, the server runs a newer
|
|
332
|
+
release — but only when the npm wrapper asks for that (`LANES_WRAPPER_REPLY`, [Join with an
|
|
333
|
+
invite](#join-with-an-invite)); the wrapper then runs that release, and `npx @vibelord/lanes` ends with its status.
|
|
309
334
|
|
|
310
335
|
### Server
|
|
311
336
|
|
|
@@ -323,14 +348,21 @@ sudo loginctl enable-linger <account> # administrator, once: services stay up
|
|
|
323
348
|
| --- | --- |
|
|
324
349
|
| `--check` | Check prerequisites, the user manager and Docker; print `Server prerequisites: ready`; change nothing |
|
|
325
350
|
| `--no-start` | Install files and units without contacting Docker or starting services |
|
|
351
|
+
| `--update-only` | An update (W9 U2) of a server a full install set up (refused when none is installed): install this release, then restart what runs it — every step after the switch runs even when one before it failed — and record the run in `~/.local/state/lanes/update/last.json` ([Update](#update)) |
|
|
326
352
|
| `-h`, `--help` | Usage |
|
|
327
353
|
|
|
328
354
|
A normal server install verifies and installs the pinned binaries from `holders.lock` (Zellij 0.45.1, zmx 0.8.1, and
|
|
329
355
|
glow 3.0.0 unless a glow is already on the view server's PATH) into `~/.local/share/lanes/bin`, adds the
|
|
330
356
|
`xterm-lanesview` terminfo alias, renders every unit in `lanes/systemd/` into the user manager's unit directory,
|
|
331
|
-
links `~/.local/share/lanes/current`
|
|
357
|
+
links `~/.local/share/lanes/current` to this checkout (moving it last, in one step: the units, `~/.local/bin/lanes` and
|
|
358
|
+
the view layouts name that link, never the checkout path, and `current.previous` keeps the tree it named before; only
|
|
359
|
+
this installer moves the link — `lanes web` and `install/enable-studio-host.sh` re-render the units of the release it
|
|
360
|
+
names),
|
|
361
|
+
creates an empty catalog only when no
|
|
332
362
|
configuration exists, runs `lanes sync`, and enables `lanes-statusd.service`, `lanes-sync.timer`,
|
|
333
|
-
`lanes-ensure.timer`, `lanes-gc.timer
|
|
363
|
+
`lanes-ensure.timer`, `lanes-gc.timer`, the update timer `lanes-update.timer` (an npm-installed server then installs
|
|
364
|
+
each newer release itself, [Update](#update); a checkout is left alone) and the enrolment service
|
|
365
|
+
`lanes-enrold.service` (restarted by every install, so
|
|
334
366
|
it runs the installed code; it restarts a running `lanes-mobile` too, so Caddy reads the new `/enrol/*` route). Web
|
|
335
367
|
access, the Studio Host, the economy poller, push notifications,
|
|
336
368
|
the `/tmp` reaper and the OOM guards stay off (section 11 lists each unit).
|
|
@@ -343,12 +375,14 @@ lanes sync
|
|
|
343
375
|
lanes doctor
|
|
344
376
|
```
|
|
345
377
|
|
|
346
|
-
`lanes install-container <name>` is additive. In that container's home it copies `lane`, `lane-run`
|
|
347
|
-
`lane-
|
|
378
|
+
`lanes install-container <name>` is additive. In that container's home it copies `lane`, `lane-run`, `lane-signal`
|
|
379
|
+
and `lane-edit` into `~/.lz/bin/`, the quick-edit `vimrc` into `~/.lz/vimrc`, the agent hook definitions into
|
|
380
|
+
`~/.lz/hooks/`, and `env.sh` into `~/.lz/env.sh`;
|
|
348
381
|
it creates `~/.tmp` and appends one marker-guarded line (`# lanes-env`) to the login files (`.bash_profile` or
|
|
349
382
|
`.profile`, `.bashrc`, and `.zshrc` when it exists) so new shells keep temp files off the RAM-backed `/tmp`; it copies
|
|
350
383
|
the pinned zmx binaries without overwriting a running one; and it links `~/.lz/<container>` on the host. It installs
|
|
351
|
-
no agent hook (section 7)
|
|
384
|
+
no agent hook (section 7) and no editor: `Ctrl-Alt-e` uses one the container already has, and vi is the floor
|
|
385
|
+
([section 7](#the-in-container-lane-command)).
|
|
352
386
|
|
|
353
387
|
For a new user's container, `lanes onboard <login> --container <name> --workspace <id> --repo /home/coder/<repo>`
|
|
354
388
|
prints every step and changes nothing. It checks the container first, so an unknown container stops it before the
|
|
@@ -401,38 +435,178 @@ redeemed from a second machine yet.
|
|
|
401
435
|
|
|
402
436
|
| Where | Update | Then |
|
|
403
437
|
| --- | --- | --- |
|
|
404
|
-
| Client (npm) | `lanes-launch update`, which runs `npx --yes @vibelord/lanes@latest` with every choice in `~/.config/lanes/install-options` given again as its flag (the command is printed first; flags you add follow them and win, e.g. `lanes-launch update --no-palette`); or `npx @vibelord/lanes@latest` yourself (your remembered options are repeated; add a flag only to change one) | `npx @vibelord/lanes --check` |
|
|
438
|
+
| Client (npm) | Nothing, usually: a window updates its client itself when the server is newer and npm has the server's release, at most every 6 h ([Versions](#versions); `LANES_NO_AUTOUPDATE=1` turns that off). By hand: `lanes-launch update`, which runs `npx --yes @vibelord/lanes@latest` with every choice in `~/.config/lanes/install-options` given again as its flag (the command is printed first; flags you add follow them and win, e.g. `lanes-launch update --no-palette`); or `npx @vibelord/lanes@latest` yourself (your remembered options are repeated; add a flag only to change one) | `npx @vibelord/lanes --check` |
|
|
405
439
|
| Client (checkout) | `git pull`, then `./install.sh` (`lanes-launch update` refuses a client that runs from a checkout: npm would replace it with a copy) | `./install.sh --check` |
|
|
406
|
-
| Server (npm) | `npx @vibelord/lanes@latest --server` (runs from `~/.local/share/lanes/pkg/<version>/`; older trees a service still uses are kept)
|
|
407
|
-
| Server (checkout) | `git pull` in the installed checkout, then `./install.sh --server` (configured workspaces and agent sessions are preserved) | the restarts below |
|
|
408
|
-
|
|
409
|
-
After a server update
|
|
440
|
+
| Server (npm) | Nothing, usually: `lanes-update.timer` installs each newer release within about 30 min of its publish (below) — once it is enabled: the full `--server` install enables it, an update with `--update-only` never does, so a server set up before 1.2.0 needs `npx @vibelord/lanes@latest --server` once, or `systemctl --user enable --now lanes-update.timer`. By hand: `lanes update --now`, or `npx @vibelord/lanes@latest --server --update-only` (runs from `~/.local/share/lanes/pkg/<version>/`; older trees a service still uses are kept), which makes the restarts below itself; without `--update-only` the full install runs | `lanes version`; `lanes update --status`; `lanes doctor` |
|
|
441
|
+
| Server (checkout) | `git pull` in the installed checkout, then `./install.sh --server --update-only` (or the full `./install.sh --server`; configured workspaces and agent sessions are preserved) | the restarts below |
|
|
442
|
+
|
|
443
|
+
After a server update — with `--update-only` the installer runs these steps itself once the new release is switched
|
|
444
|
+
in, in this order: `systemctl --user daemon-reload`, `lanes sync`, the statusd restart, a `try-restart` of each
|
|
445
|
+
optional service that runs (economy, notify, enrolment, `lanes-mobile`, `lanes-web` and the Studio Host — restarted,
|
|
446
|
+
not re-checked as `enable-studio-host.sh` does; one that is off stays off), `lanes install-container` for every
|
|
447
|
+
container `lanes sync` recorded, `lanes reload-views` and `lanes ensure --if-layout-changed`. A step that fails is
|
|
448
|
+
reported and the next one still runs. `lanes sync` exiting 1 — a board with an error, such as a container that no longer exists, whose
|
|
449
|
+
previous lanes sync keeps — is reported as `WARN` and leaves the result `ok`: fix the board, not the update. The run
|
|
450
|
+
ends `ok`, `partial` (installed; a step after the switch failed — run it by hand), `refused` (nothing changed: a
|
|
451
|
+
prerequisite said no, no release is installed yet — run the full `--server` install first, it enables what an update
|
|
452
|
+
restarts — or another install or update is running, in which case nothing is recorded either) or `failed` (nothing was
|
|
453
|
+
installed); `~/.local/state/lanes/update/last.json` keeps the result and each step's exit status, `update/last.log`
|
|
454
|
+
every step's output. One install or update runs at a time. After a full install, or for a step `--update-only`
|
|
455
|
+
reports as `FAIL`:
|
|
410
456
|
|
|
411
457
|
- `systemctl --user restart lanes-statusd.service` (the status observer runs the old code until restarted).
|
|
458
|
+
`lanes version` then lists statusd among the names before its `✓`; each name it still marks `✗` started before
|
|
459
|
+
the update: `runs 1.1.0 ✗` after an npm update (it runs the previous `pkg/<version>` tree), `started before its tree
|
|
460
|
+
changed ✗` after a checkout update (the checkout keeps its path; `git pull` gave the code a newer time than that
|
|
461
|
+
start).
|
|
412
462
|
- When web unit definitions or the pinned Zellij changed: `systemctl --user restart lanes-web.service
|
|
413
463
|
lanes-mobile.service`; browsers reconnect.
|
|
414
464
|
- When the Studio Host is enabled: re-run `sh install/enable-studio-host.sh` (it checks, then restarts the Host).
|
|
415
465
|
- Re-run `lanes install-container <container>` to update the in-container `lane` contract; an older `lane` refuses a
|
|
416
466
|
newer registry with exactly that hint.
|
|
417
467
|
- Existing view sessions keep their layout. `lanes upgrade-session` (also run by every `lanes sync`) adds new tabs and
|
|
418
|
-
renames titles without a rebuild
|
|
468
|
+
renames titles without a rebuild, and closes the tab of a workspace your profile hides; a changed pane layout needs
|
|
469
|
+
a rebuild (agents are never touched). The update's last step, `lanes ensure --if-layout-changed`, makes it where it
|
|
470
|
+
can: a view whose tabs the new release lays out differently is rebuilt when no client is attached to it; one you are
|
|
471
|
+
looking at keeps running and its sidebar footer says `update ready · reload (r)` — press `r` in the sidebar (or run
|
|
472
|
+
`lanes rebuild <device>`) when it suits you: the window reconnects. Close the window instead and
|
|
473
|
+
`lanes-ensure.timer` rebuilds it within a minute. A full `--server` install does not run that step: run `lanes
|
|
474
|
+
ensure --if-layout-changed` yourself afterwards.
|
|
475
|
+
A layout built by 1.2.0 or later names its sidebar and viewports through `~/.local/share/lanes/current`, so running
|
|
476
|
+
views move onto the new release by themselves: each sidebar notices the new version within 30 s and, as soon as
|
|
477
|
+
nothing is open on it (no prompt, filter being typed, armed upload strip, picker, key sheet, action, undo offer,
|
|
478
|
+
upload or `r` rebuild on its way), saves its cursor, scroll, opened-lane marks and filter and restarts from the new
|
|
479
|
+
tree (a ~100 ms repaint). The update's `lanes reload-views` tells each viewport (`view.sh`) that can take it to
|
|
480
|
+
re-execute itself from `current` the next time its lane's viewer ends by itself — a detach, a switch to another
|
|
481
|
+
lane, the end of a status screen — never by cutting an attached lane (by hand: `kill -USR2 <pid>`, its pid in
|
|
482
|
+
`~/.local/state/lanes/views/<device>/<workspace>.<slot>.pid`; only a viewport of 1.2.0 or later — an older one ends
|
|
483
|
+
on that signal, so `lanes reload-views` names it and leaves it alone). A layout built before names its
|
|
484
|
+
`pkg/<version>` tree and stays on it until `lanes rebuild <device>` (`lanes doctor` warns; its footer says ` server …
|
|
485
|
+
· view … — ?`). Either kind keeps its tabs: `lanes sync` recognises them by the file they run.
|
|
486
|
+
- `lanes doctor` then names whatever still runs the previous code, each with its fix: a process that runs with an
|
|
487
|
+
older run record, or with none at all (it was started by a release before the records, such as a statusd or a
|
|
488
|
+
sidebar still running from 1.1.0), a device layout that runs the old tree (`lanes rebuild <device>`),
|
|
489
|
+
a unit whose `ExecStart` does (re-run the server installer). See [`lanes doctor`](#lanes-doctor).
|
|
419
490
|
|
|
420
491
|
Back up the catalog (`~/.config/lanes/workspaces/`, including `registered-sessions.json`) and the running checkout
|
|
421
|
-
before a server update.
|
|
492
|
+
before a server update. An npm-installed server rolls back with `lanes update --rollback` (below); a checkout, by
|
|
493
|
+
checking out the previous commit and running `./install.sh --server --update-only`. Do not rebuild device sessions as a
|
|
494
|
+
rollback shortcut.
|
|
495
|
+
|
|
496
|
+
**Automatic server updates (W9 U3).** A full `--server` install enables `lanes-update.timer`: 15 minutes after a boot,
|
|
497
|
+
then every 30 minutes (each time up to 5 minutes later, at random), it runs `lanes update --timer`. On a server the npm
|
|
498
|
+
installer set up (`~/.local/share/lanes/current` names a staged `pkg/<version>` tree), that:
|
|
499
|
+
|
|
500
|
+
1. runs the post-commit of the installed release again when the last update ended `partial` (from the timer once per
|
|
501
|
+
release, since a cause that stays would restart every service every 30 minutes; by hand every time);
|
|
502
|
+
2. asks the npm registry which release the dist-tag `latest` names — or `lanes-stable`, with the line `channel=stable`
|
|
503
|
+
in `~/.config/lanes/update.conf` (`#` comment lines; any other channel is refused and nothing is asked) — unless a
|
|
504
|
+
check answered less than 20 minutes ago (`lanes update --now` asks anyway);
|
|
505
|
+
3. installs it when it is newer than the installed release (major.minor.patch as numbers: never a downgrade) and not
|
|
506
|
+
the release a rollback left (`bad`), with the npx the npm installer recorded when it installed the server
|
|
507
|
+
(`~/.local/state/lanes/update/npx`; a unit's PATH has no Node.js): `npx --yes @vibelord/lanes@<release> --server
|
|
508
|
+
--update-only`, the update described above. The economy poller's, the notifier's and `LANES_WORKSPACES` settings
|
|
509
|
+
the installed units carry are given to it again, so the units it renders keep them.
|
|
510
|
+
|
|
511
|
+
Only the full install enables the timer: an update (`--update-only`) enables nothing, the timer included. A server set
|
|
512
|
+
up before 1.2.0, the first release with `lanes update`, and updated since with `npx @vibelord/lanes@latest --server
|
|
513
|
+
--update-only` therefore has the timer off and installs nothing by itself — run the full `npx @vibelord/lanes@latest
|
|
514
|
+
--server` once, or `systemctl --user enable --now lanes-update.timer`. Until then `lanes doctor` says `⚠ updates: …
|
|
515
|
+
lanes-update.timer is not enabled`.
|
|
516
|
+
|
|
517
|
+
`lanes update` by hand does the same; one runs at a time. Each check is recorded in `update/last.json` as `check`
|
|
518
|
+
(when, which channel and tag, what the registry named, what came of it), beside the install's own record; `lanes
|
|
519
|
+
update --status` prints that file, and `lanes doctor` says how long ago the last check was and how the last update
|
|
520
|
+
ended. A registry that does not answer, a recorded npx that is gone (Node.js moved or upgraded: install once by hand
|
|
521
|
+
with `npx @vibelord/lanes --server --update-only` from a shell whose npx works, which records it again) or an install
|
|
522
|
+
that fails changes nothing and exits 1 (the timer's unit then shows as failed); a release older than the installed
|
|
523
|
+
one, or the one marked bad, is not installed (exit 0). A server run from a checkout is left alone (`git pull`).
|
|
524
|
+
|
|
525
|
+
`lanes update --rollback` installs the release `~/.local/share/lanes/current.previous` names again, in place: this
|
|
526
|
+
release's `install/services.py` renders that tree's units and moves `current` back to it (no network, no npx), the
|
|
527
|
+
release left is marked `bad` in the record — no update installs it again; the next newer release is installed as
|
|
528
|
+
usual — and its post-commit runs. The mark is written the moment `current` moves, with the result `partial`: a rollback
|
|
529
|
+
stopped during its post-commit (Ctrl-C, a dropped SSH session) keeps the mark, and the next timer tick runs the
|
|
530
|
+
post-commit again (Ctrl-C and hangups wait for that moment and its record, then end the run). A second rollback, which
|
|
531
|
+
would put the bad release back, is refused (install it by hand with `npx @vibelord/lanes@<release> --server
|
|
532
|
+
--update-only` if you mean to).
|
|
422
533
|
|
|
423
534
|
### Versions
|
|
424
535
|
|
|
425
536
|
- `lanes/VERSION` is the release version; the npm package version equals it (`npx @vibelord/lanes --version`).
|
|
537
|
+
- The sidebar: `?` shows `lanes <installed> · view <running>` on the key sheet's first row. A server update installs a
|
|
538
|
+
new tree and points `~/.local/share/lanes/current` at it, while each running sidebar keeps the tree it started from;
|
|
539
|
+
until it runs the new one its footer says ` server 1.2.0 · view 1.1.0 — press ?` (below the health banner; at 36
|
|
540
|
+
columns ` — ?`). The installed version is read every 30 s, so the note appears within half a minute. A checkout
|
|
541
|
+
install is its own tree and never shows it.
|
|
426
542
|
- `lanes hello [device]` on the server prints `ok device=<device> version=<version> boards=<n>`. The client
|
|
427
543
|
installer calls it to prove the SSH path, the key and the server version; an answer without that greeting is
|
|
428
|
-
reported as "an older server?".
|
|
544
|
+
reported as "an older server?". A client that adds its own release, `lanes hello --client-version 1.1.0`, gets
|
|
545
|
+
` update=<version>` at the end of the line when it is older than the server (`ok device=- version=1.2.0 boards=0
|
|
546
|
+
update=1.2.0`): the exact release to update to, compared as `lanes attach` compares (major.minor.patch, as numbers:
|
|
547
|
+
`1.9.3` is older than `1.10.0`). An equal or newer client, `dev` on either side, or no flag gets the plain line.
|
|
548
|
+
- `lanes version` on the server says which release runs there and what still runs an older one, in one line:
|
|
549
|
+
`lanes 1.2.0 (39998ed) · tree pkg/1.2.0 · current ✓ · statusd runs 1.1.0 ✗ · enrold notify ✓`. In order: this
|
|
550
|
+
tree's `VERSION` and the commit of its npm stamp (none for a checkout), the tree (`pkg/<version>` for an npm
|
|
551
|
+
install), whether `~/.local/share/lanes/current` names that tree (`current pkg/1.2.0 ✗` when it names another: this
|
|
552
|
+
`lanes` was started from an old tree), then the tree each long-running process recorded when it last started
|
|
553
|
+
(`~/.local/state/lanes/run/<name>.tree`: statusd, enrold, notify, economy, studio_host and the newest sidebar).
|
|
554
|
+
Each `✗` there names a process that does not run the current code: its last start ran another tree
|
|
555
|
+
(`statusd runs 1.1.0 ✗`), or ran the current tree before its code last changed (`statusd started before its tree
|
|
556
|
+
changed ✗` — a checkout updated in place keeps its path, so `lanes version` compares the record's time with the
|
|
557
|
+
newest file of the tree's `VERSION`, `bin/` and `libexec/`; docs and web files do not count). Restart it (a stopped
|
|
558
|
+
service keeps its last record, so its `✗` stays until it starts again). `lanes version --json` prints the same
|
|
559
|
+
facts as one `lanes.version/1` document: `{schema, version, commit, tree, current, running: {<name>: {tree,
|
|
560
|
+
version, started, current, changed}}, client_min}` — `started` is the record's time (UTC), `changed` the time of the
|
|
561
|
+
tree's newest code when it is later than `started` (else null; `current` is false then), `client_min` the release
|
|
562
|
+
from which `lanes attach` stops telling a `lanes-launch` to update (the server's major.minor.patch; none on a `dev`
|
|
563
|
+
tree).
|
|
429
564
|
- The client installer prints `lanes client <version>` and copies `VERSION` into `~/.local/share/lanes/client/`.
|
|
565
|
+
`lanes-launch --version` prints it (`lanes-launch 1.2.0`; a checkout's client reads `lanes/VERSION`; `dev` without
|
|
566
|
+
either), and the dim line a window shows while it connects names it: `lanes 1.2.0: connecting to lanes…`.
|
|
567
|
+
- On a phone or tablet, **More** shows the server's release as its first row, **Lanes v1.2.0**, above **Text size**:
|
|
568
|
+
the shell asks the enrolment service's `/enrol/site` once when it opens (the release that service runs; the server
|
|
569
|
+
installer restarts it on every install). Nothing is shown when the service does not answer.
|
|
430
570
|
- `lanes-launch` passes that version to `lanes attach` (`--client-version`). A client older than the server gets one
|
|
431
571
|
dim line before the view opens, and again after it detaches: `lanes client 1.0.4 is older than the server (1.1.0) —
|
|
432
572
|
run lanes-launch update`. A `lanes-launch` from before the flag (1.0.4 or earlier; it has no `update` verb) is told
|
|
433
573
|
`update it: npx @vibelord/lanes@latest` only by a server newer than 1.0.4 — a 1.0.4 server says nothing to it.
|
|
434
574
|
Only major.minor.patch is compared (`1.1.0-rc.1` counts as `1.1.0`); a `dev` version on either side, and your own
|
|
435
575
|
`lanes attach` on the server, get no line.
|
|
576
|
+
- A window updates its own client (W9 K2). Before `lanes-launch` connects a window (a device, a workspace or
|
|
577
|
+
`lanes-launch lane <id>`), and at most once every 6 h per machine, it asks the server `lanes hello --client-version
|
|
578
|
+
<its release>` over the same SSH path (`BatchMode`, 5 s to connect, 10 s in all; the dim line `lanes 1.2.0: asking
|
|
579
|
+
lanes for a newer client…` shows meanwhile). When the answer ends ` update=<version>`, it first asks npm whether it
|
|
580
|
+
has exactly that release (`npm view @vibelord/lanes@<version> version --fetch-retries=0`, at most 15 s; the dim line
|
|
581
|
+
`lanes 1.1.0: the server runs 1.2.0; asking npm for it…` shows meanwhile), then runs `npx --yes
|
|
582
|
+
@vibelord/lanes@<version>` — exactly the server's release, never `@latest` — with the choices in
|
|
583
|
+
`~/.config/lanes/install-options` given again as flags (as `lanes-launch update` does, without your window's
|
|
584
|
+
options), then starts again as the new client with the same command line, which connects as usual. That restart
|
|
585
|
+
carries `LANES_AUTOUPDATED=1` and never updates again, so a window updates at most once. Each question touches
|
|
586
|
+
`~/.local/state/lanes-client/checked`, answered or not; windows that open at the same moment ask once between
|
|
587
|
+
them. It never runs for `--helper`, `ls`, `web`, `update` or `--version`, for a client run from a checkout (`git
|
|
588
|
+
pull` updates that one), or for one without a release. `LANES_NO_AUTOUPDATE=1` in the window's environment turns
|
|
589
|
+
it off.
|
|
590
|
+
A release npm does not have yet installs nothing and shows nothing: a server run from a checkout names its
|
|
591
|
+
`VERSION` as soon as it is pulled, which can be before the release is published, and no client can change that.
|
|
592
|
+
The window logs it (`self-update: the server runs 1.2.0, which npm does not have (yet): not installed`) and the
|
|
593
|
+
next question, 6 h later, asks npm again, so the clients follow the publish by themselves. Until then `lanes
|
|
594
|
+
attach` still says the client is older than the server.
|
|
595
|
+
When it cannot update, it says so in one dim line and the window goes on with the client it has:
|
|
596
|
+
`lanes: the update to 1.2.0 did not finish (exit 1) — this window goes on with 1.1.0; windows try again in 6 h, or
|
|
597
|
+
now: npx @vibelord/lanes@1.2.0`; also when npm cannot be asked (`asking npm for it failed (exit 1, ENOTFOUND)`),
|
|
598
|
+
when no npx with its npm is found (on the window's PATH, nor through your login shell's profile — a window a
|
|
599
|
+
desktop app opened has the system's minimal PATH), and when `install-options` was written by a newer installer
|
|
600
|
+
(`lanes-launch update` then decides). The install gets 10 minutes. When that runs out, or you close the window or
|
|
601
|
+
press Ctrl-C while it runs, the window ends every process of it — npx, npm, the npm wrapper, `install.sh` and what
|
|
602
|
+
that started — before it goes on or closes, so nothing of the install keeps writing into the window or swaps the
|
|
603
|
+
client folder afterwards. A question without an answer (no server, a busy one, or a server from before
|
|
604
|
+
1.2.0, which takes the flag for a device name) is only logged (`self-update:` lines in `launch-<target>.log`): the
|
|
605
|
+
connection that follows says what is wrong with the path.
|
|
606
|
+
- A first install with an invite is the server's release too (W9 K3): the npm installer asks the enrolment service's
|
|
607
|
+
`/enrol/site` for the server's release before it installs anything and, when that is newer, runs `npx --yes
|
|
608
|
+
@vibelord/lanes@<release>` instead, which redeems the code ([Join with an invite](#join-with-an-invite)). The phone's
|
|
609
|
+
**Lanes v1.2.0** row reads the same `version`.
|
|
436
610
|
- `lanes-launch web` asks the server's `lanes hello` first, on the same connection. A 1.0.x server does not know the
|
|
437
611
|
options a 1.1.0 client adds (`--default-device`, `--no-qr`), so the client leaves them out there and says so in one
|
|
438
612
|
line: `the server runs lanes 1.0.4, older than this client: no QR code, and the upload box of this view does not
|
|
@@ -443,8 +617,9 @@ before a server update. To roll back, restore those files; do not rebuild device
|
|
|
443
617
|
|
|
444
618
|
- Client: `./install.sh --uninstall` or `npx @vibelord/lanes --uninstall`. Your view session on the server stays until
|
|
445
619
|
an administrator removes it; it holds no agents.
|
|
446
|
-
- Server: disable `lanes-statusd.service`, `lanes-sync.timer`, `lanes-ensure.timer`, `lanes-gc.timer
|
|
447
|
-
opt-in units you enabled; for the web endpoint also run `tailscale serve --https=8443
|
|
620
|
+
- Server: disable `lanes-statusd.service`, `lanes-sync.timer`, `lanes-ensure.timer`, `lanes-gc.timer`,
|
|
621
|
+
`lanes-update.timer` and any opt-in units you enabled; for the web endpoint also run `tailscale serve --https=8443
|
|
622
|
+
off`. Keep the state and the
|
|
448
623
|
container lane data until you have decided which sessions to retain. See
|
|
449
624
|
[INSTALL §6](INSTALL.md#6-updates-optional-operations-and-removal).
|
|
450
625
|
|
|
@@ -457,19 +632,20 @@ before a server update. To roll back, restore those files; do not rebuild device
|
|
|
457
632
|
| `lanes-window` | Your device's view in its own Ghostty instance (Linux: Ghostty, else Foot with the same window class) |
|
|
458
633
|
| `lanes-window 2` | An independent second view, device `<device>-2` (another monitor) |
|
|
459
634
|
| `lanes-window w2` / `lanes-window w3` | A wide view, device `<device>-w2` / `-w3`, with two or three viewports per workspace |
|
|
460
|
-
| `lanes-window --ascii` / `--light` / `--dark` [suffix] | The same, with that look recorded
|
|
635
|
+
| `lanes-window --ascii` / `--light` / `--dark` [`--device-only`] [suffix] | The same, with that look recorded as `lanes attach` records it: in the owner's profile on a device with an owner, in the device file with `--device-only` or on a device without one (section 9) |
|
|
461
636
|
| macOS **Lanes** app | `~/Applications/Lanes.app` (Spotlight: Lanes) runs `lanes-window` |
|
|
462
637
|
| Omarchy | `Super+Shift+L` opens or focuses the window; `Super+Shift+Alt+L` opens the `w2` wide view |
|
|
463
638
|
| `lanes-launch` | Your device's view inside the current terminal |
|
|
464
639
|
| `lanes-launch <workspace>` | Your device's view with that workspace's tab in front |
|
|
465
640
|
| `lanes-launch <device> --viewports N` | A view with N (2–3) viewports per workspace |
|
|
466
|
-
| `lanes-launch <device> [--ascii] [--theme dark\|light\|auto] [--glyphs M] [--color C]` | The view with a recorded look |
|
|
641
|
+
| `lanes-launch <device> [--ascii] [--theme dark\|light\|auto] [--glyphs M] [--color C] [--notify 1\|0] [--dim 90\|2] [--editor E]` | The view with a recorded look (and desktop notifications on or off); `--editor` is the editor of [quick edit](#quick-edit) |
|
|
467
642
|
| `lanes-launch lane <lane-id>` | One lane straight in this terminal, no multiplexer (native terminal scrollback) |
|
|
468
643
|
| `lanes-launch ls` | List the lanes with live status (`lanes ls` on the server) |
|
|
644
|
+
| `lanes-launch here [device]` | Show here the lane you opened last on another of your devices (`lanes here` on the server; default: this machine's device). An open window shows it at once with its tab in front; a closed one shows it when it next attaches. Asked for, never automatic |
|
|
469
645
|
| `lanes-launch web phone` / `laptop` / `<device>` | Not a terminal view: a browser view's private URL and a login token (section 4) |
|
|
470
646
|
|
|
471
|
-
`web`, `update`, `lane` and `
|
|
472
|
-
`lanes-launch <name>`.
|
|
647
|
+
`web`, `update`, `lane`, `ls` and `here` are verbs: a device or workspace with one of those names is not reached
|
|
648
|
+
through `lanes-launch <name>`.
|
|
473
649
|
|
|
474
650
|
Closing the window detaches; nothing on the server stops. `lanes-launch` never ends by itself: when the connection
|
|
475
651
|
drops, the server drops a slow client, or the server refuses, it says so, waits with a countdown (backing off up to
|
|
@@ -477,7 +653,7 @@ drops, the server drops a slow client, or the server refuses, it says so, waits
|
|
|
477
653
|
configuration defines, first `lanes-ip` (the server's tailnet address, when `lanes` names it by its MagicDNS name), then
|
|
478
654
|
`lanes-public` (when the site defines one), and after the last it goes back to `lanes`. Exits 64–78 are server refusals and retry after 60 s. Its log is
|
|
479
655
|
`~/.local/state/lanes-client/launch-<target>.log`. When the server rebuilt the view, the client reconnects straight
|
|
480
|
-
away.
|
|
656
|
+
away. Before it connects, a window may first update its own client from npm ([Versions](#versions)).
|
|
481
657
|
|
|
482
658
|
### Devices and views
|
|
483
659
|
|
|
@@ -486,12 +662,69 @@ away.
|
|
|
486
662
|
- Each view has one tab per workspace in catalog order, then SSH split workspaces and tool tabs (the **Throttle**
|
|
487
663
|
monitor when a manifest configures `monitor`), then **Host** (a login shell on the server). An `sshWorkspaces`
|
|
488
664
|
entry with `layout: "lanes"` sits just before Host unless its `position` is `workspace`.
|
|
665
|
+
- On a device with an owner the workspace tabs are that person's: the profile's `workspaces.hidden` boards have no tab
|
|
666
|
+
on any of their devices, and `workspaces.order` puts the boards it names first, in its order (the others follow in
|
|
667
|
+
catalog order; an id no board has is ignored). Every `lanes sync` and `lanes ensure` brings running views in line
|
|
668
|
+
— a hidden board's tab is closed (only its sidebar and view panes: the lanes keep running in their containers), a
|
|
669
|
+
board shown again comes back as a new tab. A profile that hides every board hides none. A device nobody owns keeps
|
|
670
|
+
the catalog order.
|
|
489
671
|
- Phones and browsers are separate devices (`phone`, `browser`, or a name you choose); a device keeps the layout
|
|
490
672
|
(desktop or phone) it was created with.
|
|
491
673
|
- On the server: `lanes device list` shows each device (phone or desktop with its viewports, the owner's login once
|
|
492
674
|
the machine was enrolled through an invite or a request — the current member's, so a handed-over machine shows its
|
|
493
675
|
new owner — running or not);
|
|
494
676
|
`lanes device forget <name>` stops `lanes ensure` recreating it; `lanes rebuild <device>` recreates a stuck view.
|
|
677
|
+
- A device from before enrolment has no owner (`lanes doctor` names it), so it keeps its look and opened-lane marks to
|
|
678
|
+
itself. `lanes device claim <device> --login <login> [--name N]` names one: it records the owner on the device and its
|
|
679
|
+
windows and folds their look (a `--device-only` key stays the device's) and marks into that person's profile; run
|
|
680
|
+
again (with or without `--name`) it keeps the
|
|
681
|
+
name it recorded unless `--name` gives a new one. Its running sidebars follow the profile from their next tick: the
|
|
682
|
+
person's look, and a Done lane opened on any of their devices leaves Done on all of them. On a device enrolled
|
|
683
|
+
before profiles existed the profile already wins wherever it sets a key; a claim naming the login its member record
|
|
684
|
+
names folds the look the device recorded then into the profile. A member record decides
|
|
685
|
+
whenever one exists — a claim that disagrees with it is refused and names `lanes keys owner`. `lanes user list` shows
|
|
686
|
+
the people lanes knows with their devices, `lanes user show <login>` one profile (what it holds: [Your
|
|
687
|
+
profile](#your-profile)).
|
|
688
|
+
|
|
689
|
+
### Your profile
|
|
690
|
+
|
|
691
|
+
Once a device has an owner — its member record from an invite or a request, or one `lanes keys owner` or `lanes
|
|
692
|
+
device claim` named for a device from before enrolment — Lanes keeps what you choose per person, not per device: one
|
|
693
|
+
file on the server, `users/<login>.json`, shared by every device you own (the Mac, its `mac-2` window, `mac-phone`, a
|
|
694
|
+
browser view). `lanes user show <login>` prints it.
|
|
695
|
+
|
|
696
|
+
| Kept in your profile (every device you own) | Kept per device (`devices/<device>.json`, `clients/`) |
|
|
697
|
+
| --- | --- |
|
|
698
|
+
| The look: theme, colour mode, glyphs (`lanes attach --theme\|--color\|--glyphs\|--ascii`, `lanes-window --light\|--dark\|--ascii`) | A look key set with `--device-only`, and `--notify` and `--dim` (always per device) |
|
|
699
|
+
| The lanes you opened, so a Done lane opened on the phone leaves Done on the Mac, and the last lane you opened | The device's own record of what it opened (`clients/<device>.seen.json`: an older release reads it) |
|
|
700
|
+
| Board order and hidden boards (`lanes workspaces order\|hide\|show`) | The layout (desktop or phone), viewports, the machine a window belongs to, its owner |
|
|
701
|
+
| Pinned and hidden lanes (`lanes lane pin\|unpin\|hide\|show`) | |
|
|
702
|
+
| Your own quiet window for pushes, `notify.quiet_s` (section 8; no verb sets it yet) | |
|
|
703
|
+
|
|
704
|
+
- **Per user or per device.** A look flag on a device with an owner goes to your profile, and every sidebar of every
|
|
705
|
+
device you own repaints within a tick. Add `--device-only` to keep it on that device alone (`lanes-window --light
|
|
706
|
+
--device-only`); the same flag without `--device-only` hands the key back to the profile. A device nobody owns keeps
|
|
707
|
+
everything in its own file, exactly as before profiles (section 9 has the order a sidebar paints in).
|
|
708
|
+
- **Claim and adopt.** A device from before enrolment follows no profile: `lanes doctor` names it, and `lanes device
|
|
709
|
+
claim <device> --login <login>` gives it an owner and folds its look (never a `--device-only` key) and opened-lane
|
|
710
|
+
marks into that person's profile, with its windows. `lanes keys adopt --login <login>` and `lanes keys owner <device>
|
|
711
|
+
<login>` fold the devices whose keys they move in or name the same way. A device whose member record already names
|
|
712
|
+
you folds a look it recorded before profiles existed with the same claim.
|
|
713
|
+
- **Arrange boards and lanes**, on the server, for the owner of `--device <device>` (else of `$LANES_DEVICE`) or for
|
|
714
|
+
`--login <login>`: `lanes workspaces hide <id…>` / `show <id…>` takes boards off your tabs and puts them back (hiding
|
|
715
|
+
the last visible board is refused); `lanes workspaces order <id…>` puts those boards first, in that order, the rest
|
|
716
|
+
in catalog order. `lanes lane pin <lane-id>` lists a lane first in its section; `lanes lane hide <lane-id>` leaves a
|
|
717
|
+
registered lane out of your lists (a running unregistered session is always listed); `unpin` and `show` undo them.
|
|
718
|
+
The sidebar keys do the same where you are looking: `p` pins or unpins the cursor lane, `h` hides it, `H` hides
|
|
719
|
+
this board (after `y` and Enter) and `o` shows what you hid, faint, until you press it again. Every sidebar of yours
|
|
720
|
+
follows within a tick (pinned lanes first, hidden ones out); the tabs take the board order and close a hidden
|
|
721
|
+
board's tab at the next `lanes upgrade-session` — `H` starts one at once, and every `lanes sync` (2-minute timer) and
|
|
722
|
+
`lanes ensure` (1-minute timer) runs one.
|
|
723
|
+
- **Pick up elsewhere.** `lanes-launch here` (on the server `lanes here <device>`) shows in this machine's view the lane
|
|
724
|
+
you opened last on any of your devices and brings its tab to the front; it is asked for, never automatic. It says
|
|
725
|
+
why when it cannot: nothing opened yet, the lane is gone, or its board is one you hide.
|
|
726
|
+
- **Safe to delete.** Without the file the look falls back to the device files and the environment, Done lanes show
|
|
727
|
+
again until opened, and the arrangement resets.
|
|
495
728
|
|
|
496
729
|
### Panes and viewports
|
|
497
730
|
|
|
@@ -514,20 +747,28 @@ Keys in the `shared` block work in every mode, locked included:
|
|
|
514
747
|
| `Ctrl-Alt-↓` / `Ctrl-Alt-↑` | Point the focused viewport (the first one when the sidebar has the focus) at the next / previous lane of its workspace in registry order, wrapping. Runs `lanes next\|prev --focused` in a floating pane that closes at once; nothing is typed and the focus stays. Does nothing on Host or tool tabs |
|
|
515
748
|
| `Ctrl-Alt-/` | Find: moves the focus to the sidebar and starts its filter |
|
|
516
749
|
| `Ctrl-Alt-u` | Arm the sidebar's upload strip, then drop files anywhere on the window |
|
|
750
|
+
| `Ctrl-Alt-l` | Move the focus to the sidebar, nothing else: browse it with its keys, `Esc` gives the keyboard back to the lane |
|
|
517
751
|
| `Ctrl-Alt-c` | Copy the selection |
|
|
518
752
|
| `Ctrl-Alt-PageUp` / `Ctrl-Alt-PageDown` | Page up / down and enter scroll mode |
|
|
519
753
|
| `Ctrl-Alt-End` | Scroll to the bottom and return to normal mode |
|
|
520
754
|
| `Ctrl-Alt-s` | Zellij's session manager (floating) |
|
|
521
755
|
|
|
522
|
-
`Ctrl-Alt
|
|
523
|
-
|
|
756
|
+
`Ctrl-Alt-/`, `Ctrl-Alt-u` and `Ctrl-Alt-l` walk the focus left to the sidebar (three steps, enough from the third
|
|
757
|
+
viewport of a wide view). `Ctrl-Alt-/` and `Ctrl-Alt-u` then type nothing: they run `lanes sidebar-key filter|upload
|
|
758
|
+
--focused` in a floating pane that closes at once, which tells that sidebar to start its filter or arm its strip
|
|
759
|
+
(cockpit v3 S4; until then they typed `/` or `U`, which on a tab without a sidebar went into the lane). On a tab
|
|
760
|
+
without a sidebar (Host, Throttle, the phone's view tab) they do nothing. The first keys after the chord already
|
|
761
|
+
reach the filter; keys typed while the floating pane is still open (a fraction of a second) go to it, not to the
|
|
762
|
+
sidebar. Zellij 0.45.1 matches `Ctrl-Alt-/` in the kitty keyboard protocol and xterm `modifyOtherKeys` encodings
|
|
763
|
+
(measured); the legacy `Esc` + `Ctrl-_` byte pair is not read as `Ctrl-Alt-/` and reaches the focused pane as is.
|
|
524
764
|
|
|
525
765
|
Normal mode:
|
|
526
766
|
|
|
527
767
|
| Key | Action |
|
|
528
768
|
| --- | --- |
|
|
529
|
-
| `Ctrl-Alt-g` | Lock the view; press again to unlock. The mode ribbon shows LOCKED in the urgent accent. While locked, the mode keys below and `Ctrl-Alt-
|
|
769
|
+
| `Ctrl-Alt-g` | Lock the view; press again to unlock. The mode ribbon shows LOCKED in the urgent accent. While locked, the mode keys below, `Ctrl-Alt-m` and `Ctrl-Alt-e` reach the lane; the `shared` keys above stay active |
|
|
530
770
|
| `Ctrl-Alt-m` | Markdown preview of the focused lane (every mode except locked; see below) |
|
|
771
|
+
| `Ctrl-Alt-e` | [Quick edit](#quick-edit) of the focused lane's AGENTS.md, CLAUDE.md or .env.local: a one-key picker (`1`–`5`, `Enter` when one file is there, `q`/`Esc` closes), then an editor inside its container ([which one](#the-in-container-lane-command); Lanes installs none, vi is the floor), in a floating **Edit** pane that closes when the editor exits (every mode except locked). One Edit pane per view: pressing it again while one is open goes back to that editor (in another tab it names the tab instead) and opens no second one |
|
|
531
772
|
| `Ctrl-Alt-p` | Pane mode |
|
|
532
773
|
| `Ctrl-Alt-t` | Tab mode |
|
|
533
774
|
| `Ctrl-Alt-n` | Resize mode |
|
|
@@ -566,9 +807,19 @@ Top to bottom, a desktop sidebar is:
|
|
|
566
807
|
1. **Header**: the workspace title, then the attention counts in the column-2 glyphs, e.g. ` Sakio ◆1 ◇2 ▸6 ✓1`
|
|
567
808
|
(needs approval, needs input, working, done; zeros left out; a `done` you have opened is not counted). Without
|
|
568
809
|
agents it says `N lanes`. The counts are dimmed while the footer warns that status is old.
|
|
569
|
-
2. **Body**: **Needs you** (`◆` `✗` `◇`, most urgent then
|
|
570
|
-
|
|
571
|
-
|
|
810
|
+
2. **Body**: **Needs you** (`◆` `✗` `◇`, most urgent first, then in the order the lanes started needing you),
|
|
811
|
+
**Working** (in the order they started working), **Done** (the newest first), then the workspace's other lanes
|
|
812
|
+
under its own title (`── Sakio (25) ──`). Once any lane runs an agent, the **Needs you** and **Working** headers
|
|
813
|
+
stay on screen even when empty (`── ▸ Working (0)`, glyph dimmed; a header with lanes says its name in its section's
|
|
814
|
+
colour in truecolor and 256 colours), so a lane moving between them never pushes the
|
|
815
|
+
rows below it around; **Done** appears only when a lane is in it. A pending approval or an error sorts to the top
|
|
816
|
+
of Needs you while it lasts; after that (a question, an approval prompt, the question again) the lane is back in
|
|
817
|
+
the place it entered at. A lane moves up a section at once (a question or an approval is never delayed) and down
|
|
818
|
+
only after the new state has held: 8 s to leave Needs you, 5 s to leave Working or Done (the status daemon looks
|
|
819
|
+
every 3 s, so in practice 9 s and 6 s). An agent that asks, works for a few seconds and asks again stays in Needs
|
|
820
|
+
you, a working lane whose signal blinks for a cycle stays in Working, and the rows below them stay put. When a lane changes section above
|
|
821
|
+
your cursor, the list stays where it is and the cursor moves with its lane; the list scrolls only when that lane
|
|
822
|
+
would leave the pane. Then `zmx lanes (N)` when any exist, then **Unregistered (N)**
|
|
572
823
|
with its `+ New shell` row. A list longer than the pane ends in `⋮ N more`; moving onto that row or clicking it
|
|
573
824
|
scrolls instead of opening.
|
|
574
825
|
3. **Upload strip** (panes of 14 rows or more): ` ↑ uploads · click, then drop (U)` (section 5).
|
|
@@ -583,29 +834,39 @@ Unregistered section, no upload strip and no `+`, and its footer has no `x close
|
|
|
583
834
|
|
|
584
835
|
| Column | Shows |
|
|
585
836
|
| --- | --- |
|
|
586
|
-
| 1 | `▌` the cursor |
|
|
837
|
+
| 1 | `▌` the cursor; in truecolor and 256 colours the whole cursor row (to column 35) also sits on a soft background |
|
|
587
838
|
| 2 | The agent glyph (table in section 1); dimmed while the lane is stale |
|
|
588
|
-
| 3 | `›` the lane this pane shows (not on the cursor row, where the bold name says it) · `²` / `³` shown in viewport 2 / 3 |
|
|
589
|
-
| 4–27 | The name in its identity colour, bold while shown; up to three `·` when other sessions view the lane; a clipped name ends in `…` |
|
|
839
|
+
| 3 | `›` the lane this pane shows (not on the cursor row, where the bold name says it) · `²` / `³` shown in viewport 2 / 3 · `…` (dim; ASCII `~`) an action on its way: a session `x` is closing, a session `P` is registering (until its lane is listed), a shell being created (on the New shell row) · `▲` (ASCII `^`) a lane you pinned (`p`), the lowest of these marks |
|
|
840
|
+
| 4–27 | The name in its identity colour (else the theme's ink in truecolor and 256 colours), bold while shown, faint for a lane you hid while `o` shows it; up to three `·` when other sessions view the lane; a clipped name ends in `…` |
|
|
590
841
|
| 28 | `+` on an unregistered tmux session that can be added to a workspace |
|
|
591
|
-
| 29–31 | Age: the agent's `since`, else the last output: `45s`, ` 3m`, ` 2h`, ` 4d` (at most `99d`); blank when unknown; amber inside **Needs you** |
|
|
592
|
-
| 33 | The terminal status glyph (section 1); dimmed when an agent glyph is in column 2 (`▸` with `○` is a long tool call, not a stuck lane) |
|
|
842
|
+
| 29–31 | Age: the agent's `since`, else the last output: `45s`, ` 3m`, ` 2h`, ` 4d` (at most `99d`); blank when unknown; amber inside **Needs you**, and in truecolor and 256 colours blue in **Working** and green in **Done** |
|
|
843
|
+
| 33 | The terminal status glyph (section 1); dimmed when the lane is stale, and in 16 colours also when an agent glyph is in column 2 (`▸` with `○` is a long tool call, not a stuck lane) |
|
|
593
844
|
| 34 | One tail cell by priority: `?` not observed right now > `⚠` starving > `↓` downgraded > `$` mostly paid (section 8) |
|
|
594
845
|
| 35 | `×` close, on unregistered sessions only |
|
|
595
846
|
|
|
596
|
-
Marks and their ASCII twins: `▌` `|` cursor · `›` `=` viewing · `²` `2` / `³` `3` other viewport · `·` `.` viewer ·
|
|
847
|
+
Marks and their ASCII twins: `▌` `|` cursor · `›` `=` viewing · `²` `2` / `³` `3` other viewport · `▲` `^` pinned · `·` `.` viewer ·
|
|
597
848
|
`…` `~` clipped · `×` `x` close · `+` `+` new / add · `⋮` `:` more rows · `↑` `^` uploads · `↓` `v` drop now ·
|
|
598
849
|
`⚠` `!` warning or starving · `↓` `v` downgraded · `$` `$` paid. The full tables, colours and mockups are in
|
|
599
850
|
[DESIGN.md](DESIGN.md).
|
|
600
851
|
|
|
601
|
-
**Footer**, highest priority first: the
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
`
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
852
|
+
**Footer**, highest priority first: the open prompt — the sidebar asks one thing at a time, in one of two shapes. A
|
|
853
|
+
name field: ` › Rename: …█`, or ` › Register as: …█` for promote (a name longer than the field shows its end, `…` first,
|
|
854
|
+
so what you type stays in view). A confirmation: ` › close ‘name’ — y⏎ confirm · Esc cancel` or
|
|
855
|
+
` › register ‘name’ — y⏎ confirm · Esc cancel` (promote), `⏎ confirm` once `y` armed it; at 36 columns ` cancel`
|
|
856
|
+
gives way first, then ` · Esc`, then the name is clipped, so the keys always show. Then the Add to workspace prompt
|
|
857
|
+
` Choose a workspace · Esc cancels` · the filter ` /abc · 3 matches` · the health banner
|
|
858
|
+
(` ⚠ host under memory pressure · status 3 m old`, or ` ⚠ lane sockets deleted · lanes run; on the server: lanes
|
|
859
|
+
sockets repair`) · the version note ` server 1.2.0 · view 1.1.0 — press ?` while this sidebar runs another tree than
|
|
860
|
+
the installed one (at 36 columns ` — ?`; section [Versions](#versions)) · ` ⚠ registry: …` · the last message · the undo offer ` Unregistered ‘name’ · z undo` (10 s after
|
|
861
|
+
`u`) · the legend ` ⏎ open · / find · x close · ? keys`. The phone
|
|
862
|
+
draws the same footer (its picker says ` Choose a workspace · Tap Cancel`, and ` Swipe to scroll · Tap to open` when
|
|
863
|
+
idle). An empty workspace shows `no lanes declared · + New shell` on its New shell row.
|
|
864
|
+
|
|
865
|
+
**Sidebar keys** (`?` shows the short form of this table in place of the list until the next key; that key only closes
|
|
866
|
+
the sheet). Its first row says which lanes: `lanes <installed version> · view <the version this sidebar runs>`. The sheet puts companion keys on one line (`J K`, `Tab S-Tab`, `+ U`, `u z`, `p h`, `H o`, `r ?`), leaves `PageUp` / `PageDown`
|
|
867
|
+
to this table and ends with `Ctrl-Alt-l` — not a sidebar key but the chord that brings the keyboard here from a lane
|
|
868
|
+
(section [Keybindings](#keybindings)). Every key below does something in the sidebar, and no other key does
|
|
869
|
+
(`tests/unit/test_sidebar.py` `DocumentedKeys` presses each one and every other key a keyboard sends):
|
|
609
870
|
|
|
610
871
|
| Key | Action |
|
|
611
872
|
| --- | --- |
|
|
@@ -616,22 +877,63 @@ New shell row.
|
|
|
616
877
|
| `Tab` / `Shift-Tab` | Open the next / previous lane that needs you, cycling from the lane shown (` nothing needs you` when the section is empty) |
|
|
617
878
|
| `2` / `3` | Show the cursor lane in viewport 2 / 3 (wide views) |
|
|
618
879
|
| `/` | Find a lane |
|
|
619
|
-
| `Esc` | Clear the filter; cancel a prompt, a rename, Add to workspace or an armed upload strip |
|
|
880
|
+
| `Esc` | Clear the filter; cancel a prompt, a rename, Add to workspace or an armed upload strip; the keyboard goes back to the lane |
|
|
620
881
|
| `g` / `G` | First / last row |
|
|
621
882
|
| `+` | New persistent shell in this workspace |
|
|
622
883
|
| `a` | Add the cursor session to a workspace |
|
|
623
|
-
| `x` | Close the cursor session (
|
|
884
|
+
| `x` | Close the cursor session: `y` arms, `Enter` closes, any other key cancels (on a registered lane: ` registered: u to unregister first`) |
|
|
885
|
+
| `u` | Unregister the cursor session added with `a`: it moves back to **Unregistered**, still running, and the cursor goes with it; on a lane a repository's `terminals.json` declares, take its terminal out of that file |
|
|
886
|
+
| `z` | Undo that unregister (within 10 s) |
|
|
624
887
|
| `R` | Rename the cursor lane |
|
|
625
|
-
| `P` | Promote (register) the cursor session in the repository's `terminals.json` |
|
|
888
|
+
| `P` | Promote (register) the cursor session in the repository's `terminals.json`: the name first (`Enter` accepts), then `y` and `Enter` |
|
|
889
|
+
| `p` | Pin / unpin the cursor lane: on every device you own it leads its section, marked `▲` (`^` in ASCII), and never falls below `⋮ N more` |
|
|
890
|
+
| `h` | Hide the cursor lane on every device you own (` Hid ‘name’ · o shows it`); on a hidden lane `o` shows, show it again |
|
|
891
|
+
| `H` | Hide this workspace on every device you own: `y` arms, `Enter` hides — its tab closes, its lanes keep running (the last workspace shown stays) |
|
|
892
|
+
| `o` | Show / hide again the lanes you hid (shown faint) |
|
|
626
893
|
| `U` | Arm the upload strip |
|
|
627
|
-
| `r` | Reload and repaint |
|
|
894
|
+
| `r` | Reload and repaint; while the footer says `update ready · reload (r)`, rebuild this view on the installed release (the window reconnects, agents are untouched); the keyboard goes back to the lane |
|
|
628
895
|
| `?` | The key sheet |
|
|
629
896
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
897
|
+
**Pins and hidden lanes** (`p`, `h`, `H`, `o`) are yours, not the device's: they live in your profile
|
|
898
|
+
(`users/<login>.json`, section [Devices and views](#devices-and-views)) and every device you own follows them on its next
|
|
899
|
+
tick. A hidden lane leaves the list, the header counts, the fleet strip and the desktop notifications; a session under
|
|
900
|
+
**Unregistered** is never hidden (nothing alive is), and `/` finds only the lanes shown — `o` first to search hidden
|
|
901
|
+
ones. The filter keeps its match order; pins lead their section everywhere else. A hidden workspace's tab closes on
|
|
902
|
+
every view of yours (`lanes upgrade-session`, which `H` starts); `lanes workspaces show <id>` (cockpit v3 R6) brings it
|
|
903
|
+
back. On a device nobody owns there is no profile: `p`, `h` and `H` say ` no owner: lanes device claim`.
|
|
633
904
|
|
|
634
|
-
|
|
905
|
+
Mouse: a click opens a lane; the wheel moves the cursor three rows; a click on `×` asks to close, on `+` starts Add to
|
|
906
|
+
workspace, on the upload strip arms it. Under an open prompt the wheel does nothing and a click ends the prompt first
|
|
907
|
+
(` Cancelled`), then does what it does (a click on a row opens that lane); a click on the upload strip beside a Rename
|
|
908
|
+
or Register-as field arms the strip and leaves the field. The cursor follows the lane, not the row: when a lane changes
|
|
909
|
+
section the cursor and scroll position go with it.
|
|
910
|
+
|
|
911
|
+
**The keyboard goes back to the lane.** The sidebar and the lane share one keyboard: whichever pane has the focus gets
|
|
912
|
+
what you type. So every sidebar action that ends hands the focus back to the pane's viewport — opening a lane, a
|
|
913
|
+
close, a rename (`Enter`), a register (`a` → workspace, or `P` → `y` → `Enter`), an unregister (`u`) and its undo
|
|
914
|
+
(`z`), a cancel (`n`, `Esc` or any other key at a prompt), `r`, the key that closes the `?` sheet, a drop on the upload strip, and a click that opens nothing
|
|
915
|
+
(a section header, `⋮ N more`, an empty row). Anything else typed in the same burst was meant for the lane and is
|
|
916
|
+
dropped, never read as sidebar keys. A prompt, the filter and the armed upload strip keep the keyboard until you
|
|
917
|
+
answer them, and so does a refused key (`x` on a lane that cannot be closed, `u` on a declared lane), so the `y` you
|
|
918
|
+
type next lands in the
|
|
919
|
+
sidebar, not in an agent's approval prompt. The wheel and a mouse release never move the focus. On the phone the
|
|
920
|
+
picker tab keeps it; only opening a lane leaves it.
|
|
921
|
+
|
|
922
|
+
**The footer says when the keyboard is on the sidebar.** While it is, the legend line reads
|
|
923
|
+
` ▌ keys go to sidebar · Esc → lane` in the accent colour (` | keys go to sidebar - Esc -> lane` in ASCII) instead of
|
|
924
|
+
` ⏎ open · / find · x close · ? keys` — no extra row; a prompt, the filter, a banner or a message still take the line
|
|
925
|
+
first. The sidebar asks Zellij for focus reports and follows them (Zellij 0.45.1 sends them); a Zellij that never
|
|
926
|
+
sends one leaves the legend up for 3 s after each key you type in the sidebar. Every action that hands the keyboard
|
|
927
|
+
back clears it at once. On the phone the picker is a whole tab and has no such legend.
|
|
928
|
+
|
|
929
|
+
**A message stays 3 seconds, or until your next action.** What an action says in the footer (` Closed sh`,
|
|
930
|
+
` Renamed → …`, ` Cancelled`, ` view not running yet`, or why a close failed) stays after the keyboard has gone back
|
|
931
|
+
to the lane, for 3 seconds (within a second more: the sidebar looks once a second), then the legend returns. The next
|
|
932
|
+
action you start in the sidebar — opening a lane, `x`, `R`, `P`, `a`, `u`, `z`, `/`, `U`, `+`, `r`, `?`, `Tab`, a chord —
|
|
933
|
+
replaces it at once. Moving the cursor, typing in a field, `Esc`, the wheel, a mouse release or the focus leaving and
|
|
934
|
+
coming back does not clear it.
|
|
935
|
+
|
|
936
|
+
### Find, rename, add, unregister, promote, new, close
|
|
635
937
|
|
|
636
938
|
- **Find** (`/` or `Ctrl-Alt-/`): type a few letters. Matching is fuzzy (the letters in order; whole-substring and
|
|
637
939
|
prefix matches rank first, word starts score higher). The list flattens (sections drop, column 2 stays) and the
|
|
@@ -639,19 +941,74 @@ cursor and scroll position go with it.
|
|
|
639
941
|
`Esc` clears. No hits:
|
|
640
942
|
` no lane matches ‘abc’ · Esc`.
|
|
641
943
|
- **Rename** (`R`, or `lanes rename <lane-id> <new-name>` on the server): edit in the footer, `Enter` saves, `Esc`
|
|
642
|
-
cancels.
|
|
643
|
-
|
|
944
|
+
cancels. While you type, the keyboard is the field's: `Space`, `j`, `k`, `J`, `K` are letters, and the arrows,
|
|
945
|
+
`Tab`, `Shift-Tab`, `PageUp` and `PageDown` type nothing — none of them opens a lane, moves the cursor or throws the
|
|
946
|
+
name away. A click on the list cancels the field before it opens anything, so a name never stays open in the footer
|
|
947
|
+
after the keyboard has gone to a lane. The lane id never changes, so view targets and Cursor's `lane open <id>`
|
|
948
|
+
keep working. Where the name is stored depends on the lane kind (section 6). `Enter` on an emptied name, or on the
|
|
949
|
+
name the lane already has, writes nothing and says ` name unchanged`.
|
|
644
950
|
- **Add to workspace** (click `+` on the row, or `a`): choose a workspace that uses the session's container (the
|
|
645
951
|
current one first), or **Cancel** / `Esc`. The session keeps its id, name, programs, scrollback and views, moves to
|
|
646
|
-
that workspace's list, loses its `×`, and survives sync and reconnects.
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
952
|
+
that workspace's list, loses its `×`, and survives sync and reconnects. The cursor goes with it into the list;
|
|
953
|
+
**Cancel** / `Esc` puts it back on the session (added to another workspace, the session leaves this list and the
|
|
954
|
+
cursor keeps its row).
|
|
955
|
+
- **Unregister** (`u` on a session added that way, or `lanes unregister <lane-id>` on the server): takes it out of the
|
|
956
|
+
workspace again at once. Nothing stops: a running session moves back to **Unregistered** with the same id and name,
|
|
957
|
+
in the same workspace even if its directory now belongs to another one, its views stay attached, and the cursor goes
|
|
958
|
+
with it; a session that no longer runs leaves the list. The footer
|
|
959
|
+
offers ` Unregistered ‘name’ · z undo`, and `z` within 10 seconds adds it back exactly as it was — unless the session
|
|
960
|
+
was closed and started again under the same name meanwhile, which the undo refuses (`a` adds the new one). `u` hands
|
|
961
|
+
the keyboard back to the lane like every action, so press `z` on the sidebar (`Ctrl-Alt-l` first; the phone's picker
|
|
962
|
+
keeps the keyboard). On a busy host where tmux cannot be asked about the session, `u` changes nothing and says
|
|
963
|
+
` Session could not be checked; nothing was unregistered`; press it again in a moment. If the record was saved but the
|
|
964
|
+
workspace list could not be published (a full disk), it says ` Unregistered; run lanes sync to finish publishing: …`
|
|
965
|
+
and `z` still undoes it. On a lane a repository's `terminals.json` declares, `u` takes its terminal out of that file
|
|
966
|
+
(the same editor the agents' `lanes_unregister` tool uses: the terminal and its `lanes.terminals` entry go, nothing
|
|
967
|
+
else in the file moves, a `.bak-<UTC>` copy is kept the first time), starts `lanes sync` and says ` Unregistered
|
|
968
|
+
name`; the session keeps running and is listed under **Unregistered** after the sync. There is no `z` for it. The
|
|
969
|
+
editor refuses, in its own words, the file's last terminal, a symbolic link in the container's home and a file that
|
|
970
|
+
is gone. A lane of an SSH workspace says ` declared in catalog.json — edit the file`; an unregistered session says
|
|
971
|
+
` not registered — a adds it to a workspace`. `x` on any registered lane says ` registered: u to unregister first`.
|
|
972
|
+
- **Promote** (`P`): asks the name first, ` › Register as: name█` (prefilled with the session's; edit it, `Enter`
|
|
973
|
+
accepts, an emptied name cancels; the field takes keys and clicks as Rename's does), then
|
|
974
|
+
` › register ‘name’ — y⏎ confirm`. `y` arms it (` › register ‘name’ — ⏎
|
|
975
|
+
confirm`) and `Enter` declares the unregistered tmux session under that name in the workspace's repository
|
|
976
|
+
`terminals.json` (only when the workspace has exactly one repository registry) and syncs; the lane then gets a
|
|
977
|
+
repository id `<workspace>--<slug>` (the slug of the repository's prefix + the name). Any other key cancels.
|
|
978
|
+
Refused on a lane that is already registered, and on a session that `terminals.json` already declares under any
|
|
979
|
+
name — a `P` on another device, or one whose row has let go before the sync listed it: ` Already declared as
|
|
980
|
+
'name'; lanes sync lists it`, nothing written. Until the sync lists the lane (a few seconds) the row stays where it
|
|
981
|
+
is, under the new name, with a dim `…` in column 3 (ASCII `~`) and no `+` or `×`; the footer says
|
|
982
|
+
` Registering ‘name’` — from the `Enter` on, before the write itself has answered; if the write refuses, the row
|
|
983
|
+
goes back to its own name and the footer says why. Then the row moves into the workspace's list and the cursor, if
|
|
984
|
+
it was on the row, moves with it. Meanwhile `x`, `a`, `R`, `P` and `u` on that row say ` registering ‘name’ — a moment`; opening it works.
|
|
985
|
+
If no sync has listed the lane 80 s later — the longest one can still take when docker answers slowly — and the
|
|
986
|
+
sync the sidebar started has ended, the mark goes and the footer says ` ‘name’ not listed yet — lanes
|
|
987
|
+
sync` (run `lanes sync` on the server; a broken `terminals.json` shows as ` ⚠ registry: …`).
|
|
650
988
|
- **New shell** (`+`, or the `+ New shell` row): starts a persistent shell in the workspace container on the socket
|
|
651
|
-
`lanes-shells-<workspace>` and shows it. Reopening it attaches to the same shell.
|
|
652
|
-
|
|
653
|
-
|
|
989
|
+
`lanes-shells-<workspace>` and shows it. Reopening it attaches to the same shell. While it is being created (up to
|
|
990
|
+
15 s on a busy host) the sidebar keeps working: the New shell row has a dim `…` (ASCII `~`) after its `+`, the footer
|
|
991
|
+
says ` Creating a shell`, and a second `+` says ` creating a shell — a moment` instead of making another. The keyboard
|
|
992
|
+
stays on the sidebar meanwhile; the shell opens in the viewport and takes the keyboard when it is ready — unless you
|
|
993
|
+
have moved on (the keyboard went elsewhere, you opened another lane there, or a prompt is open), in which case it is
|
|
994
|
+
only listed under **Unregistered** and the cursor, if it was still on the New shell row, moves onto it (`Enter` opens
|
|
995
|
+
it). Either way it is listed once, even when the status update has already found it.
|
|
996
|
+
- **Close** (`x`, or a click on `×`): unregistered sessions only. ` › close ‘name’ — y⏎ confirm · Esc cancel`: `y` arms
|
|
997
|
+
it (` › close ‘name’ — ⏎ confirm`; a long name is clipped so the keys fit) and `Enter` ends the session and its jobs and
|
|
998
|
+
clears views showing it; any other key cancels, at either step, so typed text closes nothing unless it is `x`,
|
|
999
|
+
`y`, `Enter` in a row. The keyboard goes back to the lane at the `Enter`; the close itself runs in the background
|
|
1000
|
+
(a busy registry lock or a slow tmux can take seconds): the row stays with a dim `…` in column 3 (ASCII `~`), no `+`
|
|
1001
|
+
or `×`, and the footer says ` Closing ‘name’`, then ` Closed name` — or why nothing was closed, with the row back as
|
|
1002
|
+
it was. A viewport of this window that shows the session is cleared at the `Enter`, before the keyboard moves, so
|
|
1003
|
+
what you type next lands on its **No lane selected** screen (which ignores keys), never in the shell being ended;
|
|
1004
|
+
if nothing was closed, a viewport still cleared gets the session back (one you pointed elsewhere meanwhile keeps
|
|
1005
|
+
your choice). A view that cannot be cleared (a full disk) closes nothing: the footer says why and the keyboard
|
|
1006
|
+
stays. Meanwhile every key on that row — `x`, `a`, `R`, `P`, `u`, `Enter` — says ` closing ‘name’ — a moment`; the
|
|
1007
|
+
rest of the sidebar answers as usual. A session others are grouped onto (a Studio browser
|
|
654
1008
|
`view-…` viewer) has those viewers ended first; any other grouped session makes the close refuse and say why.
|
|
1009
|
+
- **Paste into a prompt**: a paste into ` › Rename: `, ` › Register as: ` or the filter is typed there, after what is
|
|
1010
|
+
already in the field. Line breaks and tabs become one space, so a copied line never acts as `Enter`; the prompt waits
|
|
1011
|
+
for yours. Only the armed upload strip, or a sidebar with no prompt open, takes a paste as a file drop.
|
|
655
1012
|
|
|
656
1013
|
### Markdown preview
|
|
657
1014
|
|
|
@@ -663,6 +1020,41 @@ adds a pinned glow unless the view server's PATH already has one; `lanes doctor`
|
|
|
663
1020
|
(`preview renderer:`). Preview reads tmux lanes only; on a Host or tool tab, or a lane that named no existing `.md`
|
|
664
1021
|
file, it says so and closes on `Enter`.
|
|
665
1022
|
|
|
1023
|
+
### Quick edit
|
|
1024
|
+
|
|
1025
|
+
`Ctrl-Alt-e` opens a floating **Edit** pane over the focused lane with the files that steer its agent:
|
|
1026
|
+
|
|
1027
|
+
| Key | File | Offered when |
|
|
1028
|
+
| --- | --- | --- |
|
|
1029
|
+
| `1` / `2` | `AGENTS.md` / `CLAUDE.md` of the lane's checkout | always |
|
|
1030
|
+
| `3` / `4` | `AGENTS.md` / `CLAUDE.md` of the main checkout | the lane works in a linked git worktree |
|
|
1031
|
+
| `3` (`5` in a worktree) | `.env.local` of the main checkout | always |
|
|
1032
|
+
|
|
1033
|
+
The checkout is the git checkout the lane's working directory is in (else its repository folder), found inside the
|
|
1034
|
+
container: a worktree's `.git` file leads to its main checkout. A file is offered only as a regular file inside its
|
|
1035
|
+
checkout; a symbolic link is never offered, and a missing file is shown dim, `not present` (quick edit creates no
|
|
1036
|
+
file). One key picks, without `Enter`, even one typed while the pane was still opening; `Enter` alone takes the file
|
|
1037
|
+
when only one is there; `q` or `Esc` closes. The editor then runs inside the lane's container as uid 1000, in that
|
|
1038
|
+
checkout: `docker exec -it -u coder … lane-edit <file>` with no shell between, so a save is the container's own write
|
|
1039
|
+
(owner and mode kept). Quitting the editor closes the pane.
|
|
1040
|
+
|
|
1041
|
+
Which editor: the one `lanes attach <device> --editor micro|hx|nvim|vim` recorded for the device (`--editor auto`
|
|
1042
|
+
takes the choice back; from your machine, `lanes-launch <device> --editor E` passes the same flag), else the first of
|
|
1043
|
+
micro, hx, nvim, vim and vi in the container ([lane-edit](#the-in-container-lane-command)). `LANES_EDITOR` counts
|
|
1044
|
+
only for a `lanes edit --focused` you run by hand: the `Ctrl-Alt-e` pane has the view server's clean environment,
|
|
1045
|
+
which does not carry it.
|
|
1046
|
+
|
|
1047
|
+
Quick edit reads tmux lanes only. It is refused with the reason, and closes on `Enter`, when the container is not
|
|
1048
|
+
running, when it has no `lane-edit` (`lanes install-container <name>`) or when no git checkout holds the lane's
|
|
1049
|
+
directory. `.env.local` is named by its label only, never by its path, in anything quick edit prints.
|
|
1050
|
+
|
|
1051
|
+
**What quick edit can reach, and what leaves the container.** The server side (`lanes edit`) only looks: through
|
|
1052
|
+
`/proc/<container init>/root` it resolves the checkout and checks that each file is a regular file inside it, and it
|
|
1053
|
+
never opens one. The file is opened, edited and saved by the editor inside the container, as uid 1000 (`coder`, the
|
|
1054
|
+
agents' own user), so it can reach nothing the lane's agent could not, and the save is that user's write. Nothing is
|
|
1055
|
+
copied out of the container: no temporary copy on the server, no upload, no log of the contents. What you see on
|
|
1056
|
+
screen travels the way the lane's own output does — the terminal stream of your view — and nothing else.
|
|
1057
|
+
|
|
666
1058
|
### Scroll, mouse and copy
|
|
667
1059
|
|
|
668
1060
|
- A tmux lane keeps its own history and draws on the alternate screen, so scroll inside the lane (mouse wheel or the
|
|
@@ -675,10 +1067,13 @@ file, it says so and closes on `Enter`.
|
|
|
675
1067
|
### Pane title and desktop notifications
|
|
676
1068
|
|
|
677
1069
|
The sidebar pane's title is `WORKSPACE · ◆1 ◇2` (the header counts, rewritten only when they change). A lane that
|
|
678
|
-
starts needing you (`◆` `✗` `◇`) raises one desktop notification `name: task` through OSC 9: once
|
|
1070
|
+
starts needing you (`◆` `✗` `◇`) raises one desktop notification `name: task` through OSC 9: once each time it
|
|
1071
|
+
enters Needs you (an approval prompt after a question, while it is still waiting on you, is not a second one),
|
|
679
1072
|
never for the lane this pane shows, never on a phone layout. A sidebar that starts while lanes already need you
|
|
680
1073
|
announces each once. Ghostty shows OSC 9 only while the lanes window is not focused (`desktop-notifications = true`
|
|
681
|
-
in the window's config). Not yet verified on a Mac.
|
|
1074
|
+
in the window's config). Not yet verified on a Mac. To silence them for one device, live:
|
|
1075
|
+
`lanes-launch <device> --notify 0` from the client (or `lanes attach <device> --notify 0` on the server); `--notify 1`
|
|
1076
|
+
turns them back on, also over a `LANES_NOTIFY=0` in the view's environment (section 9).
|
|
682
1077
|
|
|
683
1078
|
## 4. Phone, tablet and laptop browser
|
|
684
1079
|
|
|
@@ -831,15 +1226,17 @@ The frontend chooses once per page load, by capability:
|
|
|
831
1226
|
|
|
832
1227
|
- **Workspaces** (or the title in the header) opens the workspace sheet with a search field (each card shows its
|
|
833
1228
|
counts, `◆ 1 · ◇ 2`); **Lanes** opens the current workspace's lane picker; **Panes** switches panes; **More** opens
|
|
834
|
-
the client's own menu, where Lanes adds **Text size** (remembered on the phone)
|
|
835
|
-
whole terminal.
|
|
1229
|
+
the client's own menu, where Lanes adds **Text size** (remembered on the phone) and, above it, the server's release
|
|
1230
|
+
(**Lanes v1.2.0**, see [Versions](#versions)). The light/dark toggle repaints the whole terminal.
|
|
836
1231
|
- The keyboard opens only when you tap **Type here** or another input. Swipes, live updates and reconnects leave it
|
|
837
1232
|
closed. **Type here** is an ordinary text field: the normal keyboard with its suggestion bar, never the password
|
|
838
1233
|
keyboard. A suggestion you tap arrives as the whole word; long-press the field and choose **Paste** to paste.
|
|
839
1234
|
- The lane picker shows three rows per lane: the viewing mark, agent glyph and name; then what the agent needs, since
|
|
840
1235
|
when and the harness (`Needs you · 25m · claude`; a shell says `Quiet · 2h`); then the agent's task. Its first line
|
|
841
1236
|
counts the lanes that need you (`◆1 need approval · ◇2 need you · ▸6 working`). The footer says
|
|
842
|
-
`Swipe to scroll · Tap to open
|
|
1237
|
+
`Swipe to scroll · Tap to open`, and otherwise exactly what the desktop footer says: every prompt (Rename,
|
|
1238
|
+
Register as, the close and register confirmations), the filter and its match count, a banner, the last message.
|
|
1239
|
+
The picker has no upload strip.
|
|
843
1240
|
- On a lane's own tab a row of quick keys sits above the dock: `⏎ y n Esc ^C ↑ ↓`. A tap sends the key to the lane
|
|
844
1241
|
without opening the keyboard; `n` and `^C` need a 400 ms hold (a short tap says "Hold to send" in the header for
|
|
845
1242
|
about a second and a half).
|
|
@@ -851,9 +1248,16 @@ The frontend chooses once per page load, by capability:
|
|
|
851
1248
|
- **More** opens the client's menu; tap it again to close the menu.
|
|
852
1249
|
- The filter bar on the picker filters the lane list as you type, non-ASCII letters included: it sends each change
|
|
853
1250
|
(a typed letter, a backspace) once, and clearing the field or pressing Esc closes the filter. On a picker tab the dock
|
|
854
|
-
shows the filter in place of **Panes**. Leaving the picker
|
|
855
|
-
|
|
856
|
-
|
|
1251
|
+
shows the filter in place of **Panes**. Leaving the picker with a filter typed (a tap on a match opens that lane)
|
|
1252
|
+
sends nothing to the lane and forgets the filter; that picker's list is shown whole again the next time you are on
|
|
1253
|
+
it. Moving to another workspace's picker from the Workspaces sheet leaves the filter behind too: that picker's bar
|
|
1254
|
+
starts empty.
|
|
1255
|
+
- `+` and `×` on picker rows add and close unregistered sessions, at the same columns as on the desktop; tapping `+`
|
|
1256
|
+
then a workspace needs no typing. While the picker asks (` › close ‘sh’ — y⏎ confirm`, ` › register ‘sh’ — y⏎
|
|
1257
|
+
confirm`, or Add to workspace), a short row `⏎ y Esc` sits above the dock: `y` then `⏎` closes, `Esc` cancels — no keyboard needed.
|
|
1258
|
+
While you type a name (Rename, Register as) the row stays away, so a `y` meant as yes is never typed into it. A tap
|
|
1259
|
+
on a row while the picker asks cancels the question or the name first, then opens that lane, so nothing is left
|
|
1260
|
+
half-asked on the picker when you come back to it.
|
|
857
1261
|
|
|
858
1262
|
### What works, what does not, what is unverified
|
|
859
1263
|
|
|
@@ -888,7 +1292,8 @@ yet, added to the repository-local `.git/info/exclude`, so uploads are never com
|
|
|
888
1292
|
|
|
889
1293
|
**Why click first.** A terminal delivers a drop as a paste of the local path into the focused pane, so the strip must
|
|
890
1294
|
hold the focus when you let go. The server receives only the path; the **upload helper** on your machine sends the
|
|
891
|
-
file.
|
|
1295
|
+
file. Once the drop lands the sidebar gives the keyboard back to the lane straight away; the end of the upload never
|
|
1296
|
+
moves the focus (you may be typing somewhere else by then).
|
|
892
1297
|
|
|
893
1298
|
**The upload helper and its allowlist.** The helper is the security boundary for drops. It reads only from folders
|
|
894
1299
|
listed in `~/.config/lanes/upload-allow` (created with Desktop, Downloads, Pictures, Documents, Lanes Uploads and the
|
|
@@ -930,7 +1335,7 @@ The agent must hold the key (`AddKeysToAgent yes` in the `lanes` alias adds it o
|
|
|
930
1335
|
| `✗ not allowed on this machine: … - run: lanes-upload --allow FOLDER` | The file's folder is not on the allowlist (or you chose Deny) | `lanes-upload --allow <folder>`, then drop again |
|
|
931
1336
|
| `✗ macOS denied folder access: allow Lanes Helper in System Settings > …` | macOS refused the helper | Allow Lanes Helper in Files and Folders |
|
|
932
1337
|
| `✗ no upload helper on this device (reopen lanes-launch)` | No helper heartbeat for this device | `./install.sh --check`; reopen the window |
|
|
933
|
-
| `✗ that was not a file drop` | The paste was not a list of absolute paths | Drop files, not text |
|
|
1338
|
+
| `✗ that was not a file drop` | The paste was not a list of absolute paths (a paste into an open Rename, Register-as or filter prompt is typed there instead) | Drop files, not text |
|
|
934
1339
|
| `✗ upload timed out` | No progress for 120 s | Check the helper log |
|
|
935
1340
|
|
|
936
1341
|
## 6. Workspaces and registry
|
|
@@ -986,14 +1391,30 @@ run.
|
|
|
986
1391
|
| Key | Meaning |
|
|
987
1392
|
| --- | --- |
|
|
988
1393
|
| `terminals[]` | The terminals the editor extension opens and Lanes turns into lanes: `name` (the lane title; the id is its slug), `command`, optional `focus`, `color` (`terminal.ansi<Colour>` / `terminal.ansiBright<Colour>`), `icon` (a codicon name) |
|
|
989
|
-
| `lanes.terminals[<name>]` | The exact binding Lanes uses: `holder`, `tmux: {socketName, sessionName}`, `id` (added by `cursor-sync`), and `create` (the recipe `lane open` runs when the lane does not exist; `createPrevious` keeps an earlier one after `cursor-sync --canonicalize`/`--identity`). Written by `lanes cursor-sync
|
|
1394
|
+
| `lanes.terminals[<name>]` | The exact binding Lanes uses: `holder`, `tmux: {socketName, sessionName}`, `id` (added by `cursor-sync`), and `create` (the recipe `lane open` runs when the lane does not exist; `createPrevious` keeps an earlier one after `cursor-sync --canonicalize`/`--identity`). Optional `displayName`: the name Lanes shows for the lane (see below). Written by `lanes cursor-sync`, which carries `displayName` as is; ignored by the editor extension |
|
|
990
1395
|
| `throttle.terminals[<name>].tmux` | A binding Lanes also accepts (Throttle Studio's), used when `lanes.terminals` has none |
|
|
991
1396
|
|
|
992
|
-
Every terminal needs an explicit tmux binding; there is no guessed command parsing.
|
|
993
|
-
|
|
1397
|
+
Every terminal needs an explicit tmux binding; there is no guessed command parsing.
|
|
1398
|
+
|
|
1399
|
+
A repository lane's shown name has two layers, both keyed by the terminal's `name`, neither of which changes the id:
|
|
1400
|
+
|
|
1401
|
+
1. `repositories[i].displayNames` in the manifest (host) — the operator's override, written by `R` and
|
|
1402
|
+
`lanes rename`. It wins.
|
|
1403
|
+
2. `lanes.terminals[<name>].displayName` in `terminals.json` (container) — the repository's own name for the lane.
|
|
1404
|
+
A value that is not a usable name (not a string, blank or invisible characters only, not valid Unicode text,
|
|
1405
|
+
or with a control character) is ignored. So is a name another lane on the same board already answers to — its
|
|
1406
|
+
title, its `displayNames` override, or its own `displayName` (then neither lane keeps it): the repository names
|
|
1407
|
+
only its own lane and never makes two rows read the same. Names are compared as a row reads them, so extra
|
|
1408
|
+
spaces, a zero-width character or a full-width form do not make a second `main`. No board error is raised; the
|
|
1409
|
+
lane shows its override, else its title.
|
|
1410
|
+
|
|
1411
|
+
Without either, the lane shows its prefix plus the terminal's `name`.
|
|
994
1412
|
|
|
995
1413
|
**`registered-sessions.json`** (site directory, version 1): `sessions[]` of `{id, workspace, name, container,
|
|
996
|
-
tmux: {socket, session}, folder}`, written by Add to workspace and `lanes register
|
|
1414
|
+
tmux: {socket, session}, folder}`, written by Add to workspace and `lanes register`; `u` in the sidebar or
|
|
1415
|
+
`lanes unregister <lane-id>` removes a record (`z` puts it back within 10 s). `lanes unregister <id>` also removes a
|
|
1416
|
+
record no workspace lists any more (its workspace left the catalog, uses another container, or a repository declares
|
|
1417
|
+
the session). Back it up with the catalog.
|
|
997
1418
|
|
|
998
1419
|
### Lane ids
|
|
999
1420
|
|
|
@@ -1015,7 +1436,10 @@ target as JSON.
|
|
|
1015
1436
|
- **Hot-sync**: `lanes-statusd` starts one `lanes sync` when a referenced `terminals.json` changes, at most one per
|
|
1016
1437
|
10 s.
|
|
1017
1438
|
- `lanes-ensure.timer` runs `lanes ensure` every minute: it recreates any known device session that is missing, and
|
|
1018
|
-
never when Zellij did not answer.
|
|
1439
|
+
never when Zellij did not answer. A view an update left running because a client was attached
|
|
1440
|
+
(`clients/<device>.update-ready`) is rebuilt by the first tick that finds no client attached. While another
|
|
1441
|
+
`lanes ensure`, `lanes rebuild` or the create of a view runs, a tick waits 10 s for it and then skips (`ensure:
|
|
1442
|
+
skipped: …`); the next tick follows a minute later.
|
|
1019
1443
|
- A repository whose registry is temporarily invalid keeps its previous lanes; the sidebar footer and `lanes doctor`
|
|
1020
1444
|
say why. `lanes sync` exits 1 then (the timer treats that as informational).
|
|
1021
1445
|
- A device with no attached client gets new tabs on its next connection and the next ensure tick.
|
|
@@ -1045,7 +1469,7 @@ running lane gains its identity when it is next created.
|
|
|
1045
1469
|
|
|
1046
1470
|
| Lane kind | `R` / `lanes rename <lane-id> <name>` writes |
|
|
1047
1471
|
| --- | --- |
|
|
1048
|
-
| Repository lane | `repositories[i].displayNames` in `workspaces.<workspace>.json` (a `.bak-<UTC>` copy on the first write), then syncs. Renaming back to the terminal's own title removes the override |
|
|
1472
|
+
| Repository lane | `repositories[i].displayNames` in `workspaces.<workspace>.json` (a `.bak-<UTC>` copy on the first write), then syncs. Never `terminals.json`. Renaming back to the lane's default (its `terminals.json` `displayName` when it has one, else the terminal's own title) removes the override |
|
|
1049
1473
|
| Registered session | Its record's `name` in `registered-sessions.json` |
|
|
1050
1474
|
| Unregistered session | `~/.local/state/lanes/user-sessions/<id>.json` |
|
|
1051
1475
|
|
|
@@ -1056,12 +1480,18 @@ Names must be non-empty and free of control characters. The id, resolve file and
|
|
|
1056
1480
|
- **Register** (Add to workspace, or `lanes register <lane-id> [workspace]` with the id from
|
|
1057
1481
|
`lanes ls --unregistered`): saves the session's existing id, name and binding in `registered-sessions.json`. It
|
|
1058
1482
|
has no create recipe and does not edit any editor terminal list; an ended registered session stays absent until
|
|
1059
|
-
someone starts it. Only workspaces using the session's container are offered.
|
|
1483
|
+
someone starts it. Only workspaces using the session's container are offered. If that container refuses its own
|
|
1484
|
+
copy of the registry (`~/.lz` is no real directory), the registration is still saved and listed, and Lanes says so
|
|
1485
|
+
at once: `Registration saved; run lanes sync to finish publishing: container <name>: …` (a rename of a registered
|
|
1486
|
+
lane says `Rename saved; …` the same way); `lane open` in that container sees the change only after the fix
|
|
1487
|
+
([RUNBOOK](RUNBOOK.md)).
|
|
1060
1488
|
- **Promote** (`P`): declares the session in the workspace's single repository `terminals.json` (a terminal entry
|
|
1061
1489
|
and its `lanes.terminals` binding), so it gets a repository id, editor integration and a saved recipe when one was
|
|
1062
1490
|
known.
|
|
1063
|
-
- **Unregister** a registered session:
|
|
1064
|
-
running and returns to Unregistered
|
|
1491
|
+
- **Unregister** a registered session: `u` in the sidebar, or `lanes unregister <lane-id>` (which also clears a record
|
|
1492
|
+
`lanes sync` ignores); the terminal keeps running and returns to Unregistered with the same id and name. Removing the
|
|
1493
|
+
record from `sessions` by hand and running `lanes sync` works too, without the name hand-back. Remove a repository
|
|
1494
|
+
lane by deleting its terminal from `terminals.json`.
|
|
1065
1495
|
- If a repository later declares the same binding as a registered session, the repository declaration wins.
|
|
1066
1496
|
|
|
1067
1497
|
### `lanes doctor`
|
|
@@ -1086,6 +1516,27 @@ Names must be non-empty and free of control characters. The id, resolve file and
|
|
|
1086
1516
|
present (`✓`), missing or pointing elsewhere (`✗` once a web unit is enabled, `⚠` before), or not checked (`⚠`: no
|
|
1087
1517
|
tailscale, no answer, or no Tailscale name on the host — then `lanes web` configures no Serve and prints a loopback
|
|
1088
1518
|
URL, so doctor does not ask for the mapping either).
|
|
1519
|
+
- The release that runs, against `~/.local/share/lanes/current` (the tree an install makes current; without the link,
|
|
1520
|
+
a `⚠` and this `lanes`'s own tree): the link names a tree that exists, whose `VERSION` agrees with its
|
|
1521
|
+
`pkg/<version>` name and its npm stamp, and that this `lanes` runs (`✗` with the fix: run the current one, or re-run
|
|
1522
|
+
the server installer). Every lanes process running now (statusd, enrold, notify, economy, a sidebar — found in the
|
|
1523
|
+
same process-table sweep by the command Lanes starts it with, `python3 -S -E <tree>/libexec/<script>`, so a file
|
|
1524
|
+
open in an editor or a one-shot such as `notify.py --test` is not one) has a [run record](#versions) of the current
|
|
1525
|
+
code (`✗ … statusd runs 1.1.0`, or `statusd runs without a run record (started by an older release)`, then
|
|
1526
|
+
`systemctl --user restart …` or `lanes rebuild <device>`); a record whose process is not running is a line of
|
|
1527
|
+
information, never a `✗`. Every device's layout runs the current tree (`✗ … mac runs 1.1.0 — lanes rebuild mac`;
|
|
1528
|
+
`⚠` when it names that tree by another path than `lanes rebuild` writes now: the next update would leave that view
|
|
1529
|
+
behind). Every installed lanes unit's `ExecStart` runs it (`✗ … 12 units run 1.1.0 (…) — re-run the server installer`; `⚠` when no unit is found under
|
|
1530
|
+
`~/.config/systemd/user` or `$XDG_CONFIG_HOME/systemd/user`).
|
|
1531
|
+
- The updates ([Update](#update)), from `update/last.json`: `updates: checked 12 min ago: latest names 1.2.0: installed ·
|
|
1532
|
+
last update ok: 1.1.0 → 1.2.0`. `✗` when the last update ended `partial` (with the steps that failed, the log, and
|
|
1533
|
+
that `lanes update` runs its post-commit again) or `failed`, or when the last check found a release it could not
|
|
1534
|
+
install (no recorded npx); `⚠` when the registry gave no release, the last update was refused, no update is recorded
|
|
1535
|
+
yet, `lanes-update.timer` is not enabled (no `timers.target.wants/lanes-update.timer` link in
|
|
1536
|
+
`~/.config/systemd/user` or `$XDG_CONFIG_HOME/systemd/user`: said at once, with `systemctl --user enable --now
|
|
1537
|
+
lanes-update.timer` — an update with `--update-only` never enables it), or the last check is more than 2 hours old
|
|
1538
|
+
(is `lanes-update.timer` on?); a release a rollback marked bad is named. A checkout server gets one line of
|
|
1539
|
+
information; without an installed release there is no line.
|
|
1089
1540
|
|
|
1090
1541
|
## 7. Agents in lanes
|
|
1091
1542
|
|
|
@@ -1094,8 +1545,12 @@ Names must be non-empty and free of control characters. The id, resolve file and
|
|
|
1094
1545
|
`lanes-statusd` reads each container through `/proc/<init pid>/root` every cycle (3 s) and, without starting
|
|
1095
1546
|
anything, picks the first signal that applies:
|
|
1096
1547
|
|
|
1097
|
-
1. A **hook sidecar** `~/.lz/state/hooks/<lane id>.json` written by `lane-signal` (when hooks are installed
|
|
1098
|
-
its agent process is alive and its last event is under an
|
|
1548
|
+
1. A **hook sidecar** `~/.lz/state/hooks/<lane id>.json` written by `lane-signal` (when hooks are installed, or when
|
|
1549
|
+
the agent reports itself with `lanes_signal`), while its agent process is alive and its last event is under an
|
|
1550
|
+
hour old. Its `name`, when there is one, is the agent's own note: the lane's task line. A `lanes_signal` report
|
|
1551
|
+
stands against the agent's own hooks until the agent does something new (its next tool call, a new prompt, a
|
|
1552
|
+
permission prompt, a failed turn, the session's end); the hook of the report's own tool call and the end of the
|
|
1553
|
+
turn keep it, unless the report was `working`.
|
|
1099
1554
|
2. A **Claude Code session file** `~/.claude/sessions/<pid>.json` naming the pane, for a live pid (recycled pids are
|
|
1100
1555
|
rejected).
|
|
1101
1556
|
3. The **Codex pane title**: `[ ! ] Action Required` is needs approval, a braille spinner is working, a task title is
|
|
@@ -1104,6 +1559,17 @@ anything, picks the first signal that applies:
|
|
|
1104
1559
|
|
|
1105
1560
|
A Codex title left on a bare shell with no child process is `shell` (the harness ended).
|
|
1106
1561
|
|
|
1562
|
+
What the sidebar shows is not that word itself but a debounced one. The sections rank Needs you > Working > Done > the
|
|
1563
|
+
workspace's other lanes: a status that moves the lane up is shown at once, one that moves it down only after it has
|
|
1564
|
+
held for 8 s (leaving Needs you) or 5 s (leaving Working or Done), and a lane whose agent signal disappears keeps its
|
|
1565
|
+
glyph 5 s. `status.json` carries both:
|
|
1566
|
+
`agent.status` (debounced) and `agent.raw_status` (this cycle's word), with `agent.stable_since`, when the lane
|
|
1567
|
+
entered its section — the sidebar orders each section by it. With the Claude hooks installed, every tool call that
|
|
1568
|
+
starts signals Working (`PreToolUse`); that hook fires before the permission dialog, so a call you approve still shows
|
|
1569
|
+
`◆` while it runs and for the 8 s dwell after it ends (`PostToolUse`; DEF-1571, open — [RUNBOOK](RUNBOOK.md#installing-agent-hooks-opt-in-per-container-dry-run-first)).
|
|
1570
|
+
A Codex approval read from its pane title (a lane without hooks) keeps the same 8 s dwell once approved. The full rules:
|
|
1571
|
+
[Attention queue](ARCHITECTURE.md#attention-queue).
|
|
1572
|
+
|
|
1107
1573
|
### A user's agents in one step: `lanes onboard`
|
|
1108
1574
|
|
|
1109
1575
|
On the server, one command gives a new user's workspace container what its agents need. Without `--write` it prints
|
|
@@ -1117,14 +1583,15 @@ lanes onboard anna --container ws-anna --workspace anna-tools --repo /home/coder
|
|
|
1117
1583
|
| Step | What the agents get |
|
|
1118
1584
|
| --- | --- |
|
|
1119
1585
|
| Board | A workspace tab for the repository (`lanes workspaces add`, section 6) on every device's view |
|
|
1120
|
-
| Contract | `lane`, `lane-run`, `lane-signal` and the hook definitions in the container's `~/.lz` (`lanes install-container`) |
|
|
1586
|
+
| Contract | `lane`, `lane-run`, `lane-signal`, `lane-edit`, the quick-edit `vimrc`, `lane-mcp` and the hook definitions in the container's `~/.lz` (`lanes install-container`) |
|
|
1587
|
+
| Agent tools | The Lanes MCP server in Claude Code's and Codex's configuration, so an agent can see the lanes around it ("Agent tools (MCP)" below) |
|
|
1121
1588
|
| Hooks | The lane-signal hooks for Claude Code and Codex ("Hooks" below), so the sidebar's agent glyphs are event-driven |
|
|
1122
1589
|
| Throttle | `throttle setup --install-clients` inside the container when Throttle is installed and configured there, so the agents' requests go through the pool; otherwise the one-time setup to run on a terminal is printed (a first setup signs in, which needs a person; Lanes runs without Throttle) |
|
|
1123
1590
|
| Recipes | `lanes cursor-sync <id> --canonicalize --write` when the repository has a `.vscode/terminals.json`: each lane's create recipe gives it its identity and trace ("Lane identity" below) |
|
|
1124
1591
|
| Sync and doctor | The registry knows the new lanes; `lanes doctor`'s result is reported, never fatal |
|
|
1125
1592
|
|
|
1126
1593
|
The receipt is `~/.local/state/lanes/onboard/<login>-<UTC>.json`, and running the command again changes nothing that
|
|
1127
|
-
is already done. `--no-mcp` leaves out `lane-mcp
|
|
1594
|
+
is already done. `--no-mcp` leaves out `lane-mcp` and its entries in the agents' configuration. Every step
|
|
1128
1595
|
and status: [install guide](INSTALL.md#onboard-a-users-container).
|
|
1129
1596
|
|
|
1130
1597
|
**Laptop agents on the pool (operator-gated).** Agents a user runs on their own laptop, outside any lane, can use the
|
|
@@ -1156,7 +1623,7 @@ restore the `.lanes-bak-*` file, or delete the groups whose command contains `la
|
|
|
1156
1623
|
|
|
1157
1624
|
| Harness | Event | Status |
|
|
1158
1625
|
| --- | --- | --- |
|
|
1159
|
-
| Claude Code | `UserPromptSubmit`, `PostToolUse` | working |
|
|
1626
|
+
| Claude Code | `UserPromptSubmit`, `PreToolUse`, `PostToolUse` | working |
|
|
1160
1627
|
| Claude Code | `Stop`, `Notification` (idle prompt) | needs_input |
|
|
1161
1628
|
| Claude Code | `PermissionRequest`, `Notification` (permission prompt) | needs_approval |
|
|
1162
1629
|
| Claude Code | `StopFailure` | errored |
|
|
@@ -1168,7 +1635,8 @@ restore the `.lanes-bak-*` file, or delete the groups whose command contains `la
|
|
|
1168
1635
|
| Codex | `SessionEnd` | shell |
|
|
1169
1636
|
|
|
1170
1637
|
Known limit: after you approve a permission prompt the lane reads needs approval until the approved tool call
|
|
1171
|
-
returns, because neither harness signals "approved".
|
|
1638
|
+
returns, because neither harness signals "approved". With Claude Code, after a denied prompt or a failed approved
|
|
1639
|
+
call the lane reads working again as soon as the agent starts its next tool call (`PreToolUse`).
|
|
1172
1640
|
|
|
1173
1641
|
### The in-container `lane` command
|
|
1174
1642
|
|
|
@@ -1179,19 +1647,118 @@ verbs, so the holder stays swappable.
|
|
|
1179
1647
|
| --- | --- |
|
|
1180
1648
|
| `lane open <lane-id>` | The editor entry point: attach to the lane; if it does not exist, run its recorded create recipe. Refuses to nest inside tmux; attaches with `ignore-size` while a lanes view shows the lane |
|
|
1181
1649
|
| `lane attach <id>` | Attach this terminal; never creates; refuses to nest |
|
|
1182
|
-
| `lane send <id> [--no-enter] <text…>` | Type into the lane: tmux literally, then Enter unless `--no-enter
|
|
1650
|
+
| `lane send <id> [--no-enter] <text…>` | Type into the lane: tmux literally, then Enter half a second later unless `--no-enter` (an agent's prompt takes text and Enter that arrive together as a paste and never submits it); zmx sends the bytes as given |
|
|
1183
1651
|
| `lane capture <id> [--lines N] [--vt]` | Print the scrollback (plain, or with escapes under `--vt`) |
|
|
1184
1652
|
| `lane ls [--json] [--all]` | zmx lanes; `--all` adds the registry's tmux lanes with `alive` |
|
|
1185
1653
|
| `lane status <id>` | JSON: alive, the hook agent, metadata; exit 1 when not running |
|
|
1186
1654
|
| `lane kill <id> --yes [--force]` | End the lane (the only verb that ends an agent); refuses while the hook sidecar says `working` unless `--force` |
|
|
1187
1655
|
| `lane new <id> [--ws W] [--title T] [--cwd DIR] [--icon I] [--color C] -- <command…>` | Create a zmx-held lane |
|
|
1188
1656
|
| `lane gc --sockets` | Unlink this container's tmux socket files that tmux is refused on (no server listens); a live, empty or silent server keeps its file |
|
|
1657
|
+
| `lane mcp install [--agent claude\|codex\|all] [--write]` | Add the Lanes MCP server to `~/.claude.json` and `~/.codex/config.toml` ("Agent tools (MCP)" below); a dry run until `--write` |
|
|
1658
|
+
|
|
1659
|
+
Every tmux client these verbs start runs with `-N`, so none of them can start a tmux server: an attach to a lane
|
|
1660
|
+
whose server has just ended fails instead of leaving an empty server and its socket file behind.
|
|
1189
1661
|
|
|
1190
1662
|
`lane-run` is the root process of a zmx lane: it runs the agent command, then a login shell in the same directory,
|
|
1191
1663
|
forever ("lanes never die on agent exit"). `~/.lz/env.sh`, sourced by new shells, points `TMPDIR` and
|
|
1192
1664
|
`CLAUDE_CODE_TMPDIR` at `/home/coder/.tmp` (`LANES_TMP_DIR` names another; `LANES_TMP_OPTOUT` opts out); tmux sockets
|
|
1193
1665
|
stay in `/tmp/tmux-<uid>`.
|
|
1194
1666
|
|
|
1667
|
+
`lane-edit <file>` is the editor `Ctrl-Alt-e` opens in the container, as uid 1000 in the checkout's root. It prints
|
|
1668
|
+
one line, `editing <file> · <checkout> · :wq to finish` (micro: `Ctrl-s, Ctrl-q to finish`), then runs
|
|
1669
|
+
`$LANES_EDITOR` when that names a program on the container's PATH (one word, a name or an absolute path, no
|
|
1670
|
+
arguments; `lanes edit` sets it from the device's `lanes attach --editor`), else the first of `micro`, `hx`, `nvim`,
|
|
1671
|
+
`vim` and `vi` it finds. **Lanes installs no editor**: a
|
|
1672
|
+
container with none of them gets `lane-edit: no editor on PATH` (exit 127) and nothing opens, so vi (or vim) is the
|
|
1673
|
+
floor. vim and nvim, and a `vi` that is vim behind a link, start with `-u ~/.lz/vimrc` instead of your own vimrc: the
|
|
1674
|
+
file is written in place (it keeps its mode and owner), swap and backup files go to `~/.lz/tmp/` (0700) rather than
|
|
1675
|
+
beside the file, modelines are off, a file without a final newline keeps none, and no `~/.viminfo` is written.
|
|
1676
|
+
Everything it creates is private (`umask 077`). Quitting the editor closes the pane.
|
|
1677
|
+
|
|
1678
|
+
### Agent tools (MCP)
|
|
1679
|
+
|
|
1680
|
+
`lane-mcp` is an MCP server in each container (`~/.lz/bin/lane-mcp`). Claude Code and Codex start it from their own
|
|
1681
|
+
configuration, and it lets the agent in a lane see the lanes around it and, within limits, change them. Four tools
|
|
1682
|
+
only read:
|
|
1683
|
+
|
|
1684
|
+
| Tool | Answers |
|
|
1685
|
+
| --- | --- |
|
|
1686
|
+
| `lanes_whoami` | Which lane the agent runs in: id, board, name, kind (declared, registered or unregistered), tmux socket and session. It comes from `LANE_ID`, else from the agent's own tmux pane. Outside a lane the answer is `lane: null` with the reason |
|
|
1687
|
+
| `lanes_list` | Every lane in this container with its state and its agent (harness, status, task). Filters: `workspace`, `needs_you: true` (needs approval, errored or needs input, the sidebar's "Needs you" section) and `agent` (`any`, `none`, `claude`, `codex`). `status.fresh` is false once the host's observation is more than 60 s old |
|
|
1688
|
+
| `lanes_status` | One lane in full: what the registry says, the observed state and agent, whether its tmux session is alive now (`null` when tmux does not answer in 5 s) and its hook sidecar |
|
|
1689
|
+
| `lanes_capture` | The last `lines` (1 to 500, default 100) non-blank lines of a lane's screen and scrollback, as plain text. Any lane of the container can be read |
|
|
1690
|
+
|
|
1691
|
+
Where the answers come from: `~/.lz/registry.json`, which `lanes sync` writes, and `~/.lz/status.json`, which the
|
|
1692
|
+
status daemon writes. A container that has neither yet answers with empty lists and says why.
|
|
1693
|
+
|
|
1694
|
+
Six tools write. Each reaches the lanes of its own container only, and answers what it changed, what picks the
|
|
1695
|
+
change up (`picked_up_by`) and how soon (`expected_within_s`):
|
|
1696
|
+
|
|
1697
|
+
| Tool | Changes | Seen on the boards |
|
|
1698
|
+
| --- | --- | --- |
|
|
1699
|
+
| `lanes_register` | `name`, optional `session` and `repo`. Declares an unregistered tmux session — the one the agent runs in, or the one `session` names — as a lane: one terminal appended to its board's `.vscode/terminals.json` (command `lane open <id>`, the exact tmux binding, the canonical create recipe). Nothing is started, stopped or renamed. Answers the id the lane will have, `<workspace>--<slug of prefix + name>` | after the next `lanes sync`, within about 15 s |
|
|
1700
|
+
| `lanes_unregister` | Removes a declared lane's terminal (its `terminals[]` item and its `lanes.terminals` entry). The session keeps running and shows as an unregistered session again | same |
|
|
1701
|
+
| `lanes_rename` | Sets the lane's `displayName` in its terminals.json; the id never changes. The lane's own title removes it. A name the host sets for the lane in its workspace manifest still wins, and the answer says so (`host_override`) | same |
|
|
1702
|
+
| `lanes_new` | `name`, optional `cwd`, `command`, `workspace`, `repo`. Declares a lane on the board's shell socket `lanes-shells-<workspace>` and starts its session (detached, 100x30) with the lane's identity; the workspace defaults to the agent's own board, the folder to the repository's, the command to `bash -l`. When that starts the board's shell server, the server gets the account and locale variables (and `TMUX_TMPDIR`), never the agent's own environment, which every later shell on the board would inherit | the session at once; the lane after the next `lanes sync` |
|
|
1703
|
+
| `lanes_send` | `lane`, `text`, optional `enter` (default true), `force`. Types printable text into the lane, then Enter half a second later as a separate keystroke, so an agent's prompt submits it instead of taking it as a paste | at once |
|
|
1704
|
+
| `lanes_signal` | `status` (the agent words: `working`, `needs_input`, `needs_approval`, `done`, `errored`, `shell`, `idle`), optional `note`. Reports the agent's own status through `lane-signal`; the note (one line, at most 120 characters) is shown as the lane's task line. The report stands until the agent does something new: its next tool call, a new prompt, a permission prompt, a failed turn or the session's end. The end of its turn keeps it (a `working` report ends there), so an agent reports `needs_input`, `done` or `errored` as the last thing before it stops. A `working` note stays while the agent keeps working | on the next status cycle, within about 5 s |
|
|
1705
|
+
|
|
1706
|
+
What they refuse. A refusal is `isError` with `{"refused": <code>, "why", "human"}`: `human` says what a person can
|
|
1707
|
+
do instead, and nothing was changed. `lanes_register` refuses a session that is a lane already (`already_declared`,
|
|
1708
|
+
or `adopted_lane` for a session added with `lanes register` on the host), a name or display name another lane of the
|
|
1709
|
+
board shows (`duplicate_name`), a name whose id another lane holds or is made from (`slug_collision`), a name without
|
|
1710
|
+
a letter or digit A–Z/0–9 (`invalid_name`), a binding the file declares already (`duplicate_binding`), a session that
|
|
1711
|
+
is not running (`session_gone`) and a board with more than one repository in the container unless `repo` names one
|
|
1712
|
+
(`ambiguous_source`). `lanes_unregister` never removes the last terminal of a file (`last_terminal`), an adopted
|
|
1713
|
+
session or a lane whose declaration is not in a file the container holds (`not_declared_here`). `lanes_rename`
|
|
1714
|
+
refuses an unregistered session (register it first) and a name with a control character. `lanes_new` refuses a
|
|
1715
|
+
folder outside `/home/coder` or one that does not exist (`invalid_cwd`), a name or session in use and a command with
|
|
1716
|
+
a control character. `lanes_send` types nothing while the lane's agent is working or waiting for an approval
|
|
1717
|
+
(`agent_working`, `agent_needs_approval`) unless `force` is true, and never a control character: no newline, tab,
|
|
1718
|
+
escape or Ctrl-C (`invalid_text`). Every tool refuses a lane of another container (`not_in_container`). The edits go
|
|
1719
|
+
through the same minimal editor as everything else here: one span of strict JSON, the file re-read just before the
|
|
1720
|
+
atomic write, never the root `throttle` object, never a rename or reorder of terminals. The file is the
|
|
1721
|
+
repository's, so its history is git's: no backup file is written.
|
|
1722
|
+
|
|
1723
|
+
Left to people, on purpose (no tool exists for them): ending or closing a lane, interrupting one, opening or
|
|
1724
|
+
attaching one, uploads, the host's `lanes sync`/`gc`/`ensure`, renaming a tmux session, and any change to a session
|
|
1725
|
+
added with `lanes register` (its name and membership live on the host, in `registered-sessions.json`).
|
|
1726
|
+
|
|
1727
|
+
`lanes install-container` (and so `lanes onboard`) adds the server to both agents by default:
|
|
1728
|
+
`"mcpServers": {"lanes": {"command": "/home/coder/.lz/bin/lane-mcp"}}` in `~/.claude.json` and
|
|
1729
|
+
|
|
1730
|
+
```toml
|
|
1731
|
+
[mcp_servers.lanes]
|
|
1732
|
+
command = "/home/coder/.lz/bin/lane-mcp"
|
|
1733
|
+
env_vars = ["LANE_ID", "LANE_WS", "TMUX", "TMUX_PANE"]
|
|
1734
|
+
```
|
|
1735
|
+
|
|
1736
|
+
in `~/.codex/config.toml`. Codex starts an MCP server with a cleared environment (only `HOME`, `PATH`, `USER` and a
|
|
1737
|
+
few more of its own), so `env_vars` passes the server what `lanes_whoami` reads; without it a Codex agent could see
|
|
1738
|
+
every lane but its own. Claude Code's servers inherit the agent's environment. An entry is written only where it is
|
|
1739
|
+
missing, and nothing else in either file changes. A Codex table that starts `lane-mcp` but does not pass all four
|
|
1740
|
+
names (the one `codex mcp add` writes, for instance) gains the `env_vars` line, or the names it lacks. The old file
|
|
1741
|
+
is kept as `<file>.bak-<UTC time>` first. `--no-mcp` leaves out `lane-mcp` and both entries, and removes nothing an
|
|
1742
|
+
earlier install wrote. Inside the container the same change is one command:
|
|
1743
|
+
|
|
1744
|
+
```sh
|
|
1745
|
+
~/.lz/bin/lane mcp install # prints the diff to both files
|
|
1746
|
+
~/.lz/bin/lane mcp install --agent claude --write # applies it for Claude Code only
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1749
|
+
An entry named `lanes` that starts something other than `lane-mcp` is never replaced. A file that is not strict
|
|
1750
|
+
JSON or valid TOML is left alone too, and so is a Codex `env_vars` written other than as one line of plain names.
|
|
1751
|
+
All of these are reported and exit 1. New agent sessions load the server; a
|
|
1752
|
+
running session keeps the tools it started with. Undo: restore the `.bak-*` file, or delete the `lanes` entry. The
|
|
1753
|
+
server logs one line per request (the tool, the lane, the outcome and a refusal's code; never a capture's text, a
|
|
1754
|
+
sent text or a note) to `~/.lz/s/logs/lane-mcp.log`. It speaks MCP protocol
|
|
1755
|
+
versions 2025-06-18, 2025-03-26 and 2024-11-05 over stdio.
|
|
1756
|
+
|
|
1757
|
+
More on it: the wire contract, the latency of each write and what stays with people, in
|
|
1758
|
+
[ARCHITECTURE "Agent API"](ARCHITECTURE.md#agent-api). Uninstall, troubleshooting and a two-frame protocol smoke
|
|
1759
|
+
are in the [runbook](RUNBOOK.md#install-check-uninstall). What to tell your agents is in the
|
|
1760
|
+
[`lanes-agent-tools` skill](../../skills/lanes-agent-tools/SKILL.md).
|
|
1761
|
+
|
|
1195
1762
|
### Lane identity and the canonical recipe
|
|
1196
1763
|
|
|
1197
1764
|
A lane's identity is its environment: `LANE_ID` (`<ws>--<slug>`), `LANE_WS` (`<ws>`), and the Throttle trace
|
|
@@ -1262,7 +1829,8 @@ Covered in section 3: one notification per lane entering Needs you, from the sid
|
|
|
1262
1829
|
### Push notifications (opt-in, operator gate)
|
|
1263
1830
|
|
|
1264
1831
|
`lanes-notify.service` sends one push to an [ntfy](https://ntfy.sh) topic when a lane's agent enters needs you
|
|
1265
|
-
(`Needs you`, `Needs approval`, `Error`). It reads only `status.json`, the registry and `enrol-requests/`, every 3 s
|
|
1832
|
+
(`Needs you`, `Needs approval`, `Error`). It reads only `status.json`, the registry and `enrol-requests/`, every 3 s,
|
|
1833
|
+
and on such a transition the files that say who was at a desk (below).
|
|
1266
1834
|
A push says
|
|
1267
1835
|
`<lane name>: <agent task> — <status>` and `lanes-launch lane <id>`. It also pushes once per new enrolment request
|
|
1268
1836
|
(`Enrol request`: `<name or login> · <device>` and `lanes enrol approve <id>`), never muted by presence, once per
|
|
@@ -1276,12 +1844,17 @@ you configure, so enabling it is your decision. Not yet run against a real ntfy
|
|
|
1276
1844
|
| `url` | required | The ntfy server, `https://…` |
|
|
1277
1845
|
| `topic` | required | `[A-Za-z0-9_-]{1,64}`; on a public server the topic name is the only secret |
|
|
1278
1846
|
| `token_file` | unset | A file holding an ntfy access token (an unreadable file disables sending; written, rotated or made readable later, it is picked up within 3 s) |
|
|
1279
|
-
| `quiet_when_device_active_s` | `120` | Presence window; `0` never suppresses |
|
|
1847
|
+
| `quiet_when_device_active_s` | `120` | Presence window; `0` never suppresses — except for the one person present when their profile sets a `notify.quiet_s` of its own |
|
|
1280
1848
|
| `action_url` | unset | A URL template over `{lane}`; becomes the tap target and an **Open lane** button |
|
|
1281
1849
|
|
|
1282
1850
|
Nothing is sent when the status did not change, when the service just started (no burst on restart), or when a desk
|
|
1283
1851
|
device was present within the presence window: a `lanes attach` from any device, or a lane opened from a desk
|
|
1284
|
-
device's sidebar
|
|
1852
|
+
device's sidebar (its seen file, or your profile when that desk device wrote it). Opening a lane from the phone
|
|
1853
|
+
never mutes pushes, and neither does an admin verb run on the command line for you (`lanes workspaces …`, `lanes
|
|
1854
|
+
lane …`, `lanes device claim`, `lanes keys adopt --login`): it writes your profile as `cli:<command>`, not as your
|
|
1855
|
+
device. The window is yours when only you were present and your profile sets `notify.quiet_s` (`0`:
|
|
1856
|
+
never mute your pushes; no verb sets it yet, see the [runbook](RUNBOOK.md#push-notifications-opt-in)); when several
|
|
1857
|
+
people were, the host's `quiet_when_device_active_s` decides. A failed send is one journal line, not retried.
|
|
1285
1858
|
|
|
1286
1859
|
```sh
|
|
1287
1860
|
$EDITOR ~/.config/lanes/notify.conf
|
|
@@ -1299,20 +1872,48 @@ files. `python3 libexec/tokens.py --check` fails on drift; `--write` regenerates
|
|
|
1299
1872
|
|
|
1300
1873
|
| Switch | Values | Default |
|
|
1301
1874
|
| --- | --- | --- |
|
|
1302
|
-
| Colour (`--color`, `LANES_COLOR`) | `truecolor` · `16` · `none` | `truecolor` when `COLORTERM` is `truecolor`/`24bit` (the view's config sets it for every pane), else `16`; `NO_COLOR` set means `none` |
|
|
1875
|
+
| Colour (`--color`, `LANES_COLOR`) | `truecolor` · `256` · `16` · `none` | `truecolor` when `COLORTERM` is `truecolor`/`24bit` (the view's config sets it for every pane, so inside a view this is always `truecolor`; see below), else `256` when `TERM` contains `256color`, else `16`; `NO_COLOR` set means `none`. `truecolor` and `256` colour the section words, names and ages and lay the cursor row on a soft background; `16` (your terminal's palette) and `none` keep the quieter look. A viewport's status screens ("THIS VIEWER DISCONNECTED", "retrying in …") follow the same choice and theme: an amber lead and a readable dim line in `truecolor` and `256` |
|
|
1303
1876
|
| Theme (`--theme`, `LANES_THEME`) | `dark` · `light` · `auto` | `auto`: the sidebar asks the terminal's background once (OSC 11) and paints dark without an answer in 0.3 s |
|
|
1304
1877
|
| Glyphs (`--glyphs`, `--ascii`, `LANES_GLYPHS`) | `unicode` · `ascii` · `emoji` | `unicode`; `ascii` when no locale variable names UTF-8. `emoji` adds an icon column from the lane's `icon` |
|
|
1305
|
-
| Dim (`LANES_DIM`) | `90` · `2` | `90`; 16-colour mode only: `2` for palettes whose colour 8 is near the background |
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
the
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1878
|
+
| Dim (`--dim`, `LANES_DIM`) | `90` · `2` | `90`; 16-colour mode only: `2` for palettes whose colour 8 is near the background |
|
|
1879
|
+
| Notifications (`--notify`, `LANES_NOTIFY`) | `1` · `0` | `1` on desktop layouts; `0` silences the OSC 9 desktop notifications (section 3). A phone never gets them |
|
|
1880
|
+
|
|
1881
|
+
**Set the look**, live: `lanes attach <device> --ascii` (or `--glyphs M`, `--theme T`, `--color C`, `--notify N`,
|
|
1882
|
+
`--dim D`, and `--editor E`, the editor of [quick edit](#quick-edit)) records it for a device that already has a view (`~/.local/state/lanes/devices/<device>.json`). On a device
|
|
1883
|
+
with an owner — its member record's, its machine's for a window such as `mac-2`, or one `lanes device claim` named —
|
|
1884
|
+
`--theme`, `--color`, `--glyphs` and `--ascii` go to that person's profile, `users/<login>.json`, which every device they
|
|
1885
|
+
own shares, and leave the device file; add `--device-only` to keep them in the device file for that device alone,
|
|
1886
|
+
marked as its own (the file's `device_only` list), where they win over the profile; the same flag without
|
|
1887
|
+
`--device-only` hands the key back to the profile. `--notify`, `--dim` and `--editor` always stay per device, and a device without
|
|
1888
|
+
an owner records every flag in its device file. Every running sidebar of the device picks a device-file change up
|
|
1889
|
+
within a tick, and every sidebar of every device the person owns picks a profile change up within a tick too (`lanes
|
|
1890
|
+
attach phone --theme light` repaints the Mac); the choice survives `lanes ensure` and `lanes rebuild`. A sidebar
|
|
1891
|
+
paints, per key, the device file's `--device-only` value, else the profile's, else the environment's, else the default.
|
|
1892
|
+
A look a device recorded before it had a profile — a device enrolled before profiles existed that ran `lanes-window
|
|
1893
|
+
--dark` then — is not the device's own: it paints only where the profile sets no such key, so recording that key from
|
|
1894
|
+
any device of the person repaints this one too. `lanes user show <login>` names each look a device of that person
|
|
1895
|
+
keeps in its own file, and why; `lanes device claim <device> --login <login>` folds such a look into the profile (the
|
|
1896
|
+
profile's own keys win) and out of the device file — also on a device whose member record already names that login.
|
|
1897
|
+
Lanes never folds it by itself: a device handed to another person still holds its previous owner's look. From a
|
|
1898
|
+
client,
|
|
1899
|
+
`lanes-window --ascii|--light|--dark [--device-only]
|
|
1900
|
+
[suffix]` and `lanes-launch <device> --ascii|--theme T|--glyphs M|--color C|--notify N|--dim D|--editor E|--device-only`
|
|
1901
|
+
pass the same flags (`lanes-window --light --device-only`: light on this device alone). The device file and the profile win
|
|
1902
|
+
over the environment. `lanes user show <login>` prints a profile and the devices' own looks.
|
|
1903
|
+
|
|
1904
|
+
The environment variables `LANES_COLOR`, `LANES_THEME`, `LANES_GLYPHS`, `LANES_DIM` and `LANES_NOTIFY` reach a
|
|
1905
|
+
sidebar through the view server's environment: `bin/lanes` forwards them, when set, from the shell that creates a
|
|
1906
|
+
view session (`lanes attach` of a device whose session is not running, `lanes ensure`, `lanes rebuild`) into that
|
|
1907
|
+
session, so every sidebar it starts inherits them. A running session keeps the environment it was created with, and
|
|
1908
|
+
SSH does not pass them from your client's shell; for a device, the flags above are the live path.
|
|
1909
|
+
|
|
1910
|
+
**256 colours inside a view** are something you choose; Lanes never detects them there. The view's
|
|
1911
|
+
`zellij/config.kdl` gives every pane `COLORTERM=truecolor`, which the sidebar reads before `TERM`, so the
|
|
1912
|
+
`256color` rule (and `NO_COLOR`, which is not forwarded) applies only outside Zellij, to a sidebar started by hand. If
|
|
1913
|
+
your terminal cannot show 24-bit colour, run `lanes attach <device> --color 256` (live, kept by `lanes ensure` and
|
|
1914
|
+
`lanes rebuild`) or create the session with `LANES_COLOR=256 lanes rebuild <device>`. The 256 set uses the fixed xterm
|
|
1915
|
+
colours 16–255 (the `256 dark` / `256 light` columns of [`docs/DESIGN.md`](DESIGN.md)), so your own palette never
|
|
1916
|
+
changes it; `--color none` is the glyphs-only look.
|
|
1316
1917
|
|
|
1317
1918
|
**Ghostty palette** (opt-in): by default the lanes window inherits your everyday Ghostty look. `./install.sh --palette`
|
|
1318
1919
|
installs `~/.config/lanes/ghostty-palette.conf` generated from the tokens (a light/dark theme pair on Ghostty 1.1 or
|
|
@@ -1370,8 +1971,9 @@ All units run in `lanes.slice` and are rendered by the server installer from `la
|
|
|
1370
1971
|
| `lanes.slice` | — | (used by every unit) | High CPU weight and `MemoryLow` for the harness, so it stays scheduled on a thrashing host |
|
|
1371
1972
|
| `lanes-statusd.service` | Always on | Server install | Observes every lane (never touches one); writes `status.json` and `health`; hot-sync |
|
|
1372
1973
|
| `lanes-sync.timer` / `.service` | Every 2 min | Server install | `lanes sync` |
|
|
1373
|
-
| `lanes-ensure.timer` / `.service` | Every 1 min | Server install | `lanes ensure`: recreate missing device view sessions |
|
|
1374
|
-
| `lanes-gc.timer` / `.service` | Daily | Server install | `lanes gc`: truncate zmx logs over 5 MB, drop stale dynamic resolve files |
|
|
1974
|
+
| `lanes-ensure.timer` / `.service` | Every 1 min | Server install | `lanes ensure`: recreate missing device view sessions; rebuild a view an update left once no client is attached |
|
|
1975
|
+
| `lanes-gc.timer` / `.service` | Daily | Server install | `lanes gc`: truncate zmx logs over 5 MB, drop stale dynamic resolve files, prune old npm-staged trees — each folder keeps its 3 newest and every older tree `current`, `current.previous`, a view layout, a running daemon's run record or a process of this account uses; none when `/proc` cannot be read ([INSTALL](INSTALL.md#6-updates-optional-operations-and-removal)) |
|
|
1976
|
+
| `lanes-update.timer` / `.service` | 15 min after boot, then every 30 min (+0–5 min) | Server install (the full one: `--update-only` enables nothing — `systemctl --user enable --now lanes-update.timer` on a server set up before 1.2.0) | `lanes update --timer`: install the newest npm release when it is newer ([Update](#update)); a checkout server is left alone; stopped after 1 h |
|
|
1375
1977
|
| `lanes-enrold.service` | Always on | Server install (enabled and restarted by every one) | Enrolment service on `127.0.0.1:8184`, reached as `https://<host>:8443/enrol/*`: invite redemption, enrolment requests, `/enrol/pending` approvals ([RUNBOOK](RUNBOOK.md#enrolment-service-lanes-enrold-w8-a3)) |
|
|
1376
1978
|
| `lanes-web.service` | Always on | `enable-web.sh` (via `lanes web` / `enable-phone.sh`) | Zellij web server on `127.0.0.1:8082` |
|
|
1377
1979
|
| `lanes-mobile.service` | Always on | `enable-web.sh` | Caddy frontend on `127.0.0.1:8083` |
|
|
@@ -1394,38 +1996,49 @@ Clients also get `lanes-helper.service` and `lanes-upload.path`/`.service` (Linu
|
|
|
1394
1996
|
| Registry | `lanes resolve <lane-id>` | One lane's exact target as JSON |
|
|
1395
1997
|
| Registry | `lanes status` | One line per workspace: state counts and the `status.json` age |
|
|
1396
1998
|
| Registry | `lanes register <lane-id> [workspace]` | Add an unregistered session to a workspace without restarting it |
|
|
1999
|
+
| Registry | `lanes unregister <lane-id>` | Take a session added that way out of its workspace again (the sidebar's `u`): nothing stops, a running one is listed under Unregistered with the same id and name; refused while tmux cannot check it, and for a lane a repository's `terminals.json` declares (the file to edit is named) |
|
|
1397
2000
|
| Registry | `lanes rename <lane-id> <new-name>` | Change a lane's display name (not in the usage text) |
|
|
1398
2001
|
| Registry | `lanes workspaces` | Print the workspace ids (used by the client installer) |
|
|
1399
2002
|
| Registry | `lanes workspaces add <id> --container <name> --repo <path> [--title T] [--session S] [--write]` | Add a one-repository board to the site catalog (a dry run until `--write`; section 6) |
|
|
1400
|
-
| Views | `lanes attach <device> [--client ID] [--mobile] [--viewports N] [--ascii] [--glyphs …] [--theme …] [--color …]` | Attach this terminal to the device's view (create it if needed); record the look |
|
|
2003
|
+
| Views | `lanes attach <device> [--client ID] [--mobile] [--viewports N] [--ascii] [--glyphs …] [--theme …] [--color …] [--editor …] [--device-only]` | Attach this terminal to the device's view (create it if needed); record the look — in the owner's profile on a device with an owner, on the device with `--device-only` or without one (section 9) — and the quick-edit editor (always per device) |
|
|
1401
2004
|
| Views | `lanes prepare <device> [--mobile\|--desktop] [--viewports N]` | Create a view without attaching; a different explicit layout is refused |
|
|
1402
2005
|
| Views | `lanes web <device> [--desktop\|--mobile] [--viewports N] [--token \| --print-only]` | Browser view in one step, Tailscale Serve included (section 4) |
|
|
1403
2006
|
| Views | `lanes token list` / `revoke <device>` / `revoke --name NAME` / `revoke-all [--yes]` | Web login tokens and their devices (section 4) |
|
|
1404
2007
|
| Views | `lanes open <lane-id>` | Attach this terminal straight to one lane |
|
|
1405
2008
|
| Views | `lanes layout <device> [--mobile]` | Print the generated Zellij layout |
|
|
1406
|
-
| Views | `lanes rebuild <device>` | Recreate a device's view (agents never touched) |
|
|
1407
|
-
| Views | `lanes upgrade-session [device…]` | Sync catalog tabs and titles into live views without a rebuild |
|
|
1408
|
-
| Views | `lanes ensure` | Recreate known device sessions that are missing |
|
|
2009
|
+
| Views | `lanes rebuild <device>` | Recreate a device's view (agents never touched); waits up to 120 s while another `ensure`, `rebuild` or view create runs (the view-session lock), then names it and exits 1 |
|
|
2010
|
+
| Views | `lanes upgrade-session [device…]` | Sync catalog tabs and titles into live views without a rebuild; close the tabs of workspaces the owner's profile hides |
|
|
2011
|
+
| Views | `lanes ensure [--if-layout-changed]` | Recreate known device sessions that are missing; with the flag (the update's last step) also rebuild each view the installed release lays out differently that has no client attached, and mark the others `update ready · reload (r)`. One run at a time with every rebuild and view create: with the flag it waits up to 120 s for another, without it 10 s and then skips the run |
|
|
1409
2012
|
| Views | `lanes reattach` | Make every view re-attach its lane |
|
|
2013
|
+
| Views | `lanes reload-views` | Ask every running viewport to start again from the installed release at its next lane switch (a viewport of an older release, which the signal would end, is left alone and named: `lanes rebuild <device>`); the server update runs it |
|
|
1410
2014
|
| Views | `lanes device list` / `lanes device forget <name>` | The devices `ensure` keeps / stop keeping one |
|
|
2015
|
+
| Views | `lanes device claim <device> --login L [--name N]` | Name the owner of a device no member record speaks for (one from before enrolment) and fold it and its windows into that person's profile (all but `--device-only` keys); with the login its member record names it only folds (a look from before the profile); refused when a member record names someone else or nobody |
|
|
2016
|
+
| Views | `lanes user list` / `lanes user show <login> [--json]` | The people lanes knows with their devices / one person's profile and every look one of their devices keeps in its own file (`--device-only`, or from before the profile) (read-only) |
|
|
2017
|
+
| Views | `lanes workspaces hide\|show\|order <id…> [--device D \| --login L]` | Arrange one person's boards on every device they own, in their profile: hide or show boards (a hide that would leave no board is refused), or name the order of the first boards (the rest follow in catalog order). The person is the owner of `--device D`, else of `$LANES_DEVICE`, or `--login L`. Stored in the profile, which the tabs (cockpit v3 R4) and the sidebars (R5, and their keys `p h H o`) of every device of that person follow |
|
|
2018
|
+
| Views | `lanes lane pin\|unpin\|hide\|show <lane-id> [--device D \| --login L]` | The same person's pinned lanes (listed first) and hidden lanes (left out of their lists); hide takes a registered lane only |
|
|
2019
|
+
| Views | `lanes here <device>` | Show on that device the lane its owner opened last on any of their devices; says why when it cannot (`lanes-launch here` runs it for your own device) |
|
|
1411
2020
|
| Views | `lanes next\|prev --focused [--device D]` | What `Ctrl-Alt-↓` / `Ctrl-Alt-↑` run |
|
|
1412
2021
|
| Views | `lanes preview --focused [--device D]` | What `Ctrl-Alt-m` runs |
|
|
2022
|
+
| Views | `lanes sidebar-key filter\|upload --focused [--device D]` | What `Ctrl-Alt-/` / `Ctrl-Alt-u` run after moving the focus to the sidebar |
|
|
2023
|
+
| Views | `lanes edit --focused [--device D]` | What `Ctrl-Alt-e` runs: the focused lane's AGENTS.md, CLAUDE.md or .env.local in a one-key picker, then the editor in its container ([quick edit](#quick-edit)); with another Edit pane open it focuses or names that one and opens nothing |
|
|
1413
2024
|
| Editor | `lanes cursor-sync [ws…] [--write] [--diff] [--canonicalize] [--identity]` | Section 6 |
|
|
1414
2025
|
| Editor | `lanes cursor-commit [ws…] [--push]` | Section 6 |
|
|
1415
|
-
| Containers | `lanes install-container <name> [--no-mcp]` | Install the `lane` contract (section 2)
|
|
2026
|
+
| Containers | `lanes install-container <name> [--no-mcp]` | Install the `lane` contract (section 2) and the agents' MCP entries for `lane-mcp` (section 7, "Agent tools (MCP)"); `--no-mcp` leaves both out |
|
|
1416
2027
|
| Containers | `lanes onboard <login> --container <name> [--workspace <id> --repo <path> [--title T]] [--write] [--no-mcp]` | One user's container in one command: board, contract, Claude and Codex hooks, Throttle attachment, canonical recipes, sync, doctor, and a receipt (a dry run until `--write`; section 2) |
|
|
1417
2028
|
| Containers | `lanes hooks install --agent claude\|codex (--container <name> \| --home <dir>) [--dry-run \| --write]` | Section 7 |
|
|
1418
2029
|
| Uploads | `lanes upload <ws> \| --active <device> [--paste]` | Read a tar stream into that workspace's `uploads/` |
|
|
1419
|
-
| Client | `lanes hello [device]` | Prove path, key and server version |
|
|
2030
|
+
| Client | `lanes hello [device] [--client-version V]` | Prove path, key and server version; ` update=<version>` when V is older |
|
|
1420
2031
|
| Client | `lanes client-config` | Site facts for clients (`LANES_*` lines from `~/.config/lanes/site.conf`) |
|
|
1421
|
-
| Access | `lanes keys list \| add <device> <pubkey-file\|-> \| revoke <device> \| adopt [--dry-run]` | Who may log in: the lanes-managed block of `~/.ssh/authorized_keys`, projected from `members
|
|
1422
|
-
| Access | `lanes keys owner <device> <login> [--name NAME] [--email ADDR]` | Name the person behind a member; a key added by hand has none, and `/enrol/pending` lets in only a login a member names ([RUNBOOK](RUNBOOK.md#enrolment-service-lanes-enrold-w8-a3)) |
|
|
2032
|
+
| Access | `lanes keys list \| add <device> <pubkey-file\|-> \| revoke <device> \| adopt [--dry-run] [--login LOGIN]` | Who may log in: the lanes-managed block of `~/.ssh/authorized_keys`, projected from `members/`; `--login` names the owner of every key adopt moves in and folds their views into that profile |
|
|
2033
|
+
| Access | `lanes keys owner <device> <login> [--name NAME] [--email ADDR]` | Name the person behind a member; a key added by hand has none, and `/enrol/pending` lets in only a login a member names ([RUNBOOK](RUNBOOK.md#enrolment-service-lanes-enrold-w8-a3)); the device's view and its windows fold into that person's profile |
|
|
1423
2034
|
| Access | `lanes invite <name> [--email ADDR] [--expires 7d] [--workspaces a,b] [--tailscale-link URL] [--md]` | Mint a one-time code and print the welcome kit, on a terminal only (section 2, "Invite a user") |
|
|
1424
2035
|
| Access | `lanes invite list \| revoke <id>` | The invites and their state; refuse a code from now on |
|
|
1425
2036
|
| Access | `lanes enrol list [--all] \| approve <id> [--as <device>] \| deny <id> [--as <device>]` | The enrolment requests; decide one as `/enrol/pending` does |
|
|
1426
2037
|
| Access | `lanes enrol mode [request\|invite\|auto]` | Pin request mode across restarts (or say what is pinned) |
|
|
1427
2038
|
| Access | `lanes offboard <device> --yes` | Revoke the key, forget the device, sign out the web login tokens of the machine and its views, end its view (own terminal only) |
|
|
1428
2039
|
| Health | `lanes doctor` | Full health check (section 6) |
|
|
2040
|
+
| Health | `lanes version [--json]` | The release and tree this server runs, whether `current` names it, and each process still on older code (`✗`: another tree, or started before a checkout's `git pull`) |
|
|
2041
|
+
| Health | `lanes update [--now \| --timer \| --status \| --rollback]` | Install the newest npm release when it is newer (what `lanes-update.timer` runs as `--timer`; `--now` asks the registry even within 20 min of the last check), print the record (`--status`), or install the previous release again and mark the one left bad (`--rollback`) — [Update](#update) |
|
|
1429
2042
|
| Health | `lanes pressure` | Memory / io / cpu pressure, swap, per-container memory and `/tmp`; plain files only; exit 1 unless ok |
|
|
1430
2043
|
| Health | `lanes invariant save\|check` | Snapshot / verify every live pane's pid and window size |
|
|
1431
2044
|
| Health | `lanes sockets [check\|repair]` | tmux servers whose socket file was deleted; `repair` sends tmux's SIGUSR1, nothing restarts |
|
|
@@ -1441,10 +2054,11 @@ The client helpers call three more verbs (`rebuilt`, `upload-requests`, `upload-
|
|
|
1441
2054
|
|
|
1442
2055
|
| Command | Does |
|
|
1443
2056
|
| --- | --- |
|
|
1444
|
-
| `lanes-window [--ascii] [--light\|--dark] [suffix]` | Open the lanes window (section 3) |
|
|
1445
|
-
| `lanes-launch [device \| workspace \| lane <id> \| ls]` | The reconnecting client (section 3) |
|
|
2057
|
+
| `lanes-window [--ascii] [--light\|--dark] [--device-only] [suffix]` | Open the lanes window (section 3; the look: section 9) |
|
|
2058
|
+
| `lanes-launch [device \| workspace \| lane <id> \| ls \| here [device]]` | The reconnecting client (section 3); `here` shows the lane you opened last on another of your devices |
|
|
1446
2059
|
| `lanes-launch web phone\|laptop\|<device> [--desktop\|--mobile] [--no-qr]` | A browser view plus a login token, in this terminal (section 4) |
|
|
1447
|
-
| `lanes-launch update [install.sh options]` | The newest client from npm with your remembered install options (section 2) |
|
|
2060
|
+
| `lanes-launch update [install.sh options]` | The newest client from npm with your remembered install options (section 2); a window also updates itself to the server's release, at most every 6 h ([Versions](#versions)) |
|
|
2061
|
+
| `lanes-launch --version` | This client's release (`lanes-launch 1.2.0`; [Versions](#versions)) |
|
|
1448
2062
|
| `lanes-launch --helper <device> [--service \| --while-pid PID]` | Run only the upload helper (the service form is what the installer sets up) |
|
|
1449
2063
|
| `lanes-upload …` | Uploads (section 5) |
|
|
1450
2064
|
| `sh install/enable-phone.sh …` | Phone or browser view plus token; on the server a wrapper over `lanes web … --token` (section 4) |
|
|
@@ -1459,13 +2073,15 @@ Server (`~/.local/state/lanes/`, `$LANES_STATE`):
|
|
|
1459
2073
|
| --- | --- |
|
|
1460
2074
|
| `registry.json`, `registry.synced`, `resolve/`, `resolve.dynamic/` | The published registry, the last sync time, per-lane targets |
|
|
1461
2075
|
| `status.json`, `health` | statusd's snapshot and the one-line health record |
|
|
2076
|
+
| `run/<name>.tree` | The tree each long-running process started from — `statusd`, `enrold`, `economy`, `notify`, `studio_host` (the enable step its enrolment timer runs) and `sidebar` (the most recently started one): one resolved path, rewritten at every start, so a record that differs from where `~/.local/share/lanes/current` points is a process still running an older version |
|
|
1462
2077
|
| `containers.json`, `containers/` | Container init pids and home volumes |
|
|
1463
|
-
| `devices/<device>.json` | Each known device: layout, viewports, base device, look |
|
|
2078
|
+
| `devices/<device>.json` | Each known device: layout, viewports, base device, owner, look (colour, theme, glyphs, dim; `device_only` lists the keys `--device-only` kept for this device alone), desktop notifications on/off and the quick-edit editor |
|
|
1464
2079
|
| `layouts/<device>.kdl` | The layout a view session was built from |
|
|
1465
|
-
| `views/<device>/` | Per-viewport target, pid and error files |
|
|
1466
|
-
| `clients/` | Client pids, `.ready`, `.rebuilt`, `<device>.seen.json` (
|
|
2080
|
+
| `views/<device>/` | Per-viewport target, pid and error files; `<workspace>.sidebar.cmd`, the key `Ctrl-Alt-/` or `Ctrl-Alt-u` handed to that workspace's sidebar (`{"cmd": "filter"\|"upload", "at": …}`, ignored once 10 s old); `<workspace>.<slot>.sidebar.state`, what a sidebar that swapped itself onto a new tree hands its successor (cursor lane, scroll, opened-lane marks, filter; taken and deleted at once, ignored once 60 s old) |
|
|
2081
|
+
| `clients/` | Client pids, `.ready`, `.rebuilt`, `<device>.seen.json` (the `done` lanes opened on that device; a device with an owner also marks them in the profile), `.theme`, `.glyphs`, `.color`, `<device>.update-ready` (an update changed that view's layout while it was attached: its sidebar offers the reload) |
|
|
1467
2082
|
| `defaults/<workspace>` | The lane a workspace's first viewport shows by default (its `focus` lane, else the first) |
|
|
1468
2083
|
| `user-sessions/<id>.json` | Names of unregistered sessions |
|
|
2084
|
+
| `handback/<digest>.json` | The workspace and id a session unregistered while it ran keeps (`u`, `lanes unregister`): statusd lists it there under that id; it applies to that one tmux session only |
|
|
1469
2085
|
| `upload-requests/<device>/` | Upload hand-off between sidebar and helper |
|
|
1470
2086
|
| `economy.json`, `economy.cursor.json` | The opt-in economy projection and its log cursor |
|
|
1471
2087
|
| `notify.enrol-seen.json` | The enrolment requests the opt-in push notifier already announced (0600, pruned to the requests on disk); deleting it announces the ones still pending once more |
|
|
@@ -1475,25 +2091,39 @@ Server (`~/.local/state/lanes/`, `$LANES_STATE`):
|
|
|
1475
2091
|
| `tmpscan/` | `lanes tmp` plans and `deleted.log` |
|
|
1476
2092
|
| `studio-host/` | Optional Studio Host: public key, receipt, enrolment record |
|
|
1477
2093
|
| `members/<device>.json`, `members.lock` | Who each enrolled machine key belongs to (`lanes keys`; the managed block of `~/.ssh/authorized_keys` is its projection) and the lock every writer of it holds |
|
|
2094
|
+
| `users/<login>.json`, `users/<login>.lock` | One profile per person, named by the percent-encoded login (`anna%40example.com.json`) and shared by every device that person owns: look, opened-lane marks, workspace order and hidden boards, pinned and hidden lanes, the last lane opened; and the lock each writer holds for at most a second (0600). Written by `lanes attach`'s look flags on a device with an owner, `lanes device claim`, `lanes keys adopt --login`, `lanes keys owner`, by `lanes workspaces hide\|show\|order`, `lanes lane pin\|unpin\|hide\|show` and the sidebar keys `p`, `h`, `H` (the arrangement), and by the sidebars, `lanes next\|prev` and `lanes here` when a lane is opened (its mark and the last lane opened); read by `lanes user list\|show`, by `lanes here` (the last lane opened), by the push notifier (a desk device's write is presence — never a command-line verb's, stamped `cli:<command>`; `notify.quiet_s` is your own quiet window) by the view's tabs (`create_session`, `lanes upgrade-session`: board order and hidden boards) and by every sidebar of a device with an owner, which paints its look beneath the device file's own keys, lists its pins first and leaves its hidden lanes out, and re-reads it within a tick of a change |
|
|
1478
2095
|
| `invites/<id>.json` | Invites minted by `lanes invite`: the code's hash only (never the code), who minted it, expiry, invitee, and the redemption or revocation |
|
|
1479
2096
|
| `enrol-requests/<id>.json` | Enrolment requests waiting for, or past, a member's approval (24 h) |
|
|
1480
2097
|
| `enrol.log` | The enrolment audit log, one line per attempt, invite, revocation, offboard and mode change (0600, append-only; never a code) |
|
|
1481
2098
|
| `enrol-mode` | Request mode as `lanes enrol mode request\|invite` pinned it (0600; absent = auto); the enrolment service reads it on every request |
|
|
1482
2099
|
| `web-tokens.json`, `web-tokens.lock` | Which view — and which machine — each web login token `lanes web --token` minted was for (names and creation times only, never a token; `lanes offboard` signs a machine out by it) and the lock every mint and revocation holds (section 4, "Tokens") |
|
|
1483
2100
|
| `onboard/<login>-<UTC>.json` | One receipt per `lanes onboard` run: each step with its status (section 7) |
|
|
2101
|
+
| `update/last.json`, `update/last.log`, `update/.lock` | The last `install.sh --server --update-only` run: `{checked_at, latest, installed, previous, result, steps: [{name, rc}], log}` with `result` `ok`, `partial`, `refused` or `failed` (a step whose exit status is a warning — `lanes sync` reporting a board with an error — carries `"warning": true`; keys it does not own are kept), and that run's full output; the run lock every server install and update holds while it runs (`{pid, mode, started}` of the last one to take it). `lanes update` adds `check` — its last check, `{at, by: timer\|hand, channel, tag, latest, installed, outcome, said[, exit]}`, `outcome` one of `current`, `older`, `bad`, `unanswered`, `refused`, `installing`, `installed`, `failed` — `bad` (the release a rollback left, written with the record of its flip, before its post-commit) and `rerun` (the release whose post-commit it ran again; a rollback drops it); a rollback's run is recorded with the step `rollback` first, `partial` until its post-commit has run |
|
|
2102
|
+
| `locks/sessions.lock` | The view-session lock: `lanes ensure`, `lanes rebuild` (and the sidebar's `r`), the create of `attach`/`prepare`/`web`, `lanes offboard`'s end of a view and `lanes device forget` hold it from their session list to their last Zellij call, so two of them never create or end one view at once; `{pid, doing, since}` of the holder while held, empty after (DEF-1792) |
|
|
2103
|
+
| `update/npx`, `locks/update.lock` | The npx the npm installer ran a `--server` install with (one absolute path; `lanes update` installs with it); the lock one `lanes update` at a time holds (`{pid, started}`) |
|
|
1484
2104
|
|
|
1485
|
-
Other server paths: `~/.config/lanes/` (`workspaces/`, `site.conf`, `notify.conf`,
|
|
2105
|
+
Other server paths: `~/.config/lanes/` (`workspaces/`, `site.conf`, `notify.conf`, `update.conf` — `channel=stable`
|
|
2106
|
+
makes `lanes update` follow the `lanes-stable` dist-tag — and the optional invite key files
|
|
1486
2107
|
`resend.key` and `tailscale-api.key`), `~/.local/share/lanes/` (pinned
|
|
1487
|
-
binaries in `bin/`, the `current`
|
|
2108
|
+
binaries in `bin/`, the `current` link to the installed tree — the path units and layouts name —,
|
|
2109
|
+
`current.previous`, the tree it named before the last install that moved it, and `.install.lock`, the lock installs
|
|
2110
|
+
and unit re-renders take turns on; npm-staged `pkg/<version>/`), and
|
|
2111
|
+
`~/.lz/<container>` (links to each
|
|
1488
2112
|
container's `.lz`).
|
|
1489
2113
|
|
|
1490
|
-
In each container (`/home/coder/.lz/`): `bin/` (`lane`, `lane-run`, `lane-signal`, `
|
|
1491
|
-
|
|
1492
|
-
`
|
|
2114
|
+
In each container (`/home/coder/.lz/`): `bin/` (`lane`, `lane_core.py`, `lane-run`, `lane-signal`, `lane-edit`,
|
|
2115
|
+
`lane-mcp`, `zmx-*`), `VERSION` (the Lanes version that installed them), `s/logs/lane-mcp.log`, `hooks/` (hook
|
|
2116
|
+
definitions), `env.sh`, `vimrc` (what `lane-edit` gives vim and nvim), `tmp/` (their swap files while an edit is open;
|
|
2117
|
+
created by `lane-edit`, 0700), `registry.json` (the projection `lanes sync` writes), `status.json` (this container's
|
|
2118
|
+
lane states, which the status daemon writes), `state/hooks/` (hook sidecars), `state/<zmx name>.json` (`lane-run` phase
|
|
2119
|
+
records), `meta/` and `s/` (zmx metadata and sockets). In the container home itself, the agents' entries for
|
|
2120
|
+
`lane-mcp`: `mcpServers.lanes` in `~/.claude.json` and `[mcp_servers.lanes]` in `~/.codex/config.toml`, with the
|
|
2121
|
+
`.bak-<UTC time>` copies their first write kept.
|
|
1493
2122
|
|
|
1494
2123
|
Client: `~/.config/lanes/` (`device`, `install-options`, `ghostty.conf`, `client-site.conf`, `client-known-hosts`,
|
|
1495
2124
|
`site.cache`, `upload-allow`, `uploads-dir`, and `enrol-request` while an access request is open),
|
|
1496
|
-
`~/.local/state/lanes-client/` (`launch-<target>.log`, `helper.alive`, `helper-service.log`
|
|
2125
|
+
`~/.local/state/lanes-client/` (`launch-<target>.log`, `helper.alive`, `helper-service.log`, `checked` — the time of a
|
|
2126
|
+
window's last update question — and `checking/` while one asks), `~/.local/share/lanes/client/`.
|
|
1497
2127
|
|
|
1498
2128
|
### NEVER
|
|
1499
2129
|
|
|
@@ -1549,9 +2179,9 @@ Server and views:
|
|
|
1549
2179
|
| `LANES_WORKSPACES` | `libexec/lanes_lib.py`, `host-install.sh`, `services.py` | unset | Site directory; unset means `~/.config/lanes/workspaces` (if it has a catalog), else the checkout's `workspaces/` |
|
|
1550
2180
|
| `LANES_STATUS_INTERVAL` | `libexec/statusd.py` | `3` | Seconds between statusd cycles |
|
|
1551
2181
|
| `LANES_CONTAINER_UID` | `holders/tmux.sh`, `libexec/view.sh` | `1000` | The containers' tmux uid in socket paths |
|
|
1552
|
-
| `LANES_COLOR`, `LANES_THEME`, `LANES_GLYPHS` | `libexec/sidebar.py` | see section 9 | Sidebar look; the device file wins;
|
|
1553
|
-
| `LANES_DIM` | `libexec/
|
|
1554
|
-
| `LANES_NOTIFY` | `libexec/sidebar.py` | unset (on) | `0` silences OSC 9;
|
|
2182
|
+
| `LANES_COLOR`, `LANES_THEME`, `LANES_GLYPHS` | `libexec/sidebar.py` | see section 9 | Sidebar look; the device file wins; forwarded by `bin/lanes` into the view sessions it creates |
|
|
2183
|
+
| `LANES_DIM` | `libexec/sidebar.py` (`libexec/tokens.py` outside it) | `90` | `2` uses SGR 2 for dim text in 16-colour mode; the device file's `dim` wins; forwarded into the view sessions `bin/lanes` creates |
|
|
2184
|
+
| `LANES_NOTIFY` | `libexec/sidebar.py` | unset (on) | `0` silences OSC 9; the device file's `notify` wins; forwarded into the view sessions `bin/lanes` creates |
|
|
1555
2185
|
| `NO_COLOR`, `COLORTERM`, `LC_ALL`, `LC_CTYPE`, `LANG` | `libexec/sidebar.py` | — | Colour and glyph defaults |
|
|
1556
2186
|
| `LANES_THROTTLE_CONTROL` | `libexec/economy_poll.py` | `http://127.0.0.1:19181` | Throttle control URL; `none` sends no request |
|
|
1557
2187
|
| `LANES_THROTTLE_TOKEN_FILE` | `libexec/economy_poll.py` | unset | File holding a bearer token |
|
|
@@ -1582,7 +2212,11 @@ Client:
|
|
|
1582
2212
|
| `LANES_INVITE` | `install.sh`, `npm/lanes` wrapper | unset | An invite code, as `--invite` (the wrapper picks the site from its prefix) |
|
|
1583
2213
|
| `LANES_ENROL_WAIT`, `LANES_ENROL_POLL` | `install.sh` | `600`, `5` | Seconds an install waits for an access request's approval, and between two looks |
|
|
1584
2214
|
| `LANES_ENROL_URL` | `install.sh` | `https://<LANES_SERVER_NAME>:8443` | The enrolment service's base URL (development and tests) |
|
|
1585
|
-
| `
|
|
2215
|
+
| `LANES_NO_AUTOUPDATE` | `lanes-launch`, `npm/lanes` wrapper | unset | Any value: a window never updates its client by itself ([Versions](#versions)), and the wrapper never hands an invite to the server's newer release |
|
|
2216
|
+
| `LANES_AUTOUPDATED` | `lanes-launch` | unset | Set by a window that has just updated itself, for the client it starts again: that one never updates again (one update per window) |
|
|
2217
|
+
| `LANES_NO_SELF_UPDATE` | `npm/lanes` wrapper, `install.sh` | unset | Any value: an invite is redeemed by the package you ran, without asking `/enrol/site` for the server's release |
|
|
2218
|
+
| `LANES_WRAPPER_REPLY` | `install.sh` | unset | Set by the npm wrapper only: the file into which `install.sh` writes `update-to=<release>` before it exits 5 (Appendix B) |
|
|
2219
|
+
| `LANES_PKG_ROOT` | `npm/lanes` wrapper | `~/.local/share/lanes/pkg` | Where npm-staged trees go (any directory name: a staged tree is known by the wrapper's stamp, `lanes_lib.staged_tree`) |
|
|
1586
2220
|
| `LANES_NPM_ENV_FILE`, `LANES_NPM_TOKEN_KEY` | `npm/lanes/scripts/publish.sh` | `.env.local`, `NPMJS2` | Maintainers only: the publish token's file and key |
|
|
1587
2221
|
|
|
1588
2222
|
Container:
|
|
@@ -1593,22 +2227,28 @@ Container:
|
|
|
1593
2227
|
| `LANE_ZMX_VERSION` | `lane` | `0.8.1` | zmx binary version |
|
|
1594
2228
|
| `LANES_TMP_DIR` | `env.sh` | `/home/coder/.tmp` | Temp directory for new shells |
|
|
1595
2229
|
| `LANES_TMP_OPTOUT` | `env.sh` | unset | Set (any value) to keep `/tmp` |
|
|
1596
|
-
| `LANE_ID`, `LANE_WS`, `THROTTLE_TRACE`, `ANTHROPIC_CUSTOM_HEADERS` | Set by the canonical recipe; `lane-signal` reads `LANE_ID`; `lane` strips them | — | Lane identity (section 7) |
|
|
2230
|
+
| `LANE_ID`, `LANE_WS`, `THROTTLE_TRACE`, `ANTHROPIC_CUSTOM_HEADERS` | Set by the canonical recipe; `lane-signal` reads `LANE_ID`; `lane-mcp` reads `LANE_ID` (Codex passes it `LANE_ID`, `LANE_WS`, `TMUX` and `TMUX_PANE` through its entry's `env_vars`); `lane` strips them | — | Lane identity (section 7) |
|
|
1597
2231
|
| `LANE_HARNESS`, `CLAUDE_SESSION_ID`, `CODEX_THREAD_ID` | `lane-signal` | — | Fallbacks for the harness name and session id |
|
|
1598
2232
|
| `TMUX_TMPDIR` | `lane gc --sockets` | `/tmp` | Where tmux socket files are looked for |
|
|
2233
|
+
| `LANES_EDITOR` | `lane-edit`; `lanes edit` | unset | The editor quick edit runs: one program on the container's PATH, by name or absolute path, without arguments. Unset, empty or not found: the first of `micro`, `hx`, `nvim`, `vim`, `vi` (one that is not found is named on stderr). `lanes edit` sets it for `lane-edit` from the device's `lanes attach --editor`, else passes on its own `LANES_EDITOR` (a value with a control character is dropped; the `Ctrl-Alt-e` pane's clean environment has none, so there the device's choice is the one that counts) |
|
|
2234
|
+
| `LANES_EDIT_LABEL` | `lane-edit` | the file's name | What the `editing …` line names |
|
|
1599
2235
|
|
|
1600
2236
|
## Appendix B: exit codes
|
|
1601
2237
|
|
|
1602
2238
|
| Command | Codes |
|
|
1603
2239
|
| --- | --- |
|
|
1604
|
-
| `install.sh`, `npx @vibelord/lanes` | `0` ready · `3` key not yet authorized (no enrolment service, a refused invite, or an unanswered request) · `4` not in the tailnet, server unreachable or too slow · `1` error (also: a malformed invite code, or one for another site) |
|
|
1605
|
-
| `install.sh --server` (`host-install.sh`) | `0` · `1` missing prerequisite, not Linux x86_64, or run as root · `2` unknown option |
|
|
2240
|
+
| `install.sh`, `npx @vibelord/lanes` | `0` ready · `3` key not yet authorized (no enrolment service, a refused invite, or an unanswered request) · `4` not in the tailnet, server unreachable or too slow · `5` (`install.sh` only, and only for the npm wrapper's `LANES_WRAPPER_REPLY`) nothing installed: the server runs a newer release, which the wrapper then runs, ending with its status · `1` error (also: a malformed invite code, or one for another site) |
|
|
2241
|
+
| `install.sh --server` (`host-install.sh`) | `0` · `1` missing prerequisite, not Linux x86_64, or run as root · `2` unknown option · `3` the install was refused before anything changed (another install or update is running — it holds `~/.local/state/lanes/update/.lock` — or another install holds `~/.local/share/lanes/.install.lock`, or `current`, `current.previous` or `~/.local/bin/lanes` is no symlink) |
|
|
2242
|
+
| `install.sh --server --update-only` | `0` ok (a `lanes sync` warning included) · `4` partial: installed, a step after the switch failed · `3` refused: a prerequisite, no installed release or another install said no, nothing changed · `1` failed: nothing was installed · `2` usage (also with `--check` or `--no-start`) — the same result is in `update/last.json`, except for a run refused because another install or update was running, which records nothing |
|
|
1606
2243
|
| `lanes <verb>` | `0` · `1` error (message `lanes: …`) or unknown verb (usage printed) |
|
|
1607
2244
|
| `lanes sync` | `1` when any workspace's registry had errors (previous lanes kept) |
|
|
1608
2245
|
| `lanes doctor`, `lanes pressure`, `lanes invariant check`, `lanes sockets` | `1` when something needs attention |
|
|
1609
2246
|
| `lanes attach <workspace-id>` without a default device, `lanes web --token` without a terminal or while the running `lanes-web.service` reads another token database, `lanes web` when Tailscale Serve cannot be set (operator step printed) | `64` |
|
|
1610
2247
|
| `lanes token revoke-all` without `--yes` | `2` (nothing changed) |
|
|
1611
|
-
| `lanes preview` / `next` / `prev` without `--focused` | `2` |
|
|
2248
|
+
| `lanes preview` / `next` / `prev` / `edit` without `--focused` | `2` |
|
|
2249
|
+
| `lanes edit --focused` | the editor's own status once a file is picked (`docker exec` passes on `lane-edit`'s) · `0` closed with `q`/`Esc`, nothing to edit, or another Edit pane in this tab now has the focus · `1` another Edit pane is open in another tab (or zellij would not focus it), zellij did not answer, a lane that is not a tmux lane, a container that is not running or has no `lane-edit`, no git checkout, docker could not be started, or another error |
|
|
2250
|
+
| `lanes version` | `0` (a stale process is shown, not an exit status) · `2` an argument other than `--json` |
|
|
2251
|
+
| `lanes update` | `0` nothing to do (up to date, not due, an older or bad release) or installed `ok` · `4` partial (the install's, or a post-commit run again) · `3` refused, nothing done: another update or install is running, a checkout server, no installed release, a bad `update.conf`, no release to roll back to — under `--timer` these are `0` · `1` failed: the registry gave no release, no usable recorded npx, the install failed or recorded nothing · `2` usage; `--status`: `0`, or `1` when nothing is recorded yet |
|
|
1612
2252
|
| `lanes studio-host status` | `0` only when running enrolled; `1` otherwise; `2` usage |
|
|
1613
2253
|
| `lanes invite <name>` | `0` minted (the kit printed; a failed email or Tailscale mint is a note, not an error) · `2` not on a terminal, nothing minted · `1` error |
|
|
1614
2254
|
| `lanes invite revoke <id>` | `0` revoked or already revoked · `1` already redeemed (offboard the device) or no such invite |
|
|
@@ -1617,7 +2257,8 @@ Container:
|
|
|
1617
2257
|
| `enable-phone.sh` | `0` · `2` usage · `64` not in your own terminal, Tailscale Serve refused, or the running `lanes-web` reads another token database (from `lanes web`) · `1` a `lanes web` error |
|
|
1618
2258
|
| `enable-web.sh` | `0` · `2` usage · `1` Caddy missing |
|
|
1619
2259
|
| `lanes-launch` | `64` invalid name or option (it never exits on its own otherwise); server exits 64–78 are refusals it retries after 60 s |
|
|
1620
|
-
| `lanes-window` | `64` invalid suffix · `69` no Ghostty (macOS) or neither Ghostty nor Foot (Linux) |
|
|
2260
|
+
| `lanes-window` | `64` invalid suffix, or `--device-only` without `--ascii`/`--light`/`--dark` · `69` no Ghostty (macOS) or neither Ghostty nor Foot (Linux) |
|
|
1621
2261
|
| `lanes-upload` | `64` usage · `66` no such file or empty clipboard · otherwise the transfer's status |
|
|
1622
2262
|
| `lane status` | `1` when the lane is not running |
|
|
2263
|
+
| `lane-edit <file>` (container) | the editor's own status · `2` usage · `127` no editor on PATH; on a terminal both refusals wait for `Enter` first |
|
|
1623
2264
|
| Live test suites (`tests/*.py`) | `75` host busy, not started |
|