skillex 0.2.4 → 0.3.0

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,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-04-08
11
+
12
+ ### Added
13
+ - `skillex ui` now launches a local HTTP server serving the interactive Vue/Vite SPA instead of a terminal TUI
14
+ - New Vue 3 + Vite single-page application under `ui/` with catalog browser, skill detail pages, and router-based navigation
15
+ - `src/web-ui.ts`: self-contained HTTP server with `/api/skills` and `/api/catalog` JSON endpoints and graceful shutdown
16
+ - `src/markdown.ts`: shared Markdown-to-HTML renderer used by the web API
17
+ - Terminal browser detection: `skillex ui` opens the local server in the platform default browser or a detected terminal viewer
18
+
19
+ ### Changed
20
+ - `src/cli.ts`: `ui` command now delegates to the local web-UI server
21
+ - `src/ui.ts`: updated to detect and delegate to terminal browser when available
22
+ - Build: `npm run build` now runs `build:ui` (Vite) followed by `tsc`
23
+
24
+ ## [0.2.5] - 2026-04-08
25
+
26
+ ### Added
27
+ - `technical-writing-pro` first-party skill for structured technical writing and documentation
28
+
29
+ ### Changed
30
+ - Auto-sync now enabled by default on `skillex init` (was `false`)
31
+ - Auto-sync syncs **all detected adapters**, not only the active one — workspaces with multiple agents (e.g. `.claude/` and `.codex/`) are kept in sync automatically
32
+ - `sync` and auto-sync output now lists each adapter individually with its target path, sync mode, and whether anything changed
33
+ - Removed the restriction that required an active adapter to enable auto-sync
34
+
35
+ ### Fixed
36
+ - Skill removal now cleans up the synced adapter target file and its generated source when the last skill is removed
37
+ - `remove` correctly iterates all resolved adapters when propagating the removal
38
+
10
39
  ## [0.2.4] - 2026-04-08
11
40
 
12
41
  ### Changed
package/README.md CHANGED
@@ -41,13 +41,9 @@ npx skillex@latest list
41
41
 
42
42
  # 3. Install a skill
43
43
  npx skillex@latest install create-skills
44
-
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.
47
- npx skillex@latest sync
48
44
  ```
49
45
 
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.
46
+ > **Important:** auto-sync is enabled by default. After `init`, `install`, `update`, and `remove`, Skillex automatically synchronizes skills into every detected adapter target. For directory-native adapters such as Codex, Claude, and Gemini, this materializes one folder per skill under the agent's `skills/` directory. For file-based adapters such as Copilot, Cursor, Cline, and Windsurf, it updates the adapter config file. Use `skillex sync` when you want to preview, re-run manually, or target a specific adapter.
51
47
 
52
48
  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
49
 
@@ -75,6 +71,23 @@ npm install -D skillex
75
71
  npx skillex <command>
76
72
  ```
77
73
 
74
+ ### Local Web UI development
75
+
76
+ When working on the repository itself, the browser UI is a standalone Vue 3 + Vite frontend under [`ui/`](/Users/lgili/Documents/01%20-%20Codes/01%20-%20Github/Skill/ui). The CLI serves the built assets from [`dist-ui/`](/Users/lgili/Documents/01%20-%20Codes/01%20-%20Github/Skill/dist-ui) and keeps all install/remove/update/sync logic in the local TypeScript backend.
77
+
78
+ ```bash
79
+ npm install
80
+ npm run build:ui
81
+ npm run build
82
+ node ./bin/skillex.js ui
83
+ ```
84
+
85
+ For frontend-only iteration:
86
+
87
+ ```bash
88
+ npm run dev:ui
89
+ ```
90
+
78
91
  ---
79
92
 
80
93
  ## Commands
@@ -88,6 +101,7 @@ skillex init
88
101
  skillex init --repo <owner/repo>
89
102
  skillex init --repo lgili/skillex --adapter cursor
90
103
  skillex init --repo lgili/skillex --auto-sync
104
+ skillex init --auto-sync=false
91
105
  skillex init --global --adapter codex
