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 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
  [![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
 
@@ -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. Sync skills into your agent's config file
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 (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`.
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
- Write all installed skills into the active adapter's config file.
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-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`. |
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 local workspace state: adapter, installed skills, last sync.
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/` | `AGENTS.md` |
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/` | `CLAUDE.md` |
345
- | `gemini` | Gemini CLI | `GEMINI.md`, `.gemini/` | `GEMINI.md` |
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 adapters** (`codex`, `copilot`, `claude`, `gemini`) use a **managed block** — Skillex writes between `<!-- SKILLEX:START -->` and `<!-- SKILLEX:END -->` markers while preserving everything else in the 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
- **Dedicated-file adapters** (`cline`, `cursor`, `windsurf`) generate a file in `.agent-skills/generated/` and create a relative symlink at the adapter's target path. Use `--mode copy` to write directly instead.
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 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.
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
- > **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.
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'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.
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: "AGENTS.md",
14
- syncMode: "managed-block",
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: "CLAUDE.md",
56
- syncMode: "managed-block",
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: "GEMINI.md",
66
- syncMode: "managed-block",
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(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, 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);
@@ -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 = path.join(cwd, opts.agentSkillsDir ?? DEFAULT_AGENT_SKILLS_DIR);
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
+ }