sidekick-shared 0.21.2 → 0.21.4

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 (77) hide show
  1. package/README.md +60 -45
  2. package/dist/accountManager.js +9 -7
  3. package/dist/accountRegistry.d.ts +17 -0
  4. package/dist/accountRegistry.js +18 -14
  5. package/dist/accountStatus.js +7 -7
  6. package/dist/accounts.d.ts +13 -0
  7. package/dist/accounts.js +110 -25
  8. package/dist/aggregation/EventAggregator.d.ts +14 -11
  9. package/dist/aggregation/EventAggregator.js +68 -31
  10. package/dist/aggregation/PatternExtractor.js +5 -4
  11. package/dist/autoSwitch.js +6 -12
  12. package/dist/claudeProfiles.js +3 -3
  13. package/dist/codexProfiles.d.ts +15 -1
  14. package/dist/codexProfiles.js +96 -22
  15. package/dist/codexQuota.js +44 -16
  16. package/dist/codexQuotaWatcher.js +19 -4
  17. package/dist/context/composer.js +8 -6
  18. package/dist/context/sessionContext.js +12 -9
  19. package/dist/credentialIO.js +10 -8
  20. package/dist/extractors/sessionAssets.js +9 -1
  21. package/dist/extractors/sources/claudeAssets.js +7 -1
  22. package/dist/extractors/sources/codexAssets.js +14 -3
  23. package/dist/extractors/toolCall.js +1 -4
  24. package/dist/formatters/eventHighlighter.js +148 -28
  25. package/dist/formatters/noiseClassifier.js +3 -11
  26. package/dist/formatters/sessionDump.js +22 -13
  27. package/dist/formatters/toolSummary.js +1 -3
  28. package/dist/index.d.ts +26 -26
  29. package/dist/index.js +10 -4
  30. package/dist/modelContext.js +10 -8
  31. package/dist/modelInfo.js +11 -6
  32. package/dist/multiProviderQuotaService.d.ts +2 -2
  33. package/dist/multiProviderQuotaService.js +14 -10
  34. package/dist/parsers/codexParser.js +170 -103
  35. package/dist/parsers/debugLogParser.js +5 -4
  36. package/dist/parsers/openCodeParser.js +66 -48
  37. package/dist/parsers/planExtractor.js +3 -3
  38. package/dist/parsers/sessionActivityDetector.js +10 -6
  39. package/dist/parsers/sessionPathResolver.js +29 -34
  40. package/dist/parsers/subagentScanner.js +21 -20
  41. package/dist/parsers/subagentTraceParser.js +7 -4
  42. package/dist/phrases.js +11 -11
  43. package/dist/providerStatus.js +3 -4
  44. package/dist/providers/claudeCode.js +3 -1
  45. package/dist/providers/codex.js +35 -14
  46. package/dist/providers/codexDatabase.js +14 -5
  47. package/dist/providers/detect.js +10 -6
  48. package/dist/providers/openCode.js +91 -63
  49. package/dist/providers/openCodeDatabase.js +44 -17
  50. package/dist/quota.d.ts +19 -0
  51. package/dist/quota.js +50 -8
  52. package/dist/quotaHistory.js +10 -3
  53. package/dist/quotaSnapshots.js +2 -2
  54. package/dist/readers/decisions.js +1 -1
  55. package/dist/readers/notes.js +5 -3
  56. package/dist/readers/plans.js +43 -23
  57. package/dist/readers/tasks.js +1 -1
  58. package/dist/report/htmlHelpers.js +135 -16
  59. package/dist/report/htmlReportGenerator.js +38 -12
  60. package/dist/report/transcriptParser.js +8 -2
  61. package/dist/schemas/index.d.ts +4 -4
  62. package/dist/schemas/quota.js +2 -10
  63. package/dist/schemas/sessionEvent.js +20 -10
  64. package/dist/search/advancedFilter.js +3 -3
  65. package/dist/search/sessionSearch.js +10 -4
  66. package/dist/terminalSync.js +4 -2
  67. package/dist/turns/assistantTurn.js +5 -1
  68. package/dist/watchers/eventBridge.js +56 -26
  69. package/dist/watchers/factory.js +5 -2
  70. package/dist/watchers/jsonlTail.js +3 -1
  71. package/dist/watchers/jsonlWatcher.js +128 -52
  72. package/dist/watchers/providerReaderWatcher.js +3 -1
  73. package/dist/watchers/sqliteWatcher.js +52 -22
  74. package/dist/zaiQuota.js +6 -10
  75. package/dist/zaiQuotaApi.js +13 -5
  76. package/dist/zaiQuotaWatcher.js +1 -1
  77. package/package.json +5 -2
package/dist/quota.js CHANGED
@@ -6,26 +6,60 @@
6
6
  * Polling / eventing is the caller's responsibility.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SEVEN_DAY_WINDOW_MS = exports.FIVE_HOUR_WINDOW_MS = void 0;
10
+ exports.projectQuotaWindow = projectQuotaWindow;
11
+ exports.withQuotaProjections = withQuotaProjections;
9
12
  exports.fetchQuota = fetchQuota;
10
13
  const USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
