diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b76491..6a9c9ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- `--output json` is now a pure serialisation flag; it no longer forces non-interactive mode. Interactive pickers and prompts (e.g. `grant request get -o json` with no ID, `grant request submit -o json` without `--target`/`--role`) work in a TTY, writing prompts to stderr and JSON to stdout. + ### Added - `grant request` command group for managing access requests through the approval workflow diff --git a/CLAUDE.md b/CLAUDE.md index 3363e78..d3143fe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -98,7 +98,7 @@ Custom `SCAAccessService` follows SDK conventions: ## JSON Output - `--output` / `-o` persistent flag on root command: `text` (default) or `json` -- Validated in `PersistentPreRunE`; JSON mode forces `IsTerminalFunc` to return false (non-interactive) +- Validated in `PersistentPreRunE`. `--output json` is a pure serialisation flag; it does not affect interactivity — interactive prompts still run in a TTY and write to stderr while JSON goes to stdout - `cmd/output.go` — `outputFormat` var, `isJSONOutput()`, `writeJSON(w, data)` - `cmd/output_types.go` — JSON structs: `cloudElevationOutput`, `groupElevationJSON`, `sessionOutput`, `statusOutput`, `revocationOutput`, `favoriteOutput`, `awsCredentialOutput`, `accessRequestOutput`, `accessRequestListOutput` - All commands support JSON: root elevation, `env`, `status`, `revoke`, `favorites list`, `request list`, `request get`, `request submit`, `request cancel`, `request approve`, `request reject` diff --git a/cmd/request_picker.go b/cmd/request_picker.go index f87ad85..3307f0b 100644 --- a/cmd/request_picker.go +++ b/cmd/request_picker.go @@ -32,9 +32,6 @@ var resolveRequestIDFn = resolveRequestIDInteractive // shows the interactive picker, returning the chosen request ID. func resolveRequestIDInteractive(ctx context.Context, svc accessRequestService, scope pickerScope) (string, error) { if !ui.IsInteractive() { - if isJSONOutput() { - return "", errors.New("request ID is required with --output json; run `grant request list --output json` to find it") - } return "", fmt.Errorf("%w; pass the request ID as a positional argument (run `grant request list` to find it)", ui.ErrNotInteractive) } diff --git a/cmd/request_picker_test.go b/cmd/request_picker_test.go index d17bf05..c04ebdb 100644 --- a/cmd/request_picker_test.go +++ b/cmd/request_picker_test.go @@ -45,28 +45,6 @@ func TestResolveRequestIDInteractive_NonInteractive(t *testing.T) { } } -func TestResolveRequestIDInteractive_JSONMode(t *testing.T) { - withInteractiveTTY(t, false) - orig := outputFormat - outputFormat = "json" - t.Cleanup(func() { outputFormat = orig }) - - svc := &capturingMockAccessRequestService{} - _, err := resolveRequestIDInteractive(t.Context(), svc, pickerScope{emptyMsg: "access requests"}) - if err == nil { - t.Fatal("expected error") - } - if errors.Is(err, ui.ErrNotInteractive) { - t.Errorf("JSON mode error should not wrap ErrNotInteractive") - } - if !strings.Contains(err.Error(), "--output json") { - t.Errorf("expected --output json hint, got %v", err) - } - if strings.Contains(err.Error(), "requires a terminal") { - t.Errorf("JSON mode error should not mention terminal: %v", err) - } -} - func TestResolveRequestIDInteractive_EmptyList(t *testing.T) { withInteractiveTTY(t, true) diff --git a/cmd/root.go b/cmd/root.go index a063e60..1a80e7c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -101,9 +101,6 @@ Examples: if outputFormat != "text" && outputFormat != "json" { return fmt.Errorf("invalid output format %q: must be one of: text, json", outputFormat) } - if isJSONOutput() { - ui.IsTerminalFunc = func(fd uintptr) bool { return false } - } return nil }, RunE: runFn,