@orderful/droid 0.15.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.
Files changed (149) hide show
  1. package/.claude/CLAUDE.md +19 -1
  2. package/CHANGELOG.md +13 -0
  3. package/dist/bin/droid.js +8 -8
  4. package/dist/bin/droid.js.map +1 -1
  5. package/dist/commands/config.js +1 -1
  6. package/dist/commands/config.js.map +1 -1
  7. package/dist/commands/install.js +3 -3
  8. package/dist/commands/install.js.map +1 -1
  9. package/dist/commands/setup.d.ts +1 -1
  10. package/dist/commands/setup.d.ts.map +1 -1
  11. package/dist/commands/setup.js +3 -3
  12. package/dist/commands/setup.js.map +1 -1
  13. package/dist/commands/skills.js +4 -4
  14. package/dist/commands/skills.js.map +1 -1
  15. package/dist/commands/tui/components/Badge.d.ts +13 -0
  16. package/dist/commands/tui/components/Badge.d.ts.map +1 -0
  17. package/dist/commands/tui/components/Badge.js +29 -0
  18. package/dist/commands/tui/components/Badge.js.map +1 -0
  19. package/dist/commands/tui/components/Markdown.d.ts +5 -0
  20. package/dist/commands/tui/components/Markdown.d.ts.map +1 -0
  21. package/dist/commands/tui/components/Markdown.js +42 -0
  22. package/dist/commands/tui/components/Markdown.js.map +1 -0
  23. package/dist/commands/tui/components/SettingsDetails.d.ts +5 -0
  24. package/dist/commands/tui/components/SettingsDetails.d.ts.map +1 -0
  25. package/dist/commands/tui/components/SettingsDetails.js +11 -0
  26. package/dist/commands/tui/components/SettingsDetails.js.map +1 -0
  27. package/dist/commands/tui/components/TabBar.d.ts +10 -0
  28. package/dist/commands/tui/components/TabBar.d.ts.map +1 -0
  29. package/dist/commands/tui/components/TabBar.js +7 -0
  30. package/dist/commands/tui/components/TabBar.js.map +1 -0
  31. package/dist/commands/tui/components/ToolDetails.d.ts +8 -0
  32. package/dist/commands/tui/components/ToolDetails.d.ts.map +1 -0
  33. package/dist/commands/tui/components/ToolDetails.js +35 -0
  34. package/dist/commands/tui/components/ToolDetails.js.map +1 -0
  35. package/dist/commands/tui/components/ToolItem.d.ts +9 -0
  36. package/dist/commands/tui/components/ToolItem.d.ts.map +1 -0
  37. package/dist/commands/tui/components/ToolItem.js +11 -0
  38. package/dist/commands/tui/components/ToolItem.js.map +1 -0
  39. package/dist/commands/tui/constants.d.ts +16 -0
  40. package/dist/commands/tui/constants.d.ts.map +1 -0
  41. package/dist/commands/tui/constants.js +17 -0
  42. package/dist/commands/tui/constants.js.map +1 -0
  43. package/dist/commands/tui/hooks/useAppUpdate.d.ts +13 -0
  44. package/dist/commands/tui/hooks/useAppUpdate.d.ts.map +1 -0
  45. package/dist/commands/tui/hooks/useAppUpdate.js +52 -0
  46. package/dist/commands/tui/hooks/useAppUpdate.js.map +1 -0
  47. package/dist/commands/tui/hooks/useToolUpdates.d.ts +22 -0
  48. package/dist/commands/tui/hooks/useToolUpdates.d.ts.map +1 -0
  49. package/dist/commands/tui/hooks/useToolUpdates.js +77 -0
  50. package/dist/commands/tui/hooks/useToolUpdates.js.map +1 -0
  51. package/dist/commands/tui/types.d.ts +5 -0
  52. package/dist/commands/tui/types.d.ts.map +1 -0
  53. package/dist/commands/tui/types.js +2 -0
  54. package/dist/commands/tui/types.js.map +1 -0
  55. package/dist/commands/tui/views/ReadmeViewer.d.ts +7 -0
  56. package/dist/commands/tui/views/ReadmeViewer.d.ts.map +1 -0
  57. package/dist/commands/tui/views/ReadmeViewer.js +56 -0
  58. package/dist/commands/tui/views/ReadmeViewer.js.map +1 -0
  59. package/dist/commands/tui/views/SetupScreen.d.ts +8 -0
  60. package/dist/commands/tui/views/SetupScreen.d.ts.map +1 -0
  61. package/dist/commands/tui/views/SetupScreen.js +114 -0
  62. package/dist/commands/tui/views/SetupScreen.js.map +1 -0
  63. package/dist/commands/tui/views/SkillConfigScreen.d.ts +8 -0
  64. package/dist/commands/tui/views/SkillConfigScreen.d.ts.map +1 -0
  65. package/dist/commands/tui/views/SkillConfigScreen.js +148 -0
  66. package/dist/commands/tui/views/SkillConfigScreen.js.map +1 -0
  67. package/dist/commands/tui/views/ToolExplorer.d.ts +8 -0
  68. package/dist/commands/tui/views/ToolExplorer.d.ts.map +1 -0
  69. package/dist/commands/tui/views/ToolExplorer.js +86 -0
  70. package/dist/commands/tui/views/ToolExplorer.js.map +1 -0
  71. package/dist/commands/tui/views/ToolUpdatePrompt.d.ts +10 -0
  72. package/dist/commands/tui/views/ToolUpdatePrompt.d.ts.map +1 -0
  73. package/dist/commands/tui/views/ToolUpdatePrompt.js +38 -0
  74. package/dist/commands/tui/views/ToolUpdatePrompt.js.map +1 -0
  75. package/dist/commands/tui/views/WelcomeScreen.d.ts +11 -0
  76. package/dist/commands/tui/views/WelcomeScreen.d.ts.map +1 -0
  77. package/dist/commands/tui/views/WelcomeScreen.js +46 -0
  78. package/dist/commands/tui/views/WelcomeScreen.js.map +1 -0
  79. package/dist/commands/tui.d.ts.map +1 -1
  80. package/dist/commands/tui.js +54 -755
  81. package/dist/commands/tui.js.map +1 -1
  82. package/dist/commands/uninstall.js +2 -2
  83. package/dist/commands/uninstall.js.map +1 -1
  84. package/dist/commands/update.js +1 -1
  85. package/dist/commands/update.js.map +1 -1
  86. package/dist/index.d.ts +4 -4
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +4 -4
  89. package/dist/index.js.map +1 -1
  90. package/dist/lib/agents.d.ts +1 -1
  91. package/dist/lib/agents.d.ts.map +1 -1
  92. package/dist/lib/agents.js +4 -4
  93. package/dist/lib/agents.js.map +1 -1
  94. package/dist/lib/config.d.ts +1 -1
  95. package/dist/lib/config.d.ts.map +1 -1
  96. package/dist/lib/config.js +1 -1
  97. package/dist/lib/config.js.map +1 -1
  98. package/dist/lib/platforms.d.ts +1 -1
  99. package/dist/lib/platforms.d.ts.map +1 -1
  100. package/dist/lib/platforms.js +1 -1
  101. package/dist/lib/platforms.js.map +1 -1
  102. package/dist/lib/skill-config.d.ts +1 -1
  103. package/dist/lib/skill-config.d.ts.map +1 -1
  104. package/dist/lib/skill-config.js +2 -2
  105. package/dist/lib/skill-config.js.map +1 -1
  106. package/dist/lib/skills.d.ts +1 -1
  107. package/dist/lib/skills.d.ts.map +1 -1
  108. package/dist/lib/skills.js +5 -5
  109. package/dist/lib/skills.js.map +1 -1
  110. package/dist/lib/tools.d.ts +1 -1
  111. package/dist/lib/tools.d.ts.map +1 -1
  112. package/dist/lib/tools.js +3 -3
  113. package/dist/lib/tools.js.map +1 -1
  114. package/package.json +3 -3
  115. package/src/bin/droid.ts +8 -8
  116. package/src/commands/config.ts +1 -1
  117. package/src/commands/install.ts +3 -3
  118. package/src/commands/setup.test.ts +1 -1
  119. package/src/commands/setup.ts +3 -3
  120. package/src/commands/skills.ts +4 -4
  121. package/src/commands/tui/components/Badge.tsx +86 -0
  122. package/src/commands/tui/components/Markdown.tsx +48 -0
  123. package/src/commands/tui/components/SettingsDetails.tsx +70 -0
  124. package/src/commands/tui/components/TabBar.tsx +26 -0
  125. package/src/commands/tui/components/ToolDetails.tsx +117 -0
  126. package/src/commands/tui/components/ToolItem.tsx +39 -0
  127. package/src/commands/tui/constants.ts +17 -0
  128. package/src/commands/tui/hooks/useAppUpdate.ts +67 -0
  129. package/src/commands/tui/hooks/useToolUpdates.ts +110 -0
  130. package/src/commands/tui/types.ts +4 -0
  131. package/src/commands/tui/views/ReadmeViewer.tsx +93 -0
  132. package/src/commands/tui/views/SetupScreen.tsx +242 -0
  133. package/src/commands/tui/views/SkillConfigScreen.tsx +278 -0
  134. package/src/commands/tui/views/ToolExplorer.tsx +190 -0
  135. package/src/commands/tui/views/ToolUpdatePrompt.tsx +109 -0
  136. package/src/commands/tui/views/WelcomeScreen.tsx +149 -0
  137. package/src/commands/tui.tsx +65 -1587
  138. package/src/commands/uninstall.ts +2 -2
  139. package/src/commands/update.ts +1 -1
  140. package/src/index.ts +4 -4
  141. package/src/lib/agents.ts +4 -4
  142. package/src/lib/config.ts +1 -1
  143. package/src/lib/platforms.ts +1 -1
  144. package/src/lib/skill-config.ts +2 -2
  145. package/src/lib/skills.test.ts +2 -2
  146. package/src/lib/skills.ts +5 -5
  147. package/src/lib/tools.ts +3 -3
  148. package/src/lib/types.test.ts +1 -1
  149. package/src/lib/version.test.ts +1 -1
