wiggum-cli 0.7.7 → 0.8.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/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +9 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +11 -3
- package/dist/commands/new.js.map +1 -1
- package/dist/tui/app.d.ts +98 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +70 -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/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/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 +168 -0
- package/dist/tui/hooks/useSpecGenerator.d.ts.map +1 -0
- package/dist/tui/hooks/useSpecGenerator.js +405 -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/screens/InterviewScreen.d.ts +55 -0
- package/dist/tui/screens/InterviewScreen.d.ts.map +1 -0
- package/dist/tui/screens/InterviewScreen.js +84 -0
- package/dist/tui/screens/InterviewScreen.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 +62 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +58 -0
- package/dist/tui/theme.js.map +1 -0
- package/package.json +6 -1
- package/src/commands/init.ts +13 -2
- package/src/commands/new.ts +15 -3
- package/src/tui/app.tsx +138 -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/WorkingIndicator.tsx +72 -0
- package/src/tui/components/index.ts +21 -0
- package/src/tui/hooks/index.ts +13 -0
- package/src/tui/hooks/useSpecGenerator.ts +589 -0
- package/src/tui/index.ts +23 -0
- package/src/tui/screens/InterviewScreen.tsx +164 -0
- package/src/tui/screens/index.ts +6 -0
- package/src/tui/theme.ts +72 -0
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InterviewScreen - Main screen for the /new command interview flow
|
|
3
|
+
*
|
|
4
|
+
* The complete TUI for the spec generation interview process.
|
|
5
|
+
* Orchestrates user input and AI responses through multiple phases:
|
|
6
|
+
* 1. Context - Gather reference URLs/files
|
|
7
|
+
* 2. Goals - Understand what to build
|
|
8
|
+
* 3. Interview - Clarifying questions
|
|
9
|
+
* 4. Generation - Generate the specification
|
|
10
|
+
*/
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import type { AIProvider } from '../../ai/providers.js';
|
|
13
|
+
import type { ScanResult } from '../../scanner/types.js';
|
|
14
|
+
/**
|
|
15
|
+
* Props for the InterviewScreen component
|
|
16
|
+
*/
|
|
17
|
+
export interface InterviewScreenProps {
|
|
18
|
+
/** Name of the feature being specified */
|
|
19
|
+
featureName: string;
|
|
20
|
+
/** Project root directory path */
|
|
21
|
+
projectRoot: string;
|
|
22
|
+
/** AI provider to use */
|
|
23
|
+
provider: AIProvider;
|
|
24
|
+
/** Model ID to use */
|
|
25
|
+
model: string;
|
|
26
|
+
/** Optional scan result with detected tech stack */
|
|
27
|
+
scanResult?: ScanResult;
|
|
28
|
+
/** Called when spec generation is complete */
|
|
29
|
+
onComplete: (spec: string) => void;
|
|
30
|
+
/** Called when user cancels the interview */
|
|
31
|
+
onCancel: () => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* InterviewScreen component
|
|
35
|
+
*
|
|
36
|
+
* The main screen for the /new command interview flow. Combines all TUI
|
|
37
|
+
* components (PhaseHeader, MessageList, WorkingIndicator, ChatInput) to
|
|
38
|
+
* create the complete interview experience.
|
|
39
|
+
*
|
|
40
|
+
* Uses the useSpecGenerator hook to manage state and actions.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* <InterviewScreen
|
|
45
|
+
* featureName="user-auth"
|
|
46
|
+
* projectRoot="/path/to/project"
|
|
47
|
+
* provider="anthropic"
|
|
48
|
+
* model="claude-sonnet-4-5-20250514"
|
|
49
|
+
* onComplete={(spec) => writeSpec(spec)}
|
|
50
|
+
* onCancel={() => process.exit(0)}
|
|
51
|
+
* />
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function InterviewScreen({ featureName, projectRoot, provider, model, scanResult, onComplete, onCancel, }: InterviewScreenProps): React.ReactElement;
|
|
55
|
+
//# sourceMappingURL=InterviewScreen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InterviewScreen.d.ts","sourceRoot":"","sources":["../../../src/tui/screens/InterviewScreen.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAiC,MAAM,OAAO,CAAC;AAEtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAWzD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,QAAQ,EAAE,UAAU,CAAC;IACrB,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,8CAA8C;IAC9C,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,6CAA6C;IAC7C,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,WAAW,EACX,QAAQ,EACR,KAAK,EACL,UAAU,EACV,UAAU,EACV,QAAQ,GACT,EAAE,oBAAoB,GAAG,KAAK,CAAC,YAAY,CAyF3C"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* InterviewScreen - Main screen for the /new command interview flow
|
|
4
|
+
*
|
|
5
|
+
* The complete TUI for the spec generation interview process.
|
|
6
|
+
* Orchestrates user input and AI responses through multiple phases:
|
|
7
|
+
* 1. Context - Gather reference URLs/files
|
|
8
|
+
* 2. Goals - Understand what to build
|
|
9
|
+
* 3. Interview - Clarifying questions
|
|
10
|
+
* 4. Generation - Generate the specification
|
|
11
|
+
*/
|
|
12
|
+
import { useEffect, useCallback } from 'react';
|
|
13
|
+
import { Box, useInput } from 'ink';
|
|
14
|
+
import { PhaseHeader } from '../components/PhaseHeader.js';
|
|
15
|
+
import { MessageList } from '../components/MessageList.js';
|
|
16
|
+
import { WorkingIndicator } from '../components/WorkingIndicator.js';
|
|
17
|
+
import { ChatInput } from '../components/ChatInput.js';
|
|
18
|
+
import { useSpecGenerator, PHASE_CONFIGS, TOTAL_DISPLAY_PHASES, } from '../hooks/useSpecGenerator.js';
|
|
19
|
+
/**
|
|
20
|
+
* InterviewScreen component
|
|
21
|
+
*
|
|
22
|
+
* The main screen for the /new command interview flow. Combines all TUI
|
|
23
|
+
* components (PhaseHeader, MessageList, WorkingIndicator, ChatInput) to
|
|
24
|
+
* create the complete interview experience.
|
|
25
|
+
*
|
|
26
|
+
* Uses the useSpecGenerator hook to manage state and actions.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* <InterviewScreen
|
|
31
|
+
* featureName="user-auth"
|
|
32
|
+
* projectRoot="/path/to/project"
|
|
33
|
+
* provider="anthropic"
|
|
34
|
+
* model="claude-sonnet-4-5-20250514"
|
|
35
|
+
* onComplete={(spec) => writeSpec(spec)}
|
|
36
|
+
* onCancel={() => process.exit(0)}
|
|
37
|
+
* />
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function InterviewScreen({ featureName, projectRoot, provider, model, scanResult, onComplete, onCancel, }) {
|
|
41
|
+
const { state, submitAnswer, initialize, } = useSpecGenerator();
|
|
42
|
+
// Initialize the generator when the component mounts
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
initialize({
|
|
45
|
+
featureName,
|
|
46
|
+
projectRoot,
|
|
47
|
+
provider,
|
|
48
|
+
model,
|
|
49
|
+
});
|
|
50
|
+
}, [featureName, projectRoot, provider, model, initialize]);
|
|
51
|
+
// Call onComplete when spec is generated
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (state.generatedSpec) {
|
|
54
|
+
onComplete(state.generatedSpec);
|
|
55
|
+
}
|
|
56
|
+
}, [state.generatedSpec, onComplete]);
|
|
57
|
+
// Handle user input submission
|
|
58
|
+
const handleSubmit = useCallback(async (value) => {
|
|
59
|
+
await submitAnswer(value);
|
|
60
|
+
}, [submitAnswer]);
|
|
61
|
+
// Handle keyboard input for Escape key
|
|
62
|
+
useInput((input, key) => {
|
|
63
|
+
if (key.escape) {
|
|
64
|
+
onCancel();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
// Get current phase configuration
|
|
68
|
+
const phaseConfig = PHASE_CONFIGS[state.phase];
|
|
69
|
+
// Determine if input should be disabled
|
|
70
|
+
// Input is enabled when awaiting input and not working
|
|
71
|
+
const inputDisabled = !state.awaitingInput || state.isWorking;
|
|
72
|
+
// Build the working indicator state
|
|
73
|
+
const workingState = {
|
|
74
|
+
isWorking: state.isWorking,
|
|
75
|
+
status: state.workingStatus,
|
|
76
|
+
hint: 'esc to cancel',
|
|
77
|
+
};
|
|
78
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(PhaseHeader, { currentPhase: phaseConfig.number, totalPhases: TOTAL_DISPLAY_PHASES, phaseName: phaseConfig.name }), _jsx(Box, { marginY: 1, children: _jsx(MessageList, { messages: state.messages }) }), _jsx(Box, { marginY: 1, children: _jsx(WorkingIndicator, { state: workingState }) }), _jsx(Box, { marginTop: 1, children: _jsx(ChatInput, { onSubmit: handleSubmit, disabled: inputDisabled, allowEmpty: state.phase === 'context', placeholder: state.phase === 'context'
|
|
79
|
+
? 'Enter URL or file path, or press Enter to continue...'
|
|
80
|
+
: state.phase === 'goals'
|
|
81
|
+
? 'Describe what you want to build...'
|
|
82
|
+
: 'Type your response...' }) })] }));
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=InterviewScreen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InterviewScreen.js","sourceRoot":"","sources":["../../../src/tui/screens/InterviewScreen.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tui/screens/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tui/screens/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme configuration for Ink-based Terminal UI
|
|
3
|
+
*
|
|
4
|
+
* Adapts the Simpson color palette for Ink's styling system.
|
|
5
|
+
* Import from './theme.js' to use these constants in Ink components.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Simpson color palette - hex values for Ink components
|
|
9
|
+
*
|
|
10
|
+
* Usage in Ink:
|
|
11
|
+
* <Text color={colors.yellow}>Yellow text</Text>
|
|
12
|
+
* <Box borderColor={colors.brown}>...</Box>
|
|
13
|
+
*/
|
|
14
|
+
export declare const colors: {
|
|
15
|
+
/** Marge Simpson hair / sky blue */
|
|
16
|
+
readonly blue: "#2f64d6";
|
|
17
|
+
/** Simpson family skin tone - primary accent */
|
|
18
|
+
readonly yellow: "#f8db27";
|
|
19
|
+
/** Homer's hair / wood tones - secondary/muted */
|
|
20
|
+
readonly brown: "#9c5b01";
|
|
21
|
+
/** Highlights and emphasis */
|
|
22
|
+
readonly white: "#ffffff";
|
|
23
|
+
/** Danger/warnings/errors */
|
|
24
|
+
readonly pink: "#ff81c1";
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Box drawing characters for custom borders
|
|
28
|
+
*/
|
|
29
|
+
export declare const box: {
|
|
30
|
+
readonly topLeft: "┌";
|
|
31
|
+
readonly topRight: "┐";
|
|
32
|
+
readonly bottomLeft: "└";
|
|
33
|
+
readonly bottomRight: "┘";
|
|
34
|
+
readonly horizontal: "─";
|
|
35
|
+
readonly vertical: "│";
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Phase/progress indicator characters
|
|
39
|
+
*/
|
|
40
|
+
export declare const phase: {
|
|
41
|
+
readonly pending: "○";
|
|
42
|
+
readonly active: "◐";
|
|
43
|
+
readonly complete: "✓";
|
|
44
|
+
readonly error: "✗";
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Phase status type for type-safe status handling
|
|
48
|
+
*/
|
|
49
|
+
export type PhaseStatus = keyof typeof phase;
|
|
50
|
+
/**
|
|
51
|
+
* Color name type for type-safe color selection
|
|
52
|
+
*/
|
|
53
|
+
export type ColorName = keyof typeof colors;
|
|
54
|
+
/**
|
|
55
|
+
* Get color hex value by name
|
|
56
|
+
*/
|
|
57
|
+
export declare function getColor(name: ColorName): string;
|
|
58
|
+
/**
|
|
59
|
+
* Get phase character by status
|
|
60
|
+
*/
|
|
61
|
+
export declare function getPhaseChar(status: PhaseStatus): string;
|
|
62
|
+
//# sourceMappingURL=theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/tui/theme.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,MAAM;IACjB,oCAAoC;;IAEpC,gDAAgD;;IAEhD,kDAAkD;;IAElD,8BAA8B;;IAE9B,6BAA6B;;CAErB,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,GAAG;;;;;;;CAON,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,KAAK;;;;;CAKR,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,KAAK,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,MAAM,CAAC;AAE5C;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAEhD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAExD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme configuration for Ink-based Terminal UI
|
|
3
|
+
*
|
|
4
|
+
* Adapts the Simpson color palette for Ink's styling system.
|
|
5
|
+
* Import from './theme.js' to use these constants in Ink components.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Simpson color palette - hex values for Ink components
|
|
9
|
+
*
|
|
10
|
+
* Usage in Ink:
|
|
11
|
+
* <Text color={colors.yellow}>Yellow text</Text>
|
|
12
|
+
* <Box borderColor={colors.brown}>...</Box>
|
|
13
|
+
*/
|
|
14
|
+
export const colors = {
|
|
15
|
+
/** Marge Simpson hair / sky blue */
|
|
16
|
+
blue: '#2f64d6',
|
|
17
|
+
/** Simpson family skin tone - primary accent */
|
|
18
|
+
yellow: '#f8db27',
|
|
19
|
+
/** Homer's hair / wood tones - secondary/muted */
|
|
20
|
+
brown: '#9c5b01',
|
|
21
|
+
/** Highlights and emphasis */
|
|
22
|
+
white: '#ffffff',
|
|
23
|
+
/** Danger/warnings/errors */
|
|
24
|
+
pink: '#ff81c1',
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Box drawing characters for custom borders
|
|
28
|
+
*/
|
|
29
|
+
export const box = {
|
|
30
|
+
topLeft: '\u250c',
|
|
31
|
+
topRight: '\u2510',
|
|
32
|
+
bottomLeft: '\u2514',
|
|
33
|
+
bottomRight: '\u2518',
|
|
34
|
+
horizontal: '\u2500',
|
|
35
|
+
vertical: '\u2502',
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Phase/progress indicator characters
|
|
39
|
+
*/
|
|
40
|
+
export const phase = {
|
|
41
|
+
pending: '\u25cb', // ○
|
|
42
|
+
active: '\u25d0', // ◐
|
|
43
|
+
complete: '\u2713', // ✓
|
|
44
|
+
error: '\u2717', // ✗
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Get color hex value by name
|
|
48
|
+
*/
|
|
49
|
+
export function getColor(name) {
|
|
50
|
+
return colors[name];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get phase character by status
|
|
54
|
+
*/
|
|
55
|
+
export function getPhaseChar(status) {
|
|
56
|
+
return phase[status];
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/tui/theme.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,oCAAoC;IACpC,IAAI,EAAE,SAAS;IACf,gDAAgD;IAChD,MAAM,EAAE,SAAS;IACjB,kDAAkD;IAClD,KAAK,EAAE,SAAS;IAChB,8BAA8B;IAC9B,KAAK,EAAE,SAAS;IAChB,6BAA6B;IAC7B,IAAI,EAAE,SAAS;CACP,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,QAAQ;IAClB,UAAU,EAAE,QAAQ;IACpB,WAAW,EAAE,QAAQ;IACrB,UAAU,EAAE,QAAQ;IACpB,QAAQ,EAAE,QAAQ;CACV,CAAC;AAEX;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,OAAO,EAAE,QAAQ,EAAE,IAAI;IACvB,MAAM,EAAE,QAAQ,EAAE,IAAI;IACtB,QAAQ,EAAE,QAAQ,EAAE,IAAI;IACxB,KAAK,EAAE,QAAQ,EAAE,IAAI;CACb,CAAC;AAYX;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAe;IACtC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAAmB;IAC9C,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC;AACvB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiggum-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "AI-powered feature development loop CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,11 +37,16 @@
|
|
|
37
37
|
"braintrust": "^2.0.2",
|
|
38
38
|
"cfonts": "^3.2.0",
|
|
39
39
|
"commander": "^12.1.0",
|
|
40
|
+
"ink": "^5.2.1",
|
|
41
|
+
"ink-spinner": "^5.0.0",
|
|
42
|
+
"ink-text-input": "^6.0.0",
|
|
40
43
|
"picocolors": "^1.0.0",
|
|
44
|
+
"react": "^18.3.1",
|
|
41
45
|
"zod": "^4.3.5"
|
|
42
46
|
},
|
|
43
47
|
"devDependencies": {
|
|
44
48
|
"@types/node": "^20.10.0",
|
|
49
|
+
"@types/react": "^19.2.9",
|
|
45
50
|
"typescript": "^5.3.0",
|
|
46
51
|
"vitest": "^4.0.17"
|
|
47
52
|
},
|
package/src/commands/init.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
fileTree,
|
|
29
29
|
nextStepsBox,
|
|
30
30
|
} from '../utils/colors.js';
|
|
31
|
-
import { flushTracing } from '../utils/tracing.js';
|
|
31
|
+
import { flushTracing, traced, initTracing } from '../utils/tracing.js';
|
|
32
32
|
import { createShimmerSpinner, type ShimmerSpinner } from '../utils/spinner.js';
|
|
33
33
|
import { startRepl, createSessionState } from '../repl/index.js';
|
|
34
34
|
import { loadConfigWithDefaults } from '../utils/config.js';
|
|
@@ -288,8 +288,19 @@ export async function runInitWorkflow(
|
|
|
288
288
|
|
|
289
289
|
let enhancedResult: EnhancedScanResult;
|
|
290
290
|
|
|
291
|
+
// Initialize tracing before creating parent span
|
|
292
|
+
initTracing();
|
|
293
|
+
|
|
291
294
|
try {
|
|
292
|
-
enhancedResult = await
|
|
295
|
+
enhancedResult = await traced(
|
|
296
|
+
async () => {
|
|
297
|
+
return await aiEnhancer.enhance(scanResult);
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: 'ai-analysis',
|
|
301
|
+
type: 'task',
|
|
302
|
+
}
|
|
303
|
+
);
|
|
293
304
|
|
|
294
305
|
if (enhancedResult.aiEnhanced && enhancedResult.aiAnalysis) {
|
|
295
306
|
// Set token usage on spinner before stopping
|
package/src/commands/new.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { loadConfigWithDefaults, hasConfig } from '../utils/config.js';
|
|
|
12
12
|
import { getAvailableProvider, type AIProvider, AVAILABLE_MODELS } from '../ai/providers.js';
|
|
13
13
|
import { SpecGenerator } from '../ai/conversation/index.js';
|
|
14
14
|
import { Scanner, type ScanResult } from '../scanner/index.js';
|
|
15
|
-
import { flushTracing } from '../utils/tracing.js';
|
|
15
|
+
import { flushTracing, traced, initTracing } from '../utils/tracing.js';
|
|
16
16
|
import pc from 'picocolors';
|
|
17
17
|
import * as prompts from '@clack/prompts';
|
|
18
18
|
|
|
@@ -281,9 +281,21 @@ export async function newCommand(feature: string, options: NewOptions = {}): Pro
|
|
|
281
281
|
scanResult,
|
|
282
282
|
});
|
|
283
283
|
|
|
284
|
-
|
|
284
|
+
// Initialize tracing BEFORE creating parent span (ensures logger is ready)
|
|
285
|
+
initTracing();
|
|
286
|
+
|
|
287
|
+
const generatedSpec = await traced(
|
|
288
|
+
async () => {
|
|
289
|
+
// All AI calls inside run() automatically become child spans
|
|
290
|
+
return await specGenerator.run();
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: `generate-spec-${feature}`,
|
|
294
|
+
type: 'task',
|
|
295
|
+
}
|
|
296
|
+
);
|
|
285
297
|
|
|
286
|
-
// Flush any pending Braintrust tracing spans
|
|
298
|
+
// Flush any pending Braintrust tracing spans
|
|
287
299
|
await flushTracing();
|
|
288
300
|
|
|
289
301
|
if (!generatedSpec) {
|
package/src/tui/app.tsx
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Ink Application Entry Point
|
|
3
|
+
*
|
|
4
|
+
* The root component for the Ink-based TUI. Routes to different screens
|
|
5
|
+
* based on the mode/screen prop. Currently supports the interview screen
|
|
6
|
+
* for the /new command, with room to add more screens (init, main shell,
|
|
7
|
+
* monitor) as needed.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { render, type Instance } from 'ink';
|
|
12
|
+
import type { AIProvider } from '../ai/providers.js';
|
|
13
|
+
import type { ScanResult } from '../scanner/types.js';
|
|
14
|
+
import { InterviewScreen } from './screens/InterviewScreen.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Props for the interview screen
|
|
18
|
+
*/
|
|
19
|
+
export interface InterviewAppProps {
|
|
20
|
+
/** Name of the feature being specified */
|
|
21
|
+
featureName: string;
|
|
22
|
+
/** Project root directory path */
|
|
23
|
+
projectRoot: string;
|
|
24
|
+
/** AI provider to use */
|
|
25
|
+
provider: AIProvider;
|
|
26
|
+
/** Model ID to use */
|
|
27
|
+
model: string;
|
|
28
|
+
/** Optional scan result with detected tech stack */
|
|
29
|
+
scanResult?: ScanResult;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Available screen types for the App component
|
|
34
|
+
* Start with just 'interview', add more screens later as needed:
|
|
35
|
+
* - 'init' - Project initialization wizard
|
|
36
|
+
* - 'shell' - Main interactive shell
|
|
37
|
+
* - 'monitor' - Agent monitoring dashboard
|
|
38
|
+
*/
|
|
39
|
+
export type AppScreen = 'interview';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Props for the main App component
|
|
43
|
+
*/
|
|
44
|
+
export interface AppProps {
|
|
45
|
+
/** Screen to display */
|
|
46
|
+
screen: AppScreen;
|
|
47
|
+
/** Props for the interview screen (required when screen is 'interview') */
|
|
48
|
+
interviewProps?: InterviewAppProps;
|
|
49
|
+
/** Called when the screen completes successfully */
|
|
50
|
+
onComplete?: (result: string) => void;
|
|
51
|
+
/** Called when the user exits/cancels */
|
|
52
|
+
onExit?: () => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Main App component for the Ink-based TUI
|
|
57
|
+
*
|
|
58
|
+
* Routes to different screens based on the `screen` prop. Currently
|
|
59
|
+
* only supports the interview screen for spec generation. The component
|
|
60
|
+
* structure allows easy addition of new screens in the future.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* // Render the interview screen
|
|
65
|
+
* renderApp({
|
|
66
|
+
* screen: 'interview',
|
|
67
|
+
* interviewProps: {
|
|
68
|
+
* featureName: 'user-auth',
|
|
69
|
+
* projectRoot: '/path/to/project',
|
|
70
|
+
* provider: 'anthropic',
|
|
71
|
+
* model: 'claude-sonnet-4-5-20250514',
|
|
72
|
+
* },
|
|
73
|
+
* onComplete: (spec) => {
|
|
74
|
+
* fs.writeFileSync('spec.md', spec);
|
|
75
|
+
* },
|
|
76
|
+
* onExit: () => {
|
|
77
|
+
* process.exit(0);
|
|
78
|
+
* },
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export function App({
|
|
83
|
+
screen,
|
|
84
|
+
interviewProps,
|
|
85
|
+
onComplete,
|
|
86
|
+
onExit,
|
|
87
|
+
}: AppProps): React.ReactElement | null {
|
|
88
|
+
// Route to the appropriate screen based on the screen prop
|
|
89
|
+
if (screen === 'interview' && interviewProps) {
|
|
90
|
+
return (
|
|
91
|
+
<InterviewScreen
|
|
92
|
+
featureName={interviewProps.featureName}
|
|
93
|
+
projectRoot={interviewProps.projectRoot}
|
|
94
|
+
provider={interviewProps.provider}
|
|
95
|
+
model={interviewProps.model}
|
|
96
|
+
scanResult={interviewProps.scanResult}
|
|
97
|
+
onComplete={(spec) => {
|
|
98
|
+
onComplete?.(spec);
|
|
99
|
+
}}
|
|
100
|
+
onCancel={() => {
|
|
101
|
+
onExit?.();
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Future screens would be added here:
|
|
108
|
+
// if (screen === 'init' && initProps) { ... }
|
|
109
|
+
// if (screen === 'shell' && shellProps) { ... }
|
|
110
|
+
// if (screen === 'monitor' && monitorProps) { ... }
|
|
111
|
+
|
|
112
|
+
// Fallback - shouldn't happen in normal usage
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Render the App component to the terminal
|
|
118
|
+
*
|
|
119
|
+
* Helper function that wraps Ink's render() to provide a clean API
|
|
120
|
+
* for starting the TUI from command handlers.
|
|
121
|
+
*
|
|
122
|
+
* @param props - App component props
|
|
123
|
+
* @returns Ink Instance that can be used to control/cleanup the render
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* // In a command handler
|
|
128
|
+
* const instance = renderApp({
|
|
129
|
+
* screen: 'interview',
|
|
130
|
+
* interviewProps: { ... },
|
|
131
|
+
* onComplete: (spec) => { ... },
|
|
132
|
+
* onExit: () => instance.unmount(),
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function renderApp(props: AppProps): Instance {
|
|
137
|
+
return render(<App {...props} />);
|
|
138
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatInput - Multi-line input with history for chat interactions
|
|
3
|
+
*
|
|
4
|
+
* Displays a prompt character followed by a text input.
|
|
5
|
+
* Handles submission on Enter and clears input after submit.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useState } from 'react';
|
|
9
|
+
import { Box, Text } from 'ink';
|
|
10
|
+
import TextInput from 'ink-text-input';
|
|
11
|
+
import { colors } from '../theme.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Props for the ChatInput component
|
|
15
|
+
*/
|
|
16
|
+
export interface ChatInputProps {
|
|
17
|
+
/** Called when user presses Enter with the current input value */
|
|
18
|
+
onSubmit: (value: string) => void;
|
|
19
|
+
/** Placeholder text when empty */
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
/** Whether input is disabled (e.g., during AI processing) */
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
/** Prompt character/text shown before input (default "> ") */
|
|
24
|
+
prompt?: string;
|
|
25
|
+
/** Allow empty submissions (e.g., to continue/skip phases) */
|
|
26
|
+
allowEmpty?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* ChatInput component
|
|
31
|
+
*
|
|
32
|
+
* Provides a text input with a prompt character for chat-style interactions.
|
|
33
|
+
* Clears input after submission. Shows dimmed appearance when disabled.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* <ChatInput
|
|
38
|
+
* onSubmit={(value) => console.log('User said:', value)}
|
|
39
|
+
* placeholder="Type your response..."
|
|
40
|
+
* disabled={isProcessing}
|
|
41
|
+
* />
|
|
42
|
+
* // Renders: > Type your response...
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function ChatInput({
|
|
46
|
+
onSubmit,
|
|
47
|
+
placeholder = 'Type your message...',
|
|
48
|
+
disabled = false,
|
|
49
|
+
prompt = '> ',
|
|
50
|
+
allowEmpty = false,
|
|
51
|
+
}: ChatInputProps): React.ReactElement {
|
|
52
|
+
const [value, setValue] = useState('');
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Handle input submission
|
|
56
|
+
* Calls onSubmit with current value and clears the input
|
|
57
|
+
*/
|
|
58
|
+
const handleSubmit = (submittedValue: string): void => {
|
|
59
|
+
// Don't submit when disabled
|
|
60
|
+
if (disabled) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Don't submit empty values unless allowEmpty is true
|
|
65
|
+
if (!submittedValue.trim() && !allowEmpty) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onSubmit(submittedValue);
|
|
70
|
+
setValue('');
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Handle value changes
|
|
75
|
+
* Only update if not disabled
|
|
76
|
+
*/
|
|
77
|
+
const handleChange = (newValue: string): void => {
|
|
78
|
+
if (!disabled) {
|
|
79
|
+
setValue(newValue);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// When disabled, show a waiting message
|
|
84
|
+
if (disabled) {
|
|
85
|
+
return (
|
|
86
|
+
<Box flexDirection="row">
|
|
87
|
+
<Text dimColor color={colors.brown}>
|
|
88
|
+
{prompt}[waiting for AI...]
|
|
89
|
+
</Text>
|
|
90
|
+
</Box>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Box flexDirection="row">
|
|
96
|
+
<Text color={colors.yellow}>{prompt}</Text>
|
|
97
|
+
<TextInput
|
|
98
|
+
value={value}
|
|
99
|
+
onChange={handleChange}
|
|
100
|
+
onSubmit={handleSubmit}
|
|
101
|
+
placeholder={placeholder}
|
|
102
|
+
/>
|
|
103
|
+
</Box>
|
|
104
|
+
);
|
|
105
|
+
}
|