wiggum-cli 0.7.8 → 0.9.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/dist/ai/conversation/conversation-manager.d.ts +11 -0
- package/dist/ai/conversation/conversation-manager.d.ts.map +1 -1
- package/dist/ai/conversation/conversation-manager.js +14 -0
- package/dist/ai/conversation/conversation-manager.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/new.d.ts +2 -0
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +63 -22
- package/dist/commands/new.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -22
- package/dist/index.js.map +1 -1
- package/dist/tui/app.d.ts +108 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +169 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/components/ChatInput.d.ts +40 -0
- package/dist/tui/components/ChatInput.d.ts.map +1 -0
- package/dist/tui/components/ChatInput.js +61 -0
- package/dist/tui/components/ChatInput.js.map +1 -0
- package/dist/tui/components/MessageList.d.ts +79 -0
- package/dist/tui/components/MessageList.d.ts.map +1 -0
- package/dist/tui/components/MessageList.js +68 -0
- package/dist/tui/components/MessageList.js.map +1 -0
- package/dist/tui/components/PhaseHeader.d.ts +36 -0
- package/dist/tui/components/PhaseHeader.d.ts.map +1 -0
- package/dist/tui/components/PhaseHeader.js +31 -0
- package/dist/tui/components/PhaseHeader.js.map +1 -0
- package/dist/tui/components/StreamingText.d.ts +47 -0
- package/dist/tui/components/StreamingText.d.ts.map +1 -0
- package/dist/tui/components/StreamingText.js +38 -0
- package/dist/tui/components/StreamingText.js.map +1 -0
- package/dist/tui/components/ToolCallCard.d.ts +65 -0
- package/dist/tui/components/ToolCallCard.d.ts.map +1 -0
- package/dist/tui/components/ToolCallCard.js +100 -0
- package/dist/tui/components/ToolCallCard.js.map +1 -0
- package/dist/tui/components/WiggumBanner.d.ts +30 -0
- package/dist/tui/components/WiggumBanner.d.ts.map +1 -0
- package/dist/tui/components/WiggumBanner.js +34 -0
- package/dist/tui/components/WiggumBanner.js.map +1 -0
- package/dist/tui/components/WorkingIndicator.d.ts +45 -0
- package/dist/tui/components/WorkingIndicator.d.ts.map +1 -0
- package/dist/tui/components/WorkingIndicator.js +31 -0
- package/dist/tui/components/WorkingIndicator.js.map +1 -0
- package/dist/tui/components/index.d.ts +16 -0
- package/dist/tui/components/index.d.ts.map +1 -0
- package/dist/tui/components/index.js +10 -0
- package/dist/tui/components/index.js.map +1 -0
- package/dist/tui/demo.d.ts +8 -0
- package/dist/tui/demo.d.ts.map +1 -0
- package/dist/tui/demo.js +69 -0
- package/dist/tui/demo.js.map +1 -0
- package/dist/tui/hooks/index.d.ts +7 -0
- package/dist/tui/hooks/index.d.ts.map +1 -0
- package/dist/tui/hooks/index.js +6 -0
- package/dist/tui/hooks/index.js.map +1 -0
- package/dist/tui/hooks/useSpecGenerator.d.ts +184 -0
- package/dist/tui/hooks/useSpecGenerator.d.ts.map +1 -0
- package/dist/tui/hooks/useSpecGenerator.js +452 -0
- package/dist/tui/hooks/useSpecGenerator.js.map +1 -0
- package/dist/tui/index.d.ts +14 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +18 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/orchestration/index.d.ts +6 -0
- package/dist/tui/orchestration/index.d.ts.map +1 -0
- package/dist/tui/orchestration/index.js +6 -0
- package/dist/tui/orchestration/index.js.map +1 -0
- package/dist/tui/orchestration/interview-orchestrator.d.ts +136 -0
- package/dist/tui/orchestration/interview-orchestrator.d.ts.map +1 -0
- package/dist/tui/orchestration/interview-orchestrator.js +437 -0
- package/dist/tui/orchestration/interview-orchestrator.js.map +1 -0
- package/dist/tui/screens/InitScreen.d.ts +26 -0
- package/dist/tui/screens/InitScreen.d.ts.map +1 -0
- package/dist/tui/screens/InitScreen.js +30 -0
- package/dist/tui/screens/InitScreen.js.map +1 -0
- package/dist/tui/screens/InterviewScreen.d.ts +44 -0
- package/dist/tui/screens/InterviewScreen.d.ts.map +1 -0
- package/dist/tui/screens/InterviewScreen.js +212 -0
- package/dist/tui/screens/InterviewScreen.js.map +1 -0
- package/dist/tui/screens/MainShell.d.ts +46 -0
- package/dist/tui/screens/MainShell.d.ts.map +1 -0
- package/dist/tui/screens/MainShell.js +196 -0
- package/dist/tui/screens/MainShell.js.map +1 -0
- package/dist/tui/screens/WelcomeScreen.d.ts +45 -0
- package/dist/tui/screens/WelcomeScreen.d.ts.map +1 -0
- package/dist/tui/screens/WelcomeScreen.js +56 -0
- package/dist/tui/screens/WelcomeScreen.js.map +1 -0
- package/dist/tui/screens/index.d.ts +6 -0
- package/dist/tui/screens/index.d.ts.map +1 -0
- package/dist/tui/screens/index.js +5 -0
- package/dist/tui/screens/index.js.map +1 -0
- package/dist/tui/theme.d.ts +66 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +62 -0
- package/dist/tui/theme.js.map +1 -0
- package/package.json +6 -1
- package/src/ai/conversation/conversation-manager.ts +22 -0
- package/src/cli.ts +4 -0
- package/src/commands/new.ts +79 -27
- package/src/index.ts +109 -27
- package/src/tui/app.tsx +297 -0
- package/src/tui/components/ChatInput.tsx +105 -0
- package/src/tui/components/MessageList.tsx +186 -0
- package/src/tui/components/PhaseHeader.tsx +63 -0
- package/src/tui/components/StreamingText.tsx +69 -0
- package/src/tui/components/ToolCallCard.tsx +215 -0
- package/src/tui/components/WiggumBanner.tsx +66 -0
- package/src/tui/components/WorkingIndicator.tsx +72 -0
- package/src/tui/components/index.ts +21 -0
- package/src/tui/demo.tsx +111 -0
- package/src/tui/hooks/index.ts +13 -0
- package/src/tui/hooks/useSpecGenerator.ts +662 -0
- package/src/tui/index.ts +23 -0
- package/src/tui/orchestration/index.ts +10 -0
- package/src/tui/orchestration/interview-orchestrator.ts +559 -0
- package/src/tui/screens/InitScreen.tsx +63 -0
- package/src/tui/screens/InterviewScreen.tsx +319 -0
- package/src/tui/screens/MainShell.tsx +290 -0
- package/src/tui/screens/WelcomeScreen.tsx +141 -0
- package/src/tui/screens/index.ts +6 -0
- package/src/tui/theme.ts +76 -0
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StreamingText - Renders AI response text with optional cursor
|
|
3
|
+
*
|
|
4
|
+
* Displays text as it streams in from the AI. The parent component
|
|
5
|
+
* is responsible for accumulating text chunks and passing them via
|
|
6
|
+
* the `text` prop. This component simply renders what it receives,
|
|
7
|
+
* optionally showing a cursor when streaming is in progress.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { Text } from 'ink';
|
|
12
|
+
import { colors } from '../theme.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Block cursor character (U+2588 - Full Block)
|
|
16
|
+
*/
|
|
17
|
+
const CURSOR_CHAR = '\u2588';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Props for the StreamingText component
|
|
21
|
+
*/
|
|
22
|
+
export interface StreamingTextProps {
|
|
23
|
+
/** The accumulated text to display */
|
|
24
|
+
text: string;
|
|
25
|
+
/** Whether streaming is still in progress */
|
|
26
|
+
isStreaming: boolean;
|
|
27
|
+
/** Optional text color (defaults to white) */
|
|
28
|
+
color?: string;
|
|
29
|
+
/** Whether to show cursor when streaming (defaults to true) */
|
|
30
|
+
showCursor?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* StreamingText component
|
|
35
|
+
*
|
|
36
|
+
* Renders text with an optional cursor indicator when streaming.
|
|
37
|
+
* The cursor appears at the end of the text while `isStreaming` is true.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* // During streaming
|
|
42
|
+
* <StreamingText
|
|
43
|
+
* text="Hello, world"
|
|
44
|
+
* isStreaming={true}
|
|
45
|
+
* />
|
|
46
|
+
* // Renders: "Hello, world█"
|
|
47
|
+
*
|
|
48
|
+
* // After streaming completes
|
|
49
|
+
* <StreamingText
|
|
50
|
+
* text="Hello, world!"
|
|
51
|
+
* isStreaming={false}
|
|
52
|
+
* />
|
|
53
|
+
* // Renders: "Hello, world!"
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function StreamingText({
|
|
57
|
+
text,
|
|
58
|
+
isStreaming,
|
|
59
|
+
color = colors.white,
|
|
60
|
+
showCursor = true,
|
|
61
|
+
}: StreamingTextProps): React.ReactElement {
|
|
62
|
+
// Determine if cursor should be visible
|
|
63
|
+
const displayCursor = isStreaming && showCursor;
|
|
64
|
+
|
|
65
|
+
// Build the display text with optional cursor
|
|
66
|
+
const displayText = displayCursor ? `${text}${CURSOR_CHAR}` : text;
|
|
67
|
+
|
|
68
|
+
return <Text color={color}>{displayText}</Text>;
|
|
69
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolCallCard - Collapsible tool execution display
|
|
3
|
+
*
|
|
4
|
+
* Shows tool executions in a bordered card format, similar to Claude Code.
|
|
5
|
+
* Displays tool name, input, status indicator, and output/error.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Box, Text } from 'ink';
|
|
10
|
+
import { colors, box, phase } from '../theme.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Tool execution status
|
|
14
|
+
*/
|
|
15
|
+
export type ToolCallStatus = 'pending' | 'running' | 'complete' | 'error';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Props for the ToolCallCard component
|
|
19
|
+
*/
|
|
20
|
+
export interface ToolCallCardProps {
|
|
21
|
+
/** Name of the tool (e.g., "Read File", "Search Codebase") */
|
|
22
|
+
toolName: string;
|
|
23
|
+
/** Tool execution status */
|
|
24
|
+
status: ToolCallStatus;
|
|
25
|
+
/** Input passed to the tool (e.g., file path, search query) */
|
|
26
|
+
input: string;
|
|
27
|
+
/** Result summary when status is 'complete' */
|
|
28
|
+
output?: string;
|
|
29
|
+
/** Error message when status is 'error' */
|
|
30
|
+
error?: string;
|
|
31
|
+
/** Whether to show full details (default: false = collapsed) */
|
|
32
|
+
expanded?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Maps status to phase indicator character
|
|
37
|
+
*/
|
|
38
|
+
function getStatusIndicator(status: ToolCallStatus): string {
|
|
39
|
+
switch (status) {
|
|
40
|
+
case 'pending':
|
|
41
|
+
return phase.pending;
|
|
42
|
+
case 'running':
|
|
43
|
+
return phase.active;
|
|
44
|
+
case 'complete':
|
|
45
|
+
return phase.complete;
|
|
46
|
+
case 'error':
|
|
47
|
+
return phase.error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gets the color for the status indicator
|
|
53
|
+
*/
|
|
54
|
+
function getStatusColor(status: ToolCallStatus): string {
|
|
55
|
+
switch (status) {
|
|
56
|
+
case 'pending':
|
|
57
|
+
return colors.brown;
|
|
58
|
+
case 'running':
|
|
59
|
+
return colors.yellow;
|
|
60
|
+
case 'complete':
|
|
61
|
+
return colors.yellow;
|
|
62
|
+
case 'error':
|
|
63
|
+
return colors.pink;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gets human-readable status text
|
|
69
|
+
*/
|
|
70
|
+
function getStatusText(status: ToolCallStatus): string {
|
|
71
|
+
switch (status) {
|
|
72
|
+
case 'pending':
|
|
73
|
+
return 'Pending';
|
|
74
|
+
case 'running':
|
|
75
|
+
return 'Running';
|
|
76
|
+
case 'complete':
|
|
77
|
+
return 'Complete';
|
|
78
|
+
case 'error':
|
|
79
|
+
return 'Error';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* ToolCallCard component
|
|
85
|
+
*
|
|
86
|
+
* Displays a tool execution in a bordered card format.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```tsx
|
|
90
|
+
* // Collapsed (default)
|
|
91
|
+
* <ToolCallCard
|
|
92
|
+
* toolName="Read File"
|
|
93
|
+
* status="complete"
|
|
94
|
+
* input="src/utils/config.ts"
|
|
95
|
+
* output="45 lines read"
|
|
96
|
+
* />
|
|
97
|
+
* // Renders:
|
|
98
|
+
* // ┌─ Read File ────────────────────────────────┐
|
|
99
|
+
* // │ src/utils/config.ts ✓ 45 lines read │
|
|
100
|
+
* // └────────────────────────────────────────────┘
|
|
101
|
+
*
|
|
102
|
+
* // Expanded
|
|
103
|
+
* <ToolCallCard
|
|
104
|
+
* toolName="Read File"
|
|
105
|
+
* status="complete"
|
|
106
|
+
* input="src/utils/config.ts"
|
|
107
|
+
* output="45 lines read"
|
|
108
|
+
* expanded={true}
|
|
109
|
+
* />
|
|
110
|
+
* // Renders:
|
|
111
|
+
* // ┌─ Read File ────────────────────────────────┐
|
|
112
|
+
* // │ Input: src/utils/config.ts │
|
|
113
|
+
* // │ Status: Complete │
|
|
114
|
+
* // │ Result: 45 lines read │
|
|
115
|
+
* // └────────────────────────────────────────────┘
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export function ToolCallCard({
|
|
119
|
+
toolName,
|
|
120
|
+
status,
|
|
121
|
+
input,
|
|
122
|
+
output,
|
|
123
|
+
error,
|
|
124
|
+
expanded = false,
|
|
125
|
+
}: ToolCallCardProps): React.ReactElement {
|
|
126
|
+
const statusIndicator = getStatusIndicator(status);
|
|
127
|
+
const statusColor = getStatusColor(status);
|
|
128
|
+
const statusText = getStatusText(status);
|
|
129
|
+
|
|
130
|
+
// Build the title with box drawing
|
|
131
|
+
const titlePadding = box.horizontal.repeat(3);
|
|
132
|
+
const title = `${box.horizontal} ${toolName} ${titlePadding}`;
|
|
133
|
+
|
|
134
|
+
// Determine result text
|
|
135
|
+
const resultText = status === 'error' ? error : output;
|
|
136
|
+
|
|
137
|
+
if (expanded) {
|
|
138
|
+
// Expanded layout: multiple lines with labels
|
|
139
|
+
return (
|
|
140
|
+
<Box flexDirection="column">
|
|
141
|
+
{/* Top border with title */}
|
|
142
|
+
<Box flexDirection="row">
|
|
143
|
+
<Text color={colors.brown}>{box.topLeft}</Text>
|
|
144
|
+
<Text color={colors.yellow}>{title}</Text>
|
|
145
|
+
</Box>
|
|
146
|
+
|
|
147
|
+
{/* Input line */}
|
|
148
|
+
<Box flexDirection="row">
|
|
149
|
+
<Text color={colors.brown}>{box.vertical} </Text>
|
|
150
|
+
<Text color={colors.brown}>Input: </Text>
|
|
151
|
+
<Text color={colors.white}>{input}</Text>
|
|
152
|
+
</Box>
|
|
153
|
+
|
|
154
|
+
{/* Status line */}
|
|
155
|
+
<Box flexDirection="row">
|
|
156
|
+
<Text color={colors.brown}>{box.vertical} </Text>
|
|
157
|
+
<Text color={colors.brown}>Status: </Text>
|
|
158
|
+
<Text color={statusColor}>
|
|
159
|
+
{statusIndicator} {statusText}
|
|
160
|
+
</Text>
|
|
161
|
+
</Box>
|
|
162
|
+
|
|
163
|
+
{/* Result/Error line (if present) */}
|
|
164
|
+
{resultText && (
|
|
165
|
+
<Box flexDirection="row">
|
|
166
|
+
<Text color={colors.brown}>{box.vertical} </Text>
|
|
167
|
+
<Text color={colors.brown}>{status === 'error' ? 'Error: ' : 'Result: '}</Text>
|
|
168
|
+
<Text color={status === 'error' ? colors.pink : colors.white}>{resultText}</Text>
|
|
169
|
+
</Box>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
{/* Bottom border */}
|
|
173
|
+
<Box flexDirection="row">
|
|
174
|
+
<Text color={colors.brown}>
|
|
175
|
+
{box.bottomLeft}
|
|
176
|
+
{box.horizontal.repeat(40)}
|
|
177
|
+
</Text>
|
|
178
|
+
</Box>
|
|
179
|
+
</Box>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Collapsed layout: single content line
|
|
184
|
+
return (
|
|
185
|
+
<Box flexDirection="column">
|
|
186
|
+
{/* Top border with title */}
|
|
187
|
+
<Box flexDirection="row">
|
|
188
|
+
<Text color={colors.brown}>{box.topLeft}</Text>
|
|
189
|
+
<Text color={colors.yellow}>{title}</Text>
|
|
190
|
+
</Box>
|
|
191
|
+
|
|
192
|
+
{/* Content line: input + status + result */}
|
|
193
|
+
<Box flexDirection="row">
|
|
194
|
+
<Text color={colors.brown}>{box.vertical} </Text>
|
|
195
|
+
<Text color={colors.white}>{input}</Text>
|
|
196
|
+
<Text> </Text>
|
|
197
|
+
<Text color={statusColor}>{statusIndicator}</Text>
|
|
198
|
+
{resultText && (
|
|
199
|
+
<>
|
|
200
|
+
<Text> </Text>
|
|
201
|
+
<Text color={status === 'error' ? colors.pink : colors.white}>{resultText}</Text>
|
|
202
|
+
</>
|
|
203
|
+
)}
|
|
204
|
+
</Box>
|
|
205
|
+
|
|
206
|
+
{/* Bottom border */}
|
|
207
|
+
<Box flexDirection="row">
|
|
208
|
+
<Text color={colors.brown}>
|
|
209
|
+
{box.bottomLeft}
|
|
210
|
+
{box.horizontal.repeat(40)}
|
|
211
|
+
</Text>
|
|
212
|
+
</Box>
|
|
213
|
+
</Box>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WiggumBanner - ASCII art banner component
|
|
3
|
+
*
|
|
4
|
+
* Displays the Wiggum CLI ASCII art logo in Simpson yellow.
|
|
5
|
+
* Inspired by Claude Code's welcome banner style.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Text, Box } from 'ink';
|
|
10
|
+
import { colors } from '../theme.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ASCII art banner for Wiggum CLI
|
|
14
|
+
* Block-style font to match the cfonts 'block' style
|
|
15
|
+
*/
|
|
16
|
+
const BANNER = `
|
|
17
|
+
██╗ ██╗██╗ ██████╗ ██████╗ ██╗ ██╗███╗ ███╗
|
|
18
|
+
██║ ██║██║██╔════╝ ██╔════╝ ██║ ██║████╗ ████║
|
|
19
|
+
██║ █╗ ██║██║██║ ███╗██║ ███╗██║ ██║██╔████╔██║
|
|
20
|
+
██║███╗██║██║██║ ██║██║ ██║██║ ██║██║╚██╔╝██║
|
|
21
|
+
╚███╔███╔╝██║╚██████╔╝╚██████╔╝╚██████╔╝██║ ╚═╝ ██║
|
|
22
|
+
╚══╝╚══╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Props for WiggumBanner component
|
|
27
|
+
*/
|
|
28
|
+
export interface WiggumBannerProps {
|
|
29
|
+
/** Optional color override (defaults to Simpson yellow) */
|
|
30
|
+
color?: string;
|
|
31
|
+
/** Whether to show a compact version */
|
|
32
|
+
compact?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* WiggumBanner component
|
|
37
|
+
*
|
|
38
|
+
* Displays the Wiggum CLI ASCII art logo.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <WiggumBanner />
|
|
43
|
+
* <WiggumBanner color="blue" />
|
|
44
|
+
* <WiggumBanner compact />
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function WiggumBanner({
|
|
48
|
+
color = colors.yellow,
|
|
49
|
+
compact = false,
|
|
50
|
+
}: WiggumBannerProps): React.ReactElement {
|
|
51
|
+
if (compact) {
|
|
52
|
+
return (
|
|
53
|
+
<Box>
|
|
54
|
+
<Text color={color} bold>
|
|
55
|
+
WIGGUM CLI
|
|
56
|
+
</Text>
|
|
57
|
+
</Box>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Box flexDirection="column">
|
|
63
|
+
<Text color={color}>{BANNER}</Text>
|
|
64
|
+
</Box>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkingIndicator - Spinner + status display during AI calls
|
|
3
|
+
*
|
|
4
|
+
* Displays an animated spinner with status text when the AI is processing.
|
|
5
|
+
* Similar to Claude Code's "Thinking..." indicator.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Box, Text } from 'ink';
|
|
10
|
+
import Spinner from 'ink-spinner';
|
|
11
|
+
import { colors } from '../theme.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Working state object describing the current processing state
|
|
15
|
+
*/
|
|
16
|
+
export interface WorkingState {
|
|
17
|
+
/** Whether to show the indicator */
|
|
18
|
+
isWorking: boolean;
|
|
19
|
+
/** Status text (e.g., "Thinking...", "Reading files...", "Searching...") */
|
|
20
|
+
status: string;
|
|
21
|
+
/** Optional hint text (e.g., "esc to interrupt") */
|
|
22
|
+
hint?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Props for the WorkingIndicator component
|
|
27
|
+
*/
|
|
28
|
+
export interface WorkingIndicatorProps {
|
|
29
|
+
/** Working state object */
|
|
30
|
+
state: WorkingState;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* WorkingIndicator component
|
|
35
|
+
*
|
|
36
|
+
* Displays a spinner with status text when AI is processing.
|
|
37
|
+
* Returns null when not working.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* <WorkingIndicator
|
|
42
|
+
* state={{
|
|
43
|
+
* isWorking: true,
|
|
44
|
+
* status: "Thinking...",
|
|
45
|
+
* hint: "esc to interrupt"
|
|
46
|
+
* }}
|
|
47
|
+
* />
|
|
48
|
+
* // Renders: ⠋ Thinking... (esc to interrupt)
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function WorkingIndicator({ state }: WorkingIndicatorProps): React.ReactElement | null {
|
|
52
|
+
const { isWorking, status, hint } = state;
|
|
53
|
+
|
|
54
|
+
// Don't render anything when not working
|
|
55
|
+
if (!isWorking) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Box flexDirection="row" gap={1}>
|
|
61
|
+
<Text color={colors.yellow}>
|
|
62
|
+
<Spinner type="dots" />
|
|
63
|
+
</Text>
|
|
64
|
+
<Text color={colors.yellow}>{status}</Text>
|
|
65
|
+
{hint && (
|
|
66
|
+
<Text color={colors.brown} dimColor>
|
|
67
|
+
({hint})
|
|
68
|
+
</Text>
|
|
69
|
+
)}
|
|
70
|
+
</Box>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable Ink components for the Wiggum TUI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { WorkingIndicator } from './WorkingIndicator.js';
|
|
6
|
+
export type { WorkingIndicatorProps, WorkingState } from './WorkingIndicator.js';
|
|
7
|
+
|
|
8
|
+
export { PhaseHeader } from './PhaseHeader.js';
|
|
9
|
+
export type { PhaseHeaderProps } from './PhaseHeader.js';
|
|
10
|
+
|
|
11
|
+
export { StreamingText } from './StreamingText.js';
|
|
12
|
+
export type { StreamingTextProps } from './StreamingText.js';
|
|
13
|
+
|
|
14
|
+
export { ToolCallCard } from './ToolCallCard.js';
|
|
15
|
+
export type { ToolCallCardProps, ToolCallStatus } from './ToolCallCard.js';
|
|
16
|
+
|
|
17
|
+
export { MessageList } from './MessageList.js';
|
|
18
|
+
export type { MessageListProps, Message, ToolCall } from './MessageList.js';
|
|
19
|
+
|
|
20
|
+
export { ChatInput } from './ChatInput.js';
|
|
21
|
+
export type { ChatInputProps } from './ChatInput.js';
|
package/src/tui/demo.tsx
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TUI Demo - Test the Ink components visually
|
|
4
|
+
*
|
|
5
|
+
* Run with: npx tsx src/tui/demo.tsx
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useState, useEffect } from 'react';
|
|
9
|
+
import { render, Box, Text, useInput } from 'ink';
|
|
10
|
+
import { PhaseHeader } from './components/PhaseHeader.js';
|
|
11
|
+
import { MessageList, type Message } from './components/MessageList.js';
|
|
12
|
+
import { WorkingIndicator } from './components/WorkingIndicator.js';
|
|
13
|
+
import { ChatInput } from './components/ChatInput.js';
|
|
14
|
+
import { colors } from './theme.js';
|
|
15
|
+
|
|
16
|
+
function Demo(): React.ReactElement {
|
|
17
|
+
const [messages, setMessages] = useState<Message[]>([
|
|
18
|
+
{ id: '1', role: 'system', content: 'Spec Generator initialized for feature: demo-feature' },
|
|
19
|
+
{ id: '2', role: 'assistant', content: 'Welcome! Let\'s create a specification for your feature.' },
|
|
20
|
+
]);
|
|
21
|
+
const [phase, setPhase] = useState(1);
|
|
22
|
+
const [isWorking, setIsWorking] = useState(false);
|
|
23
|
+
const [workingStatus, setWorkingStatus] = useState('');
|
|
24
|
+
|
|
25
|
+
// Handle user input
|
|
26
|
+
const handleSubmit = (value: string) => {
|
|
27
|
+
// Add user message
|
|
28
|
+
setMessages(prev => [...prev, {
|
|
29
|
+
id: String(Date.now()),
|
|
30
|
+
role: 'user' as const,
|
|
31
|
+
content: value || '(empty - continue)',
|
|
32
|
+
}]);
|
|
33
|
+
|
|
34
|
+
// Simulate AI working
|
|
35
|
+
setIsWorking(true);
|
|
36
|
+
setWorkingStatus('Thinking...');
|
|
37
|
+
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
// Simulate tool call
|
|
40
|
+
setMessages(prev => [...prev, {
|
|
41
|
+
id: String(Date.now()),
|
|
42
|
+
role: 'assistant' as const,
|
|
43
|
+
content: 'Great! Let me analyze that...',
|
|
44
|
+
toolCalls: [{
|
|
45
|
+
toolName: 'Read File',
|
|
46
|
+
status: 'complete' as const,
|
|
47
|
+
input: 'package.json',
|
|
48
|
+
output: '42 lines read',
|
|
49
|
+
}],
|
|
50
|
+
}]);
|
|
51
|
+
|
|
52
|
+
setIsWorking(false);
|
|
53
|
+
setPhase(p => Math.min(p + 1, 4));
|
|
54
|
+
}, 1500);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Handle escape to exit
|
|
58
|
+
useInput((input, key) => {
|
|
59
|
+
if (key.escape) {
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const phaseNames = ['Context', 'Goals', 'Interview', 'Generation'];
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Box flexDirection="column" padding={1}>
|
|
68
|
+
<Box marginBottom={1}>
|
|
69
|
+
<Text color={colors.yellow} bold>Ink TUI Demo</Text>
|
|
70
|
+
<Text color={colors.brown}> - Press Esc to exit</Text>
|
|
71
|
+
</Box>
|
|
72
|
+
|
|
73
|
+
<PhaseHeader
|
|
74
|
+
currentPhase={phase}
|
|
75
|
+
totalPhases={4}
|
|
76
|
+
phaseName={phaseNames[phase - 1]}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
<Box marginY={1}>
|
|
80
|
+
<MessageList messages={messages} />
|
|
81
|
+
</Box>
|
|
82
|
+
|
|
83
|
+
<Box marginY={1}>
|
|
84
|
+
<WorkingIndicator
|
|
85
|
+
state={{
|
|
86
|
+
isWorking,
|
|
87
|
+
status: workingStatus,
|
|
88
|
+
hint: 'esc to cancel',
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
</Box>
|
|
92
|
+
|
|
93
|
+
<Box marginTop={1}>
|
|
94
|
+
<ChatInput
|
|
95
|
+
onSubmit={handleSubmit}
|
|
96
|
+
disabled={isWorking}
|
|
97
|
+
allowEmpty={phase === 1}
|
|
98
|
+
placeholder={
|
|
99
|
+
phase === 1
|
|
100
|
+
? 'Enter URL or file path, or press Enter to continue...'
|
|
101
|
+
: 'Type your response...'
|
|
102
|
+
}
|
|
103
|
+
/>
|
|
104
|
+
</Box>
|
|
105
|
+
</Box>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Render the demo
|
|
110
|
+
console.clear();
|
|
111
|
+
render(<Demo />);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom React hooks for the Wiggum TUI
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { useSpecGenerator } from './useSpecGenerator.js';
|
|
6
|
+
export type {
|
|
7
|
+
GeneratorPhase,
|
|
8
|
+
PhaseConfig,
|
|
9
|
+
SpecGeneratorState,
|
|
10
|
+
SpecGeneratorOptions,
|
|
11
|
+
UseSpecGeneratorReturn,
|
|
12
|
+
} from './useSpecGenerator.js';
|
|
13
|
+
export { PHASE_CONFIGS, TOTAL_DISPLAY_PHASES } from './useSpecGenerator.js';
|