@orderful/droid 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/.claude/CLAUDE.md +18 -40
  2. package/AGENTS.md +75 -0
  3. package/CHANGELOG.md +25 -0
  4. package/dist/bin/droid.js +3064 -54
  5. package/dist/commands/setup.d.ts +1 -1
  6. package/dist/commands/setup.d.ts.map +1 -1
  7. package/dist/commands/tui/components/Badge.d.ts +13 -0
  8. package/dist/commands/tui/components/Badge.d.ts.map +1 -0
  9. package/dist/commands/tui/components/Markdown.d.ts +5 -0
  10. package/dist/commands/tui/components/Markdown.d.ts.map +1 -0
  11. package/dist/commands/tui/components/SettingsDetails.d.ts +5 -0
  12. package/dist/commands/tui/components/SettingsDetails.d.ts.map +1 -0
  13. package/dist/commands/tui/components/TabBar.d.ts +10 -0
  14. package/dist/commands/tui/components/TabBar.d.ts.map +1 -0
  15. package/dist/commands/tui/components/ToolDetails.d.ts +8 -0
  16. package/dist/commands/tui/components/ToolDetails.d.ts.map +1 -0
  17. package/dist/commands/tui/components/ToolItem.d.ts +9 -0
  18. package/dist/commands/tui/components/ToolItem.d.ts.map +1 -0
  19. package/dist/commands/tui/constants.d.ts +16 -0
  20. package/dist/commands/tui/constants.d.ts.map +1 -0
  21. package/dist/commands/tui/hooks/useAppUpdate.d.ts +13 -0
  22. package/dist/commands/tui/hooks/useAppUpdate.d.ts.map +1 -0
  23. package/dist/commands/tui/hooks/useToolUpdates.d.ts +22 -0
  24. package/dist/commands/tui/hooks/useToolUpdates.d.ts.map +1 -0
  25. package/dist/commands/tui/types.d.ts +5 -0
  26. package/dist/commands/tui/types.d.ts.map +1 -0
  27. package/dist/commands/tui/views/ReadmeViewer.d.ts +7 -0
  28. package/dist/commands/tui/views/ReadmeViewer.d.ts.map +1 -0
  29. package/dist/commands/tui/views/SetupScreen.d.ts +8 -0
  30. package/dist/commands/tui/views/SetupScreen.d.ts.map +1 -0
  31. package/dist/commands/tui/views/SkillConfigScreen.d.ts +8 -0
  32. package/dist/commands/tui/views/SkillConfigScreen.d.ts.map +1 -0
  33. package/dist/commands/tui/views/ToolExplorer.d.ts +8 -0
  34. package/dist/commands/tui/views/ToolExplorer.d.ts.map +1 -0
  35. package/dist/commands/tui/views/ToolUpdatePrompt.d.ts +10 -0
  36. package/dist/commands/tui/views/ToolUpdatePrompt.d.ts.map +1 -0
  37. package/dist/commands/tui/views/WelcomeScreen.d.ts +11 -0
  38. package/dist/commands/tui/views/WelcomeScreen.d.ts.map +1 -0
  39. package/dist/commands/tui.d.ts.map +1 -1
  40. package/dist/index.d.ts +4 -4
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +952 -6
  43. package/dist/lib/agents.d.ts +1 -1
  44. package/dist/lib/agents.d.ts.map +1 -1
  45. package/dist/lib/config.d.ts +1 -1
  46. package/dist/lib/config.d.ts.map +1 -1
  47. package/dist/lib/platforms.d.ts +1 -1
  48. package/dist/lib/platforms.d.ts.map +1 -1
  49. package/dist/lib/skill-config.d.ts +1 -1
  50. package/dist/lib/skill-config.d.ts.map +1 -1
  51. package/dist/lib/skills.d.ts +1 -1
  52. package/dist/lib/skills.d.ts.map +1 -1
  53. package/dist/lib/tools.d.ts +1 -1
  54. package/dist/lib/tools.d.ts.map +1 -1
  55. package/package.json +3 -3
  56. package/scripts/build.ts +78 -0
  57. package/src/bin/droid.ts +8 -8
  58. package/src/commands/config.ts +1 -1
  59. package/src/commands/install.ts +3 -3
  60. package/src/commands/setup.test.ts +1 -1
  61. package/src/commands/setup.ts +3 -3
  62. package/src/commands/skills.ts +4 -4
  63. package/src/commands/tui/components/Badge.tsx +86 -0
  64. package/src/commands/tui/components/Markdown.tsx +48 -0
  65. package/src/commands/tui/components/SettingsDetails.tsx +70 -0
  66. package/src/commands/tui/components/TabBar.tsx +26 -0
  67. package/src/commands/tui/components/ToolDetails.tsx +117 -0
  68. package/src/commands/tui/components/ToolItem.tsx +39 -0
  69. package/src/commands/tui/constants.ts +17 -0
  70. package/src/commands/tui/hooks/useAppUpdate.ts +67 -0
  71. package/src/commands/tui/hooks/useToolUpdates.ts +110 -0
  72. package/src/commands/tui/types.ts +4 -0
  73. package/src/commands/tui/views/ReadmeViewer.tsx +93 -0
  74. package/src/commands/tui/views/SetupScreen.tsx +242 -0
  75. package/src/commands/tui/views/SkillConfigScreen.tsx +278 -0
  76. package/src/commands/tui/views/ToolExplorer.tsx +190 -0
  77. package/src/commands/tui/views/ToolUpdatePrompt.tsx +109 -0
  78. package/src/commands/tui/views/WelcomeScreen.tsx +149 -0
  79. package/src/commands/tui.tsx +65 -1587
  80. package/src/commands/uninstall.ts +2 -2
  81. package/src/commands/update.ts +1 -1
  82. package/src/index.ts +4 -4
  83. package/src/lib/agents.ts +4 -4
  84. package/src/lib/config.ts +1 -1
  85. package/src/lib/platforms.ts +1 -1
  86. package/src/lib/skill-config.ts +2 -2
  87. package/src/lib/skills.test.ts +2 -2
  88. package/src/lib/skills.ts +5 -5
  89. package/src/lib/tools.ts +3 -3
  90. package/src/lib/types.test.ts +1 -1
  91. package/src/lib/version.test.ts +1 -1
  92. package/dist/bin/droid.js.map +0 -1
  93. package/dist/commands/config.js +0 -67
  94. package/dist/commands/config.js.map +0 -1
  95. package/dist/commands/install.js +0 -45
  96. package/dist/commands/install.js.map +0 -1
  97. package/dist/commands/setup.js +0 -269
  98. package/dist/commands/setup.js.map +0 -1
  99. package/dist/commands/skills.js +0 -144
  100. package/dist/commands/skills.js.map +0 -1
  101. package/dist/commands/tui.js +0 -1008
  102. package/dist/commands/tui.js.map +0 -1
  103. package/dist/commands/uninstall.js +0 -26
  104. package/dist/commands/uninstall.js.map +0 -1
  105. package/dist/commands/update.js +0 -45
  106. package/dist/commands/update.js.map +0 -1
  107. package/dist/index.js.map +0 -1
  108. package/dist/lib/agents.js +0 -248
  109. package/dist/lib/agents.js.map +0 -1
  110. package/dist/lib/config.js +0 -196
  111. package/dist/lib/config.js.map +0 -1
  112. package/dist/lib/platforms.js +0 -52
  113. package/dist/lib/platforms.js.map +0 -1
  114. package/dist/lib/quotes.js +0 -24
  115. package/dist/lib/quotes.js.map +0 -1
  116. package/dist/lib/skill-config.js +0 -80
  117. package/dist/lib/skill-config.js.map +0 -1
  118. package/dist/lib/skills.js +0 -582
  119. package/dist/lib/skills.js.map +0 -1
  120. package/dist/lib/tools.js +0 -145
  121. package/dist/lib/tools.js.map +0 -1
  122. package/dist/lib/types.js +0 -50
  123. package/dist/lib/types.js.map +0 -1
  124. package/dist/lib/version.js +0 -100
  125. package/dist/lib/version.js.map +0 -1
