Recipes.
Six runnable examples, from a CLI chatbot to durable stream recovery.
Recipe 1: CLI chatbot
A complete interactive CLI chatbot with device-flow auth and streaming.
import * as readline from 'node:readline/promises';
import { stdin, stdout } from 'node:process';
import Matilda from '@maincode-ai/matilda-client-sdk';
import { createFileTokenStore } from '@maincode-ai/matilda-client-sdk/auth/node';
import { homedir } from 'node:os';
import { join } from 'node:path';
const { store, lock } = createFileTokenStore(join(homedir(), '.matilda', 'tokens.json'));
const client = new Matilda({ baseUrl: 'https://matilda.maincode.com/api' });
// Authenticate if needed
if (!(await client.auth.getTokens())) {
console.log('Starting device flow authentication...');
await client.auth.loginWithDeviceFlow({
clientId: 'matilda-code',
tokenStore: store,
tokenLock: lock,
});
console.log('Authenticated!');
}
// Start chatting
const rl = readline.createInterface({ input: stdin, output: stdout });
const conversationId = crypto.randomUUID();
while (true) {
const input = await rl.question('\nYou: ');
if (!input.trim() || input.toLowerCase() === 'exit') break;
process.stdout.write('Matilda: ');
for await (const chunk of client.chat.streamText({ input, conversationId })) {
process.stdout.write(chunk);
}
process.stdout.write('\n');
}
rl.close();Recipe 2: File Q&A
Upload a document and ask questions about it.
import { readFileSync } from 'node:fs';
import { randomUUID } from 'node:crypto';
import Matilda from '@maincode-ai/matilda-client-sdk';
const client = new Matilda({ baseUrl: 'https://matilda.maincode.com/api' });
// Authenticate with device flow — token refresh is handled automatically
if (!(await client.auth.getTokens())) {
await client.auth.loginWithDeviceFlow({ clientId: 'matilda-code' });
}
// Upload a file
const buffer = readFileSync('./report.pdf');
const file = new File([buffer], 'report.pdf', { type: 'application/pdf' });
const upload = await client.files.upload(file, {
onProgress: (pct) => process.stdout.write(`\rUploading: ${pct}%`),
});
console.log(`\nUploaded: ${upload.fileId} (${upload.status})`);
// A conversationId is required for follow-ups to share history — omitting it
// auto-creates a new conversation per call.
const conversationId = randomUUID();
// Ask a question about it
const response = await client.chat.create({
input: 'Summarise the key findings in this report.',
fileIds: [upload.fileId],
conversationId,
});
console.log(response.outputText);
// Follow-up question in the same conversation
const followUp = await client.chat.create({
input: 'What are the recommendations?',
fileIds: [upload.fileId],
conversationId,
});
console.log(followUp.outputText);Recipe 3: Conversation history browser
List, paginate, and inspect conversation history.
import Matilda from '@maincode-ai/matilda-client-sdk';
const client = new Matilda({ baseUrl: 'https://matilda.maincode.com/api' });
// Authenticate with device flow — token refresh is handled automatically
if (!(await client.auth.getTokens())) {
await client.auth.loginWithDeviceFlow({ clientId: 'matilda-code' });
}
// List first page
let offset = 0;
const limit = 10;
let page = await client.conversations.list({ limit, offset });
console.log(`Total conversations: ${page.total}\n`);
for (const conv of page.conversations) {
console.log(`[${conv.id}] ${conv.title}`);
console.log(` Updated: ${conv.updatedAt}`);
console.log();
}
// Load next page
offset += limit;
if (offset < page.total) {
page = await client.conversations.list({ limit, offset });
for (const conv of page.conversations) {
console.log(`[${conv.id}] ${conv.title}`);
}
}
// Retrieve a full conversation
if (page.conversations.length > 0) {
const full = await client.conversations.retrieve(page.conversations[0].id);
console.log(`\n--- ${full.title} ---`);
for (const msg of full.messages) {
console.log(`\n[${msg.role.toUpperCase()}]`);
console.log(msg.content);
if (msg.feedback) {
console.log(` Feedback: ${msg.feedback}`);
}
}
}Recipe 4: Durable stream recovery
Start a stream, simulate a disconnect, and resume from the last cursor.
import Matilda from '@maincode-ai/matilda-client-sdk';
const client = new Matilda({ baseUrl: 'https://matilda.maincode.com/api' });
// Authenticate with device flow — token refresh is handled automatically
if (!(await client.auth.getTokens())) {
await client.auth.loginWithDeviceFlow({ clientId: 'matilda-code' });
}
const conversationId = crypto.randomUUID();
let streamId: string | null = null;
let lastEventId: string | undefined;
let receivedText = '';
// Start streaming — simulate disconnect after a few events
console.log('Starting stream...');
try {
for await (const event of client.chat.stream({
input: 'Write a very long, detailed essay about the history of computing.',
conversationId,
})) {
if (event.type === 'response.created') streamId = event.streamId;
if (event.type === 'response.cursor') lastEventId = event.lastEventId;
if (event.type === 'response.output_text.delta') {
receivedText += event.delta;
// Simulate disconnect after 500 chars
if (receivedText.length > 500) {
console.log('\n--- Simulated disconnect ---');
break;
}
}
if (event.type === 'response.completed') {
console.log('Stream completed naturally.');
}
}
} catch (err) {
console.log('Disconnected:', err);
}
console.log(`Received ${receivedText.length} chars before disconnect.`);
// Check if the stream is still active
if (streamId) {
const active = await client.chat.activeStream(conversationId);
console.log(`Stream status: ${active.status}`);
if (active.status === 'active') {
console.log('\n--- Resuming ---');
for await (const event of client.chat.resume({ streamId, lastEventId })) {
if (event.type === 'response.output_text.delta') {
receivedText += event.delta;
process.stdout.write(event.delta);
}
if (event.type === 'response.completed') {
console.log('\n\n--- Resume complete ---');
console.log(`Total received: ${receivedText.length} chars`);
}
}
}
}Recipe 5: Multi-environment setup
Run staging and production clients in the same process. Each instance manages its own auth independently.
import Matilda from '@maincode-ai/matilda-client-sdk';
const staging = new Matilda({ baseUrl: 'https://staging.matilda.maincode.com/api' });
const production = new Matilda({ baseUrl: 'https://matilda.maincode.com/api' });
// Authenticate each instance independently — device flow or browser login
if (!(await staging.auth.getTokens())) {
await staging.auth.loginWithDeviceFlow({
clientId: 'matilda-code',
});
}
if (!(await production.auth.getTokens())) {
await production.auth.loginWithBrowser({ clientId: 'matilda-code' });
}
// Run the same prompt against both environments
const [stagingResponse, prodResponse] = await Promise.all([
staging.chat.createText({ input: 'Explain quantum entanglement.' }),
production.chat.createText({ input: 'Explain quantum entanglement.' }),
]);
console.log('Staging:', stagingResponse);
console.log('Production:', prodResponse);
// Instances are fully isolated — each manages its own token lifecycle
// Reconfiguring one never affects the other:
staging.configure({ baseUrl: 'https://override.example/api' });
// production.config.baseUrl is unchangedRecipe 6: Custom token store
Implement StorageAdapter to store tokens in a database or other custom backend.
import Matilda from '@maincode-ai/matilda-client-sdk';
import type { StorageAdapter } from '@maincode-ai/matilda-client-sdk';
// Example: a database-backed token store
class DatabaseTokenStore implements StorageAdapter {
constructor(private db: Database) {}
async get(key: string): Promise<string | null> {
const row = await this.db.query('SELECT value FROM tokens WHERE key = $1', [key]);
return row?.value ?? null;
}
async set(key: string, value: string): Promise<void> {
await this.db.query(
'INSERT INTO tokens (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = $2',
[key, value],
);
}
async remove(key: string): Promise<void> {
await this.db.query('DELETE FROM tokens WHERE key = $1', [key]);
}
}
const tokenStore = new DatabaseTokenStore(myDatabase);
const client = new Matilda({ baseUrl: 'https://matilda.maincode.com/api' });
await client.auth.loginWithDeviceFlow({
clientId: 'matilda-code',
tokenStore,
// No cross-process lock needed — the database handles concurrency
});
// Tokens are now persisted in the database and survive process restarts
const response = await client.chat.create({ input: 'Hello!' });
console.log(response.outputText);