@orderful/droid 0.11.1 → 0.13.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/CHANGELOG.md +22 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +245 -37
- package/dist/commands/tui.js.map +1 -1
- package/dist/lib/config.d.ts +9 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +26 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/tools.d.ts +11 -5
- package/dist/lib/tools.d.ts.map +1 -1
- package/dist/lib/tools.js +30 -1
- package/dist/lib/tools.js.map +1 -1
- package/dist/lib/types.d.ts +5 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/tools/droid/TOOL.yaml +18 -0
- package/dist/tools/droid/skills/droid/SKILL.md +117 -0
- package/dist/tools/droid/skills/droid/SKILL.yaml +7 -0
- package/package.json +1 -1
- package/src/commands/tui.tsx +401 -41
- package/src/lib/config.ts +30 -1
- package/src/lib/tools.ts +45 -6
- package/src/lib/types.ts +6 -0
- package/src/tools/droid/TOOL.yaml +18 -0
- package/src/tools/droid/skills/droid/SKILL.md +117 -0
- package/src/tools/droid/skills/droid/SKILL.yaml +7 -0
package/src/commands/tui.tsx
CHANGED
|
@@ -12,15 +12,18 @@ import {
|
|
|
12
12
|
updateSkill,
|
|
13
13
|
getSkillUpdateStatus,
|
|
14
14
|
} from '../lib/skills.js';
|
|
15
|
-
import { getBundledTools, getBundledToolsDir, isToolInstalled, getToolUpdateStatus, getInstalledToolVersion } from '../lib/tools.js';
|
|
16
|
-
import { configExists, loadConfig, saveConfig, loadSkillOverrides, saveSkillOverrides } from '../lib/config.js';
|
|
15
|
+
import { getBundledTools, getBundledToolsDir, isToolInstalled, getToolUpdateStatus, getInstalledToolVersion, getToolsWithUpdates, type ToolUpdateInfo } from '../lib/tools.js';
|
|
16
|
+
import { configExists, loadConfig, saveConfig, loadSkillOverrides, saveSkillOverrides, getAutoUpdateConfig, setAutoUpdateConfig } from '../lib/config.js';
|
|
17
17
|
import { configurePlatformPermissions } from './setup.js';
|
|
18
18
|
import { Platform, BuiltInOutput, ConfigOptionType, type DroidConfig, type OutputPreference, type SkillManifest, type ConfigOption, type SkillOverrides, type ToolManifest } from '../lib/types.js';
|
|
19
19
|
import { getVersion, getUpdateInfo, runUpdate, type UpdateInfo } from '../lib/version.js';
|
|
20
20
|
import { getRandomQuote } from '../lib/quotes.js';
|
|
21
21
|
|
|
22
22
|
type Tab = 'tools' | 'settings';
|
|
23
|
-
type View = 'welcome' | 'setup' | 'menu' | 'detail' | 'configure' | 'readme' | 'explorer';
|
|
23
|
+
type View = 'welcome' | 'tool-updates' | 'setup' | 'menu' | 'detail' | 'configure' | 'readme' | 'explorer';
|
|
24
|
+
|
|
25
|
+
// Module-level variable to store exit message (printed after leaving alternate screen)
|
|
26
|
+
let exitMessage: string | null = null;
|
|
24
27
|
type SetupStep = 'platform' | 'user_mention' | 'confirm';
|
|
25
28
|
type ComponentType = 'skill' | 'command' | 'agent';
|
|
26
29
|
|
|
@@ -162,12 +165,13 @@ function getOutputOptions(): Array<{ label: string; value: OutputPreference }> {
|
|
|
162
165
|
interface WelcomeScreenProps {
|
|
163
166
|
onContinue: () => void;
|
|
164
167
|
onUpdate: () => void;
|
|
168
|
+
onAlways: () => void;
|
|
165
169
|
onExit: () => void;
|
|
166
170
|
updateInfo: UpdateInfo;
|
|
167
171
|
isUpdating: boolean;
|
|
168
172
|
}
|
|
169
173
|
|
|
170
|
-
function WelcomeScreen({ onContinue, onUpdate, onExit, updateInfo, isUpdating }: WelcomeScreenProps) {
|
|
174
|
+
function WelcomeScreen({ onContinue, onUpdate, onAlways, onExit, updateInfo, isUpdating }: WelcomeScreenProps) {
|
|
171
175
|
const [selectedButton, setSelectedButton] = useState(0);
|
|
172
176
|
const welcomeQuote = useMemo(() => getRandomQuote(), []);
|
|
173
177
|
|
|
@@ -175,12 +179,17 @@ function WelcomeScreen({ onContinue, onUpdate, onExit, updateInfo, isUpdating }:
|
|
|
175
179
|
if (isUpdating) return;
|
|
176
180
|
|
|
177
181
|
if (updateInfo.hasUpdate) {
|
|
178
|
-
if (key.leftArrow
|
|
179
|
-
setSelectedButton((prev) => (prev
|
|
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));
|
|
180
187
|
}
|
|
181
188
|
if (key.return) {
|
|
182
189
|
if (selectedButton === 0) {
|
|
183
190
|
onUpdate();
|
|
191
|
+
} else if (selectedButton === 1) {
|
|
192
|
+
onAlways();
|
|
184
193
|
} else {
|
|
185
194
|
onContinue();
|
|
186
195
|
}
|
|
@@ -260,17 +269,25 @@ function WelcomeScreen({ onContinue, onUpdate, onExit, updateInfo, isUpdating }:
|
|
|
260
269
|
</Text>
|
|
261
270
|
<Text> </Text>
|
|
262
271
|
<Text
|
|
263
|
-
backgroundColor={selectedButton === 1 ? colors.bgSelected
|
|
264
|
-
color={selectedButton === 1 ?
|
|
272
|
+
backgroundColor={selectedButton === 1 ? '#eab308' : colors.bgSelected}
|
|
273
|
+
color={selectedButton === 1 ? '#000000' : colors.textMuted}
|
|
265
274
|
bold={selectedButton === 1}
|
|
266
275
|
>
|
|
267
|
-
{' '}
|
|
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{' '}
|
|
268
285
|
</Text>
|
|
269
286
|
</Box>
|
|
270
287
|
)}
|
|
271
288
|
|
|
272
289
|
<Box marginTop={1}>
|
|
273
|
-
<Text color={colors.textDim}>←→ select · enter</Text>
|
|
290
|
+
<Text color={colors.textDim}>←→ select · enter · "Always" enables auto-update</Text>
|
|
274
291
|
</Box>
|
|
275
292
|
</>
|
|
276
293
|
) : (
|
|
@@ -289,6 +306,111 @@ function WelcomeScreen({ onContinue, onUpdate, onExit, updateInfo, isUpdating }:
|
|
|
289
306
|
);
|
|
290
307
|
}
|
|
291
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
|
+
|
|
292
414
|
interface SetupScreenProps {
|
|
293
415
|
onComplete: () => void;
|
|
294
416
|
onSkip: () => void;
|
|
@@ -475,10 +597,12 @@ function ToolItem({
|
|
|
475
597
|
tool,
|
|
476
598
|
isSelected,
|
|
477
599
|
isActive,
|
|
600
|
+
wasAutoUpdated,
|
|
478
601
|
}: {
|
|
479
602
|
tool: ToolManifest;
|
|
480
603
|
isSelected: boolean;
|
|
481
604
|
isActive: boolean;
|
|
605
|
+
wasAutoUpdated?: boolean;
|
|
482
606
|
}) {
|
|
483
607
|
const installed = isToolInstalled(tool.name);
|
|
484
608
|
const installedVersion = getInstalledToolVersion(tool.name);
|
|
@@ -491,7 +615,8 @@ function ToolItem({
|
|
|
491
615
|
<Text color={isSelected || isActive ? colors.text : colors.textMuted}>{tool.name}</Text>
|
|
492
616
|
{installed && installedVersion && <Text color={colors.textDim}> v{installedVersion}</Text>}
|
|
493
617
|
{installed && <Text color={colors.success}> ✓</Text>}
|
|
494
|
-
{
|
|
618
|
+
{wasAutoUpdated && <Text color={colors.success}> ↑</Text>}
|
|
619
|
+
{updateStatus.hasUpdate && !wasAutoUpdated && <Text color={colors.primary}> ↑</Text>}
|
|
495
620
|
<Text> </Text>
|
|
496
621
|
{tool.includes.skills.length > 0 && <Text color={colors.skill}>● </Text>}
|
|
497
622
|
{tool.includes.commands.length > 0 && <Text color={colors.command}>● </Text>}
|
|
@@ -521,6 +646,7 @@ function ToolDetails({
|
|
|
521
646
|
const installed = isToolInstalled(tool.name);
|
|
522
647
|
const installedVersion = getInstalledToolVersion(tool.name);
|
|
523
648
|
const updateStatus = getToolUpdateStatus(tool.name);
|
|
649
|
+
const isSystemTool = (tool as ToolManifest & { system?: boolean }).system === true;
|
|
524
650
|
|
|
525
651
|
const actions = installed
|
|
526
652
|
? [
|
|
@@ -529,10 +655,12 @@ function ToolDetails({
|
|
|
529
655
|
? [{ id: 'update', label: `Update (${updateStatus.bundledVersion})`, variant: 'primary' }]
|
|
530
656
|
: []),
|
|
531
657
|
{ id: 'configure', label: 'Configure', variant: 'default' },
|
|
532
|
-
|
|
658
|
+
// System tools can't be uninstalled
|
|
659
|
+
...(!isSystemTool ? [{ id: 'uninstall', label: 'Uninstall', variant: 'danger' }] : []),
|
|
533
660
|
]
|
|
534
661
|
: [
|
|
535
662
|
{ id: 'explore', label: 'Explore', variant: 'default' },
|
|
663
|
+
// System tools auto-install, but show Install for manual trigger if needed
|
|
536
664
|
{ id: 'install', label: 'Install', variant: 'primary' },
|
|
537
665
|
];
|
|
538
666
|
|
|
@@ -930,12 +1058,16 @@ function ToolExplorer({ tool, onViewSource, onClose }: ToolExplorerProps) {
|
|
|
930
1058
|
function SettingsDetails({
|
|
931
1059
|
onEditSettings,
|
|
932
1060
|
isFocused,
|
|
1061
|
+
onToggleAutoUpdate,
|
|
1062
|
+
selectedSetting,
|
|
933
1063
|
}: {
|
|
934
1064
|
onEditSettings: () => void;
|
|
935
1065
|
isFocused: boolean;
|
|
1066
|
+
onToggleAutoUpdate: () => void;
|
|
1067
|
+
selectedSetting: number;
|
|
936
1068
|
}) {
|
|
937
1069
|
const config = loadConfig();
|
|
938
|
-
const
|
|
1070
|
+
const autoUpdateConfig = getAutoUpdateConfig();
|
|
939
1071
|
|
|
940
1072
|
return (
|
|
941
1073
|
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
@@ -954,21 +1086,53 @@ function SettingsDetails({
|
|
|
954
1086
|
</Text>
|
|
955
1087
|
</Box>
|
|
956
1088
|
|
|
957
|
-
<Box marginTop={
|
|
1089
|
+
<Box flexDirection="column" marginTop={2}>
|
|
1090
|
+
<Text color={colors.text} bold>Auto-Update</Text>
|
|
1091
|
+
<Box marginTop={1}>
|
|
1092
|
+
<Text>
|
|
1093
|
+
<Text color={colors.textDim}>{isFocused && selectedSetting === 0 ? '> ' : ' '}</Text>
|
|
1094
|
+
<Text color={isFocused && selectedSetting === 0 ? colors.text : colors.textMuted}>
|
|
1095
|
+
[{autoUpdateConfig.tools ? 'x' : ' '}] Auto-update tools
|
|
1096
|
+
</Text>
|
|
1097
|
+
</Text>
|
|
1098
|
+
</Box>
|
|
1099
|
+
<Text color={colors.textDim}> Update tools automatically when droid starts</Text>
|
|
1100
|
+
<Box marginTop={1}>
|
|
1101
|
+
<Text>
|
|
1102
|
+
<Text color={colors.textDim}>{isFocused && selectedSetting === 1 ? '> ' : ' '}</Text>
|
|
1103
|
+
<Text color={isFocused && selectedSetting === 1 ? colors.text : colors.textMuted}>
|
|
1104
|
+
[{autoUpdateConfig.app ? 'x' : ' '}] Auto-update app
|
|
1105
|
+
</Text>
|
|
1106
|
+
</Text>
|
|
1107
|
+
</Box>
|
|
1108
|
+
<Text color={colors.textDim}> Update droid automatically when a new version is available</Text>
|
|
1109
|
+
</Box>
|
|
1110
|
+
|
|
1111
|
+
<Box marginTop={2}>
|
|
958
1112
|
<Text color={colors.textDim}>Config: ~/.droid/config.yaml</Text>
|
|
959
1113
|
</Box>
|
|
960
1114
|
|
|
961
1115
|
{isFocused && (
|
|
962
1116
|
<Box marginTop={2}>
|
|
963
|
-
<Text
|
|
964
|
-
{
|
|
1117
|
+
<Text
|
|
1118
|
+
backgroundColor={selectedSetting === 2 ? colors.primary : colors.bgSelected}
|
|
1119
|
+
color={selectedSetting === 2 ? '#ffffff' : colors.textMuted}
|
|
1120
|
+
bold={selectedSetting === 2}
|
|
1121
|
+
>
|
|
1122
|
+
{' '}Edit Profile{' '}
|
|
965
1123
|
</Text>
|
|
966
1124
|
</Box>
|
|
967
1125
|
)}
|
|
968
1126
|
|
|
1127
|
+
{isFocused && (
|
|
1128
|
+
<Box marginTop={1}>
|
|
1129
|
+
<Text color={colors.textDim}>↑↓ select · enter toggle/edit · esc back</Text>
|
|
1130
|
+
</Box>
|
|
1131
|
+
)}
|
|
1132
|
+
|
|
969
1133
|
{!isFocused && (
|
|
970
1134
|
<Box marginTop={2}>
|
|
971
|
-
<Text color={colors.textDim}>press enter to
|
|
1135
|
+
<Text color={colors.textDim}>press enter to configure</Text>
|
|
972
1136
|
</Box>
|
|
973
1137
|
)}
|
|
974
1138
|
</Box>
|
|
@@ -1265,11 +1429,16 @@ function App() {
|
|
|
1265
1429
|
const [message, setMessage] = useState<{ text: string; type: 'success' | 'error' } | null>(null);
|
|
1266
1430
|
const [isEditingSettings, setIsEditingSettings] = useState(false);
|
|
1267
1431
|
const [readmeContent, setReadmeContent] = useState<{ title: string; content: string } | null>(null);
|
|
1432
|
+
const [selectedSetting, setSelectedSetting] = useState(0);
|
|
1268
1433
|
const [isUpdating, setIsUpdating] = useState(false);
|
|
1434
|
+
const [isUpdatingTools, setIsUpdatingTools] = useState(false);
|
|
1269
1435
|
const [previousView, setPreviousView] = useState<View>('detail'); // Track where to return from readme
|
|
1436
|
+
const [toolUpdates, setToolUpdates] = useState<ToolUpdateInfo[]>([]);
|
|
1437
|
+
const [autoUpdatedTools, setAutoUpdatedTools] = useState<string[]>([]);
|
|
1270
1438
|
|
|
1271
1439
|
// Check for updates once on mount
|
|
1272
1440
|
const updateInfo = useMemo(() => getUpdateInfo(), []);
|
|
1441
|
+
const autoUpdateConfig = useMemo(() => getAutoUpdateConfig(), []);
|
|
1273
1442
|
|
|
1274
1443
|
const handleUpdate = () => {
|
|
1275
1444
|
setIsUpdating(true);
|
|
@@ -1277,8 +1446,21 @@ function App() {
|
|
|
1277
1446
|
setTimeout(() => {
|
|
1278
1447
|
const result = runUpdate();
|
|
1279
1448
|
if (result.success) {
|
|
1280
|
-
//
|
|
1281
|
-
|
|
1449
|
+
// Store message to print after leaving alternate screen (with ANSI colors)
|
|
1450
|
+
const blue = '\x1b[38;2;99;102;241m'; // #6366f1
|
|
1451
|
+
const dim = '\x1b[38;2;106;106;106m';
|
|
1452
|
+
const reset = '\x1b[0m';
|
|
1453
|
+
exitMessage = `
|
|
1454
|
+
${dim}────────────────────────────────────────────────────────${reset}
|
|
1455
|
+
|
|
1456
|
+
${dim}╔═════╗${reset}
|
|
1457
|
+
${dim}║${reset} ${blue}●${reset} ${blue}●${reset} ${dim}║${reset} ${blue}"It's quite possible this system${reset}
|
|
1458
|
+
${dim}╚═╦═╦═╝${reset} ${blue}is now fully operational."${reset}
|
|
1459
|
+
|
|
1460
|
+
Run ${blue}droid${reset} to start the new version.
|
|
1461
|
+
|
|
1462
|
+
${dim}────────────────────────────────────────────────────────${reset}
|
|
1463
|
+
`;
|
|
1282
1464
|
exit();
|
|
1283
1465
|
} else {
|
|
1284
1466
|
setIsUpdating(false);
|
|
@@ -1293,6 +1475,142 @@ function App() {
|
|
|
1293
1475
|
}, 100);
|
|
1294
1476
|
};
|
|
1295
1477
|
|
|
1478
|
+
const handleAlwaysUpdate = () => {
|
|
1479
|
+
// Enable auto-update for app in config
|
|
1480
|
+
setAutoUpdateConfig({ app: true });
|
|
1481
|
+
// Then run the update
|
|
1482
|
+
handleUpdate();
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
// Ensure system tools (marked with system: true in TOOL.yaml) are always installed and current
|
|
1486
|
+
const ensureSystemTools = () => {
|
|
1487
|
+
// Find all tools marked as system tools
|
|
1488
|
+
const systemTools = tools.filter(t => (t as ToolManifest & { system?: boolean }).system === true);
|
|
1489
|
+
|
|
1490
|
+
for (const systemTool of systemTools) {
|
|
1491
|
+
const installed = isToolInstalled(systemTool.name);
|
|
1492
|
+
const updateStatus = getToolUpdateStatus(systemTool.name);
|
|
1493
|
+
|
|
1494
|
+
// Install if not installed, or update if outdated (regardless of auto-update settings)
|
|
1495
|
+
if (!installed || updateStatus.hasUpdate) {
|
|
1496
|
+
const primarySkill = systemTool.includes.skills.find(s => s.required)?.name || systemTool.name;
|
|
1497
|
+
installSkill(primarySkill);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
};
|
|
1501
|
+
|
|
1502
|
+
// Check for tool updates and proceed to next view
|
|
1503
|
+
const checkToolUpdatesAndProceed = () => {
|
|
1504
|
+
// Always ensure system tools are current (bypasses auto-update settings)
|
|
1505
|
+
ensureSystemTools();
|
|
1506
|
+
|
|
1507
|
+
const updates = getToolsWithUpdates();
|
|
1508
|
+
setToolUpdates(updates);
|
|
1509
|
+
|
|
1510
|
+
// If auto_update.tools is true, auto-update silently
|
|
1511
|
+
if (autoUpdateConfig.tools && updates.length > 0) {
|
|
1512
|
+
handleUpdateAllTools(updates, true);
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// If there are updates and auto-update is off, show prompt
|
|
1517
|
+
if (updates.length > 0) {
|
|
1518
|
+
setView('tool-updates');
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// No updates, proceed to setup or menu
|
|
1523
|
+
if (!configExists()) {
|
|
1524
|
+
setView('setup');
|
|
1525
|
+
} else {
|
|
1526
|
+
setView('menu');
|
|
1527
|
+
}
|
|
1528
|
+
};
|
|
1529
|
+
|
|
1530
|
+
const handleUpdateAllTools = (updates: ToolUpdateInfo[] = toolUpdates, silent = false) => {
|
|
1531
|
+
if (!silent) {
|
|
1532
|
+
setIsUpdatingTools(true);
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
setTimeout(() => {
|
|
1536
|
+
let successCount = 0;
|
|
1537
|
+
let failCount = 0;
|
|
1538
|
+
const updatedNames: string[] = [];
|
|
1539
|
+
|
|
1540
|
+
for (const tool of updates) {
|
|
1541
|
+
// Find the tool to get its primary skill
|
|
1542
|
+
const toolManifest = tools.find(t => t.name === tool.name);
|
|
1543
|
+
if (toolManifest) {
|
|
1544
|
+
const primarySkill = toolManifest.includes.skills.find(s => s.required)?.name || toolManifest.name;
|
|
1545
|
+
const result = updateSkill(primarySkill);
|
|
1546
|
+
if (result.success) {
|
|
1547
|
+
successCount++;
|
|
1548
|
+
updatedNames.push(tool.name);
|
|
1549
|
+
} else {
|
|
1550
|
+
failCount++;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
setIsUpdatingTools(false);
|
|
1556
|
+
|
|
1557
|
+
// Track which tools were auto-updated for visual indicator
|
|
1558
|
+
if (silent && updatedNames.length > 0) {
|
|
1559
|
+
setAutoUpdatedTools(updatedNames);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
if (successCount > 0) {
|
|
1563
|
+
setMessage({
|
|
1564
|
+
text: silent
|
|
1565
|
+
? `↑ Auto-updated ${successCount} tool${successCount > 1 ? 's' : ''}`
|
|
1566
|
+
: `✓ Updated ${successCount} tool${successCount > 1 ? 's' : ''}${failCount > 0 ? `, ${failCount} failed` : ''}`,
|
|
1567
|
+
type: failCount > 0 ? 'error' : 'success',
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// Proceed to next view
|
|
1572
|
+
if (!configExists()) {
|
|
1573
|
+
setView('setup');
|
|
1574
|
+
} else {
|
|
1575
|
+
setView('menu');
|
|
1576
|
+
}
|
|
1577
|
+
}, 100);
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
const handleAlwaysUpdateTools = () => {
|
|
1581
|
+
// Enable auto-update in config
|
|
1582
|
+
setAutoUpdateConfig({ tools: true });
|
|
1583
|
+
// Then update all
|
|
1584
|
+
handleUpdateAllTools();
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
const handleSkipToolUpdates = () => {
|
|
1588
|
+
if (!configExists()) {
|
|
1589
|
+
setView('setup');
|
|
1590
|
+
} else {
|
|
1591
|
+
setView('menu');
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
const handleToggleAutoUpdateTools = () => {
|
|
1596
|
+
const current = getAutoUpdateConfig();
|
|
1597
|
+
setAutoUpdateConfig({ tools: !current.tools });
|
|
1598
|
+
// Force re-render by setting message (auto-clears on next input)
|
|
1599
|
+
setMessage({
|
|
1600
|
+
text: `✓ Auto-update tools ${!current.tools ? 'enabled' : 'disabled'}`,
|
|
1601
|
+
type: 'success',
|
|
1602
|
+
});
|
|
1603
|
+
};
|
|
1604
|
+
|
|
1605
|
+
const handleToggleAutoUpdateApp = () => {
|
|
1606
|
+
const current = getAutoUpdateConfig();
|
|
1607
|
+
setAutoUpdateConfig({ app: !current.app });
|
|
1608
|
+
setMessage({
|
|
1609
|
+
text: `✓ Auto-update app ${!current.app ? 'enabled' : 'disabled'}`,
|
|
1610
|
+
type: 'success',
|
|
1611
|
+
});
|
|
1612
|
+
};
|
|
1613
|
+
|
|
1296
1614
|
const MAX_VISIBLE_ITEMS = 6;
|
|
1297
1615
|
|
|
1298
1616
|
const tools = getBundledTools();
|
|
@@ -1349,27 +1667,47 @@ function App() {
|
|
|
1349
1667
|
if (activeTab === 'tools' && tools.length > 0) {
|
|
1350
1668
|
setView('detail');
|
|
1351
1669
|
} else if (activeTab === 'settings') {
|
|
1352
|
-
|
|
1353
|
-
|
|
1670
|
+
setView('detail');
|
|
1671
|
+
setSelectedSetting(0);
|
|
1354
1672
|
}
|
|
1355
1673
|
}
|
|
1356
1674
|
} else if (view === 'detail') {
|
|
1357
1675
|
if (key.escape || key.backspace) {
|
|
1358
1676
|
setView('menu');
|
|
1359
1677
|
setSelectedAction(0);
|
|
1678
|
+
setSelectedSetting(0);
|
|
1360
1679
|
}
|
|
1361
|
-
if (
|
|
1680
|
+
if (activeTab === 'settings') {
|
|
1681
|
+
// Settings detail view navigation
|
|
1682
|
+
if (key.upArrow) {
|
|
1683
|
+
setSelectedSetting((prev) => Math.max(0, prev - 1));
|
|
1684
|
+
}
|
|
1685
|
+
if (key.downArrow) {
|
|
1686
|
+
setSelectedSetting((prev) => Math.min(2, prev + 1)); // 0: auto-update tools, 1: auto-update app, 2: edit profile
|
|
1687
|
+
}
|
|
1688
|
+
if (key.return) {
|
|
1689
|
+
if (selectedSetting === 0) {
|
|
1690
|
+
handleToggleAutoUpdateTools();
|
|
1691
|
+
} else if (selectedSetting === 1) {
|
|
1692
|
+
handleToggleAutoUpdateApp();
|
|
1693
|
+
} else if (selectedSetting === 2) {
|
|
1694
|
+
setIsEditingSettings(true);
|
|
1695
|
+
setView('setup');
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
if (key.leftArrow && activeTab === 'tools') {
|
|
1362
1700
|
setSelectedAction((prev) => Math.max(0, prev - 1));
|
|
1363
1701
|
}
|
|
1364
|
-
if (key.rightArrow) {
|
|
1702
|
+
if (key.rightArrow && activeTab === 'tools') {
|
|
1365
1703
|
let maxActions = 0;
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1704
|
+
const tool = tools[selectedIndex];
|
|
1705
|
+
const installed = tool ? isToolInstalled(tool.name) : false;
|
|
1706
|
+
const hasUpdate = tool ? getToolUpdateStatus(tool.name).hasUpdate : false;
|
|
1707
|
+
const isSystem = tool ? (tool as ToolManifest & { system?: boolean }).system === true : false;
|
|
1708
|
+
// Explore, [Update], Configure, [Uninstall] or Explore, Install
|
|
1709
|
+
// System tools don't have Uninstall, so one less action
|
|
1710
|
+
maxActions = installed ? (hasUpdate ? (isSystem ? 2 : 3) : (isSystem ? 1 : 2)) : 1;
|
|
1373
1711
|
setSelectedAction((prev) => Math.min(maxActions, prev + 1));
|
|
1374
1712
|
}
|
|
1375
1713
|
if (key.return && activeTab === 'tools') {
|
|
@@ -1377,13 +1715,14 @@ function App() {
|
|
|
1377
1715
|
if (tool) {
|
|
1378
1716
|
const installed = isToolInstalled(tool.name);
|
|
1379
1717
|
const toolUpdateStatus = getToolUpdateStatus(tool.name);
|
|
1718
|
+
const isSystemTool = (tool as ToolManifest & { system?: boolean }).system === true;
|
|
1380
1719
|
// Build actions array to match ToolDetails
|
|
1381
1720
|
const toolActions = installed
|
|
1382
1721
|
? [
|
|
1383
1722
|
{ id: 'explore' },
|
|
1384
1723
|
...(toolUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
|
|
1385
1724
|
{ id: 'configure' },
|
|
1386
|
-
{ id: 'uninstall' },
|
|
1725
|
+
...(!isSystemTool ? [{ id: 'uninstall' }] : []),
|
|
1387
1726
|
]
|
|
1388
1727
|
: [{ id: 'explore' }, { id: 'install' }];
|
|
1389
1728
|
|
|
@@ -1443,7 +1782,7 @@ function App() {
|
|
|
1443
1782
|
}
|
|
1444
1783
|
}
|
|
1445
1784
|
}
|
|
1446
|
-
}, { isActive: view !== 'welcome' && view !== 'setup' && view !== 'configure' && view !== 'explorer' });
|
|
1785
|
+
}, { isActive: view !== 'welcome' && view !== 'tool-updates' && view !== 'setup' && view !== 'configure' && view !== 'explorer' });
|
|
1447
1786
|
|
|
1448
1787
|
const selectedTool = activeTab === 'tools' ? tools[selectedIndex] ?? null : null;
|
|
1449
1788
|
// For configure view, we need the matching skill manifest
|
|
@@ -1457,15 +1796,21 @@ function App() {
|
|
|
1457
1796
|
updateInfo={updateInfo}
|
|
1458
1797
|
isUpdating={isUpdating}
|
|
1459
1798
|
onUpdate={handleUpdate}
|
|
1799
|
+
onAlways={handleAlwaysUpdate}
|
|
1460
1800
|
onExit={exit}
|
|
1461
|
-
onContinue={
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1801
|
+
onContinue={checkToolUpdatesAndProceed}
|
|
1802
|
+
/>
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
if (view === 'tool-updates' && toolUpdates.length > 0) {
|
|
1807
|
+
return (
|
|
1808
|
+
<ToolUpdatePrompt
|
|
1809
|
+
toolUpdates={toolUpdates}
|
|
1810
|
+
onUpdateAll={() => handleUpdateAllTools()}
|
|
1811
|
+
onAlways={handleAlwaysUpdateTools}
|
|
1812
|
+
onSkip={handleSkipToolUpdates}
|
|
1813
|
+
isUpdating={isUpdatingTools}
|
|
1469
1814
|
/>
|
|
1470
1815
|
);
|
|
1471
1816
|
}
|
|
@@ -1577,6 +1922,7 @@ function App() {
|
|
|
1577
1922
|
tool={tool}
|
|
1578
1923
|
isSelected={scrollOffset + index === selectedIndex}
|
|
1579
1924
|
isActive={scrollOffset + index === selectedIndex && view === 'detail'}
|
|
1925
|
+
wasAutoUpdated={autoUpdatedTools.includes(tool.name)}
|
|
1580
1926
|
/>
|
|
1581
1927
|
))}
|
|
1582
1928
|
{scrollOffset + MAX_VISIBLE_ITEMS < tools.length && (
|
|
@@ -1620,7 +1966,15 @@ function App() {
|
|
|
1620
1966
|
)}
|
|
1621
1967
|
|
|
1622
1968
|
{activeTab === 'settings' && (
|
|
1623
|
-
<SettingsDetails
|
|
1969
|
+
<SettingsDetails
|
|
1970
|
+
onEditSettings={() => {
|
|
1971
|
+
setIsEditingSettings(true);
|
|
1972
|
+
setView('setup');
|
|
1973
|
+
}}
|
|
1974
|
+
isFocused={view === 'detail'}
|
|
1975
|
+
onToggleAutoUpdate={handleToggleAutoUpdateTools}
|
|
1976
|
+
selectedSetting={selectedSetting}
|
|
1977
|
+
/>
|
|
1624
1978
|
)}
|
|
1625
1979
|
</Box>
|
|
1626
1980
|
);
|
|
@@ -1637,4 +1991,10 @@ export async function tuiCommand(): Promise<void> {
|
|
|
1637
1991
|
|
|
1638
1992
|
// Leave alternate screen
|
|
1639
1993
|
process.stdout.write('\x1b[?1049l');
|
|
1994
|
+
|
|
1995
|
+
// Print exit message if set (e.g., after successful update)
|
|
1996
|
+
if (exitMessage) {
|
|
1997
|
+
console.log(exitMessage);
|
|
1998
|
+
exitMessage = null;
|
|
1999
|
+
}
|
|
1640
2000
|
}
|