@supernovae-st/nika-client

July 10, 2026 · View on GitHub

Nika

@supernovae-st/nika-client

TypeScript client for Nika, the workflow language for AI (one file, 4 verbs, one binary), over the engine's HTTP API (nika serve).

Status: two modules, two horizons. @supernovae-st/nika-client/local works TODAY — a typed, zero-dependency driver for the shipped binary (check --json · run --json event stream · the dry-run plan object · test · trace verify). The root module targets the future nika serve HTTP API and stays clearly target-facing until the engine ships it.

import { LocalNika } from '@supernovae-st/nika-client/local';

const nika = new LocalNika();                       // PATH · or NIKA_BIN · or { bin }
const report = await nika.check('flow.nika.yaml');  // typed · report_version-guarded
if (report.clean && !report.cost?.has_unbounded) {
  const run = await nika.runToEnd('flow.nika.yaml', { maxCostUsd: 0.25 });
  console.log(run.ok, run.events.length);
}

Honesty is typed in: cost.min_path_total_usd is a FLOOR and has_unbounded lives beside it; an unpriced model's usd stays null (never coerced to 0); an unknown report_version degrades to warnings, never throws; a parse-fatal file returns a typed PARSE finding on both engine voices (plain-text 0.99.0 and the later JSON form).

  • Zero runtime dependencies (uses native fetch)
  • Full TypeScript types aligned with nika serve OpenAPI 3.1 spec
  • Namespace pattern: nika.jobs.*, nika.workflows.*
  • 6 typed error classes with full hierarchy
  • Automatic retry on 429/5xx with exponential backoff
  • Client-side concurrency limiter (semaphore, default: 24)
  • SSE streaming via AsyncIterable with idle timeout + auto-reconnect
  • Binary artifact download (Uint8Array) + streaming (ReadableStream)
  • Auto-paginating workflow listing
  • AbortSignal support on long-running operations
  • Webhook HMAC-SHA256 verification (async, Web Crypto API)
  • Dual CJS/ESM build
  • Node.js 18+

Install

npm install @supernovae-st/nika-client

Quick start

import { Nika } from '@supernovae-st/nika-client';

const nika = new Nika({
  url: 'http://localhost:3000',
  token: process.env.NIKA_TOKEN!,
});

// Submit + poll until done
const job = await nika.jobs.run('translate.nika.yaml', {
  file: 'ui.json',
  locale: 'fr_FR',
});
console.log(job.status); // 'completed'

Usage

Run a workflow and wait for completion

const job = await nika.jobs.run('pipeline.nika.yaml', { topic: 'AI' });
console.log(job.status);       // 'completed'
console.log(job.exit_code);    // 0
console.log(job.completed_at); // '2026-04-02T10:01:00Z'

Stream events in real time (SSE)

const { job_id } = await nika.jobs.submit('pipeline.nika.yaml', { topic: 'AI' });

for await (const event of nika.jobs.stream(job_id)) {
  console.log(event.type, event.task_id ?? '', event.duration_ms ?? '');
  // started
  // task_start research infer
  // task_complete research 1200
  // completed
}

Run and collect all artifacts

const artifacts = await nika.jobs.runAndCollect('research.nika.yaml', {
  topic: 'workflow engines',
});

console.log(artifacts['report.md']);   // markdown string
console.log(artifacts['data.json']);   // parsed JSON object
// binary artifacts (audio, images) are skipped

Download binary artifacts

const bytes = await nika.jobs.artifactBinary('job-id', 'audio.mp3');
// bytes is Uint8Array

Stream large artifacts without loading into memory

import * as fs from 'node:fs';

const stream = await nika.jobs.artifactStream('job-id', 'dataset.csv');
const writer = fs.createWriteStream('output.csv');
for await (const chunk of stream) {
  writer.write(chunk);
}
writer.end();

Paginate workflow listing

// Auto-pagination (default): fetches all pages transparently
const all = await nika.workflows.list();

// Manual pagination for large lists
const page1 = await nika.workflows.listPage({ limit: 50 });
if (page1.has_more) {
  const last = page1.workflows[page1.workflows.length - 1].name;
  const page2 = await nika.workflows.listPage({ limit: 50, after: last });
}

Cancel a running job with AbortSignal

const controller = new AbortController();
setTimeout(() => controller.abort(), 60_000);

const job = await nika.jobs.run('slow.nika.yaml', {}, {
  signal: controller.signal,
});

Custom fetch (logging middleware)

