Reads local assistant history directories and returns structured JSON — sessions, costs, timesheets, work patterns.
Zero-dependency, local-first TypeScript library for reading session data from provider directories such as ~/.claude/, ~/.codex/, ~/.pi/, ~/.cursor/, and ~/.windsurf/.
Security Warning: Provider home directories contain highly sensitive data — API keys, source code, credentials, and personal information may be present in plaintext session files. This library is designed with privacy as the primary concern. See SECURITY.md.
bunx --silent agent-optic sessions- Zero runtime dependencies
- No network access
- Privacy by default — strips tool results and thinking blocks
- Multi-tier session loading — index, meta, detail, and transcript streaming
- Agent-first CLI contract — stable JSON envelope + JSONL streaming + machine-readable errors
- Bun-native —
Bun.file(),Bun.Glob
bun add agent-opticThe examples/ directory contains standalone scripts that show what your session data unlocks. Run any of them with bun examples/<name>.ts.
Match sessions to git branches and calculate what each feature costs in tokens and USD.
bun examples/cost-per-feature.ts --repo /path/to/repoCost per Feature / Branch
==========================================================================================
Feature/Branch Sessions Tokens Est. Cost Commits
------------------------------------------------------------------------------------------
feat/auth-system 8 2.3M $4.12 5
fix/memory-leak 3 890K $1.55 2
refactor/api-client 5 1.1M $2.08 3
Correlate git commits with sessions by timestamp proximity — find which session produced each commit.
bun examples/match-git-commits.ts --days 7Generate a weekly timesheet grouped by day and project, with gap-capped hours.
bun examples/timesheet.tsTimesheet: 2026-02-10 → 2026-02-14
==========================================================================================
Day Date Project Hours Sessions Prompts
------------------------------------------------------------------------------------------
Mon 2026-02-10 claude-optic 2.3 4 18
my-app 1.1 2 8
Tue 2026-02-11 claude-optic 3.5 6 32
------------------------------------------------------------------------------------------
TOTAL 6.9 12 58
Compare token usage and costs across model families.
bun examples/model-costs.tsModel Usage & Costs: 2026-01-13 → 2026-02-12
====================================================================================================
Model Sessions Input Output Cache W Cache R Est. Cost
----------------------------------------------------------------------------------------------------
opus-4-5-20250514 12 4.2M 1.1M 3.8M 2.1M $98.42
sonnet-4-5-20250929 45 8.1M 2.3M 6.2M 4.5M $42.15
Export sampled prompts grouped by project as JSON — pipe to an LLM for categorization or analysis.
bun examples/prompt-history.ts --from 2026-01-01 | claude "categorize these prompts by intent"Compact session summaries as JSON — first prompt, branch, model, token counts, cost, duration.
bun examples/session-digest.ts --days 7 | claude "which sessions were the most productive?"Aggregated work pattern metrics as JSON — hour distribution, late-night/weekend counts, longest and most expensive sessions.
bun examples/work-patterns.ts | claude "analyze my work patterns and suggest improvements"Post-commit hook that records AI usage per commit to .ai-usage.jsonl.
# Install the git hook
bun examples/commit-tracker.ts install
# Or run manually for the latest commit
bun examples/commit-tracker.ts runEach commit gets a JSONL record with matched sessions, token counts, and cost:
{"commit":"7c5a457","timestamp":"2026-02-13T21:12:13.000Z","branch":"main","sessions":2,"tokens":{"input":194,"output":1638,"cache_read":1534390,"cache_write":83203},"cost_usd":1.33,"models":["claude-opus-4-6"],"messages":89,"files_changed":2}# Uninstall when done
bun examples/commit-tracker.ts uninstallGeneric stdin matcher — pipe in any JSON with timestamps, match against sessions.
# Match GitHub PRs to sessions that produced them
gh pr list --json createdAt,title | bun examples/pipe-match.ts --field createdAt
# Match GitHub issues
gh issue list --json createdAt,title | bun examples/pipe-match.ts --field createdAt
# Match any timestamped JSON
echo '[{"timestamp":"2026-02-10T14:00:00Z","title":"Deploy v2.1"}]' | bun examples/pipe-match.ts
# Works with any JSON — work items, calendar events, deploys, etc.
cat events.json | bun examples/pipe-match.tsimport { createHistory } from "agent-optic";
const ch = createHistory({ provider: "claude" });
// List today's sessions (fast — reads only history.jsonl)
const sessions = await ch.sessions.list();
// List with metadata (slower — peeks session files for branch/model/tokens)
const withMeta = await ch.sessions.listWithMeta();
// Get full session detail (projectPath is optional for codex/openai)
const detail = await ch.sessions.detail(sessionId);
// Stream transcript entries (projectPath is optional for codex/openai)
for await (const entry of ch.sessions.transcript(sessionId)) {
console.log(entry.message?.role, entry.timestamp);
}
// Daily summary (sessions + tasks + plans + todos + project memory)
const daily = await ch.aggregate.daily("2026-02-09");
// Project summaries
const projects = await ch.aggregate.byProject({ from: "2026-02-01", to: "2026-02-09" });
// Estimate cost of a session
import { estimateCost } from "agent-optic";
const cost = estimateCost(withMeta[0]); // USDconst ch = createHistory({
provider: "claude", // "claude" | "codex" | "openai" | "pi" | "cursor" | "windsurf"
providerDir: "~/.claude", // default: provider-specific home directory
privacy: "local", // "local" | "shareable" | "strict" | Partial<PrivacyConfig>
cache: true, // default: true
});openai is currently an alias of Codex-format local history and defaults to ~/.codex.
pi reads from ~/.pi/agent/sessions/ — Pi has no history.jsonl, so sessions are discovered by scanning the directory tree. Pi sessions include totalCost from accumulated message costs.
createClaudeHistory() is still exported for backward compatibility and behaves like createHistory({ provider: "claude" }).
| Method | Speed | Reads | Returns |
|---|---|---|---|
sessions.list(filter?) |
Fast | history.jsonl only |
SessionInfo[] |
sessions.listWithMeta(filter?) |
Medium | + peeks session files | SessionMeta[] |
sessions.detail(id, project?) |
Slow | Full session parse | SessionDetail |
sessions.transcript(id, project?) |
Streaming | Full session file | AsyncGenerator<TranscriptEntry> |
sessions.count(filter?) |
Fast | history.jsonl only |
number |
For codex, openai, and pi, project is optional because project/cwd is resolved from session metadata.
ch.projects.list() // ProjectInfo[]
ch.projects.memory(projectPath) // ProjectMemory | null
ch.tasks.list({ date: "2026-02-09" }) // TaskInfo[]
ch.todos.list({ date: "2026-02-09" }) // TodoItem[]
ch.plans.list({ date: "2026-02-09" }) // PlanInfo[]
ch.skills.list() // string[]
ch.skills.read("skill-name") // string (SKILL.md content)
ch.stats.get() // StatsCache | nullch.aggregate.daily("2026-02-09") // DailySummary
ch.aggregate.dailyRange("2026-02-01", "2026-02-09") // DailySummary[]
ch.aggregate.byProject({ from: "2026-02-01" }) // ProjectSummary[]
ch.aggregate.toolUsage({ date: "2026-02-09" }) // ToolUsageReport
ch.aggregate.estimateHours(sessions) // number (gap-capped)import { estimateCost, getModelPricing, MODEL_PRICING } from "agent-optic";
// Estimate cost for a session (requires SessionMeta — use listWithMeta)
const session = (await ch.sessions.listWithMeta())[0];
const cost = estimateCost(session); // USD
// Look up pricing for a model
const pricing = getModelPricing("claude-opus-4-6");
// { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 } per million tokens// Date filter (all methods)
{ date: "2026-02-09" } // Single day
{ from: "2026-02-01", to: "2026-02-09" } // Range
{ from: "2026-02-01" } // From date to today
// Session filter (extends DateFilter)
{ date: "2026-02-09", project: "my-app" } // Filter by project name| Profile | Strips |
|---|---|
local (default) |
Tool results, thinking blocks |
shareable |
+ absolute paths, home directory |
strict |
+ prompt text, emails, credential patterns, IPs |
// Use a built-in profile
const ch = createHistory({ provider: "claude", privacy: "strict" });
// Or customize
const ch = createHistory({
provider: "claude",
privacy: {
redactPrompts: false,
stripToolResults: true,
stripThinking: true,
excludeProjects: ["/work/secret-project"],
},
});# Agent-friendly list (JSONL stream)
bunx --silent agent-optic sessions --provider codex --format jsonl
# Detail for one session
bunx --silent agent-optic detail 019c9aea-484d-7200-87fd-07a545276ac4 --provider openai
# Transcript stream (limit + selected fields)
bunx --silent agent-optic transcript 019c9aea-484d-7200-87fd-07a545276ac4 --provider openai --format jsonl --limit 50 --fields timestamp,message
# Tool usage report
bunx --silent agent-optic tool-usage --provider codex --from 2026-02-01 --to 2026-02-26
# Daily summary
bunx --silent agent-optic daily --date 2026-02-09
# Raw output without envelope
bunx --silent agent-optic sessions --provider claude --date 2026-02-09 --raw--format json returns a stable envelope (schemaVersion, command, provider, generatedAt, data) by default.
Use --raw for data-only output and --format jsonl for one JSON object per line.
Common agent commands:
sessions [session-id?]list sessions (or filter to one ID)detail <session-id>full parsed sessiontranscript <session-id>transcript stream/outputtool-usageaggregated tool analytics
src/
agent-optic.ts # Main factory: createHistory()
claude-optic.ts # Backward-compatible Claude aliases
pricing.ts # Model pricing data and cost estimation
types/ # Type definitions (one file per domain)
readers/ # File readers (history, session, tasks, plans, projects, stats)
parsers/ # Session parsing, tool categorization, content extraction
aggregations/ # Daily/project/tool summaries, time estimation
privacy/ # Redaction engine, privacy profiles
utils/ # Dates, paths, JSONL streaming
cli/ # CLI entry point
examples/ # Standalone scripts showing what the data unlocks
MIT