Skip to content

feat(output): output-contract polish — TTY-aware format, detail view, partial-failure exit 7, actionable errors#230

Merged
ktn-jamf merged 16 commits into
mainfrom
worktree-output-contract-polish
May 31, 2026
Merged

feat(output): output-contract polish — TTY-aware format, detail view, partial-failure exit 7, actionable errors#230
ktn-jamf merged 16 commits into
mainfrom
worktree-output-contract-polish

Conversation

@ktn-jamf
Copy link
Copy Markdown
Collaborator

Summary

Polishes the CLI's output and error contract so it behaves like a well-mannered Unix tool: human-friendly in a terminal, machine-clean in a pipe, with actionable failures and consistent exit codes.

User-facing changes

  • TTY-aware default format — interactive terminal defaults to table; piped/redirected/--out-file defaults to json. Explicit -o and config default_output still win.
  • Single-object detail viewget renders a vertical FIELD / VALUE layout in table mode instead of an unreadable one-row table.
  • Color gated to TTY — ANSI never leaks into pipes or files.
  • Semantic --compact — keeps identity + fields common across rows, drops sparse/nested noise.
  • Partial-failure exit code 7 — bulk/backup/generated-delete operations continue on error and exit 7 when some items fail; --allow-partial-failure downgrades to 0.
  • Actionable errors — per-status hint: on stderr and hint/exitCodeName in the JSON error envelope; "did you mean" suggestions for command and flag typos.

Fixes found during live testing

  • Flag suggestions anchor on a shared first letter + length-aware distance, so --fld--field (not --all) and --id produces no misleading hint.
  • Unknown commands now exit 2 (usage) to match the unknown-flag path.
  • Group-parent typos (pro buildings lst) now error with a suggestion and exit 2 instead of silently printing help and exiting 0.

Testing

  • go test ./..., gofumpt, go vet, and make verify-generated all clean.
  • Live-verified against a real Jamf Pro instance: TTY/pipe defaults, detail view, color gating, exit codes, and typo suggestions.
  • Rebased onto current main (1.18.0); coexists with --no-hints.

🤖 Generated with Claude Code

ktn-jamf and others added 14 commits May 31, 2026 10:49
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gate

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve the effective output format in PersistentPreRunE: explicit --output
wins, then config default_output, then auto (terminal -> table, piped -> json).
Disable color whenever stdout is not a terminal so ANSI never leaks into a pipe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A single-object response now renders as a FIELD/VALUE detail layout instead of
an unreadable 1-row table (and fixes Print(map) previously emitting Go's %v map
repr). Arrays, even of length one, still render as tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
--compact now keeps high-signal scalars only: an identity/allowlist of leaf
fields plus any scalar present in >=80% of list rows; single objects keep
scalars except a verbose blocklist. Rare noise fields are dropped (use --select
for specific fields).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t-out

Handwritten bulk operations (group membership, policy enable/disable, MDM
send-command) now return exit code 7 when some items succeed and some fail,
distinguishing partial from total failure. --allow-partial-failure downgrades
to a warning + exit 0. Total failures propagate the underlying error code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When some objects export and some fail, backup now returns exit code 7 instead
of a generic error. --allow-partial-failure downgrades to a warning + exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l failure

Both modern and classic generated bulk-delete loops (--from-file and --group)
now continue past per-item failures, tally results, and return exit code 7 via
a package-local batchDeleteError helper when some succeed and some fail.
Previously they aborted at the first failure. --allow-partial-failure downgrades
to a warning + exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in JSON

Move per-status remediation out of the message and into exitcode.Error.Hint via
a pure httpStatusError helper. main prints a "hint:" line for human output;
the JSON envelope gains hint, exitCodeName, and any structured details.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enable cobra command suggestions (SuggestionsMinimumDistance) and add a
FlagErrorFunc that prints "hint: did you mean --X?" for the nearest known flag,
then classifies the failure as a usage error (exit 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Releases are the single source of truth for version history; point readers
there rather than maintaining a duplicate CHANGELOG.md that would drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…as usage

Two fixes to the actionable-error feature, found during live testing:

- suggestFlag gated only on edit distance < 3, so short typos matched
  unrelated flags by alphabetical tie-break (--fld -> --all, --id -> --wide).
  Now require a shared first letter and cap distance relative to the typo
  length, so --fld -> --field and --id produces no misleading hint.

- Unknown commands returned a plain cobra error (exit 1) while unknown flags
  exited 2. Add ClassifyError to wrap unknown-command errors as usage (exit 2),
  applied in main before formatting so the JSON envelope and exit code agree;
  cobra's "did you mean" suggestion is preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cobra rejects unknown subcommands only on the root command (legacyArgs in
Find), so `pro buildings lst` printed help and exited 0 rather than erroring.
A non-runnable command also short-circuits to help before arg validation, so
an Args validator never runs on a pure group.

guardUnknownSubcommands walks the tree and attaches a RunE to every non-root
group parent: a bare invocation still shows help, while an unknown subcommand
returns a usage error (exit 2) with a "did you mean" suggestion, matching the
root behavior. Guarded parents are annotated so PersistentPreRunE skips auth —
a group parent never calls an API itself.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ktn-jamf ktn-jamf marked this pull request as draft May 31, 2026 15:53
Walks the full command tree and verifies each group parent (279 today) is
guarded: it carries the group-parent annotation and returns a usage exit code
on an unknown subcommand. This covers new command groups automatically — a
future generated or hand-written group that slips past guardUnknownSubcommands
fails this test instead of silently printing help and exiting 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ktn-jamf ktn-jamf marked this pull request as ready for review May 31, 2026 16:07
@ktn-jamf ktn-jamf enabled auto-merge (squash) May 31, 2026 16:07
@ktn-jamf ktn-jamf merged commit ac97f75 into main May 31, 2026
1 check passed
@ktn-jamf ktn-jamf deleted the worktree-output-contract-polish branch May 31, 2026 17:26
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