@orderful/droid 0.10.5 → 0.11.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 (167) hide show
  1. package/.claude/CLAUDE.md +13 -6
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +23 -24
  4. package/dist/bin/droid.js +9 -9
  5. package/dist/bin/droid.js.map +1 -1
  6. package/dist/commands/install.d.ts +1 -1
  7. package/dist/commands/install.d.ts.map +1 -1
  8. package/dist/commands/install.js +24 -23
  9. package/dist/commands/install.js.map +1 -1
  10. package/dist/commands/setup.d.ts +3 -3
  11. package/dist/commands/setup.d.ts.map +1 -1
  12. package/dist/commands/setup.js +32 -28
  13. package/dist/commands/setup.js.map +1 -1
  14. package/dist/commands/skills.d.ts.map +1 -1
  15. package/dist/commands/skills.js +60 -53
  16. package/dist/commands/skills.js.map +1 -1
  17. package/dist/commands/tui.d.ts.map +1 -1
  18. package/dist/commands/tui.js +213 -319
  19. package/dist/commands/tui.js.map +1 -1
  20. package/dist/commands/uninstall.d.ts +1 -1
  21. package/dist/commands/uninstall.d.ts.map +1 -1
  22. package/dist/commands/uninstall.js +15 -6
  23. package/dist/commands/uninstall.js.map +1 -1
  24. package/dist/commands/update.d.ts +2 -2
  25. package/dist/commands/update.d.ts.map +1 -1
  26. package/dist/commands/update.js +9 -9
  27. package/dist/commands/update.js.map +1 -1
  28. package/dist/lib/agents.d.ts +11 -10
  29. package/dist/lib/agents.d.ts.map +1 -1
  30. package/dist/lib/agents.js +52 -54
  31. package/dist/lib/agents.js.map +1 -1
  32. package/dist/lib/config.d.ts +1 -0
  33. package/dist/lib/config.d.ts.map +1 -1
  34. package/dist/lib/config.js +42 -5
  35. package/dist/lib/config.js.map +1 -1
  36. package/dist/lib/platforms.d.ts +41 -0
  37. package/dist/lib/platforms.d.ts.map +1 -0
  38. package/dist/lib/platforms.js +52 -0
  39. package/dist/lib/platforms.js.map +1 -0
  40. package/dist/lib/skills.d.ts +19 -11
  41. package/dist/lib/skills.d.ts.map +1 -1
  42. package/dist/lib/skills.js +125 -99
  43. package/dist/lib/skills.js.map +1 -1
  44. package/dist/lib/tools.d.ts +30 -0
  45. package/dist/lib/tools.d.ts.map +1 -0
  46. package/dist/lib/tools.js +116 -0
  47. package/dist/lib/tools.js.map +1 -0
  48. package/dist/lib/types.d.ts +45 -2
  49. package/dist/lib/types.d.ts.map +1 -1
  50. package/dist/lib/types.js +24 -5
  51. package/dist/lib/types.js.map +1 -1
  52. package/dist/tools/brain/TOOL.yaml +27 -0
  53. package/dist/tools/coach/TOOL.yaml +21 -0
  54. package/dist/tools/code-review/TOOL.yaml +18 -0
  55. package/dist/tools/comments/TOOL.yaml +27 -0
  56. package/dist/tools/project/TOOL.yaml +26 -0
  57. package/package.json +2 -2
  58. package/src/bin/droid.ts +9 -9
  59. package/src/commands/install.ts +24 -23
  60. package/src/commands/setup.test.ts +2 -2
  61. package/src/commands/setup.ts +33 -29
  62. package/src/commands/skills.ts +63 -64
  63. package/src/commands/tui.tsx +432 -578
  64. package/src/commands/uninstall.ts +17 -6
  65. package/src/commands/update.ts +10 -10
  66. package/src/lib/agents.ts +58 -58
  67. package/src/lib/config.test.ts +0 -10
  68. package/src/lib/config.ts +47 -5
  69. package/src/lib/platforms.ts +59 -0
  70. package/src/lib/skills.test.ts +53 -28
  71. package/src/lib/skills.ts +134 -101
  72. package/src/lib/tools.ts +140 -0
  73. package/src/lib/types.test.ts +15 -7
  74. package/src/lib/types.ts +63 -2
  75. package/src/tools/brain/TOOL.yaml +27 -0
  76. package/src/tools/coach/TOOL.yaml +21 -0
  77. package/src/tools/code-review/TOOL.yaml +18 -0
  78. package/src/tools/comments/TOOL.yaml +27 -0
  79. package/src/tools/project/TOOL.yaml +26 -0
  80. package/dist/agents/README.md +0 -137
  81. package/src/agents/README.md +0 -137
  82. /package/dist/{skills → tools}/README.md +0 -0
  83. /package/dist/{skills → tools}/brain/commands/README.md +0 -0
  84. /package/dist/{skills → tools}/brain/commands/brain.md +0 -0
  85. /package/dist/{skills → tools}/brain/commands/scratchpad.md +0 -0
  86. /package/dist/{skills → tools/brain/skills}/brain/SKILL.md +0 -0
  87. /package/dist/{skills → tools/brain/skills}/brain/SKILL.yaml +0 -0
  88. /package/dist/{skills → tools/brain/skills}/brain/references/metadata.md +0 -0
  89. /package/dist/{skills → tools/brain/skills}/brain/references/naming.md +0 -0
  90. /package/dist/{skills → tools/brain/skills}/brain/references/templates.md +0 -0
  91. /package/dist/{skills → tools/brain/skills}/brain/references/workflows.md +0 -0
  92. /package/dist/{skills → tools/brain/skills}/brain-obsidian/SKILL.md +0 -0
  93. /package/dist/{skills → tools/brain/skills}/brain-obsidian/SKILL.yaml +0 -0
  94. /package/dist/{skills → tools/brain/skills}/brain-obsidian/references/templates.md +0 -0
  95. /package/dist/{skills → tools/brain/skills}/brain-obsidian/references/workflows.md +0 -0
  96. /package/dist/{skills → tools}/coach/commands/README.md +0 -0
  97. /package/dist/{skills → tools}/coach/commands/coach.md +0 -0
  98. /package/dist/{skills → tools/coach/skills}/coach/SKILL.md +0 -0
  99. /package/dist/{skills → tools/coach/skills}/coach/SKILL.yaml +0 -0
  100. /package/dist/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.md +0 -0
  101. /package/dist/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -0
  102. /package/dist/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.md +0 -0
  103. /package/dist/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -0
  104. /package/dist/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.md +0 -0
  105. /package/dist/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -0
  106. /package/dist/{skills → tools}/code-review/agents/type-reviewer/AGENT.md +0 -0
  107. /package/dist/{skills → tools}/code-review/agents/type-reviewer/AGENT.yaml +0 -0
  108. /package/dist/{skills → tools}/code-review/commands/code-review.md +0 -0
  109. /package/dist/{skills → tools/code-review/skills}/code-review/SKILL.md +0 -0
  110. /package/dist/{skills → tools/code-review/skills}/code-review/SKILL.yaml +0 -0
  111. /package/dist/{skills → tools}/comments/commands/README.md +0 -0
  112. /package/dist/{skills → tools}/comments/commands/comments.md +0 -0
  113. /package/dist/{skills → tools/comments/skills}/comments/SKILL.md +0 -0
  114. /package/dist/{skills → tools/comments/skills}/comments/SKILL.yaml +0 -0
  115. /package/dist/{skills → tools}/project/commands/README.md +0 -0
  116. /package/dist/{skills → tools}/project/commands/project.md +0 -0
  117. /package/dist/{skills → tools/project/skills}/project/SKILL.md +0 -0
  118. /package/dist/{skills → tools/project/skills}/project/SKILL.yaml +0 -0
  119. /package/dist/{skills → tools/project/skills}/project/references/changelog.md +0 -0
  120. /package/dist/{skills → tools/project/skills}/project/references/creating.md +0 -0
  121. /package/dist/{skills → tools/project/skills}/project/references/loading.md +0 -0
  122. /package/dist/{skills → tools/project/skills}/project/references/templates.md +0 -0
  123. /package/dist/{skills → tools/project/skills}/project/references/updating.md +0 -0
  124. /package/dist/{skills → tools/project/skills}/project/references/versioning.md +0 -0
  125. /package/src/{skills → tools}/README.md +0 -0
  126. /package/src/{skills → tools}/brain/commands/README.md +0 -0
  127. /package/src/{skills → tools}/brain/commands/brain.md +0 -0
  128. /package/src/{skills → tools}/brain/commands/scratchpad.md +0 -0
  129. /package/src/{skills → tools/brain/skills}/brain/SKILL.md +0 -0
  130. /package/src/{skills → tools/brain/skills}/brain/SKILL.yaml +0 -0
  131. /package/src/{skills → tools/brain/skills}/brain/references/metadata.md +0 -0
  132. /package/src/{skills → tools/brain/skills}/brain/references/naming.md +0 -0
  133. /package/src/{skills → tools/brain/skills}/brain/references/templates.md +0 -0
  134. /package/src/{skills → tools/brain/skills}/brain/references/workflows.md +0 -0
  135. /package/src/{skills → tools/brain/skills}/brain-obsidian/SKILL.md +0 -0
  136. /package/src/{skills → tools/brain/skills}/brain-obsidian/SKILL.yaml +0 -0
  137. /package/src/{skills → tools/brain/skills}/brain-obsidian/references/templates.md +0 -0
  138. /package/src/{skills → tools/brain/skills}/brain-obsidian/references/workflows.md +0 -0
  139. /package/src/{skills → tools}/coach/commands/README.md +0 -0
  140. /package/src/{skills → tools}/coach/commands/coach.md +0 -0
  141. /package/src/{skills → tools/coach/skills}/coach/SKILL.md +0 -0
  142. /package/src/{skills → tools/coach/skills}/coach/SKILL.yaml +0 -0
  143. /package/src/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.md +0 -0
  144. /package/src/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -0
  145. /package/src/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.md +0 -0
  146. /package/src/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -0
  147. /package/src/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.md +0 -0
  148. /package/src/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -0
  149. /package/src/{skills → tools}/code-review/agents/type-reviewer/AGENT.md +0 -0
  150. /package/src/{skills → tools}/code-review/agents/type-reviewer/AGENT.yaml +0 -0
  151. /package/src/{skills → tools}/code-review/commands/code-review.md +0 -0
  152. /package/src/{skills → tools/code-review/skills}/code-review/SKILL.md +0 -0
  153. /package/src/{skills → tools/code-review/skills}/code-review/SKILL.yaml +0 -0
  154. /package/src/{skills → tools}/comments/commands/README.md +0 -0
  155. /package/src/{skills → tools}/comments/commands/comments.md +0 -0
  156. /package/src/{skills → tools/comments/skills}/comments/SKILL.md +0 -0
  157. /package/src/{skills → tools/comments/skills}/comments/SKILL.yaml +0 -0
  158. /package/src/{skills → tools}/project/commands/README.md +0 -0
  159. /package/src/{skills → tools}/project/commands/project.md +0 -0
  160. /package/src/{skills → tools/project/skills}/project/SKILL.md +0 -0
  161. /package/src/{skills → tools/project/skills}/project/SKILL.yaml +0 -0
  162. /package/src/{skills → tools/project/skills}/project/references/changelog.md +0 -0
  163. /package/src/{skills → tools/project/skills}/project/references/creating.md +0 -0
  164. /package/src/{skills → tools/project/skills}/project/references/loading.md +0 -0
  165. /package/src/{skills → tools/project/skills}/project/references/templates.md +0 -0
  166. /package/src/{skills → tools/project/skills}/project/references/updating.md +0 -0
  167. /package/src/{skills → tools/project/skills}/project/references/versioning.md +0 -0
