@orderful/droid 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/.claude/CLAUDE.md +31 -9
  2. package/CHANGELOG.md +35 -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 -756
  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 +7 -7
  91. package/dist/lib/agents.d.ts.map +1 -1
  92. package/dist/lib/agents.js +63 -41
  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 +2 -1
  107. package/dist/lib/skills.d.ts.map +1 -1
  108. package/dist/lib/skills.js +45 -12
  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/dist/lib/types.d.ts +4 -0
  115. package/dist/lib/types.d.ts.map +1 -1
  116. package/dist/tools/README.md +79 -50
  117. package/dist/tools/brain/TOOL.yaml +1 -1
  118. package/dist/tools/brain/skills/brain/SKILL.md +1 -0
  119. package/dist/tools/brain/skills/brain-obsidian/SKILL.md +1 -0
  120. package/dist/tools/coach/TOOL.yaml +1 -1
  121. package/dist/tools/coach/skills/coach/SKILL.md +1 -0
  122. package/{src/tools/code-review/agents/edi-standards-reviewer/AGENT.md → dist/tools/code-review/agents/edi-standards-reviewer.md} +10 -0
  123. package/dist/tools/code-review/agents/{error-handling-reviewer/AGENT.md → error-handling-reviewer.md} +10 -0
  124. package/{src/tools/code-review/agents/test-coverage-analyzer/AGENT.md → dist/tools/code-review/agents/test-coverage-analyzer.md} +11 -0
  125. package/dist/tools/code-review/agents/{type-reviewer/AGENT.md → type-reviewer.md} +10 -0
  126. package/dist/tools/code-review/skills/code-review/SKILL.md +1 -0
  127. package/dist/tools/comments/TOOL.yaml +2 -2
  128. package/dist/tools/comments/skills/comments/SKILL.md +1 -0
  129. package/dist/tools/droid/skills/droid/SKILL.md +1 -0
  130. package/dist/tools/project/skills/project/SKILL.md +1 -0
  131. package/package.json +3 -3
  132. package/src/bin/droid.ts +8 -8
  133. package/src/commands/config.ts +1 -1
  134. package/src/commands/install.ts +3 -3
  135. package/src/commands/setup.test.ts +1 -1
  136. package/src/commands/setup.ts +3 -3
  137. package/src/commands/skills.ts +4 -4
  138. package/src/commands/tui/components/Badge.tsx +86 -0
  139. package/src/commands/tui/components/Markdown.tsx +48 -0
  140. package/src/commands/tui/components/SettingsDetails.tsx +70 -0
  141. package/src/commands/tui/components/TabBar.tsx +26 -0
  142. package/src/commands/tui/components/ToolDetails.tsx +117 -0
  143. package/src/commands/tui/components/ToolItem.tsx +39 -0
  144. package/src/commands/tui/constants.ts +17 -0
  145. package/src/commands/tui/hooks/useAppUpdate.ts +67 -0
  146. package/src/commands/tui/hooks/useToolUpdates.ts +110 -0
  147. package/src/commands/tui/types.ts +4 -0
  148. package/src/commands/tui/views/ReadmeViewer.tsx +93 -0
  149. package/src/commands/tui/views/SetupScreen.tsx +242 -0
  150. package/src/commands/tui/views/SkillConfigScreen.tsx +278 -0
  151. package/src/commands/tui/views/ToolExplorer.tsx +190 -0
  152. package/src/commands/tui/views/ToolUpdatePrompt.tsx +109 -0
  153. package/src/commands/tui/views/WelcomeScreen.tsx +149 -0
  154. package/src/commands/tui.tsx +65 -1588
  155. package/src/commands/uninstall.ts +2 -2
  156. package/src/commands/update.ts +1 -1
  157. package/src/index.ts +4 -4
  158. package/src/lib/agents.ts +68 -45
  159. package/src/lib/config.ts +1 -1
  160. package/src/lib/platforms.ts +1 -1
  161. package/src/lib/skill-config.ts +2 -2
  162. package/src/lib/skills.test.ts +28 -33
  163. package/src/lib/skills.ts +49 -12
  164. package/src/lib/tools.ts +3 -3
  165. package/src/lib/types.test.ts +1 -1
  166. package/src/lib/types.ts +5 -0
  167. package/src/lib/version.test.ts +1 -1
  168. package/src/tools/README.md +79 -50
  169. package/src/tools/brain/TOOL.yaml +1 -1
  170. package/src/tools/brain/skills/brain/SKILL.md +1 -0
  171. package/src/tools/brain/skills/brain-obsidian/SKILL.md +1 -0
  172. package/src/tools/coach/TOOL.yaml +1 -1
  173. package/src/tools/coach/skills/coach/SKILL.md +1 -0
  174. package/{dist/tools/code-review/agents/edi-standards-reviewer/AGENT.md → src/tools/code-review/agents/edi-standards-reviewer.md} +10 -0
  175. package/src/tools/code-review/agents/{error-handling-reviewer/AGENT.md → error-handling-reviewer.md} +10 -0
  176. package/{dist/tools/code-review/agents/test-coverage-analyzer/AGENT.md → src/tools/code-review/agents/test-coverage-analyzer.md} +11 -0
  177. package/src/tools/code-review/agents/{type-reviewer/AGENT.md → type-reviewer.md} +10 -0
  178. package/src/tools/code-review/skills/code-review/SKILL.md +1 -0
  179. package/src/tools/comments/TOOL.yaml +2 -2
  180. package/src/tools/comments/skills/comments/SKILL.md +1 -0
  181. package/src/tools/droid/skills/droid/SKILL.md +1 -0
  182. package/src/tools/project/skills/project/SKILL.md +1 -0
  183. package/dist/tools/brain/skills/brain/SKILL.yaml +0 -29
  184. package/dist/tools/brain/skills/brain-obsidian/SKILL.yaml +0 -42
  185. package/dist/tools/coach/skills/coach/SKILL.yaml +0 -25
  186. package/dist/tools/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -14
  187. package/dist/tools/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -14
  188. package/dist/tools/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -14
  189. package/dist/tools/code-review/agents/type-reviewer/AGENT.yaml +0 -13
  190. package/dist/tools/code-review/skills/code-review/SKILL.yaml +0 -19
  191. package/dist/tools/comments/skills/comments/SKILL.yaml +0 -50
  192. package/dist/tools/droid/skills/droid/SKILL.yaml +0 -7
  193. package/dist/tools/project/skills/project/SKILL.yaml +0 -30
  194. package/src/tools/brain/skills/brain/SKILL.yaml +0 -29
  195. package/src/tools/brain/skills/brain-obsidian/SKILL.yaml +0 -42
  196. package/src/tools/coach/skills/coach/SKILL.yaml +0 -25
  197. package/src/tools/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -14
  198. package/src/tools/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -14
  199. package/src/tools/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -14
  200. package/src/tools/code-review/agents/type-reviewer/AGENT.yaml +0 -13
  201. package/src/tools/code-review/skills/code-review/SKILL.yaml +0 -19
  202. package/src/tools/comments/skills/comments/SKILL.yaml +0 -50
  203. package/src/tools/droid/skills/droid/SKILL.yaml +0 -7
  204. package/src/tools/project/skills/project/SKILL.yaml +0 -30
