Skip to content

feat(cli): add yertle nodes list, with the SDK resource behind it - #22

Merged
albertcmiller1 merged 1 commit into
mainfrom
slice-3/nodes-list
Sep 2, 2026
Merged

feat(cli): add yertle nodes list, with the SDK resource behind it#22
albertcmiller1 merged 1 commit into
mainfrom
slice-3/nodes-list

Conversation

@albertcmiller1

Copy link
Copy Markdown
Contributor

First command on the refactored layout, and the first proof of the merged CLI+SDK workstream — one endpoint, one implementation, two surfaces.

src/yertle/nodes.py          SDK resource — yertle.nodes.list(org_id)
src/yertle/cli/_context.py   org resolution: --org / $YERTLE_ORG / all
cli/commands/nodes.py        the command — ~20 lines of columns

Pagination — the decision this PR sets

/orgs/{id}/nodes returns 50 at a time. The facade hides it: list() walks every page and returns one complete list, because callers want the graph, not page 1.

docs/sdk/OVERVIEW.md listed this as an open question; it's now marked decided rather than left implicit in the code, since this becomes the precedent for search and every other paginating resource. If a lazy variant is ever wanted, add nodes.iter() beside list() rather than changing its contract.

The loop stops on the reported total or a short/empty page — a total that never catches up can't spin forever. There's a test for exactly that.

Org resolution

--org$YERTLE_ORGall. Defaulting to every org rather than erroring matches the Go CLI and suits an orienting command: yertle nodes list with no arguments should show you your world, not a usage error. Blank values are treated as unset, not malformed.

Shape is validated in the CLI so a typo fails with a sentence naming where to find a real id, instead of as a ValueError from inside the SDK.

It lives in cli/, not shared/ — precisely because it never reads config.json. If a persisted default org is added later, it has to be read in shared/auth.py so config access stays in one module (invariant 1). There is no default-org concept in the Python config today; save_credentials writes only api_url and token.

The SDK takes an explicit org_id. No ambient default — hidden global state in a library is miserable to debug, and picking the org is the CLI's job.

Two things the tests caught, not review

The org column is dropped when the listing is already scoped. Two 36-character UUID columns pushed the title clean off an 80-column terminal — test_nodes_list_table_format failed on a missing title, not on anything I'd have noticed by eye. When scoped it's also the same value on every row. Short ids will make this comfortable once the id cache lands for nodes show / tree.

test_yertle_run_refuses_unlisted failed on nodes — the guard added last PR doing its job. Widening the CLI is an API change for the SRE agent, so the allowlist, tool docstring and prompts.py all moved with it, plus a positive test that nodes list is actually admitted.

Verification

make check clean. 133 tests (was 111). 100% coverage on all three new modules.

$ yertle nodes list --help
  --org     -o  TEXT          Organization to act on, or 'all'.
                              Defaults to $YERTLE_ORG, then 'all'.
  --format  -f  [table|json]  Output format. [default: table]

Follow-up

IMPLEMENTATION_PLAN.md's Slice 3 checklist lives in the other repo; happy to tick nodes list and note the pagination decision there in a separate docs PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UJhtswytWsBWUbDxPkMvUT

First command on the refactored layout, and the first proof of the merged
CLI+SDK workstream: one endpoint, one implementation, two surfaces.

  src/yertle/nodes.py        SDK resource — yertle.nodes.list(org_id)
  src/yertle/cli/_context.py org resolution: --org / $YERTLE_ORG / all
  cli/commands/nodes.py      the command, ~20 lines of columns

Pagination: /orgs/{id}/nodes returns 50 at a time and the facade hides it —
list() walks every page and returns one complete list. Callers want the graph,
not page 1. This answers the open question in docs/sdk/OVERVIEW.md, which is
updated to record the decision rather than leaving it implicit; the same shape
should apply to search and any other paginating resource. The loop stops on the
reported total OR a short/empty page, so a total that never catches up cannot
spin forever.

Org resolution is --org, then $YERTLE_ORG, then "all". Defaulting to every org
rather than erroring matches the Go CLI and suits an orienting command. Blank
values are treated as unset rather than malformed. The id shape is validated
here so a typo fails with a sentence naming where to find a real id, instead of
as a ValueError from inside the SDK. It lives in cli/, not shared/, precisely
because it never reads config.json — if a persisted default org is added later
it has to be read in shared/auth.py to keep config access in one place.

The SDK takes an explicit org_id. No ambient default: hidden global state in a
library is miserable to debug, and choosing the org is the CLI's job.

Two things the tests caught rather than review:

- The org column is dropped when the listing is already scoped to one org. Two
  36-character uuid columns pushed the title off an 80-column terminal, and
  when scoped it is the same value on every row. Short ids will make this
  cheaper once the id cache lands for `nodes show` / `tree`.
- test_yertle_run_refuses_unlisted failed on "nodes", which is the guard added
  last PR doing its job: widening the CLI is an API change for the SRE agent,
  so the allowlist, tool docstring and prompt all move with it.

Coverage: 100% on all three new modules; 133 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UJhtswytWsBWUbDxPkMvUT
@albertcmiller1
albertcmiller1 merged commit 982a35f into main Sep 2, 2026
3 checks passed
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