@orderful/droid 0.15.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +18 -40
- package/AGENTS.md +75 -0
- package/CHANGELOG.md +25 -0
- package/dist/bin/droid.js +3064 -54
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.d.ts.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/Markdown.d.ts +5 -0
- package/dist/commands/tui/components/Markdown.d.ts.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/TabBar.d.ts +10 -0
- package/dist/commands/tui/components/TabBar.d.ts.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/ToolItem.d.ts +9 -0
- package/dist/commands/tui/components/ToolItem.d.ts.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/hooks/useAppUpdate.d.ts +13 -0
- package/dist/commands/tui/hooks/useAppUpdate.d.ts.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/types.d.ts +5 -0
- package/dist/commands/tui/types.d.ts.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/SetupScreen.d.ts +8 -0
- package/dist/commands/tui/views/SetupScreen.d.ts.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/ToolExplorer.d.ts +8 -0
- package/dist/commands/tui/views/ToolExplorer.d.ts.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/WelcomeScreen.d.ts +11 -0
- package/dist/commands/tui/views/WelcomeScreen.d.ts.map +1 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +952 -6
- package/dist/lib/agents.d.ts +1 -1
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/platforms.d.ts +1 -1
- package/dist/lib/platforms.d.ts.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/skills.d.ts +1 -1
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.d.ts.map +1 -1
- package/package.json +3 -3
- package/scripts/build.ts +78 -0
- 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
- package/dist/bin/droid.js.map +0 -1
- package/dist/commands/config.js +0 -67
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/install.js +0 -45
- package/dist/commands/install.js.map +0 -1
- package/dist/commands/setup.js +0 -269
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/skills.js +0 -144
- package/dist/commands/skills.js.map +0 -1
- package/dist/commands/tui.js +0 -1008
- package/dist/commands/tui.js.map +0 -1
- package/dist/commands/uninstall.js +0 -26
- package/dist/commands/uninstall.js.map +0 -1
- package/dist/commands/update.js +0 -45
- package/dist/commands/update.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/agents.js +0 -248
- package/dist/lib/agents.js.map +0 -1
- package/dist/lib/config.js +0 -196
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/platforms.js +0 -52
- package/dist/lib/platforms.js.map +0 -1
- package/dist/lib/quotes.js +0 -24
- package/dist/lib/quotes.js.map +0 -1
- package/dist/lib/skill-config.js +0 -80
- package/dist/lib/skill-config.js.map +0 -1
- package/dist/lib/skills.js +0 -582
- package/dist/lib/skills.js.map +0 -1
- package/dist/lib/tools.js +0 -145
- package/dist/lib/tools.js.map +0 -1
- package/dist/lib/types.js +0 -50
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/version.js +0 -100
- package/dist/lib/version.js.map +0 -1
package/src/commands/tui.tsx
CHANGED
|
@@ -1,1416 +1,32 @@
|
|
|
1
1
|
import { render, Box, Text, useInput, useApp } from 'ink';
|
|
2
|
-
import
|
|
3
|
-
import { useState, useMemo } from 'react';
|
|
4
|
-
import { execSync } from 'child_process';
|
|
5
|
-
import { existsSync, readFileSync } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
2
|
+
import { useState } from 'react';
|
|
7
3
|
import {
|
|
8
4
|
getBundledSkills,
|
|
9
|
-
isSkillInstalled,
|
|
10
5
|
installSkill,
|
|
11
6
|
uninstallSkill,
|
|
12
7
|
updateSkill,
|
|
13
|
-
|
|
14
|
-
} from '../lib/
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
8
|
+
} from '../lib/skills';
|
|
9
|
+
import { getBundledTools, isToolInstalled, getToolUpdateStatus } from '../lib/tools';
|
|
10
|
+
import { configExists, loadConfig } from '../lib/config';
|
|
11
|
+
import { Platform, type ConfigOption, type ToolManifest } from '../lib/types';
|
|
12
|
+
import { getVersion } from '../lib/version';
|
|
13
|
+
import { type Tab, type View } from './tui/types';
|
|
14
|
+
import { colors, MAX_VISIBLE_ITEMS } from './tui/constants';
|
|
15
|
+
import { TabBar } from './tui/components/TabBar';
|
|
16
|
+
import { ToolItem } from './tui/components/ToolItem';
|
|
17
|
+
import { ToolDetails } from './tui/components/ToolDetails';
|
|
18
|
+
import { SettingsDetails } from './tui/components/SettingsDetails';
|
|
19
|
+
import { WelcomeScreen } from './tui/views/WelcomeScreen';
|
|
20
|
+
import { ToolUpdatePrompt } from './tui/views/ToolUpdatePrompt';
|
|
21
|
+
import { SetupScreen } from './tui/views/SetupScreen';
|
|
22
|
+
import { ReadmeViewer } from './tui/views/ReadmeViewer';
|
|
23
|
+
import { ToolExplorer } from './tui/views/ToolExplorer';
|
|
24
|
+
import { SkillConfigScreen } from './tui/views/SkillConfigScreen';
|
|
25
|
+
import { useAppUpdate } from './tui/hooks/useAppUpdate';
|
|
26
|
+
import { useToolUpdates } from './tui/hooks/useToolUpdates';
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Module-level variable to store exit message (printed after leaving alternate screen)
|
|
26
|
-
let exitMessage: string | null = null;
|
|
27
|
-
type SetupStep = 'platform' | 'user_mention' | 'confirm';
|
|
28
|
-
type ComponentType = 'skill' | 'command' | 'agent';
|
|
29
|
-
|
|
30
|
-
const colors = {
|
|
31
|
-
primary: '#6366f1',
|
|
32
|
-
bgSelected: '#2d2d2d',
|
|
33
|
-
border: '#3a3a3a',
|
|
34
|
-
text: '#e8e8e8',
|
|
35
|
-
textMuted: '#999999',
|
|
36
|
-
textDim: '#6a6a6a',
|
|
37
|
-
success: '#4ade80',
|
|
38
|
-
error: '#f87171',
|
|
39
|
-
// Component type badges
|
|
40
|
-
skill: '#ec4899', // pink/magenta
|
|
41
|
-
command: '#22d3ee', // cyan
|
|
42
|
-
agent: '#fbbf24', // yellow/amber
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
function Badge({
|
|
46
|
-
type,
|
|
47
|
-
label,
|
|
48
|
-
isSelected = false,
|
|
49
|
-
dimmed = false,
|
|
50
|
-
}: {
|
|
51
|
-
type: ComponentType;
|
|
52
|
-
label?: string;
|
|
53
|
-
isSelected?: boolean;
|
|
54
|
-
dimmed?: boolean;
|
|
55
|
-
}) {
|
|
56
|
-
const color = colors[type];
|
|
57
|
-
const displayLabel = label || type.charAt(0).toUpperCase() + type.slice(1);
|
|
58
|
-
|
|
59
|
-
if (dimmed) {
|
|
60
|
-
return (
|
|
61
|
-
<Text color={colors.textDim}>{displayLabel}</Text>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<Text
|
|
67
|
-
backgroundColor={isSelected ? color : undefined}
|
|
68
|
-
color={isSelected ? '#000000' : color}
|
|
69
|
-
bold={isSelected}
|
|
70
|
-
>
|
|
71
|
-
{isSelected ? ` ${displayLabel} ` : displayLabel}
|
|
72
|
-
</Text>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function ComponentBadges({ tool, compact = false }: { tool: ToolManifest; compact?: boolean }) {
|
|
77
|
-
const hasSkills = tool.includes.skills.length > 0;
|
|
78
|
-
const hasCommands = tool.includes.commands.length > 0;
|
|
79
|
-
const hasAgents = tool.includes.agents.length > 0;
|
|
80
|
-
|
|
81
|
-
if (compact) {
|
|
82
|
-
// Show colored squares for list view (single char with spacing)
|
|
83
|
-
const parts: string[] = [];
|
|
84
|
-
if (hasSkills) parts.push('skill');
|
|
85
|
-
if (hasCommands) parts.push('command');
|
|
86
|
-
if (hasAgents) parts.push('agent');
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
<Box flexDirection="row">
|
|
90
|
-
{parts.map((type, i) => (
|
|
91
|
-
<Text key={type}>
|
|
92
|
-
<Text backgroundColor={colors[type as ComponentType]}> </Text>
|
|
93
|
-
{i < parts.length - 1 && ' '}
|
|
94
|
-
</Text>
|
|
95
|
-
))}
|
|
96
|
-
</Box>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return (
|
|
101
|
-
<Box flexDirection="row">
|
|
102
|
-
{hasSkills && (
|
|
103
|
-
<Box marginRight={1}>
|
|
104
|
-
<Text backgroundColor={colors.skill} color="#000000" bold>
|
|
105
|
-
{` ${tool.includes.skills.length} skill${tool.includes.skills.length > 1 ? 's' : ''} `}
|
|
106
|
-
</Text>
|
|
107
|
-
</Box>
|
|
108
|
-
)}
|
|
109
|
-
{hasCommands && (
|
|
110
|
-
<Box marginRight={1}>
|
|
111
|
-
<Text backgroundColor={colors.command} color="#000000" bold>
|
|
112
|
-
{` ${tool.includes.commands.length} cmd${tool.includes.commands.length > 1 ? 's' : ''} `}
|
|
113
|
-
</Text>
|
|
114
|
-
</Box>
|
|
115
|
-
)}
|
|
116
|
-
{hasAgents && (
|
|
117
|
-
<Box marginRight={1}>
|
|
118
|
-
<Text backgroundColor={colors.agent} color="#000000" bold>
|
|
119
|
-
{` ${tool.includes.agents.length} agent${tool.includes.agents.length > 1 ? 's' : ''} `}
|
|
120
|
-
</Text>
|
|
121
|
-
</Box>
|
|
122
|
-
)}
|
|
123
|
-
</Box>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function detectInstalledPlatforms(): Set<Platform> {
|
|
128
|
-
const installed = new Set<Platform>();
|
|
129
|
-
try {
|
|
130
|
-
execSync('claude --version', { stdio: 'ignore' });
|
|
131
|
-
installed.add(Platform.ClaudeCode);
|
|
132
|
-
} catch {
|
|
133
|
-
// Claude Code not found
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
execSync('opencode --version', { stdio: 'ignore' });
|
|
137
|
-
installed.add(Platform.OpenCode);
|
|
138
|
-
} catch {
|
|
139
|
-
// OpenCode not found
|
|
140
|
-
}
|
|
141
|
-
return installed;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function getDefaultPlatform(): Platform {
|
|
145
|
-
const installed = detectInstalledPlatforms();
|
|
146
|
-
if (installed.has(Platform.ClaudeCode)) return Platform.ClaudeCode;
|
|
147
|
-
if (installed.has(Platform.OpenCode)) return Platform.OpenCode;
|
|
148
|
-
return Platform.ClaudeCode;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function getOutputOptions(): Array<{ label: string; value: OutputPreference }> {
|
|
152
|
-
const options: Array<{ label: string; value: OutputPreference }> = [
|
|
153
|
-
{ label: 'Terminal (display in CLI)', value: BuiltInOutput.Terminal },
|
|
154
|
-
{ label: 'Editor ($EDITOR)', value: BuiltInOutput.Editor },
|
|
155
|
-
];
|
|
156
|
-
const skills = getBundledSkills();
|
|
157
|
-
for (const skill of skills) {
|
|
158
|
-
if (skill.provides_output) {
|
|
159
|
-
options.push({ label: `${skill.name} (${skill.description})`, value: skill.name });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return options;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
interface WelcomeScreenProps {
|
|
166
|
-
onContinue: () => void;
|
|
167
|
-
onUpdate: () => void;
|
|
168
|
-
onAlways: () => void;
|
|
169
|
-
onExit: () => void;
|
|
170
|
-
updateInfo: UpdateInfo;
|
|
171
|
-
isUpdating: boolean;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function WelcomeScreen({ onContinue, onUpdate, onAlways, onExit, updateInfo, isUpdating }: WelcomeScreenProps) {
|
|
175
|
-
const [selectedButton, setSelectedButton] = useState(0);
|
|
176
|
-
const welcomeQuote = useMemo(() => getRandomQuote(), []);
|
|
177
|
-
|
|
178
|
-
useInput((input, key) => {
|
|
179
|
-
if (isUpdating) return;
|
|
180
|
-
|
|
181
|
-
if (updateInfo.hasUpdate) {
|
|
182
|
-
if (key.leftArrow) {
|
|
183
|
-
setSelectedButton((prev) => Math.max(0, prev - 1));
|
|
184
|
-
}
|
|
185
|
-
if (key.rightArrow) {
|
|
186
|
-
setSelectedButton((prev) => Math.min(2, prev + 1));
|
|
187
|
-
}
|
|
188
|
-
if (key.return) {
|
|
189
|
-
if (selectedButton === 0) {
|
|
190
|
-
onUpdate();
|
|
191
|
-
} else if (selectedButton === 1) {
|
|
192
|
-
onAlways();
|
|
193
|
-
} else {
|
|
194
|
-
onContinue();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (input === 'q') {
|
|
198
|
-
onExit();
|
|
199
|
-
}
|
|
200
|
-
} else {
|
|
201
|
-
if (key.return) {
|
|
202
|
-
onContinue();
|
|
203
|
-
}
|
|
204
|
-
if (input === 'q') {
|
|
205
|
-
onExit();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const hasUpdate = updateInfo.hasUpdate && updateInfo.latestVersion;
|
|
211
|
-
|
|
212
|
-
return (
|
|
213
|
-
<Box flexDirection="column" alignItems="center" justifyContent="center" height={18}>
|
|
214
|
-
<Box
|
|
215
|
-
flexDirection="column"
|
|
216
|
-
alignItems="center"
|
|
217
|
-
borderStyle="single"
|
|
218
|
-
borderColor={hasUpdate ? '#eab308' : colors.border}
|
|
219
|
-
paddingX={4}
|
|
220
|
-
paddingY={1}
|
|
221
|
-
>
|
|
222
|
-
<Box flexDirection="column">
|
|
223
|
-
<Text>
|
|
224
|
-
<Text color={colors.textDim}>╔═════╗ </Text>
|
|
225
|
-
<Text color={colors.text}>droid</Text>
|
|
226
|
-
<Text color={colors.textDim}> v{updateInfo.currentVersion}</Text>
|
|
227
|
-
</Text>
|
|
228
|
-
<Text>
|
|
229
|
-
<Text color={colors.textDim}>║ </Text>
|
|
230
|
-
<Text color={hasUpdate ? '#eab308' : colors.primary}>●</Text>
|
|
231
|
-
<Text color={colors.textDim}> </Text>
|
|
232
|
-
<Text color={hasUpdate ? '#eab308' : colors.primary}>●</Text>
|
|
233
|
-
<Text color={colors.textDim}> ║ </Text>
|
|
234
|
-
<Text color={colors.textMuted}>Droid, teaching your AI new tricks</Text>
|
|
235
|
-
</Text>
|
|
236
|
-
<Text>
|
|
237
|
-
<Text color={colors.textDim}>╚═╦═╦═╝ </Text>
|
|
238
|
-
<Text color={colors.textDim}>github.com/Orderful/droid</Text>
|
|
239
|
-
</Text>
|
|
240
|
-
</Box>
|
|
241
|
-
|
|
242
|
-
{hasUpdate ? (
|
|
243
|
-
<>
|
|
244
|
-
<Box marginTop={2} marginBottom={1} flexDirection="column" alignItems="center">
|
|
245
|
-
<Text color="#eab308" italic>
|
|
246
|
-
"The odds of functioning optimally without this
|
|
247
|
-
</Text>
|
|
248
|
-
<Text color="#eab308" italic>
|
|
249
|
-
update are approximately 3,720 to 1."
|
|
250
|
-
</Text>
|
|
251
|
-
</Box>
|
|
252
|
-
|
|
253
|
-
<Box marginBottom={1}>
|
|
254
|
-
<Text color={colors.textMuted}>
|
|
255
|
-
v{updateInfo.currentVersion} → v{updateInfo.latestVersion}
|
|
256
|
-
</Text>
|
|
257
|
-
</Box>
|
|
258
|
-
|
|
259
|
-
{isUpdating ? (
|
|
260
|
-
<Text color="#eab308">Updating...</Text>
|
|
261
|
-
) : (
|
|
262
|
-
<Box flexDirection="row">
|
|
263
|
-
<Text
|
|
264
|
-
backgroundColor={selectedButton === 0 ? '#eab308' : colors.bgSelected}
|
|
265
|
-
color={selectedButton === 0 ? '#000000' : colors.textMuted}
|
|
266
|
-
bold={selectedButton === 0}
|
|
267
|
-
>
|
|
268
|
-
{' '}Update{' '}
|
|
269
|
-
</Text>
|
|
270
|
-
<Text> </Text>
|
|
271
|
-
<Text
|
|
272
|
-
backgroundColor={selectedButton === 1 ? '#eab308' : colors.bgSelected}
|
|
273
|
-
color={selectedButton === 1 ? '#000000' : colors.textMuted}
|
|
274
|
-
bold={selectedButton === 1}
|
|
275
|
-
>
|
|
276
|
-
{' '}Always{' '}
|
|
277
|
-
</Text>
|
|
278
|
-
<Text> </Text>
|
|
279
|
-
<Text
|
|
280
|
-
backgroundColor={selectedButton === 2 ? colors.bgSelected : undefined}
|
|
281
|
-
color={selectedButton === 2 ? colors.text : colors.textMuted}
|
|
282
|
-
bold={selectedButton === 2}
|
|
283
|
-
>
|
|
284
|
-
{' '}Skip{' '}
|
|
285
|
-
</Text>
|
|
286
|
-
</Box>
|
|
287
|
-
)}
|
|
288
|
-
|
|
289
|
-
<Box marginTop={1}>
|
|
290
|
-
<Text color={colors.textDim}>←→ select · enter · "Always" enables auto-update</Text>
|
|
291
|
-
</Box>
|
|
292
|
-
</>
|
|
293
|
-
) : (
|
|
294
|
-
<>
|
|
295
|
-
<Box marginTop={2} marginBottom={1}>
|
|
296
|
-
<Text backgroundColor={colors.primary} color="#ffffff" bold>
|
|
297
|
-
{` ${welcomeQuote} `}
|
|
298
|
-
</Text>
|
|
299
|
-
</Box>
|
|
300
|
-
|
|
301
|
-
<Text color={colors.textDim}>press enter</Text>
|
|
302
|
-
</>
|
|
303
|
-
)}
|
|
304
|
-
</Box>
|
|
305
|
-
</Box>
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
interface ToolUpdatePromptProps {
|
|
310
|
-
toolUpdates: ToolUpdateInfo[];
|
|
311
|
-
onUpdateAll: () => void;
|
|
312
|
-
onAlways: () => void;
|
|
313
|
-
onSkip: () => void;
|
|
314
|
-
isUpdating: boolean;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function ToolUpdatePrompt({ toolUpdates, onUpdateAll, onAlways, onSkip, isUpdating }: ToolUpdatePromptProps) {
|
|
318
|
-
const [selectedButton, setSelectedButton] = useState(0);
|
|
319
|
-
|
|
320
|
-
useInput((input, key) => {
|
|
321
|
-
if (isUpdating) return;
|
|
322
|
-
|
|
323
|
-
if (key.leftArrow) {
|
|
324
|
-
setSelectedButton((prev) => Math.max(0, prev - 1));
|
|
325
|
-
}
|
|
326
|
-
if (key.rightArrow) {
|
|
327
|
-
setSelectedButton((prev) => Math.min(2, prev + 1));
|
|
328
|
-
}
|
|
329
|
-
if (key.return) {
|
|
330
|
-
if (selectedButton === 0) {
|
|
331
|
-
onUpdateAll();
|
|
332
|
-
} else if (selectedButton === 1) {
|
|
333
|
-
onAlways();
|
|
334
|
-
} else {
|
|
335
|
-
onSkip();
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
if (input === 'q') {
|
|
339
|
-
onSkip();
|
|
340
|
-
}
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
const buttons = [
|
|
344
|
-
{ label: 'Update All', action: onUpdateAll },
|
|
345
|
-
{ label: 'Always', action: onAlways },
|
|
346
|
-
{ label: 'Skip', action: onSkip },
|
|
347
|
-
];
|
|
348
|
-
|
|
349
|
-
return (
|
|
350
|
-
<Box flexDirection="column" alignItems="center" justifyContent="center" height={18}>
|
|
351
|
-
<Box
|
|
352
|
-
flexDirection="column"
|
|
353
|
-
alignItems="center"
|
|
354
|
-
borderStyle="single"
|
|
355
|
-
borderColor={colors.primary}
|
|
356
|
-
paddingX={4}
|
|
357
|
-
paddingY={1}
|
|
358
|
-
>
|
|
359
|
-
<Box flexDirection="column">
|
|
360
|
-
<Text>
|
|
361
|
-
<Text color={colors.textDim}>╔═════╗ </Text>
|
|
362
|
-
<Text color={colors.text}>Tool Updates</Text>
|
|
363
|
-
</Text>
|
|
364
|
-
<Text>
|
|
365
|
-
<Text color={colors.textDim}>║ </Text>
|
|
366
|
-
<Text color={colors.primary}>●</Text>
|
|
367
|
-
<Text color={colors.textDim}> </Text>
|
|
368
|
-
<Text color={colors.primary}>●</Text>
|
|
369
|
-
<Text color={colors.textDim}> ║ </Text>
|
|
370
|
-
<Text color={colors.textMuted}>{toolUpdates.length} tool{toolUpdates.length > 1 ? 's have' : ' has'} updates available</Text>
|
|
371
|
-
</Text>
|
|
372
|
-
<Text>
|
|
373
|
-
<Text color={colors.textDim}>╚═╦═╦═╝</Text>
|
|
374
|
-
</Text>
|
|
375
|
-
</Box>
|
|
376
|
-
|
|
377
|
-
<Box marginTop={1} marginBottom={1} flexDirection="column">
|
|
378
|
-
{toolUpdates.slice(0, 5).map((tool) => (
|
|
379
|
-
<Text key={tool.name} color={colors.textMuted}>
|
|
380
|
-
<Text color={colors.primary}>↑</Text> {tool.name}
|
|
381
|
-
<Text color={colors.textDim}> {tool.installedVersion} → {tool.bundledVersion}</Text>
|
|
382
|
-
</Text>
|
|
383
|
-
))}
|
|
384
|
-
{toolUpdates.length > 5 && (
|
|
385
|
-
<Text color={colors.textDim}>...and {toolUpdates.length - 5} more</Text>
|
|
386
|
-
)}
|
|
387
|
-
</Box>
|
|
388
|
-
|
|
389
|
-
{isUpdating ? (
|
|
390
|
-
<Text color={colors.primary}>Updating tools...</Text>
|
|
391
|
-
) : (
|
|
392
|
-
<Box flexDirection="row">
|
|
393
|
-
{buttons.map((button, index) => (
|
|
394
|
-
<Text
|
|
395
|
-
key={button.label}
|
|
396
|
-
backgroundColor={selectedButton === index ? colors.primary : colors.bgSelected}
|
|
397
|
-
color={selectedButton === index ? '#000000' : colors.textMuted}
|
|
398
|
-
bold={selectedButton === index}
|
|
399
|
-
>
|
|
400
|
-
{' '}{button.label}{' '}
|
|
401
|
-
</Text>
|
|
402
|
-
))}
|
|
403
|
-
</Box>
|
|
404
|
-
)}
|
|
405
|
-
|
|
406
|
-
<Box marginTop={1}>
|
|
407
|
-
<Text color={colors.textDim}>←→ select · enter · "Always" enables auto-update</Text>
|
|
408
|
-
</Box>
|
|
409
|
-
</Box>
|
|
410
|
-
</Box>
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
interface SetupScreenProps {
|
|
415
|
-
onComplete: () => void;
|
|
416
|
-
onSkip: () => void;
|
|
417
|
-
initialConfig?: DroidConfig;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function SetupScreen({ onComplete, onSkip, initialConfig }: SetupScreenProps) {
|
|
421
|
-
const [step, setStep] = useState<SetupStep>('platform');
|
|
422
|
-
const [platform, setPlatform] = useState<Platform>(
|
|
423
|
-
initialConfig?.platform || getDefaultPlatform()
|
|
424
|
-
);
|
|
425
|
-
const [userMention, setUserMention] = useState(initialConfig?.user_mention || '@user');
|
|
426
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
427
|
-
const [error, setError] = useState<string | null>(null);
|
|
428
|
-
|
|
429
|
-
// Cache detection results once on mount (avoids re-running execSync on every render)
|
|
430
|
-
const installedPlatforms = useMemo(() => detectInstalledPlatforms(), []);
|
|
431
|
-
const platformOptions = useMemo(() => [
|
|
432
|
-
{ label: 'Claude Code', value: Platform.ClaudeCode },
|
|
433
|
-
{ label: 'OpenCode', value: Platform.OpenCode },
|
|
434
|
-
], []);
|
|
435
|
-
|
|
436
|
-
const steps: SetupStep[] = ['platform', 'user_mention', 'confirm'];
|
|
437
|
-
const stepIndex = steps.indexOf(step);
|
|
438
|
-
const totalSteps = steps.length - 1; // Don't count confirm as a step
|
|
439
|
-
|
|
440
|
-
const handleUserMentionSubmit = () => {
|
|
441
|
-
// Auto-add @ prefix if missing
|
|
442
|
-
const mention = userMention.startsWith('@') ? userMention : `@${userMention}`;
|
|
443
|
-
setUserMention(mention);
|
|
444
|
-
setError(null);
|
|
445
|
-
setStep('confirm');
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
// Handle escape during text input (only intercept escape, nothing else)
|
|
449
|
-
useInput((input, key) => {
|
|
450
|
-
if (key.escape) {
|
|
451
|
-
setStep('platform');
|
|
452
|
-
setSelectedIndex(0);
|
|
453
|
-
}
|
|
454
|
-
}, { isActive: step === 'user_mention' });
|
|
455
|
-
|
|
456
|
-
// Handle all input for non-text-input steps
|
|
457
|
-
useInput((input, key) => {
|
|
458
|
-
if (key.escape) {
|
|
459
|
-
if (step === 'platform') {
|
|
460
|
-
onSkip();
|
|
461
|
-
} else if (step === 'confirm') {
|
|
462
|
-
setStep('user_mention');
|
|
463
|
-
}
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (step === 'platform') {
|
|
468
|
-
if (key.upArrow) setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
469
|
-
if (key.downArrow) setSelectedIndex((prev) => Math.min(platformOptions.length - 1, prev + 1));
|
|
470
|
-
if (key.return) {
|
|
471
|
-
setPlatform(platformOptions[selectedIndex].value);
|
|
472
|
-
setStep('user_mention');
|
|
473
|
-
}
|
|
474
|
-
} else if (step === 'confirm') {
|
|
475
|
-
if (key.return) {
|
|
476
|
-
const existingConfig = loadConfig();
|
|
477
|
-
const config: DroidConfig = {
|
|
478
|
-
...existingConfig,
|
|
479
|
-
platform: platform,
|
|
480
|
-
user_mention: userMention,
|
|
481
|
-
output_preference: BuiltInOutput.Terminal, // Default to terminal
|
|
482
|
-
};
|
|
483
|
-
saveConfig(config);
|
|
484
|
-
configurePlatformPermissions(platform);
|
|
485
|
-
onComplete();
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}, { isActive: step !== 'user_mention' });
|
|
489
|
-
|
|
490
|
-
const renderHeader = () => (
|
|
491
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
492
|
-
<Text>
|
|
493
|
-
<Text color={colors.textDim}>[</Text>
|
|
494
|
-
<Text color={colors.primary}>●</Text>
|
|
495
|
-
<Text color={colors.textDim}> </Text>
|
|
496
|
-
<Text color={colors.primary}>●</Text>
|
|
497
|
-
<Text color={colors.textDim}>] </Text>
|
|
498
|
-
<Text color={colors.text} bold>droid setup</Text>
|
|
499
|
-
<Text color={colors.textDim}> · Step {Math.min(stepIndex + 1, totalSteps)} of {totalSteps}</Text>
|
|
500
|
-
</Text>
|
|
501
|
-
</Box>
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
if (step === 'platform') {
|
|
505
|
-
return (
|
|
506
|
-
<Box flexDirection="column" padding={1}>
|
|
507
|
-
{renderHeader()}
|
|
508
|
-
<Text color={colors.text}>Which platform are you using?</Text>
|
|
509
|
-
<Box flexDirection="column" marginTop={1}>
|
|
510
|
-
{platformOptions.map((option, index) => (
|
|
511
|
-
<Text key={option.value}>
|
|
512
|
-
<Text color={colors.textDim}>{index === selectedIndex ? '>' : ' '} </Text>
|
|
513
|
-
<Text color={index === selectedIndex ? colors.text : colors.textMuted}>{option.label}</Text>
|
|
514
|
-
{installedPlatforms.has(option.value) && <Text color={colors.success}> (detected)</Text>}
|
|
515
|
-
</Text>
|
|
516
|
-
))}
|
|
517
|
-
</Box>
|
|
518
|
-
<Box marginTop={1}>
|
|
519
|
-
<Text color={colors.textDim}>↑↓ select · enter next · esc skip</Text>
|
|
520
|
-
</Box>
|
|
521
|
-
</Box>
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (step === 'user_mention') {
|
|
526
|
-
return (
|
|
527
|
-
<Box flexDirection="column" padding={1}>
|
|
528
|
-
{renderHeader()}
|
|
529
|
-
<Text color={colors.text}>What @mention should be used for you?</Text>
|
|
530
|
-
<Box marginTop={1}>
|
|
531
|
-
<Text color={colors.textDim}>{'> '}</Text>
|
|
532
|
-
<TextInput
|
|
533
|
-
value={userMention}
|
|
534
|
-
onChange={setUserMention}
|
|
535
|
-
onSubmit={handleUserMentionSubmit}
|
|
536
|
-
placeholder="@user"
|
|
537
|
-
/>
|
|
538
|
-
</Box>
|
|
539
|
-
{error && (
|
|
540
|
-
<Box marginTop={1}>
|
|
541
|
-
<Text color={colors.error}>{error}</Text>
|
|
542
|
-
</Box>
|
|
543
|
-
)}
|
|
544
|
-
<Box marginTop={1}>
|
|
545
|
-
<Text color={colors.textDim}>enter next · esc back</Text>
|
|
546
|
-
</Box>
|
|
547
|
-
</Box>
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// Confirm step
|
|
552
|
-
return (
|
|
553
|
-
<Box flexDirection="column" padding={1}>
|
|
554
|
-
{renderHeader()}
|
|
555
|
-
<Text color={colors.text} bold>Review your settings</Text>
|
|
556
|
-
<Box flexDirection="column" marginTop={1}>
|
|
557
|
-
<Text>
|
|
558
|
-
<Text color={colors.textDim}>Platform: </Text>
|
|
559
|
-
<Text color={colors.text}>{platform === Platform.ClaudeCode ? 'Claude Code' : 'OpenCode'}</Text>
|
|
560
|
-
</Text>
|
|
561
|
-
<Text>
|
|
562
|
-
<Text color={colors.textDim}>Your @mention: </Text>
|
|
563
|
-
<Text color={colors.text}>{userMention}</Text>
|
|
564
|
-
</Text>
|
|
565
|
-
</Box>
|
|
566
|
-
<Box marginTop={2}>
|
|
567
|
-
<Text backgroundColor={colors.primary} color="#ffffff" bold>
|
|
568
|
-
{' '}Save{' '}
|
|
569
|
-
</Text>
|
|
570
|
-
</Box>
|
|
571
|
-
<Box marginTop={1}>
|
|
572
|
-
<Text color={colors.textDim}>enter save · esc back</Text>
|
|
573
|
-
</Box>
|
|
574
|
-
</Box>
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function TabBar({ tabs, activeTab }: { tabs: { id: Tab; label: string }[]; activeTab: Tab }) {
|
|
579
|
-
return (
|
|
580
|
-
<Box flexDirection="row" flexWrap="wrap">
|
|
581
|
-
{tabs.map((tab) => (
|
|
582
|
-
<Text
|
|
583
|
-
key={tab.id}
|
|
584
|
-
backgroundColor={tab.id === activeTab ? colors.primary : undefined}
|
|
585
|
-
color={tab.id === activeTab ? '#ffffff' : colors.textMuted}
|
|
586
|
-
bold={tab.id === activeTab}
|
|
587
|
-
wrap="truncate"
|
|
588
|
-
>
|
|
589
|
-
{' '}{tab.label}{' '}
|
|
590
|
-
</Text>
|
|
591
|
-
))}
|
|
592
|
-
</Box>
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function ToolItem({
|
|
597
|
-
tool,
|
|
598
|
-
isSelected,
|
|
599
|
-
isActive,
|
|
600
|
-
wasAutoUpdated,
|
|
601
|
-
}: {
|
|
602
|
-
tool: ToolManifest;
|
|
603
|
-
isSelected: boolean;
|
|
604
|
-
isActive: boolean;
|
|
605
|
-
wasAutoUpdated?: boolean;
|
|
606
|
-
}) {
|
|
607
|
-
const installed = isToolInstalled(tool.name);
|
|
608
|
-
const installedVersion = getInstalledToolVersion(tool.name);
|
|
609
|
-
const updateStatus = getToolUpdateStatus(tool.name);
|
|
610
|
-
|
|
611
|
-
return (
|
|
612
|
-
<Box paddingX={1} backgroundColor={isActive ? colors.bgSelected : undefined}>
|
|
613
|
-
<Text wrap="truncate">
|
|
614
|
-
<Text color={colors.textDim}>{isSelected ? '>' : ' '} </Text>
|
|
615
|
-
<Text color={isSelected || isActive ? colors.text : colors.textMuted}>{tool.name}</Text>
|
|
616
|
-
{installed && installedVersion && <Text color={colors.textDim}> v{installedVersion}</Text>}
|
|
617
|
-
{installed && <Text color={colors.success}> ✓</Text>}
|
|
618
|
-
{wasAutoUpdated && <Text color={colors.success}> ↑</Text>}
|
|
619
|
-
{updateStatus.hasUpdate && !wasAutoUpdated && <Text color={colors.primary}> ↑</Text>}
|
|
620
|
-
<Text> </Text>
|
|
621
|
-
{tool.includes.skills.length > 0 && <Text color={colors.skill}>● </Text>}
|
|
622
|
-
{tool.includes.commands.length > 0 && <Text color={colors.command}>● </Text>}
|
|
623
|
-
{tool.includes.agents.length > 0 && <Text color={colors.agent}>●</Text>}
|
|
624
|
-
</Text>
|
|
625
|
-
</Box>
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
function ToolDetails({
|
|
630
|
-
tool,
|
|
631
|
-
isFocused,
|
|
632
|
-
selectedAction,
|
|
633
|
-
}: {
|
|
634
|
-
tool: ToolManifest | null;
|
|
635
|
-
isFocused: boolean;
|
|
636
|
-
selectedAction: number;
|
|
637
|
-
}) {
|
|
638
|
-
if (!tool) {
|
|
639
|
-
return (
|
|
640
|
-
<Box paddingLeft={2} flexGrow={1}>
|
|
641
|
-
<Text color={colors.textDim}>Select a tool</Text>
|
|
642
|
-
</Box>
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
const installed = isToolInstalled(tool.name);
|
|
647
|
-
const installedVersion = getInstalledToolVersion(tool.name);
|
|
648
|
-
const updateStatus = getToolUpdateStatus(tool.name);
|
|
649
|
-
const isSystemTool = (tool as ToolManifest & { system?: boolean }).system === true;
|
|
650
|
-
|
|
651
|
-
const actions = installed
|
|
652
|
-
? [
|
|
653
|
-
{ id: 'explore', label: 'Explore', variant: 'default' },
|
|
654
|
-
...(updateStatus.hasUpdate
|
|
655
|
-
? [{ id: 'update', label: `Update (${updateStatus.bundledVersion})`, variant: 'primary' }]
|
|
656
|
-
: []),
|
|
657
|
-
{ id: 'configure', label: 'Configure', variant: 'default' },
|
|
658
|
-
// System tools can't be uninstalled
|
|
659
|
-
...(!isSystemTool ? [{ id: 'uninstall', label: 'Uninstall', variant: 'danger' }] : []),
|
|
660
|
-
]
|
|
661
|
-
: [
|
|
662
|
-
{ id: 'explore', label: 'Explore', variant: 'default' },
|
|
663
|
-
// System tools auto-install, but show Install for manual trigger if needed
|
|
664
|
-
{ id: 'install', label: 'Install', variant: 'primary' },
|
|
665
|
-
];
|
|
666
|
-
|
|
667
|
-
return (
|
|
668
|
-
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
669
|
-
<Text color={colors.text} bold>{tool.name}</Text>
|
|
670
|
-
|
|
671
|
-
<Box marginTop={1}>
|
|
672
|
-
<Text color={colors.textDim}>
|
|
673
|
-
{tool.version}
|
|
674
|
-
{tool.status && ` · ${tool.status}`}
|
|
675
|
-
{installed && <Text color={colors.success}> · installed</Text>}
|
|
676
|
-
{updateStatus.hasUpdate && (
|
|
677
|
-
<Text color={colors.primary}> · update ({installedVersion} → {updateStatus.bundledVersion})</Text>
|
|
678
|
-
)}
|
|
679
|
-
</Text>
|
|
680
|
-
</Box>
|
|
681
|
-
|
|
682
|
-
<Box marginTop={1}>
|
|
683
|
-
<Text color={colors.textMuted}>{tool.description}</Text>
|
|
684
|
-
</Box>
|
|
685
|
-
|
|
686
|
-
{/* Colored component badges */}
|
|
687
|
-
<Box flexDirection="column" marginTop={1}>
|
|
688
|
-
<Text color={colors.textDim}>Includes:</Text>
|
|
689
|
-
<Box marginTop={1}>
|
|
690
|
-
<ComponentBadges tool={tool} />
|
|
691
|
-
</Box>
|
|
692
|
-
{/* Show names below badges */}
|
|
693
|
-
<Box flexDirection="column" marginTop={1} marginLeft={1}>
|
|
694
|
-
{tool.includes.skills.length > 0 && (
|
|
695
|
-
<Text>
|
|
696
|
-
<Text color={colors.skill}>Skills: </Text>
|
|
697
|
-
<Text color={colors.textMuted}>{tool.includes.skills.map(s => s.name).join(', ')}</Text>
|
|
698
|
-
</Text>
|
|
699
|
-
)}
|
|
700
|
-
{tool.includes.commands.length > 0 && (
|
|
701
|
-
<Text>
|
|
702
|
-
<Text color={colors.command}>Commands: </Text>
|
|
703
|
-
<Text color={colors.textMuted}>{tool.includes.commands.map(c => `/${c}`).join(', ')}</Text>
|
|
704
|
-
</Text>
|
|
705
|
-
)}
|
|
706
|
-
{tool.includes.agents.length > 0 && (
|
|
707
|
-
<Text>
|
|
708
|
-
<Text color={colors.agent}>Agents: </Text>
|
|
709
|
-
<Text color={colors.textMuted}>{tool.includes.agents.join(', ')}</Text>
|
|
710
|
-
</Text>
|
|
711
|
-
)}
|
|
712
|
-
</Box>
|
|
713
|
-
</Box>
|
|
714
|
-
|
|
715
|
-
{isFocused && (
|
|
716
|
-
<Box flexDirection="row" marginTop={1}>
|
|
717
|
-
{actions.map((action, index) => (
|
|
718
|
-
<Text
|
|
719
|
-
key={action.id}
|
|
720
|
-
backgroundColor={
|
|
721
|
-
selectedAction === index
|
|
722
|
-
? action.variant === 'danger'
|
|
723
|
-
? colors.error
|
|
724
|
-
: colors.primary
|
|
725
|
-
: colors.bgSelected
|
|
726
|
-
}
|
|
727
|
-
color={selectedAction === index ? '#ffffff' : colors.textMuted}
|
|
728
|
-
bold={selectedAction === index}
|
|
729
|
-
>
|
|
730
|
-
{' '}{action.label}{' '}
|
|
731
|
-
</Text>
|
|
732
|
-
))}
|
|
733
|
-
</Box>
|
|
734
|
-
)}
|
|
735
|
-
</Box>
|
|
736
|
-
);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
function MarkdownLine({ line, inCodeBlock }: { line: string; inCodeBlock: boolean }) {
|
|
740
|
-
// Code block content
|
|
741
|
-
if (inCodeBlock) {
|
|
742
|
-
return <Text color="#a5d6ff">{line || ' '}</Text>;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Code block delimiter
|
|
746
|
-
if (line.startsWith('```')) {
|
|
747
|
-
return <Text color={colors.textDim}>{line}</Text>;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// Headers
|
|
751
|
-
if (line.startsWith('# ')) {
|
|
752
|
-
return <Text color={colors.text} bold>{line.slice(2)}</Text>;
|
|
753
|
-
}
|
|
754
|
-
if (line.startsWith('## ')) {
|
|
755
|
-
return <Text color={colors.text} bold>{line.slice(3)}</Text>;
|
|
756
|
-
}
|
|
757
|
-
if (line.startsWith('### ')) {
|
|
758
|
-
return <Text color="#c9d1d9" bold>{line.slice(4)}</Text>;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// YAML frontmatter delimiter
|
|
762
|
-
if (line === '---') {
|
|
763
|
-
return <Text color={colors.textDim}>{line}</Text>;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// List items
|
|
767
|
-
if (line.match(/^[\s]*[-*]\s/)) {
|
|
768
|
-
return <Text color={colors.textMuted}>{line}</Text>;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Blockquotes
|
|
772
|
-
if (line.startsWith('>')) {
|
|
773
|
-
return <Text color="#8b949e" italic>{line}</Text>;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// Table rows
|
|
777
|
-
if (line.includes('|')) {
|
|
778
|
-
return <Text color={colors.textMuted}>{line}</Text>;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// Default
|
|
782
|
-
return <Text color={colors.textMuted}>{line || ' '}</Text>;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function ReadmeViewer({
|
|
786
|
-
title,
|
|
787
|
-
content,
|
|
788
|
-
onClose,
|
|
789
|
-
}: {
|
|
790
|
-
title: string;
|
|
791
|
-
content: string;
|
|
792
|
-
onClose: () => void;
|
|
793
|
-
}) {
|
|
794
|
-
const [scrollOffset, setScrollOffset] = useState(0);
|
|
795
|
-
const lines = useMemo(() => content.split('\n'), [content]);
|
|
796
|
-
const maxVisible = 20;
|
|
797
|
-
|
|
798
|
-
// Pre-compute code block state for each line
|
|
799
|
-
const lineStates = useMemo(() => {
|
|
800
|
-
const states: boolean[] = [];
|
|
801
|
-
let inCode = false;
|
|
802
|
-
for (const line of lines) {
|
|
803
|
-
if (line.startsWith('```')) {
|
|
804
|
-
states.push(false); // Delimiter itself is not "in" code block for styling
|
|
805
|
-
inCode = !inCode;
|
|
806
|
-
} else {
|
|
807
|
-
states.push(inCode);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return states;
|
|
811
|
-
}, [lines]);
|
|
812
|
-
|
|
813
|
-
// Max offset: when at end, we have top indicator + (maxVisible-1) content lines
|
|
814
|
-
// So max offset is lines.length - (maxVisible - 1) = lines.length - maxVisible + 1
|
|
815
|
-
const maxOffset = Math.max(0, lines.length - maxVisible + 1);
|
|
816
|
-
|
|
817
|
-
useInput((input, key) => {
|
|
818
|
-
if (key.escape) {
|
|
819
|
-
onClose();
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
if (key.upArrow) {
|
|
823
|
-
setScrollOffset((prev) => Math.max(0, prev - 1));
|
|
824
|
-
}
|
|
825
|
-
if (key.downArrow) {
|
|
826
|
-
setScrollOffset((prev) => Math.min(maxOffset, prev + 1));
|
|
827
|
-
}
|
|
828
|
-
if (key.pageDown || input === ' ') {
|
|
829
|
-
setScrollOffset((prev) => Math.min(maxOffset, prev + maxVisible));
|
|
830
|
-
}
|
|
831
|
-
if (key.pageUp) {
|
|
832
|
-
setScrollOffset((prev) => Math.max(0, prev - maxVisible));
|
|
833
|
-
}
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
// Adjust visible lines based on whether indicators are shown
|
|
837
|
-
const showTopIndicator = scrollOffset > 0;
|
|
838
|
-
// Reserve space for bottom indicator if not at end
|
|
839
|
-
const contentLines = maxVisible - (showTopIndicator ? 1 : 0);
|
|
840
|
-
const endIndex = Math.min(scrollOffset + contentLines, lines.length);
|
|
841
|
-
const showBottomIndicator = endIndex < lines.length;
|
|
842
|
-
const actualContentLines = contentLines - (showBottomIndicator ? 1 : 0);
|
|
843
|
-
const visibleLines = lines.slice(scrollOffset, scrollOffset + actualContentLines);
|
|
844
|
-
|
|
845
|
-
return (
|
|
846
|
-
<Box flexDirection="column" padding={1}>
|
|
847
|
-
<Box marginBottom={1}>
|
|
848
|
-
<Text color={colors.text} bold>{title}</Text>
|
|
849
|
-
<Text color={colors.textDim}> · {lines.length} lines</Text>
|
|
850
|
-
</Box>
|
|
851
|
-
|
|
852
|
-
<Box
|
|
853
|
-
flexDirection="column"
|
|
854
|
-
borderStyle="single"
|
|
855
|
-
borderColor={colors.border}
|
|
856
|
-
paddingX={1}
|
|
857
|
-
>
|
|
858
|
-
{showTopIndicator && (
|
|
859
|
-
<Text color={colors.textDim}>↑ {scrollOffset} more lines</Text>
|
|
860
|
-
)}
|
|
861
|
-
{visibleLines.map((line, i) => (
|
|
862
|
-
<MarkdownLine key={scrollOffset + i} line={line} inCodeBlock={lineStates[scrollOffset + i]} />
|
|
863
|
-
))}
|
|
864
|
-
{showBottomIndicator && (
|
|
865
|
-
<Text color={colors.textDim}>↓ {lines.length - scrollOffset - actualContentLines} more lines</Text>
|
|
866
|
-
)}
|
|
867
|
-
</Box>
|
|
868
|
-
|
|
869
|
-
<Box marginTop={1}>
|
|
870
|
-
<Text color={colors.textDim}>↑↓ scroll · space/pgdn page · esc back</Text>
|
|
871
|
-
</Box>
|
|
872
|
-
</Box>
|
|
873
|
-
);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
interface ExplorerItem {
|
|
877
|
-
type: ComponentType;
|
|
878
|
-
name: string;
|
|
879
|
-
path: string;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
interface ToolExplorerProps {
|
|
883
|
-
tool: ToolManifest;
|
|
884
|
-
onViewSource: (title: string, content: string) => void;
|
|
885
|
-
onClose: () => void;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
function ToolExplorer({ tool, onViewSource, onClose }: ToolExplorerProps) {
|
|
889
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
890
|
-
|
|
891
|
-
// Build list of all explorable items
|
|
892
|
-
const items: ExplorerItem[] = useMemo(() => {
|
|
893
|
-
const result: ExplorerItem[] = [];
|
|
894
|
-
const toolDir = getBundledToolsDir();
|
|
895
|
-
|
|
896
|
-
// Add skills
|
|
897
|
-
for (const skill of tool.includes.skills) {
|
|
898
|
-
result.push({
|
|
899
|
-
type: 'skill',
|
|
900
|
-
name: skill.name,
|
|
901
|
-
path: join(toolDir, tool.name, 'skills', skill.name, 'SKILL.md'),
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
// Add commands
|
|
906
|
-
for (const cmd of tool.includes.commands) {
|
|
907
|
-
result.push({
|
|
908
|
-
type: 'command',
|
|
909
|
-
name: `/${cmd}`,
|
|
910
|
-
path: join(toolDir, tool.name, 'commands', `${cmd}.md`),
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
for (const agent of tool.includes.agents) {
|
|
915
|
-
result.push({
|
|
916
|
-
type: 'agent',
|
|
917
|
-
name: agent,
|
|
918
|
-
path: join(toolDir, tool.name, 'agents', `${agent}.md`),
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
return result;
|
|
923
|
-
}, [tool]);
|
|
924
|
-
|
|
925
|
-
useInput((input, key) => {
|
|
926
|
-
if (key.escape) {
|
|
927
|
-
onClose();
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (key.leftArrow || key.upArrow) {
|
|
932
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
933
|
-
}
|
|
934
|
-
if (key.rightArrow || key.downArrow) {
|
|
935
|
-
setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
if (key.return && items.length > 0) {
|
|
939
|
-
const item = items[selectedIndex];
|
|
940
|
-
if (existsSync(item.path)) {
|
|
941
|
-
const content = readFileSync(item.path, 'utf-8');
|
|
942
|
-
onViewSource(`${tool.name} / ${item.name}`, content);
|
|
943
|
-
} else {
|
|
944
|
-
// Try YAML fallback for skills/agents
|
|
945
|
-
const yamlPath = item.path.replace('.md', '.yaml');
|
|
946
|
-
if (existsSync(yamlPath)) {
|
|
947
|
-
const content = readFileSync(yamlPath, 'utf-8');
|
|
948
|
-
onViewSource(`${tool.name} / ${item.name}`, content);
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
// Group items by type for display
|
|
955
|
-
const skillItems = items.filter(i => i.type === 'skill');
|
|
956
|
-
const commandItems = items.filter(i => i.type === 'command');
|
|
957
|
-
const agentItems = items.filter(i => i.type === 'agent');
|
|
958
|
-
|
|
959
|
-
const getItemIndex = (item: ExplorerItem) => items.indexOf(item);
|
|
960
|
-
|
|
961
|
-
return (
|
|
962
|
-
<Box flexDirection="column" padding={1}>
|
|
963
|
-
<Box marginBottom={1}>
|
|
964
|
-
<Text>
|
|
965
|
-
<Text color={colors.textDim}>[</Text>
|
|
966
|
-
<Text color={colors.primary}>●</Text>
|
|
967
|
-
<Text color={colors.textDim}> </Text>
|
|
968
|
-
<Text color={colors.primary}>●</Text>
|
|
969
|
-
<Text color={colors.textDim}>] </Text>
|
|
970
|
-
<Text color={colors.text} bold>{tool.name}</Text>
|
|
971
|
-
</Text>
|
|
972
|
-
</Box>
|
|
973
|
-
|
|
974
|
-
<Box marginBottom={1}>
|
|
975
|
-
<Text color={colors.textMuted}>{tool.description}</Text>
|
|
976
|
-
</Box>
|
|
977
|
-
|
|
978
|
-
{/* Skills section */}
|
|
979
|
-
{skillItems.length > 0 && (
|
|
980
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
981
|
-
<Text color={colors.skill} bold>Skills</Text>
|
|
982
|
-
<Box flexDirection="row" flexWrap="wrap" marginTop={1}>
|
|
983
|
-
{skillItems.map((item) => {
|
|
984
|
-
const idx = getItemIndex(item);
|
|
985
|
-
const isSelected = selectedIndex === idx;
|
|
986
|
-
return (
|
|
987
|
-
<Box key={item.name} marginRight={1} marginBottom={1}>
|
|
988
|
-
<Text
|
|
989
|
-
backgroundColor={isSelected ? colors.skill : colors.bgSelected}
|
|
990
|
-
color={isSelected ? '#000000' : colors.skill}
|
|
991
|
-
bold={isSelected}
|
|
992
|
-
>
|
|
993
|
-
{` ${item.name} `}
|
|
994
|
-
</Text>
|
|
995
|
-
</Box>
|
|
996
|
-
);
|
|
997
|
-
})}
|
|
998
|
-
</Box>
|
|
999
|
-
</Box>
|
|
1000
|
-
)}
|
|
1001
|
-
|
|
1002
|
-
{/* Commands section */}
|
|
1003
|
-
{commandItems.length > 0 && (
|
|
1004
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
1005
|
-
<Text color={colors.command} bold>Commands</Text>
|
|
1006
|
-
<Box flexDirection="row" flexWrap="wrap" marginTop={1}>
|
|
1007
|
-
{commandItems.map((item) => {
|
|
1008
|
-
const idx = getItemIndex(item);
|
|
1009
|
-
const isSelected = selectedIndex === idx;
|
|
1010
|
-
return (
|
|
1011
|
-
<Box key={item.name} marginRight={1} marginBottom={1}>
|
|
1012
|
-
<Text
|
|
1013
|
-
backgroundColor={isSelected ? colors.command : colors.bgSelected}
|
|
1014
|
-
color={isSelected ? '#000000' : colors.command}
|
|
1015
|
-
bold={isSelected}
|
|
1016
|
-
>
|
|
1017
|
-
{` ${item.name} `}
|
|
1018
|
-
</Text>
|
|
1019
|
-
</Box>
|
|
1020
|
-
);
|
|
1021
|
-
})}
|
|
1022
|
-
</Box>
|
|
1023
|
-
</Box>
|
|
1024
|
-
)}
|
|
1025
|
-
|
|
1026
|
-
{/* Agents section */}
|
|
1027
|
-
{agentItems.length > 0 && (
|
|
1028
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
1029
|
-
<Text color={colors.agent} bold>Agents</Text>
|
|
1030
|
-
<Box flexDirection="row" flexWrap="wrap" marginTop={1}>
|
|
1031
|
-
{agentItems.map((item) => {
|
|
1032
|
-
const idx = getItemIndex(item);
|
|
1033
|
-
const isSelected = selectedIndex === idx;
|
|
1034
|
-
return (
|
|
1035
|
-
<Box key={item.name} marginRight={1} marginBottom={1}>
|
|
1036
|
-
<Text
|
|
1037
|
-
backgroundColor={isSelected ? colors.agent : colors.bgSelected}
|
|
1038
|
-
color={isSelected ? '#000000' : colors.agent}
|
|
1039
|
-
bold={isSelected}
|
|
1040
|
-
>
|
|
1041
|
-
{` ${item.name} `}
|
|
1042
|
-
</Text>
|
|
1043
|
-
</Box>
|
|
1044
|
-
);
|
|
1045
|
-
})}
|
|
1046
|
-
</Box>
|
|
1047
|
-
</Box>
|
|
1048
|
-
)}
|
|
1049
|
-
|
|
1050
|
-
<Box marginTop={1}>
|
|
1051
|
-
<Text color={colors.textDim}>←→ navigate · enter view source · esc back</Text>
|
|
1052
|
-
</Box>
|
|
1053
|
-
</Box>
|
|
1054
|
-
);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
function SettingsDetails({
|
|
1058
|
-
onEditSettings,
|
|
1059
|
-
isFocused,
|
|
1060
|
-
onToggleAutoUpdate,
|
|
1061
|
-
selectedSetting,
|
|
1062
|
-
}: {
|
|
1063
|
-
onEditSettings: () => void;
|
|
1064
|
-
isFocused: boolean;
|
|
1065
|
-
onToggleAutoUpdate: () => void;
|
|
1066
|
-
selectedSetting: number;
|
|
1067
|
-
}) {
|
|
1068
|
-
const config = loadConfig();
|
|
1069
|
-
const autoUpdateConfig = getAutoUpdateConfig();
|
|
1070
|
-
|
|
1071
|
-
return (
|
|
1072
|
-
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
1073
|
-
<Text color={colors.text} bold>Settings</Text>
|
|
1074
|
-
|
|
1075
|
-
<Box flexDirection="column" marginTop={1}>
|
|
1076
|
-
<Text>
|
|
1077
|
-
<Text color={colors.textDim}>Platform: </Text>
|
|
1078
|
-
<Text color={colors.text}>
|
|
1079
|
-
{config.platform === Platform.ClaudeCode ? 'Claude Code' : 'OpenCode'}
|
|
1080
|
-
</Text>
|
|
1081
|
-
</Text>
|
|
1082
|
-
<Text>
|
|
1083
|
-
<Text color={colors.textDim}>Your @mention: </Text>
|
|
1084
|
-
<Text color={colors.text}>{config.user_mention}</Text>
|
|
1085
|
-
</Text>
|
|
1086
|
-
</Box>
|
|
1087
|
-
|
|
1088
|
-
<Box flexDirection="column" marginTop={2}>
|
|
1089
|
-
<Text color={colors.text} bold>Auto-Update</Text>
|
|
1090
|
-
<Box marginTop={1}>
|
|
1091
|
-
<Text>
|
|
1092
|
-
<Text color={colors.textDim}>{isFocused && selectedSetting === 0 ? '> ' : ' '}</Text>
|
|
1093
|
-
<Text color={isFocused && selectedSetting === 0 ? colors.text : colors.textMuted}>
|
|
1094
|
-
[{autoUpdateConfig.tools ? 'x' : ' '}] Auto-update tools
|
|
1095
|
-
</Text>
|
|
1096
|
-
</Text>
|
|
1097
|
-
</Box>
|
|
1098
|
-
<Text color={colors.textDim}> Update tools automatically when droid starts</Text>
|
|
1099
|
-
<Box marginTop={1}>
|
|
1100
|
-
<Text>
|
|
1101
|
-
<Text color={colors.textDim}>{isFocused && selectedSetting === 1 ? '> ' : ' '}</Text>
|
|
1102
|
-
<Text color={isFocused && selectedSetting === 1 ? colors.text : colors.textMuted}>
|
|
1103
|
-
[{autoUpdateConfig.app ? 'x' : ' '}] Auto-update app
|
|
1104
|
-
</Text>
|
|
1105
|
-
</Text>
|
|
1106
|
-
</Box>
|
|
1107
|
-
<Text color={colors.textDim}> Update droid automatically when a new version is available</Text>
|
|
1108
|
-
</Box>
|
|
1109
|
-
|
|
1110
|
-
<Box marginTop={2}>
|
|
1111
|
-
<Text color={colors.textDim}>Config: ~/.droid/config.yaml</Text>
|
|
1112
|
-
</Box>
|
|
1113
|
-
|
|
1114
|
-
{isFocused && (
|
|
1115
|
-
<Box marginTop={2}>
|
|
1116
|
-
<Text
|
|
1117
|
-
backgroundColor={selectedSetting === 2 ? colors.primary : colors.bgSelected}
|
|
1118
|
-
color={selectedSetting === 2 ? '#ffffff' : colors.textMuted}
|
|
1119
|
-
bold={selectedSetting === 2}
|
|
1120
|
-
>
|
|
1121
|
-
{' '}Edit Profile{' '}
|
|
1122
|
-
</Text>
|
|
1123
|
-
</Box>
|
|
1124
|
-
)}
|
|
1125
|
-
|
|
1126
|
-
{isFocused && (
|
|
1127
|
-
<Box marginTop={1}>
|
|
1128
|
-
<Text color={colors.textDim}>↑↓ select · enter toggle/edit · esc back</Text>
|
|
1129
|
-
</Box>
|
|
1130
|
-
)}
|
|
1131
|
-
|
|
1132
|
-
{!isFocused && (
|
|
1133
|
-
<Box marginTop={2}>
|
|
1134
|
-
<Text color={colors.textDim}>press enter to configure</Text>
|
|
1135
|
-
</Box>
|
|
1136
|
-
)}
|
|
1137
|
-
</Box>
|
|
1138
|
-
);
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
interface SkillConfigScreenProps {
|
|
1142
|
-
skill: SkillManifest;
|
|
1143
|
-
onComplete: () => void;
|
|
1144
|
-
onCancel: () => void;
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
const MAX_VISIBLE_CONFIG_ITEMS = 4; // Each config item takes ~3 lines
|
|
1148
|
-
|
|
1149
|
-
function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenProps) {
|
|
1150
|
-
const configSchema = skill.config_schema || {};
|
|
1151
|
-
const configKeys = Object.keys(configSchema);
|
|
1152
|
-
|
|
1153
|
-
const initialOverrides = useMemo(() => loadSkillOverrides(skill.name), [skill.name]);
|
|
1154
|
-
|
|
1155
|
-
// Initialize values from saved overrides or defaults
|
|
1156
|
-
const [values, setValues] = useState<SkillOverrides>(() => {
|
|
1157
|
-
const initial: SkillOverrides = {};
|
|
1158
|
-
for (const key of configKeys) {
|
|
1159
|
-
const option = configSchema[key];
|
|
1160
|
-
initial[key] = initialOverrides[key] ?? option.default ?? (option.type === ConfigOptionType.Boolean ? false : '');
|
|
1161
|
-
}
|
|
1162
|
-
return initial;
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
1166
|
-
const [scrollOffset, setScrollOffset] = useState(0);
|
|
1167
|
-
const [editingField, setEditingField] = useState<string | null>(null);
|
|
1168
|
-
const [editValue, setEditValue] = useState('');
|
|
1169
|
-
const [editingSelect, setEditingSelect] = useState<string | null>(null);
|
|
1170
|
-
const [selectOptionIndex, setSelectOptionIndex] = useState(0);
|
|
1171
|
-
|
|
1172
|
-
// Total items = config keys + Save button
|
|
1173
|
-
const totalItems = configKeys.length + 1;
|
|
1174
|
-
|
|
1175
|
-
const handleSave = () => {
|
|
1176
|
-
saveSkillOverrides(skill.name, values);
|
|
1177
|
-
onComplete();
|
|
1178
|
-
};
|
|
1179
|
-
|
|
1180
|
-
const handleSubmitEdit = () => {
|
|
1181
|
-
if (editingField) {
|
|
1182
|
-
setValues((prev) => ({ ...prev, [editingField]: editValue }));
|
|
1183
|
-
setEditingField(null);
|
|
1184
|
-
setEditValue('');
|
|
1185
|
-
}
|
|
1186
|
-
};
|
|
1187
|
-
|
|
1188
|
-
// Handle text input for string fields
|
|
1189
|
-
useInput((input, key) => {
|
|
1190
|
-
if (key.escape) {
|
|
1191
|
-
setEditingField(null);
|
|
1192
|
-
setEditValue('');
|
|
1193
|
-
}
|
|
1194
|
-
}, { isActive: editingField !== null });
|
|
1195
|
-
|
|
1196
|
-
// Handle select field editing
|
|
1197
|
-
useInput((input, key) => {
|
|
1198
|
-
if (!editingSelect) return;
|
|
1199
|
-
const option = configSchema[editingSelect];
|
|
1200
|
-
if (!option?.options) return;
|
|
1201
|
-
|
|
1202
|
-
if (key.escape) {
|
|
1203
|
-
setEditingSelect(null);
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1206
|
-
if (key.leftArrow || key.upArrow) {
|
|
1207
|
-
setSelectOptionIndex((prev) => Math.max(0, prev - 1));
|
|
1208
|
-
}
|
|
1209
|
-
if (key.rightArrow || key.downArrow) {
|
|
1210
|
-
setSelectOptionIndex((prev) => Math.min(option.options!.length - 1, prev + 1));
|
|
1211
|
-
}
|
|
1212
|
-
if (key.return) {
|
|
1213
|
-
setValues((prev) => ({ ...prev, [editingSelect]: option.options![selectOptionIndex] }));
|
|
1214
|
-
setEditingSelect(null);
|
|
1215
|
-
}
|
|
1216
|
-
}, { isActive: editingSelect !== null });
|
|
1217
|
-
|
|
1218
|
-
// Handle navigation and actions when not editing
|
|
1219
|
-
useInput((input, key) => {
|
|
1220
|
-
if (key.escape) {
|
|
1221
|
-
onCancel();
|
|
1222
|
-
return;
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
if (key.upArrow) {
|
|
1226
|
-
setSelectedIndex((prev) => {
|
|
1227
|
-
const newIndex = Math.max(0, prev - 1);
|
|
1228
|
-
// Scroll up if needed
|
|
1229
|
-
if (newIndex < scrollOffset) {
|
|
1230
|
-
setScrollOffset(newIndex);
|
|
1231
|
-
}
|
|
1232
|
-
return newIndex;
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
if (key.downArrow) {
|
|
1236
|
-
// +1 for the Save button at the end
|
|
1237
|
-
setSelectedIndex((prev) => {
|
|
1238
|
-
const newIndex = Math.min(totalItems - 1, prev + 1);
|
|
1239
|
-
// Scroll down if needed
|
|
1240
|
-
if (newIndex >= scrollOffset + MAX_VISIBLE_CONFIG_ITEMS) {
|
|
1241
|
-
setScrollOffset(newIndex - MAX_VISIBLE_CONFIG_ITEMS + 1);
|
|
1242
|
-
}
|
|
1243
|
-
return newIndex;
|
|
1244
|
-
});
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
if (key.return) {
|
|
1248
|
-
// Save button is at index === configKeys.length
|
|
1249
|
-
if (selectedIndex === configKeys.length) {
|
|
1250
|
-
handleSave();
|
|
1251
|
-
return;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
const key = configKeys[selectedIndex];
|
|
1255
|
-
const option = configSchema[key];
|
|
1256
|
-
|
|
1257
|
-
if (option.type === ConfigOptionType.Boolean) {
|
|
1258
|
-
// Toggle boolean
|
|
1259
|
-
setValues((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
1260
|
-
} else if (option.type === ConfigOptionType.Select && option.options) {
|
|
1261
|
-
// Enter select edit mode
|
|
1262
|
-
const currentValue = String(values[key] || option.options[0]);
|
|
1263
|
-
const currentIndex = option.options.indexOf(currentValue);
|
|
1264
|
-
setSelectOptionIndex(currentIndex >= 0 ? currentIndex : 0);
|
|
1265
|
-
setEditingSelect(key);
|
|
1266
|
-
} else if (option.type === ConfigOptionType.String) {
|
|
1267
|
-
// Enter edit mode for string
|
|
1268
|
-
setEditingField(key);
|
|
1269
|
-
setEditValue(String(values[key] || ''));
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
}, { isActive: editingField === null && editingSelect === null });
|
|
1273
|
-
|
|
1274
|
-
if (configKeys.length === 0) {
|
|
1275
|
-
return (
|
|
1276
|
-
<Box flexDirection="column" padding={1}>
|
|
1277
|
-
<Box marginBottom={1}>
|
|
1278
|
-
<Text>
|
|
1279
|
-
<Text color={colors.textDim}>[</Text>
|
|
1280
|
-
<Text color={colors.primary}>●</Text>
|
|
1281
|
-
<Text color={colors.textDim}> </Text>
|
|
1282
|
-
<Text color={colors.primary}>●</Text>
|
|
1283
|
-
<Text color={colors.textDim}>] </Text>
|
|
1284
|
-
<Text color={colors.text} bold>configure {skill.name}</Text>
|
|
1285
|
-
</Text>
|
|
1286
|
-
</Box>
|
|
1287
|
-
<Text color={colors.textMuted}>This skill has no configuration options.</Text>
|
|
1288
|
-
<Box marginTop={1}>
|
|
1289
|
-
<Text color={colors.textDim}>esc to go back</Text>
|
|
1290
|
-
</Box>
|
|
1291
|
-
</Box>
|
|
1292
|
-
);
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// Calculate visible range
|
|
1296
|
-
const visibleEndIndex = Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, totalItems);
|
|
1297
|
-
const visibleConfigKeys = configKeys.slice(scrollOffset, Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, configKeys.length));
|
|
1298
|
-
const showSaveButton = visibleEndIndex > configKeys.length || scrollOffset + MAX_VISIBLE_CONFIG_ITEMS > configKeys.length;
|
|
1299
|
-
const showTopIndicator = scrollOffset > 0;
|
|
1300
|
-
const showBottomIndicator = scrollOffset + MAX_VISIBLE_CONFIG_ITEMS < totalItems;
|
|
1301
|
-
|
|
1302
|
-
return (
|
|
1303
|
-
<Box flexDirection="column" padding={1}>
|
|
1304
|
-
<Box marginBottom={1}>
|
|
1305
|
-
<Text>
|
|
1306
|
-
<Text color={colors.textDim}>[</Text>
|
|
1307
|
-
<Text color={colors.primary}>●</Text>
|
|
1308
|
-
<Text color={colors.textDim}> </Text>
|
|
1309
|
-
<Text color={colors.primary}>●</Text>
|
|
1310
|
-
<Text color={colors.textDim}>] </Text>
|
|
1311
|
-
<Text color={colors.text} bold>configure {skill.name}</Text>
|
|
1312
|
-
</Text>
|
|
1313
|
-
</Box>
|
|
1314
|
-
|
|
1315
|
-
<Box flexDirection="column">
|
|
1316
|
-
{/* Scroll up indicator */}
|
|
1317
|
-
{showTopIndicator && (
|
|
1318
|
-
<Box marginBottom={1}>
|
|
1319
|
-
<Text color={colors.textDim}> ↑ {scrollOffset} more</Text>
|
|
1320
|
-
</Box>
|
|
1321
|
-
)}
|
|
1322
|
-
|
|
1323
|
-
{visibleConfigKeys.map((key) => {
|
|
1324
|
-
const actualIndex = configKeys.indexOf(key);
|
|
1325
|
-
const option = configSchema[key];
|
|
1326
|
-
const isSelected = selectedIndex === actualIndex;
|
|
1327
|
-
const isEditing = editingField === key;
|
|
1328
|
-
|
|
1329
|
-
return (
|
|
1330
|
-
<Box key={key} flexDirection="column" marginBottom={1}>
|
|
1331
|
-
<Text>
|
|
1332
|
-
<Text color={colors.textDim}>{isSelected ? '>' : ' '} </Text>
|
|
1333
|
-
<Text color={isSelected ? colors.text : colors.textMuted}>{key}</Text>
|
|
1334
|
-
</Text>
|
|
1335
|
-
<Text color={colors.textDim}> {option.description}</Text>
|
|
1336
|
-
<Box>
|
|
1337
|
-
<Text color={colors.textDim}> </Text>
|
|
1338
|
-
{option.type === ConfigOptionType.Boolean ? (
|
|
1339
|
-
<Text color={colors.text}>
|
|
1340
|
-
[{values[key] ? 'x' : ' '}] {values[key] ? 'enabled' : 'disabled'}
|
|
1341
|
-
</Text>
|
|
1342
|
-
) : option.type === ConfigOptionType.Select && option.options ? (
|
|
1343
|
-
<Text color={colors.text}>
|
|
1344
|
-
{option.options.map((opt, i) => {
|
|
1345
|
-
const isCurrentValue = String(values[key]) === opt;
|
|
1346
|
-
const isEditingThis = editingSelect === key;
|
|
1347
|
-
const isHighlighted = isEditingThis && selectOptionIndex === i;
|
|
1348
|
-
return (
|
|
1349
|
-
<Text key={opt}>
|
|
1350
|
-
{i > 0 && <Text color={colors.textDim}> · </Text>}
|
|
1351
|
-
<Text
|
|
1352
|
-
color={isHighlighted ? '#ffffff' : isCurrentValue ? colors.primary : colors.textMuted}
|
|
1353
|
-
backgroundColor={isHighlighted ? colors.primary : undefined}
|
|
1354
|
-
>
|
|
1355
|
-
{isCurrentValue && !isEditingThis ? `[${opt}]` : isHighlighted ? ` ${opt} ` : opt}
|
|
1356
|
-
</Text>
|
|
1357
|
-
</Text>
|
|
1358
|
-
);
|
|
1359
|
-
})}
|
|
1360
|
-
</Text>
|
|
1361
|
-
) : isEditing ? (
|
|
1362
|
-
<Box>
|
|
1363
|
-
<Text color={colors.textDim}>{'> '}</Text>
|
|
1364
|
-
<TextInput
|
|
1365
|
-
value={editValue}
|
|
1366
|
-
onChange={setEditValue}
|
|
1367
|
-
onSubmit={handleSubmitEdit}
|
|
1368
|
-
/>
|
|
1369
|
-
</Box>
|
|
1370
|
-
) : (
|
|
1371
|
-
<Text color={colors.text}>{String(values[key]) || '(not set)'}</Text>
|
|
1372
|
-
)}
|
|
1373
|
-
</Box>
|
|
1374
|
-
</Box>
|
|
1375
|
-
);
|
|
1376
|
-
})}
|
|
1377
|
-
|
|
1378
|
-
{/* Save button - show if in visible range */}
|
|
1379
|
-
{showSaveButton && (
|
|
1380
|
-
<Box marginTop={1}>
|
|
1381
|
-
<Text>
|
|
1382
|
-
<Text color={colors.textDim}>{selectedIndex === configKeys.length ? '>' : ' '} </Text>
|
|
1383
|
-
<Text
|
|
1384
|
-
backgroundColor={selectedIndex === configKeys.length ? colors.primary : undefined}
|
|
1385
|
-
color={selectedIndex === configKeys.length ? '#ffffff' : colors.textMuted}
|
|
1386
|
-
bold={selectedIndex === configKeys.length}
|
|
1387
|
-
>
|
|
1388
|
-
{' '}Save{' '}
|
|
1389
|
-
</Text>
|
|
1390
|
-
</Text>
|
|
1391
|
-
</Box>
|
|
1392
|
-
)}
|
|
1393
|
-
|
|
1394
|
-
{/* Scroll down indicator */}
|
|
1395
|
-
{showBottomIndicator && (
|
|
1396
|
-
<Box marginTop={1}>
|
|
1397
|
-
<Text color={colors.textDim}> ↓ {totalItems - scrollOffset - MAX_VISIBLE_CONFIG_ITEMS} more</Text>
|
|
1398
|
-
</Box>
|
|
1399
|
-
)}
|
|
1400
|
-
</Box>
|
|
1401
|
-
|
|
1402
|
-
<Box marginTop={1}>
|
|
1403
|
-
<Text color={colors.textDim}>
|
|
1404
|
-
{editingField
|
|
1405
|
-
? 'enter save · esc cancel'
|
|
1406
|
-
: editingSelect
|
|
1407
|
-
? '←→ choose · enter select · esc cancel'
|
|
1408
|
-
: '↑↓ select · enter toggle/edit · esc back'}
|
|
1409
|
-
</Text>
|
|
1410
|
-
</Box>
|
|
1411
|
-
</Box>
|
|
1412
|
-
);
|
|
1413
|
-
}
|
|
28
|
+
// Module-level variable to store exit message (printed after leaving alternate screen)
|
|
29
|
+
let exitMessage: string | null = null;
|
|
1414
30
|
|
|
1415
31
|
function App() {
|
|
1416
32
|
const { exit } = useApp();
|
|
@@ -1428,97 +44,23 @@ function App() {
|
|
|
1428
44
|
const [message, setMessage] = useState<{ text: string; type: 'success' | 'error' } | null>(null);
|
|
1429
45
|
const [isEditingSettings, setIsEditingSettings] = useState(false);
|
|
1430
46
|
const [readmeContent, setReadmeContent] = useState<{ title: string; content: string } | null>(null);
|
|
1431
|
-
const [selectedSetting, setSelectedSetting] = useState(0);
|
|
1432
|
-
const [isUpdating, setIsUpdating] = useState(false);
|
|
1433
|
-
const [isUpdatingTools, setIsUpdatingTools] = useState(false);
|
|
1434
47
|
const [previousView, setPreviousView] = useState<View>('detail'); // Track where to return from readme
|
|
1435
|
-
const [toolUpdates, setToolUpdates] = useState<ToolUpdateInfo[]>([]);
|
|
1436
|
-
const [autoUpdatedTools, setAutoUpdatedTools] = useState<string[]>([]);
|
|
1437
|
-
|
|
1438
|
-
// Check for updates once on mount
|
|
1439
|
-
const updateInfo = useMemo(() => getUpdateInfo(), []);
|
|
1440
|
-
const autoUpdateConfig = useMemo(() => getAutoUpdateConfig(), []);
|
|
1441
48
|
|
|
1442
|
-
const handleUpdate = (
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
const dim = '\x1b[38;2;106;106;106m';
|
|
1451
|
-
const reset = '\x1b[0m';
|
|
1452
|
-
exitMessage = `
|
|
1453
|
-
${dim}────────────────────────────────────────────────────────${reset}
|
|
1454
|
-
|
|
1455
|
-
${dim}╔═════╗${reset}
|
|
1456
|
-
${dim}║${reset} ${blue}●${reset} ${blue}●${reset} ${dim}║${reset} ${blue}"It's quite possible this system${reset}
|
|
1457
|
-
${dim}╚═╦═╦═╝${reset} ${blue}is now fully operational."${reset}
|
|
1458
|
-
|
|
1459
|
-
Run ${blue}droid${reset} to start the new version.
|
|
1460
|
-
|
|
1461
|
-
${dim}────────────────────────────────────────────────────────${reset}
|
|
1462
|
-
`;
|
|
1463
|
-
exit();
|
|
49
|
+
const { updateInfo, isUpdating, handleUpdate, handleAlwaysUpdate } = useAppUpdate({
|
|
50
|
+
onUpdateSuccess: (msg) => {
|
|
51
|
+
exitMessage = msg;
|
|
52
|
+
},
|
|
53
|
+
onUpdateFailure: (error) => {
|
|
54
|
+
setMessage({ text: error, type: 'error' });
|
|
55
|
+
if (!configExists()) {
|
|
56
|
+
setView('setup');
|
|
1464
57
|
} else {
|
|
1465
|
-
|
|
1466
|
-
setMessage({ text: result.message, type: 'error' });
|
|
1467
|
-
// Continue to menu on failure
|
|
1468
|
-
if (!configExists()) {
|
|
1469
|
-
setView('setup');
|
|
1470
|
-
} else {
|
|
1471
|
-
setView('menu');
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
}, 100);
|
|
1475
|
-
};
|
|
1476
|
-
|
|
1477
|
-
const handleAlwaysUpdate = () => {
|
|
1478
|
-
// Enable auto-update for app in config
|
|
1479
|
-
setAutoUpdateConfig({ app: true });
|
|
1480
|
-
// Then run the update
|
|
1481
|
-
handleUpdate();
|
|
1482
|
-
};
|
|
1483
|
-
|
|
1484
|
-
// Ensure system tools (marked with system: true in TOOL.yaml) are always installed and current
|
|
1485
|
-
const ensureSystemTools = () => {
|
|
1486
|
-
// Find all tools marked as system tools
|
|
1487
|
-
const systemTools = tools.filter(t => (t as ToolManifest & { system?: boolean }).system === true);
|
|
1488
|
-
|
|
1489
|
-
for (const systemTool of systemTools) {
|
|
1490
|
-
const installed = isToolInstalled(systemTool.name);
|
|
1491
|
-
const updateStatus = getToolUpdateStatus(systemTool.name);
|
|
1492
|
-
|
|
1493
|
-
// Install if not installed, or update if outdated (regardless of auto-update settings)
|
|
1494
|
-
if (!installed || updateStatus.hasUpdate) {
|
|
1495
|
-
const primarySkill = systemTool.includes.skills.find(s => s.required)?.name || systemTool.name;
|
|
1496
|
-
installSkill(primarySkill);
|
|
58
|
+
setView('menu');
|
|
1497
59
|
}
|
|
1498
|
-
}
|
|
1499
|
-
};
|
|
1500
|
-
|
|
1501
|
-
// Check for tool updates and proceed to next view
|
|
1502
|
-
const checkToolUpdatesAndProceed = () => {
|
|
1503
|
-
// Always ensure system tools are current (bypasses auto-update settings)
|
|
1504
|
-
ensureSystemTools();
|
|
1505
|
-
|
|
1506
|
-
const updates = getToolsWithUpdates();
|
|
1507
|
-
setToolUpdates(updates);
|
|
1508
|
-
|
|
1509
|
-
// If auto_update.tools is true, auto-update silently
|
|
1510
|
-
if (autoUpdateConfig.tools && updates.length > 0) {
|
|
1511
|
-
handleUpdateAllTools(updates, true);
|
|
1512
|
-
return;
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
// If there are updates and auto-update is off, show prompt
|
|
1516
|
-
if (updates.length > 0) {
|
|
1517
|
-
setView('tool-updates');
|
|
1518
|
-
return;
|
|
1519
|
-
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
1520
62
|
|
|
1521
|
-
|
|
63
|
+
const proceedToNextView = () => {
|
|
1522
64
|
if (!configExists()) {
|
|
1523
65
|
setView('setup');
|
|
1524
66
|
} else {
|
|
@@ -1526,38 +68,15 @@ ${dim}────────────────────────
|
|
|
1526
68
|
}
|
|
1527
69
|
};
|
|
1528
70
|
|
|
1529
|
-
const
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
for (const tool of updates) {
|
|
1540
|
-
// Find the tool to get its primary skill
|
|
1541
|
-
const toolManifest = tools.find(t => t.name === tool.name);
|
|
1542
|
-
if (toolManifest) {
|
|
1543
|
-
const primarySkill = toolManifest.includes.skills.find(s => s.required)?.name || toolManifest.name;
|
|
1544
|
-
const result = updateSkill(primarySkill);
|
|
1545
|
-
if (result.success) {
|
|
1546
|
-
successCount++;
|
|
1547
|
-
updatedNames.push(tool.name);
|
|
1548
|
-
} else {
|
|
1549
|
-
failCount++;
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
setIsUpdatingTools(false);
|
|
1555
|
-
|
|
1556
|
-
// Track which tools were auto-updated for visual indicator
|
|
1557
|
-
if (silent && updatedNames.length > 0) {
|
|
1558
|
-
setAutoUpdatedTools(updatedNames);
|
|
1559
|
-
}
|
|
1560
|
-
|
|
71
|
+
const {
|
|
72
|
+
toolUpdates,
|
|
73
|
+
isUpdatingTools,
|
|
74
|
+
autoUpdatedTools,
|
|
75
|
+
checkForUpdates,
|
|
76
|
+
updateAllTools,
|
|
77
|
+
enableAutoUpdateAndUpdate,
|
|
78
|
+
} = useToolUpdates({
|
|
79
|
+
onUpdateComplete: ({ successCount, failCount, silent }) => {
|
|
1561
80
|
if (successCount > 0) {
|
|
1562
81
|
setMessage({
|
|
1563
82
|
text: silent
|
|
@@ -1566,52 +85,31 @@ ${dim}────────────────────────
|
|
|
1566
85
|
type: failCount > 0 ? 'error' : 'success',
|
|
1567
86
|
});
|
|
1568
87
|
}
|
|
88
|
+
proceedToNextView();
|
|
89
|
+
},
|
|
90
|
+
});
|
|
1569
91
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
} else {
|
|
1574
|
-
setView('menu');
|
|
1575
|
-
}
|
|
1576
|
-
}, 100);
|
|
1577
|
-
};
|
|
92
|
+
// Check for tool updates and proceed to next view
|
|
93
|
+
const checkToolUpdatesAndProceed = () => {
|
|
94
|
+
const { updates, shouldAutoUpdate } = checkForUpdates();
|
|
1578
95
|
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
handleUpdateAllTools();
|
|
1584
|
-
};
|
|
96
|
+
if (shouldAutoUpdate) {
|
|
97
|
+
updateAllTools(updates, true);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
1585
100
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
} else {
|
|
1590
|
-
setView('menu');
|
|
101
|
+
if (updates.length > 0) {
|
|
102
|
+
setView('tool-updates');
|
|
103
|
+
return;
|
|
1591
104
|
}
|
|
1592
|
-
};
|
|
1593
105
|
|
|
1594
|
-
|
|
1595
|
-
const current = getAutoUpdateConfig();
|
|
1596
|
-
setAutoUpdateConfig({ tools: !current.tools });
|
|
1597
|
-
// Force re-render by setting message (auto-clears on next input)
|
|
1598
|
-
setMessage({
|
|
1599
|
-
text: `✓ Auto-update tools ${!current.tools ? 'enabled' : 'disabled'}`,
|
|
1600
|
-
type: 'success',
|
|
1601
|
-
});
|
|
106
|
+
proceedToNextView();
|
|
1602
107
|
};
|
|
1603
108
|
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1606
|
-
setAutoUpdateConfig({ app: !current.app });
|
|
1607
|
-
setMessage({
|
|
1608
|
-
text: `✓ Auto-update app ${!current.app ? 'enabled' : 'disabled'}`,
|
|
1609
|
-
type: 'success',
|
|
1610
|
-
});
|
|
109
|
+
const handleSkipToolUpdates = () => {
|
|
110
|
+
proceedToNextView();
|
|
1611
111
|
};
|
|
1612
112
|
|
|
1613
|
-
const MAX_VISIBLE_ITEMS = 6;
|
|
1614
|
-
|
|
1615
113
|
const tools = getBundledTools();
|
|
1616
114
|
// Keep skills for configure view (tools configure via their primary skill)
|
|
1617
115
|
const skills = getBundledSkills();
|
|
@@ -1667,32 +165,18 @@ ${dim}────────────────────────
|
|
|
1667
165
|
setView('detail');
|
|
1668
166
|
} else if (activeTab === 'settings') {
|
|
1669
167
|
setView('detail');
|
|
1670
|
-
setSelectedSetting(0);
|
|
1671
168
|
}
|
|
1672
169
|
}
|
|
1673
170
|
} else if (view === 'detail') {
|
|
1674
171
|
if (key.escape || key.backspace) {
|
|
1675
172
|
setView('menu');
|
|
1676
173
|
setSelectedAction(0);
|
|
1677
|
-
setSelectedSetting(0);
|
|
1678
174
|
}
|
|
1679
175
|
if (activeTab === 'settings') {
|
|
1680
|
-
// Settings detail view
|
|
1681
|
-
if (key.upArrow) {
|
|
1682
|
-
setSelectedSetting((prev) => Math.max(0, prev - 1));
|
|
1683
|
-
}
|
|
1684
|
-
if (key.downArrow) {
|
|
1685
|
-
setSelectedSetting((prev) => Math.min(2, prev + 1)); // 0: auto-update tools, 1: auto-update app, 2: edit profile
|
|
1686
|
-
}
|
|
176
|
+
// Settings detail view - just enter to edit
|
|
1687
177
|
if (key.return) {
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
} else if (selectedSetting === 1) {
|
|
1691
|
-
handleToggleAutoUpdateApp();
|
|
1692
|
-
} else if (selectedSetting === 2) {
|
|
1693
|
-
setIsEditingSettings(true);
|
|
1694
|
-
setView('setup');
|
|
1695
|
-
}
|
|
178
|
+
setIsEditingSettings(true);
|
|
179
|
+
setView('setup');
|
|
1696
180
|
}
|
|
1697
181
|
}
|
|
1698
182
|
if (key.leftArrow && activeTab === 'tools') {
|
|
@@ -1806,8 +290,8 @@ ${dim}────────────────────────
|
|
|
1806
290
|
return (
|
|
1807
291
|
<ToolUpdatePrompt
|
|
1808
292
|
toolUpdates={toolUpdates}
|
|
1809
|
-
onUpdateAll={() =>
|
|
1810
|
-
onAlways={
|
|
293
|
+
onUpdateAll={() => updateAllTools()}
|
|
294
|
+
onAlways={enableAutoUpdateAndUpdate}
|
|
1811
295
|
onSkip={handleSkipToolUpdates}
|
|
1812
296
|
isUpdating={isUpdatingTools}
|
|
1813
297
|
/>
|
|
@@ -1966,13 +450,7 @@ ${dim}────────────────────────
|
|
|
1966
450
|
|
|
1967
451
|
{activeTab === 'settings' && (
|
|
1968
452
|
<SettingsDetails
|
|
1969
|
-
onEditSettings={() => {
|
|
1970
|
-
setIsEditingSettings(true);
|
|
1971
|
-
setView('setup');
|
|
1972
|
-
}}
|
|
1973
453
|
isFocused={view === 'detail'}
|
|
1974
|
-
onToggleAutoUpdate={handleToggleAutoUpdateTools}
|
|
1975
|
-
selectedSetting={selectedSetting}
|
|
1976
454
|
/>
|
|
1977
455
|
)}
|
|
1978
456
|
</Box>
|
|
@@ -1984,7 +462,7 @@ export async function tuiCommand(): Promise<void> {
|
|
|
1984
462
|
process.stdout.write('\x1b[?1049h');
|
|
1985
463
|
process.stdout.write('\x1b[H'); // Move cursor to top-left
|
|
1986
464
|
|
|
1987
|
-
const {
|
|
465
|
+
const { waitUntilExit } = render(<App />);
|
|
1988
466
|
|
|
1989
467
|
await waitUntilExit();
|
|
1990
468
|
|