@@ -1,595 +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
- // Add agents
392
- for (const agent of tool.includes.agents) {
393
- result.push({
394
- type: 'agent',
395
- name: agent,
396
- path: join(toolDir, tool.name, 'agents', agent, 'AGENT.md'),
397
- });
398
- }
399
- return result;
400
- }, [tool]);
401
- useInput((input, key) => {
402
- if (key.escape) {
403
- onClose();
404
- return;
405
- }
406
- if (key.leftArrow || key.upArrow) {
407
- setSelectedIndex((prev) => Math.max(0, prev - 1));
408
- }
409
- if (key.rightArrow || key.downArrow) {
410
- setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
411
- }
412
- if (key.return && items.length > 0) {
413
- const item = items[selectedIndex];
414
- if (existsSync(item.path)) {
415
- const content = readFileSync(item.path, 'utf-8');
416
- onViewSource(`${tool.name} / ${item.name}`, content);
417
- }
418
- else {
419
- // Try YAML fallback for skills/agents
420
- const yamlPath = item.path.replace('.md', '.yaml');
421
- if (existsSync(yamlPath)) {
422
- const content = readFileSync(yamlPath, 'utf-8');
423
- onViewSource(`${tool.name} / ${item.name}`, content);
424
- }
425
- }
426
- }
427
- });
428
- // Group items by type for display
429
- const skillItems = items.filter(i => i.type === 'skill');
430
- const commandItems = items.filter(i => i.type === 'command');
431
- const agentItems = items.filter(i => i.type === 'agent');
432
- const getItemIndex = (item) => items.indexOf(item);
433
- 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) => {
434
- const idx = getItemIndex(item);
435
- const isSelected = selectedIndex === idx;
436
- 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));
437
- }) })] })), 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) => {
438
- const idx = getItemIndex(item);
439
- const isSelected = selectedIndex === idx;
440
- 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));
441
- }) })] })), 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) => {
442
- const idx = getItemIndex(item);
443
- const isSelected = selectedIndex === idx;
444
- 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));
445
- }) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2190\u2192 navigate \u00B7 enter view source \u00B7 esc back" }) })] }));
446
- }
447
- function SettingsDetails({ onEditSettings, isFocused, onToggleAutoUpdate, selectedSetting, }) {
448
- const config = loadConfig();
449
- const autoUpdateConfig = getAutoUpdateConfig();
450
- 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" }) }))] }));
451
- }
452
- const MAX_VISIBLE_CONFIG_ITEMS = 4; // Each config item takes ~3 lines
453
- function SkillConfigScreen({ skill, onComplete, onCancel }) {
454
- const configSchema = skill.config_schema || {};
455
- const configKeys = Object.keys(configSchema);
456
- const initialOverrides = useMemo(() => loadSkillOverrides(skill.name), [skill.name]);
457
- // Initialize values from saved overrides or defaults
458
- const [values, setValues] = useState(() => {
459
- const initial = {};
460
- for (const key of configKeys) {
461
- const option = configSchema[key];
462
- initial[key] = initialOverrides[key] ?? option.default ?? (option.type === ConfigOptionType.Boolean ? false : '');
463
- }
464
- return initial;
465
- });
466
- const [selectedIndex, setSelectedIndex] = useState(0);
467
- const [scrollOffset, setScrollOffset] = useState(0);
468
- const [editingField, setEditingField] = useState(null);
469
- const [editValue, setEditValue] = useState('');
470
- const [editingSelect, setEditingSelect] = useState(null);
471
- const [selectOptionIndex, setSelectOptionIndex] = useState(0);
472
- // Total items = config keys + Save button
473
- const totalItems = configKeys.length + 1;
474
- const handleSave = () => {
475
- saveSkillOverrides(skill.name, values);
476
- onComplete();
477
- };
478
- const handleSubmitEdit = () => {
479
- if (editingField) {
480
- setValues((prev) => ({ ...prev, [editingField]: editValue }));
481
- setEditingField(null);
482
- setEditValue('');
483
- }
484
- };
485
- // Handle text input for string fields
486
- useInput((input, key) => {
487
- if (key.escape) {
488
- setEditingField(null);
489
- setEditValue('');
490
- }
491
- }, { isActive: editingField !== null });
492
- // Handle select field editing
493
- useInput((input, key) => {
494
- if (!editingSelect)
495
- return;
496
- const option = configSchema[editingSelect];
497
- if (!option?.options)
498
- return;
499
- if (key.escape) {
500
- setEditingSelect(null);
501
- return;
502
- }
503
- if (key.leftArrow || key.upArrow) {
504
- setSelectOptionIndex((prev) => Math.max(0, prev - 1));
505
- }
506
- if (key.rightArrow || key.downArrow) {
507
- setSelectOptionIndex((prev) => Math.min(option.options.length - 1, prev + 1));
508
- }
509
- if (key.return) {
510
- setValues((prev) => ({ ...prev, [editingSelect]: option.options[selectOptionIndex] }));
511
- setEditingSelect(null);
512
- }
513
- }, { isActive: editingSelect !== null });
514
- // Handle navigation and actions when not editing
515
- useInput((input, key) => {
516
- if (key.escape) {
517
- onCancel();
518
- return;
519
- }
520
- if (key.upArrow) {
521
- setSelectedIndex((prev) => {
522
- const newIndex = Math.max(0, prev - 1);
523
- // Scroll up if needed
524
- if (newIndex < scrollOffset) {
525
- setScrollOffset(newIndex);
526
- }
527
- return newIndex;
528
- });
529
- }
530
- if (key.downArrow) {
531
- // +1 for the Save button at the end
532
- setSelectedIndex((prev) => {
533
- const newIndex = Math.min(totalItems - 1, prev + 1);
534
- // Scroll down if needed
535
- if (newIndex >= scrollOffset + MAX_VISIBLE_CONFIG_ITEMS) {
536
- setScrollOffset(newIndex - MAX_VISIBLE_CONFIG_ITEMS + 1);
537
- }
538
- return newIndex;
539
- });
540
- }
541
- if (key.return) {
542
- // Save button is at index === configKeys.length
543
- if (selectedIndex === configKeys.length) {
544
- handleSave();
545
- return;
546
- }
547
- const key = configKeys[selectedIndex];
548
- const option = configSchema[key];
549
- if (option.type === ConfigOptionType.Boolean) {
550
- // Toggle boolean
551
- setValues((prev) => ({ ...prev, [key]: !prev[key] }));
552
- }
553
- else if (option.type === ConfigOptionType.Select && option.options) {
554
- // Enter select edit mode
555
- const currentValue = String(values[key] || option.options[0]);
556
- const currentIndex = option.options.indexOf(currentValue);
557
- setSelectOptionIndex(currentIndex >= 0 ? currentIndex : 0);
558
- setEditingSelect(key);
559
- }
560
- else if (option.type === ConfigOptionType.String) {
561
- // Enter edit mode for string
562
- setEditingField(key);
563
- setEditValue(String(values[key] || ''));
564
- }
565
- }
566
- }, { isActive: editingField === null && editingSelect === null });
567
- if (configKeys.length === 0) {
568
- 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" }) })] }));
569
- }
570
- // Calculate visible range
571
- const visibleEndIndex = Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, totalItems);
572
- const visibleConfigKeys = configKeys.slice(scrollOffset, Math.min(scrollOffset + MAX_VISIBLE_CONFIG_ITEMS, configKeys.length));
573
- const showSaveButton = visibleEndIndex > configKeys.length || scrollOffset + MAX_VISIBLE_CONFIG_ITEMS > configKeys.length;
574
- const showTopIndicator = scrollOffset > 0;
575
- const showBottomIndicator = scrollOffset + MAX_VISIBLE_CONFIG_ITEMS < totalItems;
576
- 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) => {
577
- const actualIndex = configKeys.indexOf(key);
578
- const option = configSchema[key];
579
- const isSelected = selectedIndex === actualIndex;
580
- const isEditing = editingField === key;
581
- 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) => {
582
- const isCurrentValue = String(values[key]) === opt;
583
- const isEditingThis = editingSelect === key;
584
- const isHighlighted = isEditingThis && selectOptionIndex === i;
585
- 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));
586
- }) })) : 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));
587
- }), 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
588
- ? 'enter save · esc cancel'
589
- : editingSelect
590
- ? '←→ choose · enter select · esc cancel'
591
- : '↑↓ select · enter toggle/edit · esc back' }) })] }));
592
- }
593
24
  function App() {
594
25
  const { exit } = useApp();
595
26
  const tabs = [
@@ -605,88 +36,22 @@ function App() {
605
36
  const [message, setMessage] = useState(null);
606
37
  const [isEditingSettings, setIsEditingSettings] = useState(false);
607
38
  const [readmeContent, setReadmeContent] = useState(null);
608
- const [selectedSetting, setSelectedSetting] = useState(0);
609
- const [isUpdating, setIsUpdating] = useState(false);
610
- const [isUpdatingTools, setIsUpdatingTools] = useState(false);
611
39
  const [previousView, setPreviousView] = useState('detail'); // Track where to return from readme
612
- const [toolUpdates, setToolUpdates] = useState([]);
613
- const [autoUpdatedTools, setAutoUpdatedTools] = useState([]);
614
- // Check for updates once on mount
615
- const updateInfo = useMemo(() => getUpdateInfo(), []);
616
- const autoUpdateConfig = useMemo(() => getAutoUpdateConfig(), []);
617
- const handleUpdate = () => {
618
- setIsUpdating(true);
619
- // Run update in next tick to allow UI to show "Updating..."
620
- setTimeout(() => {
621
- const result = runUpdate();
622
- if (result.success) {
623
- // Store message to print after leaving alternate screen (with ANSI colors)
624
- const blue = '\x1b[38;2;99;102;241m'; // #6366f1
625
- const dim = '\x1b[38;2;106;106;106m';
626
- const reset = '\x1b[0m';
627
- exitMessage = `
628
- ${dim}────────────────────────────────────────────────────────${reset}
629
-
630
- ${dim}╔═════╗${reset}
631
- ${dim}║${reset} ${blue}●${reset} ${blue}●${reset} ${dim}║${reset} ${blue}"It's quite possible this system${reset}
632
- ${dim}╚═╦═╦═╝${reset} ${blue}is now fully operational."${reset}
633
-
634
- Run ${blue}droid${reset} to start the new version.
635
-
636
- ${dim}────────────────────────────────────────────────────────${reset}
637
- `;
638
- 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');
639
48
  }
640
49
  else {
641
- setIsUpdating(false);
642
- setMessage({ text: result.message, type: 'error' });
643
- // Continue to menu on failure
644
- if (!configExists()) {
645
- setView('setup');
646
- }
647
- else {
648
- setView('menu');
649
- }
650
- }
651
- }, 100);
652
- };
653
- const handleAlwaysUpdate = () => {
654
- // Enable auto-update for app in config
655
- setAutoUpdateConfig({ app: true });
656
- // Then run the update
657
- handleUpdate();
658
- };
659
- // Ensure system tools (marked with system: true in TOOL.yaml) are always installed and current
660
- const ensureSystemTools = () => {
661
- // Find all tools marked as system tools
662
- const systemTools = tools.filter(t => t.system === true);
663
- for (const systemTool of systemTools) {
664
- const installed = isToolInstalled(systemTool.name);
665
- const updateStatus = getToolUpdateStatus(systemTool.name);
666
- // Install if not installed, or update if outdated (regardless of auto-update settings)
667
- if (!installed || updateStatus.hasUpdate) {
668
- const primarySkill = systemTool.includes.skills.find(s => s.required)?.name || systemTool.name;
669
- installSkill(primarySkill);
50
+ setView('menu');
670
51
  }
671
- }
672
- };
673
- // Check for tool updates and proceed to next view
674
- const checkToolUpdatesAndProceed = () => {
675
- // Always ensure system tools are current (bypasses auto-update settings)
676
- ensureSystemTools();
677
- const updates = getToolsWithUpdates();
678
- setToolUpdates(updates);
679
- // If auto_update.tools is true, auto-update silently
680
- if (autoUpdateConfig.tools && updates.length > 0) {
681
- handleUpdateAllTools(updates, true);
682
- return;
683
- }
684
- // If there are updates and auto-update is off, show prompt
685
- if (updates.length > 0) {
686
- setView('tool-updates');
687
- return;
688
- }
689
- // No updates, proceed to setup or menu
52
+ },
53
+ });
54
+ const proceedToNextView = () => {
690
55
  if (!configExists()) {
691
56
  setView('setup');
692
57
  }
@@ -694,34 +59,8 @@ ${dim}────────────────────────
694
59
  setView('menu');
695
60
  }
696
61
  };
