rafcode 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +4 -1
- package/CLAUDE.md +59 -11
- package/RAF/ahslfe-config-wizard/decisions.md +34 -0
- package/RAF/ahslfe-config-wizard/input.md +1 -0
- package/RAF/ahslfe-config-wizard/outcomes/01-define-config-schema.md +38 -0
- package/RAF/ahslfe-config-wizard/outcomes/02-refactor-codebase-to-use-config.md +67 -0
- package/RAF/ahslfe-config-wizard/outcomes/03-create-config-documentation.md +37 -0
- package/RAF/ahslfe-config-wizard/outcomes/04-implement-raf-config-command.md +47 -0
- package/RAF/ahslfe-config-wizard/outcomes/05-update-claude-md.md +26 -0
- package/RAF/ahslfe-config-wizard/plans/01-define-config-schema.md +73 -0
- package/RAF/ahslfe-config-wizard/plans/02-refactor-codebase-to-use-config.md +74 -0
- package/RAF/ahslfe-config-wizard/plans/03-create-config-documentation.md +57 -0
- package/RAF/ahslfe-config-wizard/plans/04-implement-raf-config-command.md +66 -0
- package/RAF/ahslfe-config-wizard/plans/05-update-claude-md.md +60 -0
- package/RAF/ahstvo-token-tracker/decisions.md +44 -0
- package/RAF/ahstvo-token-tracker/input.md +3 -0
- package/RAF/ahstvo-token-tracker/outcomes/01-full-model-id-support.md +43 -0
- package/RAF/ahstvo-token-tracker/outcomes/02-name-generation-no-session.md +33 -0
- package/RAF/ahstvo-token-tracker/outcomes/03-unify-stream-json-execution.md +48 -0
- package/RAF/ahstvo-token-tracker/outcomes/04-token-tracking-cost-calculation.md +53 -0
- package/RAF/ahstvo-token-tracker/outcomes/05-token-cost-console-reporting.md +57 -0
- package/RAF/ahstvo-token-tracker/outcomes/06-runtime-verbose-toggle.md +53 -0
- package/RAF/ahstvo-token-tracker/outcomes/07-readme-config-docs.md +36 -0
- package/RAF/ahstvo-token-tracker/plans/01-full-model-id-support.md +35 -0
- package/RAF/ahstvo-token-tracker/plans/02-name-generation-no-session.md +36 -0
- package/RAF/ahstvo-token-tracker/plans/03-unify-stream-json-execution.md +44 -0
- package/RAF/ahstvo-token-tracker/plans/04-token-tracking-cost-calculation.md +56 -0
- package/RAF/ahstvo-token-tracker/plans/05-token-cost-console-reporting.md +55 -0
- package/RAF/ahstvo-token-tracker/plans/06-runtime-verbose-toggle.md +48 -0
- package/RAF/ahstvo-token-tracker/plans/07-readme-config-docs.md +44 -0
- package/README.md +34 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +173 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/do.d.ts.map +1 -1
- package/dist/commands/do.js +50 -28
- package/dist/commands/do.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +3 -2
- package/dist/commands/plan.js.map +1 -1
- package/dist/core/claude-runner.d.ts +17 -13
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +42 -257
- package/dist/core/claude-runner.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +6 -3
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/git.d.ts.map +1 -1
- package/dist/core/git.js +10 -3
- package/dist/core/git.js.map +1 -1
- package/dist/core/pull-request.d.ts +1 -1
- package/dist/core/pull-request.d.ts.map +1 -1
- package/dist/core/pull-request.js +7 -4
- package/dist/core/pull-request.js.map +1 -1
- package/dist/core/shutdown-handler.d.ts.map +1 -1
- package/dist/core/shutdown-handler.js +0 -4
- package/dist/core/shutdown-handler.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/stream-renderer.d.ts +16 -4
- package/dist/parsers/stream-renderer.d.ts.map +1 -1
- package/dist/parsers/stream-renderer.js +35 -5
- package/dist/parsers/stream-renderer.js.map +1 -1
- package/dist/prompts/execution.d.ts.map +1 -1
- package/dist/prompts/execution.js +11 -1
- package/dist/prompts/execution.js.map +1 -1
- package/dist/types/config.d.ts +95 -5
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +63 -3
- package/dist/types/config.js.map +1 -1
- package/dist/utils/config.d.ts +59 -7
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +276 -21
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/name-generator.d.ts +3 -7
- package/dist/utils/name-generator.d.ts.map +1 -1
- package/dist/utils/name-generator.js +75 -61
- package/dist/utils/name-generator.js.map +1 -1
- package/dist/utils/terminal-symbols.d.ts +21 -0
- package/dist/utils/terminal-symbols.d.ts.map +1 -1
- package/dist/utils/terminal-symbols.js +62 -0
- package/dist/utils/terminal-symbols.js.map +1 -1
- package/dist/utils/token-tracker.d.ts +45 -0
- package/dist/utils/token-tracker.d.ts.map +1 -0
- package/dist/utils/token-tracker.js +107 -0
- package/dist/utils/token-tracker.js.map +1 -0
- package/dist/utils/validation.d.ts +5 -5
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +10 -6
- package/dist/utils/validation.js.map +1 -1
- package/dist/utils/verbose-toggle.d.ts +33 -0
- package/dist/utils/verbose-toggle.d.ts.map +1 -0
- package/dist/utils/verbose-toggle.js +94 -0
- package/dist/utils/verbose-toggle.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/config.ts +204 -0
- package/src/commands/do.ts +59 -27
- package/src/commands/plan.ts +3 -2
- package/src/core/claude-runner.ts +58 -311
- package/src/core/failure-analyzer.ts +6 -3
- package/src/core/git.ts +10 -3
- package/src/core/pull-request.ts +7 -4
- package/src/core/shutdown-handler.ts +0 -5
- package/src/index.ts +2 -0
- package/src/parsers/stream-renderer.ts +55 -8
- package/src/prompts/config-docs.md +331 -0
- package/src/prompts/execution.ts +13 -1
- package/src/types/config.ts +156 -8
- package/src/utils/config.ts +335 -21
- package/src/utils/name-generator.ts +84 -71
- package/src/utils/terminal-symbols.ts +68 -0
- package/src/utils/token-tracker.ts +135 -0
- package/src/utils/validation.ts +15 -10
- package/src/utils/verbose-toggle.ts +103 -0
- package/tests/unit/claude-runner.test.ts +216 -403
- package/tests/unit/config-command.test.ts +163 -0
- package/tests/unit/config.test.ts +608 -30
- package/tests/unit/name-generator.test.ts +99 -75
- package/tests/unit/pull-request.test.ts +2 -0
- package/tests/unit/stream-renderer.test.ts +83 -30
- package/tests/unit/terminal-symbols.test.ts +157 -0
- package/tests/unit/token-tracker.test.ts +352 -0
- package/tests/unit/verbose-toggle.test.ts +204 -0
- package/RAF/ahrtxf-session-sentinel/decisions.md +0 -19
- package/RAF/ahrtxf-session-sentinel/input.md +0 -1
- package/RAF/ahrtxf-session-sentinel/outcomes/01-capture-session-id.md +0 -37
- package/RAF/ahrtxf-session-sentinel/outcomes/02-resume-flag.md +0 -45
- package/RAF/ahrtxf-session-sentinel/plans/01-capture-session-id.md +0 -41
- package/RAF/ahrtxf-session-sentinel/plans/02-resume-flag.md +0 -51
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { UsageData, PricingConfig } from '../types/config.js';
|
|
2
|
+
import { resolveModelPricingCategory, getPricingConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
/** Cost breakdown for a single task or accumulated total. */
|
|
5
|
+
export interface CostBreakdown {
|
|
6
|
+
inputCost: number;
|
|
7
|
+
outputCost: number;
|
|
8
|
+
cacheReadCost: number;
|
|
9
|
+
cacheCreateCost: number;
|
|
10
|
+
totalCost: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Per-task usage snapshot stored by the tracker. */
|
|
14
|
+
export interface TaskUsageEntry {
|
|
15
|
+
taskId: string;
|
|
16
|
+
usage: UsageData;
|
|
17
|
+
cost: CostBreakdown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Accumulates token usage across multiple task executions and calculates costs
|
|
22
|
+
* using configurable per-model pricing.
|
|
23
|
+
*/
|
|
24
|
+
export class TokenTracker {
|
|
25
|
+
private entries: TaskUsageEntry[] = [];
|
|
26
|
+
private pricingConfig: PricingConfig;
|
|
27
|
+
|
|
28
|
+
constructor(pricingConfig?: PricingConfig) {
|
|
29
|
+
this.pricingConfig = pricingConfig ?? getPricingConfig();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Record usage data from a completed task.
|
|
34
|
+
*/
|
|
35
|
+
addTask(taskId: string, usage: UsageData): TaskUsageEntry {
|
|
36
|
+
const cost = this.calculateCost(usage);
|
|
37
|
+
const entry: TaskUsageEntry = { taskId, usage, cost };
|
|
38
|
+
this.entries.push(entry);
|
|
39
|
+
return entry;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get all recorded task entries.
|
|
44
|
+
*/
|
|
45
|
+
getEntries(): readonly TaskUsageEntry[] {
|
|
46
|
+
return this.entries;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get accumulated totals across all tasks.
|
|
51
|
+
*/
|
|
52
|
+
getTotals(): { usage: UsageData; cost: CostBreakdown } {
|
|
53
|
+
const totalUsage: UsageData = {
|
|
54
|
+
inputTokens: 0,
|
|
55
|
+
outputTokens: 0,
|
|
56
|
+
cacheReadInputTokens: 0,
|
|
57
|
+
cacheCreationInputTokens: 0,
|
|
58
|
+
modelUsage: {},
|
|
59
|
+
};
|
|
60
|
+
const totalCost: CostBreakdown = {
|
|
61
|
+
inputCost: 0,
|
|
62
|
+
outputCost: 0,
|
|
63
|
+
cacheReadCost: 0,
|
|
64
|
+
cacheCreateCost: 0,
|
|
65
|
+
totalCost: 0,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
for (const entry of this.entries) {
|
|
69
|
+
totalUsage.inputTokens += entry.usage.inputTokens;
|
|
70
|
+
totalUsage.outputTokens += entry.usage.outputTokens;
|
|
71
|
+
totalUsage.cacheReadInputTokens += entry.usage.cacheReadInputTokens;
|
|
72
|
+
totalUsage.cacheCreationInputTokens += entry.usage.cacheCreationInputTokens;
|
|
73
|
+
|
|
74
|
+
// Merge per-model usage
|
|
75
|
+
for (const [modelId, modelUsage] of Object.entries(entry.usage.modelUsage)) {
|
|
76
|
+
const existing = totalUsage.modelUsage[modelId];
|
|
77
|
+
if (existing) {
|
|
78
|
+
existing.inputTokens += modelUsage.inputTokens;
|
|
79
|
+
existing.outputTokens += modelUsage.outputTokens;
|
|
80
|
+
existing.cacheReadInputTokens += modelUsage.cacheReadInputTokens;
|
|
81
|
+
existing.cacheCreationInputTokens += modelUsage.cacheCreationInputTokens;
|
|
82
|
+
} else {
|
|
83
|
+
totalUsage.modelUsage[modelId] = { ...modelUsage };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
totalCost.inputCost += entry.cost.inputCost;
|
|
88
|
+
totalCost.outputCost += entry.cost.outputCost;
|
|
89
|
+
totalCost.cacheReadCost += entry.cost.cacheReadCost;
|
|
90
|
+
totalCost.cacheCreateCost += entry.cost.cacheCreateCost;
|
|
91
|
+
totalCost.totalCost += entry.cost.totalCost;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { usage: totalUsage, cost: totalCost };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Calculate cost for a given UsageData using per-model pricing.
|
|
99
|
+
* Uses per-model breakdown when available, falls back to aggregate with sonnet pricing.
|
|
100
|
+
*/
|
|
101
|
+
calculateCost(usage: UsageData): CostBreakdown {
|
|
102
|
+
const result: CostBreakdown = {
|
|
103
|
+
inputCost: 0,
|
|
104
|
+
outputCost: 0,
|
|
105
|
+
cacheReadCost: 0,
|
|
106
|
+
cacheCreateCost: 0,
|
|
107
|
+
totalCost: 0,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const modelEntries = Object.entries(usage.modelUsage);
|
|
111
|
+
|
|
112
|
+
if (modelEntries.length > 0) {
|
|
113
|
+
// Use per-model breakdown for accurate pricing
|
|
114
|
+
for (const [modelId, modelUsage] of modelEntries) {
|
|
115
|
+
const category = resolveModelPricingCategory(modelId);
|
|
116
|
+
const pricing = this.pricingConfig[category ?? 'sonnet'];
|
|
117
|
+
|
|
118
|
+
result.inputCost += (modelUsage.inputTokens / 1_000_000) * pricing.inputPerMTok;
|
|
119
|
+
result.outputCost += (modelUsage.outputTokens / 1_000_000) * pricing.outputPerMTok;
|
|
120
|
+
result.cacheReadCost += (modelUsage.cacheReadInputTokens / 1_000_000) * pricing.cacheReadPerMTok;
|
|
121
|
+
result.cacheCreateCost += (modelUsage.cacheCreationInputTokens / 1_000_000) * pricing.cacheCreatePerMTok;
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// Fallback: use aggregate totals with sonnet pricing
|
|
125
|
+
const pricing = this.pricingConfig.sonnet;
|
|
126
|
+
result.inputCost = (usage.inputTokens / 1_000_000) * pricing.inputPerMTok;
|
|
127
|
+
result.outputCost = (usage.outputTokens / 1_000_000) * pricing.outputPerMTok;
|
|
128
|
+
result.cacheReadCost = (usage.cacheReadInputTokens / 1_000_000) * pricing.cacheReadPerMTok;
|
|
129
|
+
result.cacheCreateCost = (usage.cacheCreationInputTokens / 1_000_000) * pricing.cacheCreatePerMTok;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
result.totalCost = result.inputCost + result.outputCost + result.cacheReadCost + result.cacheCreateCost;
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/utils/validation.ts
CHANGED
|
@@ -2,6 +2,9 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { execSync } from 'node:child_process';
|
|
4
4
|
import { logger } from './logger.js';
|
|
5
|
+
import type { ClaudeModelName, ModelScenario } from '../types/config.js';
|
|
6
|
+
import { VALID_MODEL_ALIASES, FULL_MODEL_ID_PATTERN } from '../types/config.js';
|
|
7
|
+
import { getModel } from './config.js';
|
|
5
8
|
|
|
6
9
|
export interface ValidationResult {
|
|
7
10
|
valid: boolean;
|
|
@@ -88,19 +91,21 @@ export function reportValidation(result: ValidationResult): void {
|
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
|
|
94
|
+
/** @deprecated Use ClaudeModelName from types/config.js instead */
|
|
95
|
+
export type ValidModelName = ClaudeModelName;
|
|
92
96
|
|
|
93
|
-
export
|
|
94
|
-
|
|
95
|
-
export function validateModelName(model: string): ValidModelName | null {
|
|
97
|
+
export function validateModelName(model: string): ClaudeModelName | null {
|
|
96
98
|
const normalized = model.toLowerCase();
|
|
97
|
-
if (
|
|
98
|
-
return normalized as
|
|
99
|
+
if ((VALID_MODEL_ALIASES as readonly string[]).includes(normalized)) {
|
|
100
|
+
return normalized as ClaudeModelName;
|
|
101
|
+
}
|
|
102
|
+
if (FULL_MODEL_ID_PATTERN.test(normalized)) {
|
|
103
|
+
return normalized as ClaudeModelName;
|
|
99
104
|
}
|
|
100
105
|
return null;
|
|
101
106
|
}
|
|
102
107
|
|
|
103
|
-
export function resolveModelOption(model?: string, sonnet?: boolean):
|
|
108
|
+
export function resolveModelOption(model?: string, sonnet?: boolean, scenario: ModelScenario = 'execute'): ClaudeModelName {
|
|
104
109
|
// Check for conflicting flags
|
|
105
110
|
if (model && sonnet) {
|
|
106
111
|
throw new Error('Cannot specify both --model and --sonnet flags');
|
|
@@ -115,11 +120,11 @@ export function resolveModelOption(model?: string, sonnet?: boolean): ValidModel
|
|
|
115
120
|
if (model) {
|
|
116
121
|
const validated = validateModelName(model);
|
|
117
122
|
if (!validated) {
|
|
118
|
-
throw new Error(`Invalid model name: "${model}". Valid options: ${
|
|
123
|
+
throw new Error(`Invalid model name: "${model}". Valid options: ${VALID_MODEL_ALIASES.join(', ')} or a full model ID (e.g., claude-sonnet-4-5-20250929)`);
|
|
119
124
|
}
|
|
120
125
|
return validated;
|
|
121
126
|
}
|
|
122
127
|
|
|
123
|
-
// Default
|
|
124
|
-
return
|
|
128
|
+
// Default from config
|
|
129
|
+
return getModel(scenario);
|
|
125
130
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime verbose toggle for task execution.
|
|
3
|
+
*
|
|
4
|
+
* Listens for Tab keypress on process.stdin to toggle verbose display on/off.
|
|
5
|
+
* When verbose is on, tool-use activity lines from stream-json are displayed.
|
|
6
|
+
* When verbose is off, they are suppressed (but data is still captured).
|
|
7
|
+
*
|
|
8
|
+
* Requires a TTY stdin. Silently skips setup when stdin is not a TTY (e.g., piped input).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { logger } from './logger.js';
|
|
12
|
+
|
|
13
|
+
export class VerboseToggle {
|
|
14
|
+
private _verbose: boolean;
|
|
15
|
+
private _active = false;
|
|
16
|
+
private _dataHandler: ((data: Buffer) => void) | null = null;
|
|
17
|
+
|
|
18
|
+
constructor(initialVerbose: boolean) {
|
|
19
|
+
this._verbose = initialVerbose;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Current verbose display state. */
|
|
23
|
+
get isVerbose(): boolean {
|
|
24
|
+
return this._verbose;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Whether the toggle listener is currently active. */
|
|
28
|
+
get isActive(): boolean {
|
|
29
|
+
return this._active;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Start listening for Tab keypress on stdin.
|
|
34
|
+
* Sets stdin to raw mode to capture individual keypresses.
|
|
35
|
+
* Shows a hint message about the toggle.
|
|
36
|
+
*
|
|
37
|
+
* No-op if stdin is not a TTY or if already active.
|
|
38
|
+
*/
|
|
39
|
+
start(): void {
|
|
40
|
+
if (this._active) return;
|
|
41
|
+
if (!process.stdin.isTTY) return;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
process.stdin.setRawMode(true);
|
|
45
|
+
} catch {
|
|
46
|
+
// Cannot set raw mode — skip
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
process.stdin.resume();
|
|
51
|
+
|
|
52
|
+
this._dataHandler = (data: Buffer) => {
|
|
53
|
+
for (let i = 0; i < data.length; i++) {
|
|
54
|
+
const byte = data[i];
|
|
55
|
+
|
|
56
|
+
if (byte === 0x09) {
|
|
57
|
+
// Tab key
|
|
58
|
+
this._verbose = !this._verbose;
|
|
59
|
+
const state = this._verbose ? 'on' : 'off';
|
|
60
|
+
logger.dim(` [verbose: ${state}]`);
|
|
61
|
+
} else if (byte === 0x03) {
|
|
62
|
+
// Ctrl+C — re-emit SIGINT so the shutdown handler catches it
|
|
63
|
+
process.emit('SIGINT');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
process.stdin.on('data', this._dataHandler);
|
|
69
|
+
this._active = true;
|
|
70
|
+
|
|
71
|
+
// Show toggle hint
|
|
72
|
+
logger.dim(' Press Tab to toggle verbose mode');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Stop listening and restore stdin to normal mode.
|
|
77
|
+
* Safe to call multiple times.
|
|
78
|
+
*/
|
|
79
|
+
stop(): void {
|
|
80
|
+
if (!this._active) return;
|
|
81
|
+
|
|
82
|
+
if (this._dataHandler) {
|
|
83
|
+
process.stdin.off('data', this._dataHandler);
|
|
84
|
+
this._dataHandler = null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
if (process.stdin.isTTY) {
|
|
89
|
+
process.stdin.setRawMode(false);
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
// Ignore — stdin may already be closed
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
process.stdin.pause();
|
|
97
|
+
} catch {
|
|
98
|
+
// Ignore
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this._active = false;
|
|
102
|
+
}
|
|
103
|
+
}
|