agent-optic 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  > Reads local assistant history directories and returns structured JSON — sessions, costs, timesheets, work patterns.
4
4
 
5
- Zero-dependency, local-first TypeScript library for reading session data from provider directories such as `~/.claude/`, `~/.codex/`, `~/.cursor/`, and `~/.windsurf/`.
5
+ Zero-dependency, local-first TypeScript library for reading session data from provider directories such as `~/.claude/`, `~/.codex/`, `~/.pi/`, `~/.cursor/`, and `~/.windsurf/`.
6
6
 
7
7
  > **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](./SECURITY.md).
8
8
 
9
9
  ## Try it
10
10
 
11
11
  ```bash
12
- bunx agent-optic sessions
12
+ bunx --silent agent-optic sessions
13
13
  ```
14
14
 
15
15
  ## Features
@@ -17,7 +17,8 @@ bunx agent-optic sessions
17
17
  - **Zero runtime dependencies**
18
18
  - **No network access**
19
19
  - **Privacy by default** — strips tool results and thinking blocks
20
- - **Two-tier session loading** — fast (`history.jsonl`) or detailed (full parse)
20
+ - **Multi-tier session loading** — index, meta, detail, and transcript streaming
21
+ - **Agent-first CLI contract** — stable JSON envelope + JSONL streaming + machine-readable errors
21
22
  - **Bun-native** — `Bun.file()`, `Bun.Glob`
22
23
 
23
24
  ## Install
@@ -69,9 +70,9 @@ Timesheet: 2026-02-10 → 2026-02-14
69
70
  ==========================================================================================
70
71
  Day Date Project Hours Sessions Prompts
71
72
  ------------------------------------------------------------------------------------------
72
- Mon 2026-02-10 claude-optic 2.3 4 18
73
+ Mon 2026-02-10 agent-optic 2.3 4 18
73
74
  my-app 1.1 2 8
74
- Tue 2026-02-11 claude-optic 3.5 6 32
75
+ Tue 2026-02-11 agent-optic 3.5 6 32
75
76
  ------------------------------------------------------------------------------------------
76
77
  TOTAL 6.9 12 58
77
78
  ```
@@ -98,7 +99,8 @@ sonnet-4-5-20250929 45 8.1M 2.3M 6.2M 4.5M $4
98
99
  Export sampled prompts grouped by project as JSON — pipe to an LLM for categorization or analysis.
99
100
 
100
101
  ```bash
101
- bun examples/prompt-history.ts --from 2026-01-01 | claude "categorize these prompts by intent"
102
+ # Pipe to your preferred LLM CLI
103
+ bun examples/prompt-history.ts --from 2026-01-01 | your-llm-cli "categorize these prompts by intent"
102
104
  ```
103
105
 
104
106
  ### Session Digest
@@ -106,7 +108,8 @@ bun examples/prompt-history.ts --from 2026-01-01 | claude "categorize these prom
106
108
  Compact session summaries as JSON — first prompt, branch, model, token counts, cost, duration.
107
109
 
108
110
  ```bash
109
- bun examples/session-digest.ts --days 7 | claude "which sessions were the most productive?"
111
+ # Pipe to your preferred LLM CLI
112
+ bun examples/session-digest.ts --days 7 | your-llm-cli "which sessions were the most productive?"
110
113
  ```
111
114
 
112
115
  ### Work Patterns
@@ -114,7 +117,8 @@ bun examples/session-digest.ts --days 7 | claude "which sessions were the most p
114
117
  Aggregated work pattern metrics as JSON — hour distribution, late-night/weekend counts, longest and most expensive sessions.
115
118
 
116
119
  ```bash
117
- bun examples/work-patterns.ts | claude "analyze my work patterns and suggest improvements"
120
+ # Pipe to your preferred LLM CLI
121
+ bun examples/work-patterns.ts | your-llm-cli "analyze my work patterns and suggest improvements"
118
122
  ```
119
123
 
120
124
  ### Commit Tracker
@@ -168,14 +172,14 @@ const ch = createHistory({ provider: "claude" });
168
172
  // List today's sessions (fast — reads only history.jsonl)
169
173
  const sessions = await ch.sessions.list();
170
174
 
171
- // List with metadata (slower — peeks session files for branch/model/tokens)
175
+ // List with metadata (slower — reads session files for branch/model/tokens)
172
176
  const withMeta = await ch.sessions.listWithMeta();
173
177
 
174
- // Get full session detail
175
- const detail = await ch.sessions.detail(sessionId, projectPath);
178
+ // Get full session detail (projectPath is optional for codex/openai)
179
+ const detail = await ch.sessions.detail(sessionId);
176
180
 
177
- // Stream transcript entries
178
- for await (const entry of ch.sessions.transcript(sessionId, projectPath)) {
181
+ // Stream transcript entries (projectPath is optional for codex/openai)
182
+ for await (const entry of ch.sessions.transcript(sessionId)) {
179
183
  console.log(entry.message?.role, entry.timestamp);
180
184
  }
181
185
 
@@ -192,31 +196,35 @@ const cost = estimateCost(withMeta[0]); // USD
192
196
 
193
197
  ## API
194
198
 
199
+ The public package surface is intentionally small: `createHistory`, core types, privacy profiles, pricing helpers, and a few date/project helpers. Low-level readers, parsers, and JSONL utilities remain internal.
200
+
195
201
  ### `createHistory(config?)`
196
202
 
197
203
  ```typescript
