Skip to content
Merged
10 changes: 10 additions & 0 deletions docs/src/fragments/commands/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,14 @@ sentry api organizations/ --dry-run

When querying the Events API (`/events/` endpoint), valid dataset values are: `spans`, `transactions`, `logs`, `errors`, `discover`.

### Binary responses

Endpoints that return binary data — image attachments, minidumps, debug files — are streamed to stdout as raw bytes, so you can redirect them straight to a file:

```bash
sentry api "projects/my-org/my-project/events/EVENT_ID/attachments/ATTACHMENT_ID/?download=1" > screenshot.png
```

When the response is a PNG or JPEG image **and** you're on a sixel-capable terminal, the image is rendered inline instead of dumping raw bytes into your session. Redirecting or piping stdout always keeps the raw bytes. Set `SENTRY_NO_SIXEL=1` to disable inline rendering.

For full API documentation, see the [Sentry API Reference](https://docs.sentry.io/api/).
2 changes: 2 additions & 0 deletions plugins/sentry-cli/skills/sentry-cli/references/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ sentry api organizations/ --verbose

# Preview the request without sending
sentry api organizations/ --dry-run

sentry api "projects/my-org/my-project/events/EVENT_ID/attachments/ATTACHMENT_ID/?download=1" > screenshot.png
```

All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags.
70 changes: 62 additions & 8 deletions src/commands/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { CommandOutput } from "../lib/formatters/output.js";
import { validateEndpoint } from "../lib/input-validation.js";
import { logger } from "../lib/logger.js";
import { getDefaultSdkConfig } from "../lib/sentry-client.js";
import { canRenderSixel, terminalPixelWidth } from "../lib/sixel.js";
import { imageBytesToSixel } from "../lib/sixel-image.js";

const log = logger.withTag("api");

Expand Down Expand Up @@ -1140,7 +1142,7 @@ function logResponse(response: { status: number; headers: Headers }): void {
*
* - silent: no body (OutputError(null) on error for exit code only)
* - binary error: short status/content-type summary (never dump bytes)
* - binary success: raw Uint8Array (TTY warn only; no hard-refuse)
* - binary success: raw Uint8Array (the func decides TTY rendering/warning)
* - text/JSON: body as-is for the formatter path
*
* Extracted from the command func to keep cognitive complexity under the lint threshold.
Expand Down Expand Up @@ -1176,16 +1178,51 @@ export function resolveApiResponseOutput(
throw new OutputError(response.body);
}

// Binary success: stream raw bytes. TTY warn only — hard-refuse belongs
// with a future --output flag.
if (isBinary && options.isTTY) {
log.warn(
"Binary response written to a TTY — redirect stdout to a file " +
"(e.g. `> file.bin`) to capture raw bytes cleanly."
// Binary success: return raw bytes. The command func decides whether to
// render images inline (sixel) or warn about the raw-byte dump for a TTY.
return response.body;
}

/**
* For a binary response headed to an interactive TTY, either render it inline
* as a sixel image (when it's a supported image format and the terminal
* advertises sixel support) or warn that raw bytes are being dumped.
*
* @param body - The raw response bytes.
* @param headers - Response headers (Content-Type is used as a decode hint).
* @param allowSixel - Whether inline sixel rendering is permitted. Pass `false`
* in `--json` mode: the raw bytes still stream out unchanged, but injecting a
* sixel escape sequence would corrupt machine-readable output. The raw-dump
* warning still fires so the user knows their terminal is about to be flooded.
* @returns A sixel escape string to write instead of the raw bytes, or
* `undefined` to fall through to the raw-byte behavior.
* @internal Exported for testing
*/
export function resolveBinaryTtyOutput(
body: Uint8Array,
headers: Headers,
allowSixel = true
): string | undefined {
if (allowSixel && canRenderSixel()) {
// Cap the rendered width to the terminal's pixel budget so a wide image
// doesn't overflow the columns and garble the session. Falls back to the
// encoder's default when the terminal didn't report a cell width.
const maxWidth = terminalPixelWidth();
const sixel = imageBytesToSixel(
body,
headers.get("content-type"),
maxWidth
);
if (sixel) {
return sixel;
}
Comment thread
jared-outpost[bot] marked this conversation as resolved.
}

return response.body;
log.warn(
"Binary response written to a TTY — redirect stdout to a file " +
"(e.g. `> file.bin`) to capture raw bytes cleanly."
);
return;
}

export const apiCommand = buildCommand({
Expand Down Expand Up @@ -1370,6 +1407,23 @@ export const apiCommand = buildCommand({
if (output === undefined) {
return;
}

// Binary body headed to an interactive TTY. Render supported images inline
// as sixel when the terminal is capable, otherwise warn about the raw dump.
// In --json mode we skip sixel (it would corrupt machine-readable output)
// but still warn — renderCommandOutput dumps the raw bytes either way.
// Redirected/piped stdout skips this entirely (raw bytes flow untouched).
if (output instanceof Uint8Array && this.stdout.isTTY) {
const sixel = resolveBinaryTtyOutput(
output,
response.headers,
!flags.json
);
if (sixel !== undefined) {
return yield new CommandOutput(sixel);
}
}
Comment thread
cursor[bot] marked this conversation as resolved.

// Binary Uint8Array bodies are written raw by renderCommandOutput (no
// formatter, no trailing newline). Text/JSON go through formatApiResponse.
return yield new CommandOutput(output);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/env-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ export const ENV_VAR_REGISTRY: readonly EnvVarEntry[] = [
{
name: "SENTRY_NO_SIXEL",
description:
"Disable the sixel image banner on terminals that support it; the block-art banner is shown instead.",
"Disable sixel graphics on terminals that support it; the banner falls back to block art, and image attachments printed by `sentry api` are written as raw bytes instead of rendered inline.",
example: "1",
},
{
Expand Down
Loading
Loading