@@ -1,594 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
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';
3
+ import { useState } from 'react';
4
+ import { getBundledSkills, installSkill, uninstallSkill, updateSkill, } from '../lib/skills';
5
+ import { getBundledTools, isToolInstalled, getToolUpdateStatus } from '../lib/tools';
6
+ import { configExists, loadConfig } from '../lib/config';
7
+ import { Platform } from '../lib/types';
8
+ import { getVersion } from '../lib/version';
9
+ import { colors, MAX_VISIBLE_ITEMS } from './tui/constants';
10
+ import { TabBar } from './tui/components/TabBar';
11
+ import { ToolItem } from './tui/components/ToolItem';
12
+ import { ToolDetails } from './tui/components/ToolDetails';
13
+ import { SettingsDetails } from './tui/components/SettingsDetails';
14
+ import { WelcomeScreen } from './tui/views/WelcomeScreen';
15
+ import { ToolUpdatePrompt } from './tui/views/ToolUpdatePrompt';
16
+ import { SetupScreen } from './tui/views/SetupScreen';
17
+ import { ReadmeViewer } from './tui/views/ReadmeViewer';
18
+ import { ToolExplorer } from './tui/views/ToolExplorer';
19
+ import { SkillConfigScreen } from './tui/views/SkillConfigScreen';
20
+ import { useAppUpdate } from './tui/hooks/useAppUpdate';
21
+ import { useToolUpdates } from './tui/hooks/useToolUpdates';
15
22
  // Module-level variable to store exit message (printed after leaving alternate screen)
