ginskill-init 1.0.0 → 1.0.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/DEVELOPMENT.md +510 -0
- package/bin/cli.js +343 -108
- package/package.json +8 -2
package/DEVELOPMENT.md
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# GinStudio Skills — Development Guide
|
|
2
|
+
|
|
3
|
+
Everything you need to contribute skills, release new versions, and manage the CLI.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
1. [Project Structure](#project-structure)
|
|
10
|
+
2. [How to Add a New Skill](#how-to-add-a-new-skill)
|
|
11
|
+
3. [How to Add a New Agent](#how-to-add-a-new-agent)
|
|
12
|
+
4. [How to Release (Publish to npm)](#how-to-release-publish-to-npm)
|
|
13
|
+
5. [CLI Reference](#cli-reference)
|
|
14
|
+
6. [How Users Install Skills](#how-users-install-skills)
|
|
15
|
+
7. [How to Upgrade Installed Skills](#how-to-upgrade-installed-skills)
|
|
16
|
+
8. [How to Uninstall](#how-to-uninstall)
|
|
17
|
+
9. [Local Development & Testing](#local-development--testing)
|
|
18
|
+
10. [Troubleshooting](#troubleshooting)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Project Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
ginstudio-skills/
|
|
26
|
+
├── bin/
|
|
27
|
+
│ └── cli.js # npm CLI entrypoint (ginskill-init)
|
|
28
|
+
├── skills/
|
|
29
|
+
│ └── my-skill/ # One directory per skill
|
|
30
|
+
│ ├── SKILL.md # Main skill file (required)
|
|
31
|
+
│ ├── docs/ # Supporting docs loaded at runtime
|
|
32
|
+
│ └── scripts/ # Shell scripts used by the skill
|
|
33
|
+
├── agents/
|
|
34
|
+
│ ├── my-agent.md # Single-file agent (simple)
|
|
35
|
+
│ └── my-agent/ # Directory agent (with supporting files)
|
|
36
|
+
│ └── agent.md
|
|
37
|
+
├── package.json # npm package config
|
|
38
|
+
├── .npmignore # Files excluded from npm publish
|
|
39
|
+
├── .gitignore
|
|
40
|
+
├── README.md
|
|
41
|
+
└── DEVELOPMENT.md # This file
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## How to Add a New Skill
|
|
47
|
+
|
|
48
|
+
### 1. Create the skill directory
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
mkdir -p skills/my-skill/docs
|
|
52
|
+
mkdir -p skills/my-skill/scripts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Write `SKILL.md` (required)
|
|
56
|
+
|
|
57
|
+
Every skill needs a `SKILL.md` at `skills/my-skill/SKILL.md`.
|
|
58
|
+
|
|
59
|
+
**Minimal template:**
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
---
|
|
63
|
+
name: my-skill
|
|
64
|
+
description: |
|
|
65
|
+
Short description visible to Claude when deciding to load this skill.
|
|
66
|
+
- MANDATORY TRIGGERS: keyword1, keyword2, keyword3
|
|
67
|
+
- Use this skill when the user wants to: do X, Y, Z
|
|
68
|
+
argument-hint: "[optional | args | here]"
|
|
69
|
+
disable-model-invocation: false
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
# My Skill Title
|
|
73
|
+
|
|
74
|
+
You are an expert in [domain]. Help the user [goal].
|
|
75
|
+
|
|
76
|
+
## Step 1 — Do Something
|
|
77
|
+
|
|
78
|
+
...instructions...
|
|
79
|
+
|
|
80
|
+
## Step 2 — Do Something Else
|
|
81
|
+
|
|
82
|
+
...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**SKILL.md frontmatter fields:**
|
|
86
|
+
|
|
87
|
+
| Field | Required | Description |
|
|
88
|
+
|-------|----------|-------------|
|
|
89
|
+
| `name` | yes | Identifier, must match the directory name |
|
|
90
|
+
| `description` | yes | Shown to Claude; include `MANDATORY TRIGGERS` keywords |
|
|
91
|
+
| `argument-hint` | no | Shown in `/skill-name [hint]` |
|
|
92
|
+
| `disable-model-invocation` | no | Set `true` to block Claude from calling models inside the skill |
|
|
93
|
+
| `user-invocable` | no | Set `false` for domain-knowledge skills that auto-load but aren't called directly |
|
|
94
|
+
|
|
95
|
+
**Rules:**
|
|
96
|
+
- Keep `SKILL.md` under **500 lines**. Move excess content to `docs/`.
|
|
97
|
+
- Put reusable deep-dives in `docs/*.md` and load them via a script or inline reference.
|
|
98
|
+
- Scripts go in `scripts/` and are invoked with `` !`bash skills/my-skill/scripts/my-script.sh $ARGUMENTS` ``.
|
|
99
|
+
|
|
100
|
+
### 3. Load docs dynamically (optional)
|
|
101
|
+
|
|
102
|
+
Create `scripts/load-tutorial.sh` if your skill routes to different docs based on arguments:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
#!/bin/bash
|
|
106
|
+
TOPIC="${1:-overview}"
|
|
107
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
108
|
+
DOCS_DIR="$SCRIPT_DIR/../docs"
|
|
109
|
+
|
|
110
|
+
case "$TOPIC" in
|
|
111
|
+
intro) cat "$DOCS_DIR/intro.md" ;;
|
|
112
|
+
advanced) cat "$DOCS_DIR/advanced.md" ;;
|
|
113
|
+
*) cat "$DOCS_DIR/overview.md" ;;
|
|
114
|
+
esac
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Then in `SKILL.md`:
|
|
118
|
+
|
|
119
|
+
```markdown
|
|
120
|
+
!`bash skills/my-skill/scripts/load-tutorial.sh $ARGUMENTS`
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 4. Register the skill's auto-triggers
|
|
124
|
+
|
|
125
|
+
The `description` field in the frontmatter is used by Claude to decide when to auto-load the skill. Include:
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
description: |
|
|
129
|
+
Brief one-liner.
|
|
130
|
+
- MANDATORY TRIGGERS: the exact words or phrases that should trigger it
|
|
131
|
+
- Use this skill when the user wants to: create X, build Y, fix Z
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 5. Test the skill locally
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Install from local repo to a test .claude directory
|
|
138
|
+
node bin/cli.js --skills my-skill -t /tmp/test-project
|
|
139
|
+
|
|
140
|
+
# Verify it's there
|
|
141
|
+
ls /tmp/test-project/.claude/skills/my-skill/
|
|
142
|
+
|
|
143
|
+
# In Claude Code, invoke directly
|
|
144
|
+
/my-skill
|
|
145
|
+
# Or let it auto-trigger by describing your goal
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## How to Add a New Agent
|
|
151
|
+
|
|
152
|
+
Agents live in `agents/` and are copied to `.claude/agents/` during install.
|
|
153
|
+
|
|
154
|
+
### Simple agent (single `.md` file)
|
|
155
|
+
|
|
156
|
+
Create `agents/my-agent.md`:
|
|
157
|
+
|
|
158
|
+
```markdown
|
|
159
|
+
---
|
|
160
|
+
name: my-agent
|
|
161
|
+
description: Use this agent to do X. TRIGGER when user asks about Y.
|
|
162
|
+
tools: Read, Grep, Glob, Bash
|
|
163
|
+
model: claude-haiku-4-5-20251001
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
You are a specialized agent for [domain].
|
|
167
|
+
|
|
168
|
+
## Capabilities
|
|
169
|
+
...
|
|
170
|
+
|
|
171
|
+
## Instructions
|
|
172
|
+
...
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Directory agent (with supporting files)
|
|
176
|
+
|
|
177
|
+
Create `agents/my-agent/` directory with an `agent.md` (or any `.md`) and supporting scripts/configs.
|
|
178
|
+
|
|
179
|
+
### Agent frontmatter fields
|
|
180
|
+
|
|
181
|
+
| Field | Description |
|
|
182
|
+
|-------|-------------|
|
|
183
|
+
| `name` | Agent identifier |
|
|
184
|
+
| `description` | When to invoke; visible in `/agents` list |
|
|
185
|
+
| `tools` | Comma-separated tool allowlist (restricts what the agent can call) |
|
|
186
|
+
| `model` | Override the model (e.g., `claude-haiku-4-5-20251001` for speed) |
|
|
187
|
+
|
|
188
|
+
### Test the agent
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
node bin/cli.js --agents my-agent -t /tmp/test-project
|
|
192
|
+
# In Claude Code: "Use the my-agent agent to [task]"
|
|
193
|
+
# Or: /agents to verify it appears
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## How to Release (Publish to npm)
|
|
199
|
+
|
|
200
|
+
### Prerequisites
|
|
201
|
+
|
|
202
|
+
- npm account with access to the `ginskill-init` package
|
|
203
|
+
- `npm login` completed
|
|
204
|
+
|
|
205
|
+
### Step 1 — Bump the version
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# patch: 1.0.0 → 1.0.1 (bug fixes, content updates)
|
|
209
|
+
npm version patch
|
|
210
|
+
|
|
211
|
+
# minor: 1.0.0 → 1.1.0 (new skills/agents added)
|
|
212
|
+
npm version minor
|
|
213
|
+
|
|
214
|
+
# major: 1.0.0 → 2.0.0 (breaking changes to CLI or skill format)
|
|
215
|
+
npm version major
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
This updates `package.json` and creates a git tag automatically.
|
|
219
|
+
|
|
220
|
+
### Step 2 — Preview what will be published
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm pack --dry-run
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Check that:
|
|
227
|
+
- No `node_modules/` is included
|
|
228
|
+
- No `screenshots/`, `evals/`, or large media files
|
|
229
|
+
- All `skills/` and `agents/` directories are present
|
|
230
|
+
- Total size is reasonable (< 2MB target)
|
|
231
|
+
|
|
232
|
+
### Step 3 — Publish
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm publish
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
For a pre-release (beta):
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npm version prerelease --preid=beta # 1.1.0-beta.0
|
|
242
|
+
npm publish --tag beta
|
|
243
|
+
# Users install with: npx ginskill-init@beta
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Step 4 — Push the tag
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
git push && git push --tags
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### What `.npmignore` excludes
|
|
253
|
+
|
|
254
|
+
The `.npmignore` file keeps the package small by excluding:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
**/screenshots/ # demo images
|
|
258
|
+
**/evals/ # evaluation files
|
|
259
|
+
**/*.mov, *.mp4 # video files
|
|
260
|
+
node_modules/
|
|
261
|
+
install.py, install.js # legacy installers
|
|
262
|
+
*.local.json
|
|
263
|
+
**/.env*
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
To add more exclusions, edit `.npmignore`. **Do not add a `files` field to `package.json`** — it overrides `.npmignore` and can accidentally include everything.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## CLI Reference
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
ginskill-init [command] [options]
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Default: `install`
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
ginskill-init # Interactive TUI — select skills/agents
|
|
280
|
+
ginskill-init --all # Install everything, no prompts
|
|
281
|
+
ginskill-init --skills react-query,mongodb
|
|
282
|
+
ginskill-init --agents developer,tester
|
|
283
|
+
ginskill-init -g # Install to ~/.claude (global)
|
|
284
|
+
ginskill-init -t /path/to/project # Install to specific project
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### `upgrade`
|
|
288
|
+
|
|
289
|
+
Re-copies installed skills/agents from the bundled version.
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
ginskill-init upgrade # Interactive — choose what to upgrade
|
|
293
|
+
ginskill-init upgrade --all # Upgrade everything
|
|
294
|
+
ginskill-init upgrade -t /path/to/project
|
|
295
|
+
ginskill-init upgrade -g
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### `uninstall` (alias: `remove`)
|
|
299
|
+
|
|
300
|
+
Removes installed skills/agents from the target `.claude/` directory.
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
ginskill-init uninstall # Interactive — choose what to remove
|
|
304
|
+
ginskill-init uninstall -t /path/to/project
|
|
305
|
+
ginskill-init uninstall -g
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### `status` (alias: `info`)
|
|
309
|
+
|
|
310
|
+
Shows which skills/agents are installed vs available.
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
ginskill-init status
|
|
314
|
+
ginskill-init status -t /path/to/project
|
|
315
|
+
ginskill-init status -g
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### `list` (alias: `ls`)
|
|
319
|
+
|
|
320
|
+
Lists all skills and agents bundled in this package (not what's installed).
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
ginskill-init list
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `versions` (alias: `ver`)
|
|
327
|
+
|
|
328
|
+
Fetches all published versions from the npm registry.
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
ginskill-init versions
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Global flags (work on all commands)
|
|
335
|
+
|
|
336
|
+
| Flag | Short | Description |
|
|
337
|
+
|------|-------|-------------|
|
|
338
|
+
| `--global` | `-g` | Target `~/.claude/` (available in all projects) |
|
|
339
|
+
| `--target <path>` | `-t` | Target a specific project directory |
|
|
340
|
+
| `--version` | `-V` | Print CLI version |
|
|
341
|
+
| `--help` | `-h` | Print help |
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## How Users Install Skills
|
|
346
|
+
|
|
347
|
+
### One-time install (recommended)
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
npx ginskill-init
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Runs the interactive TUI to select skills and agents.
|
|
354
|
+
|
|
355
|
+
### Install to a specific project
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
cd /path/to/my-project
|
|
359
|
+
npx ginskill-init
|
|
360
|
+
# or
|
|
361
|
+
npx ginskill-init -t /path/to/my-project
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Install globally (all projects)
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
npx ginskill-init -g
|
|
368
|
+
# or
|
|
369
|
+
npx ginskill-init --global
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Non-interactive (CI/scripts)
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
npx ginskill-init --all # all skills + agents
|
|
376
|
+
npx ginskill-init --skills react-query,mongodb # specific skills only
|
|
377
|
+
npx ginskill-init --agents developer # specific agent only
|
|
378
|
+
npx ginskill-init --all -g # everything, globally
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## How to Upgrade Installed Skills
|
|
384
|
+
|
|
385
|
+
When a new version of `ginskill-init` is published, users upgrade their installed skills by running:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
# Interactive — choose which skills/agents to upgrade
|
|
389
|
+
npx ginskill-init@latest upgrade
|
|
390
|
+
|
|
391
|
+
# Upgrade everything silently
|
|
392
|
+
npx ginskill-init@latest upgrade --all
|
|
393
|
+
|
|
394
|
+
# Upgrade in a specific project
|
|
395
|
+
npx ginskill-init@latest upgrade -t /path/to/project
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
The `upgrade` command re-copies skills/agents from the npm package, overwriting the existing files. Only skills/agents already installed are shown.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## How to Uninstall
|
|
403
|
+
|
|
404
|
+
### Remove specific skills/agents interactively
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
ginskill-init uninstall
|
|
408
|
+
# or from any version:
|
|
409
|
+
npx ginskill-init uninstall
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Remove from a specific project
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
ginskill-init uninstall -t /path/to/project
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Remove globally installed skills
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
ginskill-init uninstall -g
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Manual removal
|
|
425
|
+
|
|
426
|
+
Skills are plain directories — you can also delete them manually:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
# Remove a specific skill
|
|
430
|
+
rm -rf .claude/skills/my-skill
|
|
431
|
+
|
|
432
|
+
# Remove a specific agent
|
|
433
|
+
rm .claude/agents/my-agent.md
|
|
434
|
+
# or
|
|
435
|
+
rm -rf .claude/agents/my-agent/
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Local Development & Testing
|
|
441
|
+
|
|
442
|
+
### Setup
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
cd ginstudio-skills
|
|
446
|
+
npm install
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Run CLI locally
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
node bin/cli.js --help
|
|
453
|
+
node bin/cli.js list
|
|
454
|
+
node bin/cli.js status -t /tmp/test
|
|
455
|
+
node bin/cli.js --all -t /tmp/test
|
|
456
|
+
node bin/cli.js upgrade --all -t /tmp/test
|
|
457
|
+
node bin/cli.js uninstall -t /tmp/test
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Link globally for testing
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
npm link
|
|
464
|
+
# Now you can run: ginskill-init --help
|
|
465
|
+
npm unlink ginskill-init # when done
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Test pack before publishing
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
npm pack --dry-run # preview what's included
|
|
472
|
+
npm pack # creates ginskill-init-x.x.x.tgz
|
|
473
|
+
# Test install from the tgz:
|
|
474
|
+
npx ./ginskill-init-x.x.x.tgz
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Troubleshooting
|
|
480
|
+
|
|
481
|
+
### Skills not appearing in Claude Code
|
|
482
|
+
|
|
483
|
+
Run `/agents` or restart Claude Code after installing. Skills are picked up on session start.
|
|
484
|
+
|
|
485
|
+
### `npx ginskill-init` installs an old version
|
|
486
|
+
|
|
487
|
+
Force the latest:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
npx ginskill-init@latest
|
|
491
|
+
# or clear npx cache:
|
|
492
|
+
npx --yes ginskill-init@latest
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### Target not recognized
|
|
496
|
+
|
|
497
|
+
If `-t /path/to/project` seems to be ignored, make sure you're passing it **after** the command:
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
ginskill-init upgrade -t /path/to/project ✓
|
|
501
|
+
ginskill-init -t /path/to/project upgrade ✗ (flag consumed by parent)
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Package is too large
|
|
505
|
+
|
|
506
|
+
Check `.npmignore` is present and `package.json` has no `files` field. Run `npm pack --dry-run` to inspect.
|
|
507
|
+
|
|
508
|
+
### Skill doesn't auto-trigger
|
|
509
|
+
|
|
510
|
+
Verify the `description` frontmatter in `SKILL.md` includes `MANDATORY TRIGGERS` with the exact words the user would say.
|
package/bin/cli.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { createRequire } from 'module';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
4
|
import { dirname, join } from 'path';
|
|
6
|
-
import { existsSync, readdirSync, statSync, mkdirSync, copyFileSync } from 'fs';
|
|
5
|
+
import { existsSync, readdirSync, statSync, mkdirSync, copyFileSync, rmSync, readFileSync } from 'fs';
|
|
7
6
|
import { homedir } from 'os';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
8
|
import { Command } from 'commander';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import ora from 'ora';
|
|
11
11
|
import prompts from 'prompts';
|
|
12
12
|
|
|
13
|
-
const __dirname
|
|
14
|
-
const PACKAGE_DIR
|
|
15
|
-
const SRC_SKILLS
|
|
16
|
-
const SRC_AGENTS
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const PACKAGE_DIR = join(__dirname, '..');
|
|
15
|
+
const SRC_SKILLS = join(PACKAGE_DIR, 'skills');
|
|
16
|
+
const SRC_AGENTS = join(PACKAGE_DIR, 'agents');
|
|
17
|
+
const PKG = JSON.parse(readFileSync(join(PACKAGE_DIR, 'package.json'), 'utf-8'));
|
|
17
18
|
|
|
18
|
-
// ───
|
|
19
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
19
20
|
|
|
20
21
|
function getDirs(dir) {
|
|
21
22
|
if (!existsSync(dir)) return [];
|
|
@@ -29,6 +30,10 @@ function getMdFiles(dir) {
|
|
|
29
30
|
.map(f => f.replace('.md', ''));
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
function unique(arr) {
|
|
34
|
+
return [...new Set(arr)];
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
function copyDir(src, dest) {
|
|
33
38
|
mkdirSync(dest, { recursive: true });
|
|
34
39
|
for (const entry of readdirSync(src)) {
|
|
@@ -38,37 +43,64 @@ function copyDir(src, dest) {
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
function resolveTarget(targetArg, isGlobal) {
|
|
41
|
-
if (isGlobal)
|
|
42
|
-
if (targetArg)
|
|
46
|
+
if (isGlobal) return join(homedir(), '.claude');
|
|
47
|
+
if (targetArg) return join(targetArg, '.claude');
|
|
43
48
|
return join(process.cwd(), '.claude');
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
function getAvailable() {
|
|
52
|
+
const skills = getDirs(SRC_SKILLS);
|
|
53
|
+
const agents = unique([...getDirs(SRC_AGENTS), ...getMdFiles(SRC_AGENTS)]);
|
|
54
|
+
return { skills, agents };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getInstalled(claudeDir) {
|
|
58
|
+
const skills = getDirs(join(claudeDir, 'skills'))
|
|
59
|
+
.filter(s => getDirs(SRC_SKILLS).includes(s)); // only ours
|
|
60
|
+
const agentDir = join(claudeDir, 'agents');
|
|
61
|
+
const available = getAvailable().agents;
|
|
62
|
+
const agents = unique([
|
|
63
|
+
...getDirs(agentDir),
|
|
64
|
+
...getMdFiles(agentDir),
|
|
65
|
+
]).filter(a => available.includes(a));
|
|
66
|
+
return { skills, agents };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Logger ───────────────────────────────────────────────────────────────────
|
|
47
70
|
|
|
48
71
|
const log = {
|
|
49
72
|
title: msg => console.log('\n' + chalk.bold.cyan(msg) + '\n'),
|
|
50
|
-
info: msg => console.log(chalk.blue(' info
|
|
51
|
-
success: msg => console.log(chalk.green(' ✓
|
|
52
|
-
warn: msg => console.log(chalk.yellow(' warn
|
|
73
|
+
info: msg => console.log(chalk.blue(' info ') + msg),
|
|
74
|
+
success: msg => console.log(chalk.green(' ✓ ') + msg),
|
|
75
|
+
warn: msg => console.log(chalk.yellow(' warn ') + msg),
|
|
76
|
+
error: msg => console.log(chalk.red(' error ') + msg),
|
|
53
77
|
dim: msg => console.log(chalk.dim(' ' + msg)),
|
|
54
78
|
};
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
const onCancel = () => { log.warn('Cancelled.'); process.exit(0); };
|
|
57
81
|
|
|
58
|
-
|
|
82
|
+
// ─── Core Install ─────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
async function runInstall({ target, skills, agents, verb = 'Installed' }) {
|
|
59
85
|
const destSkills = join(target, 'skills');
|
|
60
86
|
const destAgents = join(target, 'agents');
|
|
61
|
-
|
|
87
|
+
|
|
88
|
+
if (skills.length === 0 && agents.length === 0) {
|
|
89
|
+
log.warn('Nothing selected.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const spinner = ora({ text: 'Working...', color: 'cyan' }).start();
|
|
62
94
|
let count = 0;
|
|
63
95
|
|
|
64
96
|
for (const skill of skills) {
|
|
65
|
-
spinner.text =
|
|
97
|
+
spinner.text = `${verb.replace('d','ing')} skill ${chalk.cyan(skill)}...`;
|
|
66
98
|
copyDir(join(SRC_SKILLS, skill), join(destSkills, skill));
|
|
67
99
|
count++;
|
|
68
100
|
}
|
|
69
101
|
|
|
70
102
|
for (const agent of agents) {
|
|
71
|
-
spinner.text =
|
|
103
|
+
spinner.text = `${verb.replace('d','ing')} agent ${chalk.cyan(agent)}...`;
|
|
72
104
|
const srcDir = join(SRC_AGENTS, agent);
|
|
73
105
|
const srcFile = join(SRC_AGENTS, `${agent}.md`);
|
|
74
106
|
mkdirSync(destAgents, { recursive: true });
|
|
@@ -80,39 +112,48 @@ async function runInstall({ target, skills, agents }) {
|
|
|
80
112
|
count++;
|
|
81
113
|
}
|
|
82
114
|
|
|
83
|
-
|
|
84
|
-
spinner.warn('Nothing selected to install.');
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
spinner.succeed(chalk.bold.green(`Done! ${count} item(s) installed`));
|
|
115
|
+
spinner.succeed(chalk.bold.green(`${verb}! ${count} item(s)`));
|
|
89
116
|
console.log();
|
|
90
117
|
log.info(`Target: ${chalk.cyan(target)}`);
|
|
91
|
-
log.dim('Restart Claude Code (or run /agents) to pick up
|
|
118
|
+
log.dim('Restart Claude Code (or run /agents) to pick up changes.');
|
|
92
119
|
console.log();
|
|
93
120
|
}
|
|
94
121
|
|
|
95
|
-
// ───
|
|
122
|
+
// ─── Multiselect helper ───────────────────────────────────────────────────────
|
|
96
123
|
|
|
97
|
-
async function
|
|
98
|
-
const {
|
|
124
|
+
async function multiSelect(message, choices) {
|
|
125
|
+
const { selected } = await prompts({
|
|
126
|
+
type: 'multiselect',
|
|
127
|
+
name: 'selected',
|
|
128
|
+
message,
|
|
129
|
+
choices,
|
|
130
|
+
hint: 'Space to toggle · A to toggle all · Enter to confirm',
|
|
131
|
+
instructions: false,
|
|
132
|
+
min: 0,
|
|
133
|
+
}, { onCancel });
|
|
134
|
+
return selected ?? [];
|
|
135
|
+
}
|
|
99
136
|
|
|
100
|
-
|
|
137
|
+
async function confirmPrompt(message) {
|
|
138
|
+
const { ok } = await prompts({ type: 'confirm', name: 'ok', message, initial: true }, { onCancel });
|
|
139
|
+
return ok;
|
|
140
|
+
}
|
|
101
141
|
|
|
102
|
-
|
|
103
|
-
const allAgents = [...getDirs(SRC_AGENTS), ...getMdFiles(SRC_AGENTS)]
|
|
104
|
-
.filter((v, i, a) => a.indexOf(v) === i);
|
|
142
|
+
// ─── COMMAND: install ─────────────────────────────────────────────────────────
|
|
105
143
|
|
|
106
|
-
|
|
144
|
+
async function cmdInstall(options, targetArg) {
|
|
145
|
+
log.title('GinStudio — Install Skills & Agents');
|
|
107
146
|
|
|
108
|
-
|
|
147
|
+
const target = resolveTarget(options.target || targetArg, options.global);
|
|
148
|
+
const { skills: allSkills, agents: allAgents } = getAvailable();
|
|
109
149
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
150
|
+
// Non-interactive shortcut flags
|
|
151
|
+
if (options.all || options.skills || options.agents) {
|
|
152
|
+
const skills = options.all ? allSkills
|
|
153
|
+
: options.skills ? options.skills.split(',').map(s => s.trim()).filter(s => allSkills.includes(s))
|
|
113
154
|
: allSkills;
|
|
114
|
-
|
|
115
|
-
:
|
|
155
|
+
const agents = options.all ? allAgents
|
|
156
|
+
: options.agents ? options.agents.split(',').map(s => s.trim()).filter(s => allAgents.includes(s))
|
|
116
157
|
: allAgents;
|
|
117
158
|
|
|
118
159
|
log.info(`Target: ${chalk.cyan(target)}`);
|
|
@@ -122,105 +163,299 @@ async function interactiveInstall(options) {
|
|
|
122
163
|
return runInstall({ target, skills, agents });
|
|
123
164
|
}
|
|
124
165
|
|
|
125
|
-
//
|
|
166
|
+
// Full TUI
|
|
167
|
+
log.info(`Target: ${chalk.cyan(target)}\n`);
|
|
168
|
+
|
|
169
|
+
const installed = getInstalled(target);
|
|
170
|
+
|
|
171
|
+
const skills = await multiSelect('Select skills to install:', allSkills.map(s => ({
|
|
172
|
+
title: s,
|
|
173
|
+
value: s,
|
|
174
|
+
selected: installed.skills.includes(s) || !existsSync(target),
|
|
175
|
+
})));
|
|
176
|
+
|
|
177
|
+
console.log();
|
|
178
|
+
|
|
179
|
+
const agents = await multiSelect('Select agents to install:', allAgents.map(a => ({
|
|
180
|
+
title: a,
|
|
181
|
+
value: a,
|
|
182
|
+
selected: installed.agents.includes(a) || !existsSync(target),
|
|
183
|
+
})));
|
|
184
|
+
|
|
185
|
+
console.log();
|
|
186
|
+
console.log(chalk.bold(' Summary'));
|
|
187
|
+
console.log(` ${chalk.green('Skills')} (${skills.length}): ${skills.join(', ') || chalk.dim('none')}`);
|
|
188
|
+
console.log(` ${chalk.cyan('Agents')} (${agents.length}): ${agents.join(', ') || chalk.dim('none')}`);
|
|
189
|
+
console.log(` Target: ${target}`);
|
|
190
|
+
console.log();
|
|
191
|
+
|
|
192
|
+
if (!await confirmPrompt('Install?')) { log.warn('Cancelled.'); return; }
|
|
193
|
+
console.log();
|
|
194
|
+
|
|
195
|
+
return runInstall({ target, skills, agents });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ─── COMMAND: upgrade ─────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
async function cmdUpgrade(options, targetArg) {
|
|
201
|
+
log.title('GinStudio — Upgrade Skills & Agents');
|
|
202
|
+
|
|
203
|
+
const target = resolveTarget(options.target || targetArg, options.global);
|
|
204
|
+
const installed = getInstalled(target);
|
|
205
|
+
const total = installed.skills.length + installed.agents.length;
|
|
206
|
+
|
|
207
|
+
if (total === 0) {
|
|
208
|
+
log.warn(`Nothing from GinStudio is installed in ${chalk.cyan(target)}.`);
|
|
209
|
+
log.dim(`Run ${chalk.white('ginskill-init')} to install first.`);
|
|
210
|
+
console.log();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
log.info(`Target: ${chalk.cyan(target)}`);
|
|
215
|
+
log.info(`Current package: ${chalk.cyan(`v${PKG.version}`)}\n`);
|
|
216
|
+
|
|
217
|
+
// Non-interactive
|
|
218
|
+
if (options.all) {
|
|
219
|
+
return runInstall({ target, skills: installed.skills, agents: installed.agents, verb: 'Upgraded' });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const skills = await multiSelect('Select skills to upgrade:', installed.skills.map(s => ({
|
|
223
|
+
title: s, value: s, selected: true,
|
|
224
|
+
})));
|
|
225
|
+
|
|
226
|
+
console.log();
|
|
227
|
+
|
|
228
|
+
const agents = await multiSelect('Select agents to upgrade:', installed.agents.map(a => ({
|
|
229
|
+
title: a, value: a, selected: true,
|
|
230
|
+
})));
|
|
231
|
+
|
|
232
|
+
console.log();
|
|
233
|
+
|
|
234
|
+
if (!await confirmPrompt(`Re-install ${skills.length + agents.length} item(s) from v${PKG.version}?`)) {
|
|
235
|
+
log.warn('Cancelled.');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
console.log();
|
|
239
|
+
|
|
240
|
+
return runInstall({ target, skills, agents, verb: 'Upgraded' });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ─── COMMAND: uninstall ───────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
async function cmdUninstall(options, targetArg) {
|
|
246
|
+
log.title('GinStudio — Uninstall Skills & Agents');
|
|
247
|
+
|
|
248
|
+
const target = resolveTarget(options.target || targetArg, options.global);
|
|
249
|
+
const installed = getInstalled(target);
|
|
250
|
+
const total = installed.skills.length + installed.agents.length;
|
|
251
|
+
|
|
252
|
+
if (total === 0) {
|
|
253
|
+
log.warn(`Nothing from GinStudio is installed in ${chalk.cyan(target)}.`);
|
|
254
|
+
console.log();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
126
257
|
|
|
127
258
|
log.info(`Target: ${chalk.cyan(target)}\n`);
|
|
128
259
|
|
|
129
|
-
|
|
130
|
-
|
|
260
|
+
const skills = await multiSelect('Select skills to uninstall:', installed.skills.map(s => ({
|
|
261
|
+
title: s, value: s, selected: false,
|
|
262
|
+
})));
|
|
131
263
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
hint: 'Space to toggle, A to toggle all, Enter to confirm',
|
|
138
|
-
instructions: false,
|
|
139
|
-
min: 0,
|
|
140
|
-
}, {
|
|
141
|
-
onCancel: () => { log.warn('Cancelled.'); process.exit(0); }
|
|
142
|
-
});
|
|
264
|
+
console.log();
|
|
265
|
+
|
|
266
|
+
const agents = await multiSelect('Select agents to uninstall:', installed.agents.map(a => ({
|
|
267
|
+
title: a, value: a, selected: false,
|
|
268
|
+
})));
|
|
143
269
|
|
|
144
270
|
console.log();
|
|
145
271
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
name: 'agents',
|
|
149
|
-
message: 'Select agents to install:',
|
|
150
|
-
choices: allAgents.map(a => ({ title: a, value: a, selected: true })),
|
|
151
|
-
hint: 'Space to toggle, A to toggle all, Enter to confirm',
|
|
152
|
-
instructions: false,
|
|
153
|
-
min: 0,
|
|
154
|
-
}, {
|
|
155
|
-
onCancel: () => { log.warn('Cancelled.'); process.exit(0); }
|
|
156
|
-
});
|
|
272
|
+
const count = skills.length + agents.length;
|
|
273
|
+
if (count === 0) { log.warn('Nothing selected.'); return; }
|
|
157
274
|
|
|
275
|
+
console.log(chalk.bold(' Will remove:'));
|
|
276
|
+
skills.forEach(s => console.log(` ${chalk.red('✕')} skill ${s}`));
|
|
277
|
+
agents.forEach(a => console.log(` ${chalk.red('✕')} agent ${a}`));
|
|
158
278
|
console.log();
|
|
159
279
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
280
|
+
if (!await confirmPrompt(chalk.red(`Permanently remove ${count} item(s)?`))) {
|
|
281
|
+
log.warn('Cancelled.');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const spinner = ora({ text: 'Removing...', color: 'red' }).start();
|
|
286
|
+
|
|
287
|
+
for (const skill of skills) {
|
|
288
|
+
const dir = join(target, 'skills', skill);
|
|
289
|
+
if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
|
|
290
|
+
}
|
|
291
|
+
for (const agent of agents) {
|
|
292
|
+
const dir = join(target, 'agents', agent);
|
|
293
|
+
const file = join(target, 'agents', `${agent}.md`);
|
|
294
|
+
if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
|
|
295
|
+
if (existsSync(file)) rmSync(file, { force: true });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
spinner.succeed(chalk.bold.red(`Removed ${count} item(s)`));
|
|
165
299
|
console.log();
|
|
300
|
+
log.dim('Restart Claude Code to pick up changes.');
|
|
301
|
+
console.log();
|
|
302
|
+
}
|
|
166
303
|
|
|
167
|
-
|
|
168
|
-
type: 'confirm',
|
|
169
|
-
name: 'ok',
|
|
170
|
-
message: 'Install?',
|
|
171
|
-
initial: true,
|
|
172
|
-
}, {
|
|
173
|
-
onCancel: () => { log.warn('Cancelled.'); process.exit(0); }
|
|
174
|
-
});
|
|
304
|
+
// ─── COMMAND: status ──────────────────────────────────────────────────────────
|
|
175
305
|
|
|
176
|
-
|
|
306
|
+
function cmdStatus(options, targetArg) {
|
|
307
|
+
log.title('GinStudio — Status');
|
|
177
308
|
|
|
309
|
+
const target = resolveTarget(options.target || targetArg, options.global);
|
|
310
|
+
const { skills: allSkills, agents: allAgents } = getAvailable();
|
|
311
|
+
const installed = getInstalled(target);
|
|
312
|
+
|
|
313
|
+
log.info(`Package: ${chalk.cyan(`ginskill-init v${PKG.version}`)}`);
|
|
314
|
+
log.info(`Target: ${chalk.cyan(target)}`);
|
|
315
|
+
console.log();
|
|
316
|
+
|
|
317
|
+
console.log(chalk.bold(' Skills:'));
|
|
318
|
+
for (const s of allSkills) {
|
|
319
|
+
const isInstalled = installed.skills.includes(s);
|
|
320
|
+
const icon = isInstalled ? chalk.green('✓') : chalk.dim('○');
|
|
321
|
+
const label = isInstalled ? chalk.white(s) : chalk.dim(s);
|
|
322
|
+
const badge = isInstalled ? chalk.green('installed') : chalk.dim('not installed');
|
|
323
|
+
console.log(` ${icon} ${label.padEnd(28)} ${badge}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log();
|
|
327
|
+
console.log(chalk.bold(' Agents:'));
|
|
328
|
+
for (const a of allAgents) {
|
|
329
|
+
const isInstalled = installed.agents.includes(a);
|
|
330
|
+
const icon = isInstalled ? chalk.green('✓') : chalk.dim('○');
|
|
331
|
+
const label = isInstalled ? chalk.cyan(a) : chalk.dim(a);
|
|
332
|
+
const badge = isInstalled ? chalk.green('installed') : chalk.dim('not installed');
|
|
333
|
+
console.log(` ${icon} ${label.padEnd(28)} ${badge}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
console.log();
|
|
337
|
+
const si = installed.skills.length, ai = installed.agents.length;
|
|
338
|
+
log.info(`${si} skill(s) · ${ai} agent(s) installed out of ${allSkills.length} skills · ${allAgents.length} agents available`);
|
|
178
339
|
console.log();
|
|
179
|
-
return runInstall({ target, skills, agents });
|
|
180
340
|
}
|
|
181
341
|
|
|
182
|
-
// ───
|
|
342
|
+
// ─── COMMAND: versions ────────────────────────────────────────────────────────
|
|
343
|
+
|
|
344
|
+
async function cmdVersions() {
|
|
345
|
+
log.title('GinStudio — Available Versions');
|
|
346
|
+
|
|
347
|
+
const spinner = ora({ text: 'Fetching from npm registry...', color: 'cyan' }).start();
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const res = await fetch(`https://registry.npmjs.org/${PKG.name}`);
|
|
351
|
+
if (!res.ok) throw new Error(`Registry returned ${res.status}`);
|
|
352
|
+
const data = await res.json();
|
|
353
|
+
|
|
354
|
+
const versions = Object.keys(data.versions || {}).reverse();
|
|
355
|
+
const latest = data['dist-tags']?.latest;
|
|
356
|
+
const times = data.time || {};
|
|
357
|
+
|
|
358
|
+
spinner.succeed(`Found ${versions.length} version(s)\n`);
|
|
359
|
+
|
|
360
|
+
console.log(chalk.bold(' Available versions:\n'));
|
|
361
|
+
for (const v of versions.slice(0, 20)) {
|
|
362
|
+
const isLatest = v === latest;
|
|
363
|
+
const date = times[v] ? new Date(times[v]).toLocaleDateString() : '';
|
|
364
|
+
const isCurrent = v === PKG.version;
|
|
365
|
+
const tag = isLatest ? chalk.green('[latest]') : '';
|
|
366
|
+
const cur = isCurrent ? chalk.blue('[current]') : '';
|
|
367
|
+
const vStr = isLatest ? chalk.bold.green(`v${v}`) : chalk.white(`v${v}`);
|
|
368
|
+
console.log(` ${vStr} ${chalk.dim(date)} ${tag} ${cur}`);
|
|
369
|
+
}
|
|
370
|
+
if (versions.length > 20) log.dim(`... and ${versions.length - 20} more`);
|
|
371
|
+
|
|
372
|
+
console.log();
|
|
373
|
+
log.dim(`Run ${chalk.white('npx ginskill-init@latest')} to get the newest version.`);
|
|
374
|
+
console.log();
|
|
375
|
+
|
|
376
|
+
} catch (err) {
|
|
377
|
+
spinner.fail('Could not fetch versions from npm registry.');
|
|
378
|
+
log.error(err.message);
|
|
379
|
+
log.dim(`Current installed version: ${chalk.cyan(`v${PKG.version}`)}`);
|
|
380
|
+
console.log();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ─── COMMAND: list ────────────────────────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
function cmdList() {
|
|
387
|
+
log.title('GinStudio — Available Skills & Agents');
|
|
388
|
+
const { skills, agents } = getAvailable();
|
|
183
389
|
|
|
184
|
-
function listCommand() {
|
|
185
|
-
log.title('GinStudio Skills & Agents');
|
|
186
390
|
console.log(chalk.bold(' Skills:'));
|
|
187
|
-
|
|
391
|
+
skills.forEach(s => console.log(` ${chalk.green('✦')} ${s}`));
|
|
188
392
|
console.log();
|
|
189
393
|
console.log(chalk.bold(' Agents:'));
|
|
190
|
-
const agents = [...getDirs(SRC_AGENTS), ...getMdFiles(SRC_AGENTS)]
|
|
191
|
-
.filter((v, i, a) => a.indexOf(v) === i);
|
|
192
394
|
agents.forEach(a => console.log(` ${chalk.cyan('✦')} ${a}`));
|
|
193
395
|
console.log();
|
|
194
396
|
}
|
|
195
397
|
|
|
196
|
-
// ─── CLI
|
|
398
|
+
// ─── CLI wiring ───────────────────────────────────────────────────────────────
|
|
197
399
|
|
|
198
400
|
const program = new Command();
|
|
199
401
|
|
|
200
402
|
program
|
|
201
403
|
.name('ginskill-init')
|
|
202
|
-
.description('Install GinStudio skills
|
|
203
|
-
.version(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
global: opts.global,
|
|
213
|
-
all: opts.all,
|
|
214
|
-
skills: opts.skills,
|
|
215
|
-
agents: opts.agents,
|
|
216
|
-
target: opts.target || targetArg || null,
|
|
217
|
-
});
|
|
218
|
-
});
|
|
404
|
+
.description('Install GinStudio skills & agents for Claude Code')
|
|
405
|
+
.version(PKG.version);
|
|
406
|
+
|
|
407
|
+
// Shared options factory
|
|
408
|
+
function addTargetOptions(cmd) {
|
|
409
|
+
return cmd
|
|
410
|
+
.option('-g, --global', 'Target ~/.claude/ (available in all projects)')
|
|
411
|
+
.option('-t, --target <path>', 'Custom target project path')
|
|
412
|
+
.argument('[target]', 'Target project path (positional)');
|
|
413
|
+
}
|
|
219
414
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
415
|
+
// Default: install
|
|
416
|
+
addTargetOptions(
|
|
417
|
+
program
|
|
418
|
+
.option('-a, --all', 'Install all without prompts')
|
|
419
|
+
.option('--skills <list>', 'Install specific skills (comma-separated names)')
|
|
420
|
+
.option('--agents <list>', 'Install specific agents (comma-separated names)')
|
|
421
|
+
)
|
|
422
|
+
.action((targetArg, opts) => cmdInstall(opts, targetArg));
|
|
423
|
+
|
|
424
|
+
// Merge parent opts (handles case where Commander consumes -t/-g at parent level)
|
|
425
|
+
function mergeOpts(opts) {
|
|
426
|
+
return { ...program.opts(), ...opts };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// upgrade
|
|
430
|
+
addTargetOptions(
|
|
431
|
+
program.command('upgrade')
|
|
432
|
+
.description('Upgrade installed skills & agents to the bundled version')
|
|
433
|
+
.option('-a, --all', 'Upgrade all without prompts')
|
|
434
|
+
)
|
|
435
|
+
.action((targetArg, opts) => cmdUpgrade(mergeOpts(opts), targetArg));
|
|
436
|
+
|
|
437
|
+
// uninstall
|
|
438
|
+
addTargetOptions(
|
|
439
|
+
program.command('uninstall').alias('remove')
|
|
440
|
+
.description('Remove installed skills & agents')
|
|
441
|
+
)
|
|
442
|
+
.action((targetArg, opts) => cmdUninstall(mergeOpts(opts), targetArg));
|
|
443
|
+
|
|
444
|
+
// status
|
|
445
|
+
addTargetOptions(
|
|
446
|
+
program.command('status').alias('info')
|
|
447
|
+
.description('Show installed vs available skills & agents')
|
|
448
|
+
)
|
|
449
|
+
.action((targetArg, opts) => cmdStatus(mergeOpts(opts), targetArg));
|
|
450
|
+
|
|
451
|
+
// versions
|
|
452
|
+
program.command('versions').alias('ver')
|
|
453
|
+
.description('List all published npm versions')
|
|
454
|
+
.action(() => cmdVersions());
|
|
455
|
+
|
|
456
|
+
// list
|
|
457
|
+
program.command('list').alias('ls')
|
|
458
|
+
.description('List all available skills & agents in this package')
|
|
459
|
+
.action(() => cmdList());
|
|
225
460
|
|
|
226
461
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ginskill-init",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Install GinStudio skills and agents for Claude Code",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude-code",
|
|
7
|
+
"claude",
|
|
8
|
+
"skills",
|
|
9
|
+
"agents",
|
|
10
|
+
"ai"
|
|
11
|
+
],
|
|
6
12
|
"license": "MIT",
|
|
7
13
|
"type": "module",
|
|
8
14
|
"bin": {
|