skillex 0.3.1 → 0.4.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/CHANGELOG.md +262 -1
- package/README.md +57 -10
- package/dist/auto-sync.d.ts +66 -0
- package/dist/auto-sync.js +91 -0
- package/dist/catalog.js +5 -29
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +247 -141
- package/dist/confirm.js +3 -1
- package/dist/direct-github.d.ts +60 -0
- package/dist/direct-github.js +177 -0
- package/dist/doctor.d.ts +31 -0
- package/dist/doctor.js +172 -0
- package/dist/downloader.d.ts +42 -0
- package/dist/downloader.js +41 -0
- package/dist/fs.d.ts +21 -1
- package/dist/fs.js +30 -3
- package/dist/http.d.ts +28 -7
- package/dist/http.js +143 -42
- package/dist/install.d.ts +23 -9
- package/dist/install.js +75 -348
- package/dist/lockfile.d.ts +46 -0
- package/dist/lockfile.js +169 -0
- package/dist/output.d.ts +11 -0
- package/dist/output.js +49 -0
- package/dist/recommended.d.ts +13 -0
- package/dist/recommended.js +21 -0
- package/dist/runner.js +9 -9
- package/dist/skill.d.ts +2 -0
- package/dist/skill.js +3 -0
- package/dist/sync.js +12 -9
- package/dist/types.d.ts +39 -0
- package/dist/types.js +28 -0
- package/dist/ui.js +1 -1
- package/dist/user-config.d.ts +5 -0
- package/dist/user-config.js +22 -1
- package/dist/web-ui.js +5 -0
- package/dist-ui/assets/CatalogPage-CbtMTkxd.js +1 -0
- package/dist-ui/assets/CatalogPage-W5MqylAz.css +1 -0
- package/dist-ui/assets/DoctorPage-oUZyX91t.js +1 -0
- package/dist-ui/assets/Skeleton-B_xm5L3P.js +1 -0
- package/dist-ui/assets/Skeleton-_Ooiw1nN.css +1 -0
- package/dist-ui/assets/SkillDetailPage-5JHQLq3q.js +1 -0
- package/dist-ui/assets/SkillDetailPage-CBAaWpcc.css +1 -0
- package/dist-ui/assets/{index-UBECch6X.css → index-CWm7zQTg.css} +1 -1
- package/dist-ui/assets/index-I0b-syhc.js +26 -0
- package/dist-ui/assets/recommended-D_i10hwH.js +1 -0
- package/dist-ui/index.html +2 -2
- package/package.json +2 -2
- package/dist-ui/assets/CatalogPage-B_qic36n.js +0 -1
- package/dist-ui/assets/SkillDetailPage-BJ3onKk4.js +0 -1
- package/dist-ui/assets/index-DN-z--cR.js +0 -25
package/dist/http.js
CHANGED
|
@@ -1,25 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP fetch utilities with GitHub API support.
|
|
3
3
|
*
|
|
4
|
-
* Automatically attaches
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Automatically attaches a default 30-second timeout, a `User-Agent`, and a
|
|
5
|
+
* GitHub `Accept` header. When `GITHUB_TOKEN` is set in the environment, an
|
|
6
|
+
* `Authorization: Bearer` header is attached **only for GitHub hosts** so the
|
|
7
|
+
* token does not leak to third-party `--catalog-url` targets.
|
|
8
|
+
*
|
|
9
|
+
* HTTP errors raise typed `HttpError` instances with codes that distinguish
|
|
10
|
+
* timeouts, rate limits, auth failures, and server errors.
|
|
7
11
|
*/
|
|
8
12
|
import { debug } from "./output.js";
|
|
13
|
+
import { HttpError } from "./types.js";
|
|
14
|
+
let defaultHttpTimeoutMs = 30_000;
|
|
15
|
+
/**
|
|
16
|
+
* Overrides the default HTTP timeout (in milliseconds) used when callers do
|
|
17
|
+
* not pass their own `init.signal`. Primarily for tests; production callers
|
|
18
|
+
* should rely on the default.
|
|
19
|
+
*
|
|
20
|
+
* @param ms - Positive integer in milliseconds.
|
|
21
|
+
*/
|
|
22
|
+
export function setDefaultHttpTimeoutMs(ms) {
|
|
23
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
24
|
+
throw new RangeError(`Invalid HTTP timeout: ${ms}`);
|
|
25
|
+
}
|
|
26
|
+
defaultHttpTimeoutMs = Math.floor(ms);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns the current default HTTP timeout in milliseconds.
|
|
30
|
+
*/
|
|
31
|
+
export function getDefaultHttpTimeoutMs() {
|
|
32
|
+
return defaultHttpTimeoutMs;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Hostnames that are considered GitHub-owned for the purpose of attaching the
|
|
36
|
+
* `Authorization: Bearer ${GITHUB_TOKEN}` header.
|
|
37
|
+
*/
|
|
38
|
+
const GITHUB_HOSTS = new Set(["api.github.com", "raw.githubusercontent.com"]);
|
|
39
|
+
/**
|
|
40
|
+
* Returns `true` when the URL targets a GitHub-owned host (api or raw mirrors,
|
|
41
|
+
* including any `*.githubusercontent.com` subdomain).
|
|
42
|
+
*/
|
|
43
|
+
export function isGitHubHost(url) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(url);
|
|
46
|
+
if (GITHUB_HOSTS.has(parsed.hostname)) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (parsed.hostname.endsWith(".githubusercontent.com")) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
9
58
|
/**
|
|
10
59
|
* Fetches a JSON document and parses it with default Skillex headers.
|
|
11
60
|
*
|
|
12
61
|
* @param url - Target URL.
|
|
13
62
|
* @param init - Fetch init overrides.
|
|
14
63
|
* @returns Parsed JSON payload.
|
|
15
|
-
* @throws {
|
|
64
|
+
* @throws {HttpError} On non-2xx responses or timeouts.
|
|
16
65
|
*/
|
|
17
66
|
export async function fetchJson(url, init = {}) {
|
|
18
|
-
|
|
19
|
-
const response = await fetch(url, withDefaultHeaders(init));
|
|
20
|
-
debug(`${response.status} ${url}`);
|
|
67
|
+
const response = await fetchWithDefaults(url, init);
|
|
21
68
|
if (!response.ok) {
|
|
22
|
-
throw
|
|
69
|
+
throw await buildHttpError(url, response);
|
|
23
70
|
}
|
|
24
71
|
return (await response.json());
|
|
25
72
|
}
|
|
@@ -29,14 +76,12 @@ export async function fetchJson(url, init = {}) {
|
|
|
29
76
|
* @param url - Target URL.
|
|
30
77
|
* @param init - Fetch init overrides.
|
|
31
78
|
* @returns Response text body.
|
|
32
|
-
* @throws {
|
|
79
|
+
* @throws {HttpError} On non-2xx responses or timeouts.
|
|
33
80
|
*/
|
|
34
81
|
export async function fetchText(url, init = {}) {
|
|
35
|
-
|
|
36
|
-
const response = await fetch(url, withDefaultHeaders(init));
|
|
37
|
-
debug(`${response.status} ${url}`);
|
|
82
|
+
const response = await fetchWithDefaults(url, init);
|
|
38
83
|
if (!response.ok) {
|
|
39
|
-
throw
|
|
84
|
+
throw await buildHttpError(url, response);
|
|
40
85
|
}
|
|
41
86
|
return response.text();
|
|
42
87
|
}
|
|
@@ -46,17 +91,15 @@ export async function fetchText(url, init = {}) {
|
|
|
46
91
|
* @param url - Target URL.
|
|
47
92
|
* @param init - Fetch init overrides.
|
|
48
93
|
* @returns Response text body or `null` for HTTP 404.
|
|
49
|
-
* @throws {
|
|
94
|
+
* @throws {HttpError} On non-404 non-2xx responses or timeouts.
|
|
50
95
|
*/
|
|
51
96
|
export async function fetchOptionalText(url, init = {}) {
|
|
52
|
-
|
|
53
|
-
const response = await fetch(url, withDefaultHeaders(init));
|
|
54
|
-
debug(`${response.status} ${url}`);
|
|
97
|
+
const response = await fetchWithDefaults(url, init);
|
|
55
98
|
if (response.status === 404) {
|
|
56
99
|
return null;
|
|
57
100
|
}
|
|
58
101
|
if (!response.ok) {
|
|
59
|
-
throw
|
|
102
|
+
throw await buildHttpError(url, response);
|
|
60
103
|
}
|
|
61
104
|
return response.text();
|
|
62
105
|
}
|
|
@@ -66,28 +109,44 @@ export async function fetchOptionalText(url, init = {}) {
|
|
|
66
109
|
* @param url - Target URL.
|
|
67
110
|
* @param init - Fetch init overrides.
|
|
68
111
|
* @returns Parsed JSON payload or `null` for HTTP 404.
|
|
69
|
-
* @throws {
|
|
112
|
+
* @throws {HttpError} On non-404 non-2xx responses or timeouts.
|
|
70
113
|
*/
|
|
71
114
|
export async function fetchOptionalJson(url, init = {}) {
|
|
72
|
-
|
|
73
|
-
const response = await fetch(url, withDefaultHeaders(init));
|
|
74
|
-
debug(`${response.status} ${url}`);
|
|
115
|
+
const response = await fetchWithDefaults(url, init);
|
|
75
116
|
if (response.status === 404) {
|
|
76
117
|
return null;
|
|
77
118
|
}
|
|
78
119
|
if (!response.ok) {
|
|
79
|
-
throw
|
|
120
|
+
throw await buildHttpError(url, response);
|
|
80
121
|
}
|
|
81
122
|
return (await response.json());
|
|
82
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Performs a `fetch` with default headers and a default abort timeout. Wraps
|
|
126
|
+
* abort errors into a typed `HttpError` with code `HTTP_TIMEOUT`.
|
|
127
|
+
*/
|
|
128
|
+
async function fetchWithDefaults(url, init) {
|
|
129
|
+
debug(`GET ${url}`);
|
|
130
|
+
const { merged, attachedTimeout } = withDefaultHeaders(url, init);
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch(url, merged);
|
|
133
|
+
debug(`${response.status} ${url}`);
|
|
134
|
+
return response;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) {
|
|
138
|
+
const timeout = attachedTimeout ?? defaultHttpTimeoutMs;
|
|
139
|
+
throw new HttpError(`Request timed out after ${timeout}ms: ${url}`, "HTTP_TIMEOUT", { url });
|
|
140
|
+
}
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
83
144
|
/**
|
|
84
145
|
* Applies default HTTP headers expected by GitHub-hosted catalog requests.
|
|
85
|
-
* Attaches `Authorization: Bearer` when `GITHUB_TOKEN` is set
|
|
86
|
-
*
|
|
87
|
-
* @param init - User-supplied fetch options.
|
|
88
|
-
* @returns Fetch options with default headers merged in.
|
|
146
|
+
* Attaches `Authorization: Bearer` only when `GITHUB_TOKEN` is set AND the
|
|
147
|
+
* target host is GitHub-owned.
|
|
89
148
|
*/
|
|
90
|
-
function withDefaultHeaders(init) {
|
|
149
|
+
function withDefaultHeaders(url, init) {
|
|
91
150
|
const headers = new Headers(init.headers || {});
|
|
92
151
|
if (!headers.has("User-Agent")) {
|
|
93
152
|
headers.set("User-Agent", "skillex");
|
|
@@ -97,27 +156,69 @@ function withDefaultHeaders(init) {
|
|
|
97
156
|
}
|
|
98
157
|
const token = process.env.GITHUB_TOKEN;
|
|
99
158
|
if (token && !headers.has("Authorization")) {
|
|
100
|
-
|
|
101
|
-
|
|
159
|
+
if (isGitHubHost(url)) {
|
|
160
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
161
|
+
debug("Using GITHUB_TOKEN for authentication");
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
debug(`GITHUB_TOKEN suppressed: non-GitHub host (${safeHost(url)})`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
let attachedTimeout = null;
|
|
168
|
+
let signal = init.signal;
|
|
169
|
+
if (signal === undefined || signal === null) {
|
|
170
|
+
attachedTimeout = defaultHttpTimeoutMs;
|
|
171
|
+
signal = AbortSignal.timeout(defaultHttpTimeoutMs);
|
|
172
|
+
}
|
|
173
|
+
const merged = {
|
|
174
|
+
...init,
|
|
175
|
+
headers,
|
|
176
|
+
signal,
|
|
177
|
+
};
|
|
178
|
+
return { merged, attachedTimeout };
|
|
179
|
+
}
|
|
180
|
+
function safeHost(url) {
|
|
181
|
+
try {
|
|
182
|
+
return new URL(url).hostname;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return "<invalid-url>";
|
|
102
186
|
}
|
|
103
|
-
return { ...init, headers };
|
|
104
187
|
}
|
|
105
188
|
/**
|
|
106
|
-
* Builds a
|
|
107
|
-
*
|
|
108
|
-
* @param url - The URL that failed.
|
|
109
|
-
* @param status - HTTP response status code.
|
|
110
|
-
* @returns Descriptive error message.
|
|
189
|
+
* Builds a typed `HttpError` for a non-2xx response, splitting 403 into
|
|
190
|
+
* rate-limit vs auth based on `X-RateLimit-Remaining`.
|
|
111
191
|
*/
|
|
112
|
-
function
|
|
113
|
-
|
|
114
|
-
|
|
192
|
+
async function buildHttpError(url, response) {
|
|
193
|
+
const status = response.status;
|
|
194
|
+
if (status === 403 || status === 401) {
|
|
195
|
+
const remainingHeader = response.headers.get("x-ratelimit-remaining");
|
|
196
|
+
const isRateLimited = status === 403 && remainingHeader !== null && remainingHeader.trim() === "0";
|
|
197
|
+
if (isRateLimited) {
|
|
198
|
+
const reset = response.headers.get("x-ratelimit-reset");
|
|
199
|
+
const resetHint = reset ? buildResetHint(reset) : "Set GITHUB_TOKEN to raise the limit.";
|
|
200
|
+
return new HttpError(`GitHub API rate limit exceeded. ${resetHint}`, "HTTP_RATE_LIMIT", { status, url });
|
|
201
|
+
}
|
|
202
|
+
return new HttpError("GitHub API authentication failed. Check that GITHUB_TOKEN is valid and has access.", "HTTP_AUTH_FAILED", { status, url });
|
|
115
203
|
}
|
|
116
204
|
if (status === 404) {
|
|
117
|
-
return `Repository or file not found. Check that --repo is correct and the repository is public. (${url})
|
|
205
|
+
return new HttpError(`Repository or file not found. Check that --repo is correct and the repository is public. (${url})`, "HTTP_NOT_FOUND", { status, url });
|
|
118
206
|
}
|
|
119
207
|
if (status >= 500) {
|
|
120
|
-
return `GitHub API returned a server error (${status}). Try again in a moment
|
|
208
|
+
return new HttpError(`GitHub API returned a server error (${status}). Try again in a moment.`, "HTTP_SERVER_ERROR", { status, url });
|
|
209
|
+
}
|
|
210
|
+
return new HttpError(`Failed to fetch ${url} (HTTP ${status})`, "HTTP_ERROR", { status, url });
|
|
211
|
+
}
|
|
212
|
+
function buildResetHint(resetEpoch) {
|
|
213
|
+
const epoch = Number(resetEpoch);
|
|
214
|
+
if (!Number.isFinite(epoch)) {
|
|
215
|
+
return "Set GITHUB_TOKEN to raise the limit.";
|
|
216
|
+
}
|
|
217
|
+
const resetDate = new Date(epoch * 1000);
|
|
218
|
+
const seconds = Math.max(0, Math.round((resetDate.getTime() - Date.now()) / 1000));
|
|
219
|
+
if (seconds < 60) {
|
|
220
|
+
return `Resets in ${seconds}s. Set GITHUB_TOKEN to raise the limit.`;
|
|
121
221
|
}
|
|
122
|
-
|
|
222
|
+
const minutes = Math.round(seconds / 60);
|
|
223
|
+
return `Resets in ~${minutes}m. Set GITHUB_TOKEN to raise the limit.`;
|
|
123
224
|
}
|
package/dist/install.d.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Install / update / remove orchestration for catalog and direct-GitHub skills.
|
|
3
|
+
*
|
|
4
|
+
* Historically this module owned every install-related concern. Lockfile
|
|
5
|
+
* shape, direct-GitHub install, auto-sync, and the shared file downloader
|
|
6
|
+
* have been extracted into focused modules. This file now contains only
|
|
7
|
+
* orchestration and re-exports the moved symbols so existing imports keep
|
|
8
|
+
* working until callers migrate to the canonical paths.
|
|
9
|
+
*
|
|
10
|
+
* Re-export shim → canonical module mapping:
|
|
11
|
+
* - lockfile shape and source-list helpers → ./lockfile.js
|
|
12
|
+
* - direct GitHub parsing / fetch / download → ./direct-github.js
|
|
13
|
+
* - auto-sync orchestration → ./auto-sync.js
|
|
14
|
+
* - shared per-file download helper → ./downloader.js
|
|
15
|
+
*/
|
|
16
|
+
import type { AggregatedCatalogData, CatalogLoader, CatalogSource, InitProjectResult, InstallSkillsResult, LockfileSource, LockfileState, ProjectOptions, RemoveSkillsResult, SkillDownloader, SyncCommandResult, UpdateInstalledSkillsResult } from "./types.js";
|
|
17
|
+
export { createBaseLockfile, dedupeSources, getLockfileSources, normalizeLockfile, normalizeSyncHistory, parseCatalogSource, PLACEHOLDER_REPOS, toLockfileSource, } from "./lockfile.js";
|
|
18
|
+
export { confirmDirectInstall, downloadDirectGitHubSkill, fetchDirectGitHubSkill, normalizeDirectManifest, parseDirectGitHubRef, parseGitHubSource, } from "./direct-github.js";
|
|
19
|
+
export type { DirectInstallPayload } from "./direct-github.js";
|
|
20
|
+
export { maybeAutoSync, maybeSyncAfterRemove, resolveSyncAdapterIds, } from "./auto-sync.js";
|
|
21
|
+
export type { SyncFn } from "./auto-sync.js";
|
|
22
|
+
export { downloadSkillFiles, writeDownloadedManifest, } from "./downloader.js";
|
|
23
|
+
export type { DownloadedSkillManifest } from "./downloader.js";
|
|
2
24
|
interface InstallOptions extends ProjectOptions {
|
|
3
25
|
catalogLoader?: CatalogLoader;
|
|
4
26
|
downloader?: SkillDownloader;
|
|
@@ -105,11 +127,3 @@ export declare function removeProjectSource(repo: string, options?: ProjectOptio
|
|
|
105
127
|
* @returns Normalized source list.
|
|
106
128
|
*/
|
|
107
129
|
export declare function listProjectSources(options?: ProjectOptions): Promise<LockfileSource[]>;
|
|
108
|
-
/**
|
|
109
|
-
* Parses a direct GitHub install reference in `owner/repo[@ref]` format.
|
|
110
|
-
*
|
|
111
|
-
* @param input - User-supplied install argument.
|
|
112
|
-
* @returns Parsed direct GitHub reference or `null` when the value is not a direct ref.
|
|
113
|
-
*/
|
|
114
|
-
export declare function parseDirectGitHubRef(input: string): DirectGitHubRef | null;
|
|
115
|
-
export {};
|