npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@devtune/client

v0.4.0

Published

Project-scoped JavaScript and TypeScript client for the DevTune public API

Readme

@devtune/client

A project-scoped JavaScript and TypeScript client for DevTune's public API. Every method and request/response type comes from the committed public OpenAPI. The runtime uses fetch and has no dependencies.

Installation

npm install @devtune/client

Use ESM in Node 20+ or a modern fetch-capable runtime. Keep API keys on the server, outside browser bundles. This is an API consumer client; @devtune/ai-traffic is the separate traffic ingestion package.

import { createClient } from '@devtune/client';

const apiKey = process.env.DEVTUNE_API_KEY;
const projectId = process.env.DEVTUNE_PROJECT_ID;
if (!apiKey || !projectId) throw new Error('Set DEVTUNE_API_KEY and DEVTUNE_PROJECT_ID');

const client = createClient({ apiKey, projectId });
const citations = await client.getCitationStats({ query: { windowDays: 30 } });
console.log(citations.data?.coverage, citations.data?.stats);

Create the Bearer project API key in API Keys in the account sidebar. Its project must match projectId. Select the read/write scopes needed for your operations. Existing keys do not automatically gain scopes added later.

OAuth access tokens and async token providers require SDK 0.2.0 or later and are limited to the CLI-supported OAuth endpoints. SDK 0.1.x supports project API keys. The SDK does not perform browser login or persist credentials.

Operations and types

Method names are OpenAPI operationIds, such as listActions, getTrafficSummary, getManagedAgentRun, and listOutcomesLedger. Your editor lists every operation. Pass resource IDs in path, filters in query, and JSON in body. The configured project cannot be overridden per call.

import type { OperationInput, OperationResponse } from '@devtune/client';

const input: OperationInput<'getManagedAgentRun'> = { path: { runId: 'RUN_UUID' } };
const run: OperationResponse<'getManagedAgentRun'> = await client.getManagedAgentRun(input);
console.log(run.data.status);

OperationId, ProjectClient, ClientOptions, RequestOptions, and the raw generated operations, paths, components types are also exported. Types retain the OpenAPI's required and optional properties. The client does not validate every schema at runtime. The API validates UUIDs, ranges and cross-field constraints. Responses are returned as documented, usually { data, meta }. The client does not unwrap data, rename fields, fill in missing metadata, or replace nulls.

A success with no documented response schema, such as changeRetrievalIntent, returns unknown. Narrow its value before using it. Explicitly bodyless successes return void, including HTTP 204/205 or an explicitly empty content map. An unspecified response may contain JSON or be empty. Declared JSON content requires valid nonempty JSON, even when its schema is unspecified. Unexpected success statuses, malformed nonempty JSON and bodies on explicitly bodyless successes raise DevTuneProtocolError.

Response history

For retained answers beyond the latest 20 successful executions, use listVisibilityResponseHistory and getVisibilityResponse. History pages all lifecycle statuses in a fixed 30/90-day started-at window, with exact prompt, platform and status filters. Preserve data.window, data.population, nulls and continuation. Export row counts are not visibility denominators. The CLI adds a bounded responses export workflow using this SDK, with NDJSON completion and page replay recovery.

Timeouts and errors

import { DevTuneHttpError, DevTuneNetworkError, DevTuneAbortError } from '@devtune/client';

try {
  await client.getProjectUsage(undefined, { signal: AbortSignal.timeout(5000) });
} catch (error) {
  if (error instanceof DevTuneHttpError) {
    console.error(error.status, error.body?.error, error.body?.reason, error.retryAfterMs);
  } else if (error instanceof DevTuneNetworkError || error instanceof DevTuneAbortError) {
    console.error(error.message);
  } else {
    throw error;
  }
}

The default total call timeout is 30 seconds, including response body reading, retries and waits. Set timeoutMs on the client or a call, up to 300000 ms. Cancellation throws DevTuneAbortError with reason cancelled or timeout. Invalid success JSON or a response contradicting the documented success contract throws DevTuneProtocolError. An HTTP error exposes its status, operation ID, attempts, sanitized documented error body, retry delay and rate limit values. rateLimit.resetAt is Unix seconds. Raw URLs, request headers, network causes and arbitrary response fields are not retained. The SDK never logs.

GET requests retry network failures and HTTP 408, 429, 500, 502, 503 and 504 up to twice. Set maxRetries from 0 to 5 to change that read budget. Backoff starts at 250 ms and doubles to a maximum of 4 seconds. Retry-After seconds or HTTP dates take precedence over X-RateLimit-Reset Unix seconds, and a retry never happens before that wait. If the required wait exceeds the remaining timeout, the HTTP error is returned immediately so you can schedule a later read.

POST, PATCH, PUT and DELETE are never automatically retried, even with an idempotency key. A lost response or timeout does not prove a write failed. For a managed-agent launch, reuse the saved requestIdempotencyKey with the same agent and inputs if you explicitly retry. Another key can create another charge.

Use baseUrl to override the API root, including /api/v2, and fetch to inject a compatible implementation. Redirects are refused. No authentication discovery, account discovery, write retry opt-in is added.

Recipes

