wiggum-cli 0.12.1 → 0.13.1
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/index.js +7 -6
- package/dist/templates/prompts/PROMPT_review_auto.md.tmpl +19 -23
- package/dist/tui/app.d.ts +12 -22
- package/dist/tui/app.js +130 -314
- package/dist/tui/components/AppShell.d.ts +47 -0
- package/dist/tui/components/AppShell.js +19 -0
- package/dist/tui/components/FooterStatusBar.js +2 -3
- package/dist/tui/components/HeaderContent.d.ts +28 -0
- package/dist/tui/components/HeaderContent.js +16 -0
- package/dist/tui/components/MessageList.d.ts +9 -7
- package/dist/tui/components/MessageList.js +23 -17
- package/dist/tui/components/RunCompletionSummary.d.ts +22 -0
- package/dist/tui/components/RunCompletionSummary.js +23 -0
- package/dist/tui/components/SpecCompletionSummary.d.ts +47 -0
- package/dist/tui/components/SpecCompletionSummary.js +124 -0
- package/dist/tui/components/TipsBar.d.ts +24 -0
- package/dist/tui/components/TipsBar.js +23 -0
- package/dist/tui/components/WiggumBanner.js +8 -3
- package/dist/tui/hooks/useBackgroundRuns.d.ts +52 -0
- package/dist/tui/hooks/useBackgroundRuns.js +121 -0
- package/dist/tui/orchestration/interview-orchestrator.js +1 -1
- package/dist/tui/screens/InitScreen.d.ts +13 -8
- package/dist/tui/screens/InitScreen.js +86 -87
- package/dist/tui/screens/InterviewScreen.d.ts +11 -8
- package/dist/tui/screens/InterviewScreen.js +145 -99
- package/dist/tui/screens/MainShell.d.ts +13 -12
- package/dist/tui/screens/MainShell.js +65 -69
- package/dist/tui/screens/RunScreen.d.ts +17 -1
- package/dist/tui/screens/RunScreen.js +235 -80
- package/dist/tui/screens/index.d.ts +0 -2
- package/dist/tui/screens/index.js +0 -1
- package/dist/tui/utils/loop-status.d.ts +22 -3
- package/dist/tui/utils/loop-status.js +65 -15
- package/package.json +5 -1
- package/src/templates/prompts/PROMPT_review_auto.md.tmpl +19 -23
- package/dist/tui/screens/WelcomeScreen.d.ts +0 -44
- package/dist/tui/screens/WelcomeScreen.js +0 -54
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* InterviewScreen - Main screen for the /new command interview flow
|
|
4
4
|
*
|
|
@@ -8,55 +8,54 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
8
8
|
* 2. Goals - Understand what to build
|
|
9
9
|
* 3. Interview - Clarifying questions
|
|
10
10
|
* 4. Generation - Generate the specification
|
|
11
|
+
* 5. Complete - Show summary and return to shell
|
|
12
|
+
*
|
|
13
|
+
* Wrapped in AppShell for consistent layout. On completion,
|
|
14
|
+
* shows SpecCompletionSummary inline before returning to shell.
|
|
11
15
|
*/
|
|
12
16
|
import { useEffect, useCallback, useRef, useState } from 'react';
|
|
13
17
|
import { Box, Text, useInput } from 'ink';
|
|
14
|
-
import {
|
|
18
|
+
import { logger } from '../../utils/logger.js';
|
|
15
19
|
import { MessageList } from '../components/MessageList.js';
|
|
16
|
-
import { WorkingIndicator } from '../components/WorkingIndicator.js';
|
|
17
20
|
import { ChatInput } from '../components/ChatInput.js';
|
|
18
21
|
import { MultiSelect } from '../components/MultiSelect.js';
|
|
22
|
+
import { AppShell } from '../components/AppShell.js';
|
|
23
|
+
import { SpecCompletionSummary } from '../components/SpecCompletionSummary.js';
|
|
19
24
|
import { useSpecGenerator, PHASE_CONFIGS, TOTAL_DISPLAY_PHASES, } from '../hooks/useSpecGenerator.js';
|
|
20
25
|
import { InterviewOrchestrator } from '../orchestration/interview-orchestrator.js';
|
|
21
|
-
import { theme } from '../theme.js';
|
|
26
|
+
import { theme, phase } from '../theme.js';
|
|
22
27
|
import { loadContext, toScanResultFromPersisted, getContextAge, } from '../../context/index.js';
|
|
28
|
+
import { join } from 'node:path';
|
|
23
29
|
import { initTracing, flushTracing } from '../../utils/tracing.js';
|
|
24
30
|
import { resolveOptionLabels } from '../types/interview.js';
|
|
25
31
|
/**
|
|
26
32
|
* InterviewScreen component
|
|
27
33
|
*
|
|
28
34
|
* The main screen for the /new command interview flow. Combines all TUI
|
|
29
|
-
* components
|
|
30
|
-
* FooterStatusBar) to create the complete interview experience.
|
|
31
|
-
*
|
|
32
|
-
* Uses the useSpecGenerator hook for state and InterviewOrchestrator
|
|
33
|
-
* to bridge to the AI conversation.
|
|
35
|
+
* components within an AppShell layout.
|
|
34
36
|
*/
|
|
35
|
-
export function InterviewScreen({ featureName, projectRoot, provider, model, scanResult, specsPath = '.ralph/specs', onComplete, onCancel, }) {
|
|
37
|
+
export function InterviewScreen({ header, featureName, projectRoot, provider, model, scanResult, specsPath = '.ralph/specs', onComplete, onCancel, }) {
|
|
36
38
|
const { state, initialize, addMessage, addStreamingMessage, updateStreamingMessage, completeStreamingMessage, startToolCall, completeToolCall, setPhase, setGeneratedSpec, setError, setWorking, setReady, } = useSpecGenerator();
|
|
37
|
-
// Track orchestrator instance
|
|
38
39
|
const orchestratorRef = useRef(null);
|
|
39
|
-
// Track if we're in streaming mode for the current message
|
|
40
40
|
const isStreamingRef = useRef(false);
|
|
41
41
|
const streamContentRef = useRef('');
|
|
42
|
-
// Track if we're in generation phase (more reliable than checking orchestrator)
|
|
43
42
|
const isGeneratingRef = useRef(false);
|
|
44
|
-
// Track if component is unmounted to prevent callbacks after cleanup
|
|
45
43
|
const isCancelledRef = useRef(false);
|
|
46
|
-
// Use refs for callbacks and state to avoid stale closures
|
|
47
44
|
const onCompleteRef = useRef(onComplete);
|
|
48
45
|
onCompleteRef.current = onComplete;
|
|
49
|
-
// Track messages in ref for access in callbacks
|
|
50
46
|
const messagesRef = useRef(state.messages);
|
|
51
47
|
messagesRef.current = state.messages;
|
|
52
|
-
// State for tool call expansion (Ctrl+O toggle)
|
|
53
48
|
const [toolCallsExpanded, setToolCallsExpanded] = useState(false);
|
|
54
|
-
// State for multi-select interview questions (null = free-text mode)
|
|
55
49
|
const [currentQuestion, setCurrentQuestion] = useState(null);
|
|
56
|
-
//
|
|
57
|
-
|
|
50
|
+
// Completion state: when spec is done, show summary inline
|
|
51
|
+
const [completionData, setCompletionData] = useState(null);
|
|
58
52
|
useEffect(() => {
|
|
59
|
-
|
|
53
|
+
try {
|
|
54
|
+
initTracing();
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
logger.error(`Failed to init tracing: ${err instanceof Error ? err.message : String(err)}`);
|
|
58
|
+
}
|
|
60
59
|
return () => {
|
|
61
60
|
void flushTracing();
|
|
62
61
|
};
|
|
@@ -70,17 +69,13 @@ export function InterviewScreen({ featureName, projectRoot, provider, model, sca
|
|
|
70
69
|
provider,
|
|
71
70
|
model,
|
|
72
71
|
});
|
|
73
|
-
// Async IIFE to allow loading persisted context
|
|
74
72
|
(async () => {
|
|
75
|
-
// Determine session context: use scanResult prop if available,
|
|
76
|
-
// otherwise try loading persisted context from disk
|
|
77
73
|
let resolvedScanResult = scanResult;
|
|
78
74
|
let resolvedSessionContext;
|
|
79
75
|
if (!scanResult) {
|
|
80
76
|
try {
|
|
81
77
|
const persisted = await loadContext(projectRoot);
|
|
82
78
|
if (persisted) {
|
|
83
|
-
// Map persisted AI analysis to SessionContext
|
|
84
79
|
resolvedSessionContext = {
|
|
85
80
|
entryPoints: persisted.aiAnalysis.projectContext?.entryPoints,
|
|
86
81
|
keyDirectories: persisted.aiAnalysis.projectContext?.keyDirectories,
|
|
@@ -89,16 +84,15 @@ export function InterviewScreen({ featureName, projectRoot, provider, model, sca
|
|
|
89
84
|
implementationGuidelines: persisted.aiAnalysis.implementationGuidelines,
|
|
90
85
|
keyPatterns: persisted.aiAnalysis.technologyPractices?.practices,
|
|
91
86
|
};
|
|
92
|
-
// Rehydrate a minimal scan result for Project Tech Stack context
|
|
93
87
|
resolvedScanResult = toScanResultFromPersisted(persisted.scanResult, projectRoot);
|
|
94
88
|
const { human } = getContextAge(persisted);
|
|
95
89
|
addMessage('system', `Using cached project context from .ralph/.context.json (updated ${human} ago). Run /sync to refresh.`);
|
|
96
90
|
}
|
|
97
91
|
}
|
|
98
92
|
catch (err) {
|
|
99
|
-
// Show error but continue without context
|
|
100
93
|
if (!isCancelledRef.current) {
|
|
101
|
-
|
|
94
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
95
|
+
addMessage('system', `Unable to load cached project context (${reason}); continuing without it.`);
|
|
102
96
|
}
|
|
103
97
|
}
|
|
104
98
|
}
|
|
@@ -160,7 +154,9 @@ export function InterviewScreen({ featureName, projectRoot, provider, model, sca
|
|
|
160
154
|
if (isCancelledRef.current)
|
|
161
155
|
return;
|
|
162
156
|
setGeneratedSpec(spec);
|
|
163
|
-
|
|
157
|
+
// Show completion summary inline instead of navigating away immediately
|
|
158
|
+
const specFilePath = join(projectRoot, specsPath, `${featureName}.md`);
|
|
159
|
+
setCompletionData({ spec, specPath: specFilePath });
|
|
164
160
|
},
|
|
165
161
|
onError: (error) => {
|
|
166
162
|
if (isCancelledRef.current)
|
|
@@ -184,114 +180,153 @@ export function InterviewScreen({ featureName, projectRoot, provider, model, sca
|
|
|
184
180
|
},
|
|
185
181
|
});
|
|
186
182
|
orchestratorRef.current = orchestrator;
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
try {
|
|
184
|
+
orchestrator.start();
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
if (!isCancelledRef.current) {
|
|
188
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
189
|
+
logger.error(`Orchestrator start failed: ${reason}`);
|
|
190
|
+
setError(reason);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
})().catch((err) => {
|
|
194
|
+
if (!isCancelledRef.current) {
|
|
195
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
196
|
+
logger.error(`Interview initialization failed: ${reason}`);
|
|
197
|
+
setError(reason);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
189
200
|
return () => {
|
|
190
201
|
isCancelledRef.current = true;
|
|
191
202
|
orchestratorRef.current = null;
|
|
192
203
|
};
|
|
193
|
-
}, [featureName, projectRoot, provider, model, scanResult]);
|
|
194
|
-
// Handle user input submission based on current phase
|
|
204
|
+
}, [featureName, projectRoot, provider, model, scanResult, specsPath]);
|
|
195
205
|
const handleSubmit = useCallback(async (value) => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
206
|
+
try {
|
|
207
|
+
const orchestrator = orchestratorRef.current;
|
|
208
|
+
if (!orchestrator) {
|
|
209
|
+
logger.debug('Interview submit ignored: orchestrator not ready yet');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (value) {
|
|
213
|
+
addMessage('user', value);
|
|
214
|
+
}
|
|
215
|
+
const currentPhase = orchestrator.getPhase();
|
|
216
|
+
switch (currentPhase) {
|
|
217
|
+
case 'context':
|
|
218
|
+
if (value) {
|
|
219
|
+
await orchestrator.addReference(value);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
await orchestrator.advanceToGoals();
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
case 'goals':
|
|
226
|
+
await orchestrator.submitGoals(value);
|
|
227
|
+
break;
|
|
228
|
+
case 'interview':
|
|
229
|
+
if (value.toLowerCase() === 'done' || value.toLowerCase() === 'skip') {
|
|
230
|
+
await orchestrator.skipToGeneration();
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
const answer = {
|
|
234
|
+
mode: 'freeText',
|
|
235
|
+
questionId: currentQuestion?.id || '',
|
|
236
|
+
text: value,
|
|
237
|
+
};
|
|
238
|
+
await orchestrator.submitAnswer(answer);
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
default:
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
202
244
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// User entered a reference URL/path
|
|
208
|
-
await orchestrator.addReference(value);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
// Empty input = done with context, advance to goals
|
|
212
|
-
await orchestrator.advanceToGoals();
|
|
213
|
-
}
|
|
214
|
-
break;
|
|
215
|
-
case 'goals':
|
|
216
|
-
// User entered their goals
|
|
217
|
-
await orchestrator.submitGoals(value);
|
|
218
|
-
break;
|
|
219
|
-
case 'interview':
|
|
220
|
-
if (value.toLowerCase() === 'done' || value.toLowerCase() === 'skip') {
|
|
221
|
-
// Skip to generation
|
|
222
|
-
await orchestrator.skipToGeneration();
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
// Submit free-text answer
|
|
226
|
-
const answer = {
|
|
227
|
-
mode: 'freeText',
|
|
228
|
-
questionId: currentQuestion?.id || '',
|
|
229
|
-
text: value,
|
|
230
|
-
};
|
|
231
|
-
await orchestrator.submitAnswer(answer);
|
|
232
|
-
}
|
|
233
|
-
break;
|
|
234
|
-
default:
|
|
235
|
-
// In generation or complete phase, ignore input
|
|
236
|
-
break;
|
|
245
|
+
catch (error) {
|
|
246
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
247
|
+
logger.error(`Interview submit failed: ${reason}`);
|
|
248
|
+
setError(reason);
|
|
237
249
|
}
|
|
238
|
-
}, [addMessage, currentQuestion]);
|
|
239
|
-
// Handle multi-select answer submission
|
|
250
|
+
}, [addMessage, currentQuestion, setError]);
|
|
240
251
|
const handleMultiSelectSubmit = useCallback(async (selectedValues) => {
|
|
241
252
|
try {
|
|
242
253
|
const orchestrator = orchestratorRef.current;
|
|
243
254
|
if (!orchestrator || !currentQuestion)
|
|
244
255
|
return;
|
|
245
|
-
//
|
|
256
|
+
// Capture question before clearing so MultiSelect disappears immediately
|
|
257
|
+
const question = currentQuestion;
|
|
258
|
+
setCurrentQuestion(null);
|
|
246
259
|
if (selectedValues.length === 0) {
|
|
247
260
|
addMessage('user', '(No options selected)');
|
|
248
261
|
}
|
|
249
262
|
else {
|
|
250
|
-
const labels = resolveOptionLabels(
|
|
263
|
+
const labels = resolveOptionLabels(question.options, selectedValues);
|
|
251
264
|
addMessage('user', labels.join(', '));
|
|
252
265
|
}
|
|
253
|
-
// Submit multi-select answer
|
|
254
266
|
const answer = {
|
|
255
267
|
mode: 'multiSelect',
|
|
256
|
-
questionId:
|
|
268
|
+
questionId: question.id,
|
|
257
269
|
selectedOptionIds: selectedValues,
|
|
258
270
|
};
|
|
259
271
|
await orchestrator.submitAnswer(answer);
|
|
260
272
|
}
|
|
261
273
|
catch (error) {
|
|
262
|
-
|
|
274
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
275
|
+
logger.error(`Interview multi-select submit failed: ${reason}`);
|
|
276
|
+
setError(reason);
|
|
263
277
|
}
|
|
264
278
|
}, [addMessage, currentQuestion, setError]);
|
|
265
|
-
// Handle "Chat about this" mode switch
|
|
266
279
|
const handleChatMode = useCallback(() => {
|
|
267
280
|
setCurrentQuestion(null);
|
|
268
281
|
}, []);
|
|
269
|
-
// Handle
|
|
282
|
+
// Handle completion dismiss (user presses Enter or Esc on summary)
|
|
283
|
+
const handleCompletionDismiss = useCallback(() => {
|
|
284
|
+
if (completionData) {
|
|
285
|
+
onCompleteRef.current(completionData.spec, messagesRef.current, completionData.specPath);
|
|
286
|
+
}
|
|
287
|
+
}, [completionData]);
|
|
270
288
|
useInput((input, key) => {
|
|
289
|
+
// If showing completion summary, Enter or Esc dismisses
|
|
290
|
+
if (completionData) {
|
|
291
|
+
if (key.return || key.escape) {
|
|
292
|
+
handleCompletionDismiss();
|
|
293
|
+
}
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
271
296
|
if (key.escape) {
|
|
272
|
-
// When in multiSelect mode, Escape switches back to free-text instead of cancelling
|
|
273
297
|
if (currentQuestion) {
|
|
274
298
|
setCurrentQuestion(null);
|
|
275
299
|
return;
|
|
276
300
|
}
|
|
277
301
|
onCancel();
|
|
278
302
|
}
|
|
279
|
-
// Ctrl+O to toggle tool call expansion
|
|
280
303
|
if (key.ctrl && input === 'o') {
|
|
281
304
|
setToolCallsExpanded((prev) => !prev);
|
|
282
305
|
}
|
|
283
306
|
});
|
|
284
|
-
// Get current phase configuration
|
|
285
307
|
const phaseConfig = PHASE_CONFIGS[state.phase];
|
|
286
|
-
// Determine if input should be disabled
|
|
287
308
|
const inputDisabled = !state.awaitingInput || state.isWorking || state.phase === 'complete';
|
|
288
|
-
//
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
309
|
+
// Get tips text based on phase
|
|
310
|
+
const getTips = () => {
|
|
311
|
+
if (completionData)
|
|
312
|
+
return null;
|
|
313
|
+
switch (state.phase) {
|
|
314
|
+
case 'context':
|
|
315
|
+
return 'Enter URLs or file paths. Empty input to continue.';
|
|
316
|
+
case 'goals':
|
|
317
|
+
return 'Describe what you want to build.';
|
|
318
|
+
case 'interview':
|
|
319
|
+
return currentQuestion
|
|
320
|
+
? 'Space select, Enter confirm, C to chat, Esc cancel'
|
|
321
|
+
: "Type answer, 'done' to generate, Esc cancel";
|
|
322
|
+
case 'generation':
|
|
323
|
+
return 'Generating specification\u2026 Esc to cancel';
|
|
324
|
+
case 'complete':
|
|
325
|
+
return 'Enter to return to shell';
|
|
326
|
+
default:
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
293
329
|
};
|
|
294
|
-
// Get placeholder text based on phase
|
|
295
330
|
const getPlaceholder = () => {
|
|
296
331
|
switch (state.phase) {
|
|
297
332
|
case 'context':
|
|
@@ -308,13 +343,24 @@ export function InterviewScreen({ featureName, projectRoot, provider, model, sca
|
|
|
308
343
|
return 'Type your response...';
|
|
309
344
|
}
|
|
310
345
|
};
|
|
311
|
-
// Build phase string for status line
|
|
312
346
|
const totalPhases = state.phase === 'complete' ? PHASE_CONFIGS.complete.number : TOTAL_DISPLAY_PHASES;
|
|
313
347
|
const phaseString = `${phaseConfig.name} (${phaseConfig.number}/${totalPhases})`;
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
348
|
+
// Build input element based on phase
|
|
349
|
+
let inputElement = null;
|
|
350
|
+
if (!completionData && state.phase !== 'complete') {
|
|
351
|
+
if (state.phase === 'interview' && currentQuestion) {
|
|
352
|
+
inputElement = (_jsx(MultiSelect, { message: currentQuestion.text, options: currentQuestion.options.map(opt => ({
|
|
353
|
+
value: opt.id,
|
|
354
|
+
label: opt.label,
|
|
355
|
+
})), onSubmit: handleMultiSelectSubmit, onChatMode: handleChatMode }, currentQuestion.id));
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
inputElement = (_jsx(ChatInput, { onSubmit: handleSubmit, disabled: inputDisabled, allowEmpty: state.phase === 'context', placeholder: getPlaceholder() }));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return (_jsx(AppShell, { header: header, tips: getTips(), isWorking: state.isWorking && !completionData, workingStatus: state.workingStatus, workingHint: "esc to cancel", error: state.error, input: inputElement, footerStatus: {
|
|
362
|
+
action: 'New Spec',
|
|
363
|
+
phase: phaseString,
|
|
364
|
+
path: featureName,
|
|
365
|
+
}, children: completionData ? (_jsx(SpecCompletionSummary, { featureName: featureName, spec: completionData.spec, specPath: completionData.specPath, messages: state.messages })) : (_jsxs(_Fragment, { children: [_jsx(MessageList, { messages: state.messages, toolCallsExpanded: toolCallsExpanded }), state.phase === 'complete' && !completionData && (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: theme.colors.success, children: [phase.complete, " "] }), _jsx(Text, { children: "Specification complete." })] }))] })) }));
|
|
320
366
|
}
|
|
@@ -3,43 +3,44 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The main interactive shell for Wiggum CLI, replacing the readline REPL.
|
|
5
5
|
* Handles slash commands and provides navigation to other screens.
|
|
6
|
+
* Wrapped in AppShell for consistent layout.
|
|
6
7
|
*/
|
|
7
8
|
import React from 'react';
|
|
8
9
|
import type { SessionState } from '../../repl/session-state.js';
|
|
10
|
+
import type { BackgroundRun } from '../hooks/useBackgroundRuns.js';
|
|
9
11
|
/**
|
|
10
12
|
* Navigation targets for the shell
|
|
11
13
|
*/
|
|
12
|
-
export type NavigationTarget = '
|
|
14
|
+
export type NavigationTarget = 'shell' | 'interview' | 'init' | 'run';
|
|
13
15
|
/**
|
|
14
16
|
* Navigation props passed to target screens
|
|
15
17
|
*/
|
|
16
18
|
export interface NavigationProps {
|
|
17
19
|
featureName?: string;
|
|
20
|
+
monitorOnly?: boolean;
|
|
18
21
|
[key: string]: unknown;
|
|
19
22
|
}
|
|
20
23
|
/**
|
|
21
24
|
* Props for MainShell component
|
|
22
25
|
*/
|
|
23
26
|
export interface MainShellProps {
|
|
27
|
+
/** Pre-built header element from App */
|
|
28
|
+
header: React.ReactNode;
|
|
24
29
|
/** Current session state */
|
|
25
30
|
sessionState: SessionState;
|
|
26
31
|
/** Called when navigating to another screen */
|
|
27
32
|
onNavigate: (target: NavigationTarget, props?: NavigationProps) => void;
|
|
28
|
-
/**
|
|
29
|
-
|
|
33
|
+
/** Active background runs */
|
|
34
|
+
backgroundRuns?: BackgroundRun[];
|
|
35
|
+
/** Message to display when the shell first mounts (e.g. from init completion) */
|
|
36
|
+
initialMessage?: string;
|
|
37
|
+
/** File paths to display as dimmed lines below the initial message */
|
|
38
|
+
initialFiles?: string[];
|
|
30
39
|
}
|
|
31
40
|
/**
|
|
32
41
|
* MainShell component
|
|
33
42
|
*
|
|
34
43
|
* The main interactive shell that handles slash commands and navigation.
|
|
35
44
|
* Replaces the readline-based REPL with an Ink-powered TUI.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```tsx
|
|
39
|
-
* <MainShell
|
|
40
|
-
* sessionState={state}
|
|
41
|
-
* onNavigate={(target, props) => setScreen(target, props)}
|
|
42
|
-
* />
|
|
43
|
-
* ```
|
|
44
45
|
*/
|
|
45
|
-
export declare function MainShell({ sessionState, onNavigate, }: MainShellProps): React.ReactElement;
|
|
46
|
+
export declare function MainShell({ header, sessionState, onNavigate, backgroundRuns, initialMessage, initialFiles, }: MainShellProps): React.ReactElement;
|