skillhub 0.1.2 → 0.1.4
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/index.js +81 -30
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,27 +22,78 @@ import ora from "ora";
|
|
|
22
22
|
import { parseSkillMd } from "skillhub-core";
|
|
23
23
|
|
|
24
24
|
// src/utils/api.ts
|
|
25
|
+
import https from "https";
|
|
26
|
+
import http from "http";
|
|
25
27
|
var API_BASE_URL = process.env.SKILLHUB_API_URL || "https://skills.palebluedot.live/api";
|
|
26
28
|
var API_TIMEOUT = parseInt(process.env.SKILLHUB_API_TIMEOUT || "30000");
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
function httpsRequest(url, options = {}) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const parsedUrl = new URL(url);
|
|
32
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
33
|
+
const lib = isHttps ? https : http;
|
|
34
|
+
const reqOptions = {
|
|
35
|
+
hostname: parsedUrl.hostname,
|
|
36
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
37
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
38
|
+
method: options.method || "GET",
|
|
39
|
+
headers: {
|
|
40
|
+
"User-Agent": "skillhub-cli",
|
|
41
|
+
"Accept": "application/json",
|
|
42
|
+
...options.headers
|
|
43
|
+
},
|
|
44
|
+
timeout: API_TIMEOUT
|
|
45
|
+
};
|
|
46
|
+
const req = lib.request(reqOptions, (res) => {
|
|
47
|
+
const chunks = [];
|
|
48
|
+
let resolved = false;
|
|
49
|
+
const tryResolve = () => {
|
|
50
|
+
if (resolved) return;
|
|
51
|
+
const data = Buffer.concat(chunks).toString();
|
|
52
|
+
if (data.startsWith("{") || data.startsWith("[")) {
|
|
53
|
+
try {
|
|
54
|
+
JSON.parse(data);
|
|
55
|
+
resolved = true;
|
|
56
|
+
res.destroy();
|
|
57
|
+
resolve({
|
|
58
|
+
statusCode: res.statusCode || 0,
|
|
59
|
+
data
|
|
60
|
+
});
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
res.on("data", (chunk) => {
|
|
66
|
+
chunks.push(chunk);
|
|
67
|
+
tryResolve();
|
|
68
|
+
});
|
|
69
|
+
res.on("end", () => {
|
|
70
|
+
if (!resolved) {
|
|
71
|
+
resolve({
|
|
72
|
+
statusCode: res.statusCode || 0,
|
|
73
|
+
data: Buffer.concat(chunks).toString()
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
res.setTimeout(API_TIMEOUT, () => {
|
|
78
|
+
if (!resolved) {
|
|
79
|
+
res.destroy();
|
|
80
|
+
reject(new Error(`Response timeout after ${API_TIMEOUT / 1e3}s`));
|
|
81
|
+
}
|
|
82
|
+
});
|
|
36
83
|
});
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
84
|
+
req.on("error", (err) => {
|
|
85
|
+
if (err.code === "ERR_STREAM_DESTROYED") return;
|
|
86
|
+
reject(new Error(`Network error: ${err.message}`));
|
|
87
|
+
});
|
|
88
|
+
req.on("timeout", () => {
|
|
89
|
+
req.destroy();
|
|
90
|
+
reject(new Error(`Request timeout after ${API_TIMEOUT / 1e3}s`));
|
|
91
|
+
});
|
|
92
|
+
if (options.body) {
|
|
93
|
+
req.write(options.body);
|
|
41
94
|
}
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
clearTimeout(timeoutId);
|
|
45
|
-
}
|
|
95
|
+
req.end();
|
|
96
|
+
});
|
|
46
97
|
}
|
|
47
98
|
async function searchSkills(query, options = {}) {
|
|
48
99
|
const params = new URLSearchParams({
|
|
@@ -53,27 +104,27 @@ async function searchSkills(query, options = {}) {
|
|
|
53
104
|
if (options.platform) {
|
|
54
105
|
params.set("platform", options.platform);
|
|
55
106
|
}
|
|
56
|
-
const response = await
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
throw new Error(`API error ${response.status}: ${text || response.statusText}`);
|
|
107
|
+
const response = await httpsRequest(`${API_BASE_URL}/skills?${params}`);
|
|
108
|
+
if (response.statusCode !== 200) {
|
|
109
|
+
throw new Error(`API error ${response.statusCode}: ${response.data || "Unknown error"}`);
|
|
60
110
|
}
|
|
61
|
-
return response.
|
|
111
|
+
return JSON.parse(response.data);
|
|
62
112
|
}
|
|
63
113
|
async function getSkill(id) {
|
|
64
|
-
const
|
|
65
|
-
|
|
114
|
+
const encodedPath = id.split("/").map(encodeURIComponent).join("/");
|
|
115
|
+
const response = await httpsRequest(`${API_BASE_URL}/skills/${encodedPath}`);
|
|
116
|
+
if (response.statusCode === 404) {
|
|
66
117
|
return null;
|
|
67
118
|
}
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
throw new Error(`API error ${response.status}: ${text || response.statusText}`);
|
|
119
|
+
if (response.statusCode !== 200) {
|
|
120
|
+
throw new Error(`API error ${response.statusCode}: ${response.data || "Unknown error"}`);
|
|
71
121
|
}
|
|
72
|
-
return response.
|
|
122
|
+
return JSON.parse(response.data);
|
|
73
123
|
}
|
|
74
124
|
async function trackInstall(skillId, platform, method = "cli") {
|
|
75
125
|
try {
|
|
76
|
-
|
|
126
|
+
const encodedPath = skillId.split("/").map(encodeURIComponent).join("/");
|
|
127
|
+
await httpsRequest(`${API_BASE_URL}/skills/${encodedPath}/install`, {
|
|
77
128
|
method: "POST",
|
|
78
129
|
headers: { "Content-Type": "application/json" },
|
|
79
130
|
body: JSON.stringify({ platform, method })
|
|
@@ -308,7 +359,7 @@ async function search(query, options) {
|
|
|
308
359
|
const verified = skill.isVerified ? chalk2.green("\u2713") : " ";
|
|
309
360
|
const security = getSecurityBadge(skill.securityScore);
|
|
310
361
|
console.log(
|
|
311
|
-
`${verified} ${chalk2.cyan(skill.
|
|
362
|
+
`${verified} ${chalk2.cyan(skill.id.padEnd(40))} ${security} \u2B50 ${formatNumber(skill.githubStars).padStart(6)} \u2B07 ${formatNumber(skill.downloadCount).padStart(6)}`
|
|
312
363
|
);
|
|
313
364
|
console.log(
|
|
314
365
|
` ${chalk2.dim(skill.description.slice(0, 70))}${skill.description.length > 70 ? "..." : ""}`
|
package/package.json
CHANGED