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 +1 -1
- package/bin/cli.js +206 -206
- package/package.json +49 -49
- package/src/adapters/opencode.js +26 -31
- package/src/ui.js +158 -158
package/LICENSE
CHANGED
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.
|
|
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
|
+
}
|
package/src/adapters/opencode.js
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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}
|
|
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
|
+
};
|