@lyupro/skillforge-mcp 1.1.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 +114 -0
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/dist/cli/install.d.ts +41 -0
- package/dist/cli/install.d.ts.map +1 -0
- package/dist/cli/install.js +223 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/config/config-schema.d.ts +175 -0
- package/dist/config/config-schema.d.ts.map +1 -0
- package/dist/config/config-schema.js +49 -0
- package/dist/config/config-schema.js.map +1 -0
- package/dist/config/config-store.d.ts +24 -0
- package/dist/config/config-store.d.ts.map +1 -0
- package/dist/config/config-store.js +67 -0
- package/dist/config/config-store.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/core/errors.d.ts +9 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +13 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/skill-content-cache.d.ts +18 -0
- package/dist/core/skill-content-cache.d.ts.map +1 -0
- package/dist/core/skill-content-cache.js +56 -0
- package/dist/core/skill-content-cache.js.map +1 -0
- package/dist/core/skill-metadata-cache.d.ts +15 -0
- package/dist/core/skill-metadata-cache.d.ts.map +1 -0
- package/dist/core/skill-metadata-cache.js +31 -0
- package/dist/core/skill-metadata-cache.js.map +1 -0
- package/dist/core/skill-registry.d.ts +12 -0
- package/dist/core/skill-registry.d.ts.map +1 -0
- package/dist/core/skill-registry.js +28 -0
- package/dist/core/skill-registry.js.map +1 -0
- package/dist/core/skill-resolver.d.ts +6 -0
- package/dist/core/skill-resolver.d.ts.map +1 -0
- package/dist/core/skill-resolver.js +32 -0
- package/dist/core/skill-resolver.js.map +1 -0
- package/dist/core/types.d.ts +80 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +10 -0
- package/dist/core/types.js.map +1 -0
- package/dist/decorators/base-decorator.d.ts +10 -0
- package/dist/decorators/base-decorator.d.ts.map +1 -0
- package/dist/decorators/base-decorator.js +13 -0
- package/dist/decorators/base-decorator.js.map +1 -0
- package/dist/decorators/cache-decorator.d.ts +33 -0
- package/dist/decorators/cache-decorator.d.ts.map +1 -0
- package/dist/decorators/cache-decorator.js +89 -0
- package/dist/decorators/cache-decorator.js.map +1 -0
- package/dist/decorators/decorator-chain.d.ts +16 -0
- package/dist/decorators/decorator-chain.d.ts.map +1 -0
- package/dist/decorators/decorator-chain.js +31 -0
- package/dist/decorators/decorator-chain.js.map +1 -0
- package/dist/decorators/index.d.ts +10 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +6 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/logging-decorator.d.ts +15 -0
- package/dist/decorators/logging-decorator.d.ts.map +1 -0
- package/dist/decorators/logging-decorator.js +34 -0
- package/dist/decorators/logging-decorator.js.map +1 -0
- package/dist/decorators/timeout-decorator.d.ts +19 -0
- package/dist/decorators/timeout-decorator.d.ts.map +1 -0
- package/dist/decorators/timeout-decorator.js +59 -0
- package/dist/decorators/timeout-decorator.js.map +1 -0
- package/dist/factory/index.d.ts +2 -0
- package/dist/factory/index.d.ts.map +1 -0
- package/dist/factory/index.js +2 -0
- package/dist/factory/index.js.map +1 -0
- package/dist/factory/strategy-factory.d.ts +10 -0
- package/dist/factory/strategy-factory.d.ts.map +1 -0
- package/dist/factory/strategy-factory.js +37 -0
- package/dist/factory/strategy-factory.js.map +1 -0
- package/dist/handlers/composite-resolver.d.ts +34 -0
- package/dist/handlers/composite-resolver.d.ts.map +1 -0
- package/dist/handlers/composite-resolver.js +94 -0
- package/dist/handlers/composite-resolver.js.map +1 -0
- package/dist/handlers/hybrid-strategy.d.ts +14 -0
- package/dist/handlers/hybrid-strategy.d.ts.map +1 -0
- package/dist/handlers/hybrid-strategy.js +42 -0
- package/dist/handlers/hybrid-strategy.js.map +1 -0
- package/dist/handlers/index.d.ts +3 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +2 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/invocation-strategy.d.ts +17 -0
- package/dist/handlers/invocation-strategy.d.ts.map +1 -0
- package/dist/handlers/invocation-strategy.js +2 -0
- package/dist/handlers/invocation-strategy.js.map +1 -0
- package/dist/handlers/prompt-strategy.d.ts +8 -0
- package/dist/handlers/prompt-strategy.d.ts.map +1 -0
- package/dist/handlers/prompt-strategy.js +31 -0
- package/dist/handlers/prompt-strategy.js.map +1 -0
- package/dist/handlers/script-strategy.d.ts +20 -0
- package/dist/handlers/script-strategy.d.ts.map +1 -0
- package/dist/handlers/script-strategy.js +87 -0
- package/dist/handlers/script-strategy.js.map +1 -0
- package/dist/installers/atomic-write.d.ts +13 -0
- package/dist/installers/atomic-write.d.ts.map +1 -0
- package/dist/installers/atomic-write.js +98 -0
- package/dist/installers/atomic-write.js.map +1 -0
- package/dist/installers/claude-installer.d.ts +26 -0
- package/dist/installers/claude-installer.d.ts.map +1 -0
- package/dist/installers/claude-installer.js +118 -0
- package/dist/installers/claude-installer.js.map +1 -0
- package/dist/installers/codex-installer.d.ts +30 -0
- package/dist/installers/codex-installer.d.ts.map +1 -0
- package/dist/installers/codex-installer.js +125 -0
- package/dist/installers/codex-installer.js.map +1 -0
- package/dist/installers/cursor-installer.d.ts +29 -0
- package/dist/installers/cursor-installer.d.ts.map +1 -0
- package/dist/installers/cursor-installer.js +126 -0
- package/dist/installers/cursor-installer.js.map +1 -0
- package/dist/installers/paths.d.ts +20 -0
- package/dist/installers/paths.d.ts.map +1 -0
- package/dist/installers/paths.js +50 -0
- package/dist/installers/paths.js.map +1 -0
- package/dist/installers/registry.d.ts +8 -0
- package/dist/installers/registry.d.ts.map +1 -0
- package/dist/installers/registry.js +18 -0
- package/dist/installers/registry.js.map +1 -0
- package/dist/installers/types.d.ts +45 -0
- package/dist/installers/types.d.ts.map +1 -0
- package/dist/installers/types.js +9 -0
- package/dist/installers/types.js.map +1 -0
- package/dist/parser/file-scanner.d.ts +10 -0
- package/dist/parser/file-scanner.d.ts.map +1 -0
- package/dist/parser/file-scanner.js +37 -0
- package/dist/parser/file-scanner.js.map +1 -0
- package/dist/parser/format-detector.d.ts +11 -0
- package/dist/parser/format-detector.d.ts.map +1 -0
- package/dist/parser/format-detector.js +13 -0
- package/dist/parser/format-detector.js.map +1 -0
- package/dist/parser/frontmatter-parser.d.ts +14 -0
- package/dist/parser/frontmatter-parser.d.ts.map +1 -0
- package/dist/parser/frontmatter-parser.js +98 -0
- package/dist/parser/frontmatter-parser.js.map +1 -0
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +4 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/scripts-dir-detector.d.ts +7 -0
- package/dist/parser/scripts-dir-detector.d.ts.map +1 -0
- package/dist/parser/scripts-dir-detector.js +17 -0
- package/dist/parser/scripts-dir-detector.js.map +1 -0
- package/dist/security/blacklist-filter.d.ts +32 -0
- package/dist/security/blacklist-filter.d.ts.map +1 -0
- package/dist/security/blacklist-filter.js +35 -0
- package/dist/security/blacklist-filter.js.map +1 -0
- package/dist/security/index.d.ts +5 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +3 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/pattern-scanner.d.ts +27 -0
- package/dist/security/pattern-scanner.d.ts.map +1 -0
- package/dist/security/pattern-scanner.js +46 -0
- package/dist/security/pattern-scanner.js.map +1 -0
- package/dist/security/sandbox-runner.d.ts +48 -0
- package/dist/security/sandbox-runner.d.ts.map +1 -0
- package/dist/security/sandbox-runner.js +131 -0
- package/dist/security/sandbox-runner.js.map +1 -0
- package/dist/server-deps.d.ts +28 -0
- package/dist/server-deps.d.ts.map +1 -0
- package/dist/server-deps.js +2 -0
- package/dist/server-deps.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +174 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/configure.d.ts +22 -0
- package/dist/tools/configure.d.ts.map +1 -0
- package/dist/tools/configure.js +126 -0
- package/dist/tools/configure.js.map +1 -0
- package/dist/tools/get.d.ts +10 -0
- package/dist/tools/get.d.ts.map +1 -0
- package/dist/tools/get.js +20 -0
- package/dist/tools/get.js.map +1 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/invoke.d.ts +12 -0
- package/dist/tools/invoke.d.ts.map +1 -0
- package/dist/tools/invoke.js +36 -0
- package/dist/tools/invoke.js.map +1 -0
- package/dist/tools/list.d.ts +16 -0
- package/dist/tools/list.d.ts.map +1 -0
- package/dist/tools/list.js +33 -0
- package/dist/tools/list.js.map +1 -0
- package/dist/tools/loader.d.ts +30 -0
- package/dist/tools/loader.d.ts.map +1 -0
- package/dist/tools/loader.js +92 -0
- package/dist/tools/loader.js.map +1 -0
- package/dist/tools/reload.d.ts +22 -0
- package/dist/tools/reload.d.ts.map +1 -0
- package/dist/tools/reload.js +39 -0
- package/dist/tools/reload.js.map +1 -0
- package/dist/watcher/chokidar-types.d.ts +20 -0
- package/dist/watcher/chokidar-types.d.ts.map +1 -0
- package/dist/watcher/chokidar-types.js +2 -0
- package/dist/watcher/chokidar-types.js.map +1 -0
- package/dist/watcher/folder-watcher.d.ts +27 -0
- package/dist/watcher/folder-watcher.d.ts.map +1 -0
- package/dist/watcher/folder-watcher.js +123 -0
- package/dist/watcher/folder-watcher.js.map +1 -0
- package/dist/watcher/index.d.ts +4 -0
- package/dist/watcher/index.d.ts.map +1 -0
- package/dist/watcher/index.js +2 -0
- package/dist/watcher/index.js.map +1 -0
- package/manifest.json +96 -0
- package/package.json +78 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## [1.1.0] — 2026-05-14
|
|
6
|
+
|
|
7
|
+
One-command install across Claude Code, Codex CLI, and Cursor.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `skillforge install` CLI — wires SkillForge MCP into host tools by editing each host's config file directly.
|
|
12
|
+
- Three installers — Claude Code (`~/.claude.json`), Codex CLI (`~/.codex/config.toml`), Cursor (OS-specific `settings.json`).
|
|
13
|
+
- Flags — `--claude`, `--codex`, `--cursor`, `--all`, `--dry-run`, `--uninstall`, `--force`, `--entry npx|local`, `--binary-path <path>`.
|
|
14
|
+
- Atomic-write helper with `.backup` snapshot — failed write never leaves the host config in a broken state.
|
|
15
|
+
- OS-specific path detection for Cursor (Windows `%APPDATA%`, macOS `Library/Application Support`, Linux `~/.config`).
|
|
16
|
+
- Second package bin — `skillforge` → `./dist/cli/install.js` alongside the existing `skillforge-mcp` stdio server entry.
|
|
17
|
+
- New documentation — [docs/INSTALL_CLI.md](./docs/INSTALL_CLI.md) (flag table, examples, host edit shapes, troubleshooting).
|
|
18
|
+
|
|
19
|
+
### Verified
|
|
20
|
+
|
|
21
|
+
- 451 / 451 tests passing + 1 win32-skip (+81 new tests for the install CLI).
|
|
22
|
+
- `pnpm lint` (`tsc --noEmit`) clean.
|
|
23
|
+
- `pnpm build` clean.
|
|
24
|
+
- `pnpm check:size` — all 54 source files ≤ 400 lines.
|
|
25
|
+
|
|
26
|
+
## [1.0.0] — 2026-05-13
|
|
27
|
+
|
|
28
|
+
First public release. Universal Skills MCP server: load Markdown skills from arbitrary folders, lazy-by-design, cross-tool (Claude Code / Codex CLI / Cursor / custom MCP clients).
|
|
29
|
+
|
|
30
|
+
### Added — MCP tool surface (5 tools)
|
|
31
|
+
|
|
32
|
+
- `skills__list` — enumerate available skills with metadata-only response (filters: `folder`, `search`, `source`).
|
|
33
|
+
- `skills__get` — retrieve full content (body + metadata) of a named skill.
|
|
34
|
+
- `skills__invoke` — invoke a skill by name, forwarding optional input. Composite skills walk nested skills sequentially with DFS cycle detection. Pipeline: Logging → Timeout → Cache → strategy (Prompt / Script / Hybrid).
|
|
35
|
+
- `skills__configure` — manage configured folders, blacklist, reset (actions: `add_folder`, `remove_folder`, `list_folders`, `set_blacklist`, `get_blacklist`, `reset`). Persists to platform config path.
|
|
36
|
+
- `skills__reload` — force a full rescan of all configured folders. Returns `{ loaded, added, removed, errors }` diff.
|
|
37
|
+
|
|
38
|
+
### Added — Strategies
|
|
39
|
+
|
|
40
|
+
- **PromptStrategy** — default for skills without `scripts` metadata. Returns Markdown body as MCP text content.
|
|
41
|
+
- **ScriptStrategy** — sibling `scripts/` directory + manifest `scripts: [...]` entry. Opt-in via both `config.security.allowScripts = true` and frontmatter `allowScripts: true`. Sandboxed: env whitelist, mkdtemp cwd, AbortSignal kill.
|
|
42
|
+
- **HybridStrategy** — script produces context block prepended to the prompt body. Useful for `git log --since=last-tag` style enrichment.
|
|
43
|
+
- **CompositeResolver** — `skills: [a, b, c]` frontmatter walks nested skills sequentially. DFS cycle detection. Combined output returned.
|
|
44
|
+
|
|
45
|
+
### Added — Decorators
|
|
46
|
+
|
|
47
|
+
- **LoggingDecorator** — stderr trace of invocation lifecycle. Includes strategy name + ms duration.
|
|
48
|
+
- **TimeoutDecorator** — wraps `InvocationContext.signal` to enforce per-invocation timeout. Default 30s, override via `metadata.timeoutMs`.
|
|
49
|
+
- **CacheDecorator** — opt-in via `metadata.cacheable = true`. Returns identical `InvocationResult` instance (frozen `durationMs`) for repeat invocations within TTL.
|
|
50
|
+
|
|
51
|
+
### Added — Universal skill format
|
|
52
|
+
|
|
53
|
+
- Markdown body + YAML frontmatter contract.
|
|
54
|
+
- Canonical camelCase frontmatter + 4 snake_case aliases (`allow_scripts` / `allow_network` / `timeout_ms` / `cache_ttl_ms`).
|
|
55
|
+
- Four format dialects auto-detected: Claude (Anthropic), Codex (TOML-flavored), persona (character-style), custom (anything with frontmatter).
|
|
56
|
+
- Strategy decision tree: explicit `kind` field → metadata signals → default Prompt.
|
|
57
|
+
|
|
58
|
+
### Added — Configuration
|
|
59
|
+
|
|
60
|
+
- Persistent JSON config at the canonical Lyu Pro brand path `~/.lyupro/.skillforge/config.json`, resolved cross-platform via `os.homedir()`. Skills shared content lives under `~/.lyupro/skills/`.
|
|
61
|
+
- Folder cascade with priority — higher priority wins on name conflicts.
|
|
62
|
+
- Blacklist (manual + automatic via `PatternScanner` security audit).
|
|
63
|
+
- Hot reload via `chokidar` `FolderWatcher` — debounced batches, content-cache invalidation.
|
|
64
|
+
- Five worked configs in `examples/configs/`: `default.json`, `team-shared-folder.json`, `team-priority-with-default-fallback.json`, `scripts-enabled.json`, `multifolder-cascade.json`.
|
|
65
|
+
|
|
66
|
+
### Added — Security
|
|
67
|
+
|
|
68
|
+
- `PatternScanner` audit primitive — regex-based detection of dangerous patterns (`subprocess`, `os.system`, `pickle`, `__import__`, etc.).
|
|
69
|
+
- `BlacklistFilter` — manual entries + auto-flagged via audit. Loader gates skills before registry insertion.
|
|
70
|
+
- `SandboxRunner` for ScriptStrategy — env whitelist (`/usr/bin:/bin` POSIX), `mkdtemp` cwd, stdout/stderr cap (1 MB), AbortSignal kill on timeout, exit-code propagation.
|
|
71
|
+
- `Composite cycle detection` — DFS visited-set throws `CompositeCycleError` on loops.
|
|
72
|
+
|
|
73
|
+
### Added — Documentation
|
|
74
|
+
|
|
75
|
+
- `README.md` v1.0 — Pain/solution matrix, 60-second Quick Start, badges, status banner.
|
|
76
|
+
- `docs/INSTALL.md` — 4 wiring tracks (Claude Code / Codex CLI / Cursor / manual MCP client), folder-config 3 ways, verify checklist, upgrade/uninstall, 8-row troubleshooting matrix.
|
|
77
|
+
- `docs/SKILL_FORMAT.md` — field-level frontmatter contract, 4 dialects, strategy decision tree, 5 worked examples (prompt / script / hybrid / composite / persona), conflict resolution, validation rules.
|
|
78
|
+
- `docs/CONFIGURATION.md` — env vars + persisted JSON layers, annotated schema (live vs reserved fields), 5 `skills__configure` actions, `skills__reload` semantics, 4 worked configs.
|
|
79
|
+
- `docs/ARCHITECTURE.md` — module map (11 directories), 9 design patterns with local references, full `skills__invoke` request flow trace, 3 cache surfaces, 5 extension points with snippets, rationale for anchor decisions.
|
|
80
|
+
- `docs/SECURITY.md` — threat model (8 in-scope + 5 out-of-scope rows), 5-step SandboxRunner contract, defence-in-depth layers, Windows PATH compromise documentation, operator checklist, responsible-disclosure policy.
|
|
81
|
+
- `docs/INTEGRATION/` — per-tool wiring: claude-code.md, codex.md, cursor.md, custom-llm-tools.md.
|
|
82
|
+
|
|
83
|
+
### Added — Sample skills
|
|
84
|
+
|
|
85
|
+
10 production-quality skills in `skills/`:
|
|
86
|
+
|
|
87
|
+
- **6 prompt** — `apple-hig-check`, `refactor-suggester`, `commit-message-writer`, `prompt-optimizer`, `idea-onepager`, `obsidian-resume`.
|
|
88
|
+
- **2 script** — `dependency-checker` (npm audit summariser, `scripts/main.sh`), `markdown-linter` (stdlib-only Python, `scripts/main.py`).
|
|
89
|
+
- **2 hybrid** — `changelog-generator` (git log since last semver tag), `git-blame-summary` (blame + recent commits enriched prompt).
|
|
90
|
+
|
|
91
|
+
All 10 verified through real parse pipeline + `StrategyFactory.create()` correct-kind assertion.
|
|
92
|
+
|
|
93
|
+
### Added — Marketplace artifacts
|
|
94
|
+
|
|
95
|
+
- `manifest.json` (upstream MCP schema) — 5 tools, env vars, doc pointers, verification commands.
|
|
96
|
+
- `plugin.json` (Claude Code marketplace schema) — install command, exposedTools, honest permissions block (subprocess opt-in, network none, filesystem write = platform config + tmpdir sandbox).
|
|
97
|
+
|
|
98
|
+
### Fixed — Real-frontmatter promotion (commit `a38e2c4`)
|
|
99
|
+
|
|
100
|
+
- `FrontmatterParser.CONSUMED_KEYS` now promotes `scripts`, `cacheable`, `cache_ttl_ms`, `cacheTtlMs` from real frontmatter. Earlier these fields fell into `extra` because `CONSUMED_KEYS` whitelist was opt-in; `ScriptStrategy.canHandle()` always returned false in the real subprocess pipeline.
|
|
101
|
+
- Defensive extraction: array-of-strings for `scripts`, boolean literal for `cacheable`, finite positive for `cacheTtlMs`.
|
|
102
|
+
- +6 unit tests (promotion, snake_case alias, defensive drops, extra-isolation).
|
|
103
|
+
- +2 integration tests verifying `script-promo` skill triggers `ScriptStrategy` and `cached-prompt` skill activates `CacheDecorator` (second invoke returns identical instance with frozen `durationMs`).
|
|
104
|
+
|
|
105
|
+
### Verified
|
|
106
|
+
|
|
107
|
+
- **370 / 370** tests passing + 1 win32-skip.
|
|
108
|
+
- **46** source files all ≤ 400 lines (`pnpm check:size`).
|
|
109
|
+
- **`pnpm lint`** (`tsc --noEmit`) clean.
|
|
110
|
+
- **`pnpm build`** clean.
|
|
111
|
+
- **`pnpm smoke`** end-to-end via subprocess `dist/server.js` — LoggingDecorator trace visible.
|
|
112
|
+
|
|
113
|
+
[1.1.0]: https://github.com/lyupro/skillforge-mcp/releases/tag/v1.1.0
|
|
114
|
+
[1.0.0]: https://github.com/lyupro/skillforge-mcp/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lyupro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# SkillForge MCP
|
|
2
|
+
|
|
3
|
+
> Universal Skills MCP server — load Markdown skills from arbitrary folders, lazy-by-design, cross-tool.
|
|
4
|
+
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](https://modelcontextprotocol.io)
|
|
8
|
+
|
|
9
|
+
**v1.1.0** — 5 MCP tools, one-command install across Claude Code / Codex CLI / Cursor, 451 tests, 10 sample skills, modular architecture (all source files ≤ 400 lines).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What it is
|
|
14
|
+
|
|
15
|
+
A standalone [Model Context Protocol](https://modelcontextprotocol.io) server that exposes Markdown-defined **skills** (prompts, templates, scripts) to any MCP-capable LLM tool — Claude Code, OpenAI Codex CLI, Cursor, or custom clients via `@modelcontextprotocol/sdk`.
|
|
16
|
+
|
|
17
|
+
One skill folder. One config file. Any tool can ask for any skill on demand.
|
|
18
|
+
|
|
19
|
+
## Why it exists
|
|
20
|
+
|
|
21
|
+
| Pain | Today | With SkillForge |
|
|
22
|
+
|------|-------|-----------------|
|
|
23
|
+
| Auto-loading 122+ skills per session burns ~4880 tokens on init | Every Claude Code session pays the toll, most skills are never used | Lazy MCP discovery — pay only for `skills__get` / `skills__invoke` calls actually made |
|
|
24
|
+
| Hardcoded paths (`~/.claude/plugins/cache/...`, `~/.codex/skills/`) | One folder, one tool, hardcoded | Multi-folder, per-project, priority-ordered, env override |
|
|
25
|
+
| No cross-tool format | Each tool ships its own skill layout | Universal frontmatter parser auto-detects Claude / Codex / persona / custom dialects |
|
|
26
|
+
| Skill execution = "just inline body in prompt" | No scripts, no caching, no timeouts, no composition | Strategy pattern (prompt / script / hybrid) + decorator chain (logging → timeout → cache) + composite skills with cycle detection |
|
|
27
|
+
|
|
28
|
+
## One-command install (v1.1)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx @lyupro/skillforge-mcp install --all
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Auto-detects Claude Code, Codex CLI, and Cursor on your machine and wires SkillForge into each. Supports `--dry-run`, `--uninstall`, and `--force`. Full reference: [docs/INSTALL_CLI.md](./docs/INSTALL_CLI.md).
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
### Option 1 — Claude Code marketplace (recommended)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
/plugin marketplace add lyupro/llm-plugins-marketplace
|
|
42
|
+
/plugin install skillforge-mcp@lyupro-llm-plugins
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Restart your Claude Code session. The five tools (`skills__list`, `skills__get`, `skills__invoke`, `skills__configure`, `skills__reload`) appear in the tool list.
|
|
46
|
+
|
|
47
|
+
### Option 2 — npm
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
claude mcp add skillforge -- npx -y @lyupro/skillforge-mcp
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Works for any MCP host that can spawn a stdio command (Claude Code, Codex CLI, Cursor).
|
|
54
|
+
|
|
55
|
+
### Option 3 — local build
|
|
56
|
+
|
|
57
|
+
See [Contributing](#contributing).
|
|
58
|
+
|
|
59
|
+
After install, point SkillForge at your skill folder:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
> use skills__configure with action="add_folder", folder="/abs/path/to/your/skills"
|
|
63
|
+
> use skills__list
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
See [docs/INSTALL.md](./docs/INSTALL.md) for Codex CLI, Cursor, and manual MCP-client setups.
|
|
67
|
+
|
|
68
|
+
## Verify Installation
|
|
69
|
+
|
|
70
|
+
After the install step, run these three checks from inside any wired LLM tool session:
|
|
71
|
+
|
|
72
|
+
1. `skills__list` — returns an array of skill summaries (possibly empty if no skills folders are configured yet).
|
|
73
|
+
2. `skills__configure` with `action: "list_folders"` — shows the resolved folder list with priorities and `enabled` flags.
|
|
74
|
+
3. `skills__reload` — forces a fresh scan, returns `{loaded, added, removed, errors}` diff.
|
|
75
|
+
|
|
76
|
+
If any call fails with `[skillforge] fatal:` on stderr, the most likely cause is a corrupt config file or a missing folder path — the error message points at the offending file. Delete or fix `~/.lyupro/.skillforge/config.json` and retry.
|
|
77
|
+
|
|
78
|
+
## MCP tool surface
|
|
79
|
+
|
|
80
|
+
| Tool | Purpose |
|
|
81
|
+
|------|---------|
|
|
82
|
+
| `skills__list` | Enumerate available skills (metadata only). Filters: `folder`, `search`, `source`. |
|
|
83
|
+
| `skills__get` | Fetch full SKILL.md body + metadata for one skill. |
|
|
84
|
+
| `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. |
|
|
85
|
+
| `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. |
|
|
86
|
+
| `skills__reload` | Force a full rescan of all configured folders. Returns `{ loaded, added, removed, errors }` diff vs. the previous registry snapshot. |
|
|
87
|
+
|
|
88
|
+
## Configure which folders to scan
|
|
89
|
+
|
|
90
|
+
By default SkillForge scans `~/.claude/plugins/cache/claude-code-skills/`. Override via environment:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Windows (PowerShell)
|
|
94
|
+
$env:SKILLFORGE_FOLDERS = "C:\path\to\skills;C:\other\folder"
|
|
95
|
+
|
|
96
|
+
# macOS / Linux
|
|
97
|
+
export SKILLFORGE_FOLDERS=/home/me/skills:/home/me/team-skills
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Path separator is platform-native (`;` on Windows, `:` elsewhere). Or use `skills__configure` to manage the persisted list — see [docs/CONFIGURATION.md](./docs/CONFIGURATION.md).
|
|
101
|
+
|
|
102
|
+
For shared content across multiple tools, the convention is `~/.lyupro/skills/` (Lyu Pro brand shared content folder).
|
|
103
|
+
|
|
104
|
+
## Persisted config + hot reload
|
|
105
|
+
|
|
106
|
+
- **Config file:** `~/.lyupro/.skillforge/config.json` (resolved cross-platform via `os.homedir()`). Schema-validated via Zod; missing → schema defaults; corrupt JSON / schema → loud error with the file path.
|
|
107
|
+
- **Merge order for folders:** `SKILLFORGE_FOLDERS` env (when set) > persisted `folders[]` with `enabled: true` sorted by `priority` desc > built-in default.
|
|
108
|
+
- **Auto-audit:** `security.autoAudit: true` (default) scans skill bodies on load against `security.auditPatterns` (default: `shell=True`, `eval(`, `exec(`, `base64.b64decode`). Matched skills are excluded and logged to stderr.
|
|
109
|
+
- **Manual blacklist:** `blacklist: string[]` excludes skills by exact name (case-sensitive). Short-circuits before the audit step.
|
|
110
|
+
- **Hot reload:** chokidar watches all configured folders for `.md` add/change/unlink events. Debounced batches invalidate the metadata cache so the next `skills__list` re-scans. Folders mutated via `skills__configure` auto-re-watch via the same diff path.
|
|
111
|
+
|
|
112
|
+
## Skill format
|
|
113
|
+
|
|
114
|
+
Any `.md` file with a YAML frontmatter block defining at least `name:`:
|
|
115
|
+
|
|
116
|
+
```markdown
|
|
117
|
+
---
|
|
118
|
+
name: apple-hig-check
|
|
119
|
+
description: Audit code against Apple Human Interface Guidelines.
|
|
120
|
+
tags: [ios, design]
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
You are an Apple HIG expert. When asked to review code...
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Optional camelCase fields validated by `SkillMetadata`: `strategy` (`prompt` / `script` / `hybrid`), `allowScripts`, `allowNetwork`, `skills` (composite — string[] of nested skill names invoked sequentially), `timeoutMs`, `cacheable`, `cacheTtlMs`, `scripts` (string[], single-entry — `main.py` / `entry.sh` / `app.mjs`). Anything else passes through to `extra`.
|
|
127
|
+
|
|
128
|
+
`FormatDetector` recognizes Claude (`SKILL.md`), Codex (`AGENTS.md`), persona-style (frontmatter has `persona:`), and generic-custom dialects automatically.
|
|
129
|
+
|
|
130
|
+
Full spec: [docs/SKILL_FORMAT.md](./docs/SKILL_FORMAT.md).
|
|
131
|
+
|
|
132
|
+
## Architecture (one-liner)
|
|
133
|
+
|
|
134
|
+
`MCP request → Tool handler → Registry lookup → DecoratorChain.wrap(strategy).invoke(skill, ctx) → Logging → Timeout → Cache → Strategy (Prompt / Script / Hybrid / Composite-resolver) → InvocationResult`
|
|
135
|
+
|
|
136
|
+
Patterns used: Registry, Strategy, Factory, Adapter, Decorator (chain composition), Composite (sequential nested invocation), Observer (chokidar), Singleton.
|
|
137
|
+
|
|
138
|
+
Full design: [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md).
|
|
139
|
+
|
|
140
|
+
## Limitations
|
|
141
|
+
|
|
142
|
+
Read this before enabling scripts.
|
|
143
|
+
|
|
144
|
+
`ScriptStrategy` runs user-provided scripts in a `SandboxRunner` subprocess. **The sandbox is best-effort env/cwd isolation, not an OS-grade jail.** Node `child_process` cannot guarantee network isolation, filesystem confinement outside `cwd`, or CPU/memory limits — those require Docker/firecracker/gVisor (future enhancement).
|
|
145
|
+
|
|
146
|
+
| Property | Enforced? | How |
|
|
147
|
+
|----------|-----------|-----|
|
|
148
|
+
| `env` whitelist | Yes | Subprocess receives only `PATH` (+ explicit `opts.env` like `SKILLFORGE_INPUT`). No `HOME`/`USER`/`SSH_AUTH_SOCK`/`~/.ssh`/`~/.aws` propagation. |
|
|
149
|
+
| Temp `cwd` | Yes | Fresh `fs.mkdtemp(os.tmpdir()/skillforge-XXXX)`, recursive cleanup in `finally`. |
|
|
150
|
+
| Abort signal | Yes | `signal.abort()` → SIGTERM → 5s grace → SIGKILL. |
|
|
151
|
+
| stdout/stderr cap | Yes | Tail-truncate at 1 MB each. |
|
|
152
|
+
| Network egress | No | Subprocess inherits host network stack. `metadata.allowNetwork` is a documentation signal, not a runtime constraint. |
|
|
153
|
+
| Filesystem reads outside `cwd` | No | Subprocess has full OS user permissions. |
|
|
154
|
+
| Filesystem writes outside `cwd` | No | Same. |
|
|
155
|
+
| CPU / memory limits | No | Only the timeout decorator wall-clock-kills runaways. |
|
|
156
|
+
|
|
157
|
+
**Defence in depth** layered on top:
|
|
158
|
+
|
|
159
|
+
1. **Global gate** — `config.security.allowScripts: false` by default.
|
|
160
|
+
2. **Per-skill opt-in** — `metadata.allowScripts: true` required per skill.
|
|
161
|
+
3. **Audit pattern scanner** — `PatternScanner` detects `shell=True`, `eval(`, `exec(`, base64 decode patterns in skill bodies before load.
|
|
162
|
+
4. **Manual blacklist** — explicit skill names in `config.security.blacklist`.
|
|
163
|
+
|
|
164
|
+
For production use with untrusted skill authors, run SkillForge inside Docker or another OS-level sandbox. Full threat model: [docs/SECURITY.md](./docs/SECURITY.md).
|
|
165
|
+
|
|
166
|
+
## Updating
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Marketplace install
|
|
170
|
+
/plugin update skillforge-mcp@lyupro-llm-plugins
|
|
171
|
+
|
|
172
|
+
# npm install
|
|
173
|
+
claude mcp remove skillforge
|
|
174
|
+
claude mcp add skillforge -- npx -y @lyupro/skillforge-mcp@latest
|
|
175
|
+
|
|
176
|
+
# Local-build install
|
|
177
|
+
cd skillforge-mcp
|
|
178
|
+
git pull
|
|
179
|
+
pnpm install
|
|
180
|
+
pnpm build
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The wiring in Claude Code / Codex / Cursor points at the same binary path — restarting the host session picks up the new build. Your persisted config at `~/.lyupro/.skillforge/config.json` survives the upgrade.
|
|
184
|
+
|
|
185
|
+
## Documentation
|
|
186
|
+
|
|
187
|
+
| Doc | Audience |
|
|
188
|
+
|-----|----------|
|
|
189
|
+
| [docs/INSTALL.md](./docs/INSTALL.md) | First-time setup for Claude Code, Codex CLI, Cursor, manual MCP clients |
|
|
190
|
+
| [docs/SKILL_FORMAT.md](./docs/SKILL_FORMAT.md) | Skill authors — full frontmatter spec, dialect detection, examples |
|
|
191
|
+
| [docs/CONFIGURATION.md](./docs/CONFIGURATION.md) | Power users — folder management, blacklist, sandbox config, env overrides |
|
|
192
|
+
| [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) | Contributors — design patterns, module responsibilities, extension points |
|
|
193
|
+
| [docs/SECURITY.md](./docs/SECURITY.md) | Security-conscious operators — threat model, audit checklist, sandbox limits, disclosure policy |
|
|
194
|
+
| [docs/INTEGRATION/](./docs/INTEGRATION/) | Per-tool wiring guides (claude-code / codex / cursor / custom-llm-tools) |
|
|
195
|
+
| [skills/](./skills/) | 10 ready-to-use sample skills (prompt / script / hybrid examples) |
|
|
196
|
+
| [examples/configs/](./examples/configs/) | Sample `config.json` files for common setups |
|
|
197
|
+
|
|
198
|
+
## Contributing
|
|
199
|
+
|
|
200
|
+
Local build (for development or pre-publish testing):
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
git clone https://github.com/lyupro/skillforge-mcp.git
|
|
204
|
+
cd skillforge-mcp
|
|
205
|
+
pnpm install
|
|
206
|
+
pnpm build
|
|
207
|
+
|
|
208
|
+
# Wire into Claude Code using the absolute path
|
|
209
|
+
claude mcp add skillforge -- node /absolute/path/to/skillforge-mcp/dist/server.js
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Development commands:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
pnpm install
|
|
216
|
+
pnpm dev # tsx watch src/server.ts
|
|
217
|
+
pnpm test # vitest run
|
|
218
|
+
pnpm test:coverage # coverage report
|
|
219
|
+
pnpm lint # tsc --noEmit
|
|
220
|
+
pnpm check:size # file-size gate (≤400 lines per file)
|
|
221
|
+
pnpm build # emit dist/
|
|
222
|
+
pnpm smoke # post-build subprocess smoke test
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
MIT — see [LICENSE](./LICENSE).
|
|
228
|
+
|
|
229
|
+
## Author
|
|
230
|
+
|
|
231
|
+
[lyupro](https://lyupro.com) — independent dev. Part of the Lyu Pro tooling portfolio.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SkillForge install CLI (v1.1).
|
|
4
|
+
*
|
|
5
|
+
* Wires SkillForge MCP into Claude Code, Codex CLI, and Cursor by editing
|
|
6
|
+
* each host's config file. Manual argv parsing — no `commander` dep.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* skillforge install --claude
|
|
10
|
+
* skillforge install --codex --cursor
|
|
11
|
+
* skillforge install --all
|
|
12
|
+
* skillforge install --all --dry-run
|
|
13
|
+
* skillforge install --claude --uninstall
|
|
14
|
+
* skillforge install --all --entry local --binary-path /abs/path/to/server.js
|
|
15
|
+
* skillforge install --claude --force
|
|
16
|
+
*/
|
|
17
|
+
import type { Installer } from '../installers/types.js';
|
|
18
|
+
export interface ParsedArgs {
|
|
19
|
+
claude: boolean;
|
|
20
|
+
codex: boolean;
|
|
21
|
+
cursor: boolean;
|
|
22
|
+
all: boolean;
|
|
23
|
+
dryRun: boolean;
|
|
24
|
+
uninstall: boolean;
|
|
25
|
+
force: boolean;
|
|
26
|
+
entry: 'npx' | 'local';
|
|
27
|
+
binaryPath?: string;
|
|
28
|
+
showHelp: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare class UsageError extends Error {
|
|
31
|
+
constructor(message: string);
|
|
32
|
+
}
|
|
33
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
34
|
+
export interface RunDeps {
|
|
35
|
+
installers?: Installer[];
|
|
36
|
+
stdout?: (line: string) => void;
|
|
37
|
+
stderr?: (line: string) => void;
|
|
38
|
+
}
|
|
39
|
+
export declare function runInstall(args: ParsedArgs, deps?: RunDeps): Promise<number>;
|
|
40
|
+
export declare function main(rawArgv: string[]): Promise<number>;
|
|
41
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/cli/install.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EACV,SAAS,EAKV,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AA0BD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA+DpD;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAkCD,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,GAAE,OAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAwDtF;AAED,wBAAsB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAc7D"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SkillForge install CLI (v1.1).
|
|
4
|
+
*
|
|
5
|
+
* Wires SkillForge MCP into Claude Code, Codex CLI, and Cursor by editing
|
|
6
|
+
* each host's config file. Manual argv parsing — no `commander` dep.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* skillforge install --claude
|
|
10
|
+
* skillforge install --codex --cursor
|
|
11
|
+
* skillforge install --all
|
|
12
|
+
* skillforge install --all --dry-run
|
|
13
|
+
* skillforge install --claude --uninstall
|
|
14
|
+
* skillforge install --all --entry local --binary-path /abs/path/to/server.js
|
|
15
|
+
* skillforge install --claude --force
|
|
16
|
+
*/
|
|
17
|
+
import { getAllInstallers, getInstallerByName } from '../installers/registry.js';
|
|
18
|
+
export class UsageError extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'UsageError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const USAGE = `skillforge install — wire SkillForge MCP into Claude Code / Codex CLI / Cursor.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
skillforge install [flags]
|
|
28
|
+
|
|
29
|
+
Targets (at least one required, unless --help):
|
|
30
|
+
--claude Edit ~/.claude.json
|
|
31
|
+
--codex Edit ~/.codex/config.toml
|
|
32
|
+
--cursor Edit Cursor's settings.json (OS-specific path)
|
|
33
|
+
--all Auto-detect installed hosts and install into each
|
|
34
|
+
|
|
35
|
+
Modes:
|
|
36
|
+
--dry-run Print intended edits, do not touch disk
|
|
37
|
+
--uninstall Reverse a previous install
|
|
38
|
+
--force Overwrite an existing SkillForge entry
|
|
39
|
+
|
|
40
|
+
Entry shape:
|
|
41
|
+
--entry npx (default) command=npx args=['-y','@lyupro/skillforge-mcp']
|
|
42
|
+
--entry local command=node args=[<binary-path>]
|
|
43
|
+
--binary-path PATH Override the local-entry binary path (defaults to dist/server.js)
|
|
44
|
+
|
|
45
|
+
--help, -h Show this message
|
|
46
|
+
`;
|
|
47
|
+
export function parseArgs(argv) {
|
|
48
|
+
const out = {
|
|
49
|
+
claude: false,
|
|
50
|
+
codex: false,
|
|
51
|
+
cursor: false,
|
|
52
|
+
all: false,
|
|
53
|
+
dryRun: false,
|
|
54
|
+
uninstall: false,
|
|
55
|
+
force: false,
|
|
56
|
+
entry: 'npx',
|
|
57
|
+
showHelp: false,
|
|
58
|
+
};
|
|
59
|
+
for (let i = 0; i < argv.length; i++) {
|
|
60
|
+
const arg = argv[i];
|
|
61
|
+
switch (arg) {
|
|
62
|
+
case '--claude':
|
|
63
|
+
out.claude = true;
|
|
64
|
+
break;
|
|
65
|
+
case '--codex':
|
|
66
|
+
out.codex = true;
|
|
67
|
+
break;
|
|
68
|
+
case '--cursor':
|
|
69
|
+
out.cursor = true;
|
|
70
|
+
break;
|
|
71
|
+
case '--all':
|
|
72
|
+
out.all = true;
|
|
73
|
+
break;
|
|
74
|
+
case '--dry-run':
|
|
75
|
+
out.dryRun = true;
|
|
76
|
+
break;
|
|
77
|
+
case '--uninstall':
|
|
78
|
+
out.uninstall = true;
|
|
79
|
+
break;
|
|
80
|
+
case '--force':
|
|
81
|
+
out.force = true;
|
|
82
|
+
break;
|
|
83
|
+
case '--help':
|
|
84
|
+
case '-h':
|
|
85
|
+
out.showHelp = true;
|
|
86
|
+
break;
|
|
87
|
+
case '--entry': {
|
|
88
|
+
const next = argv[++i];
|
|
89
|
+
if (next !== 'npx' && next !== 'local') {
|
|
90
|
+
throw new UsageError(`--entry must be 'npx' or 'local' (got: ${String(next)})`);
|
|
91
|
+
}
|
|
92
|
+
out.entry = next;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case '--binary-path': {
|
|
96
|
+
const next = argv[++i];
|
|
97
|
+
if (next === undefined || next.startsWith('--')) {
|
|
98
|
+
throw new UsageError(`--binary-path requires a path argument`);
|
|
99
|
+
}
|
|
100
|
+
out.binaryPath = next;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
default:
|
|
104
|
+
throw new UsageError(`Unknown flag: ${arg}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function chooseInstallers(args, all) {
|
|
110
|
+
if (args.all)
|
|
111
|
+
return all;
|
|
112
|
+
const picked = [];
|
|
113
|
+
if (args.claude)
|
|
114
|
+
picked.push(all.find((i) => i.name === 'claude') ?? getInstallerByName('claude'));
|
|
115
|
+
if (args.codex)
|
|
116
|
+
picked.push(all.find((i) => i.name === 'codex') ?? getInstallerByName('codex'));
|
|
117
|
+
if (args.cursor)
|
|
118
|
+
picked.push(all.find((i) => i.name === 'cursor') ?? getInstallerByName('cursor'));
|
|
119
|
+
return picked;
|
|
120
|
+
}
|
|
121
|
+
function formatInstall(r) {
|
|
122
|
+
const tag = r.status.toUpperCase();
|
|
123
|
+
const msg = r.message !== undefined ? ` (${r.message})` : '';
|
|
124
|
+
return `[${r.tool}] ${tag} ${r.configPath}${msg}`;
|
|
125
|
+
}
|
|
126
|
+
function formatUninstall(r) {
|
|
127
|
+
const tag = r.status.toUpperCase();
|
|
128
|
+
const msg = r.message !== undefined ? ` (${r.message})` : '';
|
|
129
|
+
return `[${r.tool}] ${tag} ${r.configPath}${msg}`;
|
|
130
|
+
}
|
|
131
|
+
function formatPreview(r) {
|
|
132
|
+
const lines = [
|
|
133
|
+
`--- DRY RUN [${r.tool}] action=${r.action} configPath=${r.configPath} willCreate=${r.willCreate}`,
|
|
134
|
+
`--- before:`,
|
|
135
|
+
r.before ?? '(file does not exist)',
|
|
136
|
+
`--- after:`,
|
|
137
|
+
r.after,
|
|
138
|
+
];
|
|
139
|
+
return lines.join('\n');
|
|
140
|
+
}
|
|
141
|
+
export async function runInstall(args, deps = {}) {
|
|
142
|
+
const stdout = deps.stdout ?? ((line) => console.log(line));
|
|
143
|
+
const stderr = deps.stderr ?? ((line) => console.error(line));
|
|
144
|
+
if (args.showHelp) {
|
|
145
|
+
stdout(USAGE);
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
if (!args.all && !args.claude && !args.codex && !args.cursor) {
|
|
149
|
+
stderr(USAGE);
|
|
150
|
+
stderr('\nError: choose at least one of --claude / --codex / --cursor / --all');
|
|
151
|
+
return 2;
|
|
152
|
+
}
|
|
153
|
+
const allInstallers = deps.installers ?? getAllInstallers();
|
|
154
|
+
let installers = chooseInstallers(args, allInstallers);
|
|
155
|
+
if (args.all) {
|
|
156
|
+
const detected = [];
|
|
157
|
+
for (const inst of installers) {
|
|
158
|
+
if (await inst.detect())
|
|
159
|
+
detected.push(inst);
|
|
160
|
+
}
|
|
161
|
+
if (detected.length === 0) {
|
|
162
|
+
stderr('No supported hosts detected (claude / codex / cursor). Pass --claude / --codex / --cursor explicitly to force.');
|
|
163
|
+
return 1;
|
|
164
|
+
}
|
|
165
|
+
installers = detected;
|
|
166
|
+
}
|
|
167
|
+
const opts = {
|
|
168
|
+
entry: args.entry,
|
|
169
|
+
binaryPath: args.binaryPath,
|
|
170
|
+
force: args.force,
|
|
171
|
+
};
|
|
172
|
+
let exit = 0;
|
|
173
|
+
for (const inst of installers) {
|
|
174
|
+
try {
|
|
175
|
+
if (args.dryRun) {
|
|
176
|
+
const preview = await inst.preview({ ...opts, action: args.uninstall ? 'uninstall' : 'install' });
|
|
177
|
+
stdout(formatPreview(preview));
|
|
178
|
+
}
|
|
179
|
+
else if (args.uninstall) {
|
|
180
|
+
const result = await inst.uninstall();
|
|
181
|
+
stdout(formatUninstall(result));
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const result = await inst.install(opts);
|
|
185
|
+
stdout(formatInstall(result));
|
|
186
|
+
if (result.status === 'already-installed')
|
|
187
|
+
exit = exit === 0 ? 0 : exit;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
192
|
+
stderr(`[${inst.name}] error: ${msg}`);
|
|
193
|
+
exit = 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return exit;
|
|
197
|
+
}
|
|
198
|
+
export async function main(rawArgv) {
|
|
199
|
+
try {
|
|
200
|
+
const args = parseArgs(rawArgv);
|
|
201
|
+
return await runInstall(args);
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
if (err instanceof UsageError) {
|
|
205
|
+
console.error(USAGE);
|
|
206
|
+
console.error(`\nError: ${err.message}`);
|
|
207
|
+
return 2;
|
|
208
|
+
}
|
|
209
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
210
|
+
console.error(`skillforge install: fatal: ${msg}`);
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
import { fileURLToPath } from 'node:url';
|
|
215
|
+
const isDirectRun = process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1];
|
|
216
|
+
if (isDirectRun) {
|
|
217
|
+
// First positional arg may be the subcommand "install" — strip it if present
|
|
218
|
+
// so users can invoke as `skillforge install --claude` or `skillforge-install --claude`.
|
|
219
|
+
const argv = process.argv.slice(2);
|
|
220
|
+
const stripped = argv[0] === 'install' ? argv.slice(1) : argv;
|
|
221
|
+
main(stripped).then((code) => process.exit(code));
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/cli/install.ts"],"names":[],"mappings":"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"}
|