@orderful/droid 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +31 -9
- package/CHANGELOG.md +35 -0
- package/dist/bin/droid.js +8 -8
- package/dist/bin/droid.js.map +1 -1
- package/dist/commands/config.js +1 -1
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/install.js +3 -3
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +3 -3
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills.js +4 -4
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/tui/components/Badge.d.ts +13 -0
- package/dist/commands/tui/components/Badge.d.ts.map +1 -0
- package/dist/commands/tui/components/Badge.js +29 -0
- package/dist/commands/tui/components/Badge.js.map +1 -0
- package/dist/commands/tui/components/Markdown.d.ts +5 -0
- package/dist/commands/tui/components/Markdown.d.ts.map +1 -0
- package/dist/commands/tui/components/Markdown.js +42 -0
- package/dist/commands/tui/components/Markdown.js.map +1 -0
- package/dist/commands/tui/components/SettingsDetails.d.ts +5 -0
- package/dist/commands/tui/components/SettingsDetails.d.ts.map +1 -0
- package/dist/commands/tui/components/SettingsDetails.js +11 -0
- package/dist/commands/tui/components/SettingsDetails.js.map +1 -0
- package/dist/commands/tui/components/TabBar.d.ts +10 -0
- package/dist/commands/tui/components/TabBar.d.ts.map +1 -0
- package/dist/commands/tui/components/TabBar.js +7 -0
- package/dist/commands/tui/components/TabBar.js.map +1 -0
- package/dist/commands/tui/components/ToolDetails.d.ts +8 -0
- package/dist/commands/tui/components/ToolDetails.d.ts.map +1 -0
- package/dist/commands/tui/components/ToolDetails.js +35 -0
- package/dist/commands/tui/components/ToolDetails.js.map +1 -0
- package/dist/commands/tui/components/ToolItem.d.ts +9 -0
- package/dist/commands/tui/components/ToolItem.d.ts.map +1 -0
- package/dist/commands/tui/components/ToolItem.js +11 -0
- package/dist/commands/tui/components/ToolItem.js.map +1 -0
- package/dist/commands/tui/constants.d.ts +16 -0
- package/dist/commands/tui/constants.d.ts.map +1 -0
- package/dist/commands/tui/constants.js +17 -0
- package/dist/commands/tui/constants.js.map +1 -0
- package/dist/commands/tui/hooks/useAppUpdate.d.ts +13 -0
- package/dist/commands/tui/hooks/useAppUpdate.d.ts.map +1 -0
- package/dist/commands/tui/hooks/useAppUpdate.js +52 -0
- package/dist/commands/tui/hooks/useAppUpdate.js.map +1 -0
- package/dist/commands/tui/hooks/useToolUpdates.d.ts +22 -0
- package/dist/commands/tui/hooks/useToolUpdates.d.ts.map +1 -0
- package/dist/commands/tui/hooks/useToolUpdates.js +77 -0
- package/dist/commands/tui/hooks/useToolUpdates.js.map +1 -0
- package/dist/commands/tui/types.d.ts +5 -0
- package/dist/commands/tui/types.d.ts.map +1 -0
- package/dist/commands/tui/types.js +2 -0
- package/dist/commands/tui/types.js.map +1 -0
- package/dist/commands/tui/views/ReadmeViewer.d.ts +7 -0
- package/dist/commands/tui/views/ReadmeViewer.d.ts.map +1 -0
- package/dist/commands/tui/views/ReadmeViewer.js +56 -0
- package/dist/commands/tui/views/ReadmeViewer.js.map +1 -0
- package/dist/commands/tui/views/SetupScreen.d.ts +8 -0
- package/dist/commands/tui/views/SetupScreen.d.ts.map +1 -0
- package/dist/commands/tui/views/SetupScreen.js +114 -0
- package/dist/commands/tui/views/SetupScreen.js.map +1 -0
- package/dist/commands/tui/views/SkillConfigScreen.d.ts +8 -0
- package/dist/commands/tui/views/SkillConfigScreen.d.ts.map +1 -0
- package/dist/commands/tui/views/SkillConfigScreen.js +148 -0
- package/dist/commands/tui/views/SkillConfigScreen.js.map +1 -0
- package/dist/commands/tui/views/ToolExplorer.d.ts +8 -0
- package/dist/commands/tui/views/ToolExplorer.d.ts.map +1 -0
- package/dist/commands/tui/views/ToolExplorer.js +86 -0
- package/dist/commands/tui/views/ToolExplorer.js.map +1 -0
- package/dist/commands/tui/views/ToolUpdatePrompt.d.ts +10 -0
- package/dist/commands/tui/views/ToolUpdatePrompt.d.ts.map +1 -0
- package/dist/commands/tui/views/ToolUpdatePrompt.js +38 -0
- package/dist/commands/tui/views/ToolUpdatePrompt.js.map +1 -0
- package/dist/commands/tui/views/WelcomeScreen.d.ts +11 -0
- package/dist/commands/tui/views/WelcomeScreen.d.ts.map +1 -0
- package/dist/commands/tui/views/WelcomeScreen.js +46 -0
- package/dist/commands/tui/views/WelcomeScreen.js.map +1 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +54 -756
- package/dist/commands/tui.js.map +1 -1
- package/dist/commands/uninstall.js +2 -2
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/agents.d.ts +7 -7
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +63 -41
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/platforms.d.ts +1 -1
- package/dist/lib/platforms.d.ts.map +1 -1
- package/dist/lib/platforms.js +1 -1
- package/dist/lib/platforms.js.map +1 -1
- package/dist/lib/skill-config.d.ts +1 -1
- package/dist/lib/skill-config.d.ts.map +1 -1
- package/dist/lib/skill-config.js +2 -2
- package/dist/lib/skill-config.js.map +1 -1
- package/dist/lib/skills.d.ts +2 -1
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +45 -12
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.d.ts.map +1 -1
- package/dist/lib/tools.js +3 -3
- package/dist/lib/tools.js.map +1 -1
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/README.md +79 -50
- package/dist/tools/brain/TOOL.yaml +1 -1
- package/dist/tools/brain/skills/brain/SKILL.md +1 -0
- package/dist/tools/brain/skills/brain-obsidian/SKILL.md +1 -0
- package/dist/tools/coach/TOOL.yaml +1 -1
- package/dist/tools/coach/skills/coach/SKILL.md +1 -0
- package/{src/tools/code-review/agents/edi-standards-reviewer/AGENT.md → dist/tools/code-review/agents/edi-standards-reviewer.md} +10 -0
- package/dist/tools/code-review/agents/{error-handling-reviewer/AGENT.md → error-handling-reviewer.md} +10 -0
- package/{src/tools/code-review/agents/test-coverage-analyzer/AGENT.md → dist/tools/code-review/agents/test-coverage-analyzer.md} +11 -0
- package/dist/tools/code-review/agents/{type-reviewer/AGENT.md → type-reviewer.md} +10 -0
- package/dist/tools/code-review/skills/code-review/SKILL.md +1 -0
- package/dist/tools/comments/TOOL.yaml +2 -2
- package/dist/tools/comments/skills/comments/SKILL.md +1 -0
- package/dist/tools/droid/skills/droid/SKILL.md +1 -0
- package/dist/tools/project/skills/project/SKILL.md +1 -0
- package/package.json +3 -3
- package/src/bin/droid.ts +8 -8
- package/src/commands/config.ts +1 -1
- package/src/commands/install.ts +3 -3
- package/src/commands/setup.test.ts +1 -1
- package/src/commands/setup.ts +3 -3
- package/src/commands/skills.ts +4 -4
- package/src/commands/tui/components/Badge.tsx +86 -0
- package/src/commands/tui/components/Markdown.tsx +48 -0
- package/src/commands/tui/components/SettingsDetails.tsx +70 -0
- package/src/commands/tui/components/TabBar.tsx +26 -0
- package/src/commands/tui/components/ToolDetails.tsx +117 -0
- package/src/commands/tui/components/ToolItem.tsx +39 -0
- package/src/commands/tui/constants.ts +17 -0
- package/src/commands/tui/hooks/useAppUpdate.ts +67 -0
- package/src/commands/tui/hooks/useToolUpdates.ts +110 -0
- package/src/commands/tui/types.ts +4 -0
- package/src/commands/tui/views/ReadmeViewer.tsx +93 -0
- package/src/commands/tui/views/SetupScreen.tsx +242 -0
- package/src/commands/tui/views/SkillConfigScreen.tsx +278 -0
- package/src/commands/tui/views/ToolExplorer.tsx +190 -0
- package/src/commands/tui/views/ToolUpdatePrompt.tsx +109 -0
- package/src/commands/tui/views/WelcomeScreen.tsx +149 -0
- package/src/commands/tui.tsx +65 -1588
- 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 +68 -45
- 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 +28 -33
- package/src/lib/skills.ts +49 -12
- package/src/lib/tools.ts +3 -3
- package/src/lib/types.test.ts +1 -1
- package/src/lib/types.ts +5 -0
- package/src/lib/version.test.ts +1 -1
- package/src/tools/README.md +79 -50
- package/src/tools/brain/TOOL.yaml +1 -1
- package/src/tools/brain/skills/brain/SKILL.md +1 -0
- package/src/tools/brain/skills/brain-obsidian/SKILL.md +1 -0
- package/src/tools/coach/TOOL.yaml +1 -1
- package/src/tools/coach/skills/coach/SKILL.md +1 -0
- package/{dist/tools/code-review/agents/edi-standards-reviewer/AGENT.md → src/tools/code-review/agents/edi-standards-reviewer.md} +10 -0
- package/src/tools/code-review/agents/{error-handling-reviewer/AGENT.md → error-handling-reviewer.md} +10 -0
- package/{dist/tools/code-review/agents/test-coverage-analyzer/AGENT.md → src/tools/code-review/agents/test-coverage-analyzer.md} +11 -0
- package/src/tools/code-review/agents/{type-reviewer/AGENT.md → type-reviewer.md} +10 -0
- package/src/tools/code-review/skills/code-review/SKILL.md +1 -0
- package/src/tools/comments/TOOL.yaml +2 -2
- package/src/tools/comments/skills/comments/SKILL.md +1 -0
- package/src/tools/droid/skills/droid/SKILL.md +1 -0
- package/src/tools/project/skills/project/SKILL.md +1 -0
- package/dist/tools/brain/skills/brain/SKILL.yaml +0 -29
- package/dist/tools/brain/skills/brain-obsidian/SKILL.yaml +0 -42
- package/dist/tools/coach/skills/coach/SKILL.yaml +0 -25
- package/dist/tools/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -14
- package/dist/tools/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -14
- package/dist/tools/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -14
- package/dist/tools/code-review/agents/type-reviewer/AGENT.yaml +0 -13
- package/dist/tools/code-review/skills/code-review/SKILL.yaml +0 -19
- package/dist/tools/comments/skills/comments/SKILL.yaml +0 -50
- package/dist/tools/droid/skills/droid/SKILL.yaml +0 -7
- package/dist/tools/project/skills/project/SKILL.yaml +0 -30
- package/src/tools/brain/skills/brain/SKILL.yaml +0 -29
- package/src/tools/brain/skills/brain-obsidian/SKILL.yaml +0 -42
- package/src/tools/coach/skills/coach/SKILL.yaml +0 -25
- package/src/tools/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -14
- package/src/tools/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -14
- package/src/tools/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -14
- package/src/tools/code-review/agents/type-reviewer/AGENT.yaml +0 -13
- package/src/tools/code-review/skills/code-review/SKILL.yaml +0 -19
- package/src/tools/comments/skills/comments/SKILL.yaml +0 -50
- package/src/tools/droid/skills/droid/SKILL.yaml +0 -7
- package/src/tools/project/skills/project/SKILL.yaml +0 -30
package/src/commands/tui.tsx
CHANGED
|
@@ -1,1417 +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
|
-
// Add agents
|
|
915
|
-
for (const agent of tool.includes.agents) {
|
|
916
|
-
result.push({
|
|
917
|
-
type: 'agent',
|
|
918
|
-
name: agent,
|
|
919
|
-
path: join(toolDir, tool.name, 'agents', agent, 'AGENT.md'),
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
return result;
|
|
924
|
-
}, [tool]);
|
|
925
|
-
|
|
926
|
-
useInput((input, key) => {
|
|
927
|
-
if (key.escape) {
|
|
928
|
-
onClose();
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
if (key.leftArrow || key.upArrow) {
|
|
933
|
-
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
934
|
-
}
|
|
935
|
-
if (key.rightArrow || key.downArrow) {
|
|
936
|
-
setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
if (key.return && items.length > 0) {
|
|
940
|
-
const item = items[selectedIndex];
|
|
941
|
-
if (existsSync(item.path)) {
|
|
942
|
-
const content = readFileSync(item.path, 'utf-8');
|
|
943
|
-
onViewSource(`${tool.name} / ${item.name}`, content);
|
|
944
|
-
} else {
|
|
945
|
-
// Try YAML fallback for skills/agents
|
|
946
|
-
const yamlPath = item.path.replace('.md', '.yaml');
|
|
947
|
-
if (existsSync(yamlPath)) {
|
|
948
|
-
const content = readFileSync(yamlPath, 'utf-8');
|
|
949
|
-
onViewSource(`${tool.name} / ${item.name}`, content);
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
// Group items by type for display
|
|
956
|
-
const skillItems = items.filter(i => i.type === 'skill');
|
|
957
|
-
const commandItems = items.filter(i => i.type === 'command');
|
|
958
|
-
const agentItems = items.filter(i => i.type === 'agent');
|
|
959
|
-
|
|
960
|
-
const getItemIndex = (item: ExplorerItem) => items.indexOf(item);
|
|
961
|
-
|
|
962
|
-
return (
|
|
963
|
-
<Box flexDirection="column" padding={1}>
|
|
964
|
-
<Box marginBottom={1}>
|
|
965
|
-
<Text>
|
|
966
|
-
<Text color={colors.textDim}>[</Text>
|
|
967
|
-
<Text color={colors.primary}>●</Text>
|
|
968
|
-
<Text color={colors.textDim}> </Text>
|
|
969
|
-
<Text color={colors.primary}>●</Text>
|
|
970
|
-
<Text color={colors.textDim}>] </Text>
|
|
971
|
-
<Text color={colors.text} bold>{tool.name}</Text>
|
|
972
|
-
</Text>
|
|
973
|
-
</Box>
|
|
974
|
-
|
|
975
|
-
<Box marginBottom={1}>
|
|
976
|
-
<Text color={colors.textMuted}>{tool.description}</Text>
|
|
977
|
-
</Box>
|
|
978
|
-
|
|
979
|
-
{/* Skills section */}
|
|
980
|
-
{skillItems.length > 0 && (
|
|
981
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
982
|
-
<Text color={colors.skill} bold>Skills</Text>
|
|
983
|
-
<Box flexDirection="row" flexWrap="wrap" marginTop={1}>
|
|
984
|
-
{skillItems.map((item) => {
|
|
985
|
-
const idx = getItemIndex(item);
|
|
986
|
-
const isSelected = selectedIndex === idx;
|
|
987
|
-
return (
|
|
988
|
-
<Box key={item.name} marginRight={1} marginBottom={1}>
|
|
989
|
-
<Text
|
|
990
|
-
backgroundColor={isSelected ? colors.skill : colors.bgSelected}
|
|
991
|
-
color={isSelected ? '#000000' : colors.skill}
|
|
992
|
-
bold={isSelected}
|
|
993
|
-
>
|
|
994
|
-
{` ${item.name} `}
|
|
995
|
-
</Text>
|
|
996
|
-
</Box>
|
|
997
|
-
);
|
|
998
|
-
})}
|
|
999
|
-
</Box>
|
|
1000
|
-
</Box>
|
|
1001
|
-
)}
|
|
1002
|
-
|
|
1003
|
-
{/* Commands section */}
|
|
1004
|
-
{commandItems.length > 0 && (
|
|
1005
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
1006
|
-
<Text color={colors.command} bold>Commands</Text>
|
|
1007
|
-
<Box flexDirection="row" flexWrap="wrap" marginTop={1}>
|
|
1008
|
-
{commandItems.map((item) => {
|
|
1009
|
-
const idx = getItemIndex(item);
|
|
1010
|
-
const isSelected = selectedIndex === idx;
|
|
1011
|
-
return (
|
|
1012
|
-
<Box key={item.name} marginRight={1} marginBottom={1}>
|
|
1013
|
-
<Text
|
|
1014
|
-
backgroundColor={isSelected ? colors.command : colors.bgSelected}
|
|
1015
|
-
color={isSelected ? '#000000' : colors.command}
|
|
1016
|
-
bold={isSelected}
|
|
1017
|
-
>
|
|
1018
|
-
{` ${item.name} `}
|
|
1019
|
-
</Text>
|
|
1020
|
-
</Box>
|
|
1021
|
-
);
|
|
1022
|
-
})}
|
|
1023
|
-
</Box>
|
|
1024
|
-
</Box>
|
|
1025
|
-
)}
|
|
1026
|
-
|
|
1027
|
-
{/* Agents section */}
|
|
1028
|
-
{agentItems.length > 0 && (
|
|
1029
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
1030
|
-
<Text color={colors.agent} bold>Agents</Text>
|
|
1031
|
-
<Box flexDirection="row" flexWrap="wrap" marginTop={1}>
|
|
1032
|
-
{agentItems.map((item) => {
|
|
1033
|
-
const idx = getItemIndex(item);
|
|
1034
|
-
const isSelected = selectedIndex === idx;
|
|
1035
|
-
return (
|
|
1036
|
-
<Box key={item.name} marginRight={1} marginBottom={1}>
|
|
1037
|
-
<Text
|
|
1038
|
-
backgroundColor={isSelected ? colors.agent : colors.bgSelected}
|
|
1039
|
-
color={isSelected ? '#000000' : colors.agent}
|
|
1040
|
-
bold={isSelected}
|
|
1041
|
-
>
|
|
1042
|
-
{` ${item.name} `}
|
|
1043
|
-
</Text>
|
|
1044
|
-
</Box>
|
|
1045
|
-
);
|
|
1046
|
-
})}
|
|
1047
|
-
</Box>
|
|
1048
|
-
</Box>
|
|
1049
|
-
)}
|
|
1050
|
-
|
|
1051
|
-
<Box marginTop={1}>
|
|
1052
|
-
<Text color={colors.textDim}>←→ navigate · enter view source · esc back</Text>
|
|
1053
|
-
</Box>
|
|
1054
|
-
</Box>
|
|
1055
|
-
);
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
function SettingsDetails({
|
|
1059
|
-
onEditSettings,
|
|
1060
|
-
isFocused,
|
|
1061
|
-
onToggleAutoUpdate,
|
|
1062
|
-
selectedSetting,
|
|
1063
|
-
}: {
|
|
1064
|
-
onEditSettings: () => void;
|
|
1065
|
-
isFocused: boolean;
|
|
1066
|
-
onToggleAutoUpdate: () => void;
|
|
1067
|
-
selectedSetting: number;
|
|
1068
|
-
}) {
|
|
1069
|
-
const config = loadConfig();
|
|
1070
|
-
const autoUpdateConfig = getAutoUpdateConfig();
|
|
1071
|
-
|
|
1072
|
-
return (
|
|
1073
|
-
<Box flexDirection="column" paddingLeft={2} flexGrow={1}>
|
|
1074
|
-
<Text color={colors.text} bold>Settings</Text>
|
|
1075
|
-
|
|
1076
|
-
<Box flexDirection="column" marginTop={1}>
|
|
1077
|
-
<Text>
|
|
1078
|
-
<Text color={colors.textDim}>Platform: </Text>
|
|
1079
|
-
<Text color={colors.text}>
|
|
1080
|
-
{config.platform === Platform.ClaudeCode ? 'Claude Code' : 'OpenCode'}
|
|
1081
|
-
</Text>
|
|
1082
|
-
</Text>
|
|
1083
|
-
<Text>
|
|
1084
|
-
<Text color={colors.textDim}>Your @mention: </Text>
|
|
1085
|
-
<Text color={colors.text}>{config.user_mention}</Text>
|
|
1086
|
-
</Text>
|
|
1087
|
-
</Box>
|
|
1088
|
-
|
|
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}>
|
|
1112
|
-
<Text color={colors.textDim}>Config: ~/.droid/config.yaml</Text>
|
|
1113
|
-
</Box>
|
|
1114
|
-
|
|
1115
|
-
{isFocused && (
|
|
1116
|
-
<Box marginTop={2}>
|
|
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{' '}
|
|
1123
|
-
</Text>
|
|
1124
|
-
</Box>
|
|
1125
|
-
)}
|
|
1126
|
-
|
|
1127
|
-
{isFocused && (
|
|
1128
|
-
<Box marginTop={1}>
|
|
1129
|
-
<Text color={colors.textDim}>↑↓ select · enter toggle/edit · esc back</Text>
|
|
1130
|
-
</Box>
|
|
1131
|
-
)}
|
|
1132
|
-
|
|
1133
|
-
{!isFocused && (
|
|
1134
|
-
<Box marginTop={2}>
|
|
1135
|
-
<Text color={colors.textDim}>press enter to configure</Text>
|
|
1136
|
-
</Box>
|
|
1137
|
-
)}
|
|
1138
|
-
</Box>
|
|
1139
|
-
);
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
interface SkillConfigScreenProps {
|
|
1143
|
-
skill: SkillManifest;
|
|
1144
|
-
onComplete: () => void;
|
|
1145
|
-
onCancel: () => void;
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
const MAX_VISIBLE_CONFIG_ITEMS = 4; // Each config item takes ~3 lines
|
|
1149
|
-
|
|
1150
|
-
function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenProps) {
|
|
1151
|
-
const configSchema = skill.config_schema || {};
|
|
1152
|
-
const configKeys = Object.keys(configSchema);
|
|
1153
|
-
|
|
1154
|
-
const initialOverrides = useMemo(() => loadSkillOverrides(skill.name), [skill.name]);
|
|
1155
|
-
|
|
1156
|
-
// Initialize values from saved overrides or defaults
|
|
1157
|
-
const [values, setValues] = useState<SkillOverrides>(() => {
|
|
1158
|
-
const initial: SkillOverrides = {};
|
|
1159
|
-
for (const key of configKeys) {
|
|
1160
|
-
const option = configSchema[key];
|
|
1161
|
-
initial[key] = initialOverrides[key] ?? option.default ?? (option.type === ConfigOptionType.Boolean ? false : '');
|
|
1162
|
-
}
|
|
1163
|
-
return initial;
|
|
1164
|
-
});
|
|
1165
|
-
|
|
1166
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
1167
|
-
const [scrollOffset, setScrollOffset] = useState(0);
|
|
1168
|
-
const [editingField, setEditingField] = useState<string | null>(null);
|
|
1169
|
-
const [editValue, setEditValue] = useState('');
|
|
1170
|
-
const [editingSelect, setEditingSelect] = useState<string | null>(null);
|
|
1171
|
-
const [selectOptionIndex, setSelectOptionIndex] = useState(0);
|
|
1172
|
-
|
|
1173
|
-
// Total items = config keys + Save button
|
|
1174
|
-
const totalItems = configKeys.length + 1;
|
|
1175
|
-
|
|
1176
|
-
const handleSave = () => {
|
|
1177
|
-
saveSkillOverrides(skill.name, values);
|
|
1178
|
-
onComplete();
|
|
1179
|
-
};
|
|
1180
|
-
|
|
1181
|
-
const handleSubmitEdit = () => {
|
|
1182
|
-
if (editingField) {
|
|
1183
|
-
setValues((prev) => ({ ...prev, [editingField]: editValue }));
|
|
1184
|
-
setEditingField(null);
|
|
1185
|
-
setEditValue('');
|
|
1186
|
-
}
|
|
1187
|
-
};
|
|
1188
|
-
|
|
1189
|
-
// Handle text input for string fields
|
|
1190
|
-
useInput((input, key) => {
|
|
1191
|
-
if (key.escape) {
|
|
1192
|
-
setEditingField(null);
|
|
1193
|
-
setEditValue('');
|
|
1194
|
-
}
|
|
1195
|
-
}, { isActive: editingField !== null });
|
|
1196
|
-
|
|
1197
|
-
// Handle select field editing
|
|
1198
|
-
useInput((input, key) => {
|
|
1199
|
-
if (!editingSelect) return;
|
|
1200
|
-
const option = configSchema[editingSelect];
|
|
1201
|
-
if (!option?.options) return;
|
|
1202
|
-
|
|
1203
|
-
if (key.escape) {
|
|
1204
|
-
setEditingSelect(null);
|
|
1205
|
-
return;
|
|
1206
|
-
}
|
|
1207
|
-
if (key.leftArrow || key.upArrow) {
|
|
1208
|
-
setSelectOptionIndex((prev) => Math.max(0, prev - 1));
|
|
1209
|
-
}
|
|
1210
|
-
if (key.rightArrow || key.downArrow) {
|
|
1211
|
-
setSelectOptionIndex((prev) => Math.min(option.options!.length - 1, prev + 1));
|
|
1212
|
-
}
|
|
1213
|
-
if (key.return) {
|
|
1214
|
-
setValues((prev) => ({ ...prev, [editingSelect]: option.options![selectOptionIndex] }));
|
|
1215
|
-
setEditingSelect(null);
|
|
1216
|
-
}
|
|
1217
|
-
}, { isActive: editingSelect !== null });
|
|
1218
|
-
|
|
1219
|
-
// Handle navigation and actions when not editing
|
|
1220
|
-
useInput((input, key) => {
|
|
1221
|
-
if (key.escape) {
|
|
1222
|
-
onCancel();
|
|
1223
|
-
return;
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
if (key.upArrow) {
|
|
1227
|
-
setSelectedIndex((prev) => {
|
|
1228
|
-
const newIndex = Math.max(0, prev - 1);
|
|
1229
|
-
// Scroll up if needed
|
|
1230
|
-
if (newIndex < scrollOffset) {
|
|
1231
|
-
setScrollOffset(newIndex);
|
|
1232
|
-
}
|
|
1233
|
-
return newIndex;
|
|
1234
|
-
});
|
|
1235
|
-
}
|
|
1236
|
-
if (key.downArrow) {
|
|
1237
|
-
// +1 for the Save button at the end
|
|
1238
|
-
setSelectedIndex((prev) => {
|
|
1239
|
-
const newIndex = Math.min(totalItems - 1, prev + 1);
|
|
1240
|
-
// Scroll down if needed
|
|
1241
|
-
if (newIndex >= scrollOffset + MAX_VISIBLE_CONFIG_ITEMS) {
|
|
1242
|
-
setScrollOffset(newIndex - MAX_VISIBLE_CONFIG_ITEMS + 1);
|
|
1243
|
-
}
|
|
1244
|
-
return newIndex;
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
if (key.return) {
|
|
1249
|
-
// Save button is at index === configKeys.length
|
|
1250
|
-
if (selectedIndex === configKeys.length) {
|
|
1251
|
-
handleSave();
|
|
1252
|
-
return;
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
const key = configKeys[selectedIndex];
|
|
1256
|
-
const option = configSchema[key];
|
|
1257
|
-
|
|
1258
|
-
if (option.type === ConfigOptionType.Boolean) {
|
|
1259
|
-
// Toggle boolean
|
|
1260
|
-
setValues((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
1261
|
-
} else if (option.type === ConfigOptionType.Select && option.options) {
|
|
1262
|
-
// Enter select edit mode
|
|
1263
|
-
const currentValue = String(values[key] || option.options[0]);
|
|
1264
|
-
const currentIndex = option.options.indexOf(currentValue);
|
|
1265
|
-
setSelectOptionIndex(currentIndex >= 0 ? currentIndex : 0);
|
|
1266
|
-
setEditingSelect(key);
|
|
1267
|
-
} else if (option.type === ConfigOptionType.String) {
|
|
1268
|
-
// Enter edit mode for string
|
|
1269
|
-
setEditingField(key);
|
|
1270
|
-
setEditValue(String(values[key] || ''));
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
}, { isActive: editingField === null && editingSelect === null });
|
|
1274
|
-
|
|
1275
|
-
if (configKeys.length === 0) {
|
|
1276
|
-
return (
|
|
1277
|
-
<Box flexDirection="column" padding={1}>
|
|
1278
|
-
<Box marginBottom={1}>
|
|
1279
|
-
<Text>
|
|
1280
|
-
<Text color={colors.textDim}>[</Text>
|
|
1281
|
-
<Text color={colors.primary}>●</Text>
|
|
1282
|
-
<Text color={colors.textDim}> </Text>
|
|
1283
|
-
<Text color={colors.primary}>●</Text>
|
|
1284
|
-
<Text color={colors.textDim}>] </Text>
|
|
1285
|
-
<Text color={colors.text} bold>configure {skill.name}</Text>
|
|
1286
|
-
</Text>
|
|
1287
|
-
</Box>
|
|
1288
|
-
<Text color={colors.textMuted}>This skill has no configuration options.</Text>
|
|
1289
|
-
<Box marginTop={1}>
|
|
1290
|
-
<Text color={colors.textDim}>esc to go back</Text>
|
|
1291
|
-
</Box>
|
|
1292
|
-
</Box>
|
|
1293
|
-
);
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
// Calculate visible range
|
|
1297
|
-
const visibleEndIndex = Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, totalItems);
|
|
1298
|
-
const visibleConfigKeys = configKeys.slice(scrollOffset, Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, configKeys.length));
|
|
1299
|
-
const showSaveButton = visibleEndIndex > configKeys.length || scrollOffset + MAX_VISIBLE_CONFIG_ITEMS > configKeys.length;
|
|
1300
|
-
const showTopIndicator = scrollOffset > 0;
|
|
1301
|
-
const showBottomIndicator = scrollOffset + MAX_VISIBLE_CONFIG_ITEMS < totalItems;
|
|
1302
|
-
|
|
1303
|
-
return (
|
|
1304
|
-
<Box flexDirection="column" padding={1}>
|
|
1305
|
-
<Box marginBottom={1}>
|
|
1306
|
-
<Text>
|
|
1307
|
-
<Text color={colors.textDim}>[</Text>
|
|
1308
|
-
<Text color={colors.primary}>●</Text>
|
|
1309
|
-
<Text color={colors.textDim}> </Text>
|
|
1310
|
-
<Text color={colors.primary}>●</Text>
|
|
1311
|
-
<Text color={colors.textDim}>] </Text>
|
|
1312
|
-
<Text color={colors.text} bold>configure {skill.name}</Text>
|
|
1313
|
-
</Text>
|
|
1314
|
-
</Box>
|
|
1315
|
-
|
|
1316
|
-
<Box flexDirection="column">
|
|
1317
|
-
{/* Scroll up indicator */}
|
|
1318
|
-
{showTopIndicator && (
|
|
1319
|
-
<Box marginBottom={1}>
|
|
1320
|
-
<Text color={colors.textDim}> ↑ {scrollOffset} more</Text>
|
|
1321
|
-
</Box>
|
|
1322
|
-
)}
|
|
1323
|
-
|
|
1324
|
-
{visibleConfigKeys.map((key) => {
|
|
1325
|
-
const actualIndex = configKeys.indexOf(key);
|
|
1326
|
-
const option = configSchema[key];
|
|
1327
|
-
const isSelected = selectedIndex === actualIndex;
|
|
1328
|
-
const isEditing = editingField === key;
|
|
1329
|
-
|
|
1330
|
-
return (
|
|
1331
|
-
<Box key={key} flexDirection="column" marginBottom={1}>
|
|
1332
|
-
<Text>
|
|
1333
|
-
<Text color={colors.textDim}>{isSelected ? '>' : ' '} </Text>
|
|
1334
|
-
<Text color={isSelected ? colors.text : colors.textMuted}>{key}</Text>
|
|
1335
|
-
</Text>
|
|
1336
|
-
<Text color={colors.textDim}> {option.description}</Text>
|
|
1337
|
-
<Box>
|
|
1338
|
-
<Text color={colors.textDim}> </Text>
|
|
1339
|
-
{option.type === ConfigOptionType.Boolean ? (
|
|
1340
|
-
<Text color={colors.text}>
|
|
1341
|
-
[{values[key] ? 'x' : ' '}] {values[key] ? 'enabled' : 'disabled'}
|
|
1342
|
-
</Text>
|
|
1343
|
-
) : option.type === ConfigOptionType.Select && option.options ? (
|
|
1344
|
-
<Text color={colors.text}>
|
|
1345
|
-
{option.options.map((opt, i) => {
|
|
1346
|
-
const isCurrentValue = String(values[key]) === opt;
|
|
1347
|
-
const isEditingThis = editingSelect === key;
|
|
1348
|
-
const isHighlighted = isEditingThis && selectOptionIndex === i;
|
|
1349
|
-
return (
|
|
1350
|
-
<Text key={opt}>
|
|
1351
|
-
{i > 0 && <Text color={colors.textDim}> · </Text>}
|
|
1352
|
-
<Text
|
|
1353
|
-
color={isHighlighted ? '#ffffff' : isCurrentValue ? colors.primary : colors.textMuted}
|
|
1354
|
-
backgroundColor={isHighlighted ? colors.primary : undefined}
|
|
1355
|
-
>
|
|
1356
|
-
{isCurrentValue && !isEditingThis ? `[${opt}]` : isHighlighted ? ` ${opt} ` : opt}
|
|
1357
|
-
</Text>
|
|
1358
|
-
</Text>
|
|
1359
|
-
);
|
|
1360
|
-
})}
|
|
1361
|
-
</Text>
|
|
1362
|
-
) : isEditing ? (
|
|
1363
|
-
<Box>
|
|
1364
|
-
<Text color={colors.textDim}>{'> '}</Text>
|
|
1365
|
-
<TextInput
|
|
1366
|
-
value={editValue}
|
|
1367
|
-
onChange={setEditValue}
|
|
1368
|
-
onSubmit={handleSubmitEdit}
|
|
1369
|
-
/>
|
|
1370
|
-
</Box>
|
|
1371
|
-
) : (
|
|
1372
|
-
<Text color={colors.text}>{String(values[key]) || '(not set)'}</Text>
|
|
1373
|
-
)}
|
|
1374
|
-
</Box>
|
|
1375
|
-
</Box>
|
|
1376
|
-
);
|
|
1377
|
-
})}
|
|
1378
|
-
|
|
1379
|
-
{/* Save button - show if in visible range */}
|
|
1380
|
-
{showSaveButton && (
|
|
1381
|
-
<Box marginTop={1}>
|
|
1382
|
-
<Text>
|
|
1383
|
-
<Text color={colors.textDim}>{selectedIndex === configKeys.length ? '>' : ' '} </Text>
|
|
1384
|
-
<Text
|
|
1385
|
-
backgroundColor={selectedIndex === configKeys.length ? colors.primary : undefined}
|
|
1386
|
-
color={selectedIndex === configKeys.length ? '#ffffff' : colors.textMuted}
|
|
1387
|
-
bold={selectedIndex === configKeys.length}
|
|
1388
|
-
>
|
|
1389
|
-
{' '}Save{' '}
|
|
1390
|
-
</Text>
|
|
1391
|
-
</Text>
|
|
1392
|
-
</Box>
|
|
1393
|
-
)}
|
|
1394
|
-
|
|
1395
|
-
{/* Scroll down indicator */}
|
|
1396
|
-
{showBottomIndicator && (
|
|
1397
|
-
<Box marginTop={1}>
|
|
1398
|
-
<Text color={colors.textDim}> ↓ {totalItems - scrollOffset - MAX_VISIBLE_CONFIG_ITEMS} more</Text>
|
|
1399
|
-
</Box>
|
|
1400
|
-
)}
|
|
1401
|
-
</Box>
|
|
1402
|
-
|
|
1403
|
-
<Box marginTop={1}>
|
|
1404
|
-
<Text color={colors.textDim}>
|
|
1405
|
-
{editingField
|
|
1406
|
-
? 'enter save · esc cancel'
|
|
1407
|
-
: editingSelect
|
|
1408
|
-
? '←→ choose · enter select · esc cancel'
|
|
1409
|
-
: '↑↓ select · enter toggle/edit · esc back'}
|
|
1410
|
-
</Text>
|
|
1411
|
-
</Box>
|
|
1412
|
-
</Box>
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
28
|
+
// Module-level variable to store exit message (printed after leaving alternate screen)
|
|
29
|
+
let exitMessage: string | null = null;
|
|
1415
30
|
|
|
1416
31
|
function App() {
|
|
1417
32
|
const { exit } = useApp();
|
|
@@ -1429,97 +44,23 @@ function App() {
|
|
|
1429
44
|
const [message, setMessage] = useState<{ text: string; type: 'success' | 'error' } | null>(null);
|
|
1430
45
|
const [isEditingSettings, setIsEditingSettings] = useState(false);
|
|
1431
46
|
const [readmeContent, setReadmeContent] = useState<{ title: string; content: string } | null>(null);
|
|
1432
|
-
const [selectedSetting, setSelectedSetting] = useState(0);
|
|
1433
|
-
const [isUpdating, setIsUpdating] = useState(false);
|
|
1434
|
-
const [isUpdatingTools, setIsUpdatingTools] = useState(false);
|
|
1435
47
|
const [previousView, setPreviousView] = useState<View>('detail'); // Track where to return from readme
|
|
1436
|
-
const [toolUpdates, setToolUpdates] = useState<ToolUpdateInfo[]>([]);
|
|
1437
|
-
const [autoUpdatedTools, setAutoUpdatedTools] = useState<string[]>([]);
|
|
1438
|
-
|
|
1439
|
-
// Check for updates once on mount
|
|
1440
|
-
const updateInfo = useMemo(() => getUpdateInfo(), []);
|
|
1441
|
-
const autoUpdateConfig = useMemo(() => getAutoUpdateConfig(), []);
|
|
1442
48
|
|
|
1443
|
-
const handleUpdate = (
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
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
|
-
`;
|
|
1464
|
-
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');
|
|
1465
57
|
} else {
|
|
1466
|
-
|
|
1467
|
-
setMessage({ text: result.message, type: 'error' });
|
|
1468
|
-
// Continue to menu on failure
|
|
1469
|
-
if (!configExists()) {
|
|
1470
|
-
setView('setup');
|
|
1471
|
-
} else {
|
|
1472
|
-
setView('menu');
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
}, 100);
|
|
1476
|
-
};
|
|
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);
|
|
58
|
+
setView('menu');
|
|
1498
59
|
}
|
|
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
|
-
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
1521
62
|
|
|
1522
|
-
|
|
63
|
+
const proceedToNextView = () => {
|
|
1523
64
|
if (!configExists()) {
|
|
1524
65
|
setView('setup');
|
|
1525
66
|
} else {
|
|
@@ -1527,38 +68,15 @@ ${dim}────────────────────────
|
|
|
1527
68
|
}
|
|
1528
69
|
};
|
|
1529
70
|
|
|
1530
|
-
const
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
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
|
-
|
|
71
|
+
const {
|
|
72
|
+
toolUpdates,
|
|
73
|
+
isUpdatingTools,
|
|
74
|
+
autoUpdatedTools,
|
|
75
|
+
checkForUpdates,
|
|
76
|
+
updateAllTools,
|
|
77
|
+
enableAutoUpdateAndUpdate,
|
|
78
|
+
} = useToolUpdates({
|
|
79
|
+
onUpdateComplete: ({ successCount, failCount, silent }) => {
|
|
1562
80
|
if (successCount > 0) {
|
|
1563
81
|
setMessage({
|
|
1564
82
|
text: silent
|
|
@@ -1567,52 +85,31 @@ ${dim}────────────────────────
|
|
|
1567
85
|
type: failCount > 0 ? 'error' : 'success',
|
|
1568
86
|
});
|
|
1569
87
|
}
|
|
88
|
+
proceedToNextView();
|
|
89
|
+
},
|
|
90
|
+
});
|
|
1570
91
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
} else {
|
|
1575
|
-
setView('menu');
|
|
1576
|
-
}
|
|
1577
|
-
}, 100);
|
|
1578
|
-
};
|
|
92
|
+
// Check for tool updates and proceed to next view
|
|
93
|
+
const checkToolUpdatesAndProceed = () => {
|
|
94
|
+
const { updates, shouldAutoUpdate } = checkForUpdates();
|
|
1579
95
|
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
handleUpdateAllTools();
|
|
1585
|
-
};
|
|
96
|
+
if (shouldAutoUpdate) {
|
|
97
|
+
updateAllTools(updates, true);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
1586
100
|
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
} else {
|
|
1591
|
-
setView('menu');
|
|
101
|
+
if (updates.length > 0) {
|
|
102
|
+
setView('tool-updates');
|
|
103
|
+
return;
|
|
1592
104
|
}
|
|
1593
|
-
};
|
|
1594
105
|
|
|
1595
|
-
|
|
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
|
-
});
|
|
106
|
+
proceedToNextView();
|
|
1603
107
|
};
|
|
1604
108
|
|
|
1605
|
-
const
|
|
1606
|
-
|
|
1607
|
-
setAutoUpdateConfig({ app: !current.app });
|
|
1608
|
-
setMessage({
|
|
1609
|
-
text: `✓ Auto-update app ${!current.app ? 'enabled' : 'disabled'}`,
|
|
1610
|
-
type: 'success',
|
|
1611
|
-
});
|
|
109
|
+
const handleSkipToolUpdates = () => {
|
|
110
|
+
proceedToNextView();
|
|
1612
111
|
};
|
|
1613
112
|
|
|
1614
|
-
const MAX_VISIBLE_ITEMS = 6;
|
|
1615
|
-
|
|
1616
113
|
const tools = getBundledTools();
|
|
1617
114
|
// Keep skills for configure view (tools configure via their primary skill)
|
|
1618
115
|
const skills = getBundledSkills();
|
|
@@ -1668,32 +165,18 @@ ${dim}────────────────────────
|
|
|
1668
165
|
setView('detail');
|
|
1669
166
|
} else if (activeTab === 'settings') {
|
|
1670
167
|
setView('detail');
|
|
1671
|
-
setSelectedSetting(0);
|
|
1672
168
|
}
|
|
1673
169
|
}
|
|
1674
170
|
} else if (view === 'detail') {
|
|
1675
171
|
if (key.escape || key.backspace) {
|
|
1676
172
|
setView('menu');
|
|
1677
173
|
setSelectedAction(0);
|
|
1678
|
-
setSelectedSetting(0);
|
|
1679
174
|
}
|
|
1680
175
|
if (activeTab === 'settings') {
|
|
1681
|
-
// Settings detail view
|
|
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
|
-
}
|
|
176
|
+
// Settings detail view - just enter to edit
|
|
1688
177
|
if (key.return) {
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
} else if (selectedSetting === 1) {
|
|
1692
|
-
handleToggleAutoUpdateApp();
|
|
1693
|
-
} else if (selectedSetting === 2) {
|
|
1694
|
-
setIsEditingSettings(true);
|
|
1695
|
-
setView('setup');
|
|
1696
|
-
}
|
|
178
|
+
setIsEditingSettings(true);
|
|
179
|
+
setView('setup');
|
|
1697
180
|
}
|
|
1698
181
|
}
|
|
1699
182
|
if (key.leftArrow && activeTab === 'tools') {
|
|
@@ -1807,8 +290,8 @@ ${dim}────────────────────────
|
|
|
1807
290
|
return (
|
|
1808
291
|
<ToolUpdatePrompt
|
|
1809
292
|
toolUpdates={toolUpdates}
|
|
1810
|
-
onUpdateAll={() =>
|
|
1811
|
-
onAlways={
|
|
293
|
+
onUpdateAll={() => updateAllTools()}
|
|
294
|
+
onAlways={enableAutoUpdateAndUpdate}
|
|
1812
295
|
onSkip={handleSkipToolUpdates}
|
|
1813
296
|
isUpdating={isUpdatingTools}
|
|
1814
297
|
/>
|
|
@@ -1967,13 +450,7 @@ ${dim}────────────────────────
|
|
|
1967
450
|
|
|
1968
451
|
{activeTab === 'settings' && (
|
|
1969
452
|
<SettingsDetails
|
|
1970
|
-
onEditSettings={() => {
|
|
1971
|
-
setIsEditingSettings(true);
|
|
1972
|
-
setView('setup');
|
|
1973
|
-
}}
|
|
1974
453
|
isFocused={view === 'detail'}
|
|
1975
|
-
onToggleAutoUpdate={handleToggleAutoUpdateTools}
|
|
1976
|
-
selectedSetting={selectedSetting}
|
|
1977
454
|
/>
|
|
1978
455
|
)}
|
|
1979
456
|
</Box>
|
|
@@ -1985,7 +462,7 @@ export async function tuiCommand(): Promise<void> {
|
|
|
1985
462
|
process.stdout.write('\x1b[?1049h');
|
|
1986
463
|
process.stdout.write('\x1b[H'); // Move cursor to top-left
|
|
1987
464
|
|
|
1988
|
-
const {
|
|
465
|
+
const { waitUntilExit } = render(<App />);
|
|
1989
466
|
|
|
1990
467
|
await waitUntilExit();
|
|
1991
468
|
|