@orderful/droid 0.24.0 → 0.25.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.
Files changed (47) hide show
  1. package/.eslintrc.json +6 -4
  2. package/AGENTS.md +58 -0
  3. package/CHANGELOG.md +35 -0
  4. package/README.md +11 -6
  5. package/dist/bin/droid.js +384 -170
  6. package/dist/commands/config.d.ts +15 -1
  7. package/dist/commands/config.d.ts.map +1 -1
  8. package/dist/commands/exec.d.ts +10 -0
  9. package/dist/commands/exec.d.ts.map +1 -0
  10. package/dist/commands/tui.d.ts.map +1 -1
  11. package/dist/index.js +171 -33
  12. package/dist/lib/migrations.d.ts.map +1 -1
  13. package/dist/lib/skills.d.ts.map +1 -1
  14. package/dist/tools/codex/TOOL.yaml +1 -1
  15. package/dist/tools/codex/skills/droid-codex/SKILL.md +92 -72
  16. package/dist/tools/codex/skills/droid-codex/references/creating.md +13 -51
  17. package/dist/tools/codex/skills/droid-codex/references/decisions.md +15 -19
  18. package/dist/tools/codex/skills/droid-codex/references/topics.md +14 -12
  19. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts +31 -0
  20. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts.map +1 -0
  21. package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
  22. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts +20 -0
  23. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts.map +1 -0
  24. package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
  25. package/dist/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
  26. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts +23 -0
  27. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts.map +1 -0
  28. package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.ts +172 -0
  29. package/package.json +1 -1
  30. package/src/bin/droid.ts +9 -0
  31. package/src/commands/config.ts +38 -4
  32. package/src/commands/exec.ts +96 -0
  33. package/src/commands/install.ts +1 -1
  34. package/src/commands/setup.ts +6 -6
  35. package/src/commands/tui.tsx +254 -175
  36. package/src/lib/migrations.ts +103 -10
  37. package/src/lib/quotes.ts +6 -6
  38. package/src/lib/skills.ts +168 -45
  39. package/src/tools/codex/TOOL.yaml +1 -1
  40. package/src/tools/codex/skills/droid-codex/SKILL.md +92 -72
  41. package/src/tools/codex/skills/droid-codex/references/creating.md +13 -51
  42. package/src/tools/codex/skills/droid-codex/references/decisions.md +15 -19
  43. package/src/tools/codex/skills/droid-codex/references/topics.md +14 -12
  44. package/src/tools/codex/skills/droid-codex/scripts/git-finish-write.ts +236 -0
  45. package/src/tools/codex/skills/droid-codex/scripts/git-preamble.ts +156 -0
  46. package/src/tools/codex/skills/droid-codex/scripts/git-scripts.test.ts +364 -0
  47. 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
+ }
@@ -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(` - Reconfigure anytime with \`droid\` → Configure`));
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);
@@ -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(`✓ Added droid permissions to Claude Code settings`));
283
+ console.log(chalk.green('✓ Added droid permissions to Claude Code settings'));
284
284
  } else if (alreadyPresent) {
285
- console.log(chalk.gray(` Droid permissions already configured in Claude Code`));
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(`✓ 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`));
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(` opencode-skills plugin already configured in OpenCode`));
293
+ console.log(chalk.gray(' opencode-skills plugin already configured in OpenCode'));
294
294
  }
295
295
  }
296
296
 
@@ -6,10 +6,15 @@ import {
6
6
  uninstallSkill,
7
7
  updateSkill,
8
8
  } from '../lib/skills';
9
- import { getBundledTools, isToolInstalled, getToolUpdateStatus } from '../lib/tools';
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<{ text: string; type: 'success' | 'error' } | null>(null);
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<{ title: string; content: string } | null>(null);
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 } = useAppUpdate({
50
- onUpdateSuccess: (msg) => {
51
- exitMessage = msg;
52
- },
53
- onUpdateFailure: (error) => {
54
- setMessage({ text: error, type: 'error' });
55
- if (!configExists()) {
56
- setView('setup');
57
- } else {
58
- setView('menu');
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 (autoUpdateConfig.app && updateInfo.hasUpdate && view === 'welcome' && !isUpdating) {
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((input, key) => {
126
- if (message) setMessage(null);
127
-
128
- if (input === 'q') {
129
- exit();
130
- return;
131
- }
148
+ useInput(
149
+ (input, key) => {
150
+ if (message) setMessage(null);
132
151
 
133
- if (view === 'menu') {
134
- if (key.leftArrow) {
135
- const newIndex = Math.max(0, tabIndex - 1);
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
- if (key.rightArrow) {
142
- const newIndex = Math.min(tabs.length - 1, tabIndex + 1);
143
- setTabIndex(newIndex);
144
- setActiveTab(tabs[newIndex].id);
145
- setSelectedIndex(0);
146
- setScrollOffset(0);
147
- }
148
- if (key.upArrow) {
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
- } else if (view === 'detail') {
179
- if (key.escape || key.backspace) {
180
- setView('menu');
181
- setSelectedAction(0);
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
- if (key.leftArrow && activeTab === 'tools') {
191
- setSelectedAction((prev) => Math.max(0, prev - 1));
192
- }
193
- if (key.rightArrow && activeTab === 'tools') {
194
- let maxActions = 0;
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
- } else if (actionId === 'configure') {
237
- setView('configure');
238
- } else if (actionId === 'uninstall') {
239
- // Uninstall the primary skill of the tool
240
- const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
241
- const result = uninstallSkill(primarySkill);
242
- setMessage({
243
- text: result.success ? `✓ Uninstalled ${tool.name}` : `✗ ${result.message}`,
244
- type: result.success ? 'success' : 'error',
245
- });
246
- if (result.success) {
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
- } else if (actionId === 'install') {
251
- // Install the primary skill of the tool
252
- const primarySkill = tool.includes.skills.find(s => s.required)?.name || tool.name;
253
- const result = installSkill(primarySkill);
254
- setMessage({
255
- text: result.success ? `✓ Installed ${tool.name}` : `✗ ${result.message}`,
256
- type: result.success ? 'success' : 'error',
257
- });
258
- if (result.success) {
259
- // Check if tool has required config (options without defaults)
260
- const configSchema = tool.config_schema || {};
261
- const hasRequiredConfig = Object.values(configSchema).some(
262
- (option) => (option as ConfigOption).default === undefined
263
- );
264
- if (hasRequiredConfig) {
265
- // Go to configure view for required setup
266
- setView('configure');
267
- } else {
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
- }, { isActive: view !== 'welcome' && view !== 'tool-updates' && view !== 'setup' && view !== 'configure' && view !== 'explorer' });
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 = activeTab === 'tools' ? tools[selectedIndex] ?? null : null;
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(s => s.name === (selectedTool.includes.skills.find(sk => sk.required)?.name || selectedTool.name))
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({ text: `✓ Configuration saved for ${selectedTool?.name || selectedSkillForConfig.name}`, type: 'success' });
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 ? 'Claude Code' : 'OpenCode'}
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.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS).map((tool, index) => (
411
- <ToolItem
412
- key={tool.name}
413
- tool={tool}
414
- isSelected={scrollOffset + index === selectedIndex}
415
- isActive={scrollOffset + index === selectedIndex && view === 'detail'}
416
- wasAutoUpdated={autoUpdatedTools.includes(tool.name)}
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}>↓ {tools.length - scrollOffset - MAX_VISIBLE_ITEMS} more</Text>
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 color={message.type === 'success' ? colors.success : colors.error}>{message.text}</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 tool={selectedTool} isFocused={view === 'detail'} selectedAction={selectedAction} />
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
  );