get-shit-done-cc 1.9.11 → 1.10.0-experimental.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 (61) hide show
  1. package/README.md +10 -9
  2. package/agents/design-specialist.md +222 -0
  3. package/agents/gsd-executor.md +37 -375
  4. package/agents/gsd-planner.md +15 -108
  5. package/bin/install.js +92 -5
  6. package/commands/gsd/autopilot.md +518 -0
  7. package/commands/gsd/checkpoints.md +229 -0
  8. package/commands/gsd/design-system.md +70 -0
  9. package/commands/gsd/discuss-design.md +77 -0
  10. package/commands/gsd/extend.md +80 -0
  11. package/commands/gsd/help.md +46 -17
  12. package/commands/gsd/new-project.md +94 -8
  13. package/commands/gsd/plan-phase.md +35 -5
  14. package/get-shit-done/references/ccr-integration.md +468 -0
  15. package/get-shit-done/references/checkpoint-execution.md +369 -0
  16. package/get-shit-done/references/checkpoint-types.md +728 -0
  17. package/get-shit-done/references/deviation-rules.md +215 -0
  18. package/get-shit-done/references/framework-patterns.md +543 -0
  19. package/get-shit-done/references/ui-principles.md +258 -0
  20. package/get-shit-done/references/verification-patterns.md +1 -1
  21. package/get-shit-done/skills/gsd-extend/SKILL.md +154 -0
  22. package/get-shit-done/skills/gsd-extend/references/agent-structure.md +305 -0
  23. package/get-shit-done/skills/gsd-extend/references/extension-anatomy.md +123 -0
  24. package/get-shit-done/skills/gsd-extend/references/reference-structure.md +408 -0
  25. package/get-shit-done/skills/gsd-extend/references/template-structure.md +370 -0
  26. package/get-shit-done/skills/gsd-extend/references/validation-rules.md +140 -0
  27. package/get-shit-done/skills/gsd-extend/references/workflow-structure.md +253 -0
  28. package/get-shit-done/skills/gsd-extend/templates/agent-template.md +234 -0
  29. package/get-shit-done/skills/gsd-extend/templates/reference-template.md +239 -0
  30. package/get-shit-done/skills/gsd-extend/templates/workflow-template.md +169 -0
  31. package/get-shit-done/skills/gsd-extend/workflows/create-approach.md +332 -0
  32. package/get-shit-done/skills/gsd-extend/workflows/list-extensions.md +133 -0
  33. package/get-shit-done/skills/gsd-extend/workflows/remove-extension.md +93 -0
  34. package/get-shit-done/skills/gsd-extend/workflows/validate-extension.md +184 -0
  35. package/get-shit-done/templates/autopilot-script-simple.sh +181 -0
  36. package/get-shit-done/templates/autopilot-script.sh +1142 -0
  37. package/get-shit-done/templates/autopilot-script.sh.backup +1142 -0
  38. package/get-shit-done/templates/design-system.md +238 -0
  39. package/get-shit-done/templates/phase-design.md +205 -0
  40. package/get-shit-done/templates/phase-models-template.json +71 -0
  41. package/get-shit-done/templates/phase-prompt.md +4 -4
  42. package/get-shit-done/templates/state.md +37 -0
  43. package/get-shit-done/tui/App.tsx +169 -0
  44. package/get-shit-done/tui/README.md +107 -0
  45. package/get-shit-done/tui/build.js +37 -0
  46. package/get-shit-done/tui/components/ActivityFeed.tsx +126 -0
  47. package/get-shit-done/tui/components/PhaseCard.tsx +86 -0
  48. package/get-shit-done/tui/components/StatsBar.tsx +147 -0
  49. package/get-shit-done/tui/dist/index.js +387 -0
  50. package/get-shit-done/tui/index.tsx +12 -0
  51. package/get-shit-done/tui/package-lock.json +1074 -0
  52. package/get-shit-done/tui/package.json +22 -0
  53. package/get-shit-done/tui/utils/pipeReader.ts +129 -0
  54. package/get-shit-done/workflows/design-system.md +245 -0
  55. package/get-shit-done/workflows/discuss-design.md +330 -0
  56. package/get-shit-done/workflows/execute-phase.md +44 -1
  57. package/get-shit-done/workflows/execute-plan-auth.md +122 -0
  58. package/get-shit-done/workflows/execute-plan-checkpoints.md +541 -0
  59. package/get-shit-done/workflows/execute-plan.md +34 -856
  60. package/package.json +8 -3
  61. package/commands/gsd/whats-new.md +0 -124
