@orderful/droid 0.2.0 → 0.4.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 (50) hide show
  1. package/.claude/CLAUDE.md +41 -0
  2. package/.github/workflows/changeset-check.yml +43 -0
  3. package/.github/workflows/release.yml +6 -3
  4. package/CHANGELOG.md +43 -0
  5. package/bun.lock +357 -14
  6. package/dist/agents/README.md +137 -0
  7. package/dist/bin/droid.js +12 -1
  8. package/dist/bin/droid.js.map +1 -1
  9. package/dist/commands/setup.d.ts +8 -0
  10. package/dist/commands/setup.d.ts.map +1 -1
  11. package/dist/commands/setup.js +67 -0
  12. package/dist/commands/setup.js.map +1 -1
  13. package/dist/commands/tui.d.ts +2 -0
  14. package/dist/commands/tui.d.ts.map +1 -0
  15. package/dist/commands/tui.js +737 -0
  16. package/dist/commands/tui.js.map +1 -0
  17. package/dist/lib/agents.d.ts +53 -0
  18. package/dist/lib/agents.d.ts.map +1 -0
  19. package/dist/lib/agents.js +149 -0
  20. package/dist/lib/agents.js.map +1 -0
  21. package/dist/lib/skills.d.ts +20 -0
  22. package/dist/lib/skills.d.ts.map +1 -1
  23. package/dist/lib/skills.js +102 -0
  24. package/dist/lib/skills.js.map +1 -1
  25. package/dist/lib/types.d.ts +5 -0
  26. package/dist/lib/types.d.ts.map +1 -1
  27. package/dist/lib/version.d.ts +5 -0
  28. package/dist/lib/version.d.ts.map +1 -1
  29. package/dist/lib/version.js +19 -1
  30. package/dist/lib/version.js.map +1 -1
  31. package/dist/skills/README.md +85 -0
  32. package/dist/skills/comments/SKILL.md +8 -0
  33. package/dist/skills/comments/SKILL.yaml +32 -0
  34. package/dist/skills/comments/commands/README.md +58 -0
  35. package/package.json +15 -2
  36. package/src/agents/README.md +137 -0
  37. package/src/bin/droid.ts +12 -1
  38. package/src/commands/setup.ts +77 -0
  39. package/src/commands/tui.tsx +1535 -0
  40. package/src/lib/agents.ts +186 -0
  41. package/src/lib/skills.test.ts +75 -1
  42. package/src/lib/skills.ts +125 -0
  43. package/src/lib/types.ts +7 -0
  44. package/src/lib/version.test.ts +20 -1
  45. package/src/lib/version.ts +19 -1
  46. package/src/skills/README.md +85 -0
  47. package/src/skills/comments/SKILL.md +8 -0
  48. package/src/skills/comments/SKILL.yaml +32 -0
  49. package/src/skills/comments/commands/README.md +58 -0
  50. package/tsconfig.json +5 -3
