refactor(cli): split into a command package and route commands through the SDK - #21
Merged
Merged
Conversation
…h the SDK Prepares the CLI to absorb the rest of Slice 3. main.py was 321 lines with half of it private helpers pooled above the app, and every helper was really per-command presentation: _source_checkout belongs to version, _mask_token and _source_label to auth status, _web_url_for to login. Six more commands into that file and the split only gets more expensive. Layout, one module per noun group: cli/main.py composition root — builds the app, mounts commands cli/_render.py Format enum, Column, render(), display_path() cli/_errors.py api_errors() — wire/credential failures to sentences cli/commands/ version, login, auth, orgs The bigger change is layering. cli/main.py imported list_organizations_orgs_get from yertle_client and hand-rolled the response check, which src/yertle/orgs.py already does — two implementations of "list orgs" in one package. That would have multiplied: IMPLEMENTATION_PLAN.md has Slice 3 (nodes list, tree, rag) and "SDK wider surface" (yertle.nodes.*, yertle.search) as separate workstreams hitting identical endpoints. Commands now call the SDK, so each CLI command ships the SDK function it needs instead of the two surfaces being built twice, and the SDK gets exercised by the CLI rather than sitting parallel and untested. test_cli_calls_the_sdk_not_the_wire_layer enforces it — verified it fails on a reintroduced wire import rather than passing vacuously. CLAUDE.md gains the CLI extension surface, which was the only subpackage without one, and that is how this drifted into a single file in the first place. Two intentional behavior changes, both previously logged: - --format is a Format enum, so `--format xml` errors instead of silently falling through to the table branch. Help now shows [table|json]. - _is_edge_rejection is deleted. It detected API Gateway's JWT authorizer, removed 2026-08-31 in the in-app auth migration, and told users PATs only work against a local backend — the opposite of true since that migration. This was the folded-in cleanup queued in Slice 3. Coverage on src/yertle/cli is 98%; 111 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UJhtswytWsBWUbDxPkMvUT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prepares the CLI to absorb the rest of Slice 3. No new commands — layout and layering only, so the next six land into a shape that can hold them.
Why now
main.pywas 321 lines, and half of it was private helpers pooled above the app. Those helpers were never CLI-generic — they're per-command presentation that had nowhere else to go:_source_checkoutbelongs toversion,_mask_token/_source_labeltoauth status,_web_url_fortologin. Six more commands into that file and the split only gets more expensive.Layout
_render.pyis the one that had to come first: every command branches on--formatidentically, and six copies is exactly the near-copy duplicationmake hygienewatches. Commands now declare columns and hand them over.The layering change
main.pyimportedlist_organizations_orgs_getfromyertle_clientand hand-rolled the response check — whichsrc/yertle/orgs.pyalready does. Two implementations of "list orgs" in one package.That was about to multiply.
IMPLEMENTATION_PLAN.mdtracks Slice 3 (nodes list,tree,rag) and "SDK wider surface" (yertle.nodes.*,yertle.search) as separate workstreams hitting identical endpoints. Commands now call the SDK, so each CLI command ships the SDK function it needs rather than the two surfaces being built twice — and the SDK gets exercised by the CLI instead of sitting parallel and untested.test_cli_calls_the_sdk_not_the_wire_layerenforces it. I checked it fails on a reintroduced wire import rather than passing vacuously:Wire types (
yertle_client.models,.errors) stay allowed — rendering and error handling legitimately need them.Two intentional behavior changes
Both were already logged as Slice 3 cleanups; flagging them because the rest of the diff is structural.
--formatis an enum, so--format xmlnow errors instead of silently falling through to the table branch. Help shows[table|json]._is_edge_rejectionis deleted. It detected API Gateway's JWT authorizer — removed 2026-08-31 in the in-app auth migration — and told users PATs only work against a locally-run backend, which has been the opposite of true since. A test now asserts that advice can't come back.Also
CLAUDE.mdgains the CLI extension surface. It was the only subpackage without one (SDK resources and SRE tools both have theirs), which is how the CLI drifted into a single 321-line file.tests/cli/. The shared default-client reset moved totests/conftest.pyrather than being copied into a second file.tests/cli/test_orgs.pymocks the wire layer, notyertle.orgs.list— patching the SDK would pass even if the CLI stopped calling it.Verification
make checkclean. 111 tests (was 100), coverage onsrc/yertle/cli98%.yertle --help,yertle orgs --help, andyertle orgs list --helpall render as before.🤖 Generated with Claude Code
https://claude.ai/code/session_01UJhtswytWsBWUbDxPkMvUT