Skip to content

feat: agent-first root discovery, curated help, and TTY-aware output (DEVEX-695)#13

Merged
jpage-godaddy merged 5 commits into
mainfrom
next-actions-root
Jun 5, 2026
Merged

feat: agent-first root discovery, curated help, and TTY-aware output (DEVEX-695)#13
jpage-godaddy merged 5 commits into
mainfrom
next-actions-root

Conversation

@jpage-godaddy
Copy link
Copy Markdown
Collaborator

@jpage-godaddy jpage-godaddy commented Jun 4, 2026

Addresses Rust-port review item #2 (PR #49 review) · DEVEX-695

Original concern (item 2): the TypeScript CLI root returned JSON discovery (command tree + environment/auth snapshot + next actions); the Rust port renders clap long-help text for the empty command path → an agent-first discovery regression.

How each part of that concern is handled here:

Review item #2 expectation Status Notes
Bare root returns JSON for agents (the regression) ✅ Addressed Empty command path now emits a JSON discovery envelope in machine context (piped / CI / agent, or --output json).
next actions ✅ Addressed (superset) Original three (auth status, env get, application list) + tree.
command tree inline 🔀 Pivot Surfaced as a next_actions pointer to tree (godaddy tree --output json returns the full tree) instead of embedded — hypermedia model.
environment / auth snapshot inline 🔀 Pivot Reachable via the env get / auth status pointers instead of embedded.
Always-JSON default 🔀 Pivot TTY-aware default instead (human in a terminal, JSON when piped/CI/agent), with --output / ${APP_ID}_OUTPUT overrides. Whether to force always-JSON is left for the team.

Net: agent-first discovery is restored, in a leaner hypermedia form — the bare command returns pointers and the agent follows tree / auth status / env get for details, rather than one fat payload. Nothing from item 2 was dropped silently; each element is either restored or a deliberate pivot above.

Scope: this PR is the cli-engine capability. The godaddy CLI adopts it (calls with_root_next_actions(...) and rides the TTY-aware default) in a follow-up PR once this releases; that PR will be linked to DEVEX-695.


Summary

Makes the bare-invocation and help experience friendlier for humans without sacrificing machine-readable output for agents. Three related engine changes:

1. Root discovery (with_root_next_actions)

New opt-in CliConfig::with_root_next_actions hook. On bare invocation (no subcommand):

  • human output appends a "Suggested next actions" section (cold-start guidance), and
  • machine output emits a small discovery envelope — { data: { description, version }, next_actions }.

Actions are pointers to existing commands (e.g. auth status, env get, tree), not embedded snapshots — the agent follows only what it needs. With no hook configured, behavior is unchanged long-help (back-compat for existing consumers).

2. Curated root/group help

  • A root help template suppresses clap's duplicate command list and the global-options wall (still shown on leaf commands, where they matter).
  • Group pages keep their subcommand list but drop the options wall.
  • Categories — and the commands within each — are sorted.
  • The engine-injected auth command is filed under an admin category (CliConfig::with_admin_category, default "Admin") so it stays discoverable once the auto list is suppressed; any uncategorized top-level command falls under a generic "Commands" section, so nothing is ever lost.

3. TTY-aware default output format

Default output is now human on an interactive terminal, JSON otherwise (pipes, files, CI, most agents). Precedence:

  1. explicit --output/--json/--toon/--human
  2. ${APP_ID}_OUTPUT env (e.g. GODADDY_OUTPUT=json, GDX_OUTPUT=json)
  3. TTY policy

Uses std::io::IsTerminalno new dependency. Agents that capture output via pipes (the norm) keep getting JSON automatically; PTY-backed agents can force it via the env var or a flag.

Why

Restores agent-first discoverability for the bare command (the original TypeScript GoDaddy CLI returned JSON discovery on bare invocation; ref DEVEX-695) while making the human terminal experience pleasant — the engine is shared by the godaddy CLI and gdx.

Consumers

No consumer code change is required. There is, however, one runtime behavior change on dependency bump: with the TTY-aware default, interactive-terminal invocations now default to human output where they previously got JSON — across all commands, including the --search and --schema raw-bypass paths (previously hardcoded to JSON). Machine / piped / CI / agent output is unchanged (still JSON), so real-world breakage is low. Prior behavior is fully preserved via explicit --output/--json/--toon/--human or the ${APP_ID}_OUTPUT env (e.g. GODADDY_OUTPUT=json). Whether to instead force always-JSON is an open one-way-door decision for the team to settle before release.

