Skip to content

Quick Start

View as Markdown

This guide gets you from installation to your first typed LLM call.

  • Node.js 18 or later
  • Zod 4 (Nuabase uses Zod 4’s built-in JSON Schema conversion)
  • One of:
    • A coding agent CLI installed and logged in: Claude Code, Codex, or Pi
    • An API key for Groq, Cerebras, Gemini, OpenRouter, or DeepSeek
Terminal window
npm install nuabase zod

With the Headless Coding Agent API, no API key is needed. findLocalAgent() picks the first agent that is installed and logged in, trying Claude Code first:

import { Nua, z } from 'nuabase';
import { findLocalAgent } from 'nuabase/local-agent';
const nua = Nua.direct({ localAgent: await findLocalAgent() });

Or, with an LLM provider API key, using the LLM Provider APIs:

import { Nua, z } from 'nuabase';
const nua = Nua.direct({
providers: { openrouter: { apiKey: process.env.OPENROUTER_API_KEY! } },
});

Everything below works the same with either client.

Pass only a prompt to get a plain string back:

const result = await nua.get('What is the capital of France?');
if (result.success) {
console.log(result.data); // "Paris"
} else {
console.error(result.error);
}

This is the whole result from a run on Claude Code:

{
"success": true,
"data": "Paris",
"usage": {
"promptTokens": 1571,
"completionTokens": 53,
"totalTokens": 1624,
"promptCacheReadTokens": 0,
"promptCacheWriteTokens": 1569
},
"model": "claude-code:claude-opus-5-5",
"latencyMs": 3332,
"source": "direct",
"meta": {
"engine": "local-agent",
"agent": "claude-code",
"model": "claude-opus-5-5",
"costUsdEstimate": 0.01362,
"schemaEnforcement": "native"
}
}

Each call is one agent process, so expect a few seconds per call rather than milliseconds. The default logger also prints a line when each call starts and completes:

[INFO] LLM call started {
type: 'llm_call_start',
span_id: '7e42fedf-760f-46b9-bcaa-017a061c3748',
service: 'claude-code',
method: 'native',
binaryPath: '/Users/you/.local/bin/claude',
model: 'default'
}
[INFO] LLM call completed {
type: 'llm_call_complete',
span_id: '7e42fedf-760f-46b9-bcaa-017a061c3748',
service: 'claude-code',
method: 'native',
exitCode: 0,
usage: { promptTokens: 1571, completionTokens: 53, totalTokens: 1624, promptCacheReadTokens: 0, promptCacheWriteTokens: 1569 },
duration_ms: 3331
}

Pass your own logger to localAgent() to route or silence these.

Pass input data and a Zod schema to get a typed value, parsed by your schema:

const AddressSchema = z.object({
street: z.string(),
city: z.string(),
state: z.string(),
zip: z.string(),
});
const result = await nua.get('Extract the shipping address', {
input: 'Please ship the order to 123 Main St, Springfield, IL 62701',
output: { name: 'address', schema: AddressSchema },
});
if (result.success) {
// result.data is typed as z.infer<typeof AddressSchema>
console.log(result.data);
// { street: '123 Main St', city: 'Springfield', state: 'IL', zip: '62701' }
}

The name field ('address') names the output for the model. Choose a name that describes the value.

nua.list() processes an array of records in a single request:

const LeadInsights = z.object({
company_name: z.string(),
industry: z.string(),
company_size: z.enum(['SMB', 'Mid-market', 'Enterprise']),
recommended_follow_up: z.enum(['Call', 'Email', 'Event']),
});
const leads = [
{ leadId: 'lead-101', name: 'Acme Analytics', notes: 'Growth-stage SaaS, 80 employees. Wants a live demo next week.' },
{ leadId: 'lead-102', name: 'Bright Logistics', notes: 'Regional freight operator asking for pricing via email.' },
];
const result = await nua.list(
'Extract company_name, industry, company_size, and recommended_follow_up from each lead.',
{
input: leads,
primaryKey: 'leadId',
output: { name: 'leadInsights', schema: LeadInsights },
},
);
if (!result.success) throw new Error(result.error);
console.log(result.data);

Output:

[
{
"leadId": "lead-101",
"leadInsights": {
"company_name": "Acme Analytics",
"industry": "SaaS / Analytics",
"company_size": "SMB",
"recommended_follow_up": "Call"
}
},
{
"leadId": "lead-102",
"leadInsights": {
"company_name": "Bright Logistics",
"industry": "Logistics / Freight",
"company_size": "Mid-market",
"recommended_follow_up": "Email"
}
}
]

Each returned row carries the primaryKey value (leadId) next to the output field (leadInsights). The model may return rows in a different order than the input, so match rows by key, not by array index.

Calls don’t throw for LLM failures. Check result.success:

const result = await nua.get('Summarize this in one sentence', {
input: text,
output: { name: 'summary', schema: z.string() },
});
if (!result.success) {
console.error(`Failed after ${result.attempts} attempts: ${result.reason}`);
return;
}
console.log(result.data);
console.log(`${result.usage.totalTokens} tokens via ${result.model}`);

On success:

The Q3 release moved exports to a background queue, which cut median export time from 40 seconds to 6, but large workspaces now get exports finishing out of order.
1743 tokens via claude-code:claude-opus-5-5

On failure, here with a model name the user’s Claude Code account doesn’t have:

Failed after 3 attempts: There's an issue with the selected model (claude-nonexistent-9). It may not exist or you may not have access to it. Run --model to pick a different model.

reason says why the call failed, in the provider’s or agent’s own words. When the answer never fit your schema, reason is the validation error followed by the model’s raw response, so show your own message for those. error is a one-line summary for logs. A call throws only when its arguments are invalid, before anything is sent. See API Overview for the full list.

When a job needs the agent to work in the user’s project rather than return one answer, open an Interactive Coding Agent Session with a first prompt your app writes:

import { startAgentSession } from 'nuabase/local-agent';
const session = await startAgentSession({
agent: 'claude-code',
cwd: projectDir,
prompt: 'Add tests for the CSV importer in src/import/csv.ts. Reply with your plan first and wait for me to say go.',
dir: `${projectDir}/tmp/agent-sessions`,
});
if (session.kind === 'command') {
console.log(`Run in a terminal: ${session.files.command}`);
}

On macOS this opens a new terminal window running Claude Code in the project. See Interactive Coding Agent Session for every result and option.