@@ -3,13 +3,13 @@ import { render, Box, Text, useInput, useApp } from 'ink';
3
3
  import TextInput from 'ink-text-input';
4
4
  import { useState, useMemo } from 'react';
5
5
  import { execSync } from 'child_process';
6
- import { existsSync, readdirSync, readFileSync } from 'fs';
6
+ import { existsSync, readFileSync } from 'fs';
7
7
  import { join } from 'path';
8
- import { getBundledSkills, getBundledSkillsDir, isSkillInstalled, getInstalledSkill, installSkill, uninstallSkill, updateSkill, getSkillUpdateStatus, } from '../lib/skills.js';
9
- import { getBundledAgents, getBundledAgentsDir, isAgentInstalled, installAgent, uninstallAgent } from '../lib/agents.js';
8
+ import { getBundledSkills, installSkill, uninstallSkill, updateSkill, } from '../lib/skills.js';
9
+ import { getBundledTools, getBundledToolsDir, isToolInstalled, getToolUpdateStatus, getInstalledToolVersion } from '../lib/tools.js';
10
10
  import { configExists, loadConfig, saveConfig, loadSkillOverrides, saveSkillOverrides } from '../lib/config.js';
11
- import { configureAIToolPermissions } from './setup.js';
12
- import { AITool, BuiltInOutput, ConfigOptionType } from '../lib/types.js';
11
+ import { configurePlatformPermissions } from './setup.js';
12
+ import { Platform, BuiltInOutput, ConfigOptionType } from '../lib/types.js';
13
13
  import { getVersion, getUpdateInfo, runUpdate } from '../lib/version.js';
14
14
  import { getRandomQuote } from '../lib/quotes.js';
