Skip to content

feat: expose CLI as a programmatic library#565

Merged
BYK merged 9 commits intomainfrom
byk/library-export
Mar 27, 2026
Merged

feat: expose CLI as a programmatic library#565
BYK merged 9 commits intomainfrom
byk/library-export

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented Mar 25, 2026

Summary

Exposes the Sentry CLI as a JavaScript/TypeScript library with a typed SDK that auto-generates methods for every CLI command.

import createSentrySDK from "sentry";

const sdk = createSentrySDK({ token: "sntrys_..." });

// Typed methods — use CLI route names directly
const orgs = await sdk.org.list();
const issues = await sdk.issues.list({ org: "acme", project: "frontend", limit: 5 });
const issue = await sdk.issue.view({ issueId: "ACME-123" });
await sdk.issue.explain({ issueId: "ACME-123" });

// Nested commands
await sdk.dashboard.widget.add({ ... });

// Escape hatch for any command
const raw = await sdk.run("api", "/organizations/", "--method", "GET");

44 commands auto-discovered from the route tree. Zero manual config.

API

createSentrySDK(options?) is the single entry point (default export).

Option Type Default Description
token string Auto-detected from env Auth token (falls back to SENTRY_AUTH_TOKEN / SENTRY_TOKEN)
text boolean false Return human-readable text instead of parsed JSON
cwd string process.cwd() Working directory for DSN auto-detection
  • Typed methods bypass Stricli's string dispatch — flags as objects, direct Command.loader() invocation
  • sdk.run(...args) escape hatch routes through Stricli for commands not yet typed or interactive workflows
  • Errors throw SentryError with .exitCode and .stderr

Architecture

Auto-generated SDK (script/generate-sdk.ts)

Build-time codegen walks the route tree via Stricli introspection:

Direct command invocation (src/lib/sdk-invoke.ts)

buildInvoker() resolves commands from the route tree (cached), calls Command.loader() directly with pre-built flag objects. buildRunner() provides the sdk.run() escape hatch via Stricli's run().

Env registry (src/lib/env.ts)

getEnv()/setEnv() replaces all direct process.env reads (~14 files). Library mode creates an isolated env copy — consumer's process.env is never mutated.

CLI extraction (src/cli.ts)

CLI runner extracted from bin.ts. Both the npm bin wrapper and library share the same internals.

Single npm bundle

esbuild entry: src/index.tsdist/index.cjs. Tiny dist/bin.cjs wrapper (~200 bytes) for CLI. Package size stays flat.

Zero-copy return

captureObject duck-type on Writer hands back in-memory objects directly.

Library-safe telemetry

initSentry({ libraryMode: true }) strips global-polluting integrations.

Lazy node:sqlite

Polyfill defers node:sqlite import to first DB access, so require("sentry") doesn't crash.

Tests

  • test/lib/env.test.ts — env registry (5 tests)
  • test/lib/index.test.tscreateSentrySDK + sdk.run() (7 tests)
  • test/lib/sdk.test.ts — typed SDK methods + namespaces (7 tests)
  • test/e2e/library.test.ts — bundled library via Node.js subprocesses (17 tests)

Documentation

  • README "Library Usage" section
  • Full docs page at docs/src/content/docs/library-usage.md

Related

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Dashboard

  • Add pagination and glob filtering to dashboard list by BYK in #560
  • Add a full chart rendering engine for sentry dashboard view that transforms widget data into rich terminal visualizations. by BYK in #555

Init

  • Propagate sentry-trace headers to wizard API calls by betegon in #567
  • Treat bare slug as new project name when not found by BYK in #554

Other

  • (formatters) Colorize SQL in DB span descriptions by BYK in #546
  • (output) Add Zod schema registration to OutputConfig for self-documenting JSON fields by BYK in #582
  • (telemetry) Report unknown commands to Sentry by BYK in #563
  • Expose CLI as a programmatic library by BYK in #565
  • Bidirectional cursor pagination (-c next / -c prev) by BYK in #564
  • Add sentry sourcemap inject and sentry sourcemap upload commands by BYK in #547
  • Native debug ID injection and sourcemap upload by BYK in #543

Bug Fixes 🐛

Dashboard

  • Fix table widget rendering and timeseries bar chart width by BYK in #584
  • Validate display types against all datasets by betegon in #577
  • Auto-clamp widget limit instead of erroring by BYK in #573
  • Default issue dataset table columns to ["issue"] by betegon in #570
  • Scale timeseries bar width to fill chart area by BYK in #562
  • Resolve dashboard by ID/slug in addition to title by BYK in #559

Event

  • Detect SHORT-ID/EVENT-ID format in event view by BYK in #574
  • Auto-fallback to org-wide search when event 404s in project by BYK in #575