@@ -0,0 +1,737 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { render, Box, Text, useInput, useApp } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import { useState, useMemo } from 'react';
5
+ import { execSync } from 'child_process';
6
+ import { existsSync, readdirSync, readFileSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { getBundledSkills, getBundledSkillsDir, isSkillInstalled, getInstalledSkill, installSkill, uninstallSkill, isCommandInstalled, installCommand, uninstallCommand, } from '../lib/skills.js';
9
+ import { getBundledAgents, getBundledAgentsDir, isAgentInstalled, installAgent, uninstallAgent } from '../lib/agents.js';
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';
13
+ import { getVersion } from '../lib/version.js';
14
+ const colors = {
15
+ primary: '#6366f1',
16
+ bgSelected: '#2d2d2d',
17
+ border: '#3a3a3a',
18
+ text: '#e8e8e8',
19
+ textMuted: '#999999',
20
+ textDim: '#6a6a6a',
21
+ success: '#4ade80',
22
+ error: '#f87171',
23
+ };
24
+ function getCommandsFromSkills() {
25
+ const skillsDir = getBundledSkillsDir();
26
+ const commands = [];
27
+ if (!existsSync(skillsDir))
28
+ return commands;
29
+ const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
30
+ .filter((d) => d.isDirectory())
31
+ .map((d) => d.name);
32
+ for (const skillName of skillDirs) {
33
+ const commandsDir = join(skillsDir, skillName, 'commands');
34
+ if (!existsSync(commandsDir))
35
+ continue;
36
+ const cmdFiles = readdirSync(commandsDir).filter((f) => f.endsWith('.md'));
37
+ for (const file of cmdFiles) {
38
+ const content = readFileSync(join(commandsDir, file), 'utf-8');
39
+ const lines = content.split('\n');
40
+ const headerMatch = lines[0]?.match(/^#\s+\/(\S+)\s*-?\s*(.*)/);
41
+ if (!headerMatch)
42
+ continue;
43
+ const usage = [];
44
+ let inUsage = false;
45
+ for (const line of lines) {
46
+ if (line.startsWith('## Usage'))
47
+ inUsage = true;
48
+ else if (line.startsWith('## ') && inUsage)
49
+ break;
50
+ else if (inUsage && line.startsWith('/'))
51
+ usage.push(line.trim());
52
+ }
53
+ commands.push({
54
+ name: headerMatch[1],
55
+ description: headerMatch[2] || '',
56
+ skillName,
57
+ usage,
58
+ });
59
+ }
60
+ }
61
+ return commands;
62
+ }
63
+ function detectAITool() {
64
+ try {
65
+ execSync('claude --version', { stdio: 'ignore' });
66
+ return AITool.ClaudeCode;
67
+ }
68
+ catch {
69
+ // Claude Code not found
70
+ }
71
+ try {
72
+ execSync('opencode --version', { stdio: 'ignore' });
73
+ return AITool.OpenCode;
74
+ }
75
+ catch {
76
+ // OpenCode not found
77
+ }
78
+ return null;
79
+ }
80
+ function getOutputOptions() {
81
+ const options = [
82
+ { label: 'Terminal (display in CLI)', value: BuiltInOutput.Terminal },
83
+ { label: 'Editor ($EDITOR)', value: BuiltInOutput.Editor },
84
+ ];
85
+ const skills = getBundledSkills();
86
+ for (const skill of skills) {
87
+ if (skill.provides_output) {
88
+ options.push({ label: `${skill.name} (${skill.description})`, value: skill.name });
89
+ }
90
+ }
91
+ return options;
92
+ }
93
+ function WelcomeScreen({ onContinue }) {
94
+ useInput((input, key) => {
95
+ if (key.return || input === 'q') {
96
+ onContinue();
97
+ }
98
+ });
99
+ return (_jsx(Box, { flexDirection: "column", alignItems: "center", justifyContent: "center", height: 16, children: _jsxs(Box, { flexDirection: "column", alignItems: "center", borderStyle: "single", borderColor: 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", getVersion()] })] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "\u2551 " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " \u2551 " }), _jsx(Text, { color: colors.textMuted, children: "Teaching your droid 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" })] })] }), _jsx(Box, { marginTop: 2, marginBottom: 1, children: _jsxs(Text, { backgroundColor: colors.primary, color: "#ffffff", bold: true, children: [' ', "I'm probably the droid you are looking for.", ' '] }) }), _jsx(Text, { color: colors.textDim, children: "press enter" })] }) }));
100
+ }
101
+ function SetupScreen({ onComplete, onSkip, initialConfig }) {
102
+ const [step, setStep] = useState('ai_tool');
103
+ const [aiTool, setAITool] = useState(initialConfig?.ai_tool || detectAITool() || AITool.ClaudeCode);
104
+ const [userMention, setUserMention] = useState(initialConfig?.user_mention || '@user');
105
+ const [outputPreference, setOutputPreference] = useState(initialConfig?.output_preference || BuiltInOutput.Terminal);
106
+ const [selectedIndex, setSelectedIndex] = useState(0);
107
+ const [error, setError] = useState(null);
108
+ const aiToolOptions = useMemo(() => [
109
+ { label: 'Claude Code', value: AITool.ClaudeCode },
110
+ { label: 'OpenCode', value: AITool.OpenCode },
111
+ ], []);
112
+ const outputOptions = useMemo(() => getOutputOptions(), []);
113
+ const steps = ['ai_tool', 'user_mention', 'output_preference', 'confirm'];
114
+ const stepIndex = steps.indexOf(step);
115
+ const totalSteps = steps.length - 1; // Don't count confirm as a step
116
+ const handleUserMentionSubmit = () => {
117
+ if (!userMention.startsWith('@')) {
118
+ setError('Mention should start with @');
119
+ return;
120
+ }
121
+ setError(null);
122
+ setStep('output_preference');
123
+ setSelectedIndex(0);
124
+ };
125
+ // Handle escape during text input (only intercept escape, nothing else)
126
+ useInput((input, key) => {
127
+ if (key.escape) {
128
+ setStep('ai_tool');
129
+ setSelectedIndex(0);
130
+ }
131
+ }, { isActive: step === 'user_mention' });
132
+ // Handle all input for non-text-input steps
133
+ useInput((input, key) => {
134
+ if (key.escape) {
135
+ if (step === 'ai_tool') {
136
+ onSkip();
137
+ }
138
+ else if (step === 'output_preference') {
139
+ setStep('user_mention');
140
+ }
141
+ else if (step === 'confirm') {
142
+ setStep('output_preference');
143
+ setSelectedIndex(0);
144
+ }
145
+ return;
146
+ }
147
+ if (step === 'ai_tool') {
148
+ if (key.upArrow)
149
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
150
+ if (key.downArrow)
151
+ setSelectedIndex((prev) => Math.min(aiToolOptions.length - 1, prev + 1));
152
+ if (key.return) {
153
+ setAITool(aiToolOptions[selectedIndex].value);
154
+ setStep('user_mention');
155
+ }
156
+ }
157
+ else if (step === 'output_preference') {
158
+ if (key.upArrow)
159
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
160
+ if (key.downArrow)
161
+ setSelectedIndex((prev) => Math.min(outputOptions.length - 1, prev + 1));
162
+ if (key.return) {
163
+ setOutputPreference(outputOptions[selectedIndex].value);
164
+ setStep('confirm');
165
+ }
166
+ }
167
+ else if (step === 'confirm') {
168
+ if (key.return) {
169
+ const config = {
170
+ ...loadConfig(),
171
+ ai_tool: aiTool,
172
+ user_mention: userMention,
173
+ output_preference: outputPreference,
174
+ };
175
+ saveConfig(config);
176
+ configureAIToolPermissions(aiTool);
177
+ onComplete();
178
+ }
179
+ }
180
+ }, { isActive: step !== 'user_mention' });
181
+ 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] })] }) }));
182
+ if (step === 'ai_tool') {
183
+ 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 }), option.value === detectAITool() && _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" }) })] }));
184
+ }
185
+ if (step === 'user_mention') {
186
+ 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" }) })] }));
187
+ }
188
+ if (step === 'output_preference') {
189
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [renderHeader(), _jsx(Text, { color: colors.text, children: "Default output preference for skill results?" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: outputOptions.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 })] }, option.value))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "\u2191\u2193 select \u00B7 enter next \u00B7 esc back" }) })] }));
190
+ }
191
+ // Confirm step
192
+ 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 })] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "Output: " }), _jsx(Text, { color: colors.text, children: outputOptions.find((o) => o.value === outputPreference)?.label || outputPreference })] })] }), _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" }) })] }));
193
+ }
194
+ function TabBar({ tabs, activeTab }) {
195
+ return (_jsx(Box, { flexDirection: "row", children: tabs.map((tab) => (_jsxs(Text, { backgroundColor: tab.id === activeTab ? colors.primary : undefined, color: tab.id === activeTab ? '#ffffff' : colors.textMuted, bold: tab.id === activeTab, children: [' ', tab.label, ' '] }, tab.id))) }));
196
+ }
197
+ function SkillItem({ skill, isSelected, isActive, }) {
198
+ const installed = isSkillInstalled(skill.name);
199
+ const installedInfo = getInstalledSkill(skill.name);
200
+ 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: " *" })] }) }));
201
+ }
202
+ function CommandItem({ command, isSelected, }) {
203
+ const installed = isSkillInstalled(command.skillName);
204
+ 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: " *" })] }) }));
205
+ }
206
+ function SkillDetails({ skill, isFocused, selectedAction, }) {
207
+ if (!skill) {
208
+ return (_jsx(Box, { paddingLeft: 2, flexGrow: 1, children: _jsx(Text, { color: colors.textDim, children: "Select a skill" }) }));
209
+ }
210
+ const installed = isSkillInstalled(skill.name);
211
+ const skillCommands = getCommandsFromSkills().filter((c) => c.skillName === skill.name);
212
+ const actions = installed
213
+ ? [
214
+ { id: 'view', label: 'View', variant: 'default' },
215
+ { id: 'configure', label: 'Configure', variant: 'primary' },
216
+ { id: 'uninstall', label: 'Uninstall', variant: 'danger' },
217
+ ]
218
+ : [
219
+ { id: 'view', label: 'View', variant: 'default' },
220
+ { id: 'install', label: 'Install', variant: 'primary' },
221
+ ];
222
+ 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" })] }) }), _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
223
+ .trim()
224
+ .split('\n')
225
+ .slice(0, 3)
226
+ .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
227
+ ? action.variant === 'danger'
228
+ ? colors.error
229
+ : colors.primary
230
+ : colors.bgSelected, color: selectedAction === index ? '#ffffff' : colors.textMuted, bold: selectedAction === index, children: [' ', action.label, ' '] }, action.id))) }))] }));
231
+ }
232
+ function CommandDetails({ command, isFocused, selectedAction, }) {
233
+ if (!command) {
234
+ return (_jsx(Box, { paddingLeft: 2, flexGrow: 1, children: _jsx(Text, { color: colors.textDim, children: "Select a command" }) }));
235
+ }
236
+ const skillInstalled = isSkillInstalled(command.skillName);
237
+ const installed = isCommandInstalled(command.name, command.skillName);
238
+ // If skill is installed, command comes with it - no standalone uninstall
239
+ const actions = skillInstalled
240
+ ? [{ id: 'view', label: 'View', variant: 'default' }]
241
+ : installed
242
+ ? [
243
+ { id: 'view', label: 'View', variant: 'default' },
244
+ { id: 'uninstall', label: 'Uninstall', variant: 'danger' },
245
+ ]
246
+ : [
247
+ { id: 'view', label: 'View', variant: 'default' },
248
+ { id: 'install', label: 'Install', variant: 'primary' },
249
+ ];
250
+ 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 via skill" }), !skillInstalled && installed && _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
251
+ ? action.variant === 'danger'
252
+ ? colors.error
253
+ : colors.primary
254
+ : colors.bgSelected, color: selectedAction === index ? '#ffffff' : colors.textMuted, bold: selectedAction === index, children: [' ', action.label, ' '] }, action.id))) }))] }));
255
+ }
256
+ function AgentItem({ agent, isSelected }) {
257
+ const statusDisplay = agent.status === 'alpha' ? '[alpha]' : agent.status === 'beta' ? '[beta]' : '';
258
+ 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] })] }) }));
259
+ }
260
+ function AgentDetails({ agent, isFocused, selectedAction, }) {
261
+ if (!agent) {
262
+ return (_jsx(Box, { paddingLeft: 2, flexGrow: 1, children: _jsx(Text, { color: colors.textDim, children: "Select an agent" }) }));
263
+ }
264
+ const installed = isAgentInstalled(agent.name);
265
+ const statusDisplay = agent.status === 'alpha' ? '[alpha]' : agent.status === 'beta' ? '[beta]' : '';
266
+ const modeDisplay = agent.mode === 'primary' ? 'primary' : agent.mode === 'all' ? 'primary/subagent' : 'subagent';
267
+ const actions = installed
268
+ ? [
269
+ { id: 'view', label: 'View', variant: 'default' },
270
+ { id: 'uninstall', label: 'Uninstall', variant: 'danger' },
271
+ ]
272
+ : [
273
+ { id: 'view', label: 'View', variant: 'default' },
274
+ { id: 'install', label: 'Install', variant: 'primary' },
275
+ ];
276
+ 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, 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
277
+ ? action.variant === 'danger'
278
+ ? colors.error
279
+ : colors.primary
280
+ : colors.bgSelected, color: selectedAction === index ? '#ffffff' : colors.textMuted, bold: selectedAction === index, children: [' ', action.label, ' '] }, action.id))) }))] }));
281
+ }
282
+ function MarkdownLine({ line, inCodeBlock }) {
283
+ // Code block content
284
+ if (inCodeBlock) {
285
+ return _jsx(Text, { color: "#a5d6ff", children: line || ' ' });
286
+ }
287
+ // Code block delimiter
288
+ if (line.startsWith('```')) {
289
+ return _jsx(Text, { color: colors.textDim, children: line });
290
+ }
291
+ // Headers
292
+ if (line.startsWith('# ')) {
293
+ return _jsx(Text, { color: colors.text, bold: true, children: line.slice(2) });
294
+ }
295
+ if (line.startsWith('## ')) {
296
+ return _jsx(Text, { color: colors.text, bold: true, children: line.slice(3) });
297
+ }
298
+ if (line.startsWith('### ')) {
299
+ return _jsx(Text, { color: "#c9d1d9", bold: true, children: line.slice(4) });
300
+ }
301
+ // YAML frontmatter delimiter
302
+ if (line === '---') {
303
+ return _jsx(Text, { color: colors.textDim, children: line });
304
+ }
305
+ // List items
306
+ if (line.match(/^[\s]*[-*]\s/)) {
307
+ return _jsx(Text, { color: colors.textMuted, children: line });
308
+ }
309
+ // Blockquotes
310
+ if (line.startsWith('>')) {
311
+ return _jsx(Text, { color: "#8b949e", italic: true, children: line });
312
+ }
313
+ // Table rows
314
+ if (line.includes('|')) {
315
+ return _jsx(Text, { color: colors.textMuted, children: line });
316
+ }
317
+ // Default
318
+ return _jsx(Text, { color: colors.textMuted, children: line || ' ' });
319
+ }
320
+ function ReadmeViewer({ title, content, onClose, }) {
321
+ const [scrollOffset, setScrollOffset] = useState(0);
322
+ const lines = useMemo(() => content.split('\n'), [content]);
323
+ const maxVisible = 20;
324
+ // Pre-compute code block state for each line
325
+ const lineStates = useMemo(() => {
326
+ const states = [];
327
+ let inCode = false;
328
+ for (const line of lines) {
329
+ if (line.startsWith('```')) {
330
+ states.push(false); // Delimiter itself is not "in" code block for styling
331
+ inCode = !inCode;
332
+ }
333
+ else {
334
+ states.push(inCode);
335
+ }
336
+ }
337
+ return states;
338
+ }, [lines]);
339
+ // Max offset: when at end, we have top indicator + (maxVisible-1) content lines
340
+ // So max offset is lines.length - (maxVisible - 1) = lines.length - maxVisible + 1
341
+ const maxOffset = Math.max(0, lines.length - maxVisible + 1);
342
+ useInput((input, key) => {
343
+ if (key.escape) {
344
+ onClose();
345
+ return;
346
+ }
347
+ if (key.upArrow) {
348
+ setScrollOffset((prev) => Math.max(0, prev - 1));
349
+ }
350
+ if (key.downArrow) {
351
+ setScrollOffset((prev) => Math.min(maxOffset, prev + 1));
352
+ }
353
+ if (key.pageDown || input === ' ') {
354
+ setScrollOffset((prev) => Math.min(maxOffset, prev + maxVisible));
355
+ }
356
+ if (key.pageUp) {
357
+ setScrollOffset((prev) => Math.max(0, prev - maxVisible));
358
+ }
359
+ });
360
+ // Adjust visible lines based on whether indicators are shown
361
+ const showTopIndicator = scrollOffset > 0;
362
+ // Reserve space for bottom indicator if not at end
363
+ const contentLines = maxVisible - (showTopIndicator ? 1 : 0);
364
+ const endIndex = Math.min(scrollOffset + contentLines, lines.length);
365
+ const showBottomIndicator = endIndex < lines.length;
366
+ const actualContentLines = contentLines - (showBottomIndicator ? 1 : 0);
367
+ const visibleLines = lines.slice(scrollOffset, scrollOffset + actualContentLines);
368
+ 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" }) })] }));
369
+ }
370
+ function SettingsDetails({ onEditSettings, isFocused, }) {
371
+ const config = loadConfig();
372
+ const outputOptions = getOutputOptions();
373
+ 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 })] }), _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "Output: " }), _jsx(Text, { color: colors.text, children: outputOptions.find((o) => o.value === config.output_preference)?.label || config.output_preference })] })] }), _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" }) }))] }));
374
+ }
375
+ function SkillConfigScreen({ skill, onComplete, onCancel }) {
376
+ const configSchema = skill.config_schema || {};
377
+ const configKeys = Object.keys(configSchema);
378
+ const initialOverrides = useMemo(() => loadSkillOverrides(skill.name), [skill.name]);
379
+ // Initialize values from saved overrides or defaults
380
+ const [values, setValues] = useState(() => {
381
+ const initial = {};
382
+ for (const key of configKeys) {
383
+ const option = configSchema[key];
384
+ initial[key] = initialOverrides[key] ?? option.default ?? (option.type === ConfigOptionType.Boolean ? false : '');
385
+ }
386
+ return initial;
387
+ });
388
+ const [selectedIndex, setSelectedIndex] = useState(0);
389
+ const [editingField, setEditingField] = useState(null);
390
+ const [editValue, setEditValue] = useState('');
391
+ const handleSave = () => {
392
+ saveSkillOverrides(skill.name, values);
393
+ onComplete();
394
+ };
395
+ const handleSubmitEdit = () => {
396
+ if (editingField) {
397
+ setValues((prev) => ({ ...prev, [editingField]: editValue }));
398
+ setEditingField(null);
399
+ setEditValue('');
400
+ }
401
+ };
402
+ // Handle text input for string fields
403
+ useInput((input, key) => {
404
+ if (key.escape) {
405
+ setEditingField(null);
406
+ setEditValue('');
407
+ }
408
+ }, { isActive: editingField !== null });
409
+ // Handle navigation and actions when not editing
410
+ useInput((input, key) => {
411
+ if (key.escape) {
412
+ onCancel();
413
+ return;
414
+ }
415
+ if (key.upArrow) {
416
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
417
+ }
418
+ if (key.downArrow) {
419
+ // +1 for the Save button at the end
420
+ setSelectedIndex((prev) => Math.min(configKeys.length, prev + 1));
421
+ }
422
+ if (key.return) {
423
+ // Save button is at index === configKeys.length
424
+ if (selectedIndex === configKeys.length) {
425
+ handleSave();
426
+ return;
427
+ }
428
+ const key = configKeys[selectedIndex];
429
+ const option = configSchema[key];
430
+ if (option.type === ConfigOptionType.Boolean) {
431
+ // Toggle boolean
432
+ setValues((prev) => ({ ...prev, [key]: !prev[key] }));
433
+ }
434
+ else if (option.type === ConfigOptionType.String) {
435
+ // Enter edit mode for string
436
+ setEditingField(key);
437
+ setEditValue(String(values[key] || ''));
438
+ }
439
+ }
440
+ }, { isActive: editingField === null });
441
+ if (configKeys.length === 0) {
442
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "[" }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: "] " }), _jsxs(Text, { color: colors.text, bold: true, children: ["configure ", skill.name] })] }) }), _jsx(Text, { color: colors.textMuted, children: "This skill has no configuration options." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: "esc to go back" }) })] }));
443
+ }
444
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: colors.textDim, children: "[" }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: " " }), _jsx(Text, { color: colors.primary, children: "\u25CF" }), _jsx(Text, { color: colors.textDim, children: "] " }), _jsxs(Text, { color: colors.text, bold: true, children: ["configure ", skill.name] })] }) }), _jsxs(Box, { flexDirection: "column", children: [configKeys.map((key, index) => {
445
+ const option = configSchema[key];
446
+ const isSelected = selectedIndex === index;
447
+ const isEditing = editingField === key;
448
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { children: [_jsxs(Text, { color: colors.textDim, children: [isSelected ? '>' : ' ', " "] }), _jsx(Text, { color: isSelected ? colors.text : colors.textMuted, children: key })] }), _jsxs(Text, { color: colors.textDim, children: [" ", option.description] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, children: " " }), option.type === ConfigOptionType.Boolean ? (_jsxs(Text, { color: colors.text, children: ["[", values[key] ? 'x' : ' ', "] ", values[key] ? 'enabled' : 'disabled'] })) : isEditing ? (_jsxs(Box, { children: [_jsx(Text, { color: colors.textDim, children: '> ' }), _jsx(TextInput, { value: editValue, onChange: setEditValue, onSubmit: handleSubmitEdit })] })) : (_jsx(Text, { color: colors.text, children: String(values[key]) || '(not set)' }))] })] }, key));
449
+ }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { children: [_jsxs(Text, { color: colors.textDim, children: [selectedIndex === configKeys.length ? '>' : ' ', " "] }), _jsxs(Text, { backgroundColor: selectedIndex === configKeys.length ? colors.primary : undefined, color: selectedIndex === configKeys.length ? '#ffffff' : colors.textMuted, bold: selectedIndex === configKeys.length, children: [' ', "Save", ' '] })] }) })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.textDim, children: editingField ? 'enter save · esc cancel' : '↑↓ select · enter toggle/edit · esc back' }) })] }));
450
+ }
451
+ function App() {
452
+ const { exit } = useApp();
453
+ const tabs = [
454
+ { id: 'skills', label: 'Skills' },
455
+ { id: 'commands', label: 'Commands' },
456
+ { id: 'agents', label: 'Agents' },
457
+ { id: 'settings', label: 'Settings' },
458
+ ];
459
+ const [activeTab, setActiveTab] = useState('skills');
460
+ const [tabIndex, setTabIndex] = useState(0);
461
+ const [view, setView] = useState('welcome');
462
+ const [selectedIndex, setSelectedIndex] = useState(0);
463
+ const [selectedAction, setSelectedAction] = useState(0);
464
+ const [scrollOffset, setScrollOffset] = useState(0);
465
+ const [message, setMessage] = useState(null);
466
+ const [isEditingSettings, setIsEditingSettings] = useState(false);
467
+ const [readmeContent, setReadmeContent] = useState(null);
468
+ const MAX_VISIBLE_ITEMS = 6;
469
+ const skills = getBundledSkills();
470
+ const commands = getCommandsFromSkills();
471
+ const agents = getBundledAgents();
472
+ useInput((input, key) => {
473
+ if (message)
474
+ setMessage(null);
475
+ if (view === 'welcome')
476
+ return;
477
+ if (input === 'q') {
478
+ exit();
479
+ return;
480
+ }
481
+ if (view === 'menu') {
482
+ if (key.leftArrow) {
483
+ const newIndex = Math.max(0, tabIndex - 1);
484
+ setTabIndex(newIndex);
485
+ setActiveTab(tabs[newIndex].id);
486
+ setSelectedIndex(0);
487
+ setScrollOffset(0);
488
+ }
489
+ if (key.rightArrow) {
490
+ const newIndex = Math.min(tabs.length - 1, tabIndex + 1);
491
+ setTabIndex(newIndex);
492
+ setActiveTab(tabs[newIndex].id);
493
+ setSelectedIndex(0);
494
+ setScrollOffset(0);
495
+ }
496
+ if (key.upArrow) {
497
+ setSelectedIndex((prev) => {
498
+ const newIndex = Math.max(0, prev - 1);
499
+ // Scroll up if needed
500
+ if (newIndex < scrollOffset) {
501
+ setScrollOffset(newIndex);
502
+ }
503
+ return newIndex;
504
+ });
505
+ setSelectedAction(0);
506
+ }
507
+ if (key.downArrow) {
508
+ const maxIndex = activeTab === 'skills' ? skills.length - 1 : activeTab === 'commands' ? commands.length - 1 : 0;
509
+ setSelectedIndex((prev) => {
510
+ const newIndex = Math.min(maxIndex, prev + 1);
511
+ // Scroll down if needed
512
+ if (newIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
513
+ setScrollOffset(newIndex - MAX_VISIBLE_ITEMS + 1);
514
+ }
515
+ return newIndex;
516
+ });
517
+ setSelectedAction(0);
518
+ }
519
+ if (key.return) {
520
+ if (activeTab === 'skills' && skills.length > 0) {
521
+ setView('detail');
522
+ }
523
+ else if (activeTab === 'commands' && commands.length > 0) {
524
+ setView('detail');
525
+ }
526
+ else if (activeTab === 'agents' && agents.length > 0) {
527
+ setView('detail');
528
+ }
529
+ else if (activeTab === 'settings') {
530
+ setIsEditingSettings(true);
531
+ setView('setup');
532
+ }
533
+ }
534
+ }
535
+ else if (view === 'detail') {
536
+ if (key.escape || key.backspace) {
537
+ setView('menu');
538
+ setSelectedAction(0);
539
+ }
540
+ if (key.leftArrow) {
541
+ setSelectedAction((prev) => Math.max(0, prev - 1));
542
+ }
543
+ if (key.rightArrow) {
544
+ let maxActions = 0;
545
+ if (activeTab === 'skills') {
546
+ const skill = skills[selectedIndex];
547
+ const installed = skill ? isSkillInstalled(skill.name) : false;
548
+ maxActions = installed ? 2 : 1; // View, Configure, Uninstall or View, Install
549
+ }
550
+ else if (activeTab === 'agents') {
551
+ maxActions = 1; // View, Install/Uninstall
552
+ }
553
+ else if (activeTab === 'commands') {
554
+ const command = commands[selectedIndex];
555
+ // If parent skill is installed, only View is available
556
+ const skillInstalled = command ? isSkillInstalled(command.skillName) : false;
557
+ maxActions = skillInstalled ? 0 : 1;
558
+ }
559
+ setSelectedAction((prev) => Math.min(maxActions, prev + 1));
560
+ }
561
+ if (key.return && activeTab === 'skills') {
562
+ const skill = skills[selectedIndex];
563
+ if (skill) {
564
+ const installed = isSkillInstalled(skill.name);
565
+ // Actions: installed = [View, Configure, Uninstall], not installed = [View, Install]
566
+ if (selectedAction === 0) {
567
+ // View
568
+ const skillMdPath = join(getBundledSkillsDir(), skill.name, 'SKILL.md');
569
+ if (existsSync(skillMdPath)) {
570
+ const content = readFileSync(skillMdPath, 'utf-8');
571
+ setReadmeContent({ title: `${skill.name}/SKILL.md`, content });
572
+ setView('readme');
573
+ }
574
+ }
575
+ else if (installed && selectedAction === 1) {
576
+ // Configure
577
+ setView('configure');
578
+ }
579
+ else if (installed && selectedAction === 2) {
580
+ // Uninstall
581
+ const result = uninstallSkill(skill.name);
582
+ setMessage({
583
+ text: result.success ? `✓ Uninstalled ${skill.name}` : `✗ ${result.message}`,
584
+ type: result.success ? 'success' : 'error',
585
+ });
586
+ if (result.success) {
587
+ setView('menu');
588
+ setSelectedAction(0);
589
+ }
590
+ }
591
+ else if (!installed && selectedAction === 1) {
592
+ // Install
593
+ const result = installSkill(skill.name);
594
+ setMessage({
595
+ text: result.success ? `✓ Installed ${skill.name}` : `✗ ${result.message}`,
596
+ type: result.success ? 'success' : 'error',
597
+ });
598
+ if (result.success) {
599
+ setView('menu');
600
+ setSelectedAction(0);
601
+ }
602
+ }
603
+ }
604
+ }
605
+ if (key.return && activeTab === 'agents') {
606
+ const agent = agents[selectedIndex];
607
+ if (agent) {
608
+ const installed = isAgentInstalled(agent.name);
609
+ if (selectedAction === 0) {
610
+ // View
611
+ const agentMdPath = join(getBundledAgentsDir(), agent.name, 'AGENT.md');
612
+ if (existsSync(agentMdPath)) {
613
+ const content = readFileSync(agentMdPath, 'utf-8');
614
+ setReadmeContent({ title: `${agent.name}/AGENT.md`, content });
615
+ setView('readme');
616
+ }
617
+ }
618
+ else if (installed && selectedAction === 1) {
619
+ // Uninstall
620
+ const result = uninstallAgent(agent.name);
621
+ setMessage({
622
+ text: result.success ? `✓ Uninstalled ${agent.name}` : `✗ ${result.message}`,
623
+ type: result.success ? 'success' : 'error',
624
+ });
625
+ if (result.success) {
626
+ setView('menu');
627
+ setSelectedAction(0);
628
+ }
629
+ }
630
+ else if (!installed && selectedAction === 1) {
631
+ // Install
632
+ const result = installAgent(agent.name);
633
+ setMessage({
634
+ text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
635
+ type: result.success ? 'success' : 'error',
636
+ });
637
+ if (result.success) {
638
+ setView('menu');
639
+ setSelectedAction(0);
640
+ }
641
+ }
642
+ }
643
+ }
644
+ if (key.return && activeTab === 'commands') {
645
+ const command = commands[selectedIndex];
646
+ if (command) {
647
+ const installed = isCommandInstalled(command.name, command.skillName);
648
+ // Command file: extract part after skill name (e.g., "comments check" → "check.md")
649
+ const cmdPart = command.name.startsWith(command.skillName + ' ')
650
+ ? command.name.slice(command.skillName.length + 1)
651
+ : command.name;
652
+ const commandMdPath = join(getBundledSkillsDir(), command.skillName, 'commands', `${cmdPart}.md`);
653
+ if (selectedAction === 0) {
654
+ // View
655
+ if (existsSync(commandMdPath)) {
656
+ const content = readFileSync(commandMdPath, 'utf-8');
657
+ setReadmeContent({ title: `/${command.name}`, content });
658
+ setView('readme');
659
+ }
660
+ }
661
+ else if (installed && selectedAction === 1) {
662
+ // Uninstall
663
+ const result = uninstallCommand(command.name, command.skillName);
664
+ setMessage({
665
+ text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
666
+ type: result.success ? 'success' : 'error',
667
+ });
668
+ if (result.success) {
669
+ setView('menu');
670
+ setSelectedAction(0);
671
+ }
672
+ }
673
+ else if (!installed && selectedAction === 1) {
674
+ // Install
675
+ const result = installCommand(command.name, command.skillName);
676
+ setMessage({
677
+ text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
678
+ type: result.success ? 'success' : 'error',
679
+ });
680
+ if (result.success) {
681
+ setView('menu');
682
+ setSelectedAction(0);
683
+ }
684
+ }
685
+ }
686
+ }
687
+ }
688
+ });
689
+ const selectedSkill = activeTab === 'skills' ? skills[selectedIndex] ?? null : null;
690
+ const selectedCommand = activeTab === 'commands' ? commands[selectedIndex] ?? null : null;
691
+ const selectedAgent = activeTab === 'agents' ? agents[selectedIndex] ?? null : null;
692
+ if (view === 'welcome') {
693
+ return (_jsx(WelcomeScreen, { onContinue: () => {
694
+ // If no config exists, show setup first
695
+ if (!configExists()) {
696
+ setView('setup');
697
+ }
698
+ else {
699
+ setView('menu');
700
+ }
701
+ } }));
702
+ }
703
+ if (view === 'setup') {
704
+ return (_jsx(SetupScreen, { onComplete: () => {
705
+ setIsEditingSettings(false);
706
+ setView('menu');
707
+ }, onSkip: () => {
708
+ setIsEditingSettings(false);
709
+ setView('menu');
710
+ }, initialConfig: isEditingSettings ? loadConfig() : undefined }));
711
+ }
712
+ if (view === 'readme' && readmeContent) {
713
+ return (_jsx(ReadmeViewer, { title: readmeContent.title, content: readmeContent.content, onClose: () => {
714
+ setReadmeContent(null);
715
+ setView('detail');
716
+ } }));
717
+ }
718
+ if (view === 'configure' && selectedSkill) {
719
+ return (_jsx(SkillConfigScreen, { skill: selectedSkill, onComplete: () => {
720
+ setMessage({ text: `✓ Configuration saved for ${selectedSkill.name}`, type: 'success' });
721
+ setView('detail');
722
+ }, onCancel: () => {
723
+ setView('detail');
724
+ } }));
725
+ }
726
+ 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" })] }) }), _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" }) }))] }), _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 })), message && (_jsx(Box, { position: "absolute", marginTop: 12, children: _jsx(Text, { color: message.type === 'success' ? colors.success : colors.error, children: message.text }) }))] }));
727
+ }
728
+ export async function tuiCommand() {
729
+ // Enter alternate screen (fullscreen mode)
730
+ process.stdout.write('\x1b[?1049h');
731
+ process.stdout.write('\x1b[H'); // Move cursor to top-left
732
+ const { unmount, waitUntilExit } = render(_jsx(App, {}));
733
+ await waitUntilExit();
734
+ // Leave alternate screen
735
+ process.stdout.write('\x1b[?1049l');
736
+ }
737
+ //# sourceMappingURL=tui.js.map