rafcode 2.1.1 → 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 +47 -6
- 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 +19 -2
- package/dist/core/claude-runner.d.ts.map +1 -1
- package/dist/core/claude-runner.js +43 -96
- 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/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/stream-renderer.d.ts +16 -1
- package/dist/parsers/stream-renderer.d.ts.map +1 -1
- package/dist/parsers/stream-renderer.js +34 -4
- 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 -4
- 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 +56 -5
- package/src/commands/plan.ts +3 -2
- package/src/core/claude-runner.ts +59 -115
- 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/index.ts +2 -0
- package/src/parsers/stream-renderer.ts +54 -4
- package/src/prompts/config-docs.md +331 -0
- package/src/prompts/execution.ts +13 -1
- package/src/types/config.ts +156 -7
- 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 +171 -7
- 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 -0
- 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
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
export declare class VerboseToggle {
|
|
11
|
+
private _verbose;
|
|
12
|
+
private _active;
|
|
13
|
+
private _dataHandler;
|
|
14
|
+
constructor(initialVerbose: boolean);
|
|
15
|
+
/** Current verbose display state. */
|
|
16
|
+
get isVerbose(): boolean;
|
|
17
|
+
/** Whether the toggle listener is currently active. */
|
|
18
|
+
get isActive(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Start listening for Tab keypress on stdin.
|
|
21
|
+
* Sets stdin to raw mode to capture individual keypresses.
|
|
22
|
+
* Shows a hint message about the toggle.
|
|
23
|
+
*
|
|
24
|
+
* No-op if stdin is not a TTY or if already active.
|
|
25
|
+
*/
|
|
26
|
+
start(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Stop listening and restore stdin to normal mode.
|
|
29
|
+
* Safe to call multiple times.
|
|
30
|
+
*/
|
|
31
|
+
stop(): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=verbose-toggle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verbose-toggle.d.ts","sourceRoot":"","sources":["../../src/utils/verbose-toggle.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAyC;gBAEjD,cAAc,EAAE,OAAO;IAInC,qCAAqC;IACrC,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,uDAAuD;IACvD,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;;;;OAMG;IACH,KAAK,IAAI,IAAI;IAoCb;;;OAGG;IACH,IAAI,IAAI,IAAI;CAwBb"}
|
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
import { logger } from './logger.js';
|
|
11
|
+
export class VerboseToggle {
|
|
12
|
+
_verbose;
|
|
13
|
+
_active = false;
|
|
14
|
+
_dataHandler = null;
|
|
15
|
+
constructor(initialVerbose) {
|
|
16
|
+
this._verbose = initialVerbose;
|
|
17
|
+
}
|
|
18
|
+
/** Current verbose display state. */
|
|
19
|
+
get isVerbose() {
|
|
20
|
+
return this._verbose;
|
|
21
|
+
}
|
|
22
|
+
/** Whether the toggle listener is currently active. */
|
|
23
|
+
get isActive() {
|
|
24
|
+
return this._active;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Start listening for Tab keypress on stdin.
|
|
28
|
+
* Sets stdin to raw mode to capture individual keypresses.
|
|
29
|
+
* Shows a hint message about the toggle.
|
|
30
|
+
*
|
|
31
|
+
* No-op if stdin is not a TTY or if already active.
|
|
32
|
+
*/
|
|
33
|
+
start() {
|
|
34
|
+
if (this._active)
|
|
35
|
+
return;
|
|
36
|
+
if (!process.stdin.isTTY)
|
|
37
|
+
return;
|
|
38
|
+
try {
|
|
39
|
+
process.stdin.setRawMode(true);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Cannot set raw mode — skip
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
process.stdin.resume();
|
|
46
|
+
this._dataHandler = (data) => {
|
|
47
|
+
for (let i = 0; i < data.length; i++) {
|
|
48
|
+
const byte = data[i];
|
|
49
|
+
if (byte === 0x09) {
|
|
50
|
+
// Tab key
|
|
51
|
+
this._verbose = !this._verbose;
|
|
52
|
+
const state = this._verbose ? 'on' : 'off';
|
|
53
|
+
logger.dim(` [verbose: ${state}]`);
|
|
54
|
+
}
|
|
55
|
+
else if (byte === 0x03) {
|
|
56
|
+
// Ctrl+C — re-emit SIGINT so the shutdown handler catches it
|
|
57
|
+
process.emit('SIGINT');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
process.stdin.on('data', this._dataHandler);
|
|
62
|
+
this._active = true;
|
|
63
|
+
// Show toggle hint
|
|
64
|
+
logger.dim(' Press Tab to toggle verbose mode');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Stop listening and restore stdin to normal mode.
|
|
68
|
+
* Safe to call multiple times.
|
|
69
|
+
*/
|
|
70
|
+
stop() {
|
|
71
|
+
if (!this._active)
|
|
72
|
+
return;
|
|
73
|
+
if (this._dataHandler) {
|
|
74
|
+
process.stdin.off('data', this._dataHandler);
|
|
75
|
+
this._dataHandler = null;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
if (process.stdin.isTTY) {
|
|
79
|
+
process.stdin.setRawMode(false);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore — stdin may already be closed
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
process.stdin.pause();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Ignore
|
|
90
|
+
}
|
|
91
|
+
this._active = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=verbose-toggle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verbose-toggle.js","sourceRoot":"","sources":["../../src/utils/verbose-toggle.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,OAAO,aAAa;IAChB,QAAQ,CAAU;IAClB,OAAO,GAAG,KAAK,CAAC;IAChB,YAAY,GAAoC,IAAI,CAAC;IAE7D,YAAY,cAAuB;QACjC,IAAI,CAAC,QAAQ,GAAG,cAAc,CAAC;IACjC,CAAC;IAED,qCAAqC;IACrC,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,uDAAuD;IACvD,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;;;OAMG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK;YAAE,OAAO;QAEjC,IAAI,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;YAC7B,OAAO;QACT,CAAC;QAED,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAEvB,IAAI,CAAC,YAAY,GAAG,CAAC,IAAY,EAAE,EAAE;YACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBAErB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBAClB,UAAU;oBACV,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;oBAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;oBAC3C,MAAM,CAAC,GAAG,CAAC,eAAe,KAAK,GAAG,CAAC,CAAC;gBACtC,CAAC;qBAAM,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBACzB,6DAA6D;oBAC7D,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QAEpB,mBAAmB;QACnB,MAAM,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IACnD,CAAC;IAED;;;OAGG;IACH,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAE1B,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;YAC7C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QAC3B,CAAC;QAED,IAAI,CAAC;YACH,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;gBACxB,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;QAED,IAAI,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;IACvB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as readline from 'node:readline';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { ClaudeRunner } from '../core/claude-runner.js';
|
|
7
|
+
import { shutdownHandler } from '../core/shutdown-handler.js';
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import {
|
|
10
|
+
getConfigPath,
|
|
11
|
+
getModel,
|
|
12
|
+
getEffort,
|
|
13
|
+
validateConfig,
|
|
14
|
+
ConfigValidationError,
|
|
15
|
+
} from '../utils/config.js';
|
|
16
|
+
|
|
17
|
+
interface ConfigCommandOptions {
|
|
18
|
+
reset?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load the config documentation markdown from src/prompts/config-docs.md.
|
|
23
|
+
* Resolved relative to this file's location in the dist/ tree.
|
|
24
|
+
*/
|
|
25
|
+
function loadConfigDocs(): string {
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = path.dirname(__filename);
|
|
28
|
+
// From dist/commands/config.js -> ../../src/prompts/config-docs.md
|
|
29
|
+
const docsPath = path.join(__dirname, '..', '..', 'src', 'prompts', 'config-docs.md');
|
|
30
|
+
return fs.readFileSync(docsPath, 'utf-8');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read the current user config file contents, or a message indicating no file exists.
|
|
35
|
+
*/
|
|
36
|
+
function getCurrentConfigState(configPath: string): string {
|
|
37
|
+
if (!fs.existsSync(configPath)) {
|
|
38
|
+
return 'No config file exists yet. All settings use defaults. The file will be created at: ' + configPath;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
42
|
+
return `Current config file (${configPath}):\n\`\`\`json\n${content}\`\`\``;
|
|
43
|
+
} catch {
|
|
44
|
+
return 'Config file exists but could not be read: ' + configPath;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build the system prompt for the config editing Claude session.
|
|
50
|
+
*/
|
|
51
|
+
function buildConfigSystemPrompt(configDocs: string, configState: string): string {
|
|
52
|
+
return [
|
|
53
|
+
'You are helping the user edit their RAF configuration.',
|
|
54
|
+
'You have full permission to read and write ~/.raf/raf.config.json.',
|
|
55
|
+
'',
|
|
56
|
+
'# Current Config State',
|
|
57
|
+
configState,
|
|
58
|
+
'',
|
|
59
|
+
'# Config Documentation',
|
|
60
|
+
configDocs,
|
|
61
|
+
].join('\n');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validate the config file after the Claude session ends and report results.
|
|
66
|
+
*/
|
|
67
|
+
function postSessionValidation(configPath: string): void {
|
|
68
|
+
if (!fs.existsSync(configPath)) {
|
|
69
|
+
logger.info('No config file exists — using all defaults.');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
75
|
+
const parsed: unknown = JSON.parse(content);
|
|
76
|
+
validateConfig(parsed);
|
|
77
|
+
logger.success('Config updated successfully.');
|
|
78
|
+
|
|
79
|
+
// Show a summary of what's set
|
|
80
|
+
const userConfig = parsed as Record<string, unknown>;
|
|
81
|
+
const keys = Object.keys(userConfig);
|
|
82
|
+
if (keys.length > 0) {
|
|
83
|
+
logger.info(`Custom settings: ${keys.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof ConfigValidationError) {
|
|
87
|
+
logger.warn(`Config validation warning: ${error.message}`);
|
|
88
|
+
logger.warn('The file was not deleted — you can fix it manually or run `raf config` again.');
|
|
89
|
+
} else if (error instanceof SyntaxError) {
|
|
90
|
+
logger.warn('Config file contains invalid JSON.');
|
|
91
|
+
logger.warn('The file was not deleted — you can fix it manually or run `raf config` again.');
|
|
92
|
+
} else {
|
|
93
|
+
logger.warn(`Could not validate config: ${error}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Prompt the user for Y/N confirmation via readline.
|
|
100
|
+
*/
|
|
101
|
+
async function confirm(message: string): Promise<boolean> {
|
|
102
|
+
const rl = readline.createInterface({
|
|
103
|
+
input: process.stdin,
|
|
104
|
+
output: process.stdout,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return new Promise((resolve) => {
|
|
108
|
+
rl.question(message, (answer) => {
|
|
109
|
+
rl.close();
|
|
110
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createConfigCommand(): Command {
|
|
116
|
+
const command = new Command('config')
|
|
117
|
+
.description('View and edit RAF configuration with Claude')
|
|
118
|
+
.argument('[prompt...]', 'Optional initial prompt for the config session')
|
|
119
|
+
.option('--reset', 'Delete config file and restore all defaults')
|
|
120
|
+
.action(async (promptParts: string[], options: ConfigCommandOptions) => {
|
|
121
|
+
if (options.reset) {
|
|
122
|
+
await handleReset();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const initialPrompt = promptParts.length > 0 ? promptParts.join(' ') : undefined;
|
|
127
|
+
await runConfigSession(initialPrompt);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return command;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function handleReset(): Promise<void> {
|
|
134
|
+
const configPath = getConfigPath();
|
|
135
|
+
|
|
136
|
+
if (!fs.existsSync(configPath)) {
|
|
137
|
+
logger.info('No config file exists — already using defaults.');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const confirmed = await confirm(
|
|
142
|
+
'This will delete ~/.raf/raf.config.json and restore all defaults. Continue? [y/N] '
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (!confirmed) {
|
|
146
|
+
logger.info('Cancelled.');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fs.unlinkSync(configPath);
|
|
151
|
+
logger.success('Config file deleted. All settings restored to defaults.');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function runConfigSession(initialPrompt?: string): Promise<void> {
|
|
155
|
+
const configPath = getConfigPath();
|
|
156
|
+
const model = getModel('config');
|
|
157
|
+
const effort = getEffort('config');
|
|
158
|
+
|
|
159
|
+
// Set effort level env var for the Claude session
|
|
160
|
+
process.env['CLAUDE_CODE_EFFORT_LEVEL'] = effort;
|
|
161
|
+
|
|
162
|
+
// Load config docs
|
|
163
|
+
let configDocs: string;
|
|
164
|
+
try {
|
|
165
|
+
configDocs = loadConfigDocs();
|
|
166
|
+
} catch (error) {
|
|
167
|
+
logger.error(`Failed to load config documentation: ${error}`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Build system prompt
|
|
172
|
+
const configState = getCurrentConfigState(configPath);
|
|
173
|
+
const systemPrompt = buildConfigSystemPrompt(configDocs, configState);
|
|
174
|
+
|
|
175
|
+
// Build user message
|
|
176
|
+
const userMessage = initialPrompt
|
|
177
|
+
?? 'Show me my current config and help me make changes.';
|
|
178
|
+
|
|
179
|
+
// Set up Claude runner
|
|
180
|
+
const claudeRunner = new ClaudeRunner({ model });
|
|
181
|
+
shutdownHandler.init();
|
|
182
|
+
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
183
|
+
|
|
184
|
+
logger.info('Starting config session with Claude...');
|
|
185
|
+
logger.info(`Using model: ${model}`);
|
|
186
|
+
logger.newline();
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const exitCode = await claudeRunner.runInteractive(systemPrompt, userMessage, {
|
|
190
|
+
dangerouslySkipPermissions: true,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (exitCode !== 0) {
|
|
194
|
+
logger.warn(`Claude exited with code ${exitCode}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Post-session validation
|
|
198
|
+
logger.newline();
|
|
199
|
+
postSessionValidation(configPath);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
logger.error(`Config session failed: ${error}`);
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
package/src/commands/do.ts
CHANGED
|
@@ -13,14 +13,18 @@ import { getRafDir, extractProjectNumber, extractProjectName, extractTaskNameFro
|
|
|
13
13
|
import { pickPendingProject, getPendingProjects, getPendingWorktreeProjects } from '../ui/project-picker.js';
|
|
14
14
|
import type { PendingProjectInfo } from '../ui/project-picker.js';
|
|
15
15
|
import { logger } from '../utils/logger.js';
|
|
16
|
-
import { getConfig } from '../utils/config.js';
|
|
16
|
+
import { getConfig, getEffort, getWorktreeDefault } from '../utils/config.js';
|
|
17
17
|
import { createTaskTimer, formatElapsedTime } from '../utils/timer.js';
|
|
18
18
|
import { createStatusLine } from '../utils/status-line.js';
|
|
19
19
|
import {
|
|
20
20
|
formatProjectHeader,
|
|
21
21
|
formatSummary,
|
|
22
22
|
formatTaskProgress,
|
|
23
|
+
formatTaskTokenSummary,
|
|
24
|
+
formatTokenTotalSummary,
|
|
23
25
|
} from '../utils/terminal-symbols.js';
|
|
26
|
+
import { TokenTracker } from '../utils/token-tracker.js';
|
|
27
|
+
import { VerboseToggle } from '../utils/verbose-toggle.js';
|
|
24
28
|
import {
|
|
25
29
|
deriveProjectState,
|
|
26
30
|
discoverProjects,
|
|
@@ -134,12 +138,12 @@ export function createDoCommand(): Command {
|
|
|
134
138
|
async function runDoCommand(projectIdentifierArg: string | undefined, options: DoCommandOptions): Promise<void> {
|
|
135
139
|
const rafDir = getRafDir();
|
|
136
140
|
let projectIdentifier = projectIdentifierArg;
|
|
137
|
-
let worktreeMode = options.worktree ??
|
|
141
|
+
let worktreeMode = options.worktree ?? getWorktreeDefault();
|
|
138
142
|
|
|
139
143
|
// Validate and resolve model option
|
|
140
144
|
let model: string;
|
|
141
145
|
try {
|
|
142
|
-
model = resolveModelOption(options.model as string | undefined, options.sonnet);
|
|
146
|
+
model = resolveModelOption(options.model as string | undefined, options.sonnet, 'execute');
|
|
143
147
|
} catch (error) {
|
|
144
148
|
logger.error((error as Error).message);
|
|
145
149
|
process.exit(1);
|
|
@@ -711,6 +715,13 @@ async function executeSingleProject(
|
|
|
711
715
|
shutdownHandler.init();
|
|
712
716
|
shutdownHandler.registerClaudeRunner(claudeRunner);
|
|
713
717
|
|
|
718
|
+
// Initialize token tracker for usage reporting
|
|
719
|
+
const tokenTracker = new TokenTracker();
|
|
720
|
+
|
|
721
|
+
// Set up runtime verbose toggle (Tab key to toggle during execution)
|
|
722
|
+
const verboseToggle = new VerboseToggle(verbose);
|
|
723
|
+
shutdownHandler.onShutdown(() => verboseToggle.stop());
|
|
724
|
+
|
|
714
725
|
// Start project timer
|
|
715
726
|
const projectStartTime = Date.now();
|
|
716
727
|
|
|
@@ -816,6 +827,9 @@ async function executeSingleProject(
|
|
|
816
827
|
return lines.join('\n');
|
|
817
828
|
}
|
|
818
829
|
|
|
830
|
+
// Start verbose toggle listener (Tab key)
|
|
831
|
+
verboseToggle.start();
|
|
832
|
+
|
|
819
833
|
let task = getNextTaskToProcess(state);
|
|
820
834
|
|
|
821
835
|
while (task) {
|
|
@@ -891,6 +905,7 @@ async function executeSingleProject(
|
|
|
891
905
|
let attempts = 0;
|
|
892
906
|
let lastOutput = '';
|
|
893
907
|
let failureReason = '';
|
|
908
|
+
let lastUsageData: import('../types/config.js').UsageData | undefined;
|
|
894
909
|
// Track failure history for each attempt (attempt number -> reason)
|
|
895
910
|
const failureHistory: Array<{ attempt: number; reason: string }> = [];
|
|
896
911
|
|
|
@@ -940,11 +955,23 @@ async function executeSingleProject(
|
|
|
940
955
|
} : undefined;
|
|
941
956
|
|
|
942
957
|
// Run Claude (use worktree root as cwd if in worktree mode)
|
|
958
|
+
const executeEffort = getEffort('execute');
|
|
959
|
+
const runnerOptions = {
|
|
960
|
+
timeout,
|
|
961
|
+
outcomeFilePath,
|
|
962
|
+
commitContext,
|
|
963
|
+
cwd: worktreeCwd,
|
|
964
|
+
effortLevel: executeEffort,
|
|
965
|
+
verboseCheck: () => verboseToggle.isVerbose,
|
|
966
|
+
};
|
|
943
967
|
const result = verbose
|
|
944
|
-
? await claudeRunner.runVerbose(prompt,
|
|
945
|
-
: await claudeRunner.run(prompt,
|
|
968
|
+
? await claudeRunner.runVerbose(prompt, runnerOptions)
|
|
969
|
+
: await claudeRunner.run(prompt, runnerOptions);
|
|
946
970
|
|
|
947
971
|
lastOutput = result.output;
|
|
972
|
+
if (result.usageData) {
|
|
973
|
+
lastUsageData = result.usageData;
|
|
974
|
+
}
|
|
948
975
|
|
|
949
976
|
// Parse result
|
|
950
977
|
const parsed = parseOutput(result.output);
|
|
@@ -1059,6 +1086,13 @@ Task completed. No detailed report provided.
|
|
|
1059
1086
|
// Minimal mode: show completed task line
|
|
1060
1087
|
logger.info(formatTaskProgress(taskNumber, totalTasks, 'completed', displayName, elapsedMs, task.id));
|
|
1061
1088
|
}
|
|
1089
|
+
|
|
1090
|
+
// Track and display token usage for this task
|
|
1091
|
+
if (lastUsageData) {
|
|
1092
|
+
const entry = tokenTracker.addTask(task.id, lastUsageData);
|
|
1093
|
+
logger.dim(formatTaskTokenSummary(entry.usage, entry.cost));
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1062
1096
|
completedInSession.add(task.id);
|
|
1063
1097
|
} else {
|
|
1064
1098
|
// Stash any uncommitted changes on complete failure
|
|
@@ -1080,6 +1114,12 @@ Task completed. No detailed report provided.
|
|
|
1080
1114
|
logger.info(formatTaskProgress(taskNumber, totalTasks, 'failed', displayName, elapsedMs, task.id));
|
|
1081
1115
|
}
|
|
1082
1116
|
|
|
1117
|
+
// Track token usage even for failed tasks (partial data still useful for totals)
|
|
1118
|
+
if (lastUsageData) {
|
|
1119
|
+
const entry = tokenTracker.addTask(task.id, lastUsageData);
|
|
1120
|
+
logger.dim(formatTaskTokenSummary(entry.usage, entry.cost));
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1083
1123
|
// Analyze failure and generate structured report
|
|
1084
1124
|
const analysisReport = await analyzeFailure(lastOutput, failureReason, task.id);
|
|
1085
1125
|
|
|
@@ -1114,6 +1154,9 @@ ${stashName ? `- Stash: ${stashName}` : ''}
|
|
|
1114
1154
|
task = getNextTaskToProcess(state);
|
|
1115
1155
|
}
|
|
1116
1156
|
|
|
1157
|
+
// Stop verbose toggle listener before summary output
|
|
1158
|
+
verboseToggle.stop();
|
|
1159
|
+
|
|
1117
1160
|
// Ensure context is cleared for summary
|
|
1118
1161
|
logger.clearContext();
|
|
1119
1162
|
|
|
@@ -1182,6 +1225,14 @@ ${stashName ? `- Stash: ${stashName}` : ''}
|
|
|
1182
1225
|
}
|
|
1183
1226
|
}
|
|
1184
1227
|
|
|
1228
|
+
// Show token usage summary if any tasks reported usage data
|
|
1229
|
+
const trackerEntries = tokenTracker.getEntries();
|
|
1230
|
+
if (trackerEntries.length > 0) {
|
|
1231
|
+
logger.newline();
|
|
1232
|
+
const totals = tokenTracker.getTotals();
|
|
1233
|
+
logger.dim(formatTokenTotalSummary(totals.usage, totals.cost));
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1185
1236
|
// Show retry history for tasks that had failures (even if eventually successful)
|
|
1186
1237
|
if (projectRetryHistory.length > 0) {
|
|
1187
1238
|
logger.newline();
|
package/src/commands/plan.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
resolveModelOption,
|
|
16
16
|
} from '../utils/validation.js';
|
|
17
17
|
import { logger } from '../utils/logger.js';
|
|
18
|
+
import { getWorktreeDefault } from '../utils/config.js';
|
|
18
19
|
import { generateProjectNames } from '../utils/name-generator.js';
|
|
19
20
|
import { pickProjectName } from '../ui/name-picker.js';
|
|
20
21
|
import {
|
|
@@ -73,14 +74,14 @@ export function createPlanCommand(): Command {
|
|
|
73
74
|
// Validate and resolve model option
|
|
74
75
|
let model: string;
|
|
75
76
|
try {
|
|
76
|
-
model = resolveModelOption(options.model, options.sonnet);
|
|
77
|
+
model = resolveModelOption(options.model, options.sonnet, 'plan');
|
|
77
78
|
} catch (error) {
|
|
78
79
|
logger.error((error as Error).message);
|
|
79
80
|
process.exit(1);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
const autoMode = options.auto ?? false;
|
|
83
|
-
const worktreeMode = options.worktree ??
|
|
84
|
+
const worktreeMode = options.worktree ?? getWorktreeDefault();
|
|
84
85
|
|
|
85
86
|
if (options.amend) {
|
|
86
87
|
if (!projectName) {
|