fullstack-skills 1.0.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/LICENSE +21 -0
- package/README.md +199 -0
- package/bin/cli.js +206 -0
- package/package.json +49 -0
- package/skills.bundle.json.gz +0 -0
- package/src/adapters/antigravity.js +27 -0
- package/src/adapters/base.js +90 -0
- package/src/adapters/claude.js +26 -0
- package/src/adapters/opencode.js +31 -0
- package/src/adapters/vscode.js +61 -0
- package/src/compactor.js +59 -0
- package/src/flags.js +123 -0
- package/src/index.js +9 -0
- package/src/installer.js +145 -0
- package/src/ui.js +158 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fullstack-skills contributors
|
|
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,199 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ⚡ fullstack-skills
|
|
4
|
+
|
|
5
|
+
**Universal AI developer skills for modern Full-Stack Web Development.**
|
|
6
|
+
Install 20 battle-tested engineering & UI/UX skills into your favorite AI coding environment in seconds.
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/fullstack-skills)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](package.json)
|
|
11
|
+
[](https://www.npmjs.com/package/fullstack-skills)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## ⚡ 10-Second Quick Start
|
|
18
|
+
|
|
19
|
+
You don't even need to install anything permanently. Just run:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx fullstack-skills
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
An interactive menu will guide you through picking your environment and skill bundle:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
================================================================
|
|
29
|
+
⚡ fullstack-skills v1.0.0
|
|
30
|
+
Universal AI Skills for Claude Code, Antigravity, OpenCode & VS Code
|
|
31
|
+
================================================================
|
|
32
|
+
|
|
33
|
+
[1/3] Installation Scope:
|
|
34
|
+
❯ Current Project (./) (Recommended: installs inside this repository)
|
|
35
|
+
Global Machine (~/) (Available in any project on your computer)
|
|
36
|
+
|
|
37
|
+
[2/3] Target AI Environment:
|
|
38
|
+
❯ Claude Code CLI (~/.claude/skills/ or .claude/skills/)
|
|
39
|
+
Google Antigravity (~/.gemini/config/skills/ or .agents/skills/)
|
|
40
|
+
OpenCode (~/.config/opencode/skills/ or .opencode/skills/)
|
|
41
|
+
VS Code (.vscode/skills/ + Copilot / Cline / Roo)
|
|
42
|
+
All Environments (Set up all 4 simultaneously)
|
|
43
|
+
|
|
44
|
+
[3/3] Skill Bundle:
|
|
45
|
+
❯ Full Stack (All 20 Frontend & Backend Skills)
|
|
46
|
+
Frontend UI/UX Only (11 Design, A11y, Layout & Motion Skills)
|
|
47
|
+
Backend Only (9 Architecture, API, Auth & DB Skills)
|
|
48
|
+
|
|
49
|
+
✔ Done! Installed 20 skills to ./.claude/skills/
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 📦 Installation Options
|
|
55
|
+
|
|
56
|
+
Choose the method that fits your workflow:
|
|
57
|
+
|
|
58
|
+
### Option A: Direct Execution (No Install)
|
|
59
|
+
```bash
|
|
60
|
+
npx fullstack-skills
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Option B: Install Globally (Run from any folder)
|
|
64
|
+
```bash
|
|
65
|
+
npm install -g fullstack-skills
|
|
66
|
+
|
|
67
|
+
# Run anywhere:
|
|
68
|
+
fullstack-skills
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Option C: Add to Current Project
|
|
72
|
+
```bash
|
|
73
|
+
npm install fullstack-skills
|
|
74
|
+
|
|
75
|
+
# Run the installer:
|
|
76
|
+
npx fullstack-skills
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 🎯 Supported Environments
|
|
82
|
+
|
|
83
|
+
| Environment | Global Location | Project Location | AI Extension / Agent Support |
|
|
84
|
+
| :--- | :--- | :--- | :--- |
|
|
85
|
+
| **Claude Code CLI** | `~/.claude/skills/` | `./.claude/skills/` | Native Claude Code skills |
|
|
86
|
+
| **VS Code** | `~/.vscode/skills/` | `./.vscode/skills/` | GitHub Copilot, Roo Code, Cline |
|
|
87
|
+
| **Google Antigravity** | `~/.gemini/config/skills/` | `./.agents/skills/` | Native Antigravity / Gemini agent skills |
|
|
88
|
+
| **OpenCode** | `~/.config/opencode/skills/` | `./.opencode/skills/` | OpenCode agent skill system |
|
|
89
|
+
|
|
90
|
+
> **VS Code Note**: When installed to a project, `fullstack-skills` automatically configures `.github/copilot-instructions.md` with an index referencing all installed skills so **GitHub Copilot**, **Cline**, and **Roo Code** instantly discover and enforce the skills!
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 🤖 How the Skills Work in Your AI Assistant
|
|
95
|
+
|
|
96
|
+
Once installed, your AI assistant will automatically use the skills as specialist playbooks:
|
|
97
|
+
|
|
98
|
+
1. **Backend Routing (`use-backend`)**:
|
|
99
|
+
- When you say *"Build an auth API in FastAPI"* or *"Create an Express user router"*, the AI automatically detects your framework and follows `use-backend` → `backend-architecture` → `security` → `api-design` → `backend-qa`.
|
|
100
|
+
2. **Frontend Routing (`using-frontend`)**:
|
|
101
|
+
- When you say *"Design a landing page with hero and pricing"*, the AI routes to `frontend-ui-ux` → `layout-composition` → `typography` → `motion-interaction` → `accessibility`.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🚀 Scriptable CLI Flags (CI / Power Users)
|
|
106
|
+
|
|
107
|
+
Bypass interactive prompts with command-line flags:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Install all 20 skills to Claude Code globally:
|
|
111
|
+
fullstack-skills install -e claude-code -s global -b fullstack -y
|
|
112
|
+
|
|
113
|
+
# Install frontend only to current VS Code workspace:
|
|
114
|
+
fullstack-skills install -e vscode -s project -b frontend -y
|
|
115
|
+
|
|
116
|
+
# Install all skills across all 4 environments:
|
|
117
|
+
npx fullstack-skills install -e all -s project -y
|
|
118
|
+
|
|
119
|
+
# Force overwrite existing skill files:
|
|
120
|
+
fullstack-skills install -e claude-code -f
|
|
121
|
+
|
|
122
|
+
# List all available skills:
|
|
123
|
+
fullstack-skills list
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Flags Reference
|
|
127
|
+
|
|
128
|
+
| Flag | Shorthand | Options | Description |
|
|
129
|
+
| :--- | :--- | :--- | :--- |
|
|
130
|
+
| `--editor` | `-e` | `claude-code`, `antigravity`, `opencode`, `vscode`, `all` | Target environment |
|
|
131
|
+
| `--scope` | `-s` | `project`, `global` | Target directory level |
|
|
132
|
+
| `--bundle` | `-b` | `fullstack`, `frontend`, `backend` | Which skill set to install |
|
|
133
|
+
| `--yes` | `-y` | _boolean_ | Skip confirmation prompt |
|
|
134
|
+
| `--force` | `-f` | _boolean_ | Overwrite existing files |
|
|
135
|
+
| `--list` | | _boolean_ | Print all 20 skills & descriptions |
|
|
136
|
+
| `--help` | `-h` | _boolean_ | Show help and usage examples |
|
|
137
|
+
| `--version` | `-v` | _boolean_ | Print installed version |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 📚 Complete Skills Catalog (20 Skills)
|
|
142
|
+
|
|
143
|
+
### 🎨 Frontend & UI/UX Design (11 Skills)
|
|
144
|
+
| Skill | Description |
|
|
145
|
+
| :--- | :--- |
|
|
146
|
+
| **`using-frontend`** | Master entry point and router for all frontend, component, and UI/UX tasks. |
|
|
147
|
+
| **`frontend-ui-ux`** | Design-first direction: visual hierarchy, whitespace rhythm, color harmony, and micro-interactions. |
|
|
148
|
+
| **`typography`** | Modular type scales, font pairing, line-height, and responsive typography rules. |
|
|
149
|
+
| **`layout-composition`** | Spatial grids, dynamic section rhythm, and modern container layouts. |
|
|
150
|
+
| **`responsive-design`** | Fluid breakpoints, responsive navigation, touch targets, and mobile-first adaptation. |
|
|
151
|
+
| **`shadcn-design-system`** | Radix primitives, Tailwind CSS tokens, and clean variant architectures. |
|
|
152
|
+
| **`motion-interaction`** | Micro-animations, scroll-driven interactions, and smooth CSS transitions. |
|
|
153
|
+
| **`accessibility`** | WCAG 2.1 AA compliance, keyboard navigation, focus rings, and screen reader semantics. |
|
|
154
|
+
| **`design-inspiration`** | Visual benchmark analysis from top modern SaaS, editorial, and ecommerce designs. |
|
|
155
|
+
| **`visual-qa`** | Layout overflow detection, contrast validation, and visual polish audits. |
|
|
156
|
+
| **`bangla-typography`** | Native Bengali/Bangla typography rules, font selections, and line-height calibration. |
|
|
157
|
+
|
|
158
|
+
### ⚙️ Backend & API Engineering (9 Skills)
|
|
159
|
+
| Skill | Description |
|
|
160
|
+
| :--- | :--- |
|
|
161
|
+
| **`use-backend`** | Master router: detects framework (Express, FastAPI, Django, Next.js, Go, Spring) and routes tasks. |
|
|
162
|
+
| **`backend-architecture`** | Clean layering (Controller → Service → Repository), dependency injection, and modular structure. |
|
|
163
|
+
| **`api-design`** | RESTful API design, request/response models, pagination, JSON envelopes, and status codes. |
|
|
164
|
+
| **`security`** | Authentication (JWT/OAuth), authorization (RBAC/ABAC), injection prevention, CORS, and OWASP hardening. |
|
|
165
|
+
| **`data-persistence`** | Database schemas, ORM queries, migrations, atomic transactions, and N+1 query elimination. |
|
|
166
|
+
| **`performance-scalability`** | Redis caching, async task queues, non-blocking I/O, and latency profiling. |
|
|
167
|
+
| **`rate-limiting-resilience`** | Exponential backoff, jitter, circuit breakers, timeouts, and DDoS protections. |
|
|
168
|
+
| **`observability`** | Structured JSON logging, correlation IDs, health checks (`/health/live`, `/health/ready`), and metrics. |
|
|
169
|
+
| **`backend-qa`** | Pre-ship verification checklists, contract tests, and test runner execution. |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 🗜️ Ultra-Lightweight & Gzip-Compressed
|
|
174
|
+
|
|
175
|
+
- **Zero dependencies**: No heavy node_modules to download.
|
|
176
|
+
- **Fast execution**: Tarball is only **~48 KB** compressed.
|
|
177
|
+
- Run `npm run compact` to inspect native `node:zlib` high-density archive generation.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 🤝 Contributing & Local Development
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# Clone the repository
|
|
185
|
+
git clone https://github.com/MU1147-LEGEND/fullstack-skills.git
|
|
186
|
+
cd fullstack-skills
|
|
187
|
+
|
|
188
|
+
# Run tests (100% pass)
|
|
189
|
+
npm test
|
|
190
|
+
|
|
191
|
+
# Try the CLI locally
|
|
192
|
+
node bin/cli.js
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 📄 License
|
|
198
|
+
|
|
199
|
+
MIT © 2026 [MU1147-LEGEND](https://github.com/MU1147-LEGEND)
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import { parseArgs } from '../src/flags.js';
|
|
9
|
+
import { ui, c } from '../src/ui.js';
|
|
10
|
+
import { installSkills, listSkills, getAdapters } from '../src/installer.js';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
|
|
16
|
+
async function getVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const pkgJson = await fs.readFile(path.join(PACKAGE_ROOT, 'package.json'), 'utf8');
|
|
19
|
+
return JSON.parse(pkgJson).version || '1.0.0';
|
|
20
|
+
} catch {
|
|
21
|
+
return '1.0.0';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function printHelp(version) {
|
|
26
|
+
ui.banner();
|
|
27
|
+
console.log(`${c.bold}USAGE:${c.reset}`);
|
|
28
|
+
console.log(` ${c.cyan}npx fullstack-skills${c.reset} Launch interactive installer wizard`);
|
|
29
|
+
console.log(` ${c.cyan}fullstack-skills install [options]${c.reset} Install skills with flags`);
|
|
30
|
+
console.log(` ${c.cyan}fullstack-skills list${c.reset} List all 20 skills & descriptions`);
|
|
31
|
+
console.log(` ${c.cyan}fullstack-skills --help${c.reset} Show this help message`);
|
|
32
|
+
console.log(` ${c.cyan}fullstack-skills --version${c.reset} Show version number\n`);
|
|
33
|
+
|
|
34
|
+
console.log(`${c.bold}OPTIONS:${c.reset}`);
|
|
35
|
+
console.log(` ${c.cyan}-e, --editor <id>${c.reset} Target environment:`);
|
|
36
|
+
console.log(` ${c.dim}claude-code | antigravity | opencode | vscode | all${c.reset}`);
|
|
37
|
+
console.log(` ${c.cyan}-s, --scope <type>${c.reset} Installation scope:`);
|
|
38
|
+
console.log(` ${c.dim}project (./) | global (~/)${c.reset}`);
|
|
39
|
+
console.log(` ${c.cyan}-b, --bundle <bundle>${c.reset} Skill bundle:`);
|
|
40
|
+
console.log(` ${c.dim}fullstack (20) | frontend (11) | backend (9)${c.reset}`);
|
|
41
|
+
console.log(` ${c.cyan}-y, --yes${c.reset} Skip confirmation prompts`);
|
|
42
|
+
console.log(` ${c.cyan}-f, --force${c.reset} Overwrite existing skill files`);
|
|
43
|
+
console.log(` ${c.cyan}-h, --help${c.reset} Show help`);
|
|
44
|
+
console.log(` ${c.cyan}-v, --version${c.reset} Show version\n`);
|
|
45
|
+
|
|
46
|
+
console.log(`${c.bold}EXAMPLES:${c.reset}`);
|
|
47
|
+
console.log(` ${c.dim}# Interactive prompt:${c.reset}`);
|
|
48
|
+
console.log(` npx fullstack-skills\n`);
|
|
49
|
+
console.log(` ${c.dim}# Install full-stack to Claude Code globally:${c.reset}`);
|
|
50
|
+
console.log(` fullstack-skills install --editor claude-code --scope global --bundle fullstack\n`);
|
|
51
|
+
console.log(` ${c.dim}# Install frontend only to current VS Code project:${c.reset}`);
|
|
52
|
+
console.log(` fullstack-skills install --editor vscode --scope project --bundle frontend -y\n`);
|
|
53
|
+
console.log(` ${c.dim}# Install to all environments:${c.reset}`);
|
|
54
|
+
console.log(` npx fullstack-skills install --editor all --scope project --yes\n`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function handleList() {
|
|
58
|
+
ui.banner();
|
|
59
|
+
const skills = await listSkills();
|
|
60
|
+
|
|
61
|
+
console.log(`${c.bold}${c.cyan}FRONTEND & UI/UX SKILLS (11):${c.reset}`);
|
|
62
|
+
const frontend = skills.filter((s) => s.category === 'frontend');
|
|
63
|
+
for (const s of frontend) {
|
|
64
|
+
console.log(` ${c.green}✔ ${c.bold}${s.name.padEnd(24)}${c.reset} ${c.dim}${s.description}${c.reset}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
console.log(`\n${c.bold}${c.cyan}BACKEND & API ENGINEERING SKILLS (9):${c.reset}`);
|
|
68
|
+
const backend = skills.filter((s) => s.category === 'backend');
|
|
69
|
+
for (const s of backend) {
|
|
70
|
+
console.log(` ${c.green}✔ ${c.bold}${s.name.padEnd(24)}${c.reset} ${c.dim}${s.description}${c.reset}`);
|
|
71
|
+
}
|
|
72
|
+
console.log(`\nTotal: ${c.bold}20 skills${c.reset} ready to install.\n`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function main() {
|
|
76
|
+
process.on('SIGINT', () => {
|
|
77
|
+
console.log(`\n${c.yellow}Operation cancelled by user.${c.reset}`);
|
|
78
|
+
process.exit(130);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const version = await getVersion();
|
|
82
|
+
let args;
|
|
83
|
+
try {
|
|
84
|
+
args = parseArgs(process.argv.slice(2));
|
|
85
|
+
} catch (err) {
|
|
86
|
+
ui.error(err.message);
|
|
87
|
+
console.log(`Run ${c.cyan}fullstack-skills --help${c.reset} for usage details.`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (args.help) {
|
|
92
|
+
printHelp(version);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (args.version) {
|
|
97
|
+
console.log(`fullstack-skills v${version}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (args.list) {
|
|
102
|
+
await handleList();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
ui.banner();
|
|
107
|
+
|
|
108
|
+
let { editor, scope, bundle, yes, force } = args;
|
|
109
|
+
|
|
110
|
+
// Interactive Flow if options are missing
|
|
111
|
+
if (!editor || !scope || !bundle) {
|
|
112
|
+
// 1. Detect Scope
|
|
113
|
+
if (!scope) {
|
|
114
|
+
let isProject = false;
|
|
115
|
+
try {
|
|
116
|
+
await fs.access(path.join(process.cwd(), 'package.json'));
|
|
117
|
+
isProject = true;
|
|
118
|
+
} catch {
|
|
119
|
+
try {
|
|
120
|
+
await fs.access(path.join(process.cwd(), '.git'));
|
|
121
|
+
isProject = true;
|
|
122
|
+
} catch {
|
|
123
|
+
// not in project root
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
ui.step(1, 3, 'Installation Scope');
|
|
128
|
+
scope = await ui.select('Where would you like to install the skills?', [
|
|
129
|
+
{
|
|
130
|
+
label: 'Current Project',
|
|
131
|
+
value: 'project',
|
|
132
|
+
desc: isProject ? 'Recommended: Detected local repository' : 'Local folder (.claude / .agents / .vscode)'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: 'Global Machine',
|
|
136
|
+
value: 'global',
|
|
137
|
+
desc: 'Available everywhere on this machine'
|
|
138
|
+
}
|
|
139
|
+
], isProject ? 0 : 1);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. Select Target Environment
|
|
143
|
+
if (!editor) {
|
|
144
|
+
ui.step(2, 3, 'Target AI Environment');
|
|
145
|
+
editor = await ui.select('Select the environment you want to configure:', [
|
|
146
|
+
{ label: 'Claude Code CLI', value: 'claude-code', desc: '~/.claude/skills/ or .claude/skills/' },
|
|
147
|
+
{ label: 'Google Antigravity', value: 'antigravity', desc: '~/.gemini/config/skills/ or .agents/skills/' },
|
|
148
|
+
{ label: 'OpenCode', value: 'opencode', desc: '~/.config/opencode/skills/ or .opencode/skills/' },
|
|
149
|
+
{ label: 'VS Code', value: 'vscode', desc: '.vscode/skills/ with Copilot / Cline / Roo support' },
|
|
150
|
+
{ label: 'All Environments', value: 'all', desc: 'Install and configure all 4 environments simultaneously' }
|
|
151
|
+
]);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 3. Select Skill Bundle
|
|
155
|
+
if (!bundle) {
|
|
156
|
+
ui.step(3, 3, 'Skill Bundle');
|
|
157
|
+
bundle = await ui.select('Select which skills you want to install:', [
|
|
158
|
+
{ label: 'Full Stack', value: 'fullstack', desc: 'Complete suite: All 20 Frontend & Backend Skills' },
|
|
159
|
+
{ label: 'Frontend UI/UX Only', value: 'frontend', desc: '11 design, accessibility, and UI skills' },
|
|
160
|
+
{ label: 'Backend Engineering Only', value: 'backend', desc: '9 architecture, API, security, and persistence skills' }
|
|
161
|
+
]);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Confirmation if not -y
|
|
165
|
+
if (!yes) {
|
|
166
|
+
console.log(`\n${c.bold}Configuration Summary:${c.reset}`);
|
|
167
|
+
console.log(` • Environment: ${c.cyan}${editor}${c.reset}`);
|
|
168
|
+
console.log(` • Scope: ${c.cyan}${scope}${c.reset}`);
|
|
169
|
+
console.log(` • Bundle: ${c.cyan}${bundle}${c.reset}`);
|
|
170
|
+
|
|
171
|
+
const proceed = await ui.confirm('\nProceed with installation?', true);
|
|
172
|
+
if (!proceed) {
|
|
173
|
+
console.log(`${c.yellow}Installation cancelled.${c.reset}`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log(`\n${c.cyan}Installing skills...${c.reset}\n`);
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const summary = await installSkills({
|
|
183
|
+
editor,
|
|
184
|
+
scope,
|
|
185
|
+
bundle,
|
|
186
|
+
force
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
for (const res of summary.results) {
|
|
190
|
+
console.log(`${c.green}${c.bold}✔ ${res.adapterName}${c.reset}`);
|
|
191
|
+
console.log(` ${c.dim}Path:${c.reset} ${res.destination}`);
|
|
192
|
+
console.log(` ${c.dim}Count:${c.reset} ${c.bold}${res.installedCount}${c.reset} skills installed`);
|
|
193
|
+
if (res.postNotes) {
|
|
194
|
+
console.log(` ${c.cyan}Tip:${c.reset} ${res.postNotes}`);
|
|
195
|
+
}
|
|
196
|
+
console.log('');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(`${c.green}${c.bold}🎉 Done! Successfully installed ${summary.selectedSkillsCount} skills.${c.reset}\n`);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
ui.error(err.message);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fullstack-skills",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Universal CLI installer for 20 premium Frontend & Backend AI skills across Claude Code, Antigravity, OpenCode, and VS Code",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"fullstack-skills": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src",
|
|
13
|
+
"skills.bundle.json.gz",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"ai",
|
|
19
|
+
"agent",
|
|
20
|
+
"skills",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"antigravity",
|
|
23
|
+
"opencode",
|
|
24
|
+
"vscode",
|
|
25
|
+
"copilot",
|
|
26
|
+
"fullstack",
|
|
27
|
+
"frontend",
|
|
28
|
+
"backend"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test test/*.test.js",
|
|
35
|
+
"start": "node bin/cli.js",
|
|
36
|
+
"compact": "node scripts/compact.js",
|
|
37
|
+
"prepack": "node scripts/compact.js"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/MU1147-LEGEND/fullstack-skills.git"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/MU1147-LEGEND/fullstack-skills/issues"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/MU1147-LEGEND/fullstack-skills#readme",
|
|
47
|
+
"author": "MU1147-LEGEND",
|
|
48
|
+
"license": "MIT"
|
|
49
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { BaseAdapter } from './base.js';
|
|
4
|
+
|
|
5
|
+
export class AntigravityAdapter extends BaseAdapter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(
|
|
8
|
+
'antigravity',
|
|
9
|
+
'Google Antigravity',
|
|
10
|
+
'Installs skills to ~/.gemini/config/skills/ or .agents/skills/'
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
resolveDestination({ scope, projectDir, homedir }) {
|
|
15
|
+
if (scope === 'global') {
|
|
16
|
+
return path.join(homedir, '.gemini', 'config', 'skills');
|
|
17
|
+
}
|
|
18
|
+
return path.join(projectDir, '.agents', 'skills');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async postInstall({ scope, destDir }) {
|
|
22
|
+
if (scope === 'global') {
|
|
23
|
+
return `Antigravity will load these skills globally from ~/.gemini/config/skills/`;
|
|
24
|
+
}
|
|
25
|
+
return `Antigravity will load these skills in this workspace from .agents/skills/`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
|
|
5
|
+
export class BaseAdapter {
|
|
6
|
+
constructor(id, name, description) {
|
|
7
|
+
this.id = id;
|
|
8
|
+
this.name = name;
|
|
9
|
+
this.description = description;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
resolveDestination({ scope, projectDir = process.cwd(), homedir = os.homedir() }) {
|
|
13
|
+
throw new Error('resolveDestination must be implemented by adapter');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async ensureDir(dirPath) {
|
|
17
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async copyDir(src, dest, force = false) {
|
|
21
|
+
await this.ensureDir(dest);
|
|
22
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const srcPath = path.join(src, entry.name);
|
|
26
|
+
const destPath = path.join(dest, entry.name);
|
|
27
|
+
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
await this.copyDir(srcPath, destPath, force);
|
|
30
|
+
} else {
|
|
31
|
+
if (!force) {
|
|
32
|
+
try {
|
|
33
|
+
await fs.access(destPath);
|
|
34
|
+
// File exists, skip if not force
|
|
35
|
+
continue;
|
|
36
|
+
} catch {
|
|
37
|
+
// File does not exist, proceed
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
await fs.copyFile(srcPath, destPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async install({ skills, scope, projectDir = process.cwd(), homedir = os.homedir(), force = false }) {
|
|
46
|
+
const destDir = this.resolveDestination({ scope, projectDir, homedir });
|
|
47
|
+
await this.ensureDir(destDir);
|
|
48
|
+
|
|
49
|
+
const installedSkills = [];
|
|
50
|
+
|
|
51
|
+
for (const skill of skills) {
|
|
52
|
+
const targetSkillDir = path.join(destDir, skill.name);
|
|
53
|
+
await this.ensureDir(targetSkillDir);
|
|
54
|
+
|
|
55
|
+
if (skill.files) {
|
|
56
|
+
for (const [filename, fileContent] of Object.entries(skill.files)) {
|
|
57
|
+
const targetFilePath = path.join(targetSkillDir, filename);
|
|
58
|
+
if (!force) {
|
|
59
|
+
try {
|
|
60
|
+
await fs.access(targetFilePath);
|
|
61
|
+
continue;
|
|
62
|
+
} catch {
|
|
63
|
+
// proceed
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
await fs.writeFile(targetFilePath, fileContent, 'utf8');
|
|
67
|
+
}
|
|
68
|
+
} else if (skill.path) {
|
|
69
|
+
await this.copyDir(skill.path, targetSkillDir, force);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
installedSkills.push(skill.name);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const postNotes = await this.postInstall({ destDir, skills, scope, projectDir, homedir, force });
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
adapterId: this.id,
|
|
79
|
+
adapterName: this.name,
|
|
80
|
+
destination: destDir,
|
|
81
|
+
installedCount: installedSkills.length,
|
|
82
|
+
skills: installedSkills,
|
|
83
|
+
postNotes: postNotes || null
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async postInstall(context) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { BaseAdapter } from './base.js';
|
|
3
|
+
|
|
4
|
+
export class ClaudeAdapter extends BaseAdapter {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(
|
|
7
|
+
'claude-code',
|
|
8
|
+
'Claude Code CLI',
|
|
9
|
+
'Installs skills to ~/.claude/skills/ or .claude/skills/ for Claude Code'
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
resolveDestination({ scope, projectDir, homedir }) {
|
|
14
|
+
if (scope === 'global') {
|
|
15
|
+
return path.join(homedir, '.claude', 'skills');
|
|
16
|
+
}
|
|
17
|
+
return path.join(projectDir, '.claude', 'skills');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async postInstall({ scope, destDir }) {
|
|
21
|
+
if (scope === 'global') {
|
|
22
|
+
return `Claude Code will automatically recognize all skills globally from ${destDir}`;
|
|
23
|
+
}
|
|
24
|
+
return `Claude Code will automatically recognize all skills in this repository from ${destDir}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { BaseAdapter } from './base.js';
|
|
4
|
+
|
|
5
|
+
export class OpenCodeAdapter extends BaseAdapter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(
|
|
8
|
+
'opencode',
|
|
9
|
+
'OpenCode',
|
|
10
|
+
'Installs skills to ~/.config/opencode/skills/ or .opencode/skills/'
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
resolveDestination({ scope, projectDir, homedir }) {
|
|
15
|
+
if (scope === 'global') {
|
|
16
|
+
if (process.platform === 'win32') {
|
|
17
|
+
const appData = process.env.APPDATA || path.join(homedir, 'AppData', 'Roaming');
|
|
18
|
+
return path.join(appData, 'opencode', 'skills');
|
|
19
|
+
}
|
|
20
|
+
return path.join(homedir, '.config', 'opencode', 'skills');
|
|
21
|
+
}
|
|
22
|
+
return path.join(projectDir, '.opencode', 'skills');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async postInstall({ scope, destDir }) {
|
|
26
|
+
if (scope === 'global') {
|
|
27
|
+
return `OpenCode will load these skills globally from ${destDir}`;
|
|
28
|
+
}
|
|
29
|
+
return `OpenCode will load these skills in this workspace from ${destDir}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { BaseAdapter } from './base.js';
|
|
4
|
+
|
|
5
|
+
export class VSCodeAdapter extends BaseAdapter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(
|
|
8
|
+
'vscode',
|
|
9
|
+
'VS Code (Copilot / Cline / Roo / Prompts)',
|
|
10
|
+
'Installs skills to .vscode/skills/ with Copilot & AI agent integration'
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
resolveDestination({ scope, projectDir, homedir }) {
|
|
15
|
+
if (scope === 'global') {
|
|
16
|
+
return path.join(homedir, '.vscode', 'skills');
|
|
17
|
+
}
|
|
18
|
+
return path.join(projectDir, '.vscode', 'skills');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async postInstall({ scope, projectDir, skills }) {
|
|
22
|
+
if (scope === 'project') {
|
|
23
|
+
try {
|
|
24
|
+
const githubDir = path.join(projectDir, '.github');
|
|
25
|
+
await this.ensureDir(githubDir);
|
|
26
|
+
|
|
27
|
+
const copilotInstructionsPath = path.join(githubDir, 'copilot-instructions.md');
|
|
28
|
+
let existingContent = '';
|
|
29
|
+
try {
|
|
30
|
+
existingContent = await fs.readFile(copilotInstructionsPath, 'utf8');
|
|
31
|
+
} catch {
|
|
32
|
+
// File does not exist yet
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const marker = '<!-- FULLSTACK-SKILLS-INDEX-START -->';
|
|
36
|
+
const endMarker = '<!-- FULLSTACK-SKILLS-INDEX-END -->';
|
|
37
|
+
|
|
38
|
+
const skillList = skills.map((s) => `- **${s.name}**: \`.vscode/skills/${s.name}/SKILL.md\``).join('\n');
|
|
39
|
+
const section = `${marker}\n## Full-Stack AI Skills Library\n\nThe following engineering and design skills are installed in \`.vscode/skills/\`. Review and follow them when answering relevant engineering tasks:\n\n${skillList}\n${endMarker}`;
|
|
40
|
+
|
|
41
|
+
let newContent = '';
|
|
42
|
+
if (existingContent.includes(marker)) {
|
|
43
|
+
newContent = existingContent.replace(
|
|
44
|
+
new RegExp(`${marker}[\\s\\S]*?${endMarker}`),
|
|
45
|
+
section
|
|
46
|
+
);
|
|
47
|
+
} else {
|
|
48
|
+
newContent = existingContent
|
|
49
|
+
? `${existingContent.trim()}\n\n${section}\n`
|
|
50
|
+
: `${section}\n`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await fs.writeFile(copilotInstructionsPath, newContent, 'utf8');
|
|
54
|
+
return `Configured .vscode/skills/ and updated .github/copilot-instructions.md for Copilot & AI extensions`;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
return `Installed to .vscode/skills/ (Could not update copilot-instructions: ${err.message})`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return `Installed to global ~/.vscode/skills/`;
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/compactor.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import zlib from 'node:zlib';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Native Node.js zlib gzip utilities for compacting skill packages
|
|
7
|
+
*/
|
|
8
|
+
export const compactor = {
|
|
9
|
+
/**
|
|
10
|
+
* Compresses a UTF-8 string or Buffer into a Gzip Buffer
|
|
11
|
+
*/
|
|
12
|
+
compress(data) {
|
|
13
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
|
|
14
|
+
return zlib.gzipSync(buffer, { level: 9 });
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Decompresses a Gzip Buffer into a UTF-8 string
|
|
19
|
+
*/
|
|
20
|
+
decompress(buffer) {
|
|
21
|
+
return zlib.gunzipSync(buffer).toString('utf8');
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Bundles an array of skills into a single gzipped JSON archive
|
|
26
|
+
*/
|
|
27
|
+
async createBundle(skills) {
|
|
28
|
+
const bundleData = {};
|
|
29
|
+
|
|
30
|
+
for (const skill of skills) {
|
|
31
|
+
const skillFiles = {};
|
|
32
|
+
const entries = await fs.readdir(skill.path, { withFileTypes: true });
|
|
33
|
+
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
if (entry.isFile()) {
|
|
36
|
+
const filePath = path.join(skill.path, entry.name);
|
|
37
|
+
skillFiles[entry.name] = await fs.readFile(filePath, 'utf8');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
bundleData[skill.name] = {
|
|
42
|
+
name: skill.name,
|
|
43
|
+
category: skill.category,
|
|
44
|
+
description: skill.description,
|
|
45
|
+
files: skillFiles,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const json = JSON.stringify(bundleData);
|
|
50
|
+
const compressed = this.compress(json);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
compressed,
|
|
54
|
+
rawSizeBytes: Buffer.byteLength(json, 'utf8'),
|
|
55
|
+
compressedSizeBytes: compressed.length,
|
|
56
|
+
ratio: ((1 - compressed.length / Buffer.byteLength(json, 'utf8')) * 100).toFixed(1) + '%'
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
};
|
package/src/flags.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const VALID_EDITORS = new Set(['claude-code', 'claude', 'antigravity', 'opencode', 'vscode', 'all']);
|
|
2
|
+
const VALID_SCOPES = new Set(['project', 'global']);
|
|
3
|
+
const VALID_BUNDLES = new Set(['fullstack', 'frontend', 'backend']);
|
|
4
|
+
|
|
5
|
+
export function parseArgs(argv = []) {
|
|
6
|
+
const result = {
|
|
7
|
+
command: 'interactive',
|
|
8
|
+
editor: null,
|
|
9
|
+
scope: null,
|
|
10
|
+
bundle: null,
|
|
11
|
+
yes: false,
|
|
12
|
+
force: false,
|
|
13
|
+
list: false,
|
|
14
|
+
help: false,
|
|
15
|
+
version: false,
|
|
16
|
+
unknown: []
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const args = [...argv];
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < args.length; i++) {
|
|
22
|
+
const arg = args[i];
|
|
23
|
+
|
|
24
|
+
if (arg === 'install') {
|
|
25
|
+
result.command = 'install';
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (arg === 'list') {
|
|
30
|
+
result.command = 'list';
|
|
31
|
+
result.list = true;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (arg === '--help' || arg === '-h') {
|
|
36
|
+
result.help = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (arg === '--version' || arg === '-v') {
|
|
41
|
+
result.version = true;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (arg === '--list') {
|
|
46
|
+
result.list = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (arg === '--yes' || arg === '-y') {
|
|
51
|
+
result.yes = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (arg === '--force' || arg === '-f') {
|
|
56
|
+
result.force = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (arg.startsWith('--editor=') || arg.startsWith('-e=')) {
|
|
61
|
+
const val = arg.split('=')[1];
|
|
62
|
+
result.editor = val;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (arg === '--editor' || arg === '-e') {
|
|
66
|
+
result.editor = args[++i];
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (arg.startsWith('--scope=') || arg.startsWith('-s=')) {
|
|
71
|
+
const val = arg.split('=')[1];
|
|
72
|
+
result.scope = val;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg === '--scope' || arg === '-s') {
|
|
76
|
+
result.scope = args[++i];
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (arg.startsWith('--bundle=') || arg.startsWith('-b=')) {
|
|
81
|
+
const val = arg.split('=')[1];
|
|
82
|
+
result.bundle = val;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (arg === '--bundle' || arg === '-b') {
|
|
86
|
+
result.bundle = args[++i];
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
result.unknown.push(arg);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Normalization & Validation
|
|
94
|
+
if (result.editor) {
|
|
95
|
+
result.editor = result.editor.toLowerCase();
|
|
96
|
+
if (result.editor === 'claude') result.editor = 'claude-code';
|
|
97
|
+
if (!VALID_EDITORS.has(result.editor)) {
|
|
98
|
+
throw new Error(`Invalid editor: "${result.editor}". Allowed values: claude-code, antigravity, opencode, vscode, all`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (result.scope) {
|
|
103
|
+
result.scope = result.scope.toLowerCase();
|
|
104
|
+
if (!VALID_SCOPES.has(result.scope)) {
|
|
105
|
+
throw new Error(`Invalid scope: "${result.scope}". Allowed values: project, global`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (result.bundle) {
|
|
110
|
+
result.bundle = result.bundle.toLowerCase();
|
|
111
|
+
if (!VALID_BUNDLES.has(result.bundle)) {
|
|
112
|
+
throw new Error(`Invalid bundle: "${result.bundle}". Allowed values: fullstack, frontend, backend`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (result.editor || result.scope || result.bundle) {
|
|
117
|
+
if (result.command === 'interactive') {
|
|
118
|
+
result.command = 'install';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return result;
|
|
123
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { installSkills, listSkills, getAdapters } from './installer.js';
|
|
2
|
+
export { parseArgs } from './flags.js';
|
|
3
|
+
export { ui } from './ui.js';
|
|
4
|
+
export { compactor } from './compactor.js';
|
|
5
|
+
export { BaseAdapter } from './adapters/base.js';
|
|
6
|
+
export { ClaudeAdapter } from './adapters/claude.js';
|
|
7
|
+
export { AntigravityAdapter } from './adapters/antigravity.js';
|
|
8
|
+
export { OpenCodeAdapter } from './adapters/opencode.js';
|
|
9
|
+
export { VSCodeAdapter } from './adapters/vscode.js';
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { ClaudeAdapter } from './adapters/claude.js';
|
|
6
|
+
import { AntigravityAdapter } from './adapters/antigravity.js';
|
|
7
|
+
import { OpenCodeAdapter } from './adapters/opencode.js';
|
|
8
|
+
import { VSCodeAdapter } from './adapters/vscode.js';
|
|
9
|
+
|
|
10
|
+
import { compactor } from './compactor.js';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
const SKILLS_DIR = path.join(PACKAGE_ROOT, 'skills');
|
|
16
|
+
const BUNDLE_PATH = path.join(PACKAGE_ROOT, 'skills.bundle.json.gz');
|
|
17
|
+
|
|
18
|
+
export function getAdapters() {
|
|
19
|
+
return {
|
|
20
|
+
'claude-code': new ClaudeAdapter(),
|
|
21
|
+
antigravity: new AntigravityAdapter(),
|
|
22
|
+
opencode: new OpenCodeAdapter(),
|
|
23
|
+
vscode: new VSCodeAdapter(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function listSkills(skillsBaseDir = SKILLS_DIR, forceScan = false) {
|
|
28
|
+
if (!forceScan) {
|
|
29
|
+
try {
|
|
30
|
+
const compressed = await fs.readFile(BUNDLE_PATH);
|
|
31
|
+
const jsonStr = compactor.decompress(compressed);
|
|
32
|
+
const bundleData = JSON.parse(jsonStr);
|
|
33
|
+
const list = Object.values(bundleData);
|
|
34
|
+
if (list.length > 0) {
|
|
35
|
+
return list;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// Fallback to directory scan
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const skills = [];
|
|
43
|
+
const categories = ['backend', 'frontend'];
|
|
44
|
+
|
|
45
|
+
for (const cat of categories) {
|
|
46
|
+
const catDir = path.join(skillsBaseDir, cat);
|
|
47
|
+
try {
|
|
48
|
+
const entries = await fs.readdir(catDir, { withFileTypes: true });
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
const skillPath = path.join(catDir, entry.name);
|
|
52
|
+
const skillMdPath = path.join(skillPath, 'SKILL.md');
|
|
53
|
+
|
|
54
|
+
let description = '';
|
|
55
|
+
try {
|
|
56
|
+
const content = await fs.readFile(skillMdPath, 'utf8');
|
|
57
|
+
const match = content.match(/^---\s*[\r\n]+([\s\S]*?)[\r\n]+---/);
|
|
58
|
+
if (match) {
|
|
59
|
+
const yaml = match[1];
|
|
60
|
+
const descMatch = yaml.match(/description:\s*(?:>[-+]?|\|[-+]?)?\s*\n?((?:(?:\s{2,}|\t)[^\n\r]+(?:\r?\n|$))+|[^\n\r]+)/);
|
|
61
|
+
if (descMatch) {
|
|
62
|
+
description = descMatch[1]
|
|
63
|
+
.split(/\r?\n/)
|
|
64
|
+
.map((l) => l.trim())
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.join(' ')
|
|
67
|
+
.replace(/^['"]|['"]$/g, '');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// ignore missing SKILL.md
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
skills.push({
|
|
75
|
+
name: entry.name,
|
|
76
|
+
category: cat,
|
|
77
|
+
path: skillPath,
|
|
78
|
+
description: description || 'Specialized full-stack development skill'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// directory might not exist yet
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return skills;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function installSkills({
|
|
91
|
+
editor = 'all',
|
|
92
|
+
scope = 'project',
|
|
93
|
+
bundle = 'fullstack',
|
|
94
|
+
projectDir = process.cwd(),
|
|
95
|
+
homedir,
|
|
96
|
+
force = false,
|
|
97
|
+
skillsBaseDir = SKILLS_DIR
|
|
98
|
+
}) {
|
|
99
|
+
const allSkills = await listSkills(skillsBaseDir);
|
|
100
|
+
|
|
101
|
+
let selectedSkills = allSkills;
|
|
102
|
+
if (bundle === 'frontend') {
|
|
103
|
+
selectedSkills = allSkills.filter((s) => s.category === 'frontend');
|
|
104
|
+
} else if (bundle === 'backend') {
|
|
105
|
+
selectedSkills = allSkills.filter((s) => s.category === 'backend');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (selectedSkills.length === 0) {
|
|
109
|
+
throw new Error(`No skills found for bundle: ${bundle}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const adaptersMap = getAdapters();
|
|
113
|
+
let targetAdapters = [];
|
|
114
|
+
|
|
115
|
+
if (editor === 'all') {
|
|
116
|
+
targetAdapters = Object.values(adaptersMap);
|
|
117
|
+
} else {
|
|
118
|
+
const adapter = adaptersMap[editor];
|
|
119
|
+
if (!adapter) {
|
|
120
|
+
throw new Error(`Unknown editor target: "${editor}"`);
|
|
121
|
+
}
|
|
122
|
+
targetAdapters = [adapter];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const results = [];
|
|
126
|
+
|
|
127
|
+
for (const adapter of targetAdapters) {
|
|
128
|
+
const res = await adapter.install({
|
|
129
|
+
skills: selectedSkills,
|
|
130
|
+
scope,
|
|
131
|
+
projectDir,
|
|
132
|
+
homedir,
|
|
133
|
+
force,
|
|
134
|
+
});
|
|
135
|
+
results.push(res);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
editor,
|
|
140
|
+
scope,
|
|
141
|
+
bundle,
|
|
142
|
+
selectedSkillsCount: selectedSkills.length,
|
|
143
|
+
results,
|
|
144
|
+
};
|
|
145
|
+
}
|
package/src/ui.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
|
|
4
|
+
// ANSI escape sequences
|
|
5
|
+
export const c = {
|
|
6
|
+
reset: '\x1b[0m',
|
|
7
|
+
bold: '\x1b[1m',
|
|
8
|
+
dim: '\x1b[2m',
|
|
9
|
+
italic: '\x1b[3m',
|
|
10
|
+
underline: '\x1b[4m',
|
|
11
|
+
red: '\x1b[31m',
|
|
12
|
+
green: '\x1b[32m',
|
|
13
|
+
yellow: '\x1b[33m',
|
|
14
|
+
blue: '\x1b[34m',
|
|
15
|
+
magenta: '\x1b[35m',
|
|
16
|
+
cyan: '\x1b[36m',
|
|
17
|
+
white: '\x1b[37m',
|
|
18
|
+
gray: '\x1b[90m',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const ui = {
|
|
22
|
+
banner() {
|
|
23
|
+
console.log(`\n${c.cyan}${c.bold}================================================================${c.reset}`);
|
|
24
|
+
console.log(`${c.cyan}${c.bold} ⚡ fullstack-skills ${c.reset}${c.dim}v1.0.0${c.reset}`);
|
|
25
|
+
console.log(` ${c.white}Universal AI Skills for Claude Code, Antigravity, OpenCode & VS Code${c.reset}`);
|
|
26
|
+
console.log(`${c.cyan}${c.bold}================================================================${c.reset}\n`);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
info(msg) {
|
|
30
|
+
console.log(`${c.cyan}ℹ${c.reset} ${msg}`);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
success(msg) {
|
|
34
|
+
console.log(`${c.green}${c.bold}✔${c.reset} ${msg}`);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
warn(msg) {
|
|
38
|
+
console.log(`${c.yellow}${c.bold}▲${c.reset} ${msg}`);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
error(msg) {
|
|
42
|
+
console.error(`${c.red}${c.bold}✖ Error:${c.reset} ${msg}`);
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
step(current, total, label) {
|
|
46
|
+
console.log(`\n${c.cyan}${c.bold}[${current}/${total}]${c.reset} ${c.bold}${label}${c.reset}`);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async confirm(promptText, defaultVal = true) {
|
|
50
|
+
const rl = readline.createInterface({
|
|
51
|
+
input: process.stdin,
|
|
52
|
+
output: process.stdout,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const suffix = defaultVal ? '[Y/n]' : '[y/N]';
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
rl.question(`${promptText} ${c.dim}${suffix}${c.reset} `, (answer) => {
|
|
58
|
+
rl.close();
|
|
59
|
+
const trimmed = answer.trim().toLowerCase();
|
|
60
|
+
if (trimmed === '') return resolve(defaultVal);
|
|
61
|
+
resolve(trimmed === 'y' || trimmed === 'yes');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async select(title, choices, defaultIndex = 0) {
|
|
67
|
+
// If not a TTY or in non-interactive environment, use simple numeric prompt
|
|
68
|
+
if (!process.stdin.isTTY) {
|
|
69
|
+
return this._fallbackSelect(title, choices, defaultIndex);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
let selected = defaultIndex;
|
|
74
|
+
const stdin = process.stdin;
|
|
75
|
+
const stdout = process.stdout;
|
|
76
|
+
|
|
77
|
+
readline.emitKeypressEvents(stdin);
|
|
78
|
+
if (stdin.isTTY) {
|
|
79
|
+
stdin.setRawMode(true);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function render(firstTime = false) {
|
|
83
|
+
if (!firstTime) {
|
|
84
|
+
stdout.write(`\x1b[${choices.length}A`); // Move cursor up
|
|
85
|
+
}
|
|
86
|
+
for (let i = 0; i < choices.length; i++) {
|
|
87
|
+
const isSelected = i === selected;
|
|
88
|
+
const prefix = isSelected ? `${c.cyan}${c.bold}❯ ` : ' ';
|
|
89
|
+
const label = isSelected ? `${c.cyan}${c.bold}${choices[i].label}${c.reset}` : `${choices[i].label}`;
|
|
90
|
+
const desc = choices[i].desc ? ` ${c.dim}(${choices[i].desc})${c.reset}` : '';
|
|
91
|
+
stdout.write(`\x1b[2K${prefix}${label}${desc}\n`); // Clear line and write
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
render(true);
|
|
96
|
+
|
|
97
|
+
function cleanup() {
|
|
98
|
+
if (stdin.isTTY) {
|
|
99
|
+
stdin.setRawMode(false);
|
|
100
|
+
}
|
|
101
|
+
stdin.removeListener('keypress', onKeypress);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function onKeypress(str, key) {
|
|
105
|
+
if (key.ctrl && key.name === 'c') {
|
|
106
|
+
cleanup();
|
|
107
|
+
process.exit(130);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (key.name === 'up' || (key.name === 'k')) {
|
|
111
|
+
selected = selected > 0 ? selected - 1 : choices.length - 1;
|
|
112
|
+
render(false);
|
|
113
|
+
} else if (key.name === 'down' || (key.name === 'j')) {
|
|
114
|
+
selected = selected < choices.length - 1 ? selected + 1 : 0;
|
|
115
|
+
render(false);
|
|
116
|
+
} else if (key.name === 'return' || key.name === 'enter' || key.name === 'space') {
|
|
117
|
+
cleanup();
|
|
118
|
+
stdout.write('\n');
|
|
119
|
+
resolve(choices[selected].value);
|
|
120
|
+
} else if (/^[1-9]$/.test(str)) {
|
|
121
|
+
const num = parseInt(str, 10) - 1;
|
|
122
|
+
if (num >= 0 && num < choices.length) {
|
|
123
|
+
selected = num;
|
|
124
|
+
render(false);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
stdin.on('keypress', onKeypress);
|
|
130
|
+
stdin.resume();
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
async _fallbackSelect(title, choices, defaultIndex = 0) {
|
|
135
|
+
const rl = readline.createInterface({
|
|
136
|
+
input: process.stdin,
|
|
137
|
+
output: process.stdout,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
console.log(title);
|
|
141
|
+
choices.forEach((choice, idx) => {
|
|
142
|
+
const isDefault = idx === defaultIndex ? ' [default]' : '';
|
|
143
|
+
console.log(` ${idx + 1}. ${choice.label}${choice.desc ? ` (${choice.desc})` : ''}${isDefault}`);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
rl.question(`Select option (1-${choices.length}): `, (answer) => {
|
|
148
|
+
rl.close();
|
|
149
|
+
const num = parseInt(answer.trim(), 10);
|
|
150
|
+
if (num >= 1 && num <= choices.length) {
|
|
151
|
+
resolve(choices[num - 1].value);
|
|
152
|
+
} else {
|
|
153
|
+
resolve(choices[defaultIndex].value);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
};
|