gencode-ai 0.1.2 → 0.1.3
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/CLAUDE.md +86 -0
- package/README.md +14 -17
- package/dist/agent/agent.d.ts +8 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +14 -1
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/types.d.ts +14 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +38 -3
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +17 -0
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/components/QuestionPrompt.d.ts +23 -0
- package/dist/cli/components/QuestionPrompt.d.ts.map +1 -0
- package/dist/cli/components/QuestionPrompt.js +231 -0
- package/dist/cli/components/QuestionPrompt.js.map +1 -0
- package/dist/cli/components/index.d.ts +1 -0
- package/dist/cli/components/index.d.ts.map +1 -1
- package/dist/cli/components/index.js +1 -0
- package/dist/cli/components/index.js.map +1 -1
- package/dist/cli/components/theme.d.ts +7 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +11 -1
- package/dist/cli/components/theme.js.map +1 -1
- package/dist/permissions/types.d.ts.map +1 -1
- package/dist/permissions/types.js +2 -0
- package/dist/permissions/types.js.map +1 -1
- package/dist/tools/builtin/ask-user.d.ts +64 -0
- package/dist/tools/builtin/ask-user.d.ts.map +1 -0
- package/dist/tools/builtin/ask-user.js +148 -0
- package/dist/tools/builtin/ask-user.js.map +1 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/types.d.ts +17 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/docs/proposals/0012-ask-user-question.md +66 -1
- package/docs/proposals/README.md +1 -1
- package/examples/test-ask-user.ts +167 -0
- package/package.json +1 -1
- package/src/agent/agent.ts +20 -1
- package/src/agent/index.ts +1 -0
- package/src/agent/types.ts +13 -1
- package/src/cli/components/App.tsx +55 -3
- package/src/cli/components/Messages.tsx +43 -0
- package/src/cli/components/QuestionPrompt.tsx +462 -0
- package/src/cli/components/index.ts +1 -0
- package/src/cli/components/theme.ts +11 -1
- package/src/permissions/types.ts +2 -0
- package/src/prompts/system/base.txt +42 -0
- package/src/prompts/tools/ask-user.txt +110 -0
- package/src/tools/builtin/ask-user.ts +185 -0
- package/src/tools/index.ts +15 -0
- package/src/tools/types.ts +18 -0
|
@@ -28,8 +28,10 @@ import {
|
|
|
28
28
|
PermissionAuditDisplay,
|
|
29
29
|
} from './PermissionPrompt.js';
|
|
30
30
|
import { TodoList } from './TodoList.js';
|
|
31
|
+
import { QuestionPrompt, AnswerDisplay } from './QuestionPrompt.js';
|
|
31
32
|
import { colors, icons } from './theme.js';
|
|
32
|
-
import { getTodos } from '../../tools/index.js';
|
|
33
|
+
import { getTodos, formatAnswersForDisplay } from '../../tools/index.js';
|
|
34
|
+
import type { Question, QuestionAnswer } from '../../tools/types.js';
|
|
33
35
|
import type { ProviderName } from '../../providers/index.js';
|
|
34
36
|
import type { ApprovalAction, ApprovalSuggestion } from '../../permissions/types.js';
|
|
35
37
|
import { gatherContextFiles, buildInitPrompt, getContextSummary } from '../../memory/index.js';
|
|
@@ -49,6 +51,11 @@ interface ConfirmState {
|
|
|
49
51
|
resolve: (action: ApprovalAction) => void;
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
interface QuestionState {
|
|
55
|
+
questions: Question[];
|
|
56
|
+
resolve: (answers: QuestionAnswer[]) => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
52
59
|
interface SettingsManager {
|
|
53
60
|
save: (settings: { model?: string }) => Promise<void>;
|
|
54
61
|
getCwd?: () => string;
|
|
@@ -235,6 +242,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
235
242
|
const [processingStartTime, setProcessingStartTime] = useState<number | undefined>(undefined);
|
|
236
243
|
const [tokenCount, setTokenCount] = useState(0);
|
|
237
244
|
const [confirmState, setConfirmState] = useState<ConfirmState | null>(null);
|
|
245
|
+
const [questionState, setQuestionState] = useState<QuestionState | null>(null);
|
|
238
246
|
const [showModelSelector, setShowModelSelector] = useState(false);
|
|
239
247
|
const [showProviderManager, setShowProviderManager] = useState(false);
|
|
240
248
|
const [currentModel, setCurrentModel] = useState(config.model);
|
|
@@ -276,6 +284,13 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
276
284
|
});
|
|
277
285
|
});
|
|
278
286
|
|
|
287
|
+
// Set askUser callback for AskUserQuestion tool
|
|
288
|
+
agent.setAskUserCallback(async (questions) => {
|
|
289
|
+
return new Promise<QuestionAnswer[]>((resolve) => {
|
|
290
|
+
setQuestionState({ questions, resolve });
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
279
294
|
// Set callback to save permission rules to settings.local.json
|
|
280
295
|
if (settingsManager?.addPermissionRule) {
|
|
281
296
|
agent.setSaveRuleCallback(async (tool, pattern) => {
|
|
@@ -295,6 +310,32 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
295
310
|
init();
|
|
296
311
|
}, [agent, resumeLatest, addHistory, permissionSettings, settingsManager]);
|
|
297
312
|
|
|
313
|
+
// Handle question answers (AskUserQuestion)
|
|
314
|
+
const handleQuestionComplete = useCallback((answers: QuestionAnswer[]) => {
|
|
315
|
+
if (questionState) {
|
|
316
|
+
// Show confirmation in history
|
|
317
|
+
addHistory({
|
|
318
|
+
type: 'info',
|
|
319
|
+
content: formatAnswersForDisplay(answers),
|
|
320
|
+
});
|
|
321
|
+
questionState.resolve(answers);
|
|
322
|
+
setQuestionState(null);
|
|
323
|
+
}
|
|
324
|
+
}, [questionState, addHistory]);
|
|
325
|
+
|
|
326
|
+
// Handle question cancel
|
|
327
|
+
const handleQuestionCancel = useCallback(() => {
|
|
328
|
+
if (questionState) {
|
|
329
|
+
// Clear pending tool display (no more spinner)
|
|
330
|
+
pendingToolRef.current = null;
|
|
331
|
+
setPendingTool(null);
|
|
332
|
+
// Add canceled message to history
|
|
333
|
+
addHistory({ type: 'info', content: 'Question canceled' });
|
|
334
|
+
questionState.resolve([]); // Return empty answers on cancel
|
|
335
|
+
setQuestionState(null);
|
|
336
|
+
}
|
|
337
|
+
}, [questionState, addHistory]);
|
|
338
|
+
|
|
298
339
|
// Handle permission decision
|
|
299
340
|
const handlePermissionDecision = (action: ApprovalAction) => {
|
|
300
341
|
if (confirmState) {
|
|
@@ -730,6 +771,9 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
730
771
|
setIsProcessing(false);
|
|
731
772
|
setStreamingText('');
|
|
732
773
|
streamingTextRef.current = '';
|
|
774
|
+
// Clear pending tool (stop spinner)
|
|
775
|
+
pendingToolRef.current = null;
|
|
776
|
+
setPendingTool(null);
|
|
733
777
|
addHistory({ type: 'info', content: 'Interrupted' });
|
|
734
778
|
}
|
|
735
779
|
|
|
@@ -837,6 +881,14 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
837
881
|
/>
|
|
838
882
|
)}
|
|
839
883
|
|
|
884
|
+
{questionState && (
|
|
885
|
+
<QuestionPrompt
|
|
886
|
+
questions={questionState.questions}
|
|
887
|
+
onComplete={handleQuestionComplete}
|
|
888
|
+
onCancel={handleQuestionCancel}
|
|
889
|
+
/>
|
|
890
|
+
)}
|
|
891
|
+
|
|
840
892
|
{showModelSelector && (
|
|
841
893
|
<Box marginTop={1}>
|
|
842
894
|
<ModelSelector
|
|
@@ -861,7 +913,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
861
913
|
</Box>
|
|
862
914
|
)}
|
|
863
915
|
|
|
864
|
-
{!confirmState && !showModelSelector && !showProviderManager && (
|
|
916
|
+
{!confirmState && !questionState && !showModelSelector && !showProviderManager && (
|
|
865
917
|
<Box flexDirection="column" marginTop={1}>
|
|
866
918
|
<PromptInput
|
|
867
919
|
key={inputKey}
|
|
@@ -875,7 +927,7 @@ export function App({ config, settingsManager, resumeLatest, permissionSettings
|
|
|
875
927
|
</Box>
|
|
876
928
|
)}
|
|
877
929
|
|
|
878
|
-
{isProcessing && !confirmState ? (
|
|
930
|
+
{isProcessing && !confirmState && !questionState ? (
|
|
879
931
|
<ProgressBar startTime={processingStartTime} tokenCount={tokenCount} isThinking={isThinking} />
|
|
880
932
|
) : showCmdSuggestions && cmdSuggestions.length > 0 ? (
|
|
881
933
|
<Box marginTop={1}>
|
|
@@ -126,6 +126,11 @@ function formatToolInput(name: string, input: Record<string, unknown>): string {
|
|
|
126
126
|
const todos = input.todos as Array<{ content: string; status: string }> || [];
|
|
127
127
|
return `${todos.length} task${todos.length !== 1 ? 's' : ''}`;
|
|
128
128
|
}
|
|
129
|
+
case 'AskUserQuestion': {
|
|
130
|
+
// Show collapsed JSON preview
|
|
131
|
+
const json = JSON.stringify(input);
|
|
132
|
+
return truncate(json, 60);
|
|
133
|
+
}
|
|
129
134
|
default:
|
|
130
135
|
return truncate(JSON.stringify(input), 40);
|
|
131
136
|
}
|
|
@@ -135,6 +140,25 @@ export function ToolCall({ name, input }: ToolCallProps) {
|
|
|
135
140
|
// Hide TodoWrite (shown in TodoList component)
|
|
136
141
|
if (name === 'TodoWrite') return null;
|
|
137
142
|
|
|
143
|
+
// Special display for AskUserQuestion (Claude Code style with expand hint)
|
|
144
|
+
if (name === 'AskUserQuestion') {
|
|
145
|
+
const json = JSON.stringify(input);
|
|
146
|
+
const displayJson = truncate(json, 70);
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<Box marginTop={1} flexDirection="column">
|
|
150
|
+
<Box>
|
|
151
|
+
<Text color={colors.tool}>{icons.tool}</Text>
|
|
152
|
+
<Text> </Text>
|
|
153
|
+
<Text bold>{name}</Text>
|
|
154
|
+
<Text color={colors.textMuted}> </Text>
|
|
155
|
+
<Text color={colors.textSecondary}>{displayJson}</Text>
|
|
156
|
+
<Text color={colors.textMuted}> ctrl+o</Text>
|
|
157
|
+
</Box>
|
|
158
|
+
</Box>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
138
162
|
const displayInput = formatToolInput(name, input);
|
|
139
163
|
|
|
140
164
|
return (
|
|
@@ -162,6 +186,25 @@ export function PendingToolCall({ name, input }: PendingToolCallProps) {
|
|
|
162
186
|
// Hide TodoWrite (shown in TodoList component)
|
|
163
187
|
if (name === 'TodoWrite') return null;
|
|
164
188
|
|
|
189
|
+
// Special display for AskUserQuestion
|
|
190
|
+
if (name === 'AskUserQuestion') {
|
|
191
|
+
const json = JSON.stringify(input);
|
|
192
|
+
const displayJson = truncate(json, 70);
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<Box marginTop={1}>
|
|
196
|
+
<Text color={colors.tool}>
|
|
197
|
+
<InkSpinner type="dots" />
|
|
198
|
+
</Text>
|
|
199
|
+
<Text> </Text>
|
|
200
|
+
<Text bold>{name}</Text>
|
|
201
|
+
<Text color={colors.textMuted}> </Text>
|
|
202
|
+
<Text color={colors.textSecondary}>{displayJson}</Text>
|
|
203
|
+
<Text color={colors.textMuted}> ctrl+o</Text>
|
|
204
|
+
</Box>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
165
208
|
const displayInput = formatToolInput(name, input);
|
|
166
209
|
|
|
167
210
|
return (
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QuestionPrompt Component - Claude Code style structured questioning UI
|
|
3
|
+
*
|
|
4
|
+
* Matches Claude Code's AskUserQuestion UI pattern:
|
|
5
|
+
* - Progress bar with step indicators (← □ Step1 □ Step2 ✓ Submit →)
|
|
6
|
+
* - Numbered options with cursor indicator
|
|
7
|
+
* - Description on separate line
|
|
8
|
+
* - Keyboard hints at bottom
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useState, useCallback } from 'react';
|
|
12
|
+
import { Box, Text, useInput } from 'ink';
|
|
13
|
+
import { colors } from './theme.js';
|
|
14
|
+
import type { Question, QuestionAnswer } from '../../tools/types.js';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Types
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
interface QuestionPromptProps {
|
|
21
|
+
questions: Question[];
|
|
22
|
+
onComplete: (answers: QuestionAnswer[]) => void;
|
|
23
|
+
onCancel?: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface OptionWithOther {
|
|
27
|
+
label: string;
|
|
28
|
+
description: string;
|
|
29
|
+
isOther?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Progress Bar Component
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
interface ProgressBarProps {
|
|
37
|
+
questions: Question[];
|
|
38
|
+
currentIndex: number;
|
|
39
|
+
answers: QuestionAnswer[];
|
|
40
|
+
showSubmit?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ProgressBar({ questions, currentIndex, answers, showSubmit = true }: ProgressBarProps) {
|
|
44
|
+
const isReviewMode = currentIndex >= questions.length;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Box>
|
|
48
|
+
<Text color={colors.textMuted}>← </Text>
|
|
49
|
+
{questions.map((q, idx) => {
|
|
50
|
+
const isCompleted = idx < answers.length;
|
|
51
|
+
const isCurrent = idx === currentIndex;
|
|
52
|
+
const checkmark = isCompleted ? '☒' : '☐';
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Box key={idx}>
|
|
56
|
+
{isCurrent ? (
|
|
57
|
+
<Text backgroundColor="#6366F1" color="#FFFFFF" bold>
|
|
58
|
+
{` □ ${q.header} `}
|
|
59
|
+
</Text>
|
|
60
|
+
) : (
|
|
61
|
+
<Text color={isCompleted ? colors.textSecondary : colors.textMuted}>
|
|
62
|
+
{checkmark} {q.header}
|
|
63
|
+
</Text>
|
|
64
|
+
)}
|
|
65
|
+
<Text color={colors.textMuted}> </Text>
|
|
66
|
+
</Box>
|
|
67
|
+
);
|
|
68
|
+
})}
|
|
69
|
+
{showSubmit && (
|
|
70
|
+
<>
|
|
71
|
+
{isReviewMode ? (
|
|
72
|
+
<Text backgroundColor="#22C55E" color="#FFFFFF" bold>
|
|
73
|
+
{` ✓ Submit `}
|
|
74
|
+
</Text>
|
|
75
|
+
) : (
|
|
76
|
+
<Text color={colors.textMuted}>✓ Submit</Text>
|
|
77
|
+
)}
|
|
78
|
+
</>
|
|
79
|
+
)}
|
|
80
|
+
<Text color={colors.textMuted}> →</Text>
|
|
81
|
+
</Box>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// QuestionPrompt Component
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
export function QuestionPrompt({ questions, onComplete, onCancel }: QuestionPromptProps) {
|
|
90
|
+
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
91
|
+
const [answers, setAnswers] = useState<QuestionAnswer[]>([]);
|
|
92
|
+
const [selectedOptions, setSelectedOptions] = useState<Set<string>>(new Set());
|
|
93
|
+
const [optionIndex, setOptionIndex] = useState(0);
|
|
94
|
+
const [showOtherInput, setShowOtherInput] = useState(false);
|
|
95
|
+
const [otherInput, setOtherInput] = useState('');
|
|
96
|
+
const [isReviewMode, setIsReviewMode] = useState(false);
|
|
97
|
+
const [reviewOptionIndex, setReviewOptionIndex] = useState(0);
|
|
98
|
+
|
|
99
|
+
const currentQuestion = questions[currentQuestionIndex];
|
|
100
|
+
const showProgressBar = questions.length > 1;
|
|
101
|
+
|
|
102
|
+
// Add "Type something." option to the list
|
|
103
|
+
const optionsWithOther: OptionWithOther[] = currentQuestion
|
|
104
|
+
? [
|
|
105
|
+
...currentQuestion.options,
|
|
106
|
+
{ label: 'Type something.', description: '', isOther: true },
|
|
107
|
+
]
|
|
108
|
+
: [];
|
|
109
|
+
|
|
110
|
+
// Toggle option selection (for multi-select)
|
|
111
|
+
const toggleOption = useCallback(() => {
|
|
112
|
+
const option = optionsWithOther[optionIndex];
|
|
113
|
+
setSelectedOptions((prev) => {
|
|
114
|
+
const next = new Set(prev);
|
|
115
|
+
if (next.has(option.label)) {
|
|
116
|
+
next.delete(option.label);
|
|
117
|
+
} else {
|
|
118
|
+
next.add(option.label);
|
|
119
|
+
}
|
|
120
|
+
return next;
|
|
121
|
+
});
|
|
122
|
+
}, [optionIndex, optionsWithOther]);
|
|
123
|
+
|
|
124
|
+
// Finish current question and move to next or review
|
|
125
|
+
const finishQuestion = useCallback(
|
|
126
|
+
(selected: string[], customInput?: string) => {
|
|
127
|
+
const answer: QuestionAnswer = {
|
|
128
|
+
question: currentQuestion.question,
|
|
129
|
+
header: currentQuestion.header,
|
|
130
|
+
selectedOptions: selected.filter((s) => s !== 'Type something.'),
|
|
131
|
+
customInput,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const newAnswers = [...answers, answer];
|
|
135
|
+
|
|
136
|
+
if (currentQuestionIndex < questions.length - 1) {
|
|
137
|
+
// Move to next question
|
|
138
|
+
setAnswers(newAnswers);
|
|
139
|
+
setCurrentQuestionIndex((i) => i + 1);
|
|
140
|
+
setSelectedOptions(new Set());
|
|
141
|
+
setOptionIndex(0);
|
|
142
|
+
setShowOtherInput(false);
|
|
143
|
+
setOtherInput('');
|
|
144
|
+
} else {
|
|
145
|
+
// All questions answered, go to review mode (for multi-question)
|
|
146
|
+
setAnswers(newAnswers);
|
|
147
|
+
if (questions.length > 1) {
|
|
148
|
+
setIsReviewMode(true);
|
|
149
|
+
setReviewOptionIndex(0);
|
|
150
|
+
} else {
|
|
151
|
+
// Single question, complete immediately
|
|
152
|
+
onComplete(newAnswers);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
[currentQuestion, currentQuestionIndex, questions.length, answers, onComplete]
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Handle selection (Enter key)
|
|
160
|
+
const handleSelect = useCallback(() => {
|
|
161
|
+
const option = optionsWithOther[optionIndex];
|
|
162
|
+
|
|
163
|
+
if (currentQuestion.multiSelect) {
|
|
164
|
+
if (selectedOptions.size === 0) {
|
|
165
|
+
toggleOption();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (selectedOptions.has('Type something.')) {
|
|
169
|
+
setShowOtherInput(true);
|
|
170
|
+
} else {
|
|
171
|
+
finishQuestion([...selectedOptions]);
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
if (option.isOther) {
|
|
175
|
+
setShowOtherInput(true);
|
|
176
|
+
} else {
|
|
177
|
+
finishQuestion([option.label]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}, [
|
|
181
|
+
optionIndex,
|
|
182
|
+
optionsWithOther,
|
|
183
|
+
currentQuestion?.multiSelect,
|
|
184
|
+
selectedOptions,
|
|
185
|
+
toggleOption,
|
|
186
|
+
finishQuestion,
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
// Handle keyboard input
|
|
190
|
+
useInput((input, key) => {
|
|
191
|
+
// Review mode navigation
|
|
192
|
+
if (isReviewMode) {
|
|
193
|
+
if (key.upArrow) {
|
|
194
|
+
setReviewOptionIndex((i) => Math.max(0, i - 1));
|
|
195
|
+
} else if (key.downArrow) {
|
|
196
|
+
setReviewOptionIndex((i) => Math.min(1, i + 1));
|
|
197
|
+
} else if (key.return) {
|
|
198
|
+
if (reviewOptionIndex === 0) {
|
|
199
|
+
// Submit
|
|
200
|
+
onComplete(answers);
|
|
201
|
+
} else {
|
|
202
|
+
// Cancel
|
|
203
|
+
onCancel?.();
|
|
204
|
+
}
|
|
205
|
+
} else if (key.escape) {
|
|
206
|
+
onCancel?.();
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (showOtherInput) {
|
|
212
|
+
if (key.return) {
|
|
213
|
+
if (otherInput.trim()) {
|
|
214
|
+
if (currentQuestion.multiSelect) {
|
|
215
|
+
const selected = [...selectedOptions].filter((s) => s !== 'Type something.');
|
|
216
|
+
finishQuestion(selected, otherInput.trim());
|
|
217
|
+
} else {
|
|
218
|
+
finishQuestion([], otherInput.trim());
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} else if (key.escape) {
|
|
222
|
+
setShowOtherInput(false);
|
|
223
|
+
setOtherInput('');
|
|
224
|
+
} else if (key.backspace || key.delete) {
|
|
225
|
+
setOtherInput((prev) => prev.slice(0, -1));
|
|
226
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
227
|
+
setOtherInput((prev) => prev + input);
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Navigation
|
|
233
|
+
if (key.upArrow) {
|
|
234
|
+
setOptionIndex((i) => Math.max(0, i - 1));
|
|
235
|
+
} else if (key.downArrow) {
|
|
236
|
+
setOptionIndex((i) => Math.min(optionsWithOther.length - 1, i + 1));
|
|
237
|
+
} else if (key.tab) {
|
|
238
|
+
// Tab cycles through options
|
|
239
|
+
setOptionIndex((i) => (i + 1) % optionsWithOther.length);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Selection
|
|
243
|
+
if (key.return) {
|
|
244
|
+
handleSelect();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Toggle (multi-select only)
|
|
248
|
+
if (input === ' ' && currentQuestion.multiSelect) {
|
|
249
|
+
toggleOption();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Number shortcuts (1-5)
|
|
253
|
+
const num = parseInt(input, 10);
|
|
254
|
+
if (num >= 1 && num <= optionsWithOther.length) {
|
|
255
|
+
setOptionIndex(num - 1);
|
|
256
|
+
if (!currentQuestion.multiSelect) {
|
|
257
|
+
const option = optionsWithOther[num - 1];
|
|
258
|
+
if (option.isOther) {
|
|
259
|
+
setShowOtherInput(true);
|
|
260
|
+
} else {
|
|
261
|
+
finishQuestion([option.label]);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Escape to cancel
|
|
267
|
+
if (key.escape && onCancel) {
|
|
268
|
+
onCancel();
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Review mode UI
|
|
273
|
+
if (isReviewMode) {
|
|
274
|
+
return (
|
|
275
|
+
<Box flexDirection="column" marginTop={1}>
|
|
276
|
+
{/* Progress bar */}
|
|
277
|
+
{showProgressBar && (
|
|
278
|
+
<Box marginBottom={1}>
|
|
279
|
+
<ProgressBar
|
|
280
|
+
questions={questions}
|
|
281
|
+
currentIndex={questions.length}
|
|
282
|
+
answers={answers}
|
|
283
|
+
/>
|
|
284
|
+
</Box>
|
|
285
|
+
)}
|
|
286
|
+
|
|
287
|
+
{/* Review title */}
|
|
288
|
+
<Box marginBottom={1}>
|
|
289
|
+
<Text bold>Review your answers</Text>
|
|
290
|
+
</Box>
|
|
291
|
+
|
|
292
|
+
{/* Answered questions summary */}
|
|
293
|
+
<Box flexDirection="column" marginBottom={1} paddingLeft={1}>
|
|
294
|
+
{answers.map((answer, idx) => {
|
|
295
|
+
const selections = answer.customInput
|
|
296
|
+
? [...answer.selectedOptions, answer.customInput].join(', ')
|
|
297
|
+
: answer.selectedOptions.join(', ');
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<Box key={idx} flexDirection="column" marginBottom={1}>
|
|
301
|
+
<Box>
|
|
302
|
+
<Text color={colors.textSecondary}>● </Text>
|
|
303
|
+
<Text bold>{answer.question}</Text>
|
|
304
|
+
</Box>
|
|
305
|
+
<Box paddingLeft={2}>
|
|
306
|
+
<Text color={colors.primary}>→ {selections}</Text>
|
|
307
|
+
</Box>
|
|
308
|
+
</Box>
|
|
309
|
+
);
|
|
310
|
+
})}
|
|
311
|
+
</Box>
|
|
312
|
+
|
|
313
|
+
{/* Ready to submit */}
|
|
314
|
+
<Box marginBottom={1}>
|
|
315
|
+
<Text>Ready to submit your answers?</Text>
|
|
316
|
+
</Box>
|
|
317
|
+
|
|
318
|
+
{/* Submit/Cancel options */}
|
|
319
|
+
<Box flexDirection="column" paddingLeft={1}>
|
|
320
|
+
<Box>
|
|
321
|
+
<Text color={reviewOptionIndex === 0 ? colors.text : colors.textMuted}>
|
|
322
|
+
{reviewOptionIndex === 0 ? '❯ ' : ' '}
|
|
323
|
+
</Text>
|
|
324
|
+
<Text color={colors.primary} bold={reviewOptionIndex === 0}>
|
|
325
|
+
1. Submit answers
|
|
326
|
+
</Text>
|
|
327
|
+
</Box>
|
|
328
|
+
<Box>
|
|
329
|
+
<Text color={reviewOptionIndex === 1 ? colors.text : colors.textMuted}>
|
|
330
|
+
{reviewOptionIndex === 1 ? '❯ ' : ' '}
|
|
331
|
+
</Text>
|
|
332
|
+
<Text bold={reviewOptionIndex === 1}>2. Cancel</Text>
|
|
333
|
+
</Box>
|
|
334
|
+
</Box>
|
|
335
|
+
|
|
336
|
+
{/* Separator */}
|
|
337
|
+
<Box marginTop={1} marginBottom={1}>
|
|
338
|
+
<Text color={colors.textMuted}>{'─'.repeat(60)}</Text>
|
|
339
|
+
</Box>
|
|
340
|
+
|
|
341
|
+
{/* Keyboard hints */}
|
|
342
|
+
<Box>
|
|
343
|
+
<Text color={colors.textMuted}>
|
|
344
|
+
Enter to select · Tab/Arrow keys to navigate · Esc to cancel
|
|
345
|
+
</Text>
|
|
346
|
+
</Box>
|
|
347
|
+
</Box>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Normal question mode UI
|
|
352
|
+
return (
|
|
353
|
+
<Box flexDirection="column" marginTop={1}>
|
|
354
|
+
{/* Progress bar */}
|
|
355
|
+
{showProgressBar && (
|
|
356
|
+
<Box marginBottom={1}>
|
|
357
|
+
<ProgressBar
|
|
358
|
+
questions={questions}
|
|
359
|
+
currentIndex={currentQuestionIndex}
|
|
360
|
+
answers={answers}
|
|
361
|
+
/>
|
|
362
|
+
</Box>
|
|
363
|
+
)}
|
|
364
|
+
|
|
365
|
+
{/* Question text */}
|
|
366
|
+
<Box marginBottom={1}>
|
|
367
|
+
<Text bold>{currentQuestion.question}</Text>
|
|
368
|
+
</Box>
|
|
369
|
+
|
|
370
|
+
{/* Options list */}
|
|
371
|
+
<Box flexDirection="column" paddingLeft={1}>
|
|
372
|
+
{optionsWithOther.map((option, index) => {
|
|
373
|
+
const isSelected = index === optionIndex;
|
|
374
|
+
const isChecked = selectedOptions.has(option.label);
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
<Box key={option.label} flexDirection="column" marginBottom={option.description ? 1 : 0}>
|
|
378
|
+
{/* Option row */}
|
|
379
|
+
<Box>
|
|
380
|
+
<Text color={isSelected ? colors.text : colors.textMuted}>
|
|
381
|
+
{isSelected ? '❯ ' : ' '}
|
|
382
|
+
</Text>
|
|
383
|
+
<Text color={colors.textMuted}>{index + 1}. </Text>
|
|
384
|
+
{currentQuestion.multiSelect && (
|
|
385
|
+
<Text color={colors.primary}>{isChecked ? '☒ ' : '☐ '}</Text>
|
|
386
|
+
)}
|
|
387
|
+
<Text color={colors.primary} bold>
|
|
388
|
+
{option.label}
|
|
389
|
+
</Text>
|
|
390
|
+
</Box>
|
|
391
|
+
|
|
392
|
+
{/* Description (if exists) */}
|
|
393
|
+
{option.description && (
|
|
394
|
+
<Box paddingLeft={4}>
|
|
395
|
+
<Text color={colors.textMuted}>{option.description}</Text>
|
|
396
|
+
</Box>
|
|
397
|
+
)}
|
|
398
|
+
</Box>
|
|
399
|
+
);
|
|
400
|
+
})}
|
|
401
|
+
</Box>
|
|
402
|
+
|
|
403
|
+
{/* Other input field */}
|
|
404
|
+
{showOtherInput && (
|
|
405
|
+
<Box marginTop={1} paddingLeft={1}>
|
|
406
|
+
<Text color={colors.textMuted}>Type your answer: </Text>
|
|
407
|
+
<Text>{otherInput}</Text>
|
|
408
|
+
<Text color={colors.primary}>▋</Text>
|
|
409
|
+
</Box>
|
|
410
|
+
)}
|
|
411
|
+
|
|
412
|
+
{/* Separator */}
|
|
413
|
+
<Box marginTop={1} marginBottom={1}>
|
|
414
|
+
<Text color={colors.textMuted}>{'─'.repeat(60)}</Text>
|
|
415
|
+
</Box>
|
|
416
|
+
|
|
417
|
+
{/* Chat about this (placeholder) */}
|
|
418
|
+
<Box marginBottom={1}>
|
|
419
|
+
<Text color={colors.textMuted}> Chat about this</Text>
|
|
420
|
+
</Box>
|
|
421
|
+
|
|
422
|
+
{/* Keyboard hints */}
|
|
423
|
+
<Box>
|
|
424
|
+
<Text color={colors.textMuted}>
|
|
425
|
+
Enter to select · Tab/Arrow keys to navigate
|
|
426
|
+
{currentQuestion.multiSelect && ' · Space to toggle'}
|
|
427
|
+
{onCancel && ' · Esc to cancel'}
|
|
428
|
+
</Text>
|
|
429
|
+
</Box>
|
|
430
|
+
</Box>
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ============================================================================
|
|
435
|
+
// Answer Display Component (shown after completion)
|
|
436
|
+
// ============================================================================
|
|
437
|
+
|
|
438
|
+
interface AnswerDisplayProps {
|
|
439
|
+
answers: QuestionAnswer[];
|
|
440
|
+
compact?: boolean;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function AnswerDisplay({ answers, compact = true }: AnswerDisplayProps) {
|
|
444
|
+
return (
|
|
445
|
+
<Box flexDirection="column">
|
|
446
|
+
{answers.map((answer, index) => {
|
|
447
|
+
const selections = answer.customInput
|
|
448
|
+
? [...answer.selectedOptions, answer.customInput].join(', ')
|
|
449
|
+
: answer.selectedOptions.join(', ');
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
<Box key={index}>
|
|
453
|
+
<Text color={colors.textSecondary}>● </Text>
|
|
454
|
+
<Text>{answer.question}</Text>
|
|
455
|
+
<Text color={colors.textMuted}> → </Text>
|
|
456
|
+
<Text color={colors.primary}>{selections || '(none)'}</Text>
|
|
457
|
+
</Box>
|
|
458
|
+
);
|
|
459
|
+
})}
|
|
460
|
+
</Box>
|
|
461
|
+
);
|
|
462
|
+
}
|
|
@@ -35,9 +35,19 @@ export const icons = {
|
|
|
35
35
|
// UI
|
|
36
36
|
thinking: '✱', // Star for thinking state
|
|
37
37
|
cursor: '▋',
|
|
38
|
-
// Selection
|
|
38
|
+
// Selection (single-select)
|
|
39
39
|
radio: '●', // Filled radio for selected
|
|
40
40
|
radioEmpty: '○', // Empty radio for unselected
|
|
41
|
+
// Selection (multi-select)
|
|
42
|
+
checkbox: '☑', // Checked checkbox
|
|
43
|
+
checkboxEmpty: '☐', // Empty checkbox
|
|
44
|
+
// Chip/tag borders (Claude Code style headers)
|
|
45
|
+
chipLeft: '╭─',
|
|
46
|
+
chipRight: '─╮',
|
|
47
|
+
// Box drawing
|
|
48
|
+
boxTop: '╭',
|
|
49
|
+
boxBottom: '╰',
|
|
50
|
+
boxVertical: '│',
|
|
41
51
|
// Tree connectors
|
|
42
52
|
treeEnd: '└', // Tree end connector for tool results
|
|
43
53
|
};
|
package/src/permissions/types.ts
CHANGED
|
@@ -227,6 +227,8 @@ export const DEFAULT_PERMISSION_CONFIG: PermissionConfig = {
|
|
|
227
227
|
{ tool: 'LSP', mode: 'auto', description: 'Language server' },
|
|
228
228
|
// Internal state management - auto-approve (no side effects)
|
|
229
229
|
{ tool: 'TodoWrite', mode: 'auto', description: 'Task tracking' },
|
|
230
|
+
// User interaction - auto-approve (asking user questions, not dangerous)
|
|
231
|
+
{ tool: 'AskUserQuestion', mode: 'auto', description: 'User questioning' },
|
|
230
232
|
],
|
|
231
233
|
allowedPrompts: [],
|
|
232
234
|
};
|