@vibelord/lanes 1.0.4 → 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.
Files changed (37) hide show
  1. package/README.md +52 -15
  2. package/bin/lanes-install.mjs +8 -2
  3. package/lib/plan.mjs +34 -1
  4. package/package.json +2 -2
  5. package/payload/lanes/README.md +39 -18
  6. package/payload/lanes/VERSION +1 -1
  7. package/payload/lanes/bin/lanes +994 -49
  8. package/payload/lanes/client/lanes-launch +160 -14
  9. package/payload/lanes/client/ssh.stanza.in +11 -2
  10. package/payload/lanes/docs/ARCHITECTURE.md +86 -2
  11. package/payload/lanes/docs/INSTALL.md +296 -43
  12. package/payload/lanes/docs/RUNBOOK.md +453 -27
  13. package/payload/lanes/docs/USERGUIDE.md +449 -76
  14. package/payload/lanes/install/enable-phone.sh +8 -20
  15. package/payload/lanes/install/enable-web.sh +2 -2
  16. package/payload/lanes/install/host-install.sh +6 -0
  17. package/payload/lanes/install.sh +888 -52
  18. package/payload/lanes/libexec/enrol.py +695 -0
  19. package/payload/lanes/libexec/enrold.py +1264 -0
  20. package/payload/lanes/libexec/keys.py +644 -0
  21. package/payload/lanes/libexec/lanes_lib.py +14 -6
  22. package/payload/lanes/libexec/members.py +297 -0
  23. package/payload/lanes/libexec/notify.py +146 -21
  24. package/payload/lanes/libexec/qr.py +217 -0
  25. package/payload/lanes/libexec/site_edit.py +237 -0
  26. package/payload/lanes/libexec/web_tokens.py +315 -0
  27. package/payload/lanes/site/README.md +13 -1
  28. package/payload/lanes/site/beyondwork/site.conf +3 -0
  29. package/payload/lanes/site/site.example.conf +5 -0
  30. package/payload/lanes/sudo/one-time-root.sh +1 -1
  31. package/payload/lanes/systemd/lanes-enrold.service +22 -0
  32. package/payload/lanes/systemd/lanes-notify.service +4 -4
  33. package/payload/lanes/systemd/lanes-web.service +4 -0
  34. package/payload/lanes/web/Caddyfile +10 -0
  35. package/payload/lanes/web/README.md +8 -2
  36. package/payload/lanes/workspaces.example/README.md +6 -1
  37. package/payload/manifest.json +7 -4
package/README.md CHANGED
@@ -4,35 +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 **restricted** to the `@vibelord` npm organization.
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
10
12
  [Lanes user guide](../../lanes/docs/USERGUIDE.md).
11
13
 
12
- ## Install on a new Mac or Linux machine
14
+ ## Join a site
13
15
 
14
16
  Requirements: Node.js 18+ (for npm only), OpenSSH, and a route to the server (Tailscale for the Beyond Work profile).
15
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
+
16
21
  ```sh
17
- npm login --scope=@vibelord # once per machine: an @vibelord member account
18
- npx @vibelord/lanes --site beyondwork # install or update the client
19
- lanes-window # open the desktop view
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
20
26
  ```
21
27
 
22
- Instead of `npm login`, a machine can use a **read-only** npm token (npmjs.com → Access Tokens → Granular,
23
- read-only, scoped to `@vibelord/lanes`):
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:**
24
34
 
25
35
  ```sh
26
- printf '//registry.npmjs.org/:_authToken=%s\n' "$LANES_READ_TOKEN" >> ~/.npmrc
36
+ npx @vibelord/lanes --site beyondwork # install or update the client
37
+ lanes-window # open the desktop view
27
38
  ```
28
39
 
29
- Never put the organization's publish token on an installing machine.
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`.
30
44
 
31
- Exit status is the installer's: `0` ready · `3` installed, but this machine's SSH key is not yet authorized on
32
- the server (send the printed **public** key to the admin, then run `npx @vibelord/lanes --check`) · `4` installed,
33
- 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.
34
46
 
35
- Other sites pass their own profile: `npx @vibelord/lanes --site /path/to/site.conf`.
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.
36
54
 
37
55
  ## Server
38
56
 
@@ -43,6 +61,14 @@ npx @vibelord/lanes --server --check
43
61
  npx @vibelord/lanes --server
44
62
  ```
45
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
+
46
72
  ## What the wrapper does
47
73
 
48
74
  `lanes-install` copies the bundled tree to `~/.local/share/lanes/pkg/<version>/` (`$LANES_PKG_ROOT` overrides the
@@ -53,11 +79,15 @@ server still runs from are kept.
53
79
  | Option | Effect |
54
80
  | --- | --- |
55
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`) |
56
83
  | `--stage-only` | stage the tree and print its path, run nothing |
57
84
  | `--version` | print the package version |
58
85
  | `--wrapper-help` | wrapper help only |
59
86
 
60
- Update: re-run `npx @vibelord/lanes@latest` with the same options. Remove the client: `npx @vibelord/lanes --uninstall`.
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`.
61
91
 
62
92
  ## Maintainers
63
93
 
@@ -70,4 +100,11 @@ scripts/publish.sh --dry-run # then without --dry-run
70
100
  ```
71
101
 
72
102
  `publish.sh` reads the publish token from the main checkout's `.env.local` (`NPMJS2` by default; override with
73
- `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.
@@ -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
- args = installArgs(passthrough, staged, manifest.sites);
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.4",
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": "restricted"
30
+ "access": "public"
31
31
  },
