ht-skills 0.2.6 → 0.2.7

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 +210 -16
  2. package/package.json +1 -1
package/lib/cli.js CHANGED
@@ -819,6 +819,64 @@ function formatSearchCard(item, { index = 0, colorize = (text) => text, width =
819
819
  return [title, blank, firstLine, ...detailRendered, blank, bottom].join("\n");
820
820
  }
821
821
 
822
+ const PUBLISH_FLOW_STEPS = [
823
+ { id: "auth", label: "Login" },
824
+ { id: "pack", label: "Pack" },
825
+ { id: "inspect", label: "Inspect" },
826
+ { id: "submit", label: "Submit" },
827
+ ];
828
+
829
+ function getPublishStepStatusLabel(status) {
830
+ switch (status) {
831
+ case "done":
832
+ return "Done";
833
+ case "active":
834
+ return "Active";
835
+ case "error":
836
+ return "Error";
837
+ default:
838
+ return "Pending";
839
+ }
840
+ }
841
+
842
+ function renderPublishFlowCard(flowState, { colorize = (text) => text, width = 80 } = {}) {
843
+ const maxInnerWidth = Math.max(28, width - 6);
844
+ const lines = PUBLISH_FLOW_STEPS.map((step, index) => {
845
+ const status = flowState?.[step.id] || "pending";
846
+ const symbol = status === "done"
847
+ ? colorize("●", "success")
848
+ : status === "active"
849
+ ? colorize("◉", "accent")
850
+ : status === "error"
851
+ ? colorize("▲", "warn")
852
+ : colorize("○", "muted");
853
+ const label = status === "done"
854
+ ? colorize(step.label, "success")
855
+ : status === "active"
856
+ ? colorize(step.label, "accent")
857
+ : status === "error"
858
+ ? colorize(step.label, "warn")
859
+ : step.label;
860
+ const statusLabel = colorize(getPublishStepStatusLabel(status), status === "pending" ? "muted" : status === "error" ? "warn" : status === "active" ? "accent" : "success");
861
+ const plainLine = `${index + 1}. ${step.label} ${getPublishStepStatusLabel(status)}`;
862
+ const paddedWidth = Math.max(0, maxInnerWidth - getDisplayWidth(plainLine));
863
+ return `│ ${symbol} ${label}${" ".repeat(paddedWidth)} ${statusLabel} │`;
864
+ });
865
+
866
+ const contentWidth = Math.max(
867
+ 28,
868
+ ...PUBLISH_FLOW_STEPS.map((step, index) => getDisplayWidth(`${index + 1}. ${step.label} Pending`)),
869
+ );
870
+ const title = `◇ ${colorize("Publish Flow", "accent")} ${colorize("─".repeat(Math.max(0, contentWidth - getDisplayWidth("Publish Flow") - 1)), "muted")}╮`;
871
+ const normalizedLines = lines.map((line) => {
872
+ const plainWidth = getDisplayWidth(line);
873
+ const targetWidth = contentWidth + 4;
874
+ return plainWidth < targetWidth ? `${line.slice(0, -2)}${" ".repeat(targetWidth - plainWidth)} │` : line;
875
+ });
876
+ const bottom = `╰${"─".repeat(contentWidth + 2)}╯`;
877
+ return [title, ...normalizedLines, bottom].join("\n");
878
+ }
879
+
822
880
  function printFallbackIntro({ registry, slug, version, skillName, skillDescription, installTargets }, log = console.log) {
823
881
  log("");
824
882
  log("ht-skills");
@@ -848,6 +906,19 @@ function printFallbackSearchIntro({ registry, query, limit }, log = console.log)
848
906
  log("");
849
907
  }
850
908
 
909
+ function printFallbackPublishIntro({ registry, skillDir, archiveName, visibility }, log = console.log) {
910
+ log("");
911
+ log(renderGradientBanner());
912
+ log("");
913
+ log("Publish");
914
+ log("");
915
+ log(`Source: ${registry}`);
916
+ log(`Directory: ${skillDir}`);
917
+ log(`Archive: ${archiveName}`);
918
+ log(`Access: ${visibility}`);
919
+ log("");
920
+ }
921
+
851
922
  async function loadTerminalUi() {
852
923
  const [{ intro, outro, note, multiselect, select, spinner, isCancel, cancel }, pcModule] = await Promise.all([
853
924
  import("@clack/prompts"),
@@ -1440,17 +1511,88 @@ async function cmdPublish(flags, deps = {}) {
1440
1511
  const log = deps.log || ((message) => console.log(message));
1441
1512
  const registry = getRegistryUrl(flags);
1442
1513
  const skillDir = path.resolve(flags._[0] || ".");
1443
- log(`Checking login for ${registry}...`);
1514
+ const visibility = String(flags.access || flags.visibility || "public").trim().toLowerCase() || "public";
1515
+ const archiveName = `${path.basename(skillDir) || "skill"}.zip`;
1516
+ const isInteractive = isInteractiveSession(deps);
1517
+ const canUseFancyUi = Boolean(isInteractive && !deps.disableUi);
1518
+ const ui = deps.ui || (canUseFancyUi ? await loadTerminalUi().catch(() => null) : null);
1519
+ const colorize = ui ? createUiColorizer(ui.pc) : (text) => String(text);
1520
+ const outputWidth = getOutputWidth(deps.outputWidth);
1521
+ const pollIntervalMs = Math.max(500, Number(flags["poll-interval"] || deps.pollIntervalMs || DEFAULT_PUBLISH_POLL_MS));
1522
+ const flowState = {
1523
+ auth: "pending",
1524
+ pack: "pending",
1525
+ inspect: "pending",
1526
+ submit: "pending",
1527
+ };
1528
+
1529
+ const renderPublishFlow = () => {
1530
+ if (!ui) return;
1531
+ // eslint-disable-next-line no-console
1532
+ console.log(renderPublishFlowCard(flowState, {
1533
+ colorize,
1534
+ width: Math.max(44, Math.min(outputWidth - 4, 72)),
1535
+ }));
1536
+ };
1537
+
1538
+ if (ui) {
1539
+ // eslint-disable-next-line no-console
1540
+ console.log(`\n${renderGradientBanner()}\n`);
1541
+ ui.intro(ui.pc.bgCyan(ui.pc.black(" ht-skills ")));
1542
+ ui.note(
1543
+ [
1544
+ `${colorize("Source", "muted")}: ${registry}`,
1545
+ `${colorize("Directory", "muted")}: ${skillDir}`,
1546
+ `${colorize("Archive", "muted")}: ${archiveName}`,
1547
+ `${colorize("Access", "muted")}: ${visibility}`,
1548
+ ].join("\n"),
1549
+ "Publish",
1550
+ );
1551
+ renderPublishFlow();
1552
+ } else {
1553
+ printFallbackPublishIntro({ registry, skillDir, archiveName, visibility }, log);
1554
+ }
1555
+
1556
+ const authSpinner = ui ? ui.spinner() : null;
1557
+ flowState.auth = "active";
1558
+ renderPublishFlow();
1559
+ if (authSpinner) {
1560
+ authSpinner.start("Checking login");
1561
+ } else {
1562
+ log(`Checking login for ${registry}...`);
1563
+ }
1444
1564
  const { token } = await ensureValidAuthToken(registry, flags, deps);
1565
+ flowState.auth = "done";
1566
+ renderPublishFlow();
1567
+ if (authSpinner) {
1568
+ authSpinner.stop("Login confirmed");
1569
+ }
1445
1570
 
1446
- const archiveName = `${path.basename(skillDir) || "skill"}.zip`;
1447
- log(`Packing skill directory: ${skillDir}`);
1571
+ const packSpinner = ui ? ui.spinner() : null;
1572
+ flowState.pack = "active";
1573
+ renderPublishFlow();
1574
+ if (packSpinner) {
1575
+ packSpinner.start(`Packing ${path.basename(skillDir) || skillDir}`);
1576
+ } else {
1577
+ log(`Packing skill directory: ${skillDir}`);
1578
+ }
1448
1579
  const archiveBuffer = await createZipFromDirectory(skillDir);
1449
- log(`Created archive ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1450
-
1451
- const pollIntervalMs = Math.max(500, Number(flags["poll-interval"] || deps.pollIntervalMs || DEFAULT_PUBLISH_POLL_MS));
1580
+ flowState.pack = "done";
1581
+ renderPublishFlow();
1582
+ if (packSpinner) {
1583
+ packSpinner.stop(`Created ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1584
+ } else {
1585
+ log(`Created archive ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1586
+ }
1452
1587
 
1453
- log("Uploading archive for package inspection...");
1588
+ const uploadSpinner = ui ? ui.spinner() : null;
1589
+ flowState.inspect = "active";
1590
+ renderPublishFlow();
1591
+ if (uploadSpinner) {
1592
+ uploadSpinner.start("Uploading archive for package inspection");
1593
+ } else {
1594
+ log("Uploading archive for package inspection...");
1595
+ }
1454
1596
  const job = await requestJsonImpl(
1455
1597
  `${registry}/api/skills/inspect-package-jobs/upload?archive_name=${encodeURIComponent(archiveName)}`,
1456
1598
  {
@@ -1466,10 +1608,18 @@ async function cmdPublish(flags, deps = {}) {
1466
1608
  if (!jobId) {
1467
1609
  throw new Error("registry did not return an inspection job id");
1468
1610
  }
1469
- log(`Inspection job created: ${jobId}`);
1611
+ if (uploadSpinner) {
1612
+ uploadSpinner.stop(`Inspection job created (${jobId})`);
1613
+ } else {
1614
+ log(`Inspection job created: ${jobId}`);
1615
+ }
1470
1616
 
1471
1617
  let inspection = job;
1472
1618
  let lastProgressKey = "";
1619
+ const inspectSpinner = ui ? ui.spinner() : null;
1620
+ if (inspectSpinner) {
1621
+ inspectSpinner.start("Inspecting archive");
1622
+ }
1473
1623
  while (inspection.status !== "succeeded" && inspection.status !== "failed") {
1474
1624
  await sleep(pollIntervalMs);
1475
1625
  inspection = await requestJsonImpl(
@@ -1487,22 +1637,38 @@ async function cmdPublish(flags, deps = {}) {
1487
1637
  const progressLabel = step
1488
1638
  ? `${step}${typeof percent === "number" ? ` ${percent}%` : ""}`
1489
1639
  : inspection.status;
1490
- log(`Inspection in progress: ${progressLabel}`);
1640
+ if (inspectSpinner) {
1641
+ inspectSpinner.message(`Inspecting archive (${progressLabel})`);
1642
+ } else {
1643
+ log(`Inspection in progress: ${progressLabel}`);
1644
+ }
1491
1645
  }
1492
1646
  }
1493
1647
 
1494
1648
  if (inspection.status !== "succeeded") {
1649
+ flowState.inspect = "error";
1650
+ renderPublishFlow();
1651
+ if (inspectSpinner) {
1652
+ inspectSpinner.stop("Inspection failed");
1653
+ }
1495
1654
  throw new Error(inspection.error || "skill archive inspection failed");
1496
1655
  }
1497
- log("Inspection passed.");
1656
+ flowState.inspect = "done";
1657
+ renderPublishFlow();
1658
+ if (inspectSpinner) {
1659
+ inspectSpinner.stop("Inspection passed");
1660
+ } else {
1661
+ log("Inspection passed.");
1662
+ }
1498
1663
 
1499
1664
  const preview = inspection.result || {};
1500
1665
  if (!preview.valid || !preview.preview_token) {
1501
1666
  throw new Error(summarizePreviewErrors(preview));
1502
1667
  }
1503
- log(`Preview token created: ${preview.preview_token}`);
1668
+ if (!ui) {
1669
+ log(`Preview token created: ${preview.preview_token}`);
1670
+ }
1504
1671
 
1505
- const visibility = String(flags.access || flags.visibility || "public").trim().toLowerCase() || "public";
1506
1672
  const body = {
1507
1673
  preview_token: preview.preview_token,
1508
1674
  visibility,
@@ -1517,7 +1683,14 @@ async function cmdPublish(flags, deps = {}) {
1517
1683
  body.publish_now = true;
1518
1684
  }
1519
1685
 
1520
- log(`Submitting review request with access=${visibility}...`);
1686
+ const submitSpinner = ui ? ui.spinner() : null;
1687
+ flowState.submit = "active";
1688
+ renderPublishFlow();
1689
+ if (submitSpinner) {
1690
+ submitSpinner.start(`Submitting review request (${visibility})`);
1691
+ } else {
1692
+ log(`Submitting review request with access=${visibility}...`);
1693
+ }
1521
1694
  const result = await requestJsonImpl(`${registry}/api/skills/submit`, {
1522
1695
  method: "POST",
1523
1696
  headers: withBearerToken({
@@ -1525,9 +1698,15 @@ async function cmdPublish(flags, deps = {}) {
1525
1698
  }, token),
1526
1699
  body: JSON.stringify(body),
1527
1700
  });
1528
- log(`Review submission created: ${result.submission_id || "(no submission id returned)"}`);
1701
+ flowState.submit = "done";
1702
+ renderPublishFlow();
1703
+ if (submitSpinner) {
1704
+ submitSpinner.stop(`Review submission created (${result.submission_id || "pending"})`);
1705
+ } else {
1706
+ log(`Review submission created: ${result.submission_id || "(no submission id returned)"}`);
1707
+ }
1529
1708
 
1530
- log(JSON.stringify({
1709
+ const summaryPayload = {
1531
1710
  status: result.status,
1532
1711
  submission_id: result.submission_id || null,
1533
1712
  created_at: result.created_at || null,
@@ -1535,7 +1714,22 @@ async function cmdPublish(flags, deps = {}) {
1535
1714
  publication: result.publication || null,
1536
1715
  preview_token: preview.preview_token,
1537
1716
  archive_name: archiveName,
1538
- }, null, 2));
1717
+ };
1718
+
1719
+ if (ui) {
1720
+ ui.note(
1721
+ [
1722
+ `${colorize("Status", "muted")}: ${result.status || "pending"}`,
1723
+ `${colorize("Submission", "muted")}: ${result.submission_id || "-"}`,
1724
+ `${colorize("Preview", "muted")}: ${preview.preview_token}`,
1725
+ `${colorize("Access", "muted")}: ${summaryPayload.visibility || visibility}`,
1726
+ ].join("\n"),
1727
+ "Published",
1728
+ );
1729
+ ui.outro(`Submitted ${ui.pc.cyan(archiveName)} for review.`);
1730
+ } else {
1731
+ log(JSON.stringify(summaryPayload, null, 2));
1732
+ }
1539
1733
 
1540
1734
  return result;
1541
1735
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ht-skills",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "CLI for installing and submitting skills from HT Skills Marketplace.",
5
5
  "type": "commonjs",
6
6
  "bin": {