@orderful/droid 0.11.0 → 0.12.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 +19 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +221 -35
- 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/comments/TOOL.yaml +1 -1
- package/dist/tools/comments/skills/comments/SKILL.md +12 -3
- package/dist/tools/comments/skills/comments/SKILL.yaml +1 -1
- package/package.json +1 -1
- package/src/commands/tui.tsx +373 -39
- package/src/lib/config.ts +30 -1
- package/src/lib/tools.ts +45 -6
- package/src/lib/types.ts +6 -0
- package/src/tools/comments/TOOL.yaml +1 -1
- package/src/tools/comments/skills/comments/SKILL.md +12 -3
- package/src/tools/comments/skills/comments/SKILL.yaml +1 -1
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>}
|
|
@@ -930,12 +1055,16 @@ function ToolExplorer({ tool, onViewSource, onClose }: ToolExplorerProps) {
|
|
|
930
1055
|
function SettingsDetails({
|
|
931
1056
|
onEditSettings,
|
|
932
1057
|
isFocused,
|
|
1058
|
+
onToggleAutoUpdate,
|
|
1059
|
+
selectedSetting,
|
|
933
1060
|
}: {
|
|
934
1061
|
onEditSettings: () => void;
|
|
935
1062
|
isFocused: boolean;
|
|
1063
|
+
onToggleAutoUpdate: () => void;
|
|
1064
|
+
selectedSetting: number;
|
|
936
1065
|
}) {
|
|
937
1066
|
const config = loadConfig();
|
|
938
|
-
const
|
|
1067
|
+
const autoUpdateConfig = getAutoUpdateConfig();
|
|
939
1068
|
|
|
940
1069
|
return (
|
|
941
1070
|
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
@@ -954,21 +1083,53 @@ function SettingsDetails({
|
|
|
954
1083
|
</Text>
|
|
955
1084
|
</Box>
|
|
956
1085
|
|
|
957
|
-
<Box marginTop={
|
|
1086
|
+
<Box flexDirection="column" marginTop={2}>
|
|
1087
|
+
<Text color={colors.text} bold>Auto-Update</Text>
|
|
1088
|
+
<Box marginTop={1}>
|
|
1089
|
+
<Text>
|
|
1090
|
+
<Text color={colors.textDim}>{isFocused && selectedSetting === 0 ? '> ' : ' '}</Text>
|
|
1091
|
+
<Text color={isFocused && selectedSetting === 0 ? colors.text : colors.textMuted}>
|
|
1092
|
+
[{autoUpdateConfig.tools ? 'x' : ' '}] Auto-update tools
|
|
1093
|
+
</Text>
|
|
1094
|
+
</Text>
|
|
1095
|
+
</Box>
|
|
1096
|
+
<Text color={colors.textDim}> Update tools automatically when droid starts</Text>
|
|
1097
|
+
<Box marginTop={1}>
|
|
1098
|
+
<Text>
|
|
1099
|
+
<Text color={colors.textDim}>{isFocused && selectedSetting === 1 ? '> ' : ' '}</Text>
|
|
1100
|
+
<Text color={isFocused && selectedSetting === 1 ? colors.text : colors.textMuted}>
|
|
1101
|
+
[{autoUpdateConfig.app ? 'x' : ' '}] Auto-update app
|
|
1102
|
+
</Text>
|
|
1103
|
+
</Text>
|
|
1104
|
+
</Box>
|
|
1105
|
+
<Text color={colors.textDim}> Update droid automatically when a new version is available</Text>
|
|
1106
|
+
</Box>
|
|
1107
|
+
|
|
1108
|
+
<Box marginTop={2}>
|
|
958
1109
|
<Text color={colors.textDim}>Config: ~/.droid/config.yaml</Text>
|
|
959
1110
|
</Box>
|
|
960
1111
|
|
|
961
1112
|
{isFocused && (
|
|
962
1113
|
<Box marginTop={2}>
|
|
963
|
-
<Text
|
|
964
|
-
{
|
|
1114
|
+
<Text
|
|
1115
|
+
backgroundColor={selectedSetting === 2 ? colors.primary : colors.bgSelected}
|
|
1116
|
+
color={selectedSetting === 2 ? '#ffffff' : colors.textMuted}
|
|
1117
|
+
bold={selectedSetting === 2}
|
|
1118
|
+
>
|
|
1119
|
+
{' '}Edit Profile{' '}
|
|
965
1120
|
</Text>
|
|
966
1121
|
</Box>
|
|
967
1122
|
)}
|
|
968
1123
|
|
|
1124
|
+
{isFocused && (
|
|
1125
|
+
<Box marginTop={1}>
|
|
1126
|
+
<Text color={colors.textDim}>↑↓ select · enter toggle/edit · esc back</Text>
|
|
1127
|
+
</Box>
|
|
1128
|
+
)}
|
|
1129
|
+
|
|
969
1130
|
{!isFocused && (
|
|
970
1131
|
<Box marginTop={2}>
|
|
971
|
-
<Text color={colors.textDim}>press enter to
|
|
1132
|
+
<Text color={colors.textDim}>press enter to configure</Text>
|
|
972
1133
|
</Box>
|
|
973
1134
|
)}
|
|
974
1135
|
</Box>
|
|
@@ -1265,11 +1426,16 @@ function App() {
|
|
|
1265
1426
|
const [message, setMessage] = useState<{ text: string; type: 'success' | 'error' } | null>(null);
|
|
1266
1427
|
const [isEditingSettings, setIsEditingSettings] = useState(false);
|
|
1267
1428
|
const [readmeContent, setReadmeContent] = useState<{ title: string; content: string } | null>(null);
|
|
1429
|
+
const [selectedSetting, setSelectedSetting] = useState(0);
|
|
1268
1430
|
const [isUpdating, setIsUpdating] = useState(false);
|
|
1431
|
+
const [isUpdatingTools, setIsUpdatingTools] = useState(false);
|
|
1269
1432
|
const [previousView, setPreviousView] = useState<View>('detail'); // Track where to return from readme
|
|
1433
|
+
const [toolUpdates, setToolUpdates] = useState<ToolUpdateInfo[]>([]);
|
|
1434
|
+
const [autoUpdatedTools, setAutoUpdatedTools] = useState<string[]>([]);
|
|
1270
1435
|
|
|
1271
1436
|
// Check for updates once on mount
|
|
1272
1437
|
const updateInfo = useMemo(() => getUpdateInfo(), []);
|
|
1438
|
+
const autoUpdateConfig = useMemo(() => getAutoUpdateConfig(), []);
|
|
1273
1439
|
|
|
1274
1440
|
const handleUpdate = () => {
|
|
1275
1441
|
setIsUpdating(true);
|
|
@@ -1277,8 +1443,21 @@ function App() {
|
|
|
1277
1443
|
setTimeout(() => {
|
|
1278
1444
|
const result = runUpdate();
|
|
1279
1445
|
if (result.success) {
|
|
1280
|
-
//
|
|
1281
|
-
|
|
1446
|
+
// Store message to print after leaving alternate screen (with ANSI colors)
|
|
1447
|
+
const blue = '\x1b[38;2;99;102;241m'; // #6366f1
|
|
1448
|
+
const dim = '\x1b[38;2;106;106;106m';
|
|
1449
|
+
const reset = '\x1b[0m';
|
|
1450
|
+
exitMessage = `
|
|
1451
|
+
${dim}────────────────────────────────────────────────────────${reset}
|
|
1452
|
+
|
|
1453
|
+
${dim}╔═════╗${reset}
|
|
1454
|
+
${dim}║${reset} ${blue}●${reset} ${blue}●${reset} ${dim}║${reset} ${blue}"It's quite possible this system${reset}
|
|
1455
|
+
${dim}╚═╦═╦═╝${reset} ${blue}is now fully operational."${reset}
|
|
1456
|
+
|
|
1457
|
+
Run ${blue}droid${reset} to start the new version.
|
|
1458
|
+
|
|
1459
|
+
${dim}────────────────────────────────────────────────────────${reset}
|
|
1460
|
+
`;
|
|
1282
1461
|
exit();
|
|
1283
1462
|
} else {
|
|
1284
1463
|
setIsUpdating(false);
|
|
@@ -1293,6 +1472,122 @@ function App() {
|
|
|
1293
1472
|
}, 100);
|
|
1294
1473
|
};
|
|
1295
1474
|
|
|
1475
|
+
const handleAlwaysUpdate = () => {
|
|
1476
|
+
// Enable auto-update for app in config
|
|
1477
|
+
setAutoUpdateConfig({ app: true });
|
|
1478
|
+
// Then run the update
|
|
1479
|
+
handleUpdate();
|
|
1480
|
+
};
|
|
1481
|
+
|
|
1482
|
+
// Check for tool updates and proceed to next view
|
|
1483
|
+
const checkToolUpdatesAndProceed = () => {
|
|
1484
|
+
const updates = getToolsWithUpdates();
|
|
1485
|
+
setToolUpdates(updates);
|
|
1486
|
+
|
|
1487
|
+
// If auto_update.tools is true, auto-update silently
|
|
1488
|
+
if (autoUpdateConfig.tools && updates.length > 0) {
|
|
1489
|
+
handleUpdateAllTools(updates, true);
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// If there are updates and auto-update is off, show prompt
|
|
1494
|
+
if (updates.length > 0) {
|
|
1495
|
+
setView('tool-updates');
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// No updates, proceed to setup or menu
|
|
1500
|
+
if (!configExists()) {
|
|
1501
|
+
setView('setup');
|
|
1502
|
+
} else {
|
|
1503
|
+
setView('menu');
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
|
|
1507
|
+
const handleUpdateAllTools = (updates: ToolUpdateInfo[] = toolUpdates, silent = false) => {
|
|
1508
|
+
if (!silent) {
|
|
1509
|
+
setIsUpdatingTools(true);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
setTimeout(() => {
|
|
1513
|
+
let successCount = 0;
|
|
1514
|
+
let failCount = 0;
|
|
1515
|
+
const updatedNames: string[] = [];
|
|
1516
|
+
|
|
1517
|
+
for (const tool of updates) {
|
|
1518
|
+
// Find the tool to get its primary skill
|
|
1519
|
+
const toolManifest = tools.find(t => t.name === tool.name);
|
|
1520
|
+
if (toolManifest) {
|
|
1521
|
+
const primarySkill = toolManifest.includes.skills.find(s => s.required)?.name || toolManifest.name;
|
|
1522
|
+
const result = updateSkill(primarySkill);
|
|
1523
|
+
if (result.success) {
|
|
1524
|
+
successCount++;
|
|
1525
|
+
updatedNames.push(tool.name);
|
|
1526
|
+
} else {
|
|
1527
|
+
failCount++;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
setIsUpdatingTools(false);
|
|
1533
|
+
|
|
1534
|
+
// Track which tools were auto-updated for visual indicator
|
|
1535
|
+
if (silent && updatedNames.length > 0) {
|
|
1536
|
+
setAutoUpdatedTools(updatedNames);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
if (successCount > 0) {
|
|
1540
|
+
setMessage({
|
|
1541
|
+
text: silent
|
|
1542
|
+
? `↑ Auto-updated ${successCount} tool${successCount > 1 ? 's' : ''}`
|
|
1543
|
+
: `✓ Updated ${successCount} tool${successCount > 1 ? 's' : ''}${failCount > 0 ? `, ${failCount} failed` : ''}`,
|
|
1544
|
+
type: failCount > 0 ? 'error' : 'success',
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Proceed to next view
|
|
1549
|
+
if (!configExists()) {
|
|
1550
|
+
setView('setup');
|
|
1551
|
+
} else {
|
|
1552
|
+
setView('menu');
|
|
1553
|
+
}
|
|
1554
|
+
}, 100);
|
|
1555
|
+
};
|
|
1556
|
+
|
|
1557
|
+
const handleAlwaysUpdateTools = () => {
|
|
1558
|
+
// Enable auto-update in config
|
|
1559
|
+
setAutoUpdateConfig({ tools: true });
|
|
1560
|
+
// Then update all
|
|
1561
|
+
handleUpdateAllTools();
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
const handleSkipToolUpdates = () => {
|
|
1565
|
+
if (!configExists()) {
|
|
1566
|
+
setView('setup');
|
|
1567
|
+
} else {
|
|
1568
|
+
setView('menu');
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1572
|
+
const handleToggleAutoUpdateTools = () => {
|
|
1573
|
+
const current = getAutoUpdateConfig();
|
|
1574
|
+
setAutoUpdateConfig({ tools: !current.tools });
|
|
1575
|
+
// Force re-render by setting message (auto-clears on next input)
|
|
1576
|
+
setMessage({
|
|
1577
|
+
text: `✓ Auto-update tools ${!current.tools ? 'enabled' : 'disabled'}`,
|
|
1578
|
+
type: 'success',
|
|
1579
|
+
});
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
const handleToggleAutoUpdateApp = () => {
|
|
1583
|
+
const current = getAutoUpdateConfig();
|
|
1584
|
+
setAutoUpdateConfig({ app: !current.app });
|
|
1585
|
+
setMessage({
|
|
1586
|
+
text: `✓ Auto-update app ${!current.app ? 'enabled' : 'disabled'}`,
|
|
1587
|
+
type: 'success',
|
|
1588
|
+
});
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1296
1591
|
const MAX_VISIBLE_ITEMS = 6;
|
|
1297
1592
|
|
|
1298
1593
|
const tools = getBundledTools();
|
|
@@ -1349,27 +1644,45 @@ function App() {
|
|
|
1349
1644
|
if (activeTab === 'tools' && tools.length > 0) {
|
|
1350
1645
|
setView('detail');
|
|
1351
1646
|
} else if (activeTab === 'settings') {
|
|
1352
|
-
|
|
1353
|
-
|
|
1647
|
+
setView('detail');
|
|
1648
|
+
setSelectedSetting(0);
|
|
1354
1649
|
}
|
|
1355
1650
|
}
|
|
1356
1651
|
} else if (view === 'detail') {
|
|
1357
1652
|
if (key.escape || key.backspace) {
|
|
1358
1653
|
setView('menu');
|
|
1359
1654
|
setSelectedAction(0);
|
|
1655
|
+
setSelectedSetting(0);
|
|
1360
1656
|
}
|
|
1361
|
-
if (
|
|
1657
|
+
if (activeTab === 'settings') {
|
|
1658
|
+
// Settings detail view navigation
|
|
1659
|
+
if (key.upArrow) {
|
|
1660
|
+
setSelectedSetting((prev) => Math.max(0, prev - 1));
|
|
1661
|
+
}
|
|
1662
|
+
if (key.downArrow) {
|
|
1663
|
+
setSelectedSetting((prev) => Math.min(2, prev + 1)); // 0: auto-update tools, 1: auto-update app, 2: edit profile
|
|
1664
|
+
}
|
|
1665
|
+
if (key.return) {
|
|
1666
|
+
if (selectedSetting === 0) {
|
|
1667
|
+
handleToggleAutoUpdateTools();
|
|
1668
|
+
} else if (selectedSetting === 1) {
|
|
1669
|
+
handleToggleAutoUpdateApp();
|
|
1670
|
+
} else if (selectedSetting === 2) {
|
|
1671
|
+
setIsEditingSettings(true);
|
|
1672
|
+
setView('setup');
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
if (key.leftArrow && activeTab === 'tools') {
|
|
1362
1677
|
setSelectedAction((prev) => Math.max(0, prev - 1));
|
|
1363
1678
|
}
|
|
1364
|
-
if (key.rightArrow) {
|
|
1679
|
+
if (key.rightArrow && activeTab === 'tools') {
|
|
1365
1680
|
let maxActions = 0;
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
maxActions = installed ? (hasUpdate ? 3 : 2) : 1;
|
|
1372
|
-
}
|
|
1681
|
+
const tool = tools[selectedIndex];
|
|
1682
|
+
const installed = tool ? isToolInstalled(tool.name) : false;
|
|
1683
|
+
const hasUpdate = tool ? getToolUpdateStatus(tool.name).hasUpdate : false;
|
|
1684
|
+
// Explore, [Update], Configure, Uninstall or Explore, Install
|
|
1685
|
+
maxActions = installed ? (hasUpdate ? 3 : 2) : 1;
|
|
1373
1686
|
setSelectedAction((prev) => Math.min(maxActions, prev + 1));
|
|
1374
1687
|
}
|
|
1375
1688
|
if (key.return && activeTab === 'tools') {
|
|
@@ -1443,7 +1756,7 @@ function App() {
|
|
|
1443
1756
|
}
|
|
1444
1757
|
}
|
|
1445
1758
|
}
|
|
1446
|
-
}, { isActive: view !== 'welcome' && view !== 'setup' && view !== 'configure' && view !== 'explorer' });
|
|
1759
|
+
}, { isActive: view !== 'welcome' && view !== 'tool-updates' && view !== 'setup' && view !== 'configure' && view !== 'explorer' });
|
|
1447
1760
|
|
|
1448
1761
|
const selectedTool = activeTab === 'tools' ? tools[selectedIndex] ?? null : null;
|
|
1449
1762
|
// For configure view, we need the matching skill manifest
|
|
@@ -1457,15 +1770,21 @@ function App() {
|
|
|
1457
1770
|
updateInfo={updateInfo}
|
|
1458
1771
|
isUpdating={isUpdating}
|
|
1459
1772
|
onUpdate={handleUpdate}
|
|
1773
|
+
onAlways={handleAlwaysUpdate}
|
|
1460
1774
|
onExit={exit}
|
|
1461
|
-
onContinue={
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1775
|
+
onContinue={checkToolUpdatesAndProceed}
|
|
1776
|
+
/>
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
if (view === 'tool-updates' && toolUpdates.length > 0) {
|
|
1781
|
+
return (
|
|
1782
|
+
<ToolUpdatePrompt
|
|
1783
|
+
toolUpdates={toolUpdates}
|
|
1784
|
+
onUpdateAll={() => handleUpdateAllTools()}
|
|
1785
|
+
onAlways={handleAlwaysUpdateTools}
|
|
1786
|
+
onSkip={handleSkipToolUpdates}
|
|
1787
|
+
isUpdating={isUpdatingTools}
|
|
1469
1788
|
/>
|
|
1470
1789
|
);
|
|
1471
1790
|
}
|
|
@@ -1577,6 +1896,7 @@ function App() {
|
|
|
1577
1896
|
tool={tool}
|
|
1578
1897
|
isSelected={scrollOffset + index === selectedIndex}
|
|
1579
1898
|
isActive={scrollOffset + index === selectedIndex && view === 'detail'}
|
|
1899
|
+
wasAutoUpdated={autoUpdatedTools.includes(tool.name)}
|
|
1580
1900
|
/>
|
|
1581
1901
|
))}
|
|
1582
1902
|
{scrollOffset + MAX_VISIBLE_ITEMS < tools.length && (
|
|
@@ -1620,7 +1940,15 @@ function App() {
|
|
|
1620
1940
|
)}
|
|
1621
1941
|
|
|
1622
1942
|
{activeTab === 'settings' && (
|
|
1623
|
-
<SettingsDetails
|
|
1943
|
+
<SettingsDetails
|
|
1944
|
+
onEditSettings={() => {
|
|
1945
|
+
setIsEditingSettings(true);
|
|
1946
|
+
setView('setup');
|
|
1947
|
+
}}
|
|
1948
|
+
isFocused={view === 'detail'}
|
|
1949
|
+
onToggleAutoUpdate={handleToggleAutoUpdateTools}
|
|
1950
|
+
selectedSetting={selectedSetting}
|
|
1951
|
+
/>
|
|
1624
1952
|
)}
|
|
1625
1953
|
</Box>
|
|
1626
1954
|
);
|
|
@@ -1637,4 +1965,10 @@ export async function tuiCommand(): Promise<void> {
|
|
|
1637
1965
|
|
|
1638
1966
|
// Leave alternate screen
|
|
1639
1967
|
process.stdout.write('\x1b[?1049l');
|
|
1968
|
+
|
|
1969
|
+
// Print exit message if set (e.g., after successful update)
|
|
1970
|
+
if (exitMessage) {
|
|
1971
|
+
console.log(exitMessage);
|
|
1972
|
+
exitMessage = null;
|
|
1973
|
+
}
|
|
1640
1974
|
}
|
package/src/lib/config.ts
CHANGED
|
@@ -2,11 +2,16 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import YAML from 'yaml';
|
|
5
|
-
import { Platform, BuiltInOutput, type DroidConfig, type LegacyDroidConfig, type SkillOverrides } from './types.js';
|
|
5
|
+
import { Platform, BuiltInOutput, type DroidConfig, type LegacyDroidConfig, type SkillOverrides, type AutoUpdateConfig } from './types.js';
|
|
6
6
|
|
|
7
7
|
const CONFIG_DIR = join(homedir(), '.droid');
|
|
8
8
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.yaml');
|
|
9
9
|
|
|
10
|
+
const DEFAULT_AUTO_UPDATE: AutoUpdateConfig = {
|
|
11
|
+
app: false, // Opt-in: user must enable
|
|
12
|
+
tools: true, // Opt-out: enabled by default (tools only update when app updates)
|
|
13
|
+
};
|
|
14
|
+
|
|
10
15
|
const DEFAULT_CONFIG: DroidConfig = {
|
|
11
16
|
platform: Platform.ClaudeCode,
|
|
12
17
|
user_mention: '@user',
|
|
@@ -194,3 +199,27 @@ export function saveSkillOverrides(skillName: string, overrides: SkillOverrides)
|
|
|
194
199
|
const content = YAML.stringify(overrides, { indent: 2 });
|
|
195
200
|
writeFileSync(overridesPath, content, 'utf-8');
|
|
196
201
|
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get auto-update config with defaults applied
|
|
205
|
+
*/
|
|
206
|
+
export function getAutoUpdateConfig(): AutoUpdateConfig {
|
|
207
|
+
const config = loadConfig();
|
|
208
|
+
return {
|
|
209
|
+
...DEFAULT_AUTO_UPDATE,
|
|
210
|
+
...config.auto_update,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Update auto-update config
|
|
216
|
+
*/
|
|
217
|
+
export function setAutoUpdateConfig(updates: Partial<AutoUpdateConfig>): void {
|
|
218
|
+
const config = loadConfig();
|
|
219
|
+
config.auto_update = {
|
|
220
|
+
...DEFAULT_AUTO_UPDATE,
|
|
221
|
+
...config.auto_update,
|
|
222
|
+
...updates,
|
|
223
|
+
};
|
|
224
|
+
saveConfig(config);
|
|
225
|
+
}
|