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 +40 -27
- package/examples/annotate-commits.ts +119 -0
- package/examples/branch-report.ts +566 -0
- package/examples/commit-tracker.ts +155 -34
- package/examples/cost-per-feature.ts +107 -57
- package/examples/match-git-commits.ts +5 -5
- package/examples/model-costs.ts +3 -3
- package/examples/pipe-match.ts +3 -3
- package/examples/prompt-history.ts +3 -3
- package/examples/session-digest.ts +2 -2
- package/examples/timesheet.ts +3 -3
- package/examples/ubiquitous-language.ts +184 -0
- package/examples/work-patterns.ts +2 -2
- package/package.json +13 -7
- package/skills/agent-optic/SKILL.md +302 -0
- package/src/agent-optic.ts +4 -25
- package/src/aggregations/daily.ts +1 -1
- package/src/aggregations/project.ts +0 -1
- package/src/aggregations/tools.ts +1 -1
- package/src/cli/index.ts +2 -2
- package/src/index.ts +8 -36
- package/src/parsers/session-detail.ts +11 -6
- package/src/parsers/tool-categories.ts +8 -0
- package/src/readers/history-reader.ts +7 -0
- package/src/readers/pi-session-reader.ts +466 -0
- package/src/readers/project-reader.ts +13 -4
- package/src/readers/session-reader.ts +15 -1
- package/src/readers/task-reader.ts +0 -7
- package/src/types/provider.ts +1 -0
- package/src/types/session.ts +1 -0
- package/src/types/transcript.ts +17 -0
- package/src/utils/dates.ts +4 -8
- package/src/utils/paths.ts +22 -4
- package/src/utils/providers.ts +1 -4
- package/src/claude-optic.ts +0 -7
- package/src/utils/jsonl.ts +0 -83
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
|
-
- **
|
|
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
|
|
73
|
+
Mon 2026-02-10 agent-optic 2.3 4 18
|
|
73
74
|
my-app 1.1 2 8
|
|
74
|
-
Tue 2026-02-11
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 —
|
|
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
|
|
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
|
|
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
|
-
`
|
|
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 | +
|
|
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
|
|
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
|
+
}
|