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.
Files changed (3) hide show
  1. package/README.md +6 -2
  2. package/lib/cli.js +67 -18
  3. 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({ registry, skillDir, archiveName, visibility }, log = console.log) {
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(parsed.slug, { homeDir });
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(`(${parsed.slug}@${version})`, "muted")}`,
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: parsed.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: parsed.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: parsed.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: parsed.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(parsed.slug)} to ${installTargets.length} target${installTargets.length === 1 ? "" : "s"}.`);
1481
+ ui.outro(`Installed ${ui.pc.cyan(installSlug)} to ${installTargets.length} target${installTargets.length === 1 ? "" : "s"}.`);
1457
1482
  } else {
1458
- log(`Installed ${parsed.slug} to ${formatCount(installTargets.length, "target")}`);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ht-skills",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "CLI for installing and submitting skills from HT Skills Marketplace.",
5
5
  "type": "commonjs",
6
6
  "bin": {