15
15
  const colors = {
@@ -21,87 +21,61 @@ const colors = {
21
21
  textDim: '#6a6a6a',
22
22
  success: '#4ade80',
23
23
  error: '#f87171',
24
+ // Component type badges
25
+ skill: '#ec4899', // pink/magenta
26
+ command: '#22d3ee', // cyan
27
+ agent: '#fbbf24', // yellow/amber
24
28
  };
25
- function getCommandsFromSkills() {
26
- const skillsDir = getBundledSkillsDir();
27
- const commands = [];
28
- if (!existsSync(skillsDir))
29
- return commands;
30
- const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
31
- .filter((d) => d.isDirectory())
32
- .map((d) => d.name);
33
- for (const skillName of skillDirs) {
34
- const commandsDir = join(skillsDir, skillName, 'commands');
35
- if (!existsSync(commandsDir))
36
- continue;
37
- const cmdFiles = readdirSync(commandsDir).filter((f) => f.endsWith('.md'));
38
- for (const file of cmdFiles) {
39
- const content = readFileSync(join(commandsDir, file), 'utf-8');
40
- const lines = content.split('\n');
41
- // Skip YAML frontmatter if present
42
- let startLine = 0;
43
- if (lines[0]?.trim() === '---') {
44
- for (let i = 1; i < lines.length; i++) {
45
- if (lines[i]?.trim() === '---') {
46
- startLine = i + 1;
47
- break;
48
- }
49
- }
50
- }
51
- // Find first H1 header
52
- let headerMatch = null;
53
- for (let i = startLine; i < lines.length; i++) {
54
- headerMatch = lines[i]?.match(/^#\s+\/(\S+)\s*-?\s*(.*)/);
55
- if (headerMatch)
56
- break;
57
- }
58
- if (!headerMatch)
59
- continue;
60
- const usage = [];
61
- let inUsage = false;
62
- for (const line of lines) {
63
- if (line.startsWith('## Usage'))
64
- inUsage = true;
65
- else if (line.startsWith('## ') && inUsage)
66
- break;
67
- else if (inUsage && line.startsWith('/'))
68
- usage.push(line.trim());
69
- }
70
- commands.push({
71
- name: headerMatch[1],
72
- description: headerMatch[2] || '',
73
- skillName,
74
- usage,
75
- });
76
- }
29
+ function Badge({ type, label, isSelected = false, dimmed = false, }) {
30
+ const color = colors[type];
31
+ const displayLabel = label || type.charAt(0).toUpperCase() + type.slice(1);
32
+ if (dimmed) {
33
+ return (_jsx(Text, { color: colors.textDim, children: displayLabel }));
34
+ }
35
+ return (_jsx(Text, { backgroundColor: isSelected ? color : undefined, color: isSelected ? '#000000' : color, bold: isSelected, children: isSelected ? ` ${displayLabel} ` : displayLabel }));
36
+ }
37
+ function ComponentBadges({ tool, compact = false }) {
38
+ const hasSkills = tool.includes.skills.length > 0;
39
+ const hasCommands = tool.includes.commands.length > 0;
40
+ const hasAgents = tool.includes.agents.length > 0;
41
+ if (compact) {
42
+ // Show colored squares for list view (single char with spacing)
43
+ const parts = [];
44
+ if (hasSkills)
45
+ parts.push('skill');
46
+ if (hasCommands)
47
+ parts.push('command');
48
+ if (hasAgents)
49
+ parts.push('agent');
50
+ return (_jsx(Box, { flexDirection: "row", children: parts.map((type, i) => (_jsxs(Text, { children: [_jsx(Text, { backgroundColor: colors[type], children: " " }), i < parts.length - 1 && ' '] }, type))) }));
77
51
  }
78
- return commands;
52
+ 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' : ''} ` }) }))] }));
79
53
  }
80
- function detectInstalledAITools() {
54
+ function detectInstalledPlatforms() {
81
55
  const installed = new Set();
82
56
  try {
83
57
  execSync('claude --version', { stdio: 'ignore' });
84
- installed.add(AITool.ClaudeCode);
58
+ installed.add(Platform.ClaudeCode);
85
59
  }
86
60
  catch {
87
61
  // Claude Code not found
88
62
  }
89
63
  try {
90
64
  execSync('opencode --version', { stdio: 'ignore' });
91
- installed.add(AITool.OpenCode);
65
+ installed.add(Platform.OpenCode);
92
66
  }
93
67
  catch {
94
68
  // OpenCode not found
95
69
  }
96
70
  return installed;
97
71
  }
98
- function getDefaultAITool() {
99
- const installed = detectInstalledAITools();
100
- if (installed.has(AITool.ClaudeCode))
101
- return AITool.ClaudeCode;
102
- if (installed.has(AITool.OpenCode))
103
- return AITool.OpenCode;
104
- return AITool.ClaudeCode;
72
+ function getDefaultPlatform() {
73
+ const installed = detectInstalledPlatforms();
74
+ if (installed.has(Platform.ClaudeCode))
75
+ return Platform.ClaudeCode;
76
+ if (installed.has(Platform.OpenCode))
77
+ return Platform.OpenCode;
78
+ return Platform.ClaudeCode;
105
79
  }
106
80
  function getOutputOptions() {
107
81
  const options = [
@@ -116,50 +90,6 @@ function getOutputOptions() {
116
90
  }
117
91
  return options;
118
92
  }
119
- /**
120
- * Check if an agent belongs to a skill bundle (whether installed or not)
121
- * Returns the skill name if so, null otherwise
122
- */
123
- function getAgentBundledSkill(agentName) {
124
- const skillsDir = getBundledSkillsDir();
125
- if (!existsSync(skillsDir))
126
- return null;
127
- const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
128
- .filter((d) => d.isDirectory())
129
- .map((d) => d.name);
130
- for (const skillName of skillDirs) {
131
- const agentDir = join(skillsDir, skillName, 'agents', agentName);
132
- if (existsSync(agentDir)) {
133
- return skillName;
134
- }
135
- }
136
- return null;
137
- }
138
- /**
139
- * Find the source path for an agent's AGENT.md
140
- * Checks both standalone agents dir and skill-bundled agents
141
- */
142
- function findAgentSourcePath(agentName) {
143
- // Check standalone agents first
144
- const standalonePath = join(getBundledAgentsDir(), agentName, 'AGENT.md');
145
- if (existsSync(standalonePath)) {
146
- return standalonePath;
147
- }
148
- // Check skill-bundled agents
149
- const skillsDir = getBundledSkillsDir();
150
- if (existsSync(skillsDir)) {
151
- const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
152
- .filter((d) => d.isDirectory())
153
- .map((d) => d.name);
154
- for (const skillName of skillDirs) {
155
- const skillAgentPath = join(skillsDir, skillName, 'agents', agentName, 'AGENT.md');
156
- if (existsSync(skillAgentPath)) {
157
- return skillAgentPath;
158
- }
159
- }
160
- }
161
- return null;
162
- }
163
93
  function WelcomeScreen({ onContinue, onUpdate, onExit, updateInfo, isUpdating }) {
164
94
  const [selectedButton, setSelectedButton] = useState(0);
165
95
  const welcomeQuote = useMemo(() => getRandomQuote(), []);
@@ -195,18 +125,18 @@ function WelcomeScreen({ onContinue, onUpdate, onExit, updateInfo, isUpdating })
195
125
  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 ? colors.bgSelected : undefined, color: selectedButton === 1 ? colors.text : colors.textMuted, bold: selectedButton === 1, children: [' ', "Skip for now", ' '] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2190\u2192 select \u00B7 enter" }) })] })) : (_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" })] }))] }) }));
