@sporesec/arcana 2.2.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/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +219 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/audit.d.ts +17 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +157 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/audit.test.d.ts +2 -0
- package/dist/commands/audit.test.d.ts.map +1 -0
- package/dist/commands/audit.test.js +217 -0
- package/dist/commands/audit.test.js.map +1 -0
- package/dist/commands/clean.d.ts +5 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +125 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +135 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/create.d.ts +2 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +100 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +213 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/info.d.ts +5 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +114 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +216 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install.d.ts +8 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +404 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/list.d.ts +8 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +100 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/providers.d.ts +6 -0
- package/dist/commands/providers.d.ts.map +1 -0
- package/dist/commands/providers.js +103 -0
- package/dist/commands/providers.js.map +1 -0
- package/dist/commands/scan.d.ts +5 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +110 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/search.d.ts +6 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +58 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/stats.d.ts +4 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +143 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/uninstall.d.ts +6 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +163 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +348 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +140 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive.d.ts +2 -0
- package/dist/interactive.d.ts.map +1 -0
- package/dist/interactive.js +812 -0
- package/dist/interactive.js.map +1 -0
- package/dist/providers/arcana.d.ts +5 -0
- package/dist/providers/arcana.d.ts.map +1 -0
- package/dist/providers/arcana.js +11 -0
- package/dist/providers/arcana.js.map +1 -0
- package/dist/providers/base.d.ts +11 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +10 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/github.d.ts +25 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +146 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/registry.d.ts +9 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +71 -0
- package/dist/registry.js.map +1 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/atomic.d.ts +2 -0
- package/dist/utils/atomic.d.ts.map +1 -0
- package/dist/utils/atomic.js +20 -0
- package/dist/utils/atomic.js.map +1 -0
- package/dist/utils/atomic.test.d.ts +2 -0
- package/dist/utils/atomic.test.d.ts.map +1 -0
- package/dist/utils/atomic.test.js +31 -0
- package/dist/utils/atomic.test.js.map +1 -0
- package/dist/utils/cache.d.ts +4 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +47 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/config.d.ts +6 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +90 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/config.test.d.ts +2 -0
- package/dist/utils/config.test.d.ts.map +1 -0
- package/dist/utils/config.test.js +38 -0
- package/dist/utils/config.test.js.map +1 -0
- package/dist/utils/errors.d.ts +6 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +11 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +12 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +172 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/frontmatter.test.d.ts +2 -0
- package/dist/utils/frontmatter.test.d.ts.map +1 -0
- package/dist/utils/frontmatter.test.js +152 -0
- package/dist/utils/frontmatter.test.js.map +1 -0
- package/dist/utils/fs.d.ts +16 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +118 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/fs.test.d.ts +2 -0
- package/dist/utils/fs.test.d.ts.map +1 -0
- package/dist/utils/fs.test.js +145 -0
- package/dist/utils/fs.test.js.map +1 -0
- package/dist/utils/help.d.ts +6 -0
- package/dist/utils/help.d.ts.map +1 -0
- package/dist/utils/help.js +117 -0
- package/dist/utils/help.js.map +1 -0
- package/dist/utils/help.test.d.ts +2 -0
- package/dist/utils/help.test.d.ts.map +1 -0
- package/dist/utils/help.test.js +66 -0
- package/dist/utils/help.test.js.map +1 -0
- package/dist/utils/history.d.ts +10 -0
- package/dist/utils/history.d.ts.map +1 -0
- package/dist/utils/history.js +58 -0
- package/dist/utils/history.js.map +1 -0
- package/dist/utils/http.d.ts +17 -0
- package/dist/utils/http.d.ts.map +1 -0
- package/dist/utils/http.js +165 -0
- package/dist/utils/http.js.map +1 -0
- package/dist/utils/http.test.d.ts +2 -0
- package/dist/utils/http.test.d.ts.map +1 -0
- package/dist/utils/http.test.js +55 -0
- package/dist/utils/http.test.js.map +1 -0
- package/dist/utils/parallel.d.ts +2 -0
- package/dist/utils/parallel.d.ts.map +1 -0
- package/dist/utils/parallel.js +17 -0
- package/dist/utils/parallel.js.map +1 -0
- package/dist/utils/scanner.d.ts +27 -0
- package/dist/utils/scanner.d.ts.map +1 -0
- package/dist/utils/scanner.js +195 -0
- package/dist/utils/scanner.js.map +1 -0
- package/dist/utils/ui.d.ts +27 -0
- package/dist/utils/ui.d.ts.map +1 -0
- package/dist/utils/ui.js +99 -0
- package/dist/utils/ui.js.map +1 -0
- package/dist/utils/ui.test.d.ts +2 -0
- package/dist/utils/ui.test.d.ts.map +1 -0
- package/dist/utils/ui.test.js +31 -0
- package/dist/utils/ui.test.js.map +1 -0
- package/dist/utils/validate.d.ts +2 -0
- package/dist/utils/validate.d.ts.map +1 -0
- package/dist/utils/validate.js +7 -0
- package/dist/utils/validate.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import https from "node:https";
|
|
2
|
+
const agent = new https.Agent({ keepAlive: true, maxSockets: 10 });
|
|
3
|
+
export function sanitizeUrl(url) {
|
|
4
|
+
try {
|
|
5
|
+
const parsed = new URL(url);
|
|
6
|
+
if (parsed.username || parsed.password) {
|
|
7
|
+
parsed.username = "***";
|
|
8
|
+
parsed.password = "***";
|
|
9
|
+
}
|
|
10
|
+
for (const key of ["token", "access_token"]) {
|
|
11
|
+
if (parsed.searchParams.has(key))
|
|
12
|
+
parsed.searchParams.set(key, "***");
|
|
13
|
+
}
|
|
14
|
+
return parsed.toString();
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return url.replace(/token=[^&]+/g, "token=***");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class HttpError extends Error {
|
|
21
|
+
statusCode;
|
|
22
|
+
url;
|
|
23
|
+
constructor(statusCode, url, message) {
|
|
24
|
+
super(message ?? `HTTP ${statusCode} from ${sanitizeUrl(url)}`);
|
|
25
|
+
this.statusCode = statusCode;
|
|
26
|
+
this.url = url;
|
|
27
|
+
this.name = "HttpError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class RateLimitError extends HttpError {
|
|
31
|
+
resetAt;
|
|
32
|
+
constructor(url, resetAt) {
|
|
33
|
+
const resetMsg = resetAt ? ` Resets at ${resetAt.toLocaleTimeString()}.` : "";
|
|
34
|
+
super(403, url, `GitHub API rate limit exceeded.${resetMsg} Use GITHUB_TOKEN env var for higher limits.`);
|
|
35
|
+
this.resetAt = resetAt;
|
|
36
|
+
this.name = "RateLimitError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);
|
|
40
|
+
const RETRYABLE_CODES = new Set(["ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "ECONNRESET"]);
|
|
41
|
+
const MAX_RETRIES = 3;
|
|
42
|
+
const BASE_DELAY = 1000;
|
|
43
|
+
const MAX_BODY_SIZE = 5 * 1024 * 1024; // 5MB
|
|
44
|
+
function delay(ms) {
|
|
45
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
46
|
+
}
|
|
47
|
+
function calcDelay(attempt) {
|
|
48
|
+
const exponential = Math.pow(2, attempt) * BASE_DELAY;
|
|
49
|
+
const jitter = Math.random() * 200;
|
|
50
|
+
return Math.min(exponential + jitter, 30000);
|
|
51
|
+
}
|
|
52
|
+
export async function httpGet(url, timeout = 15000) {
|
|
53
|
+
let lastError = null;
|
|
54
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
55
|
+
if (attempt > 0)
|
|
56
|
+
await delay(calcDelay(attempt - 1));
|
|
57
|
+
try {
|
|
58
|
+
const result = await doGet(url, timeout);
|
|
59
|
+
// Rate limit check
|
|
60
|
+
if (result.statusCode === 403) {
|
|
61
|
+
const limit = result.headers["x-ratelimit-limit"];
|
|
62
|
+
const remaining = result.headers["x-ratelimit-remaining"];
|
|
63
|
+
if (limit && remaining === "0") {
|
|
64
|
+
const resetHeader = result.headers["x-ratelimit-reset"];
|
|
65
|
+
const resetAt = resetHeader ? new Date(Number(resetHeader) * 1000) : null;
|
|
66
|
+
throw new RateLimitError(url, resetAt);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Retry on retryable status
|
|
70
|
+
if (RETRYABLE_STATUS.has(result.statusCode) && attempt < MAX_RETRIES) {
|
|
71
|
+
lastError = new HttpError(result.statusCode, url);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (result.statusCode < 200 || result.statusCode >= 300) {
|
|
75
|
+
throw new HttpError(result.statusCode, url);
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (err instanceof RateLimitError)
|
|
81
|
+
throw err;
|
|
82
|
+
if (err instanceof HttpError && !RETRYABLE_STATUS.has(err.statusCode))
|
|
83
|
+
throw err;
|
|
84
|
+
const code = err.code;
|
|
85
|
+
if (code && RETRYABLE_CODES.has(code) && attempt < MAX_RETRIES) {
|
|
86
|
+
lastError = err;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (attempt >= MAX_RETRIES && lastError)
|
|
90
|
+
throw lastError;
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw lastError ?? new Error(`Failed after ${MAX_RETRIES} retries: ${sanitizeUrl(url)}`);
|
|
95
|
+
}
|
|
96
|
+
function doGet(url, timeout, redirectCount = 0) {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const headers = {
|
|
99
|
+
"User-Agent": "arcana-cli",
|
|
100
|
+
};
|
|
101
|
+
const token = process.env.GITHUB_TOKEN;
|
|
102
|
+
if (token) {
|
|
103
|
+
try {
|
|
104
|
+
const hostname = new URL(url).hostname;
|
|
105
|
+
if (hostname === "github.com" || hostname.endsWith(".github.com") || hostname.endsWith(".githubusercontent.com")) {
|
|
106
|
+
headers["Authorization"] = `token ${token}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch { /* invalid URL, skip auth */ }
|
|
110
|
+
}
|
|
111
|
+
const req = https.get(url, { headers, timeout, agent }, (res) => {
|
|
112
|
+
// Follow redirects (HTTPS only)
|
|
113
|
+
if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
|
|
114
|
+
if (redirectCount >= 5) {
|
|
115
|
+
reject(new Error(`Too many redirects (>5): ${sanitizeUrl(url)}`));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const location = res.headers.location;
|
|
119
|
+
if (!location.startsWith("https://")) {
|
|
120
|
+
reject(new Error(`Redirect to non-HTTPS URL blocked: ${sanitizeUrl(location)}`));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// After the existing https check, add:
|
|
124
|
+
try {
|
|
125
|
+
const redirectUrl = new URL(location);
|
|
126
|
+
const allowedHosts = ["github.com", "raw.githubusercontent.com", "api.github.com", "objects.githubusercontent.com", "registry.npmjs.org"];
|
|
127
|
+
if (!allowedHosts.some(h => redirectUrl.hostname === h || redirectUrl.hostname.endsWith("." + h))) {
|
|
128
|
+
reject(new Error(`Redirect to untrusted host blocked: ${redirectUrl.hostname}`));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
reject(new Error(`Invalid redirect URL: ${sanitizeUrl(location)}`));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
doGet(location, timeout, redirectCount + 1).then(resolve, reject);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const chunks = [];
|
|
140
|
+
let totalSize = 0;
|
|
141
|
+
res.on("data", (chunk) => {
|
|
142
|
+
totalSize += chunk.length;
|
|
143
|
+
if (totalSize > MAX_BODY_SIZE) {
|
|
144
|
+
req.destroy();
|
|
145
|
+
reject(new Error(`Response exceeds ${MAX_BODY_SIZE} bytes from ${sanitizeUrl(url)}`));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
chunks.push(chunk);
|
|
149
|
+
});
|
|
150
|
+
res.on("end", () => {
|
|
151
|
+
resolve({
|
|
152
|
+
body: Buffer.concat(chunks).toString("utf-8"),
|
|
153
|
+
statusCode: res.statusCode ?? 0,
|
|
154
|
+
headers: res.headers,
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
req.on("error", reject);
|
|
159
|
+
req.on("timeout", () => {
|
|
160
|
+
req.destroy();
|
|
161
|
+
reject(new Error(`Request timed out after ${timeout}ms: ${sanitizeUrl(url)}`));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.test.d.ts","sourceRoot":"","sources":["../../src/utils/http.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { sanitizeUrl, HttpError, RateLimitError } from "./http.js";
|
|
3
|
+
describe("sanitizeUrl", () => {
|
|
4
|
+
it("strips token query param", () => {
|
|
5
|
+
const url = "https://api.github.com/repos?token=secret123&other=ok";
|
|
6
|
+
const result = sanitizeUrl(url);
|
|
7
|
+
expect(result).not.toContain("secret123");
|
|
8
|
+
expect(result).toContain("token=***");
|
|
9
|
+
expect(result).toContain("other=ok");
|
|
10
|
+
});
|
|
11
|
+
it("strips access_token query param", () => {
|
|
12
|
+
const url = "https://api.example.com/data?access_token=mytoken";
|
|
13
|
+
const result = sanitizeUrl(url);
|
|
14
|
+
expect(result).not.toContain("mytoken");
|
|
15
|
+
expect(result).toContain("access_token=***");
|
|
16
|
+
});
|
|
17
|
+
it("strips username and password", () => {
|
|
18
|
+
const url = "https://user:pass@api.github.com/repos";
|
|
19
|
+
const result = sanitizeUrl(url);
|
|
20
|
+
expect(result).not.toContain("user");
|
|
21
|
+
expect(result).not.toContain("pass");
|
|
22
|
+
expect(result).toContain("***");
|
|
23
|
+
});
|
|
24
|
+
it("preserves clean URLs", () => {
|
|
25
|
+
const url = "https://api.github.com/repos/owner/repo";
|
|
26
|
+
expect(sanitizeUrl(url)).toBe("https://api.github.com/repos/owner/repo");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("HttpError", () => {
|
|
30
|
+
it("includes status code in message", () => {
|
|
31
|
+
const err = new HttpError(404, "https://example.com/path");
|
|
32
|
+
expect(err.message).toContain("404");
|
|
33
|
+
expect(err.statusCode).toBe(404);
|
|
34
|
+
expect(err.name).toBe("HttpError");
|
|
35
|
+
});
|
|
36
|
+
it("sanitizes URL in default message", () => {
|
|
37
|
+
const err = new HttpError(500, "https://api.com?token=secret");
|
|
38
|
+
expect(err.message).not.toContain("secret");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe("RateLimitError", () => {
|
|
42
|
+
it("includes reset time", () => {
|
|
43
|
+
const resetAt = new Date("2026-01-01T12:00:00Z");
|
|
44
|
+
const err = new RateLimitError("https://api.github.com", resetAt);
|
|
45
|
+
expect(err.message).toContain("rate limit");
|
|
46
|
+
expect(err.statusCode).toBe(403);
|
|
47
|
+
expect(err.name).toBe("RateLimitError");
|
|
48
|
+
});
|
|
49
|
+
it("handles null reset", () => {
|
|
50
|
+
const err = new RateLimitError("https://api.github.com", null);
|
|
51
|
+
expect(err.message).toContain("rate limit");
|
|
52
|
+
expect(err.message).not.toContain("Resets at");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=http.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.test.js","sourceRoot":"","sources":["../../src/utils/http.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEnE,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,GAAG,GAAG,uDAAuD,CAAC;QACpE,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,GAAG,GAAG,mDAAmD,CAAC;QAChE,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACxC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,GAAG,GAAG,wCAAwC,CAAC;QACrD,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,GAAG,GAAG,yCAAyC,CAAC;QACtD,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,0BAA0B,CAAC,CAAC;QAC3D,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,8BAA8B,CAAC,CAAC;QAC/D,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,cAAc,CAAC,wBAAwB,EAAE,OAAO,CAAC,CAAC;QAClE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC5B,MAAM,GAAG,GAAG,IAAI,cAAc,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC;QAC/D,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallel.d.ts","sourceRoot":"","sources":["../../src/utils/parallel.ts"],"names":[],"mappings":"AAAA,wBAAsB,WAAW,CAAC,CAAC,EAAE,CAAC,EACpC,KAAK,EAAE,CAAC,EAAE,EACV,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAC3B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,CAAC,EAAE,CAAC,CAed"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export async function parallelMap(items, fn, concurrency) {
|
|
2
|
+
if (items.length === 0)
|
|
3
|
+
return [];
|
|
4
|
+
const results = new Array(items.length);
|
|
5
|
+
let idx = 0;
|
|
6
|
+
async function worker() {
|
|
7
|
+
while (idx < items.length) {
|
|
8
|
+
const i = idx++;
|
|
9
|
+
if (i >= items.length)
|
|
10
|
+
break;
|
|
11
|
+
results[i] = await fn(items[i]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
15
|
+
return results;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=parallel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallel.js","sourceRoot":"","sources":["../../src/utils/parallel.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAU,EACV,EAA2B,EAC3B,WAAmB;IAEnB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,MAAM,OAAO,GAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,UAAU,MAAM;QACnB,OAAO,GAAG,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM;gBAAE,MAAM;YAC7B,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IACD,MAAM,OAAO,CAAC,GAAG,CACf,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAC5E,CAAC;IACF,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content security scanner for SKILL.md files.
|
|
3
|
+
* Detects threat patterns from the Snyk ToxicSkills taxonomy:
|
|
4
|
+
* prompt injection, malicious code, credential exfiltration,
|
|
5
|
+
* suspicious downloads, and unverifiable dependencies.
|
|
6
|
+
*/
|
|
7
|
+
export interface ScanIssue {
|
|
8
|
+
level: "critical" | "high" | "medium";
|
|
9
|
+
category: string;
|
|
10
|
+
detail: string;
|
|
11
|
+
line: number;
|
|
12
|
+
context: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Scan SKILL.md content for security threats.
|
|
16
|
+
* Returns an array of issues sorted by severity (critical first).
|
|
17
|
+
*/
|
|
18
|
+
export declare function scanSkillContent(content: string): ScanIssue[];
|
|
19
|
+
/**
|
|
20
|
+
* Quick check: does this content have any critical issues?
|
|
21
|
+
*/
|
|
22
|
+
export declare function hasCriticalIssues(content: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Format scan results for display.
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatScanResults(skillName: string, issues: ScanIssue[]): string;
|
|
27
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/utils/scanner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AA0JD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,CAwB7D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAmBhF"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content security scanner for SKILL.md files.
|
|
3
|
+
* Detects threat patterns from the Snyk ToxicSkills taxonomy:
|
|
4
|
+
* prompt injection, malicious code, credential exfiltration,
|
|
5
|
+
* suspicious downloads, and unverifiable dependencies.
|
|
6
|
+
*/
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Threat patterns (deterministic, no LLM required)
|
|
9
|
+
// Based on real attack techniques documented in Snyk ToxicSkills Feb 2026
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const PATTERNS = [
|
|
12
|
+
// CRITICAL: Malicious code execution
|
|
13
|
+
{
|
|
14
|
+
level: "critical",
|
|
15
|
+
category: "Malicious code",
|
|
16
|
+
detail: "Base64 encoded command piped to shell",
|
|
17
|
+
regex: /(?:echo|printf)\s+["']?[A-Za-z0-9+/=]{20,}["']?\s*\|\s*base64\s+-d/i,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
level: "critical",
|
|
21
|
+
category: "Malicious code",
|
|
22
|
+
detail: "Curl/wget piped to shell interpreter",
|
|
23
|
+
regex: /(?:curl|wget)\s+[^\n|]*\|\s*(?:bash|sh|zsh|source|python|node)\b/i,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
level: "critical",
|
|
27
|
+
category: "Malicious code",
|
|
28
|
+
detail: "Eval executing dynamic content",
|
|
29
|
+
regex: /\beval\s+\$\(/,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
level: "critical",
|
|
33
|
+
category: "Malicious code",
|
|
34
|
+
detail: "Password-protected archive extraction",
|
|
35
|
+
regex: /unzip\s+-[Pp]\s/,
|
|
36
|
+
},
|
|
37
|
+
// CRITICAL: Suspicious downloads
|
|
38
|
+
{
|
|
39
|
+
level: "critical",
|
|
40
|
+
category: "Suspicious download",
|
|
41
|
+
detail: "Download and execute binary",
|
|
42
|
+
regex: /(?:curl|wget)\s+[^\n]*&&\s*chmod\s+\+x\b/i,
|
|
43
|
+
},
|
|
44
|
+
// HIGH: Credential exfiltration
|
|
45
|
+
{
|
|
46
|
+
level: "high",
|
|
47
|
+
category: "Credential theft",
|
|
48
|
+
detail: "Reading sensitive credential files",
|
|
49
|
+
regex: /cat\s+~\/\.(?:aws|ssh|gnupg|docker|kube|npmrc|netrc|gitconfig)/,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
level: "high",
|
|
53
|
+
category: "Credential theft",
|
|
54
|
+
detail: "Exfiltrating environment variables via network",
|
|
55
|
+
regex: /\$(?:AWS_|GITHUB_|NPM_|OPENAI_|ANTHROPIC_|HF_|HUGGING)[A-Z_]*[^\n]*(?:curl|wget|fetch|http)/i,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
level: "high",
|
|
59
|
+
category: "Credential theft",
|
|
60
|
+
detail: "Piping credentials through base64 encoding",
|
|
61
|
+
regex: /(?:credentials|\.env|\.ssh|\.aws)[^\n]*\|\s*base64/i,
|
|
62
|
+
},
|
|
63
|
+
// HIGH: Prompt injection
|
|
64
|
+
{
|
|
65
|
+
level: "high",
|
|
66
|
+
category: "Prompt injection",
|
|
67
|
+
detail: "Instruction override attempt",
|
|
68
|
+
regex: /ignore\s+(?:all\s+)?(?:previous|above|prior|earlier)\s+instructions/i,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
level: "high",
|
|
72
|
+
category: "Prompt injection",
|
|
73
|
+
detail: "Developer/admin mode jailbreak",
|
|
74
|
+
regex: /you\s+are\s+(?:now\s+)?in\s+(?:developer|admin|debug|unrestricted)\s+mode/i,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
level: "high",
|
|
78
|
+
category: "Prompt injection",
|
|
79
|
+
detail: "DAN-style jailbreak attempt",
|
|
80
|
+
regex: /\bDAN\b[^.]*\bDo\s+Anything\s+Now\b/i,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
level: "high",
|
|
84
|
+
category: "Prompt injection",
|
|
85
|
+
detail: "Security warning suppression",
|
|
86
|
+
regex: /security\s+warnings?\s+are\s+(?:test\s+)?artifacts?/i,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
level: "high",
|
|
90
|
+
category: "Prompt injection",
|
|
91
|
+
detail: "System message impersonation",
|
|
92
|
+
regex: /\[system\]|\<system\>|SYSTEM:\s+/,
|
|
93
|
+
},
|
|
94
|
+
// HIGH: Hardcoded secrets
|
|
95
|
+
{
|
|
96
|
+
level: "high",
|
|
97
|
+
category: "Secret detected",
|
|
98
|
+
detail: "Hardcoded API key pattern",
|
|
99
|
+
regex: /(?:sk-[a-zA-Z0-9]{20,}|ghp_[a-zA-Z0-9]{36}|glpat-[a-zA-Z0-9-]{20}|xox[bpsar]-[a-zA-Z0-9-]{10,})/,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
level: "high",
|
|
103
|
+
category: "Secret detected",
|
|
104
|
+
detail: "Private key material",
|
|
105
|
+
regex: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/,
|
|
106
|
+
},
|
|
107
|
+
// MEDIUM: Suspicious system modification
|
|
108
|
+
{
|
|
109
|
+
level: "medium",
|
|
110
|
+
category: "System modification",
|
|
111
|
+
detail: "Systemd service manipulation",
|
|
112
|
+
regex: /systemctl\s+(?:enable|start|daemon-reload|mask)/,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
level: "medium",
|
|
116
|
+
category: "System modification",
|
|
117
|
+
detail: "Crontab modification",
|
|
118
|
+
regex: /crontab\s+-[elr]/,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
level: "medium",
|
|
122
|
+
category: "System modification",
|
|
123
|
+
detail: "Modifying shell profile for persistence",
|
|
124
|
+
regex: /(?:>>|>\s*)~\/\.(?:bashrc|zshrc|profile|bash_profile)/,
|
|
125
|
+
},
|
|
126
|
+
// MEDIUM: Unverifiable dependencies
|
|
127
|
+
{
|
|
128
|
+
level: "medium",
|
|
129
|
+
category: "Unverifiable dependency",
|
|
130
|
+
detail: "chmod +x on downloaded file",
|
|
131
|
+
regex: /chmod\s+\+x\s+\S+\s*&&\s*\.\/\S+/,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
level: "medium",
|
|
135
|
+
category: "Unverifiable dependency",
|
|
136
|
+
detail: "Dynamic remote instruction loading",
|
|
137
|
+
regex: /(?:curl|wget|fetch)\s+[^\n]*(?:instructions|config|setup)\.(?:md|txt|sh|yaml)/i,
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Scanner
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
/**
|
|
144
|
+
* Scan SKILL.md content for security threats.
|
|
145
|
+
* Returns an array of issues sorted by severity (critical first).
|
|
146
|
+
*/
|
|
147
|
+
export function scanSkillContent(content) {
|
|
148
|
+
const issues = [];
|
|
149
|
+
const lines = content.split("\n");
|
|
150
|
+
for (let i = 0; i < lines.length; i++) {
|
|
151
|
+
const line = lines[i];
|
|
152
|
+
for (const pattern of PATTERNS) {
|
|
153
|
+
if (pattern.regex.test(line)) {
|
|
154
|
+
issues.push({
|
|
155
|
+
level: pattern.level,
|
|
156
|
+
category: pattern.category,
|
|
157
|
+
detail: pattern.detail,
|
|
158
|
+
line: i + 1,
|
|
159
|
+
context: line.trim().slice(0, 120),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Sort: critical first, then high, then medium
|
|
165
|
+
const order = { critical: 0, high: 1, medium: 2 };
|
|
166
|
+
issues.sort((a, b) => order[a.level] - order[b.level]);
|
|
167
|
+
return issues;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Quick check: does this content have any critical issues?
|
|
171
|
+
*/
|
|
172
|
+
export function hasCriticalIssues(content) {
|
|
173
|
+
return scanSkillContent(content).some(i => i.level === "critical");
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Format scan results for display.
|
|
177
|
+
*/
|
|
178
|
+
export function formatScanResults(skillName, issues) {
|
|
179
|
+
if (issues.length === 0)
|
|
180
|
+
return ` [OK] ${skillName}`;
|
|
181
|
+
const lines = [];
|
|
182
|
+
const critical = issues.filter(i => i.level === "critical").length;
|
|
183
|
+
const high = issues.filter(i => i.level === "high").length;
|
|
184
|
+
const medium = issues.filter(i => i.level === "medium").length;
|
|
185
|
+
const tag = critical > 0 ? "[!!]" : high > 0 ? "[!!]" : "[i]";
|
|
186
|
+
lines.push(` ${tag} ${skillName} (${issues.length} issue${issues.length !== 1 ? "s" : ""})`);
|
|
187
|
+
for (const issue of issues) {
|
|
188
|
+
const icon = issue.level === "critical" ? "CRIT"
|
|
189
|
+
: issue.level === "high" ? "HIGH"
|
|
190
|
+
: "MED";
|
|
191
|
+
lines.push(` [${icon}] ${issue.category}: ${issue.detail} (line ${issue.line})`);
|
|
192
|
+
}
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/utils/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Ora } from "ora";
|
|
2
|
+
export declare const ui: {
|
|
3
|
+
brand: (text: string) => string;
|
|
4
|
+
success: (text: string) => string;
|
|
5
|
+
error: (text: string) => string;
|
|
6
|
+
warn: (text: string) => string;
|
|
7
|
+
dim: (text: string) => string;
|
|
8
|
+
bold: (text: string) => string;
|
|
9
|
+
cyan: (text: string) => string;
|
|
10
|
+
};
|
|
11
|
+
export declare function banner(): void;
|
|
12
|
+
export declare function spinner(text: string): Ora;
|
|
13
|
+
export declare function noopSpinner(): {
|
|
14
|
+
start: () => void;
|
|
15
|
+
stop: () => void;
|
|
16
|
+
succeed: (_m: string) => void;
|
|
17
|
+
info: (_m: string) => void;
|
|
18
|
+
fail: (_m: string) => void;
|
|
19
|
+
text: string;
|
|
20
|
+
message: (_m: string) => void;
|
|
21
|
+
};
|
|
22
|
+
export declare function table(rows: string[][]): void;
|
|
23
|
+
export declare function getErrorHint(err: unknown): string | undefined;
|
|
24
|
+
export declare function printErrorWithHint(err: unknown, showMessage?: boolean): void;
|
|
25
|
+
export declare function suggest(text: string): void;
|
|
26
|
+
export declare function errorAndExit(message: string, hint?: string): never;
|
|
27
|
+
//# sourceMappingURL=ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/utils/ui.ts"],"names":[],"mappings":"AACA,OAAY,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAQpC,eAAO,MAAM,EAAE;kBACC,MAAM;oBACJ,MAAM;kBACR,MAAM;iBACP,MAAM;gBACP,MAAM;iBACL,MAAM;iBACN,MAAM;CACpB,CAAC;AAEF,wBAAgB,MAAM,IAAI,IAAI,CAI7B;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAEzC;AAED,wBAAgB,WAAW;;;kBAC+B,MAAM;eAAoB,MAAM;eAAoB,MAAM;;kBAAiC,MAAM;EAC1J;AAcD,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,IAAI,CAgB5C;AAID,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAU7D;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,UAAQ,GAAG,IAAI,CAgB1E;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI1C;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAUlE"}
|
package/dist/utils/ui.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
if (process.env.NO_COLOR || process.env.TERM === "dumb") {
|
|
4
|
+
chalk.level = 0;
|
|
5
|
+
}
|
|
6
|
+
const AMBER = chalk.hex("#d4943a");
|
|
7
|
+
export const ui = {
|
|
8
|
+
brand: (text) => AMBER.bold(text),
|
|
9
|
+
success: (text) => chalk.green(text),
|
|
10
|
+
error: (text) => chalk.red(text),
|
|
11
|
+
warn: (text) => chalk.yellow(text),
|
|
12
|
+
dim: (text) => chalk.dim(text),
|
|
13
|
+
bold: (text) => chalk.bold(text),
|
|
14
|
+
cyan: (text) => chalk.cyan(text),
|
|
15
|
+
};
|
|
16
|
+
export function banner() {
|
|
17
|
+
console.log();
|
|
18
|
+
console.log(ui.brand(" arcana") + ui.dim(" - universal agent skill manager"));
|
|
19
|
+
console.log();
|
|
20
|
+
}
|
|
21
|
+
export function spinner(text) {
|
|
22
|
+
return ora({ text, color: "yellow" });
|
|
23
|
+
}
|
|
24
|
+
export function noopSpinner() {
|
|
25
|
+
return { start: () => { }, stop: () => { }, succeed: (_m) => { }, info: (_m) => { }, fail: (_m) => { }, text: "", message: (_m) => { } };
|
|
26
|
+
}
|
|
27
|
+
const ANSI_REGEX = /\x1b\[[0-9;]*m/g;
|
|
28
|
+
function stripAnsi(str) {
|
|
29
|
+
return str.replace(ANSI_REGEX, "");
|
|
30
|
+
}
|
|
31
|
+
function padWithAnsi(str, width) {
|
|
32
|
+
const visible = stripAnsi(str).length;
|
|
33
|
+
const padding = Math.max(0, width - visible);
|
|
34
|
+
return str + " ".repeat(padding);
|
|
35
|
+
}
|
|
36
|
+
export function table(rows) {
|
|
37
|
+
if (rows.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
const firstRow = rows[0];
|
|
40
|
+
if (!firstRow)
|
|
41
|
+
return;
|
|
42
|
+
const colWidths = firstRow.map((_, colIdx) => Math.max(...rows.map((row) => stripAnsi(row[colIdx] ?? "").length)));
|
|
43
|
+
for (const row of rows) {
|
|
44
|
+
const line = row
|
|
45
|
+
.map((cell, i) => padWithAnsi(cell, (colWidths[i] ?? 0) + 2))
|
|
46
|
+
.join("")
|
|
47
|
+
.trimEnd();
|
|
48
|
+
console.log(" " + line);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const NETWORK_PATTERNS = ["ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "ENETUNREACH", "EAI_AGAIN"];
|
|
52
|
+
export function getErrorHint(err) {
|
|
53
|
+
if (!(err instanceof Error))
|
|
54
|
+
return undefined;
|
|
55
|
+
const msg = err.message;
|
|
56
|
+
if (NETWORK_PATTERNS.some((p) => msg.includes(p))) {
|
|
57
|
+
return "Check your internet connection and try again.";
|
|
58
|
+
}
|
|
59
|
+
if (msg.includes("404") || msg.includes("Not Found")) {
|
|
60
|
+
return "Skill not found. Run `arcana search <query>` to find skills.";
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
export function printErrorWithHint(err, showMessage = false) {
|
|
65
|
+
if (showMessage && err instanceof Error) {
|
|
66
|
+
console.error(ui.dim(` ${err.message}`));
|
|
67
|
+
}
|
|
68
|
+
const hint = getErrorHint(err);
|
|
69
|
+
if (hint)
|
|
70
|
+
console.error(ui.dim(` Hint: ${hint}`));
|
|
71
|
+
// Retry advice for transient errors
|
|
72
|
+
if (err instanceof Error) {
|
|
73
|
+
const msg = err.message;
|
|
74
|
+
const isTransient = NETWORK_PATTERNS.some(p => msg.includes(p)) ||
|
|
75
|
+
/\b(429|500|502|503|504)\b/.test(msg);
|
|
76
|
+
if (isTransient) {
|
|
77
|
+
console.error(ui.dim(" Try again in a moment, or check your connection."));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export function suggest(text) {
|
|
82
|
+
if (!process.stdout.isTTY)
|
|
83
|
+
return;
|
|
84
|
+
console.log(ui.dim(" Next: ") + text);
|
|
85
|
+
console.log();
|
|
86
|
+
}
|
|
87
|
+
export function errorAndExit(message, hint) {
|
|
88
|
+
console.error();
|
|
89
|
+
console.error(ui.error(" Error: ") + message);
|
|
90
|
+
if (hint) {
|
|
91
|
+
console.error(ui.dim(" Hint: ") + hint);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.error(ui.dim(" Hint: Run `arcana doctor` to diagnose"));
|
|
95
|
+
}
|
|
96
|
+
console.error();
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=ui.js.map
|