ht-skills 0.2.12 → 0.2.13
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/README.md +6 -2
- package/lib/cli.js +67 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,9 +12,9 @@ npx ht-skills add --skill repo-bug-analyze
|
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
14
|
ht-skills login [--registry <url>]
|
|
15
|
-
ht-skills publish [skillDir] [--registry <url>] [--access public|private|shared] [--shared-with a@b.com,c@d.com] [--publish-now]
|
|
15
|
+
ht-skills publish [skillDir] [--registry <url>] [--path-slug <path>] [--skip-user-dir] [--access public|private|shared] [--shared-with a@b.com,c@d.com] [--publish-now]
|
|
16
16
|
ht-skills search <query> [--registry <url>] [--limit <n>]
|
|
17
|
-
ht-skills submit <skillDir> [--registry <url>] [--submitter <name>] [--visibility public|private|shared] [--shared-with a@b.com,c@d.com] [--publish-now]
|
|
17
|
+
ht-skills submit <skillDir> [--registry <url>] [--path-slug <path>] [--skip-user-dir] [--submitter <name>] [--visibility public|private|shared] [--shared-with a@b.com,c@d.com] [--publish-now]
|
|
18
18
|
ht-skills install <slug[@version]> [more-skills...] [--registry <url>] [--target <dir>] [--tool codex|claude|vscode]
|
|
19
19
|
ht-skills add [registry] --skill <slug[@version]>,<slug[@version]> [--tool codex|claude|vscode]
|
|
20
20
|
```
|
|
@@ -22,3 +22,7 @@ ht-skills add [registry] --skill <slug[@version]>,<slug[@version]> [--tool codex
|
|
|
22
22
|
`login` prints a dedicated `/cli-login` URL, waits for Enter, opens the browser, and stores the approved registry token under `~/.ht-skills/config.json`.
|
|
23
23
|
|
|
24
24
|
`publish` zips the target skill directory, uploads it through the marketplace package inspection flow, and then submits the generated preview token for review. The default access is `public`; use `--access private` to keep the skill private before review.
|
|
25
|
+
|
|
26
|
+
`--path-slug` lets you publish under a nested registry path (for example `gcs/abc`) while keeping the skill manifest slug (for example `abc`) unchanged for local install naming.
|
|
27
|
+
|
|
28
|
+
`--skip-user-dir` requests publishing to the top-level path (without the default username prefix). The server only accepts this for admin users.
|
package/lib/cli.js
CHANGED
|
@@ -50,8 +50,8 @@ function printHelp() {
|
|
|
50
50
|
console.log(`Usage:
|
|
51
51
|
ht-skills search <query> [--registry <url>] [--limit <n>]
|
|
52
52
|
ht-skills login [--registry <url>]
|
|
53
|
-
ht-skills publish [skillDir] [--registry <url>] [--access public|private|shared] [--shared-with a@b.com,c@d.com] [--publish-now]
|
|
54
|
-
ht-skills submit <skillDir> [--registry <url>] [--submitter <name>] [--visibility public|private|shared] [--shared-with a@b.com,c@d.com] [--publish-now]
|
|
53
|
+
ht-skills publish [skillDir] [--registry <url>] [--path-slug <path>] [--skip-user-dir] [--access public|private|shared] [--shared-with a@b.com,c@d.com] [--publish-now]
|
|
54
|
+
ht-skills submit <skillDir> [--registry <url>] [--path-slug <path>] [--skip-user-dir] [--submitter <name>] [--visibility public|private|shared] [--shared-with a@b.com,c@d.com] [--publish-now]
|
|
55
55
|
ht-skills install <slug[@version]> [more-skills...] [--registry <url>] [--target <dir>] [--tool codex|claude|vscode]
|
|
56
56
|
ht-skills add [registry] --skill <slug[@version]>,<slug[@version]> [--tool codex|claude|vscode]
|
|
57
57
|
|
|
@@ -59,6 +59,8 @@ Examples:
|
|
|
59
59
|
ht-skills search openai
|
|
60
60
|
ht-skills login
|
|
61
61
|
ht-skills publish .
|
|
62
|
+
ht-skills publish . --path-slug gcs/abc
|
|
63
|
+
ht-skills publish . --skip-user-dir
|
|
62
64
|
ht-skills publish . --access private
|
|
63
65
|
ht-skills submit ./examples/hello-skill
|
|
64
66
|
ht-skills install hello-skill@1.0.0 --tool codex
|
|
@@ -98,6 +100,14 @@ function getRegistryUrl(flags) {
|
|
|
98
100
|
return String(flags.registry || process.env.SKILLS_REGISTRY_URL || DEFAULT_REGISTRY_URL).replace(/\/$/, "");
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
function isBooleanFlagEnabled(value) {
|
|
104
|
+
if (value === true) return true;
|
|
105
|
+
if (value === false || value == null) return false;
|
|
106
|
+
const normalized = String(value).trim().toLowerCase();
|
|
107
|
+
if (!normalized) return false;
|
|
108
|
+
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
|
|
109
|
+
}
|
|
110
|
+
|
|
101
111
|
async function requestJson(url, options = {}) {
|
|
102
112
|
const res = await fetch(url, options);
|
|
103
113
|
const text = await res.text();
|
|
@@ -952,7 +962,14 @@ function printFallbackSearchIntro({ registry, query, limit }, log = console.log)
|
|
|
952
962
|
log("");
|
|
953
963
|
}
|
|
954
964
|
|
|
955
|
-
function printFallbackPublishIntro({
|
|
965
|
+
function printFallbackPublishIntro({
|
|
966
|
+
registry,
|
|
967
|
+
skillDir,
|
|
968
|
+
archiveName,
|
|
969
|
+
visibility,
|
|
970
|
+
pathSlug = null,
|
|
971
|
+
skipUserDir = false,
|
|
972
|
+
}, log = console.log) {
|
|
956
973
|
log("");
|
|
957
974
|
log(renderGradientBanner());
|
|
958
975
|
log("");
|
|
@@ -962,6 +979,12 @@ function printFallbackPublishIntro({ registry, skillDir, archiveName, visibility
|
|
|
962
979
|
log(`Directory: ${skillDir}`);
|
|
963
980
|
log(`Archive: ${archiveName}`);
|
|
964
981
|
log(`Access: ${visibility}`);
|
|
982
|
+
if (pathSlug) {
|
|
983
|
+
log(`Path Slug: ${pathSlug}`);
|
|
984
|
+
}
|
|
985
|
+
if (skipUserDir) {
|
|
986
|
+
log("Skip User Dir: true");
|
|
987
|
+
}
|
|
965
988
|
log("");
|
|
966
989
|
}
|
|
967
990
|
|
|
@@ -1218,12 +1241,12 @@ async function resolveInteractiveToolIds(flags, deps, specs) {
|
|
|
1218
1241
|
}
|
|
1219
1242
|
|
|
1220
1243
|
const skillLabel = specs.length === 1
|
|
1221
|
-
? `${metadata?.manifest?.name || primarySpec.slug} (${primarySpec.slug}@${version})`
|
|
1244
|
+
? `${metadata?.manifest?.name || primarySpec.slug} (${(metadata?.manifest?.slug || primarySpec.slug)}@${version})`
|
|
1222
1245
|
: `${specs.length} skills`;
|
|
1223
1246
|
const skillDescription = specs.length === 1
|
|
1224
1247
|
? (metadata?.manifest?.description || "")
|
|
1225
1248
|
: specs.join(", ");
|
|
1226
|
-
const targetSlug = specs.length === 1 ? primarySpec.slug : "<skill-slug>";
|
|
1249
|
+
const targetSlug = specs.length === 1 ? (metadata?.manifest?.slug || primarySpec.slug) : "<skill-slug>";
|
|
1227
1250
|
const availableTargets = getAvailableInstallTargets(targetSlug, { homeDir });
|
|
1228
1251
|
|
|
1229
1252
|
if (availableTargets.length > 0 && ui) {
|
|
@@ -1238,9 +1261,9 @@ async function resolveInteractiveToolIds(flags, deps, specs) {
|
|
|
1238
1261
|
} else if (availableTargets.length > 0) {
|
|
1239
1262
|
printFallbackIntro({
|
|
1240
1263
|
registry,
|
|
1241
|
-
slug: specs.length === 1 ? primarySpec.slug : `${specs.length} skills`,
|
|
1264
|
+
slug: specs.length === 1 ? (metadata?.manifest?.slug || primarySpec.slug) : `${specs.length} skills`,
|
|
1242
1265
|
version: specs.length === 1 ? version : "mixed",
|
|
1243
|
-
skillName: specs.length === 1 ? (metadata?.manifest?.name || primarySpec.slug) : `${specs.length} skills`,
|
|
1266
|
+
skillName: specs.length === 1 ? (metadata?.manifest?.name || metadata?.manifest?.slug || primarySpec.slug) : `${specs.length} skills`,
|
|
1244
1267
|
skillDescription,
|
|
1245
1268
|
installTargets: availableTargets,
|
|
1246
1269
|
}, log);
|
|
@@ -1313,7 +1336,6 @@ async function installOne(flags, deps = {}) {
|
|
|
1313
1336
|
}
|
|
1314
1337
|
|
|
1315
1338
|
let toolIds = selectedToolIds.length > 0 ? selectedToolIds : normalizeToolIds(flags.tool);
|
|
1316
|
-
const renderInstallLine = deps.renderInstallLine || ((target) => `installed ${parsed.slug}@${version} -> ${target.target}`);
|
|
1317
1339
|
let metadata = null;
|
|
1318
1340
|
|
|
1319
1341
|
try {
|
|
@@ -1333,15 +1355,17 @@ async function installOne(flags, deps = {}) {
|
|
|
1333
1355
|
}
|
|
1334
1356
|
|
|
1335
1357
|
const skillName = metadata?.manifest?.name || parsed.slug;
|
|
1358
|
+
const installSlug = metadata?.manifest?.slug || parsed.slug;
|
|
1336
1359
|
const skillDescription = metadata?.manifest?.description || "";
|
|
1360
|
+
const renderInstallLine = deps.renderInstallLine || ((target) => `installed ${installSlug}@${version} -> ${target.target}`);
|
|
1337
1361
|
|
|
1338
1362
|
if (toolIds.length === 0 && !flags.target && isInteractive) {
|
|
1339
|
-
const availableTargets = getAvailableInstallTargets(
|
|
1363
|
+
const availableTargets = getAvailableInstallTargets(installSlug, { homeDir });
|
|
1340
1364
|
if (availableTargets.length > 0 && ui) {
|
|
1341
1365
|
ui.note(
|
|
1342
1366
|
[
|
|
1343
1367
|
`${colorize("Source", "muted")}: ${registry}`,
|
|
1344
|
-
`${colorize("Skill", "muted")}: ${colorize(skillName, "accent")} ${colorize(`(${
|
|
1368
|
+
`${colorize("Skill", "muted")}: ${colorize(skillName, "accent")} ${colorize(`(${installSlug}@${version})`, "muted")}`,
|
|
1345
1369
|
skillDescription ? `${colorize("Summary", "muted")}: ${skillDescription}` : "",
|
|
1346
1370
|
].filter(Boolean).join("\n"),
|
|
1347
1371
|
"Install plan",
|
|
@@ -1349,7 +1373,7 @@ async function installOne(flags, deps = {}) {
|
|
|
1349
1373
|
} else if (availableTargets.length > 0) {
|
|
1350
1374
|
printFallbackIntro({
|
|
1351
1375
|
registry,
|
|
1352
|
-
slug:
|
|
1376
|
+
slug: installSlug,
|
|
1353
1377
|
version,
|
|
1354
1378
|
skillName,
|
|
1355
1379
|
skillDescription,
|
|
@@ -1379,7 +1403,7 @@ async function installOne(flags, deps = {}) {
|
|
|
1379
1403
|
const installTargets = resolveInstallTargets({
|
|
1380
1404
|
toolIds,
|
|
1381
1405
|
target: flags.target,
|
|
1382
|
-
slug:
|
|
1406
|
+
slug: installSlug,
|
|
1383
1407
|
version,
|
|
1384
1408
|
homeDir,
|
|
1385
1409
|
});
|
|
@@ -1387,7 +1411,7 @@ async function installOne(flags, deps = {}) {
|
|
|
1387
1411
|
if (!ui) {
|
|
1388
1412
|
printFallbackIntro({
|
|
1389
1413
|
registry,
|
|
1390
|
-
slug:
|
|
1414
|
+
slug: installSlug,
|
|
1391
1415
|
version,
|
|
1392
1416
|
skillName,
|
|
1393
1417
|
skillDescription,
|
|
@@ -1428,7 +1452,8 @@ async function installOne(flags, deps = {}) {
|
|
|
1428
1452
|
metadataPath,
|
|
1429
1453
|
`${JSON.stringify(
|
|
1430
1454
|
{
|
|
1431
|
-
slug:
|
|
1455
|
+
slug: installSlug,
|
|
1456
|
+
requestedSlug: parsed.slug,
|
|
1432
1457
|
version,
|
|
1433
1458
|
installedAt: new Date().toISOString(),
|
|
1434
1459
|
registry,
|
|
@@ -1453,9 +1478,9 @@ async function installOne(flags, deps = {}) {
|
|
|
1453
1478
|
if (installTargets.length > 0) {
|
|
1454
1479
|
if (ui) {
|
|
1455
1480
|
ui.note(formatTargetsNote(installTargets, colorize), "Installed to");
|
|
1456
|
-
ui.outro(`Installed ${ui.pc.cyan(
|
|
1481
|
+
ui.outro(`Installed ${ui.pc.cyan(installSlug)} to ${installTargets.length} target${installTargets.length === 1 ? "" : "s"}.`);
|
|
1457
1482
|
} else {
|
|
1458
|
-
log(`Installed ${
|
|
1483
|
+
log(`Installed ${installSlug} to ${formatCount(installTargets.length, "target")}`);
|
|
1459
1484
|
}
|
|
1460
1485
|
}
|
|
1461
1486
|
|
|
@@ -1557,6 +1582,11 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1557
1582
|
const log = deps.log || ((message) => console.log(message));
|
|
1558
1583
|
const registry = getRegistryUrl(flags);
|
|
1559
1584
|
const skillDir = path.resolve(flags._[0] || ".");
|
|
1585
|
+
const pathSlug = String(flags["path-slug"] || "").trim() || null;
|
|
1586
|
+
const skipUserDir = isBooleanFlagEnabled(flags["skip-user-dir"]);
|
|
1587
|
+
if (pathSlug && skipUserDir) {
|
|
1588
|
+
throw new Error("--path-slug and --skip-user-dir cannot be used together");
|
|
1589
|
+
}
|
|
1560
1590
|
const visibility = String(flags.access || flags.visibility || "public").trim().toLowerCase() || "public";
|
|
1561
1591
|
const archiveName = `${path.basename(skillDir) || "skill"}.zip`;
|
|
1562
1592
|
const isInteractive = isInteractiveSession(deps);
|
|
@@ -1610,12 +1640,14 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1610
1640
|
`${colorize("Directory", "muted")}: ${skillDir}`,
|
|
1611
1641
|
`${colorize("Archive", "muted")}: ${archiveName}`,
|
|
1612
1642
|
`${colorize("Access", "muted")}: ${visibility}`,
|
|
1643
|
+
pathSlug ? `${colorize("Path Slug", "muted")}: ${pathSlug}` : "",
|
|
1644
|
+
skipUserDir ? `${colorize("Skip User Dir", "muted")}: true` : "",
|
|
1613
1645
|
].join("\n"),
|
|
1614
1646
|
"Publish",
|
|
1615
1647
|
);
|
|
1616
1648
|
renderPublishFlow();
|
|
1617
1649
|
} else {
|
|
1618
|
-
printFallbackPublishIntro({ registry, skillDir, archiveName, visibility }, log);
|
|
1650
|
+
printFallbackPublishIntro({ registry, skillDir, archiveName, visibility, pathSlug, skipUserDir }, log);
|
|
1619
1651
|
}
|
|
1620
1652
|
|
|
1621
1653
|
setFlowMessage(`Checking login for ${registry}`);
|
|
@@ -1761,6 +1793,12 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1761
1793
|
preview_token: preview.preview_token,
|
|
1762
1794
|
visibility,
|
|
1763
1795
|
};
|
|
1796
|
+
if (pathSlug) {
|
|
1797
|
+
body.path_slug = pathSlug;
|
|
1798
|
+
}
|
|
1799
|
+
if (skipUserDir) {
|
|
1800
|
+
body.skip_user_dir = true;
|
|
1801
|
+
}
|
|
1764
1802
|
if (flags["shared-with"]) {
|
|
1765
1803
|
body.shared_with = String(flags["shared-with"])
|
|
1766
1804
|
.split(",")
|
|
@@ -1815,6 +1853,7 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1815
1853
|
summaryPayload.skill_url = skillUrl;
|
|
1816
1854
|
|
|
1817
1855
|
if (ui) {
|
|
1856
|
+
flowRenderer?.clear();
|
|
1818
1857
|
ui.note(
|
|
1819
1858
|
[
|
|
1820
1859
|
`${colorize("Status", "muted")}: ${result.status || "pending"}`,
|
|
@@ -1825,7 +1864,6 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1825
1864
|
].join("\n"),
|
|
1826
1865
|
"Published",
|
|
1827
1866
|
);
|
|
1828
|
-
setFlowMessage(`Submitted ${archiveName} for review`);
|
|
1829
1867
|
ui.outro(skillUrl
|
|
1830
1868
|
? `Submitted ${ui.pc.cyan(archiveName)} for review. Skill URL: ${skillUrl}`
|
|
1831
1869
|
: `Submitted ${ui.pc.cyan(archiveName)} for review.`);
|
|
@@ -1865,6 +1903,17 @@ async function cmdSubmit(flags, deps = {}) {
|
|
|
1865
1903
|
files,
|
|
1866
1904
|
submitter: String(flags.submitter || process.env.USERNAME || os.userInfo().username || "anonymous"),
|
|
1867
1905
|
};
|
|
1906
|
+
const pathSlug = String(flags["path-slug"] || "").trim();
|
|
1907
|
+
const skipUserDir = isBooleanFlagEnabled(flags["skip-user-dir"]);
|
|
1908
|
+
if (pathSlug && skipUserDir) {
|
|
1909
|
+
throw new Error("--path-slug and --skip-user-dir cannot be used together");
|
|
1910
|
+
}
|
|
1911
|
+
if (pathSlug) {
|
|
1912
|
+
body.path_slug = pathSlug;
|
|
1913
|
+
}
|
|
1914
|
+
if (skipUserDir) {
|
|
1915
|
+
body.skip_user_dir = true;
|
|
1916
|
+
}
|
|
1868
1917
|
|
|
1869
1918
|
if (flags.visibility) {
|
|
1870
1919
|
body.visibility = String(flags.visibility);
|