skillex 0.2.0 → 0.2.2
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 +22 -0
- package/README.md +80 -15
- package/dist/adapters.js +15 -6
- package/dist/catalog.js +24 -3
- package/dist/cli.js +65 -17
- package/dist/config.d.ts +24 -1
- package/dist/config.js +38 -0
- package/dist/fs.d.ts +7 -0
- package/dist/fs.js +15 -2
- package/dist/install.d.ts +4 -4
- package/dist/install.js +55 -27
- package/dist/runner.js +9 -4
- package/dist/sync.d.ts +2 -2
- package/dist/sync.js +194 -47
- package/dist/types.d.ts +24 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.2] - 2026-04-08
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- 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
|
|
14
|
+
- `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
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- `catalog.json` is now a slim index (`formatVersion: 2`): each entry contains only `{ "id" }` — no repeated metadata
|
|
18
|
+
- Full skill metadata (name, version, description, tags, compatibility, files) now lives exclusively in each skill's `skill.json`
|
|
19
|
+
- CLI catalog loader detects v2 format and fetches each `skill.json` in parallel to hydrate the full manifest
|
|
20
|
+
|
|
21
|
+
## [0.2.1] - 2026-04-07
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Skills and generated symlink sources are now stored in `~/.skillex/` (user-level) instead of `<project>/.agent-skills/`, so a single install is shared across all workspaces and symlinks always resolve correctly
|
|
25
|
+
- `claude` adapter: switched from managed-block in `CLAUDE.md` to a dedicated file at `.claude/skills/skillex-skills.md`; old `CLAUDE.md` block cleaned up automatically on next `sync`
|
|
26
|
+
- `gemini` adapter: switched from managed-block in `GEMINI.md` to a dedicated file at `.gemini/skills/skillex-skills.md`; old `GEMINI.md` block cleaned up automatically on next `sync`
|
|
27
|
+
- `codex` adapter: switched from managed-block in `AGENTS.md` to a dedicated file at `.codex/skills/skillex-skills.md` (introduced in this cycle)
|
|
28
|
+
- Symlinks are now absolute paths instead of relative, ensuring they work regardless of project depth
|
|
29
|
+
- `copilot` is now the only adapter that uses managed-block injection
|
|
30
|
+
- `--agent-skills-dir` flag still overrides to a project-local directory when isolation is needed
|
|
31
|
+
|
|
10
32
|
## [0.2.0] - 2026-04-07
|
|
11
33
|
|
|
12
34
|
### Added
|
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
|
|
|
@@ -42,10 +42,13 @@ npx skillex@latest list
|
|
|
42
42
|
# 3. Install a skill
|
|
43
43
|
npx skillex@latest install create-skills
|
|
44
44
|
|
|
45
|
-
# 4.
|
|
45
|
+
# 4. Write the installed skills into your agent's config file
|
|
46
|
+
# ⚠️ This step is required — without it, your agent cannot see the skills.
|
|
46
47
|
npx skillex@latest sync
|
|
47
48
|
```
|
|
48
49
|
|
|
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
|
+
|
|
49
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`.
|
|
50
53
|
|
|
51
54
|
---
|
|
@@ -78,13 +81,14 @@ npx skillex <command>
|
|
|
78
81
|
|
|
79
82
|
### `init`
|
|
80
83
|
|
|
81
|
-
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`.
|
|
82
85
|
|
|
83
86
|
```bash
|
|
84
87
|
skillex init
|
|
85
88
|
skillex init --repo <owner/repo>
|
|
86
89
|
skillex init --repo lgili/skillex --adapter cursor
|
|
87
90
|
skillex init --repo lgili/skillex --auto-sync
|
|
91
|
+
skillex init --global --adapter codex
|
|
88
92
|
```
|
|
89
93
|
|
|
90
94
|
| Flag | Description |
|
|
@@ -93,6 +97,8 @@ skillex init --repo lgili/skillex --auto-sync
|
|
|
93
97
|
| `--adapter <id>` | Force a specific adapter instead of auto-detecting. |
|
|
94
98
|
| `--auto-sync` | Automatically run `sync` after every install, update, and remove. |
|
|
95
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`. |
|
|
96
102
|
|
|
97
103
|
---
|
|
98
104
|
|
|
@@ -154,6 +160,9 @@ skillex install code-review --repo myorg/my-skills
|
|
|
154
160
|
|
|
155
161
|
# Install directly from a GitHub repository (no catalog needed)
|
|
156
162
|
skillex install owner/repo/path/to/skill@main --trust
|
|
163
|
+
|
|
164
|
+
# Install once for all workspaces
|
|
165
|
+
skillex install create-skills --global
|
|
157
166
|
```
|
|
158
167
|
|
|
159
168
|
| Flag | Description |
|
|
@@ -161,6 +170,10 @@ skillex install owner/repo/path/to/skill@main --trust
|
|
|
161
170
|
| `--all` | Install every skill in the catalog. |
|
|
162
171
|
| `--repo <owner/repo>` | Limit resolution to a single source when needed. |
|
|
163
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`. |
|
|
175
|
+
|
|
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.
|
|
164
177
|
|
|
165
178
|
---
|
|
166
179
|
|
|
@@ -174,6 +187,9 @@ skillex update
|
|
|
174
187
|
|
|
175
188
|
# Update a specific skill
|
|
176
189
|
skillex update git-master
|
|
190
|
+
|
|
191
|
+
# Update global skills
|
|
192
|
+
skillex update --global
|
|
177
193
|
```
|
|
178
194
|
|
|
179
195
|
---
|
|
@@ -185,13 +201,14 @@ Remove one or more installed skills. Aliases: `rm`, `uninstall`.
|
|
|
185
201
|
```bash
|
|
186
202
|
skillex remove git-master
|
|
187
203
|
skillex rm git-master code-review
|
|
204
|
+
skillex remove create-skills --global
|
|
188
205
|
```
|
|
189
206
|
|
|
190
207
|
---
|
|
191
208
|
|
|
192
209
|
### `sync`
|
|
193
210
|
|
|
194
|
-
|
|
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`.
|
|
195
212
|
|
|
196
213
|
```bash
|
|
197
214
|
# Sync to the detected adapter
|
|
@@ -205,13 +222,37 @@ skillex sync --adapter codex
|
|
|
205
222
|
|
|
206
223
|
# Copy files instead of using symlinks
|
|
207
224
|
skillex sync --mode copy
|
|
225
|
+
|
|
226
|
+
# Sync globally to your user-level Codex skills directory
|
|
227
|
+
skillex sync --global --adapter codex
|
|
208
228
|
```
|
|
209
229
|
|
|
210
230
|
| Flag | Description |
|
|
211
231
|
|------|-------------|
|
|
212
232
|
| `--dry-run` | Show what would change without writing anything. |
|
|
213
233
|
| `--adapter <id>` | Override the active adapter for this sync. |
|
|
214
|
-
| `--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`. |
|
|
237
|
+
|
|
238
|
+
#### Using multiple agents in the same workspace
|
|
239
|
+
|
|
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:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Write skill folders into .claude/skills/<skill-id>/
|
|
244
|
+
skillex sync --adapter claude
|
|
245
|
+
|
|
246
|
+
# Write skill folders into .codex/skills/<skill-id>/
|
|
247
|
+
skillex sync --adapter codex
|
|
248
|
+
```
|
|
249
|
+
|
|
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:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Sync to all agents at once (shell alias / Makefile target)
|
|
254
|
+
skillex sync --adapter claude && skillex sync --adapter codex
|
|
255
|
+
```
|
|
215
256
|
|
|
216
257
|
---
|
|
217
258
|
|
|
@@ -238,10 +279,11 @@ skillex ui
|
|
|
238
279
|
|
|
239
280
|
### `status`
|
|
240
281
|
|
|
241
|
-
Show the current
|
|
282
|
+
Show the current Skillex state for the selected scope: adapter, installed skills, last sync.
|
|
242
283
|
|
|
243
284
|
```bash
|
|
244
285
|
skillex status
|
|
286
|
+
skillex status --global
|
|
245
287
|
```
|
|
246
288
|
|
|
247
289
|
---
|
|
@@ -337,17 +379,23 @@ Skillex auto-detects the AI agent you use by looking for known marker files in y
|
|
|
337
379
|
|
|
338
380
|
| Adapter ID | Agent | Detection Markers | Sync Target |
|
|
339
381
|
|------------|-------|-------------------|-------------|
|
|
340
|
-
| `codex` | OpenAI Codex | `AGENTS.md`, `.codex/` |
|
|
382
|
+
| `codex` | OpenAI Codex | `AGENTS.md`, `.codex/` | `.codex/skills/<skill-id>/` |
|
|
341
383
|
| `copilot` | GitHub Copilot | `.github/copilot-instructions.md` | `.github/copilot-instructions.md` |
|
|
342
384
|
| `cline` | Cline / Roo Code | `.cline/`, `.roo/`, `.clinerules` | `.clinerules/skillex-skills.md` |
|
|
343
385
|
| `cursor` | Cursor | `.cursor/`, `.cursorrules` | `.cursor/rules/skillex-skills.mdc` |
|
|
344
|
-
| `claude` | Claude Code | `CLAUDE.md`, `.claude/` |
|
|
345
|
-
| `gemini` | Gemini CLI | `GEMINI.md`, `.gemini/` |
|
|
386
|
+
| `claude` | Claude Code | `CLAUDE.md`, `.claude/` | `.claude/skills/<skill-id>/` |
|
|
387
|
+
| `gemini` | Gemini CLI | `GEMINI.md`, `.gemini/` | `.gemini/skills/<skill-id>/` |
|
|
346
388
|
| `windsurf` | Windsurf | `.windsurf/`, `.windsurf/rules/` | `.windsurf/rules/skillex-skills.md` |
|
|
347
389
|
|
|
348
|
-
**Shared-file
|
|
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.
|
|
391
|
+
|
|
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.
|
|
349
395
|
|
|
350
|
-
**
|
|
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`.
|
|
351
399
|
|
|
352
400
|
Compatibility aliases are normalized automatically: `claude-code` → `claude`, `github-copilot` → `copilot`, `roo-code` → `cline`, `gemini-cli` → `gemini`.
|
|
353
401
|
|
|
@@ -435,7 +483,7 @@ activationPrompt: "Always apply Git Master rules when the user asks for Git help
|
|
|
435
483
|
Your skill content goes here...
|
|
436
484
|
```
|
|
437
485
|
|
|
438
|
-
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.
|
|
439
487
|
|
|
440
488
|
---
|
|
441
489
|
|
|
@@ -484,7 +532,7 @@ CLI flags > GITHUB_TOKEN env var > ~/.askillrc.json > defaults
|
|
|
484
532
|
|
|
485
533
|
## Workspace Structure
|
|
486
534
|
|
|
487
|
-
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:
|
|
488
536
|
|
|
489
537
|
```
|
|
490
538
|
.agent-skills/
|
|
@@ -512,13 +560,27 @@ The `skills.json` lockfile records:
|
|
|
512
560
|
- All installed skills with their versions, tags, compatibility, and install timestamps
|
|
513
561
|
- Installation source (e.g. `github:owner/repo@ref` for direct installs)
|
|
514
562
|
|
|
515
|
-
|
|
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.
|
|
516
578
|
|
|
517
579
|
---
|
|
518
580
|
|
|
519
581
|
## Auto-sync
|
|
520
582
|
|
|
521
|
-
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.
|
|
522
584
|
|
|
523
585
|
```bash
|
|
524
586
|
# Enable at init time
|
|
@@ -526,6 +588,9 @@ skillex init --auto-sync
|
|
|
526
588
|
|
|
527
589
|
# Or re-initialize to enable it
|
|
528
590
|
skillex init --repo lgili/skillex --auto-sync
|
|
591
|
+
|
|
592
|
+
# Enable it for global installs
|
|
593
|
+
skillex init --global --adapter codex --auto-sync
|
|
529
594
|
```
|
|
530
595
|
|
|
531
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: "
|
|
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,9 +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: "
|
|
56
|
-
|
|
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",
|
|
57
63
|
},
|
|
58
64
|
{
|
|
59
65
|
id: "gemini",
|
|
@@ -61,9 +67,12 @@ const ADAPTERS = [
|
|
|
61
67
|
markers: [
|
|
62
68
|
{ path: "GEMINI.md", weight: 16 },
|
|
63
69
|
{ path: ".gemini", weight: 18 },
|
|
70
|
+
{ path: ".gemini/skills", weight: 20 },
|
|
64
71
|
],
|
|
65
|
-
syncTarget: "
|
|
66
|
-
|
|
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",
|
|
67
76
|
},
|
|
68
77
|
{
|
|
69
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);
|
|
@@ -755,12 +784,29 @@ function commonOptions(flags, userConfig = {}) {
|
|
|
755
784
|
/** Returns cache-related options to spread into a loadCatalog call. */
|
|
756
785
|
function cacheOptions(opts) {
|
|
757
786
|
const cwd = opts.cwd ?? process.cwd();
|
|
758
|
-
const stateDir =
|
|
787
|
+
const stateDir = getScopedStatePaths(cwd, {
|
|
788
|
+
scope: opts.scope ?? DEFAULT_INSTALL_SCOPE,
|
|
789
|
+
baseDir: opts.agentSkillsDir,
|
|
790
|
+
}).stateDir;
|
|
759
791
|
return {
|
|
760
792
|
cacheDir: path.join(stateDir, ".cache"),
|
|
761
793
|
...(opts.noCache !== undefined ? { noCache: opts.noCache } : {}),
|
|
762
794
|
};
|
|
763
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
|
+
}
|
|
764
810
|
function parseArgs(argv) {
|
|
765
811
|
const flags = {};
|
|
766
812
|
const positionals = [];
|
|
@@ -824,6 +870,8 @@ Global flags:
|
|
|
824
870
|
--adapter <id> Force adapter: ${listAdapters()
|
|
825
871
|
.map((a) => a.id)
|
|
826
872
|
.join(", ")}
|
|
873
|
+
--scope <scope> local or global (default: local)
|
|
874
|
+
--global Shortcut for --scope global
|
|
827
875
|
--cwd <path> Project directory (default: current)
|
|
828
876
|
--verbose, -v Enable debug output
|
|
829
877
|
--json Machine-readable JSON output
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
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;
|
|
4
|
+
/**
|
|
5
|
+
* Returns the default user-level Skillex directory (`~/.skillex`).
|
|
6
|
+
*/
|
|
7
|
+
export declare function getDefaultSkillsDir(): string;
|
|
3
8
|
export declare const DEFAULT_LOCKFILE = "skills.json";
|
|
4
9
|
export declare const DEFAULT_LOCAL_SKILLS_DIR = "skills";
|
|
5
10
|
export declare const DEFAULT_GENERATED_DIR = "generated";
|
|
@@ -15,3 +20,21 @@ export declare const DEFAULT_REF = "main";
|
|
|
15
20
|
* @returns Absolute paths for the state directory and lockfile.
|
|
16
21
|
*/
|
|
17
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,5 +1,13 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
export const DEFAULT_AGENT_SKILLS_DIR = ".agent-skills";
|
|
4
|
+
export const DEFAULT_INSTALL_SCOPE = "local";
|
|
5
|
+
/**
|
|
6
|
+
* Returns the default user-level Skillex directory (`~/.skillex`).
|
|
7
|
+
*/
|
|
8
|
+
export function getDefaultSkillsDir() {
|
|
9
|
+
return path.join(os.homedir(), ".skillex");
|
|
10
|
+
}
|
|
3
11
|
export const DEFAULT_LOCKFILE = "skills.json";
|
|
4
12
|
export const DEFAULT_LOCAL_SKILLS_DIR = "skills";
|
|
5
13
|
export const DEFAULT_GENERATED_DIR = "generated";
|
|
@@ -17,9 +25,39 @@ export const DEFAULT_REF = "main";
|
|
|
17
25
|
export function getStatePaths(cwd, baseDir = DEFAULT_AGENT_SKILLS_DIR) {
|
|
18
26
|
const stateDir = path.resolve(cwd, baseDir);
|
|
19
27
|
return {
|
|
28
|
+
scope: "local",
|
|
29
|
+
stateDir,
|
|
30
|
+
lockfilePath: path.join(stateDir, DEFAULT_LOCKFILE),
|
|
31
|
+
skillsDirPath: path.join(stateDir, DEFAULT_LOCAL_SKILLS_DIR),
|
|
32
|
+
generatedDirPath: path.join(stateDir, DEFAULT_GENERATED_DIR),
|
|
33
|
+
};
|
|
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",
|
|
20
45
|
stateDir,
|
|
21
46
|
lockfilePath: path.join(stateDir, DEFAULT_LOCKFILE),
|
|
22
47
|
skillsDirPath: path.join(stateDir, DEFAULT_LOCAL_SKILLS_DIR),
|
|
23
48
|
generatedDirPath: path.join(stateDir, DEFAULT_GENERATED_DIR),
|
|
24
49
|
};
|
|
25
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
|
+
}
|