Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cli-docs/src/fragments/commands/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@ Endpoints that return binary data — image attachments, minidumps, debug files
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.
When the response is a PNG or JPEG image **and** you're on a graphics-capable terminal, the image is rendered inline instead of dumping raw bytes into your session. Terminals that speak the newer kitty graphics protocol (kitty, WezTerm, Ghostty, recent Konsole) are used in preference to sixel, which remains the fallback for older terminals. Redirecting or piping stdout always keeps the raw bytes. Set `SENTRY_NO_GRAPHICS=1` (or run `sentry cli defaults graphics off`) to disable inline rendering; `SENTRY_NO_SIXEL` is still honored as a deprecated alias.

For full API documentation, see the [Sentry API Reference](https://docs.sentry.io/api/).
3 changes: 0 additions & 3 deletions apps/cli-docs/src/fragments/commands/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ sentry dashboard view 12345
# Auto-refresh every 30 seconds
sentry dashboard view "Backend Performance" --refresh 30

# Render as a sixel image (for terminals that support sixel graphics)
sentry dashboard view "Backend Performance" --sixel

# Open in browser
sentry dashboard view 12345 -w
```
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/lint-rules/prefer-paginate-helper.grit
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ file($name, $body) where {
$name <: not r".*infrastructure\.ts$",
$body <: contains or {
`autoPaginate($fetch, $bound)` as $match where { $bound <: `limit` },
`autoPaginate($fetch, $bound, $cursor)` as $match where { $bound <: `limit` }
`autoPaginate($fetch, $bound, $cursor)` as $match where {
$bound <: `limit`
}
},
register_diagnostic(span=$match, message="Use paginate() from infrastructure.js instead of wiring autoPaginate() with a raw limit. It centralizes the per_page cap and limit+cursor threading (see #1473).")
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ View a dashboard

**Flags:**
- `-w, --web - Open in browser`
- `-s, --sixel - Render the dashboard as a sixel image`
- `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data`
- `-r, --refresh <value> - Auto-refresh interval in seconds (default: 60, min: 10)`
- `-t, --period <value> - Time range: "7d", "2026-07-01..2026-08-01", ">=2026-07-01"`
Expand All @@ -57,9 +56,6 @@ sentry dashboard view 12345
# Auto-refresh every 30 seconds
sentry dashboard view "Backend Performance" --refresh 30

# Render as a sixel image (for terminals that support sixel graphics)
sentry dashboard view "Backend Performance" --sixel

# Open in browser
sentry dashboard view 12345 -w
```
Expand Down
4 changes: 0 additions & 4 deletions packages/cli/script/check-env-coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ const INTERNAL_ENV_VARS = new Map<string, string>([
"SENTRY_SCAN_DISABLE_WORKERS",
"internal scanner performance tuning, not user-facing",
],
[
"SENTRY_DASHBOARD_SIXEL",
"internal sixel debug toggle; users use the --sixel flag",
],
[
"SENTRY_CLI_INTEGRATION_TEST_VERSION_OVERRIDE",
"integration-test-only version override",
Expand Down
46 changes: 29 additions & 17 deletions packages/cli/src/commands/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ import { OutputError, ValidationError } from "../lib/errors.js";
import { filterFields } from "../lib/formatters/json.js";
import { CommandOutput } from "../lib/formatters/output.js";
import { validateEndpoint } from "../lib/input-validation.js";
import { imageBytesToKitty } from "../lib/kitty-image.js";
import { logger } from "../lib/logger.js";
import { getDefaultSdkConfig } from "../lib/sentry-client.js";
import { canRenderSixel, terminalPixelWidth } from "../lib/sixel.js";
import {
canRenderKitty,
canRenderSixel,
terminalPixelWidth,
} from "../lib/sixel.js";
import { imageBytesToSixel } from "../lib/sixel-image.js";

const log = logger.withTag("api");
Expand Down Expand Up @@ -1299,36 +1304,43 @@ export function resolveApiResponseOutput(

/**
* 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.
* as an image (when it's a supported format and the terminal advertises a
* graphics protocol) or warn that raw bytes are being dumped. Newer terminals
* that speak the kitty protocol are preferred; sixel is the fallback.
*
* @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
* @param allowGraphics - Whether inline image rendering is permitted. Pass
* `false` in `--json` mode: the raw bytes still stream out unchanged, but
* injecting a graphics 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 graphics 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
allowGraphics = true
): string | undefined {
if (allowSixel && canRenderSixel()) {
if (allowGraphics) {
// 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;
const contentType = headers.get("content-type");
if (canRenderKitty()) {
const kitty = imageBytesToKitty(body, contentType, maxWidth);
if (kitty) {
return kitty;
}
}
if (canRenderSixel()) {
const sixel = imageBytesToSixel(body, contentType, maxWidth);
if (sixel) {
return sixel;
}
}
}

Expand Down
30 changes: 29 additions & 1 deletion packages/cli/src/commands/cli/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ import {
getDefaultOrganization,
getDefaultProject,
getDefaultUrl,
getGraphicsPreference,
getTelemetryPreference,
setAgentSkillsPreference,
setDefaultCaCert,
setDefaultHeaders,
setDefaultOrganization,
setDefaultProject,
setDefaultUrl,
setGraphicsPreference,
setTelemetryPreference,
} from "../../lib/db/defaults.js";
import { ValidationError } from "../../lib/errors.js";
Expand All @@ -59,6 +61,7 @@ type DefaultKey =
| "project"
| "telemetry"
| "agent-skills"
| "graphics"
| "url"
| "headers"
| "ca-cert";
Expand Down Expand Up @@ -140,6 +143,29 @@ const DEFAULTS_REGISTRY: Record<DefaultKey, DefaultHandler> = {
},
clear: () => setAgentSkillsPreference(null),
},
graphics: {
get: () => {
const pref = getGraphicsPreference();
if (pref === true) {
return "on";
}
if (pref === false) {
return "off";
}
return null;
},
set: (value) => {
const parsed = parseBoolValue(value);
if (parsed === null) {
throw new ValidationError(
`Invalid graphics value: '${value}'. Use on/off, yes/no, true/false, or 1/0.`,
"graphics"
);
}
setGraphicsPreference(parsed);
},
clear: () => setGraphicsPreference(null),
},
url: {
get: getDefaultUrl,
set: (value) => {
Expand Down Expand Up @@ -254,6 +280,7 @@ export const defaultsCommand = buildCommand({
"sentry cli defaults project my-proj # Set default project\n" +
"sentry cli defaults telemetry off # Disable telemetry\n" +
"sentry cli defaults agent-skills off # Stop installing agent skills on upgrade\n" +
"sentry cli defaults graphics off # Disable inline terminal images\n" +
"sentry cli defaults url https://... # Set Sentry URL (self-hosted)\n" +
"sentry cli defaults headers 'X-IAP: t' # Set custom headers (self-hosted)\n" +
"sentry cli defaults ca-cert /path/to/ca.pem # Trust a custom CA certificate\n" +
Expand All @@ -267,6 +294,7 @@ export const defaultsCommand = buildCommand({
"| `project` | Default project slug |\n" +
"| `telemetry` | Telemetry preference (on/off, yes/no, true/false, 1/0) |\n" +
"| `agent-skills` | Install agent skills on setup/upgrade (on/off, yes/no, true/false, 1/0) |\n" +
"| `graphics` | Render images inline on capable terminals (on/off, yes/no, true/false, 1/0) |\n" +
"| `url` | Sentry instance URL (for self-hosted installations) |\n" +
"| `headers` | Custom HTTP headers for self-hosted proxies (semicolon-separated `Name: Value`) |\n" +
"| `ca-cert` | Path to PEM file with custom CA certificates (for corporate proxies) |",
Expand Down Expand Up @@ -323,7 +351,7 @@ export const defaultsCommand = buildCommand({
guardNonInteractive(flags);
if (!isConfirmationBypassed(flags)) {
const confirmed = await log.prompt(
"This will clear all defaults (organization, project, telemetry, URL, headers, ca-cert, agent-skills). Continue?",
"This will clear all defaults (organization, project, telemetry, URL, headers, ca-cert, agent-skills, graphics). Continue?",
{ type: "confirm" }
);
if (confirmed !== true) {
Expand Down
12 changes: 1 addition & 11 deletions packages/cli/src/commands/dashboard/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ type ViewFlags = {
readonly period?: TimeRange;
readonly json: boolean;
readonly fields?: string[];
readonly sixel: boolean;
};

/**
Expand Down Expand Up @@ -108,7 +107,7 @@ function buildViewData(
},
widgetResults: Map<number, WidgetDataResult>,
widgets: DashboardWidget[],
opts: { period: string; url: string; sixel: boolean }
opts: { period: string; url: string }
): DashboardViewData {
return {
id: dashboard.id,
Expand All @@ -118,7 +117,6 @@ function buildViewData(
url: opts.url,
dateCreated: dashboard.dateCreated,
environment: dashboard.environment,
sixel: opts.sixel,
widgets: widgets.map((w, i) => ({
title: w.title,
displayType: w.displayType,
Expand Down Expand Up @@ -190,11 +188,6 @@ export const viewCommand = buildCommand({
brief: "Open in browser",
default: false,
},
sixel: {
kind: "boolean",
brief: "Render the dashboard as a sixel image",
default: false,
},
fresh: FRESH_FLAG,
refresh: {
kind: "parsed",
Expand All @@ -213,7 +206,6 @@ export const viewCommand = buildCommand({
aliases: {
...FRESH_ALIASES,
w: "web",
s: "sixel",
r: "refresh",
t: "period",
},
Expand Down Expand Up @@ -294,7 +286,6 @@ export const viewCommand = buildCommand({
const viewData = buildViewData(dashboard, widgetData, widgets, {
period: formatTimeRangeFlag(timeRange),
url,
sixel: flags.sixel,
});

if (!isFirstRender) {
Expand Down Expand Up @@ -326,7 +317,6 @@ export const viewCommand = buildCommand({
buildViewData(dashboard, widgetData, widgets, {
period: formatTimeRangeFlag(timeRange),
url,
sixel: flags.sixel,
})
);
return { hint: `Dashboard: ${url}` };
Expand Down
38 changes: 38 additions & 0 deletions packages/cli/src/lib/db/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* - `defaults.telemetry` — telemetry preference (`"on"` / `"off"`)
* - `defaults.url` — Sentry instance URL (for self-hosted)
* - `defaults.agent-skills` — agent skill install preference (`"on"` / `"off"`)
* - `defaults.graphics` — inline terminal graphics preference (`"on"` / `"off"`)
*/

import { getDatabase } from "./index.js";
Expand All @@ -19,6 +20,7 @@ const DEFAULTS_URL = "defaults.url";
const DEFAULTS_HEADERS = "defaults.headers";
const DEFAULTS_CA_CERT = "defaults.ca-cert";
const DEFAULTS_AGENT_SKILLS = "defaults.agent-skills";
const DEFAULTS_GRAPHICS = "defaults.graphics";

/** All metadata keys used for defaults (for bulk operations) */
const ALL_DEFAULTS_KEYS = [
Expand All @@ -29,6 +31,7 @@ const ALL_DEFAULTS_KEYS = [
DEFAULTS_HEADERS,
DEFAULTS_CA_CERT,
DEFAULTS_AGENT_SKILLS,
DEFAULTS_GRAPHICS,
];

/** State of all persistent defaults */
Expand All @@ -47,6 +50,8 @@ export type DefaultsState = {
"ca-cert": string | null;
/** Agent skill install preference: "on", "off", or null (= default enabled) */
"agent-skills": "on" | "off" | null;
/** Inline terminal graphics preference: "on", "off", or null (= default enabled) */
graphics: "on" | "off" | null;
Comment thread
cursor[bot] marked this conversation as resolved.
};

/** Parse a raw "on" / "off" metadata value to a typed "on" | "off" | null. */
Expand Down Expand Up @@ -116,6 +121,25 @@ export function getAgentSkillsPreference(): boolean | undefined {
return;
}

/**
* Get the persistent inline-graphics preference.
*
* @returns `true` if explicitly enabled, `false` if explicitly disabled,
* `undefined` if no preference is stored (callers should default to enabled)
*/
export function getGraphicsPreference(): boolean | undefined {
const db = getDatabase();
const m = getMetadata(db, [DEFAULTS_GRAPHICS]);
const val = m.get(DEFAULTS_GRAPHICS);
if (val === "on") {
return true;
}
if (val === "off") {
return false;
}
return;
}

/** Get the default Sentry instance URL, or null if not set. */
export function getDefaultUrl(): string | null {
const db = getDatabase();
Expand Down Expand Up @@ -158,6 +182,7 @@ export function getAllDefaults(): DefaultsState {
headers: m.get(DEFAULTS_HEADERS) ?? null,
"ca-cert": m.get(DEFAULTS_CA_CERT) ?? null,
"agent-skills": parseOnOffValue(m.get(DEFAULTS_AGENT_SKILLS)),
graphics: parseOnOffValue(m.get(DEFAULTS_GRAPHICS)),
};
}

Expand Down Expand Up @@ -211,6 +236,19 @@ export function setAgentSkillsPreference(enabled: boolean | null): void {
}
}

/**
* Set or clear the persistent inline-graphics preference.
* Pass `null` to remove the preference (callers will default to enabled).
*/
export function setGraphicsPreference(enabled: boolean | null): void {
const db = getDatabase();
if (enabled === null) {
clearMetadata(db, [DEFAULTS_GRAPHICS]);
} else {
setMetadata(db, { [DEFAULTS_GRAPHICS]: enabled ? "on" : "off" });
}
}

/** Set or clear the default Sentry instance URL. Pass `null` to clear. */
export function setDefaultUrl(url: string | null): void {
const db = getDatabase();
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/lib/env-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,16 @@ export const ENV_VAR_REGISTRY: readonly EnvVarEntry[] = [
"Force plain text output (no colors or ANSI formatting). Takes precedence over `NO_COLOR`.",
example: "1",
},
{
name: "SENTRY_NO_GRAPHICS",
description:
"Disable inline terminal graphics (kitty and sixel) on terminals that support them; the banner falls back to block art, and image attachments printed by `sentry api` are written as raw bytes instead of rendered inline. You can also set this persistently with `sentry cli defaults graphics off`.",
example: "1",
},
{
name: "SENTRY_NO_SIXEL",
description:
"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.",
"Deprecated alias for `SENTRY_NO_GRAPHICS`. Still honored for backward compatibility.",
example: "1",
},
{
Expand Down
Loading
Loading