ht-skills 0.2.12 → 0.2.14
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 +81 -21
- 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();
|
|
@@ -441,6 +451,15 @@ function parseSpec(spec) {
|
|
|
441
451
|
return { slug, version: version || null };
|
|
442
452
|
}
|
|
443
453
|
|
|
454
|
+
function encodeSkillPathForUrl(pathValue) {
|
|
455
|
+
return String(pathValue || "")
|
|
456
|
+
.split("/")
|
|
457
|
+
.map((segment) => segment.trim())
|
|
458
|
+
.filter(Boolean)
|
|
459
|
+
.map((segment) => encodeURIComponent(segment))
|
|
460
|
+
.join("/");
|
|
461
|
+
}
|
|
462
|
+
|
|
444
463
|
function normalizeSkillSpecs(rawSpecs, { source = "install" } = {}) {
|
|
445
464
|
const values = Array.isArray(rawSpecs) ? rawSpecs : [rawSpecs];
|
|
446
465
|
const result = [];
|
|
@@ -952,7 +971,14 @@ function printFallbackSearchIntro({ registry, query, limit }, log = console.log)
|
|
|
952
971
|
log("");
|
|
953
972
|
}
|
|
954
973
|
|
|
955
|
-
function printFallbackPublishIntro({
|
|
974
|
+
function printFallbackPublishIntro({
|
|
975
|
+
registry,
|
|
976
|
+
skillDir,
|
|
977
|
+
archiveName,
|
|
978
|
+
visibility,
|
|
979
|
+
pathSlug = null,
|
|
980
|
+
skipUserDir = false,
|
|
981
|
+
}, log = console.log) {
|
|
956
982
|
log("");
|
|
957
983
|
log(renderGradientBanner());
|
|
958
984
|
log("");
|
|
@@ -962,6 +988,12 @@ function printFallbackPublishIntro({ registry, skillDir, archiveName, visibility
|
|
|
962
988
|
log(`Directory: ${skillDir}`);
|
|
963
989
|
log(`Archive: ${archiveName}`);
|
|
964
990
|
log(`Access: ${visibility}`);
|
|
991
|
+
if (pathSlug) {
|
|
992
|
+
log(`Path Slug: ${pathSlug}`);
|
|
993
|
+
}
|
|
994
|
+
if (skipUserDir) {
|
|
995
|
+
log("Skip User Dir: true");
|
|
996
|
+
}
|
|
965
997
|
log("");
|
|
966
998
|
}
|
|
967
999
|
|
|
@@ -1218,12 +1250,12 @@ async function resolveInteractiveToolIds(flags, deps, specs) {
|
|
|
1218
1250
|
}
|
|
1219
1251
|
|
|
1220
1252
|
const skillLabel = specs.length === 1
|
|
1221
|
-
? `${metadata?.manifest?.name || primarySpec.slug} (${primarySpec.slug}@${version})`
|
|
1253
|
+
? `${metadata?.manifest?.name || primarySpec.slug} (${(metadata?.manifest?.slug || primarySpec.slug)}@${version})`
|
|
1222
1254
|
: `${specs.length} skills`;
|
|
1223
1255
|
const skillDescription = specs.length === 1
|
|
1224
1256
|
? (metadata?.manifest?.description || "")
|
|
1225
1257
|
: specs.join(", ");
|
|
1226
|
-
const targetSlug = specs.length === 1 ? primarySpec.slug : "<skill-slug>";
|
|
1258
|
+
const targetSlug = specs.length === 1 ? (metadata?.manifest?.slug || primarySpec.slug) : "<skill-slug>";
|
|
1227
1259
|
const availableTargets = getAvailableInstallTargets(targetSlug, { homeDir });
|
|
1228
1260
|
|
|
1229
1261
|
if (availableTargets.length > 0 && ui) {
|
|
@@ -1238,9 +1270,9 @@ async function resolveInteractiveToolIds(flags, deps, specs) {
|
|
|
1238
1270
|
} else if (availableTargets.length > 0) {
|
|
1239
1271
|
printFallbackIntro({
|
|
1240
1272
|
registry,
|
|
1241
|
-
slug: specs.length === 1 ? primarySpec.slug : `${specs.length} skills`,
|
|
1273
|
+
slug: specs.length === 1 ? (metadata?.manifest?.slug || primarySpec.slug) : `${specs.length} skills`,
|
|
1242
1274
|
version: specs.length === 1 ? version : "mixed",
|
|
1243
|
-
skillName: specs.length === 1 ? (metadata?.manifest?.name || primarySpec.slug) : `${specs.length} skills`,
|
|
1275
|
+
skillName: specs.length === 1 ? (metadata?.manifest?.name || metadata?.manifest?.slug || primarySpec.slug) : `${specs.length} skills`,
|
|
1244
1276
|
skillDescription,
|
|
1245
1277
|
installTargets: availableTargets,
|
|
1246
1278
|
}, log);
|
|
@@ -1313,7 +1345,6 @@ async function installOne(flags, deps = {}) {
|
|
|
1313
1345
|
}
|
|
1314
1346
|
|
|
1315
1347
|
let toolIds = selectedToolIds.length > 0 ? selectedToolIds : normalizeToolIds(flags.tool);
|
|
1316
|
-
const renderInstallLine = deps.renderInstallLine || ((target) => `installed ${parsed.slug}@${version} -> ${target.target}`);
|
|
1317
1348
|
let metadata = null;
|
|
1318
1349
|
|
|
1319
1350
|
try {
|
|
@@ -1333,15 +1364,17 @@ async function installOne(flags, deps = {}) {
|
|
|
1333
1364
|
}
|
|
1334
1365
|
|
|
1335
1366
|
const skillName = metadata?.manifest?.name || parsed.slug;
|
|
1367
|
+
const installSlug = metadata?.manifest?.slug || parsed.slug;
|
|
1336
1368
|
const skillDescription = metadata?.manifest?.description || "";
|
|
1369
|
+
const renderInstallLine = deps.renderInstallLine || ((target) => `installed ${installSlug}@${version} -> ${target.target}`);
|
|
1337
1370
|
|
|
1338
1371
|
if (toolIds.length === 0 && !flags.target && isInteractive) {
|
|
1339
|
-
const availableTargets = getAvailableInstallTargets(
|
|
1372
|
+
const availableTargets = getAvailableInstallTargets(installSlug, { homeDir });
|
|
1340
1373
|
if (availableTargets.length > 0 && ui) {
|
|
1341
1374
|
ui.note(
|
|
1342
1375
|
[
|
|
1343
1376
|
`${colorize("Source", "muted")}: ${registry}`,
|
|
1344
|
-
`${colorize("Skill", "muted")}: ${colorize(skillName, "accent")} ${colorize(`(${
|
|
1377
|
+
`${colorize("Skill", "muted")}: ${colorize(skillName, "accent")} ${colorize(`(${installSlug}@${version})`, "muted")}`,
|
|
1345
1378
|
skillDescription ? `${colorize("Summary", "muted")}: ${skillDescription}` : "",
|
|
1346
1379
|
].filter(Boolean).join("\n"),
|
|
1347
1380
|
"Install plan",
|
|
@@ -1349,7 +1382,7 @@ async function installOne(flags, deps = {}) {
|
|
|
1349
1382
|
} else if (availableTargets.length > 0) {
|
|
1350
1383
|
printFallbackIntro({
|
|
1351
1384
|
registry,
|
|
1352
|
-
slug:
|
|
1385
|
+
slug: installSlug,
|
|
1353
1386
|
version,
|
|
1354
1387
|
skillName,
|
|
1355
1388
|
skillDescription,
|
|
@@ -1379,7 +1412,7 @@ async function installOne(flags, deps = {}) {
|
|
|
1379
1412
|
const installTargets = resolveInstallTargets({
|
|
1380
1413
|
toolIds,
|
|
1381
1414
|
target: flags.target,
|
|
1382
|
-
slug:
|
|
1415
|
+
slug: installSlug,
|
|
1383
1416
|
version,
|
|
1384
1417
|
homeDir,
|
|
1385
1418
|
});
|
|
@@ -1387,7 +1420,7 @@ async function installOne(flags, deps = {}) {
|
|
|
1387
1420
|
if (!ui) {
|
|
1388
1421
|
printFallbackIntro({
|
|
1389
1422
|
registry,
|
|
1390
|
-
slug:
|
|
1423
|
+
slug: installSlug,
|
|
1391
1424
|
version,
|
|
1392
1425
|
skillName,
|
|
1393
1426
|
skillDescription,
|
|
@@ -1428,7 +1461,8 @@ async function installOne(flags, deps = {}) {
|
|
|
1428
1461
|
metadataPath,
|
|
1429
1462
|
`${JSON.stringify(
|
|
1430
1463
|
{
|
|
1431
|
-
slug:
|
|
1464
|
+
slug: installSlug,
|
|
1465
|
+
requestedSlug: parsed.slug,
|
|
1432
1466
|
version,
|
|
1433
1467
|
installedAt: new Date().toISOString(),
|
|
1434
1468
|
registry,
|
|
@@ -1453,9 +1487,9 @@ async function installOne(flags, deps = {}) {
|
|
|
1453
1487
|
if (installTargets.length > 0) {
|
|
1454
1488
|
if (ui) {
|
|
1455
1489
|
ui.note(formatTargetsNote(installTargets, colorize), "Installed to");
|
|
1456
|
-
ui.outro(`Installed ${ui.pc.cyan(
|
|
1490
|
+
ui.outro(`Installed ${ui.pc.cyan(installSlug)} to ${installTargets.length} target${installTargets.length === 1 ? "" : "s"}.`);
|
|
1457
1491
|
} else {
|
|
1458
|
-
log(`Installed ${
|
|
1492
|
+
log(`Installed ${installSlug} to ${formatCount(installTargets.length, "target")}`);
|
|
1459
1493
|
}
|
|
1460
1494
|
}
|
|
1461
1495
|
|
|
@@ -1557,6 +1591,11 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1557
1591
|
const log = deps.log || ((message) => console.log(message));
|
|
1558
1592
|
const registry = getRegistryUrl(flags);
|
|
1559
1593
|
const skillDir = path.resolve(flags._[0] || ".");
|
|
1594
|
+
const pathSlug = String(flags["path-slug"] || "").trim() || null;
|
|
1595
|
+
const skipUserDir = isBooleanFlagEnabled(flags["skip-user-dir"]);
|
|
1596
|
+
if (pathSlug && skipUserDir) {
|
|
1597
|
+
throw new Error("--path-slug and --skip-user-dir cannot be used together");
|
|
1598
|
+
}
|
|
1560
1599
|
const visibility = String(flags.access || flags.visibility || "public").trim().toLowerCase() || "public";
|
|
1561
1600
|
const archiveName = `${path.basename(skillDir) || "skill"}.zip`;
|
|
1562
1601
|
const isInteractive = isInteractiveSession(deps);
|
|
@@ -1610,12 +1649,14 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1610
1649
|
`${colorize("Directory", "muted")}: ${skillDir}`,
|
|
1611
1650
|
`${colorize("Archive", "muted")}: ${archiveName}`,
|
|
1612
1651
|
`${colorize("Access", "muted")}: ${visibility}`,
|
|
1652
|
+
pathSlug ? `${colorize("Path Slug", "muted")}: ${pathSlug}` : "",
|
|
1653
|
+
skipUserDir ? `${colorize("Skip User Dir", "muted")}: true` : "",
|
|
1613
1654
|
].join("\n"),
|
|
1614
1655
|
"Publish",
|
|
1615
1656
|
);
|
|
1616
1657
|
renderPublishFlow();
|
|
1617
1658
|
} else {
|
|
1618
|
-
printFallbackPublishIntro({ registry, skillDir, archiveName, visibility }, log);
|
|
1659
|
+
printFallbackPublishIntro({ registry, skillDir, archiveName, visibility, pathSlug, skipUserDir }, log);
|
|
1619
1660
|
}
|
|
1620
1661
|
|
|
1621
1662
|
setFlowMessage(`Checking login for ${registry}`);
|
|
@@ -1761,6 +1802,12 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1761
1802
|
preview_token: preview.preview_token,
|
|
1762
1803
|
visibility,
|
|
1763
1804
|
};
|
|
1805
|
+
if (pathSlug) {
|
|
1806
|
+
body.path_slug = pathSlug;
|
|
1807
|
+
}
|
|
1808
|
+
if (skipUserDir) {
|
|
1809
|
+
body.skip_user_dir = true;
|
|
1810
|
+
}
|
|
1764
1811
|
if (flags["shared-with"]) {
|
|
1765
1812
|
body.shared_with = String(flags["shared-with"])
|
|
1766
1813
|
.split(",")
|
|
@@ -1805,16 +1852,19 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1805
1852
|
preview_token: preview.preview_token,
|
|
1806
1853
|
archive_name: archiveName,
|
|
1807
1854
|
};
|
|
1808
|
-
const
|
|
1809
|
-
result.publication?.
|
|
1855
|
+
const skillPath = String(
|
|
1856
|
+
result.publication?.pathSlug
|
|
1857
|
+
|| result.publication?.slug
|
|
1810
1858
|
|| result.publication?.manifest?.slug
|
|
1811
1859
|
|| preview.manifest?.slug
|
|
1812
1860
|
|| "",
|
|
1813
1861
|
).trim();
|
|
1814
|
-
const
|
|
1862
|
+
const encodedSkillPath = encodeSkillPathForUrl(skillPath);
|
|
1863
|
+
const skillUrl = encodedSkillPath ? `${registry}/skills/${encodedSkillPath}` : null;
|
|
1815
1864
|
summaryPayload.skill_url = skillUrl;
|
|
1816
1865
|
|
|
1817
1866
|
if (ui) {
|
|
1867
|
+
flowRenderer?.clear();
|
|
1818
1868
|
ui.note(
|
|
1819
1869
|
[
|
|
1820
1870
|
`${colorize("Status", "muted")}: ${result.status || "pending"}`,
|
|
@@ -1825,7 +1875,6 @@ async function cmdPublish(flags, deps = {}) {
|
|
|
1825
1875
|
].join("\n"),
|
|
1826
1876
|
"Published",
|
|
1827
1877
|
);
|
|
1828
|
-
setFlowMessage(`Submitted ${archiveName} for review`);
|
|
1829
1878
|
ui.outro(skillUrl
|
|
1830
1879
|
? `Submitted ${ui.pc.cyan(archiveName)} for review. Skill URL: ${skillUrl}`
|
|
1831
1880
|
: `Submitted ${ui.pc.cyan(archiveName)} for review.`);
|
|
@@ -1865,6 +1914,17 @@ async function cmdSubmit(flags, deps = {}) {
|
|
|
1865
1914
|
files,
|
|
1866
1915
|
submitter: String(flags.submitter || process.env.USERNAME || os.userInfo().username || "anonymous"),
|
|
1867
1916
|
};
|
|
1917
|
+
const pathSlug = String(flags["path-slug"] || "").trim();
|
|
1918
|
+
const skipUserDir = isBooleanFlagEnabled(flags["skip-user-dir"]);
|
|
1919
|
+
if (pathSlug && skipUserDir) {
|
|
1920
|
+
throw new Error("--path-slug and --skip-user-dir cannot be used together");
|
|
1921
|
+
}
|
|
1922
|
+
if (pathSlug) {
|
|
1923
|
+
body.path_slug = pathSlug;
|
|
1924
|
+
}
|
|
1925
|
+
if (skipUserDir) {
|
|
1926
|
+
body.skip_user_dir = true;
|
|
1927
|
+
}
|
|
1868
1928
|
|
|
1869
1929
|
if (flags.visibility) {
|
|
1870
1930
|
body.visibility = String(flags.visibility);
|