@supercorks/skills-installer 1.5.0 ā 1.7.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 +3 -3
- package/bin/install.js +53 -10
- package/lib/git.js +9 -1
- package/lib/prompts.js +78 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ npx @supercorks/skills-installer install
|
|
|
16
16
|
|
|
17
17
|
## What it does
|
|
18
18
|
|
|
19
|
-
1. **Choose installation path** - Select where skills should be installed:
|
|
20
|
-
- `.github/skills/` (
|
|
21
|
-
-
|
|
19
|
+
1. **Choose installation path(s)** - Select one or more locations where skills should be installed:
|
|
20
|
+
- `.github/skills/` (Copilot)
|
|
21
|
+
- `~/.codex/skills/` (Codex)
|
|
22
22
|
- `.claude/skills/` (Claude)
|
|
23
23
|
- Custom path of your choice
|
|
24
24
|
|
package/bin/install.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { existsSync, appendFileSync, readFileSync, writeFileSync } from 'fs';
|
|
11
11
|
import { resolve, join } from 'path';
|
|
12
|
+
import { homedir } from 'os';
|
|
12
13
|
import {
|
|
13
14
|
promptInstallType,
|
|
14
15
|
promptInstallPath,
|
|
@@ -40,8 +41,14 @@ const require = createRequire(import.meta.url);
|
|
|
40
41
|
const { version: VERSION } = require('../package.json');
|
|
41
42
|
|
|
42
43
|
// Common installation paths to check for existing installations
|
|
43
|
-
const SKILL_PATHS = ['.github/skills/', '
|
|
44
|
-
const AGENT_PATHS = ['.github/agents/', '.claude/agents/'];
|
|
44
|
+
const SKILL_PATHS = ['.github/skills/', '~/.codex/skills/', '.claude/skills/'];
|
|
45
|
+
const AGENT_PATHS = ['.github/agents/', '.agents/agents/', '.claude/agents/'];
|
|
46
|
+
|
|
47
|
+
function resolveInstallPath(path) {
|
|
48
|
+
if (path === '~') return homedir();
|
|
49
|
+
if (path.startsWith('~/')) return resolve(homedir(), path.slice(2));
|
|
50
|
+
return resolve(process.cwd(), path);
|
|
51
|
+
}
|
|
45
52
|
|
|
46
53
|
/**
|
|
47
54
|
* Detect existing skill installations in common paths
|
|
@@ -51,7 +58,7 @@ async function detectExistingSkillInstallations() {
|
|
|
51
58
|
const installations = [];
|
|
52
59
|
|
|
53
60
|
for (const path of SKILL_PATHS) {
|
|
54
|
-
const absolutePath =
|
|
61
|
+
const absolutePath = resolveInstallPath(path);
|
|
55
62
|
const gitDir = join(absolutePath, '.git');
|
|
56
63
|
|
|
57
64
|
if (existsSync(gitDir)) {
|
|
@@ -79,7 +86,7 @@ async function detectExistingAgentInstallations() {
|
|
|
79
86
|
const installations = [];
|
|
80
87
|
|
|
81
88
|
for (const path of AGENT_PATHS) {
|
|
82
|
-
const absolutePath =
|
|
89
|
+
const absolutePath = resolveInstallPath(path);
|
|
83
90
|
const gitDir = join(absolutePath, '.git');
|
|
84
91
|
|
|
85
92
|
if (existsSync(gitDir)) {
|
|
@@ -208,8 +215,26 @@ async function runSkillsInstall() {
|
|
|
208
215
|
const existingInstalls = await detectExistingSkillInstallations();
|
|
209
216
|
|
|
210
217
|
// Ask where to install (showing existing installations if any)
|
|
211
|
-
const
|
|
212
|
-
|
|
218
|
+
const installTargets = await promptInstallPath(existingInstalls);
|
|
219
|
+
|
|
220
|
+
for (let i = 0; i < installTargets.length; i++) {
|
|
221
|
+
const target = installTargets[i];
|
|
222
|
+
if (installTargets.length > 1) {
|
|
223
|
+
console.log(`\nš Skills target ${i + 1}/${installTargets.length}: ${target.path}`);
|
|
224
|
+
}
|
|
225
|
+
await runSkillsInstallForTarget(skills, existingInstalls, target);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Install/update skills for a specific target path
|
|
231
|
+
* @param {Array<{name: string, description: string, folder: string}>} skills
|
|
232
|
+
* @param {Array<{path: string, skillCount: number, skills: string[]}>} existingInstalls
|
|
233
|
+
* @param {{path: string, isExisting: boolean}} target
|
|
234
|
+
*/
|
|
235
|
+
async function runSkillsInstallForTarget(skills, existingInstalls, target) {
|
|
236
|
+
const { path: installPath, isExisting } = target;
|
|
237
|
+
const absoluteInstallPath = resolveInstallPath(installPath);
|
|
213
238
|
|
|
214
239
|
// Get currently installed skills if managing existing installation
|
|
215
240
|
let installedSkills = [];
|
|
@@ -248,7 +273,7 @@ async function runSkillsInstall() {
|
|
|
248
273
|
|
|
249
274
|
// Ask about .gitignore (only for fresh installs and if not already in .gitignore)
|
|
250
275
|
let shouldGitignore = false;
|
|
251
|
-
const gitignorePath =
|
|
276
|
+
const gitignorePath = resolveInstallPath('.gitignore');
|
|
252
277
|
if (!isManageMode && !isInGitignore(gitignorePath, installPath)) {
|
|
253
278
|
shouldGitignore = await promptGitignore(installPath);
|
|
254
279
|
}
|
|
@@ -337,8 +362,26 @@ async function runSubagentsInstall() {
|
|
|
337
362
|
const existingInstalls = await detectExistingAgentInstallations();
|
|
338
363
|
|
|
339
364
|
// Ask where to install (showing existing installations if any)
|
|
340
|
-
const
|
|
341
|
-
|
|
365
|
+
const installTargets = await promptAgentInstallPath(existingInstalls);
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < installTargets.length; i++) {
|
|
368
|
+
const target = installTargets[i];
|
|
369
|
+
if (installTargets.length > 1) {
|
|
370
|
+
console.log(`\nš Subagents target ${i + 1}/${installTargets.length}: ${target.path}`);
|
|
371
|
+
}
|
|
372
|
+
await runSubagentsInstallForTarget(subagents, existingInstalls, target);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Install/update subagents for a specific target path
|
|
378
|
+
* @param {Array<{name: string, description: string, filename: string}>} subagents
|
|
379
|
+
* @param {Array<{path: string, agentCount: number, agents: string[]}>} existingInstalls
|
|
380
|
+
* @param {{path: string, isExisting: boolean}} target
|
|
381
|
+
*/
|
|
382
|
+
async function runSubagentsInstallForTarget(subagents, existingInstalls, target) {
|
|
383
|
+
const { path: installPath, isExisting } = target;
|
|
384
|
+
const absoluteInstallPath = resolveInstallPath(installPath);
|
|
342
385
|
|
|
343
386
|
// Get currently installed subagents if managing existing installation
|
|
344
387
|
let installedAgents = [];
|
|
@@ -377,7 +420,7 @@ async function runSubagentsInstall() {
|
|
|
377
420
|
|
|
378
421
|
// Ask about .gitignore (only for fresh installs and if not already in .gitignore)
|
|
379
422
|
let shouldGitignore = false;
|
|
380
|
-
const gitignorePath =
|
|
423
|
+
const gitignorePath = resolveInstallPath('.gitignore');
|
|
381
424
|
if (!isManageMode && !isInGitignore(gitignorePath, installPath)) {
|
|
382
425
|
shouldGitignore = await promptGitignore(installPath);
|
|
383
426
|
}
|
package/lib/git.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { execSync, spawn } from 'child_process';
|
|
6
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, appendFileSync } from 'fs';
|
|
6
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, appendFileSync, readdirSync } from 'fs';
|
|
7
7
|
import { join, resolve } from 'path';
|
|
8
8
|
import { getRepoUrl } from './skills.js';
|
|
9
9
|
import { getSubagentsRepoUrl } from './subagents.js';
|
|
@@ -71,6 +71,10 @@ export async function sparseCloneSkills(targetPath, skillFolders, onProgress = (
|
|
|
71
71
|
if (existsSync(gitDir)) {
|
|
72
72
|
throw new Error(`Directory "${targetPath}" already contains a git repository. Please remove it first or choose a different path.`);
|
|
73
73
|
}
|
|
74
|
+
const entries = readdirSync(absolutePath).filter(name => name !== '.DS_Store');
|
|
75
|
+
if (entries.length > 0) {
|
|
76
|
+
throw new Error(`Directory "${targetPath}" already exists and is not empty. Choose an empty directory or an existing managed installation.`);
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
// Create target directory
|
|
@@ -328,6 +332,10 @@ export async function sparseCloneSubagents(targetPath, agentFilenames, onProgres
|
|
|
328
332
|
if (existsSync(gitDir)) {
|
|
329
333
|
throw new Error(`Directory "${targetPath}" already contains a git repository. Please remove it first or choose a different path.`);
|
|
330
334
|
}
|
|
335
|
+
const entries = readdirSync(absolutePath).filter(name => name !== '.DS_Store');
|
|
336
|
+
if (entries.length > 0) {
|
|
337
|
+
throw new Error(`Directory "${targetPath}" already exists and is not empty. Choose an empty directory or an existing managed installation.`);
|
|
338
|
+
}
|
|
331
339
|
}
|
|
332
340
|
|
|
333
341
|
// Create target directory
|
package/lib/prompts.js
CHANGED
|
@@ -7,13 +7,14 @@ import * as readline from 'readline';
|
|
|
7
7
|
|
|
8
8
|
const SKILL_PATH_CHOICES = {
|
|
9
9
|
GITHUB: '.github/skills/',
|
|
10
|
-
|
|
10
|
+
CODEX_HOME: '~/.codex/skills/',
|
|
11
11
|
CLAUDE: '.claude/skills/',
|
|
12
12
|
CUSTOM: '__custom__'
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
const AGENT_PATH_CHOICES = {
|
|
16
16
|
GITHUB: '.github/agents/',
|
|
17
|
+
CODEX: '.agents/agents/',
|
|
17
18
|
CLAUDE: '.claude/agents/',
|
|
18
19
|
CUSTOM: '__custom__'
|
|
19
20
|
};
|
|
@@ -58,48 +59,67 @@ export async function promptInstallType() {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
|
-
* Prompt user to select installation
|
|
62
|
+
* Prompt user to select one or more installation paths, showing existing installations
|
|
62
63
|
* @param {Array<{path: string, skillCount: number}>} existingInstalls - Detected existing installations
|
|
63
|
-
* @returns {Promise<{path: string, isExisting: boolean}
|
|
64
|
+
* @returns {Promise<Array<{path: string, isExisting: boolean}>>} Selected paths and whether each is existing
|
|
64
65
|
*/
|
|
65
66
|
export async function promptInstallPath(existingInstalls = []) {
|
|
66
67
|
const choices = [];
|
|
68
|
+
const existingPaths = existingInstalls.map(i => i.path);
|
|
67
69
|
|
|
68
70
|
// Add existing installations at the top
|
|
69
71
|
if (existingInstalls.length > 0) {
|
|
70
72
|
existingInstalls.forEach(install => {
|
|
71
73
|
choices.push({
|
|
72
74
|
name: `${install.path} (${install.skillCount} skill${install.skillCount !== 1 ? 's' : ''} installed)`,
|
|
73
|
-
value:
|
|
75
|
+
value: install.path
|
|
74
76
|
});
|
|
75
77
|
});
|
|
76
78
|
choices.push(new inquirer.Separator('āā New installation āā'));
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
// Standard path options
|
|
80
|
-
const standardPaths = [
|
|
81
|
-
|
|
82
|
+
const standardPaths = [
|
|
83
|
+
{ path: SKILL_PATH_CHOICES.GITHUB, label: `${SKILL_PATH_CHOICES.GITHUB} (Copilot)` },
|
|
84
|
+
{ path: SKILL_PATH_CHOICES.CODEX_HOME, label: `${SKILL_PATH_CHOICES.CODEX_HOME} (Codex)` },
|
|
85
|
+
{ path: SKILL_PATH_CHOICES.CLAUDE, label: `${SKILL_PATH_CHOICES.CLAUDE} (Claude)` }
|
|
86
|
+
];
|
|
82
87
|
|
|
83
|
-
standardPaths.forEach(path => {
|
|
88
|
+
standardPaths.forEach(({ path, label }) => {
|
|
84
89
|
if (!existingPaths.includes(path)) {
|
|
85
|
-
choices.push({ name:
|
|
90
|
+
choices.push({ name: label, value: path });
|
|
86
91
|
}
|
|
87
92
|
});
|
|
88
93
|
|
|
89
|
-
choices.push({ name: 'Custom path...', value:
|
|
94
|
+
choices.push({ name: 'Custom path...', value: SKILL_PATH_CHOICES.CUSTOM });
|
|
90
95
|
|
|
91
|
-
const {
|
|
96
|
+
const { pathChoices } = await inquirer.prompt([
|
|
92
97
|
{
|
|
93
|
-
type: '
|
|
94
|
-
name: '
|
|
98
|
+
type: 'checkbox',
|
|
99
|
+
name: 'pathChoices',
|
|
95
100
|
message: existingInstalls.length > 0
|
|
96
|
-
? 'Select
|
|
97
|
-
: 'Where would you like to install the skills?',
|
|
98
|
-
choices
|
|
101
|
+
? 'Select one or more installations to manage, or choose new locations:'
|
|
102
|
+
: 'Where would you like to install the skills? (Select one or more)',
|
|
103
|
+
choices,
|
|
104
|
+
validate: (input) => {
|
|
105
|
+
if (!input || input.length === 0) {
|
|
106
|
+
return 'Please select at least one installation path';
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
99
110
|
}
|
|
100
111
|
]);
|
|
101
112
|
|
|
102
|
-
|
|
113
|
+
const selected = [];
|
|
114
|
+
const selectedSet = new Set(pathChoices);
|
|
115
|
+
|
|
116
|
+
pathChoices
|
|
117
|
+
.filter(path => path !== SKILL_PATH_CHOICES.CUSTOM)
|
|
118
|
+
.forEach(path => {
|
|
119
|
+
selected.push({ path, isExisting: existingPaths.includes(path) });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (selectedSet.has(SKILL_PATH_CHOICES.CUSTOM)) {
|
|
103
123
|
const { customPath } = await inquirer.prompt([
|
|
104
124
|
{
|
|
105
125
|
type: 'input',
|
|
@@ -113,55 +133,76 @@ export async function promptInstallPath(existingInstalls = []) {
|
|
|
113
133
|
}
|
|
114
134
|
}
|
|
115
135
|
]);
|
|
116
|
-
|
|
136
|
+
selected.push({ path: customPath.trim(), isExisting: false });
|
|
117
137
|
}
|
|
118
138
|
|
|
119
|
-
|
|
139
|
+
const deduped = new Map();
|
|
140
|
+
selected.forEach(entry => deduped.set(entry.path, entry));
|
|
141
|
+
return Array.from(deduped.values());
|
|
120
142
|
}
|
|
121
143
|
|
|
122
144
|
/**
|
|
123
|
-
* Prompt user to select subagent installation
|
|
145
|
+
* Prompt user to select one or more subagent installation paths
|
|
124
146
|
* @param {Array<{path: string, agentCount: number}>} existingInstalls - Detected existing installations
|
|
125
|
-
* @returns {Promise<{path: string, isExisting: boolean}
|
|
147
|
+
* @returns {Promise<Array<{path: string, isExisting: boolean}>>} Selected paths and whether each is existing
|
|
126
148
|
*/
|
|
127
149
|
export async function promptAgentInstallPath(existingInstalls = []) {
|
|
128
150
|
const choices = [];
|
|
151
|
+
const existingPaths = existingInstalls.map(i => i.path);
|
|
129
152
|
|
|
130
153
|
// Add existing installations at the top
|
|
131
154
|
if (existingInstalls.length > 0) {
|
|
132
155
|
existingInstalls.forEach(install => {
|
|
133
156
|
choices.push({
|
|
134
157
|
name: `${install.path} (${install.agentCount} agent${install.agentCount !== 1 ? 's' : ''} installed)`,
|
|
135
|
-
value:
|
|
158
|
+
value: install.path
|
|
136
159
|
});
|
|
137
160
|
});
|
|
138
161
|
choices.push(new inquirer.Separator('āā New installation āā'));
|
|
139
162
|
}
|
|
140
163
|
|
|
141
164
|
// Standard path options
|
|
142
|
-
const standardPaths = [
|
|
143
|
-
|
|
165
|
+
const standardPaths = [
|
|
166
|
+
{ path: AGENT_PATH_CHOICES.GITHUB, label: `${AGENT_PATH_CHOICES.GITHUB} (Copilot)` },
|
|
167
|
+
{ path: AGENT_PATH_CHOICES.CODEX, label: `${AGENT_PATH_CHOICES.CODEX} (Codex)` },
|
|
168
|
+
{ path: AGENT_PATH_CHOICES.CLAUDE, label: `${AGENT_PATH_CHOICES.CLAUDE} (Claude)` }
|
|
169
|
+
];
|
|
144
170
|
|
|
145
|
-
standardPaths.forEach(path => {
|
|
171
|
+
standardPaths.forEach(({ path, label }) => {
|
|
146
172
|
if (!existingPaths.includes(path)) {
|
|
147
|
-
choices.push({ name:
|
|
173
|
+
choices.push({ name: label, value: path });
|
|
148
174
|
}
|
|
149
175
|
});
|
|
150
176
|
|
|
151
|
-
choices.push({ name: 'Custom path...', value:
|
|
177
|
+
choices.push({ name: 'Custom path...', value: AGENT_PATH_CHOICES.CUSTOM });
|
|
152
178
|
|
|
153
|
-
const {
|
|
179
|
+
const { pathChoices } = await inquirer.prompt([
|
|
154
180
|
{
|
|
155
|
-
type: '
|
|
156
|
-
name: '
|
|
181
|
+
type: 'checkbox',
|
|
182
|
+
name: 'pathChoices',
|
|
157
183
|
message: existingInstalls.length > 0
|
|
158
|
-
? 'Select
|
|
159
|
-
: 'Where would you like to install the subagents?',
|
|
160
|
-
choices
|
|
184
|
+
? 'Select one or more installations to manage, or choose new locations:'
|
|
185
|
+
: 'Where would you like to install the subagents? (Select one or more)',
|
|
186
|
+
choices,
|
|
187
|
+
validate: (input) => {
|
|
188
|
+
if (!input || input.length === 0) {
|
|
189
|
+
return 'Please select at least one installation path';
|
|
190
|
+
}
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
161
193
|
}
|
|
162
194
|
]);
|
|
163
195
|
|
|
164
|
-
|
|
196
|
+
const selected = [];
|
|
197
|
+
const selectedSet = new Set(pathChoices);
|
|
198
|
+
|
|
199
|
+
pathChoices
|
|
200
|
+
.filter(path => path !== AGENT_PATH_CHOICES.CUSTOM)
|
|
201
|
+
.forEach(path => {
|
|
202
|
+
selected.push({ path, isExisting: existingPaths.includes(path) });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (selectedSet.has(AGENT_PATH_CHOICES.CUSTOM)) {
|
|
165
206
|
const { customPath } = await inquirer.prompt([
|
|
166
207
|
{
|
|
167
208
|
type: 'input',
|
|
@@ -175,10 +216,12 @@ export async function promptAgentInstallPath(existingInstalls = []) {
|
|
|
175
216
|
}
|
|
176
217
|
}
|
|
177
218
|
]);
|
|
178
|
-
|
|
219
|
+
selected.push({ path: customPath.trim(), isExisting: false });
|
|
179
220
|
}
|
|
180
221
|
|
|
181
|
-
|
|
222
|
+
const deduped = new Map();
|
|
223
|
+
selected.forEach(entry => deduped.set(entry.path, entry));
|
|
224
|
+
return Array.from(deduped.values());
|
|
182
225
|
}
|
|
183
226
|
|
|
184
227
|
/**
|