ht-skills 0.2.9 → 0.2.10

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 +94 -74
  2. package/package.json +1 -1
package/lib/cli.js CHANGED
@@ -841,8 +841,15 @@ function getPublishStepStatusLabel(status) {
841
841
  }
842
842
  }
843
843
 
844
- function renderPublishFlowCard(flowState, { colorize = (text) => text, width = 80 } = {}) {
845
- const maxInnerWidth = Math.max(28, width - 6);
844
+ function renderPublishFlowCard(flowState, {
845
+ colorize = (text) => text,
846
+ width = 80,
847
+ currentMessage = "",
848
+ } = {}) {
849
+ const contentWidth = Math.max(
850
+ 28,
851
+ ...PUBLISH_FLOW_STEPS.map((step, index) => getDisplayWidth(`${index + 1}. ${step.label} Pending`)),
852
+ );
846
853
  const lines = PUBLISH_FLOW_STEPS.map((step, index) => {
847
854
  const status = flowState?.[step.id] || "pending";
848
855
  const symbol = status === "done"
@@ -859,24 +866,57 @@ function renderPublishFlowCard(flowState, { colorize = (text) => text, width = 8
859
866
  : status === "error"
860
867
  ? colorize(step.label, "warn")
861
868
  : step.label;
862
- const statusLabel = colorize(getPublishStepStatusLabel(status), status === "pending" ? "muted" : status === "error" ? "warn" : status === "active" ? "accent" : "success");
869
+ const statusLabel = colorize(
870
+ getPublishStepStatusLabel(status),
871
+ status === "pending" ? "muted" : status === "error" ? "warn" : status === "active" ? "accent" : "success",
872
+ );
863
873
  const plainLine = `${index + 1}. ${step.label} ${getPublishStepStatusLabel(status)}`;
864
- const paddedWidth = Math.max(0, maxInnerWidth - getDisplayWidth(plainLine));
874
+ const paddedWidth = Math.max(0, contentWidth - getDisplayWidth(plainLine));
865
875
  return `│ ${symbol} ${label}${" ".repeat(paddedWidth)} ${statusLabel} │`;
866
876
  });
867
877
 
868
- const contentWidth = Math.max(
869
- 28,
870
- ...PUBLISH_FLOW_STEPS.map((step, index) => getDisplayWidth(`${index + 1}. ${step.label} Pending`)),
871
- );
872
- const title = `◇ ${colorize("Publish Flow", "accent")} ${colorize("─".repeat(Math.max(0, contentWidth - getDisplayWidth("Publish Flow") - 1)), "muted")}╮`;
873
- const normalizedLines = lines.map((line) => {
874
- const plainWidth = getDisplayWidth(line);
875
- const targetWidth = contentWidth + 4;
876
- return plainWidth < targetWidth ? `${line.slice(0, -2)}${" ".repeat(targetWidth - plainWidth)} │` : line;
877
- });
878
+ const titleSuffix = "─".repeat(Math.max(0, contentWidth - getDisplayWidth("Publish Flow") - 1));
879
+ const title = `◇ ${colorize("Publish Flow", "accent")} ${colorize(titleSuffix, "muted")}╮`;
880
+ const messageLines = currentMessage
881
+ ? wrapText(currentMessage, contentWidth).map((line) => `│ ${colorize(line, "muted")}${" ".repeat(Math.max(0, contentWidth - getDisplayWidth(line)))} │`)
882
+ : [];
878
883
  const bottom = `╰${"─".repeat(contentWidth + 2)}╯`;
879
- return [title, ...normalizedLines, bottom].join("\n");
884
+ return [title, ...lines, ...messageLines, bottom].join("\n");
885
+ }
886
+
887
+ function createLiveBlockRenderer(output = process.stdout) {
888
+ let renderedLines = 0;
889
+
890
+ function clearPrevious() {
891
+ if (!renderedLines || !output.isTTY) {
892
+ return;
893
+ }
894
+ output.write(`\x1B[${renderedLines}F`);
895
+ for (let index = 0; index < renderedLines; index += 1) {
896
+ output.write("\x1B[2K");
897
+ if (index < renderedLines - 1) {
898
+ output.write("\x1B[1E");
899
+ }
900
+ }
901
+ if (renderedLines > 1) {
902
+ output.write(`\x1B[${renderedLines - 1}F`);
903
+ } else {
904
+ output.write("\r");
905
+ }
906
+ }
907
+
908
+ return {
909
+ render(content) {
910
+ const text = String(content || "");
911
+ clearPrevious();
912
+ output.write(`${text}\n`);
913
+ renderedLines = text.split("\n").length;
914
+ },
915
+ clear() {
916
+ clearPrevious();
917
+ renderedLines = 0;
918
+ },
919
+ };
880
920
  }
881
921
 
882
922
  function printFallbackIntro({ registry, slug, version, skillName, skillDescription, installTargets }, log = console.log) {
@@ -1529,19 +1569,25 @@ async function cmdPublish(flags, deps = {}) {
1529
1569
  inspect: "pending",
1530
1570
  submit: "pending",
1531
1571
  };
1572
+ let currentMessage = "Ready to publish";
1573
+ const flowRenderer = ui ? createLiveBlockRenderer(process.stdout) : null;
1532
1574
 
1533
1575
  const renderPublishFlow = () => {
1534
- if (!ui) return;
1535
- // eslint-disable-next-line no-console
1536
- console.log(renderPublishFlowCard(flowState, {
1576
+ if (!flowRenderer) return;
1577
+ flowRenderer.render(renderPublishFlowCard(flowState, {
1537
1578
  colorize,
1538
1579
  width: Math.max(44, Math.min(outputWidth - 4, 72)),
1580
+ currentMessage,
1539
1581
  }));
1540
1582
  };
1541
1583
  const setFlowStatus = (stepId, status) => {
1542
1584
  flowState[stepId] = status;
1543
1585
  renderPublishFlow();
1544
1586
  };
1587
+ const setFlowMessage = (message) => {
1588
+ currentMessage = message;
1589
+ renderPublishFlow();
1590
+ };
1545
1591
 
1546
1592
  if (ui) {
1547
1593
  // eslint-disable-next-line no-console
@@ -1561,42 +1607,37 @@ async function cmdPublish(flags, deps = {}) {
1561
1607
  printFallbackPublishIntro({ registry, skillDir, archiveName, visibility }, log);
1562
1608
  }
1563
1609
 
1610
+ setFlowMessage(`Checking login for ${registry}`);
1564
1611
  setFlowStatus("auth", "active");
1565
1612
  if (!ui) {
1566
1613
  log(`Checking login for ${registry}...`);
1567
1614
  }
1568
1615
  const { token } = await ensureValidAuthToken(registry, flags, deps);
1616
+ setFlowMessage("Login confirmed");
1569
1617
  setFlowStatus("auth", "done");
1570
1618
 
1571
- const packSpinner = ui ? ui.spinner() : null;
1619
+ setFlowMessage(`Packing ${path.basename(skillDir) || skillDir}`);
1572
1620
  setFlowStatus("pack", "active");
1573
- if (packSpinner) {
1574
- packSpinner.start(`Packing ${path.basename(skillDir) || skillDir}`);
1575
- } else {
1621
+ if (!ui) {
1576
1622
  log(`Packing skill directory: ${skillDir}`);
1577
1623
  }
1578
1624
  let archiveBuffer;
1579
1625
  try {
1580
1626
  archiveBuffer = await createZipFromDirectory(skillDir);
1627
+ setFlowMessage(`Created ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1581
1628
  setFlowStatus("pack", "done");
1582
- if (packSpinner) {
1583
- packSpinner.stop(`Created ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1584
- } else {
1629
+ if (!ui) {
1585
1630
  log(`Created archive ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1586
1631
  }
1587
1632
  } catch (error) {
1633
+ setFlowMessage(`Packing failed: ${error.message}`);
1588
1634
  setFlowStatus("pack", "error");
1589
- if (packSpinner) {
1590
- packSpinner.stop("Packing failed");
1591
- }
1592
1635
  throw error;
1593
1636
  }
1594
1637
 
1595
- const uploadSpinner = ui ? ui.spinner() : null;
1638
+ setFlowMessage("Uploading archive for package inspection");
1596
1639
  setFlowStatus("inspect", "active");
1597
- if (uploadSpinner) {
1598
- uploadSpinner.start("Uploading archive for package inspection");
1599
- } else {
1640
+ if (!ui) {
1600
1641
  log("Uploading archive for package inspection...");
1601
1642
  }
1602
1643
  let job;
@@ -1612,24 +1653,19 @@ async function cmdPublish(flags, deps = {}) {
1612
1653
  },
1613
1654
  );
1614
1655
  } catch (error) {
1656
+ setFlowMessage(`Inspection upload failed: ${error.message}`);
1615
1657
  setFlowStatus("inspect", "error");
1616
- if (uploadSpinner) {
1617
- uploadSpinner.stop("Inspection upload failed");
1618
- }
1619
1658
  throw error;
1620
1659
  }
1621
1660
 
1622
1661
  const jobId = String(job.job_id || "").trim();
1623
1662
  if (!jobId) {
1663
+ setFlowMessage("Inspection upload failed: missing inspection job id");
1624
1664
  setFlowStatus("inspect", "error");
1625
- if (uploadSpinner) {
1626
- uploadSpinner.stop("Inspection upload failed");
1627
- }
1628
1665
  throw new Error("registry did not return an inspection job id");
1629
1666
  }
1630
- if (uploadSpinner) {
1631
- uploadSpinner.stop(`Inspection job created (${jobId})`);
1632
- } else {
1667
+ setFlowMessage(`Inspection job created (${jobId})`);
1668
+ if (!ui) {
1633
1669
  log(`Inspection job created: ${jobId}`);
1634
1670
  }
1635
1671
 
@@ -1637,16 +1673,10 @@ async function cmdPublish(flags, deps = {}) {
1637
1673
  let lastProgressKey = "";
1638
1674
  let consecutivePollErrors = 0;
1639
1675
  const publishDeadline = Date.now() + publishTimeoutMs;
1640
- const inspectSpinner = ui ? ui.spinner() : null;
1641
- if (inspectSpinner) {
1642
- inspectSpinner.start("Inspecting archive");
1643
- }
1644
1676
  while (inspection.status !== "succeeded" && inspection.status !== "failed") {
1645
1677
  if (Date.now() > publishDeadline) {
1678
+ setFlowMessage(`Inspection timed out after ${Math.round(publishTimeoutMs / 1000)}s`);
1646
1679
  setFlowStatus("inspect", "error");
1647
- if (inspectSpinner) {
1648
- inspectSpinner.stop("Inspection timed out");
1649
- }
1650
1680
  throw new Error(`Inspection timed out after ${Math.round(publishTimeoutMs / 1000)}s`);
1651
1681
  }
1652
1682
 
@@ -1662,13 +1692,12 @@ async function cmdPublish(flags, deps = {}) {
1662
1692
  } catch (error) {
1663
1693
  consecutivePollErrors += 1;
1664
1694
  if (consecutivePollErrors >= pollErrorLimit) {
1695
+ setFlowMessage(`Inspection polling failed: ${error.message}`);
1665
1696
  setFlowStatus("inspect", "error");
1666
- if (inspectSpinner) {
1667
- inspectSpinner.stop("Inspection polling failed");
1668
- }
1669
1697
  throw new Error(`Inspection polling failed ${consecutivePollErrors} times: ${error.message}`);
1670
1698
  }
1671
- if (!inspectSpinner) {
1699
+ setFlowMessage(`Inspection polling retry ${consecutivePollErrors}/${pollErrorLimit}: ${error.message}`);
1700
+ if (!ui) {
1672
1701
  log(`Inspection polling retry ${consecutivePollErrors}/${pollErrorLimit}: ${error.message}`);
1673
1702
  }
1674
1703
  continue;
@@ -1676,10 +1705,8 @@ async function cmdPublish(flags, deps = {}) {
1676
1705
 
1677
1706
  const runtimeError = String(inspection.error?.message || inspection.error || "").trim();
1678
1707
  if (runtimeError && inspection.status !== "succeeded") {
1708
+ setFlowMessage(`Inspection failed: ${runtimeError}`);
1679
1709
  setFlowStatus("inspect", "error");
1680
- if (inspectSpinner) {
1681
- inspectSpinner.stop("Inspection failed");
1682
- }
1683
1710
  throw new Error(runtimeError);
1684
1711
  }
1685
1712
 
@@ -1691,30 +1718,27 @@ async function cmdPublish(flags, deps = {}) {
1691
1718
  const progressLabel = step
1692
1719
  ? `${step}${typeof percent === "number" ? ` ${percent}%` : ""}`
1693
1720
  : inspection.status;
1694
- if (inspectSpinner) {
1695
- inspectSpinner.message(`Inspecting archive (${progressLabel})`);
1696
- } else {
1721
+ setFlowMessage(`Inspecting archive (${progressLabel})`);
1722
+ if (!ui) {
1697
1723
  log(`Inspection in progress: ${progressLabel}`);
1698
1724
  }
1699
1725
  }
1700
1726
  }
1701
1727
 
1702
1728
  if (inspection.status !== "succeeded") {
1729
+ setFlowMessage(`Inspection failed: ${inspection.error || "unknown error"}`);
1703
1730
  setFlowStatus("inspect", "error");
1704
- if (inspectSpinner) {
1705
- inspectSpinner.stop("Inspection failed");
1706
- }
1707
1731
  throw new Error(inspection.error || "skill archive inspection failed");
1708
1732
  }
1733
+ setFlowMessage("Inspection passed");
1709
1734
  setFlowStatus("inspect", "done");
1710
- if (inspectSpinner) {
1711
- inspectSpinner.stop("Inspection passed");
1712
- } else {
1735
+ if (!ui) {
1713
1736
  log("Inspection passed.");
1714
1737
  }
1715
1738
 
1716
1739
  const preview = inspection.result || {};
1717
1740
  if (!preview.valid || !preview.preview_token) {
1741
+ setFlowMessage("Inspection failed: preview token missing");
1718
1742
  setFlowStatus("inspect", "error");
1719
1743
  throw new Error(summarizePreviewErrors(preview));
1720
1744
  }
@@ -1736,11 +1760,9 @@ async function cmdPublish(flags, deps = {}) {
1736
1760
  body.publish_now = true;
1737
1761
  }
1738
1762
 
1739
- const submitSpinner = ui ? ui.spinner() : null;
1763
+ setFlowMessage(`Submitting review request (${visibility})`);
1740
1764
  setFlowStatus("submit", "active");
1741
- if (submitSpinner) {
1742
- submitSpinner.start(`Submitting review request (${visibility})`);
1743
- } else {
1765
+ if (!ui) {
1744
1766
  log(`Submitting review request with access=${visibility}...`);
1745
1767
  }
1746
1768
  let result;
@@ -1752,17 +1774,14 @@ async function cmdPublish(flags, deps = {}) {
1752
1774
  }, token),
1753
1775
  body: JSON.stringify(body),
1754
1776
  });
1777
+ setFlowMessage(`Review submission created (${result.submission_id || "pending"})`);
1755
1778
  setFlowStatus("submit", "done");
1756
- if (submitSpinner) {
1757
- submitSpinner.stop(`Review submission created (${result.submission_id || "pending"})`);
1758
- } else {
1779
+ if (!ui) {
1759
1780
  log(`Review submission created: ${result.submission_id || "(no submission id returned)"}`);
1760
1781
  }
1761
1782
  } catch (error) {
1783
+ setFlowMessage(`Submit failed: ${error.message}`);
1762
1784
  setFlowStatus("submit", "error");
1763
- if (submitSpinner) {
1764
- submitSpinner.stop("Submit failed");
1765
- }
1766
1785
  throw error;
1767
1786
  }
1768
1787
 
@@ -1786,6 +1805,7 @@ async function cmdPublish(flags, deps = {}) {
1786
1805
  ].join("\n"),
1787
1806
  "Published",
1788
1807
  );
1808
+ setFlowMessage(`Submitted ${archiveName} for review`);
1789
1809
  ui.outro(`Submitted ${ui.pc.cyan(archiveName)} for review.`);
1790
1810
  } else {
1791
1811
  log(JSON.stringify(summaryPayload, null, 2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ht-skills",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "CLI for installing and submitting skills from HT Skills Marketplace.",
5
5
  "type": "commonjs",
6
6
  "bin": {