fullstack-skills 1.0.0 → 1.0.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 fullstack-skills contributors
3
+ Copyright (c) 2026 Mohammad Ullah
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/bin/cli.js CHANGED
@@ -1,206 +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();
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(version) {
58
+ ui.banner(version);
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(version);
103
+ return;
104
+ }
105
+
106
+ ui.banner(version);
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 CHANGED
@@ -1,49 +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
- }
1
+ {
2
+ "name": "fullstack-skills",
3
+ "version": "1.0.1",
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
+ }
@@ -1,31 +1,26 @@
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
- }
1
+ import path from 'node:path';
2
+ import { BaseAdapter } from './base.js';
3
+
4
+ export class OpenCodeAdapter extends BaseAdapter {
5
+ constructor() {
6
+ super(
7
+ 'opencode',
8
+ 'OpenCode',
9
+ 'Installs skills to ~/.config/opencode/skills/ or .opencode/skills/'
10
+ );
11
+ }
12
+
13
+ resolveDestination({ scope, projectDir, homedir }) {
14
+ if (scope === 'global') {
15
+ return path.join(homedir, '.config', 'opencode', 'skills');
16
+ }
17
+ return path.join(projectDir, '.opencode', 'skills');
18
+ }
19
+
20
+ async postInstall({ scope, destDir }) {
21
+ if (scope === 'global') {
22
+ return `OpenCode will load these skills globally from ${destDir}`;
23
+ }
24
+ return `OpenCode will load these skills in this workspace from ${destDir}`;
25
+ }
26
+ }
package/src/ui.js CHANGED
@@ -1,158 +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
- };
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(version = '1.0.1') {
23
+ console.log(`\n${c.cyan}${c.bold}================================================================${c.reset}`);
24
+ console.log(`${c.cyan}${c.bold} ⚡ fullstack-skills ${c.reset}${c.dim}v${version}${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
+ };