aiblueprint-cli 1.4.12 → 1.4.14

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 (69) hide show
  1. package/claude-code-config/scripts/.claude/commands/fix-on-my-computer.md +87 -0
  2. package/claude-code-config/scripts/CLAUDE.md +50 -0
  3. package/claude-code-config/scripts/{statusline/biome.json → biome.json} +5 -2
  4. package/claude-code-config/scripts/bun.lockb +0 -0
  5. package/claude-code-config/scripts/command-validator/CLAUDE.md +112 -0
  6. package/claude-code-config/scripts/command-validator/src/__tests__/validator.test.ts +62 -111
  7. package/claude-code-config/scripts/command-validator/src/cli.ts +5 -3
  8. package/claude-code-config/scripts/command-validator/src/lib/security-rules.ts +3 -4
  9. package/claude-code-config/scripts/command-validator/src/lib/types.ts +1 -0
  10. package/claude-code-config/scripts/command-validator/src/lib/validator.ts +47 -317
  11. package/claude-code-config/scripts/package.json +39 -0
  12. package/claude-code-config/scripts/statusline/CLAUDE.md +29 -7
  13. package/claude-code-config/scripts/statusline/README.md +89 -1
  14. package/claude-code-config/scripts/statusline/__tests__/context.test.ts +229 -0
  15. package/claude-code-config/scripts/statusline/__tests__/formatters.test.ts +108 -0
  16. package/claude-code-config/scripts/statusline/__tests__/statusline.test.ts +309 -0
  17. package/claude-code-config/scripts/statusline/data/.gitignore +8 -0
  18. package/claude-code-config/scripts/statusline/data/.gitkeep +0 -0
  19. package/claude-code-config/scripts/statusline/defaults.json +79 -0
  20. package/claude-code-config/scripts/statusline/docs/ARCHITECTURE.md +166 -0
  21. package/claude-code-config/scripts/statusline/fixtures/mock-transcript.jsonl +4 -0
  22. package/claude-code-config/scripts/statusline/fixtures/test-input.json +12 -2
  23. package/claude-code-config/scripts/statusline/src/index.ts +175 -24
  24. package/claude-code-config/scripts/statusline/src/lib/config-types.ts +104 -0
  25. package/claude-code-config/scripts/statusline/src/lib/config.ts +21 -0
  26. package/claude-code-config/scripts/statusline/src/lib/context.ts +32 -11
  27. package/claude-code-config/scripts/statusline/src/lib/formatters.ts +360 -22
  28. package/claude-code-config/scripts/statusline/src/lib/git.ts +100 -0
  29. package/claude-code-config/scripts/statusline/src/lib/menu-factories.ts +224 -0
  30. package/claude-code-config/scripts/statusline/src/lib/presets.ts +177 -0
  31. package/claude-code-config/scripts/statusline/src/lib/render-pure.ts +497 -0
  32. package/claude-code-config/scripts/statusline/src/lib/types.ts +11 -0
  33. package/claude-code-config/scripts/statusline/src/lib/utils.ts +15 -0
  34. package/claude-code-config/scripts/statusline/src/tests/spend-v2.test.ts +306 -0
  35. package/claude-code-config/scripts/statusline/statusline.config.json +79 -0
  36. package/claude-code-config/scripts/statusline/test-with-fixtures.ts +37 -0
  37. package/claude-code-config/scripts/tsconfig.json +27 -0
  38. package/claude-code-config/skills/claude-memory/SKILL.md +689 -0
  39. package/claude-code-config/skills/claude-memory/references/comprehensive-example.md +175 -0
  40. package/claude-code-config/skills/claude-memory/references/project-patterns.md +334 -0
  41. package/claude-code-config/skills/claude-memory/references/prompting-techniques.md +411 -0
  42. package/claude-code-config/skills/claude-memory/references/section-templates.md +347 -0
  43. package/claude-code-config/skills/create-slash-commands/SKILL.md +1110 -0
  44. package/claude-code-config/skills/create-slash-commands/references/arguments.md +273 -0
  45. package/claude-code-config/skills/create-slash-commands/references/patterns.md +947 -0
  46. package/claude-code-config/skills/create-slash-commands/references/prompt-examples.md +656 -0
  47. package/claude-code-config/skills/create-slash-commands/references/tool-restrictions.md +389 -0
  48. package/claude-code-config/skills/create-subagents/SKILL.md +425 -0
  49. package/claude-code-config/skills/create-subagents/references/context-management.md +567 -0
  50. package/claude-code-config/skills/create-subagents/references/debugging-agents.md +714 -0
  51. package/claude-code-config/skills/create-subagents/references/error-handling-and-recovery.md +502 -0
  52. package/claude-code-config/skills/create-subagents/references/evaluation-and-testing.md +374 -0
  53. package/claude-code-config/skills/create-subagents/references/orchestration-patterns.md +591 -0
  54. package/claude-code-config/skills/create-subagents/references/subagents.md +599 -0
  55. package/claude-code-config/skills/create-subagents/references/writing-subagent-prompts.md +513 -0
  56. package/package.json +1 -1
  57. package/claude-code-config/commands/apex.md +0 -109
  58. package/claude-code-config/commands/tasks/run-task.md +0 -220
  59. package/claude-code-config/commands/utils/watch-ci.md +0 -47
  60. package/claude-code-config/scripts/command-validator/biome.json +0 -29
  61. package/claude-code-config/scripts/command-validator/bun.lockb +0 -0
  62. package/claude-code-config/scripts/command-validator/package.json +0 -27
  63. package/claude-code-config/scripts/command-validator/vitest.config.ts +0 -7
  64. package/claude-code-config/scripts/hook-post-file.ts +0 -162
  65. package/claude-code-config/scripts/statusline/bun.lockb +0 -0
  66. package/claude-code-config/scripts/statusline/package.json +0 -19
  67. package/claude-code-config/scripts/statusline/statusline.config.ts +0 -25
  68. package/claude-code-config/scripts/validate-command.js +0 -712
  69. package/claude-code-config/scripts/validate-command.readme.md +0 -283