@@ -1,1008 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { render, Box, Text, useInput, useApp } from 'ink';
3
- import TextInput from 'ink-text-input';
4
- import { useState, useMemo } from 'react';
5
- import { execSync } from 'child_process';
6
- import { existsSync, readFileSync } from 'fs';
7
- import { join } from 'path';
8
- import { getBundledSkills, installSkill, uninstallSkill, updateSkill, } from '../lib/skills.js';
9
- import { getBundledTools, getBundledToolsDir, isToolInstalled, getToolUpdateStatus, getInstalledToolVersion, getToolsWithUpdates } from '../lib/tools.js';
10
- import { configExists, loadConfig, saveConfig, loadSkillOverrides, saveSkillOverrides, getAutoUpdateConfig, setAutoUpdateConfig } from '../lib/config.js';
11
- import { configurePlatformPermissions } from './setup.js';
12
- import { Platform, BuiltInOutput, ConfigOptionType } from '../lib/types.js';
13
- import { getVersion, getUpdateInfo, runUpdate } from '../lib/version.js';
14
- import { getRandomQuote } from '../lib/quotes.js';
15
- // Module-level variable to store exit message (printed after leaving alternate screen)
16
- let exitMessage = null;
17
- const colors = {
18
- primary: '#6366f1',
19
- bgSelected: '#2d2d2d',
20
- border: '#3a3a3a',
21
- text: '#e8e8e8',
22
- textMuted: '#999999',
23
- textDim: '#6a6a6a',
24
- success: '#4ade80',
25
- error: '#f87171',
26
- // Component type badges
27
- skill: '#ec4899', // pink/magenta
28
- command: '#22d3ee', // cyan
29
- agent: '#fbbf24', // yellow/amber
30
- };
31
- function Badge({ type, label, isSelected = false, dimmed = false, }) {
32
- const color = colors[type];
33
- const displayLabel = label || type.charAt(0).toUpperCase() + type.slice(1);
34
- if (dimmed) {
35
- return (_jsx(Text, { color: colors.textDim, children: displayLabel }));
36
- }
37
- return (_jsx(Text, { backgroundColor: isSelected ? color : undefined, color: isSelected ? '#000000' : color, bold: isSelected, children: isSelected ? ` ${displayLabel} ` : displayLabel }));
38
- }
39
- function ComponentBadges({ tool, compact = false }) {
40
- const hasSkills = tool.includes.skills.length > 0;
41
- const hasCommands = tool.includes.commands.length > 0;
42
- const hasAgents = tool.includes.agents.length > 0;
43
- if (compact) {
44
- // Show colored squares for list view (single char with spacing)
45
- const parts = [];
46
- if (hasSkills)
47
- parts.push('skill');
48
- if (hasCommands)
49
- parts.push('command');
50
- if (hasAgents)
51
- parts.push('agent');
52
- return (_jsx(Box, { flexDirection: "row", children: parts.map((type, i) => (_jsxs(Text, { children: [_jsx(Text, { backgroundColor: colors[type], children: " " }), i < parts.length - 1 && ' '] }, type))) }));
53
- }
54
- return (_jsxs(Box, { flexDirection: "row", children: [hasSkills && (_jsx(Box, { marginRight: 1, children: _jsx(Text, { backgroundColor: colors.skill, color: "#000000", bold: true, children: ` ${tool.includes.skills.length} skill${tool.includes.skills.length > 1 ? 's' : ''} ` }) })), hasCommands && (_jsx(Box, { marginRight: 1, children: _jsx(Text, { backgroundColor: colors.command, color: "#000000", bold: true, children: ` ${tool.includes.commands.length} cmd${tool.includes.commands.length > 1 ? 's' : ''} ` }) })), hasAgents && (_jsx(Box, { marginRight: 1, children: _jsx(Text, { backgroundColor: colors.agent, color: "#000000", bold: true, children: ` ${tool.includes.agents.length} agent${tool.includes.agents.length > 1 ? 's' : ''} ` }) }))] }));
55
- }
56
- function detectInstalledPlatforms() {
57
- const installed = new Set();
58
- try {
59
- execSync('claude --version', { stdio: 'ignore' });
60
- installed.add(Platform.ClaudeCode);
61
- }
62
- catch {
63
- // Claude Code not found
64
- }
65
- try {
66
- execSync('opencode --version', { stdio: 'ignore' });
67
- installed.add(Platform.OpenCode);
68
- }
69
- catch {
70
- // OpenCode not found
71
- }
72
- return installed;
73
- }
74
- function getDefaultPlatform() {
75
- const installed = detectInstalledPlatforms();
76
- if (installed.has(Platform.ClaudeCode))
77
- return Platform.ClaudeCode;
78
- if (installed.has(Platform.OpenCode))
79
- return Platform.OpenCode;
80
- return Platform.ClaudeCode;
81
- }
82
- function getOutputOptions() {
83
- const options = [
84
- { label: 'Terminal (display in CLI)', value: BuiltInOutput.Terminal },
85
- { label: 'Editor ($EDITOR)', value: BuiltInOutput.Editor },
86
- ];
87
- const skills = getBundledSkills();
88
- for (const skill of skills) {
89
- if (skill.provides_output) {
90
- options.push({ label: `${skill.name} (${skill.description})`, value: skill.name });
91
- }
92
- }
93
- return options;
94
- }
95
- function WelcomeScreen({ onContinue, onUpdate, onAlways, onExit, updateInfo, isUpdating }) {
96
- const [selectedButton, setSelectedButton] = useState(0);
97
- const welcomeQuote = useMemo(() => getRandomQuote(), []);
98
- useInput((input, key) => {
99
- if (isUpdating)
100
- return;
101
- if (updateInfo.hasUpdate) {
102
- if (key.leftArrow) {
103
- setSelectedButton((prev) => Math.max(0, prev - 1));
104
- }
105
- if (key.rightArrow) {
106
- setSelectedButton((prev) => Math.min(2, prev + 1));
107
- }
108
- if (key.return) {
109
- if (selectedButton === 0) {
110
- onUpdate();
111
- }
112
- else if (selectedButton === 1) {
113
- onAlways();
114
- }
115
- else {
116
- onContinue();
117
- }
118
- }
119
- if (input === 'q') {
120
- onExit();
121
- }
122
- }
123
- else {
124
- if (key.return) {
125
- onContinue();
126
- }
127
- if (input === 'q') {
128
- onExit();
129
- }
130
- }
131
- });
132
- const hasUpdate = updateInfo.hasUpdate && updateInfo.latestVersion;
133
- return (_jsx(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: 18, children: _jsxs(Box, { flexDirection: "column", alignItems: "center", borderStyle: "single", borderColor: hasUpdate ? '#eab308' : colors.border, paddingX: 4, paddingY: 1, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "\u2554\u2550\u2550\u2550\u2550\u2550\u2557 " }), _jsx(Text, { color: colors.text, children: "droid" }), _jsxs(Text, { color: colors.textDim, children: [" v", updateInfo.currentVersion] })] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "\u2551 " }), _jsx(Text, { color: hasUpdate ? '#eab308' : colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: hasUpdate ? '#eab308' : colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " \u2551 " }), _jsx(Text, { color: colors.textMuted, children: "Droid, teaching your AI new tricks" })] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "\u255A\u2550\u2566\u2550\u2566\u2550\u255D " }), _jsx(Text, { color: colors.textDim, children: "github.com/Orderful/droid" })] })] }), hasUpdate ? (_jsxs(_Fragment, { children: [_jsxs(Box, { marginTop: 2, marginBottom: 1, flexDirection: "column", alignItems: "center", children: [_jsx(Text, { color: "#eab308", italic: true, children: "\"The odds of functioning optimally without this" }), _jsx(Text, { color: "#eab308", italic: true, children: "update are approximately 3,720 to 1.\"" })] }), _jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: colors.textMuted, children: ["v", updateInfo.currentVersion, " \u2192 v", updateInfo.latestVersion] }) }), isUpdating ? (_jsx(Text, { color: "#eab308", children: "Updating..." })) : (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { backgroundColor: selectedButton === 0 ? '#eab308' : colors.bgSelected, color: selectedButton === 0 ? '#000000' : colors.textMuted, bold: selectedButton === 0, children: [' ', "Update", ' '] }), _jsx(Text, { children: " " }), _jsxs(Text, { backgroundColor: selectedButton === 1 ? '#eab308' : colors.bgSelected, color: selectedButton === 1 ? '#000000' : colors.textMuted, bold: selectedButton === 1, children: [' ', "Always", ' '] }), _jsx(Text, { children: " " }), _jsxs(Text, { backgroundColor: selectedButton === 2 ? colors.bgSelected : undefined, color: selectedButton === 2 ? colors.text : colors.textMuted, bold: selectedButton === 2, children: [' ', "Skip", ' '] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2190\u2192 select \u00B7 enter \u00B7 \"Always\" enables auto-update" }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 2, marginBottom: 1, children: _jsx(Text, { backgroundColor: colors.primary, color: "#ffffff", bold: true, children: ` ${welcomeQuote} ` }) }), _jsx(Text, { color: colors.textDim, children: "press enter" })] }))] }) }));
134
- }
135
- function ToolUpdatePrompt({ toolUpdates, onUpdateAll, onAlways, onSkip, isUpdating }) {
136
- const [selectedButton, setSelectedButton] = useState(0);
137
- useInput((input, key) => {
138
- if (isUpdating)
139
- return;
140
- if (key.leftArrow) {
141
- setSelectedButton((prev) => Math.max(0, prev - 1));
142
- }
143
- if (key.rightArrow) {
144
- setSelectedButton((prev) => Math.min(2, prev + 1));
145
- }
146
- if (key.return) {
147
- if (selectedButton === 0) {
148
- onUpdateAll();
149
- }
150
- else if (selectedButton === 1) {
151
- onAlways();
152
- }
153
- else {
154
- onSkip();
155
- }
156
- }
157
- if (input === 'q') {
158
- onSkip();
159
- }
160
- });
161
- const buttons = [
162
- { label: 'Update All', action: onUpdateAll },
163
- { label: 'Always', action: onAlways },
164
- { label: 'Skip', action: onSkip },
165
- ];
166
- return (_jsx(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: 18, children: _jsxs(Box, { flexDirection: "column", alignItems: "center", borderStyle: "single", borderColor: colors.primary, paddingX: 4, paddingY: 1, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "\u2554\u2550\u2550\u2550\u2550\u2550\u2557 " }), _jsx(Text, { color: colors.text, children: "Tool Updates" })] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "\u2551 " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " \u2551 " }), _jsxs(Text, { color: colors.textMuted, children: [toolUpdates.length, " tool", toolUpdates.length > 1 ? 's have' : ' has', " updates available"] })] }), _jsx(Text, { children: _jsx(Text, { color: colors.textDim, children: "\u255A\u2550\u2566\u2550\u2566\u2550\u255D" }) })] }), _jsxs(Box, { marginTop: 1, marginBottom: 1, flexDirection: "column", children: [toolUpdates.slice(0, 5).map((tool) => (_jsxs(Text, { color: colors.textMuted, children: [_jsx(Text, { color: colors.primary, children: "\u2191" }), " ", tool.name, _jsxs(Text, { color: colors.textDim, children: [" ", tool.installedVersion, " \u2192 ", tool.bundledVersion] })] }, tool.name))), toolUpdates.length > 5 && (_jsxs(Text, { color: colors.textDim, children: ["...and ", toolUpdates.length - 5, " more"] }))] }), isUpdating ? (_jsx(Text, { color: colors.primary, children: "Updating tools..." })) : (_jsx(Box, { flexDirection: "row", children: buttons.map((button, index) => (_jsxs(Text, { backgroundColor: selectedButton === index ? colors.primary : colors.bgSelected, color: selectedButton === index ? '#000000' : colors.textMuted, bold: selectedButton === index, children: [' ', button.label, ' '] }, button.label))) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2190\u2192 select \u00B7 enter \u00B7 \"Always\" enables auto-update" }) })] }) }));
167
- }
168
- function SetupScreen({ onComplete, onSkip, initialConfig }) {
169
- const [step, setStep] = useState('platform');
170
- const [platform, setPlatform] = useState(initialConfig?.platform || getDefaultPlatform());
171
- const [userMention, setUserMention] = useState(initialConfig?.user_mention || '@user');
172
- const [selectedIndex, setSelectedIndex] = useState(0);
173
- const [error, setError] = useState(null);
174
- // Cache detection results once on mount (avoids re-running execSync on every render)
175
- const installedPlatforms = useMemo(() => detectInstalledPlatforms(), []);
176
- const platformOptions = useMemo(() => [
177
- { label: 'Claude Code', value: Platform.ClaudeCode },
178
- { label: 'OpenCode', value: Platform.OpenCode },
179
- ], []);
180
- const steps = ['platform', 'user_mention', 'confirm'];
181
- const stepIndex = steps.indexOf(step);
182
- const totalSteps = steps.length - 1; // Don't count confirm as a step
183
- const handleUserMentionSubmit = () => {
184
- // Auto-add @ prefix if missing
185
- const mention = userMention.startsWith('@') ? userMention : `@${userMention}`;
186
- setUserMention(mention);
187
- setError(null);
188
- setStep('confirm');
189
- };
190
- // Handle escape during text input (only intercept escape, nothing else)
191
- useInput((input, key) => {
192
- if (key.escape) {
193
- setStep('platform');
194
- setSelectedIndex(0);
195
- }
196
- }, { isActive: step === 'user_mention' });
197
- // Handle all input for non-text-input steps
198
- useInput((input, key) => {
199
- if (key.escape) {
200
- if (step === 'platform') {
201
- onSkip();
202
- }
203
- else if (step === 'confirm') {
204
- setStep('user_mention');
205
- }
206
- return;
207
- }
208
- if (step === 'platform') {
209
- if (key.upArrow)
210
- setSelectedIndex((prev) => Math.max(0, prev - 1));
211
- if (key.downArrow)
212
- setSelectedIndex((prev) => Math.min(platformOptions.length - 1, prev + 1));
213
- if (key.return) {
214
- setPlatform(platformOptions[selectedIndex].value);
215
- setStep('user_mention');
216
- }
217
- }
218
- else if (step === 'confirm') {
219
- if (key.return) {
220
- const existingConfig = loadConfig();
221
- const config = {
222
- ...existingConfig,
223
- platform: platform,
224
- user_mention: userMention,
225
- output_preference: BuiltInOutput.Terminal, // Default to terminal
226
- };
227
- saveConfig(config);
228
- configurePlatformPermissions(platform);
229
- onComplete();
230
- }
231
- }
232
- }, { isActive: step !== 'user_mention' });
233
- const renderHeader = () => (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "[" }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: "] " }), _jsx(Text, { color: colors.text, bold: true, children: "droid setup" }), _jsxs(Text, { color: colors.textDim, children: [" \u00B7 Step ", Math.min(stepIndex + 1, totalSteps), " of ", totalSteps] })] }) }));
234
- if (step === 'platform') {
235
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [renderHeader(), _jsx(Text, { color: colors.text, children: "Which platform are you using?" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: platformOptions.map((option, index) => (_jsxs(Text, { children: [_jsxs(Text, { color: colors.textDim, children: [index === selectedIndex ? '>' : ' ', " "] }), _jsx(Text, { color: index === selectedIndex ? colors.text : colors.textMuted, children: option.label }), installedPlatforms.has(option.value) && _jsx(Text, { color: colors.success, children: " (detected)" })] }, option.value))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2191\u2193 select \u00B7 enter next \u00B7 esc skip" }) })] }));
236
- }
237
- if (step === 'user_mention') {
238
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [renderHeader(), _jsx(Text, { color: colors.text, children: "What @mention should be used for you?" }), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: colors.textDim, children: '> ' }), _jsx(TextInput, { value: userMention, onChange: setUserMention, onSubmit: handleUserMentionSubmit, placeholder: "@user" })] }), error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.error, children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "enter next \u00B7 esc back" }) })] }));
239
- }
240
- // Confirm step
241
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [renderHeader(), _jsx(Text, { color: colors.text, bold: true, children: "Review your settings" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "Platform: " }), _jsx(Text, { color: colors.text, children: platform === Platform.ClaudeCode ? 'Claude Code' : 'OpenCode' })] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "Your @mention: " }), _jsx(Text, { color: colors.text, children: userMention })] })] }), _jsx(Box, { marginTop: 2, children: _jsxs(Text, { backgroundColor: colors.primary, color: "#ffffff", bold: true, children: [' ', "Save", ' '] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "enter save \u00B7 esc back" }) })] }));
242
- }
243
- function TabBar({ tabs, activeTab }) {
244
- return (_jsx(Box, { flexDirection: "row", flexWrap: "wrap", children: tabs.map((tab) => (_jsxs(Text, { backgroundColor: tab.id === activeTab ? colors.primary : undefined, color: tab.id === activeTab ? '#ffffff' : colors.textMuted, bold: tab.id === activeTab, wrap: "truncate", children: [' ', tab.label, ' '] }, tab.id))) }));
245
- }
246
- function ToolItem({ tool, isSelected, isActive, wasAutoUpdated, }) {
247
- const installed = isToolInstalled(tool.name);
248
- const installedVersion = getInstalledToolVersion(tool.name);
249
- const updateStatus = getToolUpdateStatus(tool.name);
250
- return (_jsx(Box, { paddingX: 1, backgroundColor: isActive ? colors.bgSelected : undefined, children: _jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: colors.textDim, children: [isSelected ? '>' : ' ', " "] }), _jsx(Text, { color: isSelected || isActive ? colors.text : colors.textMuted, children: tool.name }), installed && installedVersion && _jsxs(Text, { color: colors.textDim, children: [" v", installedVersion] }), installed && _jsx(Text, { color: colors.success, children: " \u2713" }), wasAutoUpdated && _jsx(Text, { color: colors.success, children: " \u2191" }), updateStatus.hasUpdate && !wasAutoUpdated && _jsx(Text, { color: colors.primary, children: " \u2191" }), _jsx(Text, { children: " " }), tool.includes.skills.length > 0 && _jsx(Text, { color: colors.skill, children: "\u25CF " }), tool.includes.commands.length > 0 && _jsx(Text, { color: colors.command, children: "\u25CF " }), tool.includes.agents.length > 0 && _jsx(Text, { color: colors.agent, children: "\u25CF" })] }) }));
251
- }
252
- function ToolDetails({ tool, isFocused, selectedAction, }) {
253
- if (!tool) {
254
- return (_jsx(Box, { paddingLeft: 2, flexGrow: 1, children: _jsx(Text, { color: colors.textDim, children: "Select a tool" }) }));
255
- }
256
- const installed = isToolInstalled(tool.name);
257
- const installedVersion = getInstalledToolVersion(tool.name);
258
- const updateStatus = getToolUpdateStatus(tool.name);
259
- const isSystemTool = tool.system === true;
260
- const actions = installed
261
- ? [
262
- { id: 'explore', label: 'Explore', variant: 'default' },
263
- ...(updateStatus.hasUpdate
264
- ? [{ id: 'update', label: `Update (${updateStatus.bundledVersion})`, variant: 'primary' }]
265
- : []),
266
- { id: 'configure', label: 'Configure', variant: 'default' },
267
- // System tools can't be uninstalled
268
- ...(!isSystemTool ? [{ id: 'uninstall', label: 'Uninstall', variant: 'danger' }] : []),
269
- ]
270
- : [
271
- { id: 'explore', label: 'Explore', variant: 'default' },
272
- // System tools auto-install, but show Install for manual trigger if needed
273
- { id: 'install', label: 'Install', variant: 'primary' },
274
- ];
275
- return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, flexGrow: 1, children: [_jsx(Text, { color: colors.text, bold: true, children: tool.name }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, children: [tool.version, tool.status && ` · ${tool.status}`, installed && _jsx(Text, { color: colors.success, children: " \u00B7 installed" }), updateStatus.hasUpdate && (_jsxs(Text, { color: colors.primary, children: [" \u00B7 update (", installedVersion, " \u2192 ", updateStatus.bundledVersion, ")"] }))] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: tool.description }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: colors.textDim, children: "Includes:" }), _jsx(Box, { marginTop: 1, children: _jsx(ComponentBadges, { tool: tool }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 1, children: [tool.includes.skills.length > 0 && (_jsxs(Text, { children: [_jsx(Text, { color: colors.skill, children: "Skills: " }), _jsx(Text, { color: colors.textMuted, children: tool.includes.skills.map(s => s.name).join(', ') })] })), tool.includes.commands.length > 0 && (_jsxs(Text, { children: [_jsx(Text, { color: colors.command, children: "Commands: " }), _jsx(Text, { color: colors.textMuted, children: tool.includes.commands.map(c => `/${c}`).join(', ') })] })), tool.includes.agents.length > 0 && (_jsxs(Text, { children: [_jsx(Text, { color: colors.agent, children: "Agents: " }), _jsx(Text, { color: colors.textMuted, children: tool.includes.agents.join(', ') })] }))] })] }), isFocused && (_jsx(Box, { flexDirection: "row", marginTop: 1, children: actions.map((action, index) => (_jsxs(Text, { backgroundColor: selectedAction === index
276
- ? action.variant === 'danger'
277
- ? colors.error
278
- : colors.primary
279
- : colors.bgSelected, color: selectedAction === index ? '#ffffff' : colors.textMuted, bold: selectedAction === index, children: [' ', action.label, ' '] }, action.id))) }))] }));
280
- }
281
- function MarkdownLine({ line, inCodeBlock }) {
282
- // Code block content
283
- if (inCodeBlock) {
284
- return _jsx(Text, { color: "#a5d6ff", children: line || ' ' });
285
- }
286
- // Code block delimiter
287
- if (line.startsWith('```')) {
288
- return _jsx(Text, { color: colors.textDim, children: line });
289
- }
290
- // Headers
291
- if (line.startsWith('# ')) {
292
- return _jsx(Text, { color: colors.text, bold: true, children: line.slice(2) });
293
- }
294
- if (line.startsWith('## ')) {
295
- return _jsx(Text, { color: colors.text, bold: true, children: line.slice(3) });
296
- }
297
- if (line.startsWith('### ')) {
298
- return _jsx(Text, { color: "#c9d1d9", bold: true, children: line.slice(4) });
299
- }
300
- // YAML frontmatter delimiter
301
- if (line === '---') {
302
- return _jsx(Text, { color: colors.textDim, children: line });
303
- }
304
- // List items
305
- if (line.match(/^[\s]*[-*]\s/)) {
306
- return _jsx(Text, { color: colors.textMuted, children: line });
307
- }
308
- // Blockquotes
309
- if (line.startsWith('>')) {
310
- return _jsx(Text, { color: "#8b949e", italic: true, children: line });
311
- }
312
- // Table rows
313
- if (line.includes('|')) {
314
- return _jsx(Text, { color: colors.textMuted, children: line });
315
- }
316
- // Default
317
- return _jsx(Text, { color: colors.textMuted, children: line || ' ' });
318
- }
319
- function ReadmeViewer({ title, content, onClose, }) {
320
- const [scrollOffset, setScrollOffset] = useState(0);
321
- const lines = useMemo(() => content.split('\n'), [content]);
322
- const maxVisible = 20;
323
- // Pre-compute code block state for each line
324
- const lineStates = useMemo(() => {
325
- const states = [];
326
- let inCode = false;
327
- for (const line of lines) {
328
- if (line.startsWith('```')) {
329
- states.push(false); // Delimiter itself is not "in" code block for styling
330
- inCode = !inCode;
331
- }
332
- else {
333
- states.push(inCode);
334
- }
335
- }
336
- return states;
337
- }, [lines]);
338
- // Max offset: when at end, we have top indicator + (maxVisible-1) content lines
339
- // So max offset is lines.length - (maxVisible - 1) = lines.length - maxVisible + 1
340
- const maxOffset = Math.max(0, lines.length - maxVisible + 1);
341
- useInput((input, key) => {
342
- if (key.escape) {
343
- onClose();
344
- return;
345
- }
346
- if (key.upArrow) {
347
- setScrollOffset((prev) => Math.max(0, prev - 1));
348
- }
349
- if (key.downArrow) {
350
- setScrollOffset((prev) => Math.min(maxOffset, prev + 1));
351
- }
352
- if (key.pageDown || input === ' ') {
353
- setScrollOffset((prev) => Math.min(maxOffset, prev + maxVisible));
354
- }
355
- if (key.pageUp) {
356
- setScrollOffset((prev) => Math.max(0, prev - maxVisible));
357
- }
358
- });
359
- // Adjust visible lines based on whether indicators are shown
360
- const showTopIndicator = scrollOffset > 0;
361
- // Reserve space for bottom indicator if not at end
362
- const contentLines = maxVisible - (showTopIndicator ? 1 : 0);
363
- const endIndex = Math.min(scrollOffset + contentLines, lines.length);
364
- const showBottomIndicator = endIndex < lines.length;
365
- const actualContentLines = contentLines - (showBottomIndicator ? 1 : 0);
366
- const visibleLines = lines.slice(scrollOffset, scrollOffset + actualContentLines);
367
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: colors.text, bold: true, children: title }), _jsxs(Text, { color: colors.textDim, children: [" \u00B7 ", lines.length, " lines"] })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: colors.border, paddingX: 1, children: [showTopIndicator && (_jsxs(Text, { color: colors.textDim, children: ["\u2191 ", scrollOffset, " more lines"] })), visibleLines.map((line, i) => (_jsx(MarkdownLine, { line: line, inCodeBlock: lineStates[scrollOffset + i] }, scrollOffset + i))), showBottomIndicator && (_jsxs(Text, { color: colors.textDim, children: ["\u2193 ", lines.length - scrollOffset - actualContentLines, " more lines"] }))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2191\u2193 scroll \u00B7 space/pgdn page \u00B7 esc back" }) })] }));
368
- }
369
- function ToolExplorer({ tool, onViewSource, onClose }) {
370
- const [selectedIndex, setSelectedIndex] = useState(0);
371
- // Build list of all explorable items
372
- const items = useMemo(() => {
373
- const result = [];
374
- const toolDir = getBundledToolsDir();
375
- // Add skills
376
- for (const skill of tool.includes.skills) {
377
- result.push({
378
- type: 'skill',
379
- name: skill.name,
380
- path: join(toolDir, tool.name, 'skills', skill.name, 'SKILL.md'),
381
- });
382
- }
383
- // Add commands
384
- for (const cmd of tool.includes.commands) {
385
- result.push({
386
- type: 'command',
387
- name: `/${cmd}`,
388
- path: join(toolDir, tool.name, 'commands', `${cmd}.md`),
389
- });
390
- }
391
- for (const agent of tool.includes.agents) {
392
- result.push({
393
- type: 'agent',
394
- name: agent,
395
- path: join(toolDir, tool.name, 'agents', `${agent}.md`),
396
- });
397
- }
398
- return result;
399
- }, [tool]);
400
- useInput((input, key) => {
401
- if (key.escape) {
402
- onClose();
403
- return;
404
- }
405
- if (key.leftArrow || key.upArrow) {
406
- setSelectedIndex((prev) => Math.max(0, prev - 1));
407
- }
408
- if (key.rightArrow || key.downArrow) {
409
- setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
410
- }
411
- if (key.return && items.length > 0) {
412
- const item = items[selectedIndex];
413
- if (existsSync(item.path)) {
414
- const content = readFileSync(item.path, 'utf-8');
415
- onViewSource(`${tool.name} / ${item.name}`, content);
416
- }
417
- else {
418
- // Try YAML fallback for skills/agents
419
- const yamlPath = item.path.replace('.md', '.yaml');
420
- if (existsSync(yamlPath)) {
421
- const content = readFileSync(yamlPath, 'utf-8');
422
- onViewSource(`${tool.name} / ${item.name}`, content);
423
- }
424
- }
425
- }
426
- });
427
- // Group items by type for display
428
- const skillItems = items.filter(i => i.type === 'skill');
429
- const commandItems = items.filter(i => i.type === 'command');
430
- const agentItems = items.filter(i => i.type === 'agent');
431
- const getItemIndex = (item) => items.indexOf(item);
432
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "[" }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: "] " }), _jsx(Text, { color: colors.text, bold: true, children: tool.name })] }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.textMuted, children: tool.description }) }), skillItems.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: colors.skill, bold: true, children: "Skills" }), _jsx(Box, { flexDirection: "row", flexWrap: "wrap", marginTop: 1, children: skillItems.map((item) => {
433
- const idx = getItemIndex(item);
434
- const isSelected = selectedIndex === idx;
435
- return (_jsx(Box, { marginRight: 1, marginBottom: 1, children: _jsx(Text, { backgroundColor: isSelected ? colors.skill : colors.bgSelected, color: isSelected ? '#000000' : colors.skill, bold: isSelected, children: ` ${item.name} ` }) }, item.name));
436
- }) })] })), commandItems.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: colors.command, bold: true, children: "Commands" }), _jsx(Box, { flexDirection: "row", flexWrap: "wrap", marginTop: 1, children: commandItems.map((item) => {
437
- const idx = getItemIndex(item);
438
- const isSelected = selectedIndex === idx;
439
- return (_jsx(Box, { marginRight: 1, marginBottom: 1, children: _jsx(Text, { backgroundColor: isSelected ? colors.command : colors.bgSelected, color: isSelected ? '#000000' : colors.command, bold: isSelected, children: ` ${item.name} ` }) }, item.name));
440
- }) })] })), agentItems.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: colors.agent, bold: true, children: "Agents" }), _jsx(Box, { flexDirection: "row", flexWrap: "wrap", marginTop: 1, children: agentItems.map((item) => {
441
- const idx = getItemIndex(item);
442
- const isSelected = selectedIndex === idx;
443
- return (_jsx(Box, { marginRight: 1, marginBottom: 1, children: _jsx(Text, { backgroundColor: isSelected ? colors.agent : colors.bgSelected, color: isSelected ? '#000000' : colors.agent, bold: isSelected, children: ` ${item.name} ` }) }, item.name));
444
- }) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2190\u2192 navigate \u00B7 enter view source \u00B7 esc back" }) })] }));
445
- }
446
- function SettingsDetails({ onEditSettings, isFocused, onToggleAutoUpdate, selectedSetting, }) {
447
- const config = loadConfig();
448
- const autoUpdateConfig = getAutoUpdateConfig();
449
- return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, flexGrow: 1, children: [_jsx(Text, { color: colors.text, bold: true, children: "Settings" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "Platform: " }), _jsx(Text, { color: colors.text, children: config.platform === Platform.ClaudeCode ? 'Claude Code' : 'OpenCode' })] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "Your @mention: " }), _jsx(Text, { color: colors.text, children: config.user_mention })] })] }), _jsxs(Box, { flexDirection: "column", marginTop: 2, children: [_jsx(Text, { color: colors.text, bold: true, children: "Auto-Update" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: isFocused && selectedSetting === 0 ? '> ' : ' ' }), _jsxs(Text, { color: isFocused && selectedSetting === 0 ? colors.text : colors.textMuted, children: ["[", autoUpdateConfig.tools ? 'x' : ' ', "] Auto-update tools"] })] }) }), _jsx(Text, { color: colors.textDim, children: " Update tools automatically when droid starts" }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: isFocused && selectedSetting === 1 ? '> ' : ' ' }), _jsxs(Text, { color: isFocused && selectedSetting === 1 ? colors.text : colors.textMuted, children: ["[", autoUpdateConfig.app ? 'x' : ' ', "] Auto-update app"] })] }) }), _jsx(Text, { color: colors.textDim, children: " Update droid automatically when a new version is available" })] }), _jsx(Box, { marginTop: 2, children: _jsx(Text, { color: colors.textDim, children: "Config: ~/.droid/config.yaml" }) }), isFocused && (_jsx(Box, { marginTop: 2, children: _jsxs(Text, { backgroundColor: selectedSetting === 2 ? colors.primary : colors.bgSelected, color: selectedSetting === 2 ? '#ffffff' : colors.textMuted, bold: selectedSetting === 2, children: [' ', "Edit Profile", ' '] }) })), isFocused && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2191\u2193 select \u00B7 enter toggle/edit \u00B7 esc back" }) })), !isFocused && (_jsx(Box, { marginTop: 2, children: _jsx(Text, { color: colors.textDim, children: "press enter to configure" }) }))] }));
450
- }
451
- const MAX_VISIBLE_CONFIG_ITEMS = 4; // Each config item takes ~3 lines
452
- function SkillConfigScreen({ skill, onComplete, onCancel }) {
453
- const configSchema = skill.config_schema || {};
454
- const configKeys = Object.keys(configSchema);
455
- const initialOverrides = useMemo(() => loadSkillOverrides(skill.name), [skill.name]);
456
- // Initialize values from saved overrides or defaults
457
- const [values, setValues] = useState(() => {
458
- const initial = {};
459
- for (const key of configKeys) {
460
- const option = configSchema[key];
461
- initial[key] = initialOverrides[key] ?? option.default ?? (option.type === ConfigOptionType.Boolean ? false : '');
462
- }
463
- return initial;
464
- });
465
- const [selectedIndex, setSelectedIndex] = useState(0);
466
- const [scrollOffset, setScrollOffset] = useState(0);
467
- const [editingField, setEditingField] = useState(null);
468
- const [editValue, setEditValue] = useState('');
469
- const [editingSelect, setEditingSelect] = useState(null);
470
- const [selectOptionIndex, setSelectOptionIndex] = useState(0);
471
- // Total items = config keys + Save button
472
- const totalItems = configKeys.length + 1;
473
- const handleSave = () => {
474
- saveSkillOverrides(skill.name, values);
475
- onComplete();
476
- };
477
- const handleSubmitEdit = () => {
478
- if (editingField) {
479
- setValues((prev) => ({ ...prev, [editingField]: editValue }));
480
- setEditingField(null);
481
- setEditValue('');
482
- }
483
- };
484
- // Handle text input for string fields
485
- useInput((input, key) => {
486
- if (key.escape) {
487
- setEditingField(null);
488
- setEditValue('');
489
- }
490
- }, { isActive: editingField !== null });
491
- // Handle select field editing
492
- useInput((input, key) => {
493
- if (!editingSelect)
494
- return;
495
- const option = configSchema[editingSelect];
496
- if (!option?.options)
497
- return;
498
- if (key.escape) {
499
- setEditingSelect(null);
500
- return;
501
- }
502
- if (key.leftArrow || key.upArrow) {
503
- setSelectOptionIndex((prev) => Math.max(0, prev - 1));
504
- }
505
- if (key.rightArrow || key.downArrow) {
506
- setSelectOptionIndex((prev) => Math.min(option.options.length - 1, prev + 1));
507
- }
508
- if (key.return) {
509
- setValues((prev) => ({ ...prev, [editingSelect]: option.options[selectOptionIndex] }));
510
- setEditingSelect(null);
511
- }
512
- }, { isActive: editingSelect !== null });
513
- // Handle navigation and actions when not editing
514
- useInput((input, key) => {
515
- if (key.escape) {
516
- onCancel();
517
- return;
518
- }
519
- if (key.upArrow) {
520
- setSelectedIndex((prev) => {
521
- const newIndex = Math.max(0, prev - 1);
522
- // Scroll up if needed
523
- if (newIndex < scrollOffset) {
524
- setScrollOffset(newIndex);
525
- }
526
- return newIndex;
527
- });
528
- }
529
- if (key.downArrow) {
530
- // +1 for the Save button at the end
531
- setSelectedIndex((prev) => {
532
- const newIndex = Math.min(totalItems - 1, prev + 1);
533
- // Scroll down if needed
534
- if (newIndex >= scrollOffset + MAX_VISIBLE_CONFIG_ITEMS) {
535
- setScrollOffset(newIndex - MAX_VISIBLE_CONFIG_ITEMS + 1);
536
- }
537
- return newIndex;
538
- });
539
- }
540
- if (key.return) {
541
- // Save button is at index === configKeys.length
542
- if (selectedIndex === configKeys.length) {
543
- handleSave();
544
- return;
545
- }
546
- const key = configKeys[selectedIndex];
547
- const option = configSchema[key];
548
- if (option.type === ConfigOptionType.Boolean) {
549
- // Toggle boolean
550
- setValues((prev) => ({ ...prev, [key]: !prev[key] }));
551
- }
552
- else if (option.type === ConfigOptionType.Select && option.options) {
553
- // Enter select edit mode
554
- const currentValue = String(values[key] || option.options[0]);
555
- const currentIndex = option.options.indexOf(currentValue);
556
- setSelectOptionIndex(currentIndex >= 0 ? currentIndex : 0);
557
- setEditingSelect(key);
558
- }
559
- else if (option.type === ConfigOptionType.String) {
560
- // Enter edit mode for string
561
- setEditingField(key);
562
- setEditValue(String(values[key] || ''));
563
- }
564
- }
565
- }, { isActive: editingField === null && editingSelect === null });
566
- if (configKeys.length === 0) {
567
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "[" }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: "] " }), _jsxs(Text, { color: colors.text, bold: true, children: ["configure ", skill.name] })] }) }), _jsx(Text, { color: colors.textMuted, children: "This skill has no configuration options." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "esc to go back" }) })] }));
568
- }
569
- // Calculate visible range
570
- const visibleEndIndex = Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, totalItems);
571
- const visibleConfigKeys = configKeys.slice(scrollOffset, Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, configKeys.length));
572
- const showSaveButton = visibleEndIndex > configKeys.length || scrollOffset + MAX_VISIBLE_CONFIG_ITEMS > configKeys.length;
573
- const showTopIndicator = scrollOffset > 0;
574
- const showBottomIndicator = scrollOffset + MAX_VISIBLE_CONFIG_ITEMS < totalItems;
575
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "[" }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: "] " }), _jsxs(Text, { color: colors.text, bold: true, children: ["configure ", skill.name] })] }) }), _jsxs(Box, { flexDirection: "column", children: [showTopIndicator && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: colors.textDim, children: [" \u2191 ", scrollOffset, " more"] }) })), visibleConfigKeys.map((key) => {
576
- const actualIndex = configKeys.indexOf(key);
577
- const option = configSchema[key];
578
- const isSelected = selectedIndex === actualIndex;
579
- const isEditing = editingField === key;
580
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsxs(Text, { color: colors.textDim, children: [isSelected ? '>' : ' ', " "] }), _jsx(Text, { color: isSelected ? colors.text : colors.textMuted, children: key })] }), _jsxs(Text, { color: colors.textDim, children: [" ", option.description] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, children: " " }), option.type === ConfigOptionType.Boolean ? (_jsxs(Text, { color: colors.text, children: ["[", values[key] ? 'x' : ' ', "] ", values[key] ? 'enabled' : 'disabled'] })) : option.type === ConfigOptionType.Select && option.options ? (_jsx(Text, { color: colors.text, children: option.options.map((opt, i) => {
581
- const isCurrentValue = String(values[key]) === opt;
582
- const isEditingThis = editingSelect === key;
583
- const isHighlighted = isEditingThis && selectOptionIndex === i;
584
- return (_jsxs(Text, { children: [i > 0 && _jsx(Text, { color: colors.textDim, children: " \u00B7 " }), _jsx(Text, { color: isHighlighted ? '#ffffff' : isCurrentValue ? colors.primary : colors.textMuted, backgroundColor: isHighlighted ? colors.primary : undefined, children: isCurrentValue && !isEditingThis ? `[${opt}]` : isHighlighted ? ` ${opt} ` : opt })] }, opt));
585
- }) })) : isEditing ? (_jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, children: '> ' }), _jsx(TextInput, { value: editValue, onChange: setEditValue, onSubmit: handleSubmitEdit })] })) : (_jsx(Text, { color: colors.text, children: String(values[key]) || '(not set)' }))] })] }, key));
586
- }), showSaveButton && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: colors.textDim, children: [selectedIndex === configKeys.length ? '>' : ' ', " "] }), _jsxs(Text, { backgroundColor: selectedIndex === configKeys.length ? colors.primary : undefined, color: selectedIndex === configKeys.length ? '#ffffff' : colors.textMuted, bold: selectedIndex === configKeys.length, children: [' ', "Save", ' '] })] }) })), showBottomIndicator && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, children: [" \u2193 ", totalItems - scrollOffset - MAX_VISIBLE_CONFIG_ITEMS, " more"] }) }))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: editingField
587
- ? 'enter save · esc cancel'
588
- : editingSelect
589
- ? '←→ choose · enter select · esc cancel'
590
- : '↑↓ select · enter toggle/edit · esc back' }) })] }));
591
- }
592
- function App() {
593
- const { exit } = useApp();
594
- const tabs = [
595
- { id: 'tools', label: 'Tools' },
596
- { id: 'settings', label: 'Settings' },
597
- ];
598
- const [activeTab, setActiveTab] = useState('tools');
599
- const [tabIndex, setTabIndex] = useState(0);
600
- const [view, setView] = useState('welcome');
601
- const [selectedIndex, setSelectedIndex] = useState(0);
602
- const [selectedAction, setSelectedAction] = useState(0);
603
- const [scrollOffset, setScrollOffset] = useState(0);
604
- const [message, setMessage] = useState(null);
605
- const [isEditingSettings, setIsEditingSettings] = useState(false);
606
- const [readmeContent, setReadmeContent] = useState(null);
607
- const [selectedSetting, setSelectedSetting] = useState(0);
608
- const [isUpdating, setIsUpdating] = useState(false);
609
- const [isUpdatingTools, setIsUpdatingTools] = useState(false);
610
- const [previousView, setPreviousView] = useState('detail'); // Track where to return from readme
611
- const [toolUpdates, setToolUpdates] = useState([]);
612
- const [autoUpdatedTools, setAutoUpdatedTools] = useState([]);
613
- // Check for updates once on mount
614
- const updateInfo = useMemo(() => getUpdateInfo(), []);
615
- const autoUpdateConfig = useMemo(() => getAutoUpdateConfig(), []);
616
- const handleUpdate = () => {
617
- setIsUpdating(true);
618
- // Run update in next tick to allow UI to show "Updating..."
619
- setTimeout(() => {
620
- const result = runUpdate();
621
- if (result.success) {
622
- // Store message to print after leaving alternate screen (with ANSI colors)
623
- const blue = '\x1b[38;2;99;102;241m'; // #6366f1
624
- const dim = '\x1b[38;2;106;106;106m';
625
- const reset = '\x1b[0m';
626
- exitMessage = `
627
- ${dim}────────────────────────────────────────────────────────${reset}
628
-
629
- ${dim}╔═════╗${reset}
630
- ${dim}║${reset} ${blue}●${reset} ${blue}●${reset} ${dim}║${reset} ${blue}"It's quite possible this system${reset}
631
- ${dim}╚═╦═╦═╝${reset} ${blue}is now fully operational."${reset}
632
-
633
- Run ${blue}droid${reset} to start the new version.
634
-
635
- ${dim}────────────────────────────────────────────────────────${reset}
636
- `;
637
- exit();
638
- }
639
- else {
640
- setIsUpdating(false);
641
- setMessage({ text: result.message, type: 'error' });
642
- // Continue to menu on failure
643
- if (!configExists()) {
644
- setView('setup');
645
- }
646
- else {
647
- setView('menu');
648
- }
649
- }
650
- }, 100);
651
- };
652
- const handleAlwaysUpdate = () => {
653
- // Enable auto-update for app in config
654
- setAutoUpdateConfig({ app: true });
655
- // Then run the update
656
- handleUpdate();
657
- };
658
- // Ensure system tools (marked with system: true in TOOL.yaml) are always installed and current
659
- const ensureSystemTools = () => {
660
- // Find all tools marked as system tools
661
- const systemTools = tools.filter(t => t.system === true);
662
- for (const systemTool of systemTools) {
663
- const installed = isToolInstalled(systemTool.name);
664
- const updateStatus = getToolUpdateStatus(systemTool.name);
665
- // Install if not installed, or update if outdated (regardless of auto-update settings)
666
- if (!installed || updateStatus.hasUpdate) {
667
- const primarySkill = systemTool.includes.skills.find(s => s.required)?.name || systemTool.name;
668
- installSkill(primarySkill);
669
- }
670
- }
671
- };
672
- // Check for tool updates and proceed to next view
673
- const checkToolUpdatesAndProceed = () => {
674
- // Always ensure system tools are current (bypasses auto-update settings)
675
- ensureSystemTools();
676
- const updates = getToolsWithUpdates();
677
- setToolUpdates(updates);
678
- // If auto_update.tools is true, auto-update silently
679
- if (autoUpdateConfig.tools && updates.length > 0) {
680
- handleUpdateAllTools(updates, true);
681
- return;
682
- }
683
- // If there are updates and auto-update is off, show prompt
684
- if (updates.length > 0) {
685
- setView('tool-updates');
686
- return;
687
- }
688
- // No updates, proceed to setup or menu
689
- if (!configExists()) {
690
- setView('setup');
691
- }
692
- else {
693
- setView('menu');
694
- }
695
- };
696
- const handleUpdateAllTools = (updates = toolUpdates, silent = false) => {
697
- if (!silent) {
698
- setIsUpdatingTools(true);
699
- }
700
- setTimeout(() => {
701
- let successCount = 0;
702
- let failCount = 0;
703
- const updatedNames = [];
704
- for (const tool of updates) {
705
- // Find the tool to get its primary skill
706
- const toolManifest = tools.find(t => t.name === tool.name);
707
- if (toolManifest) {
708
- const primarySkill = toolManifest.includes.skills.find(s => s.required)?.name || toolManifest.name;
709
- const result = updateSkill(primarySkill);
710
- if (result.success) {
711
- successCount++;
712
- updatedNames.push(tool.name);
713
- }
714
- else {
715
- failCount++;
716
- }
717
- }
718
- }
719
- setIsUpdatingTools(false);
720
- // Track which tools were auto-updated for visual indicator
721
- if (silent && updatedNames.length > 0) {
722
- setAutoUpdatedTools(updatedNames);
723
- }
724
- if (successCount > 0) {
725
- setMessage({
726
- text: silent
727
- ? `↑ Auto-updated ${successCount} tool${successCount > 1 ? 's' : ''}`
728
- : `✓ Updated ${successCount} tool${successCount > 1 ? 's' : ''}${failCount > 0 ? `, ${failCount} failed` : ''}`,
729
- type: failCount > 0 ? 'error' : 'success',
730
- });
731
- }
732
- // Proceed to next view
733
- if (!configExists()) {
734
- setView('setup');
735
- }
736
- else {
737
- setView('menu');
738
- }
739
- }, 100);
740
- };
741
- const handleAlwaysUpdateTools = () => {
742
- // Enable auto-update in config
743
- setAutoUpdateConfig({ tools: true });
744
- // Then update all
745
- handleUpdateAllTools();
746
- };
747
- const handleSkipToolUpdates = () => {
748
- if (!configExists()) {
749
- setView('setup');
750
- }
751
- else {
752
- setView('menu');
753
- }
754
- };
755
- const handleToggleAutoUpdateTools = () => {
756
- const current = getAutoUpdateConfig();
757
- setAutoUpdateConfig({ tools: !current.tools });
758
- // Force re-render by setting message (auto-clears on next input)
759
- setMessage({
760
- text: `✓ Auto-update tools ${!current.tools ? 'enabled' : 'disabled'}`,
761
- type: 'success',
762
- });
763
- };
764
- const handleToggleAutoUpdateApp = () => {
765
- const current = getAutoUpdateConfig();
766
- setAutoUpdateConfig({ app: !current.app });
767
- setMessage({
768
- text: `✓ Auto-update app ${!current.app ? 'enabled' : 'disabled'}`,
769
- type: 'success',
770
- });
771
- };
772
- const MAX_VISIBLE_ITEMS = 6;
773
- const tools = getBundledTools();
774
- // Keep skills for configure view (tools configure via their primary skill)
775
- const skills = getBundledSkills();
776
- useInput((input, key) => {
777
- if (message)
778
- setMessage(null);
779
- if (input === 'q') {
780
- exit();
781
- return;
782
- }
783
- if (view === 'menu') {
784
- if (key.leftArrow) {
785
- const newIndex = Math.max(0, tabIndex - 1);
786
- setTabIndex(newIndex);
787
- setActiveTab(tabs[newIndex].id);
788
- setSelectedIndex(0);
789
- setScrollOffset(0);
790
- }
791
- if (key.rightArrow) {
792
- const newIndex = Math.min(tabs.length - 1, tabIndex + 1);
793
- setTabIndex(newIndex);
794
- setActiveTab(tabs[newIndex].id);
795
- setSelectedIndex(0);
796
- setScrollOffset(0);
797
- }
798
- if (key.upArrow) {
799
- setSelectedIndex((prev) => {
800
- const newIndex = Math.max(0, prev - 1);
801
- // Scroll up if needed
802
- if (newIndex < scrollOffset) {
803
- setScrollOffset(newIndex);
804
- }
805
- return newIndex;
806
- });
807
- setSelectedAction(0);
808
- }
809
- if (key.downArrow) {
810
- const maxIndex = activeTab === 'tools' ? tools.length - 1 : 0;
811
- setSelectedIndex((prev) => {
812
- const newIndex = Math.min(maxIndex, prev + 1);
813
- // Scroll down if needed
814
- if (newIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
815
- setScrollOffset(newIndex - MAX_VISIBLE_ITEMS + 1);
816
- }
817
- return newIndex;
818
- });
819
- setSelectedAction(0);
820
- }
821
- if (key.return) {
822
- if (activeTab === 'tools' && tools.length > 0) {
823
- setView('detail');
824
- }
825
- else if (activeTab === 'settings') {
826
- setView('detail');
827
- setSelectedSetting(0);
828
- }
829
- }
830
- }
831
- else if (view === 'detail') {
832
- if (key.escape || key.backspace) {
833
- setView('menu');
834
- setSelectedAction(0);
835
- setSelectedSetting(0);
836
- }
837
- if (activeTab === 'settings') {
838
- // Settings detail view navigation
839
- if (key.upArrow) {
840
- setSelectedSetting((prev) => Math.max(0, prev - 1));
841
- }
842
- if (key.downArrow) {
843
- setSelectedSetting((prev) => Math.min(2, prev + 1)); // 0: auto-update tools, 1: auto-update app, 2: edit profile
844
- }
845
- if (key.return) {
846
- if (selectedSetting === 0) {
847
- handleToggleAutoUpdateTools();
848
- }
849
- else if (selectedSetting === 1) {
850
- handleToggleAutoUpdateApp();
851
- }
852
- else if (selectedSetting === 2) {
853
- setIsEditingSettings(true);
854
- setView('setup');
855
- }
856
- }
857
- }
858
- if (key.leftArrow && activeTab === 'tools') {
859
- setSelectedAction((prev) => Math.max(0, prev - 1));
860
- }
861
- if (key.rightArrow && activeTab === 'tools') {
862
- let maxActions = 0;
863
- const tool = tools[selectedIndex];
864
- const installed = tool ? isToolInstalled(tool.name) : false;
865
- const hasUpdate = tool ? getToolUpdateStatus(tool.name).hasUpdate : false;
866
- const isSystem = tool ? tool.system === true : false;
867
- // Explore, [Update], Configure, [Uninstall] or Explore, Install
868
- // System tools don't have Uninstall, so one less action
869
- maxActions = installed ? (hasUpdate ? (isSystem ? 2 : 3) : (isSystem ? 1 : 2)) : 1;
870
- setSelectedAction((prev) => Math.min(maxActions, prev + 1));
871
- }
872
- if (key.return && activeTab === 'tools') {
873
- const tool = tools[selectedIndex];
874
- if (tool) {
875
- const installed = isToolInstalled(tool.name);
876
- const toolUpdateStatus = getToolUpdateStatus(tool.name);
877
- const isSystemTool = tool.system === true;
878
- // Build actions array to match ToolDetails
879
- const toolActions = installed
880
- ? [
881
- { id: 'explore' },
882
- ...(toolUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
883
- { id: 'configure' },
884
- ...(!isSystemTool ? [{ id: 'uninstall' }] : []),
885
- ]
886
- : [{ id: 'explore' }, { id: 'install' }];
887
- const actionId = toolActions[selectedAction]?.id;
888
- if (actionId === 'explore') {
889
- // Open the tool explorer view
890
- setView('explorer');
891
- }
892
- else if (actionId === 'update') {
893
- // Update the primary skill of the tool
894
- const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
895
- const result = updateSkill(primarySkill);
896
- setMessage({
897
- text: result.success ? `✓ Updated ${tool.name}` : `✗ ${result.message}`,
898
- type: result.success ? 'success' : 'error',
899
- });
900
- if (result.success) {
901
- setSelectedAction(0);
902
- }
903
- }
904
- else if (actionId === 'configure') {
905
- setView('configure');
906
- }
907
- else if (actionId === 'uninstall') {
908
- // Uninstall the primary skill of the tool
909
- const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
910
- const result = uninstallSkill(primarySkill);
911
- setMessage({
912
- text: result.success ? `✓ Uninstalled ${tool.name}` : `✗ ${result.message}`,
913
- type: result.success ? 'success' : 'error',
914
- });
915
- if (result.success) {
916
- setView('menu');
917
- setSelectedAction(0);
918
- }
919
- }
920
- else if (actionId === 'install') {
921
- // Install the primary skill of the tool
922
- const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
923
- const result = installSkill(primarySkill);
924
- setMessage({
925
- text: result.success ? `✓ Installed ${tool.name}` : `✗ ${result.message}`,
926
- type: result.success ? 'success' : 'error',
927
- });
928
- if (result.success) {
929
- // Check if tool has required config (options without defaults)
930
- const configSchema = tool.config_schema || {};
931
- const hasRequiredConfig = Object.values(configSchema).some((option) => option.default === undefined);
932
- if (hasRequiredConfig) {
933
- // Go to configure view for required setup
934
- setView('configure');
935
- }
936
- else {
937
- setView('menu');
938
- setSelectedAction(0);
939
- }
940
- }
941
- }
942
- }
943
- }
944
- }
945
- }, { isActive: view !== 'welcome' && view !== 'tool-updates' && view !== 'setup' && view !== 'configure' && view !== 'explorer' });
946
- const selectedTool = activeTab === 'tools' ? tools[selectedIndex] ?? null : null;
947
- // For configure view, we need the matching skill manifest
948
- const selectedSkillForConfig = selectedTool
949
- ? skills.find(s => s.name === (selectedTool.includes.skills.find(sk => sk.required)?.name || selectedTool.name))
950
- : null;
951
- if (view === 'welcome') {
952
- return (_jsx(WelcomeScreen, { updateInfo: updateInfo, isUpdating: isUpdating, onUpdate: handleUpdate, onAlways: handleAlwaysUpdate, onExit: exit, onContinue: checkToolUpdatesAndProceed }));
953
- }
954
- if (view === 'tool-updates' && toolUpdates.length > 0) {
955
- return (_jsx(ToolUpdatePrompt, { toolUpdates: toolUpdates, onUpdateAll: () => handleUpdateAllTools(), onAlways: handleAlwaysUpdateTools, onSkip: handleSkipToolUpdates, isUpdating: isUpdatingTools }));
956
- }
957
- if (view === 'setup') {
958
- return (_jsx(SetupScreen, { onComplete: () => {
959
- setIsEditingSettings(false);
960
- setView('menu');
961
- }, onSkip: () => {
962
- setIsEditingSettings(false);
963
- setView('menu');
964
- }, initialConfig: isEditingSettings ? loadConfig() : undefined }));
965
- }
966
- if (view === 'readme' && readmeContent) {
967
- return (_jsx(ReadmeViewer, { title: readmeContent.title, content: readmeContent.content, onClose: () => {
968
- setReadmeContent(null);
969
- setView(previousView);
970
- } }));
971
- }
972
- if (view === 'explorer' && selectedTool) {
973
- return (_jsx(ToolExplorer, { tool: selectedTool, onViewSource: (title, content) => {
974
- setPreviousView('explorer');
975
- setReadmeContent({ title, content });
976
- setView('readme');
977
- }, onClose: () => {
978
- setView('detail');
979
- } }));
980
- }
981
- if (view === 'configure' && selectedSkillForConfig) {
982
- return (_jsx(SkillConfigScreen, { skill: selectedSkillForConfig, onComplete: () => {
983
- setMessage({ text: `✓ Configuration saved for ${selectedTool?.name || selectedSkillForConfig.name}`, type: 'success' });
984
- setView('detail');
985
- }, onCancel: () => {
986
- setView('detail');
987
- } }));
988
- }
989
- return (_jsxs(Box, { flexDirection: "row", padding: 1, children: [_jsxs(Box, { flexDirection: "column", width: 44, borderStyle: "single", borderColor: colors.border, children: [_jsx(Box, { paddingX: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "[" }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: "] " }), _jsx(Text, { color: colors.textMuted, children: "droid" }), _jsxs(Text, { color: colors.textDim, children: [" v", getVersion()] })] }) }), _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: colors.textDim, children: loadConfig().platform === Platform.ClaudeCode ? 'Claude Code' : 'OpenCode' }) }), _jsx(Box, { paddingX: 1, marginTop: 1, children: _jsx(TabBar, { tabs: tabs, activeTab: activeTab }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [activeTab === 'tools' && (_jsxs(_Fragment, { children: [scrollOffset > 0 && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, children: ["\u2191 ", scrollOffset, " more"] }) })), tools.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS).map((tool, index) => (_jsx(ToolItem, { tool: tool, isSelected: scrollOffset + index === selectedIndex, isActive: scrollOffset + index === selectedIndex && view === 'detail', wasAutoUpdated: autoUpdatedTools.includes(tool.name) }, tool.name))), scrollOffset + MAX_VISIBLE_ITEMS < tools.length && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, children: ["\u2193 ", tools.length - scrollOffset - MAX_VISIBLE_ITEMS, " more"] }) })), tools.length > MAX_VISIBLE_ITEMS && (_jsx(Box, { paddingX: 1, marginTop: 1, children: _jsxs(Text, { color: colors.textDim, children: [tools.length, " tools total"] }) }))] })), activeTab === 'settings' && (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: colors.textDim, children: "View and edit config" }) }))] }), message && (_jsx(Box, { paddingX: 1, marginTop: 1, children: _jsx(Text, { color: message.type === 'success' ? colors.success : colors.error, children: message.text }) })), _jsx(Box, { paddingX: 1, marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: view === 'menu' ? '←→ ↑↓ enter q' : '←→ enter esc q' }) })] }), activeTab === 'tools' && (_jsx(ToolDetails, { tool: selectedTool, isFocused: view === 'detail', selectedAction: selectedAction })), activeTab === 'settings' && (_jsx(SettingsDetails, { onEditSettings: () => {
990
- setIsEditingSettings(true);
991
- setView('setup');
992
- }, isFocused: view === 'detail', onToggleAutoUpdate: handleToggleAutoUpdateTools, selectedSetting: selectedSetting }))] }));
993
- }
994
- export async function tuiCommand() {
995
- // Enter alternate screen (fullscreen mode)
996
- process.stdout.write('\x1b[?1049h');
997
- process.stdout.write('\x1b[H'); // Move cursor to top-left
998
- const { unmount, waitUntilExit } = render(_jsx(App, {}));
999
- await waitUntilExit();
1000
- // Leave alternate screen
1001
- process.stdout.write('\x1b[?1049l');
1002
- // Print exit message if set (e.g., after successful update)
1003
- if (exitMessage) {
1004
- console.log(exitMessage);
1005
- exitMessage = null;
1006
- }
1007
- }
1008
- //# sourceMappingURL=tui.js.map