Skip to content

Add --json output to read commands - #77

Merged
ret2libc merged 6 commits into
mainfrom
add-json-output
Sep 30, 2026
Merged

ret2libc merged 6 commits into
mainfrom
add-json-output

Conversation

@akshithg

Copy link
Copy Markdown
Member

Adds a --json flag to the read/query commands so agents and scripts can consume untruncated, structured output instead of the formatted tables. Default table rendering is unchanged.

Commands

  • list --json — droplets (id, name, status, ip, tailscale_ip, region, size, cost_monthly, in_ssh_config, ssh_hostname, tags), hibernated, total_monthly_cost
  • info --json — the list fields plus created_at, vcpus, memory_mb, disk_gb, transfer_tb, image, features, full networks (incl. IPv6)
  • list-ssh-keys --json — username and ssh_keys (name/id/fingerprint)
  • version --json — version string

Notes

  • Shared emit_json() and field-extraction builders are centralized; build_droplet_detail reuses build_droplet_record rather than duplicating extraction.
  • Missing values are emitted as null, not placeholder strings.
  • JSON mode suppresses decorative status lines.
  • Follow-up planned: --json + non-interactive --yes for the mutating commands.

Tests

New tests/test_list.py and tests/test_json_output.py cover the builders and all four commands. Full suite: 291 passed; coverage 30.61%. (The 12 test_rename.py failures are pre-existing on main, unrelated to this change.)

🤖 Generated with Claude Code

Add a --json flag to the read/query commands (list, info, list-ssh-keys,
version) so agents and scripts can consume untruncated, structured output
instead of the formatted tables. The default table rendering is unchanged.

Shared serialization (emit_json) and field extraction are centralized:
build_droplet_detail reuses build_droplet_record for the info command, and
the same helper is used across commands rather than duplicated.

Missing values are emitted as null rather than placeholder strings so
consumers can branch on them. JSON mode suppresses decorative status lines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@akshithg
akshithg requested a review from ret2libc as a code owner June 26, 2026 00:10
Comment thread dropkit/main.py Outdated
@ret2libc

Copy link
Copy Markdown
Collaborator

Automated review — --json output for read commands

Solid refactor; the table path looks behavior-preserving (cost totals, status colors, placeholders, summary line all match the old inline logic). Findings below are mostly about the JSON contract being a bit leakier than its docstring/purpose promises.

1. dropkit/main.py:~2596 (and the except/not-found paths) — errors print non-JSON to stdout in --json mode.
console = Console() writes to stdout, and error/not-found messages aren't guarded by json_output. dropkit info missing --json | jq . emits Error: Droplet 'missing' not found... on stdout (exit 1) instead of structured output — the exact failure the flag exists to prevent. Consider routing errors to stderr (or emitting a JSON error object) in JSON mode.

2. dropkit/main.py:2262, 2283 — builder defaults contradict the documented "missing values are None" contract.
build_droplet_record's docstring says missing values are None "so JSON consumers can branch on them cleanly," but name defaults to "" and cost_monthly to 0.0. An agent can't distinguish a free/$0 resource from one whose price_monthly was absent (both serialize as 0). Either make these None or correct the docstring.

3. dropkit/main.py:2350 — render_droplet_list exceeds the CLAUDE.md hard limit "cyclomatic complexity ≤8".
Two table builds, two row loops, and the summary block stack well past 8 decision points. Splitting the droplet table and hibernated table into separate render helpers would bring it under the limit.

4. dropkit/main.py:2443 — list inlines its --json help instead of reusing JSON_HELP.
The other three commands use the shared JSON_HELP constant; list hardcodes a differently-worded string ("instead of a table" vs "instead of formatted output"). A future change to JSON_HELP will silently skip list.

5. dropkit/main.py:54 — emit_json double-encodes.
console.print_json(json.dumps(payload)) serializes to a string only for print_json to re-parse it. console.print_json(data=payload) does the same in one step.

6. dropkit/main.py:2320 — build_droplet_detail assumes nested objects are non-null.
size = droplet.get("size", {}) returns {} only when the key is absent; an explicit "size": null yields None, and size.get("vcpus") then raises AttributeError. Low likelihood (DO populates these for live droplets), so plausible rather than confirmed — droplet.get("size") or {} guards it.

Nothing blocking for correctness; #1 and #2 most directly affect the machine-readability this PR is adding.

🤖 Generated with Claude Code

@akshithg
akshithg requested a review from ret2libc July 18, 2026 08:01

@ret2libc ret2libc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSON error contract needs one more pass before approval. list --json, info --json, and list-ssh-keys --json all call load_config_and_api(), which still writes missing/invalid config errors to stdout and raises typer.Exit before these commands reach emit_error(). I reproduced this with an empty home directory: dropkit list --json exits 1, writes Error: Config not found. Run 'dropkit init' first. to stdout, and writes nothing to stderr. This breaks the documented clean-stdout/JSON-stderr behavior. info --json also reaches find_user_droplet(), whose username/listing API error branches still print plain text to stdout. Please route these helper failures through the JSON error path in JSON mode and cover them with CLI tests.

I ran the full suite on the PR head: 308 passed.

@akshithg

Copy link
Copy Markdown
Member Author

Thanks for the reproduction. I updated load_config_and_api() and find_user_droplet() to honor --json. Missing or invalid config and username or droplet-listing API failures now exit with empty stdout and a JSON error on stderr. CLI tests cover both config failures across list, info, and list-ssh-keys, plus the info lookup failures.

I also moved the existing list table rendering back to its original path to keep the diff focused. Validation passed: 441 pytest tests, prek run, and the manual E2E lifecycle hook.

@ret2libc ret2libc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the latest head. The JSON error paths and output contract are addressed; no remaining relevant findings. Verified with 441 passing tests locally.

@ret2libc
ret2libc merged commit 1f6395f into main Sep 30, 2026
8 checks passed
@ret2libc
ret2libc deleted the add-json-output branch September 30, 2026 08:10
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.

2 participants