@lyupro/skillforge-mcp 1.2.0 → 1.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.
Files changed (40) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +22 -0
  4. package/README.md +26 -12
  5. package/dist/cli/folders-format.d.ts +10 -0
  6. package/dist/cli/folders-format.d.ts.map +1 -0
  7. package/dist/cli/folders-format.js +41 -0
  8. package/dist/cli/folders-format.js.map +1 -0
  9. package/dist/cli/folders-handlers.d.ts +16 -0
  10. package/dist/cli/folders-handlers.d.ts.map +1 -0
  11. package/dist/cli/folders-handlers.js +195 -0
  12. package/dist/cli/folders-handlers.js.map +1 -0
  13. package/dist/cli/folders-shared.d.ts +29 -0
  14. package/dist/cli/folders-shared.d.ts.map +1 -0
  15. package/dist/cli/folders-shared.js +82 -0
  16. package/dist/cli/folders-shared.js.map +1 -0
  17. package/dist/cli/folders.d.ts +7 -2
  18. package/dist/cli/folders.d.ts.map +1 -1
  19. package/dist/cli/folders.js +28 -177
  20. package/dist/cli/folders.js.map +1 -1
  21. package/dist/cli/tools.d.ts.map +1 -1
  22. package/dist/cli/tools.js +6 -0
  23. package/dist/cli/tools.js.map +1 -1
  24. package/dist/config/config-schema.d.ts +8 -0
  25. package/dist/config/config-schema.d.ts.map +1 -1
  26. package/dist/config/config-schema.js +6 -0
  27. package/dist/config/config-schema.js.map +1 -1
  28. package/dist/server.d.ts.map +1 -1
  29. package/dist/server.js +3 -1
  30. package/dist/server.js.map +1 -1
  31. package/dist/tools/configure.d.ts +2 -0
  32. package/dist/tools/configure.d.ts.map +1 -1
  33. package/dist/tools/configure.js +14 -1
  34. package/dist/tools/configure.js.map +1 -1
  35. package/dist/tools/list.d.ts +2 -0
  36. package/dist/tools/list.d.ts.map +1 -1
  37. package/dist/tools/list.js +12 -0
  38. package/dist/tools/list.js.map +1 -1
  39. package/manifest.json +3 -3
  40. package/package.json +1 -1
@@ -14,7 +14,7 @@
14
14
  "package": "@lyupro/skillforge-mcp"
15
15
  },
16
16
  "description": "Universal Skills MCP server — load Markdown skills from arbitrary folders, lazy-by-design, cross-tool.",
