hngr 0.3.0 → 0.5.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.
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-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";
1
+ import { a as kebabCase, i as normalizeCommandName, n as COMMANDS, o as COMMAND_ALIASES, r as findCommand, t as commandCatalog } from "./catalog-Bv304SXJ.mjs";
2
+ import { C as HngrError, S as CancelledError, c as redactUrlForDisplay, g as resolveConnection, i as buildRequestUrl, o as redactQuery, r as apiRequest, s as redactSensitive, u as DEFAULT_SERVER_URL, w as renderError } from "./http-DgHX7Fux.mjs";
3
+ import { t as collectPages } from "./pagination-BXyFxGYA.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.3.0";
5282
+ var version = "0.5.0";
5283
5283
  // src/generated/completion.ts
5284
5284
  function isCompletionShell(value) {
5285
5285
  return value === "bash" || value === "zsh" || value === "fish";
@@ -6499,6 +6499,32 @@ let u$1 = class u extends y {
6499
6499
  });
6500
6500
  }
6501
6501
  };
6502
+ let n$1 = class n extends y {
6503
+ options;
6504
+ cursor = 0;
6505
+ get _selectedValue() {
6506
+ return this.options[this.cursor];
6507
+ }
6508
+ changeValue() {
6509
+ const e = this._selectedValue;
6510
+ this.value = e === void 0 ? void 0 : e.value;
6511
+ }
6512
+ constructor(e) {
6513
+ super(e, false), this.options = e.options;
6514
+ const o = this.options.findIndex(({ value: s }) => s === e.initialValue), t = o === -1 ? 0 : o;
6515
+ this.cursor = this.options[t]?.disabled ? findCursor(t, 1, this.options) : t, this.changeValue(), this.on("cursor", (s) => {
6516
+ switch (s) {
6517
+ case "left":
6518
+ case "up":
6519
+ this.cursor = findCursor(this.cursor, -1, this.options);
6520
+ break;
6521
+ case "down":
6522
+ case "right": this.cursor = findCursor(this.cursor, 1, this.options);
6523
+ }
6524
+ this.changeValue();
6525
+ });
6526
+ }
6527
+ };
6502
6528
  var n = class extends y {
6503
6529
  get userInputWithCursor() {
6504
6530
  if (this.state === "submit") return this.userInput;
@@ -6534,6 +6560,8 @@ const S_STEP_ERROR = unicodeOr("▲", "x");
6534
6560
  const S_STEP_SUBMIT = unicodeOr("◇", "o");
6535
6561
  const S_BAR = unicodeOr("│", "|");
6536
6562
  const S_BAR_END = unicodeOr("└", "—");
6563
+ const S_RADIO_ACTIVE = unicodeOr("●", ">");
6564
+ const S_RADIO_INACTIVE = unicodeOr("○", " ");
6537
6565
  const S_CHECKBOX_ACTIVE = unicodeOr("◻", "[•]");
6538
6566
  const S_CHECKBOX_SELECTED = unicodeOr("◼", "[+]");
6539
6567
  const S_CHECKBOX_INACTIVE = unicodeOr("◻", "[ ]");
@@ -6693,6 +6721,13 @@ ${h}
6693
6721
  }
6694
6722
  }).prompt();
6695
6723
  };
6724
+ const outro = (o = "", t) => {
6725
+ const i = t?.output ?? process.stdout, e = t?.withGuide ?? settings.withGuide ? `${styleText("gray", S_BAR)}
6726
+ ${styleText("gray", S_BAR_END)} ` : "";
6727
+ i.write(`${e}${o}
6728
+
6729
+ `);
6730
+ };
6696
6731
  const password = (e) => new u$1({
6697
6732
  validate: e.validate,
6698
6733
  mask: e.mask ?? S_PASSWORD_MASK,
@@ -6720,7 +6755,64 @@ ${r ? styleText("cyan", S_BAR_END) : ""}
6720
6755
  }
6721
6756
  }
6722
6757
  }).prompt();