196
126
  }
197
127
  function SetupScreen({ onComplete, onSkip, initialConfig }) {
198
- const [step, setStep] = useState('ai_tool');
199
- const [aiTool, setAITool] = useState(initialConfig?.ai_tool || getDefaultAITool());
128
+ const [step, setStep] = useState('platform');
129
+ const [platform, setPlatform] = useState(initialConfig?.platform || getDefaultPlatform());
200
130
  const [userMention, setUserMention] = useState(initialConfig?.user_mention || '@user');
201
131
  const [selectedIndex, setSelectedIndex] = useState(0);
202
132
  const [error, setError] = useState(null);
203
133
  // Cache detection results once on mount (avoids re-running execSync on every render)
204
- const installedTools = useMemo(() => detectInstalledAITools(), []);
205
- const aiToolOptions = useMemo(() => [
206
- { label: 'Claude Code', value: AITool.ClaudeCode },
207
- { label: 'OpenCode', value: AITool.OpenCode },
134
+ const installedPlatforms = useMemo(() => detectInstalledPlatforms(), []);
135
+ const platformOptions = useMemo(() => [
136
+ { label: 'Claude Code', value: Platform.ClaudeCode },
137
+ { label: 'OpenCode', value: Platform.OpenCode },
208
138
  ], []);
209
- const steps = ['ai_tool', 'user_mention', 'confirm'];
139
+ const steps = ['platform', 'user_mention', 'confirm'];
210
140
  const stepIndex = steps.indexOf(step);
211
141
  const totalSteps = steps.length - 1; // Don't count confirm as a step
212
142
  const handleUserMentionSubmit = () => {
@@ -219,14 +149,14 @@ function SetupScreen({ onComplete, onSkip, initialConfig }) {
219
149
  // Handle escape during text input (only intercept escape, nothing else)
220
150
  useInput((input, key) => {
221
151
  if (key.escape) {
222
- setStep('ai_tool');
152
+ setStep('platform');
223
153
  setSelectedIndex(0);
224
154
  }
225
155
  }, { isActive: step === 'user_mention' });
226
156
  // Handle all input for non-text-input steps
227
157
  useInput((input, key) => {
228
158
  if (key.escape) {
229
- if (step === 'ai_tool') {
159
+ if (step === 'platform') {
230
160
  onSkip();
231
161
  }
232
162
  else if (step === 'confirm') {
@@ -234,63 +164,60 @@ function SetupScreen({ onComplete, onSkip, initialConfig }) {
234
164
  }
235
165
  return;
236
166
  }
237
- if (step === 'ai_tool') {
167
+ if (step === 'platform') {
238
168
  if (key.upArrow)
239
169
  setSelectedIndex((prev) => Math.max(0, prev - 1));
240
170
  if (key.downArrow)
241
- setSelectedIndex((prev) => Math.min(aiToolOptions.length - 1, prev + 1));
171
+ setSelectedIndex((prev) => Math.min(platformOptions.length - 1, prev + 1));
242
172
  if (key.return) {
243
- setAITool(aiToolOptions[selectedIndex].value);
173
+ setPlatform(platformOptions[selectedIndex].value);
244
174
  setStep('user_mention');
245
175
  }
246
176
  }
247
177
  else if (step === 'confirm') {
248
178
  if (key.return) {
179
+ const existingConfig = loadConfig();
249
180
  const config = {
250
- ...loadConfig(),
251
- ai_tool: aiTool,
181
+ ...existingConfig,
182
+ platform: platform,
252
183
  user_mention: userMention,
253
184
  output_preference: BuiltInOutput.Terminal, // Default to terminal
254
185
  };
255
186
  saveConfig(config);
256
- configureAIToolPermissions(aiTool);
187
+ configurePlatformPermissions(platform);
257
188
  onComplete();
258
189
  }
259
190
  }
260
191
  }, { isActive: step !== 'user_mention' });
261
192
  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] })] }) }));
262
- if (step === 'ai_tool') {
263
- return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [renderHeader(), _jsx(Text, { color: colors.text, children: "Which AI tool are you using?" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: aiToolOptions.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 }), installedTools.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" }) })] }));
193
+ if (step === 'platform') {
194
+ 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" }) })] }));
264
195
  }
265
196
  if (step === 'user_mention') {
266
197
  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" }) })] }));
267
198
  }
268
199
  // Confirm step
269
- 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: "AI Tool: " }), _jsx(Text, { color: colors.text, children: aiTool === AITool.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" }) })] }));
200
+ 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" }) })] }));
270
201
  }
271
202
  function TabBar({ tabs, activeTab }) {
272
203
  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))) }));
273
204
  }
274
- function SkillItem({ skill, isSelected, isActive, }) {
275
- const installed = isSkillInstalled(skill.name);
276
- const installedInfo = getInstalledSkill(skill.name);
277
- const updateStatus = getSkillUpdateStatus(skill.name);
278
- return (_jsx(Box, { paddingX: 1, backgroundColor: isActive ? colors.bgSelected : undefined, children: _jsxs(Text, { children: [_jsxs(Text, { color: colors.textDim, children: [isSelected ? '>' : ' ', " "] }), _jsx(Text, { color: isSelected || isActive ? colors.text : colors.textMuted, children: skill.name }), installed && installedInfo && _jsxs(Text, { color: colors.textDim, children: [" v", installedInfo.version] }), installed && _jsx(Text, { color: colors.success, children: " *" }), updateStatus.hasUpdate && _jsx(Text, { color: colors.primary, children: " \u2191" })] }) }));
205
+ function ToolItem({ tool, isSelected, isActive, }) {
206
+ const installed = isToolInstalled(tool.name);
207
+ const installedVersion = getInstalledToolVersion(tool.name);
208
+ const updateStatus = getToolUpdateStatus(tool.name);
209
+ 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" }), updateStatus.hasUpdate && _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" })] }) }));
279
210
  }
