rafcode 2.4.1-0 → 2.5.0-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CLAUDE.md +4 -4
  2. package/RAF/ahwqwq-model-whisperer/decisions.md +22 -0
  3. package/RAF/ahwqwq-model-whisperer/input.md +5 -0
  4. package/RAF/ahwqwq-model-whisperer/outcomes/01-show-model-on-task-line.md +49 -0
  5. package/RAF/ahwqwq-model-whisperer/outcomes/02-use-claude-cost-estimation.md +107 -0
  6. package/RAF/ahwqwq-model-whisperer/outcomes/03-add-plan-resume-flag.md +87 -0
  7. package/RAF/ahwqwq-model-whisperer/plans/01-show-model-on-task-line.md +45 -0
  8. package/RAF/ahwqwq-model-whisperer/plans/02-use-claude-cost-estimation.md +115 -0
  9. package/RAF/ahwqwq-model-whisperer/plans/03-add-plan-resume-flag.md +70 -0
  10. package/dist/commands/do.js +13 -15
  11. package/dist/commands/do.js.map +1 -1
  12. package/dist/commands/plan.d.ts.map +1 -1
  13. package/dist/commands/plan.js +92 -1
  14. package/dist/commands/plan.js.map +1 -1
  15. package/dist/core/claude-runner.d.ts +8 -0
  16. package/dist/core/claude-runner.d.ts.map +1 -1
  17. package/dist/core/claude-runner.js +72 -0
  18. package/dist/core/claude-runner.js.map +1 -1
  19. package/dist/parsers/stream-renderer.d.ts +2 -0
  20. package/dist/parsers/stream-renderer.d.ts.map +1 -1
  21. package/dist/parsers/stream-renderer.js +2 -0
  22. package/dist/parsers/stream-renderer.js.map +1 -1
  23. package/dist/types/config.d.ts +4 -24
  24. package/dist/types/config.d.ts.map +1 -1
  25. package/dist/types/config.js +0 -24
  26. package/dist/types/config.js.map +1 -1
  27. package/dist/utils/config.d.ts +1 -26
  28. package/dist/utils/config.d.ts.map +1 -1
  29. package/dist/utils/config.js +2 -98
  30. package/dist/utils/config.js.map +1 -1
  31. package/dist/utils/terminal-symbols.d.ts +7 -16
  32. package/dist/utils/terminal-symbols.d.ts.map +1 -1
  33. package/dist/utils/terminal-symbols.js +16 -42
  34. package/dist/utils/terminal-symbols.js.map +1 -1
  35. package/dist/utils/token-tracker.d.ts +4 -30
  36. package/dist/utils/token-tracker.d.ts.map +1 -1
  37. package/dist/utils/token-tracker.js +17 -98
  38. package/dist/utils/token-tracker.js.map +1 -1
  39. package/package.json +1 -1
  40. package/src/commands/do.ts +14 -15
  41. package/src/commands/plan.ts +107 -1
  42. package/src/core/claude-runner.ts +81 -0
  43. package/src/parsers/stream-renderer.ts +4 -0
  44. package/src/prompts/config-docs.md +1 -72
  45. package/src/types/config.ts +4 -52
  46. package/src/utils/config.ts +2 -112
  47. package/src/utils/terminal-symbols.ts +16 -46
  48. package/src/utils/token-tracker.ts +19 -113
  49. package/tests/unit/claude-runner.test.ts +1 -0
  50. package/tests/unit/config-command.test.ts +4 -13
  51. package/tests/unit/config.test.ts +6 -148
  52. package/tests/unit/stream-renderer.test.ts +82 -0
  53. package/tests/unit/terminal-symbols.test.ts +86 -124
  54. package/tests/unit/token-tracker.test.ts +159 -679
@@ -8,7 +8,6 @@ import {
8
8
  formatCost,
9
9
  formatTaskTokenSummary,
10
10
  formatTokenTotalSummary,
11
- formatRateLimitPercentage,
12
11
  TokenSummaryOptions,
13
12
  TaskStatus,
14
13
  } from '../../src/utils/terminal-symbols.js';
@@ -110,6 +109,61 @@ describe('Terminal Symbols', () => {
110
109
  const result = formatTaskProgress(3, 5, 'failed', 'deploy', 45000, '003');
111
110
  expect(result).toBe('✗ 003-deploy 45s');
112
111
  });