16
23
  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
24
  function App() {
593
25
  const { exit } = useApp();
594
26
  const tabs = [
@@ -604,88 +36,22 @@ function App() {
604
36
  const [message, setMessage] = useState(null);
605
37
  const [isEditingSettings, setIsEditingSettings] = useState(false);
606
38
  const [readmeContent, setReadmeContent] = useState(null);
607
- const [selectedSetting, setSelectedSetting] = useState(0);
608
- const [isUpdating, setIsUpdating] = useState(false);
609
- const [isUpdatingTools, setIsUpdatingTools] = useState(false);
610
39
  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();
40
+ const { updateInfo, isUpdating, handleUpdate, handleAlwaysUpdate } = useAppUpdate({
41
+ onUpdateSuccess: (msg) => {
42
+ exitMessage = msg;
43
+ },
44
+ onUpdateFailure: (error) => {
45
+ setMessage({ text: error, type: 'error' });
46
+ if (!configExists()) {
47
+ setView('setup');
638
48
  }
639
49
  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);
50
+ setView('menu');
669
51
  }
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
52
+ },
53
+ });
54
+ const proceedToNextView = () => {
689
55
  if (!configExists()) {
690
56
  setView('setup');
691
57
  }
@@ -693,34 +59,8 @@ ${dim}────────────────────────
693
59
  setView('menu');
694
60
  }
695
61
  };
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
- }
62
+ const { toolUpdates, isUpdatingTools, autoUpdatedTools, checkForUpdates, updateAllTools, enableAutoUpdateAndUpdate, } = useToolUpdates({
63
+ onUpdateComplete: ({ successCount, failCount, silent }) => {
724
64
  if (successCount > 0) {
725
65
  setMessage({
726
66
  text: silent
@@ -729,47 +69,25 @@ ${dim}────────────────────────
729
69
  type: failCount > 0 ? 'error' : 'success',
730
70
  });
731
71
  }
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');
72
+ proceedToNextView();
73
+ },
74
+ });
75
+ // Check for tool updates and proceed to next view
76
+ const checkToolUpdatesAndProceed = () => {
77
+ const { updates, shouldAutoUpdate } = checkForUpdates();
78
+ if (shouldAutoUpdate) {
79
+ updateAllTools(updates, true);
80
+ return;
750
81
  }
751
- else {
752
- setView('menu');
82
+ if (updates.length > 0) {
83
+ setView('tool-updates');
84
+ return;
753
85
  }
86
+ proceedToNextView();
754
87
  };
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
- });
88
+ const handleSkipToolUpdates = () => {
89
+ proceedToNextView();
771
90
  };
