@orderful/droid 0.4.1 → 0.5.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 (41) hide show
  1. package/.claude/CLAUDE.md +6 -7
  2. package/.github/workflows/changeset-check.yml +11 -1
  3. package/.github/workflows/ci.yml +5 -0
  4. package/.github/workflows/release.yml +7 -0
  5. package/CHANGELOG.md +36 -0
  6. package/dist/commands/tui.d.ts.map +1 -1
  7. package/dist/commands/tui.js +98 -19
  8. package/dist/commands/tui.js.map +1 -1
  9. package/dist/lib/skills.d.ts +38 -0
  10. package/dist/lib/skills.d.ts.map +1 -1
  11. package/dist/lib/skills.js +130 -4
  12. package/dist/lib/skills.js.map +1 -1
  13. package/dist/skills/comments/SKILL.md +28 -5
  14. package/dist/skills/comments/SKILL.yaml +6 -2
  15. package/dist/skills/comments/commands/comments.md +15 -34
  16. package/dist/skills/project/SKILL.md +93 -0
  17. package/dist/skills/project/SKILL.yaml +35 -0
  18. package/dist/skills/project/commands/README.md +26 -0
  19. package/dist/skills/project/commands/project.md +39 -0
  20. package/dist/skills/project/references/changelog.md +70 -0
  21. package/dist/skills/project/references/creating.md +58 -0
  22. package/dist/skills/project/references/loading.md +40 -0
  23. package/dist/skills/project/references/templates.md +124 -0
  24. package/dist/skills/project/references/updating.md +64 -0
  25. package/dist/skills/project/references/versioning.md +36 -0
  26. package/package.json +1 -1
  27. package/src/commands/tui.tsx +120 -21
  28. package/src/lib/skills.ts +160 -4
  29. package/src/skills/comments/SKILL.md +28 -5
  30. package/src/skills/comments/SKILL.yaml +6 -2
  31. package/src/skills/comments/commands/comments.md +15 -34
  32. package/src/skills/project/SKILL.md +93 -0
  33. package/src/skills/project/SKILL.yaml +35 -0
  34. package/src/skills/project/commands/README.md +26 -0
  35. package/src/skills/project/commands/project.md +39 -0
  36. package/src/skills/project/references/changelog.md +70 -0
  37. package/src/skills/project/references/creating.md +58 -0
  38. package/src/skills/project/references/loading.md +40 -0
  39. package/src/skills/project/references/templates.md +124 -0
  40. package/src/skills/project/references/updating.md +64 -0
  41. package/src/skills/project/references/versioning.md +36 -0
