@supercorks/skills-installer 1.8.0 → 1.10.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/bin/install.js +18 -10
- package/lib/github-auth.js +72 -0
- package/lib/skills.js +4 -8
- package/lib/subagents.js +4 -8
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -50,6 +50,10 @@ function resolveInstallPath(path) {
|
|
|
50
50
|
return resolve(process.cwd(), path);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function isHomePath(path) {
|
|
54
|
+
return path === '~' || path.startsWith('~/');
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
/**
|
|
54
58
|
* Detect existing skill installations in common paths
|
|
55
59
|
* @returns {Promise<Array<{path: string, skillCount: number, skills: string[]}>>}
|
|
@@ -235,6 +239,8 @@ async function runSkillsInstall() {
|
|
|
235
239
|
async function runSkillsInstallForTarget(skills, existingInstalls, target) {
|
|
236
240
|
const { path: installPath, isExisting } = target;
|
|
237
241
|
const absoluteInstallPath = resolveInstallPath(installPath);
|
|
242
|
+
const gitDir = join(absoluteInstallPath, '.git');
|
|
243
|
+
const hasExistingRepo = existsSync(gitDir);
|
|
238
244
|
|
|
239
245
|
// Get currently installed skills if managing existing installation
|
|
240
246
|
let installedSkills = [];
|
|
@@ -243,17 +249,17 @@ async function runSkillsInstallForTarget(skills, existingInstalls, target) {
|
|
|
243
249
|
installedSkills = existingInstall?.skills || [];
|
|
244
250
|
} else {
|
|
245
251
|
// Check if manually entered path has an existing installation
|
|
246
|
-
|
|
247
|
-
if (existsSync(gitDir)) {
|
|
252
|
+
if (hasExistingRepo) {
|
|
248
253
|
try {
|
|
249
254
|
installedSkills = await listCheckedOutSkills(absoluteInstallPath);
|
|
250
255
|
} catch {
|
|
251
|
-
// If
|
|
256
|
+
// If repo exists but sparse-checkout can't be read, still treat as manage mode.
|
|
257
|
+
// Prompt will default to selecting all skills.
|
|
252
258
|
}
|
|
253
259
|
}
|
|
254
260
|
}
|
|
255
261
|
|
|
256
|
-
const isManageMode = installedSkills.length > 0;
|
|
262
|
+
const isManageMode = isExisting || hasExistingRepo || installedSkills.length > 0;
|
|
257
263
|
|
|
258
264
|
// Check for updates if in manage mode
|
|
259
265
|
let skillsNeedingUpdate = new Set();
|
|
@@ -274,7 +280,7 @@ async function runSkillsInstallForTarget(skills, existingInstalls, target) {
|
|
|
274
280
|
// Ask about .gitignore (only for fresh installs and if not already in .gitignore)
|
|
275
281
|
let shouldGitignore = false;
|
|
276
282
|
const gitignorePath = resolveInstallPath('.gitignore');
|
|
277
|
-
if (!isManageMode && !isInGitignore(gitignorePath, installPath)) {
|
|
283
|
+
if (!isManageMode && !isHomePath(installPath) && !isInGitignore(gitignorePath, installPath)) {
|
|
278
284
|
shouldGitignore = await promptGitignore(installPath);
|
|
279
285
|
}
|
|
280
286
|
|
|
@@ -387,6 +393,8 @@ async function runSubagentsInstall() {
|
|
|
387
393
|
async function runSubagentsInstallForTarget(subagents, existingInstalls, target) {
|
|
388
394
|
const { path: installPath, isExisting } = target;
|
|
389
395
|
const absoluteInstallPath = resolveInstallPath(installPath);
|
|
396
|
+
const gitDir = join(absoluteInstallPath, '.git');
|
|
397
|
+
const hasExistingRepo = existsSync(gitDir);
|
|
390
398
|
|
|
391
399
|
// Get currently installed subagents if managing existing installation
|
|
392
400
|
let installedAgents = [];
|
|
@@ -395,17 +403,17 @@ async function runSubagentsInstallForTarget(subagents, existingInstalls, target)
|
|
|
395
403
|
installedAgents = existingInstall?.agents || [];
|
|
396
404
|
} else {
|
|
397
405
|
// Check if manually entered path has an existing installation
|
|
398
|
-
|
|
399
|
-
if (existsSync(gitDir)) {
|
|
406
|
+
if (hasExistingRepo) {
|
|
400
407
|
try {
|
|
401
408
|
installedAgents = await listCheckedOutSubagents(absoluteInstallPath);
|
|
402
409
|
} catch {
|
|
403
|
-
// If
|
|
410
|
+
// If repo exists but sparse-checkout can't be read, still treat as manage mode.
|
|
411
|
+
// Prompt will default to selecting all subagents.
|
|
404
412
|
}
|
|
405
413
|
}
|
|
406
414
|
}
|
|
407
415
|
|
|
408
|
-
const isManageMode = installedAgents.length > 0;
|
|
416
|
+
const isManageMode = isExisting || hasExistingRepo || installedAgents.length > 0;
|
|
409
417
|
|
|
410
418
|
// Check for updates if in manage mode
|
|
411
419
|
let subagentsNeedingUpdate = new Set();
|
|
@@ -426,7 +434,7 @@ async function runSubagentsInstallForTarget(subagents, existingInstalls, target)
|
|
|
426
434
|
// Ask about .gitignore (only for fresh installs and if not already in .gitignore)
|
|
427
435
|
let shouldGitignore = false;
|
|
428
436
|
const gitignorePath = resolveInstallPath('.gitignore');
|
|
429
|
-
if (!isManageMode && !isInGitignore(gitignorePath, installPath)) {
|
|
437
|
+
if (!isManageMode && !isHomePath(installPath) && !isInGitignore(gitignorePath, installPath)) {
|
|
430
438
|
shouldGitignore = await promptGitignore(installPath);
|
|
431
439
|
}
|
|
432
440
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for authenticated GitHub API requests.
|
|
3
|
+
* Uses env tokens first, then falls back to `gh auth token` when available.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from 'child_process';
|
|
7
|
+
|
|
8
|
+
let cachedToken = '';
|
|
9
|
+
let tokenResolved = false;
|
|
10
|
+
|
|
11
|
+
function normalizeToken(raw) {
|
|
12
|
+
if (!raw || typeof raw !== 'string') return '';
|
|
13
|
+
return raw.trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readTokenFromGhCli() {
|
|
17
|
+
try {
|
|
18
|
+
const token = execFileSync('gh', ['auth', 'token'], {
|
|
19
|
+
encoding: 'utf-8',
|
|
20
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
21
|
+
});
|
|
22
|
+
return normalizeToken(token);
|
|
23
|
+
} catch {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns a GitHub token if available.
|
|
30
|
+
* Resolution order: GITHUB_TOKEN -> GH_TOKEN -> gh auth token.
|
|
31
|
+
* Value is cached for process lifetime.
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
export function getGitHubAuthToken() {
|
|
35
|
+
if (tokenResolved) return cachedToken;
|
|
36
|
+
|
|
37
|
+
tokenResolved = true;
|
|
38
|
+
cachedToken =
|
|
39
|
+
normalizeToken(process.env.GITHUB_TOKEN) ||
|
|
40
|
+
normalizeToken(process.env.GH_TOKEN) ||
|
|
41
|
+
readTokenFromGhCli() ||
|
|
42
|
+
'';
|
|
43
|
+
|
|
44
|
+
return cachedToken;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build GitHub API headers with optional auth.
|
|
49
|
+
* @param {string} [userAgent='@supercorks/skills-installer']
|
|
50
|
+
* @returns {Record<string, string>}
|
|
51
|
+
*/
|
|
52
|
+
export function getGitHubHeaders(userAgent = '@supercorks/skills-installer') {
|
|
53
|
+
const headers = {
|
|
54
|
+
Accept: 'application/vnd.github.v3+json',
|
|
55
|
+
'User-Agent': userAgent
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const token = getGitHubAuthToken();
|
|
59
|
+
if (token) {
|
|
60
|
+
headers.Authorization = `Bearer ${token}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return headers;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Test-only cache reset helper.
|
|
68
|
+
*/
|
|
69
|
+
export function __resetGitHubAuthCacheForTests() {
|
|
70
|
+
cachedToken = '';
|
|
71
|
+
tokenResolved = false;
|
|
72
|
+
}
|
package/lib/skills.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Fetch and parse available skills from the GitHub repository
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { getGitHubHeaders } from './github-auth.js';
|
|
6
|
+
|
|
5
7
|
const REPO_OWNER = 'supercorks';
|
|
6
8
|
const REPO_NAME = 'agent-skills';
|
|
7
9
|
const GITHUB_API = 'https://api.github.com';
|
|
@@ -26,10 +28,7 @@ export async function fetchAvailableSkills() {
|
|
|
26
28
|
const repoUrl = `${GITHUB_API}/repos/${REPO_OWNER}/${REPO_NAME}/contents`;
|
|
27
29
|
|
|
28
30
|
const response = await fetch(repoUrl, {
|
|
29
|
-
headers:
|
|
30
|
-
'Accept': 'application/vnd.github.v3+json',
|
|
31
|
-
'User-Agent': '@supercorks/skills-installer'
|
|
32
|
-
}
|
|
31
|
+
headers: getGitHubHeaders()
|
|
33
32
|
});
|
|
34
33
|
|
|
35
34
|
if (!response.ok) {
|
|
@@ -61,10 +60,7 @@ export async function fetchSkillMetadata(skillFolder) {
|
|
|
61
60
|
|
|
62
61
|
try {
|
|
63
62
|
const response = await fetch(skillMdUrl, {
|
|
64
|
-
headers:
|
|
65
|
-
'Accept': 'application/vnd.github.v3+json',
|
|
66
|
-
'User-Agent': '@supercorks/skills-installer'
|
|
67
|
-
}
|
|
63
|
+
headers: getGitHubHeaders()
|
|
68
64
|
});
|
|
69
65
|
|
|
70
66
|
if (!response.ok) {
|
package/lib/subagents.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Fetch and parse available subagents from the GitHub repository
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { getGitHubHeaders } from './github-auth.js';
|
|
6
|
+
|
|
5
7
|
const SUBAGENTS_REPO_OWNER = 'supercorks';
|
|
6
8
|
const SUBAGENTS_REPO_NAME = 'subagents';
|
|
7
9
|
const GITHUB_API = 'https://api.github.com';
|
|
@@ -24,10 +26,7 @@ export async function fetchAvailableSubagents() {
|
|
|
24
26
|
const repoUrl = `${GITHUB_API}/repos/${SUBAGENTS_REPO_OWNER}/${SUBAGENTS_REPO_NAME}/contents`;
|
|
25
27
|
|
|
26
28
|
const response = await fetch(repoUrl, {
|
|
27
|
-
headers:
|
|
28
|
-
'Accept': 'application/vnd.github.v3+json',
|
|
29
|
-
'User-Agent': '@supercorks/skills-installer'
|
|
30
|
-
}
|
|
29
|
+
headers: getGitHubHeaders()
|
|
31
30
|
});
|
|
32
31
|
|
|
33
32
|
if (!response.ok) {
|
|
@@ -59,10 +58,7 @@ export async function fetchSubagentMetadata(filename) {
|
|
|
59
58
|
|
|
60
59
|
try {
|
|
61
60
|
const response = await fetch(fileUrl, {
|
|
62
|
-
headers:
|
|
63
|
-
'Accept': 'application/vnd.github.v3+json',
|
|
64
|
-
'User-Agent': '@supercorks/skills-installer'
|
|
65
|
-
}
|
|
61
|
+
headers: getGitHubHeaders()
|
|
66
62
|
});
|
|
67
63
|
|
|
68
64
|
if (!response.ok) {
|