@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.
- package/.claude/CLAUDE.md +6 -7
- package/.github/workflows/changeset-check.yml +11 -1
- package/.github/workflows/ci.yml +5 -0
- package/.github/workflows/release.yml +7 -0
- package/CHANGELOG.md +36 -0
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +98 -19
- package/dist/commands/tui.js.map +1 -1
- package/dist/lib/skills.d.ts +38 -0
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +130 -4
- package/dist/lib/skills.js.map +1 -1
- package/dist/skills/comments/SKILL.md +28 -5
- package/dist/skills/comments/SKILL.yaml +6 -2
- package/dist/skills/comments/commands/comments.md +15 -34
- package/dist/skills/project/SKILL.md +93 -0
- package/dist/skills/project/SKILL.yaml +35 -0
- package/dist/skills/project/commands/README.md +26 -0
- package/dist/skills/project/commands/project.md +39 -0
- package/dist/skills/project/references/changelog.md +70 -0
- package/dist/skills/project/references/creating.md +58 -0
- package/dist/skills/project/references/loading.md +40 -0
- package/dist/skills/project/references/templates.md +124 -0
- package/dist/skills/project/references/updating.md +64 -0
- package/dist/skills/project/references/versioning.md +36 -0
- package/package.json +1 -1
- package/src/commands/tui.tsx +120 -21
- package/src/lib/skills.ts +160 -4
- package/src/skills/comments/SKILL.md +28 -5
- package/src/skills/comments/SKILL.yaml +6 -2
- package/src/skills/comments/commands/comments.md +15 -34
- package/src/skills/project/SKILL.md +93 -0
- package/src/skills/project/SKILL.yaml +35 -0
- package/src/skills/project/commands/README.md +26 -0
- package/src/skills/project/commands/project.md +39 -0
- package/src/skills/project/references/changelog.md +70 -0
- package/src/skills/project/references/creating.md +58 -0
- package/src/skills/project/references/loading.md +40 -0
- package/src/skills/project/references/templates.md +124 -0
- package/src/skills/project/references/updating.md +64 -0
- 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
package/src/commands/tui.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
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 (
|
|
1212
|
-
|
|
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 (
|
|
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 (
|
|
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
|
}
|