198
204
  const ch = createHistory({
199
- provider: "claude", // "claude" | "codex" | "openai" | "cursor" | "windsurf"
205
+ provider: "claude", // "claude" | "codex" | "openai" | "pi" | "cursor" | "windsurf"
200
206
  providerDir: "~/.claude", // default: provider-specific home directory
201
207
  privacy: "local", // "local" | "shareable" | "strict" | Partial<PrivacyConfig>
202
- cache: true, // default: true
203
208
  });
204
209
  ```
205
210
 
206
211
  `openai` is currently an alias of Codex-format local history and defaults to `~/.codex`.
207
212
 
208
- `createClaudeHistory()` is still exported for backward compatibility and behaves like `createHistory({ provider: "claude" })`.
213
+ `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.
214
+
209
215
 
210
216
  ### Sessions
211
217
 
212
218
  | Method | Speed | Reads | Returns |
213
219
  |--------|-------|-------|---------|
214
220
  | `sessions.list(filter?)` | Fast | `history.jsonl` only | `SessionInfo[]` |
215
- | `sessions.listWithMeta(filter?)` | Medium | + peeks session files | `SessionMeta[]` |
216
- | `sessions.detail(id, project)` | Slow | Full session parse | `SessionDetail` |
217
- | `sessions.transcript(id, project)` | Streaming | Full session file | `AsyncGenerator<TranscriptEntry>` |
221
+ | `sessions.listWithMeta(filter?)` | Medium | + reads session files for metadata | `SessionMeta[]` |
222
+ | `sessions.detail(id, project?)` | Slow | Full session parse | `SessionDetail` |
223
+ | `sessions.transcript(id, project?)` | Streaming | Full session file | `AsyncGenerator<TranscriptEntry>` |
218
224
  | `sessions.count(filter?)` | Fast | `history.jsonl` only | `number` |
219
225
 
226
+ For `codex`, `openai`, and `pi`, `project` is optional because project/cwd is resolved from session metadata.
227
+
220
228
  ### Other Data
221
229
 
222
230
  ```typescript
@@ -294,33 +302,38 @@ const ch = createHistory({
294
302
 
295
303
  ```bash
296
304
  # Agent-friendly list (JSONL stream)
297
- bunx agent-optic sessions --provider codex --format jsonl
305
+ bunx --silent agent-optic sessions --provider codex --format jsonl
298
306
 
299
307
  # Detail for one session
300
- bunx agent-optic detail 019c9aea-484d-7200-87fd-07a545276ac4 --provider openai
308
+ bunx --silent agent-optic detail 019c9aea-484d-7200-87fd-07a545276ac4 --provider openai
301
309
 
302
310
  # Transcript stream (limit + selected fields)
303
- bunx agent-optic transcript 019c9aea-484d-7200-87fd-07a545276ac4 --provider openai --format jsonl --limit 50 --fields timestamp,message
311
+ bunx --silent agent-optic transcript 019c9aea-484d-7200-87fd-07a545276ac4 --provider openai --format jsonl --limit 50 --fields timestamp,message
304
312
 
305
313
  # Tool usage report
306
- bunx agent-optic tool-usage --provider codex --from 2026-02-01 --to 2026-02-26
314
+ bunx --silent agent-optic tool-usage --provider codex --from 2026-02-01 --to 2026-02-26
307
315
 
308
316
  # Daily summary
309
- bunx agent-optic daily --date 2026-02-09
317
+ bunx --silent agent-optic daily --date 2026-02-09
310
318
 
311
319
  # Raw output without envelope
312
- bunx agent-optic sessions --provider claude --date 2026-02-09 --raw
320
+ bunx --silent agent-optic sessions --provider claude --date 2026-02-09 --raw
313
321
  ```
314
322
 
315
323
  `--format json` returns a stable envelope (`schemaVersion`, `command`, `provider`, `generatedAt`, `data`) by default.
316
324
  Use `--raw` for data-only output and `--format jsonl` for one JSON object per line.
317
325
 
326
+ Common agent commands:
327
+ - `sessions [session-id?]` list sessions (or filter to one ID)
328
+ - `detail <session-id>` full parsed session
329
+ - `transcript <session-id>` transcript stream/output
330
+ - `tool-usage` aggregated tool analytics
331
+
318
332
  ## Architecture
319
333
 
320
334
  ```
321
335
  src/
322
- agent-optic.ts # Main factory: createHistory()
323
- claude-optic.ts # Backward-compatible Claude aliases
336
+ agent-optic.ts # Main factory and runtime API
324
337
  pricing.ts # Model pricing data and cost estimation
