@ailog/cli
v0.4.1
Published
CLI to run the ailog LLM logging dashboard
Readme
ailog
Local devtools for inspecting LLM calls. Records generateText / streamText (or any provider you instrument by hand) to disk and serves it in a live web viewer.
Local development only. Throws if
NODE_ENV=production.
Install
bun add -D @ailog/cli
# or
npm install -D @ailog/cliUsage
AI SDK middleware
import { wrapLanguageModel } from 'ai';
import { aiSdkMiddleware } from '@ailog/cli';
const model = wrapLanguageModel({
middleware: aiSdkMiddleware(),
model: anthropic('claude-opus-4-7'),
});Provider-agnostic logger
For non-AI-SDK providers, record runs manually:
import { createLogger } from '@ailog/cli';
const run = await createLogger({ functionId: 'chat' });
const step = await run.step({
modelId: 'claude-opus-4-7',
provider: 'anthropic',
input: { messages },
});
try {
const res = await callAnthropic(messages);
await step.end({ output: res, usage: res.usage });
} catch (err) {
await step.end({ error: err });
throw err;
}Threads
Tag a run with a threadId to group it with other runs from the same logical conversation / session / workflow. The viewer renders the id as a small #thread-id pill on each run card, and a Thread record (id, created_at, updated_at, run_count) is persisted in .ailog/index.json for future thread-list features.
await createLogger({
threadId: 'chat-42',
functionId: 'message',
});threadIdis honored only on the first writer that creates a given run (first-writer-wins, likefunctionId/metadata).- Runs without a
threadIdsimply don't appear in any thread.
Metadata
Attach arbitrary fields to a run. Child runs (subagents) inherit metadata automatically — shallow-merged, with metadata: null to opt out:
await createLogger({
metadata: { userId: 'u_42', organizationId: 'o_lr' },
});Rendered as key: value pills on the run header.
Subagents (shared threadId)
Each aiSdkMiddleware instance owns one Run automatically. To group orchestrator and subagent calls — each constructed with its own wrapped model — share a threadId. They show up side-by-side under the same Thread in the viewer.
import { aiSdkMiddleware } from '@ailog/cli';
const orchestrator = wrapLanguageModel({
middleware: aiSdkMiddleware({
threadId: 'tokyo',
functionId: 'orchestrator',
metadata: { userId: '...' },
}),
model,
});
const tools = {
research: tool({
execute: async ({ question }) => {
const sub = wrapLanguageModel({
// Share only the threadId — each tool call gets its own Run in the same Thread.
middleware: aiSdkMiddleware({ threadId: 'tokyo', functionId: 'researcher' }),
model,
});
return { answer: (await generateText({ model: sub, prompt: question })).text };
},
}),
};
await generateText({ model: orchestrator, tools, ... });Rules:
threadIdis auto-generated (auto-<short>) when omitted, so every run still belongs to a Thread.metadatalives on the Thread record (not the Run). First-writer-wins: only the call that creates the Thread sets it.- Run ids are always auto-generated; you can't reuse one across loggers. Share a Thread, not a Run.
Viewer
bunx ailog # → http://localhost:4985 (override via AILOG_PORT)How it works
your code → middleware / createLogger → .ailog/{index, runs/*, blobs/*} → Hono API → React UIEach step write touches only its run's small JSON file plus the index — never the whole history. Large raw_* fields are externalized into .ailog/blobs/. Aborted requests (Ctrl-C) close in-flight steps with error: 'Request aborted' before exit.
License
Apache-2.0. Structure and viewer code derived from @ai-sdk/devtools (Apache-2.0). See NOTICE for attribution.
