Skip to content

CLI: one HTTP path in client.py; checkout.py and forge.py split from main.py - #52

Merged
Bobowski merged 1 commit into
mainfrom
cli-http-split
Nov 2, 2026
Merged

Bobowski merged 1 commit into
mainfrom
cli-http-split

Conversation

@Bobowski

@Bobowski Bobowski commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Intent

Split the 1,684-line app/cli/main.py along its seams, and send every HTTP call through one function. Source: the user, in the Yard audit. This pull request stacks on #51.

Found

  • _api_try, _stream, _follow_events, and _follow_deploy each repeated the urlopen, HTTPError, TimeoutError, and URLError branches. The copies had drifted apart: _follow_events had no TimeoutError branch.
  • The pattern "print the raw answer for --json or for a wrong shape, else print the table" was copied in eight commands.
  • _checkout_pair and _app_name ran the same three git calls. _read_pem and _ask_key both read and checked a PEM. The GitHub PUT body was built in two places.
  • The manifest handler read its server state with getattr, but _ManifestServer declares those fields.
  • Small leftovers in render.py: _ago was an alias of ago, _name_when had one caller, and the deploy block wrapped one record in a list (from the old deploys field).

Changed

  • app/cli/client.py holds the login (load), the calls (api, api_try, stream, open_stream), the flags (out.json, out.no_color), and die, fail, ask, color_on, and show. _urlopen is the one place that opens a request and turns a connection failure into the "did not answer" message.
  • app/cli/checkout.py holds checkout() (top, repo, branch), remote_repo, app_name, and the name helpers.
  • app/cli/forge.py holds yard forge setup, with one _pem_at and one _put_github.
  • main.py keeps the commands and the argument tree: 958 lines.
  • The other modules call client.api_try through the module. Thus a test patches one name. tests/cli/test_main.py has _fake_api(monkeypatch, fake) in place of the patch, _forge_rows, and _env lines it repeated 34 times.
  • AGENTS.md names the new modules.

Not done

  • I kept most isinstance guards on the answers from the server. Without them, an older server can make the CLI raise a traceback in place of printing the raw answer.
  • I did not convert the remaining hand-written fakes in test_main.py to a table of routes. Many of them assert on the request body or on the order of the calls, and a table hides that.
  • _align and _render in render.py stay separate: one has a header and an (none) line, the other has neither.

Proof

$ just check
...
501 passed in 63.19s

The CLI tests run unchanged in what they assert. Only their patch targets changed (client.api_try, client.out.json, forge.SETUP_POLL, and so on).

Questions

None

🤖 Generated with Claude Code

@Bobowski
Bobowski changed the base branch from forges-and-journal to main November 2, 2026 22:56
…main.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Bobowski
Bobowski merged commit f00c078 into main Nov 2, 2026
1 check passed
@Bobowski
Bobowski deleted the cli-http-split branch November 2, 2026 22:56
Bobowski added a commit that referenced this pull request Nov 2, 2026
## Intent

Remove the code that moves a 0.10 host to 0.11. Source: the user, in the
Yard audit. The user asked me to check the host with the yard CLI first.
This pull request stacks on #52.

## Found

The host runs 0.11 and has moved:

- `yard --version` prints `0.11.0`. `yard status` shows `yard-app-1` on
the commit of #47.
- The `host` column of `yard status` comes from the site files, so the
site files exist. Thus the 0.10 import does nothing at boot.
- `yard forge ls --json` shows the GitHub state with an `installations`
list, and each entry has an account. Thus no state has the old top-level
`installation_id`.

The code that only served the move:

- `sites._import_binds`, `_bind`, and `_owner`: with no site file, boot
wrote default sites from the 0.10 bind routes that Caddy resumed. Four
tests covered it, one of them on a real Caddy with a 0.10 autosave.
- `Forge.installations` read a top-level `installation_id` as one
installation. `installation_for` let an installation with no account own
every repo. Each `save` dropped the old keys.

## Changed

- `sites.boot` only loads the Caddyfile, with the same retry when Caddy
is not up yet. Three new small tests cover the retry, the give-up, and a
refused load.
- `Forge.installations` reads the `installations` list only.
`installation_for` matches the account, case-insensitively. The saves
drop only `installations`.
- `CHANGELOG.md`: two breaking lines ("upgrade a 0.10 host through 0.11
first") and the line for the hosts cache from #50. `AGENTS.md` no longer
names `_import_binds`.

## Not done

- `CADDY_ADMIN` stays in `ops/compose.yml`. `ops/Caddyfile` sets the
same admin socket, so the env is now redundant. But a change to the
Caddy service recreates the Caddy container on the next yard deploy, and
this pull request must not touch the proxy.
- `status._image_plan` (a host with no labelled deploy container) stays.
It also serves an app that has no deploy container at all.

## Proof

```
$ just check
...
498 passed in 64.96s
```

## Questions

None

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant