@orderful/droid 0.10.4 → 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 +21 -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 +127 -106
  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/{skills → tools}/code-review/commands/code-review.md +23 -1
  56. package/dist/tools/comments/TOOL.yaml +27 -0
  57. package/dist/tools/project/TOOL.yaml +26 -0
  58. package/package.json +2 -2
  59. package/src/bin/droid.ts +9 -9
  60. package/src/commands/install.ts +24 -23
  61. package/src/commands/setup.test.ts +2 -2
  62. package/src/commands/setup.ts +33 -29
  63. package/src/commands/skills.ts +63 -64
  64. package/src/commands/tui.tsx +432 -578
  65. package/src/commands/uninstall.ts +17 -6
  66. package/src/commands/update.ts +10 -10
  67. package/src/lib/agents.ts +58 -58
  68. package/src/lib/config.test.ts +0 -10
  69. package/src/lib/config.ts +47 -5
  70. package/src/lib/platforms.ts +59 -0
  71. package/src/lib/skills.test.ts +53 -28
  72. package/src/lib/skills.ts +136 -109
  73. package/src/lib/tools.ts +140 -0
  74. package/src/lib/types.test.ts +15 -7
  75. package/src/lib/types.ts +63 -2
  76. package/src/tools/brain/TOOL.yaml +27 -0
  77. package/src/tools/coach/TOOL.yaml +21 -0
  78. package/src/tools/code-review/TOOL.yaml +18 -0
  79. package/src/{skills → tools}/code-review/commands/code-review.md +23 -1
  80. package/src/tools/comments/TOOL.yaml +27 -0
  81. package/src/tools/project/TOOL.yaml +26 -0
  82. package/dist/agents/README.md +0 -137
  83. package/src/agents/README.md +0 -137
  84. /package/dist/{skills → tools}/README.md +0 -0
  85. /package/dist/{skills → tools}/brain/commands/README.md +0 -0
  86. /package/dist/{skills → tools}/brain/commands/brain.md +0 -0
  87. /package/dist/{skills → tools}/brain/commands/scratchpad.md +0 -0
  88. /package/dist/{skills → tools/brain/skills}/brain/SKILL.md +0 -0
  89. /package/dist/{skills → tools/brain/skills}/brain/SKILL.yaml +0 -0
  90. /package/dist/{skills → tools/brain/skills}/brain/references/metadata.md +0 -0
  91. /package/dist/{skills → tools/brain/skills}/brain/references/naming.md +0 -0
  92. /package/dist/{skills → tools/brain/skills}/brain/references/templates.md +0 -0
  93. /package/dist/{skills → tools/brain/skills}/brain/references/workflows.md +0 -0
  94. /package/dist/{skills → tools/brain/skills}/brain-obsidian/SKILL.md +0 -0
  95. /package/dist/{skills → tools/brain/skills}/brain-obsidian/SKILL.yaml +0 -0
  96. /package/dist/{skills → tools/brain/skills}/brain-obsidian/references/templates.md +0 -0
  97. /package/dist/{skills → tools/brain/skills}/brain-obsidian/references/workflows.md +0 -0
  98. /package/dist/{skills → tools}/coach/commands/README.md +0 -0
  99. /package/dist/{skills → tools}/coach/commands/coach.md +0 -0
  100. /package/dist/{skills → tools/coach/skills}/coach/SKILL.md +0 -0
  101. /package/dist/{skills → tools/coach/skills}/coach/SKILL.yaml +0 -0
  102. /package/dist/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.md +0 -0
  103. /package/dist/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -0
  104. /package/dist/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.md +0 -0
  105. /package/dist/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -0
  106. /package/dist/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.md +0 -0
  107. /package/dist/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -0
  108. /package/dist/{skills → tools}/code-review/agents/type-reviewer/AGENT.md +0 -0
  109. /package/dist/{skills → tools}/code-review/agents/type-reviewer/AGENT.yaml +0 -0
  110. /package/dist/{skills → tools/code-review/skills}/code-review/SKILL.md +0 -0
  111. /package/dist/{skills → tools/code-review/skills}/code-review/SKILL.yaml +0 -0
  112. /package/dist/{skills → tools}/comments/commands/README.md +0 -0
  113. /package/dist/{skills → tools}/comments/commands/comments.md +0 -0
  114. /package/dist/{skills → tools/comments/skills}/comments/SKILL.md +0 -0
  115. /package/dist/{skills → tools/comments/skills}/comments/SKILL.yaml +0 -0
  116. /package/dist/{skills → tools}/project/commands/README.md +0 -0
  117. /package/dist/{skills → tools}/project/commands/project.md +0 -0
  118. /package/dist/{skills → tools/project/skills}/project/SKILL.md +0 -0
  119. /package/dist/{skills → tools/project/skills}/project/SKILL.yaml +0 -0
  120. /package/dist/{skills → tools/project/skills}/project/references/changelog.md +0 -0
  121. /package/dist/{skills → tools/project/skills}/project/references/creating.md +0 -0
  122. /package/dist/{skills → tools/project/skills}/project/references/loading.md +0 -0
  123. /package/dist/{skills → tools/project/skills}/project/references/templates.md +0 -0
  124. /package/dist/{skills → tools/project/skills}/project/references/updating.md +0 -0
  125. /package/dist/{skills → tools/project/skills}/project/references/versioning.md +0 -0
  126. /package/src/{skills → tools}/README.md +0 -0
  127. /package/src/{skills → tools}/brain/commands/README.md +0 -0
  128. /package/src/{skills → tools}/brain/commands/brain.md +0 -0
  129. /package/src/{skills → tools}/brain/commands/scratchpad.md +0 -0
  130. /package/src/{skills → tools/brain/skills}/brain/SKILL.md +0 -0
  131. /package/src/{skills → tools/brain/skills}/brain/SKILL.yaml +0 -0
  132. /package/src/{skills → tools/brain/skills}/brain/references/metadata.md +0 -0
  133. /package/src/{skills → tools/brain/skills}/brain/references/naming.md +0 -0
  134. /package/src/{skills → tools/brain/skills}/brain/references/templates.md +0 -0
  135. /package/src/{skills → tools/brain/skills}/brain/references/workflows.md +0 -0
  136. /package/src/{skills → tools/brain/skills}/brain-obsidian/SKILL.md +0 -0
  137. /package/src/{skills → tools/brain/skills}/brain-obsidian/SKILL.yaml +0 -0
  138. /package/src/{skills → tools/brain/skills}/brain-obsidian/references/templates.md +0 -0
  139. /package/src/{skills → tools/brain/skills}/brain-obsidian/references/workflows.md +0 -0
  140. /package/src/{skills → tools}/coach/commands/README.md +0 -0
  141. /package/src/{skills → tools}/coach/commands/coach.md +0 -0
  142. /package/src/{skills → tools/coach/skills}/coach/SKILL.md +0 -0
  143. /package/src/{skills → tools/coach/skills}/coach/SKILL.yaml +0 -0
  144. /package/src/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.md +0 -0
  145. /package/src/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -0
  146. /package/src/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.md +0 -0
  147. /package/src/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -0
  148. /package/src/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.md +0 -0
  149. /package/src/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -0
  150. /package/src/{skills → tools}/code-review/agents/type-reviewer/AGENT.md +0 -0
  151. /package/src/{skills → tools}/code-review/agents/type-reviewer/AGENT.yaml +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
@@ -2,28 +2,27 @@ import { render, Box, Text, useInput, useApp } from 'ink';
2
2
  import TextInput from 'ink-text-input';
3
3
  import { useState, useMemo } from 'react';
4
4
  import { execSync } from 'child_process';
5
- import { existsSync, readdirSync, readFileSync } from 'fs';
5
+ import { existsSync, readFileSync } from 'fs';
6
6
  import { join } from 'path';
7
7
  import {
8
8
  getBundledSkills,
9
- getBundledSkillsDir,
10
9
  isSkillInstalled,
11
- getInstalledSkill,
12
10
  installSkill,
13
11
  uninstallSkill,
14
12
  updateSkill,
15
13
  getSkillUpdateStatus,
16
14
  } from '../lib/skills.js';
17
- import { getBundledAgents, getBundledAgentsDir, isAgentInstalled, installAgent, uninstallAgent, type AgentManifest } from '../lib/agents.js';
15
+ import { getBundledTools, getBundledToolsDir, isToolInstalled, getToolUpdateStatus, getInstalledToolVersion } from '../lib/tools.js';
18
16
  import { configExists, loadConfig, saveConfig, loadSkillOverrides, saveSkillOverrides } from '../lib/config.js';
19
- import { configureAIToolPermissions } from './setup.js';
20
- import { AITool, BuiltInOutput, ConfigOptionType, type DroidConfig, type OutputPreference, type SkillManifest, type ConfigOption, type SkillOverrides } from '../lib/types.js';
17
+ import { configurePlatformPermissions } from './setup.js';
18
+ import { Platform, BuiltInOutput, ConfigOptionType, type DroidConfig, type OutputPreference, type SkillManifest, type ConfigOption, type SkillOverrides, type ToolManifest } from '../lib/types.js';
21
19
  import { getVersion, getUpdateInfo, runUpdate, type UpdateInfo } from '../lib/version.js';
22
20
  import { getRandomQuote } from '../lib/quotes.js';
23
21
 
24
- type Tab = 'skills' | 'commands' | 'agents' | 'settings';
25
- type View = 'welcome' | 'setup' | 'menu' | 'detail' | 'configure' | 'readme';
26
- type SetupStep = 'ai_tool' | 'user_mention' | 'confirm';
22
+ type Tab = 'tools' | 'settings';
23
+ type View = 'welcome' | 'setup' | 'menu' | 'detail' | 'configure' | 'readme' | 'explorer';
24
+ type SetupStep = 'platform' | 'user_mention' | 'confirm';
25
+ type ComponentType = 'skill' | 'command' | 'agent';
27
26
 
28
27
  const colors = {
29
28
  primary: '#6366f1',
@@ -34,95 +33,116 @@ const colors = {
34
33
  textDim: '#6a6a6a',
35
34
  success: '#4ade80',
36
35
  error: '#f87171',
36
+ // Component type badges
37
+ skill: '#ec4899', // pink/magenta
38
+ command: '#22d3ee', // cyan
39
+ agent: '#fbbf24', // yellow/amber
37
40
  };
38
41
 
39
- interface Command {
40
- name: string;
41
- description: string;
42
- skillName: string;
43
- usage: string[];
44
- }
45
-
46
- function getCommandsFromSkills(): Command[] {
47
- const skillsDir = getBundledSkillsDir();
48
- const commands: Command[] = [];
49
-
50
- if (!existsSync(skillsDir)) return commands;
51
-
52
- const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
53
- .filter((d) => d.isDirectory())
54
- .map((d) => d.name);
42
+ function Badge({
43
+ type,
44
+ label,
45
+ isSelected = false,
46
+ dimmed = false,
47
+ }: {
48
+ type: ComponentType;
49
+ label?: string;
50
+ isSelected?: boolean;
51
+ dimmed?: boolean;
52
+ }) {
53
+ const color = colors[type];
54
+ const displayLabel = label || type.charAt(0).toUpperCase() + type.slice(1);
55
55
 
56
- for (const skillName of skillDirs) {
57
- const commandsDir = join(skillsDir, skillName, 'commands');
58
- if (!existsSync(commandsDir)) continue;
56
+ if (dimmed) {
57
+ return (
58
+ <Text color={colors.textDim}>{displayLabel}</Text>
59
+ );
60
+ }
59
61
 
60
- const cmdFiles = readdirSync(commandsDir).filter((f) => f.endsWith('.md'));
61
- for (const file of cmdFiles) {
62
- const content = readFileSync(join(commandsDir, file), 'utf-8');
63
- const lines = content.split('\n');
62
+ return (
63
+ <Text
64
+ backgroundColor={isSelected ? color : undefined}
65
+ color={isSelected ? '#000000' : color}
66
+ bold={isSelected}
67
+ >
68
+ {isSelected ? ` ${displayLabel} ` : displayLabel}
69
+ </Text>
70
+ );
71
+ }
64
72
 
65
- // Skip YAML frontmatter if present
66
- let startLine = 0;
67
- if (lines[0]?.trim() === '---') {
68
- for (let i = 1; i < lines.length; i++) {
69
- if (lines[i]?.trim() === '---') {
70
- startLine = i + 1;
71
- break;
72
- }
73
- }
74
- }
73
+ function ComponentBadges({ tool, compact = false }: { tool: ToolManifest; compact?: boolean }) {
74
+ const hasSkills = tool.includes.skills.length > 0;
75
+ const hasCommands = tool.includes.commands.length > 0;
76
+ const hasAgents = tool.includes.agents.length > 0;
75
77
 
76
- // Find first H1 header
77
- let headerMatch: RegExpMatchArray | null = null;
78
- for (let i = startLine; i < lines.length; i++) {
79
- headerMatch = lines[i]?.match(/^#\s+\/(\S+)\s*-?\s*(.*)/);
80
- if (headerMatch) break;
81
- }
82
- if (!headerMatch) continue;
83
-
84
- const usage: string[] = [];
85
- let inUsage = false;
86
- for (const line of lines) {
87
- if (line.startsWith('## Usage')) inUsage = true;
88
- else if (line.startsWith('## ') && inUsage) break;
89
- else if (inUsage && line.startsWith('/')) usage.push(line.trim());
90
- }
78
+ if (compact) {
79
+ // Show colored squares for list view (single char with spacing)
80
+ const parts: string[] = [];
81
+ if (hasSkills) parts.push('skill');
82
+ if (hasCommands) parts.push('command');
83
+ if (hasAgents) parts.push('agent');
91
84
 
92
- commands.push({
93
- name: headerMatch[1],
94
- description: headerMatch[2] || '',
95
- skillName,
96
- usage,
97
- });
98
- }
85
+ return (
86
+ <Box flexDirection="row">
87
+ {parts.map((type, i) => (
88
+ <Text key={type}>
89
+ <Text backgroundColor={colors[type as ComponentType]}> </Text>
90
+ {i < parts.length - 1 && ' '}
91
+ </Text>
92
+ ))}
93
+ </Box>
94
+ );
99
95
  }
100
96
 
101
- return commands;
97
+ return (
98
+ <Box flexDirection="row">
99
+ {hasSkills && (
100
+ <Box marginRight={1}>
101
+ <Text backgroundColor={colors.skill} color="#000000" bold>
102
+ {` ${tool.includes.skills.length} skill${tool.includes.skills.length > 1 ? 's' : ''} `}
103
+ </Text>
104
+ </Box>
105
+ )}
106
+ {hasCommands && (
107
+ <Box marginRight={1}>
108
+ <Text backgroundColor={colors.command} color="#000000" bold>
109
+ {` ${tool.includes.commands.length} cmd${tool.includes.commands.length > 1 ? 's' : ''} `}
110
+ </Text>
111
+ </Box>
112
+ )}
113
+ {hasAgents && (
114
+ <Box marginRight={1}>
115
+ <Text backgroundColor={colors.agent} color="#000000" bold>
116
+ {` ${tool.includes.agents.length} agent${tool.includes.agents.length > 1 ? 's' : ''} `}
117
+ </Text>
118
+ </Box>
119
+ )}
120
+ </Box>
121
+ );
102
122
  }
103
123
 
104
- function detectInstalledAITools(): Set<AITool> {
105
- const installed = new Set<AITool>();
124
+ function detectInstalledPlatforms(): Set<Platform> {
125
+ const installed = new Set<Platform>();
106
126
  try {
107
127
  execSync('claude --version', { stdio: 'ignore' });
108
- installed.add(AITool.ClaudeCode);
128
+ installed.add(Platform.ClaudeCode);
109
129
  } catch {
110
130
  // Claude Code not found
111
131
  }
112
132
  try {
113
133
  execSync('opencode --version', { stdio: 'ignore' });
114
- installed.add(AITool.OpenCode);
134
+ installed.add(Platform.OpenCode);
115
135
  } catch {
116
136
  // OpenCode not found
117
137
  }
118
138
  return installed;
119
139
  }
120
140
 
121
- function getDefaultAITool(): AITool {
122
- const installed = detectInstalledAITools();
123
- if (installed.has(AITool.ClaudeCode)) return AITool.ClaudeCode;
124
- if (installed.has(AITool.OpenCode)) return AITool.OpenCode;
125
- return AITool.ClaudeCode;
141
+ function getDefaultPlatform(): Platform {
142
+ const installed = detectInstalledPlatforms();
143
+ if (installed.has(Platform.ClaudeCode)) return Platform.ClaudeCode;
144
+ if (installed.has(Platform.OpenCode)) return Platform.OpenCode;
145
+ return Platform.ClaudeCode;
126
146
  }
127
147
 
128
148
  function getOutputOptions(): Array<{ label: string; value: OutputPreference }> {
@@ -139,56 +159,6 @@ function getOutputOptions(): Array<{ label: string; value: OutputPreference }> {
139
159
  return options;
140
160
  }
141
161
 
142
- /**
143
- * Check if an agent belongs to a skill bundle (whether installed or not)
144
- * Returns the skill name if so, null otherwise
145
- */
146
- function getAgentBundledSkill(agentName: string): string | null {
147
- const skillsDir = getBundledSkillsDir();
148
- if (!existsSync(skillsDir)) return null;
149
-
150
- const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
151
- .filter((d) => d.isDirectory())
152
- .map((d) => d.name);
153
-
154
- for (const skillName of skillDirs) {
155
- const agentDir = join(skillsDir, skillName, 'agents', agentName);
156
- if (existsSync(agentDir)) {
157
- return skillName;
158
- }
159
- }
160
- return null;
161
- }
162
-
163
- /**
164
- * Find the source path for an agent's AGENT.md
165
- * Checks both standalone agents dir and skill-bundled agents
166
- */
167
- function findAgentSourcePath(agentName: string): string | null {
168
- // Check standalone agents first
169
- const standalonePath = join(getBundledAgentsDir(), agentName, 'AGENT.md');
170
- if (existsSync(standalonePath)) {
171
- return standalonePath;
172
- }
173
-
174
- // Check skill-bundled agents
175
- const skillsDir = getBundledSkillsDir();
176
- if (existsSync(skillsDir)) {
177
- const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
178
- .filter((d) => d.isDirectory())
179
- .map((d) => d.name);
180
-
181
- for (const skillName of skillDirs) {
182
- const skillAgentPath = join(skillsDir, skillName, 'agents', agentName, 'AGENT.md');
183
- if (existsSync(skillAgentPath)) {
184
- return skillAgentPath;
185
- }
186
- }
187
- }
188
-
189
- return null;
190
- }
191
-
192
162
  interface WelcomeScreenProps {
193
163
  onContinue: () => void;
194
164
  onUpdate: () => void;
@@ -326,22 +296,22 @@ interface SetupScreenProps {
326
296
  }
327
297
 
328
298
  function SetupScreen({ onComplete, onSkip, initialConfig }: SetupScreenProps) {
329
- const [step, setStep] = useState<SetupStep>('ai_tool');
330
- const [aiTool, setAITool] = useState<AITool>(
331
- initialConfig?.ai_tool || getDefaultAITool()
299
+ const [step, setStep] = useState<SetupStep>('platform');
300
+ const [platform, setPlatform] = useState<Platform>(
301
+ initialConfig?.platform || getDefaultPlatform()
332
302
  );
333
303
  const [userMention, setUserMention] = useState(initialConfig?.user_mention || '@user');
334
304
  const [selectedIndex, setSelectedIndex] = useState(0);
335
305
  const [error, setError] = useState<string | null>(null);
336
306
 
337
307
  // Cache detection results once on mount (avoids re-running execSync on every render)
338
- const installedTools = useMemo(() => detectInstalledAITools(), []);
339
- const aiToolOptions = useMemo(() => [
340
- { label: 'Claude Code', value: AITool.ClaudeCode },
341
- { label: 'OpenCode', value: AITool.OpenCode },
308
+ const installedPlatforms = useMemo(() => detectInstalledPlatforms(), []);
309
+ const platformOptions = useMemo(() => [
310
+ { label: 'Claude Code', value: Platform.ClaudeCode },
311
+ { label: 'OpenCode', value: Platform.OpenCode },
342
312
  ], []);
343
313
 
344
- const steps: SetupStep[] = ['ai_tool', 'user_mention', 'confirm'];
314
+ const steps: SetupStep[] = ['platform', 'user_mention', 'confirm'];
345
315
  const stepIndex = steps.indexOf(step);
346
316
  const totalSteps = steps.length - 1; // Don't count confirm as a step
347
317
 
@@ -356,7 +326,7 @@ function SetupScreen({ onComplete, onSkip, initialConfig }: SetupScreenProps) {
356
326
  // Handle escape during text input (only intercept escape, nothing else)
357
327
  useInput((input, key) => {
358
328
  if (key.escape) {
359
- setStep('ai_tool');
329
+ setStep('platform');
360
330
  setSelectedIndex(0);
361
331
  }
362
332
  }, { isActive: step === 'user_mention' });
@@ -364,7 +334,7 @@ function SetupScreen({ onComplete, onSkip, initialConfig }: SetupScreenProps) {
364
334
  // Handle all input for non-text-input steps
365
335
  useInput((input, key) => {
366
336
  if (key.escape) {
367
- if (step === 'ai_tool') {
337
+ if (step === 'platform') {
368
338
  onSkip();
369
339
  } else if (step === 'confirm') {
370
340
  setStep('user_mention');
@@ -372,23 +342,24 @@ function SetupScreen({ onComplete, onSkip, initialConfig }: SetupScreenProps) {
372
342
  return;
373
343
  }
374
344
 
375
- if (step === 'ai_tool') {
345
+ if (step === 'platform') {
376
346
  if (key.upArrow) setSelectedIndex((prev) => Math.max(0, prev - 1));
377
- if (key.downArrow) setSelectedIndex((prev) => Math.min(aiToolOptions.length - 1, prev + 1));
347
+ if (key.downArrow) setSelectedIndex((prev) => Math.min(platformOptions.length - 1, prev + 1));
378
348
  if (key.return) {
379
- setAITool(aiToolOptions[selectedIndex].value);
349
+ setPlatform(platformOptions[selectedIndex].value);
380
350
  setStep('user_mention');
381
351
  }
382
352
  } else if (step === 'confirm') {
383
353
  if (key.return) {
354
+ const existingConfig = loadConfig();
384
355
  const config: DroidConfig = {
385
- ...loadConfig(),
386
- ai_tool: aiTool,
356
+ ...existingConfig,
357
+ platform: platform,
387
358
  user_mention: userMention,
388
359
  output_preference: BuiltInOutput.Terminal, // Default to terminal
389
360
  };
390
361
  saveConfig(config);
391
- configureAIToolPermissions(aiTool);
362
+ configurePlatformPermissions(platform);
392
363
  onComplete();
393
364
  }
394
365
  }
@@ -408,17 +379,17 @@ function SetupScreen({ onComplete, onSkip, initialConfig }: SetupScreenProps) {
408
379
  </Box>
409
380
  );
410
381
 
411
- if (step === 'ai_tool') {
382
+ if (step === 'platform') {
412
383
  return (
413
384
  <Box flexDirection="column" padding={1}>
414
385
  {renderHeader()}
415
- <Text color={colors.text}>Which AI tool are you using?</Text>
386
+ <Text color={colors.text}>Which platform are you using?</Text>
416
387
  <Box flexDirection="column" marginTop={1}>
417
- {aiToolOptions.map((option, index) => (
388
+ {platformOptions.map((option, index) => (
418
389
  <Text key={option.value}>
419
390
  <Text color={colors.textDim}>{index === selectedIndex ? '>' : ' '} </Text>
420
391
  <Text color={index === selectedIndex ? colors.text : colors.textMuted}>{option.label}</Text>
421
- {installedTools.has(option.value) && <Text color={colors.success}> (detected)</Text>}
392
+ {installedPlatforms.has(option.value) && <Text color={colors.success}> (detected)</Text>}
422
393
  </Text>
423
394
  ))}
424
395
  </Box>
@@ -462,8 +433,8 @@ function SetupScreen({ onComplete, onSkip, initialConfig }: SetupScreenProps) {
462
433
  <Text color={colors.text} bold>Review your settings</Text>
463
434
  <Box flexDirection="column" marginTop={1}>
464
435
  <Text>
465
- <Text color={colors.textDim}>AI Tool: </Text>
466
- <Text color={colors.text}>{aiTool === AITool.ClaudeCode ? 'Claude Code' : 'OpenCode'}</Text>
436
+ <Text color={colors.textDim}>Platform: </Text>
437
+ <Text color={colors.text}>{platform === Platform.ClaudeCode ? 'Claude Code' : 'OpenCode'}</Text>
467
438
  </Text>
468
439
  <Text>
469
440
  <Text color={colors.textDim}>Your @mention: </Text>
@@ -500,76 +471,60 @@ function TabBar({ tabs, activeTab }: { tabs: { id: Tab; label: string }[]; activ
500
471
  );
501
472
  }
502
473
 
503
- function SkillItem({
504
- skill,
474
+ function ToolItem({
475
+ tool,
505
476
  isSelected,
506
477
  isActive,
507
478
  }: {
508
- skill: SkillManifest;
479
+ tool: ToolManifest;
509
480
  isSelected: boolean;
510
481
  isActive: boolean;
511
482
  }) {
512
- const installed = isSkillInstalled(skill.name);
513
- const installedInfo = getInstalledSkill(skill.name);
514
- const updateStatus = getSkillUpdateStatus(skill.name);
483
+ const installed = isToolInstalled(tool.name);
484
+ const installedVersion = getInstalledToolVersion(tool.name);
485
+ const updateStatus = getToolUpdateStatus(tool.name);
515
486
 
516
487
  return (
517
488
  <Box paddingX={1} backgroundColor={isActive ? colors.bgSelected : undefined}>
518
- <Text>
489
+ <Text wrap="truncate">
519
490
  <Text color={colors.textDim}>{isSelected ? '>' : ' '} </Text>
520
- <Text color={isSelected || isActive ? colors.text : colors.textMuted}>{skill.name}</Text>
521
- {installed && installedInfo && <Text color={colors.textDim}> v{installedInfo.version}</Text>}
522
- {installed && <Text color={colors.success}> *</Text>}
491
+ <Text color={isSelected || isActive ? colors.text : colors.textMuted}>{tool.name}</Text>
492
+ {installed && installedVersion && <Text color={colors.textDim}> v{installedVersion}</Text>}
493
+ {installed && <Text color={colors.success}> ✓</Text>}
523
494
  {updateStatus.hasUpdate && <Text color={colors.primary}> ↑</Text>}
495
+ <Text> </Text>
496
+ {tool.includes.skills.length > 0 && <Text color={colors.skill}>● </Text>}
497
+ {tool.includes.commands.length > 0 && <Text color={colors.command}>● </Text>}
498
+ {tool.includes.agents.length > 0 && <Text color={colors.agent}>●</Text>}
524
499
  </Text>
525
500
  </Box>
526
501
  );
527
502
  }
528
503
 
529
- function CommandItem({
530
- command,
531
- isSelected,
532
- }: {
533
- command: Command;
534
- isSelected: boolean;
535
- }) {
536
- const installed = isSkillInstalled(command.skillName);
537
-
538
- return (
539
- <Box paddingX={1}>
540
- <Text>
541
- <Text color={colors.textDim}>{isSelected ? '>' : ' '} </Text>
542
- <Text color={isSelected ? colors.text : colors.textMuted}>/{command.name}</Text>
543
- {installed && <Text color={colors.success}> *</Text>}
544
- </Text>
545
- </Box>
546
- );
547
- }
548
-
549
- function SkillDetails({
550
- skill,
504
+ function ToolDetails({
505
+ tool,
551
506
  isFocused,
552
507
  selectedAction,
553
508
  }: {
554
- skill: SkillManifest | null;
509
+ tool: ToolManifest | null;
555
510
  isFocused: boolean;
556
511
  selectedAction: number;
557
512
  }) {
558
- if (!skill) {
513
+ if (!tool) {
559
514
  return (
560
515
  <Box paddingLeft={2} flexGrow={1}>
561
- <Text color={colors.textDim}>Select a skill</Text>
516
+ <Text color={colors.textDim}>Select a tool</Text>
562
517
  </Box>
563
518
  );
564
519
  }
565
520
 
566
- const installed = isSkillInstalled(skill.name);
567
- const updateStatus = getSkillUpdateStatus(skill.name);
568
- const skillCommands = getCommandsFromSkills().filter((c) => c.skillName === skill.name);
521
+ const installed = isToolInstalled(tool.name);
522
+ const installedVersion = getInstalledToolVersion(tool.name);
523
+ const updateStatus = getToolUpdateStatus(tool.name);
569
524
 
570
525
  const actions = installed
571
526
  ? [
572
- { id: 'view', label: 'View', variant: 'default' },
527
+ { id: 'explore', label: 'Explore', variant: 'default' },
573
528
  ...(updateStatus.hasUpdate
574
529
  ? [{ id: 'update', label: `Update (${updateStatus.bundledVersion})`, variant: 'primary' }]
575
530
  : []),
@@ -577,245 +532,58 @@ function SkillDetails({
577
532
  { id: 'uninstall', label: 'Uninstall', variant: 'danger' },
578
533
  ]
579
534
  : [
580
- { id: 'view', label: 'View', variant: 'default' },
535
+ { id: 'explore', label: 'Explore', variant: 'default' },
581
536
  { id: 'install', label: 'Install', variant: 'primary' },
582
537
  ];
583
538
 
584
539
  return (
585
540
  <Box flexDirection="column" paddingLeft={2} flexGrow={1}>
586
- <Text color={colors.text} bold>{skill.name}</Text>
541
+ <Text color={colors.text} bold>{tool.name}</Text>
587
542
 
588
543
  <Box marginTop={1}>
589
544
  <Text color={colors.textDim}>
590
- {skill.version}
591
- {skill.status && ` · ${skill.status}`}
545
+ {tool.version}
546
+ {tool.status && ` · ${tool.status}`}
592
547
  {installed && <Text color={colors.success}> · installed</Text>}
593
548
  {updateStatus.hasUpdate && (
594
- <Text color={colors.primary}> · update available ({updateStatus.installedVersion} → {updateStatus.bundledVersion})</Text>
549
+ <Text color={colors.primary}> · update ({installedVersion} → {updateStatus.bundledVersion})</Text>
595
550
  )}
596
551
  </Text>
597
552
  </Box>
598
553
 
599
554
  <Box marginTop={1}>
600
- <Text color={colors.textMuted}>{skill.description}</Text>
555
+ <Text color={colors.textMuted}>{tool.description}</Text>
601
556
  </Box>
602
557
 
603
- {skillCommands.length > 0 && (
558
+ {/* Colored component badges */}
559
+ <Box flexDirection="column" marginTop={1}>
560
+ <Text color={colors.textDim}>Includes:</Text>
604
561
  <Box marginTop={1}>
605
- <Text color={colors.textDim}>
606
- Commands: {skillCommands.map((c) => `/${c.name}`).join(', ')}
607
- </Text>
562
+ <ComponentBadges tool={tool} />
608
563
  </Box>
609
- )}
610
-
611
- {skill.examples && skill.examples.length > 0 && (
612
- <Box flexDirection="column" marginTop={1}>
613
- <Text color={colors.textDim}>
614
- Examples{skill.examples.length > 2 ? ` (showing 2 of ${skill.examples.length})` : ''}:
615
- </Text>
616
- {skill.examples.slice(0, 2).map((example, i) => (
617
- <Box key={i} flexDirection="column" marginTop={i > 0 ? 1 : 0}>
618
- <Text color={colors.textMuted}> {example.title}</Text>
619
- {example.code
620
- .trim()
621
- .split('\n')
622
- .slice(0, 3)
623
- .map((line, j) => (
624
- <Text key={j} color={colors.textDim}>
625
- {' '}{line}
626
- </Text>
627
- ))}
628
- </Box>
629
- ))}
630
- </Box>
631
- )}
632
-
633
- {isFocused && (
634
- <Box flexDirection="row" marginTop={1}>
635
- {actions.map((action, index) => (
636
- <Text
637
- key={action.id}
638
- backgroundColor={
639
- selectedAction === index
640
- ? action.variant === 'danger'
641
- ? colors.error
642
- : colors.primary
643
- : colors.bgSelected
644
- }
645
- color={selectedAction === index ? '#ffffff' : colors.textMuted}
646
- bold={selectedAction === index}
647
- >
648
- {' '}{action.label}{' '}
564
+ {/* Show names below badges */}
565
+ <Box flexDirection="column" marginTop={1} marginLeft={1}>
566
+ {tool.includes.skills.length > 0 && (
567
+ <Text>
568
+ <Text color={colors.skill}>Skills: </Text>
569
+ <Text color={colors.textMuted}>{tool.includes.skills.map(s => s.name).join(', ')}</Text>
649
570
  </Text>
650
- ))}
651
- </Box>
652
- )}
653
- </Box>
654
- );
655
- }
656
-
657
- function CommandDetails({
658
- command,
659
- isFocused,
660
- selectedAction,
661
- }: {
662
- command: Command | null;
663
- isFocused: boolean;
664
- selectedAction: number;
665
- }) {
666
- if (!command) {
667
- return (
668
- <Box paddingLeft={2} flexGrow={1}>
669
- <Text color={colors.textDim}>Select a command</Text>
670
- </Box>
671
- );
672
- }
673
-
674
- const skillInstalled = isSkillInstalled(command.skillName);
675
-
676
- // Commands belong to skills - only show View, install via skill
677
- const actions = [{ id: 'view', label: 'View', variant: 'default' }];
678
-
679
- return (
680
- <Box flexDirection="column" paddingLeft={2} flexGrow={1}>
681
- <Text color={colors.text} bold>/{command.name}</Text>
682
-
683
- <Box marginTop={1}>
684
- <Text color={colors.textDim}>
685
- from {command.skillName}
686
- {skillInstalled && <Text color={colors.success}> · installed</Text>}
687
- </Text>
688
- </Box>
689
-
690
- <Box marginTop={1}>
691
- <Text color={colors.textMuted}>{command.description}</Text>
692
- </Box>
693
-
694
- {command.usage.length > 0 && (
695
- <Box flexDirection="column" marginTop={1}>
696
- <Text color={colors.textDim}>Usage:</Text>
697
- {command.usage.map((u, i) => (
698
- <Text key={i} color={colors.textMuted}>
699
- {' '}{u}
571
+ )}
572
+ {tool.includes.commands.length > 0 && (
573
+ <Text>
574
+ <Text color={colors.command}>Commands: </Text>
575
+ <Text color={colors.textMuted}>{tool.includes.commands.map(c => `/${c}`).join(', ')}</Text>
700
576
  </Text>
701
- ))}
702
- </Box>
703
- )}
704
-
705
- {isFocused && (
706
- <Box flexDirection="row" marginTop={1}>
707
- {actions.map((action, index) => (
708
- <Text
709
- key={action.id}
710
- backgroundColor={
711
- selectedAction === index
712
- ? action.variant === 'danger'
713
- ? colors.error
714
- : colors.primary
715
- : colors.bgSelected
716
- }
717
- color={selectedAction === index ? '#ffffff' : colors.textMuted}
718
- bold={selectedAction === index}
719
- >
720
- {' '}{action.label}{' '}
577
+ )}
578
+ {tool.includes.agents.length > 0 && (
579
+ <Text>
580
+ <Text color={colors.agent}>Agents: </Text>
581
+ <Text color={colors.textMuted}>{tool.includes.agents.join(', ')}</Text>
721
582
  </Text>
722
- ))}
583
+ )}
723
584
  </Box>
724
- )}
725
- </Box>
726
- );
727
- }
728
-
729
- function AgentItem({ agent, isSelected }: { agent: AgentManifest; isSelected: boolean }) {
730
- const statusDisplay = agent.status === 'alpha' ? '[alpha]' : agent.status === 'beta' ? '[beta]' : '';
731
-
732
- return (
733
- <Box paddingX={1}>
734
- <Text>
735
- <Text color={colors.textDim}>{isSelected ? '>' : ' '} </Text>
736
- <Text color={isSelected ? colors.text : colors.textMuted}>{agent.name}</Text>
737
- <Text color={colors.textDim}> v{agent.version}</Text>
738
- {statusDisplay && <Text color={colors.textDim}> {statusDisplay}</Text>}
739
- </Text>
740
- </Box>
741
- );
742
- }
743
-
744
- function AgentDetails({
745
- agent,
746
- isFocused,
747
- selectedAction,
748
- }: {
749
- agent: AgentManifest | null;
750
- isFocused: boolean;
751
- selectedAction: number;
752
- }) {
753
- if (!agent) {
754
- return (
755
- <Box paddingLeft={2} flexGrow={1}>
756
- <Text color={colors.textDim}>Select an agent</Text>
757
- </Box>
758
- );
759
- }
760
-
761
- const installed = isAgentInstalled(agent.name);
762
- const bundledSkill = getAgentBundledSkill(agent.name);
763
- const skillInstalled = bundledSkill ? isSkillInstalled(bundledSkill) : false;
764
- const statusDisplay = agent.status === 'alpha' ? '[alpha]' : agent.status === 'beta' ? '[beta]' : '';
765
- const modeDisplay = agent.mode === 'primary' ? 'primary' : agent.mode === 'all' ? 'primary/subagent' : 'subagent';
766
-
767
- // If agent belongs to a skill, only show View (install via skill)
768
- // Otherwise show Install/Uninstall for standalone agents
769
- const actions = bundledSkill
770
- ? [{ id: 'view', label: 'View', variant: 'default' }]
771
- : installed
772
- ? [
773
- { id: 'view', label: 'View', variant: 'default' },
774
- { id: 'uninstall', label: 'Uninstall', variant: 'danger' },
775
- ]
776
- : [
777
- { id: 'view', label: 'View', variant: 'default' },
778
- { id: 'install', label: 'Install', variant: 'primary' },
779
- ];
780
-
781
- return (
782
- <Box flexDirection="column" paddingLeft={2} flexGrow={1}>
783
- <Text color={colors.text} bold>{agent.name}</Text>
784
-
785
- <Box marginTop={1}>
786
- <Text color={colors.textDim}>
787
- v{agent.version}
788
- {statusDisplay && <Text> · {statusDisplay}</Text>}
789
- {' · '}{modeDisplay}
790
- {bundledSkill && <Text color={colors.textMuted}> · from {bundledSkill}</Text>}
791
- {skillInstalled && <Text color={colors.success}> · installed</Text>}
792
- {!bundledSkill && installed && <Text color={colors.success}> · installed</Text>}
793
- </Text>
794
- </Box>
795
-
796
- <Box marginTop={1}>
797
- <Text color={colors.textMuted}>{agent.description}</Text>
798
585
  </Box>
799
586
 
800
- {agent.tools && agent.tools.length > 0 && (
801
- <Box marginTop={1}>
802
- <Text color={colors.textDim}>
803
- Tools: {agent.tools.join(', ')}
804
- </Text>
805
- </Box>
806
- )}
807
-
808
- {agent.triggers && agent.triggers.length > 0 && (
809
- <Box flexDirection="column" marginTop={1}>
810
- <Text color={colors.textDim}>Triggers:</Text>
811
- {agent.triggers.slice(0, 3).map((trigger, i) => (
812
- <Text key={i} color={colors.textMuted}>
813
- {' '}&quot;{trigger}&quot;
814
- </Text>
815
- ))}
816
- </Box>
817
- )}
818
-
819
587
  {isFocused && (
820
588
  <Box flexDirection="row" marginTop={1}>
821
589
  {actions.map((action, index) => (
@@ -977,6 +745,188 @@ function ReadmeViewer({
977
745
  );
978
746
  }
979
747
 
748
+ interface ExplorerItem {
749
+ type: ComponentType;
750
+ name: string;
751
+ path: string;
752
+ }
753
+
754
+ interface ToolExplorerProps {
755
+ tool: ToolManifest;
756
+ onViewSource: (title: string, content: string) => void;
757
+ onClose: () => void;
758
+ }
759
+
760
+ function ToolExplorer({ tool, onViewSource, onClose }: ToolExplorerProps) {
761
+ const [selectedIndex, setSelectedIndex] = useState(0);
762
+
763
+ // Build list of all explorable items
764
+ const items: ExplorerItem[] = useMemo(() => {
765
+ const result: ExplorerItem[] = [];
766
+ const toolDir = getBundledToolsDir();
767
+
768
+ // Add skills
769
+ for (const skill of tool.includes.skills) {
770
+ result.push({
771
+ type: 'skill',
772
+ name: skill.name,
773
+ path: join(toolDir, tool.name, 'skills', skill.name, 'SKILL.md'),
774
+ });
775
+ }
776
+
777
+ // Add commands
778
+ for (const cmd of tool.includes.commands) {
779
+ result.push({
780
+ type: 'command',
781
+ name: `/${cmd}`,
782
+ path: join(toolDir, tool.name, 'commands', `${cmd}.md`),
783
+ });
784
+ }
785
+
786
+ // Add agents
787
+ for (const agent of tool.includes.agents) {
788
+ result.push({
789
+ type: 'agent',
790
+ name: agent,
791
+ path: join(toolDir, tool.name, 'agents', agent, 'AGENT.md'),
792
+ });
793
+ }
794
+
795
+ return result;
796
+ }, [tool]);
797
+
798
+ useInput((input, key) => {
799
+ if (key.escape) {
800
+ onClose();
801
+ return;
802
+ }
803
+
804
+ if (key.leftArrow || key.upArrow) {
805
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
806
+ }
807
+ if (key.rightArrow || key.downArrow) {
808
+ setSelectedIndex((prev) => Math.min(items.length - 1, prev + 1));
809
+ }
810
+
811
+ if (key.return && items.length > 0) {
812
+ const item = items[selectedIndex];
813
+ if (existsSync(item.path)) {
814
+ const content = readFileSync(item.path, 'utf-8');
815
+ onViewSource(`${tool.name} / ${item.name}`, content);
816
+ } else {
817
+ // Try YAML fallback for skills/agents
818
+ const yamlPath = item.path.replace('.md', '.yaml');
819
+ if (existsSync(yamlPath)) {
820
+ const content = readFileSync(yamlPath, 'utf-8');
821
+ onViewSource(`${tool.name} / ${item.name}`, content);
822
+ }
823
+ }
824
+ }
825
+ });
826
+
827
+ // Group items by type for display
828
+ const skillItems = items.filter(i => i.type === 'skill');
829
+ const commandItems = items.filter(i => i.type === 'command');
830
+ const agentItems = items.filter(i => i.type === 'agent');
831
+
832
+ const getItemIndex = (item: ExplorerItem) => items.indexOf(item);
833
+
834
+ return (
835
+ <Box flexDirection="column" padding={1}>
836
+ <Box marginBottom={1}>
837
+ <Text>
838
+ <Text color={colors.textDim}>[</Text>
839
+ <Text color={colors.primary}>●</Text>
840
+ <Text color={colors.textDim}> </Text>
841
+ <Text color={colors.primary}>●</Text>
842
+ <Text color={colors.textDim}>] </Text>
843
+ <Text color={colors.text} bold>{tool.name}</Text>
844
+ </Text>
845
+ </Box>
846
+
847
+ <Box marginBottom={1}>
848
+ <Text color={colors.textMuted}>{tool.description}</Text>
849
+ </Box>
850
+
851
+ {/* Skills section */}
852
+ {skillItems.length > 0 && (
853
+ <Box flexDirection="column" marginBottom={1}>
854
+ <Text color={colors.skill} bold>Skills</Text>
855
+ <Box flexDirection="row" flexWrap="wrap" marginTop={1}>
856
+ {skillItems.map((item) => {
857
+ const idx = getItemIndex(item);
858
+ const isSelected = selectedIndex === idx;
859
+ return (
860
+ <Box key={item.name} marginRight={1} marginBottom={1}>
861
+ <Text
862
+ backgroundColor={isSelected ? colors.skill : colors.bgSelected}
863
+ color={isSelected ? '#000000' : colors.skill}
864
+ bold={isSelected}
865
+ >
866
+ {` ${item.name} `}
867
+ </Text>
868
+ </Box>
869
+ );
870
+ })}
871
+ </Box>
872
+ </Box>
873
+ )}
874
+
875
+ {/* Commands section */}
876
+ {commandItems.length > 0 && (
877
+ <Box flexDirection="column" marginBottom={1}>
878
+ <Text color={colors.command} bold>Commands</Text>
879
+ <Box flexDirection="row" flexWrap="wrap" marginTop={1}>
880
+ {commandItems.map((item) => {
881
+ const idx = getItemIndex(item);
882
+ const isSelected = selectedIndex === idx;
883
+ return (
884
+ <Box key={item.name} marginRight={1} marginBottom={1}>
885
+ <Text
886
+ backgroundColor={isSelected ? colors.command : colors.bgSelected}
887
+ color={isSelected ? '#000000' : colors.command}
888
+ bold={isSelected}
889
+ >
890
+ {` ${item.name} `}
891
+ </Text>
892
+ </Box>
893
+ );
894
+ })}
895
+ </Box>
896
+ </Box>
897
+ )}
898
+
899
+ {/* Agents section */}
900
+ {agentItems.length > 0 && (
901
+ <Box flexDirection="column" marginBottom={1}>
902
+ <Text color={colors.agent} bold>Agents</Text>
903
+ <Box flexDirection="row" flexWrap="wrap" marginTop={1}>
904
+ {agentItems.map((item) => {
905
+ const idx = getItemIndex(item);
906
+ const isSelected = selectedIndex === idx;
907
+ return (
908
+ <Box key={item.name} marginRight={1} marginBottom={1}>
909
+ <Text
910
+ backgroundColor={isSelected ? colors.agent : colors.bgSelected}
911
+ color={isSelected ? '#000000' : colors.agent}
912
+ bold={isSelected}
913
+ >
914
+ {` ${item.name} `}
915
+ </Text>
916
+ </Box>
917
+ );
918
+ })}
919
+ </Box>
920
+ </Box>
921
+ )}
922
+
923
+ <Box marginTop={1}>
924
+ <Text color={colors.textDim}>←→ navigate · enter view source · esc back</Text>
925
+ </Box>
926
+ </Box>
927
+ );
928
+ }
929
+
980
930
  function SettingsDetails({
981
931
  onEditSettings,
982
932
  isFocused,
@@ -993,9 +943,9 @@ function SettingsDetails({
993
943
 
994
944
  <Box flexDirection="column" marginTop={1}>
995
945
  <Text>
996
- <Text color={colors.textDim}>AI Tool: </Text>
946
+ <Text color={colors.textDim}>Platform: </Text>
997
947
  <Text color={colors.text}>
998
- {config.ai_tool === AITool.ClaudeCode ? 'Claude Code' : 'OpenCode'}
948
+ {config.platform === Platform.ClaudeCode ? 'Claude Code' : 'OpenCode'}
999
949
  </Text>
1000
950
  </Text>
1001
951
  <Text>
@@ -1302,13 +1252,11 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
1302
1252
  function App() {
1303
1253
  const { exit } = useApp();
1304
1254
  const tabs: { id: Tab; label: string }[] = [
1305
- { id: 'skills', label: 'Skills' },
1306
- { id: 'commands', label: 'Commands' },
1307
- { id: 'agents', label: 'Agents' },
1255
+ { id: 'tools', label: 'Tools' },
1308
1256
  { id: 'settings', label: 'Settings' },
1309
1257
  ];
1310
1258
 
1311
- const [activeTab, setActiveTab] = useState<Tab>('skills');
1259
+ const [activeTab, setActiveTab] = useState<Tab>('tools');
1312
1260
  const [tabIndex, setTabIndex] = useState(0);
1313
1261
  const [view, setView] = useState<View>('welcome');
1314
1262
  const [selectedIndex, setSelectedIndex] = useState(0);
@@ -1318,6 +1266,7 @@ function App() {
1318
1266
  const [isEditingSettings, setIsEditingSettings] = useState(false);
1319
1267
  const [readmeContent, setReadmeContent] = useState<{ title: string; content: string } | null>(null);
1320
1268
  const [isUpdating, setIsUpdating] = useState(false);
1269
+ const [previousView, setPreviousView] = useState<View>('detail'); // Track where to return from readme
1321
1270
 
1322
1271
  // Check for updates once on mount
1323
1272
  const updateInfo = useMemo(() => getUpdateInfo(), []);
@@ -1346,9 +1295,9 @@ function App() {
1346
1295
 
1347
1296
  const MAX_VISIBLE_ITEMS = 6;
1348
1297
 
1298
+ const tools = getBundledTools();
1299
+ // Keep skills for configure view (tools configure via their primary skill)
1349
1300
  const skills = getBundledSkills();
1350
- const commands = getCommandsFromSkills();
1351
- const agents = getBundledAgents();
1352
1301
 
1353
1302
  useInput((input, key) => {
1354
1303
  if (message) setMessage(null);
@@ -1385,11 +1334,7 @@ function App() {
1385
1334
  setSelectedAction(0);
1386
1335
  }
1387
1336
  if (key.downArrow) {
1388
- const maxIndex =
1389
- activeTab === 'skills' ? skills.length - 1
1390
- : activeTab === 'commands' ? commands.length - 1
1391
- : activeTab === 'agents' ? agents.length - 1
1392
- : 0;
1337
+ const maxIndex = activeTab === 'tools' ? tools.length - 1 : 0;
1393
1338
  setSelectedIndex((prev) => {
1394
1339
  const newIndex = Math.min(maxIndex, prev + 1);
1395
1340
  // Scroll down if needed
@@ -1401,11 +1346,7 @@ function App() {
1401
1346
  setSelectedAction(0);
1402
1347
  }
1403
1348
  if (key.return) {
1404
- if (activeTab === 'skills' && skills.length > 0) {
1405
- setView('detail');
1406
- } else if (activeTab === 'commands' && commands.length > 0) {
1407
- setView('detail');
1408
- } else if (activeTab === 'agents' && agents.length > 0) {
1349
+ if (activeTab === 'tools' && tools.length > 0) {
1409
1350
  setView('detail');
1410
1351
  } else if (activeTab === 'settings') {
1411
1352
  setIsEditingSettings(true);
@@ -1422,50 +1363,41 @@ function App() {
1422
1363
  }
1423
1364
  if (key.rightArrow) {
1424
1365
  let maxActions = 0;
1425
- if (activeTab === 'skills') {
1426
- const skill = skills[selectedIndex];
1427
- const installed = skill ? isSkillInstalled(skill.name) : false;
1428
- const hasUpdate = skill ? getSkillUpdateStatus(skill.name).hasUpdate : false;
1429
- // View, [Update], Configure, Uninstall or View, Install
1366
+ if (activeTab === 'tools') {
1367
+ const tool = tools[selectedIndex];
1368
+ const installed = tool ? isToolInstalled(tool.name) : false;
1369
+ const hasUpdate = tool ? getToolUpdateStatus(tool.name).hasUpdate : false;
1370
+ // Explore, [Update], Configure, Uninstall or Explore, Install
1430
1371
  maxActions = installed ? (hasUpdate ? 3 : 2) : 1;
1431
- } else if (activeTab === 'agents') {
1432
- maxActions = 1; // View, Install/Uninstall
1433
- } else if (activeTab === 'commands') {
1434
- const command = commands[selectedIndex];
1435
- // If parent skill is installed, only View is available
1436
- const skillInstalled = command ? isSkillInstalled(command.skillName) : false;
1437
- maxActions = skillInstalled ? 0 : 1;
1438
1372
  }
1439
1373
  setSelectedAction((prev) => Math.min(maxActions, prev + 1));
1440
1374
  }
1441
- if (key.return && activeTab === 'skills') {
1442
- const skill = skills[selectedIndex];
1443
- if (skill) {
1444
- const installed = isSkillInstalled(skill.name);
1445
- const skillUpdateStatus = getSkillUpdateStatus(skill.name);
1446
- // Build actions array to match SkillDetails
1447
- const skillActions = installed
1375
+ if (key.return && activeTab === 'tools') {
1376
+ const tool = tools[selectedIndex];
1377
+ if (tool) {
1378
+ const installed = isToolInstalled(tool.name);
1379
+ const toolUpdateStatus = getToolUpdateStatus(tool.name);
1380
+ // Build actions array to match ToolDetails
1381
+ const toolActions = installed
1448
1382
  ? [
1449
- { id: 'view' },
1450
- ...(skillUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
1383
+ { id: 'explore' },
1384
+ ...(toolUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
1451
1385
  { id: 'configure' },
1452
1386
  { id: 'uninstall' },
1453
1387
  ]
1454
- : [{ id: 'view' }, { id: 'install' }];
1388
+ : [{ id: 'explore' }, { id: 'install' }];
1455
1389
 
1456
- const actionId = skillActions[selectedAction]?.id;
1390
+ const actionId = toolActions[selectedAction]?.id;
1457
1391
 
1458
- if (actionId === 'view') {
1459
- const skillMdPath = join(getBundledSkillsDir(), skill.name, 'SKILL.md');
1460
- if (existsSync(skillMdPath)) {
1461
- const content = readFileSync(skillMdPath, 'utf-8');
1462
- setReadmeContent({ title: `${skill.name}/SKILL.md`, content });
1463
- setView('readme');
1464
- }
1392
+ if (actionId === 'explore') {
1393
+ // Open the tool explorer view
1394
+ setView('explorer');
1465
1395
  } else if (actionId === 'update') {
1466
- const result = updateSkill(skill.name);
1396
+ // Update the primary skill of the tool
1397
+ const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
1398
+ const result = updateSkill(primarySkill);
1467
1399
  setMessage({
1468
- text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
1400
+ text: result.success ? `✓ Updated ${tool.name}` : `✗ ${result.message}`,
1469
1401
  type: result.success ? 'success' : 'error',
1470
1402
  });
1471
1403
  if (result.success) {
@@ -1474,9 +1406,11 @@ function App() {
1474
1406
  } else if (actionId === 'configure') {
1475
1407
  setView('configure');
1476
1408
  } else if (actionId === 'uninstall') {
1477
- const result = uninstallSkill(skill.name);
1409
+ // Uninstall the primary skill of the tool
1410
+ const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
1411
+ const result = uninstallSkill(primarySkill);
1478
1412
  setMessage({
1479
- text: result.success ? `✓ Uninstalled ${skill.name}` : `✗ ${result.message}`,
1413
+ text: result.success ? `✓ Uninstalled ${tool.name}` : `✗ ${result.message}`,
1480
1414
  type: result.success ? 'success' : 'error',
1481
1415
  });
1482
1416
  if (result.success) {
@@ -1484,14 +1418,16 @@ function App() {
1484
1418
  setSelectedAction(0);
1485
1419
  }
1486
1420
  } else if (actionId === 'install') {
1487
- const result = installSkill(skill.name);
1421
+ // Install the primary skill of the tool
1422
+ const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
1423
+ const result = installSkill(primarySkill);
1488
1424
  setMessage({
1489
- text: result.success ? `✓ Installed ${skill.name}` : `✗ ${result.message}`,
1425
+ text: result.success ? `✓ Installed ${tool.name}` : `✗ ${result.message}`,
1490
1426
  type: result.success ? 'success' : 'error',
1491
1427
  });
1492
1428
  if (result.success) {
1493
- // Check if skill has required config (options without defaults)
1494
- const configSchema = skill.config_schema || {};
1429
+ // Check if tool has required config (options without defaults)
1430
+ const configSchema = tool.config_schema || {};
1495
1431
  const hasRequiredConfig = Object.values(configSchema).some(
1496
1432
  (option) => (option as ConfigOption).default === undefined
1497
1433
  );
@@ -1506,67 +1442,14 @@ function App() {
1506
1442
  }
1507
1443
  }
1508
1444
  }
1509
- if (key.return && activeTab === 'agents') {
1510
- const agent = agents[selectedIndex];
1511
- if (agent) {
1512
- const installed = isAgentInstalled(agent.name);
1513
- const bundledSkill = getAgentBundledSkill(agent.name);
1514
- if (selectedAction === 0) {
1515
- // View
1516
- const agentMdPath = findAgentSourcePath(agent.name);
1517
- if (agentMdPath) {
1518
- const content = readFileSync(agentMdPath, 'utf-8');
1519
- setReadmeContent({ title: `${agent.name}/AGENT.md`, content });
1520
- setView('readme');
1521
- }
1522
- } else if (!bundledSkill && installed && selectedAction === 1) {
1523
- // Uninstall (only for standalone agents)
1524
- const result = uninstallAgent(agent.name);
1525
- setMessage({
1526
- text: result.success ? `✓ Uninstalled ${agent.name}` : `✗ ${result.message}`,
1527
- type: result.success ? 'success' : 'error',
1528
- });
1529
- if (result.success) {
1530
- setView('menu');
1531
- setSelectedAction(0);
1532
- }
1533
- } else if (!bundledSkill && !installed && selectedAction === 1) {
1534
- // Install (only for standalone agents)
1535
- const result = installAgent(agent.name);
1536
- setMessage({
1537
- text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
1538
- type: result.success ? 'success' : 'error',
1539
- });
1540
- if (result.success) {
1541
- setView('menu');
1542
- setSelectedAction(0);
1543
- }
1544
- }
1545
- }
1546
- }
1547
- if (key.return && activeTab === 'commands') {
1548
- const command = commands[selectedIndex];
1549
- if (command) {
1550
- // Command file: extract part after skill name (e.g., "comments check" → "check.md")
1551
- const cmdPart = command.name.startsWith(command.skillName + ' ')
1552
- ? command.name.slice(command.skillName.length + 1)
1553
- : command.name;
1554
- const commandMdPath = join(getBundledSkillsDir(), command.skillName, 'commands', `${cmdPart}.md`);
1555
-
1556
- // Only View action available - commands install via skills
1557
- if (selectedAction === 0 && existsSync(commandMdPath)) {
1558
- const content = readFileSync(commandMdPath, 'utf-8');
1559
- setReadmeContent({ title: `/${command.name}`, content });
1560
- setView('readme');
1561
- }
1562
- }
1563
- }
1564
1445
  }
1565
- }, { isActive: view !== 'welcome' && view !== 'setup' && view !== 'configure' });
1446
+ }, { isActive: view !== 'welcome' && view !== 'setup' && view !== 'configure' && view !== 'explorer' });
1566
1447
 
1567
- const selectedSkill = activeTab === 'skills' ? skills[selectedIndex] ?? null : null;
1568
- const selectedCommand = activeTab === 'commands' ? commands[selectedIndex] ?? null : null;
1569
- const selectedAgent = activeTab === 'agents' ? agents[selectedIndex] ?? null : null;
1448
+ const selectedTool = activeTab === 'tools' ? tools[selectedIndex] ?? null : null;
1449
+ // For configure view, we need the matching skill manifest
1450
+ const selectedSkillForConfig = selectedTool
1451
+ ? skills.find(s => s.name === (selectedTool.includes.skills.find(sk => sk.required)?.name || selectedTool.name))
1452
+ : null;
1570
1453
 
1571
1454
  if (view === 'welcome') {
1572
1455
  return (
@@ -1610,18 +1493,34 @@ function App() {
1610
1493
  content={readmeContent.content}
1611
1494
  onClose={() => {
1612
1495
  setReadmeContent(null);
1496
+ setView(previousView);
1497
+ }}
1498
+ />
1499
+ );
1500
+ }
1501
+
1502
+ if (view === 'explorer' && selectedTool) {
1503
+ return (
1504
+ <ToolExplorer
1505
+ tool={selectedTool}
1506
+ onViewSource={(title, content) => {
1507
+ setPreviousView('explorer');
1508
+ setReadmeContent({ title, content });
1509
+ setView('readme');
1510
+ }}
1511
+ onClose={() => {
1613
1512
  setView('detail');
1614
1513
  }}
1615
1514
  />
1616
1515
  );
1617
1516
  }
1618
1517
 
1619
- if (view === 'configure' && selectedSkill) {
1518
+ if (view === 'configure' && selectedSkillForConfig) {
1620
1519
  return (
1621
1520
  <SkillConfigScreen
1622
- skill={selectedSkill}
1521
+ skill={selectedSkillForConfig}
1623
1522
  onComplete={() => {
1624
- setMessage({ text: `✓ Configuration saved for ${selectedSkill.name}`, type: 'success' });
1523
+ setMessage({ text: `✓ Configuration saved for ${selectedTool?.name || selectedSkillForConfig.name}`, type: 'success' });
1625
1524
  setView('detail');
1626
1525
  }}
1627
1526
  onCancel={() => {
@@ -1652,6 +1551,11 @@ function App() {
1652
1551
  <Text color={colors.textDim}> v{getVersion()}</Text>
1653
1552
  </Text>
1654
1553
  </Box>
1554
+ <Box paddingX={1}>
1555
+ <Text color={colors.textDim}>
1556
+ {loadConfig().platform === Platform.ClaudeCode ? 'Claude Code' : 'OpenCode'}
1557
+ </Text>
1558
+ </Box>
1655
1559
 
1656
1560
  {/* Tabs */}
1657
1561
  <Box paddingX={1} marginTop={1}>
@@ -1660,71 +1564,29 @@ function App() {
1660
1564
 
1661
1565
  {/* List */}
1662
1566
  <Box flexDirection="column" marginTop={1}>
1663
- {activeTab === 'skills' && (
1567
+ {activeTab === 'tools' && (
1664
1568
  <>
1665
1569
  {scrollOffset > 0 && (
1666
1570
  <Box paddingX={1}>
1667
1571
  <Text color={colors.textDim}>↑ {scrollOffset} more</Text>
1668
1572
  </Box>
1669
1573
  )}
1670
- {skills.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS).map((skill, index) => (
1671
- <SkillItem
1672
- key={skill.name}
1673
- skill={skill}
1574
+ {tools.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS).map((tool, index) => (
1575
+ <ToolItem
1576
+ key={tool.name}
1577
+ tool={tool}
1674
1578
  isSelected={scrollOffset + index === selectedIndex}
1675
1579
  isActive={scrollOffset + index === selectedIndex && view === 'detail'}
1676
1580
  />
1677
1581
  ))}
1678
- {scrollOffset + MAX_VISIBLE_ITEMS < skills.length && (
1582
+ {scrollOffset + MAX_VISIBLE_ITEMS < tools.length && (
1679
1583
  <Box paddingX={1}>
1680
- <Text color={colors.textDim}>↓ {skills.length - scrollOffset - MAX_VISIBLE_ITEMS} more</Text>
1584
+ <Text color={colors.textDim}>↓ {tools.length - scrollOffset - MAX_VISIBLE_ITEMS} more</Text>
1681
1585
  </Box>
1682
1586
  )}
1683
- {skills.length > MAX_VISIBLE_ITEMS && (
1587
+ {tools.length > MAX_VISIBLE_ITEMS && (
1684
1588
  <Box paddingX={1} marginTop={1}>
1685
- <Text color={colors.textDim}>{skills.length} skills total</Text>
1686
- </Box>
1687
- )}
1688
- </>
1689
- )}
1690
-
1691
- {activeTab === 'commands' && (
1692
- <>
1693
- {scrollOffset > 0 && (
1694
- <Box paddingX={1}>
1695
- <Text color={colors.textDim}>↑ {scrollOffset} more</Text>
1696
- </Box>
1697
- )}
1698
- {commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS).map((cmd, index) => (
1699
- <CommandItem key={cmd.name} command={cmd} isSelected={scrollOffset + index === selectedIndex} />
1700
- ))}
1701
- {scrollOffset + MAX_VISIBLE_ITEMS < commands.length && (
1702
- <Box paddingX={1}>
1703
- <Text color={colors.textDim}>↓ {commands.length - scrollOffset - MAX_VISIBLE_ITEMS} more</Text>
1704
- </Box>
1705
- )}
1706
- </>
1707
- )}
1708
-
1709
- {activeTab === 'agents' && (
1710
- <>
1711
- {scrollOffset > 0 && (
1712
- <Box paddingX={1}>
1713
- <Text color={colors.textDim}>↑ {scrollOffset} more</Text>
1714
- </Box>
1715
- )}
1716
- {agents.length === 0 ? (
1717
- <Box paddingX={1}>
1718
- <Text color={colors.textDim}>No agents available</Text>
1719
- </Box>
1720
- ) : (
1721
- agents.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS).map((agent, index) => (
1722
- <AgentItem key={agent.name} agent={agent} isSelected={scrollOffset + index === selectedIndex} />
1723
- ))
1724
- )}
1725
- {scrollOffset + MAX_VISIBLE_ITEMS < agents.length && (
1726
- <Box paddingX={1}>
1727
- <Text color={colors.textDim}>↓ {agents.length - scrollOffset - MAX_VISIBLE_ITEMS} more</Text>
1589
+ <Text color={colors.textDim}>{tools.length} tools total</Text>
1728
1590
  </Box>
1729
1591
  )}
1730
1592
  </>
@@ -1753,21 +1615,13 @@ function App() {
1753
1615
  </Box>
1754
1616
 
1755
1617
  {/* Right column */}
1756
- {activeTab === 'skills' && (
1757
- <SkillDetails skill={selectedSkill} isFocused={view === 'detail'} selectedAction={selectedAction} />
1758
- )}
1759
-
1760
- {activeTab === 'commands' && (
1761
- <CommandDetails command={selectedCommand} isFocused={view === 'detail'} selectedAction={selectedAction} />
1618
+ {activeTab === 'tools' && (
1619
+ <ToolDetails tool={selectedTool} isFocused={view === 'detail'} selectedAction={selectedAction} />
1762
1620
  )}
1763
1621
 
1764
1622
  {activeTab === 'settings' && (
1765
1623
  <SettingsDetails onEditSettings={() => setView('setup')} isFocused={false} />
1766
1624
  )}
1767
-
1768
- {activeTab === 'agents' && (
1769
- <AgentDetails agent={selectedAgent} isFocused={view === 'detail'} selectedAction={selectedAction} />
1770
- )}
1771
1625
  </Box>
1772
1626
  );
1773
1627
  }