@vibelord/lanes 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -15
- package/bin/lanes-install.mjs +8 -2
- package/lib/plan.mjs +34 -1
- package/package.json +2 -2
- package/payload/lanes/README.md +91 -33
- package/payload/lanes/VERSION +1 -1
- package/payload/lanes/bin/lanes +994 -49
- package/payload/lanes/client/lanes-launch +160 -14
- package/payload/lanes/client/ssh.stanza.in +11 -2
- package/payload/lanes/docs/ARCHITECTURE.md +88 -3
- package/payload/lanes/docs/INSTALL.md +297 -43
- package/payload/lanes/docs/RUNBOOK.md +458 -31
- package/payload/lanes/docs/USERGUIDE.md +1612 -0
- package/payload/lanes/install/enable-phone.sh +8 -20
- package/payload/lanes/install/enable-web.sh +2 -2
- package/payload/lanes/install/host-install.sh +6 -0
- package/payload/lanes/install.sh +888 -52
- package/payload/lanes/libexec/enrol.py +695 -0
- package/payload/lanes/libexec/enrold.py +1264 -0
- package/payload/lanes/libexec/keys.py +644 -0
- package/payload/lanes/libexec/lanes_lib.py +14 -6
- package/payload/lanes/libexec/members.py +297 -0
- package/payload/lanes/libexec/notify.py +146 -21
- package/payload/lanes/libexec/qr.py +217 -0
- package/payload/lanes/libexec/site_edit.py +237 -0
- package/payload/lanes/libexec/web_tokens.py +315 -0
- package/payload/lanes/site/README.md +13 -1
- package/payload/lanes/site/beyondwork/site.conf +3 -0
- package/payload/lanes/site/site.example.conf +5 -0
- package/payload/lanes/sudo/one-time-root.sh +1 -1
- package/payload/lanes/systemd/lanes-enrold.service +22 -0
- package/payload/lanes/systemd/lanes-notify.service +4 -4
- package/payload/lanes/systemd/lanes-web.service +4 -0
- package/payload/lanes/web/Caddyfile +10 -0
- package/payload/lanes/web/README.md +8 -2
- package/payload/lanes/workspaces.example/README.md +6 -1
- package/payload/manifest.json +7 -4
package/README.md
CHANGED
|
@@ -4,32 +4,53 @@ npm distribution of [Lanes](../../lanes/README.md): shared agent terminals over
|
|
|
4
4
|
registry sync, uploads and a phone interface. The package carries the committed `lanes/` tree and runs its own
|
|
5
5
|
`install.sh`; every option and exit code of that installer applies unchanged.
|
|
6
6
|
|
|
7
|
-
The package is **
|
|
7
|
+
The package is **public**: installing needs no npm account or token. The only site facts it carries are the bundled
|
|
8
|
+
profiles' public SSH host keys and tailnet names and addresses (reachable only inside that tailnet); access to a server
|
|
9
|
+
is still its own key authorization or invite.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
How to use Lanes once installed (desktop, phone and browser views, keys, uploads, operations): the
|
|
12
|
+
[Lanes user guide](../../lanes/docs/USERGUIDE.md).
|
|
13
|
+
|
|
14
|
+
## Join a site
|
|
10
15
|
|
|
11
16
|
Requirements: Node.js 18+ (for npm only), OpenSSH, and a route to the server (Tailscale for the Beyond Work profile).
|
|
12
17
|
|
|
18
|
+
A member of the site sends you an invite with a one-time code (`lanes invite` on the server). Join the tailnet with
|
|
19
|
+
the Tailscale link in it, on this machine and on your phone; then:
|
|
20
|
+
|
|
13
21
|
```sh
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
lanes-
|
|
22
|
+
npx @vibelord/lanes --invite LN-xxxx-xxxxxxxxxxxx # the prefix picks the bundled site (LN = beyondwork)
|
|
23
|
+
lanes-launch # your desktop view (lanes-window: in its own window)
|
|
24
|
+
lanes-launch web phone # your phone's view: QR code of the URL, then a login token
|
|
25
|
+
lanes-launch web laptop # this laptop's browser view
|
|
17
26
|
```
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
The wrapper prints `site beyondwork, inferred from the invite code's prefix LN` and passes the code to the installer,
|
|
29
|
+
which redeems it at the server's enrolment service: `invite: redeemed — this machine's key is now authorized for
|
|
30
|
+
<login> (device <device>)`. An unknown prefix is refused with the bundled list; `--site` given explicitly wins. The
|
|
31
|
+
code is never printed, stored, or put on a command line. `LANES_INVITE` works like `--invite`.
|
|
32
|
+
|
|
33
|
+
**Without a code:**
|
|
21
34
|
|
|
22
35
|
```sh
|
|
23
|
-
|
|
36
|
+
npx @vibelord/lanes --site beyondwork # install or update the client
|
|
37
|
+
lanes-window # open the desktop view
|
|
24
38
|
```
|
|
25
39
|
|
|
26
|
-
|
|
40
|
+
When the server does not know this machine's key, the installer offers, on a terminal, to ask the site's members for
|
|
41
|
+
access; a member approves it on their phone (`/enrol/pending`) or with `lanes enrol approve <id>` on the server, while
|
|
42
|
+
the installer waits up to 10 minutes (`npx @vibelord/lanes --resume` waits again). Other sites pass their own profile:
|
|
43
|
+
`npx @vibelord/lanes --site /path/to/site.conf`.
|
|
27
44
|
|
|
28
|
-
|
|
29
|
-
the server (send the printed **public** key to the admin, then run `npx @vibelord/lanes --check`) · `4` installed,
|
|
30
|
-
but the server is unreachable (Tailscale?) · `1` error.
|
|
45
|
+
Never put the organization's publish token on an installing machine; it needs no npm credential.
|
|
31
46
|
|
|
32
|
-
|
|
47
|
+
Exit status is the installer's: `0` ready · `3` installed, but this machine's SSH key is not yet authorized on
|
|
48
|
+
the server (a refused code, an unanswered request, or a server without the enrolment service: send the printed
|
|
49
|
+
**public** key to the admin, then run `npx @vibelord/lanes --check`; a device name or key the server already has: run
|
|
50
|
+
the printed command again with `--device NAME` or a key of its own — the invite is still valid) · `4` installed, but
|
|
51
|
+
this machine is not in the
|
|
52
|
+
tailnet (the installer names the Tailscale install command, or opens `tailscale login` and waits up to 120 s) or the
|
|
53
|
+
server is unreachable · `1` error.
|
|
33
54
|
|
|
34
55
|
## Server
|
|
35
56
|
|
|
@@ -40,6 +61,14 @@ npx @vibelord/lanes --server --check
|
|
|
40
61
|
npx @vibelord/lanes --server
|
|
41
62
|
```
|
|
42
63
|
|
|
64
|
+
The server install also enables the enrolment service, `lanes-enrold`, that redeems invites and takes access
|
|
65
|
+
requests. Once its operator check passes, pin request mode with `lanes enrol mode request` (`invite` for invites
|
|
66
|
+
only), so no restart changes it. Members then invite from the server, in their own terminal, with `lanes invite <name>
|
|
67
|
+
--email <address>`, decide requests with `lanes enrol approve <id>`, and remove a machine — key, browsers and phones,
|
|
68
|
+
view — with `lanes offboard <device> --yes`; see the [install guide](../../lanes/docs/INSTALL.md#invite-a-user).
|
|
69
|
+
Until the server runs this release, `lanes-launch web` from an updated client opens the view without a QR code and
|
|
70
|
+
says the server is older.
|
|
71
|
+
|
|
43
72
|
## What the wrapper does
|
|
44
73
|
|
|
45
74
|
`lanes-install` copies the bundled tree to `~/.local/share/lanes/pkg/<version>/` (`$LANES_PKG_ROOT` overrides the
|
|
@@ -50,11 +79,15 @@ server still runs from are kept.
|
|
|
50
79
|
| Option | Effect |
|
|
51
80
|
| --- | --- |
|
|
52
81
|
| `--site NAME` | a bundled profile (`beyondwork`); a value with `/` or `.conf` is your own file |
|
|
82
|
+
| `--invite CODE` | passed to the installer; without `--site`, the code's prefix picks the bundled profile (`LN` → `beyondwork`) |
|
|
53
83
|
| `--stage-only` | stage the tree and print its path, run nothing |
|
|
54
84
|
| `--version` | print the package version |
|
|
55
85
|
| `--wrapper-help` | wrapper help only |
|
|
56
86
|
|
|
57
|
-
Update: re-run `npx @vibelord/lanes@latest`
|
|
87
|
+
Update: `lanes-launch update` on the client, or re-run `npx @vibelord/lanes@latest` — the installer repeats the
|
|
88
|
+
options of the last install (remembered in `~/.config/lanes/install-options`; a flag given again overrides one), and
|
|
89
|
+
`lanes-launch update` also gives each remembered option to it again as its flag. Remove the client:
|
|
90
|
+
`npx @vibelord/lanes --uninstall`.
|
|
58
91
|
|
|
59
92
|
## Maintainers
|
|
60
93
|
|
|
@@ -67,4 +100,11 @@ scripts/publish.sh --dry-run # then without --dry-run
|
|
|
67
100
|
```
|
|
68
101
|
|
|
69
102
|
`publish.sh` reads the publish token from the main checkout's `.env.local` (`NPMJS2` by default; override with
|
|
70
|
-
`LANES_NPM_ENV_FILE` / `LANES_NPM_TOKEN_KEY`) and hands it to npm through the environment only.
|
|
103
|
+
`LANES_NPM_ENV_FILE` / `LANES_NPM_TOKEN_KEY`) and hands it to npm through the environment only. It publishes with
|
|
104
|
+
`--access public`, as `publishConfig.access` says; `npm test` pins both, and fails when a bundled site profile carries
|
|
105
|
+
more than its public host key, tailnet name and tailnet address, any shipped file looks like a key or token, or a
|
|
106
|
+
shipped file names an e-mail address outside the example domains.
|
|
107
|
+
|
|
108
|
+
Access is a setting of the whole package, not of one version: the first public publish makes every version already
|
|
109
|
+
on the registry publicly installable too, including 1.0.0–1.0.4, which were published for `@vibelord` members only.
|
|
110
|
+
Deprecating or unpublishing an earlier version is a separate, deliberate `npm deprecate` / `npm unpublish` step.
|
package/bin/lanes-install.mjs
CHANGED
|
@@ -20,7 +20,7 @@ import os from 'node:os';
|
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
|
|
23
|
-
import { installArgs, platformError, prunable, splitArgs } from '../lib/plan.mjs';
|
|
23
|
+
import { inferredSite, installArgs, platformError, prunable, splitArgs } from '../lib/plan.mjs';
|
|
24
24
|
|
|
25
25
|
const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
26
|
const payload = path.join(packageDir, 'payload');
|
|
@@ -105,6 +105,7 @@ function prune(root, target) {
|
|
|
105
105
|
function usage(manifest) {
|
|
106
106
|
process.stdout.write(`@vibelord/lanes ${manifest.version} — installs Lanes from this package
|
|
107
107
|
|
|
108
|
+
npx @vibelord/lanes --invite LN-… join a site with an invite code (the code's prefix picks the site)
|
|
108
109
|
npx @vibelord/lanes --site beyondwork install or update the client (macOS / Linux)
|
|
109
110
|
npx @vibelord/lanes --check verify SSH path, key and server; changes nothing
|
|
110
111
|
npx @vibelord/lanes --server install or update the Linux workspace server
|
|
@@ -116,6 +117,7 @@ Wrapper options
|
|
|
116
117
|
--wrapper-help this text
|
|
117
118
|
|
|
118
119
|
Bundled site profiles: ${manifest.sites.join(', ') || 'none'} (or --site /path/to/site.conf)
|
|
120
|
+
Invite prefixes: ${Object.entries(manifest.site_codes || {}).map(([p, site]) => `${p} → ${site}`).join(', ') || 'none'} (an explicit --site wins)
|
|
119
121
|
Every other option goes to install.sh; its own help follows.
|
|
120
122
|
|
|
121
123
|
`);
|
|
@@ -144,11 +146,15 @@ function main() {
|
|
|
144
146
|
if (help) usage(manifest);
|
|
145
147
|
if (own.has('--wrapper-help')) return 0;
|
|
146
148
|
let args;
|
|
149
|
+
let inferred;
|
|
147
150
|
try {
|
|
148
|
-
|
|
151
|
+
const codes = manifest.site_codes || {};
|
|
152
|
+
args = installArgs(passthrough, staged, manifest.sites, codes, process.env);
|
|
153
|
+
inferred = inferredSite(passthrough, codes, process.env);
|
|
149
154
|
} catch (error) {
|
|
150
155
|
fail(error.message);
|
|
151
156
|
}
|
|
157
|
+
if (inferred) process.stdout.write(`site ${inferred.site}, inferred from the invite code's prefix ${inferred.prefix}\n`);
|
|
152
158
|
// Run through the staged path (not the npx cache), so install.sh resolves its checkout to the stable tree.
|
|
153
159
|
const result = spawnSync('sh', [path.join(staged, 'install.sh'), ...args], { stdio: 'inherit', cwd: staged });
|
|
154
160
|
if (result.error) fail(`could not run install.sh: ${result.error.message}`);
|
package/lib/plan.mjs
CHANGED
|
@@ -28,8 +28,10 @@ export function splitArgs(argv) {
|
|
|
28
28
|
// Rewrite install.sh arguments for a staged tree:
|
|
29
29
|
// * `--site NAME` names a profile bundled under lanes/site/NAME/ (a value with a `/` or ending in `.conf` stays a
|
|
30
30
|
// path the user owns);
|
|
31
|
+
// * without `--site`, an invite code (`--invite CODE`, or LANES_INVITE in `env`) picks the bundled profile its prefix
|
|
32
|
+
// names (`LN-…` -> the site whose site.conf says LANES_SITE_CODE=LN); the code itself passes through unchanged;
|
|
31
33
|
// * `--server` must be install.sh's FIRST argument (it dispatches on $1 only).
|
|
32
|
-
export function installArgs(passthrough, stagedDir, sites) {
|
|
34
|
+
export function installArgs(passthrough, stagedDir, sites, siteCodes = {}, env = {}) {
|
|
33
35
|
const out = [];
|
|
34
36
|
let server = false;
|
|
35
37
|
for (let i = 0; i < passthrough.length; i += 1) {
|
|
@@ -45,9 +47,40 @@ export function installArgs(passthrough, stagedDir, sites) {
|
|
|
45
47
|
}
|
|
46
48
|
out.push(arg);
|
|
47
49
|
}
|
|
50
|
+
const inferred = inferredSite(passthrough, siteCodes, env);
|
|
51
|
+
if (inferred) out.unshift('--site', resolveSite(inferred.site, stagedDir, sites));
|
|
48
52
|
return server ? ['--server', ...out] : out;
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
// The invite code of this run: the last `--invite CODE`, else LANES_INVITE; null when there is none.
|
|
56
|
+
export function inviteCode(passthrough, env = {}) {
|
|
57
|
+
let code = null;
|
|
58
|
+
for (let i = 0; i < passthrough.length; i += 1) {
|
|
59
|
+
if (passthrough[i] !== '--invite') continue;
|
|
60
|
+
if (i + 1 >= passthrough.length) throw new Error('--invite CODE: the code is missing');
|
|
61
|
+
code = passthrough[i + 1];
|
|
62
|
+
i += 1;
|
|
63
|
+
}
|
|
64
|
+
if (code === null && env.LANES_INVITE) code = env.LANES_INVITE;
|
|
65
|
+
return code === null ? null : code.replace(/\s+/g, '');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// {site, prefix} when an invite code picks the profile (no explicit --site), else null. Messages name the prefix at
|
|
69
|
+
// most, never the code: it is a secret until redeemed.
|
|
70
|
+
export function inferredSite(passthrough, siteCodes, env = {}) {
|
|
71
|
+
if (passthrough.includes('--site') || passthrough.includes('--server')) return null;
|
|
72
|
+
const code = inviteCode(passthrough, env);
|
|
73
|
+
if (code === null) return null;
|
|
74
|
+
const match = /^([A-Za-z]{1,8})-[A-Za-z0-9-]+$/.exec(code);
|
|
75
|
+
if (!match) throw new Error('not an invite code (letters, digits and dashes, like LN-xxxx-xxxxxxxxxxxx)');
|
|
76
|
+
const prefix = match[1].toUpperCase();
|
|
77
|
+
if (!Object.hasOwn(siteCodes, prefix)) {
|
|
78
|
+
const known = Object.entries(siteCodes).map(([p, site]) => `${p} → ${site}`).join(', ') || 'none';
|
|
79
|
+
throw new Error(`unknown invite prefix '${prefix}' (bundled: ${known}); or pass --site /path/to/site.conf`);
|
|
80
|
+
}
|
|
81
|
+
return { site: siteCodes[prefix], prefix };
|
|
82
|
+
}
|
|
83
|
+
|
|
51
84
|
export function resolveSite(value, stagedDir, sites) {
|
|
52
85
|
if (value.includes('/') || value.endsWith('.conf')) return value;
|
|
53
86
|
if (!sites.includes(value)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibelord/lanes",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Installer for Lanes: shared agent terminals over SSH with Zellij views, registry sync, uploads and a phone interface.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"test": "node --test"
|
|
28
28
|
},
|
|
29
29
|
"publishConfig": {
|
|
30
|
-
"access": "
|
|
30
|
+
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
package/payload/lanes/README.md
CHANGED
|
@@ -1,30 +1,67 @@
|
|
|
1
1
|
# Lanes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
Lanes is the shared terminal setup for long-running coding agents. The agents run in tmux sessions inside workspace
|
|
4
|
+
containers on one Linux host; every device you use gets its own live view of the same terminals: Zellij workspace
|
|
5
|
+
tabs with a lane sidebar that sorts the agents that need you to the top, registry sync from the repositories'
|
|
6
|
+
terminal lists, Cursor / VS Code integration, file uploads, desktop launchers, and a phone, tablet and laptop browser
|
|
7
|
+
interface. The complete implementation and its tests live in this folder.
|
|
8
|
+
|
|
9
|
+
**Start here: the [Lanes user guide](docs/USERGUIDE.md)** covers daily use on every surface, every key, sidebar
|
|
10
|
+
glyph and command, installation and updates, and the operations reference.
|
|
11
|
+
|
|
12
|
+
Closing a view or losing a connection leaves the terminals running in their workspace containers: a lane ends only
|
|
13
|
+
when someone closes it explicitly.
|
|
14
|
+
|
|
15
|
+
**Who uses it.** Operators and teammates who run several Claude Code or Codex agents at once use Lanes daily to see
|
|
16
|
+
which lane needs them, switch to it, answer, and move on, from a desk or from a phone. An administrator installs the
|
|
17
|
+
server once on the workspace host. Any member invites a new person with `lanes invite`, and that person joins from
|
|
18
|
+
their own Mac or Linux machine with one command, `npx @vibelord/lanes --invite <code>`, which installs the client and
|
|
19
|
+
authorizes the machine.
|
|
20
|
+
|
|
21
|
+
**Three surfaces, one set of lanes:**
|
|
22
|
+
|
|
23
|
+
| Surface | How you reach it | Typical use |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Desktop terminal | `lanes-window` (own Ghostty window) or `lanes-launch` in any terminal, over SSH | Daily work: workspace tabs, the sidebar, one to three lanes side by side |
|
|
26
|
+
| Browser | `lanes-launch web phone` or `lanes-launch web laptop` from a client: a private URL over Tailscale with a login token (on a phone, Add to Home Screen) | Checking and answering agents away from the desk |
|
|
27
|
+
| Editor | Cursor / VS Code terminals that run `lane open <lane-id>` inside the container | Opening the same lane next to the code |
|
|
28
|
+
|
|
29
|
+
**How it relates to the rest of the repository.**
|
|
30
|
+
|
|
31
|
+
- **Throttle**: Lanes runs without it. With the opt-in economy poller, the sidebar shows what the fleet spends and
|
|
32
|
+
which lanes are starving, downgraded or mostly paid, read from the Throttle daemon. A lane created with the
|
|
33
|
+
canonical recipe sends a `lanes/<workspace>/<lane>` trace to the Throttle proxy (enabling that on a live workspace is
|
|
34
|
+
an operator decision).
|
|
35
|
+
- **Throttle Studio**: a second client of the same lanes. Its **Shared lanes** console gives teammates a read-only,
|
|
36
|
+
SSO-protected view of selected workspaces, and one co-located Studio Host can serve every workspace of a Lanes server
|
|
37
|
+
([sharing](docs/SHARING.md)).
|
|
38
|
+
- **Cursor / VS Code**: each repository's `.vscode/terminals.json` is the source of truth for its lanes;
|
|
39
|
+
`lanes cursor-sync` points the editor's terminals at the same tmux sessions the views show.
|
|
9
40
|
|
|
10
41
|
```text
|
|
11
|
-
Mac / Linux terminal
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
42
|
+
Mac / Linux terminal ──── SSH ───────┐
|
|
43
|
+
├─ Linux workspace host ── container tmux sessions ── Cursor / VS Code
|
|
44
|
+
Phone / tablet / laptop ── HTTPS ────┘ Zellij views agent lanes `lane open <id>`
|
|
45
|
+
browser (Tailscale) registry + status
|
|
15
46
|
```
|
|
16
47
|
|
|
17
48
|
## Quick start
|
|
18
49
|
|
|
19
|
-
**
|
|
50
|
+
**Join a site with an invite** (public npm package, no npm login; Node.js 18+ for npm only). Join the tailnet with
|
|
51
|
+
the Tailscale link in the invite, then:
|
|
20
52
|
|
|
21
53
|
```sh
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
lanes-
|
|
54
|
+
npx @vibelord/lanes --invite LN-xxxx-xxxxxxxxxxxx # installs the client and authorizes this machine
|
|
55
|
+
lanes-launch # your desktop view (lanes-window: in its own window)
|
|
56
|
+
lanes-launch web phone # your phone's view: QR code of the URL, then a login token
|
|
57
|
+
lanes-launch web laptop # this laptop's browser view
|
|
25
58
|
```
|
|
26
59
|
|
|
27
|
-
The
|
|
60
|
+
The code's prefix picks the site. Without a code, `npx @vibelord/lanes --site beyondwork` installs the client and, in
|
|
61
|
+
your own terminal, offers to ask the site's members for access; a member approves it on their phone or with
|
|
62
|
+
`lanes enrol approve <id>`. Later, `lanes-launch update` updates the client with the same choices. See the user
|
|
63
|
+
guide's [Join a site](docs/USERGUIDE.md#2-join-a-site). The package runs the same
|
|
64
|
+
installer with the same options and exit codes; see [`npm/lanes`](../npm/lanes/README.md).
|
|
28
65
|
|
|
29
66
|
**From a checkout:** clone the repository once on each machine that needs it:
|
|
30
67
|
|
|
@@ -47,9 +84,13 @@ Beyond Work's existing deployment has an explicit profile:
|
|
|
47
84
|
```
|
|
48
85
|
|
|
49
86
|
The client works on macOS and Linux. It needs OpenSSH and a route to the server (Tailscale for the supplied
|
|
50
|
-
profile
|
|
51
|
-
|
|
52
|
-
|
|
87
|
+
profile: the installer checks it first, names the install command when it is missing and opens `tailscale login`
|
|
88
|
+
when this machine is logged out). Ghostty is recommended for a separate window; `lanes-launch` works in an existing
|
|
89
|
+
terminal.
|
|
90
|
+
The installer uses an existing SSH key or creates a dedicated one. An invite code or an approved access request
|
|
91
|
+
authorizes it through the server's enrolment service; otherwise an administrator authorizes its **public** key on the
|
|
92
|
+
server (`lanes keys add`). Re-run `./install.sh --check` after authorization. Your site and every other install choice
|
|
93
|
+
are remembered.
|
|
53
94
|
|
|
54
95
|
When an ended view was rebuilt on the server, the client probes that state and reconnects
|
|
55
96
|
automatically. The probe's watchdog cancels and reaps its timer when the probe completes;
|
|
@@ -64,39 +105,49 @@ client installer after updating their checkout.
|
|
|
64
105
|
./install.sh --server
|
|
65
106
|
```
|
|
66
107
|
|
|
67
|
-
Then configure the workspace catalog and install the container helper
|
|
68
|
-
[installation guide](docs/INSTALL.md) for prerequisites, a complete
|
|
108
|
+
Then configure the workspace catalog and install the container helper — for one user's container, `lanes onboard`
|
|
109
|
+
does both and attaches its agents. Follow the [installation guide](docs/INSTALL.md) for prerequisites, a complete
|
|
110
|
+
example, invites, phone access, upgrades and removal.
|
|
69
111
|
The installer starts an empty catalog safely; it does not assume anyone's containers, projects or login name.
|
|
70
112
|
|
|
71
113
|
## Daily use
|
|
72
114
|
|
|
73
115
|
| Action | Command or control |
|
|
74
116
|
| --- | --- |
|
|
117
|
+
| Join a site from a new machine | `npx @vibelord/lanes --invite <code>` with the code from a member's invite: installs the client, authorizes this machine, then `lanes-launch`, `lanes-launch web phone`, `lanes-launch web laptop`. Without a code the installer asks the site's members for access ([guide](docs/USERGUIDE.md#2-join-a-site)) |
|
|
75
118
|
| Open desktop view | `lanes-window` or the Lanes app; `lanes-launch` inside any terminal |
|
|
76
119
|
| Independent second view | `lanes-window 2` |
|
|
77
120
|
| Two or three panes per workspace | `lanes-window w2` / `lanes-window w3` |
|
|
78
121
|
| Switch workspace | Ctrl–Alt–Left / Right |
|
|
79
122
|
| Next or previous lane | Ctrl–Alt–Down / Up (the focused pane moves to the next / previous lane of its workspace; nothing is typed, the focus stays), or tap the sidebar |
|
|
80
123
|
| Plain-ASCII glyphs, light or dark tokens | `lanes-window --ascii`, `lanes-window --light` / `--dark` (remembered per device in `devices/<device>.json`; a running sidebar picks it up within one tick) |
|
|
81
|
-
| Lock the view (
|
|
82
|
-
|
|
|
124
|
+
| Lock the view (plain keys and Zellij's mode keys reach the lane) | Ctrl–Alt–G, again to unlock; the bar shows LOCKED in red. The Ctrl–Alt keys of `zellij/config.kdl`'s `shared` block (←/→/↓/↑, `/`, U, C, S, PageUp/PageDown/End) stay active while locked. Ctrl–G itself always reaches the lane (Claude Code's editor key) |
|
|
125
|
+
| Zellij modes | Ctrl–Alt–P pane · Ctrl–Alt–T tab · Ctrl–Alt–N resize · Ctrl–Alt–H move · Ctrl–Alt–F scroll · Ctrl–Alt–O session (`d` detaches) · Ctrl–Alt–B tmux-style; Esc returns to normal. Every key: [user guide](docs/USERGUIDE.md#keybindings) |
|
|
126
|
+
| Search lanes | Ctrl–Alt–/, or `/` in the sidebar: fuzzy (the letters in order, substring and prefix matches first); Enter opens the match under the cursor, Esc clears |
|
|
83
127
|
| Select a lane for pane 2 or 3 | `2` / `3` in the sidebar of a wide view |
|
|
84
128
|
| Open one lane directly | `lanes-launch lane <lane-id>`; find IDs with `lanes-launch ls` |
|
|
85
129
|
| Read a markdown file a lane mentioned | Ctrl–Alt–M opens a floating **Preview** pane over the focused lane: the `.md` paths it printed in its last 300 lines that exist in its container (relative ones resolved against its working directory). One is shown at once; several give a numbered menu, newest first (Enter = the newest, `q` closes). Rendered by `glow` in the view's dark/light theme (the server installer adds a pinned glow unless the view server's PATH — `~/.local/share/lanes/bin`, the checkout's `bin`, `/usr/local/bin`, `/usr/bin`, `/bin` — already has one; a glow only in `~/.local/bin` or `~/go/bin` does not count), else `less -R`; `lanes doctor` names which (`preview renderer:`); quitting the pager closes the pane |
|
|
86
|
-
| Create a persistent workspace shell | **+ New shell** under **Unregistered** (
|
|
87
|
-
| Register an existing session | **+** beside an unregistered session, or select it and press `a`; choose a workspace |
|
|
130
|
+
| Create a persistent workspace shell | The **+ New shell** row under **Unregistered** (desktop sidebar and phone picker), or `+` in the sidebar |
|
|
131
|
+
| Register an existing session | **+** beside an unregistered session, or select it and press `a`; choose a workspace (`lanes register <lane-id> [workspace]` on the server) |
|
|
132
|
+
| Declare a session in the repository | `P` in the sidebar, then `y`: writes the session's binding into the workspace's one repository `terminals.json`, so it becomes a repository lane `<workspace>--<slug>` |
|
|
133
|
+
| Rename a lane | `R` in the sidebar (Enter saves, Esc cancels), or `lanes rename <lane-id> <name>` on the server; the lane id never changes |
|
|
134
|
+
| Add a workspace (board) | `lanes workspaces add <id> --container <name> --repo /home/coder/<repo> [--title T]` on the server prints the catalog and manifest diff; add `--write` to apply, then `lanes sync` ([install](docs/INSTALL.md#2-configure-workspaces-and-editor-sync)) |
|
|
135
|
+
| Onboard a user's container | `lanes onboard <login> --container <name> --workspace <id> --repo /home/coder/<repo>` on the server prints every step (board, contract, Claude and Codex hooks, Throttle attachment, canonical recipes, sync, doctor); add `--write` to apply them and keep a receipt ([install](docs/INSTALL.md#onboard-a-users-container)) |
|
|
88
136
|
| See which agents need you | The sidebar sorts **Needs you** (◆ approval, ✗ error, ◇ waiting for a prompt), **Working** (▸) and **Done** (✓, until you open the lane) above the rest; the header counts them in the same glyphs (`◆1 ◇2 ▸6 ✓1`, zeros left out; `N lanes` when no lane runs an agent) |
|
|
89
137
|
| Read a sidebar row | `▌` cursor · agent glyph · `›` the lane this pane shows (`²`/`³`: shown in pane 2/3) · the name (`·` per other viewer, `…` when clipped) · an **age column** on every row (agent turn, else last output: `45s`, ` 3m`, ` 2h`, ` 4d`; amber in **Needs you**) · stream glyph · one tail cell by priority (`?` not observed right now > `⚠` starving: producing output but no request for over 10 min, or a 429 > `↓` downgraded > `$` mostly paid — the economy cells need the opt-in economy poller; a dim `⚠`/`↓` is a frozen last-known figure, not a live alarm) · `×` close. `⋮ N more` ends a list that continues below |
|
|
90
138
|
| End an unregistered session and its jobs | **×** beside the session, or select it and press `x`; confirm with `y` (any other key cancels) |
|
|
91
139
|
| Every sidebar key | `?` replaces the list with the key sheet until the next key (that key only closes it). The footer legend says ` ⏎ open · / find · x close · ? keys`; prompts share one shape (` › close ‘name’? y/n`, ` › Rename: …█`, ` › Register ‘name’? y/n`) and a filter counts its matches (` /abc · 3 matches`) |
|
|
92
|
-
| Jump to the next lane that needs you | `Tab` / `Shift-Tab` in the sidebar (cycles **Needs you** from the lane the pane shows; the pane title carries the counts and a lane that starts needing you raises one OSC 9 desktop notification — `LANES_NOTIFY=0` silences it) |
|
|
140
|
+
| Jump to the next lane that needs you | `Tab` / `Shift-Tab` in the sidebar (cycles **Needs you** from the lane the pane shows; the pane title carries the counts and a lane that starts needing you raises one OSC 9 desktop notification on desktop layouts — `LANES_NOTIFY=0` in the sidebar's environment silences it, see the [user guide](docs/USERGUIDE.md#9-appearance) for how environment variables reach a sidebar) |
|
|
93
141
|
| Other workspaces that need you | The row above the footer rule: ` ◆1 Worlds · ◇2 Code` (blank and absent when none; panes of 18 rows or more) |
|
|
94
142
|
| What the fleet spends | With the opt-in economy poller ([RUNBOOK](docs/RUNBOOK.md#economy-projection-opt-in)) the same row is the fleet economy strip instead: ` $8.5k/16k 53% · F2 S1 · spill 76% · free 3/19` — overflow spend against the daily cap (amber at the alert level), new upstream 429s, this workspace's lanes by model tier (`F` fable, `O` opus, `S` sonnet, `H` haiku, `G` gpt), the pool's spill fraction, free accounts and the next 5 h reset, whole parts dropped from the end to fit; a leading `?` means the figures are frozen — the poller could not reach the daemon, or it stopped (no rewrite for over 3 min) |
|
|
95
143
|
| Upload | Click the one-row upload strip above the footer (` ↑ uploads · click, then drop (U)`) or press `U`, then drop anywhere on the window; or use `~/Lanes Uploads/<workspace>/`, or `lanes-upload <files>` |
|
|
96
|
-
| Phone
|
|
97
|
-
| Laptop or tablet browser view | `lanes web <device> --desktop [--token]`
|
|
144
|
+
| Phone view from a client | `lanes-launch web phone` in your own terminal: prepares `<your device>-phone` (phone layout), makes Tailscale Serve forward private HTTPS 8443 when the mapping is missing, prints the private URL with a QR code of it and a login token; scan the code or open the URL in Safari. `sh install/enable-phone.sh --host lanes` is the older one-shot for the device `phone` ([guide](docs/USERGUIDE.md#phone-safari-home-screen)) |
|
|
145
|
+
| Laptop or tablet browser view | `lanes-launch web laptop` (`<your device>-web`, desktop layout) or `lanes-launch web <device> [--desktop\|--mobile]` from a client; on the server `lanes web <device> [--desktop\|--mobile] [--viewports N] [--token \| --print-only]` (prepares the view, enables the loopback web services, adds the Tailscale Serve `https:8443` mapping when missing, prints the private URL) ([guide](docs/INSTALL.md#laptop-and-tablet-browser)) |
|
|
146
|
+
| Web login tokens | `lanes token list` (each token with the device it was created for), `lanes token revoke <device>`, `lanes token revoke-all --yes` ([runbook](docs/RUNBOOK.md#login-tokens)) |
|
|
147
|
+
| Update the client | `lanes-launch update` (the newest client from npm with your remembered install options; flags you add win). A client older than the server says so in one dim line when it attaches |
|
|
98
148
|
| Switch workspace or lane in a laptop browser | The **Lanes** chip (top right) opens the workspace and lane pickers |
|
|
99
|
-
|
|
|
149
|
+
| Invite a teammate, approve a machine, remove one | On the server, in your own terminal: `lanes invite <name> --email <addr>` prints a five-line welcome kit with a one-time code (shown once, on a terminal only); `lanes enrol list` / `lanes enrol approve <id>` decide machines that asked without a code; `lanes enrol mode request` pins request mode once the Serve header check passed; `lanes offboard <device> --yes` revokes a machine's key, signs its browsers and phones out and ends its view ([guide](docs/USERGUIDE.md#invite-a-user-administrator), [install](docs/INSTALL.md#offboard-a-machine)) |
|
|
150
|
+
| Detach | Close the window, or Ctrl–Alt–O then `d`; the agents keep running |
|
|
100
151
|
|
|
101
152
|
Unregistered sessions also appear when they use shared or previously unlisted tmux servers. Reopening a
|
|
102
153
|
session reuses it; closing a view leaves it running. Registered lanes have no **×** control. See the
|
|
@@ -180,13 +231,19 @@ See [workspace configuration](workspaces.example/README.md) and the [architectur
|
|
|
180
231
|
| `site/`, `workspaces.example/` | Deployment profiles and editable configuration examples |
|
|
181
232
|
| `sudo/` | Optional administrator setup and host protection, applied explicitly |
|
|
182
233
|
| `tests/` | Unit tests plus isolated host/container integration suites |
|
|
183
|
-
| `docs/` | [
|
|
234
|
+
| `docs/` | [User guide](docs/USERGUIDE.md), [install](docs/INSTALL.md), [architecture](docs/ARCHITECTURE.md), [operations](docs/RUNBOOK.md), [design](docs/DESIGN.md), [sharing](docs/SHARING.md), [host guard](docs/HOST-GUARD.md) |
|
|
184
235
|
|
|
185
236
|
## Update and verify
|
|
186
237
|
|
|
187
|
-
After `git pull`, clients re-run `./install.sh
|
|
188
|
-
|
|
189
|
-
|
|
238
|
+
After `git pull`, clients re-run `./install.sh` (it repeats the options remembered in
|
|
239
|
+
`~/.config/lanes/install-options`); servers re-run `./install.sh --server` from their installed checkout. npm
|
|
240
|
+
client installations update with `lanes-launch update` (`npx @vibelord/lanes@latest` with those options given again
|
|
241
|
+
as flags); a server updates with `npx @vibelord/lanes@latest --server` on the host. After a server
|
|
242
|
+
update, restart `lanes-statusd.service` (and `lanes-web`/`lanes-mobile` when their units or the pinned Zellij
|
|
243
|
+
changed); existing views keep their layout until `lanes rebuild <device>`. `lanes hello` prints the server's
|
|
244
|
+
version ([user guide](docs/USERGUIDE.md#update)). The server installer resolves the checkout's actual path,
|
|
245
|
+
including `bw-tools/lanes`; no `~/lanes` clone is required. It preserves configured workspaces and existing agent
|
|
246
|
+
sessions.
|
|
190
247
|
|
|
191
248
|
```sh
|
|
192
249
|
./install.sh --check # client connectivity
|
|
@@ -201,7 +258,8 @@ For recovery, service updates and troubleshooting, see the [runbook](docs/RUNBOO
|
|
|
201
258
|
|
|
202
259
|
The host's site catalog (`LANES_WORKSPACES/catalog.json`, normally
|
|
203
260
|
`~/.config/lanes/workspaces/catalog.json`) is the workspace authority. Add its
|
|
204
|
-
manifest there
|
|
261
|
+
manifest there (`lanes workspaces add <id> --container <name> --repo <path>
|
|
262
|
+
--write` writes a one-repository board) and run `lanes sync`. The existing sync timer refreshes it every
|
|
205
263
|
2 minutes; the ensure timer also reconciles known device sessions every minute.
|
|
206
264
|
New desktop and phone tabs appear without rebuilding sessions or restarting
|
|
207
265
|
agents. Changing a catalog title renames the existing tab using its workspace ID.
|
package/payload/lanes/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0
|
|
1
|
+
1.1.0
|