API reference
Index Claude Code and Codex data into one SQLite store, query with source-scoped reads, run full-text search, and subscribe to live updates. Same surface the CLI uses — version 0.5.17.
Install
Installs a platform-specific native binary via optional dependencies. If the binary is missing, the TypeScript engine takes over automatically — no flags required.
npm install @vibecook/spaghetti-sdk
# or
pnpm add @vibecook/spaghetti-sdk
@vibecook/spaghetti-sdk/react. Runtime needs Node ≥ 18 and
better-sqlite3 (prebuilds for common platforms).
Quickstart
Create a service, initialize once (cold or warm start), query, then dispose.
import {
createSpaghettiService,
createCodexSource,
} from '@vibecook/spaghetti-sdk';
const api = createSpaghettiService({
additionalSources: [createCodexSource()],
});
api.onProgress((p) => {
console.log(p.phase, p.message, p.current, p.total);
});
await api.initialize();
const projects = api.getProjectList();
const p = projects[0];
const sessions = api.getSessionList(p);
const page = api.getSessionMessages(
sessions[0].projectSlug,
sessions[0].sessionId,
50,
0,
{ sourceId: sessions[0].sourceId },
);
const hits = api.search({ text: 'refactor parser', limit: 20 });
await api.dispose(); // preferred over shutdown()
createSpaghettiService
Factory that wires FileService, shared SqliteService, parsers, ingest, and the public app service.
function createSpaghettiService(
options?: SpaghettiServiceOptions
): SpaghettiAPI
| Option | Type | Default | Description |
|---|---|---|---|
| claudeDir | string | ~/.claude |
Claude Code data root. Ignored when source is set. |
| source | AgentSource | Claude Code | Primary agent adapter. Defaults to createClaudeCodeSource(). |
| additionalSources | AgentSource[] | [] |
Extra agents into the same store (e.g. createCodexSource()). CLI auto-detects ~/.codex/sessions. |
| dbPath | string | ~/.spaghetti/cache/spaghetti-{rs,ts}.db |
SQLite index path. Per-engine default so switching engines does not force a re-ingest. |
| engine | 'rs' | 'ts' |
resolved | Pin ingest engine for this instance. Wins over env and config. Codex always uses the TS path. |
| live | boolean | false |
Start filesystem watchers and expose api.live (Claude + Codex when present). |
| dataService | ClaudeCodeAgentDataService | — | Inject a custom/mock implementation for tests. |
| errorSink | ErrorSink | console.warn | Unified sink for live-pipeline and internal errors. |
Multi-source (RFC 006)
One index, source_id column — not one DB per agent. Project PK is
composite (source_id, slug). The project list aggregates agents that worked in
the same directory; pass its stable project locator when drilling into sessions.
import {
createSpaghettiService,
createCodexSource,
defaultCodexDir,
} from '@vibecook/spaghetti-sdk';
const api = createSpaghettiService({
additionalSources: [createCodexSource({ rootDir: defaultCodexDir() })],
});
await api.initialize();
api.getSourceIds(); // ['claude-code', 'codex']
api.getProjectList({ sourceId: 'codex' }); // Codex only
const p = api.getProjectList().find((x) => x.sourceIds.includes('codex'))!;
api.getSessionList(p); // sessions from every agent in this workspace
token_count events;
when missing, tiktoken estimates visible text and sets
tokensEstimated: true (UI shows ~N).
Engine selection
Resolution order (first match wins). Per-instance engine on the factory is independent of process-wide resolution used when the option is omitted.
1. createSpaghettiService({ engine: 'rs' | 'ts' })
2. SPAG_ENGINE=ts|rs
3. SPAG_NATIVE_INGEST=0|1 // legacy
4. ~/.spaghetti/config.json → { "engine": "rs" }
5. Default: rs
dbPath from two services in one process — risks
SQLITE_BUSY. Use separate paths for parallel indexes.
Lifecycle
Initialize runs cold start (empty DB) or warm start (fingerprint diff). Always call dispose/shutdown when done.
Methods
Parse source data, write/update the SQLite index, build FTS. Emits progress via onProgress and fires onReady when complete.
Fire-and-forget teardown: stop watchers, close the database. Prefer dispose() when you can await.
Awaitable teardown. Stops the live pipeline, drains in-flight writes, disposes subscribers, closes SQLite.
Force a full cold rebuild. Closes the DB, deletes its files, re-ingests from scratch via native Rust (with TS fallback). Use after schema bumps or when the index looks out of sync.
Whether the service finished initialize and can accept queries.
Projects & sessions
Primary navigation surface. Lists are sorted by recency.
Methods
Distinct agent sources present in the index (e.g. ['claude-code', 'codex']).
Unique workspaces sorted by last active date. Each item includes its stable projectId, exact source/slug members, sourceIds, session counts, token totals, and tokensEstimated.
| Param | Type | Description |
|---|---|---|
| options.sourceId | string? | Optional filter to one agent source. |
Sessions for a project sorted by last update. Pass a ProjectListItem (or other ProjectLocator) for exact multi-agent lookup. A slug string remains available for legacy callers.
| Param | Type | Description |
|---|---|---|
| project | ProjectReference | Stable project locator, or a legacy project slug string. |
| options.sourceId | string? | Optional agent scope for legacy slug calls. |
Paginated messages. Payloads preserve the source-native record (Claude JSONL or Codex rollout line). Optional sourceId for defense in depth.
| Param | Type | Description |
|---|---|---|
| projectSlug | string | Project slug. |
| sessionId | string | Session UUID. |
| limit | number? | Page size (default 30). |
| offset | number? | Row offset. |
| options.sourceId | string? | Optional agent scope. |
Project MEMORY.md. Claude-only today — returns null when sourceId is not claude-code.
Artifacts
Plans, todos, memory, subagents, workflows, tool results, and teams.
Methods
Raw MEMORY.md content for a project, or null if absent.
Todo items for a session (agent-scoped todo files under ~/.claude/todos/).
Plan linked to the session, if any.
Task metadata (highwatermark / lock presence). Task item bodies are not fully ingested.
Persisted tool result text for a tool_use id.
Subagent metadata with normalized message counts and parent Task/Agent linkage. Pass { includeNested: true } to include workflow-nested agents.
DB-filtered, independently paginated display rows for an inline subagent branch. Raw sidecar messages remain available through getSubagentMessages.
Paginated messages from a subagent transcript.
Agent-orchestration workflow runs for a session (schema v4+).
Subagents that ran under a specific workflow.
Agent teams from ~/.claude/teams/. Empty when the feature is unused. config may be null for orphaned team dirs.
Search & stats
FTS5 over message text (and auxiliary entities). Stats reflect the open SQLite store.
Methods
Full-text search. Supports optional filters by segment type, project, session, and pagination.
const { results, total, hasMore } = api.search({
text: 'worker pool',
projectSlug: '-Users-me-Projects-app',
limit: 25,
offset: 0,
});
Aggregate store statistics: segment counts by type, fingerprint count, DB size, and search index size.
Events
Subscribe to init progress, ready, and coarse data-change batches. For fine-grained live events, use api.live.
Methods
Subscribe to init progress. Callback receives InitProgress with phase, message, and optional current/total. Returns unsubscribe.
Fires once when initialize completes with { durationMs }.
Coarse segment change batches during ingest/reconcile. For live file appends, prefer api.live.onChange.
Live updates
Present only when constructed with { live: true }. Watchers keep SQLite warm;
typed Change events fan out after each commit.
const api = createSpaghettiService({ live: true });
await api.initialize();
const dispose = api.live!.onChange(
{ type: 'session', slug, sessionId },
(e) => {
if (e.type === 'session.message.added') {
console.log(e.message);
}
},
);
// Async iterable form
for await (const e of api.live!.events()) {
console.log(e.type, e.seq);
}
dispose();
api.live surface
Firehose or scoped subscribe. Prewarms the relevant watcher scope. Returns a dispose handle. Supports single-event or coalesced batch delivery via options.
Async iterable over changes. Bounded ring buffer with drop-oldest on overflow. Options: bufferSize, onDrop.
Explicit watcher attachment without a subscriber. Ref-counted; dispose detaches when last ref drops.
Change
unionDiscriminated by type. Each event carries seq (in-memory monotonic) and ts.
'session.message.added' // new message append
'session.created' // new session file
'session.rewritten' // truncate / rewrite
'subagent.updated'
'tool-result.added'
'file-history.added'
'todo.updated'
'task.updated'
'plan.upserted'
'settings.changed'
Runtime (Plane 3)
api.runtime exposes hooks streams and channel session discovery.
Always attached on the default factory path; watchers start lazily on first subscribe.
Runtime events do not write the SQLite index — the durable store is a pure function of files on disk.
const unsub = api.runtime?.onHookEvent((ev) => {
console.log(ev.hookName, ev.sessionId);
});
const channels = api.runtime?.listChannelSessions() ?? [];
List & page types
Return shapes from the primary list and pagination methods.
ProjectListItem
interface{
projectId: string // stable workspace identity
members: ProjectMember[] // exact { sourceId, slug } pairs
slug: string // representative legacy slug
sourceIds: string[] // agents active in the workspace
folderName: string
absolutePath: string
sessionCount: number
messageCount: number
tokenUsage: TokenUsageSummary
tokensEstimated: boolean // true → show "~" (tiktoken fallback)
lastActiveAt: string
firstActiveAt: string
latestGitBranch: string
hasMemory: boolean
}
SessionListItem
interface{
sessionId: string
sourceId: string
projectSlug: string
startTime: string
lastUpdate: string
lifespanMs: number
tokenUsage: TokenUsageSummary
tokensEstimated: boolean
messageCount: number
fullPath: string
summary: string
firstPrompt: string
gitBranch: string
todoCount: number
planSlug: string | null
hasTask: boolean
isSidechain: boolean
}
SourceFilter
interface{ sourceId?: string }
MessagePage
interface{
messages: SessionMessage[]
total: number
offset: number
hasMore: boolean
}
SubagentListItem · WorkflowListItem
interface// SubagentListItem
{
sourceId: string
agentId: string
agentType: string
messageCount: number
workflowId: string
spawnToolId: string | null
linkMethod: 'tool_result' | 'ordinal' | 'unlinked'
}
// WorkflowListItem
{
workflowId: string
name: string
status: string
agentCount: number
totalTokens: number
totalToolCalls: number
durationMs: number
subagentCount: number
}
Search types
SearchQuery
interface{
text: string
type?: SegmentType
projectSlug?: string
sessionId?: string
tags?: string[]
limit?: number
offset?: number
}
SearchResultSet
interface{
results: SearchResult[] // key, type, snippet, rank, slugs
total: number
hasMore: boolean
}
StoreStats
interface{
totalSegments: number
segmentsByType: Record<string, number>
totalFingerprints: number
dbSizeBytes: number
searchIndexed: number
}
InitProgress
interface{
phase: 'parsing' | 'storing' | 'indexing' | 'reconciling'
message: string
current?: number
total?: number
}
Service options & settings
Helpers exported from the package root for engine preferences.
import {
resolveEngine,
readSettings,
writeSettings,
defaultDbPathForEngine,
type IngestEngine, // 'ts' | 'rs'
} from '@vibecook/spaghetti-sdk';
writeSettings({ engine: 'ts' });
const engine = resolveEngine();
const db = defaultDbPathForEngine(engine);
React exports
Subpath @vibecook/spaghetti-sdk/react. Requires React 19 peer dependency.
import {
SpaghettiProvider,
AgentDataPlayground,
ProjectCard,
SessionCard,
MessageEntry,
useSpaghettiAPI,
formatTokenCount,
formatRelativeTime,
} from '@vibecook/spaghetti-sdk/react';
export default function App({ api }: { api: SpaghettiAPI }) {
return (
<SpaghettiProvider api={api}>
<AgentDataPlayground />
</SpaghettiProvider>
);
}
| Export | Kind | Description |
|---|---|---|
| SpaghettiProvider | component | Provides API context to the tree. |
| useSpaghettiAPI | hook | Read the API from context. |
| AgentDataPlayground | component | Full browse UI. |
| ProjectCard · SessionCard | component | List cards. |
| MessageEntry | component | Rendered session message. |
| DetailOverlay · MetaRow · Badge | component | UI primitives. |
| formatTokenCount · formatRelativeTime · formatDuration · formatBytes | util | Display formatters. |
React live hooks
Require a service created with { live: true }.
| Hook | Description |
|---|---|
| useLiveSessionMessages | Live-updating message list for a session. |
| useLiveSessionList | Live-updating session list for a project. |
| useLiveSettings | Subscribe to settings file changes. |
| useLiveChanges | Low-level change stream subscription. |
Default paths
| Path | Purpose |
|---|---|
~/.claude |
Claude Code source data (read-only for Spaghetti). |
~/.codex |
OpenAI Codex CLI data; rollouts under sessions/YYYY/MM/DD/. |
~/.spaghetti/cache/spaghetti-rs.db |
Default SQLite index for Rust engine (schema v7+). |
~/.spaghetti/cache/spaghetti-ts.db |
Default SQLite index for TypeScript engine. |
~/.spaghetti/config.json |
Persisted settings (engine preference). |
~/.spaghetti/channel/ |
Live chat channel discovery (CLI chat / plugins). |
~/.spaghetti/hooks/ |
Hook event stream from spaghetti-hooks plugin. |
Errors & disposal
Initialize throws if the primary source root is missing or unreadable. Codex failures are non-fatal (additive source). Live pipeline errors route through errorSink rather than crashing the process.
initialize() with
await dispose() (or shutdown() in sync contexts).
Leaving the process without teardown can keep SQLite WAL files and watchers alive.
const api = createSpaghettiService();
try {
await api.initialize();
// … work …
} finally {
await api.dispose();
}