const nika = new Nika({
  url: 'http://localhost:3000',
  token: process.env.NIKA_TOKEN!,
  fetch: async (url, init) => {
    console.log(`>> ${init?.method ?? 'GET'} ${url}`);
    const res = await fetch(url, init);
    console.log(`<< ${res.status}`);
    return res;
  },
});

Webhook verification

import { Nika } from '@supernovae-st/nika-client';

// Stripe-style HMAC-SHA256 verification (async, uses Web Crypto API)
// Works in Node.js 18+, Deno, Cloudflare Workers, and Bun.
const isValid = await Nika.verifyWebhook(
  rawBody,
  signatureHeader, // 't=1234567890,v1=abc123...'
  webhookSecret,
);

Configuration

new Nika(config)

OptionTypeDefaultDescription
urlstring(required)nika serve URL (http/https)
tokenstring(required)Bearer token (NIKA_SERVE_TOKEN)
timeoutnumber30000HTTP request timeout in ms
retriesnumber2Retries on 429/5xx
concurrencynumber24Max concurrent HTTP requests
pollIntervalnumber2000Initial poll interval in ms
pollTimeoutnumber300000Max poll duration in ms
pollBackoffnumber1.5Poll backoff multiplier
fetchtypeof fetchglobalThis.fetchCustom fetch function
loggerNikaLogger(none)Logger interface (debug, info, warn, error)

API reference

Jobs: nika.jobs.*

MethodReturnsDescription
submit(workflow, inputs?, opts?)RunResponseSubmit workflow, return { job_id, status }
status(jobId)NikaJobGet job status
cancel(jobId)CancelResponseCancel a running job
run(workflow, inputs?, opts?)NikaJobSubmit + poll until terminal state
stream(jobId, opts?)AsyncIterable<NikaEvent>SSE event stream
artifacts(jobId)NikaArtifact[]List job artifacts
artifact(jobId, name)stringDownload artifact as text
artifactJson<T>(jobId, name)TDownload artifact as parsed JSON
artifactBinary(jobId, name)Uint8ArrayDownload artifact as raw bytes
artifactStream(jobId, name)ReadableStream<Uint8Array>Stream artifact (for large files)
runAndCollect(workflow, inputs?, opts?)Record<string, unknown>Run + collect all non-binary artifacts

Workflows: nika.workflows.*

MethodReturnsDescription
list()WorkflowInfo[]List all workflows (auto-paginates)
listPage(opts?)ListWorkflowsResponseList single page (manual pagination)
reload()WorkflowInfo[]Rescan workflows directory
source(name)stringGet raw YAML source

System

MethodReturnsDescription
nika.health()HealthResponseHealth check (no auth required)
Nika.verifyWebhook(body, sig, secret)booleanStatic: verify webhook HMAC-SHA256

Error classes

All SDK errors extend NikaError. Catch it to handle any SDK error:

NikaError (base)
├── NikaAPIError        — HTTP errors (status, body, requestId)
├── NikaConnectionError — Network errors (DNS, TCP, abort)
├── NikaTimeoutError    — Request or poll timeout
└── NikaJobError        — Job failed (exitCode, job object)
    └── NikaJobCancelledError — Job was cancelled
import { NikaError, NikaAPIError, NikaJobError } from '@supernovae-st/nika-client';

try {
  await nika.jobs.run('pipeline.nika.yaml');
} catch (err) {
  if (err instanceof NikaJobError) {
    console.error('Job failed:', err.job.output, 'exit:', err.exitCode);
  } else if (err instanceof NikaAPIError) {
    console.error('HTTP error:', err.status, err.body);
  } else if (err instanceof NikaError) {
    console.error('SDK error:', err.message);
  }
}

SSE event types

TypeFieldsTerminal
startedjob_idNo
task_startjob_id, task_id, verbNo
task_completejob_id, task_id, duration_msNo
task_failedjob_id, task_id, error, duration_msNo
artifact_writtenjob_id, task_id, path, sizeNo
completedjob_id, output?Yes
failedjob_id, error?Yes
cancelledjob_idYes

Terminal events close the SSE stream automatically.

The SDK auto-reconnects on stream drops (up to 3 attempts), using the Last-Event-Id header to resume without losing events. Configure via StreamOptions:

for await (const event of nika.jobs.stream(jobId, {
  maxReconnects: 5,
  reconnectDelay: 2000,
  idleTimeout: 120_000,
})) {
  // events are guaranteed in order, even across reconnects
}

License

Apache-2.0 — the adoption side of the Nika license split, same as the spec. The engine stays AGPL-3.0-or-later; this SDK talks to it over HTTP, and importing this package carries no copyleft obligation for your code.