hngr 0.1.1 → 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();
@@ -161,7 +161,12 @@ async function readStoredConfig(env) {
161
161
  });
162
162
  }
163
163
  }
164
- const NO_SERVER_DETAIL = "Pass --server <url>, set HANGAR_URL, or run `hngr auth login` to store one in the config file.";
164
+ /**
165
+ * The instance the CLI talks to when `--server`, `HANGAR_URL` and the config
166
+ * file are all absent. Point a deployment elsewhere with any of those.
167
+ */
168
+ const DEFAULT_SERVER_URL = "http://10.199.100.24:26434";
169
+ const INVALID_SERVER_DETAIL = "Pass --server <url>, set HANGAR_URL, or run `hngr auth login` to store one in the config file.";
165
170
  /** Parses and normalizes a server URL (no trailing slash); invalid input is a config error. */
166
171
  function normalizeServerUrl(server) {
167
172
  let url;
@@ -171,7 +176,7 @@ function normalizeServerUrl(server) {
171
176
  throw new HngrError({
172
177
  type: "config",
173
178
  title: `Invalid server URL: ${redactUrl(server)}`,
174
- detail: NO_SERVER_DETAIL
179
+ detail: INVALID_SERVER_DETAIL
175
180
  });
176
181
  }
177
182
  return url.toString().replace(/\/+$/, "");
@@ -209,16 +214,12 @@ async function deleteStoredConfig(env) {
209
214
  }
210
215
  /**
211
216
  * Resolves the server and credentials for one call: `--server` beats
212
- * `HANGAR_URL` beats the config file; `HANGAR_TOKEN` beats a stored token.
217
+ * `HANGAR_URL` beats the config file beats the built-in default; `HANGAR_TOKEN`
218
+ * beats a stored token.
213
219
  */
214
220
  async function resolveConnection(flags, env) {
215
221
  const config = await readStoredConfig(env);
216
- const server = flags.server ?? env.HANGAR_URL ?? config?.server;
217
- if (!server) throw new HngrError({
218
- type: "config",
219
- title: "No Hangar server configured",
220
- detail: NO_SERVER_DETAIL
221
- });
222
+ const server = flags.server ?? env.HANGAR_URL ?? config?.server ?? "http://10.199.100.24:26434";
222
223
  const token = env.HANGAR_TOKEN ?? config?.token;
223
224
  return {
224
225
  server: normalizeServerUrl(server),
@@ -455,4 +456,4 @@ async function apiFetch(connection, options) {
455
456
  signal: options.signal
456
457
  });
457
458
  }
458
- export { renderError as S, writeStoredConfig as _, toHngrError as a, CancelledError as b, redactUrlForDisplay as c, deleteStoredConfig as d, normalizeServerUrl as f, scrubCredentials as g, resolveConnection as h, buildRequestUrl as i, apiErrorFromResponse as l, redactUrl as m, apiGet as n, redactQuery as o, readStoredConfig as p, apiRequest as r, redactSensitive as s, apiFetch as t, configFilePath as u, writeDirectoryAtomically as v, HngrError as x, writeFileAtomically as y };
459
+ export { renderError as C, HngrError as S, scrubCredentials as _, toHngrError as a, writeFileAtomically as b, redactUrlForDisplay as c, configFilePath as d, deleteStoredConfig as f, resolveConnection as g, redactUrl as h, buildRequestUrl as i, apiErrorFromResponse as l, readStoredConfig as m, apiGet as n, redactQuery as o, normalizeServerUrl as p, apiRequest as r, redactSensitive as s, apiFetch as t, DEFAULT_SERVER_URL as u, writeStoredConfig as v, CancelledError as x, writeDirectoryAtomically as y };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
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";
2
- import { S as renderError, b as CancelledError, c as redactUrlForDisplay, h as resolveConnection, i as buildRequestUrl, o as redactQuery, r as apiRequest, s as redactSensitive, x as HngrError } from "./http-DpXNrkSL.mjs";
3
- import { t as collectPages } from "./pagination-DYIwy5ZX.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
+ 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
+ import { t as collectPages } from "./pagination-BbKLKSw3.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { notStrictEqual, strictEqual } from "assert";
6
6
  import { basename, dirname, extname, join, normalize, relative, resolve } from "path";
@@ -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.1.1";
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";
@@ -6822,7 +6822,7 @@ const USAGE_HINT = "Run 'hngr --help' to list available commands.";
6822
6822
  const GLOBAL_OPTIONS = {
6823
6823
  server: {
6824
6824
  type: "string",
6825
- describe: "Override HANGAR_URL and the configured server for this call",
6825
+ describe: `Override HANGAR_URL and the configured server for this call (default: ${DEFAULT_SERVER_URL})`,
6826
6826
  global: true
6827
6827
  },
6828
6828
  json: {
@@ -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-Bf4n5qaq.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-Bf4n5qaq.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-Bf4n5qaq.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-Bf4n5qaq.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-Bf4n5qaq.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-By449rb4.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-By449rb4.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-bi4OOz5J.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-bi4OOz5J.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
- import { h as resolveConnection, s as redactSensitive, t as apiFetch, x as HngrError, y as writeFileAtomically } from "./http-DpXNrkSL.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-DHAZ9BB_.mjs";
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-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),
@@ -1,4 +1,4 @@
1
- import { x as HngrError } from "./http-DpXNrkSL.mjs";
1
+ import { S as HngrError } from "./http-2HWxLIOq.mjs";
2
2
  // src/pagination.ts
3
3
  function asCursorPage(body, path) {
4
4
  if (body === null || typeof body !== "object" || !Array.isArray(body.items) || !("next_cursor" in body)) throw new HngrError({
@@ -1,5 +1,5 @@
1
- import { a as toHngrError, g as scrubCredentials, h as resolveConnection, i as buildRequestUrl, l as apiErrorFromResponse, m as redactUrl, n as apiGet, x as HngrError } from "./http-DpXNrkSL.mjs";
2
- import { t as collectPages } from "./pagination-DYIwy5ZX.mjs";
1
+ import { S as HngrError, _ as scrubCredentials, a as toHngrError, g as resolveConnection, h as redactUrl, i as buildRequestUrl, l as apiErrorFromResponse, n as apiGet } from "./http-2HWxLIOq.mjs";
2
+ import { t as collectPages } from "./pagination-BbKLKSw3.mjs";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { join, resolve } from "node:path";
@@ -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
- import { h as resolveConnection, r as apiRequest, v as writeDirectoryAtomically, x as HngrError } from "./http-DpXNrkSL.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-DHAZ9BB_.mjs";
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-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.1.1",
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",