GuideTypeScript Client

TypeScript Client

If you’re scripting Agor from Node, building a custom dashboard, wiring it into a CI workflow, or embedding live session views in your own app — @agor-live/client is the way. It’s the same fully-typed client the Agor UI consumes internally, so it stays in lockstep with the daemon API and ships first-class types for every service, model, and event.

Two doorways into Agor — same daemon, two audiences:

  • Inside agents (Claude Code, Codex, Gemini) — the Agor MCP Server gives sessions self-aware orchestration.
  • Outside agents (TS/JS apps, scripts, services) — @agor-live/client gives humans and services the same API.

Use whichever side of the fence you’re on.

Install

npm install @agor-live/client
# or
pnpm add @agor-live/client

Requires Node ≥ 22.12 (or any modern browser). ESM and CJS builds are both included; TypeScript types are first-class.

Quickstart — REST

For one-off scripts, automation, or anywhere you don’t need real-time updates:

import { createRestClient, getApiKeyFromEnv } from '@agor-live/client';
 
const apiKey = getApiKeyFromEnv() ?? 'agor_sk_…'; // from User Settings → Personal API Keys
const client = await createRestClient('http://localhost:3030', apiKey);
 
const worktrees = await client.service('worktrees').find({ query: { $limit: 50 } });
const session = await client.service('sessions').create({
  worktree_id: worktrees.data[0].worktree_id,
  agentic_tool: 'claude-code',
  initial_prompt: 'Run the test suite and summarize the output.',
});
 
console.log(`Session ${session.session_id} started`);

REST clients exit cleanly without keeping a socket open — perfect for CLI tools and short-lived scripts.

Driving the executor without MCP

Two equivalent ways to send work to a session — pick whichever fits your harness:

// One-shot: create the task and start execution in a single call.
await client.sessions.prompt(session.session_id, 'Run the tests and summarize.', {
  stream: true,
});
 
// Or do it in two steps if your orchestrator wants the task_id up-front.
const task = await client.service('tasks').create({
  session_id: session.session_id,
  full_prompt: 'Run the tests and summarize.',
  status: 'created',
});
await client.tasks.run(task.task_id, { stream: true });

client.tasks.run() calls POST /tasks/:id/run — the explicit “fire this task now” trigger. It accepts created tasks on idle sessions; queued tasks drain automatically in order, and busy sessions should be prompted via client.sessions.prompt() (which queues atomically).

Quickstart — Real-time

For UIs, live dashboards, or anything that needs to react to session events as they fire:

import { createClient } from '@agor-live/client';
 
const client = createClient('http://localhost:3030');
 
await client.authenticate({ strategy: 'jwt', accessToken: '…' });
 
client.service('messages').on('created', (msg) => {
  console.log('[message]', msg.session_id, msg.role, msg.content?.slice(0, 80));
});
 
client.service('sessions').on('patched', (session) => {
  console.log('[session]', session.session_id, session.status);
});

createClient() returns a Socket.IO-backed Feathers client with full event subscriptions: created, patched, removed per service, plus the streaming events that power live agent transcripts.

Reactive Sessions — the cool part

The headline feature: a reactive session handle that mirrors the entire UI state surface for one session — tasks, messages, queued prompts, streaming chunks, tool executions — and notifies you on every change. Same primitive that powers the Agor UI’s session panel.

import { createClient, retainReactiveSession } from '@agor-live/client';
 
const client = createClient('http://localhost:3030');
await client.authenticate({ strategy: 'jwt', accessToken: '…' });
 
const handle = retainReactiveSession(client, sessionId, { taskHydration: 'lazy' });
await handle.ready();
 
const unsubscribe = handle.subscribe(() => {
  const { tasks, messagesByTask, streamingMessages, queuedTasks, connected } = handle.state;
  // Re-render whatever you're driving — React, Vue, plain DOM, terminal UI, etc.
});
 
await handle.prompt('Now write a follow-up summary in 3 bullet points.');
 
// When you're done:
unsubscribe();
handle.release();

What you get on handle.state:

FieldDescription
sessionThe full Session model (status, current task, agent config, …)
tasksAll tasks for this session, ordered
messagesByTaskMap<taskId, Message[]> — hydrated lazily or eagerly per taskHydration option
queuedTasksTasks queued but not yet running, ordered by queue_position
streamingMessagesIn-flight assistant messages with live content + thinkingContent
toolsByTaskTool executions per task, with executing / complete status
connected, loading, error, lastSyncedAtConnection + sync metadata

retainReactiveSession() is reference-counted — multiple subscribers on the same session ID share one handle and one set of socket listeners. Call release() when you’re done; the handle tears itself down when the last consumer releases.

This is what you’d reach for if you wanted to:

  • Build a custom session viewer in your own React/Vue/Svelte app
  • Mirror Agor sessions into a Slack or terminal UI
  • Pipe live tool calls into your own observability stack
  • Embed an Agor session widget in an internal portal

Authentication

StrategyWhen to useHow
API KeyScripts, services, CIPass to createRestClient(url, apiKey), or set AGOR_API_KEY=agor_sk_… and use getApiKeyFromEnv()
JWTBrowser apps after loginawait client.authenticate({ strategy: 'jwt', accessToken })

Authentication is required on every endpoint. There is no anonymous strategy.

Issue API keys from User Settings → Personal API Keys. Keys are scoped to your user — every action runs under your identity, billing, env vars, and MCP OAuth grants.

What’s in the surface

Service types are exported and fully typed — client.service('sessions'), client.service('worktrees'), client.service('boards'), client.service('messages'), client.service('tasks'), client.service('repos'), client.service('users'), and the rest. Every model lives in @agor/core/types (re-exported from @agor-live/client), so Session, Worktree, Task, Message, Board, etc. are available without round-tripping through any.

Helpers worth knowing:

  • isDaemonRunning(url) — health check before connecting
  • getApiKeyFromEnv() — pull AGOR_API_KEY from the environment
  • formatShortId(id) — render the 8-char short form Agor uses everywhere

When to reach for which client

You are…Use…
An agent running inside a sessionAgor MCP Server (already injected)
A Node script poking at the daemoncreateRestClient()
A live dashboard or custom UIcreateClient() + reactive sessions
A long-lived service routing eventscreateClient() with service .on('created' | 'patched' | 'removed', …)
BSL 1.1 © 2026 Maxime Beauchemin