@@ -0,0 +1,58 @@
1
+ # Creating a Project
2
+
3
+ **Trigger:** `/project create {name?}` or user asks to start a new project
4
+
5
+ ## Procedure
6
+
7
+ 1. **Get project name**
8
+ - Use provided name, or ask if not provided
9
+ - Convert to kebab-case for folder name
10
+ - Convert to Title Case for display name
11
+
12
+ 2. **Create project folder**
13
+ - Path: `{projects_dir}/{kebab-case-name}/`
14
+ - Verify folder doesn't already exist
15
+
16
+ 3. **Create files from templates** (see `templates.md`)
17
+ - `PROJECT.md` - Main context file
18
+ - `CHANGELOG.md` - Version history
19
+ - Format varies by `preset` config (markdown vs obsidian)
20
+
21
+ 4. **Confirm creation**
22
+ - Show created folder path
23
+ - Offer to help fill in sections (Overview, Goals, Technical Details)
24
+
25
+ ## Naming Convention
26
+
27
+ | Input | Folder Name | Display Name |
28
+ |-------|-------------|--------------|
29
+ | "billing refactor" | `billing-refactor` | "Billing Refactor" |
30
+ | "API v2" | `api-v2` | "API V2" |
31
+ | "my-feature" | `my-feature` | "My Feature" |
32
+
33
+ ## Example
34
+
35
+ ```
36
+ User: /project create billing refactor
37
+
38
+ Claude: Creating new project...
39
+
40
+ Created: ~/.claude/projects/billing-refactor/
41
+ ├── PROJECT.md
42
+ └── CHANGELOG.md
43
+
44
+ Project "Billing Refactor" initialized at v0.1.0.
45
+
46
+ Would you like help filling in the Overview, Goals, or Technical Details sections?
47
+ ```
48
+
49
+ ## Initial Structure
50
+
51
+ New projects start minimal and grow organically. Common sections added over time:
52
+
53
+ - **Current Work** - Active tasks and focus areas
54
+ - **Decisions Log** - Key decisions with rationale
55
+ - **Related** - Links to other docs, tickets, repos
56
+ - **Roadmap** - Future phases
57
+
58
+ See `templates.md` for the full initial template.
@@ -0,0 +1,40 @@
1
+ # Loading a Project
2
+
3
+ **Trigger:** `/project {keywords}` or user asks to load/open a project
4
+
5
+ ## Procedure
6
+
7
+ 1. **List projects** in configured `projects_dir`
8
+ - Each subfolder with a `PROJECT.md` is a project
9
+
10
+ 2. **If no name provided:**
11
+ - Use AskUserQuestion to present available projects
12
+ - Let user select which to load
13
+
14
+ 3. **If name/keywords provided:**
15
+ - Parse as space-separated keywords (e.g., "transaction templates" → `["transaction", "templates"]`)
16
+ - Find folders where name contains ALL keywords (case-insensitive, hyphens as word separators)
17
+
18
+ 4. **Based on matches:**
19
+ - **No matches**: List available projects, ask user to select
20
+ - **One match**: Read `{folder}/PROJECT.md`
21
+ - **Multiple matches**: Use AskUserQuestion to select from matches
22
+
23
+ 5. **After loading:**
24
+ - Confirm which project was loaded
25
+ - Summarize key context (2-3 sentences)
26
+ - Use project contents for all subsequent work in the session
27
+
28
+ ## Example
29
+
30
+ ```
31
+ User: /project transaction templates
32
+
33
+ Claude: Found project "transaction-templates". Loading...
34
+
35
+ Loaded #project-transaction-templates (v1.16.1)
36
+
37
+ This project covers Handlebars templates for EDI transaction generation.
38
+ Key focus areas: template rendering service, permission controls, and
39
+ the upcoming UI for template management.
40
+ ```
@@ -0,0 +1,124 @@
1
+ # Project Templates
2
+
3
+ Templates vary by `preset` setting.
4
+
5
+ ## Markdown Preset (default)
6
+
7
+ ### PROJECT.md
8
+
9
+ ```markdown
10
+ ---
11
+ status: active
12
+ ---
13
+
14
+ # Project: {Name}
15
+
16
+ {Brief description}
17
+
18
+ | | |
19
+ |---|---|
20
+ | **Version** | 0.1.0 |
21
+ | **Started** | {today} |
22
+ | **Status** | Active |
23
+
24
+ ## Overview
25
+
26
+ {**What** this project does, **why** it exists}
27
+
28
+ ## Goals
29
+
30
+ - {Goal 1}
31
+
32
+ ## Technical Details
33
+
34
+ {Architecture, key files, patterns}
35
+
36
+ ## Changelog
37
+
38
+ See [CHANGELOG.md](CHANGELOG.md) for full history.
39
+
40
+ ### 0.1.0 - {today}
41
+ - Initial project setup
42
+ ```
43
+
44
+ ### CHANGELOG.md
45
+
46
+ ```markdown
47
+ # Changelog
48
+
49
+ All notable changes to the {Name} project.
50
+
51
+ ## 0.1.0 - {today}
52
+ - Initial project setup
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Obsidian Preset
58
+
59
+ ### PROJECT.md
60
+
61
+ ```markdown
62
+ ---
63
+ type: project
64
+ status: active
65
+ created: {today}
66
+ updated: {today}
67
+ ---
68
+
69
+ # Project: {Name}
70
+
71
+ {Brief description}
72
+
73
+ | | |
74
+ |---|---|
75
+ | **Version** | 0.1.0 |
76
+ | **Started** | {today} |
77
+ | **Status** | Active |
78
+
79
+ ## Overview
80
+
81
+ {**What** this project does, **why** it exists}
82
+
83
+ ## Goals
84
+
85
+ - {Goal 1}
86
+
87
+ ## Technical Details
88
+
89
+ {Architecture, key files, patterns}
90
+
91
+ ## Related
92
+
93
+ - {Links to related notes}
94
+
95
+ ## Changelog
96
+
97
+ See [[CHANGELOG|full history]].
98
+
99
+ ### 0.1.0 - {today}
100
+ - Initial project setup
101
+ ```
102
+
103
+ ### CHANGELOG.md
104
+
105
+ Same as markdown preset.
106
+
107
+ ---
108
+
109
+ ## Template Variables
110
+
111
+ | Variable | Expands To |
112
+ |----------|------------|
113
+ | `{today}` | YYYY-MM-DD |
114
+ | `{Name}` | Project name (Title Case) |
115
+ | `{name}` | Project name (lowercase) |
116
+ | `{kebab-name}` | Folder name (kebab-case) |
117
+
118
+ ## Preset Differences Summary
119
+
120
+ | Feature | Markdown | Obsidian |
121
+ |---------|----------|----------|
122
+ | Links | `[text](path)` | `[[path\|text]]` |
123
+ | YAML properties | Minimal (`status`) | Rich (`type`, `created`, `updated`) |
124
+ | Related section | Optional | Included |
@@ -0,0 +1,64 @@
1
+ # Updating a Project
2
+
3
+ **Trigger:** `/project update` or user asks to capture/save learnings
4
+
5
+ ## Procedure
6
+
7
+ 1. **Identify the project:**
8
+ - If name provided: Find matching project (same logic as loading)
9
+ - If no name: Check conversation for prior project load
10
+ - If still unclear: List projects and ask which to update
11
+
12
+ 2. **Analyze the conversation for:**
13
+ - New implementation details or patterns
14
+ - Key decisions and rationale
15
+ - Technical constraints discovered
16
+ - Progress on features
17
+ - Important file paths or code locations
18
+ - Bug fixes or gotchas encountered
19
+
20
+ 3. **If significant new information found:**
21
+ - Propose specific sections/content to add or update
22
+ - Show what will be changed
23
+ - Ask for confirmation before proceeding
24
+
25
+ 4. **If no obvious updates:**
26
+ - Ask what the user would like to update
27
+ - Suggest sections: metadata, implementation details, technical decisions, notes
28
+
29
+ 5. **After approval:**
30
+ - Read current project file
31
+ - Make approved updates, preserving existing structure
32
+ - Determine version bump (see `versioning.md`)
33
+ - Add changelog entry (see `changelog.md`)
34
+ - Confirm what was updated
35
+
36
+ ## What to Capture
37
+
38
+ Good project updates include:
39
+
40
+ | Type | Examples |
41
+ |------|----------|
42
+ | **Decisions** | "Chose cursor-based pagination over offset" |
43
+ | **Patterns** | "Using repository pattern for data access" |
44
+ | **Constraints** | "Must support backwards compatibility with v1 API" |
45
+ | **Gotchas** | "Watch out for timezone handling in date fields" |
46
+ | **Progress** | "Completed validation layer, starting on UI" |
47
+ | **File locations** | "Key files: `src/services/template.service.ts`" |
48
+
49
+ ## Example
50
+
51
+ ```
52
+ User: /project update
53
+
54
+ Claude: I'll update #project-transaction-templates with learnings from this session.
55
+
56
+ Proposed changes:
57
+ - Add "Permission Controls" section documenting the new RBAC approach
58
+ - Update Technical Details with the Handlebars helper patterns we established
59
+ - Note the decision to use soft deletes
60
+
61
+ This looks like a minor version bump (0.16.0 → 0.17.0).
62
+
63
+ Proceed with these updates?
64
+ ```
@@ -0,0 +1,36 @@
1
+ # Version Bumping Rules
2
+
3
+ Use semantic versioning for project docs.
4
+
5
+ ## When to Bump
6
+
7
+ | Type | When | Examples |
8
+ |------|------|----------|
9
+ | **Major** | Breaking changes, complete rewrites, fundamental architecture changes | New approach that invalidates previous docs |
10
+ | **Minor** | New features, new sections, significant additions | New helper added, new endpoint, new workflow |
11
+ | **Patch** | Clarifications, small updates, typo fixes, status changes | Wording tweaks, deferred features, bug notes |
12
+
13
+ ## Auto-Determination
14
+
15
+ Analyze the changes being made:
16
+
17
+ 1. Default to **minor** for most content additions
18
+ 2. Use **patch** for small clarifications or status updates
19
+ 3. Use **major** only for fundamental changes (rare)
20
+ 4. Only ask user if genuinely ambiguous
21
+
22
+ ## Examples
23
+
24
+ **Patch (0.1.0 -> 0.1.1):**
25
+ - Fixed typo in technical details
26
+ - Updated status from active to reference
27
+ - Added note about known limitation
28
+
29
+ **Minor (0.1.1 -> 0.2.0):**
30
+ - Added new "Authentication" section
31
+ - Documented new API endpoint
32
+ - Added architecture diagram
33
+
34
+ **Major (0.2.0 -> 1.0.0):**
35
+ - Complete rewrite of implementation approach
36
+ - Switched from REST to GraphQL (invalidates prior docs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderful/droid",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,8 @@ import {
11
11
  getInstalledSkill,
12
12
  installSkill,
13
13
  uninstallSkill,
14
+ updateSkill,
15
+ getSkillUpdateStatus,
14
16
  isCommandInstalled,
15
17
  installCommand,
16
18
  uninstallCommand,
@@ -62,7 +64,23 @@ function getCommandsFromSkills(): Command[] {
62
64
  const content = readFileSync(join(commandsDir, file), 'utf-8');
63
65
  const lines = content.split('\n');
64
66
 
65
- const headerMatch = lines[0]?.match(/^#\s+\/(\S+)\s*-?\s*(.*)/);
67
+ // Skip YAML frontmatter if present
68
+ let startLine = 0;
69
+ if (lines[0]?.trim() === '---') {
70
+ for (let i = 1; i < lines.length; i++) {
71
+ if (lines[i]?.trim() === '---') {
72
+ startLine = i + 1;
73
+ break;
74
+ }
75
+ }
76
+ }
77
+
78
+ // Find first H1 header
79
+ let headerMatch: RegExpMatchArray | null = null;
80
+ for (let i = startLine; i < lines.length; i++) {
81
+ headerMatch = lines[i]?.match(/^#\s+\/(\S+)\s*-?\s*(.*)/);
82
+ if (headerMatch) break;
83
+ }
66
84
  if (!headerMatch) continue;
67
85
 
68
86
  const usage: string[] = [];
@@ -393,6 +411,7 @@ function SkillItem({
393
411
  }) {
394
412
  const installed = isSkillInstalled(skill.name);
395
413
  const installedInfo = getInstalledSkill(skill.name);
414
+ const updateStatus = getSkillUpdateStatus(skill.name);
396
415
 
397
416
  return (
398
417
  <Box paddingX={1} backgroundColor={isActive ? colors.bgSelected : undefined}>
@@ -401,6 +420,7 @@ function SkillItem({
401
420
  <Text color={isSelected || isActive ? colors.text : colors.textMuted}>{skill.name}</Text>
402
421
  {installed && installedInfo && <Text color={colors.textDim}> v{installedInfo.version}</Text>}
403
422
  {installed && <Text color={colors.success}> *</Text>}
423
+ {updateStatus.hasUpdate && <Text color={colors.primary}> ↑</Text>}
404
424
  </Text>
405
425
  </Box>
406
426
  );
@@ -444,12 +464,16 @@ function SkillDetails({
444
464
  }
445
465
 
446
466
  const installed = isSkillInstalled(skill.name);
467
+ const updateStatus = getSkillUpdateStatus(skill.name);
447
468
  const skillCommands = getCommandsFromSkills().filter((c) => c.skillName === skill.name);
448
469
 
449
470
  const actions = installed
450
471
  ? [
451
472
  { id: 'view', label: 'View', variant: 'default' },
452
- { id: 'configure', label: 'Configure', variant: 'primary' },
473
+ ...(updateStatus.hasUpdate
474
+ ? [{ id: 'update', label: `Update (${updateStatus.bundledVersion})`, variant: 'primary' }]
475
+ : []),
476
+ { id: 'configure', label: 'Configure', variant: 'default' },
453
477
  { id: 'uninstall', label: 'Uninstall', variant: 'danger' },
454
478
  ]
455
479
  : [
@@ -466,6 +490,9 @@ function SkillDetails({
466
490
  {skill.version}
467
491
  {skill.status && ` · ${skill.status}`}
468
492
  {installed && <Text color={colors.success}> · installed</Text>}
493
+ {updateStatus.hasUpdate && (
494
+ <Text color={colors.primary}> · update available ({updateStatus.installedVersion} → {updateStatus.bundledVersion})</Text>
495
+ )}
469
496
  </Text>
470
497
  </Box>
471
498
 
@@ -933,6 +960,8 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
933
960
  const [selectedIndex, setSelectedIndex] = useState(0);
934
961
  const [editingField, setEditingField] = useState<string | null>(null);
935
962
  const [editValue, setEditValue] = useState('');
963
+ const [editingSelect, setEditingSelect] = useState<string | null>(null);
964
+ const [selectOptionIndex, setSelectOptionIndex] = useState(0);
936
965
 
937
966
  const handleSave = () => {
938
967
  saveSkillOverrides(skill.name, values);
@@ -955,6 +984,28 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
955
984
  }
956
985
  }, { isActive: editingField !== null });
957
986
 
987
+ // Handle select field editing
988
+ useInput((input, key) => {
989
+ if (!editingSelect) return;
990
+ const option = configSchema[editingSelect];
991
+ if (!option?.options) return;
992
+
993
+ if (key.escape) {
994
+ setEditingSelect(null);
995
+ return;
996
+ }
997
+ if (key.leftArrow || key.upArrow) {
998
+ setSelectOptionIndex((prev) => Math.max(0, prev - 1));
999
+ }
1000
+ if (key.rightArrow || key.downArrow) {
1001
+ setSelectOptionIndex((prev) => Math.min(option.options!.length - 1, prev + 1));
1002
+ }
1003
+ if (key.return) {
1004
+ setValues((prev) => ({ ...prev, [editingSelect]: option.options![selectOptionIndex] }));
1005
+ setEditingSelect(null);
1006
+ }
1007
+ }, { isActive: editingSelect !== null });
1008
+
958
1009
  // Handle navigation and actions when not editing
959
1010
  useInput((input, key) => {
960
1011
  if (key.escape) {
@@ -983,13 +1034,19 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
983
1034
  if (option.type === ConfigOptionType.Boolean) {
984
1035
  // Toggle boolean
985
1036
  setValues((prev) => ({ ...prev, [key]: !prev[key] }));
1037
+ } else if (option.type === ConfigOptionType.Select && option.options) {
1038
+ // Enter select edit mode
1039
+ const currentValue = String(values[key] || option.options[0]);
1040
+ const currentIndex = option.options.indexOf(currentValue);
1041
+ setSelectOptionIndex(currentIndex >= 0 ? currentIndex : 0);
1042
+ setEditingSelect(key);
986
1043
  } else if (option.type === ConfigOptionType.String) {
987
1044
  // Enter edit mode for string
988
1045
  setEditingField(key);
989
1046
  setEditValue(String(values[key] || ''));
990
1047
  }
991
1048
  }
992
- }, { isActive: editingField === null });
1049
+ }, { isActive: editingField === null && editingSelect === null });
993
1050
 
994
1051
  if (configKeys.length === 0) {
995
1052
  return (
@@ -1044,6 +1101,25 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
1044
1101
  <Text color={colors.text}>
1045
1102
  [{values[key] ? 'x' : ' '}] {values[key] ? 'enabled' : 'disabled'}
1046
1103
  </Text>
1104
+ ) : option.type === ConfigOptionType.Select && option.options ? (
1105
+ <Text color={colors.text}>
1106
+ {option.options.map((opt, i) => {
1107
+ const isCurrentValue = String(values[key]) === opt;
1108
+ const isEditingThis = editingSelect === key;
1109
+ const isHighlighted = isEditingThis && selectOptionIndex === i;
1110
+ return (
1111
+ <Text key={opt}>
1112
+ {i > 0 && <Text color={colors.textDim}> · </Text>}
1113
+ <Text
1114
+ color={isHighlighted ? '#ffffff' : isCurrentValue ? colors.primary : colors.textMuted}
1115
+ backgroundColor={isHighlighted ? colors.primary : undefined}
1116
+ >
1117
+ {isCurrentValue && !isEditingThis ? `[${opt}]` : isHighlighted ? ` ${opt} ` : opt}
1118
+ </Text>
1119
+ </Text>
1120
+ );
1121
+ })}
1122
+ </Text>
1047
1123
  ) : isEditing ? (
1048
1124
  <Box>
1049
1125
  <Text color={colors.textDim}>{'> '}</Text>
@@ -1078,7 +1154,11 @@ function SkillConfigScreen({ skill, onComplete, onCancel }: SkillConfigScreenPro
1078
1154
 
1079
1155
  <Box marginTop={1}>
1080
1156
  <Text color={colors.textDim}>
1081
- {editingField ? 'enter save · esc cancel' : '↑↓ select · enter toggle/edit · esc back'}
1157
+ {editingField
1158
+ ? 'enter save · esc cancel'
1159
+ : editingSelect
1160
+ ? '←→ choose · enter select · esc cancel'
1161
+ : '↑↓ select · enter toggle/edit · esc back'}
1082
1162
  </Text>
1083
1163
  </Box>
1084
1164
  </Box>
@@ -1184,7 +1264,9 @@ function App() {
1184
1264
  if (activeTab === 'skills') {
1185
1265
  const skill = skills[selectedIndex];
1186
1266
  const installed = skill ? isSkillInstalled(skill.name) : false;
1187
- maxActions = installed ? 2 : 1; // View, Configure, Uninstall or View, Install
1267
+ const hasUpdate = skill ? getSkillUpdateStatus(skill.name).hasUpdate : false;
1268
+ // View, [Update], Configure, Uninstall or View, Install
1269
+ maxActions = installed ? (hasUpdate ? 3 : 2) : 1;
1188
1270
  } else if (activeTab === 'agents') {
1189
1271
  maxActions = 1; // View, Install/Uninstall
1190
1272
  } else if (activeTab === 'commands') {
@@ -1199,20 +1281,38 @@ function App() {
1199
1281
  const skill = skills[selectedIndex];
1200
1282
  if (skill) {
1201
1283
  const installed = isSkillInstalled(skill.name);
1202
- // Actions: installed = [View, Configure, Uninstall], not installed = [View, Install]
1203
- if (selectedAction === 0) {
1204
- // View
1284
+ const skillUpdateStatus = getSkillUpdateStatus(skill.name);
1285
+ // Build actions array to match SkillDetails
1286
+ const skillActions = installed
1287
+ ? [
1288
+ { id: 'view' },
1289
+ ...(skillUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
1290
+ { id: 'configure' },
1291
+ { id: 'uninstall' },
1292
+ ]
1293
+ : [{ id: 'view' }, { id: 'install' }];
1294
+
1295
+ const actionId = skillActions[selectedAction]?.id;
1296
+
1297
+ if (actionId === 'view') {
1205
1298
  const skillMdPath = join(getBundledSkillsDir(), skill.name, 'SKILL.md');
1206
1299
  if (existsSync(skillMdPath)) {
1207
1300
  const content = readFileSync(skillMdPath, 'utf-8');
1208
1301
  setReadmeContent({ title: `${skill.name}/SKILL.md`, content });
1209
1302
  setView('readme');
1210
1303
  }
1211
- } else if (installed && selectedAction === 1) {
1212
- // Configure
1304
+ } else if (actionId === 'update') {
1305
+ const result = updateSkill(skill.name);
1306
+ setMessage({
1307
+ text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
1308
+ type: result.success ? 'success' : 'error',
1309
+ });
1310
+ if (result.success) {
1311
+ setSelectedAction(0);
1312
+ }
1313
+ } else if (actionId === 'configure') {
1213
1314
  setView('configure');
1214
- } else if (installed && selectedAction === 2) {
1215
- // Uninstall
1315
+ } else if (actionId === 'uninstall') {
1216
1316
  const result = uninstallSkill(skill.name);
1217
1317
  setMessage({
1218
1318
  text: result.success ? `✓ Uninstalled ${skill.name}` : `✗ ${result.message}`,
@@ -1222,8 +1322,7 @@ function App() {
1222
1322
  setView('menu');
1223
1323
  setSelectedAction(0);
1224
1324
  }
1225
- } else if (!installed && selectedAction === 1) {
1226
- // Install
1325
+ } else if (actionId === 'install') {
1227
1326
  const result = installSkill(skill.name);
1228
1327
  setMessage({
1229
1328
  text: result.success ? `✓ Installed ${skill.name}` : `✗ ${result.message}`,
@@ -1486,6 +1585,13 @@ function App() {
1486
1585
  )}
1487
1586
  </Box>
1488
1587
 
1588
+ {/* Message */}
1589
+ {message && (
1590
+ <Box paddingX={1} marginTop={1}>
1591
+ <Text color={message.type === 'success' ? colors.success : colors.error}>{message.text}</Text>
1592
+ </Box>
1593
+ )}
1594
+
1489
1595
  {/* Footer */}
1490
1596
  <Box paddingX={1} marginTop={1}>
1491
1597
  <Text color={colors.textDim}>
@@ -1510,13 +1616,6 @@ function App() {
1510
1616
  {activeTab === 'agents' && (
1511
1617
  <AgentDetails agent={selectedAgent} isFocused={view === 'detail'} selectedAction={selectedAction} />
1512
1618
  )}
1513
-
1514
- {/* Message */}
1515
- {message && (
1516
- <Box position="absolute" marginTop={12}>
1517
- <Text color={message.type === 'success' ? colors.success : colors.error}>{message.text}</Text>
1518
- </Box>
1519
- )}
1520
1619
  </Box>
1521
1620
  );
1522
1621
  }