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.
Files changed (2) hide show
  1. package/lib/cli.js +120 -6
  2. 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...\n");
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...\n");
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
- const token = await getRequiredAuthToken(registry, flags, { homeDir });
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 getRequiredAuthToken(registry, flags, { homeDir });
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ht-skills",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "CLI for installing and submitting skills from HT Skills Marketplace.",
5
5
  "type": "commonjs",
6
6
  "bin": {