ht-skills 0.2.8 → 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 +150 -95
  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,15 +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
  };
1583
+ const setFlowStatus = (stepId, status) => {
1584
+ flowState[stepId] = status;
1585
+ renderPublishFlow();
1586
+ };
1587
+ const setFlowMessage = (message) => {
1588
+ currentMessage = message;
1589
+ renderPublishFlow();
1590
+ };
1541
1591
 
1542
1592
  if (ui) {
1543
1593
  // eslint-disable-next-line no-console
@@ -1557,53 +1607,65 @@ async function cmdPublish(flags, deps = {}) {
1557
1607
  printFallbackPublishIntro({ registry, skillDir, archiveName, visibility }, log);
1558
1608
  }
1559
1609
 
1560
- flowState.auth = "active";
1610
+ setFlowMessage(`Checking login for ${registry}`);
1611
+ setFlowStatus("auth", "active");
1561
1612
  if (!ui) {
1562
1613
  log(`Checking login for ${registry}...`);
1563
1614
  }
1564
1615
  const { token } = await ensureValidAuthToken(registry, flags, deps);
1565
- flowState.auth = "done";
1616
+ setFlowMessage("Login confirmed");
1617
+ setFlowStatus("auth", "done");
1566
1618
 
1567
- const packSpinner = ui ? ui.spinner() : null;
1568
- flowState.pack = "active";
1569
- if (packSpinner) {
1570
- packSpinner.start(`Packing ${path.basename(skillDir) || skillDir}`);
1571
- } else {
1619
+ setFlowMessage(`Packing ${path.basename(skillDir) || skillDir}`);
1620
+ setFlowStatus("pack", "active");
1621
+ if (!ui) {
1572
1622
  log(`Packing skill directory: ${skillDir}`);
1573
1623
  }
1574
- const archiveBuffer = await createZipFromDirectory(skillDir);
1575
- flowState.pack = "done";
1576
- if (packSpinner) {
1577
- packSpinner.stop(`Created ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1578
- } else {
1579
- log(`Created archive ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1624
+ let archiveBuffer;
1625
+ try {
1626
+ archiveBuffer = await createZipFromDirectory(skillDir);
1627
+ setFlowMessage(`Created ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1628
+ setFlowStatus("pack", "done");
1629
+ if (!ui) {
1630
+ log(`Created archive ${archiveName} (${formatBytes(archiveBuffer.length)})`);
1631
+ }
1632
+ } catch (error) {
1633
+ setFlowMessage(`Packing failed: ${error.message}`);
1634
+ setFlowStatus("pack", "error");
1635
+ throw error;
1580
1636
  }
1581
1637
 
1582
- const uploadSpinner = ui ? ui.spinner() : null;
1583
- flowState.inspect = "active";
1584
- if (uploadSpinner) {
1585
- uploadSpinner.start("Uploading archive for package inspection");
1586
- } else {
1638
+ setFlowMessage("Uploading archive for package inspection");
1639
+ setFlowStatus("inspect", "active");
1640
+ if (!ui) {
1587
1641
  log("Uploading archive for package inspection...");
1588
1642
  }
1589
- const job = await requestJsonImpl(
1590
- `${registry}/api/skills/inspect-package-jobs/upload?archive_name=${encodeURIComponent(archiveName)}`,
1591
- {
1592
- method: "POST",
1593
- headers: withBearerToken({
1594
- "content-type": "application/zip",
1595
- }, token),
1596
- body: archiveBuffer,
1597
- },
1598
- );
1643
+ let job;
1644
+ try {
1645
+ job = await requestJsonImpl(
1646
+ `${registry}/api/skills/inspect-package-jobs/upload?archive_name=${encodeURIComponent(archiveName)}`,
1647
+ {
1648
+ method: "POST",
1649
+ headers: withBearerToken({
1650
+ "content-type": "application/zip",
1651
+ }, token),
1652
+ body: archiveBuffer,
1653
+ },
1654
+ );
1655
+ } catch (error) {
1656
+ setFlowMessage(`Inspection upload failed: ${error.message}`);
1657
+ setFlowStatus("inspect", "error");
1658
+ throw error;
1659
+ }
1599
1660
 
1600
1661
  const jobId = String(job.job_id || "").trim();
1601
1662
  if (!jobId) {
1663
+ setFlowMessage("Inspection upload failed: missing inspection job id");
1664
+ setFlowStatus("inspect", "error");
1602
1665
  throw new Error("registry did not return an inspection job id");
1603
1666
  }
1604
- if (uploadSpinner) {
1605
- uploadSpinner.stop(`Inspection job created (${jobId})`);
1606
- } else {
1667
+ setFlowMessage(`Inspection job created (${jobId})`);
1668
+ if (!ui) {
1607
1669
  log(`Inspection job created: ${jobId}`);
1608
1670
  }
1609
1671
 
@@ -1611,16 +1673,10 @@ async function cmdPublish(flags, deps = {}) {
1611
1673
  let lastProgressKey = "";
1612
1674
  let consecutivePollErrors = 0;
1613
1675
  const publishDeadline = Date.now() + publishTimeoutMs;
1614
- const inspectSpinner = ui ? ui.spinner() : null;
1615
- if (inspectSpinner) {
1616
- inspectSpinner.start("Inspecting archive");
1617
- }
1618
1676
  while (inspection.status !== "succeeded" && inspection.status !== "failed") {
1619
1677
  if (Date.now() > publishDeadline) {
1620
- flowState.inspect = "error";
1621
- if (inspectSpinner) {
1622
- inspectSpinner.stop("Inspection timed out");
1623
- }
1678
+ setFlowMessage(`Inspection timed out after ${Math.round(publishTimeoutMs / 1000)}s`);
1679
+ setFlowStatus("inspect", "error");
1624
1680
  throw new Error(`Inspection timed out after ${Math.round(publishTimeoutMs / 1000)}s`);
1625
1681
  }
1626
1682
 
@@ -1636,13 +1692,12 @@ async function cmdPublish(flags, deps = {}) {
1636
1692
  } catch (error) {
1637
1693
  consecutivePollErrors += 1;
1638
1694
  if (consecutivePollErrors >= pollErrorLimit) {
1639
- flowState.inspect = "error";
1640
- if (inspectSpinner) {
1641
- inspectSpinner.stop("Inspection polling failed");
1642
- }
1695
+ setFlowMessage(`Inspection polling failed: ${error.message}`);
1696
+ setFlowStatus("inspect", "error");
1643
1697
  throw new Error(`Inspection polling failed ${consecutivePollErrors} times: ${error.message}`);
1644
1698
  }
1645
- if (!inspectSpinner) {
1699
+ setFlowMessage(`Inspection polling retry ${consecutivePollErrors}/${pollErrorLimit}: ${error.message}`);
1700
+ if (!ui) {
1646
1701
  log(`Inspection polling retry ${consecutivePollErrors}/${pollErrorLimit}: ${error.message}`);
1647
1702
  }
1648
1703
  continue;
@@ -1650,10 +1705,8 @@ async function cmdPublish(flags, deps = {}) {
1650
1705
 
1651
1706
  const runtimeError = String(inspection.error?.message || inspection.error || "").trim();
1652
1707
  if (runtimeError && inspection.status !== "succeeded") {
1653
- flowState.inspect = "error";
1654
- if (inspectSpinner) {
1655
- inspectSpinner.stop("Inspection failed");
1656
- }
1708
+ setFlowMessage(`Inspection failed: ${runtimeError}`);
1709
+ setFlowStatus("inspect", "error");
1657
1710
  throw new Error(runtimeError);
1658
1711
  }
1659
1712
 
@@ -1665,30 +1718,28 @@ async function cmdPublish(flags, deps = {}) {
1665
1718
  const progressLabel = step
1666
1719
  ? `${step}${typeof percent === "number" ? ` ${percent}%` : ""}`
1667
1720
  : inspection.status;
1668
- if (inspectSpinner) {
1669
- inspectSpinner.message(`Inspecting archive (${progressLabel})`);
1670
- } else {
1721
+ setFlowMessage(`Inspecting archive (${progressLabel})`);
1722
+ if (!ui) {
1671
1723
  log(`Inspection in progress: ${progressLabel}`);
1672
1724
  }
1673
1725
  }
1674
1726
  }
1675
1727
 
1676
1728
  if (inspection.status !== "succeeded") {
1677
- flowState.inspect = "error";
1678
- if (inspectSpinner) {
1679
- inspectSpinner.stop("Inspection failed");
1680
- }
1729
+ setFlowMessage(`Inspection failed: ${inspection.error || "unknown error"}`);
1730
+ setFlowStatus("inspect", "error");
1681
1731
  throw new Error(inspection.error || "skill archive inspection failed");
1682
1732
  }
1683
- flowState.inspect = "done";
1684
- if (inspectSpinner) {
1685
- inspectSpinner.stop("Inspection passed");
1686
- } else {
1733
+ setFlowMessage("Inspection passed");
1734
+ setFlowStatus("inspect", "done");
1735
+ if (!ui) {
1687
1736
  log("Inspection passed.");
1688
1737
  }
1689
1738
 
1690
1739
  const preview = inspection.result || {};
1691
1740
  if (!preview.valid || !preview.preview_token) {
1741
+ setFlowMessage("Inspection failed: preview token missing");
1742
+ setFlowStatus("inspect", "error");
1692
1743
  throw new Error(summarizePreviewErrors(preview));
1693
1744
  }
1694
1745
  if (!ui) {
@@ -1709,25 +1760,29 @@ async function cmdPublish(flags, deps = {}) {
1709
1760
  body.publish_now = true;
1710
1761
  }
1711
1762
 
1712
- const submitSpinner = ui ? ui.spinner() : null;
1713
- flowState.submit = "active";
1714
- if (submitSpinner) {
1715
- submitSpinner.start(`Submitting review request (${visibility})`);
1716
- } else {
1763
+ setFlowMessage(`Submitting review request (${visibility})`);
1764
+ setFlowStatus("submit", "active");
1765
+ if (!ui) {
1717
1766
  log(`Submitting review request with access=${visibility}...`);
1718
1767
  }
1719
- const result = await requestJsonImpl(`${registry}/api/skills/submit`, {
1720
- method: "POST",
1721
- headers: withBearerToken({
1722
- "content-type": "application/json",
1723
- }, token),
1724
- body: JSON.stringify(body),
1725
- });
1726
- flowState.submit = "done";
1727
- if (submitSpinner) {
1728
- submitSpinner.stop(`Review submission created (${result.submission_id || "pending"})`);
1729
- } else {
1730
- log(`Review submission created: ${result.submission_id || "(no submission id returned)"}`);
1768
+ let result;
1769
+ try {
1770
+ result = await requestJsonImpl(`${registry}/api/skills/submit`, {
1771
+ method: "POST",
1772
+ headers: withBearerToken({
1773
+ "content-type": "application/json",
1774
+ }, token),
1775
+ body: JSON.stringify(body),
1776
+ });
1777
+ setFlowMessage(`Review submission created (${result.submission_id || "pending"})`);
1778
+ setFlowStatus("submit", "done");
1779
+ if (!ui) {
1780
+ log(`Review submission created: ${result.submission_id || "(no submission id returned)"}`);
1781
+ }
1782
+ } catch (error) {
1783
+ setFlowMessage(`Submit failed: ${error.message}`);
1784
+ setFlowStatus("submit", "error");
1785
+ throw error;
1731
1786
  }
1732
1787
 
1733
1788
  const summaryPayload = {
@@ -1750,7 +1805,7 @@ async function cmdPublish(flags, deps = {}) {
1750
1805
  ].join("\n"),
1751
1806
  "Published",
1752
1807
  );
1753
- renderPublishFlow();
1808
+ setFlowMessage(`Submitted ${archiveName} for review`);
1754
1809
  ui.outro(`Submitted ${ui.pc.cyan(archiveName)} for review.`);
1755
1810
  } else {
1756
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.8",
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": {