11
14
  const BETA_HEADER = 'oauth-2025-04-20';
12
- const FIVE_HOUR_MS = 5 * 3_600_000;
13
- const SEVEN_DAY_MS = 7 * 86_400_000;
15
+ exports.FIVE_HOUR_WINDOW_MS = 5 * 3_600_000;
16
+ exports.SEVEN_DAY_WINDOW_MS = 7 * 86_400_000;
14
17
  /**
15
18
  * Projects utilization at end of window based on elapsed time.
16
19
  *
17
20
  * Formula: `current * (windowDuration / elapsed)`, capped at 200%.
18
21
  */
19
- function projectFromElapsed(utilization, resetsAt, windowMs) {
20
- if (!resetsAt || utilization <= 0)
22
+ function projectQuotaWindow(input) {
23
+ const { utilization, resetsAt, windowMs, capturedAt } = input;
24
+ if (!resetsAt || utilization <= 0 || !Number.isFinite(utilization) || windowMs <= 0)
21
25
  return undefined;
22
26
  const resetTime = new Date(resetsAt).getTime();
23
- const now = Date.now();
27
+ if (!Number.isFinite(resetTime))
28
+ return undefined;
29
+ const capturedTime = capturedAt ? Date.parse(capturedAt) : NaN;
30
+ const now = Number.isFinite(capturedTime) ? capturedTime : Date.now();
24
31
  const elapsed = windowMs - (resetTime - now);
25
32
  if (elapsed <= 0)
26
33
  return undefined;
27
34
  return Math.min(Math.round(utilization * (windowMs / elapsed)), 200);
28
35
  }
36
+ function withQuotaProjections(state, options = {}) {
37
+ if (!state.available)
38
+ return state;
39
+ const next = { ...state };
40
+ const capturedAt = options.capturedAt ?? state.capturedAt;
41
+ if (next.projectedFiveHour == null) {
42
+ const projected = projectQuotaWindow({
43
+ utilization: next.fiveHour.utilization,
44
+ resetsAt: next.fiveHour.resetsAt,
45
+ windowMs: options.fiveHourWindowMs ?? exports.FIVE_HOUR_WINDOW_MS,
46
+ capturedAt,
47
+ });
48
+ if (projected !== undefined)
49
+ next.projectedFiveHour = projected;
50
+ }
51
+ if (next.projectedSevenDay == null) {
52
+ const projected = projectQuotaWindow({
53
+ utilization: next.sevenDay.utilization,
54
+ resetsAt: next.sevenDay.resetsAt,
55
+ windowMs: options.sevenDayWindowMs ?? exports.SEVEN_DAY_WINDOW_MS,
56
+ capturedAt,
57
+ });
58
+ if (projected !== undefined)
59
+ next.projectedSevenDay = projected;
60
+ }
61
+ return next;
62
+ }
29
63
  /**
30
64
  * Helper to build an unavailable QuotaState.
31
65
  */
@@ -64,7 +98,7 @@ async function fetchQuota(accessToken) {
64
98
  const res = await fetch(USAGE_URL, {
65
99
  method: 'GET',
66
100
  headers: {
67
- 'Authorization': `Bearer ${accessToken}`,
101
+ Authorization: `Bearer ${accessToken}`,
68
102
  'anthropic-beta': BETA_HEADER,
69
103
  'Content-Type': 'application/json',
70
104
  },
@@ -103,8 +137,16 @@ async function fetchQuota(accessToken) {
103
137
  fiveHour: { utilization: fiveUtil, resetsAt: fiveResetsAt },
104
138
  sevenDay: { utilization: sevenUtil, resetsAt: sevenResetsAt },
105
139
  available: true,
106
- projectedFiveHour: projectFromElapsed(fiveUtil, fiveResetsAt, FIVE_HOUR_MS),
107
- projectedSevenDay: projectFromElapsed(sevenUtil, sevenResetsAt, SEVEN_DAY_MS),
140
+ projectedFiveHour: projectQuotaWindow({
141
+ utilization: fiveUtil,
142
+ resetsAt: fiveResetsAt,
143
+ windowMs: exports.FIVE_HOUR_WINDOW_MS,
144
+ }),
145
+ projectedSevenDay: projectQuotaWindow({
146
+ utilization: sevenUtil,
147
+ resetsAt: sevenResetsAt,
148
+ windowMs: exports.SEVEN_DAY_WINDOW_MS,
149
+ }),
108
150
  };
109
151
  }
110
152
  catch {
@@ -78,7 +78,10 @@ function getHistoryFilePath(workspaceId, provider) {
78
78
  return path.join((0, paths_1.getConfigDir)(), 'quota-history', workspaceId, `${provider}.jsonl`);
79
79
  }
80
80
  function ensureHistoryDir(workspaceId) {
81
- fs.mkdirSync(path.join((0, paths_1.getConfigDir)(), 'quota-history', workspaceId), { recursive: true, mode: 0o700 });
81
+ fs.mkdirSync(path.join((0, paths_1.getConfigDir)(), 'quota-history', workspaceId), {
82
+ recursive: true,
83
+ mode: 0o700,
84
+ });
82
85
  }
83
86
  function parseSampleLine(line) {
84
87
  const trimmed = line.trim();
@@ -235,7 +238,9 @@ async function runAppend(sample, filePath, options) {
235
238
  }
236
239
  }
237
240
  const sampleTs = Date.parse(sample.timestamp);
238
- if (lastTs !== undefined && Number.isFinite(sampleTs) && sampleTs - lastTs < options.minIntervalMs) {
241
+ if (lastTs !== undefined &&
242
+ Number.isFinite(sampleTs) &&
243
+ sampleTs - lastTs < options.minIntervalMs) {
239
244
  return;
240
245
  }
241
246
  ensureHistoryDir(sample.workspaceId);
@@ -269,7 +274,9 @@ async function appendQuotaHistorySample(sample, options = {}) {
269
274
  };
270
275
  const filePath = getHistoryFilePath(sample.workspaceId, sample.runtimeProvider);
271
276
  const previous = appendChains.get(filePath) ?? Promise.resolve();
272
- const next = previous.then(() => runAppend(sample, filePath, resolved)).catch(() => {
277
+ const next = previous
278
+ .then(() => runAppend(sample, filePath, resolved))
279
+ .catch(() => {
273
280
  // Swallow chain-level errors so a single failure doesn't break subsequent appends.
274
281
  });
275
282
  appendChains.set(filePath, next);
@@ -117,7 +117,7 @@ function writeQuotaSnapshot(providerId, accountId, quota) {
117
117
  source: quota.source ?? 'session',
118
118
  stale: false,
119
119
  };
120
- const index = store.snapshots.findIndex(item => item.providerId === providerId && item.accountId === accountId);
120
+ const index = store.snapshots.findIndex((item) => item.providerId === providerId && item.accountId === accountId);
121
121
  if (index >= 0 && shouldKeepExistingSnapshot(store.snapshots[index].quota, snapshot)) {
122
122
  return;
123
123
  }
@@ -136,7 +136,7 @@ function writeQuotaSnapshot(providerId, accountId, quota) {
136
136
  }
137
137
  function readQuotaSnapshot(providerId, accountId) {
138
138
  const store = readStore();
139
- const snapshot = store.snapshots.find(item => item.providerId === providerId && item.accountId === accountId);
139
+ const snapshot = store.snapshots.find((item) => item.providerId === providerId && item.accountId === accountId);
140
140
  if (!snapshot)
141
141
  return null;
142
142
  return {
@@ -16,7 +16,7 @@ async function readDecisions(slug, opts) {
16
16
  decisions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
17
17
  if (opts?.search) {
18
18
  const q = opts.search.toLowerCase();
19
- decisions = decisions.filter(d => d.description.toLowerCase().includes(q) ||
19
+ decisions = decisions.filter((d) => d.description.toLowerCase().includes(q) ||
20
20
  d.rationale.toLowerCase().includes(q) ||
21
21
  d.chosenOption.toLowerCase().includes(q));
22
22
  }
@@ -17,7 +17,9 @@ async function readNotes(slug, opts) {
17
17
  const normalizedFile = opts.file.replace(/\\/g, '/');
18
18
  for (const [filePath, fileNotes] of Object.entries(store.notesByFile)) {
19
19
  const normalizedKey = filePath.replace(/\\/g, '/');
20
- if (normalizedKey === normalizedFile || normalizedKey.endsWith('/' + normalizedFile) || normalizedFile.endsWith('/' + normalizedKey)) {
20
+ if (normalizedKey === normalizedFile ||
21
+ normalizedKey.endsWith('/' + normalizedFile) ||
22
+ normalizedFile.endsWith('/' + normalizedKey)) {
21
23
  notes.push(...fileNotes);
22
24
  }
23
25
  }
@@ -28,10 +30,10 @@ async function readNotes(slug, opts) {
28
30
  }
29
31
  }
30
32
  if (opts?.type) {
31
- notes = notes.filter(n => n.noteType === opts.type);
33
+ notes = notes.filter((n) => n.noteType === opts.type);
32
34
  }
33
35
  if (opts?.status) {
34
- notes = notes.filter(n => n.status === opts.status);
36
+ notes = notes.filter((n) => n.status === opts.status);
35
37
  }
36
38
  // Sort by importance then updatedAt
37
39
  const importanceOrder = { critical: 0, high: 1, medium: 2, low: 3 };
@@ -56,10 +56,10 @@ async function readPlans(slug, opts) {
56
56
  let plans = [...store.plans];
57
57
  const status = opts?.status ?? 'all';
58
58
  if (status !== 'all') {
59
- plans = plans.filter(p => p.status === status);
59
+ plans = plans.filter((p) => p.status === status);
60
60
  }
61
61
  if (opts?.source) {
62
- plans = plans.filter(p => p.source === opts.source);
62
+ plans = plans.filter((p) => p.source === opts.source);
63
63
  }
64
64
  // Already sorted by createdAt desc in storage
65
65
  if (opts?.limit && opts.limit > 0) {
@@ -75,26 +75,35 @@ function getPlanAnalytics(plans) {
75
75
  const totalPlans = plans.length;
76
76
  if (totalPlans === 0) {
77
77
  return {
78
- totalPlans: 0, completedPlans: 0, failedPlans: 0, abandonedPlans: 0,
79
- avgCompletionRate: 0, avgDurationMs: 0, avgStepsPerPlan: 0,
80
- avgTokensPerPlan: 0, avgCostPerPlan: 0,
81
- plansBySource: {}, mostCommonFailureStep: null, completionTrend: [],
78
+ totalPlans: 0,
79
+ completedPlans: 0,
80
+ failedPlans: 0,
81
+ abandonedPlans: 0,
82
+ avgCompletionRate: 0,
83
+ avgDurationMs: 0,
84
+ avgStepsPerPlan: 0,
85
+ avgTokensPerPlan: 0,
86
+ avgCostPerPlan: 0,
87
+ plansBySource: {},
88
+ mostCommonFailureStep: null,
89
+ completionTrend: [],
82
90
  };
83
91
  }
84
- const completedPlans = plans.filter(p => p.status === 'completed').length;
85
- const failedPlans = plans.filter(p => p.status === 'failed').length;
86
- const abandonedPlans = plans.filter(p => p.status === 'abandoned').length;
92
+ const completedPlans = plans.filter((p) => p.status === 'completed').length;
93
+ const failedPlans = plans.filter((p) => p.status === 'failed').length;
94
+ const abandonedPlans = plans.filter((p) => p.status === 'abandoned').length;
87
95
  const avgCompletionRate = plans.reduce((s, p) => s + p.completionRate, 0) / totalPlans;
88
- const plansWithDuration = plans.filter(p => p.totalDurationMs != null && p.totalDurationMs > 0);
96
+ const plansWithDuration = plans.filter((p) => p.totalDurationMs != null && p.totalDurationMs > 0);
89
97
  const avgDurationMs = plansWithDuration.length > 0
90
- ? plansWithDuration.reduce((s, p) => s + (p.totalDurationMs || 0), 0) / plansWithDuration.length
98
+ ? plansWithDuration.reduce((s, p) => s + (p.totalDurationMs || 0), 0) /
99
+ plansWithDuration.length
91
100
  : 0;
92
101
  const avgStepsPerPlan = plans.reduce((s, p) => s + p.steps.length, 0) / totalPlans;
93
- const plansWithTokens = plans.filter(p => p.totalTokensUsed != null && p.totalTokensUsed > 0);
102
+ const plansWithTokens = plans.filter((p) => p.totalTokensUsed != null && p.totalTokensUsed > 0);
94
103
  const avgTokensPerPlan = plansWithTokens.length > 0
95
104
  ? plansWithTokens.reduce((s, p) => s + (p.totalTokensUsed || 0), 0) / plansWithTokens.length
96
105
  : 0;
97
- const plansWithCost = plans.filter(p => p.totalCostUsd != null && p.totalCostUsd > 0);
106
+ const plansWithCost = plans.filter((p) => p.totalCostUsd != null && p.totalCostUsd > 0);
98
107
  const avgCostPerPlan = plansWithCost.length > 0
99
108
  ? plansWithCost.reduce((s, p) => s + (p.totalCostUsd || 0), 0) / plansWithCost.length
100
109
  : 0;
@@ -137,10 +146,18 @@ function getPlanAnalytics(plans) {
137
146
  .sort(([a], [b]) => a.localeCompare(b))
138
147
  .map(([date, { sum, count }]) => ({ date, rate: sum / count }));
139
148
  return {
140
- totalPlans, completedPlans, failedPlans, abandonedPlans,
141
- avgCompletionRate, avgDurationMs, avgStepsPerPlan,
142
- avgTokensPerPlan, avgCostPerPlan,
143
- plansBySource, mostCommonFailureStep, completionTrend,
149
+ totalPlans,
150
+ completedPlans,
151
+ failedPlans,
152
+ abandonedPlans,
153
+ avgCompletionRate,
154
+ avgDurationMs,
155
+ avgStepsPerPlan,
156
+ avgTokensPerPlan,
157
+ avgCostPerPlan,
158
+ plansBySource,
159
+ mostCommonFailureStep,
160
+ completionTrend,
144
161
  };
145
162
  }
146
163
  async function writePlans(slug, plans) {
@@ -176,8 +193,7 @@ async function readClaudeCodePlanFiles(workspacePath) {
176
193
  const slugSet = new Set();
177
194
  let jsonlFiles;
178
195
  try {
179
- jsonlFiles = (await fs.promises.readdir(projectDir))
180
- .filter(f => f.endsWith('.jsonl'));
196
+ jsonlFiles = (await fs.promises.readdir(projectDir)).filter((f) => f.endsWith('.jsonl'));
181
197
  }
182
198
  catch {
183
199
  return [];
@@ -205,7 +221,9 @@ async function readClaudeCodePlanFiles(workspacePath) {
205
221
  await handle.close();
206
222
  }
207
223
  }
208
- catch { /* skip unreadable files */ }
224
+ catch {
225
+ /* skip unreadable files */
226
+ }
209
227
  }));
210
228
  if (slugSet.size === 0)
211
229
  return [];
@@ -217,14 +235,14 @@ async function readClaudeCodePlanFiles(workspacePath) {
217
235
  const stat = await fs.promises.stat(planPath);
218
236
  const content = await fs.promises.readFile(planPath, 'utf-8');
219
237
  const parsed = (0, planExtractor_1.parsePlanMarkdown)(content);
220
- const steps = parsed.steps.map(s => ({
238
+ const steps = parsed.steps.map((s) => ({
221
239
  id: s.id,
222
240
  description: s.description,
223
241
  status: s.status,
224
242
  phase: s.phase,
225
243
  complexity: s.complexity,
226
244
  }));
227
- const completed = steps.filter(s => s.status === 'completed').length;
245
+ const completed = steps.filter((s) => s.status === 'completed').length;
228
246
  const total = steps.length;
229
247
  plans.push({
230
248
  id: `claude-plan-${slug}`,
@@ -239,7 +257,9 @@ async function readClaudeCodePlanFiles(workspacePath) {
239
257
  rawMarkdown: content,
240
258
  });
241
259
  }
242
- catch { /* plan file missing or unreadable */ }
260
+ catch {
261
+ /* plan file missing or unreadable */
262
+ }
243
263
  }
244
264
  // Sort by createdAt descending
245
265
  plans.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
@@ -14,7 +14,7 @@ async function readTasks(slug, opts) {
14
14
  let tasks = Object.values(store.tasks);
15
15
  const status = opts?.status ?? 'all';
16
16
  if (status !== 'all') {
17
- tasks = tasks.filter(t => t.status === status);
17
+ tasks = tasks.filter((t) => t.status === status);
18
18
  }
19
19
  // Sort by updatedAt descending
20
20
  tasks.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
@@ -70,30 +70,144 @@ function simpleMarkdownToHtml(text) {
70
70
  // Single newlines to <br> within paragraphs (but not before block elements)
71
71
  result = result.replace(/(?<!<\/(?:h[1-4]|ul|li|blockquote|hr|div|pre)>)\n(?!<(?:h[1-4]|ul|li|blockquote|hr|div|pre|p|\/))/g, '<br>\n');
72
72
  // Restore code blocks
73
+ result = result.replace(
73
74
  // eslint-disable-next-line no-control-regex
74
- result = result.replace(/\x00CODEBLOCK(\d+)\x00/g, (_match, idx) => codeBlocks[parseInt(idx, 10)]);
75
+ /\x00CODEBLOCK(\d+)\x00/g, (_match, idx) => codeBlocks[parseInt(idx, 10)]);
75
76
  return result;
76
77
  }
77
78
  // Keyword sets for syntax highlighting
78
79
  const JS_KEYWORDS = new Set([
79
- 'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'do',
80
- 'switch', 'case', 'break', 'continue', 'new', 'this', 'class', 'extends', 'super',
81
- 'import', 'export', 'from', 'default', 'try', 'catch', 'finally', 'throw',
82
- 'async', 'await', 'yield', 'typeof', 'instanceof', 'in', 'of', 'delete', 'void',
83
- 'true', 'false', 'null', 'undefined', 'interface', 'type', 'enum', 'implements',
84
- 'public', 'private', 'protected', 'readonly', 'static', 'abstract', 'as',
80
+ 'const',
81
+ 'let',
82
+ 'var',
83
+ 'function',
84
+ 'return',
85
+ 'if',
86
+ 'else',
87
+ 'for',
88
+ 'while',
89
+ 'do',
90
+ 'switch',
91
+ 'case',
92
+ 'break',
93
+ 'continue',
94
+ 'new',
95
+ 'this',
96
+ 'class',
97
+ 'extends',
98
+ 'super',
99
+ 'import',
100
+ 'export',
101
+ 'from',
102
+ 'default',
103
+ 'try',
104
+ 'catch',
105
+ 'finally',
106
+ 'throw',
107
+ 'async',
108
+ 'await',
109
+ 'yield',
110
+ 'typeof',
111
+ 'instanceof',
112
+ 'in',
113
+ 'of',
114
+ 'delete',
115
+ 'void',
116
+ 'true',
117
+ 'false',
118
+ 'null',
119
+ 'undefined',
120
+ 'interface',
121
+ 'type',
122
+ 'enum',
123
+ 'implements',
124
+ 'public',
125
+ 'private',
126
+ 'protected',
127
+ 'readonly',
128
+ 'static',
129
+ 'abstract',
130
+ 'as',
85
131
  ]);
86
132
  const PYTHON_KEYWORDS = new Set([
87
- 'def', 'class', 'return', 'if', 'elif', 'else', 'for', 'while', 'import', 'from',
88
- 'as', 'try', 'except', 'finally', 'raise', 'with', 'yield', 'lambda', 'pass',
89
- 'break', 'continue', 'and', 'or', 'not', 'in', 'is', 'None', 'True', 'False',
90
- 'self', 'async', 'await', 'global', 'nonlocal', 'del', 'assert',
133
+ 'def',
134
+ 'class',
135
+ 'return',
136
+ 'if',
137
+ 'elif',
138
+ 'else',
139
+ 'for',
140
+ 'while',
141
+ 'import',
142
+ 'from',
143
+ 'as',
144
+ 'try',
145
+ 'except',
146
+ 'finally',
147
+ 'raise',
148
+ 'with',
149
+ 'yield',
150
+ 'lambda',
151
+ 'pass',
152
+ 'break',
153
+ 'continue',
154
+ 'and',
155
+ 'or',
156
+ 'not',
157
+ 'in',
158
+ 'is',
159
+ 'None',
160
+ 'True',
161
+ 'False',
162
+ 'self',
163
+ 'async',
164
+ 'await',
165
+ 'global',
166
+ 'nonlocal',
167
+ 'del',
168
+ 'assert',
91
169
  ]);
92
170
  const BASH_KEYWORDS = new Set([
93
- 'if', 'then', 'else', 'elif', 'fi', 'for', 'while', 'do', 'done', 'case', 'esac',
94
- 'function', 'return', 'exit', 'echo', 'export', 'source', 'local', 'readonly',
95
- 'set', 'unset', 'shift', 'cd', 'pwd', 'mkdir', 'rm', 'cp', 'mv', 'cat', 'grep',
96
- 'sed', 'awk', 'find', 'xargs', 'chmod', 'chown', 'sudo', 'apt', 'npm', 'git',
171
+ 'if',
172
+ 'then',
173
+ 'else',
174
+ 'elif',
175
+ 'fi',
176
+ 'for',
177
+ 'while',
178
+ 'do',
179
+ 'done',
180
+ 'case',
181
+ 'esac',
182
+ 'function',
183
+ 'return',
184
+ 'exit',
185
+ 'echo',
186
+ 'export',
187
+ 'source',
188
+ 'local',
189
+ 'readonly',
190
+ 'set',
191
+ 'unset',
192
+ 'shift',
193
+ 'cd',
194
+ 'pwd',
195
+ 'mkdir',
196
+ 'rm',
197
+ 'cp',
198
+ 'mv',
199
+ 'cat',
200
+ 'grep',
201
+ 'sed',
202
+ 'awk',
203
+ 'find',
204
+ 'xargs',
205
+ 'chmod',
206
+ 'chown',
207
+ 'sudo',
208
+ 'apt',
209
+ 'npm',
210
+ 'git',
97
211
  ]);
98
212
  /**
99
213
  * Minimal keyword-based syntax highlighting.
@@ -135,7 +249,12 @@ function highlightWithKeywords(escaped, keywords, lang) {
135
249
  // Highlight strings
136
250
  let result = escaped.replace(/(&#39;(?:[^&#]|&(?!#39;))*&#39;|&quot;(?:[^&]|&(?!quot;))*&quot;)/g, '<span class="hl-string">$1</span>');
137
251
  // Highlight comments
138
- if (lang === 'python' || lang === 'py' || lang === 'bash' || lang === 'sh' || lang === 'shell' || lang === 'zsh') {
252
+ if (lang === 'python' ||
253
+ lang === 'py' ||
254
+ lang === 'bash' ||
255
+ lang === 'sh' ||
256
+ lang === 'shell' ||
257
+ lang === 'zsh') {
139
258
  result = result.replace(/(#[^\n]*)/g, '<span class="hl-comment">$1</span>');
140
259
  }
141
260
  else {
@@ -424,27 +424,47 @@ function generateStatsCards(metrics, totalTokens, duration) {
424
424
  const toolCount = metrics.toolStats.reduce((sum, t) => sum + t.successCount + t.failureCount, 0);
425
425
  const cards = [
426
426
  { label: 'Messages', value: String(metrics.messageCount), sub: `${metrics.eventCount} events` },
427
- { label: 'Duration', value: duration, sub: metrics.sessionStartTime ? (0, htmlHelpers_1.formatTimestamp)(metrics.sessionStartTime) + ' start' : '' },
428
- { label: 'Total Tokens', value: (0, htmlHelpers_1.fmtTokens)(totalTokens), sub: `${(0, htmlHelpers_1.fmtTokens)(metrics.tokens.inputTokens)} in / ${(0, htmlHelpers_1.fmtTokens)(metrics.tokens.outputTokens)} out` },
429
- { label: 'Cache', value: (0, htmlHelpers_1.fmtTokens)(metrics.tokens.cacheReadTokens), sub: `${(0, htmlHelpers_1.fmtTokens)(metrics.tokens.cacheWriteTokens)} written` },
430
- { label: 'Tool Calls', value: String(toolCount), sub: `${metrics.toolStats.length} unique tools` },
427
+ {
428
+ label: 'Duration',
429
+ value: duration,
430
+ sub: metrics.sessionStartTime ? (0, htmlHelpers_1.formatTimestamp)(metrics.sessionStartTime) + ' start' : '',
431
+ },
432
+ {
433
+ label: 'Total Tokens',
434
+ value: (0, htmlHelpers_1.fmtTokens)(totalTokens),
435
+ sub: `${(0, htmlHelpers_1.fmtTokens)(metrics.tokens.inputTokens)} in / ${(0, htmlHelpers_1.fmtTokens)(metrics.tokens.outputTokens)} out`,
436
+ },
437
+ {
438
+ label: 'Cache',
439
+ value: (0, htmlHelpers_1.fmtTokens)(metrics.tokens.cacheReadTokens),
440
+ sub: `${(0, htmlHelpers_1.fmtTokens)(metrics.tokens.cacheWriteTokens)} written`,
441
+ },
442
+ {
443
+ label: 'Tool Calls',
444
+ value: String(toolCount),
445
+ sub: `${metrics.toolStats.length} unique tools`,
446
+ },
431
447
  ];
432
448
  if (metrics.tokens.reportedCost > 0) {
433
449
  cards.push({ label: 'Cost', value: (0, htmlHelpers_1.fmtCost)(metrics.tokens.reportedCost), sub: 'reported' });
434
450
  }
435
451
  return `<div class="container">
436
452
  <div class="stats-grid">
437
- ${cards.map(c => ` <div class="stat-card">
453
+ ${cards
454
+ .map((c) => ` <div class="stat-card">
438
455
  <div class="label">${c.label}</div>
439
456
  <div class="value">${c.value}</div>
440
457
  ${c.sub ? `<div class="sub">${c.sub}</div>` : ''}
441
- </div>`).join('\n')}
458
+ </div>`)
459
+ .join('\n')}
442
460
  </div>`;
443
461
  }
444
462
  function generateModelBreakdown(metrics) {
445
463
  if (metrics.modelStats.length === 0)
446
464
  return '';
447
- const rows = metrics.modelStats.map(m => `<tr><td>${(0, htmlHelpers_1.escapeHtml)(m.model)}</td><td>${m.calls}</td><td>${(0, htmlHelpers_1.fmtTokens)(m.tokens)}</td><td>${(0, htmlHelpers_1.fmtTokens)(m.inputTokens)} / ${(0, htmlHelpers_1.fmtTokens)(m.outputTokens)}</td><td>${(0, htmlHelpers_1.fmtCost)(m.cost)}</td></tr>`).join('\n');
465
+ const rows = metrics.modelStats
466
+ .map((m) => `<tr><td>${(0, htmlHelpers_1.escapeHtml)(m.model)}</td><td>${m.calls}</td><td>${(0, htmlHelpers_1.fmtTokens)(m.tokens)}</td><td>${(0, htmlHelpers_1.fmtTokens)(m.inputTokens)} / ${(0, htmlHelpers_1.fmtTokens)(m.outputTokens)}</td><td>${(0, htmlHelpers_1.fmtCost)(m.cost)}</td></tr>`)
467
+ .join('\n');
448
468
  return `<div class="section">
449
469
  <div class="section-title">Model Breakdown</div>
450
470
  <table>
@@ -456,13 +476,15 @@ function generateModelBreakdown(metrics) {
456
476
  function generateToolBreakdown(metrics) {
457
477
  if (metrics.toolStats.length === 0)
458
478
  return '';
459
- const sorted = [...metrics.toolStats].sort((a, b) => (b.successCount + b.failureCount) - (a.successCount + a.failureCount));
460
- const rows = sorted.map(t => {
479
+ const sorted = [...metrics.toolStats].sort((a, b) => b.successCount + b.failureCount - (a.successCount + a.failureCount));
480
+ const rows = sorted
481
+ .map((t) => {
461
482
  const total = t.successCount + t.failureCount;
462
483
  const avgMs = t.completedCount > 0 ? Math.round(t.totalDuration / t.completedCount) : 0;
463
484
  const failHtml = t.failureCount > 0 ? `<span style="color:var(--accent-red)">${t.failureCount}</span>` : '0';
464
485
  return `<tr><td><code>${(0, htmlHelpers_1.escapeHtml)(t.name)}</code></td><td>${total}</td><td>${failHtml}</td><td>${avgMs > 0 ? `${avgMs}ms` : '-'}</td></tr>`;
465
- }).join('\n');
486
+ })
487
+ .join('\n');
466
488
  return `<div class="section">
467
489
  <div class="section-title">Tool Breakdown</div>
468
490
  <table>
@@ -499,8 +521,12 @@ function generateTranscript(transcript, includeThinking, includeToolDetail) {
499
521
  function renderMessage(entry, includeThinking, includeToolDetail) {
500
522
  const ts = entry.timestamp ? (0, htmlHelpers_1.formatTimestamp)(entry.timestamp) : '';
501
523
  const roleBadge = `<span class="role-badge ${entry.type}">${entry.type}</span>`;
502
- const sourceStr = entry.sourceLabel ? `<span class="message-source">${(0, htmlHelpers_1.escapeHtml)(entry.sourceLabel)}</span>` : '';
503
- const modelStr = entry.model ? `<span class="message-model">${(0, htmlHelpers_1.escapeHtml)(entry.model)}</span>` : '';
524
+ const sourceStr = entry.sourceLabel
525
+ ? `<span class="message-source">${(0, htmlHelpers_1.escapeHtml)(entry.sourceLabel)}</span>`
526
+ : '';
527
+ const modelStr = entry.model
528
+ ? `<span class="message-model">${(0, htmlHelpers_1.escapeHtml)(entry.model)}</span>`
529
+ : '';
504
530
  const tokenStr = entry.usage
505
531
  ? `<span class="message-tokens">${(0, htmlHelpers_1.fmtTokens)(entry.usage.input_tokens + entry.usage.output_tokens)} tokens</span>`
506
532
  : '';
@@ -100,7 +100,10 @@ function eventToTranscriptEntry(event) {
100
100
  // Skip empty entries (warmup messages, etc.)
101
101
  if (content.length === 0) {
102
102
  if (usage || sourceLabel) {
103
- content.push({ type: 'text', text: sourceLabel ? `${sourceLabel} updated` : 'Usage updated' });
103
+ content.push({
104
+ type: 'text',
105
+ text: sourceLabel ? `${sourceLabel} updated` : 'Usage updated',
106
+ });
104
107
  }
105
108
  else {
106
109
  return null;
@@ -189,7 +192,10 @@ function extractToolResultContent(content) {
189
192
  if (Array.isArray(content)) {
190
193
  const parts = [];
191
194
  for (const part of content) {
192
- if (part && typeof part === 'object' && part.type === 'text' && typeof part.text === 'string') {
195
+ if (part &&
196
+ typeof part === 'object' &&
197
+ part.type === 'text' &&
198
+ typeof part.text === 'string') {
193
199
  parts.push(part.text);
194
200
  }
195
201
  }
@@ -16,14 +16,14 @@ export { quotaWindowSchema, quotaStateSchema, quotaFailureKindSchema, quotaProvi
16
16
  export type { QuotaWindow, QuotaState } from '../quota';
17
17
  export type { PeakHoursState } from '../peakHours';
18
18
  export type { QuotaFailureDescriptor } from '../quotaPresentation';
19
- export type { ProviderQuotaState, ProviderQuotaMap, RuntimeQuotaProvider, } from '../providerQuota';
19
+ export type { ProviderQuotaState, ProviderQuotaMap, RuntimeQuotaProvider } from '../providerQuota';
20
20
  export { quotaHistoryRuntimeProviderSchema, quotaHistorySampleSchema, quotaHistoryDailyBucketSchema, } from './quotaHistory';
21
21
  export type { QuotaHistoryRuntimeProvider, QuotaHistorySample, QuotaHistoryDailyBucket, } from '../quotaHistory';
22
- export { activeProviderAccountStatusSchema, activeAccountStatusSchema, } from './accountStatus';
23
- export type { ActiveProviderAccountStatus, ActiveAccountStatus, } from '../accountStatus';
22
+ export { activeProviderAccountStatusSchema, activeAccountStatusSchema } from './accountStatus';
23
+ export type { ActiveProviderAccountStatus, ActiveAccountStatus } from '../accountStatus';
24
24
  export { accountProviderIdSchema, accountManagerResultSchema, beginAccountLoginResultSchema, accountLoginStatusSchema, accountEntrySchema, savedAccountProfileSchema, listAllAccountsResultSchema, } from './accountManager';
25
25
  export type { AccountEntry, AccountManagerResult } from '../accounts';
26
- export type { AccountProviderId, SavedAccountProfile, } from '../accountRegistry';
26
+ export type { AccountProviderId, SavedAccountProfile } from '../accountRegistry';
27
27
  export type { AccountLoginStatus, BeginAccountLoginResult, ListAllAccountsResult, } from '../accountManager';
28
28
  export { assistantTurnEventSchema, assistantTurnEventTypeSchema, assistantTurnNarrationStepSchema, assistantTurnProcessSchema, assistantTurnProcessStepSchema, assistantTurnProjectionSchema, assistantTurnReasoningTimelineItemSchema, assistantTurnSubagentSchema, assistantTurnSubagentStatusSchema, assistantTurnTimelineItemSchema, assistantTurnToolGroupStepSchema, assistantTurnToolRefSchema, } from './assistantTurn';
29
29
  export type { AssistantTurnEvent, AssistantTurnEventType, AssistantTurnProcess, AssistantTurnProcessStep, AssistantTurnProjection, AssistantTurnSubagent, AssistantTurnSubagentStatus, AssistantTurnTimelineItem, AssistantTurnToolRef, } from '../turns/assistantTurn';
@@ -25,16 +25,8 @@ exports.quotaFailureKindSchema = zod_1.z.enum([
25
25
  'server',
26
26
  'unknown',
27
27
  ]);
28
- exports.quotaProviderIdSchema = zod_1.z.enum([
29
- 'claude-code',
30
- 'codex',
31
- 'zai',
32
- ]);
33
- exports.quotaSourceSchema = zod_1.z.enum([
34
- 'api',
35
- 'session',
36
- 'cache',
37
- ]);
28
+ exports.quotaProviderIdSchema = zod_1.z.enum(['claude-code', 'codex', 'zai']);
29
+ exports.quotaSourceSchema = zod_1.z.enum(['api', 'session', 'cache']);
38
30
  exports.quotaStateSchema = zod_1.z.object({
39
31
  fiveHour: exports.quotaWindowSchema,
40
32
  sevenDay: exports.quotaWindowSchema,