hngr 0.2.0 → 0.3.0

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.
@@ -1,4 +1,4 @@
1
- import { t as commandCatalog } from "./catalog-jU0-wzh3.mjs";
1
+ import { t as commandCatalog } from "./catalog-DbGLqQ8a.mjs";
2
2
  // src/commands/commands.ts
3
3
  function run(ctx) {
4
4
  const catalog = commandCatalog();
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as kebabCase, i as normalizeCommandName, n as COMMANDS, o as COMMAND_ALIASES, r as findCommand, t as commandCatalog } from "./catalog-jU0-wzh3.mjs";
1
+ import { a as kebabCase, i as normalizeCommandName, n as COMMANDS, o as COMMAND_ALIASES, r as findCommand, t as commandCatalog } from "./catalog-DbGLqQ8a.mjs";
2
2
  import { C as renderError, S as HngrError, c as redactUrlForDisplay, g as resolveConnection, i as buildRequestUrl, o as redactQuery, r as apiRequest, s as redactSensitive, u as DEFAULT_SERVER_URL, x as CancelledError } from "./http-2HWxLIOq.mjs";
3
3
  import { t as collectPages } from "./pagination-BbKLKSw3.mjs";
4
4
  import { createRequire } from "node:module";
@@ -5279,7 +5279,7 @@ function isYargsInstance(y) {
5279
5279
  // ../../node_modules/.pnpm/yargs@18.2.0/node_modules/yargs/index.mjs
5280
5280
  const Yargs = YargsFactory(esm_default);
5281
5281
  // package.json
5282
- var version = "0.2.0";
5282
+ var version = "0.3.0";
5283
5283
  // src/generated/completion.ts
5284
5284
  function isCompletionShell(value) {
5285
5285
  return value === "bash" || value === "zsh" || value === "fish";
@@ -6911,24 +6911,26 @@ function buildParser(onMatched) {
6911
6911
  parser.command("commands", "Print the command catalog", () => {}, (argv) => {
6912
6912
  onMatched({
6913
6913
  argv,
6914
- run: async (ctx) => (await import("./commands-BchmocR_.mjs")).run(ctx)
6914
+ run: async (ctx) => (await import("./commands-BHXSZ04F.mjs")).run(ctx)
6915
6915
  });
6916
6916
  });
6917
6917
  parser.command("auth", "Manage credentials", (auth) => {
6918
- auth.command("login", "Log in with username and password and mint an access token", (login) => login.option("token", {
6918
+ auth.command("login", "Log in through the browser device flow, or with --password / --token", (login) => login.option("password", {
6919
+ type: "boolean",
6920
+ describe: "Prompt for a username and password instead of the device flow"
6921
+ }).option("token", {
6919
6922
  type: "boolean",
6920
- default: false,
6921
6923
  describe: "Read an access token from stdin instead of prompting"
6922
- }), (argv) => {
6924
+ }).conflicts("password", "token"), (argv) => {
6923
6925
  onMatched({
6924
6926
  argv,
6925
- run: async (ctx, matchedArgv) => (await import("./auth-BT8HuCif.mjs")).login(ctx, matchedArgv)
6927
+ run: async (ctx, matchedArgv) => (await import("./auth-CgBUQUQn.mjs")).login(ctx, matchedArgv)
6926
6928
  });
6927
6929
  });
6928
6930
  auth.command("logout", "Revoke the stored token and delete the local config", () => {}, (argv) => {
6929
6931
  onMatched({
6930
6932
  argv,
6931
- run: async (ctx) => (await import("./auth-BT8HuCif.mjs")).logout(ctx)
6933
+ run: async (ctx) => (await import("./auth-CgBUQUQn.mjs")).logout(ctx)
6932
6934
  });
6933
6935
  });
6934
6936
  auth.command("status", "Show credential source, server, principal and token prefix", (status) => status.option("offline", {
@@ -6938,19 +6940,19 @@ function buildParser(onMatched) {
6938
6940
  }), (argv) => {
6939
6941
  onMatched({
6940
6942
  argv,
6941
- run: async (ctx, matchedArgv) => (await import("./auth-BT8HuCif.mjs")).status(ctx, matchedArgv)
6943
+ run: async (ctx, matchedArgv) => (await import("./auth-CgBUQUQn.mjs")).status(ctx, matchedArgv)
6942
6944
  });
6943
6945
  });
6944
6946
  auth.command("token", "Print the access token to stdout and nothing else", () => {}, (argv) => {
6945
6947
  onMatched({
6946
6948
  argv,
6947
- run: async (ctx) => (await import("./auth-BT8HuCif.mjs")).token(ctx)
6949
+ run: async (ctx) => (await import("./auth-CgBUQUQn.mjs")).token(ctx)
6948
6950
  });
6949
6951
  });
6950
6952
  auth.command("whoami", "Print the GET /me payload", () => {}, (argv) => {
6951
6953
  onMatched({
6952
6954
  argv,
6953
- run: async (ctx) => (await import("./auth-BT8HuCif.mjs")).whoami(ctx)
6955
+ run: async (ctx) => (await import("./auth-CgBUQUQn.mjs")).whoami(ctx)
6954
6956
  });
6955
6957
  });
6956
6958
  registerGeneratedAuth(auth, onMatched);
@@ -6984,7 +6986,7 @@ function buildParser(onMatched) {
6984
6986
  }), (argv) => {
6985
6987
  onMatched({
6986
6988
  argv,
6987
- run: async (ctx, matchedArgv) => (await import("./skills-DwoDzsXd.mjs")).add(ctx, matchedArgv)
6989
+ run: async (ctx, matchedArgv) => (await import("./skills-XDbBCC5f.mjs")).add(ctx, matchedArgv)
6988
6990
  });
6989
6991
  });
6990
6992
  skills.command("publish <paths..>", "Publish a local skill directory as a capability or a new version", (publish) => publish.option("namespace", {
@@ -7002,11 +7004,9 @@ function buildParser(onMatched) {
7002
7004
  }).option("readme", {
7003
7005
  type: "string",
7004
7006
  describe: "Markdown file to attach as the README"
7005
- }).option("tag", {
7007
+ }).option("category", {
7006
7008
  type: "string",
7007
- array: true,
7008
- nargs: 1,
7009
- describe: "Capability tag (repeatable, at most eight)"
7009
+ describe: "Category slug in the target namespace (create only; resolved even with --dry-run)"
7010
7010
  }).option("new", {
7011
7011
  type: "boolean",
7012
7012
  default: false,
@@ -7014,7 +7014,7 @@ function buildParser(onMatched) {
7014
7014
  }), (argv) => {
7015
7015
  onMatched({
7016
7016
  argv,
7017
- run: async (ctx, matchedArgv) => (await import("./skills-DwoDzsXd.mjs")).publish(ctx, matchedArgv)
7017
+ run: async (ctx, matchedArgv) => (await import("./skills-XDbBCC5f.mjs")).publish(ctx, matchedArgv)
7018
7018
  });
7019
7019
  });
7020
7020
  return skills.demandCommand(1);
@@ -7036,7 +7036,7 @@ function buildParser(onMatched) {
7036
7036
  }), (argv) => {
7037
7037
  onMatched({
7038
7038
  argv,
7039
- run: async (ctx, matchedArgv) => (await import("./mcp-DeWHHgQK.mjs")).add(ctx, matchedArgv)
7039
+ run: async (ctx, matchedArgv) => (await import("./mcp-b6bE3ZRt.mjs")).add(ctx, matchedArgv)
7040
7040
  });
7041
7041
  });
7042
7042
  mcp.command("publish <paths..>", "Publish an MCP snapshot as a capability or a new version", (publish) => publish.option("namespace", {
@@ -7054,11 +7054,9 @@ function buildParser(onMatched) {
7054
7054
  }).option("readme", {
7055
7055
  type: "string",
7056
7056
  describe: "Markdown file to attach as the README"
7057
- }).option("tag", {
7057
+ }).option("category", {
7058
7058
  type: "string",
7059
- array: true,
7060
- nargs: 1,
7061
- describe: "Capability tag (repeatable, at most eight)"
7059
+ describe: "Category slug in the target namespace (create only; resolved even with --dry-run)"
7062
7060
  }).option("new", {
7063
7061
  type: "boolean",
7064
7062
  default: false,
@@ -7066,7 +7064,7 @@ function buildParser(onMatched) {
7066
7064
  }), (argv) => {
7067
7065
  onMatched({
7068
7066
  argv,
7069
- run: async (ctx, matchedArgv) => (await import("./mcp-DeWHHgQK.mjs")).publish(ctx, matchedArgv)
7067
+ run: async (ctx, matchedArgv) => (await import("./mcp-b6bE3ZRt.mjs")).publish(ctx, matchedArgv)
7070
7068
  });
7071
7069
  });
7072
7070
  return mcp.demandCommand(1);
@@ -1,5 +1,5 @@
1
1
  import { S as HngrError, b as writeFileAtomically, g as resolveConnection, s as redactSensitive, t as apiFetch } from "./http-2HWxLIOq.mjs";
2
- import { _ as resolveCapabilityVersion, a as failedResult, c as stringValues, f as downloadContent, g as resolveCapability, h as parseCapabilityPath, i as errorBody, l as AGENTS, n as readPublishFlags, o as positionalPaths, p as MCP_KIND, r as runPublish, s as runBatch, t as explicitName, u as findAgentDefinition } from "./publish-D7zaQl9x.mjs";
2
+ import { _ as resolveCapabilityVersion, a as failedResult, c as stringValues, f as downloadContent, g as resolveCapability, h as parseCapabilityPath, i as errorBody, l as AGENTS, n as readPublishFlags, o as positionalPaths, p as MCP_KIND, r as runPublish, s as runBatch, t as explicitName, u as findAgentDefinition } from "./publish-EUqgdp0G.mjs";
3
3
  import { constants } from "node:fs";
4
4
  import { access, readFile, stat } from "node:fs/promises";
5
5
  import { basename, delimiter, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -517,7 +517,7 @@ function mcpPrepared(config, metadata) {
517
517
  visibility: metadata.visibility
518
518
  };
519
519
  if (metadata.readme !== void 0) body.readme = metadata.readme;
520
- if (metadata.tags.length > 0) body.tags = metadata.tags;
520
+ if (metadata.category !== void 0) body.category = metadata.category;
521
521
  body.config = config;
522
522
  return body;
523
523
  };
@@ -536,7 +536,7 @@ function mcpPrepared(config, metadata) {
536
536
  if (metadata.description !== void 0) preview.description = metadata.description;
537
537
  preview.visibility = metadata.visibility;
538
538
  if (metadata.readme !== void 0) preview.readme = metadata.readme;
539
- if (metadata.tags.length > 0) preview.tags = metadata.tags;
539
+ if (metadata.category !== void 0) preview.category = metadata.category;
540
540
  preview.config = `<${serverCount} server${serverCount === 1 ? "" : "s"}>`;
541
541
  return preview;
542
542
  },
@@ -569,7 +569,7 @@ function defaultMcpName(file) {
569
569
  async function publish(ctx, argv) {
570
570
  const rawPaths = positionalPaths(argv.paths);
571
571
  if (rawPaths.length === 0) throw usage("No MCP snapshot given", "Pass one or more JSON snapshot files.");
572
- const flags = await readPublishFlags(argv, "mcp");
572
+ const flags = await readPublishFlags(argv);
573
573
  const forcedName = explicitName(argv, rawPaths.length);
574
574
  const items = rawPaths.map((file) => ({
575
575
  name: forcedName ?? defaultMcpName(file),
@@ -368,6 +368,7 @@ async function runBatch(options) {
368
368
  };
369
369
  }
370
370
  // src/commands/publish.ts
371
+ const CATEGORIES_PATH = "/api/v1/categories";
371
372
  const VISIBILITIES = [
372
373
  "public",
373
374
  "internal",
@@ -397,21 +398,15 @@ async function readOptionalFile(path) {
397
398
  }
398
399
  }
399
400
  /** Reads the shared publish flags; a bad value fails before any request. */
400
- async function readPublishFlags(argv, kind) {
401
+ async function readPublishFlags(argv) {
401
402
  const visibility = stringFlag(argv, "visibility") ?? "private";
402
403
  if (!VISIBILITIES.includes(visibility)) throw usage(`Invalid --visibility ${JSON.stringify(visibility)}`, `Use one of: ${VISIBILITIES.join(", ")}.`);
403
- const tags = stringValues(argv.tag);
404
- if (tags.length > 8) throw usage("Too many --tag values", "At most eight tags are allowed.");
405
- for (const tag of tags) {
406
- if (tag.trim() === "") throw usage("Empty --tag value", "Pass a non-empty tag.");
407
- if (kind === "skill" && tag.includes(",")) throw usage(`Invalid --tag ${JSON.stringify(tag)}`, "A Skill tag cannot contain a comma; the multipart form joins tags with commas.");
408
- }
409
404
  return {
410
405
  namespace: stringFlag(argv, "namespace") ?? "system",
411
406
  description: stringFlag(argv, "description"),
412
407
  visibility,
413
408
  readme: await readOptionalFile(stringFlag(argv, "readme")),
414
- tags,
409
+ category: stringFlag(argv, "category"),
415
410
  forceNew: argv.new === true
416
411
  };
417
412
  }
@@ -424,6 +419,39 @@ function explicitName(argv, targetCount) {
424
419
  function asRecord(value) {
425
420
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
426
421
  }
422
+ /** Reads one taxonomy page and narrows the fields resolution needs. */
423
+ async function categoryPage(connection, namespace, query) {
424
+ const body = await apiGet(connection, CATEGORIES_PATH, {
425
+ namespace,
426
+ limit: 200,
427
+ ...query === void 0 ? {} : { q: query }
428
+ });
429
+ const items = asRecord(body)?.items;
430
+ if (!Array.isArray(items)) throw new HngrError({
431
+ type: "api",
432
+ title: "The server returned an unexpected response shape",
433
+ detail: `Expected {items: [...], truncated: ...} from GET ${CATEGORIES_PATH}.`
434
+ });
435
+ return {
436
+ slugs: items.map((item) => asRecord(item)?.slug).filter((value) => typeof value === "string"),
437
+ truncated: asRecord(body)?.truncated === true
438
+ };
439
+ }
440
+ /**
441
+ * Resolves a Category slug against the target Namespace's taxonomy. A slug the
442
+ * Namespace does not own is a usage error; any transport or HTTP failure
443
+ * propagates, so the caller never uploads after a failed lookup. A truncated
444
+ * first page is re-queried by slug, and only an exact match counts, so a large
445
+ * taxonomy does not turn a valid Category into an unknown one.
446
+ */
447
+ async function resolveCategorySlug(connection, namespace, slug) {
448
+ const first = await categoryPage(connection, namespace);
449
+ if (first.slugs.includes(slug)) return slug;
450
+ if (first.truncated) {
451
+ if ((await categoryPage(connection, namespace, slug)).slugs.includes(slug)) return slug;
452
+ }
453
+ throw usage(`Unknown --category ${JSON.stringify(slug)}`, first.slugs.length > 0 ? `Available in ${namespace}: ${first.slugs.join(", ")}.` : `No Categories exist in the ${namespace} namespace.`);
454
+ }
427
455
  function printPreview(ctx, kind, namespace, name, preview) {
428
456
  ctx.detail(`dry-run: would publish ${kind} ${namespace}/${name}`);
429
457
  for (const [key, value] of Object.entries(preview)) {
@@ -439,6 +467,7 @@ function printPreview(ctx, kind, namespace, name, preview) {
439
467
  async function runPublish(options) {
440
468
  const { ctx } = options;
441
469
  const connection = await resolveConnection(ctx.flags, ctx.env);
470
+ if (options.category !== void 0) await resolveCategorySlug(connection, options.namespace, options.category);
442
471
  const results = [];
443
472
  for (const item of options.items) {
444
473
  const base = {
@@ -459,7 +488,7 @@ async function runPublish(options) {
459
488
  description: options.description,
460
489
  visibility: options.visibility,
461
490
  readme: options.readme,
462
- tags: options.tags
491
+ category: options.category
463
492
  };
464
493
  const prepared = await item.prepare(metadata);
465
494
  if (ctx.flags.dryRun) {
@@ -475,6 +504,7 @@ async function runPublish(options) {
475
504
  name: item.name
476
505
  }, options.info);
477
506
  if (existing !== null && options.forceNew) throw usage(`The ${options.info.noun} ${options.namespace}/${item.name} already exists`, "Drop --new to publish a new version of it.");
507
+ if (existing !== null && options.category !== void 0) throw usage(`Cannot change the category of the existing ${options.info.noun} ${options.namespace}/${item.name}`, "Drop --category to publish a new version; change the category in the Web console.");
478
508
  let response;
479
509
  let mode;
480
510
  if (existing !== null) {
@@ -1,5 +1,5 @@
1
1
  import { S as HngrError, g as resolveConnection, r as apiRequest, y as writeDirectoryAtomically } from "./http-2HWxLIOq.mjs";
2
- import { _ as resolveCapabilityVersion, a as failedResult, c as stringValues, d as homeDir, f as downloadContent, g as resolveCapability, h as parseCapabilityPath, i as errorBody, l as AGENTS, m as SKILL_KIND, n as readPublishFlags, o as positionalPaths, r as runPublish, s as runBatch, t as explicitName } from "./publish-D7zaQl9x.mjs";
2
+ import { _ as resolveCapabilityVersion, a as failedResult, c as stringValues, d as homeDir, f as downloadContent, g as resolveCapability, h as parseCapabilityPath, i as errorBody, l as AGENTS, m as SKILL_KIND, n as readPublishFlags, o as positionalPaths, r as runPublish, s as runBatch, t as explicitName } from "./publish-EUqgdp0G.mjs";
3
3
  import { lstat, mkdir, readFile, readdir, rename, rm, symlink, writeFile } from "node:fs/promises";
4
4
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { randomUUID } from "node:crypto";
@@ -784,9 +784,9 @@ function skillPrepared(pkg, metadata) {
784
784
  name: "readme",
785
785
  value: metadata.readme
786
786
  });
787
- if (metadata.tags.length > 0) parts.push({
788
- name: "tags",
789
- value: metadata.tags.join(",")
787
+ if (metadata.category !== void 0) parts.push({
788
+ name: "category",
789
+ value: metadata.category
790
790
  });
791
791
  parts.push(packagePart(pkg, metadata.name));
792
792
  return parts;
@@ -809,7 +809,7 @@ function skillPrepared(pkg, metadata) {
809
809
  if (metadata.description !== void 0) preview.description = metadata.description;
810
810
  preview.visibility = metadata.visibility;
811
811
  if (metadata.readme !== void 0) preview.readme = metadata.readme;
812
- if (metadata.tags.length > 0) preview.tags = metadata.tags.join(",");
812
+ if (metadata.category !== void 0) preview.category = metadata.category;
813
813
  preview.package = `<${pkg.fileCount} files, ${pkg.byteCount} bytes>`;
814
814
  return preview;
815
815
  },
@@ -828,7 +828,7 @@ function skillPrepared(pkg, metadata) {
828
828
  async function publish(ctx, argv) {
829
829
  const rawPaths = positionalPaths(argv.paths);
830
830
  if (rawPaths.length === 0) throw usage("No skill directory given", "Pass one or more local directories that contain SKILL.md.");
831
- const flags = await readPublishFlags(argv, "skill");
831
+ const flags = await readPublishFlags(argv);
832
832
  const forcedName = explicitName(argv, rawPaths.length);
833
833
  const items = rawPaths.map((dir) => ({
834
834
  name: forcedName ?? basename(resolve(dir)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hngr",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "The Hangar CLI: install skills and MCP servers into coding agents, and drive the Management API.",
5
5
  "keywords": [
6
6
  "hangar",