Status: Draft Owner: Probe Labs Last updated: 2025-10-03
Visor is currently distributed primarily as a CLI and GitHub Action. Users increasingly want to:
- Call Visor programmatically from build scripts, test runners, or IDE extensions.
- Orchestrate checks dynamically (e.g., select checks at runtime, react to results, implement custom routing).
- Integrate Visor output into custom dashboards and CI pipelines without shelling out.
Delivering a first‑class SDK with proper TypeScript types and dual ESM/CJS builds unlocks these scenarios while keeping the CLI intact.
- SDK is a façade over the existing Visor engine. No new execution logic.
- No bespoke sandboxing, schedulers, or provider behavior in the SDK.
- The SDK only wires inputs/outputs, surfaces types/events, and forwards options to the core.
- All safety and behavior live in the core engine; SDK remains lightweight and stable.
- Dual module support: native ESM and CommonJS with a single npm package
@probelabs/visor. - First‑class TypeScript: bundled
.d.tswith JSDoc, noanyin public API. - Stable public surface for: loading config, resolving checks, executing checks, streaming events, and collecting results.
- Programmatic access to routing (on_fail/on_success) including retry/backoff and ancestor‑only
goto. - Reasonable defaults that mirror CLI behavior; no breaking changes to CLI or Action.
- Small, tree‑shakable entry for SDK; CLI remains bundled separately.
- Browser/runtime‑agnostic build. Node 18+ only for MVP.
- Public provider/engine SPI for third‑party engines (possible in a later phase).
Top‑level entry: @probelabs/visor/sdk (via package exports).
// ESM
import {
createVisor,
runChecks,
loadConfig,
resolveChecks,
type Visor,
type VisorOptions,
type RunOptions,
type RunSummary,
type VisorConfig,
type CheckResult,
type Issue,
type RoutingTrace,
} from '@probelabs/visor/sdk';
// CJS
const {
createVisor,
runChecks,
loadConfig,
resolveChecks,
} = require('@probelabs/visor/sdk');VisorOptionscwd?: stringlogger?: LoggerLike(info/debug/warn/error)ai?: { provider?: ProviderId; model?: string }parallelism?: numberenv?: NodeJS.ProcessEnv
RunOptionsconfig: VisorConfigorconfigPath: stringchecks?: string[](IDs or['all'])tags?: { include?: string[]; exclude?: string[] }inputs?: Record<string, unknown>(templating context)output?: { format?: 'table'|'markdown'|'json'|'sarif'; groupBy?: 'check'|'file' }
RunSummarychecks: CheckResult[]issues: Issue[](all aggregated)stats: { durationMs: number; successCount: number; failureCount: number }routing?: RoutingTrace[]
Public types will be exported from src/types/* and surfaced by the SDK bundle (.d.ts).
createVisor(opts?: VisorOptions): Visor- Returns an instance exposing:
events:EventEmitter(typed)check:start,check:success,check:failrouting:action(run/goto/retry),routing:looplog(debug/info/warn/error)done
run(opts: RunOptions): Promise<RunSummary>stream(opts: RunOptions): AsyncIterable<VisorEvent>(optional sugar)
- Returns an instance exposing:
loadConfig(path: string): Promise<VisorConfig>resolveChecks(checkIds: string[], config: VisorConfig): string[](dependency expansion)runChecks(runOpts: RunOptions & { visor?: VisorOptions }): Promise<RunSummary>(one‑shot convenience)
- The SDK honors
on_fail/on_successfrom the provided config. RunSummary.routingreturns a concise, machine‑readable trace of routing actions (retry, goto, run_js, goto_js) including loop counters and decision metadata.- Per‑check attempt counters exposed in trace; forEach items are isolated.
- Keep CLI bundle via
@vercel/nccatdist/index.jsforbinand GitHub Action. - Add SDK build via
tsup(preferred) or Rollup:- Inputs:
src/sdk.ts - Outputs:
dist/sdk/index.mjs(ESM),dist/sdk/index.cjs(CJS),dist/sdk/index.d.ts(types) sideEffects: falsefor tree‑shaking- Target: Node
>=18 - Sourcemaps enabled
- Inputs:
package.jsonchanges (illustrative):exports:{ ".": { "import": "./dist/sdk/index.mjs", "require": "./dist/sdk/index.cjs", "types": "./dist/sdk/index.d.ts" }, "./sdk": { "import": "./dist/sdk/index.mjs", "require": "./dist/sdk/index.cjs", "types": "./dist/sdk/index.d.ts" }, "./cli": { "require": "./dist/index.js" } }types:./dist/sdk/index.d.tsfiles: includedist/(SDK + CLI) anddefaults/- Scripts:
build:cli(unchangedncc)build:sdk(tsup for dual build + d.ts)buildruns both
- All exported types live in
src/types/and are re‑exported fromsrc/sdk.ts. - No
anyin public API; use discriminated unions for events and results. - Introduce
VisorErrorwith stablecode(e.g.,CONFIG_NOT_FOUND,ROUTING_MAX_LOOPS_EXCEEDED,CHECK_FAILED). - Semver policy: public types are part of the contract; breaking changes require a major bump.
Out of scope for the SDK. The SDK does not introduce or maintain any sandbox. Safety is enforced by the core engine. The SDK will:
- Pass through any routing/sandbox options to the core.
- Surface resulting events and traces unchanged.
If/when the core provides stricter sandbox controls (e.g., worker‑thread limits), the SDK will forward those knobs without adding behavior.
examples/sdk-basic.mjs— load config, run checks, print table/markdown.examples/sdk-cjs.cjs— CommonJS require + run.examples/sdk-types.ts— TypeScript usage with typed events.examples/sdk-routing.ts— demonstrates routing events and traces.
- Unit: type tests for public API (tsd or TypeScript compile fixtures).
- Integration:
- ESM import works on Node 18+.
- CJS require works on Node 18+.
- Event stream delivers expected sequence for simple config.
- Routing trace emitted for retry/goto; loop caps enforced deterministically.
- Workflows:
- Add a matrix job
sdk-consumeto compile and run the three examples withnpm ci.
- Add a matrix job
- CLI remains at
dist/index.jswith existingbinand Action behavior. - No changes required for current users. New consumers import
@probelabs/visor/sdk. - README will gain an “SDK Usage” section after implementation;
docs/NPM_USAGE.mdwill link to it.
- RFC sign‑off (this doc).
- Create
src/sdk.tsfaçade and re‑export stable types; wire through existing engine (CheckExecutionEngine). - Add
tsupand dual build; updatepackage.jsonexports; keepnccfor CLI. - (Core) Harden JS sandbox in engine as a separate track; SDK simply forwards options.
- Write examples and tests; add CI matrix for ESM/CJS/TS.
- Docs: README section and
docs/sdk.md(guide). Release minor version.
- Do we want a browser/WebWorker build later? If yes, we’ll need to abstract fs/git and remove Node‑only deps from the core.
- Should SDK expose a plugin registration API now (providers/check types), or defer until a stable SPI is designed?
- Should
run()also return an async iterator for live consumption, or keep.eventsas the primary streaming method?
If accepted, implementation will proceed under branch feat/sdk with a draft PR for incremental review.