Skip to content

Commit e40fca7

Browse files
betegonclaude
andcommitted
merge: integrate main and move normalizeTraceSpan to api/traces module
Resolve conflict from api-client split (#405). Move normalizeTraceSpan and the getDetailedTrace normalization call into src/lib/api/traces.ts. Rename test file to kebab-case to satisfy lint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 parents 9459981 + a6d62d7 commit e40fca7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+8993
-3514
lines changed

AGENTS.md

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,9 @@ mock.module("./some-module", () => ({
628628

629629
### Architecture
630630

631+
<!-- lore:019ce2be-39f1-7ad9-a4c5-4506b62f689c -->
632+
* **api-client.ts split into domain modules under src/lib/api/**: The original monolithic \`src/lib/api-client.ts\` (1,977 lines) was split into 12 focused domain modules under \`src/lib/api/\`: infrastructure.ts (shared helpers, types, raw requests), organizations.ts, projects.ts, teams.ts, repositories.ts, issues.ts, events.ts, traces.ts, logs.ts, seer.ts, trials.ts, users.ts. The original \`api-client.ts\` was converted to a ~100-line barrel re-export file preserving all existing import paths. The \`biome.jsonc\` override for \`noBarrelFile\` already includes \`api-client.ts\`. When adding new API functions, place them in the appropriate domain module under \`src/lib/api/\`, not in the barrel file.
633+
631634
<!-- lore:019cb8ea-c6f0-75d8-bda7-e32b4e217f92 -->
632635
* **CLI telemetry DSN is public write-only — safe to embed in install script**: The CLI's Sentry DSN (\`SENTRY\_CLI\_DSN\` in \`src/lib/constants.ts\`) is a public write-only ingest key already baked into every binary. Safe to hardcode in install scripts. Opt-out: \`SENTRY\_CLI\_NO\_TELEMETRY=1\`.
633636
@@ -643,10 +646,10 @@ mock.module("./some-module", () => ({
643646
<!-- lore:019c972c-9f0f-75cd-9e24-9bdbb1ac03d6 -->
644647
* **Numeric issue ID resolution returns org:undefined despite API success**: Numeric issue ID resolution in \`resolveNumericIssue()\`: (1) try DSN/env/config for org, (2) if found use \`getIssueInOrg(org, id)\` with region routing, (3) else fall back to unscoped \`getIssue(id)\`, (4) extract org from \`issue.permalink\` via \`parseSentryUrl\` as final fallback. \`parseSentryUrl\` handles path-based (\`/organizations/{org}/...\`) and subdomain-style URLs. \`matchSubdomainOrg()\` filters region subdomains by requiring slug length > 2. Self-hosted uses path-based only.
645648
646-
### Decision
649+
<!-- lore:019ce0bb-f35d-7380-b661-8dc56f9938cf -->
650+
* **Seer trial prompt uses middleware layering in bin.ts error handling chain**: The CLI's error recovery middlewares in \`bin.ts\` are layered: \`main() → executeWithAutoAuth() → executeWithSeerTrialPrompt() → runCommand()\`. Seer trial prompts (for \`no\_budget\`/\`not\_enabled\` errors) are caught by the inner wrapper; auth errors bubble up to the outer wrapper. After successful auth login retry, the retry also goes through \`executeWithSeerTrialPrompt\` (not \`runCommand\` directly) so the full middleware chain applies. Trial check API: \`GET /api/0/customers/{org}/\` → \`productTrials\[]\` (prefer \`seerUsers\`, fallback \`seerAutofix\`). Start trial: \`PUT /api/0/customers/{org}/product-trial/\`. The \`/customers/\` endpoint is getsentry SaaS-only; self-hosted 404s gracefully. \`ai\_disabled\` errors are excluded (admin's explicit choice). \`startSeerTrial\` accepts \`category\` from the trial object — don't hardcode it.
647651
648-
<!-- lore:019cc2ef-9be5-722d-bc9f-b07a8197eeed -->
649-
* **All view subcommands should use \<target> \<id> positional pattern**: All \`\* view\` subcommands should follow a consistent \`\<target> \<id>\` positional argument pattern where target is the optional \`org/project\` specifier. During migration, use opportunistic argument swapping with a stderr warning when args are in wrong order. This is an instance of the broader CLI UX auto-correction pattern: safe when input is already invalid, correction is unambiguous, warning goes to stderr. Normalize at command level, keep parsers pure. Model after \`gh\` CLI conventions.
652+
### Decision
650653
651654
<!-- lore:019c99d5-69f2-74eb-8c86-411f8512801d -->
652655
* **Raw markdown output for non-interactive terminals, rendered for TTY**: Markdown-first output pipeline: custom renderer in \`src/lib/formatters/markdown.ts\` walks \`marked\` tokens to produce ANSI-styled output. Commands build CommonMark using helpers (\`mdKvTable()\`, \`mdRow()\`, \`colorTag()\`, \`escapeMarkdownCell()\`, \`safeCodeSpan()\`) and pass through \`renderMarkdown()\`. \`isPlainOutput()\` precedence: \`SENTRY\_PLAIN\_OUTPUT\` > \`NO\_COLOR\` > \`FORCE\_COLOR\` > \`!isTTY\`. \`--json\` always outputs JSON. Colors defined in \`COLORS\` object in \`colors.ts\`. Tests run non-TTY so assertions match raw CommonMark; use \`stripAnsi()\` helper for rendered-mode assertions.
@@ -659,15 +662,9 @@ mock.module("./some-module", () => ({
659662
<!-- lore:019c8ab6-d119-7365-9359-98ecf464b704 -->
660663
* **@sentry/api SDK passes Request object to custom fetch — headers lost on Node.js**: @sentry/api SDK calls \`\_fetch(request)\` with no init object. In \`authenticatedFetch\`, \`init\` is undefined so \`prepareHeaders\` creates empty headers — on Node.js this strips Content-Type (HTTP 415). Fix: fall back to \`input.headers\` when \`init\` is undefined. Use \`unwrapPaginatedResult\` (not \`unwrapResult\`) to access the Response's Link header for pagination. \`per\_page\` is not in SDK types; cast query to pass it at runtime.
661664
662-
<!-- lore:019cc484-f0e1-7016-a851-177fb9ad2cc4 -->
663-
* **AGENTS.md must be excluded from markdown linters**: AGENTS.md is auto-managed by lore and uses \`\*\` list markers and long lines that violate typical remark-lint rules (unordered-list-marker-style, maximum-line-length). When a project uses remark with \`--frail\` (warnings become errors), AGENTS.md will fail CI. Fix: add \`AGENTS.md\` to \`.remarkignore\`. This applies to any lore-managed project with markdown linting.
664-
665665
<!-- lore:019c9e98-7af4-7e25-95f4-fc06f7abf564 -->
666666
* **Bun binary build requires SENTRY\_CLIENT\_ID env var**: The build script (\`script/bundle.ts\`) requires \`SENTRY\_CLIENT\_ID\` environment variable and exits with code 1 if missing. When building locally, use \`bun run --env-file=.env.local build\` or set the env var explicitly. The binary build (\`bun run build\`) also needs it. Without it you get: \`Error: SENTRY\_CLIENT\_ID environment variable is required.\`
667667
668-
<!-- lore:019cc40e-e56e-71e9-bc5d-545f97df732b -->
669-
* **Consola prompt cancel returns truthy Symbol, not false**: When a user cancels a \`consola\` / \`@clack/prompts\` confirmation prompt (Ctrl+C), the return value is \`Symbol(clack:cancel)\`, not \`false\`. Since Symbols are truthy in JavaScript, checking \`!confirmed\` will be \`false\` and the code falls through as if the user confirmed. Fix: use \`confirmed !== true\` (strict equality) instead of \`!confirmed\` to correctly handle cancel, false, and any other non-true values.
670-
671668
<!-- lore:019c9776-e3dd-7632-88b8-358a19506218 -->
672669
* **GitHub immutable releases prevent rolling nightly tag pattern**: getsentry/cli has immutable GitHub releases — assets can't be modified and tags can NEVER be reused. Nightly builds publish to GHCR with versioned tags like \`nightly-0.14.0-dev.1772661724\`, not GitHub Releases or npm. \`fetchManifest()\` throws \`UpgradeError("network\_error")\` for both network failures and non-200 — callers must check message for HTTP 404/403. Craft with no \`preReleaseCommand\` silently skips \`bump-version.sh\` if only target is \`github\`.
673670
@@ -680,9 +677,6 @@ mock.module("./some-module", () => ({
680677
<!-- lore:019c9741-d78e-73b1-87c2-e360ef6c7475 -->
681678
* **useTestConfigDir without isolateProjectRoot causes DSN scanning of repo tree**: \`useTestConfigDir()\` creates temp dirs under \`.test-tmp/\` in the repo tree. Without \`{ isolateProjectRoot: true }\`, \`findProjectRoot\` walks up and finds the repo's \`.git\`, causing DSN detection to scan real source code and trigger network calls against test mocks (timeouts). Always pass \`isolateProjectRoot: true\` when tests exercise \`resolveOrg\`, \`detectDsn\`, or \`findProjectRoot\`.
682679
683-
<!-- lore:019cc303-e397-75b9-9762-6f6ad108f50a -->
684-
* **Zod z.coerce.number() converts null to 0 silently**: Zod gotchas in this codebase: (1) \`z.coerce.number()\` passes input through \`Number()\`, so \`null\` silently becomes \`0\`. Be aware if \`null\` vs \`0\` distinction matters. (2) Zod v4 \`.default({})\` short-circuits — it returns the default value without parsing through inner schema defaults. So \`.object({ enabled: z.boolean().default(true) }).default({})\` returns \`{}\`, not \`{ enabled: true }\`. Fix: provide fully-populated default objects. This affected nested config sections in src/config.ts during the v3→v4 upgrade.
685-
686680
### Pattern
687681
688682
<!-- lore:019c972c-9f11-7c0d-96ce-3f8cc2641175 -->

DEVELOPMENT.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ When creating your Sentry OAuth application:
6767

6868
## Environment Variables
6969

70-
| Variable | Description | Default |
71-
| ------------------ | ------------------------------------ | -------------------- |
72-
| `SENTRY_CLIENT_ID` | Sentry OAuth app client ID | (required) |
73-
| `SENTRY_URL` | Sentry instance URL (for self-hosted)| `https://sentry.io` |
70+
| Variable | Description | Default |
71+
| ------------------ | ----------------------------------------------------- | -------------------- |
72+
| `SENTRY_CLIENT_ID` | Sentry OAuth app client ID | (required) |
73+
| `SENTRY_HOST` | Sentry instance URL (for self-hosted, takes precedence) | `https://sentry.io` |
74+
| `SENTRY_URL` | Alias for `SENTRY_HOST` | `https://sentry.io` |
7475

7576
## Building
7677

biome.jsonc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@
5656
},
5757
{
5858
// db/index.ts exports db connection utilities - not a barrel file but triggers the rule
59-
"includes": ["src/lib/db/index.ts"],
59+
// api-client.ts is a barrel that re-exports from src/lib/api/ domain modules
60+
// to preserve the existing import path for all consumers
61+
"includes": ["src/lib/db/index.ts", "src/lib/api-client.ts"],
6062
"linter": {
6163
"rules": {
6264
"performance": {

docs/src/content/docs/commands/auth.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,18 @@ sentry auth refresh
8484

8585
This is typically handled automatically when tokens expire.
8686

87-
## Configuration
87+
## Credential Storage
8888

89-
Credentials are stored in `~/.sentry/config.json` with restricted file permissions (mode 600).
89+
Credentials are stored in a SQLite database at `~/.sentry/cli.db` with restricted file permissions (mode 600).
9090

91-
**Config structure:**
91+
Use `sentry auth token` to retrieve your current access token, or `sentry auth status` to check authentication state.
9292

93-
```json
94-
{
95-
"auth": {
96-
"token": "...",
97-
"refreshToken": "...",
98-
"expiresAt": "2024-12-31T00:00:00Z"
99-
}
100-
}
101-
```
93+
### Environment Variable Precedence
94+
95+
The CLI checks for auth tokens in the following order, using the first one found:
96+
97+
1. `SENTRY_AUTH_TOKEN` environment variable (legacy)
98+
2. `SENTRY_TOKEN` environment variable
99+
3. The stored token in the SQLite database
100+
101+
When a token comes from an environment variable, the CLI skips expiry checks and automatic refresh.

docs/src/content/docs/configuration.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,22 @@ The Sentry CLI can be configured through environment variables and a local datab
77

88
## Environment Variables
99

10-
### `SENTRY_URL`
10+
### `SENTRY_HOST`
1111

1212
Base URL of your Sentry instance. **Only needed for [self-hosted Sentry](./self-hosted/).** SaaS users (sentry.io) should not set this.
1313

1414
```bash
15-
export SENTRY_URL=https://sentry.example.com
15+
export SENTRY_HOST=https://sentry.example.com
1616
```
1717

1818
When set, all API requests (including OAuth login) are directed to this URL instead of `https://sentry.io`. The CLI also sets this automatically when you pass a self-hosted Sentry URL as a command argument.
1919

20+
`SENTRY_HOST` takes precedence over `SENTRY_URL`. Both work identically — use whichever you prefer.
21+
22+
### `SENTRY_URL`
23+
24+
Alias for `SENTRY_HOST`. If both are set, `SENTRY_HOST` takes precedence.
25+
2026
### `SENTRY_ORG`
2127

2228
Default organization slug. Skips organization auto-detection.
@@ -138,9 +144,11 @@ The `sentry api` command also uses `--verbose` to show full HTTP request/respons
138144

139145
## Credential Storage
140146

141-
Credentials are stored in a SQLite database at `~/.sentry/` (or the path set by `SENTRY_CONFIG_DIR`) with restricted file permissions (mode 600) for security. The database also caches:
147+
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:
142148

143149
- Organization and project defaults
144150
- DSN resolution results
145151
- Region URL mappings
146152
- Project aliases (for monorepo support)
153+
154+
See [Credential Storage](./commands/auth/#credential-storage) in the auth command docs for more details.

docs/src/content/docs/self-hosted.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ title: Self-Hosted Sentry
33
description: Using the Sentry CLI with a self-hosted Sentry instance
44
---
55

6-
The CLI works with self-hosted Sentry instances. Set the `SENTRY_URL` environment variable to point at your instance:
6+
The CLI works with self-hosted Sentry instances. Set the `SENTRY_HOST` (or `SENTRY_URL`) environment variable to point at your instance:
77

88
```bash
9-
export SENTRY_URL=https://sentry.example.com
9+
export SENTRY_HOST=https://sentry.example.com
1010
```
1111

1212
## Authenticating
@@ -27,14 +27,14 @@ The OAuth device flow requires **Sentry 26.1.0 or later** and a public OAuth app
2727
Pass your instance URL and the client ID:
2828

2929
```bash
30-
SENTRY_URL=https://sentry.example.com SENTRY_CLIENT_ID=your-client-id sentry auth login
30+
SENTRY_HOST=https://sentry.example.com SENTRY_CLIENT_ID=your-client-id sentry auth login
3131
```
3232

3333
:::tip
3434
You can export both variables in your shell profile so every CLI invocation picks them up:
3535

3636
```bash
37-
export SENTRY_URL=https://sentry.example.com
37+
export SENTRY_HOST=https://sentry.example.com
3838
export SENTRY_CLIENT_ID=your-client-id
3939
```
4040
:::
@@ -48,7 +48,7 @@ If your instance is on an older version or you prefer not to create an OAuth app
4848
3. Pass it to the CLI:
4949

5050
```bash
51-
SENTRY_URL=https://sentry.example.com sentry auth login --token YOUR_TOKEN
51+
SENTRY_HOST=https://sentry.example.com sentry auth login --token YOUR_TOKEN
5252
```
5353

5454
## After Login
@@ -66,7 +66,8 @@ If you pass a self-hosted Sentry URL as a command argument (e.g., an issue or ev
6666

6767
| Variable | Description |
6868
|----------|-------------|
69-
| `SENTRY_URL` | Base URL of your Sentry instance |
69+
| `SENTRY_HOST` | Base URL of your Sentry instance (takes precedence over `SENTRY_URL`) |
70+
| `SENTRY_URL` | Alias for `SENTRY_HOST` |
7071
| `SENTRY_CLIENT_ID` | Client ID of your public OAuth application |
7172
| `SENTRY_ORG` | Default organization slug |
7273
| `SENTRY_PROJECT` | Default project slug (supports `org/project` format) |

plugins/sentry-cli/skills/sentry-cli/SKILL.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Authenticate with Sentry
4444
**Flags:**
4545
- `--token <value> - Authenticate using an API token instead of OAuth`
4646
- `--timeout <value> - Timeout for OAuth flow in seconds (default: 900) - (default: "900")`
47+
- `--force - Re-authenticate without prompting`
4748

4849
**Examples:**
4950

@@ -667,6 +668,26 @@ View logs associated with a trace
667668
- `--json - Output as JSON`
668669
- `--fields <value> - Comma-separated fields to include in JSON output (dot.notation supported)`
669670

671+
### Trial
672+
673+
Manage product trials
674+
675+
#### `sentry trial list <org>`
676+
677+
List product trials
678+
679+
**Flags:**
680+
- `--json - Output as JSON`
681+
- `--fields <value> - Comma-separated fields to include in JSON output (dot.notation supported)`
682+
683+
#### `sentry trial start <name> <org>`
684+
685+
Start a product trial
686+
687+
**Flags:**
688+
- `--json - Output as JSON`
689+
- `--fields <value> - Comma-separated fields to include in JSON output (dot.notation supported)`
690+
670691
### Init
671692

672693
Initialize Sentry in your project
@@ -679,6 +700,7 @@ Initialize Sentry in your project
679700
- `-y, --yes - Non-interactive mode (accept defaults)`
680701
- `--dry-run - Preview changes without applying them`
681702
- `--features <value>... - Features to enable: errors,tracing,logs,replay,metrics`
703+
- `-t, --team <value> - Team slug to create the project under`
682704

683705
### Issues
684706

@@ -793,6 +815,18 @@ List recent traces in a project
793815
- `--json - Output as JSON`
794816
- `--fields <value> - Comma-separated fields to include in JSON output (dot.notation supported)`
795817

818+
### Trials
819+
820+
List product trials
821+
822+
#### `sentry trials <org>`
823+
824+
List product trials
825+
826+
**Flags:**
827+
- `--json - Output as JSON`
828+
- `--fields <value> - Comma-separated fields to include in JSON output (dot.notation supported)`
829+
796830
### Whoami
797831

798832
Show the currently authenticated user

src/app.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import { teamRoute } from "./commands/team/index.js";
2828
import { listCommand as teamListCommand } from "./commands/team/list.js";
2929
import { traceRoute } from "./commands/trace/index.js";
3030
import { listCommand as traceListCommand } from "./commands/trace/list.js";
31+
import { trialRoute } from "./commands/trial/index.js";
32+
import { listCommand as trialListCommand } from "./commands/trial/list.js";
3133
import { CLI_VERSION } from "./lib/constants.js";
3234
import {
3335
AuthError,
@@ -49,6 +51,7 @@ const PLURAL_TO_SINGULAR: Record<string, string> = {
4951
teams: "team",
5052
logs: "log",
5153
traces: "trace",
54+
trials: "trial",
5255
};
5356

5457
/** Top-level route map containing all CLI commands */
@@ -65,6 +68,7 @@ export const routes = buildRouteMap({
6568
event: eventRoute,
6669
log: logRoute,
6770
trace: traceRoute,
71+
trial: trialRoute,
6872
init: initCommand,
6973
api: apiCommand,
7074
issues: issueListCommand,
@@ -74,6 +78,7 @@ export const routes = buildRouteMap({
7478
teams: teamListCommand,
7579
logs: logListCommand,
7680
traces: traceListCommand,
81+
trials: trialListCommand,
7782
whoami: whoamiCommand,
7883
},
7984
defaultCommand: "help",

0 commit comments

Comments
 (0)