32
32
  "repository": {
33
33
  "type": "git",
@@ -14,15 +14,16 @@ when someone closes it explicitly.
14
14
 
15
15
  **Who uses it.** Operators and teammates who run several Claude Code or Codex agents at once use Lanes daily to see
16
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 and authorizes each person's SSH key; each person installs the client on their own
18
- Mac or Linux machine.
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.
19
20
 
20
21
  **Three surfaces, one set of lanes:**
21
22
 
22
23
  | Surface | How you reach it | Typical use |
23
24
  | --- | --- | --- |
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 |
25
- | Browser | `/phone` in Safari (Add to Home Screen), or a laptop or tablet browser view from `lanes web`, private over Tailscale | Checking and answering agents away from the desk |
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 |
26
27
  | Editor | Cursor / VS Code terminals that run `lane open <lane-id>` inside the container | Opening the same lane next to the code |
27
28
 
28
29
  **How it relates to the rest of the repository.**
@@ -46,15 +47,21 @@ Phone / tablet / laptop ── HTTPS ────┘ Zellij views
46
47
 
47
48
  ## Quick start
48
49
 
49
- **New Mac or Linux machine, from npm** (members of the `@vibelord` npm organization; Node.js 18+ for npm only):
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:
50
52
 
51
53
  ```sh
52
- npm login # once per machine
53
- npx @vibelord/lanes --site beyondwork # install or update; no clone needed
54
- lanes-window
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
55
58
  ```
56
59
 
57
- The package runs the same installer with the same options and exit codes; see [`npm/lanes`](../npm/lanes/README.md).
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).
58
65
 
59
66
  **From a checkout:** clone the repository once on each machine that needs it:
60
67
 
@@ -77,9 +84,13 @@ Beyond Work's existing deployment has an explicit profile:
77
84
  ```
78
85
 
79
86
  The client works on macOS and Linux. It needs OpenSSH and a route to the server (Tailscale for the supplied
80
- profile). Ghostty is recommended for a separate window; `lanes-launch` works in an existing terminal.
81
- The installer uses an existing SSH key or creates a dedicated one. An administrator authorizes its **public**
82
- key on the server. Re-run `./install.sh --check` after authorization. Your selected site is remembered.
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.
83
94
 
84
95
  When an ended view was rebuilt on the server, the client probes that state and reconnects
85
96
  automatically. The probe's watchdog cancels and reaps its timer when the probe completes;
@@ -94,14 +105,16 @@ client installer after updating their checkout.
94
105
  ./install.sh --server
95
106
  ```
96
107
 
97
- Then configure the workspace catalog and install the container helper. Follow the
98
- [installation guide](docs/INSTALL.md) for prerequisites, a complete example, phone access, upgrades and removal.
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.
99
111
  The installer starts an empty catalog safely; it does not assume anyone's containers, projects or login name.
100
112
 
101
113
  ## Daily use
102
114
 
103
115
  | Action | Command or control |
104
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)) |
105
118
  | Open desktop view | `lanes-window` or the Lanes app; `lanes-launch` inside any terminal |
106
119
  | Independent second view | `lanes-window 2` |
107
120
  | Two or three panes per workspace | `lanes-window w2` / `lanes-window w3` |
@@ -118,6 +131,8 @@ The installer starts an empty catalog safely; it does not assume anyone's contai
118
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) |
119
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>` |
120
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)) |
121
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) |
122
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 |
123
138
  | End an unregistered session and its jobs | **×** beside the session, or select it and press `x`; confirm with `y` (any other key cancels) |
@@ -126,9 +141,12 @@ The installer starts an empty catalog safely; it does not assume anyone's contai
126
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) |
127
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) |
128
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>` |
129
- | Phone setup from a client | `sh install/enable-phone.sh --host lanes` (prepares the `phone` view; open `https://<server>:8443/phone` in Safari) |
130
- | Laptop or tablet browser view | `lanes web <device> [--desktop\|--mobile] [--viewports N] [--token \| --print-only]` on the server (prepares the view, enables the loopback web services, prints the private URL), or `sh install/enable-phone.sh --host lanes --desktop <device>` ([guide](docs/INSTALL.md#laptop-and-tablet-browser)) |
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 |
131
148
  | Switch workspace or lane in a laptop browser | The **Lanes** chip (top right) opens the workspace and lane pickers |
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)) |
132
150
  | Detach | Close the window, or Ctrl–Alt–O then `d`; the agents keep running |
133
151
 
134
152
  Unregistered sessions also appear when they use shared or previously unlisted tmux servers. Reopening a
@@ -217,8 +235,10 @@ See [workspace configuration](workspaces.example/README.md) and the [architectur
217
235
 
218
236
  ## Update and verify
219
237
 
220
- After `git pull`, clients re-run `./install.sh`; servers re-run `./install.sh --server` from their installed
221
- checkout. npm installations update with `npx @vibelord/lanes@latest` (add `--server` on the host). After a server
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
222
242
  update, restart `lanes-statusd.service` (and `lanes-web`/`lanes-mobile` when their units or the pinned Zellij
223
243
  changed); existing views keep their layout until `lanes rebuild <device>`. `lanes hello` prints the server's
224
244
  version ([user guide](docs/USERGUIDE.md#update)). The server installer resolves the checkout's actual path,
@@ -238,7 +258,8 @@ For recovery, service updates and troubleshooting, see the [runbook](docs/RUNBOO
238
258
 
239
259
  The host's site catalog (`LANES_WORKSPACES/catalog.json`, normally
240
260
  `~/.config/lanes/workspaces/catalog.json`) is the workspace authority. Add its
241
- manifest there and run `lanes sync`. The existing sync timer refreshes it every
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
242
263
  2 minutes; the ensure timer also reconciles known device sessions every minute.
243
264
  New desktop and phone tabs appear without rebuilding sessions or restarting
244
265
  agents. Changing a catalog title renames the existing tab using its workspace ID.
@@ -1 +1 @@
1
- 1.0.4
1
+ 1.1.0