Skip to content
Draft
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
16 changes: 16 additions & 0 deletions apps/cli-docs/src/fragments/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,22 @@ sentry issue list --verbose
The `sentry api` command also uses `--verbose` to show full HTTP request/response details. When used with `sentry api`, it serves both purposes (debug logging + HTTP output).
:::

### `--no-tips`

Suppress the `Tip: ...` footer hints that some commands print below their output (for example `sentry issue view`). Useful for scripting or when you find the extra guidance noisy.

```bash
sentry issue view CLI-K9 --no-tips
```

You can also disable tips for every command by setting the `SENTRY_DISABLE_TIPS` environment variable:

```bash
export SENTRY_DISABLE_TIPS=1
```

The cache-age footer (`cached · 3m ago · use -f to refresh`) is a staleness indicator, not a tip, and is not affected.

## Credential Storage

We store credentials and caches in a SQLite database (`cli.db`) inside the config directory (`~/.sentry/` by default, overridable via `SENTRY_CONFIG_DIR`). The database file and its WAL side-files are created with restricted permissions (mode 600) so that only the current user can read them. The database also caches:
Expand Down
1 change: 1 addition & 0 deletions packages/cli/script/generate-command-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const GLOBAL_FLAG_NAMES = new Set([
"help",
"helpAll",
"log-level",
"tips",
]);

/** Routes that don't need their own documentation page */
Expand Down
24 changes: 23 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 { tipsSuppressed } from "./tips.js";

/**
* Parse a string input as a number.
Expand Down Expand Up @@ -249,6 +250,21 @@ export const FIELDS_FLAG = {
optional: true as const,
} as const;

/**
* Hidden `--tips` flag injected into every command by {@link buildCommand}.
*
* Defaults to `true`; passing `--no-tips` sets it to `false`, which suppresses
* the "Tip: ..." footer hints. Hidden so it doesn't clutter individual command
* `--help` output — it's documented at the CLI level. Also controllable via the
* `SENTRY_DISABLE_TIPS` environment variable (see `tips.ts`).
*/
export const TIPS_FLAG = {
kind: "boolean" as const,
brief: "Show tip hints in command output (use --no-tips to disable)",
default: true,
hidden: true as const,
} as const;

// ---------------------------------------------------------------------------
// Hidden org/project compat flags (LLM error recovery)
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -435,6 +451,7 @@ const GLOBAL_FLAG_DEFAULTS: Record<string, unknown> = {
verbose: VERBOSE_FLAG,
org: ORG_FLAG,
project: PROJECT_FLAG,
tips: TIPS_FLAG,
};

/** Flags that are always stripped (command never sees them). */
Expand Down Expand Up @@ -794,7 +811,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;
// Drop the command's "Tip: ..." hint when tips are suppressed via
// --no-tips or SENTRY_DISABLE_TIPS. The cache-age footer is a staleness
// indicator, not a tip, so it is still appended.
const suppressTips = tipsSuppressed(flags.tips as boolean | undefined);
const commandHint = suppressTips ? undefined : returned?.hint;
const finalHint = returned ? appendCacheHint(commandHint) : undefined;
await withTracing("render", "cli.command.render", () => {
writeFinalization(stdout, finalHint, cleanFlags.json, renderer);
});
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/lib/global-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export const GLOBAL_FLAGS: readonly GlobalFlagDef[] = [
{ name: "log-level", short: null, kind: "value" },
{ name: "json", short: null, kind: "boolean" },
{ name: "fields", short: null, kind: "value" },
// `--no-tips` suppresses the "Tip: ..." footer hints. Registered as a
// boolean so Stricli recognizes the `--no-tips` negation token.
{ name: "tips", short: null, kind: "boolean" },
// Hidden compat shims: LLMs trained on the older sentry-cli generate
// `--org` and `--project` flags. We silently accept them and map to
// SENTRY_ORG / SENTRY_PROJECT env vars so the resolution chain handles them.
Expand Down
39 changes: 39 additions & 0 deletions packages/cli/src/lib/tips.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Tip-hint suppression.
*
* Commands return a footer `{ hint: "Tip: ..." }` that `buildCommand` renders
* below the output. Users who find these tips noisy can turn them off with the
* global `--no-tips` flag or the `SENTRY_DISABLE_TIPS` environment variable.
*
* The cache-age footer ("cached · 3m ago · use -f to refresh") is a staleness
* indicator rather than a tip, so it is left untouched.
*
* getsentry/cli#1412
*
* @module
*/

import { getEnv } from "./env.js";
import { isTruthyEnv } from "./formatters/plain-detect.js";

/**
* Decide whether command tip hints should be suppressed.
*
* - Explicit `--no-tips` (flag value `false`) always wins.
* - Otherwise, honor `SENTRY_DISABLE_TIPS` using the shared truthy-env
* semantics (`"0"` / `"false"` / `""` are falsy).
* - Default: tips are shown.
*
* @param tipsFlag - Value of the injected global `tips` flag (defaults to
* `true`; `false` when the user passed `--no-tips`).
*/
export function tipsSuppressed(tipsFlag: boolean | undefined): boolean {
if (tipsFlag === false) {
return true;
}
const envVal = getEnv().SENTRY_DISABLE_TIPS;
if (envVal !== undefined) {
return isTruthyEnv(envVal);
}
return false;
}
10 changes: 9 additions & 1 deletion packages/cli/test/lib/global-flags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,15 @@ describe("buildTopLevelFlags", () => {
test("matches the current GLOBAL_FLAGS definition", () => {
const { booleanFlags, valueFlags } = buildTopLevelFlags();
expect([...booleanFlags].sort()).toEqual(
["--verbose", "-v", "--no-verbose", "--json", "--no-json"].sort()
[
"--verbose",
"-v",
"--no-verbose",
"--json",
"--no-json",
"--tips",
"--no-tips",
].sort()
);
expect([...valueFlags].sort()).toEqual(
["--log-level", "--fields", "--org", "--project"].sort()
Expand Down
47 changes: 47 additions & 0 deletions packages/cli/test/lib/tips.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Tip-hint suppression tests (getsentry/cli#1412).
*
* Covers the `--no-tips` flag and `SENTRY_DISABLE_TIPS` env var precedence.
*/

import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { setEnv } from "../../src/lib/env.js";
import { tipsSuppressed } from "../../src/lib/tips.js";

describe("tipsSuppressed", () => {
beforeEach(() => {
setEnv({});
});

afterEach(() => {
setEnv(process.env);
});

test("shows tips by default", () => {
expect(tipsSuppressed(true)).toBe(false);
expect(tipsSuppressed(undefined)).toBe(false);
});

test("suppresses tips when --no-tips passed (flag false)", () => {
expect(tipsSuppressed(false)).toBe(true);
});

test("--no-tips wins even if env would enable tips", () => {
setEnv({ SENTRY_DISABLE_TIPS: "0" });
expect(tipsSuppressed(false)).toBe(true);
});

test("suppresses tips when SENTRY_DISABLE_TIPS is truthy", () => {
for (const val of ["1", "true", "yes"]) {
setEnv({ SENTRY_DISABLE_TIPS: val });
expect(tipsSuppressed(true)).toBe(true);
}
});

test("does not suppress when SENTRY_DISABLE_TIPS is falsy", () => {
for (const val of ["0", "false", ""]) {
setEnv({ SENTRY_DISABLE_TIPS: val });
expect(tipsSuppressed(true)).toBe(false);
}
});
});
Loading