diff --git a/cmd/api.go b/cmd/api.go index 952584d..f1a8e7d 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -151,7 +151,7 @@ func runAPI(cmd *cobra.Command, args []string) error { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, jq) + return output.FprintProcess(cmd.OutOrStdout(), result, jq, GetRawFlag(cmd)) } // resolveMethod determines the HTTP method from the flag or defaults. diff --git a/cmd/auth.go b/cmd/auth.go index 884b52b..2cb662d 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -373,7 +373,7 @@ Token resolution order: jq := GetJQFlag(cmd) data, _ := json.Marshal(statusResult) - return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq) + return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq, GetRawFlag(cmd)) }, } @@ -498,7 +498,7 @@ func runAuthSignupVerify(cmd *cobra.Command, args []string) error { // the response so the caller can capture the token manually. saveErr := saveSignupCredentials(result, body, baseURL) - if err := output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)); err != nil { + if err := output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)); err != nil { return err } if saveErr != nil { @@ -597,7 +597,7 @@ func runSignupRequest(cmd *cobra.Command, path string) error { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } // loadStoredServiceAccountToken reads the saved sa_live_ token from diff --git a/cmd/domains_add.go b/cmd/domains_add.go index 8a8371f..02b6162 100644 --- a/cmd/domains_add.go +++ b/cmd/domains_add.go @@ -74,5 +74,5 @@ func runDomainsAdd(cmd *cobra.Command, args []string) error { if err != nil { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } diff --git a/cmd/domains_authenticate.go b/cmd/domains_authenticate.go index 5c1cdd1..55cc8c9 100644 --- a/cmd/domains_authenticate.go +++ b/cmd/domains_authenticate.go @@ -400,7 +400,7 @@ func runDomainsVerify(cmd *cobra.Command, args []string) error { } if GetJQFlag(cmd) != "" { - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } return printDomainVerifyResult(cmd, result, dom.Name) diff --git a/cmd/domains_from_addresses.go b/cmd/domains_from_addresses.go index a56c5e1..bd0a1bb 100644 --- a/cmd/domains_from_addresses.go +++ b/cmd/domains_from_addresses.go @@ -141,7 +141,7 @@ func runFromAddressesList(cmd *cobra.Command, args []string) error { if err != nil { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } func runFromAddressesAdd(cmd *cobra.Command, args []string) error { @@ -209,7 +209,7 @@ func runFromAddressesAdd(cmd *cobra.Command, args []string) error { if err != nil { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } func runFromAddressesUpdate(cmd *cobra.Command, args []string) error { @@ -280,7 +280,7 @@ func runFromAddressesUpdate(cmd *cobra.Command, args []string) error { if err != nil { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } func runFromAddressesDelete(cmd *cobra.Command, args []string) error { diff --git a/cmd/domains_get.go b/cmd/domains_get.go index 3e47df1..0e8276b 100644 --- a/cmd/domains_get.go +++ b/cmd/domains_get.go @@ -60,5 +60,5 @@ func runDomainsGet(cmd *cobra.Command, args []string) error { if err != nil { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } diff --git a/cmd/domains_link_tracking.go b/cmd/domains_link_tracking.go index 89fe49d..a441957 100644 --- a/cmd/domains_link_tracking.go +++ b/cmd/domains_link_tracking.go @@ -148,7 +148,7 @@ func runLinkTrackingConfigure(cmd *cobra.Command, args []string) error { if err != nil { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } func runLinkTrackingVerify(cmd *cobra.Command, args []string) error { @@ -188,7 +188,7 @@ func runLinkTrackingVerify(cmd *cobra.Command, args []string) error { } if GetJQFlag(cmd) != "" { - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } return printDNSCheckResult(cmd, result, dom.Name, "link_tracking", "cio domains link_tracking verify") diff --git a/cmd/domains_list.go b/cmd/domains_list.go index f814c70..4c307c6 100644 --- a/cmd/domains_list.go +++ b/cmd/domains_list.go @@ -52,5 +52,5 @@ func runDomainsList(cmd *cobra.Command, args []string) error { if err != nil { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), result, GetJQFlag(cmd), GetRawFlag(cmd)) } diff --git a/cmd/helpers.go b/cmd/helpers.go index 46d7186..ff68457 100644 --- a/cmd/helpers.go +++ b/cmd/helpers.go @@ -15,10 +15,11 @@ import ( // doPageAll runs auto-pagination and writes NDJSON to stdout. func doPageAll(cmd *cobra.Command, c *client.Client, path string, params map[string]string, startPage, limit int) error { jq := GetJQFlag(cmd) + raw := GetRawFlag(cmd) var w io.Writer = cmd.OutOrStdout() - if jq != "" { - w = &filterWriter{w: cmd.OutOrStdout(), jq: jq} + if jq != "" || raw { + w = &filterWriter{w: cmd.OutOrStdout(), jq: jq, raw: raw} } err := c.PageAll(client.PageAllConfig{ @@ -36,10 +37,11 @@ func doPageAll(cmd *cobra.Command, c *client.Client, path string, params map[str return nil } -// filterWriter wraps an io.Writer and applies --jq to each line written. +// filterWriter wraps an io.Writer and applies --jq / --raw-output to each line written. type filterWriter struct { - w io.Writer - jq string + w io.Writer + jq string + raw bool } func (fw *filterWriter) Write(p []byte) (int, error) { @@ -47,7 +49,7 @@ func (fw *filterWriter) Write(p []byte) (int, error) { if len(trimmed) == 0 { return len(p), nil } - if err := output.FprintProcess(fw.w, json.RawMessage(trimmed), fw.jq); err != nil { + if err := output.FprintProcess(fw.w, json.RawMessage(trimmed), fw.jq, fw.raw); err != nil { return 0, err } return len(p), nil diff --git a/cmd/profile.go b/cmd/profile.go index 7750716..15bb9e5 100644 --- a/cmd/profile.go +++ b/cmd/profile.go @@ -44,7 +44,7 @@ var profileListCmd = &cobra.Command{ profiles = []client.ProfileInfo{} } data, _ := json.Marshal(map[string]any{"profiles": profiles}) - return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), GetJQFlag(cmd)) + return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), GetJQFlag(cmd), GetRawFlag(cmd)) }, } diff --git a/cmd/root.go b/cmd/root.go index 29344f7..105a1a1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -68,6 +68,7 @@ func init() { flags.String("json", "", "Raw JSON request body, @filename to read from a file, or - to read from stdin") flags.String("params", "", "Query parameters as JSON, converted to query string for GET") flags.String("jq", "", "jq expression filter (via gojq)") + flags.BoolP("raw-output", "r", false, "Print string results unquoted, like jq -r (no external jq needed)") flags.Bool("dry-run", false, "Validate and print request, don't execute") flags.Bool("read-only", false, "Request a read-only session (scope=read_only); only GET requests are permitted") flags.StringSlice("scope", nil, "Additional OAuth scope(s) to request during token exchange") @@ -227,6 +228,12 @@ func GetJQFlag(cmd *cobra.Command) string { return jq } +// GetRawFlag returns the --raw-output flag value. +func GetRawFlag(cmd *cobra.Command) bool { + raw, _ := cmd.Flags().GetBool("raw-output") + return raw +} + // GetPaginationFlags returns the --page, --limit, and --page-all flag values. func GetPaginationFlags(cmd *cobra.Command) (page, limit int, pageAll bool) { page, _ = cmd.Flags().GetInt("page") diff --git a/cmd/schema.go b/cmd/schema.go index 9c311b8..ad51cca 100644 --- a/cmd/schema.go +++ b/cmd/schema.go @@ -336,5 +336,5 @@ func schemaOutput(cmd *cobra.Command, v any) error { return err } jq := GetJQFlag(cmd) - return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq) + return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq, GetRawFlag(cmd)) } diff --git a/cmd/send.go b/cmd/send.go index 3f4664f..62d2173 100644 --- a/cmd/send.go +++ b/cmd/send.go @@ -330,7 +330,7 @@ func runTrackSend(cmd *cobra.Command, sendPath string, body json.RawMessage) err watch, _ := cmd.Flags().GetBool("watch") if !watch { - return output.FprintProcess(cmd.OutOrStdout(), result, jq) + return output.FprintProcess(cmd.OutOrStdout(), result, jq, GetRawFlag(cmd)) } // Extract the delivery_id so we can poll its status. @@ -416,7 +416,7 @@ func watchDelivery(cmd *cobra.Command, envID, deliveryID string) error { } if terminal, state := isTerminalDelivery(result); terminal { fmt.Fprintf(stderr, " email %s!\n", state) - return output.FprintProcess(cmd.OutOrStdout(), result, jq) + return output.FprintProcess(cmd.OutOrStdout(), result, jq, GetRawFlag(cmd)) } fmt.Fprint(stderr, ".") } diff --git a/cmd/skills.go b/cmd/skills.go index 81983de..765922c 100644 --- a/cmd/skills.go +++ b/cmd/skills.go @@ -226,5 +226,5 @@ func skillsOutput(cmd *cobra.Command, v any) error { return err } jq := GetJQFlag(cmd) - return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq) + return output.FprintProcess(cmd.OutOrStdout(), json.RawMessage(data), jq, GetRawFlag(cmd)) } diff --git a/cmd/transactional.go b/cmd/transactional.go index 3ba03da..4ea299b 100644 --- a/cmd/transactional.go +++ b/cmd/transactional.go @@ -280,5 +280,5 @@ func runTransactionalList(cmd *cobra.Command, args []string) error { return handleAPIError(err) } - return output.FprintProcess(cmd.OutOrStdout(), result, jq) + return output.FprintProcess(cmd.OutOrStdout(), result, jq, GetRawFlag(cmd)) } diff --git a/internal/output/json.go b/internal/output/json.go index 7fee3ec..aa774b1 100644 --- a/internal/output/json.go +++ b/internal/output/json.go @@ -36,3 +36,23 @@ func FprintNDJSON(w io.Writer, items []json.RawMessage) error { } return nil } + +// FprintRaw writes each item the way `jq -r` does: a JSON string is written as +// its raw, unquoted value; anything else (object, array, number, bool, null) is +// written as its compact JSON. One item per line. This lets callers extract a +// scalar (e.g. a compiled HTML body) without the external `jq -r` round-trip. +func FprintRaw(w io.Writer, items []json.RawMessage) error { + for _, item := range items { + var s string + if err := json.Unmarshal(item, &s); err == nil { + if _, err := fmt.Fprintln(w, s); err != nil { + return err + } + continue + } + if _, err := fmt.Fprintf(w, "%s\n", item); err != nil { + return err + } + } + return nil +} diff --git a/internal/output/process.go b/internal/output/process.go index 1d64d29..9dd0503 100644 --- a/internal/output/process.go +++ b/internal/output/process.go @@ -5,15 +5,22 @@ import ( "io" ) -// FprintProcess applies --jq filtering to data and prints to w. -func FprintProcess(w io.Writer, data json.RawMessage, jqExpr string) error { +// FprintProcess applies --jq filtering to data and prints to w. When raw is +// true, string results are printed unquoted (like `jq -r`). +func FprintProcess(w io.Writer, data json.RawMessage, jqExpr string, raw bool) error { if jqExpr != "" { results, err := ApplyJQ(data, jqExpr) if err != nil { return err } + if raw { + return FprintRaw(w, results) + } return FprintNDJSON(w, results) } + if raw { + return FprintRaw(w, []json.RawMessage{data}) + } return FprintJSON(w, json.RawMessage(data)) } diff --git a/internal/output/raw_test.go b/internal/output/raw_test.go new file mode 100644 index 0000000..42c7e90 --- /dev/null +++ b/internal/output/raw_test.go @@ -0,0 +1,57 @@ +package output + +import ( + "bytes" + "encoding/json" + "testing" +) + +func TestFprintProcess_Raw(t *testing.T) { + cases := []struct { + name string + data string + jq string + raw bool + want string + }{ + { + name: "raw string result is unquoted", + data: `{"html":"