The godaddy CLI adopts the with_root_next_actions hook (and rides the "Admin" default) in a follow-up PR once this releases; gdx is unaffected until it bumps the dependency (it would set with_admin_category("Administration")).

Testing

  • New unit tests for the output-format resolver and env-var derivation (pure, no real TTY needed).
  • New consumer/foundation tests for bare-root human + JSON paths, group/leaf help shaping, and auth categorization (default + override).
  • Full suite green; clippy -D warnings clean; cargo fmt applied.

🤖 Generated with Claude Code

Improves the bare-invocation and help experience for human users without
sacrificing machine-readable output for agents.

- Root discovery: new `CliConfig::with_root_next_actions` hook. On bare
  invocation, human output appends a "Suggested next actions" section;
  machine output emits a discovery envelope ({description, version} +
  next_actions). With no hook configured, behavior is unchanged long-help.

- Curated root/group help: a root help template suppresses clap's duplicate
  command list and the global-options wall (still shown on leaf commands);
  group pages keep their subcommand list but drop the options wall.
  Categories, and the commands within them, are sorted. The engine-injected
  `auth` command is filed under an admin category
  (`CliConfig::with_admin_category`, default "Admin") so it stays
  discoverable; any uncategorized top-level command falls under a generic
  "Commands" section.

- TTY-aware default output format: human on an interactive terminal, JSON
  otherwise (pipes, files, CI, most agents). Precedence: explicit
  --output/--json/--toon/--human > ${APP_ID}_OUTPUT env > TTY policy.
  Uses std::io::IsTerminal (no new dependency).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the “bare invocation” and help UX while preserving agent-friendly machine output by adding (1) an opt-in root discovery hook with next-action suggestions, (2) curated/sorted root & group help templates that suppress clap’s noisy sections, and (3) a TTY-aware default output-format resolver with an ${APP_ID}_OUTPUT override.

Changes:

  • Add CliConfig::with_root_next_actions and render bare-root output as either human help + “Suggested next actions” or a JSON discovery envelope (when explicitly machine output / non-TTY default).
  • Introduce curated help templates for root and group commands; sort categories and commands; ensure the built-in auth command is listed under an admin category.
  • Implement default output format resolution with env + TTY policy; update public exports and adjust tests for the new flag-resolution APIs.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/cli.rs Adds root next-actions hook, curated help behavior, admin categorization for auth, and bare-root discovery rendering.
src/cli/help.rs Adds root/group help templates, sorts help categories/entries, and renders human “Suggested next actions”.
src/flags.rs Adds TTY/env-based default output resolver + env var derivation; adjusts flag extraction APIs and adds unit tests.
src/lib.rs Re-exports new public APIs/types related to root actions and output-format resolution.
tests/consumer_cli.rs Adds contract tests for bare-root discovery (human + JSON), group/leaf help shaping, and next-actions behavior.
tests/foundation.rs Updates raw-arg output-format extraction tests and adds tests for auth help categorization (default + override).
tests/exhaustive_public_api.rs Updates tests for the new global_flags_from_matches(..., default_format) signature.
tests/exhaustive_cli_contract.rs Updates tests for the new global_flags_from_matches(..., default_format) signature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/flags.rs
Comment thread src/flags.rs Outdated
Comment thread src/cli.rs Outdated
Comment thread src/cli.rs
Comment thread src/cli.rs
- resolve_default_output_format: normalize the env override to lowercase
  and ignore blank/invalid values, so a miscased or stray ${APP_ID}_OUTPUT
  can't break all command output.
- bare-root discovery: skip pre_run so the bare command stays discoverable
  even when pre_run would fail, matching the no-hook bare-root path.
- auth categorization: invoke register_auth_help_entry from
  ensure_auth_command so auth is filed under the admin category even when a
  provider is registered after construction (not only during Cli::new).
- tests: rename the resolver test to reflect env-then-TTY precedence; add
  coverage for case-insensitive/invalid env values and post-construction
  auth registration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Comment thread src/cli.rs
