cli-five 0.2.1 → 0.2.2
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/package.json +1 -1
- package/src/steps/interview.mjs +8 -0
- package/src/steps/skills.mjs +177 -171
package/package.json
CHANGED
package/src/steps/interview.mjs
CHANGED
|
@@ -6,6 +6,14 @@ import prompts from 'prompts';
|
|
|
6
6
|
// Research via Context7: Next.js, Vite+React, Express, Hono all score
|
|
7
7
|
// 80+ benchmark with high snippet counts and well-indexed docs.
|
|
8
8
|
const STACK_PRESETS = [
|
|
9
|
+
{
|
|
10
|
+
title: 'Next.js + Supabase PWA (TypeScript)',
|
|
11
|
+
value: 'nextjs-supabase',
|
|
12
|
+
description: 'Next.js 15 static export, React 19, Tailwind v4, Supabase, SWR, Vercel hosting, PWA.',
|
|
13
|
+
stack: ['Node + TypeScript'],
|
|
14
|
+
frameworks: ['Next.js', 'React', 'Tailwind CSS', 'Supabase', 'SWR'],
|
|
15
|
+
quickstart: 'npx create-next-app@latest . --yes && npm i @supabase/supabase-js swr date-fns clsx && npm run dev',
|
|
16
|
+
},
|
|
9
17
|
{
|
|
10
18
|
title: 'Next.js (TypeScript)',
|
|
11
19
|
value: 'nextjs',
|
package/src/steps/skills.mjs
CHANGED
|
@@ -6,36 +6,9 @@ import { join, dirname } from 'node:path';
|
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
7
|
import { log } from '../util/log.mjs';
|
|
8
8
|
|
|
9
|
-
// ──
|
|
10
|
-
//
|
|
11
|
-
const
|
|
12
|
-
// Universal
|
|
13
|
-
{ terms: ['*'], name: 'frontend-design', desc: 'Production-grade UI design', type: 'skill', source: 'awesome-copilot' },
|
|
14
|
-
{ terms: ['*'], name: 'conventional-commit', desc: 'Commit message standards', type: 'skill', source: 'awesome-copilot' },
|
|
15
|
-
{ terms: ['*'], name: 'documentation-writer', desc: 'Generate project docs', type: 'skill', source: 'awesome-copilot' },
|
|
16
|
-
{ terms: ['*'], name: 'mermaid-diagrams', desc: 'Create software diagrams', type: 'skill', source: 'awesome-copilot' },
|
|
17
|
-
// JavaScript / TypeScript / React / Next
|
|
18
|
-
{ terms: ['node', 'typescript', 'javascript'], name: 'typescript', desc: 'TypeScript best practices', type: 'instruction', source: 'awesome-copilot' },
|
|
19
|
-
{ terms: ['react', 'next'], name: 'reactjs', desc: 'React patterns & conventions', type: 'instruction', source: 'awesome-copilot' },
|
|
20
|
-
{ terms: ['next'], name: 'nextjs', desc: 'Next.js patterns', type: 'instruction', source: 'awesome-copilot' },
|
|
21
|
-
{ terms: ['node', 'typescript', 'react', 'next'], name: 'playwright-tester', desc: 'E2E testing with Playwright', type: 'skill', source: 'awesome-copilot' },
|
|
22
|
-
// Python
|
|
23
|
-
{ terms: ['python', 'django', 'flask', 'fastapi'], name: 'python', desc: 'Python best practices', type: 'instruction', source: 'awesome-copilot' },
|
|
24
|
-
{ terms: ['django'], name: 'django', desc: 'Django conventions', type: 'instruction', source: 'awesome-copilot' },
|
|
25
|
-
{ terms: ['fastapi'], name: 'fastapi', desc: 'FastAPI patterns', type: 'instruction', source: 'awesome-copilot' },
|
|
26
|
-
// .NET
|
|
27
|
-
{ terms: ['dotnet', '.net', 'csharp', 'c#'], name: 'dotnet', desc: '.NET conventions', type: 'instruction', source: 'awesome-copilot' },
|
|
28
|
-
// Rust
|
|
29
|
-
{ terms: ['rust'], name: 'rust', desc: 'Rust best practices', type: 'instruction', source: 'awesome-copilot' },
|
|
30
|
-
// Go
|
|
31
|
-
{ terms: ['go', 'golang'], name: 'go', desc: 'Go conventions', type: 'instruction', source: 'awesome-copilot' },
|
|
32
|
-
// Mobile
|
|
33
|
-
{ terms: ['swift', 'ios'], name: 'swift', desc: 'Swift/iOS patterns', type: 'instruction', source: 'awesome-copilot' },
|
|
34
|
-
{ terms: ['kotlin', 'android'], name: 'kotlin', desc: 'Kotlin/Android patterns', type: 'instruction', source: 'awesome-copilot' },
|
|
35
|
-
// DevOps / Infra
|
|
36
|
-
{ terms: ['docker', 'kubernetes', 'devops'], name: 'docker', desc: 'Docker best practices', type: 'instruction', source: 'awesome-copilot' },
|
|
37
|
-
{ terms: ['terraform'], name: 'terraform', desc: 'Terraform conventions', type: 'instruction', source: 'awesome-copilot' },
|
|
38
|
-
];
|
|
9
|
+
// ── Known-good skill repos (verified working) ────────────────────────
|
|
10
|
+
// awesome-copilot skills are discovered dynamically via `skills find`.
|
|
11
|
+
const AWESOME_COPILOT_REPO = 'github/awesome-copilot';
|
|
39
12
|
|
|
40
13
|
// Well-known skill repos matched by stack term → repo + suggested skill names (skills.sh)
|
|
41
14
|
const SKILL_CATALOG = [
|
|
@@ -141,8 +114,7 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
141
114
|
if (!env) {
|
|
142
115
|
log.warn('Cannot run skills CLI: no bundled binary, pnpm, or npx found.');
|
|
143
116
|
log.warn('Install Node.js (includes npx) or pnpm, then re-run.');
|
|
144
|
-
|
|
145
|
-
await showAwesomeCopilotOnly(answers);
|
|
117
|
+
printBreadcrumbs();
|
|
146
118
|
return;
|
|
147
119
|
}
|
|
148
120
|
|
|
@@ -165,77 +137,86 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
165
137
|
}
|
|
166
138
|
|
|
167
139
|
log.ok(`Running skills via ${env.label}`);
|
|
168
|
-
log.raw('');
|
|
169
|
-
|
|
170
|
-
// ── Build recommendations from BOTH sources ─────────────────────────
|
|
171
|
-
const awesomeRecs = buildAwesomeRecommendations(answers);
|
|
172
|
-
const skillsShRecs = buildRecommendations(answers);
|
|
173
|
-
|
|
174
|
-
// ── Display hero section ────────────────────────────────────────────
|
|
175
|
-
displayHeroRecommendations(awesomeRecs, skillsShRecs, answers);
|
|
176
140
|
|
|
177
|
-
// ──
|
|
141
|
+
// ── 1. Auto-search all stack terms silently ─────────────────────────
|
|
178
142
|
const terms = suggestSearches(answers);
|
|
143
|
+
const searchResults = [];
|
|
179
144
|
|
|
180
|
-
// Search skills.sh for additional results
|
|
181
|
-
const allFound = [];
|
|
182
145
|
if (terms.length > 0) {
|
|
183
|
-
log.dim(`Suggested searches: ${terms.map((t) => `"${t}"`).join(', ')}`);
|
|
184
146
|
log.raw('');
|
|
185
|
-
|
|
147
|
+
log.info(`Searching skills.sh for: ${terms.map((t) => kleur.bold(t)).join(', ')} ...`);
|
|
186
148
|
for (const term of terms) {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const output = await runSkillsCapture(env, ['find', term], cwd);
|
|
195
|
-
allFound.push(...parseSkillRefs(output));
|
|
196
|
-
}
|
|
149
|
+
const output = await runSkillsSilent(env, ['find', term], cwd);
|
|
150
|
+
searchResults.push(...parseSkillRefs(output));
|
|
151
|
+
}
|
|
152
|
+
if (searchResults.length > 0) {
|
|
153
|
+
log.ok(`Found ${searchResults.length} skill${searchResults.length > 1 ? 's' : ''} from search.`);
|
|
154
|
+
} else {
|
|
155
|
+
log.dim('No additional skills found via search.');
|
|
197
156
|
}
|
|
198
157
|
}
|
|
199
158
|
|
|
200
|
-
//
|
|
159
|
+
// ── 2. Build static catalog recommendations ─────────────────────────
|
|
160
|
+
const catalogRecs = buildRecommendations(answers);
|
|
161
|
+
|
|
162
|
+
// ── 3. Categorize search results by source ──────────────────────────
|
|
163
|
+
const awesomeResults = [];
|
|
164
|
+
const otherResults = [];
|
|
165
|
+
for (const r of searchResults) {
|
|
166
|
+
const parsed = parseRef(r.ref);
|
|
167
|
+
if (parsed && parsed.repo === AWESOME_COPILOT_REPO) {
|
|
168
|
+
awesomeResults.push({ ...r, ...parsed });
|
|
169
|
+
} else {
|
|
170
|
+
otherResults.push(r);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── 4. Display hero section ─────────────────────────────────────────
|
|
175
|
+
displayHeroRecommendations(awesomeResults, catalogRecs, answers);
|
|
176
|
+
|
|
177
|
+
// ── 5. Build unified picker ─────────────────────────────────────────
|
|
201
178
|
const seen = new Set();
|
|
202
179
|
const choices = [];
|
|
203
180
|
|
|
204
|
-
//
|
|
205
|
-
for (const r of
|
|
206
|
-
const key =
|
|
181
|
+
// awesome-copilot results from search (dynamic, real names)
|
|
182
|
+
for (const r of awesomeResults) {
|
|
183
|
+
const key = r.ref;
|
|
207
184
|
if (seen.has(key)) continue;
|
|
208
185
|
seen.add(key);
|
|
209
186
|
choices.push({
|
|
210
|
-
title: `${kleur.cyan('⬡')} ${r.
|
|
211
|
-
value: { source: 'awesome',
|
|
187
|
+
title: `${kleur.cyan('⬡')} ${r.skill} ${kleur.dim(`(${r.installs})`)} ${kleur.cyan('← awesome-copilot')}`,
|
|
188
|
+
value: { source: 'awesome', repo: r.repo, skill: r.skill, ref: r.ref },
|
|
212
189
|
selected: true,
|
|
213
190
|
});
|
|
214
191
|
}
|
|
215
192
|
|
|
216
|
-
//
|
|
217
|
-
for (const r of
|
|
193
|
+
// Static catalog recs (verified repos)
|
|
194
|
+
for (const r of catalogRecs) {
|
|
218
195
|
const ref = `${r.repo}@${r.skill}`;
|
|
219
196
|
if (seen.has(ref)) continue;
|
|
220
197
|
seen.add(ref);
|
|
221
198
|
choices.push({
|
|
222
199
|
title: `${kleur.yellow('◆')} ${r.skill} ${kleur.dim(`(${r.repo})`)} ${kleur.yellow('← skills.sh')}`,
|
|
223
|
-
value: { source: '
|
|
200
|
+
value: { source: 'catalog', ref, repo: r.repo, skill: r.skill },
|
|
224
201
|
selected: true,
|
|
225
202
|
});
|
|
226
203
|
}
|
|
227
204
|
|
|
228
|
-
//
|
|
229
|
-
for (const r of
|
|
205
|
+
// Other search results (non-awesome-copilot)
|
|
206
|
+
for (const r of otherResults) {
|
|
230
207
|
if (seen.has(r.ref)) continue;
|
|
231
208
|
seen.add(r.ref);
|
|
209
|
+
const parsed = parseRef(r.ref);
|
|
232
210
|
choices.push({
|
|
233
|
-
title: `${kleur.yellow('◆')} ${r.ref} ${kleur.dim(
|
|
234
|
-
value: { source: '
|
|
211
|
+
title: `${kleur.yellow('◆')} ${parsed ? parsed.skill : r.ref} ${kleur.dim(`(${parsed ? parsed.repo : ''} — ${r.installs})`)} ${kleur.yellow('← skills.sh')}`,
|
|
212
|
+
value: { source: 'search', ref: r.ref, repo: parsed?.repo, skill: parsed?.skill },
|
|
235
213
|
selected: false,
|
|
236
214
|
});
|
|
237
215
|
}
|
|
238
216
|
|
|
217
|
+
// ── 6. Prompt user to select skills ─────────────────────────────────
|
|
218
|
+
const installResults = [];
|
|
219
|
+
|
|
239
220
|
if (choices.length > 0) {
|
|
240
221
|
log.raw('');
|
|
241
222
|
const { toInstall } = await prompts({
|
|
@@ -247,51 +228,47 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
247
228
|
});
|
|
248
229
|
|
|
249
230
|
if (toInstall && toInstall.length > 0) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
231
|
+
log.raw('');
|
|
232
|
+
|
|
233
|
+
// Group by repo for batch install
|
|
234
|
+
const byRepo = new Map();
|
|
235
|
+
const standalone = [];
|
|
236
|
+
|
|
237
|
+
for (const item of toInstall) {
|
|
238
|
+
if (item.repo && item.skill) {
|
|
239
|
+
if (!byRepo.has(item.repo)) byRepo.set(item.repo, []);
|
|
240
|
+
byRepo.get(item.repo).push(item.skill);
|
|
241
|
+
} else if (item.ref) {
|
|
242
|
+
standalone.push(item.ref);
|
|
262
243
|
}
|
|
263
244
|
}
|
|
264
245
|
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for (const
|
|
271
|
-
|
|
272
|
-
if (!byRepo.has(item.repo)) byRepo.set(item.repo, []);
|
|
273
|
-
byRepo.get(item.repo).push(item.skill);
|
|
274
|
-
} else {
|
|
275
|
-
standalone.push(item.ref);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
for (const [repo, skills] of byRepo) {
|
|
280
|
-
const skillArgs = skills.flatMap((s) => ['--skill', s]);
|
|
281
|
-
log.info(`Installing from ${repo}: ${skills.join(', ')}`);
|
|
282
|
-
await runSkills(env, ['add', repo, ...skillArgs, '-a', 'github-copilot'], cwd);
|
|
246
|
+
// Batch install per repo
|
|
247
|
+
for (const [repo, skills] of byRepo) {
|
|
248
|
+
const skillArgs = skills.flatMap((s) => ['--skill', s]);
|
|
249
|
+
log.info(`Installing from ${kleur.bold(repo)}: ${skills.join(', ')}`);
|
|
250
|
+
const result = await runSkillsTracked(env, ['add', repo, ...skillArgs, '-a', 'github-copilot'], cwd);
|
|
251
|
+
for (const s of skills) {
|
|
252
|
+
installResults.push({ name: `${repo}@${s}`, ...result });
|
|
283
253
|
}
|
|
254
|
+
}
|
|
284
255
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
256
|
+
// Standalone refs
|
|
257
|
+
for (const ref of standalone) {
|
|
258
|
+
log.info(`Installing ${ref}...`);
|
|
259
|
+
const result = await runSkillsTracked(env, ['add', ref, '-a', 'github-copilot'], cwd);
|
|
260
|
+
installResults.push({ name: ref, ...result });
|
|
289
261
|
}
|
|
290
262
|
|
|
291
263
|
await relocateSkills(cwd);
|
|
292
264
|
}
|
|
293
265
|
}
|
|
294
266
|
|
|
267
|
+
// ── 7. Install summary ──────────────────────────────────────────────
|
|
268
|
+
if (installResults.length > 0) {
|
|
269
|
+
printInstallSummary(installResults);
|
|
270
|
+
}
|
|
271
|
+
|
|
295
272
|
// Offer freeform catch-all
|
|
296
273
|
const { freeform } = await prompts({
|
|
297
274
|
type: 'confirm',
|
|
@@ -310,7 +287,7 @@ export async function skillDiscovery({ cwd, answers, args }) {
|
|
|
310
287
|
|
|
311
288
|
// ── Hero display ──────────────────────────────────────────────────────
|
|
312
289
|
|
|
313
|
-
function displayHeroRecommendations(
|
|
290
|
+
function displayHeroRecommendations(awesomeResults, catalogRecs, answers) {
|
|
314
291
|
const stackLabel = [...(answers.stack || []), ...(answers.frameworks || [])].join(', ') || 'general';
|
|
315
292
|
|
|
316
293
|
log.raw('');
|
|
@@ -318,42 +295,50 @@ function displayHeroRecommendations(awesomeRecs, skillsShRecs, answers) {
|
|
|
318
295
|
log.raw(kleur.bold().cyan(' ║') + kleur.bold(' 📦 RECOMMENDED FOR YOUR STACK: ') + kleur.bold().white(stackLabel) + pad('', Math.max(0, 30 - stackLabel.length)) + kleur.bold().cyan(' ║'));
|
|
319
296
|
log.raw(kleur.bold().cyan(' ╚══════════════════════════════════════════════════════════════════╝'));
|
|
320
297
|
|
|
321
|
-
if (
|
|
298
|
+
if (awesomeResults.length > 0) {
|
|
322
299
|
log.raw('');
|
|
323
300
|
log.raw(kleur.cyan(' ⬡ Source: awesome-copilot') + kleur.dim(' (github/awesome-copilot · 30k+ ★)'));
|
|
324
301
|
log.raw(kleur.dim(' ─────────────────────────────────────────────────────────'));
|
|
325
|
-
for (const r of
|
|
326
|
-
|
|
327
|
-
log.raw(` ${kleur.green('✓')} ${pad(r.name, 28)} ${pad(r.desc, 32)} ${typeTag}`);
|
|
302
|
+
for (const r of awesomeResults) {
|
|
303
|
+
log.raw(` ${kleur.green('✓')} ${pad(r.skill, 38)} ${kleur.dim(r.installs)}`);
|
|
328
304
|
}
|
|
329
305
|
}
|
|
330
306
|
|
|
331
|
-
if (
|
|
307
|
+
if (catalogRecs.length > 0) {
|
|
332
308
|
log.raw('');
|
|
333
|
-
log.raw(kleur.yellow(' ◆ Source: skills.sh') + kleur.dim(' (
|
|
309
|
+
log.raw(kleur.yellow(' ◆ Source: skills.sh') + kleur.dim(' (verified repos)'));
|
|
334
310
|
log.raw(kleur.dim(' ─────────────────────────────────────────────────────────'));
|
|
335
|
-
for (const r of
|
|
336
|
-
log.raw(` ${kleur.green('✓')} ${pad(r.skill,
|
|
311
|
+
for (const r of catalogRecs) {
|
|
312
|
+
log.raw(` ${kleur.green('✓')} ${pad(r.skill, 38)} ${kleur.dim(r.repo)}`);
|
|
337
313
|
}
|
|
338
314
|
}
|
|
339
315
|
|
|
340
316
|
log.raw('');
|
|
341
317
|
}
|
|
342
318
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
319
|
+
// ── Install summary ───────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
function printInstallSummary(results) {
|
|
322
|
+
const succeeded = results.filter((r) => r.ok);
|
|
323
|
+
const failed = results.filter((r) => !r.ok);
|
|
347
324
|
|
|
348
325
|
log.raw('');
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
326
|
+
if (succeeded.length > 0) {
|
|
327
|
+
log.ok(`Installed ${succeeded.length} skill${succeeded.length > 1 ? 's' : ''} successfully.`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (failed.length > 0) {
|
|
331
|
+
log.raw('');
|
|
332
|
+
log.warn(`${failed.length} skill${failed.length > 1 ? 's' : ''} failed to install:`);
|
|
333
|
+
for (const f of failed) {
|
|
334
|
+
log.raw(` ${kleur.red('✗')} ${f.name}`);
|
|
335
|
+
if (f.reason) {
|
|
336
|
+
log.raw(` ${kleur.dim(f.reason)}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
log.raw('');
|
|
340
|
+
log.dim('Install failed skills manually: npx skills add <owner/repo> --skill <name>');
|
|
354
341
|
}
|
|
355
|
-
log.raw('');
|
|
356
|
-
printBreadcrumbs();
|
|
357
342
|
}
|
|
358
343
|
|
|
359
344
|
// ── Post-install breadcrumbs ──────────────────────────────────────────
|
|
@@ -364,9 +349,6 @@ function printBreadcrumbs() {
|
|
|
364
349
|
log.raw(kleur.bold().green(' │') + kleur.bold(' 🎯 KEEP DISCOVERING — paste into VS Code / Copilot Chat: ') + kleur.bold().green('│'));
|
|
365
350
|
log.raw(kleur.bold().green(' ├──────────────────────────────────────────────────────────────────┤'));
|
|
366
351
|
log.raw(kleur.bold().green(' │') + ' ' + kleur.bold().green('│'));
|
|
367
|
-
log.raw(kleur.bold().green(' │') + ' Install the awesome-copilot suggestion skill: ' + kleur.bold().green('│'));
|
|
368
|
-
log.raw(kleur.bold().green(' │') + kleur.white(' copilot plugin install awesome-copilot@suggest ') + kleur.bold().green('│'));
|
|
369
|
-
log.raw(kleur.bold().green(' │') + ' ' + kleur.bold().green('│'));
|
|
370
352
|
log.raw(kleur.bold().green(' │') + ' Add awesome-copilot MCP for ongoing search: ' + kleur.bold().green('│'));
|
|
371
353
|
log.raw(kleur.bold().green(' │') + kleur.white(' Add to .vscode/mcp.json: ') + kleur.bold().green('│'));
|
|
372
354
|
log.raw(kleur.bold().green(' │') + kleur.dim(' "awesome-copilot": { ') + kleur.bold().green('│'));
|
|
@@ -381,26 +363,61 @@ function printBreadcrumbs() {
|
|
|
381
363
|
log.raw('');
|
|
382
364
|
}
|
|
383
365
|
|
|
384
|
-
// ──
|
|
366
|
+
// ── Runner ─────────────────────────────────────────────────────────────
|
|
385
367
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const out = [];
|
|
368
|
+
/** Run skills CLI with stdio: inherit (visible to user). */
|
|
369
|
+
function runSkills(env, skillsArgs, cwd) {
|
|
370
|
+
const { cmd, args } = env.runner(skillsArgs);
|
|
371
|
+
return runInteractive(cmd, args, cwd);
|
|
372
|
+
}
|
|
392
373
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
374
|
+
/** Run skills CLI, capture output, track success/failure. Returns { ok, reason }. */
|
|
375
|
+
function runSkillsTracked(env, skillsArgs, cwd) {
|
|
376
|
+
const { cmd, args } = env.runner(skillsArgs);
|
|
377
|
+
return new Promise((resolve) => {
|
|
378
|
+
let output = '';
|
|
379
|
+
const proc = spawn(cmd, args, { cwd, stdio: ['inherit', 'pipe', 'pipe'] });
|
|
380
|
+
proc.stdout.on('data', (chunk) => {
|
|
381
|
+
const text = chunk.toString();
|
|
382
|
+
process.stdout.write(text);
|
|
383
|
+
output += text;
|
|
384
|
+
});
|
|
385
|
+
proc.stderr.on('data', (chunk) => {
|
|
386
|
+
const text = chunk.toString();
|
|
387
|
+
process.stderr.write(text);
|
|
388
|
+
output += text;
|
|
389
|
+
});
|
|
390
|
+
proc.on('exit', (code) => {
|
|
391
|
+
const clean = stripAnsi(output);
|
|
392
|
+
if (code !== 0 || /failed to clone|installation failed|canceled/i.test(clean)) {
|
|
393
|
+
const reason = extractFailureReason(clean);
|
|
394
|
+
resolve({ ok: false, reason });
|
|
395
|
+
} else {
|
|
396
|
+
resolve({ ok: true, reason: null });
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
proc.on('error', (err) => {
|
|
400
|
+
resolve({ ok: false, reason: err.message });
|
|
401
|
+
});
|
|
402
|
+
});
|
|
401
403
|
}
|
|
402
404
|
|
|
403
|
-
|
|
405
|
+
/** Run skills CLI silently — capture stdout/stderr without echoing. */
|
|
406
|
+
function runSkillsSilent(env, skillsArgs, cwd) {
|
|
407
|
+
const { cmd, args } = env.runner(skillsArgs);
|
|
408
|
+
return new Promise((resolve) => {
|
|
409
|
+
let output = '';
|
|
410
|
+
const proc = spawn(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
411
|
+
proc.stdout.on('data', (chunk) => {
|
|
412
|
+
output += chunk.toString();
|
|
413
|
+
});
|
|
414
|
+
proc.stderr.on('data', (chunk) => {
|
|
415
|
+
output += chunk.toString();
|
|
416
|
+
});
|
|
417
|
+
proc.on('exit', () => resolve(output));
|
|
418
|
+
proc.on('error', () => resolve(''));
|
|
419
|
+
});
|
|
420
|
+
}
|
|
404
421
|
|
|
405
422
|
async function relocateSkills(cwd) {
|
|
406
423
|
const src = join(cwd, '.agents', 'skills');
|
|
@@ -458,12 +475,7 @@ async function relocateSkills(cwd) {
|
|
|
458
475
|
}
|
|
459
476
|
}
|
|
460
477
|
|
|
461
|
-
// ──
|
|
462
|
-
|
|
463
|
-
function runSkills(env, skillsArgs, cwd) {
|
|
464
|
-
const { cmd, args } = env.runner(skillsArgs);
|
|
465
|
-
return runInteractive(cmd, args, cwd);
|
|
466
|
-
}
|
|
478
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
467
479
|
|
|
468
480
|
function buildRecommendations(a) {
|
|
469
481
|
const stackLower = (a.stack || []).map((s) => s.toLowerCase());
|
|
@@ -503,6 +515,24 @@ function suggestSearches(a) {
|
|
|
503
515
|
return [...out];
|
|
504
516
|
}
|
|
505
517
|
|
|
518
|
+
/** Parse owner/repo@skill ref into { repo, skill }. */
|
|
519
|
+
function parseRef(ref) {
|
|
520
|
+
const match = /^(.+?\/.+?)@(.+)$/.exec(ref);
|
|
521
|
+
if (!match) return null;
|
|
522
|
+
return { repo: match[1], skill: match[2] };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** Extract a concise failure reason from skills CLI output. */
|
|
526
|
+
function extractFailureReason(output) {
|
|
527
|
+
const cloneMatch = /Failed to clone[^:]*:\s*(.+)/i.exec(output);
|
|
528
|
+
if (cloneMatch) return cloneMatch[1].trim();
|
|
529
|
+
|
|
530
|
+
const errorMatch = /(?:error|failed)[:\s]+(.+)/im.exec(output);
|
|
531
|
+
if (errorMatch) return errorMatch[1].trim();
|
|
532
|
+
|
|
533
|
+
return 'Unknown error (check output above)';
|
|
534
|
+
}
|
|
535
|
+
|
|
506
536
|
function pad(s, n) {
|
|
507
537
|
return (s || '').padEnd(n);
|
|
508
538
|
}
|
|
@@ -519,30 +549,6 @@ function runInteractive(cmd, args, cwd) {
|
|
|
519
549
|
});
|
|
520
550
|
}
|
|
521
551
|
|
|
522
|
-
/** Run skills CLI, capture stdout/stderr while echoing to the terminal. */
|
|
523
|
-
function runSkillsCapture(env, skillsArgs, cwd) {
|
|
524
|
-
const { cmd, args } = env.runner(skillsArgs);
|
|
525
|
-
return new Promise((resolve) => {
|
|
526
|
-
let output = '';
|
|
527
|
-
const proc = spawn(cmd, args, { cwd, stdio: ['inherit', 'pipe', 'pipe'] });
|
|
528
|
-
proc.stdout.on('data', (chunk) => {
|
|
529
|
-
const text = chunk.toString();
|
|
530
|
-
process.stdout.write(text);
|
|
531
|
-
output += text;
|
|
532
|
-
});
|
|
533
|
-
proc.stderr.on('data', (chunk) => {
|
|
534
|
-
const text = chunk.toString();
|
|
535
|
-
process.stderr.write(text);
|
|
536
|
-
output += text;
|
|
537
|
-
});
|
|
538
|
-
proc.on('exit', () => resolve(output));
|
|
539
|
-
proc.on('error', (err) => {
|
|
540
|
-
log.warn(`Could not launch \`${cmd} ${args.join(' ')}\`: ${err.message}`);
|
|
541
|
-
resolve('');
|
|
542
|
-
});
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
552
|
function stripAnsi(str) {
|
|
547
553
|
// eslint-disable-next-line no-control-regex
|
|
548
554
|
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
|