Skip to content

Commit 221924a

Browse files
committed
Add interactive multi-select skill install
1 parent e039190 commit 221924a

File tree

8 files changed

+579
-100
lines changed

8 files changed

+579
-100
lines changed

docs/ai/design/feature-skill-add-interactive-selection.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ graph TD
1818
SkillManager --> CacheResolver[cloneRepositoryToCache]
1919
CacheResolver --> RegistryRepo[registry checkout/cache]
2020
RegistryRepo --> SkillEnumerator[scan skills/*/SKILL.md]
21-
SkillEnumerator --> Prompt[inquirer list prompt]
21+
SkillEnumerator --> Prompt[inquirer checkbox prompt]
2222
Prompt --> SkillManager
2323
SkillManager --> Installer[existing addSkill install path]
2424
Installer --> ConfigManager[project config update]
@@ -35,13 +35,11 @@ graph TD
3535
interface RegistrySkillChoice {
3636
name: string;
3737
description?: string;
38-
skillPath: string;
3938
}
4039

4140
interface AddSkillOptions {
4241
global?: boolean;
4342
environments?: string[];
44-
interactive?: boolean;
4543
}
4644
```
4745

@@ -64,13 +62,13 @@ interface AddSkillOptions {
6462
```ts
6563
async addSkill(registryId: string, skillName?: string, options?: AddSkillOptions): Promise<void>;
6664
async listRegistrySkills(registryId: string): Promise<RegistrySkillChoice[]>;
67-
async promptForSkillSelection(skills: RegistrySkillChoice[]): Promise<string>;
65+
async promptForSkillSelection(skills: RegistrySkillChoice[]): Promise<string[]>;
6866
```
6967

7068
**Behavior contract:**
7169

7270
- If `skillName` is provided, skip prompting.
73-
- If `skillName` is missing and `stdout`/`stdin` are interactive, enumerate skills and prompt.
71+
- If `skillName` is missing and `stdout`/`stdin` are interactive, enumerate skills and prompt for one or more selections.
7472
- If `skillName` is missing in a non-interactive context, fail with an error instructing the user to provide `<skill-name>`.
7573
- If the prompt is cancelled, exit without side effects.
7674
- If exactly one valid skill exists and `skillName` is omitted, still show the selector instead of auto-installing.
@@ -88,6 +86,7 @@ async promptForSkillSelection(skills: RegistrySkillChoice[]): Promise<string>;
8886
- existing installation logic
8987
- Add a helper that enumerates valid skills from the cloned registry.
9088
- Add a helper that prompts with `inquirer`.
89+
- Reuse the existing install path for each selected skill.
9190
3. `packages/cli/src/__tests__/commands/skill.test.ts`
9291
- Add command-level coverage for omitted skill name.
9392
4. `packages/cli/src/__tests__/lib/SkillManager.test.ts`
@@ -103,6 +102,9 @@ async promptForSkillSelection(skills: RegistrySkillChoice[]): Promise<string>;
103102
- Keep interactive selection explicit even for single-skill registries:
104103
- It matches the stated UX requirement.
105104
- It avoids hidden behavior changes between one-skill and multi-skill registries.
105+
- Allow multi-select installation in the prompt:
106+
- It reduces repetitive command invocations when a user wants several skills from the same registry.
107+
- It keeps the explicit two-argument command unchanged for scripted single-skill installs.
106108
- Prefer cached registry contents when refresh fails:
107109
- It keeps the command usable offline or during transient network failures.
108110
- It aligns with existing cache-oriented registry behavior.
@@ -132,3 +134,4 @@ async promptForSkillSelection(skills: RegistrySkillChoice[]): Promise<string>;
132134
- Continue validating `registryId` and selected `skillName` before installation.
133135
- Usability:
134136
- Prompt entries should display skill name and short description when available.
137+
- Users should be able to select multiple skills in one prompt.

docs/ai/planning/feature-skill-add-interactive-selection.md

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,27 @@ feature: skill-add-interactive-selection
1010
## Milestones
1111
**What are the major checkpoints?**
1212

13-
- [ ] Milestone 1: Command contract updated to allow omitted skill name without breaking explicit installs.
14-
- [ ] Milestone 2: `SkillManager` can enumerate registry skills and prompt for one interactively.
15-
- [ ] Milestone 3: Tests cover prompt and non-prompt flows, plus failure cases.
13+
- [x] Milestone 1: Command contract updated to allow omitted skill name without breaking explicit installs.
14+
- [x] Milestone 2: `SkillManager` can enumerate registry skills and prompt for one interactively.
15+
- [x] Milestone 3: Tests cover prompt and non-prompt flows, plus failure cases.
1616

1717
## Task Breakdown
1818
**What specific work needs to be done?**
1919

2020
### Phase 1: Command Surface
21-
- [ ] Task 1.1: Update `packages/cli/src/commands/skill.ts` so `add` accepts `[skill-name]`.
22-
- [ ] Task 1.2: Update command descriptions/help text to document the interactive shorthand.
21+
- [x] Task 1.1: Update `packages/cli/src/commands/skill.ts` so `add` accepts `[skill-name]`.
22+
- [x] Task 1.2: Update command descriptions/help text to document the interactive shorthand.
2323

2424
### Phase 2: Interactive Selection Flow
25-
- [ ] Task 2.1: Refactor `SkillManager.addSkill` so it can resolve a missing skill name before install.
26-
- [ ] Task 2.2: Implement registry skill enumeration from the cloned/cached repository.
27-
- [ ] Task 2.3: Implement an `inquirer` selection prompt with skill name and short description labels.
28-
- [ ] Task 2.4: Handle cancel, empty registry, invalid registry, and non-interactive contexts cleanly.
25+
- [x] Task 2.1: Refactor `SkillManager.addSkill` so it can resolve a missing skill name before install.
26+
- [x] Task 2.2: Implement registry skill enumeration from the cloned/cached repository.
27+
- [x] Task 2.3: Implement an `inquirer` selection prompt with skill name and short description labels.
28+
- [x] Task 2.4: Handle cancel, empty registry, invalid registry, and non-interactive contexts cleanly.
2929

3030
### Phase 3: Validation & Regression Coverage
31-
- [ ] Task 3.1: Add `SkillManager` unit tests for enumeration and prompt behavior.
32-
- [ ] Task 3.2: Add command-level tests for `ai-devkit skill add <registry>` and explicit two-arg installs.
33-
- [ ] Task 3.3: Verify no regression in config updates and environment resolution after interactive selection.
31+
- [x] Task 3.1: Add `SkillManager` unit tests for enumeration and prompt behavior.
32+
- [x] Task 3.2: Add command-level tests for `ai-devkit skill add <registry>` and explicit two-arg installs.
33+
- [x] Task 3.3: Verify no regression in config updates and environment resolution after interactive selection.
3434

3535
## Dependencies
3636
**What needs to happen in what order?**
@@ -49,10 +49,10 @@ graph TD
4949
## Timeline & Estimates
5050
**When will things be done?**
5151

52-
- Phase 1: 0.25 day
53-
- Phase 2: 0.5 day
54-
- Phase 3: 0.5 day
55-
- Total estimated effort: 1.25 days
52+
- Phase 1: completed
53+
- Phase 2: completed
54+
- Phase 3: completed
55+
- Total implementation effort: completed within the current session
5656

5757
## Risks & Mitigation
5858
**What could go wrong?**
@@ -70,3 +70,6 @@ graph TD
7070
- Existing `inquirer` dependency already used across the CLI.
7171
- Existing `SkillManager` cache and registry resolution helpers.
7272
- Jest command/lib test suites for regression coverage.
73+
74+
## Progress Summary
75+
Implementation is complete for the current scope. The `skill add` command now accepts an omitted skill name, `SkillManager` resolves available skills from the target registry checkout with cached fallback on refresh failure, and targeted Jest coverage verifies direct install, interactive multi-selection, cancellation, non-interactive failure, and cache-backed selection behavior. Remaining lifecycle work should move to implementation review rather than additional Phase 4 coding unless new scope is introduced.

docs/ai/requirements/feature-skill-add-interactive-selection.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ feature: skill-add-interactive-selection
3232

3333
- Allow `ai-devkit skill add <registry>` to enter an interactive selection flow when `<skill-name>` is omitted.
3434
- Build the selectable list from the requested registry itself, not from a hardcoded list.
35-
- Reuse the existing installation path once the user selects a skill.
35+
- Reuse the existing installation path once the user selects one or more skills.
3636
- Keep the existing explicit flow `ai-devkit skill add <registry> <skill-name>` unchanged.
3737

3838
**Secondary goals:**
@@ -43,16 +43,15 @@ feature: skill-add-interactive-selection
4343

4444
**Non-goals (explicitly out of scope):**
4545

46-
- Multi-select installation in one command.
4746
- Fuzzy search across all registries in the add flow.
4847
- Changing `skill find` behavior.
4948
- Adding a new registry metadata format.
5049

5150
## User Stories & Use Cases
5251
**How will users interact with the solution?**
5352

54-
1. As a developer, I want to run `ai-devkit skill add my-org/skills` so I can choose a skill interactively when I do not remember the exact skill name.
55-
2. As a developer, I want the CLI to show the actual skills available in that registry so I can install one without opening GitHub.
53+
1. As a developer, I want to run `ai-devkit skill add my-org/skills` so I can choose one or more skills interactively when I do not remember the exact skill names.
54+
2. As a developer, I want the CLI to show the actual skills available in that registry so I can install several of them without opening GitHub.
5655
3. As an automation user, I want `ai-devkit skill add <registry> <skill-name>` to keep working non-interactively so existing scripts do not break.
5756

5857
**Key workflows and scenarios:**
@@ -61,8 +60,8 @@ feature: skill-add-interactive-selection
6160
- CLI validates the registry.
6261
- CLI fetches or reuses the cached registry repository.
6362
- CLI extracts available skills from `skills/*/SKILL.md`.
64-
- CLI shows an interactive selection list, even if the registry only exposes one valid skill.
65-
- CLI installs the selected skill using the existing add flow.
63+
- CLI shows an interactive multi-selection list, even if the registry only exposes one valid skill.
64+
- CLI installs each selected skill using the existing add flow.
6665
- User runs `ai-devkit skill add <registry> <skill-name>`:
6766
- Existing direct install flow continues with no interactive prompt.
6867
- User cancels the prompt:
@@ -84,7 +83,7 @@ feature: skill-add-interactive-selection
8483
- `ai-devkit skill add <registry>` is accepted by the CLI.
8584
- When run interactively, the command displays a selection list populated from the target registry.
8685
- The command still shows the selection list when the registry contains exactly one valid skill.
87-
- Selecting a skill installs it through the existing installation path and updates project config exactly as today.
86+
- Selecting one or more skills installs each of them through the existing installation path and updates project config exactly as today.
8887
- `ai-devkit skill add <registry> <skill-name>` continues to work without prompting.
8988
- Invalid, empty, and non-interactive cases return actionable error messages.
9089
- If registry refresh fails but a cached copy exists, the command warns and uses the cached list.
@@ -109,4 +108,4 @@ feature: skill-add-interactive-selection
109108
## Questions & Open Items
110109
**What do we still need to clarify?**
111110

112-
- None for Phase 2 review. The prompt uses a single-select list whenever `<skill-name>` is omitted, and cached registry content is acceptable when refresh fails.
111+
- None for Phase 2 review. The prompt uses a multi-select list whenever `<skill-name>` is omitted, and cached registry content is acceptable when refresh fails.

docs/ai/testing/feature-skill-add-interactive-selection.md

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,51 +18,63 @@ feature: skill-add-interactive-selection
1818
**What individual components need testing?**
1919

2020
### SkillManager
21-
- [ ] Test case 1: Omitting `skillName` triggers registry enumeration and prompt selection.
22-
- [ ] Test case 2: Providing `skillName` skips the prompt entirely.
23-
- [ ] Test case 3: Invalid registry throws before prompt.
24-
- [ ] Test case 4: Empty registry throws a clear error.
25-
- [ ] Test case 5: Non-interactive mode without `skillName` throws a clear error.
26-
- [ ] Test case 6: Prompt cancellation exits without config writes or installs.
21+
- [x] Test case 1: Omitting `skillName` triggers registry enumeration and prompt selection.
22+
- [x] Test case 2: Providing `skillName` skips the prompt entirely.
23+
- [x] Test case 3: Invalid registry throws before prompt.
24+
- [x] Test case 4: Empty registry throws a clear error.
25+
- [x] Test case 5: Non-interactive mode without `skillName` throws a clear error.
26+
- [x] Test case 6: Prompt cancellation exits without config writes or installs.
27+
- [x] Test case 7: Multi-selection installs more than one skill in a single run.
28+
- [x] Test case 8: Cached registry contents are used when refresh fails.
29+
- [x] Test case 9: Global installation still works after interactive multi-selection.
2730

2831
### Skill Command
29-
- [ ] Test case 1: `skill add <registry>` is parsed successfully.
30-
- [ ] Test case 2: `skill add <registry> <skill-name>` still forwards both args correctly.
31-
- [ ] Test case 3: Help text reflects the interactive shorthand.
32+
- [x] Test case 1: `skill add <registry>` is parsed successfully.
33+
- [x] Test case 2: `skill add <registry> <skill-name>` still forwards both args correctly.
34+
- [x] Test case 3: Cancellation is surfaced as a warning instead of an error exit.
35+
- [x] Test case 4: Command shape reflects the optional `[skill-name]` argument.
3236

3337
## Integration Tests
3438
**How do we test component interactions?**
3539

36-
- [ ] Registry cache is prepared before enumeration.
37-
- [ ] Selected skill flows into the existing install path and config update.
38-
- [ ] Global install options still work after interactive selection.
40+
- [x] Registry cache is prepared before enumeration.
41+
- [x] Selected skill flows into the existing install path and config update.
42+
- [x] Global install options still work after interactive selection.
3943

4044
## End-to-End Tests
4145
**What user flows need validation?**
4246

43-
- [ ] User flow 1: Install a skill from a registry by selecting from the prompt.
44-
- [ ] User flow 2: Install a known skill directly with two arguments.
45-
- [ ] User flow 3: Cancel out of the prompt with no side effects.
47+
- [x] User flow 1: Install skill(s) from a registry by selecting from the prompt.
48+
- [x] User flow 2: Install a known skill directly with two arguments.
49+
- [x] User flow 3: Cancel out of the prompt with no side effects.
4650

4751
## Test Data
4852
**What data do we use for testing?**
4953

5054
- Mock registry repositories with valid `skills/<name>/SKILL.md` folders.
5155
- One malformed skill directory fixture to verify skip behavior.
5256
- Mocked prompt responses for selection and cancellation.
57+
- TTY stubs for interactive vs non-interactive command behavior.
5358

5459
## Test Reporting & Coverage
5560
**How do we verify and communicate test results?**
5661

5762
- Run focused Jest suites for `commands/skill` and `lib/SkillManager`.
5863
- Confirm changed branches include prompt, no-prompt, and error paths.
64+
- Latest verification evidence:
65+
- `npm test -- skill.test.ts`
66+
- `npm test -- SkillManager.test.ts`
67+
- `npx ai-devkit@latest lint --feature skill-add-interactive-selection`
68+
- Remaining coverage gap:
69+
- no command-level assertion for full rendered help output text; current coverage checks the registered argument shape instead
5970

6071
## Manual Testing
6172
**What requires human validation?**
6273

6374
- Prompt readability when many skills are present.
6475
- Cancellation UX in a real terminal session.
6576
- Global vs project install messaging after selection.
77+
- Checkbox prompt usability when only one skill is available.
6678

6779
## Performance Testing
6880
**How do we validate performance?**
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { Command } from 'commander';
2+
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
3+
import { registerSkillCommand } from '../../commands/skill';
4+
import { ui } from '../../util/terminal-ui';
5+
6+
const mockAddSkill = jest.fn();
7+
8+
jest.mock('../../lib/Config', () => ({
9+
ConfigManager: jest.fn(),
10+
}));
11+
12+
jest.mock('../../lib/SkillManager', () => ({
13+
SkillManager: jest.fn(() => ({
14+
addSkill: (...args: unknown[]) => mockAddSkill(...args),
15+
listSkills: jest.fn(),
16+
removeSkill: jest.fn(),
17+
updateSkills: jest.fn(),
18+
findSkills: jest.fn(),
19+
rebuildIndex: jest.fn(),
20+
})),
21+
}));
22+
23+
jest.mock('../../util/terminal-ui', () => ({
24+
ui: {
25+
error: jest.fn(),
26+
warning: jest.fn(),
27+
info: jest.fn(),
28+
text: jest.fn(),
29+
table: jest.fn(),
30+
},
31+
}));
32+
33+
describe('skill command', () => {
34+
beforeEach(() => {
35+
jest.clearAllMocks();
36+
mockAddSkill.mockImplementation(async () => undefined);
37+
jest.spyOn(process, 'exit').mockImplementation((() => undefined) as any);
38+
});
39+
40+
it('parses skill add with registry only and forwards undefined skill name', async () => {
41+
const program = new Command();
42+
registerSkillCommand(program);
43+
44+
await program.parseAsync(['node', 'test', 'skill', 'add', 'anthropics/skills']);
45+
46+
expect(mockAddSkill).toHaveBeenCalledWith('anthropics/skills', undefined, {
47+
global: undefined,
48+
environments: undefined,
49+
});
50+
});
51+
52+
it('parses skill add with explicit skill name and forwards both args', async () => {
53+
const program = new Command();
54+
registerSkillCommand(program);
55+
56+
await program.parseAsync(['node', 'test', 'skill', 'add', 'anthropics/skills', 'frontend-design']);
57+
58+
expect(mockAddSkill).toHaveBeenCalledWith('anthropics/skills', 'frontend-design', {
59+
global: undefined,
60+
environments: undefined,
61+
});
62+
});
63+
64+
it('shows a warning instead of exiting when skill selection is cancelled', async () => {
65+
mockAddSkill.mockImplementation(async () => {
66+
throw new Error('Skill selection cancelled.');
67+
});
68+
69+
const program = new Command();
70+
registerSkillCommand(program);
71+
72+
await program.parseAsync(['node', 'test', 'skill', 'add', 'anthropics/skills']);
73+
74+
expect(ui.warning).toHaveBeenCalledWith('Skill selection cancelled.');
75+
expect(ui.error).not.toHaveBeenCalled();
76+
expect(process.exit).not.toHaveBeenCalled();
77+
});
78+
79+
it('registers the add command with an optional skill-name argument', () => {
80+
const program = new Command();
81+
registerSkillCommand(program);
82+
83+
const skillCommand = program.commands.find(command => command.name() === 'skill');
84+
const addCommand = skillCommand?.commands.find(command => command.name() === 'add');
85+
86+
expect(addCommand?.usage()).toContain('<registry-repo>');
87+
expect(addCommand?.usage()).toContain('[skill-name]');
88+
});
89+
});

0 commit comments

Comments
 (0)