@jpage-godaddy jpage-godaddy changed the title feat: agent-first root discovery, curated help, and TTY-aware output feat: agent-first root discovery, curated help, and TTY-aware output (DEVEX-695) Jun 4, 2026
render_root parsed the output format via the infallible OutputFormat::from_str,
which silently coerced an unrecognized explicit --output value (e.g. --output yaml)
to JSON. Normal commands reject it via Middleware::render_envelope with
CliCoreError::InvalidOutputFormat. Validate up front so the bare-root discovery
path is consistent, and add a regression test.
@jgowdy-godaddy
Copy link
Copy Markdown
Collaborator

jgowdy-godaddy commented Jun 4, 2026

Did a deep review pass. Pushed two changes directly to the branch:

  • 01f7dabrender_root was parsing the output format via the infallible OutputFormat::from_str, so an unrecognized explicit --output yaml on the bare-root discovery path silently coerced to JSON instead of erroring like normal commands do (Middleware::render_envelopeInvalidOutputFormat). Added up-front validation + a regression test.
  • 494b027 — deterministic, TTY/env-independent tests for the resolved-default wiring (global_flags_from_matches / extract_output_format fall through to the supplied default when no explicit format is given; explicit --output/--json/--toon/--human still wins), using a non-json default so they guard the value_source == CommandLine gate against regression.

fmt/clippy/full suite/doc-tests/missing-docs all green locally.

One non-blocking item I'd rather discuss than just change:

Back-compat wording vs. the always-JSON decision. The default output format change is TTY-aware for every consumer on dependency bump, and it also now applies to the --search and --schema raw-bypass paths (previously hardcoded JSON). So "No consumer change required to keep current behavior" isn't quite accurate for interactive-terminal usage — a human in a TTY now gets human output where they previously got JSON. Machine/piped/CI/agent paths are unaffected (still JSON), so real-world breakage is low, but I'd reword the back-compat note to be explicit about the interactive-terminal change. This also ties into the open question the PR itself raises ("whether to force always-JSON is left for the team") — worth nailing that down before release since it's a one-way door on the human UX contract.

Everything else looked sound: the value_source == CommandLine gate is the right way to keep --output's default_value("json") from clobbering the TTY policy, the explicit --json arm fixes a latent bug where it was previously ignored, and auth categorization is correctly idempotent across post-construction register_auth_provider.

…precedence

Adds deterministic, TTY/env-independent coverage for the resolved-default
wiring: global_flags_from_matches and extract_output_format must return the
supplied default_format when no explicit format is given, while an explicit
--output/--json/--toon/--human still wins. Uses a non-"json" default so the
assertions distinguish the passed default from a hardcoded fallback, guarding
the value_source == CommandLine gate against regression.
Copy link
Copy Markdown
Collaborator

@jgowdy-godaddy jgowdy-godaddy left a comment

Choose a reason for hiding this comment

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

Approved with comments for consideration

@jpage-godaddy
Copy link
Copy Markdown
Collaborator Author

Reworded the back-compat note in the description: it now states explicitly that interactive-terminal invocations switch from JSON to human on dependency bump (including the --search/--schema bypass paths), while machine/piped/CI/agent output stays JSON, with --output/--json/${APP_ID}_OUTPUT preserving prior behavior.

Your two commits look correct. render_root was relying on a dead Err arm (OutputFormat::from_str is infallible, so an explicit --output yaml silently coerced to JSON) — validating up front against is_valid_output_format is right and matches Middleware::render_envelope. The TTY/env-independent default fall-through tests are a good guard on the value_source == CommandLine gate.

On the always-JSON one-way-door: agreed it should be settled before release. That's a product call for the CLI owner rather than something to decide in this PR — taking it back to them and will follow up here with the decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jpage-godaddy jpage-godaddy merged commit 791b335 into main Jun 5, 2026
2 checks passed
@jpage-godaddy jpage-godaddy deleted the next-actions-root branch June 5, 2026 00:10
jpage-godaddy pushed a commit that referenced this pull request Jun 5, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.3](cli-engine-v0.1.2...cli-engine-v0.1.3)
(2026-06-05)


### Features

* agent-first root discovery, curated help, and TTY-aware output
(DEVEX-695) ([#13](#13))
([791b335](791b335))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.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.

3 participants