697
- const handleUpdateAllTools = (updates = toolUpdates, silent = false) => {
698
- if (!silent) {
699
- setIsUpdatingTools(true);
700
- }
701
- setTimeout(() => {
702
- let successCount = 0;
703
- let failCount = 0;
704
- const updatedNames = [];
705
- for (const tool of updates) {
706
- // Find the tool to get its primary skill
707
- const toolManifest = tools.find(t => t.name === tool.name);
708
- if (toolManifest) {
709
- const primarySkill = toolManifest.includes.skills.find(s => s.required)?.name || toolManifest.name;
710
- const result = updateSkill(primarySkill);
711
- if (result.success) {
712
- successCount++;
713
- updatedNames.push(tool.name);
714
- }
715
- else {
716
- failCount++;
717
- }
718
- }
719
- }
720
- setIsUpdatingTools(false);
721
- // Track which tools were auto-updated for visual indicator
722
- if (silent && updatedNames.length > 0) {
723
- setAutoUpdatedTools(updatedNames);
724
- }
62
+ const { toolUpdates, isUpdatingTools, autoUpdatedTools, checkForUpdates, updateAllTools, enableAutoUpdateAndUpdate, } = useToolUpdates({
63
+ onUpdateComplete: ({ successCount, failCount, silent }) => {
725
64
  if (successCount > 0) {
726
65
  setMessage({
727
66
  text: silent
@@ -730,47 +69,25 @@ ${dim}────────────────────────
730
69
  type: failCount > 0 ? 'error' : 'success',
731
70
  });
732
71
  }
733
- // Proceed to next view
734
- if (!configExists()) {
735
- setView('setup');
736
- }
737
- else {
738
- setView('menu');
739
- }
740
- }, 100);
741
- };
742
- const handleAlwaysUpdateTools = () => {
743
- // Enable auto-update in config
744
- setAutoUpdateConfig({ tools: true });
745
- // Then update all
746
- handleUpdateAllTools();
747
- };
748
- const handleSkipToolUpdates = () => {
749
- if (!configExists()) {
750
- 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;
751
81
  }
752
- else {
753
- setView('menu');
82
+ if (updates.length > 0) {
83
+ setView('tool-updates');
84
+ return;
754
85
  }
86
+ proceedToNextView();
755
87
  };
756
- const handleToggleAutoUpdateTools = () => {
757
- const current = getAutoUpdateConfig();
758
- setAutoUpdateConfig({ tools: !current.tools });
759
- // Force re-render by setting message (auto-clears on next input)
760
- setMessage({
761
- text: `✓ Auto-update tools ${!current.tools ? 'enabled' : 'disabled'}`,
762
- type: 'success',
763
- });
764
- };
765
- const handleToggleAutoUpdateApp = () => {
766
- const current = getAutoUpdateConfig();
767
- setAutoUpdateConfig({ app: !current.app });
768
- setMessage({
769
- text: `✓ Auto-update app ${!current.app ? 'enabled' : 'disabled'}`,
770
- type: 'success',
771
- });
88
+ const handleSkipToolUpdates = () => {
89
+ proceedToNextView();
772
90
  };
773
- const MAX_VISIBLE_ITEMS = 6;
774
91
  const tools = getBundledTools();
775
92
  // Keep skills for configure view (tools configure via their primary skill)
776
93
  const skills = getBundledSkills();
@@ -825,7 +142,6 @@ ${dim}────────────────────────
825
142
  }
826
143
  else if (activeTab === 'settings') {
827
144
  setView('detail');
828
- setSelectedSetting(0);
829
145
  }
830
146
  }
831
147
  }
@@ -833,27 +149,12 @@ ${dim}────────────────────────
833
149
  if (key.escape || key.backspace) {
834
150
  setView('menu');
835
151
  setSelectedAction(0);
836
- setSelectedSetting(0);
837
152
  }
838
153
  if (activeTab === 'settings') {
839
- // Settings detail view navigation
840
- if (key.upArrow) {
841
- setSelectedSetting((prev) => Math.max(0, prev - 1));
842
- }
843
- if (key.downArrow) {
844
- setSelectedSetting((prev) => Math.min(2, prev + 1)); // 0: auto-update tools, 1: auto-update app, 2: edit profile
845
- }
154
+ // Settings detail view - just enter to edit
846
155
  if (key.return) {
847
- if (selectedSetting === 0) {
848
- handleToggleAutoUpdateTools();
849
- }
850
- else if (selectedSetting === 1) {
851
- handleToggleAutoUpdateApp();
852
- }
853
- else if (selectedSetting === 2) {
854
- setIsEditingSettings(true);
855
- setView('setup');
856
- }
156
+ setIsEditingSettings(true);
157
+ setView('setup');
857
158
  }
858
159
  }
859
160
  if (key.leftArrow && activeTab === 'tools') {
@@ -953,7 +254,7 @@ ${dim}────────────────────────
953
254
  return (_jsx(WelcomeScreen, { updateInfo: updateInfo, isUpdating: isUpdating, onUpdate: handleUpdate, onAlways: handleAlwaysUpdate, onExit: exit, onContinue: checkToolUpdatesAndProceed }));
954
255
  }
955
256
  if (view === 'tool-updates' && toolUpdates.length > 0) {
956
- 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 }));
957
258
  }
958
259
  if (view === 'setup') {
959
260
  return (_jsx(SetupScreen, { onComplete: () => {
@@ -987,16 +288,13 @@ ${dim}────────────────────────
987
288
  setView('detail');
988
289
  } }));
989
290
  }
990
- 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: () => {
991
- setIsEditingSettings(true);
992
- setView('setup');
993
- }, 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' }))] }));
994
292
  }
995
293
  export async function tuiCommand() {
996
294
  // Enter alternate screen (fullscreen mode)
997
295
  process.stdout.write('\x1b[?1049h');
998
296
  process.stdout.write('\x1b[H'); // Move cursor to top-left
999
- const { unmount, waitUntilExit } = render(_jsx(App, {}));
297
+ const { waitUntilExit } = render(_jsx(App, {}));
1000
298
  await waitUntilExit();
1001
299
  // Leave alternate screen
1002
300
  process.stdout.write('\x1b[?1049l');