ht-skills 0.2.4 → 0.2.6
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/lib/cli.js +120 -6
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -157,6 +157,15 @@ async function setStoredRegistryAuth(registry, value, { homeDir = os.homedir() }
|
|
|
157
157
|
await saveCliConfig(config, homeDir);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
async function clearStoredRegistryAuth(registry, { homeDir = os.homedir() } = {}) {
|
|
161
|
+
const config = await loadCliConfig(homeDir);
|
|
162
|
+
if (!config.registries || typeof config.registries !== "object") {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
delete config.registries[getRegistryConfigKey(registry)];
|
|
166
|
+
await saveCliConfig(config, homeDir);
|
|
167
|
+
}
|
|
168
|
+
|
|
160
169
|
async function resolveAuthToken(registry, flags, { homeDir = os.homedir() } = {}) {
|
|
161
170
|
const inlineToken = String(flags.token || "").trim();
|
|
162
171
|
if (inlineToken) {
|
|
@@ -174,6 +183,79 @@ async function getRequiredAuthToken(registry, flags, { homeDir = os.homedir() }
|
|
|
174
183
|
return token;
|
|
175
184
|
}
|
|
176
185
|
|
|
186
|
+
function isInteractiveSession(deps = {}) {
|
|
187
|
+
return typeof deps.isInteractive === "boolean"
|
|
188
|
+
? deps.isInteractive
|
|
189
|
+
: Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function validateAuthToken(registry, token, deps = {}) {
|
|
193
|
+
const requestJsonImpl = deps.requestJson || requestJson;
|
|
194
|
+
try {
|
|
195
|
+
const payload = await requestJsonImpl(`${registry}/auth/me`, {
|
|
196
|
+
headers: withBearerToken({}, token),
|
|
197
|
+
});
|
|
198
|
+
if (!payload || payload.authenticated !== true) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return payload;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (Number(error?.status || 0) === 401) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function ensureValidAuthToken(registry, flags, deps = {}) {
|
|
211
|
+
const log = deps.log || ((message) => console.log(message));
|
|
212
|
+
const homeDir = deps.homeDir || os.homedir();
|
|
213
|
+
const loginImpl = deps.cmdLogin || cmdLogin;
|
|
214
|
+
const interactive = isInteractiveSession(deps);
|
|
215
|
+
const explicitToken = String(flags.token || "").trim();
|
|
216
|
+
|
|
217
|
+
let token = await resolveAuthToken(registry, flags, { homeDir });
|
|
218
|
+
if (!token) {
|
|
219
|
+
if (!interactive) {
|
|
220
|
+
throw new Error(`No saved login for ${registry}. Run "ht-skills login --registry ${registry}" first.`);
|
|
221
|
+
}
|
|
222
|
+
log(`Login required for ${registry}. Starting browser sign-in...`);
|
|
223
|
+
await loginImpl({ ...flags, registry }, deps);
|
|
224
|
+
token = await getRequiredAuthToken(registry, flags, { homeDir });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const authState = await validateAuthToken(registry, token, deps);
|
|
228
|
+
if (authState) {
|
|
229
|
+
return {
|
|
230
|
+
token,
|
|
231
|
+
authState,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (explicitToken) {
|
|
236
|
+
throw new Error(`The provided token for ${registry} is invalid or expired.`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
await clearStoredRegistryAuth(registry, { homeDir });
|
|
240
|
+
|
|
241
|
+
if (!interactive) {
|
|
242
|
+
throw new Error(`Saved login for ${registry} is invalid or expired. Run "ht-skills login --registry ${registry}" first.`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
log(`Saved login for ${registry} is invalid or expired. Starting browser sign-in...`);
|
|
246
|
+
await loginImpl({ ...flags, registry }, deps);
|
|
247
|
+
token = await getRequiredAuthToken(registry, flags, { homeDir });
|
|
248
|
+
const refreshedAuthState = await validateAuthToken(registry, token, deps);
|
|
249
|
+
if (!refreshedAuthState) {
|
|
250
|
+
throw new Error(`Login completed but ${registry} did not accept the refreshed token.`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
token,
|
|
255
|
+
authState: refreshedAuthState,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
177
259
|
function withBearerToken(headers = {}, token = null) {
|
|
178
260
|
if (!token) return { ...headers };
|
|
179
261
|
return {
|
|
@@ -186,6 +268,17 @@ function sleep(ms) {
|
|
|
186
268
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
187
269
|
}
|
|
188
270
|
|
|
271
|
+
function formatBytes(bytes) {
|
|
272
|
+
const value = Number(bytes || 0);
|
|
273
|
+
if (value >= 1024 * 1024) {
|
|
274
|
+
return `${(value / (1024 * 1024)).toFixed(2)} MB`;
|
|
275
|
+
}
|
|
276
|
+
if (value >= 1024) {
|
|
277
|
+
return `${(value / 1024).toFixed(2)} KB`;
|
|
278
|
+
}
|
|
279
|
+
return `${value} B`;
|
|
280
|
+
}
|
|
281
|
+
|
|
189
282
|
function openBrowserUrl(url) {
|
|
190
283
|
const safeUrl = String(url || "").trim();
|
|
191
284
|
if (!safeUrl) {
|
|
@@ -242,7 +335,7 @@ function summarizePreviewErrors(preview) {
|
|
|
242
335
|
async function promptToOpenBrowser(url, deps = {}) {
|
|
243
336
|
const ask = deps.ask || null;
|
|
244
337
|
if (ask) {
|
|
245
|
-
await ask("Press ENTER to open in the browser
|
|
338
|
+
await ask("Press ENTER to open in the browser...");
|
|
246
339
|
return;
|
|
247
340
|
}
|
|
248
341
|
|
|
@@ -259,7 +352,7 @@ async function promptToOpenBrowser(url, deps = {}) {
|
|
|
259
352
|
output: process.stdout,
|
|
260
353
|
});
|
|
261
354
|
try {
|
|
262
|
-
await rl.question("Press ENTER to open in the browser
|
|
355
|
+
await rl.question("Press ENTER to open in the browser...");
|
|
263
356
|
} finally {
|
|
264
357
|
rl.close();
|
|
265
358
|
}
|
|
@@ -1345,14 +1438,19 @@ async function cmdLogin(flags, deps = {}) {
|
|
|
1345
1438
|
async function cmdPublish(flags, deps = {}) {
|
|
1346
1439
|
const requestJsonImpl = deps.requestJson || requestJson;
|
|
1347
1440
|
const log = deps.log || ((message) => console.log(message));
|
|
1348
|
-
const homeDir = deps.homeDir || os.homedir();
|
|
1349
1441
|
const registry = getRegistryUrl(flags);
|
|
1350
1442
|
const skillDir = path.resolve(flags._[0] || ".");
|
|
1351
|
-
|
|
1443
|
+
log(`Checking login for ${registry}...`);
|
|
1444
|
+
const { token } = await ensureValidAuthToken(registry, flags, deps);
|
|
1445
|
+
|
|
1352
1446
|
const archiveName = `${path.basename(skillDir) || "skill"}.zip`;
|
|
1447
|
+
log(`Packing skill directory: ${skillDir}`);
|
|
1353
1448
|
const archiveBuffer = await createZipFromDirectory(skillDir);
|
|
1449
|
+
log(`Created archive ${archiveName} (${formatBytes(archiveBuffer.length)})`);
|
|
1450
|
+
|
|
1354
1451
|
const pollIntervalMs = Math.max(500, Number(flags["poll-interval"] || deps.pollIntervalMs || DEFAULT_PUBLISH_POLL_MS));
|
|
1355
1452
|
|
|
1453
|
+
log("Uploading archive for package inspection...");
|
|
1356
1454
|
const job = await requestJsonImpl(
|
|
1357
1455
|
`${registry}/api/skills/inspect-package-jobs/upload?archive_name=${encodeURIComponent(archiveName)}`,
|
|
1358
1456
|
{
|
|
@@ -1368,8 +1466,10 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1368
1466
|
if (!jobId) {
|
|
1369
1467
|
throw new Error("registry did not return an inspection job id");
|
|
1370
1468
|
}
|
|
1469
|
+
log(`Inspection job created: ${jobId}`);
|
|
1371
1470
|
|
|
1372
1471
|
let inspection = job;
|
|
1472
|
+
let lastProgressKey = "";
|
|
1373
1473
|
while (inspection.status !== "succeeded" && inspection.status !== "failed") {
|
|
1374
1474
|
await sleep(pollIntervalMs);
|
|
1375
1475
|
inspection = await requestJsonImpl(
|
|
@@ -1378,16 +1478,29 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1378
1478
|
headers: withBearerToken({}, token),
|
|
1379
1479
|
},
|
|
1380
1480
|
);
|
|
1481
|
+
|
|
1482
|
+
const step = String(inspection.progress?.step || inspection.result?.step || "").trim();
|
|
1483
|
+
const percent = inspection.progress?.percent;
|
|
1484
|
+
const progressKey = `${inspection.status}:${step}:${percent}`;
|
|
1485
|
+
if (progressKey !== lastProgressKey) {
|
|
1486
|
+
lastProgressKey = progressKey;
|
|
1487
|
+
const progressLabel = step
|
|
1488
|
+
? `${step}${typeof percent === "number" ? ` ${percent}%` : ""}`
|
|
1489
|
+
: inspection.status;
|
|
1490
|
+
log(`Inspection in progress: ${progressLabel}`);
|
|
1491
|
+
}
|
|
1381
1492
|
}
|
|
1382
1493
|
|
|
1383
1494
|
if (inspection.status !== "succeeded") {
|
|
1384
1495
|
throw new Error(inspection.error || "skill archive inspection failed");
|
|
1385
1496
|
}
|
|
1497
|
+
log("Inspection passed.");
|
|
1386
1498
|
|
|
1387
1499
|
const preview = inspection.result || {};
|
|
1388
1500
|
if (!preview.valid || !preview.preview_token) {
|
|
1389
1501
|
throw new Error(summarizePreviewErrors(preview));
|
|
1390
1502
|
}
|
|
1503
|
+
log(`Preview token created: ${preview.preview_token}`);
|
|
1391
1504
|
|
|
1392
1505
|
const visibility = String(flags.access || flags.visibility || "public").trim().toLowerCase() || "public";
|
|
1393
1506
|
const body = {
|
|
@@ -1404,6 +1517,7 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1404
1517
|
body.publish_now = true;
|
|
1405
1518
|
}
|
|
1406
1519
|
|
|
1520
|
+
log(`Submitting review request with access=${visibility}...`);
|
|
1407
1521
|
const result = await requestJsonImpl(`${registry}/api/skills/submit`, {
|
|
1408
1522
|
method: "POST",
|
|
1409
1523
|
headers: withBearerToken({
|
|
@@ -1411,6 +1525,7 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1411
1525
|
}, token),
|
|
1412
1526
|
body: JSON.stringify(body),
|
|
1413
1527
|
});
|
|
1528
|
+
log(`Review submission created: ${result.submission_id || "(no submission id returned)"}`);
|
|
1414
1529
|
|
|
1415
1530
|
log(JSON.stringify({
|
|
1416
1531
|
status: result.status,
|
|
@@ -1428,11 +1543,10 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1428
1543
|
async function cmdSubmit(flags, deps = {}) {
|
|
1429
1544
|
const requestJsonImpl = deps.requestJson || requestJson;
|
|
1430
1545
|
const log = deps.log || ((message) => console.log(message));
|
|
1431
|
-
const homeDir = deps.homeDir || os.homedir();
|
|
1432
1546
|
const skillDirArg = flags._[0] || ".";
|
|
1433
1547
|
const skillDir = path.resolve(skillDirArg);
|
|
1434
1548
|
const registry = getRegistryUrl(flags);
|
|
1435
|
-
const token = await
|
|
1549
|
+
const { token } = await ensureValidAuthToken(registry, flags, deps);
|
|
1436
1550
|
const manifestPath = path.resolve(flags.manifest || path.join(skillDir, "skill.json"));
|
|
1437
1551
|
const manifestRaw = await fs.readFile(manifestPath, "utf8");
|
|
1438
1552
|
const manifest = JSON.parse(manifestRaw);
|