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
3 changes: 3 additions & 0 deletions apps/cli-docs/src/fragments/commands/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ sentry cli defaults ca-cert /path/to/ca.pem
# Disable telemetry
sentry cli defaults telemetry off

# Hide Tip: hints in command output
sentry cli defaults tips off

# Clear a single default
sentry cli defaults org --clear

Expand Down
14 changes: 13 additions & 1 deletion apps/cli-docs/src/fragments/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,31 @@ If you previously used the legacy `sentry-cli` and have a `~/.sentryclirc` file,

## Persistent Defaults

Use `sentry cli defaults` to set persistent defaults for organization, project, URL, and telemetry. These are stored in the CLI's local database and apply to all commands.
Use `sentry cli defaults` to set persistent defaults for organization, project, URL, telemetry, and tips. These are stored in the CLI's local database and apply to all commands.

```bash
sentry cli defaults org my-org # Set default organization
sentry cli defaults project my-project # Set default project
sentry cli defaults url https://... # Set Sentry URL (self-hosted)
sentry cli defaults telemetry off # Disable telemetry
sentry cli defaults tips off # Hide Tip: hints in command output
sentry cli defaults # Show all current defaults
sentry cli defaults org --clear # Clear a specific default
```

See [`sentry cli defaults`](./commands/cli/#sentry-cli-defaults) for full usage.

### Disabling tips

Some commands print a `Tip: ...` hint below their output. To hide these, store the preference or set an environment variable:

```bash
sentry cli defaults tips off # persistent
export SENTRY_DISABLE_TIPS=1 # per-session, overrides the stored preference
```

Only the tips are removed. Pagination hints, dry-run and cancellation notices, and the cache-age footer (`cached · 3m ago · use -f to refresh`) are still shown.

## Global Options

These flags are accepted by every command. They are not shown in individual command `--help` output, but are always available.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ sentry cli defaults ca-cert /path/to/ca.pem
# Disable telemetry
sentry cli defaults telemetry off

# Hide Tip: hints in command output
sentry cli defaults tips off

# Clear a single default
sentry cli defaults org --clear

Expand Down
128 changes: 49 additions & 79 deletions packages/cli/src/commands/cli/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,22 @@ import { readCaCertFile } from "../../lib/custom-ca.js";
import { parseCustomHeaders } from "../../lib/custom-headers.js";
import {
clearAllDefaults,
getOnOffPreference,
ON_OFF_PREFERENCES,
type OnOffPreference,
setOnOffPreference,
type DefaultsState,
getAgentSkillsPreference,
getAllDefaults,
getDefaultCaCert,
getDefaultHeaders,
getDefaultOrganization,
getDefaultProject,
getDefaultUrl,
getGraphicsPreference,
getTelemetryPreference,
setAgentSkillsPreference,
setDefaultCaCert,
setDefaultHeaders,
setDefaultOrganization,
setDefaultProject,
setDefaultUrl,
setGraphicsPreference,
setTelemetryPreference,
} from "../../lib/db/defaults.js";
import { ValidationError } from "../../lib/errors.js";
import { formatDefaultsResult } from "../../lib/formatters/human.js";
Expand All @@ -50,6 +48,7 @@ import {
} from "../../lib/mutate-command.js";
import { parseBoolValue } from "../../lib/parse-bool.js";
import { computeTelemetryEffective } from "../../lib/telemetry.js";
import { resetTipsCache } from "../../lib/tips.js";

// ---------------------------------------------------------------------------
// Defaults registry — maps canonical keys to get/set/clear handlers
Expand All @@ -59,9 +58,7 @@ import { computeTelemetryEffective } from "../../lib/telemetry.js";
type DefaultKey =
| "organization"
| "project"
| "telemetry"
| "agent-skills"
| "graphics"
| OnOffPreference
| "url"
| "headers"
| "ca-cert";
Expand All @@ -85,87 +82,57 @@ function validateSlug(value: string, label: string): string {
return trimmed;
}

/** Registry of all supported defaults with their handlers */
const DEFAULTS_REGISTRY: Record<DefaultKey, DefaultHandler> = {
organization: {
get: getDefaultOrganization,
set: (value) => setDefaultOrganization(validateSlug(value, "Organization")),
clear: () => setDefaultOrganization(null),
},
project: {
get: getDefaultProject,
set: (value) => setDefaultProject(validateSlug(value, "Project")),
clear: () => setDefaultProject(null),
},
telemetry: {
get: () => {
const pref = getTelemetryPreference();
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 telemetry value: '${value}'. Use on/off, yes/no, true/false, or 1/0.`,
"telemetry",
);
}
setTelemetryPreference(parsed);
},
clear: () => setTelemetryPreference(null),
},
"agent-skills": {
/** Side effects to run after an on/off preference changes. */
const ON_OFF_CHANGE_HOOKS: Partial<Record<OnOffPreference, () => void>> = {
tips: resetTipsCache,
};

/**
* Build the handler for an on/off preference. Accepts on/off, yes/no,
* true/false, or 1/0 and reports the stored value as "on", "off", or null.
*/
function onOffHandler(name: OnOffPreference): DefaultHandler {
const store = (enabled: boolean | null) => {
setOnOffPreference(name, enabled);
ON_OFF_CHANGE_HOOKS[name]?.();
};
return {
get: () => {
const pref = getAgentSkillsPreference();
if (pref === true) {
return "on";
const pref = getOnOffPreference(name);
if (pref === undefined) {
return null;
}
if (pref === false) {
return "off";
}
return null;
return pref ? "on" : "off";
},
set: (value) => {
const parsed = parseBoolValue(value);
if (parsed === null) {
throw new ValidationError(
`Invalid agent-skills value: '${value}'. Use on/off, yes/no, true/false, or 1/0.`,
"agent-skills",
`Invalid ${name} value: '${value}'. Use on/off, yes/no, true/false, or 1/0.`,
name,
);
}
setAgentSkillsPreference(parsed);
store(parsed);
},
clear: () => setAgentSkillsPreference(null),
clear: () => store(null),
};
}

/** Registry of all supported defaults with their handlers */
const DEFAULTS_REGISTRY: Record<DefaultKey, DefaultHandler> = {
organization: {
get: getDefaultOrganization,
set: (value) => setDefaultOrganization(validateSlug(value, "Organization")),
clear: () => setDefaultOrganization(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),
project: {
get: getDefaultProject,
set: (value) => setDefaultProject(validateSlug(value, "Project")),
clear: () => setDefaultProject(null),
},
...(Object.fromEntries(
ON_OFF_PREFERENCES.map((name) => [name, onOffHandler(name)]),
) as Record<OnOffPreference, DefaultHandler>),
url: {
get: getDefaultUrl,
set: (value) => {
Expand Down Expand Up @@ -281,6 +248,7 @@ export const defaultsCommand = buildCommand({
"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 tips off # Hide Tip: hints in command output\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 @@ -295,6 +263,7 @@ export const defaultsCommand = buildCommand({
"| `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" +
"| `tips` | Show `Tip:` hints in command output (on/off, yes/no, true/false, 1/0). `SENTRY_DISABLE_TIPS` overrides it at runtime |\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 @@ -351,14 +320,15 @@ 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, graphics). Continue?",
`This will clear all defaults (${Object.keys(DEFAULTS_REGISTRY).join(", ")}). Continue?`,
{ type: "confirm" },
);
if (confirmed !== true) {
return { hint: "Cancelled." };
}
}
clearAllDefaults();
resetTipsCache();
yield new CommandOutput({
action: "clear-all" as const,
defaults: getAllDefaults(),
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/src/commands/cli/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ import {
import { CLI_VERSION } from "../../lib/constants.js";
import { getAuthConfig } from "../../lib/db/auth.js";
import {
getAgentSkillsPreference,
setAgentSkillsPreference,
getOnOffPreference,
setOnOffPreference,
} from "../../lib/db/defaults.js";
import { closeDatabase, resolveXdgConfigDir } from "../../lib/db/index.js";
import { setInstallInfo } from "../../lib/db/install-info.js";
Expand Down Expand Up @@ -564,12 +564,12 @@ async function runConfigurationSteps(opts: ConfigStepOptions) {
if (flags["no-agent-skills"]) {
await bestEffort(
"Recording agent skills preference",
() => setAgentSkillsPreference(false),
() => setOnOffPreference("agent-skills", false),
warn,
);
}
const skipAgentSkills =
flags["no-agent-skills"] || getAgentSkillsPreference() === false;
flags["no-agent-skills"] || getOnOffPreference("agent-skills") === false;
if (!skipAgentSkills) {
await bestEffort(
"Agent skills",
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/commands/issue/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import {
type TimeRange,
timeRangeToApiParams,
} from "../../lib/time-range.js";
import { tipsSuppressed } from "../../lib/tips.js";
import {
type SentryIssue,
SentryIssueSchema,
Expand Down Expand Up @@ -1482,7 +1483,7 @@ function formatIssueListHuman(result: IssueListResult): string {
}

// Footer tip (e.g. "Tip: Use 'sentry issue view <ID>' ...")
if (result.footerMode) {
if (result.footerMode && !tipsSuppressed()) {
parts.push(formatListFooter(result.footerMode));
}

Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/lib/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {
setLogLevel,
} from "./logger.js";
import { setArgsContext, setFlagContext, withTracing } from "./telemetry.js";
import { applyTipsPreference } from "./tips.js";

/**
* Parse a string input as a number.
Expand Down Expand Up @@ -819,7 +820,12 @@ export function buildCommand<
// "cached · 3m ago · use -f to refresh" when data was cached.
// Skip bare `return;` paths (e.g. `--web` which opens a browser
// without yielding) — no rendered output should mean no footer.
const finalHint = returned ? appendCacheHint(returned.hint) : undefined;
// `Tip:` text is stripped first when tips are disabled
// (`sentry cli defaults tips off` / SENTRY_DISABLE_TIPS); other
// notices and the cache-age staleness indicator are kept.
const finalHint = returned
? appendCacheHint(applyTipsPreference(returned.hint))
: undefined;
await withTracing("render", "cli.command.render", () => {
writeFinalization(stdout, finalHint, cleanFlags.json, renderer);
});
Expand Down
Loading
Loading