280
- function CommandItem({ command, isSelected, }) {
281
- const installed = isSkillInstalled(command.skillName);
282
- return (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: colors.textDim, children: [isSelected ? '>' : ' ', " "] }), _jsxs(Text, { color: isSelected ? colors.text : colors.textMuted, children: ["/", command.name] }), installed && _jsx(Text, { color: colors.success, children: " *" })] }) }));
283
- }
284
- function SkillDetails({ skill, isFocused, selectedAction, }) {
285
- if (!skill) {
286
- return (_jsx(Box, { paddingLeft: 2, flexGrow: 1, children: _jsx(Text, { color: colors.textDim, children: "Select a skill" }) }));
211
+ function ToolDetails({ tool, isFocused, selectedAction, }) {
212
+ if (!tool) {
213
+ return (_jsx(Box, { paddingLeft: 2, flexGrow: 1, children: _jsx(Text, { color: colors.textDim, children: "Select a tool" }) }));
287
214
  }
288
- const installed = isSkillInstalled(skill.name);
289
- const updateStatus = getSkillUpdateStatus(skill.name);
290
- const skillCommands = getCommandsFromSkills().filter((c) => c.skillName === skill.name);
215
+ const installed = isToolInstalled(tool.name);
216
+ const installedVersion = getInstalledToolVersion(tool.name);
217
+ const updateStatus = getToolUpdateStatus(tool.name);
291
218
  const actions = installed
292
219
  ? [
293
- { id: 'view', label: 'View', variant: 'default' },
220
+ { id: 'explore', label: 'Explore', variant: 'default' },
294
221
  ...(updateStatus.hasUpdate
295
222
  ? [{ id: 'update', label: `Update (${updateStatus.bundledVersion})`, variant: 'primary' }]
296
223
  : []),
@@ -298,59 +225,10 @@ function SkillDetails({ skill, isFocused, selectedAction, }) {
298
225
  { id: 'uninstall', label: 'Uninstall', variant: 'danger' },
299
226
  ]
300
227
  : [
301
- { id: 'view', label: 'View', variant: 'default' },
228
+ { id: 'explore', label: 'Explore', variant: 'default' },
302
229
  { id: 'install', label: 'Install', variant: 'primary' },
303
230
  ];
304
- return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, flexGrow: 1, children: [_jsx(Text, { color: colors.text, bold: true, children: skill.name }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, children: [skill.version, skill.status && ` · ${skill.status}`, installed && _jsx(Text, { color: colors.success, children: " \u00B7 installed" }), updateStatus.hasUpdate && (_jsxs(Text, { color: colors.primary, children: [" \u00B7 update available (", updateStatus.installedVersion, " \u2192 ", updateStatus.bundledVersion, ")"] }))] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: skill.description }) }), skillCommands.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, children: ["Commands: ", skillCommands.map((c) => `/${c.name}`).join(', ')] }) })), skill.examples && skill.examples.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: colors.textDim, children: ["Examples", skill.examples.length > 2 ? ` (showing 2 of ${skill.examples.length})` : '', ":"] }), skill.examples.slice(0, 2).map((example, i) => (_jsxs(Box, { flexDirection: "column", marginTop: i > 0 ? 1 : 0, children: [_jsxs(Text, { color: colors.textMuted, children: [" ", example.title] }), example.code
305
- .trim()
306
- .split('\n')
307
- .slice(0, 3)
308
- .map((line, j) => (_jsxs(Text, { color: colors.textDim, children: [' ', line] }, j)))] }, i)))] })), isFocused && (_jsx(Box, { flexDirection: "row", marginTop: 1, children: actions.map((action, index) => (_jsxs(Text, { backgroundColor: selectedAction === index
309
- ? action.variant === 'danger'
310
- ? colors.error
311
- : colors.primary
312
- : colors.bgSelected, color: selectedAction === index ? '#ffffff' : colors.textMuted, bold: selectedAction === index, children: [' ', action.label, ' '] }, action.id))) }))] }));
313
- }
314
- function CommandDetails({ command, isFocused, selectedAction, }) {
315
- if (!command) {
316
- return (_jsx(Box, { paddingLeft: 2, flexGrow: 1, children: _jsx(Text, { color: colors.textDim, children: "Select a command" }) }));
317
- }
318
- const skillInstalled = isSkillInstalled(command.skillName);
319
- // Commands belong to skills - only show View, install via skill
320
- const actions = [{ id: 'view', label: 'View', variant: 'default' }];
321
- return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, flexGrow: 1, children: [_jsxs(Text, { color: colors.text, bold: true, children: ["/", command.name] }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, children: ["from ", command.skillName, skillInstalled && _jsx(Text, { color: colors.success, children: " \u00B7 installed" })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: command.description }) }), command.usage.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: colors.textDim, children: "Usage:" }), command.usage.map((u, i) => (_jsxs(Text, { color: colors.textMuted, children: [' ', u] }, i)))] })), isFocused && (_jsx(Box, { flexDirection: "row", marginTop: 1, children: actions.map((action, index) => (_jsxs(Text, { backgroundColor: selectedAction === index
322
- ? action.variant === 'danger'
323
- ? colors.error
324
- : colors.primary
325
- : colors.bgSelected, color: selectedAction === index ? '#ffffff' : colors.textMuted, bold: selectedAction === index, children: [' ', action.label, ' '] }, action.id))) }))] }));
326
- }
327
- function AgentItem({ agent, isSelected }) {
328
- const statusDisplay = agent.status === 'alpha' ? '[alpha]' : agent.status === 'beta' ? '[beta]' : '';
329
- return (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: colors.textDim, children: [isSelected ? '>' : ' ', " "] }), _jsx(Text, { color: isSelected ? colors.text : colors.textMuted, children: agent.name }), _jsxs(Text, { color: colors.textDim, children: [" v", agent.version] }), statusDisplay && _jsxs(Text, { color: colors.textDim, children: [" ", statusDisplay] })] }) }));
330
- }
331
- function AgentDetails({ agent, isFocused, selectedAction, }) {
332
- if (!agent) {
333
- return (_jsx(Box, { paddingLeft: 2, flexGrow: 1, children: _jsx(Text, { color: colors.textDim, children: "Select an agent" }) }));
334
- }
335
- const installed = isAgentInstalled(agent.name);
336
- const bundledSkill = getAgentBundledSkill(agent.name);
337
- const skillInstalled = bundledSkill ? isSkillInstalled(bundledSkill) : false;
338
- const statusDisplay = agent.status === 'alpha' ? '[alpha]' : agent.status === 'beta' ? '[beta]' : '';
339
- const modeDisplay = agent.mode === 'primary' ? 'primary' : agent.mode === 'all' ? 'primary/subagent' : 'subagent';
340
- // If agent belongs to a skill, only show View (install via skill)
341
- // Otherwise show Install/Uninstall for standalone agents
342
- const actions = bundledSkill
343
- ? [{ id: 'view', label: 'View', variant: 'default' }]
344
- : installed
345
- ? [
346
- { id: 'view', label: 'View', variant: 'default' },
347
- { id: 'uninstall', label: 'Uninstall', variant: 'danger' },
348
- ]
349
- : [
350
- { id: 'view', label: 'View', variant: 'default' },
351
- { id: 'install', label: 'Install', variant: 'primary' },
352
- ];
353
- return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, flexGrow: 1, children: [_jsx(Text, { color: colors.text, bold: true, children: agent.name }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, children: ["v", agent.version, statusDisplay && _jsxs(Text, { children: [" \u00B7 ", statusDisplay] }), ' · ', modeDisplay, bundledSkill && _jsxs(Text, { color: colors.textMuted, children: [" \u00B7 from ", bundledSkill] }), skillInstalled && _jsx(Text, { color: colors.success, children: " \u00B7 installed" }), !bundledSkill && installed && _jsx(Text, { color: colors.success, children: " \u00B7 installed" })] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textMuted, children: agent.description }) }), agent.tools && agent.tools.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.textDim, children: ["Tools: ", agent.tools.join(', ')] }) })), agent.triggers && agent.triggers.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: colors.textDim, children: "Triggers:" }), agent.triggers.slice(0, 3).map((trigger, i) => (_jsxs(Text, { color: colors.textMuted, children: [' ', "\"", trigger, "\""] }, i)))] })), isFocused && (_jsx(Box, { flexDirection: "row", marginTop: 1, children: actions.map((action, index) => (_jsxs(Text, { backgroundColor: selectedAction === index
231
+ 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
354
232
  ? action.variant === 'danger'
355
233
  ? colors.error
356
234
  : colors.primary
@@ -444,10 +322,88 @@ function ReadmeViewer({ title, content, onClose, }) {
444
322
  const visibleLines = lines.slice(scrollOffset, scrollOffset + actualContentLines);
445
323
  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" }) })] }));
446
324
  }
325
+ function ToolExplorer({ tool, onViewSource, onClose }) {
326
+ const [selectedIndex, setSelectedIndex] = useState(0);
327
+ // Build list of all explorable items
328
+ const items = useMemo(() => {
329
+ const result = [];
330
+ const toolDir = getBundledToolsDir();
331
+ // Add skills
332
+ for (const skill of tool.includes.skills) {
333
+ result.push({
334
+ type: 'skill',
335
+ name: skill.name,
336
+ path: join(toolDir, tool.name, 'skills', skill.name, 'SKILL.md'),
337
+ });
338
+ }
339
+ // Add commands
340
+ for (const cmd of tool.includes.commands) {
341
+ result.push({
342
+ type: 'command',
343
+ name: `/${cmd}`,
344
+ path: join(toolDir, tool.name, 'commands', `${cmd}.md`),
345
+ });
346
+ }
347
+ // Add agents
348
+ for (const agent of tool.includes.agents) {
349
+ result.push({
350
+ type: 'agent',
351
+ name: agent,
352
+ path: join(toolDir, tool.name, 'agents', agent, 'AGENT.md'),
353
+ });
354
+ }
355
+ return result;
356
+ }, [tool]);
357
+ useInput((input, key) => {
358
+ if (key.escape) {
359
+ onClose();
360
+ return;
361
+ }
362
+ if (key.leftArrow || key.upArrow) {
363
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
364
+ }
365
+ if (key.rightArrow || key.downArrow) {
366
+ setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
367
+ }
368
+ if (key.return && items.length > 0) {
369
+ const item = items[selectedIndex];
370
+ if (existsSync(item.path)) {
371
+ const content = readFileSync(item.path, 'utf-8');
372
+ onViewSource(`${tool.name} / ${item.name}`, content);
373
+ }
374
+ else {
375
+ // Try YAML fallback for skills/agents
376
+ const yamlPath = item.path.replace('.md', '.yaml');
377
+ if (existsSync(yamlPath)) {
378
+ const content = readFileSync(yamlPath, 'utf-8');
379
+ onViewSource(`${tool.name} / ${item.name}`, content);
380
+ }
381
+ }
382
+ }
383
+ });
384
+ // Group items by type for display
385
+ const skillItems = items.filter(i => i.type === 'skill');
386
+ const commandItems = items.filter(i => i.type === 'command');
387
+ const agentItems = items.filter(i => i.type === 'agent');
388
+ const getItemIndex = (item) => items.indexOf(item);
389
+ 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) => {
390
+ const idx = getItemIndex(item);
391
+ const isSelected = selectedIndex === idx;
392
+ 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));
393
+ }) })] })), 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) => {
394
+ const idx = getItemIndex(item);
395
+ const isSelected = selectedIndex === idx;
396
+ 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));
397
+ }) })] })), 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) => {
398
+ const idx = getItemIndex(item);
399
+ const isSelected = selectedIndex === idx;
400
+ 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));
401
+ }) })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2190\u2192 navigate \u00B7 enter view source \u00B7 esc back" }) })] }));
402
+ }
447
403
  function SettingsDetails({ onEditSettings, isFocused, }) {
448
404
  const config = loadConfig();
449
405
  const outputOptions = getOutputOptions();
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: "AI Tool: " }), _jsx(Text, { color: colors.text, children: config.ai_tool === AITool.ClaudeCode ? 'Claude Code' : 'OpenCode' })] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "Your @mention: " }), _jsx(Text, { color: colors.text, children: config.user_mention })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "Config: ~/.droid/config.yaml" }) }), isFocused && (_jsx(Box, { marginTop: 2, children: _jsxs(Text, { backgroundColor: colors.primary, color: "#ffffff", bold: true, children: [' ', "Edit Settings", ' '] }) })), !isFocused && (_jsx(Box, { marginTop: 2, children: _jsx(Text, { color: colors.textDim, children: "press enter to edit" }) }))] }));
406
+ 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 })] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "Config: ~/.droid/config.yaml" }) }), isFocused && (_jsx(Box, { marginTop: 2, children: _jsxs(Text, { backgroundColor: colors.primary, color: "#ffffff", bold: true, children: [' ', "Edit Settings", ' '] }) })), !isFocused && (_jsx(Box, { marginTop: 2, children: _jsx(Text, { color: colors.textDim, children: "press enter to edit" }) }))] }));
451
407
  }
