ht-skills 0.2.9 → 0.2.11

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