Public: anyone can read this; the crew can change it.
Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:1hngo4csjz is past its budget: trevato/forge: "README.md stays under 60 KB" failed on 3 pulls in the last hour — the lane's red, not theirs; they wait… #7814
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/7776-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 2 commits fork 2 threads · 2 unresolved
5 files
changed +115
−111
README.md +2 −108
| @@ -306,114 +306,7 @@ the words it takes, on the page, in the terminal and in a `note` field of | ||
| 306 | 306 | |
| 307 | 307 | ## On your machine |
| 308 | 308 | |
| 309 | The forge holds the truth; `forge` keeps a copy of the parts you work on, the | |
| 310 | way a music player keeps the albums you listen to — downloaded, fresh, and | |
| 311 | yours when the network is not. | |
| 312 | ||
| 313 | sh | |
| 314 | forge login forge.example.com # a URL and a token, stored 0600 (macOS: keychain) | |
| 315 | forge sync # clone what is missing, fetch the rest, report | |
| 316 | forge status # the same report, without touching the network | |
| 317 | forge # the local UI over your clones | |
| 318 | ||
| 319 | ||
| 320 | A bare host means https; `localhost`, `127.0.0.1` and `[::1]` mean http. Plain | |
| 321 | `http://` to any other machine is refused — the token would cross the network in | |
| 322 | clear — unless you log in with `--insecure` for a host you trust; that is kept | |
| 323 | with that workbench only, and `FORGE_URL` to another such address is refused too. | |
| 324 | ||
| 325 | Repos land under `~/src/<host>/<owner>/<name>` — the path is the identity, so | |
| 326 | two forges with a `docs` repo never collide, and the filesystem is the index | |
| 327 | (there is no local database of "your repos"; the walk finds them). `--root` | |
| 328 | or `FORGE_ROOT` moves the library; `FORGE_URL` and `FORGE_TOKEN` override the | |
| 329 | stored config for a script or a CI job. | |
| 330 | ||
| 331 | sh | |
| 332 | forge sync --watch --interval 5m # notify only when something CHANGED | |
| 333 | forge sync --ff # fast-forward clean repos, and only those | |
| 334 | forge pin owner/name # ask the workbench to keep this one here | |
| 335 | forge ls # what is here, and how stale | |
| 336 | forge open owner/name --web # the page, in a browser | |
| 337 | forge ship 12 # land pull #12 after a y/N, as Merge does | |
| 338 | forge pulls # open pulls of this clone, newest first | |
| 339 | forge runs # the crew's last 10 runs here: state, agent, #N, title, age, cost | |
| 340 | forge asks # what waits on you, most important first | |
| 341 | forge asks --repo owner/name # only that repository's; --json for the API's answer | |
| 342 | forge asks --ship 1 3 # decide by the line numbers just printed: planned, then Apply? [y/N] | |
| 343 | forge asks --close 2 --reason "words" # also --dismiss 4; --yes skips the question (needed without a TTY) | |
| 344 | forge asks --undo <id> # take back a decision by the receipt id it printed | |
| 345 | forge map # the plan of this clone: working, ready, waiting | |
| 346 | forge search retry budget # code, issues, pulls and repos you can read; exits 6 on "not now" | |
| 347 | forge search x --kind pulls --limit 5 # one kind, five hits; --json marks truncated.issues when more matched | |
| 348 | ||
| 349 | ||
| 350 | `forge ship <number>` reads the repository from `--repo owner/name` or from | |
| 351 | the folder's `origin` when it points at your workbench, shows the pull's | |
| 352 | title, check and whether it merges, and asks `Ship #12? [y/N]`. `--yes` skips | |
| 353 | the question; with no terminal and no `--yes` it refuses (exit 2). The | |
| 354 | merge's rules stay the forge's: a refusal prints the forge's words and exits | |
| 355 | 5 (3 for a refused token), and is never retried. | |
| 356 | ||
| 357 | `forge pulls` prints one line per pull: `#N`, the title cut to the terminal | |
| 358 | width, the author, the check (`green`, `red`, `running` or `none`) and the | |
| 359 | preview address when the repository is an app. It asks about the repository | |
| 360 | your `origin` remote names, when that remote is on the configured workbench; | |
| 361 | anywhere else it exits 2 and says so, and `--repo owner/name` names one | |
| 362 | yourself. `--closed` lists closed pulls, `--json` prints the API's answer, and | |
| 363 | a list longer than the 100-pull pages it follows is marked as cut. This is a | |
| 364 | shell command on purpose: the terminal UI of the forge (`ssh`, `src/tty.ts`) | |
| 365 | already has its own pulls screen, and the local UI is about working trees, not | |
| 366 | the workbench's conversation; the pulls API and MCP are the other doors. | |
| 367 | ||
| 368 | `forge map` reads the plan of the same repository (`--repo` or `origin`, as | |
| 369 | for `pulls`) from `GET /api/v1/repos/:owner/:name/map` and prints three | |
| 370 | groups, each headed with its count: Working (`#N title — assignee`), Ready — | |
| 371 | the map's `startable` subset of its ready issues, unassigned and not held, so | |
| 372 | its count matches the map page's ready count (`#N title — pillar`) — and | |
| 373 | Waiting (`#N title — waits on #A, #B`, open blockers only). Titles are cut to | |
| 374 | the terminal, and a group past 15 lines ends `… N more`. `--json` prints the | |
| 375 | API's answer unchanged; an unknown repository prints the forge's words and | |
| 376 | exits 1 (3 for a refused token, 5 for a forbidden one). | |
| 377 | The same plan is a screen in the forge's terminal UI: press `m` on a | |
| 378 | repository's issues list (`src/tui/screens/plan.ts`). | |
| 379 | ||
| 380 | `sync` never merges, rebases, resets or checks anything out. `--ff` is the one | |
| 381 | exception and it fast-forwards a repo only when the tree is clean, nothing is | |
| 382 | ahead, no rebase or merge is half-finished, and `merge-base --is-ancestor` has | |
| 383 | proved that moving the branch cannot lose a commit. A dirty repo is reported | |
| 384 | and left exactly as you left it. | |
| 385 | ||
| 386 | `sync` hands git your token only for a remote on the workbench's own origin | |
| 387 | (scheme, host and port) over https, or plain http to loopback. A clone whose | |
| 388 | `origin` was edited to anywhere else is fetched without the token, and its row | |
| 389 | says `no token sent`. | |
| 390 | ||
| 391 | `forge` with no arguments opens the local UI on the same runtime as the SSH | |
| 392 | one: the library of clones, then a lazygit-shaped working-tree screen — | |
| 393 | `space` stages, `enter` opens the hunks, `s` stages one, `c` commits, `P` | |
| 394 | pushes, `b` branches, `l` log, `z` stashes, `?` shows every binding. | |
| 395 | ||
| 396 | `forge runs` lists the last 10 runs of the same repository (`--repo | |
| 397 | owner/name` names another, `--json` prints the API's answer). Each row of | |
| 398 | `GET /api/v1/repos/:owner/:name/runs` carries `agent` (the agent run's | |
| 399 | username, null for a check or deploy), `number` and `title` (its pull or | |
| 400 | issue, null for none), so no client reads them out of a branch name. The SSH | |
| 401 | terminal's door is `H` on a repository: the runs screen | |
| 402 | (`src/tui/screens/runs.ts`), read through the same `listRepoRuns`, so a guest | |
| 403 | sees only what the page shows them and a stranger sees no cost or session. | |
| 404 | ||
| 405 | In the SSH terminal, `enter` on a run opens its log (the last 200 lines, following new ones while it runs; "Not found" where the web would say so), and `o` there prints the web address (`src/tui/screens/runlog.ts`). | |
| 406 | ||
| 407 | Build it once and put it on your PATH: | |
| 408 | ||
| 409 | sh | |
| 410 | bun build --compile src/cli/main.ts --outfile dist/forge | |
| 411 | ||
| 412 | ||
| 413 | That produces a self-contained binary (it embeds the Bun runtime, so expect | |
| 414 | something in the 60–110 MB range; `dist/` is gitignored and binaries are never | |
| 415 | committed). Until you build it, `bun run src/cli/main.ts …` is the same | |
| 416 | program. | |
| 309 | The `forge` command keeps a local copy of the parts you work on: **[docs/ON-YOUR-MACHINE.md](docs/ON-YOUR-MACHINE.md)**. | |
| 417 | 310 | |
| 418 | 311 | ## Develop |
outdated
Extra blank line left over from the removal. Drop it. |
||
| 419 | 312 | |
| @@ -477,6 +370,7 @@ has the rule. | ||
| 477 | 370 | |
| 478 | 371 | - [](docs/DEVELOP.md) — developing the forge: tests, checks, the gate, lint and types. |
| 479 | 372 | - [](docs/STATUS.md) — what works today, one bullet per feature. |
| 373 | - [](docs/ON-YOUR-MACHINE.md) — the `forge` command on your machine: the local copy, offline work and building the binary. | |
| 480 | 374 | This page is the front door. Everything else is a file of its own under |
| 481 | 375 | `docs/`, each about one thing: |
| 482 | 376 |
docs/ON-YOUR-MACHINE.md added +110
| @@ -0,0 +1,110 @@ | ||
| 1 | # On your machine | |
| 2 | ||
| 3 | The forge holds the truth; `forge` keeps a copy of the parts you work on, the | |
| 4 | way a music player keeps the albums you listen to — downloaded, fresh, and | |
| 5 | yours when the network is not. | |
| 6 | ||
| 7 | sh | |
| 8 | forge login forge.example.com # a URL and a token, stored 0600 (macOS: keychain) | |
| 9 | forge sync # clone what is missing, fetch the rest, report | |
| 10 | forge status # the same report, without touching the network | |
| 11 | forge # the local UI over your clones | |
| 12 | ||
| 13 | ||
| 14 | A bare host means https; `localhost`, `127.0.0.1` and `[::1]` mean http. Plain | |
| 15 | `http://` to any other machine is refused — the token would cross the network in | |
| 16 | clear — unless you log in with `--insecure` for a host you trust; that is kept | |
| 17 | with that workbench only, and `FORGE_URL` to another such address is refused too. | |
| 18 | ||
| 19 | Repos land under `~/src/<host>/<owner>/<name>` — the path is the identity, so | |
| 20 | two forges with a `docs` repo never collide, and the filesystem is the index | |
| 21 | (there is no local database of "your repos"; the walk finds them). `--root` | |
| 22 | or `FORGE_ROOT` moves the library; `FORGE_URL` and `FORGE_TOKEN` override the | |
| 23 | stored config for a script or a CI job. | |
| 24 | ||
| 25 | sh | |
| 26 | forge sync --watch --interval 5m # notify only when something CHANGED | |
| 27 | forge sync --ff # fast-forward clean repos, and only those | |
| 28 | forge pin owner/name # ask the workbench to keep this one here | |
| 29 | forge ls # what is here, and how stale | |
| 30 | forge open owner/name --web # the page, in a browser | |
| 31 | forge ship 12 # land pull #12 after a y/N, as Merge does | |
| 32 | forge pulls # open pulls of this clone, newest first | |
| 33 | forge runs # the crew's last 10 runs here: state, agent, #N, title, age, cost | |
| 34 | forge asks # what waits on you, most important first | |
| 35 | forge asks --repo owner/name # only that repository's; --json for the API's answer | |
| 36 | forge asks --ship 1 3 # decide by the line numbers just printed: planned, then Apply? [y/N] | |
| 37 | forge asks --close 2 --reason "words" # also --dismiss 4; --yes skips the question (needed without a TTY) | |
| 38 | forge asks --undo <id> # take back a decision by the receipt id it printed | |
| 39 | forge map # the plan of this clone: working, ready, waiting | |
| 40 | forge search retry budget # code, issues, pulls and repos you can read; exits 6 on "not now" | |
| 41 | forge search x --kind pulls --limit 5 # one kind, five hits; --json marks truncated.issues when more matched | |
| 42 | ||
| 43 | ||
| 44 | `forge ship <number>` reads the repository from `--repo owner/name` or from | |
| 45 | the folder's `origin` when it points at your workbench, shows the pull's | |
| 46 | title, check and whether it merges, and asks `Ship #12? [y/N]`. `--yes` skips | |
| 47 | the question; with no terminal and no `--yes` it refuses (exit 2). The | |
| 48 | merge's rules stay the forge's: a refusal prints the forge's words and exits | |
| 49 | 5 (3 for a refused token), and is never retried. | |
| 50 | ||
| 51 | `forge pulls` prints one line per pull: `#N`, the title cut to the terminal | |
| 52 | width, the author, the check (`green`, `red`, `running` or `none`) and the | |
| 53 | preview address when the repository is an app. It asks about the repository | |
| 54 | your `origin` remote names, when that remote is on the configured workbench; | |
| 55 | anywhere else it exits 2 and says so, and `--repo owner/name` names one | |
| 56 | yourself. `--closed` lists closed pulls, `--json` prints the API's answer, and | |
| 57 | a list longer than the 100-pull pages it follows is marked as cut. This is a | |
| 58 | shell command on purpose: the terminal UI of the forge (`ssh`, `src/tty.ts`) | |
| 59 | already has its own pulls screen, and the local UI is about working trees, not | |
| 60 | the workbench's conversation; the pulls API and MCP are the other doors. | |
| 61 | ||
| 62 | `forge map` reads the plan of the same repository (`--repo` or `origin`, as | |
| 63 | for `pulls`) from `GET /api/v1/repos/:owner/:name/map` and prints three | |
| 64 | groups, each headed with its count: Working (`#N title — assignee`), Ready — | |
| 65 | the map's `startable` subset of its ready issues, unassigned and not held, so | |
| 66 | its count matches the map page's ready count (`#N title — pillar`) — and | |
| 67 | Waiting (`#N title — waits on #A, #B`, open blockers only). Titles are cut to | |
| 68 | the terminal, and a group past 15 lines ends `… N more`. `--json` prints the | |
| 69 | API's answer unchanged; an unknown repository prints the forge's words and | |
| 70 | exits 1 (3 for a refused token, 5 for a forbidden one). | |
| 71 | The same plan is a screen in the forge's terminal UI: press `m` on a | |
| 72 | repository's issues list (`src/tui/screens/plan.ts`). | |
| 73 | ||
| 74 | `sync` never merges, rebases, resets or checks anything out. `--ff` is the one | |
| 75 | exception and it fast-forwards a repo only when the tree is clean, nothing is | |
| 76 | ahead, no rebase or merge is half-finished, and `merge-base --is-ancestor` has | |
| 77 | proved that moving the branch cannot lose a commit. A dirty repo is reported | |
| 78 | and left exactly as you left it. | |
| 79 | ||
| 80 | `sync` hands git your token only for a remote on the workbench's own origin | |
| 81 | (scheme, host and port) over https, or plain http to loopback. A clone whose | |
| 82 | `origin` was edited to anywhere else is fetched without the token, and its row | |
| 83 | says `no token sent`. | |
| 84 | ||
| 85 | `forge` with no arguments opens the local UI on the same runtime as the SSH | |
| 86 | one: the library of clones, then a lazygit-shaped working-tree screen — | |
| 87 | `space` stages, `enter` opens the hunks, `s` stages one, `c` commits, `P` | |
| 88 | pushes, `b` branches, `l` log, `z` stashes, `?` shows every binding. | |
| 89 | ||
| 90 | `forge runs` lists the last 10 runs of the same repository (`--repo | |
| 91 | owner/name` names another, `--json` prints the API's answer). Each row of | |
| 92 | `GET /api/v1/repos/:owner/:name/runs` carries `agent` (the agent run's | |
| 93 | username, null for a check or deploy), `number` and `title` (its pull or | |
| 94 | issue, null for none), so no client reads them out of a branch name. The SSH | |
| 95 | terminal's door is `H` on a repository: the runs screen | |
| 96 | (`src/tui/screens/runs.ts`), read through the same `listRepoRuns`, so a guest | |
| 97 | sees only what the page shows them and a stranger sees no cost or session. | |
| 98 | ||
| 99 | In the SSH terminal, `enter` on a run opens its log (the last 200 lines, following new ones while it runs; "Not found" where the web would say so), and `o` there prints the web address (`src/tui/screens/runlog.ts`). | |
| 100 | ||
| 101 | Build it once and put it on your PATH: | |
| 102 | ||
| 103 | sh | |
| 104 | bun build --compile src/cli/main.ts --outfile dist/forge | |
| 105 | ||
| 106 | ||
| 107 | That produces a self-contained binary (it embeds the Bun runtime, so expect | |
| 108 | something in the 60–110 MB range; `dist/` is gitignored and binaries are never | |
| 109 | committed). Until you build it, `bun run src/cli/main.ts …` is the same | |
| 110 | program. |
docs/ORIGINS.md +1 −1
| @@ -156,7 +156,7 @@ someone else's name with someone else's token. | ||
| 156 | 156 | header (`p` on a row in the terminal, `P` on a repo's home) says "keep this on |
| 157 | 157 | my machine", and the dashboard grows an **On your machine** card listing them. |
| 158 | 158 | The server remembers only the choice — nothing about the state of a laptop — |
| 159 | and `forge sync` (the README's "On your machine") is what acts on it. | |
| 159 | and `forge sync` (docs/ON-YOUR-MACHINE.md) is what acts on it. | |
| 160 | 160 | |
| 161 | 161 | ## Flows |
| 162 | 162 |
src/tui/screens/dashboard.ts +1 −1
| @@ -1413,7 +1413,7 @@ export function dashboardScreen(f: Session, ctx: Ctx): Screen { | ||
| 1413 | 1413 | note(s, inner, c, [ |
| 1414 | 1414 | "Nothing pinned yet.", |
| 1415 | 1415 | "p pins the repository under the cursor;", |
| 1416 | 'for forge sync, see README "On your machine".', | |
| 1416 | 'for forge sync, see docs/ON-YOUR-MACHINE.md.', | |
| 1417 | 1417 | ]); |
| 1418 | 1418 | return; |
| 1419 | 1419 | } |
src/web/pages/site.ts +1 −1
| @@ -155,7 +155,7 @@ export function downloadsCard(ctx: Ctx, user: User): Html { | ||
| 155 | 155 | ${copyField("forge-sync", "forge sync", "Sync command", "Copy the sync command")} |
| 156 | 156 | <span class="row-meta" |
| 157 | 157 | >Clones what is missing, fetches the rest, and reports |
| 158 | ahead/behind/dirty. See "On your machine" in the README.</span | |
| 158 | ahead/behind/dirty. See docs/ON-YOUR-MACHINE.md.</span | |
| 159 | 159 | > |
| 160 | 160 | </span> |
| 161 | 161 | </div> |
new above
offline
This pointer is now the only thing the README says here, and src/tui/screens/dashboard.ts:1416 and src/web/pages/site.ts:158 still tell people to see README "On your machine". Retarget those strings to docs/ON-YOUR-MACHINE.md.