92
106
  ```
93
107
 
@@ -95,7 +109,7 @@ skillex init --global --adapter codex
95
109
  |------|-------------|
96
110
  | `--repo <owner/repo>` | Optional. Overrides the default first-party source for this workspace. |
97
111
  | `--adapter <id>` | Force a specific adapter instead of auto-detecting. |
98
- | `--auto-sync` | Automatically run `sync` after every install, update, and remove. |
112
+ | `--auto-sync` | Enable or disable automatic sync after install, update, and remove. Default: `true`. |
99
113
  | `--ref <branch>` | Use a specific branch or tag (default: `main`). |
100
114
  | `--scope <local\|global>` | Choose whether Skillex manages workspace or user-global state. |
101
115
  | `--global` | Shortcut for `--scope global`. |
@@ -173,7 +187,7 @@ skillex install create-skills --global
173
187
  | `--scope <local\|global>` | Choose whether Skillex writes to `.agent-skills/` or `~/.skillex/`. |
174
188
  | `--global` | Shortcut for `--scope global`. |
175
189
 
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.
190
+ > **After installing,** Skillex syncs automatically by default. Run `skillex sync` manually only when you want to preview changes, re-run synchronization, or force a specific adapter.
177
191
 
178
192
  ---
179
193
 
@@ -208,10 +222,10 @@ skillex remove create-skills --global
208
222
 
209
223
  ### `sync`
210
224
 
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`.
225
+ Expose all installed skills to the detected adapters. 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 same operation Skillex runs automatically after `install`, `update`, and `remove`.
212
226
 
213
227
  ```bash
214
- # Sync to the detected adapter
228
+ # Sync to all detected adapters
215
229
  skillex sync
216
230
 
217
231
  # Preview changes without writing (shows a diff)
@@ -237,22 +251,17 @@ skillex sync --global --adapter codex
237
251
 
238
252
  #### Using multiple agents in the same workspace
239
253
 
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:
254
+ By default, `sync` writes to every detected adapter in the workspace. If you want to limit the operation to one adapter, pass `--adapter`:
241
255
 
242
256
  ```bash
243
- # Write skill folders into .claude/skills/<skill-id>/
257
+ # Only sync Claude
244
258
  skillex sync --adapter claude
245
259
 
246
- # Write skill folders into .codex/skills/<skill-id>/
260
+ # Only sync Codex
247
261
  skillex sync --adapter codex
248
262
  ```
249
263
 
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
- ```
264
+ Each adapter writes to its own target path, so the syncs are independent and non-destructive. Automatic sync uses the same multi-adapter behavior.
256
265
 
257
266
  ---
258
267
 
@@ -267,12 +276,25 @@ skillex run git-master:cleanup --yes # skip confirmation
267
276
 
268
277
  ---
269
278
 
279
+ ### Default terminal browser
280
+
281
+ Running `skillex` with no subcommand now opens the interactive terminal browser by default.
282
+
283
+ ```bash
284
+ skillex
285
+ skillex browse
286
+ skillex tui
287
+ ```
288
+
289
+ ---
290
+
270
291
  ### `ui`
271
292
 
272
- Open an interactive terminal UI to browse and install skills.
293
+ Open the local Web UI in your browser.
273
294
 
274
295
  ```bash
275
296
  skillex ui
297
+ skillex ui --global
276
298
  ```
277
299
 
278
300
  ---
@@ -580,14 +602,14 @@ For directory-native adapters, `sync` creates per-skill directories such as:
580
602
 
581
603
  ## Auto-sync
582
604
 
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.
605
+ Auto-sync is enabled by default. After `init`, Skillex automatically runs sync after every `install`, `update`, and `remove`, targeting every detected adapter unless you explicitly override one with `--adapter`.
584
606
 
585
607
  ```bash
586
- # Enable at init time
608
+ # Explicitly enable at init time
587
609
  skillex init --auto-sync
588
610
 
589
- # Or re-initialize to enable it
590
- skillex init --repo lgili/skillex --auto-sync
611
+ # Explicitly disable it
612
+ skillex init --auto-sync=false
591
613
 
592
614
  # Enable it for global installs
593
615
  skillex init --global --adapter codex --auto-sync
package/dist/cli.d.ts CHANGED
@@ -5,3 +5,4 @@
5
5
  * @throws {CliError} When the command or flag values are invalid.
6
6
  */
7
7
  export declare function main(argv: string[]): Promise<void>;
8
+ export declare function resolveCommandRoute(command: string | undefined): string;
package/dist/cli.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import * as path from "node:path";
2
2
  import { listAdapters } from "./adapters.js";
3
- import { computeCatalogCacheKey, loadCatalog, readCatalogCache, searchCatalogSkills, } from "./catalog.js";
3
+ import { computeCatalogCacheKey, readCatalogCache, searchCatalogSkills, } from "./catalog.js";
4
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";
8
8
  import { parseSkillCommandReference, runSkillScript } from "./runner.js";
9
9
  import { runInteractiveUi } from "./ui.js";
10
+ import { startWebUiServer } from "./web-ui.js";
10
11
  import { CliError } from "./types.js";
11
12
  import { VALID_CONFIG_KEYS, readUserConfig, writeUserConfig } from "./user-config.js";
12
13
  // ---------------------------------------------------------------------------
@@ -21,7 +22,7 @@ Options:
21
22
  --repo <owner/repo> GitHub repository with skills (default: lgili/skillex)
22
23
  --ref <ref> Branch, tag, or commit (default: main)
23
24
  --adapter <id> Force a specific adapter
24
- --auto-sync Enable auto-sync after install/update/remove
25
+ --auto-sync Enable or disable auto-sync (default: on)
25
26
  --scope <scope> local or global (default: local)
26
27
  --global Shortcut for --scope global
27
28
  --cwd <path> Target project directory (default: current directory)
@@ -124,7 +125,9 @@ Options:
124
125
 
125
126
  Example:
126
127
  skillex run git-master:cleanup --yes`,
