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.
- package/README.md +6 -3
- package/dist/{auth-BT8HuCif.mjs → auth-ByGgd_Yn.mjs} +221 -40
- package/dist/{catalog-jU0-wzh3.mjs → catalog-Bv304SXJ.mjs} +1154 -122
- package/dist/{commands-BchmocR_.mjs → commands-CLqLwQHY.mjs} +1 -1
- package/dist/{http-2HWxLIOq.mjs → http-DgHX7Fux.mjs} +13 -7
- package/dist/index.mjs +126 -29
- package/dist/{mcp-DeWHHgQK.mjs → mcp-DWF_ELDU.mjs} +29 -50
- package/dist/{pagination-BbKLKSw3.mjs → pagination-BXyFxGYA.mjs} +1 -1
- package/dist/{publish-D7zaQl9x.mjs → scope-Ch6wZCgE.mjs} +63 -12
- package/dist/{skills-DwoDzsXd.mjs → skills-CxMeJ5PF.mjs} +23 -39
- package/package.json +1 -1
|
@@ -86,18 +86,16 @@ async function writeFileAtomically(path, content, options = {}) {
|
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
88
88
|
* Writes a directory through "temp → rename": the destination is either
|
|
89
|
-
* absent or complete, never half-written.
|
|
90
|
-
*
|
|
89
|
+
* absent or complete, never half-written. An existing directory is moved
|
|
90
|
+
* aside before the rename so a failed swap puts it back.
|
|
91
91
|
*/
|
|
92
|
-
async function writeDirectoryAtomically(dest, writeTemp, options) {
|
|
92
|
+
async function writeDirectoryAtomically(dest, writeTemp, options = {}) {
|
|
93
93
|
const parent = dirname(dest);
|
|
94
94
|
await mkdir(parent, { recursive: true });
|
|
95
95
|
const temp = resolve(parent, `.hngr-${basename(dest)}-${randomUUID()}.tmp`);
|
|
96
96
|
try {
|
|
97
97
|
await writeTemp(temp);
|
|
98
|
-
|
|
99
|
-
if (exists && !options.force) throw options.existsError();
|
|
100
|
-
if (!exists) {
|
|
98
|
+
if (!await lstat(dest).then(() => true, () => false)) {
|
|
101
99
|
await rename(temp, dest);
|
|
102
100
|
return;
|
|
103
101
|
}
|
|
@@ -127,6 +125,14 @@ async function writeDirectoryAtomically(dest, writeTemp, options) {
|
|
|
127
125
|
}
|
|
128
126
|
}
|
|
129
127
|
// src/config.ts
|
|
128
|
+
/**
|
|
129
|
+
* The stored token's audience. Config files written before audiences existed
|
|
130
|
+
* hold a management token by definition, so a missing field reads as
|
|
131
|
+
* `management` instead of failing to load.
|
|
132
|
+
*/
|
|
133
|
+
function storedAudience(config) {
|
|
134
|
+
return config?.audience === "mcp" ? "mcp" : "management";
|
|
135
|
+
}
|
|
130
136
|
/** `$XDG_CONFIG_HOME/hngr/config.json`, falling back to `~/.config/hngr/config.json`. */
|
|
131
137
|
function configFilePath(env) {
|
|
132
138
|
const xdg = env.XDG_CONFIG_HOME;
|
|
@@ -456,4 +462,4 @@ async function apiFetch(connection, options) {
|
|
|
456
462
|
signal: options.signal
|
|
457
463
|
});
|
|
458
464
|
}
|
|
459
|
-
export {
|
|
465
|
+
export { HngrError as C, CancelledError as S, scrubCredentials as _, toHngrError as a, writeDirectoryAtomically 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, storedAudience as v, renderError as w, writeFileAtomically as x, writeStoredConfig 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-
|
|
2
|
-
import { C as
|
|
3
|
-
import { t as collectPages } from "./pagination-
|
|
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.
|
|
5282
|
+
var version = "0.4.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("◻", "[ ]");
|
|
@@ -6720,7 +6748,64 @@ ${r ? styleText("cyan", S_BAR_END) : ""}
|
|
|
6720
6748
|
}
|
|
6721
6749
|
}
|
|
6722
6750
|
}).prompt();
|
|
6723
|
-
`${styleText("dim", "↑/↓")}`, `${styleText("dim", "Enter:")}
|
|
6751
|
+
const SELECT_INSTRUCTIONS = [`${styleText("dim", "↑/↓")} to navigate`, `${styleText("dim", "Enter:")} confirm`];
|
|
6752
|
+
const c = (t, o) => t.includes(`
|
|
6753
|
+
`) ? t.split(`
|
|
6754
|
+
`).map((d) => o(d)).join(`
|
|
6755
|
+
`) : o(t);
|
|
6756
|
+
const select = (t) => {
|
|
6757
|
+
const o = (n, m) => {
|
|
6758
|
+
if (n === void 0) return "";
|
|
6759
|
+
const s = n.label ?? String(n.value);
|
|
6760
|
+
switch (m) {
|
|
6761
|
+
case "disabled": return `${styleText("gray", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("gray", i))}${n.hint ? ` ${styleText("dim", `(${n.hint ?? "disabled"})`)}` : ""}`;
|
|
6762
|
+
case "selected": return `${c(s, (i) => styleText("dim", i))}`;
|
|
6763
|
+
case "active": return `${styleText("green", S_RADIO_ACTIVE)} ${s}${n.hint ? ` ${styleText("dim", `(${n.hint})`)}` : ""}`;
|
|
6764
|
+
case "cancelled": return `${c(s, (i) => styleText(["strikethrough", "dim"], i))}`;
|
|
6765
|
+
default: return `${styleText("dim", S_RADIO_INACTIVE)} ${c(s, (i) => styleText("dim", i))}`;
|
|
6766
|
+
}
|
|
6767
|
+
}, d = t.showInstructions ?? true;
|
|
6768
|
+
return new n$1({
|
|
6769
|
+
options: t.options,
|
|
6770
|
+
signal: t.signal,
|
|
6771
|
+
input: t.input,
|
|
6772
|
+
output: t.output,
|
|
6773
|
+
initialValue: t.initialValue,
|
|
6774
|
+
render() {
|
|
6775
|
+
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)}
|
|
6776
|
+
` : ""}${i}
|
|
6777
|
+
`;
|
|
6778
|
+
switch (this.state) {
|
|
6779
|
+
case "submit": {
|
|
6780
|
+
const r = n ? `${styleText("gray", S_BAR)} ` : "";
|
|
6781
|
+
return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "selected"), r)}`;
|
|
6782
|
+
}
|
|
6783
|
+
case "cancel": {
|
|
6784
|
+
const r = n ? `${styleText("gray", S_BAR)} ` : "";
|
|
6785
|
+
return `${u}${wrapTextWithPrefix(t.output, o(this.options[this.cursor], "cancelled"), r)}${n ? `
|
|
6786
|
+
${styleText("gray", S_BAR)}` : ""}`;
|
|
6787
|
+
}
|
|
6788
|
+
default: {
|
|
6789
|
+
const r = n ? `${styleText("cyan", S_BAR)} ` : "", a = u.split(`
|
|
6790
|
+
`).length, p = d ? formatInstructionFooter(SELECT_INSTRUCTIONS, n) : n ? [styleText("cyan", S_BAR_END)] : [], f = p.join(`
|
|
6791
|
+
`), b = p.length + 1;
|
|
6792
|
+
return `${u}${r}${limitOptions({
|
|
6793
|
+
output: t.output,
|
|
6794
|
+
cursor: this.cursor,
|
|
6795
|
+
options: this.options,
|
|
6796
|
+
maxItems: t.maxItems,
|
|
6797
|
+
columnPadding: r.length,
|
|
6798
|
+
rowPadding: a + b,
|
|
6799
|
+
style: (g, x) => o(g, g.disabled ? "disabled" : x ? "active" : "inactive")
|
|
6800
|
+
}).join(`
|
|
6801
|
+
${r}`)}
|
|
6802
|
+
${f}
|
|
6803
|
+
`;
|
|
6804
|
+
}
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
}).prompt();
|
|
6808
|
+
};
|
|
6724
6809
|
`${styleText("gray", S_BAR)}`;
|
|
6725
6810
|
const text = (t) => new n({
|
|
6726
6811
|
validate: t.validate,
|
|
@@ -6804,6 +6889,15 @@ function clackPrompter(streams = {}) {
|
|
|
6804
6889
|
}
|
|
6805
6890
|
}));
|
|
6806
6891
|
},
|
|
6892
|
+
async select(message, options, initialValue) {
|
|
6893
|
+
return unwrapPrompt(await select({
|
|
6894
|
+
message,
|
|
6895
|
+
input,
|
|
6896
|
+
output,
|
|
6897
|
+
options,
|
|
6898
|
+
initialValue
|
|
6899
|
+
}));
|
|
6900
|
+
},
|
|
6807
6901
|
async multiselect(message, options, initialValues = []) {
|
|
6808
6902
|
return unwrapPrompt(await multiselect({
|
|
6809
6903
|
message,
|
|
@@ -6846,7 +6940,7 @@ const GLOBAL_OPTIONS = {
|
|
|
6846
6940
|
force: {
|
|
6847
6941
|
type: "boolean",
|
|
6848
6942
|
default: false,
|
|
6849
|
-
describe: "
|
|
6943
|
+
describe: "Skip destructive confirmations",
|
|
6850
6944
|
global: true
|
|
6851
6945
|
},
|
|
6852
6946
|
"dry-run": {
|
|
@@ -6911,46 +7005,53 @@ function buildParser(onMatched) {
|
|
|
6911
7005
|
parser.command("commands", "Print the command catalog", () => {}, (argv) => {
|
|
6912
7006
|
onMatched({
|
|
6913
7007
|
argv,
|
|
6914
|
-
run: async (ctx) => (await import("./commands-
|
|
7008
|
+
run: async (ctx) => (await import("./commands-CLqLwQHY.mjs")).run(ctx)
|
|
6915
7009
|
});
|
|
6916
7010
|
});
|
|
6917
7011
|
parser.command("auth", "Manage credentials", (auth) => {
|
|
6918
|
-
auth.command("login", "Log in
|
|
7012
|
+
auth.command("login", "Log in through the browser device flow, or with --password / --token", (login) => login.option("password", {
|
|
7013
|
+
type: "boolean",
|
|
7014
|
+
describe: "Prompt for a username and password instead of the device flow"
|
|
7015
|
+
}).option("token", {
|
|
6919
7016
|
type: "boolean",
|
|
6920
|
-
default: false,
|
|
6921
7017
|
describe: "Read an access token from stdin instead of prompting"
|
|
6922
|
-
}), (argv) => {
|
|
7018
|
+
}).conflicts("password", "token"), (argv) => {
|
|
6923
7019
|
onMatched({
|
|
6924
7020
|
argv,
|
|
6925
|
-
run: async (ctx, matchedArgv) => (await import("./auth-
|
|
7021
|
+
run: async (ctx, matchedArgv) => (await import("./auth-ByGgd_Yn.mjs")).login(ctx, matchedArgv)
|
|
6926
7022
|
});
|
|
6927
7023
|
});
|
|
6928
7024
|
auth.command("logout", "Revoke the stored token and delete the local config", () => {}, (argv) => {
|
|
6929
7025
|
onMatched({
|
|
6930
7026
|
argv,
|
|
6931
|
-
run: async (ctx) => (await import("./auth-
|
|
7027
|
+
run: async (ctx) => (await import("./auth-ByGgd_Yn.mjs")).logout(ctx)
|
|
6932
7028
|
});
|
|
6933
7029
|
});
|
|
6934
|
-
auth.command("status", "Show credential source, server, principal
|
|
7030
|
+
auth.command("status", "Show credential source, server, principal, token prefix and audience", (status) => status.option("offline", {
|
|
6935
7031
|
type: "boolean",
|
|
6936
7032
|
default: false,
|
|
6937
7033
|
describe: "Skip the live GET /me check"
|
|
6938
7034
|
}), (argv) => {
|
|
6939
7035
|
onMatched({
|
|
6940
7036
|
argv,
|
|
6941
|
-
run: async (ctx, matchedArgv) => (await import("./auth-
|
|
7037
|
+
run: async (ctx, matchedArgv) => (await import("./auth-ByGgd_Yn.mjs")).status(ctx, matchedArgv)
|
|
6942
7038
|
});
|
|
6943
7039
|
});
|
|
6944
|
-
auth.command("token", "Print the access token to stdout and nothing else", () =>
|
|
7040
|
+
auth.command("token", "Print the access token to stdout and nothing else", (token) => token.option("audience", {
|
|
7041
|
+
type: "string",
|
|
7042
|
+
choices: ["management", "mcp"],
|
|
7043
|
+
default: "management",
|
|
7044
|
+
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"
|
|
7045
|
+
}), (argv) => {
|
|
6945
7046
|
onMatched({
|
|
6946
7047
|
argv,
|
|
6947
|
-
run: async (ctx) => (await import("./auth-
|
|
7048
|
+
run: async (ctx, matchedArgv) => (await import("./auth-ByGgd_Yn.mjs")).token(ctx, matchedArgv)
|
|
6948
7049
|
});
|
|
6949
7050
|
});
|
|
6950
7051
|
auth.command("whoami", "Print the GET /me payload", () => {}, (argv) => {
|
|
6951
7052
|
onMatched({
|
|
6952
7053
|
argv,
|
|
6953
|
-
run: async (ctx) => (await import("./auth-
|
|
7054
|
+
run: async (ctx) => (await import("./auth-ByGgd_Yn.mjs")).whoami(ctx)
|
|
6954
7055
|
});
|
|
6955
7056
|
});
|
|
6956
7057
|
registerGeneratedAuth(auth, onMatched);
|
|
@@ -6984,7 +7085,7 @@ function buildParser(onMatched) {
|
|
|
6984
7085
|
}), (argv) => {
|
|
6985
7086
|
onMatched({
|
|
6986
7087
|
argv,
|
|
6987
|
-
run: async (ctx, matchedArgv) => (await import("./skills-
|
|
7088
|
+
run: async (ctx, matchedArgv) => (await import("./skills-CxMeJ5PF.mjs")).add(ctx, matchedArgv)
|
|
6988
7089
|
});
|
|
6989
7090
|
});
|
|
6990
7091
|
skills.command("publish <paths..>", "Publish a local skill directory as a capability or a new version", (publish) => publish.option("namespace", {
|
|
@@ -7002,11 +7103,9 @@ function buildParser(onMatched) {
|
|
|
7002
7103
|
}).option("readme", {
|
|
7003
7104
|
type: "string",
|
|
7004
7105
|
describe: "Markdown file to attach as the README"
|
|
7005
|
-
}).option("
|
|
7106
|
+
}).option("category", {
|
|
7006
7107
|
type: "string",
|
|
7007
|
-
|
|
7008
|
-
nargs: 1,
|
|
7009
|
-
describe: "Capability tag (repeatable, at most eight)"
|
|
7108
|
+
describe: "Category slug in the target namespace (create only; resolved even with --dry-run)"
|
|
7010
7109
|
}).option("new", {
|
|
7011
7110
|
type: "boolean",
|
|
7012
7111
|
default: false,
|
|
@@ -7014,7 +7113,7 @@ function buildParser(onMatched) {
|
|
|
7014
7113
|
}), (argv) => {
|
|
7015
7114
|
onMatched({
|
|
7016
7115
|
argv,
|
|
7017
|
-
run: async (ctx, matchedArgv) => (await import("./skills-
|
|
7116
|
+
run: async (ctx, matchedArgv) => (await import("./skills-CxMeJ5PF.mjs")).publish(ctx, matchedArgv)
|
|
7018
7117
|
});
|
|
7019
7118
|
});
|
|
7020
7119
|
return skills.demandCommand(1);
|
|
@@ -7036,7 +7135,7 @@ function buildParser(onMatched) {
|
|
|
7036
7135
|
}), (argv) => {
|
|
7037
7136
|
onMatched({
|
|
7038
7137
|
argv,
|
|
7039
|
-
run: async (ctx, matchedArgv) => (await import("./mcp-
|
|
7138
|
+
run: async (ctx, matchedArgv) => (await import("./mcp-DWF_ELDU.mjs")).add(ctx, matchedArgv)
|
|
7040
7139
|
});
|
|
7041
7140
|
});
|
|
7042
7141
|
mcp.command("publish <paths..>", "Publish an MCP snapshot as a capability or a new version", (publish) => publish.option("namespace", {
|
|
@@ -7054,11 +7153,9 @@ function buildParser(onMatched) {
|
|
|
7054
7153
|
}).option("readme", {
|
|
7055
7154
|
type: "string",
|
|
7056
7155
|
describe: "Markdown file to attach as the README"
|
|
7057
|
-
}).option("
|
|
7156
|
+
}).option("category", {
|
|
7058
7157
|
type: "string",
|
|
7059
|
-
|
|
7060
|
-
nargs: 1,
|
|
7061
|
-
describe: "Capability tag (repeatable, at most eight)"
|
|
7158
|
+
describe: "Category slug in the target namespace (create only; resolved even with --dry-run)"
|
|
7062
7159
|
}).option("new", {
|
|
7063
7160
|
type: "boolean",
|
|
7064
7161
|
default: false,
|
|
@@ -7066,7 +7163,7 @@ function buildParser(onMatched) {
|
|
|
7066
7163
|
}), (argv) => {
|
|
7067
7164
|
onMatched({
|
|
7068
7165
|
argv,
|
|
7069
|
-
run: async (ctx, matchedArgv) => (await import("./mcp-
|
|
7166
|
+
run: async (ctx, matchedArgv) => (await import("./mcp-DWF_ELDU.mjs")).publish(ctx, matchedArgv)
|
|
7070
7167
|
});
|
|
7071
7168
|
});
|
|
7072
7169
|
return mcp.demandCommand(1);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { _ as
|
|
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-Ch6wZCgE.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.
|
|
102
|
-
*
|
|
103
|
-
*
|
|
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
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
206
|
-
*
|
|
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))
|
|
218
|
-
if (
|
|
219
|
-
|
|
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
|
|
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
|
|
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) {
|
|
@@ -419,7 +398,7 @@ async function addOne(ctx, connection, parsed, targets, state) {
|
|
|
419
398
|
let failure = null;
|
|
420
399
|
for (const plan of targets) try {
|
|
421
400
|
if (plan.kind === "json") {
|
|
422
|
-
const preview = previewJsonTarget(plan, plan.filePath ? await readExisting(plan.filePath, plan.path) : void 0, snapshot.servers
|
|
401
|
+
const preview = previewJsonTarget(plan, plan.filePath ? await readExisting(plan.filePath, plan.path) : void 0, snapshot.servers);
|
|
423
402
|
ctx.detail(`dry-run: would write ${plan.path}\n${preview}`);
|
|
424
403
|
} 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
404
|
dryTargets.push({
|
|
@@ -449,8 +428,8 @@ async function addOne(ctx, connection, parsed, targets, state) {
|
|
|
449
428
|
const installedTargets = [];
|
|
450
429
|
let failure = null;
|
|
451
430
|
for (const plan of targets) try {
|
|
452
|
-
if (plan.kind === "json") await installJsonTarget(plan, snapshot.servers
|
|
453
|
-
else for (const [name, entry] of Object.entries(snapshot.servers)) await addJsonWithClaude(ctx.env, name, entry
|
|
431
|
+
if (plan.kind === "json") await installJsonTarget(plan, snapshot.servers);
|
|
432
|
+
else for (const [name, entry] of Object.entries(snapshot.servers)) await addJsonWithClaude(ctx.env, name, entry);
|
|
454
433
|
installedTargets.push({
|
|
455
434
|
agent: plan.agent,
|
|
456
435
|
path: plan.path,
|
|
@@ -517,7 +496,7 @@ function mcpPrepared(config, metadata) {
|
|
|
517
496
|
visibility: metadata.visibility
|
|
518
497
|
};
|
|
519
498
|
if (metadata.readme !== void 0) body.readme = metadata.readme;
|
|
520
|
-
if (metadata.
|
|
499
|
+
if (metadata.category !== void 0) body.category = metadata.category;
|
|
521
500
|
body.config = config;
|
|
522
501
|
return body;
|
|
523
502
|
};
|
|
@@ -536,7 +515,7 @@ function mcpPrepared(config, metadata) {
|
|
|
536
515
|
if (metadata.description !== void 0) preview.description = metadata.description;
|
|
537
516
|
preview.visibility = metadata.visibility;
|
|
538
517
|
if (metadata.readme !== void 0) preview.readme = metadata.readme;
|
|
539
|
-
if (metadata.
|
|
518
|
+
if (metadata.category !== void 0) preview.category = metadata.category;
|
|
540
519
|
preview.config = `<${serverCount} server${serverCount === 1 ? "" : "s"}>`;
|
|
541
520
|
return preview;
|
|
542
521
|
},
|
|
@@ -569,7 +548,7 @@ function defaultMcpName(file) {
|
|
|
569
548
|
async function publish(ctx, argv) {
|
|
570
549
|
const rawPaths = positionalPaths(argv.paths);
|
|
571
550
|
if (rawPaths.length === 0) throw usage("No MCP snapshot given", "Pass one or more JSON snapshot files.");
|
|
572
|
-
const flags = await readPublishFlags(argv
|
|
551
|
+
const flags = await readPublishFlags(argv);
|
|
573
552
|
const forcedName = explicitName(argv, rawPaths.length);
|
|
574
553
|
const items = rawPaths.map((file) => ({
|
|
575
554
|
name: forcedName ?? defaultMcpName(file),
|
|
@@ -587,7 +566,7 @@ async function add(ctx, argv) {
|
|
|
587
566
|
const rawPaths = positionalPaths(argv.paths);
|
|
588
567
|
if (rawPaths.length === 0) throw usage("No capability path given", "Pass one or more `<name>` or `<namespace>/<name>` arguments.");
|
|
589
568
|
const parsedPaths = rawPaths.map(parseMcpPath);
|
|
590
|
-
const scope = argv.global === true
|
|
569
|
+
const scope = await resolveInstallScope(ctx, argv.global === true);
|
|
591
570
|
const targets = selectMcpTargets(stringValues(argv.agent), argv.all === true, scope, process.cwd(), ctx.env);
|
|
592
571
|
const connection = await resolveConnection(ctx.flags, ctx.env);
|
|
593
572
|
let approvalHintShown = false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
2
|
-
import { t as collectPages } from "./pagination-
|
|
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";
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -509,4 +539,25 @@ async function runPublish(options) {
|
|
|
509
539
|
ctx.writePayload({ results });
|
|
510
540
|
return results.some((result) => result.status === "failed") ? 1 : 0;
|
|
511
541
|
}
|
|
512
|
-
|
|
542
|
+
// src/commands/scope.ts
|
|
543
|
+
const SCOPE_OPTIONS = [{
|
|
544
|
+
value: "project",
|
|
545
|
+
label: "Project",
|
|
546
|
+
hint: "install into the current directory"
|
|
547
|
+
}, {
|
|
548
|
+
value: "global",
|
|
549
|
+
label: "Global",
|
|
550
|
+
hint: "install for the current user"
|
|
551
|
+
}];
|
|
552
|
+
/**
|
|
553
|
+
* Resolves project vs global install scope. `--global` wins without asking.
|
|
554
|
+
* Without an interactive prompter the current-directory default is kept, so
|
|
555
|
+
* scripts, pipes and `--json` runs never block on a prompt.
|
|
556
|
+
*/
|
|
557
|
+
async function resolveInstallScope(ctx, global) {
|
|
558
|
+
if (global) return "global";
|
|
559
|
+
const select = ctx.io.prompts?.select;
|
|
560
|
+
if (!select || !ctx.io.stdin.isTTY || !ctx.io.stdout.isTTY) return "project";
|
|
561
|
+
return await select("Install scope", SCOPE_OPTIONS, "project") === "global" ? "global" : "project";
|
|
562
|
+
}
|
|
563
|
+
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 };
|