Skip to content

feat(output): add --table, --quiet, --json=false flags (#12)#13

Merged
facundofarias merged 3 commits into
mainfrom
feat/output-format-flags
May 25, 2026
Merged

feat(output): add --table, --quiet, --json=false flags (#12)#13
facundofarias merged 3 commits into
mainfrom
feat/output-format-flags

Conversation

@facundofarias
Copy link
Copy Markdown
Contributor

@facundofarias facundofarias commented May 25, 2026

Summary

Resolves #12dhq projects list | grep was unusable because piping auto-switched to verbose JSON, and --json=false silently filtered to {}.

Three new global flags:

  • --table — force table output, even when piped. dhq projects list --table | grep prod now does what you'd expect.
  • -q / --quiet — print only the canonical identifier (permalink for projects, identifier elsewhere), one per line, no header. Pairs with xargs: dhq projects list -q | xargs -I {} dhq deployments list -p {}. Supported on projects, servers, server-groups, deployments, and agents list commands.
  • --json=false (also 0, no, off) — explicit opt-out of JSON. Previously set JSONFields=["false"] and silently returned {} because "false" was treated as a field selector. Now it's a real opt-out, useful for shell aliases that default to --json.

Also refactored the duplicated env.JSONMode || !env.IsTTY check across 33 command files into a single Envelope.WantsJSON() method, so future output-mode overrides live in one place.

Test plan

  • go test ./... — passes (added WantsJSON truth-table test, WriteQuiet tests, and GlobalOutputFlags_Registered smoke test)
  • golangci-lint run ./... — clean
  • Manual: dhq projects list --table | grep <name> returns matching rows in human format
  • Manual: dhq projects list -q | xargs -I {} dhq deployments list -p {} works end-to-end
  • Manual: dhq projects list --json=false shows the human table even when piped
  • Manual: bare dhq projects list (TTY) still shows table; piping still defaults to JSON

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added --table and --quiet (-q) flags; --quiet prints only identifiers (one per line).
    • --json accepts opt-out values (false,0,no,off) and comma-separated field selectors.
  • Improvements

    • CLI output mode unified: commands consistently honor --table/--quiet and the refined JSON preference logic, with quiet-mode early exits where applicable.
  • Tests

    • Added tests for JSON flag parsing, global output flags, and quiet/write-quiet behavior.

Review Change Stack

Resolves the "no way to grep dhq projects list" pain point: piping to grep
auto-switches to JSON, the table is gone, and a small permalink can match
many noisy lines.

  --table        force table output even when piped
  --quiet, -q    print only the canonical identifier, one per line, no header
                 (works on projects, servers, server-groups, deployments,
                 agents — i.e. the lists you usually grep)
  --json=false   explicit opt-out of JSON for shell aliases that default to
                 --json; previously it set JSONFields=["false"] and quietly
                 returned {} because "false" was treated as a field selector

Refactored the duplicated `env.JSONMode || !env.IsTTY` check across 37 command
files into a single `Envelope.WantsJSON()` method so the new overrides have
one place to live.

Closes #12

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b1c994d3-5985-47e4-bf32-ace889a28141

📥 Commits

Reviewing files that changed from the base of the PR and between 75be602 and f5fac10.

📒 Files selected for processing (4)
  • internal/commands/assist.go
  • internal/commands/phase3_test.go
  • internal/commands/root.go
  • internal/commands/watch.go

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

This PR centralizes output-mode selection behind Envelope.WantsJSON(), adds persistent --table and --quiet flags and Envelope.WriteQuiet(), updates root flag parsing to normalize --json values, adjusts many commands to call WantsJSON() (and adds quiet early-returns for list commands), and adds tests for the envelope and global flags.

Changes

CLI Output-Mode Control and Quiet-Mode Support

Layer / File(s) Summary
Envelope output-mode contract and methods
internal/output/envelope.go
Envelope adds TableMode and QuietMode; WantsJSON() now respects these overrides and preserves prior JSON/T TY auto behavior; WriteQuiet(items []string) added; WriteData routes via WantsJSON().
Envelope behavior tests
internal/output/envelope_test.go
Adds tests for Envelope.WantsJSON() across combinations and tests for WriteQuiet with populated and empty inputs.
Root command flag parsing and wiring
internal/commands/root.go
Adds persistent --table and --quiet flags, enhances --json parsing (parseJSONFlag), wires parsed values into the Envelope in PersistentPreRunE, and delegates IsJSONMode() to Envelope.WantsJSON().
Global output flags registration test
internal/commands/phase3_test.go
Adds TestParseJSONFlag and TestGlobalOutputFlags_Registered to validate --json parsing and presence of --table/--quiet help and --json=false opt-out text.
WantsJSON gating across many commands
internal/commands/{activity,assist,auto_deploys,build_*,config_files,deploy,deployment_checks,deployments,doctor,env_vars,excluded_files,global_*,insights,language_versions,network_agents,projects,repos,scheduled_deploys,server_groups,servers,signup,ssh_*,status,templates,test_access,url,zones,watch}.go
Replaces `env.JSONMode
Quiet-mode list command implementations
internal/commands/{deployments,projects,network_agents,servers,server_groups}.go
List commands add env.QuietMode early-return paths that output only identifiers via env.WriteQuiet() and return before rendering tables/pagination.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • deployhq/deployhq-cli#2: Both PRs modify internal/commands/deployments.go to change JSON response behavior; this PR centralizes JSON gating via WantsJSON(), which affects when the other PR's JSON payload changes would be emitted.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding three new global output flags (--table, --quiet, --json=false) to the output module, which aligns with the extensive refactoring throughout ~33 command files.
Linked Issues check ✅ Passed The PR fully addresses issue #12 by implementing all requested features: --table flag (force table output when piped), --quiet flag (minimal identifier output), and --json=false support (explicit JSON opt-out). The Envelope.WantsJSON() refactoring centralizes the output-mode logic across all commands, solving the piped output problem.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the three new output flags and refactoring JSON-output gating. No unrelated modifications to unrelated features or files were introduced; changes are confined to output handling and related tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/output-format-flags

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b32d7a7a15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/commands/root.go
Comment on lines 267 to 271
func IsJSONMode() bool {
if cliCtx != nil {
return cliCtx.Envelope.JSONMode || !cliCtx.Envelope.IsTTY
return cliCtx.Envelope.WantsJSON()
}
return flagJSON != ""
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Respect --json=false in IsJSONMode fallback

When command setup fails before cliCtx is initialized (for example, invalid --cwd or config load errors in PersistentPreRunE), IsJSONMode() falls back to flagJSON != "", so --json=false is still treated as JSON mode and main writes a JSON error envelope. That contradicts the new --json=false opt-out semantics and produces machine output in cases where users explicitly asked to disable JSON.

Useful? React with 👍 / 👎.

Pulls the inline --json flag parsing out of PersistentPreRunE into a pure
function so the falsy opt-out (--json=false/0/no/off) and field-selection
paths are testable without spinning up the cobra command tree.

Table covers 14 cases including case-insensitivity and the bug-fix path
(previously --json=false set JSONFields=["false"] and silently returned
{} from every command).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/commands/assist.go (1)

99-99: ⚡ Quick win

Align stream gating with WantsJSON() precedence.

Line 88 still checks !env.JSONMode, so --table/--quiet overrides are not fully reflected in stream vs non-stream behavior. Prefer using !env.WantsJSON() for consistency.

Proposed fix
-			if env.IsTTY && !env.JSONMode && !noStream {
+			if env.IsTTY && !env.WantsJSON() && !noStream {
 				env.Status("")
 				fmt.Fprint(env.Stderr, "✨ ") //nolint:errcheck
 				return ollama.ChatStream(cliCtx.Background(), messages, env.Stderr)
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/commands/assist.go` at line 99, Replace direct checks of the
JSONMode boolean with the WantsJSON() accessor so stream gating respects flag
precedence; specifically, change occurrences like "!env.JSONMode" to
"!env.WantsJSON()" (and any inverted uses) in the assist command flow (the code
paths around the existing if env.WantsJSON() check) so that --table/--quiet
overrides are honored when deciding stream vs non-stream behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/commands/assist.go`:
- Line 99: Replace direct checks of the JSONMode boolean with the WantsJSON()
accessor so stream gating respects flag precedence; specifically, change
occurrences like "!env.JSONMode" to "!env.WantsJSON()" (and any inverted uses)
in the assist command flow (the code paths around the existing if
env.WantsJSON() check) so that --table/--quiet overrides are honored when
deciding stream vs non-stream behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65ab63b2-8fa1-4e10-9b31-bccd47eb3edd

📥 Commits

Reviewing files that changed from the base of the PR and between efc48a9 and b32d7a7.

📒 Files selected for processing (37)
  • internal/commands/activity.go
  • internal/commands/assist.go
  • internal/commands/auto_deploys.go
  • internal/commands/build_cache_files.go
  • internal/commands/build_commands.go
  • internal/commands/build_configs.go
  • internal/commands/build_known_hosts.go
  • internal/commands/build_languages.go
  • internal/commands/config_files.go
  • internal/commands/deploy.go
  • internal/commands/deployment_checks.go
  • internal/commands/deployments.go
  • internal/commands/doctor.go
  • internal/commands/env_vars.go
  • internal/commands/excluded_files.go
  • internal/commands/global_config_files.go
  • internal/commands/global_servers.go
  • internal/commands/insights.go
  • internal/commands/language_versions.go
  • internal/commands/network_agents.go
  • internal/commands/phase3_test.go
  • internal/commands/projects.go
  • internal/commands/repos.go
  • internal/commands/root.go
  • internal/commands/scheduled_deploys.go
  • internal/commands/server_groups.go
  • internal/commands/servers.go
  • internal/commands/signup.go
  • internal/commands/ssh_commands.go
  • internal/commands/ssh_keys.go
  • internal/commands/status.go
  • internal/commands/templates.go
  • internal/commands/test_access.go
  • internal/commands/url.go
  • internal/commands/zones.go
  • internal/output/envelope.go
  • internal/output/envelope_test.go

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/commands/root.go`:
- Around line 265-272: In the switch default branch that currently does return
true, strings.Split(raw, ",") you must split raw by commas then trim and
sanitize each token (use strings.TrimSpace on each element), filter out empty
tokens produced by stray spaces or trailing commas, and return the cleaned
slice; if the cleaned slice is empty (no valid field names) return an error
instead of silently accepting the input. Locate the default case using the raw
variable in the switch and replace the simple strings.Split call with this
trim/filter/validate flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 75cf9080-d4d8-41ff-bde6-13890adbf35d

📥 Commits

Reviewing files that changed from the base of the PR and between b32d7a7 and 75be602.

📒 Files selected for processing (2)
  • internal/commands/phase3_test.go
  • internal/commands/root.go

Comment thread internal/commands/root.go Outdated
…itespace

CodeRabbit review feedback on #13:

1. assist.go and watch.go still read env.JSONMode directly to gate TTY-only
   streaming/TUI paths. Switched to !env.WantsJSON() so --table and --quiet
   participate in the same precedence as the rest of the CLI.

2. parseJSONFlag's field-selection branch did a bare strings.Split, so
   --json="name, permalink" lost the second field to a leading space.
   Trim each token, drop empties, and fall back to bare --json semantics
   when the input is all whitespace/commas.

Extended TestParseJSONFlag with 6 new cases covering the whitespace,
trailing-comma, and all-whitespace edge cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@facundofarias facundofarias merged commit 0387838 into main May 25, 2026
2 of 3 checks passed
@facundofarias facundofarias deleted the feat/output-format-flags branch May 25, 2026 10:08
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.

Option to keep project list minimal even when piped to other commands

1 participant