The package includes TypeScript recipes in recipes/. Copy the directory into an ESM project with this package installed, then run with a TypeScript runner:

npx tsx recipes/metrics.ts

Alternatively, install TypeScript and @types/node, compile with npx tsc --module nodenext --target es2022 --outDir build recipes/*.ts, and run node build/metrics.js.

All recipes require DEVTUNE_API_KEY and DEVTUNE_PROJECT_ID. Read recipes make authenticated reads when run; they are not executed against production by tests.

| Recipe | Inputs and behavior | | --- | --- | | actions-report.ts | Reads up to 10 pages of Actions with evidence context. Throws if the report is incomplete. Requires actions.read. | | metrics.ts | Reads 30-day citation stats and AI Traffic, retaining coverage, window and sensor metadata. Requires citations.read and traffic.read. | | managed-agent.ts | Requires DEVTUNE_APPROVED_METERED_RUN=true, DEVTUNE_AGENT_KEY, DEVTUNE_AGENT_INSTRUCTIONS, and a persisted UUID in DEVTUNE_RUN_IDEMPOTENCY_KEY. Launches once and polls at most 20 times within two minutes. Requires agents.run and agents.read, an eligible key creator with project manage permission, available evidence and credits. | | action-results.ts | Requires an existing measurement's DEVTUNE_INTERVENTION_ID. Reads 90-day Results and completion-based impact windows. Requires outcomes.read. |

The managed-agent recipe can spend credits. Run it only for an explicitly intended task. It prints the task ID before waiting. If waiting ends or is cancelled, resume getManagedAgentRun with that ID. Cancelling a client request does not stop the server run. Draft output still needs human approval before publishing.

Windows and coverage

Most measurement operations accept fixed 30 or 90 day rolling windows. Results ledger windows also support 7 and 365 days, while individual impact comparisons have their own returned windows. Use each operation's typed windowDays and the server's returned dates. Do not invent date filters or calculate rates from the latest-20 visibility response list.

stats: null with unavailable citation coverage means no measurement. Zero means a measured zero only when returned coverage supports it. AI Traffic sensor state is project-wide even when totals are filtered to a domain or path. A quiet healthy sensor is no_recent_traffic; inactive needs attention. Snippet coverage does not establish server-side answer fetch or 404 coverage. Keep sensor state and returned evidence with your totals.

Results show observed before/after movement, not proof that an action caused it. Keep unmeasured, still_measuring, null deltas, confidence and evidence fields. Do not substitute zero for a missing conversion or an unfinished window.

Pagination

Every paged list takes cursor and returns data.pagination.hasMore and data.pagination.nextCursor. Page size is limit or pageSize; the two are the same setting. Pass nextCursor back as query.cursor with the other parameters unchanged. A cursor sent with different filters returns 400. Where a list reads a published generation or snapshot, the cursor carries it, so a later page never mixes two publications.

paginate follows the cursor for you. It needs a bound, and it reports whether it read the whole list:

import { paginate } from '@devtune/client';

const report = await paginate(
  client,
  'listActions',
  { query: { status: 'suggested', pageSize: 100 } },
  { maxPages: 10, items: (page) => page.data.actions ?? [] },
);

report.items; // rows from every page read
report.complete; // false when maxPages or maxItems stopped the read
report.nextCursor; // pass as query.cursor to continue an incomplete read

Set maxPages, or maxItems with an items selector; reading stops after the page that reaches either bound. paginate accepts only operations that take a cursor. It throws DevTunePaginationError when a page has no pagination, reports more rows without a cursor, or repeats a cursor, instead of returning a list it cannot vouch for.

offset, page, generation and snapshotId still work on the lists that had them, and are deprecated. Sending one beside cursor returns 400. The visibility response listing returns only the latest 20 successful executions; it has no cursor and cannot retrieve complete history.

Maintainer commands

From the repository root:

pnpm --filter @devtune/client generate
pnpm --filter @devtune/client generate:check
pnpm --filter @devtune/client test
pnpm --filter @devtune/client test:types
pnpm --filter @devtune/client build
pnpm --filter @devtune/client pack --pack-destination /tmp/devtune-client-pack
pnpm --filter @devtune/client test:pack

Generation reads apps/docs/openapi.json locally. openapi-typescript generates schemas, parameters and responses; a small package-owned script generates runtime method/path bindings. Both files include the source hash and use the pinned repo formatter. generate:check fails on any source drift, including documentation changes. Unsupported serialization changes fail generation instead of silently producing unusable methods. The package pins its build-time TypeScript 5 compiler API through an npm alias, independently of the repository's TypeScript 7. Neither compiler ships at runtime.

test:pack packs, checks the file allowlist, installs offline in a temporary directory, runs a JavaScript import, and compiles valid/invalid TypeScript consumers and all recipes against the packed exports. No API credentials are used.

The package is prepared for npm with an explicit file allowlist, ESM exports, declarations and MIT license. Publishing is a separate maintainer action.

Versions and updates

The SDK and CLI have separate npm versions. Check published versions with npm view @devtune/client version. Pin a version in repeatable scripts. See CHANGELOG.md for release changes and the customer documentation.