6723
- `${styleText("dim", "↑/↓")}`, `${styleText("dim", "Enter:")}`;
6758
+ const SELECT_INSTRUCTIONS = [`${styleText("dim", "↑/↓")} to navigate`, `${styleText("dim", "Enter:")} confirm`];
6759
+ const c = (t, o) => t.includes(`
6760
+ `) ? t.split(`
6761
+ `).map((d) => o(d)).join(`
6762
+ `) : o(t);
6763
+ const select = (t) => {
6764
+ const o = (n, m) => {
6765
+ if (n === void 0) return "";
6766
+ const s = n.label ?? String(n.value);
6767
+ switch (m) {
6768
+ case "disabled": return `${styleText("gray", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("gray", i))}${n.hint ? ` ${styleText("dim", `(${n.hint ?? "disabled"})`)}` : ""}`;
6769
+ case "selected": return `${c(s, (i) => styleText("dim", i))}`;
6770
+ case "active": return `${styleText("green", S_RADIO_ACTIVE)} ${s}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`;
6771
+ case "cancelled": return `${c(s, (i) => styleText(["strikethrough", "dim"], i))}`;
6772
+ default: return `${styleText("dim", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("dim", i))}`;
6773
+ }
6774
+ }, d = t.showInstructions ?? true;
6775
+ return new n$1({
6776
+ options: t.options,
6777
+ signal: t.signal,
6778
+ input: t.input,
6779
+ output: t.output,
6780
+ initialValue: t.initialValue,
6781
+ render() {
6782
+ const n = t.withGuide ?? settings.withGuide, m = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, i = wrapTextWithPrefix(t.output, t.message, s, m), u = `${n ? `${styleText("gray", S_BAR)}
6783
+ ` : ""}${i}
6784
+ `;
6785
+ switch (this.state) {
6786
+ case "submit": {
6787
+ const r = n ? `${styleText("gray", S_BAR)} ` : "";
6788
+ return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "selected"), r)}`;
6789
+ }
6790
+ case "cancel": {
6791
+ const r = n ? `${styleText("gray", S_BAR)} ` : "";
6792
+ return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "cancelled"), r)}${n ? `
6793
+ ${styleText("gray", S_BAR)}` : ""}`;
6794
+ }
6795
+ default: {
6796
+ const r = n ? `${styleText("cyan", S_BAR)} ` : "", a = u.split(`
6797
+ `).length, p = d ? formatInstructionFooter(SELECT_INSTRUCTIONS, n) : n ? [styleText("cyan", S_BAR_END)] : [], f = p.join(`
6798
+ `), b = p.length + 1;
6799
+ return `${u}${r}${limitOptions({
6800
+ output: t.output,
6801
+ cursor: this.cursor,
6802
+ options: this.options,
6803
+ maxItems: t.maxItems,
6804
+ columnPadding: r.length,
6805
+ rowPadding: a + b,
6806
+ style: (g, x) => o(g, g.disabled ? "disabled" : x ? "active" : "inactive")
6807
+ }).join(`
6808
+ ${r}`)}
6809
+ ${f}
6810
+ `;
6811
+ }
6812
+ }
6813
+ }
6814
+ }).prompt();
6815
+ };
6724
6816
  `${styleText("gray", S_BAR)}`;
6725
6817
  const text = (t) => new n({
6726
6818
  validate: t.validate,
@@ -6804,15 +6896,33 @@ function clackPrompter(streams = {}) {
6804
6896
  }
6805
6897
  }));
6806
6898
  },
6807
- async multiselect(message, options, initialValues = []) {
6899
+ async select(message, options, initialValue) {
6900
+ return unwrapPrompt(await select({
6901
+ message,
6902
+ input,
6903
+ output,
6904
+ options,
6905
+ initialValue
6906
+ }));
6907
+ },
6908
+ async multiselect(message, options, initialValues = [], hint) {
6909
+ if (hint) output.write(`${import_picocolors.default.dim(hint)}\n`);
6808
6910
  return unwrapPrompt(await multiselect({
6809
6911
  message,
6810
6912
  input,
6811
6913
  output,
6812
6914
  options,
6813
6915
  initialValues,
6814
- required: true
6916
+ required: false
6815
6917
  }));
6918
+ },
6919
+ note(message, title) {
6920
+ output.write(`${import_picocolors.default.green("◇")} ${title}\n`);
6921
+ output.write(`${import_picocolors.default.dim("│")}\n`);
6922
+ for (const line of message.split("\n")) output.write(`${import_picocolors.default.dim("│")} ${line}\n`);
6923
+ },
6924
+ outro(message) {
6925
+ outro(message, { output });
6816
6926
  }
6817
6927
  };