452
408
  const MAX_VISIBLE_CONFIG_ITEMS = 4; // Each config item takes ~3 lines
453
409
  function SkillConfigScreen({ skill, onComplete, onCancel }) {
@@ -593,12 +549,10 @@ function SkillConfigScreen({ skill, onComplete, onCancel }) {
593
549
  function App() {
594
550
  const { exit } = useApp();
595
551
  const tabs = [
596
- { id: 'skills', label: 'Skills' },
597
- { id: 'commands', label: 'Commands' },
598
- { id: 'agents', label: 'Agents' },
552
+ { id: 'tools', label: 'Tools' },
599
553
  { id: 'settings', label: 'Settings' },
600
554
  ];
601
- const [activeTab, setActiveTab] = useState('skills');
555
+ const [activeTab, setActiveTab] = useState('tools');
602
556
  const [tabIndex, setTabIndex] = useState(0);
603
557
  const [view, setView] = useState('welcome');
604
558
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -608,6 +562,7 @@ function App() {
608
562
  const [isEditingSettings, setIsEditingSettings] = useState(false);
609
563
  const [readmeContent, setReadmeContent] = useState(null);
610
564
  const [isUpdating, setIsUpdating] = useState(false);
565
+ const [previousView, setPreviousView] = useState('detail'); // Track where to return from readme
611
566
  // Check for updates once on mount
612
567
  const updateInfo = useMemo(() => getUpdateInfo(), []);
613
568
  const handleUpdate = () => {
@@ -634,9 +589,9 @@ function App() {
634
589
  }, 100);
635
590
  };
636
591
  const MAX_VISIBLE_ITEMS = 6;
592
+ const tools = getBundledTools();
593
+ // Keep skills for configure view (tools configure via their primary skill)
637
594
  const skills = getBundledSkills();
638
- const commands = getCommandsFromSkills();
639
- const agents = getBundledAgents();
640
595
  useInput((input, key) => {
641
596
  if (message)
642
597
  setMessage(null);
@@ -671,10 +626,7 @@ function App() {
671
626
  setSelectedAction(0);
672
627
  }
673
628
  if (key.downArrow) {
674
- const maxIndex = activeTab === 'skills' ? skills.length - 1
675
- : activeTab === 'commands' ? commands.length - 1
676
- : activeTab === 'agents' ? agents.length - 1
677
- : 0;
629
+ const maxIndex = activeTab === 'tools' ? tools.length - 1 : 0;
678
630
  setSelectedIndex((prev) => {
679
631
  const newIndex = Math.min(maxIndex, prev + 1);
680
632
  // Scroll down if needed
@@ -686,13 +638,7 @@ function App() {
686
638
  setSelectedAction(0);
687
639
  }
688
640
  if (key.return) {
689
- if (activeTab === 'skills' && skills.length > 0) {
690
- setView('detail');
691
- }
692
- else if (activeTab === 'commands' && commands.length > 0) {
693
- setView('detail');
694
- }
695
- else if (activeTab === 'agents' && agents.length > 0) {
641
+ if (activeTab === 'tools' && tools.length > 0) {
696
642
  setView('detail');
697
643
  }
698
644
  else if (activeTab === 'settings') {
@@ -711,51 +657,40 @@ function App() {
711
657
  }
712
658
  if (key.rightArrow) {
713
659
  let maxActions = 0;
714
- if (activeTab === 'skills') {
715
- const skill = skills[selectedIndex];
716
- const installed = skill ? isSkillInstalled(skill.name) : false;
717
- const hasUpdate = skill ? getSkillUpdateStatus(skill.name).hasUpdate : false;
718
- // View, [Update], Configure, Uninstall or View, Install
660
+ if (activeTab === 'tools') {
661
+ const tool = tools[selectedIndex];
662
+ const installed = tool ? isToolInstalled(tool.name) : false;
663
+ const hasUpdate = tool ? getToolUpdateStatus(tool.name).hasUpdate : false;
664
+ // Explore, [Update], Configure, Uninstall or Explore, Install
719
665
  maxActions = installed ? (hasUpdate ? 3 : 2) : 1;
720
666
  }
721
- else if (activeTab === 'agents') {
722
- maxActions = 1; // View, Install/Uninstall
723
- }
724
- else if (activeTab === 'commands') {
725
- const command = commands[selectedIndex];
726
- // If parent skill is installed, only View is available
727
- const skillInstalled = command ? isSkillInstalled(command.skillName) : false;
728
- maxActions = skillInstalled ? 0 : 1;
729
- }
730
667
  setSelectedAction((prev) => Math.min(maxActions, prev + 1));
731
668
  }
732
- if (key.return && activeTab === 'skills') {
733
- const skill = skills[selectedIndex];
734
- if (skill) {
735
- const installed = isSkillInstalled(skill.name);
736
- const skillUpdateStatus = getSkillUpdateStatus(skill.name);
737
- // Build actions array to match SkillDetails
738
- const skillActions = installed
669
+ if (key.return && activeTab === 'tools') {
670
+ const tool = tools[selectedIndex];
671
+ if (tool) {
672
+ const installed = isToolInstalled(tool.name);
673
+ const toolUpdateStatus = getToolUpdateStatus(tool.name);
674
+ // Build actions array to match ToolDetails
675
+ const toolActions = installed
739
676
  ? [
740
- { id: 'view' },
741
- ...(skillUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
677
+ { id: 'explore' },
678
+ ...(toolUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
742
679
  { id: 'configure' },
743
680
  { id: 'uninstall' },
744
681
  ]
745
- : [{ id: 'view' }, { id: 'install' }];
746
- const actionId = skillActions[selectedAction]?.id;
747
- if (actionId === 'view') {
748
- const skillMdPath = join(getBundledSkillsDir(), skill.name, 'SKILL.md');
749
- if (existsSync(skillMdPath)) {
750
- const content = readFileSync(skillMdPath, 'utf-8');
751
- setReadmeContent({ title: `${skill.name}/SKILL.md`, content });
752
- setView('readme');
753
- }
682
+ : [{ id: 'explore' }, { id: 'install' }];
683
+ const actionId = toolActions[selectedAction]?.id;
684
+ if (actionId === 'explore') {
685
+ // Open the tool explorer view
686
+ setView('explorer');
754
687
  }
755
688
  else if (actionId === 'update') {
756
- const result = updateSkill(skill.name);
689
+ // Update the primary skill of the tool
690
+ const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
691
+ const result = updateSkill(primarySkill);
757
692
  setMessage({
758
- text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
693
+ text: result.success ? `✓ Updated ${tool.name}` : `✗ ${result.message}`,
759
694
  type: result.success ? 'success' : 'error',
760
695
  });
761
696
  if (result.success) {
@@ -766,9 +701,11 @@ function App() {
766
701
  setView('configure');
767
702
  }
768
703
  else if (actionId === 'uninstall') {
769
- const result = uninstallSkill(skill.name);
704
+ // Uninstall the primary skill of the tool
705
+ const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
706
+ const result = uninstallSkill(primarySkill);
770
707
  setMessage({
771
- text: result.success ? `✓ Uninstalled ${skill.name}` : `✗ ${result.message}`,
708
+ text: result.success ? `✓ Uninstalled ${tool.name}` : `✗ ${result.message}`,
772
709
  type: result.success ? 'success' : 'error',
773
710
  });
774
711
  if (result.success) {
@@ -777,14 +714,16 @@ function App() {
777
714
  }
778
715
  }
779
716
  else if (actionId === 'install') {
780
- const result = installSkill(skill.name);
717
+ // Install the primary skill of the tool
718
+ const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
719
+ const result = installSkill(primarySkill);
781
720
  setMessage({
782
- text: result.success ? `✓ Installed ${skill.name}` : `✗ ${result.message}`,
721
+ text: result.success ? `✓ Installed ${tool.name}` : `✗ ${result.message}`,
783
722
  type: result.success ? 'success' : 'error',
784
723
  });
785
724
  if (result.success) {
786
- // Check if skill has required config (options without defaults)
787
- const configSchema = skill.config_schema || {};
725
+ // Check if tool has required config (options without defaults)
726
+ const configSchema = tool.config_schema || {};
788
727
  const hasRequiredConfig = Object.values(configSchema).some((option) => option.default === undefined);
789
728
  if (hasRequiredConfig) {
790
729
  // Go to configure view for required setup
@@ -798,67 +737,13 @@ function App() {
798
737
  }
799
738
  }
800
739
  }
801
- if (key.return && activeTab === 'agents') {
802
- const agent = agents[selectedIndex];
803
- if (agent) {
804
- const installed = isAgentInstalled(agent.name);
805
- const bundledSkill = getAgentBundledSkill(agent.name);
806
- if (selectedAction === 0) {
807
- // View
808
- const agentMdPath = findAgentSourcePath(agent.name);
809
- if (agentMdPath) {
810
- const content = readFileSync(agentMdPath, 'utf-8');
811
- setReadmeContent({ title: `${agent.name}/AGENT.md`, content });
812
- setView('readme');
813
- }
814
- }
815
- else if (!bundledSkill && installed && selectedAction === 1) {
816
- // Uninstall (only for standalone agents)
817
- const result = uninstallAgent(agent.name);
818
- setMessage({
819
- text: result.success ? `✓ Uninstalled ${agent.name}` : `✗ ${result.message}`,
820
- type: result.success ? 'success' : 'error',
821
- });
822
- if (result.success) {
823
- setView('menu');
824
- setSelectedAction(0);
825
- }
826
- }
827
- else if (!bundledSkill && !installed && selectedAction === 1) {
828
- // Install (only for standalone agents)
829
- const result = installAgent(agent.name);
830
- setMessage({
831
- text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
832
- type: result.success ? 'success' : 'error',
833
- });
834
- if (result.success) {
835
- setView('menu');
836
- setSelectedAction(0);
837
- }
838
- }
839
- }
840
- }
841
- if (key.return && activeTab === 'commands') {
842
- const command = commands[selectedIndex];
843
- if (command) {
844
- // Command file: extract part after skill name (e.g., "comments check" → "check.md")
845
- const cmdPart = command.name.startsWith(command.skillName + ' ')
846
- ? command.name.slice(command.skillName.length + 1)
847
- : command.name;
848
- const commandMdPath = join(getBundledSkillsDir(), command.skillName, 'commands', `${cmdPart}.md`);
849
- // Only View action available - commands install via skills
850
- if (selectedAction === 0 && existsSync(commandMdPath)) {
851
- const content = readFileSync(commandMdPath, 'utf-8');
852
- setReadmeContent({ title: `/${command.name}`, content });
853
- setView('readme');
854
- }
855
- }
856
- }
857
740
  }
858
- }, { isActive: view !== 'welcome' && view !== 'setup' && view !== 'configure' });
859
- const selectedSkill = activeTab === 'skills' ? skills[selectedIndex] ?? null : null;
860
- const selectedCommand = activeTab === 'commands' ? commands[selectedIndex] ?? null : null;
861
- const selectedAgent = activeTab === 'agents' ? agents[selectedIndex] ?? null : null;
741
+ }, { isActive: view !== 'welcome' && view !== 'setup' && view !== 'configure' && view !== 'explorer' });
742
+ const selectedTool = activeTab === 'tools' ? tools[selectedIndex] ?? null : null;
743
+ // For configure view, we need the matching skill manifest
744
+ const selectedSkillForConfig = selectedTool
745
+ ? skills.find(s => s.name === (selectedTool.includes.skills.find(sk => sk.required)?.name || selectedTool.name))
746
+ : null;
862
747
  if (view === 'welcome') {
863
748
  return (_jsx(WelcomeScreen, { updateInfo: updateInfo, isUpdating: isUpdating, onUpdate: handleUpdate, onExit: exit, onContinue: () => {
864
749
  // If no config exists, show setup first
@@ -882,18 +767,27 @@ function App() {
882
767
  if (view === 'readme' && readmeContent) {
883
768
  return (_jsx(ReadmeViewer, { title: readmeContent.title, content: readmeContent.content, onClose: () => {
884
769
  setReadmeContent(null);
770
+ setView(previousView);
771
+ } }));
772
+ }
773
+ if (view === 'explorer' && selectedTool) {
774
+ return (_jsx(ToolExplorer, { tool: selectedTool, onViewSource: (title, content) => {
775
+ setPreviousView('explorer');
776
+ setReadmeContent({ title, content });
777
+ setView('readme');
778
+ }, onClose: () => {
885
779
  setView('detail');
886
780
  } }));
887
781
  }
888
- if (view === 'configure' && selectedSkill) {
889
- return (_jsx(SkillConfigScreen, { skill: selectedSkill, onComplete: () => {
890
- setMessage({ text: `✓ Configuration saved for ${selectedSkill.name}`, type: 'success' });
782
+ if (view === 'configure' && selectedSkillForConfig) {
783
+ return (_jsx(SkillConfigScreen, { skill: selectedSkillForConfig, onComplete: () => {
784
+ setMessage({ text: `✓ Configuration saved for ${selectedTool?.name || selectedSkillForConfig.name}`, type: 'success' });
891
785
  setView('detail');
892
786
  }, onCancel: () => {
893
787
  setView('detail');
894
788
  } }));
895
789
  }
896
- 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, marginTop: 1, children: _jsx(TabBar, { tabs: tabs, activeTab: activeTab }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [activeTab === 'skills' && (_jsxs(_Fragment, { children: [scrollOffset > 0 && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, children: ["\u2191 ", scrollOffset, " more"] }) })), skills.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS).map((skill, index) => (_jsx(SkillItem, { skill: skill, isSelected: scrollOffset + index === selectedIndex, isActive: scrollOffset + index === selectedIndex && view === 'detail' }, skill.name))), scrollOffset + MAX_VISIBLE_ITEMS < skills.length && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, children: ["\u2193 ", skills.length - scrollOffset - MAX_VISIBLE_ITEMS, " more"] }) })), skills.length > MAX_VISIBLE_ITEMS && (_jsx(Box, { paddingX: 1, marginTop: 1, children: _jsxs(Text, { color: colors.textDim, children: [skills.length, " skills total"] }) }))] })), activeTab === 'commands' && (_jsxs(_Fragment, { children: [scrollOffset > 0 && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, children: ["\u2191 ", scrollOffset, " more"] }) })), commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS).map((cmd, index) => (_jsx(CommandItem, { command: cmd, isSelected: scrollOffset + index === selectedIndex }, cmd.name))), scrollOffset + MAX_VISIBLE_ITEMS < commands.length && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, children: ["\u2193 ", commands.length - scrollOffset - MAX_VISIBLE_ITEMS, " more"] }) }))] })), activeTab === 'agents' && (_jsxs(_Fragment, { children: [scrollOffset > 0 && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, children: ["\u2191 ", scrollOffset, " more"] }) })), agents.length === 0 ? (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: colors.textDim, children: "No agents available" }) })) : (agents.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS).map((agent, index) => (_jsx(AgentItem, { agent: agent, isSelected: scrollOffset + index === selectedIndex }, agent.name)))), scrollOffset + MAX_VISIBLE_ITEMS < agents.length && (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: colors.textDim, children: ["\u2193 ", agents.length - scrollOffset - MAX_VISIBLE_ITEMS, " more"] }) }))] })), 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 === 'skills' && (_jsx(SkillDetails, { skill: selectedSkill, isFocused: view === 'detail', selectedAction: selectedAction })), activeTab === 'commands' && (_jsx(CommandDetails, { command: selectedCommand, isFocused: view === 'detail', selectedAction: selectedAction })), activeTab === 'settings' && (_jsx(SettingsDetails, { onEditSettings: () => setView('setup'), isFocused: false })), activeTab === 'agents' && (_jsx(AgentDetails, { agent: selectedAgent, isFocused: view === 'detail', selectedAction: selectedAction }))] }));
790
+ 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' }, 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: () => setView('setup'), isFocused: false }))] }));
897
791
  }
898
792
  export async function tuiCommand() {
899
793
  // Enter alternate screen (fullscreen mode)