@termfleet/core
v0.2.9
Published
Termfleet core: contracts, the provider SDK, and the agent-transcript/session library (Claude Code / Codex / Gemini / Grok Build). The reusable layer, usable beyond the console.
Maintainers
Readme
@termfleet/core
The reusable core of termfleet — usable on its own, with no console and no UI. It is three things:
- An agent-transcript / session library — read and normalize Claude Code, Codex, and Gemini session transcripts straight off disk into one shape. No network, no provider, no daemon.
- A provider SDK —
ProviderClient, a typed HTTP + socket.io client for a running termfleet provider (health, snapshots, agent sessions, terminals, windows). - Isomorphic contracts — the types and grammar the whole system speaks
(provider/registry records, the
claude:/codex:/gemini:session-id grammar, canvas/layout contracts).
If all you want is to parse the transcript files your AI tools already write, this package does that standalone.
Install
npm install @termfleet/coreRequires Node 20+ and ESM ("type": "module", or import()).
Imports are explicit, per module
There is no barrel — you import the specific module you need, with a .js
extension:
import { readLocalAgentSession } from "@termfleet/core/internal/agent-session.js";
import { parseAgentSessionId } from "@termfleet/core/agent-session-id.js";
import { ProviderClient, providerRefFromUrl } from "@termfleet/core/provider-client.js";This is deliberate: the transcript readers are Node-only (they touch the
filesystem), while the contracts are isomorphic. Importing by module means a
browser bundle that only pulls contracts/* never drags node:fs into the
build. import "@termfleet/core" (bare) does not resolve.
The export map is a stability cut, not a directory listing
There is no wildcard export — every importable subpath is a key the package deliberately declares, and each key says something about how much the shape underneath it is allowed to change. Four surfaces:
| Prefix | Stability | What lives there |
| --- | --- | --- |
| (none — package root) | Stable. Semver-respected; a breaking change is a major version. | The kernel: the provider/terminal/registry SDK clients, the claude:/codex:/gemini: id grammar, the one session→row join, shared types, and the handful of isomorphic contracts every door (CLI, MCP, browser) needs regardless of layer. |
| /console | Semi-stable. Changes with the console UI, not on every release. | Layer-1 (visualization/aggregation) contracts — today just the machine appearance/layout shape the board and Machine Settings window read and write. |
| /teams | Semi-stable. Changes with the registry/collaboration surface. | Layer-2 (org identity, shared providers, collaboration) contracts and clients: the registry record/merge logic, provider access tokens, the local-provider/tunnel plumbing a shared deployment needs, the Yjs canvas doc. |
| /internal | Explicitly unstable. No compatibility promise, ever — pin an exact version if you import from here. | Everything that is, in practice, an implementation detail that changes as fast as the four agent CLIs' own transcript formats do: the per-agent transcript parsers, the lifecycle/attention derivation, the conversation index (and its worker client), agent launch heuristics, and assorted engine-internal plumbing (route tables, boot queues, background runners, session-window resolution). |
Why the cut lands where it does: the package is consumed by code sitting at
every layer of termfleet itself (CLI/MCP at layer 0, the console at layer 1,
the registry at layer 2), and a third-party integrator is realistically only
ever building at one of those layers too. /internal exists because the
transcript parsers in particular are the most useful code in this package
and the least safe to freeze — Claude/Codex/Gemini/supercode change their
on-disk formats out from under us, so those modules have to keep changing
weekly (see agent-session.ts's own per-agent adapters). Promising stability
there would either freeze our fastest-moving code or force semver-major churn
that trains consumers to stop reading versions. If you need the transcript
library specifically, treat every /internal import as intentionally
version-pinned, not something to ^-range in package.json.
The stable root is deliberately small: it's exactly what the provider SDK, the
CLI, and the MCP server need to talk to a running provider and make sense of a
session, plus the contracts those consumers all agree on — clients
(provider-client.js, terminal-client.js, registry-client.js), the
session-id grammar, the one session→row join (session-view.js), shared
types, lib/errors.js, build-info.js, window-kind.js, and the kernel
contracts (contracts/{auth,files,provider-url,registry}.js). An alternate
front door or a tool that just wants "give me a session" builds against this
root and nothing else.
Guard-tested (test/core-export-map-boundary.test.js): every
@termfleet/core/... import inside this repo resolves to one of these
declared keys — none of them, including this package's own code, relies on a
wildcard fallback.
The full policy behind this table — stable-path semver guarantees,
deprecation windows per tier, and exactly how /internal is allowed to
break — lives in docs/STABILITY.md.
Read an agent transcript (standalone, no console) — /internal
readLocalAgentSession resolves the on-disk transcript under the agent home
(~/.claude/projects, ~/.codex/sessions, ~/.gemini/..., and isolated Grok homes) and normalizes it to
one AgentSessionDetails shape. The agent is chosen by the session-id prefix —
codex: / gemini:, defaulting to Claude. This is the transcript library —
the single most useful thing in this package, and also the fastest-moving, so
it lives under /internal (see the stability table above): pin an exact
version if you depend on it.
import { readLocalAgentSession } from "@termfleet/core/internal/agent-session.js";
const session = readLocalAgentSession({
cwd: "/path/to/the/project", // the project the session ran in
sessionId: "claude:0f9c…", // or "codex:…", "gemini:…", "grok:…", or a bare Claude uuid
});
console.log(session.sessionId); // "claude:0f9c…" (prefixed)
console.log(session.agentSessionId); // "0f9c…" (bare id, no prefix)
console.log(session.lastAssistantText);
console.log(session.endOfTurn); // is the turn finished?
for (const item of session.timeline) {
// normalized timeline items: user/assistant text, tool calls (with a
// category + isError), todos, subagent refs, …
}There's an async variant (readLocalAgentSessionAsync) and per-agent readers
(readLocalClaudeSession / readLocalCodexSession / readLocalGeminiSession).
For incremental reads, @termfleet/core/internal/agent-session-tail.js exports
readLocalAgentSessionTailed, whose tailed read always deep-equals a fresh full
parse.
The session-id grammar is its own one-owner module:
import { parseAgentSessionId, formatAgentSessionId, agentProviders }
from "@termfleet/core/agent-session-id.js";
agentProviders; // ["claude", "codex", "gemini", "grok", "supercode"]
parseAgentSessionId("gemini:abc"); // { agent: "gemini", bareId: "abc" }
formatAgentSessionId("claude", "uuid-1"); // "claude:uuid-1"Talk to a running provider (the SDK)
ProviderClient is a typed client for a termfleet provider's HTTP + socket.io
API — the same surface the console proxies:
import { ProviderClient, providerRefFromUrl } from "@termfleet/core/provider-client.js";
const client = new ProviderClient(providerRefFromUrl("http://127.0.0.1:7402"));
await client.health(); // ProviderHealth
const page = await client.listAgentSessions({ limit: 20 });
const details = await client.getAgentSession("claude", "0f9c…");
// live updates over socket.io
const socket = client.connect();
const off = client.onSnapshot((snapshot) => { /* … */ });
// create a fresh agent window (boot a claude/codex/gemini session)
await client.createAgentWindow({ /* AgentWindowCreateOptions */ });
// terminals + files: capture/send, and read/write through the provider filesystem
const { content } = await client.captureTerminal("term-1", 40);
await client.sendTerminalInput("term-1", "echo hi\n", { breakGlass: true });
const bytes = await client.readFile("term-1", "/path/file"); // Uint8Array (browser-safe)
const text = new TextDecoder().decode(bytes); // node: or Buffer.from(bytes)Pass { authToken } for an authenticated/shared provider, or a urlResolver
when proxying through a console; omit both for a direct connection. readFile
returns a Uint8Array (not a node Buffer, since this client also runs in the
browser) — decode with new TextDecoder().decode(bytes) or Buffer.from(bytes).
What's inside
Stable (package root):
| Module | What it gives you |
| --- | --- |
| agent-session-id.js | the claude:/codex:/gemini: id grammar (parse/format) |
| provider-client.js | the provider HTTP + socket.io SDK |
| terminal-client.js | terminal stream client |
| registry-client.js | registry/auth client |
| session-view.js | the one session→row join (assembleSessionView) |
| contracts/registry.js | provider/registry record contracts + mergeRegistryProviders |
| contracts/{auth,files,provider-url}.js | auth/filesystem/provider-url contracts |
| types.js | shared provider/terminal types |
| build-info.js, window-kind.js | build metadata; window-kind display helpers |
/console:
| Module | What it gives you |
| --- | --- |
| console/contracts/console-layout.js | machine appearance / board layout contract |
/teams:
| Module | What it gives you |
| --- | --- |
| teams/registry.js | local registry file (read/write/merge, org-aware) |
| teams/contracts/canvas.js, teams/collab/canvas-doc.js | Yjs collaborative canvas |
| teams/provider-access-token.js | multi-tenant provider entitlement tokens |
| teams/local-providers.js, teams/local-tunnel.js | shared-deployment provider/tunnel plumbing |
/internal (unstable — pin exact versions):
| Module | What it gives you |
| --- | --- |
| internal/agent-session.js | read/normalize Claude · Codex · Gemini · supercode transcripts |
| internal/agent-session-tail.js, internal/agent-session-roll.js, internal/agent-session-watcher.js | incremental tailing, /clear-roll detection, filesystem watching |
| internal/agent-session-index.js, internal/agent-session-index-client.js | the durable conversation index + its worker client |
| internal/session-lifecycle.js, internal/session-attention.js | session liveness/attention derivation |
| internal/agent-launch.js | per-agent launch heuristics (startup detection, command building) |
| internal/session-window.js, internal/provider-url-resolver.js | session→window→terminal descent; provider URL resolution |
| internal/session-title-store.js, internal/local-session-index.js | durable session-title store; local session index |
| internal/http-route-table.js, internal/boot-queue.js, internal/background-runner.js, internal/launch-trace.js | route matching, boot sequencing, background jobs, launch tracing |
| internal/contracts/parse-log.js, internal/lib/exec.js | unknown-field reporting; process-exec helpers |
Browser-safe modules (kernel contracts, types.js, the fetch/socket-based
clients) are isomorphic; the transcript readers and lifecycle modules under
/internal are Node-only.
License
Apache-2.0. Part of termfleet.
