skillfish 1.0.33 → 1.0.34
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 +20 -0
- package/dist/lib/auth.d.ts +20 -0
- package/dist/lib/auth.js +52 -0
- package/dist/lib/github.js +16 -5
- package/dist/lib/installer.js +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -290,6 +290,26 @@ Your submission will be reviewed and added to [skill.fish](https://skill.fish) a
|
|
|
290
290
|
|
|
291
291
|
---
|
|
292
292
|
|
|
293
|
+
## Private Repositories
|
|
294
|
+
|
|
295
|
+
skillfish installs from private repositories when a GitHub token is available. Tokens are resolved in this order:
|
|
296
|
+
|
|
297
|
+
1. `SKILLFISH_GITHUB_TOKEN` - skillfish-specific override
|
|
298
|
+
2. `GITHUB_TOKEN` - standard
|
|
299
|
+
3. `GH_TOKEN` - GitHub CLI standard
|
|
300
|
+
4. `gh auth token` - falls back to the GitHub CLI if you're logged in
|
|
301
|
+
|
|
302
|
+
The simplest setup is `gh auth login`. Otherwise, export a token before running skillfish:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
export GITHUB_TOKEN=ghp_...
|
|
306
|
+
skillfish add owner/private-repo
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Public repos work with or without a token, but providing one raises the GitHub API rate limit from 60 to 5,000 requests per hour.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
293
313
|
## Non-Interactive Mode
|
|
294
314
|
|
|
295
315
|
All commands work without prompts for use in scripts, CI pipelines, and automation. Non-interactive mode activates when:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub authentication helpers for skillfish.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a GitHub token from the environment or the local `gh` CLI.
|
|
6
|
+
*
|
|
7
|
+
* Priority:
|
|
8
|
+
* 1. SKILLFISH_GITHUB_TOKEN
|
|
9
|
+
* 2. GITHUB_TOKEN
|
|
10
|
+
* 3. GH_TOKEN
|
|
11
|
+
* 4. `gh auth token` (if gh is on PATH and the user is logged in)
|
|
12
|
+
*
|
|
13
|
+
* Resolution happens once per process and the result is cached. Set env vars
|
|
14
|
+
* before the first call, or call `resetGitHubTokenCache()` to re-resolve.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getGitHubToken(): string | undefined;
|
|
17
|
+
/** Returns true when a GitHub token is available. */
|
|
18
|
+
export declare function hasGitHubToken(): boolean;
|
|
19
|
+
/** Reset the per-process token cache. Intended for use in tests only. */
|
|
20
|
+
export declare function resetGitHubTokenCache(): void;
|
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub authentication helpers for skillfish.
|
|
3
|
+
*/
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
|
+
// Module-scoped cache: undefined = not yet resolved, null = resolved to no token
|
|
6
|
+
let cachedToken = undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a GitHub token from the environment or the local `gh` CLI.
|
|
9
|
+
*
|
|
10
|
+
* Priority:
|
|
11
|
+
* 1. SKILLFISH_GITHUB_TOKEN
|
|
12
|
+
* 2. GITHUB_TOKEN
|
|
13
|
+
* 3. GH_TOKEN
|
|
14
|
+
* 4. `gh auth token` (if gh is on PATH and the user is logged in)
|
|
15
|
+
*
|
|
16
|
+
* Resolution happens once per process and the result is cached. Set env vars
|
|
17
|
+
* before the first call, or call `resetGitHubTokenCache()` to re-resolve.
|
|
18
|
+
*/
|
|
19
|
+
export function getGitHubToken() {
|
|
20
|
+
if (cachedToken !== undefined) {
|
|
21
|
+
return cachedToken ?? undefined;
|
|
22
|
+
}
|
|
23
|
+
const fromEnv = process.env.SKILLFISH_GITHUB_TOKEN?.trim() ||
|
|
24
|
+
process.env.GITHUB_TOKEN?.trim() ||
|
|
25
|
+
process.env.GH_TOKEN?.trim();
|
|
26
|
+
if (fromEnv) {
|
|
27
|
+
cachedToken = fromEnv;
|
|
28
|
+
return cachedToken;
|
|
29
|
+
}
|
|
30
|
+
// Fallback: ask the gh CLI for the active token (~50 ms, silent on failure)
|
|
31
|
+
try {
|
|
32
|
+
const raw = execFileSync('gh', ['auth', 'token'], {
|
|
33
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
34
|
+
timeout: 2000,
|
|
35
|
+
})
|
|
36
|
+
.toString()
|
|
37
|
+
.trim();
|
|
38
|
+
cachedToken = raw || null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
cachedToken = null;
|
|
42
|
+
}
|
|
43
|
+
return cachedToken ?? undefined;
|
|
44
|
+
}
|
|
45
|
+
/** Returns true when a GitHub token is available. */
|
|
46
|
+
export function hasGitHubToken() {
|
|
47
|
+
return getGitHubToken() !== undefined;
|
|
48
|
+
}
|
|
49
|
+
/** Reset the per-process token cache. Intended for use in tests only. */
|
|
50
|
+
export function resetGitHubTokenCache() {
|
|
51
|
+
cachedToken = undefined;
|
|
52
|
+
}
|
package/dist/lib/github.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { isGitTreeResponse, extractSkillPaths } from '../utils.js';
|
|
5
5
|
import { fetchWithRetry } from './http.js';
|
|
6
|
+
import { getGitHubToken, hasGitHubToken } from './auth.js';
|
|
6
7
|
export const SKILL_FILENAME = 'SKILL.md';
|
|
7
8
|
// === Helper Functions ===
|
|
8
9
|
/**
|
|
@@ -43,7 +44,10 @@ export class RepoNotFoundError extends Error {
|
|
|
43
44
|
owner;
|
|
44
45
|
repo;
|
|
45
46
|
constructor(owner, repo) {
|
|
46
|
-
|
|
47
|
+
const hint = hasGitHubToken()
|
|
48
|
+
? ''
|
|
49
|
+
: ', or set GITHUB_TOKEN if this is a private repository (or run `gh auth login`)';
|
|
50
|
+
super(`Repository not found: ${owner}/${repo}. Check the owner/repo name${hint}.`);
|
|
47
51
|
this.owner = owner;
|
|
48
52
|
this.repo = repo;
|
|
49
53
|
this.name = 'RepoNotFoundError';
|
|
@@ -68,6 +72,13 @@ export class GitHubApiError extends Error {
|
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
// === Helper Functions ===
|
|
75
|
+
function githubHeaders() {
|
|
76
|
+
const headers = { 'User-Agent': 'skillfish' };
|
|
77
|
+
const token = getGitHubToken();
|
|
78
|
+
if (token)
|
|
79
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
80
|
+
return headers;
|
|
81
|
+
}
|
|
71
82
|
/**
|
|
72
83
|
* Check if a response indicates rate limiting and throw RateLimitError if so.
|
|
73
84
|
* @throws {RateLimitError} When rate limit is exceeded
|
|
@@ -111,7 +122,7 @@ function wrapApiError(err) {
|
|
|
111
122
|
* @throws {NetworkError} On network errors
|
|
112
123
|
*/
|
|
113
124
|
export async function fetchDefaultBranch(owner, repo) {
|
|
114
|
-
const headers =
|
|
125
|
+
const headers = githubHeaders();
|
|
115
126
|
const url = `https://api.github.com/repos/${owner}/${repo}`;
|
|
116
127
|
try {
|
|
117
128
|
const res = await fetchWithRetry(url, { headers });
|
|
@@ -137,7 +148,7 @@ export async function fetchDefaultBranch(owner, repo) {
|
|
|
137
148
|
* Uses raw.githubusercontent.com which is not rate-limited like the API.
|
|
138
149
|
*/
|
|
139
150
|
export async function fetchSkillMdContent(owner, repo, path, branch) {
|
|
140
|
-
const headers =
|
|
151
|
+
const headers = githubHeaders();
|
|
141
152
|
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`;
|
|
142
153
|
try {
|
|
143
154
|
const res = await fetchWithRetry(url, { headers }, 2);
|
|
@@ -159,7 +170,7 @@ export async function fetchSkillMdContent(owner, repo, path, branch) {
|
|
|
159
170
|
* @throws {GitHubApiError} When the API response format is unexpected
|
|
160
171
|
*/
|
|
161
172
|
export async function fetchTreeSha(owner, repo, branch) {
|
|
162
|
-
const headers =
|
|
173
|
+
const headers = githubHeaders();
|
|
163
174
|
const url = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}`;
|
|
164
175
|
try {
|
|
165
176
|
const res = await fetchWithRetry(url, { headers });
|
|
@@ -190,7 +201,7 @@ export async function fetchTreeSha(owner, repo, branch) {
|
|
|
190
201
|
* @throws {GitHubApiError} When the API response format is unexpected
|
|
191
202
|
*/
|
|
192
203
|
export async function fetchRecursiveTree(owner, repo, branch) {
|
|
193
|
-
const headers =
|
|
204
|
+
const headers = githubHeaders();
|
|
194
205
|
const url = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`;
|
|
195
206
|
try {
|
|
196
207
|
const res = await fetchWithRetry(url, { headers });
|
package/dist/lib/installer.js
CHANGED
|
@@ -8,6 +8,7 @@ import { join } from 'path';
|
|
|
8
8
|
import { randomUUID } from 'crypto';
|
|
9
9
|
import { downloadTemplate } from 'giget';
|
|
10
10
|
import { getAgentSkillDir } from './agents.js';
|
|
11
|
+
import { getGitHubToken, hasGitHubToken } from './auth.js';
|
|
11
12
|
import { SKILL_FILENAME } from './github.js';
|
|
12
13
|
import { writeManifest, MANIFEST_VERSION, } from './manifest.js';
|
|
13
14
|
/**
|
|
@@ -118,9 +119,11 @@ export async function installSkill(owner, repo, skillPath, skillName, agents, op
|
|
|
118
119
|
}
|
|
119
120
|
gigetSource = `${gigetSource}#${branch}`;
|
|
120
121
|
}
|
|
122
|
+
const githubToken = getGitHubToken();
|
|
121
123
|
await downloadTemplate(gigetSource, {
|
|
122
124
|
dir: tmpDir,
|
|
123
125
|
forceClean: true,
|
|
126
|
+
...(githubToken ? { auth: githubToken } : {}),
|
|
124
127
|
});
|
|
125
128
|
// Validate download
|
|
126
129
|
const skillMdPath = join(tmpDir, SKILL_FILENAME);
|
|
@@ -242,7 +245,10 @@ export async function installSkill(owner, repo, skillPath, skillName, agents, op
|
|
|
242
245
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
243
246
|
// Provide more helpful error messages for common failures
|
|
244
247
|
if (errMsg.includes('404') || errMsg.includes('Not Found')) {
|
|
245
|
-
|
|
248
|
+
const hint = hasGitHubToken()
|
|
249
|
+
? ''
|
|
250
|
+
: ' (set GITHUB_TOKEN if this is a private repository, or run `gh auth login`)';
|
|
251
|
+
result.failureReason = `Repository or path not found: ${owner}/${repo}${skillPath !== SKILL_FILENAME ? `/${skillPath}` : ''}${hint}`;
|
|
246
252
|
}
|
|
247
253
|
else {
|
|
248
254
|
result.failureReason = errMsg;
|
package/package.json
CHANGED