127
- ui: `Usage: skillex ui [options]
128
+ browse: `Usage: skillex browse [options]
129
+ skillex tui [options]
130
+ skillex [options]
128
131
 
129
132
  Open the interactive terminal browser to browse and install skills.
130
133
 
@@ -133,7 +136,21 @@ Options:
133
136
  --no-cache Bypass local catalog cache
134
137
 
135
138
  Example:
136
- skillex ui`,
139
+ skillex
140
+ skillex browse`,
141
+ ui: `Usage: skillex ui [options]
142
+
143
+ Open the local Web UI in your browser.
144
+
145
+ Options:
146
+ --repo <owner/repo> GitHub repository
147
+ --scope <scope> local or global (default: local)
148
+ --global Shortcut for --scope global
149
+ --no-cache Bypass local catalog cache
150
+
151
+ Example:
152
+ skillex ui
153
+ skillex ui --global`,
137
154
  status: `Usage: skillex status [options]
138
155
 
139
156
  Show the installation status of the selected scope.
@@ -206,9 +223,14 @@ export async function main(argv) {
206
223
  if (userConfig.githubToken && !process.env.GITHUB_TOKEN) {
207
224
  process.env.GITHUB_TOKEN = userConfig.githubToken;
208
225
  }
226
+ const resolvedCommand = resolveCommandRoute(command);
227
+ if (flags.help === true && !command) {
228
+ printHelp();
229
+ return;
230
+ }
209
231
  // Per-command --help
210
- if (flags.help === true && command && command !== "help") {
211
- const helpText = COMMAND_HELP[command];
232
+ if (flags.help === true && resolvedCommand && resolvedCommand !== "help") {
233
+ const helpText = COMMAND_HELP[resolvedCommand];
212
234
  if (helpText) {
213
235
  output.info(helpText);
214
236
  }
@@ -217,13 +239,13 @@ export async function main(argv) {
217
239
  }
218
240
  return;
219
241
  }
220
- // Resolve command aliases
221
- const resolvedCommand = resolveAlias(command);
222
242
  switch (resolvedCommand) {
223
243
  case "help":
224
- case undefined:
225
244
  printHelp();
226
245
  return;
246
+ case "browse":
247
+ await handleBrowse(flags, userConfig);
248
+ return;
227
249
  case "init":
228
250
  await handleInit(flags, userConfig);
229
251
  return;
@@ -249,7 +271,7 @@ export async function main(argv) {
249
271
  await handleRun(positionals, flags, userConfig);
250
272
  return;
251
273
  case "ui":
252
- await handleUi(flags, userConfig);
274
+ await handleWebUi(flags, userConfig);
253
275
  return;
254
276
  case "status":
255
277
  await handleStatus(flags, userConfig);
@@ -296,6 +318,9 @@ async function handleInit(flags, userConfig) {
296
318
  ` Use --adapter <id> to specify one. Available: ${listAdapters().map((a) => a.id).join(", ")}`);
297
319
  }
298
320
  output.info(` Auto-sync: ${result.lockfile.settings.autoSync ? "enabled" : "disabled"}`);
321
+ if (result.lockfile.adapters.detected.length > 0) {
322
+ output.info(` Detected : ${result.lockfile.adapters.detected.join(", ")}`);
323
+ }
299
324
  output.info("\nNext: run 'skillex list' to browse available skills");
300
325
  }
301
326
  async function handleList(flags, userConfig) {
@@ -406,17 +431,19 @@ async function handleRemove(positionals, flags, userConfig) {
406
431
  async function handleSync(flags, userConfig) {
407
432
  const result = await syncInstalledSkills(commonOptions(flags, userConfig));
408
433
  if (result.dryRun) {
409
- output.info(`Preview: ${result.skillCount} skill(s) → ${result.sync.adapter}`);
410
- output.info(`Target path : ${result.sync.targetPath}`);
411
- output.info(`Sync mode : ${result.syncMode}`);
434
+ output.info(`Preview: ${result.skillCount} skill(s)`);
435
+ for (const entry of result.syncs) {
436
+ output.info(` ${entry.adapter} ${entry.targetPath} [${entry.syncMode}]${entry.changed ? "" : " (no changes)"}`);
437
+ }
412
438
  process.stdout.write(result.diff);
413
439
  return;
414
440
  }
415
- output.success(`Synced ${result.skillCount} skill(s) → ${result.sync.adapter}`);
416
- output.info(`Target path : ${result.sync.targetPath}`);
417
- output.info(`Sync mode : ${result.syncMode}`);
441
+ output.success(`Synced ${result.skillCount} skill(s)`);
442
+ for (const entry of result.syncs) {
443
+ output.info(` ${entry.adapter} ${entry.targetPath} [${entry.syncMode}]${entry.changed ? "" : " (no changes)"}`);
444
+ }
418
445
  if (!result.changed) {
419
- output.info("No changes to the target path.");
446
+ output.info("No changes to the target paths.");
420
447
  }
421
448
  }
422
449
  async function handleRun(positionals, flags, userConfig) {
@@ -434,12 +461,11 @@ async function handleRun(positionals, flags, userConfig) {
434
461
  process.exitCode = exitCode;
435
462
  }
436
463
  }
437
- async function handleUi(flags, userConfig) {
464
+ async function handleBrowse(flags, userConfig) {
438
465
  const options = commonOptions(flags, userConfig);
439
466
  const state = await getInstalledSkills(options);
440
- const source = await resolveProjectSource(options);
441
467
  output.statusLine("Fetching catalog...");
442
- const catalog = await loadCatalog({ ...source, ...cacheOptions(options) });
468
+ const catalog = await loadProjectCatalogs({ ...options, ...cacheOptions(options) });
443
469
  output.clearStatus();
444
470
  if (catalog.skills.length === 0) {
445
471
  output.info("No skills available in the catalog.");
@@ -472,6 +498,15 @@ async function handleUi(flags, userConfig) {
472
498
  }
473
499
  printAutoSyncResult(installResult?.autoSync ?? removeResult?.autoSync ?? null);
474
500
  }
501
+ async function handleWebUi(flags, userConfig) {
502
+ const options = commonOptions(flags, userConfig);
503
+ const session = await startWebUiServer(options);
504
+ output.success(`Skillex Web UI running at ${session.url}`);
505
+ if (!session.opened) {
506
+ output.warn("Could not open the browser automatically. Open the URL above manually.");
507
+ }
508
+ output.info("Press Ctrl+C to stop the local server.");
509
+ }
475
510
  async function handleStatus(flags, userConfig) {
476
511
  const options = commonOptions(flags, userConfig);
477
512
  const state = await getInstalledSkills(options);
@@ -732,13 +767,17 @@ async function handleConfig(positionals, flags) {
732
767
  // ---------------------------------------------------------------------------
733
768
  // Helpers
734
769
  // ---------------------------------------------------------------------------
735
- function resolveAlias(command) {
770
+ export function resolveCommandRoute(command) {
736
771
  const ALIASES = {
737
772
  ls: "list",
738
773
  rm: "remove",
739
774
  uninstall: "remove",
775
+ tui: "browse",
740
776
  };
741
- return command !== undefined ? (ALIASES[command] ?? command) : undefined;
777
+ if (command === undefined) {
778
+ return "browse";
779
+ }
780
+ return ALIASES[command] ?? command;
742
781
  }
743
782
  function commonOptions(flags, userConfig = {}) {
744
783
  const options = {
@@ -856,6 +895,7 @@ function printHelp() {
856
895
  output.info(`skillex — AI agent skill manager
