ht-skills 0.2.7 → 0.2.9
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.
- package/lib/cli.js +117 -57
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -36,6 +36,8 @@ const CONFIG_FILE_NAME = "config.json";
|
|
|
36
36
|
const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
37
37
|
const DEFAULT_LOGIN_POLL_MS = 1500;
|
|
38
38
|
const DEFAULT_PUBLISH_POLL_MS = 1500;
|
|
39
|
+
const DEFAULT_PUBLISH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
40
|
+
const DEFAULT_PUBLISH_POLL_ERROR_LIMIT = 3;
|
|
39
41
|
|
|
40
42
|
const BANNER_TEXT = String.raw` _ _ _____ ___ _ _ _ _ __ __ _ _ _
|
|
41
43
|
| || |_ _| / __| |_(_) | |___ | \/ |__ _ _ _| |_____| |_ _ __| |__ _ __
|
|
@@ -1519,6 +1521,8 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1519
1521
|
const colorize = ui ? createUiColorizer(ui.pc) : (text) => String(text);
|
|
1520
1522
|
const outputWidth = getOutputWidth(deps.outputWidth);
|
|
1521
1523
|
const pollIntervalMs = Math.max(500, Number(flags["poll-interval"] || deps.pollIntervalMs || DEFAULT_PUBLISH_POLL_MS));
|
|
1524
|
+
const publishTimeoutMs = Math.max(10_000, Number(flags.timeout || deps.timeoutMs || DEFAULT_PUBLISH_TIMEOUT_MS));
|
|
1525
|
+
const pollErrorLimit = Math.max(1, Number(flags["poll-error-limit"] || deps.pollErrorLimit || DEFAULT_PUBLISH_POLL_ERROR_LIMIT));
|
|
1522
1526
|
const flowState = {
|
|
1523
1527
|
auth: "pending",
|
|
1524
1528
|
pack: "pending",
|
|
@@ -1534,6 +1538,10 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1534
1538
|
width: Math.max(44, Math.min(outputWidth - 4, 72)),
|
|
1535
1539
|
}));
|
|
1536
1540
|
};
|
|
1541
|
+
const setFlowStatus = (stepId, status) => {
|
|
1542
|
+
flowState[stepId] = status;
|
|
1543
|
+
renderPublishFlow();
|
|
1544
|
+
};
|
|
1537
1545
|
|
|
1538
1546
|
if (ui) {
|
|
1539
1547
|
// eslint-disable-next-line no-console
|
|
@@ -1553,59 +1561,70 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1553
1561
|
printFallbackPublishIntro({ registry, skillDir, archiveName, visibility }, log);
|
|
1554
1562
|
}
|
|
1555
1563
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
renderPublishFlow();
|
|
1559
|
-
if (authSpinner) {
|
|
1560
|
-
authSpinner.start("Checking login");
|
|
1561
|
-
} else {
|
|
1564
|
+
setFlowStatus("auth", "active");
|
|
1565
|
+
if (!ui) {
|
|
1562
1566
|
log(`Checking login for ${registry}...`);
|
|
1563
1567
|
}
|
|
1564
1568
|
const { token } = await ensureValidAuthToken(registry, flags, deps);
|
|
1565
|
-
|
|
1566
|
-
renderPublishFlow();
|
|
1567
|
-
if (authSpinner) {
|
|
1568
|
-
authSpinner.stop("Login confirmed");
|
|
1569
|
-
}
|
|
1569
|
+
setFlowStatus("auth", "done");
|
|
1570
1570
|
|
|
1571
1571
|
const packSpinner = ui ? ui.spinner() : null;
|
|
1572
|
-
|
|
1573
|
-
renderPublishFlow();
|
|
1572
|
+
setFlowStatus("pack", "active");
|
|
1574
1573
|
if (packSpinner) {
|
|
1575
1574
|
packSpinner.start(`Packing ${path.basename(skillDir) || skillDir}`);
|
|
1576
1575
|
} else {
|
|
1577
1576
|
log(`Packing skill directory: ${skillDir}`);
|
|
1578
1577
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
packSpinner
|
|
1584
|
-
|
|
1585
|
-
|
|
1578
|
+
let archiveBuffer;
|
|
1579
|
+
try {
|
|
1580
|
+
archiveBuffer = await createZipFromDirectory(skillDir);
|
|
1581
|
+
setFlowStatus("pack", "done");
|
|
1582
|
+
if (packSpinner) {
|
|
1583
|
+
packSpinner.stop(`Created ${archiveName} (${formatBytes(archiveBuffer.length)})`);
|
|
1584
|
+
} else {
|
|
1585
|
+
log(`Created archive ${archiveName} (${formatBytes(archiveBuffer.length)})`);
|
|
1586
|
+
}
|
|
1587
|
+
} catch (error) {
|
|
1588
|
+
setFlowStatus("pack", "error");
|
|
1589
|
+
if (packSpinner) {
|
|
1590
|
+
packSpinner.stop("Packing failed");
|
|
1591
|
+
}
|
|
1592
|
+
throw error;
|
|
1586
1593
|
}
|
|
1587
1594
|
|
|
1588
1595
|
const uploadSpinner = ui ? ui.spinner() : null;
|
|
1589
|
-
|
|
1590
|
-
renderPublishFlow();
|
|
1596
|
+
setFlowStatus("inspect", "active");
|
|
1591
1597
|
if (uploadSpinner) {
|
|
1592
1598
|
uploadSpinner.start("Uploading archive for package inspection");
|
|
1593
1599
|
} else {
|
|
1594
1600
|
log("Uploading archive for package inspection...");
|
|
1595
1601
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1602
|
+
let job;
|
|
1603
|
+
try {
|
|
1604
|
+
job = await requestJsonImpl(
|
|
1605
|
+
`${registry}/api/skills/inspect-package-jobs/upload?archive_name=${encodeURIComponent(archiveName)}`,
|
|
1606
|
+
{
|
|
1607
|
+
method: "POST",
|
|
1608
|
+
headers: withBearerToken({
|
|
1609
|
+
"content-type": "application/zip",
|
|
1610
|
+
}, token),
|
|
1611
|
+
body: archiveBuffer,
|
|
1612
|
+
},
|
|
1613
|
+
);
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
setFlowStatus("inspect", "error");
|
|
1616
|
+
if (uploadSpinner) {
|
|
1617
|
+
uploadSpinner.stop("Inspection upload failed");
|
|
1618
|
+
}
|
|
1619
|
+
throw error;
|
|
1620
|
+
}
|
|
1606
1621
|
|
|
1607
1622
|
const jobId = String(job.job_id || "").trim();
|
|
1608
1623
|
if (!jobId) {
|
|
1624
|
+
setFlowStatus("inspect", "error");
|
|
1625
|
+
if (uploadSpinner) {
|
|
1626
|
+
uploadSpinner.stop("Inspection upload failed");
|
|
1627
|
+
}
|
|
1609
1628
|
throw new Error("registry did not return an inspection job id");
|
|
1610
1629
|
}
|
|
1611
1630
|
if (uploadSpinner) {
|
|
@@ -1616,18 +1635,53 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1616
1635
|
|
|
1617
1636
|
let inspection = job;
|
|
1618
1637
|
let lastProgressKey = "";
|
|
1638
|
+
let consecutivePollErrors = 0;
|
|
1639
|
+
const publishDeadline = Date.now() + publishTimeoutMs;
|
|
1619
1640
|
const inspectSpinner = ui ? ui.spinner() : null;
|
|
1620
1641
|
if (inspectSpinner) {
|
|
1621
1642
|
inspectSpinner.start("Inspecting archive");
|
|
1622
1643
|
}
|
|
1623
1644
|
while (inspection.status !== "succeeded" && inspection.status !== "failed") {
|
|
1645
|
+
if (Date.now() > publishDeadline) {
|
|
1646
|
+
setFlowStatus("inspect", "error");
|
|
1647
|
+
if (inspectSpinner) {
|
|
1648
|
+
inspectSpinner.stop("Inspection timed out");
|
|
1649
|
+
}
|
|
1650
|
+
throw new Error(`Inspection timed out after ${Math.round(publishTimeoutMs / 1000)}s`);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1624
1653
|
await sleep(pollIntervalMs);
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1654
|
+
try {
|
|
1655
|
+
inspection = await requestJsonImpl(
|
|
1656
|
+
`${registry}/api/skills/inspect-package-jobs/${encodeURIComponent(jobId)}`,
|
|
1657
|
+
{
|
|
1658
|
+
headers: withBearerToken({}, token),
|
|
1659
|
+
},
|
|
1660
|
+
);
|
|
1661
|
+
consecutivePollErrors = 0;
|
|
1662
|
+
} catch (error) {
|
|
1663
|
+
consecutivePollErrors += 1;
|
|
1664
|
+
if (consecutivePollErrors >= pollErrorLimit) {
|
|
1665
|
+
setFlowStatus("inspect", "error");
|
|
1666
|
+
if (inspectSpinner) {
|
|
1667
|
+
inspectSpinner.stop("Inspection polling failed");
|
|
1668
|
+
}
|
|
1669
|
+
throw new Error(`Inspection polling failed ${consecutivePollErrors} times: ${error.message}`);
|
|
1670
|
+
}
|
|
1671
|
+
if (!inspectSpinner) {
|
|
1672
|
+
log(`Inspection polling retry ${consecutivePollErrors}/${pollErrorLimit}: ${error.message}`);
|
|
1673
|
+
}
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
const runtimeError = String(inspection.error?.message || inspection.error || "").trim();
|
|
1678
|
+
if (runtimeError && inspection.status !== "succeeded") {
|
|
1679
|
+
setFlowStatus("inspect", "error");
|
|
1680
|
+
if (inspectSpinner) {
|
|
1681
|
+
inspectSpinner.stop("Inspection failed");
|
|
1682
|
+
}
|
|
1683
|
+
throw new Error(runtimeError);
|
|
1684
|
+
}
|
|
1631
1685
|
|
|
1632
1686
|
const step = String(inspection.progress?.step || inspection.result?.step || "").trim();
|
|
1633
1687
|
const percent = inspection.progress?.percent;
|
|
@@ -1646,15 +1700,13 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1646
1700
|
}
|
|
1647
1701
|
|
|
1648
1702
|
if (inspection.status !== "succeeded") {
|
|
1649
|
-
|
|
1650
|
-
renderPublishFlow();
|
|
1703
|
+
setFlowStatus("inspect", "error");
|
|
1651
1704
|
if (inspectSpinner) {
|
|
1652
1705
|
inspectSpinner.stop("Inspection failed");
|
|
1653
1706
|
}
|
|
1654
1707
|
throw new Error(inspection.error || "skill archive inspection failed");
|
|
1655
1708
|
}
|
|
1656
|
-
|
|
1657
|
-
renderPublishFlow();
|
|
1709
|
+
setFlowStatus("inspect", "done");
|
|
1658
1710
|
if (inspectSpinner) {
|
|
1659
1711
|
inspectSpinner.stop("Inspection passed");
|
|
1660
1712
|
} else {
|
|
@@ -1663,6 +1715,7 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1663
1715
|
|
|
1664
1716
|
const preview = inspection.result || {};
|
|
1665
1717
|
if (!preview.valid || !preview.preview_token) {
|
|
1718
|
+
setFlowStatus("inspect", "error");
|
|
1666
1719
|
throw new Error(summarizePreviewErrors(preview));
|
|
1667
1720
|
}
|
|
1668
1721
|
if (!ui) {
|
|
@@ -1684,26 +1737,33 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1684
1737
|
}
|
|
1685
1738
|
|
|
1686
1739
|
const submitSpinner = ui ? ui.spinner() : null;
|
|
1687
|
-
|
|
1688
|
-
renderPublishFlow();
|
|
1740
|
+
setFlowStatus("submit", "active");
|
|
1689
1741
|
if (submitSpinner) {
|
|
1690
1742
|
submitSpinner.start(`Submitting review request (${visibility})`);
|
|
1691
1743
|
} else {
|
|
1692
1744
|
log(`Submitting review request with access=${visibility}...`);
|
|
1693
1745
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
submitSpinner
|
|
1705
|
-
|
|
1706
|
-
|
|
1746
|
+
let result;
|
|
1747
|
+
try {
|
|
1748
|
+
result = await requestJsonImpl(`${registry}/api/skills/submit`, {
|
|
1749
|
+
method: "POST",
|
|
1750
|
+
headers: withBearerToken({
|
|
1751
|
+
"content-type": "application/json",
|
|
1752
|
+
}, token),
|
|
1753
|
+
body: JSON.stringify(body),
|
|
1754
|
+
});
|
|
1755
|
+
setFlowStatus("submit", "done");
|
|
1756
|
+
if (submitSpinner) {
|
|
1757
|
+
submitSpinner.stop(`Review submission created (${result.submission_id || "pending"})`);
|
|
1758
|
+
} else {
|
|
1759
|
+
log(`Review submission created: ${result.submission_id || "(no submission id returned)"}`);
|
|
1760
|
+
}
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
setFlowStatus("submit", "error");
|
|
1763
|
+
if (submitSpinner) {
|
|
1764
|
+
submitSpinner.stop("Submit failed");
|
|
1765
|
+
}
|
|
1766
|
+
throw error;
|
|
1707
1767
|
}
|
|
1708
1768
|
|
|
1709
1769
|
const summaryPayload = {
|