oh-my-claude-sisyphus 3.4.2 → 3.5.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 +45 -0
- package/commands/cancel-ultraqa.md +1 -1
- package/dist/__tests__/analytics/analytics-summary.test.d.ts +2 -0
- package/dist/__tests__/analytics/analytics-summary.test.d.ts.map +1 -0
- package/dist/__tests__/analytics/analytics-summary.test.js +267 -0
- package/dist/__tests__/analytics/analytics-summary.test.js.map +1 -0
- package/dist/__tests__/analytics/backfill-dedup.test.d.ts +2 -0
- package/dist/__tests__/analytics/backfill-dedup.test.d.ts.map +1 -0
- package/dist/__tests__/analytics/backfill-dedup.test.js +179 -0
- package/dist/__tests__/analytics/backfill-dedup.test.js.map +1 -0
- package/dist/__tests__/analytics/backfill-engine.test.d.ts +2 -0
- package/dist/__tests__/analytics/backfill-engine.test.d.ts.map +1 -0
- package/dist/__tests__/analytics/backfill-engine.test.js +362 -0
- package/dist/__tests__/analytics/backfill-engine.test.js.map +1 -0
- package/dist/__tests__/analytics/cost-estimator.test.d.ts +2 -0
- package/dist/__tests__/analytics/cost-estimator.test.d.ts.map +1 -0
- package/dist/__tests__/analytics/cost-estimator.test.js +212 -0
- package/dist/__tests__/analytics/cost-estimator.test.js.map +1 -0
- package/dist/__tests__/analytics/output-estimator.test.d.ts +2 -0
- package/dist/__tests__/analytics/output-estimator.test.d.ts.map +1 -0
- package/dist/__tests__/analytics/output-estimator.test.js +106 -0
- package/dist/__tests__/analytics/output-estimator.test.js.map +1 -0
- package/dist/__tests__/analytics/token-extractor.test.d.ts +2 -0
- package/dist/__tests__/analytics/token-extractor.test.d.ts.map +1 -0
- package/dist/__tests__/analytics/token-extractor.test.js +121 -0
- package/dist/__tests__/analytics/token-extractor.test.js.map +1 -0
- package/dist/__tests__/analytics/transcript-parser.test.d.ts +2 -0
- package/dist/__tests__/analytics/transcript-parser.test.d.ts.map +1 -0
- package/dist/__tests__/analytics/transcript-parser.test.js +285 -0
- package/dist/__tests__/analytics/transcript-parser.test.js.map +1 -0
- package/dist/__tests__/analytics/transcript-scanner.test.d.ts +2 -0
- package/dist/__tests__/analytics/transcript-scanner.test.d.ts.map +1 -0
- package/dist/__tests__/analytics/transcript-scanner.test.js +401 -0
- package/dist/__tests__/analytics/transcript-scanner.test.js.map +1 -0
- package/dist/__tests__/analytics/transcript-token-extractor.test.d.ts +2 -0
- package/dist/__tests__/analytics/transcript-token-extractor.test.d.ts.map +1 -0
- package/dist/__tests__/analytics/transcript-token-extractor.test.js +175 -0
- package/dist/__tests__/analytics/transcript-token-extractor.test.js.map +1 -0
- package/dist/__tests__/hud/auto-tracking.integration.test.d.ts +2 -0
- package/dist/__tests__/hud/auto-tracking.integration.test.d.ts.map +1 -0
- package/dist/__tests__/hud/auto-tracking.integration.test.js +12 -0
- package/dist/__tests__/hud/auto-tracking.integration.test.js.map +1 -0
- package/dist/__tests__/learner/auto-learner.test.d.ts +7 -0
- package/dist/__tests__/learner/auto-learner.test.d.ts.map +1 -0
- package/dist/__tests__/learner/auto-learner.test.js +507 -0
- package/dist/__tests__/learner/auto-learner.test.js.map +1 -0
- package/dist/__tests__/learner/matcher.test.d.ts +2 -0
- package/dist/__tests__/learner/matcher.test.d.ts.map +1 -0
- package/dist/__tests__/learner/matcher.test.js +330 -0
- package/dist/__tests__/learner/matcher.test.js.map +1 -0
- package/dist/analytics/analytics-summary.d.ts +47 -0
- package/dist/analytics/analytics-summary.d.ts.map +1 -0
- package/dist/analytics/analytics-summary.js +171 -0
- package/dist/analytics/analytics-summary.js.map +1 -0
- package/dist/analytics/backfill-dedup.d.ts +49 -0
- package/dist/analytics/backfill-dedup.d.ts.map +1 -0
- package/dist/analytics/backfill-dedup.js +115 -0
- package/dist/analytics/backfill-dedup.js.map +1 -0
- package/dist/analytics/backfill-engine.d.ts +59 -0
- package/dist/analytics/backfill-engine.d.ts.map +1 -0
- package/dist/analytics/backfill-engine.js +172 -0
- package/dist/analytics/backfill-engine.js.map +1 -0
- package/dist/analytics/index.d.ts +8 -0
- package/dist/analytics/index.d.ts.map +1 -1
- package/dist/analytics/index.js +10 -0
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/output-estimator.d.ts +26 -0
- package/dist/analytics/output-estimator.d.ts.map +1 -0
- package/dist/analytics/output-estimator.js +61 -0
- package/dist/analytics/output-estimator.js.map +1 -0
- package/dist/analytics/query-engine.d.ts.map +1 -1
- package/dist/analytics/query-engine.js +3 -2
- package/dist/analytics/query-engine.js.map +1 -1
- package/dist/analytics/token-extractor.d.ts +31 -0
- package/dist/analytics/token-extractor.d.ts.map +1 -0
- package/dist/analytics/token-extractor.js +57 -0
- package/dist/analytics/token-extractor.js.map +1 -0
- package/dist/analytics/token-tracker.d.ts +7 -1
- package/dist/analytics/token-tracker.d.ts.map +1 -1
- package/dist/analytics/token-tracker.js +94 -18
- package/dist/analytics/token-tracker.js.map +1 -1
- package/dist/analytics/transcript-parser.d.ts +42 -0
- package/dist/analytics/transcript-parser.d.ts.map +1 -0
- package/dist/analytics/transcript-parser.js +90 -0
- package/dist/analytics/transcript-parser.js.map +1 -0
- package/dist/analytics/transcript-scanner.d.ts +50 -0
- package/dist/analytics/transcript-scanner.d.ts.map +1 -0
- package/dist/analytics/transcript-scanner.js +149 -0
- package/dist/analytics/transcript-scanner.js.map +1 -0
- package/dist/analytics/transcript-token-extractor.d.ts +35 -0
- package/dist/analytics/transcript-token-extractor.d.ts.map +1 -0
- package/dist/analytics/transcript-token-extractor.js +136 -0
- package/dist/analytics/transcript-token-extractor.js.map +1 -0
- package/dist/analytics/types.d.ts +65 -0
- package/dist/analytics/types.d.ts.map +1 -1
- package/dist/analytics/types.js.map +1 -1
- package/dist/cli/analytics.js +26 -1
- package/dist/cli/analytics.js.map +1 -1
- package/dist/cli/commands/backfill.d.ts +15 -0
- package/dist/cli/commands/backfill.d.ts.map +1 -0
- package/dist/cli/commands/backfill.js +146 -0
- package/dist/cli/commands/backfill.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +1 -0
- package/dist/cli/commands/stats.d.ts.map +1 -1
- package/dist/cli/commands/stats.js +67 -31
- package/dist/cli/commands/stats.js.map +1 -1
- package/dist/cli/components/CostDashboard.d.ts +15 -0
- package/dist/cli/components/CostDashboard.d.ts.map +1 -0
- package/dist/cli/components/CostDashboard.js +15 -0
- package/dist/cli/components/CostDashboard.js.map +1 -0
- package/dist/cli/components/LiveStats.d.ts +16 -0
- package/dist/cli/components/LiveStats.d.ts.map +1 -0
- package/dist/cli/components/LiveStats.js +16 -0
- package/dist/cli/components/LiveStats.js.map +1 -0
- package/dist/cli/components/SessionBrowser.d.ts +14 -0
- package/dist/cli/components/SessionBrowser.d.ts.map +1 -0
- package/dist/cli/components/SessionBrowser.js +14 -0
- package/dist/cli/components/SessionBrowser.js.map +1 -0
- package/dist/cli/index.js +159 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/tui.d.ts +21 -0
- package/dist/cli/tui.d.ts.map +1 -0
- package/dist/cli/tui.js +21 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/hooks/learner/auto-invoke.d.ts +82 -0
- package/dist/hooks/learner/auto-invoke.d.ts.map +1 -0
- package/dist/hooks/learner/auto-invoke.js +234 -0
- package/dist/hooks/learner/auto-invoke.js.map +1 -0
- package/dist/hooks/learner/auto-learner.d.ts +55 -0
- package/dist/hooks/learner/auto-learner.d.ts.map +1 -0
- package/dist/hooks/learner/auto-learner.js +361 -0
- package/dist/hooks/learner/auto-learner.js.map +1 -0
- package/dist/hooks/learner/index.d.ts +3 -0
- package/dist/hooks/learner/index.d.ts.map +1 -1
- package/dist/hooks/learner/index.js +4 -0
- package/dist/hooks/learner/index.js.map +1 -1
- package/dist/hooks/learner/matcher.d.ts +40 -0
- package/dist/hooks/learner/matcher.d.ts.map +1 -0
- package/dist/hooks/learner/matcher.js +230 -0
- package/dist/hooks/learner/matcher.js.map +1 -0
- package/dist/hud/analytics-display.d.ts +16 -0
- package/dist/hud/analytics-display.d.ts.map +1 -1
- package/dist/hud/analytics-display.js +42 -0
- package/dist/hud/analytics-display.js.map +1 -1
- package/dist/hud/index.js +90 -3
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +27 -1
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/types.d.ts +2 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/docs/ANALYTICS-SYSTEM.md +150 -0
- package/hooks/keyword-detector.sh +1 -1
- package/package.json +1 -1
- package/scripts/keyword-detector.mjs +1 -1
- package/scripts/persistent-mode.mjs +1 -1
- package/scripts/test-mutual-exclusion.ts +4 -4
- package/scripts/test-remember-tags.ts +6 -6
- package/scripts/test-session-injection.ts +4 -4
- package/skills/cancel-ultraqa/SKILL.md +1 -1
- package/skills/local-skills-setup/SKILL.md +465 -0
- package/skills/omc-setup/SKILL.md +30 -5
- package/skills/skill/SKILL.md +406 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { estimateOutputTokens } from './output-estimator.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extract token usage from StatuslineStdin and calculate delta from previous snapshot.
|
|
4
|
+
*/
|
|
5
|
+
export function extractTokens(stdin, previousSnapshot, modelName, agentName) {
|
|
6
|
+
const currentUsage = stdin.context_window.current_usage;
|
|
7
|
+
if (!currentUsage) {
|
|
8
|
+
return createEmptyExtraction(modelName, agentName);
|
|
9
|
+
}
|
|
10
|
+
// Calculate deltas
|
|
11
|
+
const inputDelta = previousSnapshot
|
|
12
|
+
? currentUsage.input_tokens - previousSnapshot.inputTokens
|
|
13
|
+
: currentUsage.input_tokens;
|
|
14
|
+
const cacheDelta = previousSnapshot
|
|
15
|
+
? currentUsage.cache_creation_input_tokens - previousSnapshot.cacheCreationTokens
|
|
16
|
+
: currentUsage.cache_creation_input_tokens;
|
|
17
|
+
const cacheReadDelta = previousSnapshot
|
|
18
|
+
? currentUsage.cache_read_input_tokens - previousSnapshot.cacheReadTokens
|
|
19
|
+
: currentUsage.cache_read_input_tokens;
|
|
20
|
+
// Estimate output tokens (from output-estimator.ts)
|
|
21
|
+
const outputTokens = estimateOutputTokens(inputDelta, modelName);
|
|
22
|
+
return {
|
|
23
|
+
inputTokens: Math.max(0, inputDelta),
|
|
24
|
+
outputTokens,
|
|
25
|
+
cacheCreationTokens: Math.max(0, cacheDelta),
|
|
26
|
+
cacheReadTokens: Math.max(0, cacheReadDelta),
|
|
27
|
+
modelName,
|
|
28
|
+
agentName,
|
|
29
|
+
isEstimated: true,
|
|
30
|
+
timestamp: new Date().toISOString()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create current snapshot for next delta calculation.
|
|
35
|
+
*/
|
|
36
|
+
export function createSnapshot(stdin) {
|
|
37
|
+
const usage = stdin.context_window.current_usage;
|
|
38
|
+
return {
|
|
39
|
+
inputTokens: usage?.input_tokens ?? 0,
|
|
40
|
+
cacheCreationTokens: usage?.cache_creation_input_tokens ?? 0,
|
|
41
|
+
cacheReadTokens: usage?.cache_read_input_tokens ?? 0,
|
|
42
|
+
timestamp: new Date().toISOString()
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function createEmptyExtraction(modelName, agentName) {
|
|
46
|
+
return {
|
|
47
|
+
inputTokens: 0,
|
|
48
|
+
outputTokens: 0,
|
|
49
|
+
cacheCreationTokens: 0,
|
|
50
|
+
cacheReadTokens: 0,
|
|
51
|
+
modelName,
|
|
52
|
+
agentName,
|
|
53
|
+
isEstimated: true,
|
|
54
|
+
timestamp: new Date().toISOString()
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=token-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-extractor.js","sourceRoot":"","sources":["../../src/analytics/token-extractor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AA0B7D;;GAEG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAsB,EACtB,gBAAsC,EACtC,SAAiB,EACjB,SAAkB;IAElB,MAAM,YAAY,GAAG,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC;IAExD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,qBAAqB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACrD,CAAC;IAED,mBAAmB;IACnB,MAAM,UAAU,GAAG,gBAAgB;QACjC,CAAC,CAAC,YAAY,CAAC,YAAY,GAAG,gBAAgB,CAAC,WAAW;QAC1D,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC;IAE9B,MAAM,UAAU,GAAG,gBAAgB;QACjC,CAAC,CAAC,YAAY,CAAC,2BAA2B,GAAG,gBAAgB,CAAC,mBAAmB;QACjF,CAAC,CAAC,YAAY,CAAC,2BAA2B,CAAC;IAE7C,MAAM,cAAc,GAAG,gBAAgB;QACrC,CAAC,CAAC,YAAY,CAAC,uBAAuB,GAAG,gBAAgB,CAAC,eAAe;QACzE,CAAC,CAAC,YAAY,CAAC,uBAAuB,CAAC;IAEzC,oDAAoD;IACpD,MAAM,YAAY,GAAG,oBAAoB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAEjE,OAAO;QACL,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC;QACpC,YAAY;QACZ,mBAAmB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC;QAC5C,eAAe,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC;QAC5C,SAAS;QACT,SAAS;QACT,WAAW,EAAE,IAAI;QACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC;IAEjD,OAAO;QACL,WAAW,EAAE,KAAK,EAAE,YAAY,IAAI,CAAC;QACrC,mBAAmB,EAAE,KAAK,EAAE,2BAA2B,IAAI,CAAC;QAC5D,eAAe,EAAE,KAAK,EAAE,uBAAuB,IAAI,CAAC;QACpD,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,SAAiB,EAAE,SAAkB;IAClE,OAAO;QACL,WAAW,EAAE,CAAC;QACd,YAAY,EAAE,CAAC;QACf,mBAAmB,EAAE,CAAC;QACtB,eAAe,EAAE,CAAC;QAClB,SAAS;QACT,SAAS;QACT,WAAW,EAAE,IAAI;QACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;AACJ,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TokenUsage, SessionTokenStats } from './types.js';
|
|
1
|
+
import { TokenUsage, SessionTokenStats, AggregateTokenStats } from './types.js';
|
|
2
2
|
export declare class TokenTracker {
|
|
3
3
|
private currentSessionId;
|
|
4
4
|
private sessionStats;
|
|
@@ -12,6 +12,12 @@ export declare class TokenTracker {
|
|
|
12
12
|
loadSessionStats(sessionId?: string): Promise<SessionTokenStats | null>;
|
|
13
13
|
private rebuildStatsFromLog;
|
|
14
14
|
getSessionStats(): SessionTokenStats;
|
|
15
|
+
getAllStats(): Promise<AggregateTokenStats>;
|
|
16
|
+
getTopAgentsAllSessions(limit?: number): Promise<Array<{
|
|
17
|
+
agent: string;
|
|
18
|
+
tokens: number;
|
|
19
|
+
cost: number;
|
|
20
|
+
}>>;
|
|
15
21
|
getTopAgents(limit?: number): Promise<Array<{
|
|
16
22
|
agent: string;
|
|
17
23
|
tokens: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-tracker.d.ts","sourceRoot":"","sources":["../../src/analytics/token-tracker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"token-tracker.d.ts","sourceRoot":"","sources":["../../src/analytics/token-tracker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAQhF,qBAAa,YAAY;IACvB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,YAAY,CAAoB;gBAE5B,SAAS,CAAC,EAAE,MAAM;IAK9B,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,sBAAsB;IAexB,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;YAiB3E,WAAW;IAOzB,OAAO,CAAC,kBAAkB;YAqBZ,gBAAgB;IAIxB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;YAe/D,mBAAmB;IAoBjC,eAAe,IAAI,iBAAiB;IAI9B,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAkF3C,uBAAuB,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAY3G,YAAY,CAAC,KAAK,GAAE,MAAU,GAAG,OAAO,CAAC,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAsBhG,cAAc,CAAC,aAAa,GAAE,MAAW,GAAG,OAAO,CAAC,MAAM,CAAC;CA2BlE;AAKD,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAKhE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAGlE"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { readState, writeState, StateLocation } from '../features/state-manager/index.js';
|
|
2
2
|
import * as fs from 'fs/promises';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
|
|
5
|
-
const
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
const TOKEN_LOG_FILE = path.join(homedir(), '.omc', 'state', 'token-tracking.jsonl');
|
|
6
|
+
const SESSION_STATS_FILE = path.join(homedir(), '.omc', 'state', 'session-token-stats.json');
|
|
6
7
|
export class TokenTracker {
|
|
7
8
|
currentSessionId;
|
|
8
9
|
sessionStats;
|
|
@@ -41,10 +42,9 @@ export class TokenTracker {
|
|
|
41
42
|
await this.saveSessionStats();
|
|
42
43
|
}
|
|
43
44
|
async appendToLog(record) {
|
|
44
|
-
const
|
|
45
|
-
const logDir = path.dirname(logPath);
|
|
45
|
+
const logDir = path.dirname(TOKEN_LOG_FILE);
|
|
46
46
|
await fs.mkdir(logDir, { recursive: true });
|
|
47
|
-
await fs.appendFile(
|
|
47
|
+
await fs.appendFile(TOKEN_LOG_FILE, JSON.stringify(record) + '\n', 'utf-8');
|
|
48
48
|
}
|
|
49
49
|
updateSessionStats(record) {
|
|
50
50
|
this.sessionStats.totalInputTokens += record.inputTokens;
|
|
@@ -52,13 +52,12 @@ export class TokenTracker {
|
|
|
52
52
|
this.sessionStats.totalCacheCreation += record.cacheCreationTokens;
|
|
53
53
|
this.sessionStats.totalCacheRead += record.cacheReadTokens;
|
|
54
54
|
this.sessionStats.lastUpdate = record.timestamp;
|
|
55
|
-
// Group by agent
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
this.sessionStats.byAgent[record.agentName].push(record);
|
|
55
|
+
// Group by agent (use "(main session)" for entries without agentName)
|
|
56
|
+
const agentKey = record.agentName || '(main session)';
|
|
57
|
+
if (!this.sessionStats.byAgent[agentKey]) {
|
|
58
|
+
this.sessionStats.byAgent[agentKey] = [];
|
|
61
59
|
}
|
|
60
|
+
this.sessionStats.byAgent[agentKey].push(record);
|
|
62
61
|
// Group by model
|
|
63
62
|
if (!this.sessionStats.byModel[record.modelName]) {
|
|
64
63
|
this.sessionStats.byModel[record.modelName] = [];
|
|
@@ -66,12 +65,12 @@ export class TokenTracker {
|
|
|
66
65
|
this.sessionStats.byModel[record.modelName].push(record);
|
|
67
66
|
}
|
|
68
67
|
async saveSessionStats() {
|
|
69
|
-
writeState('session-token-stats', this.sessionStats, StateLocation.
|
|
68
|
+
writeState('session-token-stats', this.sessionStats, StateLocation.GLOBAL);
|
|
70
69
|
}
|
|
71
70
|
async loadSessionStats(sessionId) {
|
|
72
71
|
const sid = sessionId || this.currentSessionId;
|
|
73
72
|
// Try to load from state
|
|
74
|
-
const result = readState('session-token-stats', StateLocation.
|
|
73
|
+
const result = readState('session-token-stats', StateLocation.GLOBAL);
|
|
75
74
|
if (result.exists && result.data && result.data.sessionId === sid) {
|
|
76
75
|
this.sessionStats = result.data;
|
|
77
76
|
return result.data;
|
|
@@ -80,9 +79,8 @@ export class TokenTracker {
|
|
|
80
79
|
return this.rebuildStatsFromLog(sid);
|
|
81
80
|
}
|
|
82
81
|
async rebuildStatsFromLog(sessionId) {
|
|
83
|
-
const logPath = path.resolve(process.cwd(), TOKEN_LOG_FILE);
|
|
84
82
|
try {
|
|
85
|
-
const content = await fs.readFile(
|
|
83
|
+
const content = await fs.readFile(TOKEN_LOG_FILE, 'utf-8');
|
|
86
84
|
const lines = content.trim().split('\n');
|
|
87
85
|
const stats = this.initializeSessionStats();
|
|
88
86
|
stats.sessionId = sessionId;
|
|
@@ -101,6 +99,85 @@ export class TokenTracker {
|
|
|
101
99
|
getSessionStats() {
|
|
102
100
|
return { ...this.sessionStats };
|
|
103
101
|
}
|
|
102
|
+
async getAllStats() {
|
|
103
|
+
const { calculateCost } = await import('./cost-estimator.js');
|
|
104
|
+
const stats = {
|
|
105
|
+
totalInputTokens: 0,
|
|
106
|
+
totalOutputTokens: 0,
|
|
107
|
+
totalCacheCreation: 0,
|
|
108
|
+
totalCacheRead: 0,
|
|
109
|
+
totalCost: 0,
|
|
110
|
+
byAgent: {},
|
|
111
|
+
byModel: {},
|
|
112
|
+
sessionCount: 0,
|
|
113
|
+
entryCount: 0,
|
|
114
|
+
firstEntry: null,
|
|
115
|
+
lastEntry: null
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
const content = await fs.readFile(TOKEN_LOG_FILE, 'utf-8');
|
|
119
|
+
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
120
|
+
if (lines.length === 0) {
|
|
121
|
+
return stats;
|
|
122
|
+
}
|
|
123
|
+
const sessions = new Set();
|
|
124
|
+
for (const line of lines) {
|
|
125
|
+
const record = JSON.parse(line);
|
|
126
|
+
stats.entryCount++;
|
|
127
|
+
// Track unique sessions
|
|
128
|
+
sessions.add(record.sessionId);
|
|
129
|
+
// Track timestamps
|
|
130
|
+
if (!stats.firstEntry || record.timestamp < stats.firstEntry) {
|
|
131
|
+
stats.firstEntry = record.timestamp;
|
|
132
|
+
}
|
|
133
|
+
if (!stats.lastEntry || record.timestamp > stats.lastEntry) {
|
|
134
|
+
stats.lastEntry = record.timestamp;
|
|
135
|
+
}
|
|
136
|
+
// Aggregate totals
|
|
137
|
+
stats.totalInputTokens += record.inputTokens;
|
|
138
|
+
stats.totalOutputTokens += record.outputTokens;
|
|
139
|
+
stats.totalCacheCreation += record.cacheCreationTokens;
|
|
140
|
+
stats.totalCacheRead += record.cacheReadTokens;
|
|
141
|
+
// Calculate cost for this record
|
|
142
|
+
const cost = calculateCost({
|
|
143
|
+
modelName: record.modelName,
|
|
144
|
+
inputTokens: record.inputTokens,
|
|
145
|
+
outputTokens: record.outputTokens,
|
|
146
|
+
cacheCreationTokens: record.cacheCreationTokens,
|
|
147
|
+
cacheReadTokens: record.cacheReadTokens
|
|
148
|
+
});
|
|
149
|
+
stats.totalCost += cost.totalCost;
|
|
150
|
+
// Aggregate by agent (use "(main session)" for entries without agentName)
|
|
151
|
+
const agentKey = record.agentName || '(main session)';
|
|
152
|
+
if (!stats.byAgent[agentKey]) {
|
|
153
|
+
stats.byAgent[agentKey] = { tokens: 0, cost: 0 };
|
|
154
|
+
}
|
|
155
|
+
stats.byAgent[agentKey].tokens += record.inputTokens + record.outputTokens;
|
|
156
|
+
stats.byAgent[agentKey].cost += cost.totalCost;
|
|
157
|
+
// Aggregate by model
|
|
158
|
+
if (!stats.byModel[record.modelName]) {
|
|
159
|
+
stats.byModel[record.modelName] = { tokens: 0, cost: 0 };
|
|
160
|
+
}
|
|
161
|
+
stats.byModel[record.modelName].tokens += record.inputTokens + record.outputTokens;
|
|
162
|
+
stats.byModel[record.modelName].cost += cost.totalCost;
|
|
163
|
+
}
|
|
164
|
+
stats.sessionCount = sessions.size;
|
|
165
|
+
return stats;
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
// If file doesn't exist or is empty, return empty stats
|
|
169
|
+
return stats;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async getTopAgentsAllSessions(limit = 5) {
|
|
173
|
+
const allStats = await this.getAllStats();
|
|
174
|
+
const agentStats = Object.entries(allStats.byAgent).map(([agent, stats]) => ({
|
|
175
|
+
agent,
|
|
176
|
+
tokens: stats.tokens,
|
|
177
|
+
cost: stats.cost
|
|
178
|
+
}));
|
|
179
|
+
return agentStats.sort((a, b) => b.cost - a.cost).slice(0, limit);
|
|
180
|
+
}
|
|
104
181
|
async getTopAgents(limit = 5) {
|
|
105
182
|
const { calculateCost } = await import('./cost-estimator.js');
|
|
106
183
|
const agentStats = Object.entries(this.sessionStats.byAgent).map(([agent, usages]) => {
|
|
@@ -120,11 +197,10 @@ export class TokenTracker {
|
|
|
120
197
|
return agentStats.sort((a, b) => b.cost - a.cost).slice(0, limit);
|
|
121
198
|
}
|
|
122
199
|
async cleanupOldLogs(retentionDays = 30) {
|
|
123
|
-
const logPath = path.resolve(process.cwd(), TOKEN_LOG_FILE);
|
|
124
200
|
const cutoffDate = new Date();
|
|
125
201
|
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
|
126
202
|
try {
|
|
127
|
-
const content = await fs.readFile(
|
|
203
|
+
const content = await fs.readFile(TOKEN_LOG_FILE, 'utf-8');
|
|
128
204
|
const lines = content.trim().split('\n');
|
|
129
205
|
let kept = 0;
|
|
130
206
|
let removed = 0;
|
|
@@ -138,7 +214,7 @@ export class TokenTracker {
|
|
|
138
214
|
removed++;
|
|
139
215
|
return false;
|
|
140
216
|
});
|
|
141
|
-
await fs.writeFile(
|
|
217
|
+
await fs.writeFile(TOKEN_LOG_FILE, filteredLines.join('\n') + '\n', 'utf-8');
|
|
142
218
|
return removed;
|
|
143
219
|
}
|
|
144
220
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/analytics/token-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAE1F,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,cAAc,GAAG,
|
|
1
|
+
{"version":3,"file":"token-tracker.js","sourceRoot":"","sources":["../../src/analytics/token-tracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { TranscriptEntry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Options for transcript parsing.
|
|
4
|
+
*/
|
|
5
|
+
export interface ParseTranscriptOptions {
|
|
6
|
+
/**
|
|
7
|
+
* AbortSignal to cancel parsing mid-stream.
|
|
8
|
+
*/
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
/**
|
|
11
|
+
* Callback for parse errors (allows custom logging).
|
|
12
|
+
*/
|
|
13
|
+
onParseError?: (line: string, error: Error) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Streaming JSONL parser for transcript files.
|
|
17
|
+
* Parses line-by-line without loading the entire file into memory.
|
|
18
|
+
*
|
|
19
|
+
* @param filePath - Path to the transcript JSONL file
|
|
20
|
+
* @param options - Parsing options including AbortSignal
|
|
21
|
+
* @yields TranscriptEntry objects
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const controller = new AbortController();
|
|
26
|
+
* for await (const entry of parseTranscript('transcript.jsonl', { signal: controller.signal })) {
|
|
27
|
+
* console.log(entry);
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseTranscript(filePath: string, options?: ParseTranscriptOptions): AsyncGenerator<TranscriptEntry>;
|
|
32
|
+
/**
|
|
33
|
+
* Load all entries from a transcript file into memory.
|
|
34
|
+
* Use this for smaller files or when you need all entries at once.
|
|
35
|
+
* For large files, prefer the streaming `parseTranscript()` generator.
|
|
36
|
+
*
|
|
37
|
+
* @param filePath - Path to the transcript JSONL file
|
|
38
|
+
* @param options - Parsing options
|
|
39
|
+
* @returns Array of all transcript entries
|
|
40
|
+
*/
|
|
41
|
+
export declare function loadTranscript(filePath: string, options?: ParseTranscriptOptions): Promise<TranscriptEntry[]>;
|
|
42
|
+
//# sourceMappingURL=transcript-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-parser.d.ts","sourceRoot":"","sources":["../../src/analytics/transcript-parser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAuB,eAAe,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,sBAA2B,GACnC,cAAc,CAAC,eAAe,CAAC,CA2DjC;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,eAAe,EAAE,CAAC,CAQ5B"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as readline from 'readline';
|
|
3
|
+
/**
|
|
4
|
+
* Streaming JSONL parser for transcript files.
|
|
5
|
+
* Parses line-by-line without loading the entire file into memory.
|
|
6
|
+
*
|
|
7
|
+
* @param filePath - Path to the transcript JSONL file
|
|
8
|
+
* @param options - Parsing options including AbortSignal
|
|
9
|
+
* @yields TranscriptEntry objects
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const controller = new AbortController();
|
|
14
|
+
* for await (const entry of parseTranscript('transcript.jsonl', { signal: controller.signal })) {
|
|
15
|
+
* console.log(entry);
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export async function* parseTranscript(filePath, options = {}) {
|
|
20
|
+
const { signal, onParseError } = options;
|
|
21
|
+
// Check if file exists
|
|
22
|
+
if (!fs.existsSync(filePath)) {
|
|
23
|
+
throw new Error(`Transcript file not found: ${filePath}`);
|
|
24
|
+
}
|
|
25
|
+
const fileStream = fs.createReadStream(filePath, { encoding: 'utf8' });
|
|
26
|
+
const rl = readline.createInterface({
|
|
27
|
+
input: fileStream,
|
|
28
|
+
crlfDelay: Infinity // Treat \r\n as single line break
|
|
29
|
+
});
|
|
30
|
+
// Handle abort signal
|
|
31
|
+
const abortHandler = () => {
|
|
32
|
+
rl.close();
|
|
33
|
+
fileStream.destroy();
|
|
34
|
+
};
|
|
35
|
+
if (signal) {
|
|
36
|
+
signal.addEventListener('abort', abortHandler);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
for await (const line of rl) {
|
|
40
|
+
// Check abort before processing each line
|
|
41
|
+
if (signal?.aborted) {
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
// Skip empty lines
|
|
45
|
+
if (!line.trim()) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const entry = JSON.parse(line);
|
|
50
|
+
yield entry;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// Handle malformed JSON gracefully
|
|
54
|
+
const parseError = error instanceof Error ? error : new Error(String(error));
|
|
55
|
+
if (onParseError) {
|
|
56
|
+
onParseError(line, parseError);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Default: log warning and skip line
|
|
60
|
+
console.warn(`[transcript-parser] Skipping malformed line: ${parseError.message}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
// Clean up
|
|
67
|
+
if (signal) {
|
|
68
|
+
signal.removeEventListener('abort', abortHandler);
|
|
69
|
+
}
|
|
70
|
+
rl.close();
|
|
71
|
+
fileStream.destroy();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Load all entries from a transcript file into memory.
|
|
76
|
+
* Use this for smaller files or when you need all entries at once.
|
|
77
|
+
* For large files, prefer the streaming `parseTranscript()` generator.
|
|
78
|
+
*
|
|
79
|
+
* @param filePath - Path to the transcript JSONL file
|
|
80
|
+
* @param options - Parsing options
|
|
81
|
+
* @returns Array of all transcript entries
|
|
82
|
+
*/
|
|
83
|
+
export async function loadTranscript(filePath, options = {}) {
|
|
84
|
+
const entries = [];
|
|
85
|
+
for await (const entry of parseTranscript(filePath, options)) {
|
|
86
|
+
entries.push(entry);
|
|
87
|
+
}
|
|
88
|
+
return entries;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=transcript-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-parser.js","sourceRoot":"","sources":["../../src/analytics/transcript-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAkBrC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,eAAe,CACpC,QAAgB,EAChB,UAAkC,EAAE;IAEpC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IAEzC,uBAAuB;IACvB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,8BAA8B,QAAQ,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,UAAU,GAAG,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACvE,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;QAClC,KAAK,EAAE,UAAU;QACjB,SAAS,EAAE,QAAQ,CAAC,kCAAkC;KACvD,CAAC,CAAC;IAEH,sBAAsB;IACtB,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,UAAU,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC,CAAC;IAEF,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,CAAC;QACH,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,EAAE,EAAE,CAAC;YAC5B,0CAA0C;YAC1C,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,MAAM;YACR,CAAC;YAED,mBAAmB;YACnB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBACjB,SAAS;YACX,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;gBAClD,MAAM,KAAK,CAAC;YACd,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,mCAAmC;gBACnC,MAAM,UAAU,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAE7E,IAAI,YAAY,EAAE,CAAC;oBACjB,YAAY,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;gBACjC,CAAC;qBAAM,CAAC;oBACN,qCAAqC;oBACrC,OAAO,CAAC,IAAI,CAAC,gDAAgD,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;gBACrF,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,WAAW;QACX,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACpD,CAAC;QACD,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,UAAU,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,UAAkC,EAAE;IAEpC,MAAM,OAAO,GAAsB,EAAE,CAAC;IAEtC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,eAAe,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata for a discovered transcript file
|
|
3
|
+
*/
|
|
4
|
+
export interface TranscriptFile {
|
|
5
|
+
projectPath: string;
|
|
6
|
+
projectDir: string;
|
|
7
|
+
sessionId: string;
|
|
8
|
+
filePath: string;
|
|
9
|
+
fileSize: number;
|
|
10
|
+
modifiedTime: Date;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Result of scanning for transcripts
|
|
14
|
+
*/
|
|
15
|
+
export interface ScanResult {
|
|
16
|
+
transcripts: TranscriptFile[];
|
|
17
|
+
totalSize: number;
|
|
18
|
+
projectCount: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Options for scanning transcripts
|
|
22
|
+
*/
|
|
23
|
+
export interface ScanOptions {
|
|
24
|
+
projectFilter?: string;
|
|
25
|
+
minDate?: Date;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Decode project directory name back to original path.
|
|
29
|
+
*
|
|
30
|
+
* The encoding scheme used by Claude Code is lossy - it converts all path
|
|
31
|
+
* separators (/) to dashes (-), but legitimate dashes in directory names
|
|
32
|
+
* also become dashes, making them indistinguishable.
|
|
33
|
+
*
|
|
34
|
+
* Strategy:
|
|
35
|
+
* 1. Try simple decode (all dashes -> slashes) and check if path exists
|
|
36
|
+
* 2. If not, try to reconstruct by checking filesystem for partial matches
|
|
37
|
+
* 3. Fall back to simple decode if nothing else works
|
|
38
|
+
*
|
|
39
|
+
* Example: "-home-bellman-my-project"
|
|
40
|
+
* - Simple decode: "/home/bellman/my/project" (WRONG if "my-project" is one dir)
|
|
41
|
+
* - Smart decode: "/home/bellman/my-project" (checks filesystem)
|
|
42
|
+
*
|
|
43
|
+
* @internal Exported for testing
|
|
44
|
+
*/
|
|
45
|
+
export declare function decodeProjectPath(dirName: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Scan for all transcript files in ~/.claude/projects/
|
|
48
|
+
*/
|
|
49
|
+
export declare function scanTranscripts(options?: ScanOptions): Promise<ScanResult>;
|
|
50
|
+
//# sourceMappingURL=transcript-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-scanner.d.ts","sourceRoot":"","sources":["../../src/analytics/transcript-scanner.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAOD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CA+CzD;AAkBD;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA8EpF"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readdir, stat } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
/**
|
|
6
|
+
* UUID regex pattern for session IDs
|
|
7
|
+
*/
|
|
8
|
+
const UUID_REGEX = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
|
9
|
+
/**
|
|
10
|
+
* Decode project directory name back to original path.
|
|
11
|
+
*
|
|
12
|
+
* The encoding scheme used by Claude Code is lossy - it converts all path
|
|
13
|
+
* separators (/) to dashes (-), but legitimate dashes in directory names
|
|
14
|
+
* also become dashes, making them indistinguishable.
|
|
15
|
+
*
|
|
16
|
+
* Strategy:
|
|
17
|
+
* 1. Try simple decode (all dashes -> slashes) and check if path exists
|
|
18
|
+
* 2. If not, try to reconstruct by checking filesystem for partial matches
|
|
19
|
+
* 3. Fall back to simple decode if nothing else works
|
|
20
|
+
*
|
|
21
|
+
* Example: "-home-bellman-my-project"
|
|
22
|
+
* - Simple decode: "/home/bellman/my/project" (WRONG if "my-project" is one dir)
|
|
23
|
+
* - Smart decode: "/home/bellman/my-project" (checks filesystem)
|
|
24
|
+
*
|
|
25
|
+
* @internal Exported for testing
|
|
26
|
+
*/
|
|
27
|
+
export function decodeProjectPath(dirName) {
|
|
28
|
+
if (!dirName.startsWith('-')) {
|
|
29
|
+
return dirName;
|
|
30
|
+
}
|
|
31
|
+
// Simple decode: replace all dashes with slashes
|
|
32
|
+
const simplePath = '/' + dirName.slice(1).replace(/-/g, '/');
|
|
33
|
+
// If simple decode exists, we're done
|
|
34
|
+
if (existsSync(simplePath)) {
|
|
35
|
+
return simplePath;
|
|
36
|
+
}
|
|
37
|
+
// Try to reconstruct by checking filesystem for partial matches
|
|
38
|
+
const segments = dirName.slice(1).split('-');
|
|
39
|
+
const possiblePaths = [];
|
|
40
|
+
// Generate all possible interpretations by trying different hyphen positions
|
|
41
|
+
function generatePaths(parts, index, currentPath) {
|
|
42
|
+
if (index >= parts.length) {
|
|
43
|
+
possiblePaths.push(currentPath);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Try adding next segment as a new directory
|
|
47
|
+
generatePaths(parts, index + 1, currentPath + '/' + parts[index]);
|
|
48
|
+
// Try combining with previous segment using hyphen (if not first segment)
|
|
49
|
+
if (index > 0 && currentPath) {
|
|
50
|
+
const pathParts = currentPath.split('/');
|
|
51
|
+
const lastPart = pathParts.pop() || '';
|
|
52
|
+
const newPath = pathParts.join('/') + '/' + lastPart + '-' + parts[index];
|
|
53
|
+
generatePaths(parts, index + 1, newPath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
generatePaths(segments, 0, '');
|
|
57
|
+
// Find the first path that exists on filesystem
|
|
58
|
+
for (const path of possiblePaths) {
|
|
59
|
+
if (existsSync(path)) {
|
|
60
|
+
return path;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Fall back to simple decode
|
|
64
|
+
return simplePath;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if a path matches a glob pattern (simple implementation)
|
|
68
|
+
*/
|
|
69
|
+
function matchesPattern(path, pattern) {
|
|
70
|
+
if (!pattern)
|
|
71
|
+
return true;
|
|
72
|
+
// Convert glob pattern to regex
|
|
73
|
+
const regexPattern = pattern
|
|
74
|
+
.replace(/\./g, '\\.')
|
|
75
|
+
.replace(/\*/g, '.*')
|
|
76
|
+
.replace(/\?/g, '.');
|
|
77
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
78
|
+
return regex.test(path);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Scan for all transcript files in ~/.claude/projects/
|
|
82
|
+
*/
|
|
83
|
+
export async function scanTranscripts(options = {}) {
|
|
84
|
+
const projectsDir = join(homedir(), '.claude', 'projects');
|
|
85
|
+
const transcripts = [];
|
|
86
|
+
const projectDirs = new Set();
|
|
87
|
+
try {
|
|
88
|
+
// Read all project directories
|
|
89
|
+
const entries = await readdir(projectsDir, { withFileTypes: true });
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
if (!entry.isDirectory())
|
|
92
|
+
continue;
|
|
93
|
+
const projectDir = entry.name;
|
|
94
|
+
const projectPath = decodeProjectPath(projectDir);
|
|
95
|
+
// Apply project filter if specified
|
|
96
|
+
if (!matchesPattern(projectPath, options.projectFilter)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const fullProjectPath = join(projectsDir, projectDir);
|
|
100
|
+
// Read all files in this project directory
|
|
101
|
+
const projectFiles = await readdir(fullProjectPath);
|
|
102
|
+
for (const fileName of projectFiles) {
|
|
103
|
+
// Skip sessions-index.json and any non-.jsonl files
|
|
104
|
+
if (fileName === 'sessions-index.json' || !fileName.endsWith('.jsonl')) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// Extract session ID from filename
|
|
108
|
+
const sessionId = fileName.replace('.jsonl', '');
|
|
109
|
+
// Validate session ID format (must be UUID)
|
|
110
|
+
if (!UUID_REGEX.test(sessionId)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const filePath = join(fullProjectPath, fileName);
|
|
114
|
+
const fileStats = await stat(filePath);
|
|
115
|
+
// Apply date filter if specified
|
|
116
|
+
if (options.minDate && fileStats.mtime < options.minDate) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
transcripts.push({
|
|
120
|
+
projectPath,
|
|
121
|
+
projectDir,
|
|
122
|
+
sessionId,
|
|
123
|
+
filePath,
|
|
124
|
+
fileSize: fileStats.size,
|
|
125
|
+
modifiedTime: fileStats.mtime
|
|
126
|
+
});
|
|
127
|
+
projectDirs.add(projectDir);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// If projects directory doesn't exist, return empty result
|
|
133
|
+
if (error.code === 'ENOENT') {
|
|
134
|
+
return {
|
|
135
|
+
transcripts: [],
|
|
136
|
+
totalSize: 0,
|
|
137
|
+
projectCount: 0
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
const totalSize = transcripts.reduce((sum, t) => sum + t.fileSize, 0);
|
|
143
|
+
return {
|
|
144
|
+
transcripts,
|
|
145
|
+
totalSize,
|
|
146
|
+
projectCount: projectDirs.size
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=transcript-scanner.js.map
|