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.
- package/README.md +10 -9
- package/agents/design-specialist.md +222 -0
- package/agents/gsd-executor.md +37 -375
- package/agents/gsd-planner.md +15 -108
- package/bin/install.js +92 -5
- package/commands/gsd/autopilot.md +518 -0
- package/commands/gsd/checkpoints.md +229 -0
- package/commands/gsd/design-system.md +70 -0
- package/commands/gsd/discuss-design.md +77 -0
- package/commands/gsd/extend.md +80 -0
- package/commands/gsd/help.md +46 -17
- package/commands/gsd/new-project.md +94 -8
- package/commands/gsd/plan-phase.md +35 -5
- package/get-shit-done/references/ccr-integration.md +468 -0
- package/get-shit-done/references/checkpoint-execution.md +369 -0
- package/get-shit-done/references/checkpoint-types.md +728 -0
- package/get-shit-done/references/deviation-rules.md +215 -0
- package/get-shit-done/references/framework-patterns.md +543 -0
- package/get-shit-done/references/ui-principles.md +258 -0
- package/get-shit-done/references/verification-patterns.md +1 -1
- package/get-shit-done/skills/gsd-extend/SKILL.md +154 -0
- package/get-shit-done/skills/gsd-extend/references/agent-structure.md +305 -0
- package/get-shit-done/skills/gsd-extend/references/extension-anatomy.md +123 -0
- package/get-shit-done/skills/gsd-extend/references/reference-structure.md +408 -0
- package/get-shit-done/skills/gsd-extend/references/template-structure.md +370 -0
- package/get-shit-done/skills/gsd-extend/references/validation-rules.md +140 -0
- package/get-shit-done/skills/gsd-extend/references/workflow-structure.md +253 -0
- package/get-shit-done/skills/gsd-extend/templates/agent-template.md +234 -0
- package/get-shit-done/skills/gsd-extend/templates/reference-template.md +239 -0
- package/get-shit-done/skills/gsd-extend/templates/workflow-template.md +169 -0
- package/get-shit-done/skills/gsd-extend/workflows/create-approach.md +332 -0
- package/get-shit-done/skills/gsd-extend/workflows/list-extensions.md +133 -0
- package/get-shit-done/skills/gsd-extend/workflows/remove-extension.md +93 -0
- package/get-shit-done/skills/gsd-extend/workflows/validate-extension.md +184 -0
- package/get-shit-done/templates/autopilot-script-simple.sh +181 -0
- package/get-shit-done/templates/autopilot-script.sh +1142 -0
- package/get-shit-done/templates/autopilot-script.sh.backup +1142 -0
- package/get-shit-done/templates/design-system.md +238 -0
- package/get-shit-done/templates/phase-design.md +205 -0
- package/get-shit-done/templates/phase-models-template.json +71 -0
- package/get-shit-done/templates/phase-prompt.md +4 -4
- package/get-shit-done/templates/state.md +37 -0
- package/get-shit-done/tui/App.tsx +169 -0
- package/get-shit-done/tui/README.md +107 -0
- package/get-shit-done/tui/build.js +37 -0
- package/get-shit-done/tui/components/ActivityFeed.tsx +126 -0
- package/get-shit-done/tui/components/PhaseCard.tsx +86 -0
- package/get-shit-done/tui/components/StatsBar.tsx +147 -0
- package/get-shit-done/tui/dist/index.js +387 -0
- package/get-shit-done/tui/index.tsx +12 -0
- package/get-shit-done/tui/package-lock.json +1074 -0
- package/get-shit-done/tui/package.json +22 -0
- package/get-shit-done/tui/utils/pipeReader.ts +129 -0
- package/get-shit-done/workflows/design-system.md +245 -0
- package/get-shit-done/workflows/discuss-design.md +330 -0
- package/get-shit-done/workflows/execute-phase.md +44 -1
- package/get-shit-done/workflows/execute-plan-auth.md +122 -0
- package/get-shit-done/workflows/execute-plan-checkpoints.md +541 -0
- package/get-shit-done/workflows/execute-plan.md +34 -856
- package/package.json +8 -3
- 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
|
+
};
|