@skilly-hand/skilly-hand 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,39 @@ All notable changes to this project are documented in this file.
16
16
  ### Removed
17
17
  - _None._
18
18
 
19
+ ## [0.6.0] - 2026-04-03
20
+ [View on npm](https://www.npmjs.com/package/@skilly-hand/skilly-hand/v/0.6.0)
21
+
22
+ ### Added
23
+ - Added portable skill `angular-guidelines` for Angular-only generation/review workflows, including mandatory latest-stable preflight checks and modern Angular best-practice defaults.
24
+
25
+ ### Changed
26
+ - _None._
27
+
28
+ ### Fixed
29
+ - _None._
30
+
31
+ ### Removed
32
+ - _None._
33
+
34
+ ## [0.5.0] - 2026-04-03
35
+ [View on npm](https://www.npmjs.com/package/@skilly-hand/skilly-hand/v/0.5.0)
36
+
37
+ ### Added
38
+ - Interactive command launcher when running `npx skilly-hand` in a TTY, including install skill/agent selection flow.
39
+ - New `selectedSkillIds` install path for explicitly choosing portable skills.
40
+ - Comprehensive CLI interaction tests in `tests/interactive-cli.test.js`.
41
+
42
+ ### Changed
43
+ - Help, docs, and install/uninstall confirmation messaging now reflect current behavior and naming (`skilly-hand` branding).
44
+ - CLI bin execution mode and command routing were refactored into testable `runCli`/service helpers.
45
+
46
+ ### Fixed
47
+ - Non-interactive invocation without a command now defaults to install output instead of opening prompts.
48
+
49
+ ### Removed
50
+ - _None._
51
+
19
52
  ## [0.4.0] - 2026-04-03
20
53
  [View on npm](https://www.npmjs.com/package/@skilly-hand/skilly-hand/v/0.4.0)
21
54
 
package/README.md CHANGED
@@ -37,6 +37,8 @@ npm install
37
37
  npx skilly-hand
38
38
  ```
39
39
 
40
+ `npx skilly-hand` opens an interactive command launcher when running in a TTY.
41
+
40
42
  ---
41
43
 
42
44
  ## Commands
@@ -49,6 +51,14 @@ npx skilly-hand
49
51
  | `npx skilly-hand doctor` | Diagnose installation and configuration issues |
50
52
  | `npx skilly-hand uninstall` | Remove installed skills |
51
53
 
54
+ ### Common Flags
55
+
56
+ | Flag | Description |
57
+ | ---- | ----------- |
58
+ | `--json` | Emit machine-readable output and disable interactive prompts |
59
+ | `--yes`, `-y` | Skip confirmation prompts for mutating commands (`install`, `uninstall`) |
60
+ | `--dry-run` | Preview install plan without writing files |
61
+
52
62
  ---
53
63
 
54
64
  ## Current Portable Catalog
@@ -56,6 +66,7 @@ npx skilly-hand
56
66
  The catalog currently includes:
57
67
 
58
68
  - `agents-root-orchestrator`
69
+ - `angular-guidelines`
59
70
  - `figma-mcp-0to1`
60
71
  - `skill-creator`
61
72
  - `spec-driven-development`
package/catalog/README.md CHANGED
@@ -5,6 +5,7 @@ Published portable skills consumed by the `skilly-hand` CLI.
5
5
  | Skill | Description | Tags | Installs For |
6
6
  | ----- | ----------- | ---- | ------------ |
7
7
  | `agents-root-orchestrator` | Author root AGENTS.md as a Where/What/When orchestrator that routes tasks and skill invocation clearly. | core, workflow, orchestration | all |
8
+ | `angular-guidelines` | Guide Angular code generation and review using latest stable Angular verification and modern framework best practices. | angular, frontend, workflow, best-practices | all |
8
9
  | `figma-mcp-0to1` | Guide users from Figma MCP installation and authentication through first canvas creation, with function-level tool coverage and operational recovery patterns. | figma, mcp, workflow, design | all |
9
10
  | `skill-creator` | Create and standardize AI skills with reusable structure, metadata rules, and templates. | core, workflow, authoring | all |
10
11
  | `spec-driven-development` | Plan, execute, and verify multi-step work through versioned specs with small, testable tasks. | core, workflow, planning | all |
@@ -1,5 +1,6 @@
1
1
  [
2
2
  "agents-root-orchestrator",
3
+ "angular-guidelines",
3
4
  "figma-mcp-0to1",
4
5
  "skill-creator",
5
6
  "spec-driven-development",
@@ -0,0 +1,176 @@
1
+ # Angular Guidelines
2
+
3
+ ## When to Use
4
+
5
+ Use this skill when:
6
+
7
+ - You are generating Angular components, directives, pipes, services, or supporting files.
8
+ - You are refactoring existing Angular code to current framework patterns.
9
+ - You are reviewing Angular code quality and framework-alignment in an Angular workspace.
10
+
11
+ Do not use this skill for:
12
+
13
+ - Non-Angular frontend stacks (React, Vue, Svelte, or framework-agnostic UI tasks).
14
+ - Deep architecture decisions outside code artifact generation/review scope.
15
+ - Pure test-strategy design unrelated to Angular implementation details.
16
+
17
+ ---
18
+
19
+ ## Routing Map
20
+
21
+ Choose sub-agents by intent:
22
+
23
+ | Intent | Sub-agent |
24
+ | --- | --- |
25
+ | Create, refactor, or review Angular components | [agents/component-creator.md](agents/component-creator.md) |
26
+ | Write or review Angular tests | [agents/angular-tester.md](agents/angular-tester.md) |
27
+
28
+ ---
29
+
30
+ ## Standard Execution Sequence
31
+
32
+ 1. Run latest stable Angular preflight checks.
33
+ 2. Route to the smallest matching sub-agent by task intent.
34
+ 3. Apply the sub-agent checklist before finalizing generated code or review output.
35
+
36
+ ---
37
+
38
+ ## Critical Patterns
39
+
40
+ ### Pattern 1: Latest Stable Angular Preflight (Mandatory)
41
+
42
+ Before generating or changing Angular code:
43
+
44
+ 1. Check the latest stable Angular core release:
45
+ `npm view @angular/core version`
46
+ 2. Check the project's installed or declared Angular version:
47
+ `npm ls @angular/core` or inspect `package.json`.
48
+ 3. If versions diverge, generate content for the latest stable APIs and call out upgrade steps.
49
+ 4. If npm metadata is unavailable, verify against official Angular release sources before proceeding.
50
+
51
+ Never hardcode a specific Angular major as the default baseline.
52
+
53
+ ### Pattern 2: Modern Angular Defaults for New Code
54
+
55
+ Use these defaults unless project constraints explicitly prevent them:
56
+
57
+ | Area | Default |
58
+ | --- | --- |
59
+ | Component model | Standalone-first (`standalone: true`) |
60
+ | State + bindings | Signals (`signal`, `computed`, `input`, `output`) |
61
+ | Template flow | `@if`, `@for`, `@switch` control flow blocks |
62
+ | Dependency injection | `inject()` over constructor injection for new code |
63
+ | Forms | Typed reactive forms |
64
+ | Rendering strategy | OnPush-friendly patterns and deferred/lazy rendering where appropriate |
65
+
66
+ ### Pattern 3: Generation and Review Guardrails
67
+
68
+ - Keep generated files focused and minimal for the requested artifact.
69
+ - Prefer framework-native patterns over custom abstractions unless required by repo conventions.
70
+ - Call out deprecated patterns in reviewed code and suggest modern Angular replacements.
71
+ - For component-specific work, apply [agents/component-creator.md](agents/component-creator.md).
72
+ - For testing-specific work, apply [agents/angular-tester.md](agents/angular-tester.md).
73
+
74
+ ---
75
+
76
+ ## Decision Tree
77
+
78
+ ```text
79
+ Is this an Angular project (angular.json or @angular/core present)?
80
+ NO -> Do not apply this skill
81
+ YES -> Continue
82
+
83
+ Is this a create/generate task?
84
+ YES -> Run latest stable preflight, then generate with modern defaults
85
+ NO -> Continue
86
+
87
+ Is this a refactor task?
88
+ YES -> Preserve behavior, migrate incrementally to modern Angular patterns
89
+ NO -> Continue
90
+
91
+ Is this a review task?
92
+ YES -> Validate latest-stable alignment + best-practice checklist
93
+ NO -> Apply the minimal Angular guidance needed for the request
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Code Examples
99
+
100
+ ### Example 1: Standalone + Signals Component
101
+
102
+ ```typescript
103
+ import { ChangeDetectionStrategy, Component, computed, input, output } from "@angular/core";
104
+
105
+ @Component({
106
+ selector: "app-badge",
107
+ standalone: true,
108
+ template: `
109
+ <span [attr.data-tone]="tone()">{{ label() }}</span>
110
+ @if (showCount()) {
111
+ <small>{{ count() }}</small>
112
+ }
113
+ `,
114
+ changeDetection: ChangeDetectionStrategy.OnPush
115
+ })
116
+ export class BadgeComponent {
117
+ readonly label = input.required<string>();
118
+ readonly count = input(0);
119
+ readonly increment = output<void>();
120
+ readonly showCount = computed(() => this.count() > 0);
121
+ }
122
+ ```
123
+
124
+ ### Example 2: Service with `inject()`
125
+
126
+ ```typescript
127
+ import { Injectable, inject } from "@angular/core";
128
+ import { HttpClient } from "@angular/common/http";
129
+
130
+ @Injectable({ providedIn: "root" })
131
+ export class ProfileService {
132
+ private readonly http = inject(HttpClient);
133
+
134
+ getProfile() {
135
+ return this.http.get("/api/profile");
136
+ }
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Review Checklist
143
+
144
+ - Latest stable Angular preflight was completed before code generation/refactor.
145
+ - New artifacts use standalone-first + signal-first patterns where applicable.
146
+ - Template control flow uses modern block syntax.
147
+ - DI and forms follow modern typed Angular practices.
148
+ - Output avoids deprecated Angular APIs unless needed for compatibility.
149
+
150
+ ---
151
+
152
+ ## Commands
153
+
154
+ ```bash
155
+ # Latest stable Angular version
156
+ npm view @angular/core version
157
+
158
+ # Workspace Angular version
159
+ npm ls @angular/core
160
+
161
+ # Create a standalone component
162
+ ng generate component <name> --standalone
163
+
164
+ # Apply Angular framework updates in a workspace
165
+ ng update @angular/core @angular/cli
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Resources
171
+
172
+ - Angular docs: https://angular.dev
173
+ - Angular API reference: https://angular.dev/api
174
+ - Angular update guide: https://angular.dev/update-guide
175
+ - Angular blog (official releases): https://blog.angular.dev
176
+ - Angular GitHub releases: https://github.com/angular/angular/releases
@@ -0,0 +1,130 @@
1
+ # Angular Tester
2
+
3
+ ## When to Use
4
+
5
+ Use this sub-agent when:
6
+
7
+ - Adding or updating Angular component/service tests.
8
+ - Reviewing Angular tests for correctness and modern framework usage.
9
+ - Choosing test patterns for async, signal-based, or HTTP-driven behaviors.
10
+
11
+ Do not use this sub-agent for:
12
+
13
+ - Component implementation guidance without tests (use `component-creator`).
14
+ - Framework-agnostic testing standards not tied to Angular behavior.
15
+
16
+ ---
17
+
18
+ ## Runner Policy
19
+
20
+ - Default: Vitest-first in Angular workspaces.
21
+ - Fallback: Use Jasmine/Karma patterns only when the workspace is already configured for Jasmine.
22
+ - Do not force runner migration inside unrelated feature tasks.
23
+
24
+ ---
25
+
26
+ ## Core Testing Patterns
27
+
28
+ - Configure `TestBed` with standalone imports for component tests.
29
+ - Test signal and computed behavior directly when possible.
30
+ - Test signal inputs/outputs using component instance APIs and DOM-triggered events.
31
+ - Cover async behavior with `fakeAsync`/`tick`/`flush` or `waitForAsync`/`whenStable`.
32
+ - For HTTP paths, use `HttpClientTestingModule`/`HttpTestingController` and verify requests.
33
+ - For services, prefer explicit DI setup and clear mocking boundaries.
34
+
35
+ ---
36
+
37
+ ## Command Matrix
38
+
39
+ | Scenario | Preferred Command | Fallback |
40
+ | --- | --- | --- |
41
+ | Run all unit tests | `ng test` | Existing workspace test command |
42
+ | Watch mode | `ng test --watch` | Workspace equivalent |
43
+ | Coverage | `ng test --code-coverage` | Workspace equivalent |
44
+ | CI-style single run | `ng test --watch=false` | Workspace equivalent |
45
+
46
+ If the workspace uses Jasmine/Karma, keep command behavior aligned with existing runner configuration.
47
+
48
+ ---
49
+
50
+ ## Decision Tree
51
+
52
+ ```text
53
+ Is the target under test a standalone component?
54
+ YES -> Use TestBed with imports: [ComponentUnderTest]
55
+ NO -> Continue
56
+
57
+ Is this primarily service logic?
58
+ YES -> Configure TestBed providers/injection and mock dependencies
59
+ NO -> Continue
60
+
61
+ Is behavior async (timers, promises, stabilization)?
62
+ YES -> Use fakeAsync/tick or waitForAsync/whenStable
63
+ NO -> Continue
64
+
65
+ Does behavior involve HTTP or resource loading?
66
+ YES -> Use HttpTestingController and assert request + response flow
67
+ NO -> Use direct synchronous assertions
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Test Snippets
73
+
74
+ ### Standalone Component Test
75
+
76
+ ```typescript
77
+ import { ComponentFixture, TestBed } from "@angular/core/testing";
78
+ import { describe, expect, it, beforeEach } from "vitest";
79
+ import { CounterComponent } from "./counter.component";
80
+
81
+ describe("CounterComponent", () => {
82
+ let fixture: ComponentFixture<CounterComponent>;
83
+
84
+ beforeEach(async () => {
85
+ await TestBed.configureTestingModule({
86
+ imports: [CounterComponent]
87
+ }).compileComponents();
88
+ fixture = TestBed.createComponent(CounterComponent);
89
+ fixture.detectChanges();
90
+ });
91
+
92
+ it("renders default count", () => {
93
+ expect(fixture.componentInstance.count()).toBe(0);
94
+ });
95
+ });
96
+ ```
97
+
98
+ ### HTTP Service Test
99
+
100
+ ```typescript
101
+ import { TestBed } from "@angular/core/testing";
102
+ import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
103
+
104
+ describe("ProfileService", () => {
105
+ it("requests profile", () => {
106
+ TestBed.configureTestingModule({
107
+ imports: [HttpClientTestingModule]
108
+ });
109
+ const service = TestBed.inject(ProfileService);
110
+ const httpMock = TestBed.inject(HttpTestingController);
111
+
112
+ service.getProfile().subscribe();
113
+
114
+ const req = httpMock.expectOne("/api/profile");
115
+ expect(req.request.method).toBe("GET");
116
+ req.flush({ id: "1" });
117
+ httpMock.verify();
118
+ });
119
+ });
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Review Checklist
125
+
126
+ - Tests follow the workspace runner policy (Vitest-first, Jasmine fallback only when preconfigured).
127
+ - Standalone Angular artifacts are tested with appropriate TestBed setup.
128
+ - Signal/input/output behavior is asserted explicitly.
129
+ - Async and HTTP behavior use deterministic Angular testing utilities.
130
+ - Assertions verify user-visible behavior or contract-level outcomes, not incidental internals.
@@ -0,0 +1,124 @@
1
+ # Angular Component Creator
2
+
3
+ ## When to Use
4
+
5
+ Use this sub-agent when:
6
+
7
+ - Creating a new Angular component.
8
+ - Refactoring an existing Angular component to modern patterns.
9
+ - Reviewing component implementation details and template patterns.
10
+
11
+ Do not use this sub-agent for:
12
+
13
+ - Directive, pipe, or service implementation.
14
+ - Repository-wide architecture decisions.
15
+ - Test-only tasks (use `angular-tester`).
16
+
17
+ ---
18
+
19
+ ## Core Rules
20
+
21
+ - Keep explicit `standalone: true` in generated component metadata.
22
+ - Use signal APIs for component IO and state (`input`, `output`, `signal`, `computed`).
23
+ - Use native template control flow blocks (`@if`, `@for`, `@switch`).
24
+ - Prefer `host` object bindings/events over decorator-based host metadata.
25
+ - Use OnPush-friendly reactive patterns and avoid zone-dependent assumptions.
26
+ - For interactive components, include keyboard and ARIA semantics in host bindings.
27
+
28
+ ---
29
+
30
+ ## Template Snippets
31
+
32
+ ### Minimal Standalone Component
33
+
34
+ ```typescript
35
+ import { ChangeDetectionStrategy, Component } from "@angular/core";
36
+
37
+ @Component({
38
+ selector: "app-example",
39
+ standalone: true,
40
+ template: `<p>Example works</p>`,
41
+ changeDetection: ChangeDetectionStrategy.OnPush
42
+ })
43
+ export class ExampleComponent {}
44
+ ```
45
+
46
+ ### Signal Inputs and Outputs
47
+
48
+ ```typescript
49
+ import { Component, input, output, computed } from "@angular/core";
50
+
51
+ @Component({
52
+ selector: "app-counter",
53
+ standalone: true,
54
+ template: `<button (click)="increment.emit()">{{ label() }} {{ count() }}</button>`
55
+ })
56
+ export class CounterComponent {
57
+ readonly label = input.required<string>();
58
+ readonly count = input(0);
59
+ readonly increment = output<void>();
60
+ readonly isNonZero = computed(() => this.count() > 0);
61
+ }
62
+ ```
63
+
64
+ ### Host Bindings and Events
65
+
66
+ ```typescript
67
+ @Component({
68
+ selector: "button[appActionButton]",
69
+ standalone: true,
70
+ template: `<ng-content />`,
71
+ host: {
72
+ "role": "button",
73
+ "[attr.aria-disabled]": "disabled() ? 'true' : null",
74
+ "[class.is-disabled]": "disabled()",
75
+ "(click)": "onClick($event)",
76
+ "(keydown.enter)": "onClick($event)"
77
+ }
78
+ })
79
+ export class ActionButtonComponent {
80
+ readonly disabled = input(false);
81
+ readonly pressed = output<void>();
82
+
83
+ onClick(event: Event) {
84
+ if (this.disabled()) {
85
+ event.preventDefault();
86
+ return;
87
+ }
88
+ this.pressed.emit();
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### Content Projection and Render-Safe Hooks
94
+
95
+ ```typescript
96
+ import { Component, afterNextRender } from "@angular/core";
97
+
98
+ @Component({
99
+ selector: "app-card",
100
+ standalone: true,
101
+ template: `
102
+ <header><ng-content select="[card-header]" /></header>
103
+ <section><ng-content /></section>
104
+ <footer><ng-content select="[card-footer]" /></footer>
105
+ `
106
+ })
107
+ export class CardComponent {
108
+ constructor() {
109
+ afterNextRender(() => {
110
+ // Run post-render DOM logic only when needed.
111
+ });
112
+ }
113
+ }
114
+ ```
115
+
116
+ ---
117
+
118
+ ## Review Checklist
119
+
120
+ - Component metadata includes explicit `standalone: true`.
121
+ - Inputs/outputs use modern signal-based APIs where applicable.
122
+ - Templates use native control flow blocks, not legacy structural directives.
123
+ - Host interaction includes required ARIA and keyboard semantics for interactive UI.
124
+ - Change detection and state flow are OnPush-friendly and predictable.
@@ -0,0 +1,41 @@
1
+ {
2
+ "id": "angular-guidelines",
3
+ "title": "Angular Guidelines",
4
+ "description": "Guide Angular code generation and review using latest stable Angular verification and modern framework best practices.",
5
+ "portable": true,
6
+ "tags": ["angular", "frontend", "workflow", "best-practices"],
7
+ "detectors": ["angular"],
8
+ "detectionTriggers": ["auto"],
9
+ "installsFor": ["all"],
10
+ "agentSupport": ["codex", "claude", "cursor", "gemini", "copilot"],
11
+ "skillMetadata": {
12
+ "author": "skilly-hand",
13
+ "last-edit": "2026-04-03",
14
+ "license": "Apache-2.0",
15
+ "version": "1.1.1",
16
+ "changelog": "Added allowed-modes metadata to declare angular-guidelines sub-agent routing targets; improves discoverability of component-creator and angular-tester delegation modes; affects angular-guidelines manifest metadata",
17
+ "auto-invoke": "Generating, reviewing, or refactoring Angular code artifacts in Angular projects",
18
+ "allowed-modes": [
19
+ "component-creator",
20
+ "angular-tester"
21
+ ],
22
+ "allowed-tools": [
23
+ "Read",
24
+ "Edit",
25
+ "Write",
26
+ "Glob",
27
+ "Grep",
28
+ "Bash",
29
+ "WebFetch",
30
+ "WebSearch",
31
+ "Task",
32
+ "SubAgent"
33
+ ]
34
+ },
35
+ "files": [
36
+ { "path": "SKILL.md", "kind": "instruction" },
37
+ { "path": "agents/component-creator.md", "kind": "instruction" },
38
+ { "path": "agents/angular-tester.md", "kind": "instruction" }
39
+ ],
40
+ "dependencies": []
41
+ }
@@ -12,9 +12,15 @@
12
12
  "author": "skilly-hand",
13
13
  "last-edit": "2026-04-03",
14
14
  "license": "Apache-2.0",
15
- "version": "1.0.0",
16
- "changelog": "Added a complete Figma MCP setup-to-canvas guide with orchestrator subskills and full official tool matrix coverage; reduces onboarding ambiguity and improves first-run success rates; affects Figma MCP setup, tool selection, and troubleshooting workflows",
15
+ "version": "1.0.1",
16
+ "changelog": "Added allowed-modes metadata to declare figma-mcp-0to1 sub-agent routing targets; improves discoverability of install-auth, tool-function-catalog, canvas-creation-playbook, and troubleshooting-ops delegation modes; affects figma-mcp-0to1 manifest metadata",
17
17
  "auto-invoke": "Installing, configuring, or using Figma MCP from setup through first canvas creation",
18
+ "allowed-modes": [
19
+ "install-auth",
20
+ "tool-function-catalog",
21
+ "canvas-creation-playbook",
22
+ "troubleshooting-ops"
23
+ ],
18
24
  "allowed-tools": [
19
25
  "Read",
20
26
  "Edit",
@@ -33,6 +33,31 @@ Recommended task size:
33
33
 
34
34
  ---
35
35
 
36
+ ## OpenSpec Complementary Support
37
+
38
+ Default execution SHOULD remain the local `.sdd` workflow.
39
+
40
+ Recur to OpenSpec support when the task needs complementary structure for:
41
+
42
+ - Multi-session continuity where planning context must persist across chats.
43
+ - Shareable planning artifacts for review before implementation.
44
+ - Requirement-delta clarity that benefits from explicit change proposals.
45
+
46
+ Routing rules:
47
+
48
+ - Keep the local `.sdd/active/<feature-name>/spec.md` as the execution source of truth unless the team explicitly standardizes on OpenSpec paths.
49
+ - If OpenSpec is unavailable, continue in `.sdd` and document assumptions directly in the active spec.
50
+
51
+ | Use local SDD only | Use OpenSpec support |
52
+ | --- | --- |
53
+ | Single-session or straightforward work with clear requirements | Work spans multiple sessions and needs persistent planning context |
54
+ | Existing `.sdd` artifacts already provide enough review clarity | Team needs proposal/design/tasks artifacts for async review |
55
+ | Requirement changes are small and easy to track in-place | Requirement deltas are complex and need explicit change framing |
56
+
57
+ Reference (informational): [https://openspec.dev/](https://openspec.dev/)
58
+
59
+ ---
60
+
36
61
  ## Spec Structure
37
62
 
38
63
  A practical spec includes:
@@ -12,9 +12,15 @@
12
12
  "author": "skilly-hand",
13
13
  "last-edit": "2026-04-03",
14
14
  "license": "Apache-2.0",
15
- "version": "1.0.1",
16
- "changelog": "Added explicit delta/full guidance, archive rules, task sizing decision tree, and common mistakes section; improves planning clarity and reduces execution ambiguity; affects spec-driven-development SKILL.md usage guidance",
15
+ "version": "1.0.3",
16
+ "changelog": "Added OpenSpec complementary support routing guidance to spec-driven-development instructions; improves planning continuity and review clarity when local SDD needs reinforcement; affects spec-driven-development SKILL guidance and manifest metadata",
17
17
  "auto-invoke": "Planning or executing feature work, bug fixes, and multi-phase implementation",
18
+ "allowed-modes": [
19
+ "plan",
20
+ "apply",
21
+ "verify",
22
+ "orchestrate"
23
+ ],
18
24
  "allowed-tools": [
19
25
  "Read",
20
26
  "Edit",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/skilly-hand",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "license": "CC-BY-NC-4.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -40,5 +40,8 @@
40
40
  "detect": "node ./packages/cli/src/bin.js detect",
41
41
  "list": "node ./packages/cli/src/bin.js list",
42
42
  "doctor": "node ./packages/cli/src/bin.js doctor"
43
+ },
44
+ "dependencies": {
45
+ "@inquirer/prompts": "^7.10.1"
43
46
  }
44
47
  }
@@ -1,17 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import path from "node:path";
3
3
  import { createRequire } from "node:module";
4
+ import { pathToFileURL } from "node:url";
5
+ import { checkbox as inquirerCheckbox, confirm as inquirerConfirm, select as inquirerSelect } from "@inquirer/prompts";
4
6
  import { loadAllSkills } from "../../catalog/src/index.js";
5
- import { installProject, runDoctor, uninstallProject } from "../../core/src/index.js";
7
+ import {
8
+ DEFAULT_AGENTS,
9
+ installProject,
10
+ resolveSkillSelection,
11
+ runDoctor,
12
+ uninstallProject
13
+ } from "../../core/src/index.js";
6
14
  import { createTerminalRenderer } from "../../core/src/terminal.js";
7
15
  import { detectProject } from "../../detectors/src/index.js";
8
16
 
9
17
  const require = createRequire(import.meta.url);
10
18
  const { version } = require("../../../package.json");
11
19
 
12
- const renderer = createTerminalRenderer();
20
+ function isExecutedDirectly(metaUrl, argv1) {
21
+ if (!argv1) return false;
22
+ return metaUrl === pathToFileURL(argv1).href;
23
+ }
13
24
 
14
- function parseArgs(argv) {
25
+ export function parseArgs(argv) {
15
26
  const args = [...argv];
16
27
  const positional = [];
17
28
  const flags = {
@@ -54,9 +65,10 @@ function parseArgs(argv) {
54
65
  return { command: positional[0], flags };
55
66
  }
56
67
 
57
- function buildHelpText() {
68
+ function buildHelpText(renderer, appVersion) {
58
69
  const usage = renderer.section("Usage", renderer.list([
59
- "npx skilly-hand [install]",
70
+ "npx skilly-hand # interactive launcher when running in a TTY",
71
+ "npx skilly-hand install",
60
72
  "npx skilly-hand detect",
61
73
  "npx skilly-hand list",
62
74
  "npx skilly-hand doctor",
@@ -66,7 +78,7 @@ function buildHelpText() {
66
78
  const flags = renderer.section("Flags", renderer.list([
67
79
  "--dry-run Show install plan without writing files",
68
80
  "--json Emit stable JSON output for automation",
69
- "--yes, -y Reserved for future non-interactive confirmations",
81
+ "--yes, -y Skip install/uninstall confirmations",
70
82
  "--verbose, -v Reserved for future debug detail",
71
83
  "--agent, -a <name> codex|claude|cursor|gemini|copilot (repeatable)",
72
84
  "--cwd <path> Project root (defaults to current directory)",
@@ -76,21 +88,22 @@ function buildHelpText() {
76
88
  ], { bullet: "-" }));
77
89
 
78
90
  const examples = renderer.section("Examples", renderer.list([
91
+ "npx skilly-hand",
79
92
  "npx skilly-hand install --dry-run",
80
93
  "npx skilly-hand detect --json",
81
94
  "npx skilly-hand install --agent codex --agent claude",
82
- "npx skilly-hand list --include workflow"
95
+ "npx skilly-hand uninstall --yes"
83
96
  ], { bullet: "-" }));
84
97
 
85
98
  return renderer.joinBlocks([
86
- renderer.banner(version),
99
+ renderer.banner(appVersion),
87
100
  usage,
88
101
  flags,
89
102
  examples
90
103
  ]);
91
104
  }
92
105
 
93
- function printInstallResult(result, flags) {
106
+ function printInstallResult(renderer, appVersion, result, flags) {
94
107
  const mode = flags.dryRun ? "dry-run" : "apply";
95
108
  const preflight = renderer.section(
96
109
  "Install Preflight",
@@ -135,19 +148,19 @@ function printInstallResult(result, flags) {
135
148
 
136
149
  const nextSteps = result.applied
137
150
  ? renderer.nextSteps([
138
- "Review generated AGENTS and assistant instruction files.",
139
- "Run `npx skilly-hand doctor` to validate installation health.",
140
- "Use `npx skilly-hand uninstall` to restore backed-up files if needed."
141
- ])
151
+ "Review generated AGENTS and assistant instruction files.",
152
+ "Run `npx skilly-hand doctor` to validate installation health.",
153
+ "Use `npx skilly-hand uninstall` to restore backed-up files if needed."
154
+ ])
142
155
  : renderer.nextSteps([
143
- "Run `npx skilly-hand install` to apply this plan.",
144
- "Adjust `--include` and `--exclude` tags to tune skill selection."
145
- ]);
156
+ "Run `npx skilly-hand install` to apply this plan.",
157
+ "Adjust `--include` and `--exclude` tags to tune skill selection."
158
+ ]);
146
159
 
147
- renderer.write(renderer.joinBlocks([renderer.banner(version), preflight, detections, skills, status, nextSteps]));
160
+ renderer.write(renderer.joinBlocks([renderer.banner(appVersion), preflight, detections, skills, status, nextSteps]));
148
161
  }
149
162
 
150
- function printDetectResult(cwd, detections) {
163
+ function printDetectResult(renderer, cwd, detections) {
151
164
  const summary = renderer.section(
152
165
  "Detection Summary",
153
166
  renderer.kv([
@@ -166,7 +179,7 @@ function printDetectResult(cwd, detections) {
166
179
  renderer.write(renderer.joinBlocks([summary, findings]));
167
180
  }
168
181
 
169
- function printListResult(skills) {
182
+ function printListResult(renderer, skills) {
170
183
  const summary = renderer.section(
171
184
  "Catalog Summary",
172
185
  renderer.kv([["Skills available", String(skills.length)]])
@@ -193,7 +206,7 @@ function printListResult(skills) {
193
206
  renderer.write(renderer.joinBlocks([summary, table]));
194
207
  }
195
208
 
196
- function printDoctorResult(result) {
209
+ function printDoctorResult(renderer, result) {
197
210
  const badge = renderer.healthBadge(result.installed);
198
211
 
199
212
  const summary = renderer.section(
@@ -239,7 +252,7 @@ function printDoctorResult(result) {
239
252
  renderer.write(renderer.joinBlocks([badge, summary, lock, issues, probes]));
240
253
  }
241
254
 
242
- function printUninstallResult(result) {
255
+ function printUninstallResult(renderer, result) {
243
256
  if (result.removed) {
244
257
  renderer.write(
245
258
  renderer.joinBlocks([
@@ -261,34 +274,193 @@ function printUninstallResult(result) {
261
274
  );
262
275
  }
263
276
 
264
- async function main() {
265
- const { command, flags } = parseArgs(process.argv.slice(2));
277
+ export function buildErrorHint(message) {
278
+ if (message.startsWith("Unknown command:")) {
279
+ return "Run `npx skilly-hand --help` to see available commands.";
280
+ }
281
+ if (message.startsWith("Unknown flag:") || message.startsWith("Missing value")) {
282
+ return "Check command flags with `npx skilly-hand --help`.";
283
+ }
284
+ return "Retry with `--verbose` for expanded context if needed.";
285
+ }
266
286
 
267
- if (flags.help) {
268
- if (flags.json) {
269
- renderer.writeJson({
270
- command: command || "install",
271
- help: true,
272
- usage: [
273
- "npx skilly-hand [install]",
274
- "npx skilly-hand detect",
275
- "npx skilly-hand list",
276
- "npx skilly-hand doctor",
277
- "npx skilly-hand uninstall"
278
- ]
279
- });
280
- return;
281
- }
287
+ export function createPromptAdapter({ selectImpl, checkboxImpl, confirmImpl } = {}) {
288
+ return {
289
+ select: selectImpl || inquirerSelect,
290
+ checkbox: checkboxImpl || inquirerCheckbox,
291
+ confirm: confirmImpl || inquirerConfirm
292
+ };
293
+ }
294
+
295
+ function createServices(overrides = {}) {
296
+ return {
297
+ loadAllSkills,
298
+ installProject,
299
+ resolveSkillSelection,
300
+ runDoctor,
301
+ uninstallProject,
302
+ detectProject,
303
+ defaultAgents: DEFAULT_AGENTS,
304
+ ...overrides
305
+ };
306
+ }
307
+
308
+ function isInteractiveLauncherMode({ command, flags, stdout }) {
309
+ return !command && !flags.json && Boolean(stdout?.isTTY);
310
+ }
311
+
312
+ async function runInteractiveInstall({
313
+ cwd,
314
+ renderer,
315
+ prompt,
316
+ services,
317
+ appVersion
318
+ }) {
319
+ const [catalog, detections] = await Promise.all([
320
+ services.loadAllSkills(),
321
+ services.detectProject(cwd)
322
+ ]);
323
+ const portableCatalog = catalog.filter((skill) => skill.portable).sort((a, b) => a.id.localeCompare(b.id));
324
+ const preselected = new Set(
325
+ services
326
+ .resolveSkillSelection({ catalog, detections, includeTags: [], excludeTags: [] })
327
+ .map((skill) => skill.id)
328
+ );
329
+
330
+ const selectedSkillIds = await prompt.checkbox({
331
+ message: "Select skills to install",
332
+ choices: portableCatalog.map((skill) => ({
333
+ value: skill.id,
334
+ name: `${skill.id} - ${skill.title}`,
335
+ checked: preselected.has(skill.id)
336
+ }))
337
+ });
338
+
339
+ const selectedAgents = await prompt.checkbox({
340
+ message: "Select AI assistants to configure",
341
+ choices: services.defaultAgents.map((agent) => ({
342
+ value: agent,
343
+ name: agent,
344
+ checked: true
345
+ }))
346
+ });
347
+
348
+ const preview = await services.installProject({
349
+ cwd,
350
+ agents: selectedAgents,
351
+ dryRun: true,
352
+ selectedSkillIds
353
+ });
354
+
355
+ printInstallResult(renderer, appVersion, preview, {
356
+ dryRun: true,
357
+ include: [],
358
+ exclude: []
359
+ });
282
360
 
283
- renderer.write(buildHelpText());
361
+ const shouldApply = await prompt.confirm({
362
+ message: "Apply installation changes now?",
363
+ default: true
364
+ });
365
+
366
+ if (!shouldApply) {
367
+ renderer.write(renderer.status("info", "Installation cancelled.", "No files were written."));
284
368
  return;
285
369
  }
286
370
 
287
- const cwd = path.resolve(flags.cwd || process.cwd());
288
- const effectiveCommand = command || "install";
371
+ const applied = await services.installProject({
372
+ cwd,
373
+ agents: selectedAgents,
374
+ dryRun: false,
375
+ selectedSkillIds
376
+ });
377
+
378
+ printInstallResult(renderer, appVersion, applied, {
379
+ dryRun: false,
380
+ include: [],
381
+ exclude: []
382
+ });
383
+ }
384
+
385
+ async function runInteractiveSession({
386
+ cwd,
387
+ renderer,
388
+ prompt,
389
+ services,
390
+ appVersion
391
+ }) {
392
+ renderer.write(renderer.banner(appVersion));
393
+
394
+ while (true) {
395
+ const selection = await prompt.select({
396
+ message: "Select a command",
397
+ choices: [
398
+ { value: "install", name: "Install" },
399
+ { value: "detect", name: "Detect" },
400
+ { value: "list", name: "List" },
401
+ { value: "doctor", name: "Doctor" },
402
+ { value: "uninstall", name: "Uninstall" },
403
+ { value: "exit", name: "Exit" }
404
+ ]
405
+ });
406
+
407
+ if (selection === "exit") {
408
+ renderer.write(renderer.status("info", "Exited skilly-hand interactive mode."));
409
+ return;
410
+ }
411
+
412
+ if (selection === "install") {
413
+ await runInteractiveInstall({ cwd, renderer, prompt, services, appVersion });
414
+ continue;
415
+ }
416
+
417
+ if (selection === "detect") {
418
+ const detections = await services.detectProject(cwd);
419
+ printDetectResult(renderer, cwd, detections);
420
+ continue;
421
+ }
422
+
423
+ if (selection === "list") {
424
+ const skills = await services.loadAllSkills();
425
+ printListResult(renderer, skills);
426
+ continue;
427
+ }
428
+
429
+ if (selection === "doctor") {
430
+ const result = await services.runDoctor(cwd);
431
+ printDoctorResult(renderer, result);
432
+ continue;
433
+ }
289
434
 
290
- if (effectiveCommand === "detect") {
291
- const detections = await detectProject(cwd);
435
+ if (selection === "uninstall") {
436
+ const confirmed = await prompt.confirm({
437
+ message: "Remove the skilly-hand installation from this project?",
438
+ default: false
439
+ });
440
+
441
+ if (!confirmed) {
442
+ renderer.write(renderer.status("info", "Uninstall cancelled."));
443
+ continue;
444
+ }
445
+
446
+ const result = await services.uninstallProject(cwd);
447
+ printUninstallResult(renderer, result);
448
+ }
449
+ }
450
+ }
451
+
452
+ async function runCommand({
453
+ command,
454
+ flags,
455
+ cwd,
456
+ stdout,
457
+ renderer,
458
+ prompt,
459
+ services,
460
+ appVersion
461
+ }) {
462
+ if (command === "detect") {
463
+ const detections = await services.detectProject(cwd);
292
464
  if (flags.json) {
293
465
  renderer.writeJson({
294
466
  command: "detect",
@@ -298,12 +470,12 @@ async function main() {
298
470
  });
299
471
  return;
300
472
  }
301
- printDetectResult(cwd, detections);
473
+ printDetectResult(renderer, cwd, detections);
302
474
  return;
303
475
  }
304
476
 
305
- if (effectiveCommand === "list") {
306
- const skills = await loadAllSkills();
477
+ if (command === "list") {
478
+ const skills = await services.loadAllSkills();
307
479
  if (flags.json) {
308
480
  renderer.writeJson({
309
481
  command: "list",
@@ -312,12 +484,12 @@ async function main() {
312
484
  });
313
485
  return;
314
486
  }
315
- printListResult(skills);
487
+ printListResult(renderer, skills);
316
488
  return;
317
489
  }
318
490
 
319
- if (effectiveCommand === "doctor") {
320
- const result = await runDoctor(cwd);
491
+ if (command === "doctor") {
492
+ const result = await services.runDoctor(cwd);
321
493
  if (flags.json) {
322
494
  renderer.writeJson({
323
495
  command: "doctor",
@@ -325,12 +497,23 @@ async function main() {
325
497
  });
326
498
  return;
327
499
  }
328
- printDoctorResult(result);
500
+ printDoctorResult(renderer, result);
329
501
  return;
330
502
  }
331
503
 
332
- if (effectiveCommand === "uninstall") {
333
- const result = await uninstallProject(cwd);
504
+ if (command === "uninstall") {
505
+ if (!flags.json && !flags.yes && Boolean(stdout?.isTTY)) {
506
+ const confirmed = await prompt.confirm({
507
+ message: "Remove the skilly-hand installation from this project?",
508
+ default: false
509
+ });
510
+ if (!confirmed) {
511
+ renderer.write(renderer.status("info", "Uninstall cancelled."));
512
+ return;
513
+ }
514
+ }
515
+
516
+ const result = await services.uninstallProject(cwd);
334
517
  if (flags.json) {
335
518
  renderer.writeJson({
336
519
  command: "uninstall",
@@ -338,12 +521,23 @@ async function main() {
338
521
  });
339
522
  return;
340
523
  }
341
- printUninstallResult(result);
524
+ printUninstallResult(renderer, result);
342
525
  return;
343
526
  }
344
527
 
345
- if (effectiveCommand === "install") {
346
- const result = await installProject({
528
+ if (command === "install") {
529
+ if (!flags.dryRun && !flags.json && !flags.yes && Boolean(stdout?.isTTY)) {
530
+ const confirmed = await prompt.confirm({
531
+ message: "Apply installation changes to this project?",
532
+ default: true
533
+ });
534
+ if (!confirmed) {
535
+ renderer.write(renderer.status("info", "Installation cancelled.", "No files were written."));
536
+ return;
537
+ }
538
+ }
539
+
540
+ const result = await services.installProject({
347
541
  cwd,
348
542
  agents: flags.agents,
349
543
  dryRun: flags.dryRun,
@@ -361,43 +555,109 @@ async function main() {
361
555
  return;
362
556
  }
363
557
 
364
- printInstallResult(result, flags);
558
+ printInstallResult(renderer, appVersion, result, flags);
365
559
  return;
366
560
  }
367
561
 
368
- throw new Error(`Unknown command: ${effectiveCommand}`);
562
+ throw new Error(`Unknown command: ${command}`);
369
563
  }
370
564
 
371
- const jsonRequested = process.argv.includes("--json");
565
+ export async function runCli({
566
+ argv = process.argv.slice(2),
567
+ stdout = process.stdout,
568
+ stderr = process.stderr,
569
+ env = process.env,
570
+ platform = process.platform,
571
+ prompt = createPromptAdapter(),
572
+ services: providedServices = {},
573
+ appVersion = version,
574
+ cwdResolver = process.cwd
575
+ } = {}) {
576
+ const renderer = createTerminalRenderer({ stdout, stderr, env, platform });
577
+ const services = createServices(providedServices);
578
+ const { command, flags } = parseArgs(argv);
372
579
 
373
- main().catch((error) => {
374
- const hint =
375
- error.message.startsWith("Unknown command:")
376
- ? "Run `npx skilly-hand --help` to see available commands."
377
- : error.message.startsWith("Unknown flag:") || error.message.startsWith("Missing value")
378
- ? "Check command flags with `npx skilly-hand --help`."
379
- : "Retry with `--verbose` for expanded context if needed.";
580
+ if (flags.help) {
581
+ if (flags.json) {
582
+ renderer.writeJson({
583
+ command: command || "install",
584
+ help: true,
585
+ usage: [
586
+ "npx skilly-hand",
587
+ "npx skilly-hand install",
588
+ "npx skilly-hand detect",
589
+ "npx skilly-hand list",
590
+ "npx skilly-hand doctor",
591
+ "npx skilly-hand uninstall"
592
+ ]
593
+ });
594
+ return;
595
+ }
380
596
 
381
- if (jsonRequested) {
382
- renderer.writeErrorJson({
383
- ok: false,
384
- error: {
385
- what: "skilly-hand command failed",
386
- why: error.message,
387
- hint
388
- }
389
- });
390
- process.exitCode = 1;
597
+ renderer.write(buildHelpText(renderer, appVersion));
391
598
  return;
392
599
  }
393
600
 
394
- renderer.writeError(
395
- renderer.error({
396
- what: "skilly-hand command failed",
397
- why: error.message,
398
- hint,
399
- exitCode: 1
400
- })
401
- );
402
- process.exitCode = 1;
403
- });
601
+ const cwd = path.resolve(flags.cwd || cwdResolver());
602
+
603
+ if (isInteractiveLauncherMode({ command, flags, stdout })) {
604
+ try {
605
+ await runInteractiveSession({
606
+ cwd,
607
+ renderer,
608
+ prompt,
609
+ services,
610
+ appVersion
611
+ });
612
+ return;
613
+ } catch (error) {
614
+ if (error?.name === "ExitPromptError") {
615
+ renderer.write(renderer.status("info", "Interactive session cancelled."));
616
+ return;
617
+ }
618
+ throw error;
619
+ }
620
+ }
621
+
622
+ const effectiveCommand = command || "install";
623
+ await runCommand({
624
+ command: effectiveCommand,
625
+ flags,
626
+ cwd,
627
+ stdout,
628
+ renderer,
629
+ prompt,
630
+ services,
631
+ appVersion
632
+ });
633
+ }
634
+
635
+ if (isExecutedDirectly(import.meta.url, process.argv[1])) {
636
+ const jsonRequested = process.argv.includes("--json");
637
+ const renderer = createTerminalRenderer();
638
+
639
+ runCli().catch((error) => {
640
+ if (jsonRequested) {
641
+ renderer.writeErrorJson({
642
+ ok: false,
643
+ error: {
644
+ what: "skilly-hand command failed",
645
+ why: error.message,
646
+ hint: buildErrorHint(error.message)
647
+ }
648
+ });
649
+ process.exitCode = 1;
650
+ return;
651
+ }
652
+
653
+ renderer.writeError(
654
+ renderer.error({
655
+ what: "skilly-hand command failed",
656
+ why: error.message,
657
+ hint: buildErrorHint(error.message),
658
+ exitCode: 1
659
+ })
660
+ );
661
+ process.exitCode = 1;
662
+ });
663
+ }
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { copySkillTo, loadAllSkills, renderAgentsMarkdown, verifyCatalogFiles } from "../../catalog/src/index.js";
4
4
  import { detectProject, inspectProjectFiles } from "../../detectors/src/index.js";
5
5
 
6
- const DEFAULT_AGENTS = ["codex", "claude", "cursor", "gemini", "copilot"];
6
+ export const DEFAULT_AGENTS = ["codex", "claude", "cursor", "gemini", "copilot"];
7
7
  const MANAGED_MARKER = "<!-- Managed by skilly-hand.";
8
8
 
9
9
  function uniq(values) {
@@ -44,6 +44,37 @@ function parseTags(input) {
44
44
  return uniq((input || []).flatMap((value) => String(value).split(",")).map((value) => value.trim()).filter(Boolean));
45
45
  }
46
46
 
47
+ function parseSkillIds(input) {
48
+ return uniq((input || []).flatMap((value) => String(value).split(",")).map((value) => value.trim()).filter(Boolean));
49
+ }
50
+
51
+ export function resolveSkillSelectionByIds({ catalog, selectedSkillIds = [] }) {
52
+ const ids = parseSkillIds(selectedSkillIds);
53
+ const portableById = new Map(
54
+ catalog
55
+ .filter((skill) => skill.portable)
56
+ .map((skill) => [skill.id, skill])
57
+ );
58
+ const allById = new Map(catalog.map((skill) => [skill.id, skill]));
59
+
60
+ const invalid = [];
61
+ for (const id of ids) {
62
+ if (!allById.has(id)) {
63
+ invalid.push(`Unknown skill id: ${id}`);
64
+ continue;
65
+ }
66
+ if (!portableById.has(id)) {
67
+ invalid.push(`Skill is not portable: ${id}`);
68
+ }
69
+ }
70
+
71
+ if (invalid.length > 0) {
72
+ throw new Error(invalid.join("; "));
73
+ }
74
+
75
+ return ids.map((id) => portableById.get(id)).sort((a, b) => a.id.localeCompare(b.id));
76
+ }
77
+
47
78
  export function resolveSkillSelection({ catalog, detections, includeTags = [], excludeTags = [] }) {
48
79
  const coreSkills = catalog.filter((skill) => skill.tags.includes("core"));
49
80
  const requested = new Set(coreSkills.map((skill) => skill.id));
@@ -168,17 +199,20 @@ export async function installProject({
168
199
  agents,
169
200
  dryRun = false,
170
201
  includeTags = [],
171
- excludeTags = []
202
+ excludeTags = [],
203
+ selectedSkillIds
172
204
  }) {
173
205
  const selectedAgents = normalizeAgentList(agents);
174
206
  const catalog = await loadAllSkills();
175
207
  const detections = await detectProject(cwd);
176
- const skills = resolveSkillSelection({
177
- catalog,
178
- detections,
179
- includeTags: parseTags(includeTags),
180
- excludeTags: parseTags(excludeTags)
181
- });
208
+ const skills = selectedSkillIds !== undefined && selectedSkillIds !== null
209
+ ? resolveSkillSelectionByIds({ catalog, selectedSkillIds })
210
+ : resolveSkillSelection({
211
+ catalog,
212
+ detections,
213
+ includeTags: parseTags(includeTags),
214
+ excludeTags: parseTags(excludeTags)
215
+ });
182
216
  const plan = buildInstallPlan({ cwd, detections, skills, agents: selectedAgents });
183
217
 
184
218
  if (dryRun) {
@@ -20,7 +20,7 @@ const LOGO_ASCII = [
20
20
 
21
21
  export function getBrand() {
22
22
  return {
23
- name: "autoskills",
23
+ name: "skilly-hand",
24
24
  tagline: "portable AI skill orchestration",
25
25
  hint: "npx skilly-hand --help",
26
26
  logo: {