@orderful/droid 0.15.0 → 0.16.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/.claude/CLAUDE.md +19 -1
- package/CHANGELOG.md +13 -0
- package/dist/bin/droid.js +8 -8
- package/dist/bin/droid.js.map +1 -1
- package/dist/commands/config.js +1 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/install.js +3 -3
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +3 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills.js +4 -4
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/tui/components/Badge.d.ts +13 -0
- package/dist/commands/tui/components/Badge.d.ts.map +1 -0
- package/dist/commands/tui/components/Badge.js +29 -0
- package/dist/commands/tui/components/Badge.js.map +1 -0
- package/dist/commands/tui/components/Markdown.d.ts +5 -0
- package/dist/commands/tui/components/Markdown.d.ts.map +1 -0
- package/dist/commands/tui/components/Markdown.js +42 -0
- package/dist/commands/tui/components/Markdown.js.map +1 -0
- package/dist/commands/tui/components/SettingsDetails.d.ts +5 -0
- package/dist/commands/tui/components/SettingsDetails.d.ts.map +1 -0
- package/dist/commands/tui/components/SettingsDetails.js +11 -0
- package/dist/commands/tui/components/SettingsDetails.js.map +1 -0
- package/dist/commands/tui/components/TabBar.d.ts +10 -0
- package/dist/commands/tui/components/TabBar.d.ts.map +1 -0
- package/dist/commands/tui/components/TabBar.js +7 -0
- package/dist/commands/tui/components/TabBar.js.map +1 -0
- package/dist/commands/tui/components/ToolDetails.d.ts +8 -0
- package/dist/commands/tui/components/ToolDetails.d.ts.map +1 -0
- package/dist/commands/tui/components/ToolDetails.js +35 -0
- package/dist/commands/tui/components/ToolDetails.js.map +1 -0
- package/dist/commands/tui/components/ToolItem.d.ts +9 -0
- package/dist/commands/tui/components/ToolItem.d.ts.map +1 -0
- package/dist/commands/tui/components/ToolItem.js +11 -0
- package/dist/commands/tui/components/ToolItem.js.map +1 -0
- package/dist/commands/tui/constants.d.ts +16 -0
- package/dist/commands/tui/constants.d.ts.map +1 -0
- package/dist/commands/tui/constants.js +17 -0
- package/dist/commands/tui/constants.js.map +1 -0
- package/dist/commands/tui/hooks/useAppUpdate.d.ts +13 -0
- package/dist/commands/tui/hooks/useAppUpdate.d.ts.map +1 -0
- package/dist/commands/tui/hooks/useAppUpdate.js +52 -0
- package/dist/commands/tui/hooks/useAppUpdate.js.map +1 -0
- package/dist/commands/tui/hooks/useToolUpdates.d.ts +22 -0
- package/dist/commands/tui/hooks/useToolUpdates.d.ts.map +1 -0
- package/dist/commands/tui/hooks/useToolUpdates.js +77 -0
- package/dist/commands/tui/hooks/useToolUpdates.js.map +1 -0
- package/dist/commands/tui/types.d.ts +5 -0
- package/dist/commands/tui/types.d.ts.map +1 -0
- package/dist/commands/tui/types.js +2 -0
- package/dist/commands/tui/types.js.map +1 -0
- package/dist/commands/tui/views/ReadmeViewer.d.ts +7 -0
- package/dist/commands/tui/views/ReadmeViewer.d.ts.map +1 -0
- package/dist/commands/tui/views/ReadmeViewer.js +56 -0
- package/dist/commands/tui/views/ReadmeViewer.js.map +1 -0
- package/dist/commands/tui/views/SetupScreen.d.ts +8 -0
- package/dist/commands/tui/views/SetupScreen.d.ts.map +1 -0
- package/dist/commands/tui/views/SetupScreen.js +114 -0
- package/dist/commands/tui/views/SetupScreen.js.map +1 -0
- package/dist/commands/tui/views/SkillConfigScreen.d.ts +8 -0
- package/dist/commands/tui/views/SkillConfigScreen.d.ts.map +1 -0
- package/dist/commands/tui/views/SkillConfigScreen.js +148 -0
- package/dist/commands/tui/views/SkillConfigScreen.js.map +1 -0
- package/dist/commands/tui/views/ToolExplorer.d.ts +8 -0
- package/dist/commands/tui/views/ToolExplorer.d.ts.map +1 -0
- package/dist/commands/tui/views/ToolExplorer.js +86 -0
- package/dist/commands/tui/views/ToolExplorer.js.map +1 -0
- package/dist/commands/tui/views/ToolUpdatePrompt.d.ts +10 -0
- package/dist/commands/tui/views/ToolUpdatePrompt.d.ts.map +1 -0
- package/dist/commands/tui/views/ToolUpdatePrompt.js +38 -0
- package/dist/commands/tui/views/ToolUpdatePrompt.js.map +1 -0
- package/dist/commands/tui/views/WelcomeScreen.d.ts +11 -0
- package/dist/commands/tui/views/WelcomeScreen.d.ts.map +1 -0
- package/dist/commands/tui/views/WelcomeScreen.js +46 -0
- package/dist/commands/tui/views/WelcomeScreen.js.map +1 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +54 -755
- package/dist/commands/tui.js.map +1 -1
- package/dist/commands/uninstall.js +2 -2
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/agents.d.ts +1 -1
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +4 -4
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/platforms.d.ts +1 -1
- package/dist/lib/platforms.d.ts.map +1 -1
- package/dist/lib/platforms.js +1 -1
- package/dist/lib/platforms.js.map +1 -1
- package/dist/lib/skill-config.d.ts +1 -1
- package/dist/lib/skill-config.d.ts.map +1 -1
- package/dist/lib/skill-config.js +2 -2
- package/dist/lib/skill-config.js.map +1 -1
- package/dist/lib/skills.d.ts +1 -1
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +5 -5
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.d.ts.map +1 -1
- package/dist/lib/tools.js +3 -3
- package/dist/lib/tools.js.map +1 -1
- package/package.json +3 -3
- package/src/bin/droid.ts +8 -8
- package/src/commands/config.ts +1 -1
- package/src/commands/install.ts +3 -3
- package/src/commands/setup.test.ts +1 -1
- package/src/commands/setup.ts +3 -3
- package/src/commands/skills.ts +4 -4
- package/src/commands/tui/components/Badge.tsx +86 -0
- package/src/commands/tui/components/Markdown.tsx +48 -0
- package/src/commands/tui/components/SettingsDetails.tsx +70 -0
- package/src/commands/tui/components/TabBar.tsx +26 -0
- package/src/commands/tui/components/ToolDetails.tsx +117 -0
- package/src/commands/tui/components/ToolItem.tsx +39 -0
- package/src/commands/tui/constants.ts +17 -0
- package/src/commands/tui/hooks/useAppUpdate.ts +67 -0
- package/src/commands/tui/hooks/useToolUpdates.ts +110 -0
- package/src/commands/tui/types.ts +4 -0
- package/src/commands/tui/views/ReadmeViewer.tsx +93 -0
- package/src/commands/tui/views/SetupScreen.tsx +242 -0
- package/src/commands/tui/views/SkillConfigScreen.tsx +278 -0
- package/src/commands/tui/views/ToolExplorer.tsx +190 -0
- package/src/commands/tui/views/ToolUpdatePrompt.tsx +109 -0
- package/src/commands/tui/views/WelcomeScreen.tsx +149 -0
- package/src/commands/tui.tsx +65 -1587
- package/src/commands/uninstall.ts +2 -2
- package/src/commands/update.ts +1 -1
- package/src/index.ts +4 -4
- package/src/lib/agents.ts +4 -4
- package/src/lib/config.ts +1 -1
- package/src/lib/platforms.ts +1 -1
- package/src/lib/skill-config.ts +2 -2
- package/src/lib/skills.test.ts +2 -2
- package/src/lib/skills.ts +5 -5
- package/src/lib/tools.ts +3 -3
- package/src/lib/types.test.ts +1 -1
- package/src/lib/version.test.ts +1 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { Box, Text, useInput } from 'ink';
|
|
2
|
+
import TextInput from 'ink-text-input';
|
|
3
|
+
import { useState, useMemo } from 'react';
|
|
4
|
+
import { loadSkillOverrides, saveSkillOverrides } from '../../../lib/config';
|
|
5
|
+
import { ConfigOptionType, type SkillManifest, type SkillOverrides } from '../../../lib/types';
|
|
6
|
+
import { colors, MAX_VISIBLE_CONFIG_ITEMS } from '../constants';
|
|
7
|
+
|
|
8
|
+
export interface SkillConfigScreenProps {
|
|
9
|
+
skill: SkillManifest;
|
|
10
|
+
onComplete: () => void;
|
|
11
|
+
onCancel: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenProps) {
|
|
15
|
+
const configSchema = skill.config_schema || {};
|
|
16
|
+
const configKeys = Object.keys(configSchema);
|
|
17
|
+
|
|
18
|
+
const initialOverrides = useMemo(() => loadSkillOverrides(skill.name), [skill.name]);
|
|
19
|
+
|
|
20
|
+
// Initialize values from saved overrides or defaults
|
|
21
|
+
const [values, setValues] = useState<SkillOverrides>(() => {
|
|
22
|
+
const initial: SkillOverrides = {};
|
|
23
|
+
for (const key of configKeys) {
|
|
24
|
+
const option = configSchema[key];
|
|
25
|
+
initial[key] = initialOverrides[key] ?? option.default ?? (option.type === ConfigOptionType.Boolean ? false : '');
|
|
26
|
+
}
|
|
27
|
+
return initial;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
31
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
32
|
+
const [editingField, setEditingField] = useState<string | null>(null);
|
|
33
|
+
const [editValue, setEditValue] = useState('');
|
|
34
|
+
const [editingSelect, setEditingSelect] = useState<string | null>(null);
|
|
35
|
+
const [selectOptionIndex, setSelectOptionIndex] = useState(0);
|
|
36
|
+
|
|
37
|
+
// Total items = config keys + Save button
|
|
38
|
+
const totalItems = configKeys.length + 1;
|
|
39
|
+
|
|
40
|
+
const handleSave = () => {
|
|
41
|
+
saveSkillOverrides(skill.name, values);
|
|
42
|
+
onComplete();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleSubmitEdit = () => {
|
|
46
|
+
if (editingField) {
|
|
47
|
+
setValues((prev) => ({ ...prev, [editingField]: editValue }));
|
|
48
|
+
setEditingField(null);
|
|
49
|
+
setEditValue('');
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Handle text input for string fields
|
|
54
|
+
useInput((input, key) => {
|
|
55
|
+
if (key.escape) {
|
|
56
|
+
setEditingField(null);
|
|
57
|
+
setEditValue('');
|
|
58
|
+
}
|
|
59
|
+
}, { isActive: editingField !== null });
|
|
60
|
+
|
|
61
|
+
// Handle select field editing
|
|
62
|
+
useInput((input, key) => {
|
|
63
|
+
if (!editingSelect) return;
|
|
64
|
+
const option = configSchema[editingSelect];
|
|
65
|
+
if (!option?.options) return;
|
|
66
|
+
|
|
67
|
+
if (key.escape) {
|
|
68
|
+
setEditingSelect(null);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (key.leftArrow || key.upArrow) {
|
|
72
|
+
setSelectOptionIndex((prev) => Math.max(0, prev - 1));
|
|
73
|
+
}
|
|
74
|
+
if (key.rightArrow || key.downArrow) {
|
|
75
|
+
setSelectOptionIndex((prev) => Math.min(option.options!.length - 1, prev + 1));
|
|
76
|
+
}
|
|
77
|
+
if (key.return) {
|
|
78
|
+
setValues((prev) => ({ ...prev, [editingSelect]: option.options![selectOptionIndex] }));
|
|
79
|
+
setEditingSelect(null);
|
|
80
|
+
}
|
|
81
|
+
}, { isActive: editingSelect !== null });
|
|
82
|
+
|
|
83
|
+
// Handle navigation and actions when not editing
|
|
84
|
+
useInput((input, key) => {
|
|
85
|
+
if (key.escape) {
|
|
86
|
+
onCancel();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (key.upArrow) {
|
|
91
|
+
setSelectedIndex((prev) => {
|
|
92
|
+
const newIndex = Math.max(0, prev - 1);
|
|
93
|
+
// Scroll up if needed
|
|
94
|
+
if (newIndex < scrollOffset) {
|
|
95
|
+
setScrollOffset(newIndex);
|
|
96
|
+
}
|
|
97
|
+
return newIndex;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (key.downArrow) {
|
|
101
|
+
// +1 for the Save button at the end
|
|
102
|
+
setSelectedIndex((prev) => {
|
|
103
|
+
const newIndex = Math.min(totalItems - 1, prev + 1);
|
|
104
|
+
// Scroll down if needed
|
|
105
|
+
if (newIndex >= scrollOffset + MAX_VISIBLE_CONFIG_ITEMS) {
|
|
106
|
+
setScrollOffset(newIndex - MAX_VISIBLE_CONFIG_ITEMS + 1);
|
|
107
|
+
}
|
|
108
|
+
return newIndex;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (key.return) {
|
|
113
|
+
// Save button is at index === configKeys.length
|
|
114
|
+
if (selectedIndex === configKeys.length) {
|
|
115
|
+
handleSave();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const key = configKeys[selectedIndex];
|
|
120
|
+
const option = configSchema[key];
|
|
121
|
+
|
|
122
|
+
if (option.type === ConfigOptionType.Boolean) {
|
|
123
|
+
// Toggle boolean
|
|
124
|
+
setValues((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
125
|
+
} else if (option.type === ConfigOptionType.Select && option.options) {
|
|
126
|
+
// Enter select edit mode
|
|
127
|
+
const currentValue = String(values[key] || option.options[0]);
|
|
128
|
+
const currentIndex = option.options.indexOf(currentValue);
|
|
129
|
+
setSelectOptionIndex(currentIndex >= 0 ? currentIndex : 0);
|
|
130
|
+
setEditingSelect(key);
|
|
131
|
+
} else if (option.type === ConfigOptionType.String) {
|
|
132
|
+
// Enter edit mode for string
|
|
133
|
+
setEditingField(key);
|
|
134
|
+
setEditValue(String(values[key] || ''));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}, { isActive: editingField === null && editingSelect === null });
|
|
138
|
+
|
|
139
|
+
if (configKeys.length === 0) {
|
|
140
|
+
return (
|
|
141
|
+
<Box flexDirection="column" padding={1}>
|
|
142
|
+
<Box marginBottom={1}>
|
|
143
|
+
<Text>
|
|
144
|
+
<Text color={colors.textDim}>[</Text>
|
|
145
|
+
<Text color={colors.primary}>●</Text>
|
|
146
|
+
<Text color={colors.textDim}> </Text>
|
|
147
|
+
<Text color={colors.primary}>●</Text>
|
|
148
|
+
<Text color={colors.textDim}>] </Text>
|
|
149
|
+
<Text color={colors.text} bold>configure {skill.name}</Text>
|
|
150
|
+
</Text>
|
|
151
|
+
</Box>
|
|
152
|
+
<Text color={colors.textMuted}>This skill has no configuration options.</Text>
|
|
153
|
+
<Box marginTop={1}>
|
|
154
|
+
<Text color={colors.textDim}>esc to go back</Text>
|
|
155
|
+
</Box>
|
|
156
|
+
</Box>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Calculate visible range
|
|
161
|
+
const visibleEndIndex = Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, totalItems);
|
|
162
|
+
const visibleConfigKeys = configKeys.slice(scrollOffset, Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, configKeys.length));
|
|
163
|
+
const showSaveButton = visibleEndIndex > configKeys.length || scrollOffset + MAX_VISIBLE_CONFIG_ITEMS > configKeys.length;
|
|
164
|
+
const showTopIndicator = scrollOffset > 0;
|
|
165
|
+
const showBottomIndicator = scrollOffset + MAX_VISIBLE_CONFIG_ITEMS < totalItems;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<Box flexDirection="column" padding={1}>
|
|
169
|
+
<Box marginBottom={1}>
|
|
170
|
+
<Text>
|
|
171
|
+
<Text color={colors.textDim}>[</Text>
|
|
172
|
+
<Text color={colors.primary}>●</Text>
|
|
173
|
+
<Text color={colors.textDim}> </Text>
|
|
174
|
+
<Text color={colors.primary}>●</Text>
|
|
175
|
+
<Text color={colors.textDim}>] </Text>
|
|
176
|
+
<Text color={colors.text} bold>configure {skill.name}</Text>
|
|
177
|
+
</Text>
|
|
178
|
+
</Box>
|
|
179
|
+
|
|
180
|
+
<Box flexDirection="column">
|
|
181
|
+
{/* Scroll up indicator */}
|
|
182
|
+
{showTopIndicator && (
|
|
183
|
+
<Box marginBottom={1}>
|
|
184
|
+
<Text color={colors.textDim}> ↑ {scrollOffset} more</Text>
|
|
185
|
+
</Box>
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{visibleConfigKeys.map((configKey) => {
|
|
189
|
+
const actualIndex = configKeys.indexOf(configKey);
|
|
190
|
+
const option = configSchema[configKey];
|
|
191
|
+
const isSelected = selectedIndex === actualIndex;
|
|
192
|
+
const isEditing = editingField === configKey;
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<Box key={configKey} flexDirection="column" marginBottom={1}>
|
|
196
|
+
<Text>
|
|
197
|
+
<Text color={colors.textDim}>{isSelected ? '>' : ' '} </Text>
|
|
198
|
+
<Text color={isSelected ? colors.text : colors.textMuted}>{configKey}</Text>
|
|
199
|
+
</Text>
|
|
200
|
+
<Text color={colors.textDim}> {option.description}</Text>
|
|
201
|
+
<Box>
|
|
202
|
+
<Text color={colors.textDim}> </Text>
|
|
203
|
+
{option.type === ConfigOptionType.Boolean ? (
|
|
204
|
+
<Text color={colors.text}>
|
|
205
|
+
[{values[configKey] ? 'x' : ' '}] {values[configKey] ? 'enabled' : 'disabled'}
|
|
206
|
+
</Text>
|
|
207
|
+
) : option.type === ConfigOptionType.Select && option.options ? (
|
|
208
|
+
<Text color={colors.text}>
|
|
209
|
+
{option.options.map((opt, i) => {
|
|
210
|
+
const isCurrentValue = String(values[configKey]) === opt;
|
|
211
|
+
const isEditingThis = editingSelect === configKey;
|
|
212
|
+
const isHighlighted = isEditingThis && selectOptionIndex === i;
|
|
213
|
+
return (
|
|
214
|
+
<Text key={opt}>
|
|
215
|
+
{i > 0 && <Text color={colors.textDim}> · </Text>}
|
|
216
|
+
<Text
|
|
217
|
+
color={isHighlighted ? '#ffffff' : isCurrentValue ? colors.primary : colors.textMuted}
|
|
218
|
+
backgroundColor={isHighlighted ? colors.primary : undefined}
|
|
219
|
+
>
|
|
220
|
+
{isCurrentValue && !isEditingThis ? `[${opt}]` : isHighlighted ? ` ${opt} ` : opt}
|
|
221
|
+
</Text>
|
|
222
|
+
</Text>
|
|
223
|
+
);
|
|
224
|
+
})}
|
|
225
|
+
</Text>
|
|
226
|
+
) : isEditing ? (
|
|
227
|
+
<Box>
|
|
228
|
+
<Text color={colors.textDim}>{'> '}</Text>
|
|
229
|
+
<TextInput
|
|
230
|
+
value={editValue}
|
|
231
|
+
onChange={setEditValue}
|
|
232
|
+
onSubmit={handleSubmitEdit}
|
|
233
|
+
/>
|
|
234
|
+
</Box>
|
|
235
|
+
) : (
|
|
236
|
+
<Text color={colors.text}>{String(values[configKey]) || '(not set)'}</Text>
|
|
237
|
+
)}
|
|
238
|
+
</Box>
|
|
239
|
+
</Box>
|
|
240
|
+
);
|
|
241
|
+
})}
|
|
242
|
+
|
|
243
|
+
{/* Save button - show if in visible range */}
|
|
244
|
+
{showSaveButton && (
|
|
245
|
+
<Box marginTop={1}>
|
|
246
|
+
<Text>
|
|
247
|
+
<Text color={colors.textDim}>{selectedIndex === configKeys.length ? '>' : ' '} </Text>
|
|
248
|
+
<Text
|
|
249
|
+
backgroundColor={selectedIndex === configKeys.length ? colors.primary : undefined}
|
|
250
|
+
color={selectedIndex === configKeys.length ? '#ffffff' : colors.textMuted}
|
|
251
|
+
bold={selectedIndex === configKeys.length}
|
|
252
|
+
>
|
|
253
|
+
{' '}Save{' '}
|
|
254
|
+
</Text>
|
|
255
|
+
</Text>
|
|
256
|
+
</Box>
|
|
257
|
+
)}
|
|
258
|
+
|
|
259
|
+
{/* Scroll down indicator */}
|
|
260
|
+
{showBottomIndicator && (
|
|
261
|
+
<Box marginTop={1}>
|
|
262
|
+
<Text color={colors.textDim}> ↓ {totalItems - scrollOffset - MAX_VISIBLE_CONFIG_ITEMS} more</Text>
|
|
263
|
+
</Box>
|
|
264
|
+
)}
|
|
265
|
+
</Box>
|
|
266
|
+
|
|
267
|
+
<Box marginTop={1}>
|
|
268
|
+
<Text color={colors.textDim}>
|
|
269
|
+
{editingField
|
|
270
|
+
? 'enter save · esc cancel'
|
|
271
|
+
: editingSelect
|
|
272
|
+
? '←→ choose · enter select · esc cancel'
|
|
273
|
+
: '↑↓ select · enter toggle/edit · esc back'}
|
|
274
|
+
</Text>
|
|
275
|
+
</Box>
|
|
276
|
+
</Box>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Box, Text, useInput } from 'ink';
|
|
2
|
+
import { useState, useMemo } from 'react';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { getBundledToolsDir } from '../../../lib/tools';
|
|
6
|
+
import type { ToolManifest } from '../../../lib/types';
|
|
7
|
+
import { colors } from '../constants';
|
|
8
|
+
import type { ComponentType } from '../types';
|
|
9
|
+
|
|
10
|
+
interface ExplorerItem {
|
|
11
|
+
type: ComponentType;
|
|
12
|
+
name: string;
|
|
13
|
+
path: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ToolExplorerProps {
|
|
17
|
+
tool: ToolManifest;
|
|
18
|
+
onViewSource: (title: string, content: string) => void;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ToolExplorer({ tool, onViewSource, onClose }: ToolExplorerProps) {
|
|
23
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
24
|
+
|
|
25
|
+
// Build list of all explorable items
|
|
26
|
+
const items: ExplorerItem[] = useMemo(() => {
|
|
27
|
+
const result: ExplorerItem[] = [];
|
|
28
|
+
const toolDir = getBundledToolsDir();
|
|
29
|
+
|
|
30
|
+
// Add skills
|
|
31
|
+
for (const skill of tool.includes.skills) {
|
|
32
|
+
result.push({
|
|
33
|
+
type: 'skill',
|
|
34
|
+
name: skill.name,
|
|
35
|
+
path: join(toolDir, tool.name, 'skills', skill.name, 'SKILL.md'),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Add commands
|
|
40
|
+
for (const cmd of tool.includes.commands) {
|
|
41
|
+
result.push({
|
|
42
|
+
type: 'command',
|
|
43
|
+
name: `/${cmd}`,
|
|
44
|
+
path: join(toolDir, tool.name, 'commands', `${cmd}.md`),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add agents
|
|
49
|
+
for (const agent of tool.includes.agents) {
|
|
50
|
+
result.push({
|
|
51
|
+
type: 'agent',
|
|
52
|
+
name: agent,
|
|
53
|
+
path: join(toolDir, tool.name, 'agents', agent, 'AGENT.md'),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return result;
|
|
58
|
+
}, [tool]);
|
|
59
|
+
|
|
60
|
+
useInput((input, key) => {
|
|
61
|
+
if (key.escape) {
|
|
62
|
+
onClose();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (key.leftArrow || key.upArrow) {
|
|
67
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
68
|
+
}
|
|
69
|
+
if (key.rightArrow || key.downArrow) {
|
|
70
|
+
setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (key.return && items.length > 0) {
|
|
74
|
+
const item = items[selectedIndex];
|
|
75
|
+
if (existsSync(item.path)) {
|
|
76
|
+
const content = readFileSync(item.path, 'utf-8');
|
|
77
|
+
onViewSource(`${tool.name} / ${item.name}`, content);
|
|
78
|
+
} else {
|
|
79
|
+
// Try YAML fallback for skills/agents
|
|
80
|
+
const yamlPath = item.path.replace('.md', '.yaml');
|
|
81
|
+
if (existsSync(yamlPath)) {
|
|
82
|
+
const content = readFileSync(yamlPath, 'utf-8');
|
|
83
|
+
onViewSource(`${tool.name} / ${item.name}`, content);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Group items by type for display
|
|
90
|
+
const skillItems = items.filter(i => i.type === 'skill');
|
|
91
|
+
const commandItems = items.filter(i => i.type === 'command');
|
|
92
|
+
const agentItems = items.filter(i => i.type === 'agent');
|
|
93
|
+
|
|
94
|
+
const getItemIndex = (item: ExplorerItem) => items.indexOf(item);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Box flexDirection="column" padding={1}>
|
|
98
|
+
<Box marginBottom={1}>
|
|
99
|
+
<Text>
|
|
100
|
+
<Text color={colors.textDim}>[</Text>
|
|
101
|
+
<Text color={colors.primary}>●</Text>
|
|
102
|
+
<Text color={colors.textDim}> </Text>
|
|
103
|
+
<Text color={colors.primary}>●</Text>
|
|
104
|
+
<Text color={colors.textDim}>] </Text>
|
|
105
|
+
<Text color={colors.text} bold>{tool.name}</Text>
|
|
106
|
+
</Text>
|
|
107
|
+
</Box>
|
|
108
|
+
|
|
109
|
+
<Box marginBottom={1}>
|
|
110
|
+
<Text color={colors.textMuted}>{tool.description}</Text>
|
|
111
|
+
</Box>
|
|
112
|
+
|
|
113
|
+
{/* Skills section */}
|
|
114
|
+
{skillItems.length > 0 && (
|
|
115
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
116
|
+
<Text color={colors.skill} bold>Skills</Text>
|
|
117
|
+
<Box flexDirection="row" flexWrap="wrap" marginTop={1}>
|
|
118
|
+
{skillItems.map((item) => {
|
|
119
|
+
const idx = getItemIndex(item);
|
|
120
|
+
const isSelected = selectedIndex === idx;
|
|
121
|
+
return (
|
|
122
|
+
<Box key={item.name} marginRight={1} marginBottom={1}>
|
|
123
|
+
<Text
|
|
124
|
+
backgroundColor={isSelected ? colors.skill : colors.bgSelected}
|
|
125
|
+
color={isSelected ? '#000000' : colors.skill}
|
|
126
|
+
bold={isSelected}
|
|
127
|
+
>
|
|
128
|
+
{` ${item.name} `}
|
|
129
|
+
</Text>
|
|
130
|
+
</Box>
|
|
131
|
+
);
|
|
132
|
+
})}
|
|
133
|
+
</Box>
|
|
134
|
+
</Box>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{/* Commands section */}
|
|
138
|
+
{commandItems.length > 0 && (
|
|
139
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
140
|
+
<Text color={colors.command} bold>Commands</Text>
|
|
141
|
+
<Box flexDirection="row" flexWrap="wrap" marginTop={1}>
|
|
142
|
+
{commandItems.map((item) => {
|
|
143
|
+
const idx = getItemIndex(item);
|
|
144
|
+
const isSelected = selectedIndex === idx;
|
|
145
|
+
return (
|
|
146
|
+
<Box key={item.name} marginRight={1} marginBottom={1}>
|
|
147
|
+
<Text
|
|
148
|
+
backgroundColor={isSelected ? colors.command : colors.bgSelected}
|
|
149
|
+
color={isSelected ? '#000000' : colors.command}
|
|
150
|
+
bold={isSelected}
|
|
151
|
+
>
|
|
152
|
+
{` ${item.name} `}
|
|
153
|
+
</Text>
|
|
154
|
+
</Box>
|
|
155
|
+
);
|
|
156
|
+
})}
|
|
157
|
+
</Box>
|
|
158
|
+
</Box>
|
|
159
|
+
)}
|
|
160
|
+
|
|
161
|
+
{/* Agents section */}
|
|
162
|
+
{agentItems.length > 0 && (
|
|
163
|
+
<Box flexDirection="column" marginBottom={1}>
|
|
164
|
+
<Text color={colors.agent} bold>Agents</Text>
|
|
165
|
+
<Box flexDirection="row" flexWrap="wrap" marginTop={1}>
|
|
166
|
+
{agentItems.map((item) => {
|
|
167
|
+
const idx = getItemIndex(item);
|
|
168
|
+
const isSelected = selectedIndex === idx;
|
|
169
|
+
return (
|
|
170
|
+
<Box key={item.name} marginRight={1} marginBottom={1}>
|
|
171
|
+
<Text
|
|
172
|
+
backgroundColor={isSelected ? colors.agent : colors.bgSelected}
|
|
173
|
+
color={isSelected ? '#000000' : colors.agent}
|
|
174
|
+
bold={isSelected}
|
|
175
|
+
>
|
|
176
|
+
{` ${item.name} `}
|
|
177
|
+
</Text>
|
|
178
|
+
</Box>
|
|
179
|
+
);
|
|
180
|
+
})}
|
|
181
|
+
</Box>
|
|
182
|
+
</Box>
|
|
183
|
+
)}
|
|
184
|
+
|
|
185
|
+
<Box marginTop={1}>
|
|
186
|
+
<Text color={colors.textDim}>←→ navigate · enter view source · esc back</Text>
|
|
187
|
+
</Box>
|
|
188
|
+
</Box>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Box, Text, useInput } from 'ink';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { colors } from '../constants';
|
|
4
|
+
import type { ToolUpdateInfo } from '../../../lib/tools';
|
|
5
|
+
|
|
6
|
+
export interface ToolUpdatePromptProps {
|
|
7
|
+
toolUpdates: ToolUpdateInfo[];
|
|
8
|
+
onUpdateAll: () => void;
|
|
9
|
+
onAlways: () => void;
|
|
10
|
+
onSkip: () => void;
|
|
11
|
+
isUpdating: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ToolUpdatePrompt({ toolUpdates, onUpdateAll, onAlways, onSkip, isUpdating }: ToolUpdatePromptProps) {
|
|
15
|
+
const [selectedButton, setSelectedButton] = useState(0);
|
|
16
|
+
|
|
17
|
+
useInput((input, key) => {
|
|
18
|
+
if (isUpdating) return;
|
|
19
|
+
|
|
20
|
+
if (key.leftArrow) {
|
|
21
|
+
setSelectedButton((prev) => Math.max(0, prev - 1));
|
|
22
|
+
}
|
|
23
|
+
if (key.rightArrow) {
|
|
24
|
+
setSelectedButton((prev) => Math.min(2, prev + 1));
|
|
25
|
+
}
|
|
26
|
+
if (key.return) {
|
|
27
|
+
if (selectedButton === 0) {
|
|
28
|
+
onUpdateAll();
|
|
29
|
+
} else if (selectedButton === 1) {
|
|
30
|
+
onAlways();
|
|
31
|
+
} else {
|
|
32
|
+
onSkip();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (input === 'q') {
|
|
36
|
+
onSkip();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const buttons = [
|
|
41
|
+
{ label: 'Update All', action: onUpdateAll },
|
|
42
|
+
{ label: 'Always', action: onAlways },
|
|
43
|
+
{ label: 'Skip', action: onSkip },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Box flexDirection="column" alignItems="center" justifyContent="center" height={18}>
|
|
48
|
+
<Box
|
|
49
|
+
flexDirection="column"
|
|
50
|
+
alignItems="center"
|
|
51
|
+
borderStyle="single"
|
|
52
|
+
borderColor={colors.primary}
|
|
53
|
+
paddingX={4}
|
|
54
|
+
paddingY={1}
|
|
55
|
+
>
|
|
56
|
+
<Box flexDirection="column">
|
|
57
|
+
<Text>
|
|
58
|
+
<Text color={colors.textDim}>╔═════╗ </Text>
|
|
59
|
+
<Text color={colors.text}>Tool Updates</Text>
|
|
60
|
+
</Text>
|
|
61
|
+
<Text>
|
|
62
|
+
<Text color={colors.textDim}>║ </Text>
|
|
63
|
+
<Text color={colors.primary}>●</Text>
|
|
64
|
+
<Text color={colors.textDim}> </Text>
|
|
65
|
+
<Text color={colors.primary}>●</Text>
|
|
66
|
+
<Text color={colors.textDim}> ║ </Text>
|
|
67
|
+
<Text color={colors.textMuted}>{toolUpdates.length} tool{toolUpdates.length > 1 ? 's have' : ' has'} updates available</Text>
|
|
68
|
+
</Text>
|
|
69
|
+
<Text>
|
|
70
|
+
<Text color={colors.textDim}>╚═╦═╦═╝</Text>
|
|
71
|
+
</Text>
|
|
72
|
+
</Box>
|
|
73
|
+
|
|
74
|
+
<Box marginTop={1} marginBottom={1} flexDirection="column">
|
|
75
|
+
{toolUpdates.slice(0, 5).map((tool) => (
|
|
76
|
+
<Text key={tool.name} color={colors.textMuted}>
|
|
77
|
+
<Text color={colors.primary}>↑</Text> {tool.name}
|
|
78
|
+
<Text color={colors.textDim}> {tool.installedVersion} → {tool.bundledVersion}</Text>
|
|
79
|
+
</Text>
|
|
80
|
+
))}
|
|
81
|
+
{toolUpdates.length > 5 && (
|
|
82
|
+
<Text color={colors.textDim}>...and {toolUpdates.length - 5} more</Text>
|
|
83
|
+
)}
|
|
84
|
+
</Box>
|
|
85
|
+
|
|
86
|
+
{isUpdating ? (
|
|
87
|
+
<Text color={colors.primary}>Updating tools...</Text>
|
|
88
|
+
) : (
|
|
89
|
+
<Box flexDirection="row">
|
|
90
|
+
{buttons.map((button, index) => (
|
|
91
|
+
<Text
|
|
92
|
+
key={button.label}
|
|
93
|
+
backgroundColor={selectedButton === index ? colors.primary : colors.bgSelected}
|
|
94
|
+
color={selectedButton === index ? '#000000' : colors.textMuted}
|
|
95
|
+
bold={selectedButton === index}
|
|
96
|
+
>
|
|
97
|
+
{' '}{button.label}{' '}
|
|
98
|
+
</Text>
|
|
99
|
+
))}
|
|
100
|
+
</Box>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
<Box marginTop={1}>
|
|
104
|
+
<Text color={colors.textDim}>←→ select · enter · "Always" enables auto-update</Text>
|
|
105
|
+
</Box>
|
|
106
|
+
</Box>
|
|
107
|
+
</Box>
|
|
108
|
+
);
|
|
109
|
+
}
|