@@ -0,0 +1,497 @@
1
+ /**
2
+ * Pure statusline renderer - no I/O, no side effects
3
+ *
4
+ * ARCHITECTURE: Raw data in, formatted string out.
5
+ * ALL config decisions happen here, not in data preparation.
6
+ */
7
+
8
+ import type { StatuslineConfig } from "./config-types";
9
+ import {
10
+ colors,
11
+ formatCost,
12
+ formatDuration,
13
+ formatPath,
14
+ formatProgressBar,
15
+ formatResetTime,
16
+ formatTokens,
17
+ } from "./formatters";
18
+
19
+ const WEEKLY_HOURS = 168; // 7 days * 24 hours
20
+ const FIVE_HOUR_MINUTES = 300; // 5 hours * 60 minutes
21
+
22
+ // ─────────────────────────────────────────────────────────────
23
+ // RAW DATA TYPES - No pre-formatting, just raw values
24
+ // ─────────────────────────────────────────────────────────────
25
+
26
+ export interface GitChanges {
27
+ files: number;
28
+ added: number;
29
+ deleted: number;
30
+ }
31
+
32
+ export interface RawGitData {
33
+ branch: string;
34
+ dirty: boolean;
35
+ staged: GitChanges;
36
+ unstaged: GitChanges;
37
+ }
38
+
39
+ export interface UsageLimit {
40
+ utilization: number;
41
+ resets_at: string | null;
42
+ }
43
+
44
+ export interface RawStatuslineData {
45
+ git: RawGitData | null;
46
+ path: string;
47
+ modelName: string;
48
+ cost: number;
49
+ durationMs: number;
50
+ contextTokens: number | null;
51
+ contextPercentage: number | null;
52
+ usageLimits?: {
53
+ five_hour: UsageLimit | null;
54
+ seven_day: UsageLimit | null;
55
+ };
56
+ periodCost?: number;
57
+ todayCost?: number;
58
+ }
59
+
60
+ // Legacy interface for backwards compatibility
61
+ export interface StatuslineData {
62
+ branch: string;
63
+ dirPath: string;
64
+ modelName: string;
65
+ sessionCost: string;
66
+ sessionDuration: string;
67
+ contextTokens: number | null;
68
+ contextPercentage: number | null;
69
+ usageLimits?: {
70
+ five_hour: UsageLimit | null;
71
+ seven_day: UsageLimit | null;
72
+ };
73
+ periodCost?: number;
74
+ todayCost?: number;
75
+ }
76
+
77
+ // ─────────────────────────────────────────────────────────────
78
+ // FORMATTING - All config-aware formatting in one place
79
+ // ─────────────────────────────────────────────────────────────
80
+
81
+ function formatGitPart(
82
+ git: RawGitData | null,
83
+ config: StatuslineConfig["git"],
84
+ ): string {
85
+ if (!git || !config.enabled) return "";
86
+
87
+ const parts: string[] = [];
88
+
89
+ if (config.showBranch) {
90
+ parts.push(colors.lightGray(git.branch));
91
+ }
92
+
93
+ if (git.dirty && config.showDirtyIndicator) {
94
+ // Append to branch name without space
95
+ if (parts.length > 0) {
96
+ parts[parts.length - 1] += colors.purple("*");
97
+ } else {
98
+ parts.push(colors.purple("*"));
99
+ }
100
+ }
101
+
102
+ const changeParts: string[] = [];
103
+
104
+ if (config.showChanges) {
105
+ const totalAdded = git.staged.added + git.unstaged.added;
106
+ const totalDeleted = git.staged.deleted + git.unstaged.deleted;
107
+ if (totalAdded > 0) changeParts.push(colors.green(`+${totalAdded}`));
108
+ if (totalDeleted > 0) changeParts.push(colors.red(`-${totalDeleted}`));
109
+ }
110
+
111
+ if (config.showStaged && git.staged.files > 0) {
112
+ changeParts.push(colors.gray(`~${git.staged.files}`));
113
+ }
114
+
115
+ if (config.showUnstaged && git.unstaged.files > 0) {
116
+ changeParts.push(colors.yellow(`~${git.unstaged.files}`));
117
+ }
118
+
119
+ if (changeParts.length > 0) {
120
+ parts.push(changeParts.join(" "));
121
+ }
122
+
123
+ return parts.join(" ");
124
+ }
125
+
126
+ function formatSessionPart(
127
+ cost: number,
128
+ durationMs: number,
129
+ contextTokens: number | null,
130
+ contextPercentage: number | null,
131
+ maxTokens: number,
132
+ config: StatuslineConfig["session"],
133
+ ): string {
134
+ // No context data yet - show placeholder
135
+ if (contextTokens === null || contextPercentage === null) {
136
+ return `${colors.gray("S:")} ${colors.gray("-")}`;
137
+ }
138
+
139
+ const items: string[] = [];
140
+
141
+ if (config.cost.enabled) {
142
+ const formattedCost = formatCost(cost, config.cost.format);
143
+ items.push(`${colors.gray("$")}${colors.dimWhite(formattedCost)}`);
144
+ }
145
+
146
+ if (config.tokens.enabled) {
147
+ const formattedUsed = formatTokens(
148
+ contextTokens,
149
+ config.tokens.showDecimals,
150
+ );
151
+ if (config.tokens.showMax) {
152
+ const formattedMax = formatTokens(maxTokens, config.tokens.showDecimals);
153
+ items.push(`${formattedUsed}${colors.gray("/")}${formattedMax}`);
154
+ } else {
155
+ items.push(formattedUsed);
156
+ }
157
+ }
158
+
159
+ if (config.percentage.enabled) {
160
+ const pctParts: string[] = [];
161
+
162
+ if (config.percentage.progressBar.enabled) {
163
+ pctParts.push(
164
+ formatProgressBar({
165
+ percentage: contextPercentage,
166
+ length: config.percentage.progressBar.length,
167
+ style: config.percentage.progressBar.style,
168
+ colorMode: config.percentage.progressBar.color,
169
+ background: config.percentage.progressBar.background,
170
+ }),
171
+ );
172
+ }
173
+
174
+ if (config.percentage.showValue) {
175
+ pctParts.push(
176
+ `${colors.lightGray(contextPercentage.toString())}${colors.gray("%")}`,
177
+ );
178
+ }
179
+
180
+ if (pctParts.length > 0) {
181
+ items.push(pctParts.join(" "));
182
+ }
183
+ }
184
+
185
+ if (config.duration.enabled) {
186
+ items.push(colors.gray(`(${formatDuration(durationMs)})`));
187
+ }
188
+
189
+ if (items.length === 0) return "";
190
+
191
+ const sep = config.infoSeparator
192
+ ? ` ${colors.gray(config.infoSeparator)} `
193
+ : " ";
194
+ return `${colors.gray("S:")} ${items.join(sep)}`;
195
+ }
196
+
197
+ function formatLimitsPart(
198
+ fiveHour: UsageLimit | null,
199
+ periodCost: number,
200
+ config: StatuslineConfig["limits"],
201
+ ): string {
202
+ if (!config.enabled || !fiveHour) return "";
203
+
204
+ const parts: string[] = [];
205
+
206
+ if (config.cost.enabled && periodCost > 0) {
207
+ parts.push(
208
+ `${colors.gray("$")}${colors.dimWhite(formatCost(periodCost, config.cost.format))}`,
209
+ );
210
+ }
211
+
212
+ if (config.percentage.enabled) {
213
+ if (config.percentage.progressBar.enabled) {
214
+ parts.push(
215
+ formatProgressBar({
216
+ percentage: fiveHour.utilization,
217
+ length: config.percentage.progressBar.length,
218
+ style: config.percentage.progressBar.style,
219
+ colorMode: config.percentage.progressBar.color,
220
+ background: config.percentage.progressBar.background,
221
+ }),
222
+ );
223
+ }
224
+
225
+ if (config.percentage.showValue) {
226
+ parts.push(
227
+ `${colors.lightGray(fiveHour.utilization.toString())}${colors.gray("%")}`,
228
+ );
229
+ }
230
+ }
231
+
232
+ if (config.showPacingDelta && fiveHour.resets_at) {
233
+ const delta = calculateFiveHourDelta(
234
+ fiveHour.utilization,
235
+ fiveHour.resets_at,
236
+ );
237
+ parts.push(
238
+ `${colors.gray("(")}${formatPacingDelta(delta)}${colors.gray(")")}`,
239
+ );
240
+ }
241
+
242
+ if (config.showTimeLeft && fiveHour.resets_at) {
243
+ parts.push(colors.gray(`(${formatResetTime(fiveHour.resets_at)})`));
244
+ }
245
+
246
+ return parts.length > 0 ? `${colors.gray("L:")} ${parts.join(" ")}` : "";
247
+ }
248
+
249
+ function shouldShowWeekly(
250
+ config: StatuslineConfig["weeklyUsage"],
251
+ fiveHourUtilization: number | null,
252
+ ): boolean {
253
+ if (config.enabled === true) return true;
254
+ if (config.enabled === false) return false;
255
+ if (config.enabled === "90%" && fiveHourUtilization !== null) {
256
+ return fiveHourUtilization >= 90;
257
+ }
258
+ return false;
259
+ }
260
+
261
+ function calculateWeeklyDelta(
262
+ utilization: number,
263
+ resetsAt: string | null,
264
+ ): number {
265
+ if (!resetsAt) return 0;
266
+
267
+ const resetDate = new Date(resetsAt);
268
+ const now = new Date();
269
+ const diffMs = resetDate.getTime() - now.getTime();
270
+ const hoursRemaining = Math.max(0, diffMs / 3600000);
271
+ const timeElapsedPercent =
272
+ ((WEEKLY_HOURS - hoursRemaining) / WEEKLY_HOURS) * 100;
273
+
274
+ return utilization - timeElapsedPercent;
275
+ }
276
+
277
+ function formatPacingDelta(delta: number): string {
278
+ const sign = delta >= 0 ? "+" : "";
279
+ const value = `${sign}${delta.toFixed(1)}%`;
280
+
281
+ if (delta > 5) return colors.green(value);
282
+ if (delta > 0) return colors.lightGray(value);
283
+ if (delta > -10) return colors.yellow(value);
284
+ return colors.red(value);
285
+ }
286
+
287
+ function calculateFiveHourDelta(
288
+ utilization: number,
289
+ resetsAt: string | null,
290
+ ): number {
291
+ if (!resetsAt) return 0;
292
+
293
+ const resetDate = new Date(resetsAt);
294
+ const now = new Date();
295
+ const diffMs = resetDate.getTime() - now.getTime();
296
+ const minutesRemaining = Math.max(0, diffMs / 60000);
297
+ const timeElapsedPercent =
298
+ ((FIVE_HOUR_MINUTES - minutesRemaining) / FIVE_HOUR_MINUTES) * 100;
299
+
300
+ return utilization - timeElapsedPercent;
301
+ }
302
+
303
+ function formatWeeklyPart(
304
+ sevenDay: UsageLimit | null,
305
+ fiveHourUtilization: number | null,
306
+ periodCost: number,
307
+ config: StatuslineConfig["weeklyUsage"],
308
+ ): string {
309
+ if (!shouldShowWeekly(config, fiveHourUtilization) || !sevenDay) return "";
310
+
311
+ const parts: string[] = [];
312
+
313
+ if (config.cost.enabled && periodCost > 0) {
314
+ parts.push(
315
+ `${colors.gray("$")}${colors.dimWhite(formatCost(periodCost, config.cost.format))}`,
316
+ );
317
+ }
318
+
319
+ if (config.percentage.enabled) {
320
+ if (config.percentage.progressBar.enabled) {
321
+ parts.push(
322
+ formatProgressBar({
323
+ percentage: sevenDay.utilization,
324
+ length: config.percentage.progressBar.length,
325
+ style: config.percentage.progressBar.style,
326
+ colorMode: config.percentage.progressBar.color,
327
+ background: config.percentage.progressBar.background,
328
+ }),
329
+ );
330
+ }
331
+
332
+ if (config.percentage.showValue) {
333
+ parts.push(
334
+ `${colors.lightGray(sevenDay.utilization.toString())}${colors.gray("%")}`,
335
+ );
336
+ }
337
+ }
338
+
339
+ if (config.showPacingDelta && sevenDay.resets_at) {
340
+ const delta = calculateWeeklyDelta(
341
+ sevenDay.utilization,
342
+ sevenDay.resets_at,
343
+ );
344
+ parts.push(
345
+ `${colors.gray("(")}${formatPacingDelta(delta)}${colors.gray(")")}`,
346
+ );
347
+ }
348
+
349
+ if (config.showTimeLeft && sevenDay.resets_at) {
350
+ parts.push(colors.gray(`(${formatResetTime(sevenDay.resets_at)})`));
351
+ }
352
+
353
+ return parts.length > 0 ? `${colors.gray("W:")} ${parts.join(" ")}` : "";
354
+ }
355
+
356
+ function formatDailyPart(
357
+ todayCost: number,
358
+ config: StatuslineConfig["dailySpend"],
359
+ ): string {
360
+ if (!config.cost.enabled || todayCost <= 0) return "";
361
+ return `${colors.gray("D:")} ${colors.gray("$")}${colors.dimWhite(formatCost(todayCost, config.cost.format))}`;
362
+ }
363
+
364
+ // ─────────────────────────────────────────────────────────────
365
+ // MAIN RENDER FUNCTION - Raw data + config = output
366
+ // ─────────────────────────────────────────────────────────────
367
+
368
+ export function renderStatuslineRaw(
369
+ data: RawStatuslineData,
370
+ config: StatuslineConfig,
371
+ ): string {
372
+ const sep = colors.gray(config.separator);
373
+ const sections: string[] = [];
374
+
375
+ // Line 1: Git + Path + Model
376
+ const line1Parts: string[] = [];
377
+
378
+ const gitPart = formatGitPart(data.git, config.git);
379
+ if (gitPart) line1Parts.push(gitPart);
380
+
381
+ const pathPart = formatPath(data.path, config.pathDisplayMode);
382
+ line1Parts.push(colors.gray(pathPart));
383
+
384
+ const isSonnet = data.modelName.toLowerCase().includes("sonnet");
385
+ if (!isSonnet || config.showSonnetModel) {
386
+ line1Parts.push(colors.peach(data.modelName));
387
+ }
388
+
389
+ sections.push(line1Parts.join(` ${sep} `));
390
+
391
+ // Line 2: Session info
392
+ const sessionPart = formatSessionPart(
393
+ data.cost,
394
+ data.durationMs,
395
+ data.contextTokens,
396
+ data.contextPercentage,
397
+ config.context.maxContextTokens,
398
+ config.session,
399
+ );
400
+ if (sessionPart) sections.push(sessionPart);
401
+
402
+ // Limits
403
+ const limitsPart = formatLimitsPart(
404
+ data.usageLimits?.five_hour ?? null,
405
+ data.periodCost ?? 0,
406
+ config.limits,
407
+ );
408
+ if (limitsPart) sections.push(limitsPart);
409
+
410
+ // Weekly
411
+ const weeklyPart = formatWeeklyPart(
412
+ data.usageLimits?.seven_day ?? null,
413
+ data.usageLimits?.five_hour?.utilization ?? null,
414
+ data.periodCost ?? 0,
415
+ config.weeklyUsage,
416
+ );
417
+ if (weeklyPart) sections.push(weeklyPart);
418
+
419
+ // Daily
420
+ const dailyPart = formatDailyPart(data.todayCost ?? 0, config.dailySpend);
421
+ if (dailyPart) sections.push(dailyPart);
422
+
423
+ const output = sections.join(` ${sep} `);
424
+
425
+ if (config.oneLine) return output;
426
+
427
+ // Two-line mode: break after line1
428
+ const line1 = sections[0];
429
+ const rest = sections.slice(1).join(` ${sep} `);
430
+ return rest ? `${line1}\n${rest}` : line1;
431
+ }
432
+
433
+ // ─────────────────────────────────────────────────────────────
434
+ // LEGACY SUPPORT - For backwards compatibility with old data format
435
+ // ─────────────────────────────────────────────────────────────
436
+
437
+ export function renderStatusline(
438
+ data: StatuslineData,
439
+ config: StatuslineConfig,
440
+ ): string {
441
+ // Convert legacy format to raw format
442
+ // Parse pre-formatted values back to raw (best effort)
443
+ const rawData: RawStatuslineData = {
444
+ git: parseGitFromBranch(data.branch),
445
+ path: data.dirPath.startsWith("~") ? data.dirPath : data.dirPath,
446
+ modelName: data.modelName,
447
+ cost: parseFloat(data.sessionCost.replace(/[$,]/g, "")) || 0,
448
+ durationMs: parseDurationToMs(data.sessionDuration),
449
+ contextTokens: data.contextTokens,
450
+ contextPercentage: data.contextPercentage,
451
+ usageLimits: data.usageLimits,
452
+ periodCost: data.periodCost,
453
+ todayCost: data.todayCost,
454
+ };
455
+
456
+ return renderStatuslineRaw(rawData, config);
457
+ }
458
+
459
+ // Helper to parse legacy branch string back to git data
460
+ function parseGitFromBranch(branch: string): RawGitData | null {
461
+ if (!branch) return null;
462
+
463
+ // Parse "main* +10 -5" format
464
+ const dirty = branch.includes("*");
465
+ const branchName =
466
+ branch.replace(/\*.*$/, "").replace(/\*/, "").trim() || "main";
467
+
468
+ const addMatch = branch.match(/\+(\d+)/);
469
+ const delMatch = branch.match(/-(\d+)/);
470
+ const added = addMatch ? parseInt(addMatch[1], 10) : 0;
471
+ const deleted = delMatch ? parseInt(delMatch[1], 10) : 0;
472
+
473
+ return {
474
+ branch: branchName,
475
+ dirty,
476
+ staged: {
477
+ files: 0,
478
+ added: Math.floor(added / 2),
479
+ deleted: Math.floor(deleted / 2),
480
+ },
481
+ unstaged: {
482
+ files: 0,
483
+ added: Math.ceil(added / 2),
484
+ deleted: Math.ceil(deleted / 2),
485
+ },
486
+ };
487
+ }
488
+
489
+ // Helper to parse "12m" or "1h 30m" back to ms
490
+ function parseDurationToMs(duration: string): number {
491
+ let ms = 0;
492
+ const hourMatch = duration.match(/(\d+)h/);
493
+ const minMatch = duration.match(/(\d+)m/);
494
+ if (hourMatch) ms += parseInt(hourMatch[1], 10) * 3600000;
495
+ if (minMatch) ms += parseInt(minMatch[1], 10) * 60000;
496
+ return ms || 720000; // Default 12 minutes
497
+ }
@@ -21,5 +21,16 @@ export interface HookInput {
21
21
  total_lines_added: number;
22
22
  total_lines_removed: number;
23
23
  };
24
+ context_window?: {
25
+ total_input_tokens: number;
26
+ total_output_tokens: number;
27
+ context_window_size: number;
28
+ current_usage?: {
29
+ input_tokens: number;
30
+ output_tokens: number;
31
+ cache_creation_input_tokens?: number;
32
+ cache_read_input_tokens?: number;
33
+ };
34
+ };
24
35
  exceeds_200k_tokens?: boolean;
25
36
  }
@@ -0,0 +1,15 @@
1
+ export function normalizeResetsAt(resetsAt: string): string {
2
+ try {
3
+ const date = new Date(resetsAt);
4
+ const minutes = date.getMinutes();
5
+ const roundedMinutes = Math.round(minutes / 5) * 5;
6
+
7
+ date.setMinutes(roundedMinutes);
8
+ date.setSeconds(0);
9
+ date.setMilliseconds(0);
10
+
11
+ return date.toISOString();
12
+ } catch {
13
+ return resetsAt;
14
+ }
15
+ }