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.
- package/lib/cli.js +210 -16
- 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
|
-
|
|
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
|
|
1447
|
-
|
|
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
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
}
|