112
+
113
+ it('should show model name in parentheses for running task with time', () => {
114
+ const result = formatTaskProgress(1, 5, 'running', 'auth-login', 83000, undefined, 'sonnet');
115
+ expect(result).toBe('● auth-login (sonnet) 1m 23s');
116
+ });
117
+
118
+ it('should show model name in parentheses for running task without time', () => {
119
+ const result = formatTaskProgress(1, 5, 'running', 'auth-login', undefined, undefined, 'opus');
120
+ expect(result).toBe('● auth-login (opus) 1/5');
121
+ });
122
+
123
+ it('should show model name in parentheses for completed task with time', () => {
124
+ const result = formatTaskProgress(3, 5, 'completed', 'setup-db', 154000, undefined, 'haiku');
125
+ expect(result).toBe('✓ setup-db (haiku) 2m 34s');
126
+ });
127
+
128
+ it('should show model name in parentheses for completed task without time', () => {
129
+ const result = formatTaskProgress(3, 5, 'completed', 'setup-db', undefined, undefined, 'sonnet');
130
+ expect(result).toBe('✓ setup-db (sonnet) 3/5');
131
+ });
132
+
133
+ it('should show model name in parentheses for failed task with time', () => {
134
+ const result = formatTaskProgress(2, 5, 'failed', 'deploy', 45000, undefined, 'opus');
135
+ expect(result).toBe('✗ deploy (opus) 45s');
136
+ });
137
+
138
+ it('should show model name in parentheses for failed task without time', () => {
139
+ const result = formatTaskProgress(2, 5, 'failed', 'deploy', undefined, undefined, 'haiku');
140
+ expect(result).toBe('✗ deploy (haiku) 2/5');
141
+ });
142
+
143
+ it('should show model name with task ID prefix', () => {
144
+ const result = formatTaskProgress(1, 5, 'running', 'auth-login', 83000, '001', 'sonnet');
145
+ expect(result).toBe('● 001-auth-login (sonnet) 1m 23s');
146
+ });
147
+
148
+ it('should show model name for blocked task without time', () => {
149
+ const result = formatTaskProgress(2, 5, 'blocked', 'depends-on-failed', undefined, undefined, 'sonnet');
150
+ expect(result).toBe('⊘ depends-on-failed (sonnet) 2/5');
151
+ });
152
+
153
+ it('should show model name for pending task', () => {
154
+ const result = formatTaskProgress(4, 5, 'pending', 'cleanup', undefined, undefined, 'haiku');
155
+ expect(result).toBe('○ cleanup (haiku) 4/5');
156
+ });
157
+
158
+ it('should not show model name when model is undefined', () => {
159
+ const result = formatTaskProgress(1, 5, 'running', 'auth-login', 83000, undefined, undefined);
160
+ expect(result).toBe('● auth-login 1m 23s');
161
+ });
162
+
163
+ it('should handle model name with task ID and time', () => {
164
+ const result = formatTaskProgress(2, 5, 'completed', 'setup-db', 154000, '002', 'opus');
165
+ expect(result).toBe('✓ 002-setup-db (opus) 2m 34s');
166
+ });
113
167
  });
114
168
 
115
169
  describe('formatProjectHeader', () => {
@@ -291,10 +345,6 @@ describe('Terminal Symbols', () => {
291
345
  });
292
346
 
293
347
  const makeCost = (total: number): CostBreakdown => ({
294
- inputCost: 0,
295
- outputCost: 0,
296
- cacheReadCost: 0,
297
- cacheCreateCost: 0,
298
348
  totalCost: total,
299
349
  });
300
350
 
@@ -309,67 +359,59 @@ describe('Terminal Symbols', () => {
309
359
  it('should format basic token summary without cache', () => {
310
360
  const usage = makeUsage();
311
361
  const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.42)));
312
- expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Est. cost: $0.42');
362
+ expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cost: $0.42');
313
363
  });
314
364
 
315
365
  it('should include cache read tokens', () => {
316
366
  const usage = makeUsage({ cacheReadInputTokens: 18500 });
317
367
  const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.42)));
318
- expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Est. cost: $0.42');
368
+ expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 18,500 read | Cost: $0.42');
319
369
  });
320
370
 
321
371
  it('should include cache creation tokens', () => {
322
372
  const usage = makeUsage({ cacheCreationInputTokens: 5000 });
323
373
  const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.55)));
324
- expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 5,000 created | Est. cost: $0.55');
374
+ expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 5,000 created | Cost: $0.55');
325
375
  });
326
376
 
327
377
  it('should include both cache read and creation tokens', () => {
328
378
  const usage = makeUsage({ cacheReadInputTokens: 18500, cacheCreationInputTokens: 5000 });
329
379
  const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.75)));
330
- expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 18,500 read / 5,000 created | Est. cost: $0.75');
380
+ expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cache: 18,500 read / 5,000 created | Cost: $0.75');
331
381
  });
