skillex 0.2.1 → 0.2.3
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 +16 -0
- package/README.md +57 -18
- package/dist/adapters.js +15 -8
- package/dist/catalog.js +24 -3
- package/dist/cli.js +65 -20
- package/dist/config.d.ts +20 -1
- package/dist/config.js +31 -0
- package/dist/fs.d.ts +7 -0
- package/dist/fs.js +16 -3
- package/dist/install.d.ts +4 -4
- package/dist/install.js +59 -28
- package/dist/runner.js +9 -4
- package/dist/sync.d.ts +2 -2
- package/dist/sync.js +194 -50
- package/dist/types.d.ts +24 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.3] - 2026-04-08
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `skillex list` and all catalog commands no longer fail with `owner/repo` when a lockfile was written by an older version that used the placeholder default source — the placeholder is now silently replaced by `lgili/skillex`
|
|
14
|
+
|
|
15
|
+
## [0.2.2] - 2026-04-08
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- 9 first-party skills in the default catalog: `commit-craft`, `secure-defaults`, `error-handling`, `test-discipline`, `code-review`, `typescript-pro`, `python-pro`, `cpp-pro`, `latex-pro` — each with full reference files and bundled scripts
|
|
19
|
+
- `create-skills` skill upgraded to advanced format: generates SKILL.md with Core Workflow, Reference Guide table, Constraints, and Output Template sections; `--references` flag scaffolds placeholder reference files automatically
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- `catalog.json` is now a slim index (`formatVersion: 2`): each entry contains only `{ "id" }` — no repeated metadata
|
|
23
|
+
- Full skill metadata (name, version, description, tags, compatibility, files) now lives exclusively in each skill's `skill.json`
|
|
24
|
+
- CLI catalog loader detects v2 format and fetches each `skill.json` in parallel to hydrate the full manifest
|
|
25
|
+
|
|
10
26
|
## [0.2.1] - 2026-04-07
|
|
11
27
|
|
|
12
28
|
### Changed
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
**Skillex** is a CLI that installs and synchronizes AI agent skills from a GitHub-hosted catalog
|
|
8
|
+
**Skillex** is a CLI that installs and synchronizes AI agent skills from a GitHub-hosted catalog. It supports both workspace-local installs and user-global installs, and it exposes skills to major AI agents such as Codex, Copilot, Cline, Cursor, Claude, Gemini, and Windsurf.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -47,7 +47,7 @@ npx skillex@latest install create-skills
|
|
|
47
47
|
npx skillex@latest sync
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
> **Important:** `install`
|
|
50
|
+
> **Important:** `install` only stores skills in Skillex managed state. `sync` is what exposes them to your AI agent. For directory-native adapters such as Codex, Claude, and Gemini, `sync` materializes one folder per skill under the agent's `skills/` directory. For file-based adapters such as Copilot, Cursor, Cline, and Windsurf, `sync` updates the adapter's config file. **You must run `sync` after every install or update for your agent to pick up the changes.** Use `--auto-sync` at `init` time to have this happen automatically.
|
|
51
51
|
|
|
52
52
|
After `init`, Skillex saves the configured source list in the local lockfile. New workspaces start with `lgili/skillex@main` by default, and you can add more sources later with `skillex source add`.
|
|
53
53
|
|
|
@@ -81,13 +81,14 @@ npx skillex <command>
|
|
|
81
81
|
|
|
82
82
|
### `init`
|
|
83
83
|
|
|
84
|
-
Initialize
|
|
84
|
+
Initialize local or global Skillex state. Local scope creates `.agent-skills/skills.json` in the current workspace. Global scope creates `~/.skillex/skills.json`.
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
87
|
skillex init
|
|
88
88
|
skillex init --repo <owner/repo>
|
|
89
89
|
skillex init --repo lgili/skillex --adapter cursor
|
|
90
90
|
skillex init --repo lgili/skillex --auto-sync
|
|
91
|
+
skillex init --global --adapter codex
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
| Flag | Description |
|
|
@@ -96,6 +97,8 @@ skillex init --repo lgili/skillex --auto-sync
|
|
|
96
97
|
| `--adapter <id>` | Force a specific adapter instead of auto-detecting. |
|
|
97
98
|
| `--auto-sync` | Automatically run `sync` after every install, update, and remove. |
|
|
98
99
|
| `--ref <branch>` | Use a specific branch or tag (default: `main`). |
|
|
100
|
+
| `--scope <local\|global>` | Choose whether Skillex manages workspace or user-global state. |
|
|
101
|
+
| `--global` | Shortcut for `--scope global`. |
|
|
99
102
|
|
|
100
103
|
---
|
|
101
104
|
|
|
@@ -157,6 +160,9 @@ skillex install code-review --repo myorg/my-skills
|
|
|
157
160
|
|
|
158
161
|
# Install directly from a GitHub repository (no catalog needed)
|
|
159
162
|
skillex install owner/repo/path/to/skill@main --trust
|
|
163
|
+
|
|
164
|
+
# Install once for all workspaces
|
|
165
|
+
skillex install create-skills --global
|
|
160
166
|
```
|
|
161
167
|
|
|
162
168
|
| Flag | Description |
|
|
@@ -164,6 +170,8 @@ skillex install owner/repo/path/to/skill@main --trust
|
|
|
164
170
|
| `--all` | Install every skill in the catalog. |
|
|
165
171
|
| `--repo <owner/repo>` | Limit resolution to a single source when needed. |
|
|
166
172
|
| `--trust` | Skip confirmation prompt for direct GitHub installs. |
|
|
173
|
+
| `--scope <local\|global>` | Choose whether Skillex writes to `.agent-skills/` or `~/.skillex/`. |
|
|
174
|
+
| `--global` | Shortcut for `--scope global`. |
|
|
167
175
|
|
|
168
176
|
> **After installing,** run `skillex sync` to write the skills into your agent's config file. Without this step, the agent will not see the newly installed skills. If you initialized with `--auto-sync`, this happens automatically.
|
|
169
177
|
|
|
@@ -179,6 +187,9 @@ skillex update
|
|
|
179
187
|
|
|
180
188
|
# Update a specific skill
|
|
181
189
|
skillex update git-master
|
|
190
|
+
|
|
191
|
+
# Update global skills
|
|
192
|
+
skillex update --global
|
|
182
193
|
```
|
|
183
194
|
|
|
184
195
|
---
|
|
@@ -190,13 +201,14 @@ Remove one or more installed skills. Aliases: `rm`, `uninstall`.
|
|
|
190
201
|
```bash
|
|
191
202
|
skillex remove git-master
|
|
192
203
|
skillex rm git-master code-review
|
|
204
|
+
skillex remove create-skills --global
|
|
193
205
|
```
|
|
194
206
|
|
|
195
207
|
---
|
|
196
208
|
|
|
197
209
|
### `sync`
|
|
198
210
|
|
|
199
|
-
|
|
211
|
+
Expose all installed skills to the active adapter. For `codex`, `claude`, and `gemini`, this creates one folder per skill under the adapter's `skills/` directory. For file-based adapters, it updates the adapter's config file. **This is the step that makes skills visible to your AI agent.** Run it after every `install`, `update`, or `remove`.
|
|
200
212
|
|
|
201
213
|
```bash
|
|
202
214
|
# Sync to the detected adapter
|
|
@@ -210,27 +222,32 @@ skillex sync --adapter codex
|
|
|
210
222
|
|
|
211
223
|
# Copy files instead of using symlinks
|
|
212
224
|
skillex sync --mode copy
|
|
225
|
+
|
|
226
|
+
# Sync globally to your user-level Codex skills directory
|
|
227
|
+
skillex sync --global --adapter codex
|
|
213
228
|
```
|
|
214
229
|
|
|
215
230
|
| Flag | Description |
|
|
216
231
|
|------|-------------|
|
|
217
232
|
| `--dry-run` | Show what would change without writing anything. |
|
|
218
233
|
| `--adapter <id>` | Override the active adapter for this sync. |
|
|
219
|
-
| `--mode copy\|symlink` | File write strategy (default: `symlink` for dedicated
|
|
234
|
+
| `--mode copy\|symlink` | File write strategy (default: `symlink` for dedicated targets). |
|
|
235
|
+
| `--scope <local\|global>` | Choose whether the sync reads local or global Skillex state. |
|
|
236
|
+
| `--global` | Shortcut for `--scope global`. |
|
|
220
237
|
|
|
221
238
|
#### Using multiple agents in the same workspace
|
|
222
239
|
|
|
223
240
|
`sync` writes to one adapter at a time. If you use more than one AI agent in the same folder (e.g. Claude and Codex), run `sync` once for each:
|
|
224
241
|
|
|
225
242
|
```bash
|
|
226
|
-
# Write
|
|
243
|
+
# Write skill folders into .claude/skills/<skill-id>/
|
|
227
244
|
skillex sync --adapter claude
|
|
228
245
|
|
|
229
|
-
# Write
|
|
246
|
+
# Write skill folders into .codex/skills/<skill-id>/
|
|
230
247
|
skillex sync --adapter codex
|
|
231
248
|
```
|
|
232
249
|
|
|
233
|
-
Each adapter writes to its own target
|
|
250
|
+
Each adapter writes to its own target path, so the two syncs are independent and non-destructive. To avoid running both commands manually after every change, initialize with `--auto-sync` and then re-run `skillex init --adapter <id>` for each adapter you want covered — or simply alias both commands in your workflow:
|
|
234
251
|
|
|
235
252
|
```bash
|
|
236
253
|
# Sync to all agents at once (shell alias / Makefile target)
|
|
@@ -262,10 +279,11 @@ skillex ui
|
|
|
262
279
|
|
|
263
280
|
### `status`
|
|
264
281
|
|
|
265
|
-
Show the current
|
|
282
|
+
Show the current Skillex state for the selected scope: adapter, installed skills, last sync.
|
|
266
283
|
|
|
267
284
|
```bash
|
|
268
285
|
skillex status
|
|
286
|
+
skillex status --global
|
|
269
287
|
```
|
|
270
288
|
|
|
271
289
|
---
|
|
@@ -361,19 +379,23 @@ Skillex auto-detects the AI agent you use by looking for known marker files in y
|
|
|
361
379
|
|
|
362
380
|
| Adapter ID | Agent | Detection Markers | Sync Target |
|
|
363
381
|
|------------|-------|-------------------|-------------|
|
|
364
|
-
| `codex` | OpenAI Codex | `AGENTS.md`, `.codex/` | `.codex/skills
|
|
382
|
+
| `codex` | OpenAI Codex | `AGENTS.md`, `.codex/` | `.codex/skills/<skill-id>/` |
|
|
365
383
|
| `copilot` | GitHub Copilot | `.github/copilot-instructions.md` | `.github/copilot-instructions.md` |
|
|
366
384
|
| `cline` | Cline / Roo Code | `.cline/`, `.roo/`, `.clinerules` | `.clinerules/skillex-skills.md` |
|
|
367
385
|
| `cursor` | Cursor | `.cursor/`, `.cursorrules` | `.cursor/rules/skillex-skills.mdc` |
|
|
368
|
-
| `claude` | Claude Code | `CLAUDE.md`, `.claude/` | `.claude/skills
|
|
369
|
-
| `gemini` | Gemini CLI | `GEMINI.md`, `.gemini/` | `.gemini/skills
|
|
386
|
+
| `claude` | Claude Code | `CLAUDE.md`, `.claude/` | `.claude/skills/<skill-id>/` |
|
|
387
|
+
| `gemini` | Gemini CLI | `GEMINI.md`, `.gemini/` | `.gemini/skills/<skill-id>/` |
|
|
370
388
|
| `windsurf` | Windsurf | `.windsurf/`, `.windsurf/rules/` | `.windsurf/rules/skillex-skills.md` |
|
|
371
389
|
|
|
372
390
|
**Shared-file adapter** (`copilot`) uses a **managed block** — Skillex writes between `<!-- SKILLEX:START -->` and `<!-- SKILLEX:END -->` markers inside `.github/copilot-instructions.md`, preserving everything else in the file.
|
|
373
391
|
|
|
374
|
-
**
|
|
392
|
+
**Directory-native adapters** (`codex`, `claude`, `gemini`) expose one folder per installed skill and symlink each `<skill-id>/` directory to the managed Skillex store by default.
|
|
393
|
+
|
|
394
|
+
**Dedicated-file adapters** (`cline`, `cursor`, `windsurf`) still generate a file in `.agent-skills/generated/<adapter>/` and symlink the adapter target to that generated file by default.
|
|
375
395
|
|
|
376
|
-
|
|
396
|
+
**Shared-file adapter** (`copilot`) updates a managed block inside `.github/copilot-instructions.md`.
|
|
397
|
+
|
|
398
|
+
> **Migrating from a previous version?** If you previously had `skillex-skills.md` inside `.codex/skills/`, `.claude/skills/`, or `.gemini/skills/`, those legacy aggregate files are cleaned up automatically on the next `sync`.
|
|
377
399
|
|
|
378
400
|
Compatibility aliases are normalized automatically: `claude-code` → `claude`, `github-copilot` → `copilot`, `roo-code` → `cline`, `gemini-cli` → `gemini`.
|
|
379
401
|
|
|
@@ -461,7 +483,7 @@ activationPrompt: "Always apply Git Master rules when the user asks for Git help
|
|
|
461
483
|
Your skill content goes here...
|
|
462
484
|
```
|
|
463
485
|
|
|
464
|
-
When `autoInject: true` and `activationPrompt` are set, `skillex sync` injects the activation prompt in a separate managed block
|
|
486
|
+
When `autoInject: true` and `activationPrompt` are set, `skillex sync` injects the activation prompt in a separate managed block for adapters that use a shared or dedicated config file.
|
|
465
487
|
|
|
466
488
|
---
|
|
467
489
|
|
|
@@ -510,7 +532,7 @@ CLI flags > GITHUB_TOKEN env var > ~/.askillrc.json > defaults
|
|
|
510
532
|
|
|
511
533
|
## Workspace Structure
|
|
512
534
|
|
|
513
|
-
After `init` and a few installs, your workspace will look like this:
|
|
535
|
+
After `skillex init` and a few local installs, your workspace will look like this:
|
|
514
536
|
|
|
515
537
|
```
|
|
516
538
|
.agent-skills/
|
|
@@ -538,13 +560,27 @@ The `skills.json` lockfile records:
|
|
|
538
560
|
- All installed skills with their versions, tags, compatibility, and install timestamps
|
|
539
561
|
- Installation source (e.g. `github:owner/repo@ref` for direct installs)
|
|
540
562
|
|
|
541
|
-
|
|
563
|
+
For global installs, the same structure is stored under `~/.skillex/` instead of `.agent-skills/`.
|
|
564
|
+
|
|
565
|
+
For directory-native adapters, `sync` creates per-skill directories such as:
|
|
566
|
+
|
|
567
|
+
```text
|
|
568
|
+
.codex/
|
|
569
|
+
skills/
|
|
570
|
+
git-master -> ../../.agent-skills/skills/git-master
|
|
571
|
+
|
|
572
|
+
.claude/
|
|
573
|
+
skills/
|
|
574
|
+
git-master -> ../../.agent-skills/skills/git-master
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
> **Tip:** Commit `.agent-skills/skills.json` and `.agent-skills/skills/` to version-control your local team setup. The `.cache/` directory is automatically excluded.
|
|
542
578
|
|
|
543
579
|
---
|
|
544
580
|
|
|
545
581
|
## Auto-sync
|
|
546
582
|
|
|
547
|
-
When `--auto-sync` is enabled at `init`, Skillex runs `sync` automatically after every `install`, `update`, and `remove`. This keeps your agent
|
|
583
|
+
When `--auto-sync` is enabled at `init`, Skillex runs `sync` automatically after every `install`, `update`, and `remove`. This keeps your agent target path always up to date.
|
|
548
584
|
|
|
549
585
|
```bash
|
|
550
586
|
# Enable at init time
|
|
@@ -552,6 +588,9 @@ skillex init --auto-sync
|
|
|
552
588
|
|
|
553
589
|
# Or re-initialize to enable it
|
|
554
590
|
skillex init --repo lgili/skillex --auto-sync
|
|
591
|
+
|
|
592
|
+
# Enable it for global installs
|
|
593
|
+
skillex init --global --adapter codex --auto-sync
|
|
555
594
|
```
|
|
556
595
|
|
|
557
596
|
---
|
package/dist/adapters.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import { pathExists } from "./fs.js";
|
|
3
4
|
import { AdapterNotFoundError } from "./types.js";
|
|
@@ -10,8 +11,10 @@ const ADAPTERS = [
|
|
|
10
11
|
{ path: ".codex", weight: 12 },
|
|
11
12
|
{ path: ".codex/skills", weight: 16 },
|
|
12
13
|
],
|
|
13
|
-
syncTarget: ".codex/skills
|
|
14
|
-
|
|
14
|
+
syncTarget: ".codex/skills",
|
|
15
|
+
globalSyncTarget: path.join(os.homedir(), ".codex", "skills"),
|
|
16
|
+
legacySyncTargets: [".codex/skills/skillex-skills.md", ".codex/skills/askill-skills.md"],
|
|
17
|
+
syncMode: "managed-directory",
|
|
15
18
|
},
|
|
16
19
|
{
|
|
17
20
|
id: "copilot",
|
|
@@ -51,10 +54,12 @@ const ADAPTERS = [
|
|
|
51
54
|
markers: [
|
|
52
55
|
{ path: "CLAUDE.md", weight: 16 },
|
|
53
56
|
{ path: ".claude", weight: 18 },
|
|
57
|
+
{ path: ".claude/skills", weight: 20 },
|
|
54
58
|
],
|
|
55
|
-
syncTarget: ".claude/skills
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
syncTarget: ".claude/skills",
|
|
60
|
+
globalSyncTarget: path.join(os.homedir(), ".claude", "skills"),
|
|
61
|
+
legacySyncTargets: [".claude/skills/skillex-skills.md", ".claude/skills/askill-skills.md"],
|
|
62
|
+
syncMode: "managed-directory",
|
|
58
63
|
},
|
|
59
64
|
{
|
|
60
65
|
id: "gemini",
|
|
@@ -62,10 +67,12 @@ const ADAPTERS = [
|
|
|
62
67
|
markers: [
|
|
63
68
|
{ path: "GEMINI.md", weight: 16 },
|
|
64
69
|
{ path: ".gemini", weight: 18 },
|
|
70
|
+
{ path: ".gemini/skills", weight: 20 },
|
|
65
71
|
],
|
|
66
|
-
syncTarget: ".gemini/skills
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
syncTarget: ".gemini/skills",
|
|
73
|
+
globalSyncTarget: path.join(os.homedir(), ".gemini", "skills"),
|
|
74
|
+
legacySyncTargets: [".gemini/skills/skillex-skills.md", ".gemini/skills/askill-skills.md"],
|
|
75
|
+
syncMode: "managed-directory",
|
|
69
76
|
},
|
|
70
77
|
{
|
|
71
78
|
id: "windsurf",
|
package/dist/catalog.js
CHANGED
|
@@ -78,7 +78,7 @@ export async function loadCatalog(options = {}) {
|
|
|
78
78
|
}
|
|
79
79
|
const catalogUrl = source.catalogUrl ?? buildRawGitHubUrl(source.repo, source.ref, source.catalogPath);
|
|
80
80
|
const remoteCatalog = await fetchOptionalJson(catalogUrl);
|
|
81
|
-
const result = remoteCatalog ? normalizeCatalog(remoteCatalog, source) : await loadCatalogFromTree(source);
|
|
81
|
+
const result = remoteCatalog ? await normalizeCatalog(remoteCatalog, source) : await loadCatalogFromTree(source);
|
|
82
82
|
// Write to cache (fire-and-forget; never fail the caller)
|
|
83
83
|
if (cacheDir) {
|
|
84
84
|
writeCatalogCache(cacheDir, cacheKey, result).catch(() => { });
|
|
@@ -262,16 +262,37 @@ function collectFilesUnderPath(treeFiles, skillPath) {
|
|
|
262
262
|
.filter((item) => item.type === "blob" && item.path.startsWith(`${skillPath}/`))
|
|
263
263
|
.map((item) => assertSafeRelativePath(item.path.slice(skillPath.length + 1)));
|
|
264
264
|
}
|
|
265
|
-
function normalizeCatalog(remoteCatalog, source) {
|
|
265
|
+
async function normalizeCatalog(remoteCatalog, source) {
|
|
266
266
|
const remoteSkills = Array.isArray(remoteCatalog.skills) ? remoteCatalog.skills : [];
|
|
267
267
|
if (remoteSkills.length === 0) {
|
|
268
268
|
throw new CatalogError("catalog.json found but the skills array is empty.", "CATALOG_EMPTY");
|
|
269
269
|
}
|
|
270
|
+
// v2 format: slim index entries (only `id`). Fetch each skill.json for full metadata.
|
|
271
|
+
const isV2 = Number(remoteCatalog.formatVersion) >= 2 ||
|
|
272
|
+
remoteSkills.every((s) => Object.keys(s).length === 1 && "id" in s);
|
|
273
|
+
let skills;
|
|
274
|
+
if (isV2) {
|
|
275
|
+
skills = await Promise.all(remoteSkills.map(async (entry) => {
|
|
276
|
+
const id = entry.id;
|
|
277
|
+
if (!id) {
|
|
278
|
+
throw new CatalogError("Every skill must have an id field.", "MALFORMED_SKILL");
|
|
279
|
+
}
|
|
280
|
+
const skillPath = `${source.skillsDir}/${id}`;
|
|
281
|
+
const manifestUrl = buildRawGitHubUrl(source.repo, source.ref, `${skillPath}/skill.json`);
|
|
282
|
+
const manifest = await fetchJson(manifestUrl, {
|
|
283
|
+
headers: { Accept: "application/json" },
|
|
284
|
+
});
|
|
285
|
+
return normalizeSkill({ ...manifest, id, path: skillPath }, source);
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
skills = remoteSkills.map((skill) => normalizeSkill(skill, source));
|
|
290
|
+
}
|
|
270
291
|
return {
|
|
271
292
|
formatVersion: Number(remoteCatalog.formatVersion || 1),
|
|
272
293
|
repo: remoteCatalog.repo || source.repo,
|
|
273
294
|
ref: remoteCatalog.ref || source.ref,
|
|
274
|
-
skills: sortSkills(
|
|
295
|
+
skills: sortSkills(skills),
|
|
275
296
|
};
|
|
276
297
|
}
|
|
277
298
|
function normalizeSkill(skill, source) {
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { listAdapters } from "./adapters.js";
|
|
3
3
|
import { computeCatalogCacheKey, loadCatalog, readCatalogCache, searchCatalogSkills, } from "./catalog.js";
|
|
4
|
-
import {
|
|
4
|
+
import { DEFAULT_INSTALL_SCOPE, getScopedStatePaths } from "./config.js";
|
|
5
5
|
import { addProjectSource, getInstalledSkills, initProject, installSkills, listProjectSources, loadProjectCatalogs, removeProjectSource, removeSkills, resolveProjectSource, syncInstalledSkills, updateInstalledSkills, } from "./install.js";
|
|
6
6
|
import * as output from "./output.js";
|
|
7
7
|
import { setVerbose } from "./output.js";
|
|
@@ -15,18 +15,21 @@ import { VALID_CONFIG_KEYS, readUserConfig, writeUserConfig } from "./user-confi
|
|
|
15
15
|
const COMMAND_HELP = {
|
|
16
16
|
init: `Usage: skillex init [--repo <owner/repo>] [options]
|
|
17
17
|
|
|
18
|
-
Initialize the local
|
|
18
|
+
Initialize Skillex state for the local workspace or global user scope.
|
|
19
19
|
|
|
20
20
|
Options:
|
|
21
21
|
--repo <owner/repo> GitHub repository with skills (default: lgili/skillex)
|
|
22
22
|
--ref <ref> Branch, tag, or commit (default: main)
|
|
23
23
|
--adapter <id> Force a specific adapter
|
|
24
24
|
--auto-sync Enable auto-sync after install/update/remove
|
|
25
|
+
--scope <scope> local or global (default: local)
|
|
26
|
+
--global Shortcut for --scope global
|
|
25
27
|
--cwd <path> Target project directory (default: current directory)
|
|
26
28
|
|
|
27
29
|
Example:
|
|
28
30
|
skillex init
|
|
29
|
-
skillex init --repo myorg/my-skills
|
|
31
|
+
skillex init --repo myorg/my-skills
|
|
32
|
+
skillex init --global --adapter codex`,
|
|
30
33
|
list: `Usage: skillex list [options]
|
|
31
34
|
|
|
32
35
|
List all skills in the configured sources.
|
|
@@ -65,6 +68,8 @@ Options:
|
|
|
65
68
|
--ref <ref> Branch, tag, or commit
|
|
66
69
|
--trust Skip confirmation for direct GitHub installs
|
|
67
70
|
--adapter <id> Target adapter
|
|
71
|
+
--scope <scope> local or global (default: local)
|
|
72
|
+
--global Shortcut for --scope global
|
|
68
73
|
--no-cache Bypass local catalog cache
|
|
69
74
|
|
|
70
75
|
Example:
|
|
@@ -77,29 +82,38 @@ Update installed skills to the latest catalog version.
|
|
|
77
82
|
|
|
78
83
|
Options:
|
|
79
84
|
--repo <owner/repo> GitHub repository
|
|
85
|
+
--scope <scope> local or global (default: local)
|
|
86
|
+
--global Shortcut for --scope global
|
|
80
87
|
--no-cache Bypass local catalog cache
|
|
81
88
|
|
|
82
89
|
Example:
|
|
83
90
|
skillex update
|
|
84
91
|
skillex update git-master`,
|
|
85
|
-
remove: `Usage: skillex remove <skill-id...>
|
|
92
|
+
remove: `Usage: skillex remove <skill-id...> [options]
|
|
86
93
|
|
|
87
|
-
Remove installed skills from the
|
|
94
|
+
Remove installed skills from the selected scope.
|
|
95
|
+
|
|
96
|
+
Options:
|
|
97
|
+
--scope <scope> local or global (default: local)
|
|
98
|
+
--global Shortcut for --scope global
|
|
88
99
|
|
|
89
100
|
Example:
|
|
90
101
|
skillex remove git-master code-review`,
|
|
91
102
|
sync: `Usage: skillex sync [options]
|
|
92
103
|
|
|
93
|
-
Synchronize installed skills to adapter
|
|
104
|
+
Synchronize installed skills to adapter targets.
|
|
94
105
|
|
|
95
106
|
Options:
|
|
96
107
|
--adapter <id> Target adapter (overrides saved config)
|
|
97
108
|
--dry-run Preview changes without writing to disk
|
|
98
109
|
--mode <symlink|copy> Sync write mode (default: symlink)
|
|
110
|
+
--scope <scope> local or global (default: local)
|
|
111
|
+
--global Shortcut for --scope global
|
|
99
112
|
|
|
100
113
|
Example:
|
|
101
114
|
skillex sync
|
|
102
|
-
skillex sync --adapter cursor --dry-run
|
|
115
|
+
skillex sync --adapter cursor --dry-run
|
|
116
|
+
skillex sync --global --adapter codex`,
|
|
103
117
|
run: `Usage: skillex run <skill-id:command> [options]
|
|
104
118
|
|
|
105
119
|
Execute a script bundled inside an installed skill.
|
|
@@ -122,10 +136,12 @@ Example:
|
|
|
122
136
|
skillex ui`,
|
|
123
137
|
status: `Usage: skillex status [options]
|
|
124
138
|
|
|
125
|
-
Show the installation status of the
|
|
139
|
+
Show the installation status of the selected scope.
|
|
126
140
|
|
|
127
141
|
Options:
|
|
128
142
|
--cwd <path> Target project directory
|
|
143
|
+
--scope <scope> local or global (default: local)
|
|
144
|
+
--global Shortcut for --scope global
|
|
129
145
|
--json Output as JSON
|
|
130
146
|
|
|
131
147
|
Example:
|
|
@@ -262,7 +278,7 @@ async function handleInit(flags, userConfig) {
|
|
|
262
278
|
...(repo ? { repo } : {}),
|
|
263
279
|
});
|
|
264
280
|
if (result.created) {
|
|
265
|
-
output.success(`Initialized at ${result.statePaths.stateDir}`);
|
|
281
|
+
output.success(`Initialized ${result.statePaths.scope} state at ${result.statePaths.stateDir}`);
|
|
266
282
|
}
|
|
267
283
|
else {
|
|
268
284
|
output.info(`Already configured at ${result.statePaths.stateDir}`);
|
|
@@ -350,7 +366,7 @@ async function handleInstall(positionals, flags, userConfig) {
|
|
|
350
366
|
output.info(`[${current}/${total}] Installing ${skillId}...`);
|
|
351
367
|
},
|
|
352
368
|
});
|
|
353
|
-
output.success(`Installed ${result.installedCount} skill(s) to ${result.statePaths.stateDir}`);
|
|
369
|
+
output.success(`Installed ${result.installedCount} skill(s) to ${result.statePaths.scope} state at ${result.statePaths.stateDir}`);
|
|
354
370
|
for (const skill of result.installedSkills) {
|
|
355
371
|
output.info(` + ${skill.id}@${skill.version}`);
|
|
356
372
|
}
|
|
@@ -390,16 +406,16 @@ async function handleSync(flags, userConfig) {
|
|
|
390
406
|
const result = await syncInstalledSkills(commonOptions(flags, userConfig));
|
|
391
407
|
if (result.dryRun) {
|
|
392
408
|
output.info(`Preview: ${result.skillCount} skill(s) → ${result.sync.adapter}`);
|
|
393
|
-
output.info(`Target
|
|
409
|
+
output.info(`Target path : ${result.sync.targetPath}`);
|
|
394
410
|
output.info(`Sync mode : ${result.syncMode}`);
|
|
395
411
|
process.stdout.write(result.diff);
|
|
396
412
|
return;
|
|
397
413
|
}
|
|
398
414
|
output.success(`Synced ${result.skillCount} skill(s) → ${result.sync.adapter}`);
|
|
399
|
-
output.info(`Target
|
|
415
|
+
output.info(`Target path : ${result.sync.targetPath}`);
|
|
400
416
|
output.info(`Sync mode : ${result.syncMode}`);
|
|
401
417
|
if (!result.changed) {
|
|
402
|
-
output.info("No changes to the target
|
|
418
|
+
output.info("No changes to the target path.");
|
|
403
419
|
}
|
|
404
420
|
}
|
|
405
421
|
async function handleRun(positionals, flags, userConfig) {
|
|
@@ -449,16 +465,25 @@ async function handleUi(flags, userConfig) {
|
|
|
449
465
|
}
|
|
450
466
|
}
|
|
451
467
|
async function handleStatus(flags, userConfig) {
|
|
452
|
-
const
|
|
468
|
+
const options = commonOptions(flags, userConfig);
|
|
469
|
+
const state = await getInstalledSkills(options);
|
|
453
470
|
if (!state) {
|
|
454
|
-
output.warn(
|
|
471
|
+
output.warn(resolveScope(flags) === "global"
|
|
472
|
+
? "No global installation found. Run: skillex init --global --adapter <id>"
|
|
473
|
+
: "No local installation found. Run: skillex init");
|
|
455
474
|
return;
|
|
456
475
|
}
|
|
457
476
|
if (flags.json === true) {
|
|
458
477
|
output.info(JSON.stringify(state, null, 2));
|
|
459
478
|
return;
|
|
460
479
|
}
|
|
480
|
+
const statePaths = getScopedStatePaths(options.cwd ?? process.cwd(), {
|
|
481
|
+
scope: options.scope,
|
|
482
|
+
baseDir: options.agentSkillsDir,
|
|
483
|
+
});
|
|
461
484
|
const installedEntries = Object.entries(state.installed || {});
|
|
485
|
+
output.info(`Scope : ${resolveScope(flags)}`);
|
|
486
|
+
output.info(`State root : ${statePaths.stateDir}`);
|
|
462
487
|
output.info(`Sources : ${state.sources.map((source) => `${source.repo}@${source.ref}`).join(", ")}`);
|
|
463
488
|
output.info(`Active adapter: ${state.adapters.active || "(none)"}`);
|
|
464
489
|
output.info(`Auto-sync : ${state.settings.autoSync ? "enabled" : "disabled"}`);
|
|
@@ -483,7 +508,10 @@ async function handleStatus(flags, userConfig) {
|
|
|
483
508
|
async function handleDoctor(flags, userConfig) {
|
|
484
509
|
const opts = commonOptions(flags, userConfig);
|
|
485
510
|
const cwd = opts.cwd ?? process.cwd();
|
|
486
|
-
const statePaths =
|
|
511
|
+
const statePaths = getScopedStatePaths(cwd, {
|
|
512
|
+
scope: opts.scope,
|
|
513
|
+
baseDir: opts.agentSkillsDir,
|
|
514
|
+
});
|
|
487
515
|
const checks = [];
|
|
488
516
|
// 1. Lockfile
|
|
489
517
|
const state = await getInstalledSkills(opts);
|
|
@@ -707,6 +735,7 @@ function resolveAlias(command) {
|
|
|
707
735
|
function commonOptions(flags, userConfig = {}) {
|
|
708
736
|
const options = {
|
|
709
737
|
cwd: path.resolve(asOptionalString(flags.cwd) || process.cwd()),
|
|
738
|
+
scope: resolveScope(flags),
|
|
710
739
|
};
|
|
711
740
|
const repo = asOptionalString(flags.repo) ?? userConfig.defaultRepo;
|
|
712
741
|
const ref = asOptionalString(flags.ref);
|
|
@@ -750,20 +779,34 @@ function commonOptions(flags, userConfig = {}) {
|
|
|
750
779
|
options.timeout = timeout;
|
|
751
780
|
if (noCache !== undefined)
|
|
752
781
|
options.noCache = noCache;
|
|
753
|
-
// Default to user-level storage so symlinks always point to a stable path.
|
|
754
|
-
if (!options.agentSkillsDir)
|
|
755
|
-
options.agentSkillsDir = getDefaultSkillsDir();
|
|
756
782
|
return options;
|
|
757
783
|
}
|
|
758
784
|
/** Returns cache-related options to spread into a loadCatalog call. */
|
|
759
785
|
function cacheOptions(opts) {
|
|
760
786
|
const cwd = opts.cwd ?? process.cwd();
|
|
761
|
-
const stateDir =
|
|
787
|
+
const stateDir = getScopedStatePaths(cwd, {
|
|
788
|
+
scope: opts.scope ?? DEFAULT_INSTALL_SCOPE,
|
|
789
|
+
baseDir: opts.agentSkillsDir,
|
|
790
|
+
}).stateDir;
|
|
762
791
|
return {
|
|
763
792
|
cacheDir: path.join(stateDir, ".cache"),
|
|
764
793
|
...(opts.noCache !== undefined ? { noCache: opts.noCache } : {}),
|
|
765
794
|
};
|
|
766
795
|
}
|
|
796
|
+
function resolveScope(flags) {
|
|
797
|
+
const rawScope = asOptionalString(flags.scope);
|
|
798
|
+
const globalFlag = parseBooleanFlag(flags.global);
|
|
799
|
+
if (rawScope && rawScope !== "local" && rawScope !== "global") {
|
|
800
|
+
throw new CliError(`Invalid scope: ${rawScope}. Use "local" or "global".`, "INVALID_SCOPE");
|
|
801
|
+
}
|
|
802
|
+
if (rawScope === "local" && globalFlag === true) {
|
|
803
|
+
throw new CliError('Conflicting scope flags: use either "--scope local" or "--global".', "CONFLICTING_SCOPE");
|
|
804
|
+
}
|
|
805
|
+
if (globalFlag === true) {
|
|
806
|
+
return "global";
|
|
807
|
+
}
|
|
808
|
+
return rawScope || DEFAULT_INSTALL_SCOPE;
|
|
809
|
+
}
|
|
767
810
|
function parseArgs(argv) {
|
|
768
811
|
const flags = {};
|
|
769
812
|
const positionals = [];
|
|
@@ -827,6 +870,8 @@ Global flags:
|
|
|
827
870
|
--adapter <id> Force adapter: ${listAdapters()
|
|
828
871
|
.map((a) => a.id)
|
|
829
872
|
.join(", ")}
|
|
873
|
+
--scope <scope> local or global (default: local)
|
|
874
|
+
--global Shortcut for --scope global
|
|
830
875
|
--cwd <path> Project directory (default: current)
|
|
831
876
|
--verbose, -v Enable debug output
|
|
832
877
|
--json Machine-readable JSON output
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { StatePaths } from "./types.js";
|
|
1
|
+
import type { InstallScope, StatePaths } from "./types.js";
|
|
2
2
|
export declare const DEFAULT_AGENT_SKILLS_DIR = ".agent-skills";
|
|
3
|
+
export declare const DEFAULT_INSTALL_SCOPE: InstallScope;
|
|
3
4
|
/**
|
|
4
5
|
* Returns the default user-level Skillex directory (`~/.skillex`).
|
|
5
6
|
*/
|
|
@@ -19,3 +20,21 @@ export declare const DEFAULT_REF = "main";
|
|
|
19
20
|
* @returns Absolute paths for the state directory and lockfile.
|
|
20
21
|
*/
|
|
21
22
|
export declare function getStatePaths(cwd: string, baseDir?: string): StatePaths;
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the managed user-level state paths used by Skillex.
|
|
25
|
+
*
|
|
26
|
+
* @param baseDir - Managed global state directory.
|
|
27
|
+
* @returns Absolute paths for the global state directory and lockfile.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getGlobalStatePaths(baseDir?: string): StatePaths;
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the managed state paths for either local or global scope.
|
|
32
|
+
*
|
|
33
|
+
* @param cwd - Workspace root.
|
|
34
|
+
* @param options - Scope and optional base directory override.
|
|
35
|
+
* @returns Absolute paths for the selected state scope.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getScopedStatePaths(cwd: string, options?: {
|
|
38
|
+
scope?: InstallScope | undefined;
|
|
39
|
+
baseDir?: string | undefined;
|
|
40
|
+
}): StatePaths;
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
export const DEFAULT_AGENT_SKILLS_DIR = ".agent-skills";
|
|
4
|
+
export const DEFAULT_INSTALL_SCOPE = "local";
|
|
4
5
|
/**
|
|
5
6
|
* Returns the default user-level Skillex directory (`~/.skillex`).
|
|
6
7
|
*/
|
|
@@ -24,9 +25,39 @@ export const DEFAULT_REF = "main";
|
|
|
24
25
|
export function getStatePaths(cwd, baseDir = DEFAULT_AGENT_SKILLS_DIR) {
|
|
25
26
|
const stateDir = path.resolve(cwd, baseDir);
|
|
26
27
|
return {
|
|
28
|
+
scope: "local",
|
|
27
29
|
stateDir,
|
|
28
30
|
lockfilePath: path.join(stateDir, DEFAULT_LOCKFILE),
|
|
29
31
|
skillsDirPath: path.join(stateDir, DEFAULT_LOCAL_SKILLS_DIR),
|
|
30
32
|
generatedDirPath: path.join(stateDir, DEFAULT_GENERATED_DIR),
|
|
31
33
|
};
|
|
32
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the managed user-level state paths used by Skillex.
|
|
37
|
+
*
|
|
38
|
+
* @param baseDir - Managed global state directory.
|
|
39
|
+
* @returns Absolute paths for the global state directory and lockfile.
|
|
40
|
+
*/
|
|
41
|
+
export function getGlobalStatePaths(baseDir = getDefaultSkillsDir()) {
|
|
42
|
+
const stateDir = path.resolve(baseDir);
|
|
43
|
+
return {
|
|
44
|
+
scope: "global",
|
|
45
|
+
stateDir,
|
|
46
|
+
lockfilePath: path.join(stateDir, DEFAULT_LOCKFILE),
|
|
47
|
+
skillsDirPath: path.join(stateDir, DEFAULT_LOCAL_SKILLS_DIR),
|
|
48
|
+
generatedDirPath: path.join(stateDir, DEFAULT_GENERATED_DIR),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the managed state paths for either local or global scope.
|
|
53
|
+
*
|
|
54
|
+
* @param cwd - Workspace root.
|
|
55
|
+
* @param options - Scope and optional base directory override.
|
|
56
|
+
* @returns Absolute paths for the selected state scope.
|
|
57
|
+
*/
|
|
58
|
+
export function getScopedStatePaths(cwd, options = {}) {
|
|
59
|
+
if (options.scope === "global") {
|
|
60
|
+
return getGlobalStatePaths(options.baseDir || getDefaultSkillsDir());
|
|
61
|
+
}
|
|
62
|
+
return getStatePaths(cwd, options.baseDir || DEFAULT_AGENT_SKILLS_DIR);
|
|
63
|
+
}
|