@@ -0,0 +1,169 @@
1
+ import React, { useState, useEffect, useMemo } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { PhaseCard } from './components/PhaseCard.js';
4
+ import { ActivityFeed } from './components/ActivityFeed.js';
5
+ import { StatsBar } from './components/StatsBar.js';
6
+ import { ActivityPipeReader, ActivityMessage } from './utils/pipeReader.js';
7
+
8
+ interface Stage {
9
+ name: string;
10
+ elapsed: string;
11
+ completed: boolean;
12
+ }
13
+
14
+ const App: React.FC = () => {
15
+ const [activities, setActivities] = useState<Array<ActivityMessage & { detail: string }>>([]);
16
+ const [currentStage, setCurrentStage] = useState<{
17
+ stage: string;
18
+ stageDesc: string;
19
+ elapsed: string;
20
+ } | null>(null);
21
+ const [completedStages, setCompletedStages] = useState<Array<{ name: string; elapsed: string }>>([]);
22
+ const [currentPhase, setCurrentPhase] = useState<string>('1');
23
+ const [phaseName, setPhaseName] = useState<string>('Initializing...');
24
+ const [totalPhases] = useState<number>(3);
25
+ const [completedPhases, setCompletedPhases] = useState<number>(0);
26
+ const [startTime] = useState<Date>(new Date());
27
+ const [tokens, setTokens] = useState<number>(0);
28
+ const [cost, setCost] = useState<string>('0.00');
29
+ const [budget] = useState<{ used: number; limit: number } | undefined>(undefined);
30
+
31
+ useEffect(() => {
32
+ const pipePath = process.env.GSD_ACTIVITY_PIPE || '.planning/logs/activity.pipe';
33
+ const reader = new ActivityPipeReader(pipePath);
34
+
35
+ reader.onMessage((msg) => {
36
+ setActivities((prev) => [...prev, msg]);
37
+
38
+ // Handle stage messages
39
+ if (msg.type === 'stage') {
40
+ const [stageType, ...descParts] = msg.detail.split(':');
41
+ const description = descParts.join(':');
42
+
43
+ // Add to completed stages
44
+ if (currentStage && currentStage.stage !== stageType) {
45
+ setCompletedStages((prev) => [
46
+ ...prev,
47
+ { name: currentStage.stage, elapsed: currentStage.elapsed },
48
+ ]);
49
+ }
50
+
51
+ setCurrentStage({
52
+ stage: stageType,
53
+ stageDesc: description,
54
+ elapsed: '0:00',
55
+ });
56
+ }
57
+
58
+ // Handle file messages
59
+ if (msg.type === 'file') {
60
+ // Already added to activities
61
+ }
62
+
63
+ // Handle commit messages
64
+ if (msg.type === 'commit') {
65
+ // Already added to activities
66
+ }
67
+ });
68
+
69
+ reader.start();
70
+
71
+ return () => {
72
+ // Cleanup handled by pipe reader
73
+ };
74
+ }, []);
75
+
76
+ // Calculate elapsed time
77
+ const elapsedTime = useMemo(() => {
78
+ const diff = Math.floor((Date.now() - startTime.getTime()) / 1000);
79
+ const hrs = Math.floor(diff / 3600);
80
+ const mins = Math.floor((diff % 3600) / 60);
81
+ const secs = diff % 60;
82
+
83
+ if (hrs > 0) {
84
+ return `${hrs}h ${mins}m ${secs}s`;
85
+ } else if (mins > 0) {
86
+ return `${mins}m ${secs}s`;
87
+ } else {
88
+ return `${secs}s`;
89
+ }
90
+ }, [startTime]);
91
+
92
+ // Build stages array
93
+ const stages: Stage[] = useMemo(() => {
94
+ const stages: Stage[] = [
95
+ ...completedStages.map((s) => ({ ...s, completed: true })),
96
+ ];
97
+
98
+ if (currentStage) {
99
+ stages.push({
100
+ name: currentStage.stage,
101
+ elapsed: currentStage.elapsed,
102
+ completed: false,
103
+ });
104
+ }
105
+
106
+ return stages;
107
+ }, [completedStages, currentStage]);
108
+
109
+ // Calculate progress
110
+ const progress = useMemo(() => {
111
+ if (stages.length === 0) return 0;
112
+ const completed = stages.filter((s) => s.completed).length;
113
+ return (completed / (stages.length + 3)) * 100; // +3 for planned future stages
114
+ }, [stages]);
115
+
116
+ return (
117
+ <Box flexDirection="column" padding={1}>
118
+ <Box justifyContent="center" marginBottom={1}>
119
+ <Text bold color="cyan">
120
+ ╔═══╗ ╔╗ ╔╗ ╔══╗
121
+ ║╔══╝ ║║ ║║ ║╔╗║
122
+ ║╚══╗ ║║ ║║ ║╚╝║
123
+ ║╔══╝ ║║ ║║ ║╔╗║
124
+ ║╚══╗ ║╚══╗║╚══╗ ║╚╝║
125
+ ╚═══╝ ╚═══╝╚═══╝ ╚══╝
126
+ </Text>
127
+ </Box>
128
+
129
+ <Text bold color="cyan">
130
+ GET SHIT DONE - AUTOPILOT
131
+ </Text>
132
+
133
+ <Box marginY={1}>
134
+ <Text dimColor>
135
+ {'─'.repeat(60)}
136
+ </Text>
137
+ </Box>
138
+
139
+ <Box flexDirection="row" gap={1} flexGrow={1}>
140
+ <Box flexDirection="column" flexGrow={1}>
141
+ <PhaseCard
142
+ phase={currentPhase}
143
+ phaseName={phaseName}
144
+ totalPhases={totalPhases}
145
+ currentPhaseIndex={completedPhases}
146
+ stages={stages}
147
+ description={currentStage?.stageDesc}
148
+ progress={progress}
149
+ />
150
+ </Box>
151
+
152
+ <Box flexDirection="column" flexGrow={1}>
153
+ <ActivityFeed activities={activities} />
154
+ </Box>
155
+ </Box>
156
+
157
+ <StatsBar
158
+ totalPhases={totalPhases}
159
+ completedPhases={completedPhases}
160
+ elapsedTime={elapsedTime}
161
+ tokens={tokens}
162
+ cost={cost}
163
+ budget={budget}
164
+ />
165
+ </Box>
166
+ );
167
+ };
168
+
169
+ export default App;
@@ -0,0 +1,107 @@
1
+ # GSD Autopilot TUI
2
+
3
+ A beautiful, React-based terminal user interface for the GSD Autopilot system.
4
+
5
+ ## Features
6
+
7
+ - **Rich Visual Components**: Professional layouts with borders, spacing, and typography
8
+ - **Real-time Updates**: Live activity feed showing file operations, commits, and test runs
9
+ - **Phase Progress Tracking**: Visual progress bars and stage completion indicators
10
+ - **Cost & Time Analytics**: Real-time token usage, cost calculation, and time tracking
11
+ - **Beautiful Graphics**: ASCII art header, emoji icons, and smooth animations
12
+
13
+ ## Architecture
14
+
15
+ The TUI is built with:
16
+ - **Ink 4.x** - React renderer for terminal UIs
17
+ - **React 18** - Component-based architecture
18
+ - **Yoga Layout** - Flexbox-like layout system
19
+ - **TypeScript** - Type-safe development
20
+
21
+ ### Components
22
+
23
+ - `App.tsx` - Main application layout and state management
24
+ - `PhaseCard.tsx` - Phase progress display with stage tracking
25
+ - `ActivityFeed.tsx` - Real-time activity stream with icons
26
+ - `StatsBar.tsx` - Cost, time, and progress statistics
27
+ - `pipeReader.ts` - Named pipe reader for activity events
28
+
29
+ ## Installation
30
+
31
+ The TUI is automatically installed when you install GSD. It requires:
32
+
33
+ - Node.js 16+
34
+ - npm or yarn
35
+
36
+ ### Manual Installation
37
+
38
+ ```bash
39
+ cd get-shit-done/tui
40
+ npm install
41
+ npm run build
42
+ ```
43
+
44
+ This creates a `dist/` directory with the built TUI binary.
45
+
46
+ ## Usage
47
+
48
+ The TUI is automatically launched by the autopilot script when available. It listens to activity events via a named pipe and renders the UI in real-time.
49
+
50
+ ### Running Standalone
51
+
52
+ ```bash
53
+ gsd-autopilot-tui
54
+ ```
55
+
56
+ ### Environment Variables
57
+
58
+ - `GSD_ACTIVITY_PIPE` - Path to activity pipe (default: `.planning/logs/activity.pipe`)
59
+ - `GSD_PROJECT_DIR` - Project directory path
60
+ - `GSD_LOG_DIR` - Log directory path
61
+
62
+ ## Message Format
63
+
64
+ The TUI reads activity messages from the named pipe in the format:
65
+
66
+ ```
67
+ STAGE:subagent-type:description
68
+ FILE:operation:path
69
+ COMMIT:message
70
+ TEST:test
71
+ INFO:message
72
+ ERROR:message
73
+ ```
74
+
75
+ ## Development
76
+
77
+ ### Build
78
+
79
+ ```bash
80
+ npm run build
81
+ ```
82
+
83
+ ### Watch Mode
84
+
85
+ ```bash
86
+ npm run build -- --watch
87
+ ```
88
+
89
+ ### Project Structure
90
+
91
+ ```
92
+ tui/
93
+ ├── components/ # React components
94
+ │ ├── PhaseCard.tsx
95
+ │ ├── ActivityFeed.tsx
96
+ │ └── StatsBar.tsx
97
+ ├── utils/ # Utilities
98
+ │ └── pipeReader.ts
99
+ ├── App.tsx # Main application
100
+ ├── index.tsx # Entry point
101
+ ├── build.js # Build script
102
+ └── package.json # Dependencies
103
+ ```
104
+
105
+ ## License
106
+
107
+ MIT - Part of GSD (Get Shit Done) project
@@ -0,0 +1,37 @@
1
+ import { build } from 'esbuild';
2
+ import { mkdir } from 'fs/promises';
3
+ import { dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ async function main() {
10
+ // Create dist directory
11
+ await mkdir('dist', { recursive: true });
12
+
13
+ // Build the application
14
+ await build({
15
+ entryPoints: ['index.tsx'],
16
+ outfile: 'dist/index.js',
17
+ bundle: true,
18
+ format: 'esm',
19
+ platform: 'node',
20
+ target: 'node16',
21
+ external: ['ink', 'react'],
22
+ define: {
23
+ 'process.env.NODE_ENV': '"production"',
24
+ },
25
+ loader: {
26
+ '.tsx': 'tsx',
27
+ '.ts': 'ts',
28
+ },
29
+ }).catch((error) => {
30
+ console.error('Build failed:', error);
31
+ process.exit(1);
32
+ });
33
+
34
+ console.log('Build completed successfully');
35
+ }
36
+
37
+ main();
@@ -0,0 +1,126 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ interface Activity {
5
+ type: 'read' | 'write' | 'edit' | 'commit' | 'test' | 'stage' | 'error' | 'info';
6
+ detail: string;
7
+ timestamp: Date;
8
+ }
9
+
10
+ interface ActivityFeedProps {
11
+ activities: Activity[];
12
+ maxItems?: number;
13
+ }
14
+
15
+ export const ActivityFeed: React.FC<ActivityFeedProps> = ({ activities, maxItems = 12 }) => {
16
+ const [dots, setDots] = useState('');
17
+
18
+ useEffect(() => {
19
+ const timer = setInterval(() => {
20
+ setDots((prev) => {
21
+ if (prev.length >= 3) return '';
22
+ return prev + '.';
23
+ });
24
+ }, 500);
25
+
26
+ return () => clearInterval(timer);
27
+ }, []);
28
+
29
+ const displayActivities = activities.slice(-maxItems);
30
+
31
+ const getActivityIcon = (type: Activity['type']) => {
32
+ switch (type) {
33
+ case 'read':
34
+ return '📖';
35
+ case 'write':
36
+ return '✍️';
37
+ case 'edit':
38
+ return '📝';
39
+ case 'commit':
40
+ return '✓';
41
+ case 'test':
42
+ return '🧪';
43
+ case 'stage':
44
+ return '⚙️';
45
+ case 'error':
46
+ return '⛔';
47
+ case 'info':
48
+ return 'ℹ️';
49
+ default:
50
+ return '•';
51
+ }
52
+ };
53
+
54
+ const getActivityColor = (type: Activity['type']): string => {
55
+ switch (type) {
56
+ case 'read':
57
+ return 'blue';
58
+ case 'write':
59
+ return 'green';
60
+ case 'edit':
61
+ return 'yellow';
62
+ case 'commit':
63
+ return 'green';
64
+ case 'test':
65
+ return 'magenta';
66
+ case 'stage':
67
+ return 'cyan';
68
+ case 'error':
69
+ return 'red';
70
+ case 'info':
71
+ return 'gray';
72
+ default:
73
+ return 'white';
74
+ }
75
+ };
76
+
77
+ const getTypeLabel = (type: Activity['type']) => {
78
+ const labels = {
79
+ read: 'READ',
80
+ write: 'WRITE',
81
+ edit: 'EDIT',
82
+ commit: 'COMMIT',
83
+ test: 'TEST',
84
+ stage: 'STAGE',
85
+ error: 'ERROR',
86
+ info: 'INFO',
87
+ };
88
+ return labels[type] || 'ACTIVITY';
89
+ };
90
+
91
+ return (
92
+ <Box flexDirection="column" borderStyle="round" borderColor="gray" padding={1} height={18}>
93
+ <Box justifyContent="space-between" alignItems="center">
94
+ <Text bold>Activity Feed</Text>
95
+ <Text color="gray">{dots}</Text>
96
+ </Box>
97
+
98
+ <Box flexDirection="column" marginTop={1} overflow="hidden">
99
+ {displayActivities.length === 0 ? (
100
+ <Text dimColor italic>
101
+ Waiting for activity...
102
+ </Text>
103
+ ) : (
104
+ displayActivities.map((activity, idx) => (
105
+ <Box
106
+ key={idx}
107
+ justifyContent="space-between"
108
+ alignItems="center"
109
+ marginBottom={idx < displayActivities.length - 1 ? 0 : 0}
110
+ >
111
+ <Box flexGrow={1}>
112
+ <Text>
113
+ <Text dimColor>[{activity.timestamp.toLocaleTimeString()}]</Text>{' '}
114
+ <Text color={getActivityColor(activity.type)}>
115
+ {getActivityIcon(activity.type)}
116
+ </Text>{' '}
117
+ <Text dimColor>{getTypeLabel(activity.type)}:</Text> {activity.detail}
118
+ </Text>
119
+ </Box>
120
+ </Box>
121
+ ))
122
+ )}
123
+ </Box>
124
+ </Box>
125
+ );
126
+ };
@@ -0,0 +1,86 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ interface Stage {
5
+ name: string;
6
+ elapsed: string;
7
+ completed: boolean;
8
+ }
9
+
10
+ interface PhaseCardProps {
11
+ phase: string;
12
+ phaseName: string;
13
+ totalPhases: number;
14
+ currentPhaseIndex: number;
15
+ stages: Stage[];
16
+ description?: string;
17
+ progress: number; // 0-100
18
+ }
19
+
20
+ export const PhaseCard: React.FC<PhaseCardProps> = ({
21
+ phase,
22
+ phaseName,
23
+ totalPhases,
24
+ currentPhaseIndex,
25
+ stages,
26
+ description,
27
+ progress,
28
+ }) => {
29
+ const getStageColor = (stage: Stage): string => {
30
+ if (stage.completed) return 'green';
31
+ if (stage.name === stages[stages.length - 1]?.name) return 'cyan';
32
+ return 'gray';
33
+ };
34
+
35
+ return (
36
+ <Box flexDirection="column" borderStyle="round" borderColor="cyan" padding={1}>
37
+ <Box justifyContent="space-between" alignItems="center">
38
+ <Text bold color="cyan">
39
+ {`PHASE ${phase}`}
40
+ </Text>
41
+ <Text dimColor>
42
+ {currentPhaseIndex + 1} / {totalPhases}
43
+ </Text>
44
+ </Box>
45
+
46
+ <Text bold>{phaseName}</Text>
47
+
48
+ {!!description && (
49
+ <Box marginTop={1}>
50
+ <Text dimColor>{description}</Text>
51
+ </Box>
52
+ )}
53
+
54
+ <Box marginTop={1} flexDirection="column">
55
+ <Text dimColor>Progress</Text>
56
+ <Box>
57
+ <Box width={40}>
58
+ <Text>
59
+ {Array.from({ length: 40 }, (_, i) => {
60
+ const fillPercent = (i / 40) * 100;
61
+ return (
62
+ <Text key={i} backgroundColor={fillPercent <= progress ? 'cyan' : undefined}>
63
+ {fillPercent <= progress ? '█' : '░'}
64
+ </Text>
65
+ );
66
+ })}
67
+ </Text>
68
+ </Box>
69
+ <Text> {Math.round(progress)}%</Text>
70
+ </Box>
71
+ </Box>
72
+
73
+ <Box marginTop={1} flexDirection="column">
74
+ <Text bold>Stages</Text>
75
+ {stages.map((stage, idx) => (
76
+ <Box key={idx} justifyContent="space-between">
77
+ <Text color={getStageColor(stage)}>
78
+ {stage.completed ? '✓' : '○'} {stage.name}
79
+ </Text>
80
+ <Text dimColor>{stage.elapsed || 'in progress...'}</Text>
81
+ </Box>
82
+ ))}
83
+ </Box>
84
+ </Box>
85
+ );
86
+ };
@@ -0,0 +1,147 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+
4
+ interface StatsBarProps {
5
+ totalPhases: number;
6
+ completedPhases: number;
7
+ elapsedTime: string;
8
+ estimatedTimeRemaining?: string;
9
+ tokens: number;
10
+ cost: string;
11
+ budget?: {
12
+ used: number;
13
+ limit: number;
14
+ };
15
+ }
16
+
17
+ export const StatsBar: React.FC<StatsBarProps> = ({
18
+ totalPhases,
19
+ completedPhases,
20
+ elapsedTime,
21
+ estimatedTimeRemaining,
22
+ tokens,
23
+ cost,
24
+ budget,
25
+ }) => {
26
+ const progress = (completedPhases / totalPhases) * 100;
27
+
28
+ const formatTime = (seconds: number): string => {
29
+ const hrs = Math.floor(seconds / 3600);
30
+ const mins = Math.floor((seconds % 3600) / 60);
31
+ const secs = seconds % 60;
32
+
33
+ if (hrs > 0) {
34
+ return `${hrs}h ${mins}m`;
35
+ } else if (mins > 0) {
36
+ return `${mins}m ${secs}s`;
37
+ } else {
38
+ return `${secs}s`;
39
+ }
40
+ };
41
+
42
+ return (
43
+ <Box
44
+ flexDirection="column"
45
+ borderStyle="round"
46
+ borderColor="green"
47
+ padding={1}
48
+ marginTop={1}
49
+ >
50
+ <Box justifyContent="space-between" alignItems="center">
51
+ <Text bold color="green">
52
+ 📊 Execution Stats
53
+ </Text>
54
+ <Text dimColor>{elapsedTime}</Text>
55
+ </Box>
56
+
57
+ <Box marginTop={1}>
58
+ <Box flexGrow={1} flexDirection="column" marginRight={2}>
59
+ <Text dimColor>Phases</Text>
60
+ <Box alignItems="center">
61
+ <Box width={30}>
62
+ <Text>
63
+ {Array.from({ length: 30 }, (_, i) => {
64
+ const fillPercent = (i / 30) * 100;
65
+ return (
66
+ <Text
67
+ key={i}
68
+ backgroundColor={fillPercent <= progress ? 'green' : undefined}
69
+ >
70
+ {fillPercent <= progress ? '█' : '░'}
71
+ </Text>
72
+ );
73
+ })}
74
+ </Text>
75
+ </Box>
76
+ <Text> {completedPhases}/{totalPhases}</Text>
77
+ </Box>
78
+ </Box>
79
+
80
+ <Box flexGrow={1} flexDirection="column" marginLeft={2}>
81
+ <Text dimColor>Time</Text>
82
+ <Text bold color="cyan">
83
+ {elapsedTime}
84
+ {estimatedTimeRemaining && (
85
+ <Text dimColor> (remaining: {estimatedTimeRemaining})</Text>
86
+ )}
87
+ </Text>
88
+ </Box>
89
+ </Box>
90
+
91
+ <Box marginTop={1} justifyContent="space-between">
92
+ <Box>
93
+ <Text dimColor>Tokens: </Text>
94
+ <Text bold>{tokens.toLocaleString()}</Text>
95
+ </Box>
96
+ <Box>
97
+ <Text dimColor>Cost: </Text>
98
+ <Text bold color="green">
99
+ ${cost}
100
+ </Text>
101
+ </Box>
102
+ {budget && (
103
+ <Box>
104
+ <Text dimColor>Budget: </Text>
105
+ <Text
106
+ bold
107
+ color={
108
+ budget.used / budget.limit > 0.8
109
+ ? 'red'
110
+ : budget.used / budget.limit > 0.6
111
+ ? 'yellow'
112
+ : 'green'
113
+ }
114
+ >
115
+ ${budget.used.toFixed(2)} / ${budget.limit}
116
+ </Text>
117
+ </Box>
118
+ )}
119
+ </Box>
120
+
121
+ {budget && (
122
+ <Box marginTop={1}>
123
+ <Text dimColor>Budget Usage: </Text>
124
+ <Box width={40}>
125
+ <Text>
126
+ {Array.from({ length: 40 }, (_, i) => {
127
+ const fillPercent = (i / 40) * (budget.used / budget.limit) * 100;
128
+ const color =
129
+ budget.used / budget.limit > 0.8
130
+ ? 'red'
131
+ : budget.used / budget.limit > 0.6
132
+ ? 'yellow'
133
+ : 'green';
134
+ return (
135
+ <Text key={i} backgroundColor={color}>
136
+ {fillPercent <= 100 ? '█' : '░'}
137
+ </Text>
138
+ );
139
+ })}
140
+ </Text>
141
+ </Box>
142
+ <Text> {Math.round((budget.used / budget.limit) * 100)}%</Text>
143
+ </Box>
144
+ )}
145
+ </Box>
146
+ );
147
+ };