hngr 0.2.0 → 0.4.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,5 +1,5 @@
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";
1
+ import { C as HngrError, b as writeDirectoryAtomically, g as resolveConnection, r as apiRequest } from "./http-DgHX7Fux.mjs";
2
+ import { _ as resolveCapability, a as errorBody, c as runBatch, f as homeDir, g as parseCapabilityPath, h as SKILL_KIND, i as runPublish, l as stringValues, n as explicitName, o as failedResult, p as downloadContent, r as readPublishFlags, s as positionalPaths, t as resolveInstallScope, u as AGENTS, v as resolveCapabilityVersion } from "./scope-Ch6wZCgE.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";
@@ -92,13 +92,6 @@ function downloadSkillPackage(connection, capabilityId, version, signal) {
92
92
  async function pathExists(path) {
93
93
  return lstat(path).then(() => true, () => false);
94
94
  }
95
- function targetExistsError(path) {
96
- return new HngrError({
97
- type: "io",
98
- title: `Target already exists: ${path}`,
99
- detail: "Pass --force to replace it."
100
- });
101
- }
102
95
  async function writeEntries(dest, entries) {
103
96
  await mkdir(dest, { recursive: true });
104
97
  for (const entry of entries) {
@@ -114,25 +107,22 @@ async function writeEntries(dest, entries) {
114
107
  }
115
108
  /**
116
109
  * Writes a skill through "temp directory → validate → rename": the target is
117
- * either absent or complete, never half-written. `--force` moves the old
118
- * directory aside before the rename so a failed swap puts it back.
110
+ * either absent or complete, never half-written. Re-installing replaces the
111
+ * previous directory, with the old one moved aside so a failed swap puts it
112
+ * back.
119
113
  */
120
- async function installAtomically(dest, entries, force) {
121
- await writeDirectoryAtomically(dest, (temp) => writeEntries(temp, entries), {
122
- force,
123
- existsError: () => targetExistsError(dest)
124
- });
114
+ async function installAtomically(dest, entries) {
115
+ await writeDirectoryAtomically(dest, (temp) => writeEntries(temp, entries));
125
116
  }
126
117
  /**
127
118
  * Creates a relative symlink to the canonical directory, falling back to a
128
119
  * copy when the platform refuses the link (Windows without Developer Mode).
129
120
  * Windows prefers a junction, which does not need elevated privileges; its
130
121
  * target must be absolute. A temp link plus rename replaces an existing link
131
- * atomically.
122
+ * atomically; re-installing replaces an existing directory or link.
132
123
  */
133
- async function linkAtomically(canonical, target, entries, force, options = {}) {
124
+ async function linkAtomically(canonical, target, entries, options = {}) {
134
125
  const exists = await pathExists(target);
135
- if (exists && !force) throw targetExistsError(target);
136
126
  await mkdir(dirname(target), { recursive: true });
137
127
  const temp = resolve(dirname(target), `.hngr-${basename(target)}-${randomUUID()}.link`);
138
128
  const windows = (options.platform ?? process.platform) === "win32";
@@ -150,7 +140,7 @@ async function linkAtomically(canonical, target, entries, force, options = {}) {
150
140
  return { mode: "symlink" };
151
141
  } catch {
152
142
  await rm(temp, { force: true }).catch(() => {});
153
- await installAtomically(target, entries, false);
143
+ await installAtomically(target, entries);
154
144
  return {
155
145
  mode: "copy",
156
146
  warning: `warning: could not create a link at ${target}; copied instead`
@@ -158,8 +148,8 @@ async function linkAtomically(canonical, target, entries, force, options = {}) {
158
148
  }
159
149
  }
160
150
  /** Copies one target directory atomically, without a canonical store. */
161
- async function copyAtomically(target, entries, force) {
162
- await installAtomically(target, entries, force);
151
+ async function copyAtomically(target, entries) {
152
+ await installAtomically(target, entries);
163
153
  }
164
154
  // src/skills/resolve.ts
165
155
  /** Parses `<name>` or `<namespace>/<name>`, with an optional `@YYYY.MM.DD.N`. */
@@ -657,16 +647,12 @@ function planInstall(cwd, env, destName, agents, options) {
657
647
  async function install(ctx, plan, entries) {
658
648
  const cwd = process.cwd();
659
649
  const display = (path) => displayPath(path, cwd);
660
- const destinations = [plan.canonical, ...plan.targets.map((target) => target.path)].filter((path) => path !== null);
661
- if (!ctx.flags.force) {
662
- for (const path of destinations) if (await pathExists(path)) throw targetExistsError(display(path));
663
- }
664
- if (plan.canonical) await installAtomically(plan.canonical, entries, ctx.flags.force);
650
+ if (plan.canonical) await installAtomically(plan.canonical, entries);
665
651
  const installed = [];
666
652
  let failure = null;
667
653
  for (const target of plan.targets) try {
668
654
  if (target.mode === "copy" || !plan.canonical) {
669
- await copyAtomically(target.path, entries, ctx.flags.force);
655
+ await copyAtomically(target.path, entries);
670
656
  installed.push({
671
657
  agent: target.agent,
672
658
  path: display(target.path),
@@ -675,7 +661,7 @@ async function install(ctx, plan, entries) {
675
661
  });
676
662
  continue;
677
663
  }
678
- const result = await linkAtomically(plan.canonical, target.path, entries, ctx.flags.force);
664
+ const result = await linkAtomically(plan.canonical, target.path, entries);
679
665
  if (result.warning) ctx.summary(result.warning);
680
666
  installed.push({
681
667
  agent: target.agent,
@@ -708,9 +694,6 @@ async function addOne(ctx, connection, parsed, destName, agents, dir, global, co
708
694
  });
709
695
  const canonicalPath = plan.canonical ? displayPath(plan.canonical, process.cwd()) : null;
710
696
  if (ctx.flags.dryRun) {
711
- if (!ctx.flags.force) {
712
- for (const path of [plan.canonical, ...plan.targets.map((target) => target.path)]) if (path && await pathExists(path)) throw targetExistsError(displayPath(path, process.cwd()));
713
- }
714
697
  ctx.summary(`resolved ${parsed.namespace}/${parsed.name} to version ${version}`);
715
698
  return { result: {
716
699
  name: capability.name,
@@ -784,9 +767,9 @@ function skillPrepared(pkg, metadata) {
784
767
  name: "readme",
785
768
  value: metadata.readme
786
769
  });
787
- if (metadata.tags.length > 0) parts.push({
788
- name: "tags",
789
- value: metadata.tags.join(",")
770
+ if (metadata.category !== void 0) parts.push({
771
+ name: "category",
772
+ value: metadata.category
790
773
  });
791
774
  parts.push(packagePart(pkg, metadata.name));
792
775
  return parts;
@@ -809,7 +792,7 @@ function skillPrepared(pkg, metadata) {
809
792
  if (metadata.description !== void 0) preview.description = metadata.description;
810
793
  preview.visibility = metadata.visibility;
811
794
  if (metadata.readme !== void 0) preview.readme = metadata.readme;
812
- if (metadata.tags.length > 0) preview.tags = metadata.tags.join(",");
795
+ if (metadata.category !== void 0) preview.category = metadata.category;
813
796
  preview.package = `<${pkg.fileCount} files, ${pkg.byteCount} bytes>`;
814
797
  return preview;
815
798
  },
@@ -828,7 +811,7 @@ function skillPrepared(pkg, metadata) {
828
811
  async function publish(ctx, argv) {
829
812
  const rawPaths = positionalPaths(argv.paths);
830
813
  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");
814
+ const flags = await readPublishFlags(argv);
832
815
  const forcedName = explicitName(argv, rawPaths.length);
833
816
  const items = rawPaths.map((dir) => ({
834
817
  name: forcedName ?? basename(resolve(dir)),
@@ -849,12 +832,13 @@ async function add(ctx, argv) {
849
832
  const dir = stringFlag(argv, "dir");
850
833
  const as = stringFlag(argv, "as");
851
834
  if (as !== void 0 && (as.includes("/") || as.includes("\\") || as === "." || as === "..")) throw usage(`Invalid --as name ${JSON.stringify(as)}`, "Pass a single directory name.");
852
- const global = argv.global === true;
835
+ const requestedGlobal = argv.global === true;
853
836
  const copy = argv.copy === true;
854
- if (dir !== void 0 && global) throw usage("--dir cannot be combined with --global");
837
+ if (dir !== void 0 && requestedGlobal) throw usage("--dir cannot be combined with --global");
855
838
  if (dir !== void 0 && (stringValues(argv.agent).length > 0 || argv.all === true)) throw usage("--dir cannot be combined with --agent or --all");
856
839
  if (as !== void 0) assertNotReserved(as);
857
840
  for (const parsed of parsedPaths) assertNotReserved(as ?? parsed.name);
841
+ const global = dir === void 0 ? await resolveInstallScope(ctx, requestedGlobal) === "global" : false;
858
842
  const agents = dir === void 0 ? await selectAgents(ctx, argv, rawPaths[0] ?? "", global) : [];
859
843
  const connection = await resolveConnection(ctx.flags, ctx.env);
860
844
  const { results, failed } = await runBatch({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hngr",
3
- "version": "0.2.0",
3
+ "version": "0.4.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",