@supercorks/skills-installer 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -14
- package/bin/install.js +110 -26
- package/lib/install-targets.js +39 -13
- package/lib/prompts.js +51 -38
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,28 +18,26 @@ npx @supercorks/skills-installer install
|
|
|
18
18
|
|
|
19
19
|
1. **Choose installation type** - Install skills, subagents, or both.
|
|
20
20
|
|
|
21
|
-
2. **Choose installation path(s)** - Select one or more locations where resources should be installed. The installer labels each option with harness, scope, and available count, for example
|
|
21
|
+
2. **Choose installation path(s)** - Select one or more locations where resources should be installed. Global locations are shown before local locations. The installer labels each option with harness, scope, and available count, for example `~/.agents/skills/ (copilot/codex | global | 24 skills)`.
|
|
22
22
|
|
|
23
23
|
Skills:
|
|
24
|
-
-
|
|
25
|
-
- `~/.copilot/skills/` (copilot | global)
|
|
26
|
-
- `.agents/skills/` (codex | local)
|
|
27
|
-
- `~/.agents/skills/` (codex | global)
|
|
28
|
-
- `.claude/skills/` (claude | local)
|
|
24
|
+
- `~/.agents/skills/` (copilot/codex | global)
|
|
29
25
|
- `~/.claude/skills/` (claude | global)
|
|
26
|
+
- `.agents/skills/` (copilot/codex | local)
|
|
27
|
+
- `.claude/skills/` (claude | local)
|
|
30
28
|
|
|
31
29
|
Agents:
|
|
32
|
-
-
|
|
33
|
-
- `~/.copilot/agents/` (copilot | global)
|
|
34
|
-
- `.claude/agents/` (claude | local)
|
|
30
|
+
- `~/.agents/agents/` (copilot | global)
|
|
35
31
|
- `~/.claude/agents/` (claude | global)
|
|
36
|
-
- `.codex/agents/` (codex | local, installed as converted TOML agents)
|
|
37
32
|
- `~/.codex/agents/` (codex | global, installed as converted TOML agents)
|
|
33
|
+
- `.agents/agents/` (copilot | local)
|
|
34
|
+
- `.claude/agents/` (claude | local)
|
|
35
|
+
- `.codex/agents/` (codex | local, installed as converted TOML agents)
|
|
38
36
|
- Custom path of your choice
|
|
39
37
|
|
|
40
38
|
3. **Gitignore option** - If launched from inside a git repository, optionally add the installation path to `.gitignore`
|
|
41
39
|
|
|
42
|
-
4. **Select skills/subagents** - Interactive checkbox to pick what to install:
|
|
40
|
+
4. **Select skills/subagents** - Interactive checkbox to pick what to install. If multiple locations are selected, the installer asks once and applies the same selection to every selected location:
|
|
43
41
|
- Use `↑`/`↓` to navigate
|
|
44
42
|
- Use `SPACE` to toggle selection
|
|
45
43
|
- Use `→` to expand and lazy-load descriptions
|
|
@@ -60,7 +58,7 @@ npx @supercorks/skills-installer install
|
|
|
60
58
|
- **Minimal download** - Uses `git clone --filter=blob:none` for efficient cloning
|
|
61
59
|
- **Push capable** - The sparse clone preserves the full git history, allowing you to commit and push changes
|
|
62
60
|
- **Auto-discovery** - Fetches the latest skill list from the repository
|
|
63
|
-
- **Global and local targets** - Offers documented project/user locations for Copilot, Codex, and Claude where the resource format is compatible
|
|
61
|
+
- **Global and local targets** - Offers documented project/user locations for Copilot, Codex, and Claude where the resource format is compatible, with shared generic `~/.agents/skills/` and `.agents/skills/` targets for Copilot/Codex skills
|
|
64
62
|
- **Codex agent conversion** - Converts Markdown subagents into Codex TOML custom agents for `.codex/agents/` targets
|
|
65
63
|
- **Recursive directory creation** - Custom paths are created automatically
|
|
66
64
|
|
|
@@ -74,7 +72,7 @@ npx @supercorks/skills-installer install
|
|
|
74
72
|
Since the installation uses a sparse git checkout, you can pull updates:
|
|
75
73
|
|
|
76
74
|
```bash
|
|
77
|
-
cd .
|
|
75
|
+
cd .agents/skills # or wherever you installed
|
|
78
76
|
git pull
|
|
79
77
|
```
|
|
80
78
|
|
|
@@ -83,7 +81,7 @@ git pull
|
|
|
83
81
|
You can add more skills to an existing installation:
|
|
84
82
|
|
|
85
83
|
```bash
|
|
86
|
-
cd .
|
|
84
|
+
cd .agents/skills
|
|
87
85
|
git sparse-checkout add new-skill-name
|
|
88
86
|
```
|
|
89
87
|
|
package/bin/install.js
CHANGED
|
@@ -57,6 +57,14 @@ function isHomePath(path) {
|
|
|
57
57
|
return path === '~' || path.startsWith('~/');
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function uniqueItems(items) {
|
|
61
|
+
return Array.from(new Set(items));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function unionSets(sets) {
|
|
65
|
+
return new Set(sets.flatMap(set => Array.from(set)));
|
|
66
|
+
}
|
|
67
|
+
|
|
60
68
|
/**
|
|
61
69
|
* Detect existing skill installations in common paths
|
|
62
70
|
* @returns {Promise<Array<{path: string, skillCount: number, skills: string[]}>>}
|
|
@@ -242,22 +250,39 @@ async function runSkillsInstall() {
|
|
|
242
250
|
// Ask where to install (showing existing installations if any)
|
|
243
251
|
const installTargets = await promptInstallPath(existingInstalls, skills.length);
|
|
244
252
|
|
|
245
|
-
|
|
246
|
-
|
|
253
|
+
const targetContexts = [];
|
|
254
|
+
for (const [index, target] of installTargets.entries()) {
|
|
247
255
|
if (installTargets.length > 1) {
|
|
248
|
-
console.log(`\n📍
|
|
256
|
+
console.log(`\n📍 Preparing skills target ${index + 1}/${installTargets.length}: ${target.path}`);
|
|
249
257
|
}
|
|
250
|
-
await
|
|
258
|
+
targetContexts.push(await prepareSkillsInstallTarget(existingInstalls, target));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const installedSkills = uniqueItems(targetContexts.flatMap(context => context.installedSkills));
|
|
262
|
+
const skillsNeedingUpdate = unionSets(targetContexts.map(context => context.skillsNeedingUpdate));
|
|
263
|
+
|
|
264
|
+
const selectedSkills = await promptSkillSelection(
|
|
265
|
+
skills,
|
|
266
|
+
installedSkills,
|
|
267
|
+
skillsNeedingUpdate,
|
|
268
|
+
(skillFolder) => fetchSkillMetadata(skillFolder)
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
for (let i = 0; i < targetContexts.length; i++) {
|
|
272
|
+
if (targetContexts.length > 1) {
|
|
273
|
+
console.log(`\n📍 Skills target ${i + 1}/${targetContexts.length}: ${targetContexts[i].installPath}`);
|
|
274
|
+
}
|
|
275
|
+
await runSkillsInstallForTarget(skills, targetContexts[i], selectedSkills);
|
|
251
276
|
}
|
|
252
277
|
}
|
|
253
278
|
|
|
254
279
|
/**
|
|
255
|
-
*
|
|
256
|
-
* @param {Array<{name: string, description: string, folder: string}>} skills
|
|
280
|
+
* Prepare a specific skills target for installation/update.
|
|
257
281
|
* @param {Array<{path: string, skillCount: number, skills: string[]}>} existingInstalls
|
|
258
282
|
* @param {{path: string, isExisting: boolean}} target
|
|
283
|
+
* @returns {Promise<object>}
|
|
259
284
|
*/
|
|
260
|
-
async function
|
|
285
|
+
async function prepareSkillsInstallTarget(existingInstalls, target) {
|
|
261
286
|
const { path: installPath, isExisting } = target;
|
|
262
287
|
const absoluteInstallPath = resolveInstallPath(installPath);
|
|
263
288
|
const gitDir = join(absoluteInstallPath, '.git');
|
|
@@ -311,13 +336,33 @@ async function runSkillsInstallForTarget(skills, existingInstalls, target) {
|
|
|
311
336
|
shouldGitignore = await promptGitignore(installPath);
|
|
312
337
|
}
|
|
313
338
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
339
|
+
return {
|
|
340
|
+
...target,
|
|
341
|
+
installPath,
|
|
342
|
+
absoluteInstallPath,
|
|
317
343
|
installedSkills,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
344
|
+
isManageMode,
|
|
345
|
+
shouldGitignore,
|
|
346
|
+
gitignorePath,
|
|
347
|
+
skillsNeedingUpdate
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Install/update skills for a specific target path
|
|
353
|
+
* @param {Array<{name: string, description: string, folder: string}>} skills
|
|
354
|
+
* @param {object} targetContext
|
|
355
|
+
* @param {string[]} selectedSkills
|
|
356
|
+
*/
|
|
357
|
+
async function runSkillsInstallForTarget(skills, targetContext, selectedSkills) {
|
|
358
|
+
const {
|
|
359
|
+
installPath,
|
|
360
|
+
absoluteInstallPath,
|
|
361
|
+
installedSkills,
|
|
362
|
+
isManageMode,
|
|
363
|
+
shouldGitignore,
|
|
364
|
+
gitignorePath
|
|
365
|
+
} = targetContext;
|
|
321
366
|
|
|
322
367
|
// Perform installation or update
|
|
323
368
|
console.log('');
|
|
@@ -402,22 +447,39 @@ async function runSubagentsInstall() {
|
|
|
402
447
|
// Ask where to install (showing existing installations if any)
|
|
403
448
|
const installTargets = await promptAgentInstallPath(existingInstalls, subagents.length);
|
|
404
449
|
|
|
405
|
-
|
|
406
|
-
|
|
450
|
+
const targetContexts = [];
|
|
451
|
+
for (const [index, target] of installTargets.entries()) {
|
|
407
452
|
if (installTargets.length > 1) {
|
|
408
|
-
console.log(`\n📍
|
|
453
|
+
console.log(`\n📍 Preparing subagents target ${index + 1}/${installTargets.length}: ${target.path}`);
|
|
454
|
+
}
|
|
455
|
+
targetContexts.push(await prepareSubagentsInstallTarget(existingInstalls, target));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const installedAgents = uniqueItems(targetContexts.flatMap(context => context.installedAgents));
|
|
459
|
+
const subagentsNeedingUpdate = unionSets(targetContexts.map(context => context.subagentsNeedingUpdate));
|
|
460
|
+
|
|
461
|
+
const selectedAgents = await promptSubagentSelection(
|
|
462
|
+
subagents,
|
|
463
|
+
installedAgents,
|
|
464
|
+
subagentsNeedingUpdate,
|
|
465
|
+
(filename) => fetchSubagentMetadata(filename)
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
for (let i = 0; i < targetContexts.length; i++) {
|
|
469
|
+
if (targetContexts.length > 1) {
|
|
470
|
+
console.log(`\n📍 Subagents target ${i + 1}/${targetContexts.length}: ${targetContexts[i].installPath}`);
|
|
409
471
|
}
|
|
410
|
-
await runSubagentsInstallForTarget(subagents,
|
|
472
|
+
await runSubagentsInstallForTarget(subagents, targetContexts[i], selectedAgents);
|
|
411
473
|
}
|
|
412
474
|
}
|
|
413
475
|
|
|
414
476
|
/**
|
|
415
|
-
*
|
|
416
|
-
* @param {Array<{name: string, description: string, filename: string}>} subagents
|
|
477
|
+
* Prepare a specific subagent target for installation/update.
|
|
417
478
|
* @param {Array<{path: string, agentCount: number, agents: string[]}>} existingInstalls
|
|
418
479
|
* @param {{path: string, isExisting: boolean}} target
|
|
480
|
+
* @returns {Promise<object>}
|
|
419
481
|
*/
|
|
420
|
-
async function
|
|
482
|
+
async function prepareSubagentsInstallTarget(existingInstalls, target) {
|
|
421
483
|
const { path: installPath, isExisting } = target;
|
|
422
484
|
const absoluteInstallPath = resolveInstallPath(installPath);
|
|
423
485
|
const installMode = getAgentInstallMode(installPath);
|
|
@@ -483,13 +545,35 @@ async function runSubagentsInstallForTarget(subagents, existingInstalls, target)
|
|
|
483
545
|
shouldGitignore = await promptGitignore(installPath);
|
|
484
546
|
}
|
|
485
547
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
548
|
+
return {
|
|
549
|
+
...target,
|
|
550
|
+
installPath,
|
|
551
|
+
absoluteInstallPath,
|
|
552
|
+
installMode,
|
|
489
553
|
installedAgents,
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
554
|
+
isManageMode,
|
|
555
|
+
shouldGitignore,
|
|
556
|
+
gitignorePath,
|
|
557
|
+
subagentsNeedingUpdate
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Install/update subagents for a specific target path
|
|
563
|
+
* @param {Array<{name: string, description: string, filename: string}>} subagents
|
|
564
|
+
* @param {object} targetContext
|
|
565
|
+
* @param {string[]} selectedAgents
|
|
566
|
+
*/
|
|
567
|
+
async function runSubagentsInstallForTarget(subagents, targetContext, selectedAgents) {
|
|
568
|
+
const {
|
|
569
|
+
installPath,
|
|
570
|
+
absoluteInstallPath,
|
|
571
|
+
installMode,
|
|
572
|
+
installedAgents,
|
|
573
|
+
isManageMode,
|
|
574
|
+
shouldGitignore,
|
|
575
|
+
gitignorePath
|
|
576
|
+
} = targetContext;
|
|
493
577
|
|
|
494
578
|
// Perform installation or update
|
|
495
579
|
console.log('');
|
package/lib/install-targets.js
CHANGED
|
@@ -3,15 +3,23 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export const SKILL_INSTALL_TARGETS = [
|
|
6
|
-
{ path: '
|
|
7
|
-
{ path: '~/.
|
|
8
|
-
{ path: '.agents/skills/', harness: 'codex', scope: 'local' },
|
|
9
|
-
{ path: '
|
|
10
|
-
{ path: '.claude/skills/', harness: 'claude', scope: 'local' },
|
|
11
|
-
{ path: '~/.claude/skills/', harness: 'claude', scope: 'global' }
|
|
6
|
+
{ path: '~/.agents/skills/', harness: 'copilot/codex', scope: 'global' },
|
|
7
|
+
{ path: '~/.claude/skills/', harness: 'claude', scope: 'global' },
|
|
8
|
+
{ path: '.agents/skills/', harness: 'copilot/codex', scope: 'local' },
|
|
9
|
+
{ path: '.claude/skills/', harness: 'claude', scope: 'local' }
|
|
12
10
|
];
|
|
13
11
|
|
|
14
12
|
export const LEGACY_SKILL_INSTALL_TARGETS = [
|
|
13
|
+
{
|
|
14
|
+
path: '.github/skills/',
|
|
15
|
+
harness: 'copilot',
|
|
16
|
+
scope: 'legacy local'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
path: '~/.copilot/skills/',
|
|
20
|
+
harness: 'copilot',
|
|
21
|
+
scope: 'legacy global'
|
|
22
|
+
},
|
|
15
23
|
{
|
|
16
24
|
path: '~/.codex/skills/',
|
|
17
25
|
harness: 'codex',
|
|
@@ -20,19 +28,24 @@ export const LEGACY_SKILL_INSTALL_TARGETS = [
|
|
|
20
28
|
];
|
|
21
29
|
|
|
22
30
|
export const AGENT_INSTALL_TARGETS = [
|
|
23
|
-
{ path: '
|
|
24
|
-
{ path: '~/.copilot/agents/', harness: 'copilot', scope: 'global', installMode: 'sparse-git' },
|
|
25
|
-
{ path: '.claude/agents/', harness: 'claude', scope: 'local', installMode: 'sparse-git' },
|
|
31
|
+
{ path: '~/.agents/agents/', harness: 'copilot', scope: 'global', installMode: 'sparse-git' },
|
|
26
32
|
{ path: '~/.claude/agents/', harness: 'claude', scope: 'global', installMode: 'sparse-git' },
|
|
27
|
-
{ path: '
|
|
28
|
-
{ path: '
|
|
33
|
+
{ path: '~/.codex/agents/', harness: 'codex', scope: 'global', installMode: 'codex-toml' },
|
|
34
|
+
{ path: '.agents/agents/', harness: 'copilot', scope: 'local', installMode: 'sparse-git' },
|
|
35
|
+
{ path: '.claude/agents/', harness: 'claude', scope: 'local', installMode: 'sparse-git' },
|
|
36
|
+
{ path: '.codex/agents/', harness: 'codex', scope: 'local', installMode: 'codex-toml' }
|
|
29
37
|
];
|
|
30
38
|
|
|
31
39
|
export const LEGACY_AGENT_INSTALL_TARGETS = [
|
|
32
40
|
{
|
|
33
|
-
path: '.
|
|
34
|
-
harness: '
|
|
41
|
+
path: '.github/agents/',
|
|
42
|
+
harness: 'copilot',
|
|
35
43
|
scope: 'legacy local'
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
path: '~/.copilot/agents/',
|
|
47
|
+
harness: 'copilot',
|
|
48
|
+
scope: 'legacy global'
|
|
36
49
|
}
|
|
37
50
|
];
|
|
38
51
|
|
|
@@ -44,6 +57,19 @@ export function allAgentDetectionTargets() {
|
|
|
44
57
|
return [...AGENT_INSTALL_TARGETS, ...LEGACY_AGENT_INSTALL_TARGETS];
|
|
45
58
|
}
|
|
46
59
|
|
|
60
|
+
export function orderTargetsGlobalFirst(targets) {
|
|
61
|
+
return [...targets].sort((left, right) => {
|
|
62
|
+
const leftGlobal = left.scope.includes('global') ? 0 : 1;
|
|
63
|
+
const rightGlobal = right.scope.includes('global') ? 0 : 1;
|
|
64
|
+
|
|
65
|
+
if (leftGlobal !== rightGlobal) {
|
|
66
|
+
return leftGlobal - rightGlobal;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return targets.indexOf(left) - targets.indexOf(right);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
47
73
|
export function getAgentInstallMode(path) {
|
|
48
74
|
const exactTarget = getTargetByPath(AGENT_INSTALL_TARGETS, path);
|
|
49
75
|
if (exactTarget?.installMode) {
|
package/lib/prompts.js
CHANGED
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
allAgentDetectionTargets,
|
|
11
11
|
allSkillDetectionTargets,
|
|
12
12
|
formatTargetLabel,
|
|
13
|
-
getTargetByPath
|
|
13
|
+
getTargetByPath,
|
|
14
|
+
orderTargetsGlobalFirst
|
|
14
15
|
} from './install-targets.js';
|
|
15
16
|
|
|
16
17
|
const SKILL_PATH_CHOICES = {
|
|
@@ -68,13 +69,29 @@ export async function promptInstallType() {
|
|
|
68
69
|
*/
|
|
69
70
|
export async function promptInstallPath(existingInstalls = [], availableSkillCount = 0) {
|
|
70
71
|
const choices = [];
|
|
71
|
-
const existingPaths = existingInstalls.map(i => i.path);
|
|
72
72
|
const detectionTargets = allSkillDetectionTargets();
|
|
73
|
+
const existingByPath = new Map(existingInstalls.map(install => [install.path, install]));
|
|
74
|
+
const standardPaths = new Set(SKILL_INSTALL_TARGETS.map(target => target.path));
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
orderTargetsGlobalFirst(SKILL_INSTALL_TARGETS).forEach(target => {
|
|
77
|
+
const install = existingByPath.get(target.path);
|
|
78
|
+
choices.push({
|
|
79
|
+
name: install
|
|
80
|
+
? formatTargetLabel(target, install.skillCount, 'skill', { installed: true })
|
|
81
|
+
: formatTargetLabel(target, availableSkillCount, 'skill'),
|
|
82
|
+
value: target.path
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const existingLegacyOrCustom = existingInstalls.filter(install => !standardPaths.has(install.path));
|
|
87
|
+
if (existingLegacyOrCustom.length > 0) {
|
|
88
|
+
choices.push(new inquirer.Separator('── Existing legacy/custom installations ──'));
|
|
89
|
+
orderTargetsGlobalFirst(existingLegacyOrCustom.map(install => getTargetByPath(detectionTargets, install.path) || {
|
|
90
|
+
path: install.path,
|
|
91
|
+
harness: 'custom',
|
|
92
|
+
scope: 'existing'
|
|
93
|
+
})).forEach(target => {
|
|
94
|
+
const install = existingByPath.get(target.path);
|
|
78
95
|
choices.push({
|
|
79
96
|
name: target
|
|
80
97
|
? formatTargetLabel(target, install.skillCount, 'skill', { installed: true })
|
|
@@ -82,19 +99,9 @@ export async function promptInstallPath(existingInstalls = [], availableSkillCou
|
|
|
82
99
|
value: install.path
|
|
83
100
|
});
|
|
84
101
|
});
|
|
85
|
-
choices.push(new inquirer.Separator('── New installation ──'));
|
|
86
102
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
SKILL_INSTALL_TARGETS.forEach(target => {
|
|
90
|
-
if (!existingPaths.includes(target.path)) {
|
|
91
|
-
choices.push({
|
|
92
|
-
name: formatTargetLabel(target, availableSkillCount, 'skill'),
|
|
93
|
-
value: target.path
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
103
|
+
|
|
104
|
+
choices.push(new inquirer.Separator('── Other ──'));
|
|
98
105
|
choices.push({ name: 'Custom path...', value: SKILL_PATH_CHOICES.CUSTOM });
|
|
99
106
|
|
|
100
107
|
const { pathChoices } = await inquirer.prompt([
|
|
@@ -120,7 +127,7 @@ export async function promptInstallPath(existingInstalls = [], availableSkillCou
|
|
|
120
127
|
pathChoices
|
|
121
128
|
.filter(path => path !== SKILL_PATH_CHOICES.CUSTOM)
|
|
122
129
|
.forEach(path => {
|
|
123
|
-
selected.push({ path, isExisting:
|
|
130
|
+
selected.push({ path, isExisting: existingByPath.has(path) });
|
|
124
131
|
});
|
|
125
132
|
|
|
126
133
|
if (selectedSet.has(SKILL_PATH_CHOICES.CUSTOM)) {
|
|
@@ -153,13 +160,30 @@ export async function promptInstallPath(existingInstalls = [], availableSkillCou
|
|
|
153
160
|
*/
|
|
154
161
|
export async function promptAgentInstallPath(existingInstalls = [], availableAgentCount = 0) {
|
|
155
162
|
const choices = [];
|
|
156
|
-
const existingPaths = existingInstalls.map(i => i.path);
|
|
157
163
|
const detectionTargets = allAgentDetectionTargets();
|
|
164
|
+
const existingByPath = new Map(existingInstalls.map(install => [install.path, install]));
|
|
165
|
+
const standardPaths = new Set(AGENT_INSTALL_TARGETS.map(target => target.path));
|
|
158
166
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
orderTargetsGlobalFirst(AGENT_INSTALL_TARGETS).forEach(target => {
|
|
168
|
+
const install = existingByPath.get(target.path);
|
|
169
|
+
choices.push({
|
|
170
|
+
name: install
|
|
171
|
+
? formatTargetLabel(target, install.agentCount, 'agent', { installed: true })
|
|
172
|
+
: formatTargetLabel(target, availableAgentCount, 'agent'),
|
|
173
|
+
value: target.path,
|
|
174
|
+
disabled: target.disabledReason
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const existingLegacyOrCustom = existingInstalls.filter(install => !standardPaths.has(install.path));
|
|
179
|
+
if (existingLegacyOrCustom.length > 0) {
|
|
180
|
+
choices.push(new inquirer.Separator('── Existing legacy/custom installations ──'));
|
|
181
|
+
orderTargetsGlobalFirst(existingLegacyOrCustom.map(install => getTargetByPath(detectionTargets, install.path) || {
|
|
182
|
+
path: install.path,
|
|
183
|
+
harness: 'custom',
|
|
184
|
+
scope: 'existing'
|
|
185
|
+
})).forEach(target => {
|
|
186
|
+
const install = existingByPath.get(target.path);
|
|
163
187
|
choices.push({
|
|
164
188
|
name: target
|
|
165
189
|
? formatTargetLabel(target, install.agentCount, 'agent', { installed: true })
|
|
@@ -167,20 +191,9 @@ export async function promptAgentInstallPath(existingInstalls = [], availableAge
|
|
|
167
191
|
value: install.path
|
|
168
192
|
});
|
|
169
193
|
});
|
|
170
|
-
choices.push(new inquirer.Separator('── New installation ──'));
|
|
171
194
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
AGENT_INSTALL_TARGETS.forEach(target => {
|
|
175
|
-
if (!existingPaths.includes(target.path)) {
|
|
176
|
-
choices.push({
|
|
177
|
-
name: formatTargetLabel(target, availableAgentCount, 'agent'),
|
|
178
|
-
value: target.path,
|
|
179
|
-
disabled: target.disabledReason
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
195
|
+
|
|
196
|
+
choices.push(new inquirer.Separator('── Other ──'));
|
|
184
197
|
choices.push({ name: 'Custom path...', value: AGENT_PATH_CHOICES.CUSTOM });
|
|
185
198
|
|
|
186
199
|
const { pathChoices } = await inquirer.prompt([
|
|
@@ -206,7 +219,7 @@ export async function promptAgentInstallPath(existingInstalls = [], availableAge
|
|
|
206
219
|
pathChoices
|
|
207
220
|
.filter(path => path !== AGENT_PATH_CHOICES.CUSTOM)
|
|
208
221
|
.forEach(path => {
|
|
209
|
-
selected.push({ path, isExisting:
|
|
222
|
+
selected.push({ path, isExisting: existingByPath.has(path) });
|
|
210
223
|
});
|
|
211
224
|
|
|
212
225
|
if (selectedSet.has(AGENT_PATH_CHOICES.CUSTOM)) {
|