@orderful/droid 0.23.0 → 0.25.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.
- package/.eslintrc.json +6 -4
- package/AGENTS.md +58 -0
- package/CHANGELOG.md +44 -0
- package/README.md +11 -6
- package/dist/bin/droid.js +384 -170
- package/dist/commands/config.d.ts +15 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/exec.d.ts +10 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/index.js +171 -33
- package/dist/lib/migrations.d.ts.map +1 -1
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/tools/codex/TOOL.yaml +2 -2
- package/dist/tools/codex/agents/codex-document-processor.md +8 -4
- package/dist/tools/codex/commands/codex.md +18 -10
- package/dist/tools/codex/skills/droid-codex/SKILL.md +140 -67
- package/dist/tools/codex/skills/droid-codex/references/creating.md +13 -51
- package/dist/tools/codex/skills/droid-codex/references/decisions.md +15 -19
- package/dist/tools/codex/skills/droid-codex/references/loading.md +49 -13
- package/dist/tools/codex/skills/droid-codex/references/topics.md +14 -12
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts +31 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts.map +1 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts +20 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts.map +1 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts +23 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts.map +1 -0
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
- package/package.json +1 -1
- package/src/bin/droid.ts +9 -0
- package/src/commands/config.ts +38 -4
- package/src/commands/exec.ts +96 -0
- package/src/commands/install.ts +1 -1
- package/src/commands/setup.ts +6 -6
- package/src/commands/tui.tsx +254 -175
- package/src/lib/migrations.ts +103 -10
- package/src/lib/quotes.ts +6 -6
- package/src/lib/skills.ts +168 -45
- package/src/tools/codex/TOOL.yaml +2 -2
- package/src/tools/codex/agents/codex-document-processor.md +8 -4
- package/src/tools/codex/commands/codex.md +18 -10
- package/src/tools/codex/skills/droid-codex/SKILL.md +140 -67
- package/src/tools/codex/skills/droid-codex/references/creating.md +13 -51
- package/src/tools/codex/skills/droid-codex/references/decisions.md +15 -19
- package/src/tools/codex/skills/droid-codex/references/loading.md +49 -13
- package/src/tools/codex/skills/droid-codex/references/topics.md +14 -12
- package/src/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
- package/src/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
- package/src/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
- package/src/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { loadConfig } from '../lib/config';
|
|
6
|
+
import { getSkillsPath } from '../lib/platforms';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Find the appropriate runtime for a tool file
|
|
10
|
+
*/
|
|
11
|
+
function getRuntime(toolPath: string): { cmd: string; args: string[] } | null {
|
|
12
|
+
if (toolPath.endsWith('.ts') || toolPath.endsWith('.js')) {
|
|
13
|
+
// Prefer bun, fall back to node
|
|
14
|
+
// For .ts files, bun can run directly; node needs tsx or ts-node
|
|
15
|
+
return { cmd: 'bun', args: ['run', toolPath] };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (toolPath.endsWith('.py')) {
|
|
19
|
+
return { cmd: 'python3', args: [toolPath] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Execute a tool script
|
|
27
|
+
*
|
|
28
|
+
* Usage: droid exec <tool> <script> [args...]
|
|
29
|
+
*
|
|
30
|
+
* Finds the script in the tool's scripts/ directory and runs it with the
|
|
31
|
+
* appropriate runtime (bun for .ts/.js, python3 for .py)
|
|
32
|
+
*/
|
|
33
|
+
export async function execCommand(
|
|
34
|
+
tool: string,
|
|
35
|
+
script: string,
|
|
36
|
+
args: string[]
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
const config = loadConfig();
|
|
39
|
+
const skillsPath = getSkillsPath(config.platform);
|
|
40
|
+
const toolDir = join(skillsPath, tool);
|
|
41
|
+
|
|
42
|
+
// Check tool exists
|
|
43
|
+
if (!existsSync(toolDir)) {
|
|
44
|
+
console.error(chalk.red(`Tool '${tool}' not found at ${toolDir}`));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Find the script file
|
|
49
|
+
const scriptsDir = join(toolDir, 'scripts');
|
|
50
|
+
if (!existsSync(scriptsDir)) {
|
|
51
|
+
console.error(chalk.red(`No scripts directory in tool '${tool}'`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Try common extensions
|
|
56
|
+
const extensions = ['.ts', '.js', '.py'];
|
|
57
|
+
let scriptPath: string | null = null;
|
|
58
|
+
|
|
59
|
+
for (const ext of extensions) {
|
|
60
|
+
const candidate = join(scriptsDir, script + ext);
|
|
61
|
+
if (existsSync(candidate)) {
|
|
62
|
+
scriptPath = candidate;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!scriptPath) {
|
|
68
|
+
console.error(chalk.red(`Script '${script}' not found in ${scriptsDir}`));
|
|
69
|
+
console.error(chalk.gray(`Looked for: ${extensions.map((e) => script + e).join(', ')}`));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Get runtime
|
|
74
|
+
const runtime = getRuntime(scriptPath);
|
|
75
|
+
if (!runtime) {
|
|
76
|
+
console.error(chalk.red(`Unsupported script type: ${scriptPath}`));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Execute the script
|
|
81
|
+
const fullArgs = [...runtime.args, ...args];
|
|
82
|
+
|
|
83
|
+
const child = spawn(runtime.cmd, fullArgs, {
|
|
84
|
+
stdio: 'inherit',
|
|
85
|
+
cwd: process.cwd(),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
child.on('error', (err) => {
|
|
89
|
+
console.error(chalk.red(`Failed to execute tool: ${err.message}`));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
child.on('close', (code) => {
|
|
94
|
+
process.exit(code ?? 0);
|
|
95
|
+
});
|
|
96
|
+
}
|
package/src/commands/install.ts
CHANGED
|
@@ -45,7 +45,7 @@ export async function installCommand(toolName: string): Promise<void> {
|
|
|
45
45
|
// Show next steps
|
|
46
46
|
console.log(chalk.gray('\nNext steps:'));
|
|
47
47
|
console.log(chalk.gray(' - Run your AI tool to start using it'));
|
|
48
|
-
console.log(chalk.gray(
|
|
48
|
+
console.log(chalk.gray(' - Reconfigure anytime with `droid` → Configure'));
|
|
49
49
|
} else {
|
|
50
50
|
console.error(chalk.red(`✗ ${result.message}`));
|
|
51
51
|
process.exit(1);
|
package/src/commands/setup.ts
CHANGED
|
@@ -280,17 +280,17 @@ export async function setupCommand(): Promise<void> {
|
|
|
280
280
|
console.log(chalk.yellow(' You may need to manually configure your platform'));
|
|
281
281
|
} else if (answers.platform === Platform.ClaudeCode) {
|
|
282
282
|
if (added.length > 0) {
|
|
283
|
-
console.log(chalk.green(
|
|
283
|
+
console.log(chalk.green('✓ Added droid permissions to Claude Code settings'));
|
|
284
284
|
} else if (alreadyPresent) {
|
|
285
|
-
console.log(chalk.gray(
|
|
285
|
+
console.log(chalk.gray(' Droid permissions already configured in Claude Code'));
|
|
286
286
|
}
|
|
287
287
|
} else if (answers.platform === Platform.OpenCode) {
|
|
288
288
|
if (added.length > 0) {
|
|
289
|
-
console.log(chalk.green(
|
|
290
|
-
console.log(chalk.gray(
|
|
291
|
-
console.log(chalk.gray(
|
|
289
|
+
console.log(chalk.green('✓ Added opencode-skills plugin to OpenCode config'));
|
|
290
|
+
console.log(chalk.gray(' This enables Claude Code-style skills in OpenCode'));
|
|
291
|
+
console.log(chalk.gray(' Restart OpenCode to activate the plugin'));
|
|
292
292
|
} else if (alreadyPresent) {
|
|
293
|
-
console.log(chalk.gray(
|
|
293
|
+
console.log(chalk.gray(' opencode-skills plugin already configured in OpenCode'));
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
package/src/commands/tui.tsx
CHANGED
|
@@ -6,10 +6,15 @@ import {
|
|
|
6
6
|
uninstallSkill,
|
|
7
7
|
updateSkill,
|
|
8
8
|
} from '../lib/skills';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getBundledTools,
|
|
11
|
+
isToolInstalled,
|
|
12
|
+
getToolUpdateStatus,
|
|
13
|
+
} from '../lib/tools';
|
|
10
14
|
import { configExists, loadConfig, getAutoUpdateConfig } from '../lib/config';
|
|
11
15
|
import { Platform, type ConfigOption, type ToolManifest } from '../lib/types';
|
|
12
16
|
import { getVersion } from '../lib/version';
|
|
17
|
+
import { runToolMigrations } from '../lib/migrations';
|
|
13
18
|
import { type Tab, type View } from './tui/types';
|
|
14
19
|
import { colors, MAX_VISIBLE_ITEMS } from './tui/constants';
|
|
15
20
|
import { TabBar } from './tui/components/TabBar';
|
|
@@ -41,29 +46,47 @@ function App() {
|
|
|
41
46
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
42
47
|
const [selectedAction, setSelectedAction] = useState(0);
|
|
43
48
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
44
|
-
const [message, setMessage] = useState<{
|
|
49
|
+
const [message, setMessage] = useState<{
|
|
50
|
+
text: string;
|
|
51
|
+
type: 'success' | 'error';
|
|
52
|
+
} | null>(null);
|
|
45
53
|
const [isEditingSettings, setIsEditingSettings] = useState(false);
|
|
46
|
-
const [readmeContent, setReadmeContent] = useState<{
|
|
54
|
+
const [readmeContent, setReadmeContent] = useState<{
|
|
55
|
+
title: string;
|
|
56
|
+
content: string;
|
|
57
|
+
} | null>(null);
|
|
47
58
|
const [previousView, setPreviousView] = useState<View>('detail'); // Track where to return from readme
|
|
48
59
|
|
|
49
|
-
const { updateInfo, isUpdating, handleUpdate, handleAlwaysUpdate } =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const { updateInfo, isUpdating, handleUpdate, handleAlwaysUpdate } =
|
|
61
|
+
useAppUpdate({
|
|
62
|
+
onUpdateSuccess: (msg) => {
|
|
63
|
+
exitMessage = msg;
|
|
64
|
+
},
|
|
65
|
+
onUpdateFailure: (error) => {
|
|
66
|
+
setMessage({ text: error, type: 'error' });
|
|
67
|
+
if (!configExists()) {
|
|
68
|
+
setView('setup');
|
|
69
|
+
} else {
|
|
70
|
+
setView('menu');
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Run droid meta-tool migration on startup (sync installed tools across platforms)
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const droidVersion = getVersion();
|
|
78
|
+
runToolMigrations('droid', droidVersion);
|
|
79
|
+
}, []);
|
|
62
80
|
|
|
63
81
|
// Auto-update app if enabled and update available
|
|
64
82
|
useEffect(() => {
|
|
65
83
|
const autoUpdateConfig = getAutoUpdateConfig();
|
|
66
|
-
if (
|
|
84
|
+
if (
|
|
85
|
+
autoUpdateConfig.app &&
|
|
86
|
+
updateInfo.hasUpdate &&
|
|
87
|
+
view === 'welcome' &&
|
|
88
|
+
!isUpdating
|
|
89
|
+
) {
|
|
67
90
|
handleUpdate();
|
|
68
91
|
}
|
|
69
92
|
}, [updateInfo.hasUpdate, view, isUpdating, handleUpdate]);
|
|
@@ -122,163 +145,201 @@ function App() {
|
|
|
122
145
|
// Keep skills for configure view (tools configure via their primary skill)
|
|
123
146
|
const skills = getBundledSkills();
|
|
124
147
|
|
|
125
|
-
useInput(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (input === 'q') {
|
|
129
|
-
exit();
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
148
|
+
useInput(
|
|
149
|
+
(input, key) => {
|
|
150
|
+
if (message) setMessage(null);
|
|
132
151
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
setTabIndex(newIndex);
|
|
137
|
-
setActiveTab(tabs[newIndex].id);
|
|
138
|
-
setSelectedIndex(0);
|
|
139
|
-
setScrollOffset(0);
|
|
152
|
+
if (input === 'q') {
|
|
153
|
+
exit();
|
|
154
|
+
return;
|
|
140
155
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
setSelectedIndex((prev) => {
|
|
150
|
-
const newIndex = Math.max(0, prev - 1);
|
|
151
|
-
// Scroll up if needed
|
|
152
|
-
if (newIndex < scrollOffset) {
|
|
153
|
-
setScrollOffset(newIndex);
|
|
154
|
-
}
|
|
155
|
-
return newIndex;
|
|
156
|
-
});
|
|
157
|
-
setSelectedAction(0);
|
|
158
|
-
}
|
|
159
|
-
if (key.downArrow) {
|
|
160
|
-
const maxIndex = activeTab === 'tools' ? tools.length - 1 : 0;
|
|
161
|
-
setSelectedIndex((prev) => {
|
|
162
|
-
const newIndex = Math.min(maxIndex, prev + 1);
|
|
163
|
-
// Scroll down if needed
|
|
164
|
-
if (newIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
|
|
165
|
-
setScrollOffset(newIndex - MAX_VISIBLE_ITEMS + 1);
|
|
166
|
-
}
|
|
167
|
-
return newIndex;
|
|
168
|
-
});
|
|
169
|
-
setSelectedAction(0);
|
|
170
|
-
}
|
|
171
|
-
if (key.return) {
|
|
172
|
-
if (activeTab === 'tools' && tools.length > 0) {
|
|
173
|
-
setView('detail');
|
|
174
|
-
} else if (activeTab === 'settings') {
|
|
175
|
-
setView('detail');
|
|
156
|
+
|
|
157
|
+
if (view === 'menu') {
|
|
158
|
+
if (key.leftArrow) {
|
|
159
|
+
const newIndex = Math.max(0, tabIndex - 1);
|
|
160
|
+
setTabIndex(newIndex);
|
|
161
|
+
setActiveTab(tabs[newIndex].id);
|
|
162
|
+
setSelectedIndex(0);
|
|
163
|
+
setScrollOffset(0);
|
|
176
164
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (activeTab === 'settings') {
|
|
184
|
-
// Settings detail view - just enter to edit
|
|
185
|
-
if (key.return) {
|
|
186
|
-
setIsEditingSettings(true);
|
|
187
|
-
setView('setup');
|
|
165
|
+
if (key.rightArrow) {
|
|
166
|
+
const newIndex = Math.min(tabs.length - 1, tabIndex + 1);
|
|
167
|
+
setTabIndex(newIndex);
|
|
168
|
+
setActiveTab(tabs[newIndex].id);
|
|
169
|
+
setSelectedIndex(0);
|
|
170
|
+
setScrollOffset(0);
|
|
188
171
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const tool = tools[selectedIndex];
|
|
196
|
-
const installed = tool ? isToolInstalled(tool.name) : false;
|
|
197
|
-
const hasUpdate = tool ? getToolUpdateStatus(tool.name).hasUpdate : false;
|
|
198
|
-
const isSystem = tool ? (tool as ToolManifest & { system?: boolean }).system === true : false;
|
|
199
|
-
// Explore, [Update], Configure, [Uninstall] or Explore, Install
|
|
200
|
-
// System tools don't have Uninstall, so one less action
|
|
201
|
-
maxActions = installed ? (hasUpdate ? (isSystem ? 2 : 3) : (isSystem ? 1 : 2)) : 1;
|
|
202
|
-
setSelectedAction((prev) => Math.min(maxActions, prev + 1));
|
|
203
|
-
}
|
|
204
|
-
if (key.return && activeTab === 'tools') {
|
|
205
|
-
const tool = tools[selectedIndex];
|
|
206
|
-
if (tool) {
|
|
207
|
-
const installed = isToolInstalled(tool.name);
|
|
208
|
-
const toolUpdateStatus = getToolUpdateStatus(tool.name);
|
|
209
|
-
const isSystemTool = (tool as ToolManifest & { system?: boolean }).system === true;
|
|
210
|
-
// Build actions array to match ToolDetails
|
|
211
|
-
const toolActions = installed
|
|
212
|
-
? [
|
|
213
|
-
{ id: 'explore' },
|
|
214
|
-
...(toolUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
|
|
215
|
-
{ id: 'configure' },
|
|
216
|
-
...(!isSystemTool ? [{ id: 'uninstall' }] : []),
|
|
217
|
-
]
|
|
218
|
-
: [{ id: 'explore' }, { id: 'install' }];
|
|
219
|
-
|
|
220
|
-
const actionId = toolActions[selectedAction]?.id;
|
|
221
|
-
|
|
222
|
-
if (actionId === 'explore') {
|
|
223
|
-
// Open the tool explorer view
|
|
224
|
-
setView('explorer');
|
|
225
|
-
} else if (actionId === 'update') {
|
|
226
|
-
// Update the primary skill of the tool
|
|
227
|
-
const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
|
|
228
|
-
const result = updateSkill(primarySkill);
|
|
229
|
-
setMessage({
|
|
230
|
-
text: result.success ? `✓ Updated ${tool.name}` : `✗ ${result.message}`,
|
|
231
|
-
type: result.success ? 'success' : 'error',
|
|
232
|
-
});
|
|
233
|
-
if (result.success) {
|
|
234
|
-
setSelectedAction(0);
|
|
172
|
+
if (key.upArrow) {
|
|
173
|
+
setSelectedIndex((prev) => {
|
|
174
|
+
const newIndex = Math.max(0, prev - 1);
|
|
175
|
+
// Scroll up if needed
|
|
176
|
+
if (newIndex < scrollOffset) {
|
|
177
|
+
setScrollOffset(newIndex);
|
|
235
178
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
setView('menu');
|
|
248
|
-
setSelectedAction(0);
|
|
179
|
+
return newIndex;
|
|
180
|
+
});
|
|
181
|
+
setSelectedAction(0);
|
|
182
|
+
}
|
|
183
|
+
if (key.downArrow) {
|
|
184
|
+
const maxIndex = activeTab === 'tools' ? tools.length - 1 : 0;
|
|
185
|
+
setSelectedIndex((prev) => {
|
|
186
|
+
const newIndex = Math.min(maxIndex, prev + 1);
|
|
187
|
+
// Scroll down if needed
|
|
188
|
+
if (newIndex >= scrollOffset + MAX_VISIBLE_ITEMS) {
|
|
189
|
+
setScrollOffset(newIndex - MAX_VISIBLE_ITEMS + 1);
|
|
249
190
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
191
|
+
return newIndex;
|
|
192
|
+
});
|
|
193
|
+
setSelectedAction(0);
|
|
194
|
+
}
|
|
195
|
+
if (key.return) {
|
|
196
|
+
if (activeTab === 'tools' && tools.length > 0) {
|
|
197
|
+
setView('detail');
|
|
198
|
+
} else if (activeTab === 'settings') {
|
|
199
|
+
setView('detail');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} else if (view === 'detail') {
|
|
203
|
+
if (key.escape || key.backspace) {
|
|
204
|
+
setView('menu');
|
|
205
|
+
setSelectedAction(0);
|
|
206
|
+
}
|
|
207
|
+
if (activeTab === 'settings') {
|
|
208
|
+
// Settings detail view - just enter to edit
|
|
209
|
+
if (key.return) {
|
|
210
|
+
setIsEditingSettings(true);
|
|
211
|
+
setView('setup');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (key.leftArrow && activeTab === 'tools') {
|
|
215
|
+
setSelectedAction((prev) => Math.max(0, prev - 1));
|
|
216
|
+
}
|
|
217
|
+
if (key.rightArrow && activeTab === 'tools') {
|
|
218
|
+
let maxActions = 0;
|
|
219
|
+
const tool = tools[selectedIndex];
|
|
220
|
+
const installed = tool ? isToolInstalled(tool.name) : false;
|
|
221
|
+
const hasUpdate = tool
|
|
222
|
+
? getToolUpdateStatus(tool.name).hasUpdate
|
|
223
|
+
: false;
|
|
224
|
+
const isSystem = tool
|
|
225
|
+
? (tool as ToolManifest & { system?: boolean }).system === true
|
|
226
|
+
: false;
|
|
227
|
+
// Explore, [Update], Configure, [Uninstall] or Explore, Install
|
|
228
|
+
// System tools don't have Uninstall, so one less action
|
|
229
|
+
maxActions = installed
|
|
230
|
+
? hasUpdate
|
|
231
|
+
? isSystem
|
|
232
|
+
? 2
|
|
233
|
+
: 3
|
|
234
|
+
: isSystem
|
|
235
|
+
? 1
|
|
236
|
+
: 2
|
|
237
|
+
: 1;
|
|
238
|
+
setSelectedAction((prev) => Math.min(maxActions, prev + 1));
|
|
239
|
+
}
|
|
240
|
+
if (key.return && activeTab === 'tools') {
|
|
241
|
+
const tool = tools[selectedIndex];
|
|
242
|
+
if (tool) {
|
|
243
|
+
const installed = isToolInstalled(tool.name);
|
|
244
|
+
const toolUpdateStatus = getToolUpdateStatus(tool.name);
|
|
245
|
+
const isSystemTool =
|
|
246
|
+
(tool as ToolManifest & { system?: boolean }).system === true;
|
|
247
|
+
// Build actions array to match ToolDetails
|
|
248
|
+
const toolActions = installed
|
|
249
|
+
? [
|
|
250
|
+
{ id: 'explore' },
|
|
251
|
+
...(toolUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
|
|
252
|
+
{ id: 'configure' },
|
|
253
|
+
...(!isSystemTool ? [{ id: 'uninstall' }] : []),
|
|
254
|
+
]
|
|
255
|
+
: [{ id: 'explore' }, { id: 'install' }];
|
|
256
|
+
|
|
257
|
+
const actionId = toolActions[selectedAction]?.id;
|
|
258
|
+
|
|
259
|
+
if (actionId === 'explore') {
|
|
260
|
+
// Open the tool explorer view
|
|
261
|
+
setView('explorer');
|
|
262
|
+
} else if (actionId === 'update') {
|
|
263
|
+
// Update the primary skill of the tool
|
|
264
|
+
const primarySkill =
|
|
265
|
+
tool.includes.skills.find((s) => s.required)?.name || tool.name;
|
|
266
|
+
const result = updateSkill(primarySkill);
|
|
267
|
+
setMessage({
|
|
268
|
+
text: result.success
|
|
269
|
+
? `✓ Updated ${tool.name}`
|
|
270
|
+
: `✗ ${result.message}`,
|
|
271
|
+
type: result.success ? 'success' : 'error',
|
|
272
|
+
});
|
|
273
|
+
if (result.success) {
|
|
274
|
+
setSelectedAction(0);
|
|
275
|
+
}
|
|
276
|
+
} else if (actionId === 'configure') {
|
|
277
|
+
setView('configure');
|
|
278
|
+
} else if (actionId === 'uninstall') {
|
|
279
|
+
// Uninstall the primary skill of the tool
|
|
280
|
+
const primarySkill =
|
|
281
|
+
tool.includes.skills.find((s) => s.required)?.name || tool.name;
|
|
282
|
+
const result = uninstallSkill(primarySkill);
|
|
283
|
+
setMessage({
|
|
284
|
+
text: result.success
|
|
285
|
+
? `✓ Uninstalled ${tool.name}`
|
|
286
|
+
: `✗ ${result.message}`,
|
|
287
|
+
type: result.success ? 'success' : 'error',
|
|
288
|
+
});
|
|
289
|
+
if (result.success) {
|
|
268
290
|
setView('menu');
|
|
269
291
|
setSelectedAction(0);
|
|
270
292
|
}
|
|
293
|
+
} else if (actionId === 'install') {
|
|
294
|
+
// Install the primary skill of the tool
|
|
295
|
+
const primarySkill =
|
|
296
|
+
tool.includes.skills.find((s) => s.required)?.name || tool.name;
|
|
297
|
+
const result = installSkill(primarySkill);
|
|
298
|
+
setMessage({
|
|
299
|
+
text: result.success
|
|
300
|
+
? `✓ Installed ${tool.name}`
|
|
301
|
+
: `✗ ${result.message}`,
|
|
302
|
+
type: result.success ? 'success' : 'error',
|
|
303
|
+
});
|
|
304
|
+
if (result.success) {
|
|
305
|
+
// Check if tool has required config (options without defaults)
|
|
306
|
+
const configSchema = tool.config_schema || {};
|
|
307
|
+
const hasRequiredConfig = Object.values(configSchema).some(
|
|
308
|
+
(option) => (option as ConfigOption).default === undefined,
|
|
309
|
+
);
|
|
310
|
+
if (hasRequiredConfig) {
|
|
311
|
+
// Go to configure view for required setup
|
|
312
|
+
setView('configure');
|
|
313
|
+
} else {
|
|
314
|
+
setView('menu');
|
|
315
|
+
setSelectedAction(0);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
271
318
|
}
|
|
272
319
|
}
|
|
273
320
|
}
|
|
274
321
|
}
|
|
275
|
-
}
|
|
276
|
-
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
isActive:
|
|
325
|
+
view !== 'welcome' &&
|
|
326
|
+
view !== 'tool-updates' &&
|
|
327
|
+
view !== 'setup' &&
|
|
328
|
+
view !== 'configure' &&
|
|
329
|
+
view !== 'explorer',
|
|
330
|
+
},
|
|
331
|
+
);
|
|
277
332
|
|
|
278
|
-
const selectedTool =
|
|
333
|
+
const selectedTool =
|
|
334
|
+
activeTab === 'tools' ? (tools[selectedIndex] ?? null) : null;
|
|
279
335
|
// For configure view, we need the matching skill manifest
|
|
280
336
|
const selectedSkillForConfig = selectedTool
|
|
281
|
-
? skills.find(
|
|
337
|
+
? skills.find(
|
|
338
|
+
(s) =>
|
|
339
|
+
s.name ===
|
|
340
|
+
(selectedTool.includes.skills.find((sk) => sk.required)?.name ||
|
|
341
|
+
selectedTool.name),
|
|
342
|
+
)
|
|
282
343
|
: null;
|
|
283
344
|
|
|
284
345
|
if (view === 'welcome') {
|
|
@@ -356,7 +417,10 @@ function App() {
|
|
|
356
417
|
<SkillConfigScreen
|
|
357
418
|
skill={selectedSkillForConfig}
|
|
358
419
|
onComplete={() => {
|
|
359
|
-
setMessage({
|
|
420
|
+
setMessage({
|
|
421
|
+
text: `✓ Configuration saved for ${selectedTool?.name || selectedSkillForConfig.name}`,
|
|
422
|
+
type: 'success',
|
|
423
|
+
});
|
|
360
424
|
setView('detail');
|
|
361
425
|
}}
|
|
362
426
|
onCancel={() => {
|
|
@@ -389,7 +453,9 @@ function App() {
|
|
|
389
453
|
</Box>
|
|
390
454
|
<Box paddingX={1}>
|
|
391
455
|
<Text color={colors.textDim}>
|
|
392
|
-
{loadConfig().platform === Platform.ClaudeCode
|
|
456
|
+
{loadConfig().platform === Platform.ClaudeCode
|
|
457
|
+
? 'Claude Code'
|
|
458
|
+
: 'OpenCode'}
|
|
393
459
|
</Text>
|
|
394
460
|
</Box>
|
|
395
461
|
|
|
@@ -407,18 +473,25 @@ function App() {
|
|
|
407
473
|
<Text color={colors.textDim}>↑ {scrollOffset} more</Text>
|
|
408
474
|
</Box>
|
|
409
475
|
)}
|
|
410
|
-
{tools
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
476
|
+
{tools
|
|
477
|
+
.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS)
|
|
478
|
+
.map((tool, index) => (
|
|
479
|
+
<ToolItem
|
|
480
|
+
key={tool.name}
|
|
481
|
+
tool={tool}
|
|
482
|
+
isSelected={scrollOffset + index === selectedIndex}
|
|
483
|
+
isActive={
|
|
484
|
+
scrollOffset + index === selectedIndex &&
|
|
485
|
+
view === 'detail'
|
|
486
|
+
}
|
|
487
|
+
wasAutoUpdated={autoUpdatedTools.includes(tool.name)}
|
|
488
|
+
/>
|
|
489
|
+
))}
|
|
419
490
|
{scrollOffset + MAX_VISIBLE_ITEMS < tools.length && (
|
|
420
491
|
<Box paddingX={1}>
|
|
421
|
-
<Text color={colors.textDim}
|
|
492
|
+
<Text color={colors.textDim}>
|
|
493
|
+
↓ {tools.length - scrollOffset - MAX_VISIBLE_ITEMS} more
|
|
494
|
+
</Text>
|
|
422
495
|
</Box>
|
|
423
496
|
)}
|
|
424
497
|
{tools.length > MAX_VISIBLE_ITEMS && (
|
|
@@ -439,7 +512,11 @@ function App() {
|
|
|
439
512
|
{/* Message */}
|
|
440
513
|
{message && (
|
|
441
514
|
<Box paddingX={1} marginTop={1}>
|
|
442
|
-
<Text
|
|
515
|
+
<Text
|
|
516
|
+
color={message.type === 'success' ? colors.success : colors.error}
|
|
517
|
+
>
|
|
518
|
+
{message.text}
|
|
519
|
+
</Text>
|
|
443
520
|
</Box>
|
|
444
521
|
)}
|
|
445
522
|
|
|
@@ -453,13 +530,15 @@ function App() {
|
|
|
453
530
|
|
|
454
531
|
{/* Right column */}
|
|
455
532
|
{activeTab === 'tools' && (
|
|
456
|
-
<ToolDetails
|
|
533
|
+
<ToolDetails
|
|
534
|
+
tool={selectedTool}
|
|
535
|
+
isFocused={view === 'detail'}
|
|
536
|
+
selectedAction={selectedAction}
|
|
537
|
+
/>
|
|
457
538
|
)}
|
|
458
539
|
|
|
459
540
|
{activeTab === 'settings' && (
|
|
460
|
-
<SettingsDetails
|
|
461
|
-
isFocused={view === 'detail'}
|
|
462
|
-
/>
|
|
541
|
+
<SettingsDetails isFocused={view === 'detail'} />
|
|
463
542
|
)}
|
|
464
543
|
</Box>
|
|
465
544
|
);
|