772
- const MAX_VISIBLE_ITEMS = 6;
773
91
  const tools = getBundledTools();
774
92
  // Keep skills for configure view (tools configure via their primary skill)
775
93
  const skills = getBundledSkills();
@@ -824,7 +142,6 @@ ${dim}────────────────────────
824
142
  }
825
143
  else if (activeTab === 'settings') {
826
144
  setView('detail');
827
- setSelectedSetting(0);
828
145
  }
829
146
  }
830
147
  }
@@ -832,27 +149,12 @@ ${dim}────────────────────────
832
149
  if (key.escape || key.backspace) {
833
150
  setView('menu');
834
151
  setSelectedAction(0);
835
- setSelectedSetting(0);
836
152
  }
837
153
  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
- }
154
+ // Settings detail view - just enter to edit
845
155
  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
- }
156
+ setIsEditingSettings(true);
157
+ setView('setup');
856
158
  }
857
159
  }
858
160
  if (key.leftArrow && activeTab === 'tools') {
@@ -952,7 +254,7 @@ ${dim}────────────────────────
952
254
  return (_jsx(WelcomeScreen, { updateInfo: updateInfo, isUpdating: isUpdating, onUpdate: handleUpdate, onAlways: handleAlwaysUpdate, onExit: exit, onContinue: checkToolUpdatesAndProceed }));
953
255
  }
954
256
  if (view === 'tool-updates' && toolUpdates.length > 0) {
955
- return (_jsx(ToolUpdatePrompt, { toolUpdates: toolUpdates, onUpdateAll: () => handleUpdateAllTools(), onAlways: handleAlwaysUpdateTools, onSkip: handleSkipToolUpdates, isUpdating: isUpdatingTools }));
257
+ return (_jsx(ToolUpdatePrompt, { toolUpdates: toolUpdates, onUpdateAll: () => updateAllTools(), onAlways: enableAutoUpdateAndUpdate, onSkip: handleSkipToolUpdates, isUpdating: isUpdatingTools }));
956
258
  }
957
259
  if (view === 'setup') {
958
260
  return (_jsx(SetupScreen, { onComplete: () => {
@@ -986,16 +288,13 @@ ${dim}────────────────────────
986
288
  setView('detail');
987
289
  } }));
988
290
  }
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 }))] }));
291
+ 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, { isFocused: view === 'detail' }))] }));
993
292
  }
994
293
  export async function tuiCommand() {
995
294
  // Enter alternate screen (fullscreen mode)
996
295
  process.stdout.write('\x1b[?1049h');
997
296
  process.stdout.write('\x1b[H'); // Move cursor to top-left
998
- const { unmount, waitUntilExit } = render(_jsx(App, {}));
297
+ const { waitUntilExit } = render(_jsx(App, {}));
999
298
  await waitUntilExit();
1000
299
  // Leave alternate screen
1001
300
  process.stdout.write('\x1b[?1049l');