17
- "version": "1.2.0",
17
+ "version": "1.3.0",
18
18
  "author": {
19
19
  "name": "Lyu Pro",
20
20
  "email": "lyupro.dev@gmail.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "skillforge",
4
- "version": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "description": "Universal Skills MCP server — load Markdown skills from arbitrary folders, lazy-by-design, cross-tool.",
6
6
  "author": {
7
7
  "name": "Lyu Pro",
package/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to **SkillForge MCP** are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
+ ## [1.3.0] — 2026-05-16
6
+
7
+ Folder ergonomics — address folders by a short alias, toggle them on and off, and filter skills by folder tag.
8
+
9
+ ### Added
10
+
11
+ - Folder alias. A folder entry now carries an optional kebab-case `alias` — a single short handle for a folder. `skillforge folders add <path> --alias <name>` registers it; `skillforge folders remove <name>` and the new `skillforge folders alias <path|alias> <name>` accept the alias in place of the full absolute path. The full path keeps working everywhere. Aliases are validated kebab-case and unique across registered folders — a collision fails with exit ≠ 0 and leaves the config untouched. The `skills__configure` `add_folder` action accepts an `alias` field too, and `folders list` shows an `ALIAS` column.
12
+ - `skillforge folders enable <path|alias>` / `skillforge folders disable <path|alias>` — a two-way toggle for the `enabled` flag. `folders add --disabled` previously had no inverse; re-activating a folder meant hand-editing `config.json`. A disabled folder stays in the config but is skipped on scan.
13
+ - Folder-tag filtering. Folder `tags` were written to the config but never read. `skills__list` now accepts a `folderTag` argument that keeps only skills under folders carrying that tag, and `skillforge folders list --tag <name>` filters the listing the same way. `docs/CONFIGURATION.md` gains a "tags vs alias" section: `alias` is one unique handle per folder for addressing, `tags` are many shared labels for grouping.
14
+
15
+ ### Changed
16
+
17
+ - `src/cli/folders.ts` split into four flat sibling modules (`folders.ts`, `folders-handlers.ts`, `folders-format.ts`, `folders-shared.ts`) to stay under the 400-line file gate. The `dispatcher.ts` import path is unchanged.
18
+
19
+ ### Verified
20
+
21
+ - 561 / 561 tests passing + 1 win32-skip.
22
+ - `pnpm lint` (`tsc --noEmit`) clean.
23
+ - `pnpm build` clean.
24
+ - `pnpm check:size` — all source files ≤ 400 lines.
25
+
5
26
  ## [1.2.0] — 2026-05-16
6
27
 
7
28
  Terminal-side tooling — inspect MCP tools and manage skill folders without an LLM session, plus repo-local install scope and Claude Code plugin packaging.
@@ -157,6 +178,7 @@ All 10 verified through real parse pipeline + `StrategyFactory.create()` correct
157
178
  - **`pnpm build`** clean.
158
179
  - **`pnpm smoke`** end-to-end via subprocess `dist/server.js` — LoggingDecorator trace visible.
159
180
 
181
+ [1.3.0]: https://github.com/lyupro/skillforge-mcp/releases/tag/v1.3.0
160
182
  [1.2.0]: https://github.com/lyupro/skillforge-mcp/releases/tag/v1.2.0
161
183
  [1.1.1]: https://github.com/lyupro/skillforge-mcp/releases/tag/v1.1.1
162
184
  [1.1.0]: https://github.com/lyupro/skillforge-mcp/releases/tag/v1.1.0
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
7
7
  [![MCP](https://img.shields.io/badge/MCP-stdio-purple)](https://modelcontextprotocol.io)
8
8
 
9
- **v1.2.0** — 5 MCP tools, one-command install across Claude Code / Codex CLI / Cursor, terminal `tools` + `folders` subcommands, global/project install scopes, Claude Code plugin packaging, 535 tests, 10 sample skills, modular architecture (all source files ≤ 400 lines).
9
+ **v1.3.0** — 5 MCP tools, one-command install across Claude Code / Codex CLI / Cursor, terminal `tools` + `folders` subcommands (folder aliases, enable/disable toggle, tag filter), global/project install scopes, Claude Code plugin packaging, 561 tests, 10 sample skills, modular architecture (all source files ≤ 400 lines).
10
10
 
11
11
  ---
12
12
 
@@ -105,7 +105,7 @@ The `skillforge` / `skillforge-mcp` binary is a dispatcher — the first positio
105
105
  | `install` | Wire SkillForge into Claude Code / Codex CLI / Cursor. Flags: `--claude` / `--codex` / `--cursor` / `--all`, `--dry-run`, `--uninstall`, `--force`, `--entry npx\|local`, `--binary-path <path>`, `--scope global\|project`. |
106
106
  | `uninstall` | Reverse a previous install. Accepts the same `--scope global\|project` flag. |
107
107
  | `tools` | Print the 5 MCP tools the server exposes (name, description, parameters, example). Pass `--json` for machine-readable output. |
108
- | `folders` | Manage skill folders from the terminal — `list` / `add` / `remove` / `reset`. |
108
+ | `folders` | Manage skill folders from the terminal — `list` / `add` / `remove` / `alias` / `enable` / `disable` / `reset`. |
109
109
  | `--version`, `-v` | Print the package version. |
110
110
  | `--help`, `-h` | Print combined usage. |
111
111
 
@@ -123,22 +123,30 @@ Prints every MCP tool the server exposes (`skills__list`, `skills__get`, `skills
123
123
  Folder management is also available from the shell, not just via the `skills__configure` MCP tool inside an LLM session:
124
124
 
125
125
  ```bash
126
- skillforge folders list [--json] # print registered folders
127
- skillforge folders add <path> [flags] # register a folder
128
- skillforge folders remove <path> # remove a folder entry
129
- skillforge folders reset --yes # reset folders to the default (empty) list
126
+ skillforge folders list [--json] [--tag <name>] # print registered folders
127
+ skillforge folders add <path> [flags] # register a folder
128
+ skillforge folders remove <path|alias> # remove a folder entry
129
+ skillforge folders alias <path|alias> <name> # set or change a folder alias
130
+ skillforge folders enable <path|alias> # re-activate a disabled folder
131
+ skillforge folders disable <path|alias> # deactivate a folder (kept in config)
132
+ skillforge folders reset --yes # reset folders to the default (empty) list
130
133
  ```
131
134
 
132
135
  `add` flags:
133
136
 
134
137
  - `--priority <n>` — folder priority (default `100`; higher wins on name collisions).
135
- - `--tags <a,b,c>` — comma-separated tags.
138
+ - `--alias <name>` — a short kebab-case handle, unique across folders. Lets `remove` / `enable` / `disable` target the folder without typing the full path.
139
+ - `--tags <a,b,c>` — comma-separated tags. Filter on them via `folders list --tag <name>` or the `skills__list` `folderTag` argument.
136
140
  - `--disabled` — register the folder disabled.
137
141
 
138
142
  ```bash
139
- skillforge folders add ~/.lyupro/skills --priority 50 --tags work,review
143
+ skillforge folders add ~/.lyupro/skills --priority 50 --alias core --tags work,review
144
+ skillforge folders disable core # address it by alias, not by path
145
+ skillforge folders list --tag work # only folders tagged "work"
140
146
  ```
141
147
 
148
+ `alias` is one unique handle per folder (addressing); `tags` are many shared labels (grouping and filtering) — see [docs/CONFIGURATION.md](./docs/CONFIGURATION.md) for the full contrast.
149
+
142
150
  `reset` requires `--yes` to apply — without it, the command prints what would change and makes no edits. All `folders` actions read and write the same persisted config (`~/.lyupro/.skillforge/config.json`) as the `skills__configure` MCP tool.
143
151
 
144
152
  If you register a folder that already lives inside another tool's native skill store (a Claude Code plugin cache or a Gemini CLI extension), `folders add` prints a hint to disable the duplicate source so the same skills don't load twice. SkillForge only prints the hint — it never edits another tool's config.
@@ -147,7 +155,7 @@ If you register a folder that already lives inside another tool's native skill s
147
155
 
148
156
  | Tool | Purpose |
149
157
  |------|---------|
150
- | `skills__list` | Enumerate available skills (metadata only). Filters: `folder`, `search`, `source`. |
158
+ | `skills__list` | Enumerate available skills (metadata only). Filters: `folder`, `search`, `source`, `folderTag`. |
151
159
  | `skills__get` | Fetch full SKILL.md body + metadata for one skill. |
152
160
  | `skills__invoke` | Execute a skill via its assigned strategy, wrapped in the decorator chain (Logging → Timeout → Cache). Composite skills (`metadata.skills: [a, b]`) walk nested skills sequentially with DFS cycle detection. |
153
161
  | `skills__configure` | Manage configured folders + manual blacklist. Actions: `add_folder`, `remove_folder`, `list_folders`, `set_blacklist`, `get_blacklist`, `reset`. Persists to the config file and reconciles in-process state without restart. |
@@ -233,15 +241,21 @@ For production use with untrusted skill authors, run SkillForge inside Docker or
233
241
 
234
242
  ## Updating
235
243
 
244
+ Pick the block that matches how you installed.
245
+
236
246
  ```bash
237
- # Plugin install
247
+ # Installed as a Claude Code plugin
238
248
  /plugin update skillforge
239
249
 
240
- # npm install
250
+ # Installed via the install CLI (global npm package)
251
+ npm install -g @lyupro/skillforge-mcp@latest
252
+ # host wiring already points at the global bin — restart the host session
253
+
254
+ # Installed as a bare MCP server (npx)
241
255
  claude mcp remove skillforge
242
256
  claude mcp add skillforge -- npx -y @lyupro/skillforge-mcp@latest
243
257
 
244
- # Local-build install
258
+ # Local-build install (git clone)
245
259
  cd skillforge-mcp
246
260
  git pull
247
261
  pnpm install
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Table rendering for the `folders list` subcommand.
3
+ *
4
+ * Split out of `folders.ts` so the entry module stays under the 400-line
5
+ * file-size gate. Pure formatting — no I/O.
6
+ */
7
+ import type { FolderEntry } from '../config/config-schema.js';
8
+ /** Render the registered folders as a fixed-width text table. */
9
+ export declare function formatFoldersTable(folders: FolderEntry[]): string;
10
+ //# sourceMappingURL=folders-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folders-format.d.ts","sourceRoot":"","sources":["../../src/cli/folders-format.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAkCjE"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Table rendering for the `folders list` subcommand.
3
+ *
4
+ * Split out of `folders.ts` so the entry module stays under the 400-line
5
+ * file-size gate. Pure formatting — no I/O.
6
+ */
7
+ /** Render the registered folders as a fixed-width text table. */
8
+ export function formatFoldersTable(folders) {
9
+ if (folders.length === 0) {
10
+ return 'No folders registered.\n';
11
+ }
12
+ const rows = folders.map((f) => ({
13
+ priority: String(f.priority),
14
+ enabled: f.enabled ? 'yes' : 'no',
15
+ alias: f.alias !== undefined && f.alias.length > 0 ? f.alias : '-',
16
+ tags: f.tags.length > 0 ? f.tags.join(',') : '-',
17
+ path: f.path,
18
+ }));
19
+ const headers = {
20
+ priority: 'PRIORITY',
21
+ enabled: 'ENABLED',
22
+ alias: 'ALIAS',
23
+ tags: 'TAGS',
24
+ path: 'PATH',
25
+ };
26
+ const width = {
27
+ priority: Math.max(headers.priority.length, ...rows.map((r) => r.priority.length)),
28
+ enabled: Math.max(headers.enabled.length, ...rows.map((r) => r.enabled.length)),
29
+ alias: Math.max(headers.alias.length, ...rows.map((r) => r.alias.length)),
30
+ tags: Math.max(headers.tags.length, ...rows.map((r) => r.tags.length)),
31
+ };
32
+ const pad = (text, len) => text.padEnd(len);
33
+ const lines = [
34
+ `${pad(headers.priority, width.priority)} ${pad(headers.enabled, width.enabled)} ${pad(headers.alias, width.alias)} ${pad(headers.tags, width.tags)} ${headers.path}`,
35
+ ];
36
+ for (const r of rows) {
37
+ lines.push(`${pad(r.priority, width.priority)} ${pad(r.enabled, width.enabled)} ${pad(r.alias, width.alias)} ${pad(r.tags, width.tags)} ${r.path}`);
38
+ }
39
+ return `${lines.join('\n')}\n`;
40
+ }
41
+ //# sourceMappingURL=folders-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folders-format.js","sourceRoot":"","sources":["../../src/cli/folders-format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Action handlers for the `folders` subcommand.
3
+ *
4
+ * Each handler takes a `ConfigStore`, the post-action args, and stdout/stderr
5
+ * sinks; it returns a process exit code. Split out of `folders.ts` so the
6
+ * entry module stays under the 400-line file-size gate.
7
+ */
8
+ import type { ConfigStore } from '../config/config-store.js';
9
+ export declare function handleList(store: ConfigStore, rest: string[], stdout: (t: string) => void, stderr: (t: string) => void): Promise<number>;
10
+ export declare function handleAdd(store: ConfigStore, rest: string[], stdout: (t: string) => void, stderr: (t: string) => void, isDirectory: (p: string) => Promise<boolean>): Promise<number>;
11
+ export declare function handleRemove(store: ConfigStore, rest: string[], stdout: (t: string) => void, stderr: (t: string) => void): Promise<number>;
12
+ export declare function handleAlias(store: ConfigStore, rest: string[], stdout: (t: string) => void, stderr: (t: string) => void): Promise<number>;
13
+ export declare function handleEnable(store: ConfigStore, rest: string[], stdout: (t: string) => void, stderr: (t: string) => void): Promise<number>;
14
+ export declare function handleDisable(store: ConfigStore, rest: string[], stdout: (t: string) => void, stderr: (t: string) => void): Promise<number>;
15
+ export declare function handleReset(store: ConfigStore, rest: string[], stdout: (t: string) => void, stderr: (t: string) => void): Promise<number>;
16
+ //# sourceMappingURL=folders-handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folders-handlers.d.ts","sourceRoot":"","sources":["../../src/cli/folders-handlers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAU7D,wBAAsB,UAAU,CAC9B,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC3B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAC1B,OAAO,CAAC,MAAM,CAAC,CAkCjB;AAED,wBAAsB,SAAS,CAC7B,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC3B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC3B,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAC3C,OAAO,CAAC,MAAM,CAAC,CAqDjB;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC3B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAC1B,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,wBAAsB,WAAW,CAC/B,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC3B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAC1B,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED,wBAAsB,YAAY,CAChC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC3B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAC1B,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC3B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAC1B,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,wBAAsB,WAAW,CAC/B,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,EAC3B,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAC1B,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Action handlers for the `folders` subcommand.
3
+ *
4
+ * Each handler takes a `ConfigStore`, the post-action args, and stdout/stderr
5
+ * sinks; it returns a process exit code. Split out of `folders.ts` so the
6
+ * entry module stays under the 400-line file-size gate.
7
+ */
8
+ import { resolve } from 'node:path';
9
+ import { defaultConfig } from '../config/config-schema.js';
10
+ import { detectSkillSourceConflict, formatConflictHint, } from '../detect/skill-source-conflict.js';
11
+ import { formatFoldersTable } from './folders-format.js';
12
+ import { findFolderEntry, isValidAlias, parseAddFlags } from './folders-shared.js';
13
+ export async function handleList(store, rest, stdout, stderr) {
14
+ let asJson = false;
15
+ let tagFilter;
16
+ for (let i = 0; i < rest.length; i += 1) {
17
+ const arg = rest[i];
18
+ if (arg === '--json') {
19
+ asJson = true;
20
+ }
21
+ else if (arg === '--tag') {
22
+ const value = rest[i + 1];
23
+ if (value === undefined) {
24
+ stderr(`skillforge folders list: --tag requires a value\n`);
25
+ return 2;
26
+ }
27
+ tagFilter = value;
28
+ i += 1;
29
+ }
30
+ else {
31
+ stderr(`skillforge folders list: unknown flag: ${arg}\n`);
32
+ return 2;
33
+ }
34
+ }
35
+ const config = await store.load();
36
+ const folders = tagFilter !== undefined
37
+ ? config.folders.filter((f) => f.tags.includes(tagFilter))
38
+ : config.folders;
39
+ if (asJson) {
40
+ stdout(`${JSON.stringify({ folders }, null, 2)}\n`);
41
+ }
42
+ else {
43
+ stdout(formatFoldersTable(folders));
44
+ }
45
+ return 0;
46
+ }
47
+ export async function handleAdd(store, rest, stdout, stderr, isDirectory) {
48
+ const rawPath = rest[0];
49
+ if (rawPath === undefined || rawPath.startsWith('--')) {
50
+ stderr(`skillforge folders add: missing <path>\n`);
51
+ return 2;
52
+ }
53
+ const flags = parseAddFlags(rest.slice(1));
54
+ if (flags === null) {
55
+ stderr(`skillforge folders add: invalid or malformed flag\n`);
56
+ return 2;
57
+ }
58
+ if (flags.alias !== undefined && !isValidAlias(flags.alias)) {
59
+ stderr(`skillforge folders add: invalid --alias "${flags.alias}" — use kebab-case (e.g. my-folder)\n`);
60
+ return 2;
61
+ }
62
+ const absPath = resolve(rawPath);
63
+ if (!(await isDirectory(absPath))) {
64
+ stderr(`skillforge folders add: path does not exist or is not a directory: ${absPath}\n`);
65
+ return 1;
66
+ }
67
+ const config = await store.load();
68
+ const alreadyPresent = config.folders.some((f) => resolve(f.path) === absPath);
69
+ if (alreadyPresent) {
70
+ stdout(`Folder already registered: ${absPath}\n`);
71
+ return 0;
72
+ }
73
+ if (flags.alias !== undefined) {
74
+ const aliasTaken = config.folders.some((f) => f.alias === flags.alias);
75
+ if (aliasTaken) {
76
+ stderr(`skillforge folders add: alias already in use: ${flags.alias}\n`);
77
+ return 2;
78
+ }
79
+ }
80
+ const entry = {
81
+ path: absPath,
82
+ priority: flags.priority ?? 100,
83
+ enabled: !flags.disabled,
84
+ tags: flags.tags ?? [],
85
+ ...(flags.alias !== undefined ? { alias: flags.alias } : {}),
86
+ };
87
+ config.folders.push(entry);
88
+ await store.save(config);
89
+ stdout(`Registered folder: ${absPath}\n`);
90
+ // Informational only: a conflict does not block the add or change the exit code.
91
+ const conflict = detectSkillSourceConflict(absPath);
92
+ if (conflict !== null) {
93
+ stdout(`${formatConflictHint(conflict)}\n`);
94
+ }
95
+ return 0;
96
+ }
97
+ export async function handleRemove(store, rest, stdout, stderr) {
98
+ const token = rest[0];
99
+ if (token === undefined || token.startsWith('--')) {
100
+ stderr(`skillforge folders remove: missing <path>\n`);
101
+ return 2;
102
+ }
103
+ const config = await store.load();
104
+ const entry = findFolderEntry(config.folders, token);
105
+ if (entry === null) {
106
+ stderr(`skillforge folders remove: no registered folder matches: ${token}\n`);
107
+ return 1;
108
+ }
109
+ config.folders = config.folders.filter((f) => f !== entry);
110
+ await store.save(config);
111
+ stdout(`Removed folder: ${entry.path}\n`);
112
+ return 0;
113
+ }
114
+ export async function handleAlias(store, rest, stdout, stderr) {
115
+ const token = rest[0];
116
+ const name = rest[1];
117
+ if (token === undefined || token.startsWith('--') || name === undefined) {
118
+ stderr(`skillforge folders alias: usage: folders alias <path> <name>\n`);
119
+ return 2;
120
+ }
121
+ if (!isValidAlias(name)) {
122
+ stderr(`skillforge folders alias: invalid alias "${name}" — use kebab-case (e.g. my-folder)\n`);
123
+ return 2;
124
+ }
125
+ const config = await store.load();
126
+ const entry = findFolderEntry(config.folders, token);
127
+ if (entry === null) {
128
+ stderr(`skillforge folders alias: no registered folder matches: ${token}\n`);
129
+ return 1;
130
+ }
131
+ const aliasTaken = config.folders.some((f) => f !== entry && f.alias === name);
132
+ if (aliasTaken) {
133
+ stderr(`skillforge folders alias: alias already in use: ${name}\n`);
134
+ return 2;
135
+ }
136
+ entry.alias = name;
137
+ await store.save(config);
138
+ stdout(`Set alias "${name}" for folder: ${entry.path}\n`);
139
+ return 0;
140
+ }
141
+ export async function handleEnable(store, rest, stdout, stderr) {
142
+ const token = rest[0];
143
+ if (token === undefined || token.startsWith('--')) {
144
+ stderr(`skillforge folders enable: missing <path|alias>\n`);
145
+ return 2;
146
+ }
147
+ const config = await store.load();
148
+ const entry = findFolderEntry(config.folders, token);
149
+ if (entry === null) {
150
+ stderr(`skillforge folders enable: no registered folder matches: ${token}\n`);
151
+ return 1;
152
+ }
153
+ entry.enabled = true;
154
+ await store.save(config);
155
+ stdout(`Enabled folder: ${entry.path}\n`);
156
+ return 0;
157
+ }
158
+ export async function handleDisable(store, rest, stdout, stderr) {
159
+ const token = rest[0];
160
+ if (token === undefined || token.startsWith('--')) {
161
+ stderr(`skillforge folders disable: missing <path|alias>\n`);
162
+ return 2;
163
+ }
164
+ const config = await store.load();
165
+ const entry = findFolderEntry(config.folders, token);
166
+ if (entry === null) {
167
+ stderr(`skillforge folders disable: no registered folder matches: ${token}\n`);
168
+ return 1;
169
+ }
170
+ entry.enabled = false;
171
+ await store.save(config);
172
+ stdout(`Disabled folder: ${entry.path}\n`);
173
+ return 0;
174
+ }
175
+ export async function handleReset(store, rest, stdout, stderr) {
176
+ const confirmed = rest.includes('--yes');
177
+ for (const arg of rest) {
178
+ if (arg !== '--yes') {
179
+ stderr(`skillforge folders reset: unknown flag: ${arg}\n`);
180
+ return 2;
181
+ }
182
+ }
183
+ if (!confirmed) {
184
+ const config = await store.load();
185
+ stdout(`Would reset ${config.folders.length} folder(s) to the default (empty) list.\n` +
186
+ `Re-run with --yes to apply. No changes were made.\n`);
187
+ return 0;
188
+ }
189
+ const config = await store.load();
190
+ config.folders = defaultConfig().folders;
191
+ await store.save(config);
192
+ stdout(`Reset folders to the default (empty) list.\n`);
193
+ return 0;
194
+ }
195
+ //# sourceMappingURL=folders-handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folders-handlers.js","sourceRoot":"","sources":["../../src/cli/folders-handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Shared helpers for the `folders` subcommand modules.
3
+ *
4
+ * Holds pure parsing/lookup logic split out of `folders.ts` so the entry
5
+ * module stays small and the handler modules can reuse it.
6
+ */
7
+ import type { FolderEntry } from '../config/config-schema.js';
8
+ /** Kebab-case shape required for a folder alias (lowercase words joined by `-`). */
9
+ export declare const ALIAS_PATTERN: RegExp;
10
+ /** Whether `name` is a valid kebab-case alias token. */
11
+ export declare function isValidAlias(name: string): boolean;
12
+ /** Check whether a path exists and is a directory. Overridable for tests. */
13
+ export declare function defaultIsDirectory(p: string): Promise<boolean>;
14
+ /**
15
+ * Locate a registered folder by `token`, matching an alias first and falling
16
+ * back to a resolved-path comparison. Shared so `remove`/`alias` address the
17
+ * same entry the same way.
18
+ */
19
+ export declare function findFolderEntry(folders: FolderEntry[], token: string): FolderEntry | null;
20
+ /** Parsed result of the flags accepted by `add`. */
21
+ export interface ParsedAddFlags {
22
+ priority?: number;
23
+ tags?: string[];
24
+ disabled: boolean;
25
+ alias?: string;
26
+ }
27
+ /** Parse the flags accepted by `add`. Returns null on a malformed flag. */
28
+ export declare function parseAddFlags(args: string[]): ParsedAddFlags | null;
29
+ //# sourceMappingURL=folders-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folders-shared.d.ts","sourceRoot":"","sources":["../../src/cli/folders-shared.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,oFAAoF;AACpF,eAAO,MAAM,aAAa,QAA6B,CAAC;AAExD,wDAAwD;AACxD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAElD;AAED,6EAA6E;AAC7E,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOpE;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAMzF;AAED,oDAAoD;AACpD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,GAAG,IAAI,CAmCnE"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Shared helpers for the `folders` subcommand modules.
3
+ *
4
+ * Holds pure parsing/lookup logic split out of `folders.ts` so the entry
5
+ * module stays small and the handler modules can reuse it.
6
+ */
7
+ import { resolve } from 'node:path';
8
+ import { stat } from 'node:fs/promises';
9
+ /** Kebab-case shape required for a folder alias (lowercase words joined by `-`). */
10
+ export const ALIAS_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
11
+ /** Whether `name` is a valid kebab-case alias token. */
12
+ export function isValidAlias(name) {
13
+ return ALIAS_PATTERN.test(name);
14
+ }
15
+ /** Check whether a path exists and is a directory. Overridable for tests. */
16
+ export async function defaultIsDirectory(p) {
17
+ try {
18
+ const info = await stat(p);
19
+ return info.isDirectory();
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
25
+ /**
26
+ * Locate a registered folder by `token`, matching an alias first and falling
27
+ * back to a resolved-path comparison. Shared so `remove`/`alias` address the
28
+ * same entry the same way.
29
+ */
30
+ export function findFolderEntry(folders, token) {
31
+ const byAlias = folders.find((f) => f.alias !== undefined && f.alias === token);
32
+ if (byAlias !== undefined)
33
+ return byAlias;
34
+ const absToken = resolve(token);
35
+ const byPath = folders.find((f) => resolve(f.path) === absToken);
36
+ return byPath ?? null;
37
+ }
38
+ /** Parse the flags accepted by `add`. Returns null on a malformed flag. */
39
+ export function parseAddFlags(args) {
40
+ let priority;
41
+ let tags;
42
+ let disabled = false;
43
+ let alias;
44
+ for (let i = 0; i < args.length; i += 1) {
45
+ const arg = args[i];
46
+ if (arg === '--disabled') {
47
+ disabled = true;
48
+ }
49
+ else if (arg === '--priority') {
50
+ const value = args[i + 1];
51
+ if (value === undefined)
52
+ return null;
53
+ const n = Number(value);
54
+ if (!Number.isInteger(n))
55
+ return null;
56
+ priority = n;
57
+ i += 1;
58
+ }
59
+ else if (arg === '--tags') {
60
+ const value = args[i + 1];
61
+ if (value === undefined)
62
+ return null;
63
+ tags = value
64
+ .split(',')
65
+ .map((t) => t.trim())
66
+ .filter((t) => t.length > 0);
67
+ i += 1;
68
+ }
69
+ else if (arg === '--alias') {
70
+ const value = args[i + 1];
71
+ if (value === undefined)
72
+ return null;
73
+ alias = value;
74
+ i += 1;
75
+ }
76
+ else {
77
+ return null;
78
+ }
79
+ }
80
+ return { priority, tags, disabled, alias };
81
+ }
82
+ //# sourceMappingURL=folders-shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folders-shared.js","sourceRoot":"","sources":["../../src/cli/folders-shared.ts"],"names":[],"mappings":"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"}
@@ -10,13 +10,18 @@
10
10
  * Usage:
11
11
  * skillforge folders list [--json] List registered folders
12
12
  * skillforge folders add <path> [flags] Register a folder
13
- * skillforge folders remove <path> Remove a folder entry
13
+ * skillforge folders remove <path|alias> Remove a folder entry
14
+ * skillforge folders alias <path|alias> <name> Set/change a folder alias
14
15
  * skillforge folders reset --yes Reset folders to default
15
16
  *
16
17
  * add flags:
17
- * --priority <n> Folder priority (default 100, lower wins ordering).
18
+ * --priority <n> Folder priority (default 100; higher wins on name collisions).
18
19
  * --tags <a,b,c> Comma-separated tags.
19
20
  * --disabled Register the folder disabled.
21
+ * --alias <name> Short kebab-case alias to address the folder later.
22
+ *
23
+ * This module keeps only the entry point + action dispatch; the handlers,
24
+ * table formatting, and shared parsing helpers live in sibling modules.
20
25
  */
21
26
  export interface FoldersDeps {
22
27
  stdout?: (text: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"folders.d.ts","sourceRoot":"","sources":["../../src/cli/folders.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;GAkBG;AAYH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/C;AAwND;;;;;GAKG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAgCrF"}
1
+ {"version":3,"file":"folders.d.ts","sourceRoot":"","sources":["../../src/cli/folders.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAcH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/C;AA8BD;;;;;GAKG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsCrF"}