@orderful/droid 0.10.5 → 0.11.1
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.
- package/.claude/CLAUDE.md +13 -6
- package/CHANGELOG.md +19 -0
- package/README.md +23 -24
- package/dist/bin/droid.js +9 -9
- package/dist/bin/droid.js.map +1 -1
- package/dist/commands/install.d.ts +1 -1
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +24 -23
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/setup.d.ts +3 -3
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +32 -28
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +60 -53
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +213 -319
- package/dist/commands/tui.js.map +1 -1
- package/dist/commands/uninstall.d.ts +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +15 -6
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts +2 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +9 -9
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/agents.d.ts +11 -10
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/lib/agents.js +52 -54
- package/dist/lib/agents.js.map +1 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +42 -5
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/platforms.d.ts +41 -0
- package/dist/lib/platforms.d.ts.map +1 -0
- package/dist/lib/platforms.js +52 -0
- package/dist/lib/platforms.js.map +1 -0
- package/dist/lib/skills.d.ts +19 -11
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +125 -99
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/tools.d.ts +30 -0
- package/dist/lib/tools.d.ts.map +1 -0
- package/dist/lib/tools.js +116 -0
- package/dist/lib/tools.js.map +1 -0
- package/dist/lib/types.d.ts +45 -2
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +24 -5
- package/dist/lib/types.js.map +1 -1
- package/dist/tools/brain/TOOL.yaml +27 -0
- package/dist/tools/coach/TOOL.yaml +21 -0
- package/dist/tools/code-review/TOOL.yaml +18 -0
- package/dist/tools/comments/TOOL.yaml +27 -0
- package/dist/{skills → tools/comments/skills}/comments/SKILL.md +12 -3
- package/dist/{skills → tools/comments/skills}/comments/SKILL.yaml +1 -1
- package/dist/tools/project/TOOL.yaml +26 -0
- package/package.json +2 -2
- package/src/bin/droid.ts +9 -9
- package/src/commands/install.ts +24 -23
- package/src/commands/setup.test.ts +2 -2
- package/src/commands/setup.ts +33 -29
- package/src/commands/skills.ts +63 -64
- package/src/commands/tui.tsx +432 -578
- package/src/commands/uninstall.ts +17 -6
- package/src/commands/update.ts +10 -10
- package/src/lib/agents.ts +58 -58
- package/src/lib/config.test.ts +0 -10
- package/src/lib/config.ts +47 -5
- package/src/lib/platforms.ts +59 -0
- package/src/lib/skills.test.ts +53 -28
- package/src/lib/skills.ts +134 -101
- package/src/lib/tools.ts +140 -0
- package/src/lib/types.test.ts +15 -7
- package/src/lib/types.ts +63 -2
- package/src/tools/brain/TOOL.yaml +27 -0
- package/src/tools/coach/TOOL.yaml +21 -0
- package/src/tools/code-review/TOOL.yaml +18 -0
- package/src/tools/comments/TOOL.yaml +27 -0
- package/src/{skills → tools/comments/skills}/comments/SKILL.md +12 -3
- package/src/{skills → tools/comments/skills}/comments/SKILL.yaml +1 -1
- package/src/tools/project/TOOL.yaml +26 -0
- package/dist/agents/README.md +0 -137
- package/src/agents/README.md +0 -137
- /package/dist/{skills → tools}/README.md +0 -0
- /package/dist/{skills → tools}/brain/commands/README.md +0 -0
- /package/dist/{skills → tools}/brain/commands/brain.md +0 -0
- /package/dist/{skills → tools}/brain/commands/scratchpad.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/SKILL.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/SKILL.yaml +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/references/metadata.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/references/naming.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/references/templates.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain/references/workflows.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain-obsidian/SKILL.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain-obsidian/SKILL.yaml +0 -0
- /package/dist/{skills → tools/brain/skills}/brain-obsidian/references/templates.md +0 -0
- /package/dist/{skills → tools/brain/skills}/brain-obsidian/references/workflows.md +0 -0
- /package/dist/{skills → tools}/coach/commands/README.md +0 -0
- /package/dist/{skills → tools}/coach/commands/coach.md +0 -0
- /package/dist/{skills → tools/coach/skills}/coach/SKILL.md +0 -0
- /package/dist/{skills → tools/coach/skills}/coach/SKILL.yaml +0 -0
- /package/dist/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.md +0 -0
- /package/dist/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -0
- /package/dist/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.md +0 -0
- /package/dist/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -0
- /package/dist/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.md +0 -0
- /package/dist/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -0
- /package/dist/{skills → tools}/code-review/agents/type-reviewer/AGENT.md +0 -0
- /package/dist/{skills → tools}/code-review/agents/type-reviewer/AGENT.yaml +0 -0
- /package/dist/{skills → tools}/code-review/commands/code-review.md +0 -0
- /package/dist/{skills → tools/code-review/skills}/code-review/SKILL.md +0 -0
- /package/dist/{skills → tools/code-review/skills}/code-review/SKILL.yaml +0 -0
- /package/dist/{skills → tools}/comments/commands/README.md +0 -0
- /package/dist/{skills → tools}/comments/commands/comments.md +0 -0
- /package/dist/{skills → tools}/project/commands/README.md +0 -0
- /package/dist/{skills → tools}/project/commands/project.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/SKILL.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/SKILL.yaml +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/changelog.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/creating.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/loading.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/templates.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/updating.md +0 -0
- /package/dist/{skills → tools/project/skills}/project/references/versioning.md +0 -0
- /package/src/{skills → tools}/README.md +0 -0
- /package/src/{skills → tools}/brain/commands/README.md +0 -0
- /package/src/{skills → tools}/brain/commands/brain.md +0 -0
- /package/src/{skills → tools}/brain/commands/scratchpad.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/SKILL.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/SKILL.yaml +0 -0
- /package/src/{skills → tools/brain/skills}/brain/references/metadata.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/references/naming.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/references/templates.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain/references/workflows.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain-obsidian/SKILL.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain-obsidian/SKILL.yaml +0 -0
- /package/src/{skills → tools/brain/skills}/brain-obsidian/references/templates.md +0 -0
- /package/src/{skills → tools/brain/skills}/brain-obsidian/references/workflows.md +0 -0
- /package/src/{skills → tools}/coach/commands/README.md +0 -0
- /package/src/{skills → tools}/coach/commands/coach.md +0 -0
- /package/src/{skills → tools/coach/skills}/coach/SKILL.md +0 -0
- /package/src/{skills → tools/coach/skills}/coach/SKILL.yaml +0 -0
- /package/src/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.md +0 -0
- /package/src/{skills → tools}/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -0
- /package/src/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.md +0 -0
- /package/src/{skills → tools}/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -0
- /package/src/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.md +0 -0
- /package/src/{skills → tools}/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -0
- /package/src/{skills → tools}/code-review/agents/type-reviewer/AGENT.md +0 -0
- /package/src/{skills → tools}/code-review/agents/type-reviewer/AGENT.yaml +0 -0
- /package/src/{skills → tools}/code-review/commands/code-review.md +0 -0
- /package/src/{skills → tools/code-review/skills}/code-review/SKILL.md +0 -0
- /package/src/{skills → tools/code-review/skills}/code-review/SKILL.yaml +0 -0
- /package/src/{skills → tools}/comments/commands/README.md +0 -0
- /package/src/{skills → tools}/comments/commands/comments.md +0 -0
- /package/src/{skills → tools}/project/commands/README.md +0 -0
- /package/src/{skills → tools}/project/commands/project.md +0 -0
- /package/src/{skills → tools/project/skills}/project/SKILL.md +0 -0
- /package/src/{skills → tools/project/skills}/project/SKILL.yaml +0 -0
- /package/src/{skills → tools/project/skills}/project/references/changelog.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/creating.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/loading.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/templates.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/updating.md +0 -0
- /package/src/{skills → tools/project/skills}/project/references/versioning.md +0 -0
package/src/commands/tui.tsx
CHANGED
|
@@ -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,
|
|
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 {
|
|
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 {
|
|
20
|
-
import {
|
|
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 = '
|
|
25
|
-
type View = 'welcome' | 'setup' | 'menu' | 'detail' | 'configure' | 'readme';
|
|
26
|
-
type SetupStep = '
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (dimmed) {
|
|
57
|
+
return (
|
|
58
|
+
<Text color={colors.textDim}>{displayLabel}</Text>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
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
|
|
105
|
-
const installed = new Set<
|
|
124
|
+
function detectInstalledPlatforms(): Set<Platform> {
|
|
125
|
+
const installed = new Set<Platform>();
|
|
106
126
|
try {
|
|
107
127
|
execSync('claude --version', { stdio: 'ignore' });
|
|
108
|
-
installed.add(
|
|
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(
|
|
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
|
|
122
|
-
const installed =
|
|
123
|
-
if (installed.has(
|
|
124
|
-
if (installed.has(
|
|
125
|
-
return
|
|
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>('
|
|
330
|
-
const [
|
|
331
|
-
initialConfig?.
|
|
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
|
|
339
|
-
const
|
|
340
|
-
{ label: 'Claude Code', value:
|
|
341
|
-
{ label: 'OpenCode', value:
|
|
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[] = ['
|
|
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('
|
|
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 === '
|
|
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 === '
|
|
345
|
+
if (step === 'platform') {
|
|
376
346
|
if (key.upArrow) setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
377
|
-
if (key.downArrow) setSelectedIndex((prev) => Math.min(
|
|
347
|
+
if (key.downArrow) setSelectedIndex((prev) => Math.min(platformOptions.length - 1, prev + 1));
|
|
378
348
|
if (key.return) {
|
|
379
|
-
|
|
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
|
-
...
|
|
386
|
-
|
|
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
|
-
|
|
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 === '
|
|
382
|
+
if (step === 'platform') {
|
|
412
383
|
return (
|
|
413
384
|
<Box flexDirection="column" padding={1}>
|
|
414
385
|
{renderHeader()}
|
|
415
|
-
<Text color={colors.text}>Which
|
|
386
|
+
<Text color={colors.text}>Which platform are you using?</Text>
|
|
416
387
|
<Box flexDirection="column" marginTop={1}>
|
|
417
|
-
{
|
|
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
|
-
{
|
|
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}>
|
|
466
|
-
<Text color={colors.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
|
|
504
|
-
|
|
474
|
+
function ToolItem({
|
|
475
|
+
tool,
|
|
505
476
|
isSelected,
|
|
506
477
|
isActive,
|
|
507
478
|
}: {
|
|
508
|
-
|
|
479
|
+
tool: ToolManifest;
|
|
509
480
|
isSelected: boolean;
|
|
510
481
|
isActive: boolean;
|
|
511
482
|
}) {
|
|
512
|
-
const installed =
|
|
513
|
-
const
|
|
514
|
-
const updateStatus =
|
|
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}>{
|
|
521
|
-
{installed &&
|
|
522
|
-
{installed && <Text color={colors.success}>
|
|
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
|
|
530
|
-
|
|
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
|
-
|
|
509
|
+
tool: ToolManifest | null;
|
|
555
510
|
isFocused: boolean;
|
|
556
511
|
selectedAction: number;
|
|
557
512
|
}) {
|
|
558
|
-
if (!
|
|
513
|
+
if (!tool) {
|
|
559
514
|
return (
|
|
560
515
|
<Box paddingLeft={2} flexGrow={1}>
|
|
561
|
-
<Text color={colors.textDim}>Select a
|
|
516
|
+
<Text color={colors.textDim}>Select a tool</Text>
|
|
562
517
|
</Box>
|
|
563
518
|
);
|
|
564
519
|
}
|
|
565
520
|
|
|
566
|
-
const installed =
|
|
567
|
-
const
|
|
568
|
-
const
|
|
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: '
|
|
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: '
|
|
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>{
|
|
541
|
+
<Text color={colors.text} bold>{tool.name}</Text>
|
|
587
542
|
|
|
588
543
|
<Box marginTop={1}>
|
|
589
544
|
<Text color={colors.textDim}>
|
|
590
|
-
{
|
|
591
|
-
{
|
|
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
|
|
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}>{
|
|
555
|
+
<Text color={colors.textMuted}>{tool.description}</Text>
|
|
601
556
|
</Box>
|
|
602
557
|
|
|
603
|
-
{
|
|
558
|
+
{/* Colored component badges */}
|
|
559
|
+
<Box flexDirection="column" marginTop={1}>
|
|
560
|
+
<Text color={colors.textDim}>Includes:</Text>
|
|
604
561
|
<Box marginTop={1}>
|
|
605
|
-
<
|
|
606
|
-
Commands: {skillCommands.map((c) => `/${c.name}`).join(', ')}
|
|
607
|
-
</Text>
|
|
562
|
+
<ComponentBadges tool={tool} />
|
|
608
563
|
</Box>
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
-
{' '}"{trigger}"
|
|
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}>
|
|
946
|
+
<Text color={colors.textDim}>Platform: </Text>
|
|
997
947
|
<Text color={colors.text}>
|
|
998
|
-
{config.
|
|
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: '
|
|
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>('
|
|
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 === '
|
|
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 === '
|
|
1426
|
-
const
|
|
1427
|
-
const installed =
|
|
1428
|
-
const hasUpdate =
|
|
1429
|
-
//
|
|
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 === '
|
|
1442
|
-
const
|
|
1443
|
-
if (
|
|
1444
|
-
const installed =
|
|
1445
|
-
const
|
|
1446
|
-
// Build actions array to match
|
|
1447
|
-
const
|
|
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: '
|
|
1450
|
-
...(
|
|
1383
|
+
{ id: 'explore' },
|
|
1384
|
+
...(toolUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
|
|
1451
1385
|
{ id: 'configure' },
|
|
1452
1386
|
{ id: 'uninstall' },
|
|
1453
1387
|
]
|
|
1454
|
-
: [{ id: '
|
|
1388
|
+
: [{ id: 'explore' }, { id: 'install' }];
|
|
1455
1389
|
|
|
1456
|
-
const actionId =
|
|
1390
|
+
const actionId = toolActions[selectedAction]?.id;
|
|
1457
1391
|
|
|
1458
|
-
if (actionId === '
|
|
1459
|
-
|
|
1460
|
-
|
|
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
|
-
|
|
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 ? `✓ ${
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
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 ${
|
|
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
|
|
1494
|
-
const configSchema =
|
|
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
|
|
1568
|
-
|
|
1569
|
-
const
|
|
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' &&
|
|
1518
|
+
if (view === 'configure' && selectedSkillForConfig) {
|
|
1620
1519
|
return (
|
|
1621
1520
|
<SkillConfigScreen
|
|
1622
|
-
skill={
|
|
1521
|
+
skill={selectedSkillForConfig}
|
|
1623
1522
|
onComplete={() => {
|
|
1624
|
-
setMessage({ text: `✓ Configuration saved for ${
|
|
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 === '
|
|
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
|
-
{
|
|
1671
|
-
<
|
|
1672
|
-
key={
|
|
1673
|
-
|
|
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 <
|
|
1582
|
+
{scrollOffset + MAX_VISIBLE_ITEMS < tools.length && (
|
|
1679
1583
|
<Box paddingX={1}>
|
|
1680
|
-
<Text color={colors.textDim}>↓ {
|
|
1584
|
+
<Text color={colors.textDim}>↓ {tools.length - scrollOffset - MAX_VISIBLE_ITEMS} more</Text>
|
|
1681
1585
|
</Box>
|
|
1682
1586
|
)}
|
|
1683
|
-
{
|
|
1587
|
+
{tools.length > MAX_VISIBLE_ITEMS && (
|
|
1684
1588
|
<Box paddingX={1} marginTop={1}>
|
|
1685
|
-
<Text color={colors.textDim}>{
|
|
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 === '
|
|
1757
|
-
<
|
|
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
|
}
|