agent-optic 0.2.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +337 -0
  3. package/examples/commit-tracker.ts +389 -0
  4. package/examples/cost-per-feature.ts +182 -0
  5. package/examples/match-git-commits.ts +171 -0
  6. package/examples/model-costs.ts +131 -0
  7. package/examples/pipe-match.ts +177 -0
  8. package/examples/prompt-history.ts +119 -0
  9. package/examples/session-digest.ts +89 -0
  10. package/examples/timesheet.ts +127 -0
  11. package/examples/work-patterns.ts +124 -0
  12. package/package.json +41 -0
  13. package/src/agent-optic.ts +325 -0
  14. package/src/aggregations/daily.ts +90 -0
  15. package/src/aggregations/project.ts +71 -0
  16. package/src/aggregations/time.ts +44 -0
  17. package/src/aggregations/tools.ts +60 -0
  18. package/src/claude-optic.ts +7 -0
  19. package/src/cli/index.ts +407 -0
  20. package/src/index.ts +69 -0
  21. package/src/parsers/content-blocks.ts +58 -0
  22. package/src/parsers/session-detail.ts +323 -0
  23. package/src/parsers/tool-categories.ts +86 -0
  24. package/src/pricing.ts +62 -0
  25. package/src/privacy/config.ts +67 -0
  26. package/src/privacy/redact.ts +99 -0
  27. package/src/readers/codex-rollout-reader.ts +145 -0
  28. package/src/readers/history-reader.ts +205 -0
  29. package/src/readers/plan-reader.ts +60 -0
  30. package/src/readers/project-reader.ts +101 -0
  31. package/src/readers/session-reader.ts +280 -0
  32. package/src/readers/skill-reader.ts +28 -0
  33. package/src/readers/stats-reader.ts +12 -0
  34. package/src/readers/task-reader.ts +117 -0
  35. package/src/types/aggregations.ts +47 -0
  36. package/src/types/plan.ts +6 -0
  37. package/src/types/privacy.ts +18 -0
  38. package/src/types/project.ts +13 -0
  39. package/src/types/provider.ts +9 -0
  40. package/src/types/session.ts +56 -0
  41. package/src/types/stats.ts +15 -0
  42. package/src/types/task.ts +16 -0
  43. package/src/types/transcript.ts +36 -0
  44. package/src/utils/dates.ts +40 -0
  45. package/src/utils/jsonl.ts +83 -0
  46. package/src/utils/paths.ts +57 -0
  47. package/src/utils/providers.ts +30 -0
