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.
Files changed (126) hide show
  1. package/dist/ai/conversation/conversation-manager.d.ts +11 -0
  2. package/dist/ai/conversation/conversation-manager.d.ts.map +1 -1
  3. package/dist/ai/conversation/conversation-manager.js +14 -0
  4. package/dist/ai/conversation/conversation-manager.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +4 -0
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/new.d.ts +2 -0
  9. package/dist/commands/new.d.ts.map +1 -1
  10. package/dist/commands/new.js +63 -22
  11. package/dist/commands/new.js.map +1 -1
  12. package/dist/index.d.ts +3 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +97 -22
  15. package/dist/index.js.map +1 -1
  16. package/dist/tui/app.d.ts +108 -0
  17. package/dist/tui/app.d.ts.map +1 -0
  18. package/dist/tui/app.js +169 -0
  19. package/dist/tui/app.js.map +1 -0
  20. package/dist/tui/components/ChatInput.d.ts +40 -0
  21. package/dist/tui/components/ChatInput.d.ts.map +1 -0
  22. package/dist/tui/components/ChatInput.js +61 -0
  23. package/dist/tui/components/ChatInput.js.map +1 -0
  24. package/dist/tui/components/MessageList.d.ts +79 -0
  25. package/dist/tui/components/MessageList.d.ts.map +1 -0
  26. package/dist/tui/components/MessageList.js +68 -0
  27. package/dist/tui/components/MessageList.js.map +1 -0
  28. package/dist/tui/components/PhaseHeader.d.ts +36 -0
  29. package/dist/tui/components/PhaseHeader.d.ts.map +1 -0
  30. package/dist/tui/components/PhaseHeader.js +31 -0
  31. package/dist/tui/components/PhaseHeader.js.map +1 -0
  32. package/dist/tui/components/StreamingText.d.ts +47 -0
  33. package/dist/tui/components/StreamingText.d.ts.map +1 -0
  34. package/dist/tui/components/StreamingText.js +38 -0
  35. package/dist/tui/components/StreamingText.js.map +1 -0
  36. package/dist/tui/components/ToolCallCard.d.ts +65 -0
  37. package/dist/tui/components/ToolCallCard.d.ts.map +1 -0
  38. package/dist/tui/components/ToolCallCard.js +100 -0
  39. package/dist/tui/components/ToolCallCard.js.map +1 -0
  40. package/dist/tui/components/WiggumBanner.d.ts +30 -0
  41. package/dist/tui/components/WiggumBanner.d.ts.map +1 -0
  42. package/dist/tui/components/WiggumBanner.js +34 -0
  43. package/dist/tui/components/WiggumBanner.js.map +1 -0
  44. package/dist/tui/components/WorkingIndicator.d.ts +45 -0
  45. package/dist/tui/components/WorkingIndicator.d.ts.map +1 -0
  46. package/dist/tui/components/WorkingIndicator.js +31 -0
  47. package/dist/tui/components/WorkingIndicator.js.map +1 -0
  48. package/dist/tui/components/index.d.ts +16 -0
  49. package/dist/tui/components/index.d.ts.map +1 -0
  50. package/dist/tui/components/index.js +10 -0
  51. package/dist/tui/components/index.js.map +1 -0
  52. package/dist/tui/demo.d.ts +8 -0
  53. package/dist/tui/demo.d.ts.map +1 -0
  54. package/dist/tui/demo.js +69 -0
  55. package/dist/tui/demo.js.map +1 -0
  56. package/dist/tui/hooks/index.d.ts +7 -0
  57. package/dist/tui/hooks/index.d.ts.map +1 -0
  58. package/dist/tui/hooks/index.js +6 -0
  59. package/dist/tui/hooks/index.js.map +1 -0
  60. package/dist/tui/hooks/useSpecGenerator.d.ts +184 -0
  61. package/dist/tui/hooks/useSpecGenerator.d.ts.map +1 -0
  62. package/dist/tui/hooks/useSpecGenerator.js +452 -0
  63. package/dist/tui/hooks/useSpecGenerator.js.map +1 -0
  64. package/dist/tui/index.d.ts +14 -0
  65. package/dist/tui/index.d.ts.map +1 -0
  66. package/dist/tui/index.js +18 -0
  67. package/dist/tui/index.js.map +1 -0
  68. package/dist/tui/orchestration/index.d.ts +6 -0
  69. package/dist/tui/orchestration/index.d.ts.map +1 -0
  70. package/dist/tui/orchestration/index.js +6 -0
  71. package/dist/tui/orchestration/index.js.map +1 -0
  72. package/dist/tui/orchestration/interview-orchestrator.d.ts +136 -0
  73. package/dist/tui/orchestration/interview-orchestrator.d.ts.map +1 -0
  74. package/dist/tui/orchestration/interview-orchestrator.js +437 -0
  75. package/dist/tui/orchestration/interview-orchestrator.js.map +1 -0
  76. package/dist/tui/screens/InitScreen.d.ts +26 -0
  77. package/dist/tui/screens/InitScreen.d.ts.map +1 -0
  78. package/dist/tui/screens/InitScreen.js +30 -0
  79. package/dist/tui/screens/InitScreen.js.map +1 -0
  80. package/dist/tui/screens/InterviewScreen.d.ts +44 -0
  81. package/dist/tui/screens/InterviewScreen.d.ts.map +1 -0
  82. package/dist/tui/screens/InterviewScreen.js +212 -0
  83. package/dist/tui/screens/InterviewScreen.js.map +1 -0
  84. package/dist/tui/screens/MainShell.d.ts +46 -0
  85. package/dist/tui/screens/MainShell.d.ts.map +1 -0
  86. package/dist/tui/screens/MainShell.js +196 -0
  87. package/dist/tui/screens/MainShell.js.map +1 -0
  88. package/dist/tui/screens/WelcomeScreen.d.ts +45 -0
  89. package/dist/tui/screens/WelcomeScreen.d.ts.map +1 -0
  90. package/dist/tui/screens/WelcomeScreen.js +56 -0
  91. package/dist/tui/screens/WelcomeScreen.js.map +1 -0
  92. package/dist/tui/screens/index.d.ts +6 -0
  93. package/dist/tui/screens/index.d.ts.map +1 -0
  94. package/dist/tui/screens/index.js +5 -0
  95. package/dist/tui/screens/index.js.map +1 -0
  96. package/dist/tui/theme.d.ts +66 -0
  97. package/dist/tui/theme.d.ts.map +1 -0
  98. package/dist/tui/theme.js +62 -0
  99. package/dist/tui/theme.js.map +1 -0
  100. package/package.json +6 -1
  101. package/src/ai/conversation/conversation-manager.ts +22 -0
  102. package/src/cli.ts +4 -0
  103. package/src/commands/new.ts +79 -27
  104. package/src/index.ts +109 -27
  105. package/src/tui/app.tsx +297 -0
  106. package/src/tui/components/ChatInput.tsx +105 -0
  107. package/src/tui/components/MessageList.tsx +186 -0
  108. package/src/tui/components/PhaseHeader.tsx +63 -0
  109. package/src/tui/components/StreamingText.tsx +69 -0
  110. package/src/tui/components/ToolCallCard.tsx +215 -0
  111. package/src/tui/components/WiggumBanner.tsx +66 -0
  112. package/src/tui/components/WorkingIndicator.tsx +72 -0
  113. package/src/tui/components/index.ts +21 -0
  114. package/src/tui/demo.tsx +111 -0
  115. package/src/tui/hooks/index.ts +13 -0
  116. package/src/tui/hooks/useSpecGenerator.ts +662 -0
  117. package/src/tui/index.ts +23 -0
  118. package/src/tui/orchestration/index.ts +10 -0
  119. package/src/tui/orchestration/interview-orchestrator.ts +559 -0
  120. package/src/tui/screens/InitScreen.tsx +63 -0
  121. package/src/tui/screens/InterviewScreen.tsx +319 -0
  122. package/src/tui/screens/MainShell.tsx +290 -0
  123. package/src/tui/screens/WelcomeScreen.tsx +141 -0
  124. package/src/tui/screens/index.ts +6 -0
  125. package/src/tui/theme.ts +76 -0
  126. 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';
@@ -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';