gencode-ai 0.1.1 → 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/.gencode/settings.local.json +7 -0
- package/CLAUDE.md +86 -0
- package/README.md +13 -16
- package/dist/agent/agent.d.ts +50 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +96 -16
- 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 +8 -1
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +266 -29
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
- package/dist/cli/components/CommandSuggestions.js +2 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -1
- package/dist/cli/components/Header.d.ts +1 -1
- package/dist/cli/components/Header.d.ts.map +1 -1
- package/dist/cli/components/Header.js +4 -6
- package/dist/cli/components/Header.js.map +1 -1
- package/dist/cli/components/Logo.d.ts +1 -0
- package/dist/cli/components/Logo.d.ts.map +1 -1
- package/dist/cli/components/Logo.js +16 -3
- package/dist/cli/components/Logo.js.map +1 -1
- package/dist/cli/components/Messages.d.ts +4 -4
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +66 -23
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/components/PermissionPrompt.d.ts +60 -0
- package/dist/cli/components/PermissionPrompt.d.ts.map +1 -0
- package/dist/cli/components/PermissionPrompt.js +192 -0
- package/dist/cli/components/PermissionPrompt.js.map +1 -0
- package/dist/cli/components/ProviderManager.js +3 -3
- package/dist/cli/components/ProviderManager.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/Spinner.d.ts +7 -2
- package/dist/cli/components/Spinner.d.ts.map +1 -1
- package/dist/cli/components/Spinner.js +116 -25
- package/dist/cli/components/Spinner.js.map +1 -1
- package/dist/cli/components/TodoList.d.ts +7 -0
- package/dist/cli/components/TodoList.d.ts.map +1 -0
- package/dist/cli/components/TodoList.js +34 -0
- package/dist/cli/components/TodoList.js.map +1 -0
- package/dist/cli/components/index.d.ts +2 -0
- package/dist/cli/components/index.d.ts.map +1 -1
- package/dist/cli/components/index.js +2 -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/cli/index.js +47 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.d.ts +13 -4
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +18 -3
- package/dist/config/index.js.map +1 -1
- package/dist/config/levels.d.ts +49 -0
- package/dist/config/levels.d.ts.map +1 -0
- package/dist/config/levels.js +222 -0
- package/dist/config/levels.js.map +1 -0
- package/dist/config/loader.d.ts +46 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +153 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/manager.d.ts +115 -15
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +260 -34
- package/dist/config/manager.js.map +1 -1
- package/dist/config/manager.test.d.ts +5 -0
- package/dist/config/manager.test.d.ts.map +1 -0
- package/dist/config/manager.test.js +192 -0
- package/dist/config/manager.test.js.map +1 -0
- package/dist/config/merger.d.ts +56 -0
- package/dist/config/merger.d.ts.map +1 -0
- package/dist/config/merger.js +177 -0
- package/dist/config/merger.js.map +1 -0
- package/dist/config/test-utils.d.ts +24 -0
- package/dist/config/test-utils.d.ts.map +1 -0
- package/dist/config/test-utils.js +55 -0
- package/dist/config/test-utils.js.map +1 -0
- package/dist/config/types.d.ts +78 -9
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +52 -2
- package/dist/config/types.js.map +1 -1
- package/dist/memory/import-resolver.d.ts +46 -0
- package/dist/memory/import-resolver.d.ts.map +1 -0
- package/dist/memory/import-resolver.js +117 -0
- package/dist/memory/import-resolver.js.map +1 -0
- package/dist/memory/index.d.ts +7 -6
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +7 -5
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/init-prompt.d.ts +22 -0
- package/dist/memory/init-prompt.d.ts.map +1 -0
- package/dist/memory/init-prompt.js +103 -0
- package/dist/memory/init-prompt.js.map +1 -0
- package/dist/memory/memory-manager.d.ts +119 -0
- package/dist/memory/memory-manager.d.ts.map +1 -0
- package/dist/memory/memory-manager.js +587 -0
- package/dist/memory/memory-manager.js.map +1 -0
- package/dist/memory/rules-parser.d.ts +38 -0
- package/dist/memory/rules-parser.d.ts.map +1 -0
- package/dist/memory/rules-parser.js +69 -0
- package/dist/memory/rules-parser.js.map +1 -0
- package/dist/memory/test-utils.d.ts +20 -0
- package/dist/memory/test-utils.d.ts.map +1 -0
- package/dist/memory/test-utils.js +44 -0
- package/dist/memory/test-utils.js.map +1 -0
- package/dist/memory/types.d.ts +70 -63
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/memory/types.js +42 -2
- package/dist/memory/types.js.map +1 -1
- package/dist/permissions/audit.d.ts +82 -0
- package/dist/permissions/audit.d.ts.map +1 -0
- package/dist/permissions/audit.js +229 -0
- package/dist/permissions/audit.js.map +1 -0
- package/dist/permissions/index.d.ts +11 -1
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +15 -0
- package/dist/permissions/index.js.map +1 -1
- package/dist/permissions/manager.d.ts +149 -13
- package/dist/permissions/manager.d.ts.map +1 -1
- package/dist/permissions/manager.js +480 -35
- package/dist/permissions/manager.js.map +1 -1
- package/dist/permissions/manager.test.d.ts +5 -0
- package/dist/permissions/manager.test.d.ts.map +1 -0
- package/dist/permissions/manager.test.js +213 -0
- package/dist/permissions/manager.test.js.map +1 -0
- package/dist/permissions/persistence.d.ts +74 -0
- package/dist/permissions/persistence.d.ts.map +1 -0
- package/dist/permissions/persistence.js +248 -0
- package/dist/permissions/persistence.js.map +1 -0
- package/dist/permissions/persistence.test.d.ts +5 -0
- package/dist/permissions/persistence.test.d.ts.map +1 -0
- package/dist/permissions/persistence.test.js +171 -0
- package/dist/permissions/persistence.test.js.map +1 -0
- package/dist/permissions/prompt-matcher.d.ts +64 -0
- package/dist/permissions/prompt-matcher.d.ts.map +1 -0
- package/dist/permissions/prompt-matcher.js +415 -0
- package/dist/permissions/prompt-matcher.js.map +1 -0
- package/dist/permissions/prompt-matcher.test.d.ts +5 -0
- package/dist/permissions/prompt-matcher.test.d.ts.map +1 -0
- package/dist/permissions/prompt-matcher.test.js +107 -0
- package/dist/permissions/prompt-matcher.test.js.map +1 -0
- package/dist/permissions/types.d.ts +157 -0
- package/dist/permissions/types.d.ts.map +1 -1
- package/dist/permissions/types.js +45 -8
- package/dist/permissions/types.js.map +1 -1
- package/dist/prompts/index.d.ts +92 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +241 -0
- package/dist/prompts/index.js.map +1 -0
- 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/builtin/bash.d.ts.map +1 -1
- package/dist/tools/builtin/bash.js +2 -1
- package/dist/tools/builtin/bash.js.map +1 -1
- package/dist/tools/builtin/edit.d.ts.map +1 -1
- package/dist/tools/builtin/edit.js +2 -1
- package/dist/tools/builtin/edit.js.map +1 -1
- package/dist/tools/builtin/glob.d.ts.map +1 -1
- package/dist/tools/builtin/glob.js +2 -1
- package/dist/tools/builtin/glob.js.map +1 -1
- package/dist/tools/builtin/grep.d.ts.map +1 -1
- package/dist/tools/builtin/grep.js +2 -1
- package/dist/tools/builtin/grep.js.map +1 -1
- package/dist/tools/builtin/read.d.ts.map +1 -1
- package/dist/tools/builtin/read.js +2 -1
- package/dist/tools/builtin/read.js.map +1 -1
- package/dist/tools/builtin/todowrite.d.ts +15 -0
- package/dist/tools/builtin/todowrite.d.ts.map +1 -0
- package/dist/tools/builtin/todowrite.js +88 -0
- package/dist/tools/builtin/todowrite.js.map +1 -0
- package/dist/tools/builtin/webfetch.d.ts.map +1 -1
- package/dist/tools/builtin/webfetch.js +2 -5
- package/dist/tools/builtin/webfetch.js.map +1 -1
- package/dist/tools/builtin/websearch.d.ts.map +1 -1
- package/dist/tools/builtin/websearch.js +2 -16
- package/dist/tools/builtin/websearch.js.map +1 -1
- package/dist/tools/builtin/write.d.ts.map +1 -1
- package/dist/tools/builtin/write.js +2 -1
- package/dist/tools/builtin/write.js.map +1 -1
- package/dist/tools/index.d.ts +19 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/types.d.ts +39 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +8 -0
- package/dist/tools/types.js.map +1 -1
- package/docs/config-system-comparison.md +707 -0
- package/docs/memory-system.md +238 -0
- package/docs/permissions.md +368 -0
- package/docs/proposals/0005-todo-system.md +350 -85
- package/docs/proposals/0006-memory-system.md +11 -10
- package/docs/proposals/0012-ask-user-question.md +1007 -207
- package/docs/proposals/0023-permission-enhancements.md +61 -2
- package/docs/proposals/0041-configuration-system.md +33 -2
- package/docs/proposals/0042-prompt-optimization.md +866 -0
- package/docs/proposals/README.md +7 -6
- package/examples/test-ask-user.ts +167 -0
- package/jest.config.js +26 -0
- package/package.json +8 -2
- package/src/agent/agent.ts +130 -16
- package/src/agent/index.ts +1 -0
- package/src/agent/types.ts +13 -1
- package/src/cli/components/App.tsx +362 -37
- package/src/cli/components/CommandSuggestions.tsx +2 -0
- package/src/cli/components/Header.tsx +11 -17
- package/src/cli/components/Logo.tsx +76 -9
- package/src/cli/components/Messages.tsx +104 -41
- package/src/cli/components/PermissionPrompt.tsx +388 -0
- package/src/cli/components/ProviderManager.tsx +5 -5
- package/src/cli/components/QuestionPrompt.tsx +462 -0
- package/src/cli/components/Spinner.tsx +138 -25
- package/src/cli/components/TodoList.tsx +54 -0
- package/src/cli/components/index.ts +7 -0
- package/src/cli/components/theme.ts +11 -1
- package/src/cli/index.tsx +54 -6
- package/src/config/index.ts +78 -4
- package/src/config/levels.test.ts +163 -0
- package/src/config/levels.ts +285 -0
- package/src/config/loader.test.ts +120 -0
- package/src/config/loader.ts +178 -0
- package/src/config/manager.test.ts +215 -0
- package/src/config/manager.ts +328 -40
- package/src/config/merger.test.ts +360 -0
- package/src/config/merger.ts +221 -0
- package/src/config/test-utils.ts +79 -0
- package/src/config/types.ts +152 -9
- package/src/memory/import-resolver.test.ts +117 -0
- package/src/memory/import-resolver.ts +149 -0
- package/src/memory/index.ts +11 -0
- package/src/memory/init-prompt.ts +113 -0
- package/src/memory/memory-manager.test.ts +198 -0
- package/src/memory/memory-manager.ts +716 -0
- package/src/memory/rules-parser.test.ts +182 -0
- package/src/memory/rules-parser.ts +82 -0
- package/src/memory/test-utils.ts +60 -0
- package/src/memory/types.ts +119 -0
- package/src/permissions/audit.ts +284 -0
- package/src/permissions/index.ts +20 -1
- package/src/permissions/manager.test.ts +260 -0
- package/src/permissions/manager.ts +592 -40
- package/src/permissions/persistence.test.ts +220 -0
- package/src/permissions/persistence.ts +301 -0
- package/src/permissions/prompt-matcher.test.ts +213 -0
- package/src/permissions/prompt-matcher.ts +472 -0
- package/src/permissions/types.ts +238 -8
- package/src/prompts/index.test.ts +279 -0
- package/src/prompts/index.ts +306 -0
- package/src/prompts/system/anthropic.txt +29 -0
- package/src/prompts/system/base.txt +166 -0
- package/src/prompts/system/gemini.txt +35 -0
- package/src/prompts/system/generic.txt +128 -0
- package/src/prompts/system/openai.txt +29 -0
- package/src/prompts/tools/ask-user.txt +110 -0
- package/src/prompts/tools/bash.txt +60 -0
- package/src/prompts/tools/edit.txt +29 -0
- package/src/prompts/tools/glob.txt +35 -0
- package/src/prompts/tools/grep.txt +43 -0
- package/src/prompts/tools/read.txt +22 -0
- package/src/prompts/tools/todowrite.txt +71 -0
- package/src/prompts/tools/webfetch.txt +34 -0
- package/src/prompts/tools/websearch.txt +41 -0
- package/src/prompts/tools/write.txt +23 -0
- package/src/tools/builtin/ask-user.ts +185 -0
- package/src/tools/builtin/bash.ts +2 -1
- package/src/tools/builtin/edit.ts +2 -1
- package/src/tools/builtin/glob.ts +2 -1
- package/src/tools/builtin/grep.ts +2 -1
- package/src/tools/builtin/read.ts +2 -1
- package/src/tools/builtin/todowrite.ts +102 -0
- package/src/tools/builtin/webfetch.ts +2 -5
- package/src/tools/builtin/websearch.ts +2 -16
- package/src/tools/builtin/write.ts +2 -1
- package/src/tools/index.ts +19 -0
- package/src/tools/types.ts +30 -0
- package/tsconfig.json +1 -1
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Spinner Component -
|
|
2
|
+
* Spinner Component - Vivid thinking animation
|
|
3
3
|
*/
|
|
4
|
-
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
5
5
|
import { Box, Text } from 'ink';
|
|
6
6
|
import InkSpinner from 'ink-spinner';
|
|
7
7
|
import { colors } from './theme.js';
|
|
@@ -33,40 +33,153 @@ export function LoadingSpinner({ text = 'Loading...' }: SpinnerProps) {
|
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Thinking phrases that rotate during processing
|
|
37
|
+
const thinkingPhrases = [
|
|
38
|
+
'Thinking',
|
|
39
|
+
'Pondering',
|
|
40
|
+
'Analyzing',
|
|
41
|
+
'Processing',
|
|
42
|
+
'Reasoning',
|
|
43
|
+
'Contemplating',
|
|
44
|
+
'Figuring out',
|
|
45
|
+
'Working on it',
|
|
46
|
+
'Almost there',
|
|
47
|
+
'Crafting response',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// Animation frames for different styles
|
|
51
|
+
const animations = {
|
|
52
|
+
// Brainwave animation
|
|
53
|
+
brainwave: ['🧠 ∿∿∿', '🧠∿ ∿∿', '🧠∿∿ ∿', '🧠∿∿∿ ', '🧠 ∿∿∿', '🧠∿ ∿∿'],
|
|
54
|
+
// Sparkle animation
|
|
55
|
+
sparkle: ['✨ ', ' ✨ ', ' ✨ ', ' ✨ ', ' ✨', ' ✨ ', ' ✨ ', ' ✨ '],
|
|
56
|
+
// DNA helix
|
|
57
|
+
dna: ['🔬 ⌬⌬⌬', '🔬⌬ ⌬⌬', '🔬⌬⌬ ⌬', '🔬⌬⌬⌬ ', '🔬 ⌬⌬⌬'],
|
|
58
|
+
// Pulse dots
|
|
59
|
+
pulse: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
|
|
60
|
+
// Wave animation
|
|
61
|
+
wave: ['≋≈∼∽', '∽≋≈∼', '∼∽≋≈', '≈∼∽≋'],
|
|
62
|
+
// Bounce bar with gradient
|
|
63
|
+
bounceGradient: [
|
|
64
|
+
'█▓▒░ ',
|
|
65
|
+
' █▓▒░ ',
|
|
66
|
+
' █▓▒░ ',
|
|
67
|
+
' █▓▒░ ',
|
|
68
|
+
' █▓▒░',
|
|
69
|
+
' ░▒▓█ ',
|
|
70
|
+
' ░▒▓█ ',
|
|
71
|
+
' ░▒▓█ ',
|
|
72
|
+
'░▒▓█ ',
|
|
73
|
+
],
|
|
74
|
+
// Orbit animation
|
|
75
|
+
orbit: ['◐', '◓', '◑', '◒'],
|
|
76
|
+
// Loading bar with shimmer
|
|
77
|
+
shimmer: [
|
|
78
|
+
'▓▓▓▓▓░░░',
|
|
79
|
+
'░▓▓▓▓▓░░',
|
|
80
|
+
'░░▓▓▓▓▓░',
|
|
81
|
+
'░░░▓▓▓▓▓',
|
|
82
|
+
'░░░░▓▓▓▓',
|
|
83
|
+
'░░░▓▓▓▓▓',
|
|
84
|
+
'░░▓▓▓▓▓░',
|
|
85
|
+
'░▓▓▓▓▓░░',
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
type AnimationType = keyof typeof animations;
|
|
90
|
+
const animationTypes = Object.keys(animations) as AnimationType[];
|
|
91
|
+
|
|
92
|
+
// Format elapsed time
|
|
93
|
+
function formatElapsed(ms: number): string {
|
|
94
|
+
const secs = Math.floor(ms / 1000);
|
|
95
|
+
if (secs < 60) return `${secs}s`;
|
|
96
|
+
const mins = Math.floor(secs / 60);
|
|
97
|
+
const remainSecs = secs % 60;
|
|
98
|
+
return `${mins}m ${remainSecs}s`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Format token count
|
|
102
|
+
function formatTokens(count: number): string {
|
|
103
|
+
if (count >= 1000) {
|
|
104
|
+
return `${(count / 1000).toFixed(1)}k`;
|
|
105
|
+
}
|
|
106
|
+
return `${count}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface ProgressBarProps {
|
|
110
|
+
startTime?: number;
|
|
111
|
+
tokenCount?: number;
|
|
112
|
+
isThinking?: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
36
115
|
/**
|
|
37
116
|
* Progress bar animation for processing state
|
|
38
|
-
*
|
|
117
|
+
* Claude Code style with time, tokens, and thinking status
|
|
39
118
|
*/
|
|
40
|
-
export function ProgressBar() {
|
|
119
|
+
export function ProgressBar({ startTime, tokenCount = 0, isThinking = false }: ProgressBarProps) {
|
|
41
120
|
const [frame, setFrame] = useState(0);
|
|
121
|
+
const [phraseIndex, setPhraseIndex] = useState(0);
|
|
122
|
+
const [elapsed, setElapsed] = useState(0);
|
|
42
123
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return () => clearInterval(timer);
|
|
124
|
+
// Pick a random animation style on mount
|
|
125
|
+
const animStyle = useMemo(() => {
|
|
126
|
+
const randomIndex = Math.floor(Math.random() * animationTypes.length);
|
|
127
|
+
return animationTypes[randomIndex];
|
|
48
128
|
}, []);
|
|
49
129
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
130
|
+
const currentAnim = animations[animStyle];
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
// Fast animation update
|
|
134
|
+
const animTimer = setInterval(() => {
|
|
135
|
+
setFrame((f) => (f + 1) % currentAnim.length);
|
|
136
|
+
}, 120);
|
|
137
|
+
|
|
138
|
+
// Slower phrase rotation (every 2.5 seconds)
|
|
139
|
+
const phraseTimer = setInterval(() => {
|
|
140
|
+
setPhraseIndex((p) => (p + 1) % thinkingPhrases.length);
|
|
141
|
+
}, 2500);
|
|
142
|
+
|
|
143
|
+
// Update elapsed time every second
|
|
144
|
+
const elapsedTimer = setInterval(() => {
|
|
145
|
+
if (startTime) {
|
|
146
|
+
setElapsed(Date.now() - startTime);
|
|
147
|
+
}
|
|
148
|
+
}, 1000);
|
|
149
|
+
|
|
150
|
+
return () => {
|
|
151
|
+
clearInterval(animTimer);
|
|
152
|
+
clearInterval(phraseTimer);
|
|
153
|
+
clearInterval(elapsedTimer);
|
|
154
|
+
};
|
|
155
|
+
}, [currentAnim.length, startTime]);
|
|
156
|
+
|
|
157
|
+
const animFrame = currentAnim[frame];
|
|
158
|
+
const phrase = thinkingPhrases[phraseIndex];
|
|
159
|
+
|
|
160
|
+
// Animated ellipsis
|
|
161
|
+
const ellipsis = '.'.repeat((frame % 3) + 1).padEnd(3, ' ');
|
|
162
|
+
|
|
163
|
+
// Build status parts
|
|
164
|
+
const parts: string[] = [];
|
|
165
|
+
if (startTime && elapsed > 0) {
|
|
166
|
+
parts.push(formatElapsed(elapsed));
|
|
167
|
+
}
|
|
168
|
+
if (tokenCount > 0) {
|
|
169
|
+
parts.push(`↓ ${formatTokens(tokenCount)} tokens`);
|
|
64
170
|
}
|
|
171
|
+
if (isThinking) {
|
|
172
|
+
parts.push('thinking');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const statusText = parts.length > 0 ? ` · ${parts.join(' · ')}` : '';
|
|
65
176
|
|
|
66
177
|
return (
|
|
67
178
|
<Box>
|
|
68
|
-
<Text color={colors.brand}>{
|
|
69
|
-
<Text color={colors.
|
|
179
|
+
<Text color={colors.brand}>{animFrame}</Text>
|
|
180
|
+
<Text color={colors.textSecondary}> {phrase}</Text>
|
|
181
|
+
<Text color={colors.textMuted}>{ellipsis}</Text>
|
|
182
|
+
<Text color={colors.textMuted}>(esc to stop{statusText})</Text>
|
|
70
183
|
</Box>
|
|
71
184
|
);
|
|
72
185
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TodoList Component - Display current todos in CLI
|
|
3
|
+
* Design: Minimal, clean, status-driven with clear visual hierarchy
|
|
4
|
+
*/
|
|
5
|
+
import { Box, Text } from 'ink';
|
|
6
|
+
import { colors } from './theme.js';
|
|
7
|
+
import type { TodoItem } from '../../tools/types.js';
|
|
8
|
+
|
|
9
|
+
interface TodoListProps {
|
|
10
|
+
todos: TodoItem[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function TodoList({ todos }: TodoListProps) {
|
|
14
|
+
if (todos.length === 0) return null;
|
|
15
|
+
|
|
16
|
+
const completed = todos.filter((t) => t.status === 'completed').length;
|
|
17
|
+
const total = todos.length;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Box flexDirection="column" marginTop={1} marginLeft={2}>
|
|
21
|
+
{/* Header with count */}
|
|
22
|
+
<Text color={colors.textMuted}>
|
|
23
|
+
Tasks {completed}/{total}
|
|
24
|
+
</Text>
|
|
25
|
+
{/* Task list */}
|
|
26
|
+
{todos.map((todo, i) => {
|
|
27
|
+
const isCompleted = todo.status === 'completed';
|
|
28
|
+
const isInProgress = todo.status === 'in_progress';
|
|
29
|
+
|
|
30
|
+
// Status indicators: [x] done, [>] active, [ ] pending
|
|
31
|
+
let bracket: string;
|
|
32
|
+
let bracketColor: string;
|
|
33
|
+
|
|
34
|
+
if (isCompleted) {
|
|
35
|
+
bracket = '[x]';
|
|
36
|
+
bracketColor = colors.success;
|
|
37
|
+
} else if (isInProgress) {
|
|
38
|
+
bracket = '[>]';
|
|
39
|
+
bracketColor = colors.warning;
|
|
40
|
+
} else {
|
|
41
|
+
bracket = '[ ]';
|
|
42
|
+
bracketColor = colors.textMuted;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Text key={i} dimColor={isCompleted}>
|
|
47
|
+
<Text color={bracketColor}>{bracket}</Text>
|
|
48
|
+
<Text strikethrough={isCompleted}> {todo.content}</Text>
|
|
49
|
+
</Text>
|
|
50
|
+
);
|
|
51
|
+
})}
|
|
52
|
+
</Box>
|
|
53
|
+
);
|
|
54
|
+
}
|