325
338
  types/ # Type definitions (one file per domain)
326
339
  readers/ # File readers (history, session, tasks, plans, projects, stats)
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * annotate-commits.ts — Write AI cost data as git notes on each commit in .ai-usage.jsonl
4
+ *
5
+ * Usage:
6
+ * bun examples/annotate-commits.ts [path-to-repo] # default: cwd
7
+ * bun examples/annotate-commits.ts --push # also push notes to origin
8
+ *
9
+ * After running, git log --show-notes displays AI cost inline:
10
+ *
11
+ * commit ad7ac31...
12
+ * Fix messageCount: also exclude toolUseResult carriers
13
+ *
14
+ * Notes:
15
+ * AI: $2.71 | out: 21K | cache: 5.8M | sessions: 9 | claude-sonnet-4-6
16
+ */
17
+
18
+ import { join } from "node:path";
19
+ import { existsSync } from "node:fs";
20
+
21
+ interface UsageRecord {
22
+ commit: string;
23
+ tokens: { input: number; output: number; cache_read: number; cache_write: number };
24
+ cost_usd: number;
25
+ models: string[];
26
+ session_ids: string[];
27
+ }
28
+
29
+ function fmt(n: number): string {
30
+ if (n >= 1_000_000_000) return (n / 1_000_000_000).toFixed(1) + "B";
31
+ if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M";
32
+ if (n >= 1_000) return (n / 1_000).toFixed(0) + "K";
33
+ return String(n);
34
+ }
35
+
36
+ // ── CLI args ──────────────────────────────────────────────────────────
37
+
38
+ const args = process.argv.slice(2);
39
+ const push = args.includes("--push");
40
+ const repoArg = args.find((a) => !a.startsWith("--"));
41
+ const repoPath = repoArg ? repoArg : process.cwd();
42
+ const trackingPath = join(repoPath, ".ai-usage.jsonl");
43
+
44
+ if (!existsSync(trackingPath)) {
45
+ console.error(`No .ai-usage.jsonl found in ${repoPath}`);
46
+ console.error("Run: bun examples/commit-tracker.ts init");
47
+ process.exit(1);
48
+ }
49
+
50
+ // ── Load records ──────────────────────────────────────────────────────
51
+
52
+ const records: UsageRecord[] = [];
53
+ for (const line of (await Bun.file(trackingPath).text()).trim().split("\n")) {
54
+ if (!line.trim()) continue;
55
+ try { records.push(JSON.parse(line) as UsageRecord); } catch {}
56
+ }
57
+
58
+ if (records.length === 0) {
59
+ console.log("No records found.");
60
+ process.exit(0);
61
+ }
62
+
63
+ // ── Annotate ──────────────────────────────────────────────────────────
64
+
65
+ let annotated = 0;
66
+ let skipped = 0;
67
+
68
+ for (const r of records) {
69
+ const model = r.models[0] ?? "unknown";
70
+
71
+ const note = [
72
+ `AI: $${r.cost_usd.toFixed(2)}`,
73
+ `out: ${fmt(r.tokens.output)}`,
74
+ `cache: ${fmt(r.tokens.cache_read)}`,
75
+ `sessions: ${r.session_ids.length}`,
76
+ model,
77
+ ].join(" | ");
78
+
79
+ const proc = Bun.spawn(["git", "notes", "add", "-f", "-m", note, r.commit], {
80
+ cwd: repoPath,
81
+ stdout: "pipe",
82
+ stderr: "pipe",
83
+ });
84
+ const exitCode = await proc.exited;
85
+
86
+ if (exitCode === 0) {
87
+ annotated++;
88
+ } else {
89
+ const err = await new Response(proc.stderr).text();
90
+ // "bad object" means the commit hash doesn't exist in this repo — skip silently
91
+ if (!err.includes("bad object") && !err.includes("not a valid")) {
92
+ console.warn(` skipped ${r.commit}: ${err.trim()}`);
93
+ }
94
+ skipped++;
95
+ }
96
+ }
97
+
98
+ console.log(`${annotated} commits annotated, ${skipped} skipped (not in this repo)`);
99
+ console.log(`\nView with: git log --show-notes`);
100
+
101
+ // ── Push notes ────────────────────────────────────────────────────────
102
+
103
+ if (push) {
104
+ console.log("\nPushing notes to origin...");
105
+ const proc = Bun.spawn(["git", "push", "origin", "refs/notes/commits"], {
106
+ cwd: repoPath,
107
+ stdout: "pipe",
108
+ stderr: "pipe",
109
+ });
110
+ const exitCode = await proc.exited;
111
+ const out = await new Response(proc.stdout).text();
112
+ const err = await new Response(proc.stderr).text();
113
+ if (exitCode === 0) {
114
+ console.log("Notes pushed. Others can fetch with:");
115
+ console.log(" git fetch origin refs/notes/commits:refs/notes/commits");
116
+ } else {
117
+ console.error("Push failed:", err.trim() || out.trim());
118
+ }
119
+ }