@orchestra-research/ai-research-skills 1.0.0 → 1.0.3
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/index.js +87 -11
- package/src/installer.js +222 -2
- package/src/prompts.js +94 -1
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -11,13 +11,16 @@ import {
|
|
|
11
11
|
askMainMenuAction,
|
|
12
12
|
askSelectAgents,
|
|
13
13
|
askAfterAction,
|
|
14
|
+
askUninstallChoice,
|
|
15
|
+
askSelectSkillsToUninstall,
|
|
16
|
+
askConfirmUninstall,
|
|
14
17
|
parseArgs,
|
|
15
18
|
CATEGORIES,
|
|
16
19
|
INDIVIDUAL_SKILLS,
|
|
17
20
|
QUICK_START_SKILLS,
|
|
18
21
|
getTotalSkillCount,
|
|
19
22
|
} from './prompts.js';
|
|
20
|
-
import { installSkills, installSpecificSkills, listInstalledSkills, getAllCategoryIds } from './installer.js';
|
|
23
|
+
import { installSkills, installSpecificSkills, listInstalledSkills, getAllCategoryIds, updateInstalledSkills, uninstallAllSkills, uninstallSpecificSkills, getInstalledSkillPaths, getInstalledSkillsForSelection } from './installer.js';
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* Sleep utility
|
|
@@ -77,14 +80,20 @@ async function interactiveFlow() {
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
if (menuAction === 'update') {
|
|
80
|
-
// Update
|
|
83
|
+
// Update only installed skills
|
|
81
84
|
showMenuHeader();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
const installedPaths = getInstalledSkillPaths();
|
|
86
|
+
if (installedPaths.length === 0) {
|
|
87
|
+
console.log(chalk.yellow(' No skills installed to update.'));
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(chalk.dim(' Install some skills first.'));
|
|
90
|
+
} else {
|
|
91
|
+
console.log(chalk.cyan(` Updating ${installedPaths.length} installed skills...`));
|
|
92
|
+
console.log();
|
|
93
|
+
await updateInstalledSkills(agents);
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(chalk.green(' ✓ All installed skills updated!'));
|
|
96
|
+
}
|
|
88
97
|
const afterUpdate = await askAfterAction();
|
|
89
98
|
if (afterUpdate === 'exit') {
|
|
90
99
|
console.log(chalk.dim(' Goodbye!'));
|
|
@@ -94,6 +103,69 @@ async function interactiveFlow() {
|
|
|
94
103
|
continue step2_menu;
|
|
95
104
|
}
|
|
96
105
|
|
|
106
|
+
if (menuAction === 'uninstall') {
|
|
107
|
+
// Uninstall skills
|
|
108
|
+
step_uninstall:
|
|
109
|
+
while (true) {
|
|
110
|
+
showMenuHeader();
|
|
111
|
+
const installedSkills = getInstalledSkillsForSelection();
|
|
112
|
+
|
|
113
|
+
if (installedSkills.length === 0) {
|
|
114
|
+
console.log(chalk.yellow(' No skills installed to uninstall.'));
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const uninstallChoice = await askUninstallChoice();
|
|
119
|
+
|
|
120
|
+
if (uninstallChoice === 'back') {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (uninstallChoice === 'all') {
|
|
125
|
+
// Uninstall everything
|
|
126
|
+
const confirmAction = await askConfirmUninstall(installedSkills.length);
|
|
127
|
+
if (confirmAction === 'confirm') {
|
|
128
|
+
console.log();
|
|
129
|
+
await uninstallAllSkills(agents);
|
|
130
|
+
console.log();
|
|
131
|
+
console.log(chalk.green(' ✓ All skills uninstalled!'));
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (uninstallChoice === 'select') {
|
|
137
|
+
// Select specific skills to uninstall
|
|
138
|
+
showMenuHeader();
|
|
139
|
+
const result = await askSelectSkillsToUninstall(installedSkills);
|
|
140
|
+
|
|
141
|
+
if (result.action === 'back') {
|
|
142
|
+
continue step_uninstall;
|
|
143
|
+
}
|
|
144
|
+
if (result.action === 'retry') {
|
|
145
|
+
continue step_uninstall;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Confirm uninstall
|
|
149
|
+
const confirmAction = await askConfirmUninstall(result.skills.length);
|
|
150
|
+
if (confirmAction === 'confirm') {
|
|
151
|
+
console.log();
|
|
152
|
+
await uninstallSpecificSkills(result.skills, agents);
|
|
153
|
+
console.log();
|
|
154
|
+
console.log(chalk.green(` ✓ ${result.skills.length} skill${result.skills.length !== 1 ? 's' : ''} uninstalled!`));
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const afterUninstall = await askAfterAction();
|
|
161
|
+
if (afterUninstall === 'exit') {
|
|
162
|
+
console.log(chalk.dim(' Goodbye!'));
|
|
163
|
+
console.log();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
continue step2_menu;
|
|
167
|
+
}
|
|
168
|
+
|
|
97
169
|
// STEP 3: Choose what to install (menuAction === 'install')
|
|
98
170
|
step3_choice:
|
|
99
171
|
while (true) {
|
|
@@ -220,14 +292,18 @@ async function commandMode(options) {
|
|
|
220
292
|
}
|
|
221
293
|
|
|
222
294
|
if (options.command === 'update') {
|
|
223
|
-
console.log(chalk.cyan('Updating skills...'));
|
|
224
295
|
const agents = detectAgents();
|
|
225
296
|
if (agents.length === 0) {
|
|
226
297
|
console.log(chalk.yellow('No agents detected.'));
|
|
227
298
|
return;
|
|
228
299
|
}
|
|
229
|
-
const
|
|
230
|
-
|
|
300
|
+
const installedPaths = getInstalledSkillPaths();
|
|
301
|
+
if (installedPaths.length === 0) {
|
|
302
|
+
console.log(chalk.yellow('No skills installed to update.'));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
console.log(chalk.cyan(`Updating ${installedPaths.length} installed skills...`));
|
|
306
|
+
await updateInstalledSkills(agents);
|
|
231
307
|
console.log(chalk.green('✓ Skills updated!'));
|
|
232
308
|
return;
|
|
233
309
|
}
|
package/src/installer.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, symlinkSync, readdirSync, readFileSync, writeFileSync, rmSync } from 'fs';
|
|
1
|
+
import { existsSync, mkdirSync, symlinkSync, readdirSync, readFileSync, writeFileSync, rmSync, lstatSync } from 'fs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join, basename, dirname } from 'path';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
|
|
8
|
-
const REPO_URL = 'https://github.com/
|
|
8
|
+
const REPO_URL = 'https://github.com/Orchestra-Research/AI-research-SKILLs';
|
|
9
9
|
const CANONICAL_DIR = join(homedir(), '.orchestra', 'skills');
|
|
10
10
|
const LOCK_FILE = join(homedir(), '.orchestra', '.lock.json');
|
|
11
11
|
|
|
@@ -389,3 +389,223 @@ export function getAllCategoryIds() {
|
|
|
389
389
|
'20-ml-paper-writing',
|
|
390
390
|
];
|
|
391
391
|
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Get installed skill paths for updating
|
|
395
|
+
* Returns array like ['06-post-training/verl', '20-ml-paper-writing']
|
|
396
|
+
*/
|
|
397
|
+
export function getInstalledSkillPaths() {
|
|
398
|
+
if (!existsSync(CANONICAL_DIR)) {
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const skillPaths = [];
|
|
403
|
+
const categories = readdirSync(CANONICAL_DIR, { withFileTypes: true })
|
|
404
|
+
.filter(d => d.isDirectory())
|
|
405
|
+
.map(d => d.name);
|
|
406
|
+
|
|
407
|
+
for (const category of categories) {
|
|
408
|
+
const categoryPath = join(CANONICAL_DIR, category);
|
|
409
|
+
|
|
410
|
+
// Check if it's a standalone skill (has SKILL.md directly)
|
|
411
|
+
const standaloneSkill = join(categoryPath, 'SKILL.md');
|
|
412
|
+
if (existsSync(standaloneSkill)) {
|
|
413
|
+
skillPaths.push(category);
|
|
414
|
+
} else {
|
|
415
|
+
// It's a category with nested skills
|
|
416
|
+
const skills = readdirSync(categoryPath, { withFileTypes: true })
|
|
417
|
+
.filter(d => d.isDirectory() && existsSync(join(categoryPath, d.name, 'SKILL.md')))
|
|
418
|
+
.map(d => d.name);
|
|
419
|
+
|
|
420
|
+
for (const skill of skills) {
|
|
421
|
+
skillPaths.push(`${category}/${skill}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return skillPaths;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Update only installed skills (re-download from GitHub)
|
|
431
|
+
*/
|
|
432
|
+
export async function updateInstalledSkills(agents) {
|
|
433
|
+
const installedPaths = getInstalledSkillPaths();
|
|
434
|
+
|
|
435
|
+
if (installedPaths.length === 0) {
|
|
436
|
+
console.log(chalk.yellow(' No skills installed to update.'));
|
|
437
|
+
return 0;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const spinner = ora('Updating from GitHub...').start();
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
// Download only the installed skills
|
|
444
|
+
const skills = await downloadSpecificSkills(installedPaths, spinner);
|
|
445
|
+
spinner.succeed(`Updated ${skills.length} skills`);
|
|
446
|
+
|
|
447
|
+
// Re-create symlinks for each agent
|
|
448
|
+
spinner.start('Refreshing symlinks...');
|
|
449
|
+
|
|
450
|
+
for (const agent of agents) {
|
|
451
|
+
const count = createSymlinks(agent, skills, spinner);
|
|
452
|
+
console.log(` ${chalk.green('✓')} ${agent.name.padEnd(14)} ${chalk.dim('→')} ${agent.skillsPath.replace(homedir(), '~').padEnd(25)} ${chalk.green(count + ' skills')}`);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
spinner.stop();
|
|
456
|
+
|
|
457
|
+
// Update lock file
|
|
458
|
+
const lock = readLock();
|
|
459
|
+
lock.version = '1.0.0';
|
|
460
|
+
lock.installedAt = new Date().toISOString();
|
|
461
|
+
lock.skills = skills;
|
|
462
|
+
lock.agents = agents.map(a => a.id);
|
|
463
|
+
writeLock(lock);
|
|
464
|
+
|
|
465
|
+
return skills.length;
|
|
466
|
+
} catch (error) {
|
|
467
|
+
spinner.fail('Update failed');
|
|
468
|
+
throw error;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Uninstall all skills
|
|
474
|
+
*/
|
|
475
|
+
export async function uninstallAllSkills(agents) {
|
|
476
|
+
const spinner = ora('Removing skills...').start();
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
// Remove symlinks from each agent
|
|
480
|
+
for (const agent of agents) {
|
|
481
|
+
if (existsSync(agent.skillsPath)) {
|
|
482
|
+
const entries = readdirSync(agent.skillsPath, { withFileTypes: true });
|
|
483
|
+
for (const entry of entries) {
|
|
484
|
+
const linkPath = join(agent.skillsPath, entry.name);
|
|
485
|
+
// Only remove if it's a symlink pointing to our canonical dir
|
|
486
|
+
try {
|
|
487
|
+
const stats = lstatSync(linkPath);
|
|
488
|
+
if (stats.isSymbolicLink()) {
|
|
489
|
+
rmSync(linkPath, { force: true });
|
|
490
|
+
}
|
|
491
|
+
} catch {
|
|
492
|
+
// Ignore errors
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
console.log(` ${chalk.green('✓')} Removed symlinks from ${agent.name}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Remove canonical skills directory
|
|
500
|
+
if (existsSync(CANONICAL_DIR)) {
|
|
501
|
+
rmSync(CANONICAL_DIR, { recursive: true, force: true });
|
|
502
|
+
console.log(` ${chalk.green('✓')} Removed ${CANONICAL_DIR.replace(homedir(), '~')}`);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Remove lock file
|
|
506
|
+
if (existsSync(LOCK_FILE)) {
|
|
507
|
+
rmSync(LOCK_FILE, { force: true });
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
spinner.stop();
|
|
511
|
+
return true;
|
|
512
|
+
} catch (error) {
|
|
513
|
+
spinner.fail('Uninstall failed');
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Uninstall specific skills
|
|
520
|
+
* @param {Array<string>} skillPaths - Paths like ['06-post-training/verl', '20-ml-paper-writing']
|
|
521
|
+
* @param {Array} agents - List of agents to remove symlinks from
|
|
522
|
+
*/
|
|
523
|
+
export async function uninstallSpecificSkills(skillPaths, agents) {
|
|
524
|
+
const spinner = ora('Removing selected skills...').start();
|
|
525
|
+
|
|
526
|
+
try {
|
|
527
|
+
for (const skillPath of skillPaths) {
|
|
528
|
+
const parts = skillPath.split('/');
|
|
529
|
+
const categoryId = parts[0];
|
|
530
|
+
const skillName = parts[1] || null;
|
|
531
|
+
|
|
532
|
+
// Determine the link name (what appears in agent's skills folder)
|
|
533
|
+
const linkName = skillName || categoryId;
|
|
534
|
+
|
|
535
|
+
// Remove symlinks from each agent
|
|
536
|
+
for (const agent of agents) {
|
|
537
|
+
const linkPath = join(agent.skillsPath, linkName);
|
|
538
|
+
try {
|
|
539
|
+
if (existsSync(linkPath)) {
|
|
540
|
+
const stats = lstatSync(linkPath);
|
|
541
|
+
if (stats.isSymbolicLink()) {
|
|
542
|
+
rmSync(linkPath, { force: true });
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
} catch {
|
|
546
|
+
// Ignore errors
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Remove from canonical directory
|
|
551
|
+
if (skillName) {
|
|
552
|
+
// Nested skill like '06-post-training/verl'
|
|
553
|
+
const skillDir = join(CANONICAL_DIR, categoryId, skillName);
|
|
554
|
+
if (existsSync(skillDir)) {
|
|
555
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
556
|
+
}
|
|
557
|
+
// Clean up empty category folder
|
|
558
|
+
const categoryDir = join(CANONICAL_DIR, categoryId);
|
|
559
|
+
if (existsSync(categoryDir)) {
|
|
560
|
+
const remaining = readdirSync(categoryDir);
|
|
561
|
+
if (remaining.length === 0) {
|
|
562
|
+
rmSync(categoryDir, { recursive: true, force: true });
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
} else {
|
|
566
|
+
// Standalone skill like '20-ml-paper-writing'
|
|
567
|
+
const skillDir = join(CANONICAL_DIR, categoryId);
|
|
568
|
+
if (existsSync(skillDir)) {
|
|
569
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
spinner.text = `Removed ${linkName}`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
spinner.succeed(`Removed ${skillPaths.length} skill${skillPaths.length !== 1 ? 's' : ''}`);
|
|
577
|
+
|
|
578
|
+
// Update lock file
|
|
579
|
+
const lock = readLock();
|
|
580
|
+
if (lock.skills) {
|
|
581
|
+
lock.skills = lock.skills.filter(s => {
|
|
582
|
+
const path = s.standalone ? s.category : `${s.category}/${s.skill}`;
|
|
583
|
+
return !skillPaths.includes(path);
|
|
584
|
+
});
|
|
585
|
+
writeLock(lock);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return skillPaths.length;
|
|
589
|
+
} catch (error) {
|
|
590
|
+
spinner.fail('Uninstall failed');
|
|
591
|
+
throw error;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Get installed skills with display info for selection
|
|
597
|
+
* Returns array of { path, name, category } for UI
|
|
598
|
+
*/
|
|
599
|
+
export function getInstalledSkillsForSelection() {
|
|
600
|
+
const paths = getInstalledSkillPaths();
|
|
601
|
+
return paths.map(path => {
|
|
602
|
+
const parts = path.split('/');
|
|
603
|
+
if (parts.length === 1) {
|
|
604
|
+
// Standalone skill
|
|
605
|
+
return { path, name: parts[0], category: 'Standalone', standalone: true };
|
|
606
|
+
} else {
|
|
607
|
+
// Nested skill
|
|
608
|
+
return { path, name: parts[1], category: parts[0], standalone: false };
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
package/src/prompts.js
CHANGED
|
@@ -104,7 +104,8 @@ export async function askMainMenuAction() {
|
|
|
104
104
|
choices: [
|
|
105
105
|
{ name: 'Install new skills', value: 'install' },
|
|
106
106
|
{ name: 'View installed skills', value: 'view' },
|
|
107
|
-
{ name: 'Update
|
|
107
|
+
{ name: 'Update installed skills', value: 'update' },
|
|
108
|
+
{ name: 'Uninstall all skills', value: 'uninstall' },
|
|
108
109
|
new inquirer.Separator(' '),
|
|
109
110
|
{ name: chalk.dim('Exit'), value: 'exit' },
|
|
110
111
|
],
|
|
@@ -114,6 +115,98 @@ export async function askMainMenuAction() {
|
|
|
114
115
|
return action;
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Ask what to uninstall
|
|
120
|
+
*/
|
|
121
|
+
export async function askUninstallChoice() {
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(chalk.dim(' What would you like to uninstall?'));
|
|
124
|
+
console.log();
|
|
125
|
+
|
|
126
|
+
const { choice } = await inquirer.prompt([
|
|
127
|
+
{
|
|
128
|
+
type: 'list',
|
|
129
|
+
name: 'choice',
|
|
130
|
+
message: ' ',
|
|
131
|
+
choices: [
|
|
132
|
+
{ name: 'Select specific skills', value: 'select' },
|
|
133
|
+
{ name: chalk.red('Uninstall everything'), value: 'all' },
|
|
134
|
+
new inquirer.Separator(' '),
|
|
135
|
+
{ name: chalk.dim('← Back'), value: 'back' },
|
|
136
|
+
],
|
|
137
|
+
prefix: ' ',
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
return choice;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Ask which installed skills to uninstall
|
|
145
|
+
*/
|
|
146
|
+
export async function askSelectSkillsToUninstall(installedSkills) {
|
|
147
|
+
console.log();
|
|
148
|
+
console.log(chalk.dim(' Select skills to uninstall:'));
|
|
149
|
+
console.log(chalk.dim(' (Space to select, Enter to confirm)'));
|
|
150
|
+
console.log();
|
|
151
|
+
|
|
152
|
+
const { skills } = await inquirer.prompt([
|
|
153
|
+
{
|
|
154
|
+
type: 'checkbox',
|
|
155
|
+
name: 'skills',
|
|
156
|
+
message: ' ',
|
|
157
|
+
choices: installedSkills.map(skill => ({
|
|
158
|
+
name: `${skill.name.padEnd(25)} ${chalk.dim(skill.category)}`,
|
|
159
|
+
value: skill.path,
|
|
160
|
+
short: skill.name,
|
|
161
|
+
})),
|
|
162
|
+
prefix: ' ',
|
|
163
|
+
pageSize: 15,
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
if (skills.length === 0) {
|
|
168
|
+
console.log();
|
|
169
|
+
const { action } = await inquirer.prompt([
|
|
170
|
+
{
|
|
171
|
+
type: 'list',
|
|
172
|
+
name: 'action',
|
|
173
|
+
message: chalk.yellow('No skills selected'),
|
|
174
|
+
choices: [
|
|
175
|
+
{ name: 'Try again', value: 'retry' },
|
|
176
|
+
{ name: chalk.dim('← Back'), value: 'back' },
|
|
177
|
+
],
|
|
178
|
+
prefix: ' ',
|
|
179
|
+
},
|
|
180
|
+
]);
|
|
181
|
+
return { skills: [], action };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { skills, action: 'confirm' };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Ask to confirm uninstall
|
|
189
|
+
*/
|
|
190
|
+
export async function askConfirmUninstall(count) {
|
|
191
|
+
console.log();
|
|
192
|
+
console.log(chalk.yellow(` This will remove ${count} skill${count !== 1 ? 's' : ''} and their symlinks.`));
|
|
193
|
+
console.log();
|
|
194
|
+
|
|
195
|
+
const { action } = await inquirer.prompt([
|
|
196
|
+
{
|
|
197
|
+
type: 'list',
|
|
198
|
+
name: 'action',
|
|
199
|
+
message: ' ',
|
|
200
|
+
choices: [
|
|
201
|
+
{ name: chalk.red('Yes, uninstall'), value: 'confirm' },
|
|
202
|
+
{ name: chalk.dim('← Back'), value: 'back' },
|
|
203
|
+
],
|
|
204
|
+
prefix: ' ',
|
|
205
|
+
},
|
|
206
|
+
]);
|
|
207
|
+
return action;
|
|
208
|
+
}
|
|
209
|
+
|
|
117
210
|
/**
|
|
118
211
|
* Ask what to install
|
|
119
212
|
*/
|