TypeScript SDK
npm i @cavsnode/cavs-sdkNode 18+ (uses global fetch). Ships ESM, CJS and type declarations.
Configure
Section titled “Configure”export CAVS_TOKEN=cavs_ci_...export CAVS_API=https://cavsnode.comexport CAVS_ORG=acme-ai # optional default organizationimport { CAVS } from '@cavsnode/cavs-sdk';
const client = CAVS.fromEnv();// or explicitlyconst client = new CAVS({ token: process.env.CAVS_TOKEN, api: 'https://cavsnode.com', org: 'acme-ai',});Upload
Section titled “Upload”const result = await client.artifacts.upload({ path: './dist/bundle.tar', // a file or a directory project: process.env.CAVS_REPO_ID!, // the repository UUID — see the note below name: 'game-client', kind: 'archive', version: process.env.GITHUB_SHA!, metadata: { branch: process.env.GITHUB_REF_NAME, ci: 'github-actions' },});
console.log(result.reference, result.version);Progress and cancellation
Section titled “Progress and cancellation”const controller = new AbortController();setTimeout(() => controller.abort(), 60_000);
await client.artifacts.upload({ path: './dist', project: repoId, name: 'client', kind: 'archive', version: 'nightly', onProgress: (done, total) => process.stdout.write(`\r${done}/${total}`), signal: controller.signal, idempotencyKey: `nightly-${new Date().toISOString().slice(0, 10)}`,});Download
Section titled “Download”What works against the deployed API
Section titled “What works against the deployed API”| Call | Status |
|---|---|
client.me() |
✓ |
client.search(q) |
✓ |
client.listRepositories(org) |
✓ |
client.artifacts.upload(...) |
✓ (UUID project; dedup stats absent) |
client.artifacts.download(...) |
✗ needs /resolve |
client.artifacts.list(...) |
✓ |
client.artifacts.get(id) / .versions(id) |
✗ needs /artifacts/{id} |
client.usage.get() |
✓ |
client.snapshots.list() / .create() |
✓ |
client.releases.list() / .get(tag) |
✓ |
client.lineage.graph() / .addEdge() |
✓ |
client.resolve(ref) |
✗ not deployed |
client.runs.* |
✗ not deployed |
client.serviceAccounts.* |
✗ not deployed |
The rest of the surface
Section titled “The rest of the surface”// Registryconst artifacts = await client.artifacts.list({ type: 'archive', limit: 20 });
// Usageconst usage = await client.usage.get();
// Releasesconst releases = await client.releases.list(repoId);const release = await client.releases.get(repoId, 'v1.4.0');
// Snapshotsawait client.snapshots.create(repoId, { idempotencyKey: 'monthly-2026-07' });
// Lineageawait client.lineage.addEdge(repoId, { fromType: 'artifact', fromKey: buildOid, toType: 'artifact', toKey: sourceOid, relation: 'derived-from',});const graph = await client.lineage.graph(repoId);Hashing and URI helpers:
import { sha256File, uri, VERSION } from '@cavsnode/cavs-sdk';
const oid = await sha256File('./dist/bundle.tar');const parsed = uri.parse('cavs://acme-ai/game-builds/archive/client:1.2.0');Errors
Section titled “Errors”import { CAVSError, AuthenticationError, AuthorizationError, NotFoundError, ConflictError, RateLimitError, QuotaExceededError, ChecksumError, UploadError, DownloadError, TemporaryServiceError,} from '@cavsnode/cavs-sdk';
try { await client.artifacts.upload({ /* … */ });} catch (err) { if (err instanceof QuotaExceededError) console.error('plan limit hit'); else if (err instanceof RateLimitError) console.error('throttled'); else if (err instanceof CAVSError) console.error('CAVS error:', err.message); else throw err;}Same taxonomy and mapping as the Python SDK, and
the same retry policy: 4 attempts on 429/5xx/network, exponential backoff with
jitter, honouring Retry-After.
Testing
Section titled “Testing”Inject a fetch implementation:
import { CAVS } from '@cavsnode/cavs-sdk';
const client = new CAVS({ token: 'cavs_pat_test', api: 'https://test', fetch: async (input, init) => { const url = String(input); if (url.endsWith('/api/v1/users/me')) { status: 200, headers: { 'content-type': 'application/json' }, }); } return new Response( JSON.stringify({ error: { code: 'not_found', message: 'nope' } }), { status: 404, headers: { 'content-type': 'application/json' } }, ); },});In a release script
Section titled “In a release script”import { CAVS, sha256File } from '@cavsnode/cavs-sdk';import { writeFile } from 'node:fs/promises';
const client = CAVS.fromEnv();const repoId = process.env.CAVS_REPO_ID!;const version = process.env.GITHUB_SHA!;
const result = await client.artifacts.upload({ path: './dist/bundle.tar', project: repoId, name: 'game-client', kind: 'archive', version, metadata: { runner: process.env.RUNNER_OS, ref: process.env.GITHUB_REF }, idempotencyKey: `release:${version}`,});
// Pin the exact bytes for downstream consumers.const oid = await sha256File('./dist/bundle.tar');await writeFile('release.json', JSON.stringify({ version, oid, reference: result.reference }, null, 2));console.log(`published ${result.reference} (oid ${oid})`);- GitHub Actions — this SDK, packaged as a step.
- Python SDK — the same contract, with slug resolution.
- Upload sessions — the underlying choreography.

