Skip to content

feat: shared foundation for API-backed commands, and the api command - #45

Merged
alukach merged 3 commits into
mainfrom
feat/cli-foundation
Oct 14, 2026
Merged

alukach merged 3 commits into
mainfrom
feat/cli-foundation

Conversation

@alukach

@alukach alukach commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

The shared base that the auth and product commands, and later ones, build on, plus its first user, the api command. Together with the Auth and Products PRs stacked on it, this supersedes #24, #40, #41 and #44, which mixed these concerns.

What

  • src/api.rs: the source.coop API client (/api/v1), its errors (ApiError, with field errors and a sign-in hint on 401) and pages (Page, next_cursor).

  • src/failure.rs and fail(e, json): under --json, a failure prints as {"error": {"code", "message", "status"?, "field_errors"?}} on stderr, and every command exits by kind:

    Code Meaning
    3 Rejected input
    4 Not signed in, or not allowed
    5 Not found
    130 Interrupted
    1 Anything else

    Commands pass errors around as plain text (String), which drops the API's code, status and field errors. failure.rs recovers them; it's a deliberate bridge, marked with a ponytail: comment naming the proper fix: a typed error.

  • output::json for --json output, and src/style.rs for color: only on a terminal, never with NO_COLOR, gray IDs, and ago().

  • Prompting and interaction:

    • src/prompt.rs: a scriptable Prompter; Esc and q cancel (CANCELLED), Ctrl+C interrupts (exit 130); typed confirmation; a line editor Esc can cancel.
    • src/picker.rs: the -i list and full screen.
    • src/draft.rs: keeps your answers when a send fails.
  • Conventions: AGENTS.md, imported by CLAUDE.md, covers commands, input, prompts, the picker, output, the JSON-output rules, stored data, testing and git.

  • source-coop api: sends any /api/v1 request as the signed-in user (SOURCE_TOKEN overrides). Access tokens issued without an API audience are no longer sent, since they'd turn public reads into 401s.

  • README: api, and JSON output and exit codes.

Modules no command uses yet are marked #[allow(dead_code)], with a comment. The Products PR removes the markers, since it uses every module.

Stack

  • this PR → main
  • Auth (auth login | status | logout | token) → this PR
  • Products (product …, -i) → this PR

Auth and Products are independent. Whichever merges second needs a small rebase: both add to main.rs's module list, command enum and tests, and to the README. That union is verified: merging the two reproduces the combined code with all 103 tests passing.

Testing

  • cargo test and cargo test --features staging: 51 passed, including every_command_is_in_the_readme.
  • cargo clippy --all-targets -- -D warnings (both builds) and cargo fmt --check: clean.

🤖 Generated with Claude Code

The reusable pieces auth, products and later commands build on:
- api.rs: the source.coop API client (/api/v1), its errors and pages
- failure.rs and fail(e, json): JSON errors under --json and one table
  of exit codes for every command (3 rejected input, 4 not signed in or
  not allowed, 5 not found, 130 interrupted, else 1)
- output::json for --json output; style.rs for color (NO_COLOR, gray IDs)
- prompt.rs (scriptable prompts, Esc/Ctrl+C, typed confirmation, an
  Esc-able line editor), picker.rs (the -i list and full screen) and
  draft.rs (answers kept when a send fails)
- AGENTS.md (with CLAUDE.md importing it): the CLI's conventions,
  including the JSON-output rules

And its first user, source-coop api, which sends any /api/v1 request as
the signed-in user. Pieces no command uses yet are allowed as dead code
until the commands that use them land. Access tokens issued without an
API audience are no longer sent to the API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@claude

claude Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Claude finished @alukach's task in 21s —— View job


✅ No blocking issues — safe to merge.

I read the API client, api command, failure/exit-code bridge, session change and main.rs wiring. I didn't run the build or tests. I only skimmed prompt.rs, picker.rs, draft.rs and style.rs, so those are not reviewed in depth.

  • Client::resolve handles the cases I traced. A full URL is rejected by the origin check. .. segments are rejected by the path-prefix check after join normalizes them. A leading // is stripped. The bearer token can't be sent to another host or path.
  • The session.rs change (no access token without an audience) matches the new test.

Non-blocking notes:

  • src/api.rs:51: the 401 hint says source-coop login, but AGENTS.md makes auth login the canonical name. Update it when the Auth PR lands.
  • src/main.rs: when the session lookup fails, api_token warns and continues signed out. A broken keyring on a write call then shows up as a 401 with the hint. That is acceptable, since the warning is printed first.

Simplify (ponytail)

  • src/failure.rs:29-37: the thread-local remember/classify text-matching bridge is a heuristic. It is already marked with ponytail: and names the typed-error fix, so leave it for now. Returning ApiError from the commands would delete this file.
  • Cargo.toml / src/prompt.rs:8: dialoguer with the editor feature is kept for Select, Confirm, MultiSelect and Editor, while prompt.rs and picker.rs replace its text prompts and its list picker with their own. Check whether console alone covers the rest. If it does, drop dialoguer. Otherwise it's fine.
  • src/main.rs:~15-30: the #[allow(dead_code)] markers on api, draft, output, picker, prompt and style are temporary. The description says the Products PR removes them.

💰 Estimated review cost: $0.18 · 0m21s · 9 turns

alukach and others added 2 commits October 14, 2026 11:47
- An error status from `source-coop api` went through to_string(),
  skipping what records its code and status, so every failure exited 1.
  It goes through From now: a 404 exits 5, a 401 or 403 exits 4, and
  --json errors carry the code and status.
- -X GET with --input is refused rather than sending a body on a GET.
- Paths with .. that leave /api/v1 are refused, as a full URL elsewhere
  already was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- style: color detection is console's colors_enabled /
  colors_enabled_stderr (NO_COLOR, CLICOLOR), still only on a terminal.
- prompt: the *_or_back methods are required; every prompter has them.
- picker: one answered() line writer and one keep() for where browse
  left off, the two go-back arms merged, keypress's doc comment back on it.
- failure: keeps the ApiError itself and serializes Failure with serde.
- api: error causes via iter::successors; the api command prints JSON
  through output::json; main's api_token and api arm without the
  match-on-Result dances; cache_dir private again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alukach
alukach marked this pull request as ready for review October 14, 2026 21:29
@alukach
alukach merged commit a0db3e6 into main Oct 14, 2026
10 checks passed
@alukach
alukach deleted the feat/cli-foundation branch October 14, 2026 21:32
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