857
896
 
858
897
  Commands:
898
+ skillex open the terminal browser
859
899
  skillex init [--repo owner/repo] [--ref main]
860
900
  skillex list [--json]
861
901
  skillex search [query] [--compatibility claude] [--tag git]
@@ -863,10 +903,11 @@ Commands:
863
903
  skillex install --all
864
904
  skillex update [skill-id...]
865
905
  skillex remove <skill-id...> aliases: rm, uninstall
906
+ skillex browse aliases: tui
866
907
  skillex source <add|remove|list> [...]
867
908
  skillex sync [--adapter id] [--dry-run] [--mode copy]
868
909
  skillex run <skill-id:command> [--yes] [--timeout 30]
869
- skillex ui
910
+ skillex ui open local Web UI
870
911
  skillex status [--json]
871
912
  skillex doctor [--json]
872
913
  skillex config set <key> <value>
@@ -933,8 +974,9 @@ function parseSyncMode(value) {
933
974
  function printAutoSyncResult(result) {
934
975
  if (!result)
935
976
  return;
936
- const suffix = result.changed ? "" : " (no changes)";
937
- output.info(`Auto-sync: ${result.sync.adapter} → ${result.sync.targetPath} [${result.syncMode}]${suffix}`);
977
+ for (const entry of result.syncs) {
978
+ output.info(`Sync: ${entry.adapter} → ${entry.targetPath} [${entry.syncMode}]${entry.changed ? "" : " (no changes)"}`);
979
+ }
938
980
  }
939
981
  function asOptionalString(value) {
940
982
  return typeof value === "string" ? value : undefined;