Other

  • (api) Show meaningful message for network errors instead of '0 Unknown' by BYK in #572
  • (event-view) Auto-redirect issue short IDs in two-arg form (CLI-MP) by BYK in #558
  • (help) Show help when user passes help as positional arg by BYK in #561
  • (issue) Auto-redirect bare org slug to org-all mode in issue list by BYK in #576
  • (log) Use 30d default period and show newest logs first by sergical in #568
  • Reject @-selectors in parseOrgProjectArg with helpful redirect by BYK in #557

Documentation 📚

  • Add missing command pages for trace, span, sourcemap, repo, trial, schema by sergical in #569

Internal Changes 🔧

Coverage

  • Use informational-patch input instead of sed hack by BYK in #544
  • Make checks informational on release branches by BYK in #541

Event

  • Replace "latest" magic string with @latest sentinel constant by BYK in #583
  • Deduplicate span tree building into shared helper by BYK in #581

Other

  • (api) Collapse stats on issue detail endpoints to save 100-300ms by BYK in #551
  • (ci) Upgrade GitHub Actions to Node 24 runtime by BYK in #542
  • (db) DRY up database layer with shared helpers and lint enforcement by BYK in #550
  • (docs) Polish sidebar, header, focus, and code block UX by sergical in #580
  • (issue-list) Use collapse parameter to skip unused Snuba queries by BYK in #545
  • Bump Bun from 1.3.9 to 1.3.11 by BYK in #552
  • Regenerate skill files by github-actions[bot] in ec1ffe28

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/pr-preview/pr-565/

Built to branch gh-pages at 2026-03-27 01:59 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

Codecov Results 📊

126 passed | Total: 126 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests
Passed Tests
Failed Tests
Skipped Tests

✨ No test changes detected

All tests are passing successfully.

✅ Patch coverage is 100.00%. Project has 1275 uncovered lines.
✅ Project coverage is 95.6%. Comparing base (base) to head (head).

Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    95.52%    95.60%    +0.08%
==========================================
  Files          194       201        +7
  Lines        28620     28983      +363
  Branches         0         0         —
==========================================
+ Hits         27339     27708      +369
- Misses        1281      1275        -6
- Partials         0         0         —

Generated by Codecov Action

@BYK BYK marked this pull request as ready for review March 25, 2026 23:35
@BYK BYK force-pushed the byk/library-export branch from 0cc60e3 to a045c50 Compare March 26, 2026 00:19
@BYK BYK force-pushed the byk/library-export branch from a045c50 to ef662d8 Compare March 26, 2026 00:29
@BYK BYK force-pushed the byk/library-export branch from ef662d8 to 2fde73b Compare March 26, 2026 00:41
@BYK BYK force-pushed the byk/library-export branch from 2fde73b to 0c03fa5 Compare March 26, 2026 11:08
@BYK BYK force-pushed the byk/library-export branch from 64604d3 to 0193b4d Compare March 26, 2026 13:09
@BYK BYK force-pushed the byk/library-export branch from dc79ed4 to 952b9d5 Compare March 26, 2026 13:43
@BYK BYK force-pushed the byk/library-export branch from 2b64c62 to 142d957 Compare March 26, 2026 14:21
@BYK BYK force-pushed the byk/library-export branch from 32e6679 to a54b206 Compare March 26, 2026 23:37
@BYK BYK force-pushed the byk/library-export branch 4 times, most recently from a763436 to 9dab485 Compare March 27, 2026 00:04
@BYK BYK force-pushed the byk/library-export branch 3 times, most recently from e7272ba to b649fa1 Compare March 27, 2026 00:57
@BYK BYK force-pushed the byk/library-export branch from b649fa1 to 8a39aa0 Compare March 27, 2026 01:09
@BYK BYK force-pushed the byk/library-export branch from 8a39aa0 to 26e6ed4 Compare March 27, 2026 01:32
BYK added 8 commits March 27, 2026 01:56
Add a variadic `sentry()` function that runs CLI commands in-process
and returns parsed JSON objects (or raw text). This enables AI coding
agents, build tools, and other JS/TS consumers to use the CLI without
spawning a subprocess.

```typescript
import sentry from "sentry";

const issues = await sentry("issue", "list", "-l", "5");
const orgs = await sentry("org", "list", { token: "sntrys_..." });
```

Options: `token` (auth override), `text` (human output), `cwd` (working dir).
Errors throw `SentryError` with `.exitCode` and `.stderr`.

- **Env registry** (`src/lib/env.ts`): `getEnv()`/`setEnv()` replaces all
  direct `process.env` reads (~14 files ported). Library mode creates an
  isolated env copy — consumer's `process.env` is never mutated.

- **Zero-copy return**: `renderCommandOutput` duck-types a `captureObject`
  method on the Writer to hand back the in-memory object directly, avoiding
  the `JSON.stringify` → buffer → `JSON.parse` round-trip.

- **Single npm bundle**: esbuild entry point changes from `src/bin.ts` to
  `src/index.ts`. A tiny `dist/bin.cjs` wrapper (~200 bytes) calls
  `require("./index.cjs")._cli()`. npm package size stays flat.