@@ -0,0 +1,71 @@
1
+ import type { PrivacyConfig } from "../types/privacy.js";
2
+ import type { Provider } from "../types/provider.js";
3
+ import type { ProjectSummary, SessionListFilter } from "../types/aggregations.js";
4
+ import type { SessionInfo, ToolCallSummary } from "../types/session.js";
5
+ import { readHistory } from "../readers/history-reader.js";
6
+ import { parseSessionDetail } from "../parsers/session-detail.js";
7
+ import { resolveDateRange } from "../utils/dates.js";
8
+ import { projectName } from "../utils/paths.js";
9
+ import { estimateHours } from "./time.js";
10
+
11
+ /** Build per-project summaries from session data. */
12
+ export async function buildProjectSummaries(
13
+ provider: Provider,
14
+ filter: SessionListFilter,
15
+ historyFile: string,
16
+ paths: { projectsDir: string; sessionsDir: string },
17
+ privacy: PrivacyConfig,
18
+ ): Promise<ProjectSummary[]> {
19
+ const { from, to } = resolveDateRange(filter);
20
+ const sessions = await readHistory(historyFile, from, to, privacy, {
21
+ provider,
22
+ sessionsDir: paths.sessionsDir,
23
+ });
24
+
25
+ // Group by project
26
+ const byProject = new Map<string, SessionInfo[]>();
27
+ for (const session of sessions) {
28
+ const name = session.projectName;
29
+ if (filter.project && !name.toLowerCase().includes(filter.project.toLowerCase())) continue;
30
+ const existing = byProject.get(name);
31
+ if (existing) {
32
+ existing.push(session);
33
+ } else {
34
+ byProject.set(name, [session]);
35
+ }
36
+ }
37
+
38
+ const summaries: ProjectSummary[] = [];
39
+
40
+ for (const [name, projectSessions] of byProject) {
41
+ const allToolCalls: ToolCallSummary[] = [];
42
+ const allFiles: string[] = [];
43
+ const allBranches: string[] = [];
44
+ const allModels: string[] = [];
45
+
46
+ // Parse detailed sessions for rich data
47
+ for (const session of projectSessions) {
48
+ if (session.prompts.length >= 3) {
49
+ const detail = await parseSessionDetail(provider, session, paths, privacy);
50
+ allToolCalls.push(...detail.toolCalls);
51
+ allFiles.push(...detail.filesReferenced);
52
+ if (detail.gitBranch) allBranches.push(detail.gitBranch);
53
+ if (detail.model) allModels.push(detail.model);
54
+ }
55
+ }
56
+
57
+ summaries.push({
58
+ project: projectSessions[0].project,
59
+ projectName: name,
60
+ sessionCount: projectSessions.length,
61
+ promptCount: projectSessions.reduce((sum, s) => sum + s.prompts.length, 0),
62
+ estimatedHours: estimateHours(projectSessions),
63
+ branches: [...new Set(allBranches)],
64
+ filesReferenced: [...new Set(allFiles)],
65
+ toolCalls: allToolCalls,
66
+ models: [...new Set(allModels)],
67
+ });
68
+ }
69
+
70
+ return summaries.sort((a, b) => b.promptCount - a.promptCount);
71
+ }
@@ -0,0 +1,44 @@
1
+ import type { SessionInfo } from "../types/session.js";
2
+
3
+ /** Gap cap in ms — if gap between consecutive prompts exceeds this, cap it. */
4
+ const GAP_CAP_MS = 15 * 60 * 1000; // 15 minutes
5
+
6
+ /**
7
+ * Estimate hours of active work from session timestamp data.
8
+ * Merges all timestamps across sessions into a single sorted timeline,
9
+ * then applies gap-capping. This deduplicates overlapping intervals so
10
+ * concurrent sessions (e.g. multiple terminal tabs) share wall-clock time
11
+ * instead of stacking it.
12
+ */
13
+ export function estimateHours(sessions: SessionInfo[]): number {
14
+ if (sessions.length === 0) return 0;
15
+
16
+ // Merge all timestamps across sessions into a single timeline
17
+ const allTimestamps: number[] = [];
18
+
19
+ for (const session of sessions) {
20
+ if (session.promptTimestamps.length > 0) {
21
+ allTimestamps.push(...session.promptTimestamps);
22
+ } else {
23
+ // Sessions with no prompt timestamps: use start/end
24
+ const ts = [session.timeRange.start, session.timeRange.end].filter(t => t > 0);
25
+ allTimestamps.push(...ts);
26
+ }
27
+ }
28
+
29
+ if (allTimestamps.length === 0) return 0;
30
+
31
+ allTimestamps.sort((a, b) => a - b);
32
+
33
+ if (allTimestamps.length === 1) {
34
+ return 5 / 60; // Single timestamp → 5 minutes
35
+ }
36
+
37
+ let totalMs = 0;
38
+ for (let i = 1; i < allTimestamps.length; i++) {
39
+ const gap = allTimestamps[i] - allTimestamps[i - 1];
40
+ totalMs += Math.min(gap, GAP_CAP_MS);
41
+ }
42
+
43
+ return totalMs / (1000 * 60 * 60);
44
+ }
@@ -0,0 +1,60 @@
1
+ import type { PrivacyConfig } from "../types/privacy.js";
2
+ import type { Provider } from "../types/provider.js";
3
+ import type { ToolUsageReport, SessionListFilter } from "../types/aggregations.js";
4
+ import type { ToolCategory, ToolCallSummary } from "../types/session.js";
5
+ import { readHistory } from "../readers/history-reader.js";
6
+ import { parseSessionDetail } from "../parsers/session-detail.js";
7
+ import { resolveDateRange } from "../utils/dates.js";
8
+
9
+ /** Build a tool usage report from session data. */
10
+ export async function buildToolUsageReport(
11
+ provider: Provider,
12
+ filter: SessionListFilter,
13
+ historyFile: string,
14
+ paths: { projectsDir: string; sessionsDir: string },
15
+ privacy: PrivacyConfig,
16
+ ): Promise<ToolUsageReport> {
17
+ const { from, to } = resolveDateRange(filter);
18
+ const sessions = await readHistory(historyFile, from, to, privacy, {
19
+ provider,
20
+ sessionsDir: paths.sessionsDir,
21
+ });
22
+
23
+ const byTool = new Map<string, number>();
24
+ const byCategory = new Map<ToolCategory, number>();
25
+ const fileCounts = new Map<string, number>();
26
+ const commandCounts = new Map<string, number>();
27
+ let total = 0;
28
+
29
+ for (const session of sessions) {
30
+ if (session.prompts.length < 2) continue; // skip trivial sessions
31
+ const detail = await parseSessionDetail(provider, session, paths, privacy);
32
+
33
+ for (const tc of detail.toolCalls) {
34
+ byTool.set(tc.name, (byTool.get(tc.name) ?? 0) + 1);
35
+ byCategory.set(tc.category, (byCategory.get(tc.category) ?? 0) + 1);
36
+ total++;
37
+
38
+ if (tc.target) {
39
+ if (tc.category === "file_read" || tc.category === "file_write") {
40
+ fileCounts.set(tc.target, (fileCounts.get(tc.target) ?? 0) + 1);
41
+ }
42
+ if (tc.category === "shell") {
43
+ commandCounts.set(tc.target, (commandCounts.get(tc.target) ?? 0) + 1);
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ const topFiles = [...fileCounts.entries()]
50
+ .sort((a, b) => b[1] - a[1])
51
+ .slice(0, 20)
52
+ .map(([path, count]) => ({ path, count }));
53
+
54
+ const topCommands = [...commandCounts.entries()]
55
+ .sort((a, b) => b[1] - a[1])
56
+ .slice(0, 20)
57
+ .map(([command, count]) => ({ command, count }));
58
+
59
+ return { byTool, byCategory, topFiles, topCommands, total };
60
+ }
@@ -0,0 +1,7 @@
1
+ export { createClaudeHistory, createHistory } from "./agent-optic.js";
2
+ export type {
3
+ ClaudeHistory,
4
+ ClaudeHistoryConfig,
5
+ History,
6
+ HistoryConfig,
7
+ } from "./agent-optic.js";
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { createHistory } from "../agent-optic.js";
4
+ import type { PrivacyProfile } from "../types/privacy.js";
5
+ import type { Provider } from "../types/provider.js";
6
+ import { today } from "../utils/dates.js";
7
+ import { defaultProviderDir, isProvider } from "../utils/providers.js";
8
+
9
+ const SCHEMA_VERSION = "1.0";
10
+
11
+ type OutputFormat = "json" | "jsonl";
12
+
13
+ const HELP = `agent-optic — Read AI assistant session data from local provider directories
14
+
15
+ USAGE
16
+ agent-optic <command> [options]
17
+
18
+ COMMANDS
19
+ sessions <optional-id> List sessions with metadata
20
+ detail <session-id> Show full detail for one session
21
+ transcript <session-id> Stream/print transcript entries
22
+ tool-usage Show aggregated tool usage
23
+ projects List all projects
24
+ stats Show pre-computed stats
25
+ daily Show daily summary
26
+ export Export session data with privacy controls
27
+
28
+ OPTIONS
29
+ --date YYYY-MM-DD Filter to specific date (default: today)
30
+ --from YYYY-MM-DD Start of date range
31
+ --to YYYY-MM-DD End of date range
32
+ --project <name> Filter by project name
33
+ --provider <name> Data provider: claude (default), codex, openai, cursor, windsurf
34
+ --provider-dir <path> Override provider data directory (default: ~/.<provider>)
35
+ --privacy <profile> Privacy profile: local (default), shareable, strict
36
+ --format <mode> Output mode: json (default), jsonl
37
+ --fields <a,b,c> Select object fields (top-level)
38
+ --limit <n> Limit array/stream length
39
+ --pretty Pretty-print JSON output
40
+ --raw Disable output envelope (data only)
41
+ --help Show this help
42
+
43
+ EXAMPLES
44
+ agent-optic sessions --provider codex --format jsonl
45
+ agent-optic detail 019c9aea-484d-7200-87fd-07a545276ac4 --provider openai
46
+ agent-optic transcript 019c9aea-484d-7200-87fd-07a545276ac4 --provider openai --format jsonl --limit 50
47
+ agent-optic tool-usage --provider codex --from 2026-02-01 --to 2026-02-26
48
+ agent-optic sessions --provider codex --date 2026-02-09
49
+ agent-optic sessions --provider openai --date 2026-02-09
50
+
51
+ SECURITY
52
+ Provider home directories contain highly sensitive data including API keys, source code,
53
+ and personal information. See SECURITY.md for details.
54
+ `;
55
+
56
+ interface CliArgs {
57
+ command: string;
58
+ commandArg?: string;
59
+ date?: string;
60
+ from?: string;
61
+ to?: string;
62
+ project?: string;
63
+ provider: Provider;
64
+ providerDir?: string;
65
+ privacy: PrivacyProfile;
66
+ format: OutputFormat;
67
+ fields?: string[];
68
+ limit?: number;
69
+ pretty: boolean;
70
+ raw: boolean;
71
+ help: boolean;
72
+ }
73
+
74
+ class CliError extends Error {
75
+ constructor(
76
+ public code: string,
77
+ message: string,
78
+ public exitCode = 1,
79
+ public details?: Record<string, unknown>,
80
+ ) {
81
+ super(message);
82
+ }
83
+ }
84
+
85
+ function parseArgs(args: string[]): CliArgs {
86
+ const result: CliArgs = {
87
+ command: "",
88
+ provider: "claude",
89
+ privacy: "local",
90
+ format: "json",
91
+ pretty: false,
92
+ raw: false,
93
+ help: false,
94
+ };
95
+
96
+ let i = 0;
97
+ while (i < args.length) {
98
+ const arg = args[i];
99
+
100
+ if (arg === "--help" || arg === "-h") {
101
+ result.help = true;
102
+ } else if (arg === "--date" && args[i + 1]) {
103
+ result.date = args[++i];
104
+ } else if (arg === "--from" && args[i + 1]) {
105
+ result.from = args[++i];
106
+ } else if (arg === "--to" && args[i + 1]) {
107
+ result.to = args[++i];
108
+ } else if (arg === "--project" && args[i + 1]) {
109
+ result.project = args[++i];
110
+ } else if (arg === "--provider" && args[i + 1]) {
111
+ result.provider = args[++i] as Provider;
112
+ } else if (arg === "--provider-dir" && args[i + 1]) {
113
+ result.providerDir = args[++i];
114
+ } else if (arg === "--privacy" && args[i + 1]) {
115
+ result.privacy = args[++i] as PrivacyProfile;
116
+ } else if (arg === "--format" && args[i + 1]) {
117
+ result.format = args[++i] as OutputFormat;
118
+ } else if (arg === "--fields" && args[i + 1]) {
119
+ result.fields = args[++i]
120
+ .split(",")
121
+ .map((f) => f.trim())
122
+ .filter(Boolean);
123
+ } else if (arg === "--limit" && args[i + 1]) {
124
+ const parsed = Number.parseInt(args[++i], 10);
125
+ if (Number.isFinite(parsed) && parsed > 0) {
126
+ result.limit = parsed;
127
+ }
128
+ } else if (arg === "--pretty") {
129
+ result.pretty = true;
130
+ } else if (arg === "--raw") {
131
+ result.raw = true;
132
+ } else if (!arg.startsWith("-") && !result.command) {
133
+ result.command = arg;
134
+ } else if (!arg.startsWith("-") && !result.commandArg) {
135
+ result.commandArg = arg;
136
+ }
137
+
138
+ i++;
139
+ }
140
+
141
+ return result;
142
+ }
143
+
144
+ function applyFieldSelection(data: unknown, fields?: string[]): unknown {
145
+ if (!fields || fields.length === 0) return data;
146
+
147
+ if (Array.isArray(data)) {
148
+ return data.map((item) => applyFieldSelection(item, fields));
149
+ }
150
+
151
+ if (!data || typeof data !== "object") return data;
152
+ const obj = data as Record<string, unknown>;
153
+ const selected: Record<string, unknown> = {};
154
+ for (const field of fields) {
155
+ if (field in obj) selected[field] = obj[field];
156
+ }
157
+ return selected;
158
+ }
159
+
160
+ function applyLimit(data: unknown, limit?: number): unknown {
161
+ if (!limit) return data;
162
+ if (Array.isArray(data)) return data.slice(0, limit);
163
+ return data;
164
+ }
165
+
166
+ function writeOutput(
167
+ command: string,
168
+ provider: Provider,
169
+ data: unknown,
170
+ args: CliArgs,
171
+ ): void {
172
+ const transformed = applyLimit(applyFieldSelection(data, args.fields), args.limit);
173
+ const generatedAt = new Date().toISOString();
174
+
175
+ if (args.format === "json") {
176
+ const payload = args.raw
177
+ ? transformed
178
+ : {
179
+ schemaVersion: SCHEMA_VERSION,
180
+ command,
181
+ provider,
182
+ generatedAt,
183
+ data: transformed,
184
+ };
185
+ console.log(
186
+ JSON.stringify(payload, mapReplacer, args.pretty ? 2 : 0),
187
+ );
188
+ return;
189
+ }
190
+
191
+ const rows = Array.isArray(transformed) ? transformed : [transformed];
192
+ for (const row of rows) {
193
+ const payload = args.raw
194
+ ? row
195
+ : {
196
+ schemaVersion: SCHEMA_VERSION,
197
+ command,
198
+ provider,
199
+ generatedAt,
200
+ data: row,
201
+ };
202
+ console.log(JSON.stringify(payload, mapReplacer));
203
+ }
204
+ }
205
+
206
+ /** JSON.stringify replacer that converts Maps to plain objects. */
207
+ function mapReplacer(_key: string, value: unknown): unknown {
208
+ if (value instanceof Map) {
209
+ return Object.fromEntries(value);
210
+ }
211
+ return value;
212
+ }
213
+
214
+ function printError(error: CliError, args?: CliArgs): void {
215
+ const format = args?.format ?? "json";
216
+ const payload = {
217
+ schemaVersion: SCHEMA_VERSION,
218
+ error: {
219
+ code: error.code,
220
+ message: error.message,
221
+ details: error.details,
222
+ },
223
+ };
224
+ const text =
225
+ format === "json" && args?.pretty
226
+ ? JSON.stringify(payload, null, 2)
227
+ : JSON.stringify(payload);
228
+ console.error(text);
229
+ }
230
+
231
+ function assertValidArgs(args: CliArgs): void {
232
+ if (!["local", "shareable", "strict"].includes(args.privacy)) {
233
+ throw new CliError(
234
+ "INVALID_PRIVACY_PROFILE",
235
+ `Invalid privacy profile: ${args.privacy}. Use: local, shareable, strict`,
236
+ );
237
+ }
238
+
239
+ if (!isProvider(args.provider)) {
240
+ throw new CliError(
241
+ "INVALID_PROVIDER",
242
+ `Invalid provider: ${args.provider}. Use: claude, codex, openai, cursor, windsurf`,
243
+ );
244
+ }
245
+
246
+ if (!["json", "jsonl"].includes(args.format)) {
247
+ throw new CliError(
248
+ "INVALID_FORMAT",
249
+ `Invalid format: ${args.format}. Use: json, jsonl`,
250
+ );
251
+ }
252
+ }
253
+
254
+ async function run(args: CliArgs): Promise<void> {
255
+ if (args.help || !args.command) {
256
+ console.log(HELP);
257
+ process.exit(args.help ? 0 : 1);
258
+ }
259
+
260
+ assertValidArgs(args);
261
+
262
+ const providerDir = args.providerDir ?? defaultProviderDir(args.provider);
263
+ const ch = createHistory({
264
+ provider: args.provider,
265
+ providerDir,
266
+ privacy: args.privacy,
267
+ });
268
+
269
+ const filter = {
270
+ date: args.date,
271
+ from: args.from,
272
+ to: args.to,
273
+ project: args.project,
274
+ };
275
+
276
+ switch (args.command) {
277
+ case "sessions": {
278
+ const sessionsFilter =
279
+ args.commandArg && !args.date && !args.from && !args.to
280
+ ? { ...filter, from: "2000-01-01", to: "2099-12-31" }
281
+ : filter;
282
+ let sessions = await ch.sessions.listWithMeta(sessionsFilter);
283
+ if (args.commandArg) {
284
+ sessions = sessions.filter((s) => s.sessionId === args.commandArg);
285
+ }
286
+ writeOutput("sessions", args.provider, sessions, args);
287
+ return;
288
+ }
289
+
290
+ case "detail": {
291
+ if (!args.commandArg) {
292
+ throw new CliError(
293
+ "MISSING_ARGUMENT",
294
+ "Missing session ID. Usage: agent-optic detail <session-id>",
295
+ );
296
+ }
297
+ const detail = await ch.sessions.detail(args.commandArg, args.project);
298
+ writeOutput("detail", args.provider, detail, args);
299
+ return;
300
+ }
301
+
302
+ case "transcript": {
303
+ if (!args.commandArg) {
304
+ throw new CliError(
305
+ "MISSING_ARGUMENT",
306
+ "Missing session ID. Usage: agent-optic transcript <session-id>",
307
+ );
308
+ }
309
+
310
+ if (args.format === "jsonl") {
311
+ const generatedAt = new Date().toISOString();
312
+ let count = 0;
313
+ for await (const entry of ch.sessions.transcript(args.commandArg, args.project)) {
314
+ if (args.limit && count >= args.limit) break;
315
+ const transformed = applyFieldSelection(entry, args.fields);
316
+ const payload = args.raw
317
+ ? transformed
318
+ : {
319
+ schemaVersion: SCHEMA_VERSION,
320
+ command: "transcript",
321
+ provider: args.provider,
322
+ generatedAt,
323
+ data: transformed,
324
+ };
325
+ console.log(JSON.stringify(payload, mapReplacer));
326
+ count++;
327
+ }
328
+ return;
329
+ }
330
+
331
+ const entries: unknown[] = [];
332
+ for await (const entry of ch.sessions.transcript(args.commandArg, args.project)) {
333
+ entries.push(entry);
334
+ if (args.limit && entries.length >= args.limit) break;
335
+ }
336
+ writeOutput("transcript", args.provider, entries, args);
337
+ return;
338
+ }
339
+
340
+ case "tool-usage": {
341
+ const usage = await ch.aggregate.toolUsage(filter);
342
+ writeOutput("tool-usage", args.provider, usage, args);
343
+ return;
344
+ }
345
+
346
+ case "projects": {
347
+ const projects = await ch.projects.list();
348
+ writeOutput("projects", args.provider, projects, args);
349
+ return;
350
+ }
351
+
352
+ case "stats": {
353
+ const stats = await ch.stats.get();
354
+ if (!stats) {
355
+ throw new CliError(
356
+ "STATS_NOT_FOUND",
357
+ `No stats cache found at ${providerDir}/stats-cache.json`,
358
+ );
359
+ }
360
+ writeOutput("stats", args.provider, stats, args);
361
+ return;
362
+ }
363
+
364
+ case "daily": {
365
+ const date = args.date ?? today();
366
+ const summary = await ch.aggregate.daily(date);
367
+ writeOutput("daily", args.provider, summary, args);
368
+ return;
369
+ }
370
+
371
+ case "export": {
372
+ const date = args.date;
373
+ const from = args.from ?? date ?? today();
374
+ const to = args.to ?? date ?? today();
375
+ const summaries = await ch.aggregate.dailyRange(from, to);
376
+ writeOutput("export", args.provider, summaries, args);
377
+ return;
378
+ }
379
+
380
+ default:
381
+ throw new CliError(
382
+ "UNKNOWN_COMMAND",
383
+ `Unknown command: ${args.command}`,
384
+ 2,
385
+ );
386
+ }
387
+ }
388
+
389
+ async function main() {
390
+ const args = parseArgs(process.argv.slice(2));
391
+ try {
392
+ await run(args);
393
+ } catch (err) {
394
+ if (err instanceof CliError) {
395
+ printError(err, args);
396
+ process.exit(err.exitCode);
397
+ }
398
+ const fallback = new CliError(
399
+ "INTERNAL_ERROR",
400
+ err instanceof Error ? err.message : "Unknown error",
401
+ );
402
+ printError(fallback, args);
403
+ process.exit(fallback.exitCode);
404
+ }
405
+ }
406
+
407
+ main();
package/src/index.ts ADDED
@@ -0,0 +1,69 @@
1
+ // Main factory
2
+ export { createHistory, createClaudeHistory } from "./agent-optic.js";
3
+ export type { History, HistoryConfig, ClaudeHistory, ClaudeHistoryConfig } from "./agent-optic.js";
4
+
5
+ // Provider types and utils
6
+ export type { Provider } from "./types/provider.js";
7
+ export { SUPPORTED_PROVIDERS } from "./types/provider.js";
8
+
9
+ // Types
10
+ export type {
11
+ HistoryEntry,
12
+ SessionInfo,
13
+ SessionMeta,
14
+ SessionDetail,
15
+ ToolCategory,
16
+ ToolCallSummary,
17
+ } from "./types/session.js";
18
+
19
+ export type { ContentBlock, TranscriptEntry } from "./types/transcript.js";
20
+
21
+ export type { TaskInfo, TodoItem } from "./types/task.js";
22
+
23
+ export type { PlanInfo } from "./types/plan.js";
24
+
25
+ export type { ProjectInfo, ProjectMemory } from "./types/project.js";
26
+
27
+ export type { StatsCache } from "./types/stats.js";
28
+
29
+ export type {
30
+ DailySummary,
31
+ ProjectSummary,
32
+ ToolUsageReport,
33
+ DateFilter,
34
+ SessionListFilter,
35
+ } from "./types/aggregations.js";
36
+
37
+ export type { PrivacyConfig, PrivacyProfile } from "./types/privacy.js";
38
+
39
+ // Privacy profiles (runtime values, not just types)
40
+ export { PRIVACY_PROFILES, resolvePrivacyConfig } from "./privacy/config.js";
41
+
42
+ // Utilities (for advanced users)
43
+ export {
44
+ encodeProjectPath,
45
+ decodeProjectPath,
46
+ projectName,
47
+ providerPaths,
48
+ claudePaths,
49
+ } from "./utils/paths.js";
50
+ export {
51
+ DEFAULT_PROVIDER,
52
+ defaultProviderDir,
53
+ providerHomeDirName,
54
+ isProvider,
55
+ } from "./utils/providers.js";
56
+ export { toLocalDate, today, formatTime, resolveDateRange } from "./utils/dates.js";
57
+ export { parseJsonl, streamJsonl, peekJsonl, readJsonl } from "./utils/jsonl.js";
58
+
59
+ // Parsers (for advanced users building custom pipelines)
60
+ export { parseSessionDetail, parseSessions } from "./parsers/session-detail.js";
61
+ export { categorizeToolName, toolDisplayName } from "./parsers/tool-categories.js";
62
+ export { extractText, extractToolCalls, extractFilePaths, countThinkingBlocks } from "./parsers/content-blocks.js";
63
+
64
+ // Pricing
65
+ export type { ModelPricing } from "./pricing.js";
66
+ export { MODEL_PRICING, getModelPricing, estimateCost, setPricing } from "./pricing.js";
67
+
68
+ // Readers (for advanced users)
69
+ export { readProjectMemories } from "./readers/project-reader.js";