332
382
 
333
383
  it('should format small costs with 4 decimal places', () => {
334
384
  const usage = makeUsage();
335
385
  const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.0042)));
336
- expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Est. cost: $0.0042');
386
+ expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cost: $0.0042');
337
387
  });
338
388
 
339
389
  it('should format single-attempt task with empty attempts array as single-line', () => {
340
390
  const usage = makeUsage();
341
391
  const result = formatTaskTokenSummary(makeEntry(usage, makeCost(0.42), []));
342
- expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Est. cost: $0.42');
392
+ expect(result).toBe(' Tokens: 5,234 in / 1,023 out | Cost: $0.42');
343
393
  });
344
394
  });
345
395
 
346
396
  describe('multi-attempt tasks', () => {
347
- it('should show per-attempt breakdown with costs when calculateAttemptCost provided', () => {
348
- const attempt1 = makeUsage({ inputTokens: 1234, outputTokens: 567 });
349
- const attempt2 = makeUsage({ inputTokens: 2345, outputTokens: 890 });
397
+ it('should show per-attempt breakdown with Claude-provided costs', () => {
398
+ const attempt1 = makeUsage({ inputTokens: 1234, outputTokens: 567, totalCostUsd: 0.02 });
399
+ const attempt2 = makeUsage({ inputTokens: 2345, outputTokens: 890, totalCostUsd: 0.04 });
350
400
  const totalUsage = makeUsage({ inputTokens: 3579, outputTokens: 1457 });
351
401
  const entry = makeEntry(totalUsage, makeCost(0.06), [attempt1, attempt2]);
352
402
 
353
- const calculateCost = (usage: UsageData): CostBreakdown => ({
354
- inputCost: 0,
355
- outputCost: 0,
356
- cacheReadCost: 0,
357
- cacheCreateCost: 0,
358
- totalCost: usage.inputTokens === 1234 ? 0.02 : 0.04,
359
- });
360
-
361
- const result = formatTaskTokenSummary(entry, calculateCost);
403
+ const result = formatTaskTokenSummary(entry);
362
404
  const lines = result.split('\n');
363
405
 
364
406
  expect(lines).toHaveLength(3);
365
- expect(lines[0]).toBe(' Attempt 1: 1,234 in / 567 out | Est. cost: $0.02');
366
- expect(lines[1]).toBe(' Attempt 2: 2,345 in / 890 out | Est. cost: $0.04');
367
- expect(lines[2]).toBe(' Total: 3,579 in / 1,457 out | Est. cost: $0.06');
407
+ expect(lines[0]).toBe(' Attempt 1: 1,234 in / 567 out | Cost: $0.02');
408
+ expect(lines[1]).toBe(' Attempt 2: 2,345 in / 890 out | Cost: $0.04');
409
+ expect(lines[2]).toBe(' Total: 3,579 in / 1,457 out | Cost: $0.06');
368
410
  });
369
411
 
370
- it('should show per-attempt breakdown without costs when no calculateAttemptCost', () => {
371
- const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200 });
372
- const attempt2 = makeUsage({ inputTokens: 2000, outputTokens: 400 });
412
+ it('should show per-attempt breakdown with zero costs', () => {
413
+ const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, totalCostUsd: 0 });
414
+ const attempt2 = makeUsage({ inputTokens: 2000, outputTokens: 400, totalCostUsd: 0 });
373
415
  const totalUsage = makeUsage({ inputTokens: 3000, outputTokens: 600 });
374
416
  const entry = makeEntry(totalUsage, makeCost(0.05), [attempt1, attempt2]);
375
417
 
@@ -377,14 +419,14 @@ describe('Terminal Symbols', () => {
377
419
  const lines = result.split('\n');
378
420
 
379
421
  expect(lines).toHaveLength(3);
380
- expect(lines[0]).toBe(' Attempt 1: 1,000 in / 200 out | Est. cost: $0.00');
381
- expect(lines[1]).toBe(' Attempt 2: 2,000 in / 400 out | Est. cost: $0.00');
382
- expect(lines[2]).toBe(' Total: 3,000 in / 600 out | Est. cost: $0.05');
422
+ expect(lines[0]).toBe(' Attempt 1: 1,000 in / 200 out | Cost: $0.00');
423
+ expect(lines[1]).toBe(' Attempt 2: 2,000 in / 400 out | Cost: $0.00');
424
+ expect(lines[2]).toBe(' Total: 3,000 in / 600 out | Cost: $0.05');
383
425
  });
384
426
 
385
427
  it('should include cache tokens in per-attempt breakdown', () => {
386
- const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, cacheReadInputTokens: 5000 });
387
- const attempt2 = makeUsage({ inputTokens: 1500, outputTokens: 300, cacheCreationInputTokens: 2000 });
428
+ const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, cacheReadInputTokens: 5000, totalCostUsd: 0.04 });
429
+ const attempt2 = makeUsage({ inputTokens: 1500, outputTokens: 300, cacheCreationInputTokens: 2000, totalCostUsd: 0.04 });
388
430
  const totalUsage = makeUsage({
389
431
  inputTokens: 2500,
390
432
  outputTokens: 500,
@@ -403,9 +445,9 @@ describe('Terminal Symbols', () => {
403
445
  });
404
446
 
405
447
  it('should handle three or more attempts', () => {
406
- const attempt1 = makeUsage({ inputTokens: 500, outputTokens: 100 });
407
- const attempt2 = makeUsage({ inputTokens: 600, outputTokens: 120 });
408
- const attempt3 = makeUsage({ inputTokens: 700, outputTokens: 140 });
448
+ const attempt1 = makeUsage({ inputTokens: 500, outputTokens: 100, totalCostUsd: 0.03 });
449
+ const attempt2 = makeUsage({ inputTokens: 600, outputTokens: 120, totalCostUsd: 0.03 });
450
+ const attempt3 = makeUsage({ inputTokens: 700, outputTokens: 140, totalCostUsd: 0.04 });
409
451
  const totalUsage = makeUsage({ inputTokens: 1800, outputTokens: 360 });
410
452
  const entry = makeEntry(totalUsage, makeCost(0.10), [attempt1, attempt2, attempt3]);
411
453
 
@@ -428,14 +470,11 @@ describe('Terminal Symbols', () => {
428
470
  cacheReadInputTokens: 0,
429
471
  cacheCreationInputTokens: 0,
430
472
  modelUsage: {},
473
+ totalCostUsd: 0,
431
474
  ...overrides,
432
475
  });
433
476
 
434
477
  const makeCost = (total: number): CostBreakdown => ({
435
- inputCost: 0,
436
- outputCost: 0,
437
- cacheReadCost: 0,
438
- cacheCreateCost: 0,
439
478
  totalCost: total,
440
479
  });
441
480
 
@@ -443,7 +482,7 @@ describe('Terminal Symbols', () => {
443
482
  const result = formatTokenTotalSummary(makeUsage(), makeCost(3.75));
444
483
  expect(result).toContain('Token Usage Summary');
445
484
  expect(result).toContain('Total tokens: 45,678 in / 12,345 out');
446
- expect(result).toContain('Estimated cost: $3.75');
485
+ expect(result).toContain('Total cost: $3.75');
447
486
  expect(result).not.toContain('Cache:');
448
487
  });
449
488
 
@@ -478,24 +517,6 @@ describe('Terminal Symbols', () => {
478
517
  expect(lines[lines.length - 1]).toContain('──');
479
518
  });
480
519
 
481
- it('should include rate limit percentage when option enabled', () => {
482
- const options: TokenSummaryOptions = {
483
- showRateLimitEstimate: true,
484
- rateLimitPercentage: 42.5,
485
- };
486
- const result = formatTokenTotalSummary(makeUsage(), makeCost(3.75), options);
487
- expect(result).toContain('~43% of 5h window');
488
- });
489
-
490
- it('should not include rate limit when option disabled', () => {
491
- const options: TokenSummaryOptions = {
492
- showRateLimitEstimate: false,
493
- rateLimitPercentage: 42.5,
494
- };
495
- const result = formatTokenTotalSummary(makeUsage(), makeCost(3.75), options);
496
- expect(result).not.toContain('5h window');
497
- });
498
-
499
520
  it('should hide cache tokens when option disabled', () => {
500
521
  const options: TokenSummaryOptions = {
501
522
  showCacheTokens: false,
@@ -509,30 +530,6 @@ describe('Terminal Symbols', () => {
509
530
  });
510
531
  });
511
532
 
512
- describe('formatRateLimitPercentage', () => {
513
- it('should format zero percentage', () => {
514
- expect(formatRateLimitPercentage(0)).toBe('~0% of 5h window');
515
- });
516
-
517
- it('should format very small percentages with 2 decimals', () => {
518
- expect(formatRateLimitPercentage(0.05)).toBe('~0.05% of 5h window');
519
- });
520
-
521
- it('should format small percentages with 1 decimal', () => {
522
- expect(formatRateLimitPercentage(0.5)).toBe('~0.5% of 5h window');
523
- });
524
-
525
- it('should round percentages >= 1', () => {
526
- expect(formatRateLimitPercentage(1.5)).toBe('~2% of 5h window');
527
- expect(formatRateLimitPercentage(42.3)).toBe('~42% of 5h window');
528
- expect(formatRateLimitPercentage(100)).toBe('~100% of 5h window');
529
- });
530
-
531
- it('should handle percentages over 100%', () => {
532
- expect(formatRateLimitPercentage(150.7)).toBe('~151% of 5h window');
533
- });
534
- });
535
-
536
533
  describe('formatTaskTokenSummary with options', () => {
537
534
  const makeUsage = (overrides: Partial<UsageData> = {}): UsageData => ({
538
535
  inputTokens: 5234,
@@ -544,10 +541,6 @@ describe('Terminal Symbols', () => {
544
541
  });
545
542
 
546
543
  const makeCost = (total: number): CostBreakdown => ({
547
- inputCost: 0,
548
- outputCost: 0,
549
- cacheReadCost: 0,
550
- cacheCreateCost: 0,
551
544
  totalCost: total,
552
545
  });
553
546
 
@@ -558,51 +551,20 @@ describe('Terminal Symbols', () => {
558
551
  attempts: attempts ?? [usage],
559
552
  });
560
553
 
561
- it('should include rate limit percentage in single-attempt summary', () => {
562
- const usage = makeUsage({ cacheReadInputTokens: 18500 });
563
- const entry = makeEntry(usage, makeCost(0.42));
564
- const options: TokenSummaryOptions = {
565
- showRateLimitEstimate: true,
566
- rateLimitPercentage: 2.5,
567
- };
568
-
569
- const result = formatTaskTokenSummary(entry, undefined, options);
570
- expect(result).toContain('~3% of 5h window');
571
- });
572
-
573
554
  it('should hide cache tokens in single-attempt summary when disabled', () => {
574
- const usage = makeUsage({ cacheReadInputTokens: 18500 });
555
+ const usage = makeUsage({ cacheReadInputTokens: 18500, totalCostUsd: 0.42 });
575
556
  const entry = makeEntry(usage, makeCost(0.42));
576
557
  const options: TokenSummaryOptions = {
577
558
  showCacheTokens: false,
578
559
  };
579
560
 
580
- const result = formatTaskTokenSummary(entry, undefined, options);
561
+ const result = formatTaskTokenSummary(entry, options);
581
562
  expect(result).not.toContain('Cache:');
582
563
  });
583
564
 
584
- it('should only show rate limit on total line in multi-attempt summary', () => {
585
- const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200 });
586
- const attempt2 = makeUsage({ inputTokens: 2000, outputTokens: 400 });
587
- const totalUsage = makeUsage({ inputTokens: 3000, outputTokens: 600 });
588
- const entry = makeEntry(totalUsage, makeCost(0.05), [attempt1, attempt2]);
589
- const options: TokenSummaryOptions = {
590
- showRateLimitEstimate: true,
591
- rateLimitPercentage: 1.5,
592
- };
593
-
594
- const result = formatTaskTokenSummary(entry, undefined, options);
595
- const lines = result.split('\n');
596
-
597
- // Rate limit should only appear on the Total line
598
- expect(lines[0]).not.toContain('5h window'); // Attempt 1
599
- expect(lines[1]).not.toContain('5h window'); // Attempt 2
600
- expect(lines[2]).toContain('~2% of 5h window'); // Total
601
- });
602
-
603
565
  it('should respect showCacheTokens in multi-attempt summary', () => {
604
- const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, cacheReadInputTokens: 5000 });
605
- const attempt2 = makeUsage({ inputTokens: 1500, outputTokens: 300, cacheCreationInputTokens: 2000 });
566
+ const attempt1 = makeUsage({ inputTokens: 1000, outputTokens: 200, cacheReadInputTokens: 5000, totalCostUsd: 0.04 });
567
+ const attempt2 = makeUsage({ inputTokens: 1500, outputTokens: 300, cacheCreationInputTokens: 2000, totalCostUsd: 0.04 });
606
568
  const totalUsage = makeUsage({
607
569
  inputTokens: 2500,
608
570
  outputTokens: 500,
@@ -614,7 +576,7 @@ describe('Terminal Symbols', () => {
614
576
  showCacheTokens: false,
615
577
  };
616
578
 
617
- const result = formatTaskTokenSummary(entry, undefined, options);
579
+ const result = formatTaskTokenSummary(entry, options);
618
580
  expect(result).not.toContain('Cache:');
619
581
  });
620
582
  });