- **Library-safe telemetry**: `initSentry({ libraryMode: true })` strips
  all global-polluting integrations (process listeners, HTTP trace headers,
  Function.prototype wrapping). Manual `client.flush()` replaces beforeExit.

- **CLI extraction**: `src/bin.ts` logic moved to `src/cli.ts` (exported
  functions, no top-level execution). `bin.ts` becomes a thin bun compile
  entry point.

- **`SENTRY_OUTPUT_FORMAT=json`**: New env var in `command.ts` forces JSON
  mode without `--json` flag — how the library gets JSON by default.
- Replace process.exit() in OutputError handler with throw + Stricli
  intercept in app.ts (BugBot: kills host process in library mode)
- Wrap withTelemetry in try/catch in sentry() to convert all internal
  errors (AuthError, OutputError, etc.) to SentryError (Seer + BugBot)
- Set process.exitCode=1 in bin wrapper catch handler (BugBot)
Add createSentrySDK() that provides typed methods with named parameters,
bypassing Stricli's string dispatch layer entirely:

  const sdk = createSentrySDK({ token: "sntrys_..." });
  const issues = await sdk.issues.list({ org: "acme", project: "frontend" });
  const org = await sdk.organizations.get("acme");

Architecture:
- sdk-invoke.ts resolves Commands from the route tree (cached) and calls
  handler functions directly with pre-built flag objects — no string
  parsing, no route scanning overhead.
- generate-sdk.ts walks the route tree at build time via introspection,
  extracts flag definitions, and generates typed parameter interfaces +
  method implementations in sdk.generated.ts.
- 7 namespaces: organizations, projects, issues, events, traces, spans,
  teams with list/get methods.

Ref: #566 tracks schema registration on OutputConfig for auto-generated
return types (currently uses a manual type map in the codegen script).
…eful payload

In library mode (sentry() and SDK), OutputError carries data that was
already captured via captureObject before the re-throw. Previously, the
catch blocks in index.ts and sdk-invoke.ts would convert ALL thrown
errors to SentryError, losing the captured data.

Now both catch blocks check capturedResult before error conversion.
If data was captured (e.g., OutputError rendered a 404 response body),
it is returned to the caller instead of throwing. This follows the
'HTTP 404 body' pattern — the data is useful even when the operation
technically 'failed'.

The check is on capturedResult !== undefined rather than instanceof
OutputError, making it future-proof for any error that captures data
before throwing.
Replace hand-written SentrySDK type in bundle.ts TYPE_DECLARATIONS with
types extracted from src/sdk.generated.ts at bundle time. This ensures
the .d.cts file stays in sync with the generated SDK automatically.

The previous hand-written type was missing many parameters (platform,
cursor, sort, period, query, spans) and used Promise<unknown> instead
of the actual return types from the generated methods.

The extraction:
1. Collects all exported param type blocks (XxxParams) verbatim
2. Parses createSDKMethods return shape into SentrySDK type
3. Transforms method implementations into type signatures
4. Strips invoke call bodies, preserves JSDoc comments
17 e2e tests that verify the bundled output works as a library:
- Bundle structure (no shebang, no warning suppression in library)
- Export shape (sentry, createSentrySDK, SentryError, SentryOptions)
- Variadic API (version, env isolation, error wrapping)
- Typed SDK (namespace methods, auth error handling)
- Type declarations (.d.cts content)

All tests run the bundled dist/index.cjs via Node.js subprocesses
to verify real npm package behavior.
The node:sqlite import in node-polyfills.ts was eagerly evaluated at
bundle load time, causing ERR_UNKNOWN_BUILTIN_MODULE on Node.js versions
without node:sqlite support. Now the import is deferred to the
NodeDatabasePolyfill constructor — only loaded when a database is
actually opened, not when the library is require()d.
Rework the typed SDK to auto-discover ALL commands from the Stricli
route tree with zero manual config:

- generate-sdk.ts walks the entire route tree recursively, discovers
  44 commands, generates typed methods using CLI route names as-is
  (sdk.org.list, sdk.dashboard.widget.add)
- Return types derived from __jsonSchema (PR #582) via
  extractSchemaFields() — commands with schemas get typed returns,
  others default to unknown
- Positional params derived from introspection placeholder strings
- createSentrySDK() is now the single public API (default export)
- sdk.run() escape hatch replaces the standalone sentry() function
- SentryError/SentryOptions extracted to sdk-types.ts to break
  circular deps between index.ts and sdk-invoke.ts
@BYK BYK force-pushed the byk/library-export branch from 26e6ed4 to b8428b2 Compare March 27, 2026 01:59
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

@BYK BYK merged commit 154d87b into main Mar 27, 2026
23 checks passed
@BYK BYK deleted the byk/library-export branch March 27, 2026 02:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant