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 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
  [![Node.js >=20](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
 
8
- **Skillex** is a CLI that installs and synchronizes AI agent skills from a GitHub-hosted catalog into your workspace. It automatically injects skill instructions into the configuration file of whichever AI agent you use Codex, Copilot, Cline, Cursor, Claude, Gemini, or Windsurf.
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` downloads skill files locally. `sync` is what actually writes the skill instructions into your agent's configuration file (e.g. `.claude/skills/skillex-skills.md`, `.codex/skills/skillex-skills.md`, `.github/copilot-instructions.md`). **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.
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 (or re-initialize) the workspace. Creates `.agent-skills/skills.json`, detects your adapter, and writes `.agent-skills/.gitignore`.
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
- Write all installed skills into the active adapter's config file (e.g. `.claude/skills/skillex-skills.md`, `.codex/skills/skillex-skills.md`, `.github/copilot-instructions.md`). **This is the step that makes skills visible to your AI agent.** Run it after every `install`, `update`, or `remove`.
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-file adapters). |
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 skills into .claude/skills/skillex-skills.md
243
+ # Write skill folders into .claude/skills/<skill-id>/
227
244
  skillex sync --adapter claude
228
245
 
229
- # Write skills into .codex/skills/skillex-skills.md
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 file, 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:
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 local workspace state: adapter, installed skills, last sync.
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/skillex-skills.md` |
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/skillex-skills.md` |
369
- | `gemini` | Gemini CLI | `GEMINI.md`, `.gemini/` | `.gemini/skills/skillex-skills.md` |
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
- **Dedicated-file adapters** (`codex`, `cline`, `claude`, `cursor`, `gemini`, `windsurf`) each generate a file in `~/.skillex/generated/<adapter>/` and create an absolute symlink at the adapter's target path (e.g. `.claude/skills/skillex-skills.md`). Use `--mode copy` to write directly instead.
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
- > **Migrating from a previous version?** If you had skill content injected into `CLAUDE.md`, `GEMINI.md`, or `AGENTS.md` by an older version of Skillex, those legacy files are cleaned up automatically on the next `sync`. You can also remove the block manually.
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 at the top of the adapter's config file so the agent always has that context loaded.
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
- > **Tip:** Commit `.agent-skills/skills.json` and `.agent-skills/skills/` to version-control your skill setup. The `.cache/` directory is automatically excluded.
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's config file always up to date.
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/skillex-skills.md",
14
- syncMode: "managed-file",
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/skillex-skills.md",
56
- legacySyncTargets: ["CLAUDE.md"],
57
- syncMode: "managed-file",
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/skillex-skills.md",
67
- legacySyncTargets: ["GEMINI.md"],
68
- syncMode: "managed-file",
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(remoteSkills.map((skill) => normalizeSkill(skill, source))),
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 { DEFAULT_AGENT_SKILLS_DIR, getDefaultSkillsDir, getStatePaths } from "./config.js";
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 Skillex workspace.
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 local workspace.
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 target files.
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 current workspace.
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 file : ${result.sync.targetPath}`);
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 file : ${result.sync.targetPath}`);
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 file.");
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 state = await getInstalledSkills(commonOptions(flags, userConfig));
468
+ const options = commonOptions(flags, userConfig);
469
+ const state = await getInstalledSkills(options);
453
470
  if (!state) {
454
- output.warn("No local installation found. Run: skillex init");
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 = getStatePaths(cwd, opts.agentSkillsDir ?? DEFAULT_AGENT_SKILLS_DIR);
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 = path.resolve(cwd, opts.agentSkillsDir ?? getDefaultSkillsDir());
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
+ }