gencode-ai 0.1.3 → 0.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/README.md +2 -1
- package/dist/agent/agent.d.ts +35 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +93 -3
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/types.d.ts +6 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/checkpointing/checkpoint-manager.d.ts +87 -0
- package/dist/checkpointing/checkpoint-manager.d.ts.map +1 -0
- package/dist/checkpointing/checkpoint-manager.js +281 -0
- package/dist/checkpointing/checkpoint-manager.js.map +1 -0
- package/dist/checkpointing/index.d.ts +29 -0
- package/dist/checkpointing/index.d.ts.map +1 -0
- package/dist/checkpointing/index.js +29 -0
- package/dist/checkpointing/index.js.map +1 -0
- package/dist/checkpointing/types.d.ts +98 -0
- package/dist/checkpointing/types.d.ts.map +1 -0
- package/dist/checkpointing/types.js +7 -0
- package/dist/checkpointing/types.js.map +1 -0
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +157 -6
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
- package/dist/cli/components/CommandSuggestions.js +5 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -1
- package/dist/cli/components/Messages.d.ts +7 -1
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +11 -2
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/components/ModeIndicator.d.ts +42 -0
- package/dist/cli/components/ModeIndicator.d.ts.map +1 -0
- package/dist/cli/components/ModeIndicator.js +52 -0
- package/dist/cli/components/ModeIndicator.js.map +1 -0
- package/dist/cli/components/PlanApproval.d.ts +36 -0
- package/dist/cli/components/PlanApproval.d.ts.map +1 -0
- package/dist/cli/components/PlanApproval.js +154 -0
- package/dist/cli/components/PlanApproval.js.map +1 -0
- package/dist/cli/components/theme.d.ts +2 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +3 -0
- package/dist/cli/components/theme.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/planning/index.d.ts +13 -0
- package/dist/planning/index.d.ts.map +1 -0
- package/dist/planning/index.js +15 -0
- package/dist/planning/index.js.map +1 -0
- package/dist/planning/plan-file.d.ts +59 -0
- package/dist/planning/plan-file.d.ts.map +1 -0
- package/dist/planning/plan-file.js +278 -0
- package/dist/planning/plan-file.js.map +1 -0
- package/dist/planning/state.d.ts +127 -0
- package/dist/planning/state.d.ts.map +1 -0
- package/dist/planning/state.js +261 -0
- package/dist/planning/state.js.map +1 -0
- package/dist/planning/tools/enter-plan-mode.d.ts +25 -0
- package/dist/planning/tools/enter-plan-mode.d.ts.map +1 -0
- package/dist/planning/tools/enter-plan-mode.js +98 -0
- package/dist/planning/tools/enter-plan-mode.js.map +1 -0
- package/dist/planning/tools/exit-plan-mode.d.ts +24 -0
- package/dist/planning/tools/exit-plan-mode.d.ts.map +1 -0
- package/dist/planning/tools/exit-plan-mode.js +149 -0
- package/dist/planning/tools/exit-plan-mode.js.map +1 -0
- package/dist/planning/types.d.ts +100 -0
- package/dist/planning/types.d.ts.map +1 -0
- package/dist/planning/types.js +28 -0
- package/dist/planning/types.js.map +1 -0
- package/dist/pricing/calculator.d.ts +21 -0
- package/dist/pricing/calculator.d.ts.map +1 -0
- package/dist/pricing/calculator.js +59 -0
- package/dist/pricing/calculator.js.map +1 -0
- package/dist/pricing/index.d.ts +7 -0
- package/dist/pricing/index.d.ts.map +1 -0
- package/dist/pricing/index.js +7 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/pricing/models.d.ts +20 -0
- package/dist/pricing/models.d.ts.map +1 -0
- package/dist/pricing/models.js +322 -0
- package/dist/pricing/models.js.map +1 -0
- package/dist/pricing/types.d.ts +30 -0
- package/dist/pricing/types.d.ts.map +1 -0
- package/dist/pricing/types.js +5 -0
- package/dist/pricing/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +17 -10
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +21 -14
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +12 -8
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +2 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/vertex-ai.d.ts.map +1 -1
- package/dist/providers/vertex-ai.js +17 -10
- package/dist/providers/vertex-ai.js.map +1 -1
- package/dist/session/manager.d.ts +4 -0
- package/dist/session/manager.d.ts.map +1 -1
- package/dist/session/manager.js +8 -0
- package/dist/session/manager.js.map +1 -1
- package/dist/tools/index.d.ts +7 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/registry.d.ts +13 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +79 -2
- package/dist/tools/registry.js.map +1 -1
- package/docs/cost-tracking-comparison.md +904 -0
- package/docs/operating-modes.md +96 -0
- package/docs/proposals/0025-cost-tracking.md +60 -2
- package/docs/proposals/README.md +1 -1
- package/examples/test-checkpointing.ts +121 -0
- package/examples/test-cost-tracking.ts +77 -0
- package/examples/test-interrupt-cleanup.ts +94 -0
- package/package.json +1 -1
- package/src/agent/agent.ts +110 -3
- package/src/agent/types.ts +6 -0
- package/src/checkpointing/checkpoint-manager.ts +327 -0
- package/src/checkpointing/index.ts +45 -0
- package/src/checkpointing/types.ts +104 -0
- package/src/cli/components/App.tsx +204 -5
- package/src/cli/components/CommandSuggestions.tsx +5 -0
- package/src/cli/components/Messages.tsx +23 -4
- package/src/cli/components/ModeIndicator.tsx +174 -0
- package/src/cli/components/PlanApproval.tsx +327 -0
- package/src/cli/components/theme.ts +3 -0
- package/src/index.ts +15 -0
- package/src/planning/index.ts +53 -0
- package/src/planning/plan-file.ts +326 -0
- package/src/planning/state.ts +305 -0
- package/src/planning/tools/enter-plan-mode.ts +111 -0
- package/src/planning/tools/exit-plan-mode.ts +170 -0
- package/src/planning/types.ts +150 -0
- package/src/pricing/calculator.ts +71 -0
- package/src/pricing/index.ts +7 -0
- package/src/pricing/models.ts +334 -0
- package/src/pricing/types.ts +32 -0
- package/src/providers/anthropic.ts +21 -10
- package/src/providers/gemini.ts +25 -14
- package/src/providers/openai.ts +17 -8
- package/src/providers/types.ts +3 -0
- package/src/providers/vertex-ai.ts +21 -10
- package/src/session/manager.ts +9 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/registry.ts +95 -2
- package/.gencode/settings.local.json +0 -7
- package/CLAUDE.md +0 -86
|
@@ -6,6 +6,8 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
6
6
|
import { Box, Text, useApp, useInput, Static } from 'ink';
|
|
7
7
|
import { Agent } from '../../agent/index.js';
|
|
8
8
|
import type { AgentConfig } from '../../agent/types.js';
|
|
9
|
+
import { formatTokens, formatCost } from '../../pricing/calculator.js';
|
|
10
|
+
import type { CostEstimate } from '../../pricing/types.js';
|
|
9
11
|
import {
|
|
10
12
|
UserMessage,
|
|
11
13
|
AssistantMessage,
|
|
@@ -35,6 +37,11 @@ import type { Question, QuestionAnswer } from '../../tools/types.js';
|
|
|
35
37
|
import type { ProviderName } from '../../providers/index.js';
|
|
36
38
|
import type { ApprovalAction, ApprovalSuggestion } from '../../permissions/types.js';
|
|
37
39
|
import { gatherContextFiles, buildInitPrompt, getContextSummary } from '../../memory/index.js';
|
|
40
|
+
// ModeIndicator kept for potential future use
|
|
41
|
+
import { PlanApproval } from './PlanApproval.js';
|
|
42
|
+
import type { ModeType, PlanApprovalOption, AllowedPrompt } from '../../planning/types.js';
|
|
43
|
+
// Planning utilities kept for potential future use
|
|
44
|
+
import { getCheckpointManager } from '../../checkpointing/index.js';
|
|
38
45
|
|
|
39
46
|
// Types
|
|
40
47
|
interface HistoryItem {
|
|
@@ -56,6 +63,14 @@ interface QuestionState {
|
|
|
56
63
|
resolve: (answers: QuestionAnswer[]) => void;
|
|
57
64
|
}
|
|
58
65
|
|
|
66
|
+
interface PlanApprovalState {
|
|
67
|
+
planSummary: string;
|
|
68
|
+
requestedPermissions: AllowedPrompt[];
|
|
69
|
+
filesToChange: Array<{ path: string; action: 'create' | 'modify' | 'delete' }>;
|
|
70
|
+
planFilePath: string;
|
|
71
|
+
resolve: (option: PlanApprovalOption, customInput?: string) => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
59
74
|
interface SettingsManager {
|
|
60
75
|
save: (settings: { model?: string }) => Promise<void>;
|
|
61
76
|
getCwd?: () => string;
|
|
@@ -108,6 +123,9 @@ const formatRelativeTime = (dateStr: string) => {
|
|
|
108
123
|
// ============================================================================
|
|
109
124
|
function HelpPanel() {
|
|
110
125
|
const commands: [string, string][] = [
|
|
126
|
+
['/plan [desc]', 'Enter plan mode'],
|
|
127
|
+
['/normal', 'Exit to normal mode'],
|
|
128
|
+
['/accept', 'Enter auto-accept mode'],
|
|
111
129
|
['/model [name]', 'Switch model'],
|
|
112
130
|
['/provider', 'Manage providers'],
|
|
113
131
|
['/sessions', 'List sessions'],
|
|
@@ -117,6 +135,8 @@ function HelpPanel() {
|
|
|
117
135
|
['/clear', 'Clear chat'],
|
|
118
136
|
['/init', 'Generate AGENT.md'],
|
|
119
137
|
['/memory', 'Show memory files'],
|
|
138
|
+
['/changes', 'List file changes'],
|
|
139
|
+
['/rewind [n|all]', 'Undo file changes'],
|
|
120
140
|
];
|
|
121
141
|
|
|
122
142
|
return (
|
|
@@ -252,6 +272,16 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
252
272
|
const pendingToolRef = useRef<{ name: string; input: Record<string, unknown> } | null>(null);
|
|
253
273
|
const [todos, setTodos] = useState<ReturnType<typeof getTodos>>([]);
|
|
254
274
|
|
|
275
|
+
// Operating mode state (normal → plan → accept → normal)
|
|
276
|
+
const [currentMode, setCurrentMode] = useState<ModeType>('normal');
|
|
277
|
+
const currentModeRef = useRef<ModeType>('normal'); // Track mode for confirm callback
|
|
278
|
+
const [planApprovalState, setPlanApprovalState] = useState<PlanApprovalState | null>(null);
|
|
279
|
+
|
|
280
|
+
// Keep ref in sync with state
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
currentModeRef.current = currentMode;
|
|
283
|
+
}, [currentMode]);
|
|
284
|
+
|
|
255
285
|
// Check if showing command suggestions
|
|
256
286
|
const showCmdSuggestions = input.startsWith('/') && !isProcessing;
|
|
257
287
|
const cmdSuggestions = showCmdSuggestions ? getFilteredCommands(input) : [];
|
|
@@ -274,6 +304,11 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
274
304
|
|
|
275
305
|
// Set enhanced confirm callback with approval options
|
|
276
306
|
agent.setEnhancedConfirmCallback(async (tool, toolInput, suggestions) => {
|
|
307
|
+
// Auto-approve in accept mode
|
|
308
|
+
if (currentModeRef.current === 'accept') {
|
|
309
|
+
return 'allow_once';
|
|
310
|
+
}
|
|
311
|
+
|
|
277
312
|
return new Promise<ApprovalAction>((resolve) => {
|
|
278
313
|
setConfirmState({
|
|
279
314
|
tool,
|
|
@@ -565,6 +600,110 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
565
600
|
return true;
|
|
566
601
|
}
|
|
567
602
|
|
|
603
|
+
case 'plan': {
|
|
604
|
+
// Enter plan mode
|
|
605
|
+
await agent.enterPlanMode(arg);
|
|
606
|
+
setCurrentMode('plan');
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
case 'normal': {
|
|
611
|
+
// Exit to normal mode
|
|
612
|
+
if (agent.isPlanModeActive()) {
|
|
613
|
+
agent.exitPlanMode(false);
|
|
614
|
+
}
|
|
615
|
+
setCurrentMode('normal');
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
case 'accept': {
|
|
620
|
+
// Enter auto-accept mode
|
|
621
|
+
if (agent.isPlanModeActive()) {
|
|
622
|
+
agent.exitPlanMode(false);
|
|
623
|
+
}
|
|
624
|
+
setCurrentMode('accept');
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
case 'changes': {
|
|
629
|
+
// List file changes (checkpoints)
|
|
630
|
+
const checkpointManager = getCheckpointManager();
|
|
631
|
+
if (!checkpointManager.hasCheckpoints()) {
|
|
632
|
+
addHistory({ type: 'info', content: '\nNo file changes in this session' });
|
|
633
|
+
} else {
|
|
634
|
+
addHistory({ type: 'info', content: '\n' + checkpointManager.formatCheckpointList(false) });
|
|
635
|
+
}
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
case 'rewind': {
|
|
640
|
+
// Rewind file changes
|
|
641
|
+
const checkpointManager = getCheckpointManager();
|
|
642
|
+
|
|
643
|
+
if (!checkpointManager.hasCheckpoints()) {
|
|
644
|
+
addHistory({ type: 'info', content: 'No file changes to rewind' });
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (arg === 'all') {
|
|
649
|
+
// Rewind all changes
|
|
650
|
+
const result = await checkpointManager.rewind({ all: true });
|
|
651
|
+
|
|
652
|
+
// Build output message showing both successes and failures
|
|
653
|
+
const messages: string[] = ['']; // Start with empty line for spacing
|
|
654
|
+
|
|
655
|
+
if (result.revertedFiles.length > 0) {
|
|
656
|
+
const files = result.revertedFiles.map((f) => {
|
|
657
|
+
const fileName = f.path.split('/').pop() || f.path;
|
|
658
|
+
return ` • ${fileName} (${f.action})`;
|
|
659
|
+
}).join('\n');
|
|
660
|
+
messages.push(`Reverted ${result.revertedFiles.length} file(s):\n${files}`);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (result.errors.length > 0) {
|
|
664
|
+
const errors = result.errors.map((e) => {
|
|
665
|
+
const fileName = e.path.split('/').pop() || e.path;
|
|
666
|
+
return ` • ${fileName}: ${e.error}`;
|
|
667
|
+
}).join('\n');
|
|
668
|
+
messages.push(`\nFailed to revert ${result.errors.length} file(s):\n${errors}`);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (messages.length > 1) {
|
|
672
|
+
addHistory({ type: 'info', content: messages.join('\n') });
|
|
673
|
+
} else {
|
|
674
|
+
addHistory({ type: 'info', content: '\nNo changes to rewind' });
|
|
675
|
+
}
|
|
676
|
+
} else if (arg) {
|
|
677
|
+
// Rewind specific checkpoint by index
|
|
678
|
+
const index = parseInt(arg, 10);
|
|
679
|
+
if (!isNaN(index) && index >= 1) {
|
|
680
|
+
const checkpoints = checkpointManager.getCheckpoints();
|
|
681
|
+
if (index <= checkpoints.length) {
|
|
682
|
+
const checkpoint = checkpoints[index - 1];
|
|
683
|
+
const result = await checkpointManager.rewind({ checkpointId: checkpoint.id });
|
|
684
|
+
if (result.success && result.revertedFiles.length > 0) {
|
|
685
|
+
const f = result.revertedFiles[0];
|
|
686
|
+
const fileName = f.path.split('/').pop() || f.path;
|
|
687
|
+
addHistory({ type: 'info', content: `\nReverted: ${fileName} (${f.action})` });
|
|
688
|
+
} else if (result.errors.length > 0) {
|
|
689
|
+
const fileName = result.errors[0].path.split('/').pop() || result.errors[0].path;
|
|
690
|
+
addHistory({ type: 'info', content: `\nFailed: ${fileName} - ${result.errors[0].error}` });
|
|
691
|
+
} else {
|
|
692
|
+
addHistory({ type: 'info', content: '\nFailed to rewind change' });
|
|
693
|
+
}
|
|
694
|
+
} else {
|
|
695
|
+
addHistory({ type: 'info', content: '\nInvalid index: ${index}' });
|
|
696
|
+
}
|
|
697
|
+
} else {
|
|
698
|
+
addHistory({ type: 'info', content: '\nUsage: /rewind [n|all]' });
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
// Show changes and usage in one message
|
|
702
|
+
addHistory({ type: 'info', content: '\n' + checkpointManager.formatCheckpointList(true) });
|
|
703
|
+
}
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
|
|
568
707
|
default:
|
|
569
708
|
return false;
|
|
570
709
|
}
|
|
@@ -661,9 +800,13 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
661
800
|
streamingTextRef.current = '';
|
|
662
801
|
setStreamingText('');
|
|
663
802
|
}
|
|
664
|
-
// Add completion message with duration
|
|
803
|
+
// Add completion message with duration and cost info
|
|
665
804
|
const durationMs = Date.now() - startTime;
|
|
666
|
-
addHistory({
|
|
805
|
+
addHistory({
|
|
806
|
+
type: 'completion',
|
|
807
|
+
content: '',
|
|
808
|
+
meta: { durationMs, usage: event.usage, cost: event.cost },
|
|
809
|
+
});
|
|
667
810
|
setProcessingStartTime(undefined);
|
|
668
811
|
break;
|
|
669
812
|
}
|
|
@@ -774,9 +917,33 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
774
917
|
// Clear pending tool (stop spinner)
|
|
775
918
|
pendingToolRef.current = null;
|
|
776
919
|
setPendingTool(null);
|
|
920
|
+
// Clean up incomplete tool_use messages to prevent API errors
|
|
921
|
+
agent.cleanupIncompleteMessages();
|
|
777
922
|
addHistory({ type: 'info', content: 'Interrupted' });
|
|
778
923
|
}
|
|
779
924
|
|
|
925
|
+
// Shift+Tab to cycle modes: normal → plan → accept → normal
|
|
926
|
+
if (key.shift && key.tab && !isProcessing && !confirmState && !questionState && !planApprovalState) {
|
|
927
|
+
const cycleMode = async () => {
|
|
928
|
+
const nextMode: Record<ModeType, ModeType> = {
|
|
929
|
+
normal: 'plan',
|
|
930
|
+
plan: 'accept',
|
|
931
|
+
accept: 'normal',
|
|
932
|
+
};
|
|
933
|
+
const newMode = nextMode[currentMode];
|
|
934
|
+
|
|
935
|
+
// Handle plan mode transitions
|
|
936
|
+
if (newMode === 'plan') {
|
|
937
|
+
await agent.enterPlanMode();
|
|
938
|
+
} else if (currentMode === 'plan') {
|
|
939
|
+
agent.exitPlanMode(false);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
setCurrentMode(newMode);
|
|
943
|
+
};
|
|
944
|
+
cycleMode();
|
|
945
|
+
}
|
|
946
|
+
|
|
780
947
|
// Command suggestion navigation
|
|
781
948
|
if (showCmdSuggestions && cmdSuggestions.length > 0) {
|
|
782
949
|
if (key.upArrow) {
|
|
@@ -853,7 +1020,13 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
853
1020
|
}
|
|
854
1021
|
return <InfoMessage text={item.content} />;
|
|
855
1022
|
case 'completion':
|
|
856
|
-
return
|
|
1023
|
+
return (
|
|
1024
|
+
<CompletionMessage
|
|
1025
|
+
durationMs={(item.meta?.durationMs as number) || 0}
|
|
1026
|
+
usage={item.meta?.usage as { inputTokens: number; outputTokens: number } | undefined}
|
|
1027
|
+
cost={item.meta?.cost as CostEstimate | undefined}
|
|
1028
|
+
/>
|
|
1029
|
+
);
|
|
857
1030
|
case 'todos':
|
|
858
1031
|
return <TodoList todos={item.meta?.todos as ReturnType<typeof getTodos>} />;
|
|
859
1032
|
default:
|
|
@@ -862,7 +1035,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
862
1035
|
};
|
|
863
1036
|
|
|
864
1037
|
return (
|
|
865
|
-
<Box flexDirection="column">
|
|
1038
|
+
<Box flexDirection="column" paddingBottom={2}>
|
|
866
1039
|
<Static items={history}>
|
|
867
1040
|
{(item) => <Box key={item.id}>{renderHistoryItem(item)}</Box>}
|
|
868
1041
|
</Static>
|
|
@@ -889,6 +1062,22 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
889
1062
|
/>
|
|
890
1063
|
)}
|
|
891
1064
|
|
|
1065
|
+
{/* Plan approval UI */}
|
|
1066
|
+
{planApprovalState && (
|
|
1067
|
+
<Box marginTop={1}>
|
|
1068
|
+
<PlanApproval
|
|
1069
|
+
planSummary={planApprovalState.planSummary}
|
|
1070
|
+
requestedPermissions={planApprovalState.requestedPermissions}
|
|
1071
|
+
filesToChange={planApprovalState.filesToChange}
|
|
1072
|
+
planFilePath={planApprovalState.planFilePath}
|
|
1073
|
+
onDecision={(option, customInput) => {
|
|
1074
|
+
planApprovalState.resolve(option, customInput);
|
|
1075
|
+
setPlanApprovalState(null);
|
|
1076
|
+
}}
|
|
1077
|
+
/>
|
|
1078
|
+
</Box>
|
|
1079
|
+
)}
|
|
1080
|
+
|
|
892
1081
|
{showModelSelector && (
|
|
893
1082
|
<Box marginTop={1}>
|
|
894
1083
|
<ModelSelector
|
|
@@ -914,7 +1103,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
914
1103
|
)}
|
|
915
1104
|
|
|
916
1105
|
{!confirmState && !questionState && !showModelSelector && !showProviderManager && (
|
|
917
|
-
<Box flexDirection="column" marginTop={
|
|
1106
|
+
<Box flexDirection="column" marginTop={2}>
|
|
918
1107
|
<PromptInput
|
|
919
1108
|
key={inputKey}
|
|
920
1109
|
value={input}
|
|
@@ -924,6 +1113,16 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
924
1113
|
{showCmdSuggestions && cmdSuggestions.length > 0 && (
|
|
925
1114
|
<CommandSuggestions input={input} selectedIndex={cmdSuggestionIndex} />
|
|
926
1115
|
)}
|
|
1116
|
+
{currentMode === 'plan' && !isProcessing && (
|
|
1117
|
+
<Text color={colors.warning} dimColor>
|
|
1118
|
+
{icons.modePlan} plan mode on (shift+tab to cycle)
|
|
1119
|
+
</Text>
|
|
1120
|
+
)}
|
|
1121
|
+
{currentMode === 'accept' && !isProcessing && (
|
|
1122
|
+
<Text color={colors.success} dimColor>
|
|
1123
|
+
{icons.modeAccept} accept edits on (shift+tab to cycle)
|
|
1124
|
+
</Text>
|
|
1125
|
+
)}
|
|
927
1126
|
</Box>
|
|
928
1127
|
)}
|
|
929
1128
|
|
|
@@ -7,6 +7,9 @@ interface Command {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export const COMMANDS: Command[] = [
|
|
10
|
+
{ name: '/plan', description: 'Enter plan mode (Shift+Tab to cycle)' },
|
|
11
|
+
{ name: '/normal', description: 'Exit to normal mode' },
|
|
12
|
+
{ name: '/accept', description: 'Enter auto-accept mode' },
|
|
10
13
|
{ name: '/model', description: 'Switch model' },
|
|
11
14
|
{ name: '/provider', description: 'Manage providers' },
|
|
12
15
|
{ name: '/permissions', description: 'View permission rules' },
|
|
@@ -20,6 +23,8 @@ export const COMMANDS: Command[] = [
|
|
|
20
23
|
{ name: '/help', description: 'Show help' },
|
|
21
24
|
{ name: '/init', description: 'Generate AGENT.md' },
|
|
22
25
|
{ name: '/memory', description: 'Show memory files' },
|
|
26
|
+
{ name: '/changes', description: 'List file changes' },
|
|
27
|
+
{ name: '/rewind', description: 'Undo file changes' },
|
|
23
28
|
];
|
|
24
29
|
|
|
25
30
|
interface CommandSuggestionsProps {
|
|
@@ -3,6 +3,8 @@ import { Box, Text } from 'ink';
|
|
|
3
3
|
import InkSpinner from 'ink-spinner';
|
|
4
4
|
import { colors, icons } from './theme.js';
|
|
5
5
|
import { renderMarkdown } from './markdown.js';
|
|
6
|
+
import { formatTokens, formatCost } from '../../pricing/calculator.js';
|
|
7
|
+
import type { CostEstimate } from '../../pricing/types.js';
|
|
6
8
|
|
|
7
9
|
// Truncate string with ellipsis
|
|
8
10
|
const truncate = (str: string, maxLen: number) =>
|
|
@@ -346,16 +348,33 @@ function formatDuration(ms: number): string {
|
|
|
346
348
|
|
|
347
349
|
interface CompletionMessageProps {
|
|
348
350
|
durationMs: number;
|
|
351
|
+
usage?: {
|
|
352
|
+
inputTokens: number;
|
|
353
|
+
outputTokens: number;
|
|
354
|
+
};
|
|
355
|
+
cost?: CostEstimate;
|
|
349
356
|
}
|
|
350
357
|
|
|
351
|
-
export function CompletionMessage({ durationMs }: CompletionMessageProps) {
|
|
358
|
+
export function CompletionMessage({ durationMs, usage, cost }: CompletionMessageProps) {
|
|
352
359
|
// Pick a random verb (stable per render via useMemo would be better, but keep simple)
|
|
353
360
|
const verb = COMPLETION_VERBS[Math.floor(Math.random() * COMPLETION_VERBS.length)];
|
|
361
|
+
|
|
362
|
+
// Build the message parts
|
|
363
|
+
const parts = [`✻ ${verb} for ${formatDuration(durationMs)}`];
|
|
364
|
+
|
|
365
|
+
if (usage) {
|
|
366
|
+
parts.push(
|
|
367
|
+
`Tokens: ${formatTokens(usage.inputTokens)} in / ${formatTokens(usage.outputTokens)} out`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (cost) {
|
|
372
|
+
parts.push(`(~${formatCost(cost.totalCost)})`);
|
|
373
|
+
}
|
|
374
|
+
|
|
354
375
|
return (
|
|
355
376
|
<Box marginTop={1}>
|
|
356
|
-
<Text color={colors.textMuted}>
|
|
357
|
-
✻ {verb} for {formatDuration(durationMs)}
|
|
358
|
-
</Text>
|
|
377
|
+
<Text color={colors.textMuted}>{parts.join(' • ')}</Text>
|
|
359
378
|
</Box>
|
|
360
379
|
);
|
|
361
380
|
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mode Indicator Component - Shows current mode (NORMAL/PLAN/ACCEPT)
|
|
3
|
+
*
|
|
4
|
+
* Design:
|
|
5
|
+
* ╭────────────────────────────────────╮
|
|
6
|
+
* │ ◉ NORMAL ○ PLAN ○ ACCEPT │
|
|
7
|
+
* ╰────────────────────────────────────╯
|
|
8
|
+
*
|
|
9
|
+
* Or with Shift+Tab toggle hint
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Box, Text } from 'ink';
|
|
13
|
+
import { colors, icons } from './theme.js';
|
|
14
|
+
import type { ModeType } from '../../planning/types.js';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Types
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
interface ModeIndicatorProps {
|
|
21
|
+
/** Current mode */
|
|
22
|
+
mode: ModeType;
|
|
23
|
+
/** Show toggle hint */
|
|
24
|
+
showHint?: boolean;
|
|
25
|
+
/** Compact mode (inline) */
|
|
26
|
+
compact?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Mode Indicator Component
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Mode Indicator - Shows NORMAL/PLAN/ACCEPT mode toggle
|
|
35
|
+
*/
|
|
36
|
+
export function ModeIndicator({ mode, showHint = false, compact = false }: ModeIndicatorProps) {
|
|
37
|
+
const isNormal = mode === 'normal';
|
|
38
|
+
const isPlan = mode === 'plan';
|
|
39
|
+
const isAccept = mode === 'accept';
|
|
40
|
+
|
|
41
|
+
if (compact) {
|
|
42
|
+
// Compact inline indicator
|
|
43
|
+
const modeLabel = isPlan ? 'PLAN' : isAccept ? 'ACCEPT' : '';
|
|
44
|
+
const modeColor = isPlan ? colors.warning : isAccept ? colors.success : colors.textMuted;
|
|
45
|
+
|
|
46
|
+
if (!modeLabel) return null;
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Box>
|
|
50
|
+
<Text color={modeColor}>
|
|
51
|
+
{isPlan ? icons.modePlan : icons.modeAccept}
|
|
52
|
+
</Text>
|
|
53
|
+
<Text color={modeColor}> {modeLabel}</Text>
|
|
54
|
+
</Box>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Box flexDirection="column">
|
|
60
|
+
<Box borderStyle="round" borderColor={isPlan ? colors.warning : isAccept ? colors.success : colors.textMuted} paddingX={1}>
|
|
61
|
+
{/* NORMAL option */}
|
|
62
|
+
<Text color={isNormal ? colors.primary : colors.textMuted}>
|
|
63
|
+
{isNormal ? icons.radio : icons.radioEmpty}
|
|
64
|
+
</Text>
|
|
65
|
+
<Text color={isNormal ? colors.text : colors.textMuted} bold={isNormal}>
|
|
66
|
+
{' '}NORMAL
|
|
67
|
+
</Text>
|
|
68
|
+
|
|
69
|
+
<Text color={colors.textMuted}> </Text>
|
|
70
|
+
|
|
71
|
+
{/* PLAN option */}
|
|
72
|
+
<Text color={isPlan ? colors.warning : colors.textMuted}>
|
|
73
|
+
{isPlan ? icons.radio : icons.radioEmpty}
|
|
74
|
+
</Text>
|
|
75
|
+
<Text color={isPlan ? colors.text : colors.textMuted} bold={isPlan}>
|
|
76
|
+
{' '}PLAN
|
|
77
|
+
</Text>
|
|
78
|
+
|
|
79
|
+
<Text color={colors.textMuted}> </Text>
|
|
80
|
+
|
|
81
|
+
{/* ACCEPT option */}
|
|
82
|
+
<Text color={isAccept ? colors.success : colors.textMuted}>
|
|
83
|
+
{isAccept ? icons.radio : icons.radioEmpty}
|
|
84
|
+
</Text>
|
|
85
|
+
<Text color={isAccept ? colors.text : colors.textMuted} bold={isAccept}>
|
|
86
|
+
{' '}ACCEPT
|
|
87
|
+
</Text>
|
|
88
|
+
</Box>
|
|
89
|
+
|
|
90
|
+
{showHint && (
|
|
91
|
+
<Box marginTop={0}>
|
|
92
|
+
<Text color={colors.textMuted} dimColor> Shift+Tab to cycle modes</Text>
|
|
93
|
+
</Box>
|
|
94
|
+
)}
|
|
95
|
+
</Box>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Mode Badge Component
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
interface ModeBadgeProps {
|
|
104
|
+
mode: ModeType;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Mode Badge - Compact badge for header display
|
|
109
|
+
*/
|
|
110
|
+
export function ModeBadge({ mode }: ModeBadgeProps) {
|
|
111
|
+
const color = mode === 'plan' ? colors.warning : mode === 'accept' ? colors.success : colors.primary;
|
|
112
|
+
const label = mode === 'plan' ? 'PLAN' : mode === 'accept' ? 'ACCEPT' : 'NORMAL';
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<Text color={color} bold>
|
|
116
|
+
[{label}]
|
|
117
|
+
</Text>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// Plan Status Bar Component
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
interface PlanStatusBarProps {
|
|
126
|
+
/** Current planning phase */
|
|
127
|
+
phase: string;
|
|
128
|
+
/** Plan file path */
|
|
129
|
+
planFilePath?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Plan Status Bar - Shows plan mode status
|
|
134
|
+
*/
|
|
135
|
+
export function PlanStatusBar({ phase, planFilePath }: PlanStatusBarProps) {
|
|
136
|
+
// Shorten plan file path for display
|
|
137
|
+
const displayPath = planFilePath
|
|
138
|
+
? planFilePath.replace(process.env.HOME || '', '~').split('/').slice(-2).join('/')
|
|
139
|
+
: '';
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<Box
|
|
143
|
+
borderStyle="round"
|
|
144
|
+
borderColor={colors.warning}
|
|
145
|
+
paddingX={1}
|
|
146
|
+
flexDirection="column"
|
|
147
|
+
>
|
|
148
|
+
{/* Status line */}
|
|
149
|
+
<Box>
|
|
150
|
+
<Text color={colors.warning}>PLAN MODE</Text>
|
|
151
|
+
<Text color={colors.textMuted}> │ </Text>
|
|
152
|
+
<Text color={colors.textSecondary}>Phase: </Text>
|
|
153
|
+
<Text color={colors.info}>{phase}</Text>
|
|
154
|
+
<Text color={colors.textMuted}> │ </Text>
|
|
155
|
+
<Text color={colors.textMuted}>Shift+Tab to switch</Text>
|
|
156
|
+
</Box>
|
|
157
|
+
|
|
158
|
+
{/* Tools info */}
|
|
159
|
+
<Box>
|
|
160
|
+
<Text color={colors.textMuted}>
|
|
161
|
+
Allowed: Read, Glob, Grep, WebFetch, WebSearch, TodoWrite
|
|
162
|
+
</Text>
|
|
163
|
+
</Box>
|
|
164
|
+
|
|
165
|
+
{/* Plan file */}
|
|
166
|
+
{displayPath && (
|
|
167
|
+
<Box>
|
|
168
|
+
<Text color={colors.textMuted}>Plan: </Text>
|
|
169
|
+
<Text color={colors.primary}>{displayPath}</Text>
|
|
170
|
+
</Box>
|
|
171
|
+
)}
|
|
172
|
+
</Box>
|
|
173
|
+
);
|
|
174
|
+
}
|