6818
6928
  }
@@ -6828,7 +6938,7 @@ const GLOBAL_OPTIONS = {
6828
6938
  json: {
6829
6939
  type: "boolean",
6830
6940
  default: false,
6831
- describe: "Machine mode: errors are single-line JSON on stderr",
6941
+ describe: "Machine mode: print the JSON payload even on a TTY, and errors as JSON",
6832
6942
  global: true
6833
6943
  },
6834
6944
  quiet: {
@@ -6846,7 +6956,7 @@ const GLOBAL_OPTIONS = {
6846
6956
  force: {
6847
6957
  type: "boolean",
6848
6958
  default: false,
6849
- describe: "Overwrite existing targets; the only way to skip destructive confirmations",
6959
+ describe: "Skip destructive confirmations",
6850
6960
  global: true
6851
6961
  },
6852
6962
  "dry-run": {
@@ -6911,7 +7021,7 @@ function buildParser(onMatched) {
6911
7021
  parser.command("commands", "Print the command catalog", () => {}, (argv) => {
6912
7022
  onMatched({
6913
7023
  argv,
6914
- run: async (ctx) => (await import("./commands-BHXSZ04F.mjs")).run(ctx)
7024
+ run: async (ctx) => (await import("./commands-CLqLwQHY.mjs")).run(ctx)
6915
7025
  });
6916
7026
  });
6917
7027
  parser.command("auth", "Manage credentials", (auth) => {
@@ -6924,35 +7034,40 @@ function buildParser(onMatched) {
6924
7034
  }).conflicts("password", "token"), (argv) => {
6925
7035
  onMatched({
6926
7036
  argv,
6927
- run: async (ctx, matchedArgv) => (await import("./auth-CgBUQUQn.mjs")).login(ctx, matchedArgv)
7037
+ run: async (ctx, matchedArgv) => (await import("./auth-ByGgd_Yn.mjs")).login(ctx, matchedArgv)
6928
7038
  });
6929
7039
  });
6930
7040
  auth.command("logout", "Revoke the stored token and delete the local config", () => {}, (argv) => {
6931
7041
  onMatched({
6932
7042
  argv,
6933
- run: async (ctx) => (await import("./auth-CgBUQUQn.mjs")).logout(ctx)
7043
+ run: async (ctx) => (await import("./auth-ByGgd_Yn.mjs")).logout(ctx)
6934
7044
  });
6935
7045
  });
6936
- auth.command("status", "Show credential source, server, principal and token prefix", (status) => status.option("offline", {
7046
+ auth.command("status", "Show credential source, server, principal, token prefix and audience", (status) => status.option("offline", {
6937
7047
  type: "boolean",
6938
7048
  default: false,
6939
7049
  describe: "Skip the live GET /me check"
6940
7050
  }), (argv) => {
6941
7051
  onMatched({
6942
7052
  argv,
6943
- run: async (ctx, matchedArgv) => (await import("./auth-CgBUQUQn.mjs")).status(ctx, matchedArgv)
7053
+ run: async (ctx, matchedArgv) => (await import("./auth-ByGgd_Yn.mjs")).status(ctx, matchedArgv)
6944
7054
  });
6945
7055
  });
6946
- auth.command("token", "Print the access token to stdout and nothing else", () => {}, (argv) => {
7056
+ auth.command("token", "Print the access token to stdout and nothing else", (token) => token.option("audience", {
7057
+ type: "string",
7058
+ choices: ["management", "mcp"],
7059
+ default: "management",
7060
+ describe: "management prints the stored token; mcp mints a new MCP token that can read and write the catalog content you have access to, without changing the stored credential"
7061
+ }), (argv) => {
6947
7062
  onMatched({
6948
7063
  argv,
6949
- run: async (ctx) => (await import("./auth-CgBUQUQn.mjs")).token(ctx)
7064
+ run: async (ctx, matchedArgv) => (await import("./auth-ByGgd_Yn.mjs")).token(ctx, matchedArgv)
6950
7065
  });
6951
7066
  });
6952
7067
  auth.command("whoami", "Print the GET /me payload", () => {}, (argv) => {
6953
7068
  onMatched({
6954
7069
  argv,
6955
- run: async (ctx) => (await import("./auth-CgBUQUQn.mjs")).whoami(ctx)
7070
+ run: async (ctx) => (await import("./auth-ByGgd_Yn.mjs")).whoami(ctx)
6956
7071
  });
6957
7072
  });
6958
7073
  registerGeneratedAuth(auth, onMatched);
@@ -6986,7 +7101,7 @@ function buildParser(onMatched) {
6986
7101
  }), (argv) => {
6987
7102
  onMatched({
6988
7103
  argv,
6989
- run: async (ctx, matchedArgv) => (await import("./skills-XDbBCC5f.mjs")).add(ctx, matchedArgv)
7104
+ run: async (ctx, matchedArgv) => (await import("./skills-ZFmFyZtw.mjs")).add(ctx, matchedArgv)
6990
7105
  });
6991
7106
  });
6992
7107
  skills.command("publish <paths..>", "Publish a local skill directory as a capability or a new version", (publish) => publish.option("namespace", {
@@ -7014,7 +7129,7 @@ function buildParser(onMatched) {
7014
7129
  }), (argv) => {
7015
7130
  onMatched({
7016
7131
  argv,
7017
- run: async (ctx, matchedArgv) => (await import("./skills-XDbBCC5f.mjs")).publish(ctx, matchedArgv)
7132
+ run: async (ctx, matchedArgv) => (await import("./skills-ZFmFyZtw.mjs")).publish(ctx, matchedArgv)
7018
7133
  });
7019
7134
  });
7020
7135
  return skills.demandCommand(1);
@@ -7036,7 +7151,7 @@ function buildParser(onMatched) {
7036
7151
  }), (argv) => {
7037
7152
  onMatched({
7038
7153
  argv,
7039
- run: async (ctx, matchedArgv) => (await import("./mcp-b6bE3ZRt.mjs")).add(ctx, matchedArgv)
7154
+ run: async (ctx, matchedArgv) => (await import("./mcp-B0sbQABp.mjs")).add(ctx, matchedArgv)
7040
7155
  });
7041
7156
  });
7042
7157
  mcp.command("publish <paths..>", "Publish an MCP snapshot as a capability or a new version", (publish) => publish.option("namespace", {
@@ -7064,7 +7179,7 @@ function buildParser(onMatched) {
7064
7179
  }), (argv) => {
7065
7180
  onMatched({
7066
7181
  argv,
7067
- run: async (ctx, matchedArgv) => (await import("./mcp-b6bE3ZRt.mjs")).publish(ctx, matchedArgv)
7182
+ run: async (ctx, matchedArgv) => (await import("./mcp-B0sbQABp.mjs")).publish(ctx, matchedArgv)
7068
7183
  });
7069
7184
  });
7070
7185
  return mcp.demandCommand(1);
@@ -1,5 +1,5 @@
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";
1
+ import { C as HngrError, g as resolveConnection, s as redactSensitive, t as apiFetch, x as writeFileAtomically } from "./http-DgHX7Fux.mjs";
2
+ import { _ as resolveCapability, a as errorBody, c as runBatch, d as findAgentDefinition, g as parseCapabilityPath, i as runPublish, l as stringValues, m as MCP_KIND, 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-DVfDeQi2.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";
@@ -98,31 +98,24 @@ async function userServerState(executable, name, env) {
98
98
  /**
99
99
  * Installs one user-scope server through Claude Code itself.
100
100
  * `~/.claude.json` is never edited directly: Claude Code owns that file and may
101
- * hold live session state. A conflicting user-scope server fails without
102
- * `force`; with `force` it is removed through the CLI before the new entry is
103
- * added, so every write still goes through Claude Code.
101
+ * hold live session state. Re-installing replaces an existing user-scope
102
+ * server through the CLI before the new entry is added, so every write still
103
+ * goes through Claude Code.
104
104
  */
105
- async function addJsonWithClaude(env, name, config, force) {
105
+ async function addJsonWithClaude(env, name, config) {
106
106
  const executable = await findExecutable(EXECUTABLE, env);
107
107
  if (!executable) throw new HngrError({
108
108
  type: "config",
109
109
  title: "Claude Code CLI not found",
110
110
  detail: "`claude` must be on PATH to install user-level MCP servers; install Claude Code or use project-level (no --global) installation."
111
111
  });
112
- if (await userServerState(executable, name, env) === "present") {
113
- if (!force) throw new HngrError({
114
- type: "io",
115
- title: `MCP server ${JSON.stringify(name)} already exists in Claude Code's user config`,
116
- detail: "Pass --force to replace it."
117
- });
118
- await runChecked(executable, [
119
- "mcp",
120
- "remove",
121
- "--scope",
122
- "user",
123
- name
124
- ], env, name);
125
- }
112
+ if (await userServerState(executable, name, env) === "present") await runChecked(executable, [
113
+ "mcp",
114
+ "remove",
115
+ "--scope",
116
+ "user",
117
+ name
118
+ ], env, name);
126
119
  await runChecked(executable, [
127
120
  "mcp",
128
121
  "add-json",
@@ -202,8 +195,8 @@ function readTargetDocument(raw, label) {
202
195
  }
203
196
  /**
204
197
  * Merges the incoming servers into the document's `mcpServers`. Existing
205
- * servers and other top-level keys survive. A name collision fails unless
206
- * `force`, which replaces only the colliding entries.
198
+ * servers and other top-level keys survive; a name collision is replaced, so
199
+ * re-installing a capability is idempotent.
207
200
  */
208
201
  function mergeSnapshot(document, incoming, options) {
209
202
  const existing = document.mcpServers;
@@ -211,21 +204,13 @@ function mergeSnapshot(document, incoming, options) {
211
204
  if (existing === null || typeof existing !== "object" || Array.isArray(existing)) throw ioError(`"mcpServers" in ${options.label} is not an object`, "Fix the target file before installing MCP servers.");
212
205
  }
213
206
  const servers = { ...existing ?? {} };
214
- const conflicts = [];
215
207
  const added = [];
216
208
  const replaced = [];
217
- for (const [name, entry] of Object.entries(incoming)) if (Object.hasOwn(servers, name)) {
218
- if (!options.force) {
219
- conflicts.push(name);
220
- continue;
221
- }
209
+ for (const [name, entry] of Object.entries(incoming)) {
210
+ if (Object.hasOwn(servers, name)) replaced.push(name);
211
+ else added.push(name);
222
212
  servers[name] = entry;
223
- replaced.push(name);
224
- } else {
225
- servers[name] = entry;
226
- added.push(name);
227
213
  }
228
- if (conflicts.length > 0) throw ioError(`MCP ${conflicts.length === 1 ? "server" : "servers"} already configured in ${options.label}: ${conflicts.join(", ")}`, "Pass --force to replace only the conflicting entries.");
229
214
  return {
230
215
  document: {
231
216
  ...document,
@@ -381,11 +366,8 @@ async function readExisting(path, label) {
381
366
  * redacted. The file is never written by dry-run, so this only affects the
382
367
  * preview.
383
368
  */
384
- function previewJsonTarget(plan, existing, servers, force) {
385
- const merged = mergeSnapshot(readTargetDocument(existing, plan.path), servers, {
386
- force,
387
- label: plan.path
388
- });
369
+ function previewJsonTarget(plan, existing, servers) {
370
+ const merged = mergeSnapshot(readTargetDocument(existing, plan.path), servers, { label: plan.path });
389
371
  return serializeTargetDocument(redactSensitive(merged.document));
390
372
  }
391
373
  /** Reports one target's failure without aborting the remaining targets. */
@@ -393,16 +375,13 @@ function reportTargetFailure(ctx, plan, err) {
393
375
  const body = errorBody(err);
394
376
  ctx.summary(`error: ${plan.agent} ${plan.path}: ${body.title}`);
395
377
  }
396
- async function installJsonTarget(plan, servers, force) {
378
+ async function installJsonTarget(plan, servers) {
397
379
  const filePath = plan.filePath;
398
380
  if (!filePath) throw new HngrError({
399
381
  type: "io",
400
382
  title: `No file path configured for ${plan.agent}`
401
383
  });
402
- const merged = mergeSnapshot(readTargetDocument(await readExisting(filePath, plan.path), plan.path), servers, {
403
- force,
404
- label: plan.path
405
- });
384
+ const merged = mergeSnapshot(readTargetDocument(await readExisting(filePath, plan.path), plan.path), servers, { label: plan.path });
406
385
  await writeFileAtomically(filePath, serializeTargetDocument(merged.document), { label: plan.path });
407
386
  }
408
387
  async function addOne(ctx, connection, parsed, targets, state) {
@@ -412,14 +391,12 @@ async function addOne(ctx, connection, parsed, targets, state) {
412
391
  const snapshot = await downloadMcpSnapshot(connection, capability.id, version);
413
392
  if (snapshot.ignoredKeys.length > 0) ctx.summary(`warning: ignoring unsupported top-level keys in the MCP snapshot: ${snapshot.ignoredKeys.join(", ")}`);
414
393
  const serverNames = Object.keys(snapshot.servers);
415
- const etag = snapshot.etag ? ` (ETag ${snapshot.etag})` : "";
416
- ctx.summary(`resolved ${parsed.namespace}/${parsed.name} to version ${version}${etag}`);
417
394
  if (ctx.flags.dryRun) {
418
395
  const dryTargets = [];
419
396
  let failure = null;
420
397
  for (const plan of targets) try {
421
398
  if (plan.kind === "json") {
422
- const preview = previewJsonTarget(plan, plan.filePath ? await readExisting(plan.filePath, plan.path) : void 0, snapshot.servers, ctx.flags.force);
399
+ const preview = previewJsonTarget(plan, plan.filePath ? await readExisting(plan.filePath, plan.path) : void 0, snapshot.servers);
423
400
  ctx.detail(`dry-run: would write ${plan.path}\n${preview}`);
424
401
  } else for (const [name, entry] of Object.entries(snapshot.servers)) ctx.detail(`dry-run: claude mcp add-json --scope user ${name} ${JSON.stringify(redactSensitive(entry))}`);
425
402
  dryTargets.push({
@@ -449,8 +426,8 @@ async function addOne(ctx, connection, parsed, targets, state) {
449
426
  const installedTargets = [];
450
427
  let failure = null;
451
428
  for (const plan of targets) try {
452
- if (plan.kind === "json") await installJsonTarget(plan, snapshot.servers, ctx.flags.force);
453
- else for (const [name, entry] of Object.entries(snapshot.servers)) await addJsonWithClaude(ctx.env, name, entry, ctx.flags.force);
429
+ if (plan.kind === "json") await installJsonTarget(plan, snapshot.servers);
430
+ else for (const [name, entry] of Object.entries(snapshot.servers)) await addJsonWithClaude(ctx.env, name, entry);
454
431
  installedTargets.push({
455
432
  agent: plan.agent,
456
433
  path: plan.path,
@@ -587,7 +564,7 @@ async function add(ctx, argv) {
587
564
  const rawPaths = positionalPaths(argv.paths);
588
565
  if (rawPaths.length === 0) throw usage("No capability path given", "Pass one or more `<name>` or `<namespace>/<name>` arguments.");
589
566
  const parsedPaths = rawPaths.map(parseMcpPath);
590
- const scope = argv.global === true ? "global" : "project";
567
+ const scope = await resolveInstallScope(ctx, argv.global === true);
591
568
  const targets = selectMcpTargets(stringValues(argv.agent), argv.all === true, scope, process.cwd(), ctx.env);
592
569
  const connection = await resolveConnection(ctx.flags, ctx.env);
593
570
  let approvalHintShown = false;
@@ -604,7 +581,7 @@ async function add(ctx, argv) {
604
581
  }
605
582
  }
606
583
  });
607
- ctx.writePayload({ results });
584
+ if (ctx.flags.json || !ctx.io.stdout.isTTY) ctx.writePayload({ results });
608
585
  return failed ? 1 : 0;
609
586
  }
610
587
  export { add, publish };
@@ -1,4 +1,4 @@
1
- import { S as HngrError } from "./http-2HWxLIOq.mjs";
1
+ import { C as HngrError } from "./http-DgHX7Fux.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 { 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";
1
+ import { C as HngrError, _ as scrubCredentials, a as toHngrError, g as resolveConnection, h as redactUrl, i as buildRequestUrl, l as apiErrorFromResponse, n as apiGet } from "./http-DgHX7Fux.mjs";
2
+ import { t as collectPages } from "./pagination-BXyFxGYA.mjs";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { join, resolve } from "node:path";
@@ -172,6 +172,10 @@ function claudeConfigDir(env) {
172
172
  function xdgConfigDir(env) {
173
173
  return env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME !== "" ? env.XDG_CONFIG_HOME : join(homeDir(env), ".config");
174
174
  }
175
+ /** Pi's agent directory: `PI_CODING_AGENT_DIR` or `~/.pi/agent`. */
176
+ function piAgentDir(env) {
177
+ return env.PI_CODING_AGENT_DIR && env.PI_CODING_AGENT_DIR !== "" ? env.PI_CODING_AGENT_DIR : join(homeDir(env), ".pi", "agent");
178
+ }
175
179
  const universalSkills = {
176
180
  projectDir: null,
177
181
  globalDir: null,
@@ -220,6 +224,15 @@ const AGENTS = [
220
224
  reason: "OpenCode uses a top-level `mcp` object with local/remote entries."
221
225
  }
222
226
  },
227
+ {
228
+ id: "pi",
229
+ skills: universalSkills,
230
+ mcp: {
231
+ kind: "json",
232
+ project: ".pi/mcp.json",
233
+ global: (env) => join(piAgentDir(env), "mcp.json")
234
+ }
235
+ },
223
236
  {
224
237
  id: "amp",
225
238
  skills: universalSkills,
@@ -539,4 +552,25 @@ async function runPublish(options) {
539
552
  ctx.writePayload({ results });
540
553
  return results.some((result) => result.status === "failed") ? 1 : 0;
541
554
  }
542
- export { resolveCapabilityVersion as _, failedResult as a, stringValues as c, homeDir as d, downloadContent as f, resolveCapability as g, parseCapabilityPath as h, errorBody as i, AGENTS as l, SKILL_KIND as m, readPublishFlags as n, positionalPaths as o, MCP_KIND as p, runPublish as r, runBatch as s, explicitName as t, findAgentDefinition as u };
555
+ // src/commands/scope.ts
556
+ const SCOPE_OPTIONS = [{
557
+ value: "project",
558
+ label: "Project",
559
+ hint: "install into the current directory"
560
+ }, {
561
+ value: "global",
562
+ label: "Global",
563
+ hint: "install for the current user"
564
+ }];
565
+ /**
566
+ * Resolves project vs global install scope. `--global` wins without asking.
567
+ * Without an interactive prompter the current-directory default is kept, so
568
+ * scripts, pipes and `--json` runs never block on a prompt.
569
+ */
570
+ async function resolveInstallScope(ctx, global) {
571
+ if (global) return "global";
572
+ const select = ctx.io.prompts?.select;
573
+ if (!select || ctx.flags.json || !ctx.io.stdin.isTTY || !ctx.io.stdout.isTTY) return "project";
574
+ return await select("Install scope", SCOPE_OPTIONS, "project") === "global" ? "global" : "project";
575
+ }
576
+ export { resolveCapability as _, errorBody as a, runBatch as c, findAgentDefinition as d, homeDir as f, parseCapabilityPath as g, SKILL_KIND as h, runPublish as i, stringValues as l, MCP_KIND as m, explicitName as n, failedResult as o, downloadContent as p, readPublishFlags as r, positionalPaths as s, resolveInstallScope as t, AGENTS as u, resolveCapabilityVersion as v };