hngr 0.5.1 → 0.7.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
CHANGED
|
@@ -59,7 +59,10 @@ install replaces the existing target; `--force` is not needed.
|
|
|
59
59
|
|
|
60
60
|
Interactive runs end with a borderless summary on stderr (canonical path, universal
|
|
61
61
|
agents, symlinked/copied agents); stdout carries the JSON payload only when piped or when
|
|
62
|
-
`--json` is passed.
|
|
62
|
+
`--json` is passed. After a successful interactive install the CLI asks once whether to
|
|
63
|
+
also install `system/find-capabilities` (the catalog's discovery skill, which finds both
|
|
64
|
+
Skills and MCP servers) for the same scope and agents; it is skipped silently when a copy
|
|
65
|
+
already exists in either scope, and a failed follow-up install only prints a warning.
|
|
63
66
|
|
|
64
67
|
MCP installs ask for the same scope and then for the MCP targets to write, with an
|
|
65
68
|
`All targets` entry; targets whose config already exists are preselected, and Claude
|
|
@@ -42,6 +42,7 @@ const EXCLUDED_PATHS = [
|
|
|
42
42
|
"/api/v1/capabilities/{capability_id}/versions/{version}/content",
|
|
43
43
|
"/api/v1/capabilities/{capability_id}/versions/{version}/files",
|
|
44
44
|
"/api/v1/capabilities/{capability_id}/versions/{version}/file",
|
|
45
|
+
"/api/v1/capabilities/{capability_id}/versions/{version}/derive",
|
|
45
46
|
"/api/v1/auth/methods",
|
|
46
47
|
"/api/v1/auth/oidc/login",
|
|
47
48
|
"/api/v1/auth/oidc/callback",
|
|
@@ -1479,6 +1480,42 @@ const COMMANDS = deriveCommands({
|
|
|
1479
1480
|
}
|
|
1480
1481
|
}
|
|
1481
1482
|
},
|
|
1483
|
+
"/api/v1/capabilities/{capability_id}/versions/{version}/derive": { "post": {
|
|
1484
|
+
"operationId": "deriveCapabilityVersion",
|
|
1485
|
+
"summary": "Publish a new Version by editing the files of the current Skill Version.",
|
|
1486
|
+
"description": "Derives a new Version from the package of `{version}` by replacing the\nlisted entries and publishes it through the same pipeline as\n`publishCapabilityVersion`: CalVer allocation, the current-version\nswitch and the `capability.version_published` Audit Event all follow\nthe existing publishing rules. Only a Skill Capability accepts this\noperation; an MCP Capability answers 400.\n\n`{version}` must be the Capability's Current Version; any other CalVer\nanswers 409 `/problems/version-conflict`. The check runs again after\nthe Capability row is locked, so a concurrent publish is reported\nrather than silently replaced. Each `edits` path is matched exactly\nagainst a non-directory entry of the baseline package (case\nsensitive); an unknown path, a directory, a non-text entry, an entry\nlarger than 256 KiB, or more than 1 MiB of edits in total answers 400.\n\nThe new Version inherits the baseline Version's `readme` unchanged,\nincluding a null value, for which the existing display rule falls back\nto the package's `SKILL.md`. The request has no README field. This\noperation does not increment `download_count`.\n\nThe caller must manage the Capability's Namespace, under the same\nrules as `publishCapabilityVersion`: a deleted or unavailable\nCapability, one in an unusable Namespace, and a private one the caller\ndoes not manage answer 404. A replayed `Idempotency-Key` returns the\nfirst response without deriving again.\n",
|
|
1487
|
+
"security": [{ "sessionCookie": [] }, { "bearerToken": [] }],
|
|
1488
|
+
"parameters": [
|
|
1489
|
+
{ "$ref": "#/components/parameters/CapabilityID" },
|
|
1490
|
+
{
|
|
1491
|
+
"name": "version",
|
|
1492
|
+
"in": "path",
|
|
1493
|
+
"required": true,
|
|
1494
|
+
"schema": { "$ref": "#/components/schemas/CalVer" }
|
|
1495
|
+
},
|
|
1496
|
+
{ "$ref": "#/components/parameters/IdempotencyKey" }
|
|
1497
|
+
],
|
|
1498
|
+
"requestBody": {
|
|
1499
|
+
"required": true,
|
|
1500
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeriveCapabilityVersionRequest" } } }
|
|
1501
|
+
},
|
|
1502
|
+
"responses": {
|
|
1503
|
+
"201": {
|
|
1504
|
+
"description": "The Capability with its new current version.",
|
|
1505
|
+
"headers": { "Location": {
|
|
1506
|
+
"description": "`/api/v1/capabilities/{capability_id}/versions/{version}`",
|
|
1507
|
+
"schema": { "type": "string" }
|
|
1508
|
+
} },
|
|
1509
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Capability" } } }
|
|
1510
|
+
},
|
|
1511
|
+
"400": { "$ref": "#/components/responses/Problem" },
|
|
1512
|
+
"401": { "$ref": "#/components/responses/Problem" },
|
|
1513
|
+
"403": { "$ref": "#/components/responses/Problem" },
|
|
1514
|
+
"404": { "$ref": "#/components/responses/Problem" },
|
|
1515
|
+
"409": { "$ref": "#/components/responses/Problem" },
|
|
1516
|
+
"default": { "$ref": "#/components/responses/Problem" }
|
|
1517
|
+
}
|
|
1518
|
+
} },
|
|
1482
1519
|
"/api/v1/capabilities/{capability_id}/versions/{version}/content": { "get": {
|
|
1483
1520
|
"operationId": "lookupCapabilityContent",
|
|
1484
1521
|
"summary": "Return the Content of one Version of a Capability.",
|
|
@@ -3412,6 +3449,34 @@ const COMMANDS = deriveCommands({
|
|
|
3412
3449
|
},
|
|
3413
3450
|
"additionalProperties": false
|
|
3414
3451
|
},
|
|
3452
|
+
"DeriveCapabilityVersionRequest": {
|
|
3453
|
+
"type": "object",
|
|
3454
|
+
"required": ["edits"],
|
|
3455
|
+
"properties": { "edits": {
|
|
3456
|
+
"type": "array",
|
|
3457
|
+
"description": "Entries to replace in the baseline package. Paths must be unique\nand must exist as non-directory entries of the baseline package.\n",
|
|
3458
|
+
"minItems": 1,
|
|
3459
|
+
"maxItems": 200,
|
|
3460
|
+
"items": { "$ref": "#/components/schemas/DeriveCapabilityVersionEdit" }
|
|
3461
|
+
} },
|
|
3462
|
+
"additionalProperties": false
|
|
3463
|
+
},
|
|
3464
|
+
"DeriveCapabilityVersionEdit": {
|
|
3465
|
+
"type": "object",
|
|
3466
|
+
"required": ["path", "content"],
|
|
3467
|
+
"properties": {
|
|
3468
|
+
"path": {
|
|
3469
|
+
"type": "string",
|
|
3470
|
+
"minLength": 1,
|
|
3471
|
+
"maxLength": 4096
|
|
3472
|
+
},
|
|
3473
|
+
"content": {
|
|
3474
|
+
"type": "string",
|
|
3475
|
+
"description": "UTF-8 text. The server enforces 256 KiB per entry and 1 MiB for\nthe whole request by UTF-8 byte length.\n"
|
|
3476
|
+
}
|
|
3477
|
+
},
|
|
3478
|
+
"additionalProperties": false
|
|
3479
|
+
},
|
|
3415
3480
|
"Capability": {
|
|
3416
3481
|
"type": "object",
|
|
3417
3482
|
"required": [
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as kebabCase, i as normalizeCommandName, n as COMMANDS, o as COMMAND_ALIASES, r as findCommand, t as commandCatalog } from "./catalog-
|
|
1
|
+
import { a as kebabCase, i as normalizeCommandName, n as COMMANDS, o as COMMAND_ALIASES, r as findCommand, t as commandCatalog } from "./catalog-yVsOzNKU.mjs";
|
|
2
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
3
|
import { t as collectPages } from "./pagination-BXyFxGYA.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
@@ -5279,7 +5279,7 @@ function isYargsInstance(y) {
|
|
|
5279
5279
|
// ../../node_modules/.pnpm/yargs@18.2.0/node_modules/yargs/index.mjs
|
|
5280
5280
|
const Yargs = YargsFactory(esm_default);
|
|
5281
5281
|
// package.json
|
|
5282
|
-
var version = "0.
|
|
5282
|
+
var version = "0.7.0";
|
|
5283
5283
|
// src/generated/completion.ts
|
|
5284
5284
|
function isCompletionShell(value) {
|
|
5285
5285
|
return value === "bash" || value === "zsh" || value === "fish";
|
|
@@ -6429,6 +6429,23 @@ var y = class {
|
|
|
6429
6429
|
}
|
|
6430
6430
|
}
|
|
6431
6431
|
};
|
|
6432
|
+
var r = class extends y {
|
|
6433
|
+
get cursor() {
|
|
6434
|
+
return this.value ? 0 : 1;
|
|
6435
|
+
}
|
|
6436
|
+
get _value() {
|
|
6437
|
+
return this.cursor === 0;
|
|
6438
|
+
}
|
|
6439
|
+
constructor(t) {
|
|
6440
|
+
super(t, false), this.value = !!t.initialValue, this.on("userInput", () => {
|
|
6441
|
+
this.value = this._value;
|
|
6442
|
+
}), this.on("confirm", (i) => {
|
|
6443
|
+
this.output.write(import_src.cursor.move(0, -1)), this.value = i, this.state = "submit", this.close();
|
|
6444
|
+
}), this.on("cursor", () => {
|
|
6445
|
+
this.value = !this.value;
|
|
6446
|
+
});
|
|
6447
|
+
}
|
|
6448
|
+
};
|
|
6432
6449
|
var a = class extends y {
|
|
6433
6450
|
options;
|
|
6434
6451
|
cursor = 0;
|
|
@@ -6633,6 +6650,35 @@ const limitOptions = ({ cursor: l, options: e, style: w, output: p = process.std
|
|
|
6633
6650
|
for (const t of s) for (const n of t) x.push(n);
|
|
6634
6651
|
return c && x.push(M), x;
|
|
6635
6652
|
};
|
|
6653
|
+
const confirm = (e) => {
|
|
6654
|
+
const a = e.active ?? "Yes", o = e.inactive ?? "No";
|
|
6655
|
+
return new r({
|
|
6656
|
+
active: a,
|
|
6657
|
+
inactive: o,
|
|
6658
|
+
signal: e.signal,
|
|
6659
|
+
input: e.input,
|
|
6660
|
+
output: e.output,
|
|
6661
|
+
initialValue: e.initialValue ?? true,
|
|
6662
|
+
render() {
|
|
6663
|
+
const i = e.withGuide ?? settings.withGuide, u = `${symbol(this.state)} `, l = i ? `${styleText("gray", S_BAR)} ` : "", f = wrapTextWithPrefix(e.output, e.message, l, u), s = `${i ? `${styleText("gray", S_BAR)}
|
|
6664
|
+
` : ""}${f}
|
|
6665
|
+
`, c = this.value ? a : o;
|
|
6666
|
+
switch (this.state) {
|
|
6667
|
+
case "submit": return `${s}${i ? `${styleText("gray", S_BAR)} ` : ""}${styleText("dim", c)}`;
|
|
6668
|
+
case "cancel": return `${s}${i ? `${styleText("gray", S_BAR)} ` : ""}${styleText(["strikethrough", "dim"], c)}${i ? `
|
|
6669
|
+
${styleText("gray", S_BAR)}` : ""}`;
|
|
6670
|
+
default: {
|
|
6671
|
+
const r = i ? `${styleText("cyan", S_BAR)} ` : "", g = i ? styleText("cyan", S_BAR_END) : "";
|
|
6672
|
+
return `${s}${r}${this.value ? `${styleText("green", S_RADIO_ACTIVE)} ${a}` : `${styleText("dim", S_RADIO_INACTIVE)} ${styleText("dim", a)}`}${e.vertical ? i ? `
|
|
6673
|
+
${styleText("cyan", S_BAR)} ` : `
|
|
6674
|
+
` : ` ${styleText("dim", "/")} `}${this.value ? `${styleText("dim", S_RADIO_INACTIVE)} ${styleText("dim", o)}` : `${styleText("green", S_RADIO_ACTIVE)} ${o}`}
|
|
6675
|
+
${g}
|
|
6676
|
+
`;
|
|
6677
|
+
}
|
|
6678
|
+
}
|
|
6679
|
+
}
|
|
6680
|
+
}).prompt();
|
|
6681
|
+
};
|
|
6636
6682
|
const MULTISELECT_INSTRUCTIONS = [
|
|
6637
6683
|
`${styleText("dim", "↑/↓")} to navigate`,
|
|
6638
6684
|
`${styleText("dim", "Space:")} select`,
|
|
@@ -6905,6 +6951,14 @@ function clackPrompter(streams = {}) {
|
|
|
6905
6951
|
initialValue
|
|
6906
6952
|
}));
|
|
6907
6953
|
},
|
|
6954
|
+
async confirm(message, initialValue = true) {
|
|
6955
|
+
return unwrapPrompt(await confirm({
|
|
6956
|
+
message,
|
|
6957
|
+
input,
|
|
6958
|
+
output,
|
|
6959
|
+
initialValue
|
|
6960
|
+
}));
|
|
6961
|
+
},
|
|
6908
6962
|
async multiselect(message, options, initialValues = [], hint) {
|
|
6909
6963
|
if (hint) output.write(`${import_picocolors.default.dim(hint)}\n`);
|
|
6910
6964
|
return unwrapPrompt(await multiselect({
|
|
@@ -7021,7 +7075,7 @@ function buildParser(onMatched) {
|
|
|
7021
7075
|
parser.command("commands", "Print the command catalog", () => {}, (argv) => {
|
|
7022
7076
|
onMatched({
|
|
7023
7077
|
argv,
|
|
7024
|
-
run: async (ctx) => (await import("./commands-
|
|
7078
|
+
run: async (ctx) => (await import("./commands-KKAq0m6z.mjs")).run(ctx)
|
|
7025
7079
|
});
|
|
7026
7080
|
});
|
|
7027
7081
|
parser.command("auth", "Manage credentials", (auth) => {
|
|
@@ -7101,7 +7155,7 @@ function buildParser(onMatched) {
|
|
|
7101
7155
|
}), (argv) => {
|
|
7102
7156
|
onMatched({
|
|
7103
7157
|
argv,
|
|
7104
|
-
run: async (ctx, matchedArgv) => (await import("./skills-
|
|
7158
|
+
run: async (ctx, matchedArgv) => (await import("./skills-BH59pgRw.mjs")).add(ctx, matchedArgv)
|
|
7105
7159
|
});
|
|
7106
7160
|
});
|
|
7107
7161
|
skills.command("publish <paths..>", "Publish a local skill directory as a capability or a new version", (publish) => publish.option("namespace", {
|
|
@@ -7129,7 +7183,7 @@ function buildParser(onMatched) {
|
|
|
7129
7183
|
}), (argv) => {
|
|
7130
7184
|
onMatched({
|
|
7131
7185
|
argv,
|
|
7132
|
-
run: async (ctx, matchedArgv) => (await import("./skills-
|
|
7186
|
+
run: async (ctx, matchedArgv) => (await import("./skills-BH59pgRw.mjs")).publish(ctx, matchedArgv)
|
|
7133
7187
|
});
|
|
7134
7188
|
});
|
|
7135
7189
|
return skills.demandCommand(1);
|
|
@@ -241,12 +241,12 @@ function readSkillPackage(buffer) {
|
|
|
241
241
|
if (seen.has(folded)) throw invalid$1(`entry ${JSON.stringify(name)} appears more than once`);
|
|
242
242
|
seen.add(folded);
|
|
243
243
|
}
|
|
244
|
-
const manifests = files.filter((entry) => lastSegment$1(entry.name).toLowerCase() === "skill.md");
|
|
245
|
-
if (manifests.length === 0) throw invalid$1("the package has no SKILL.md");
|
|
246
|
-
if (manifests.length > 1) throw invalid$1(`the package has more than one SKILL.md: ${manifests.map((entry) => entry.name).join(", ")}`);
|
|
247
244
|
const root = packageRoot$1(names);
|
|
248
245
|
const expectedManifest = `${root}SKILL.md`;
|
|
249
|
-
if (
|
|
246
|
+
if (!files.find((entry) => entry.name === expectedManifest)) {
|
|
247
|
+
const manifests = files.filter((entry) => lastSegment$1(entry.name).toLowerCase() === "skill.md");
|
|
248
|
+
throw invalid$1(manifests.length === 0 ? "the package has no SKILL.md" : `found ${manifests.map((entry) => entry.name).join(", ")}, but the package needs SKILL.md at its root or inside its single top-level directory`);
|
|
249
|
+
}
|
|
250
250
|
let actualBytes = 0;
|
|
251
251
|
const entries = [];
|
|
252
252
|
let manifest;
|
|
@@ -455,11 +455,11 @@ async function packageSkillDirectory(dir) {
|
|
|
455
455
|
if (seen.has(folded)) throw invalid(`entry ${JSON.stringify(entry.path)} appears more than once`);
|
|
456
456
|
seen.add(folded);
|
|
457
457
|
}
|
|
458
|
-
const manifests = entries.filter((entry) => lastSegment(entry.path).toLowerCase() === "skill.md");
|
|
459
|
-
if (manifests.length === 0) throw invalid("the directory has no SKILL.md");
|
|
460
|
-
if (manifests.length > 1) throw invalid(`the directory has more than one SKILL.md: ${manifests.map((entry) => entry.path).join(", ")}`);
|
|
461
458
|
const expected = `${packageRoot(entries.map((entry) => entry.path))}SKILL.md`;
|
|
462
|
-
if (
|
|
459
|
+
if (!entries.some((entry) => entry.path === expected)) {
|
|
460
|
+
const manifests = entries.filter((entry) => lastSegment(entry.path).toLowerCase() === "skill.md");
|
|
461
|
+
throw invalid(manifests.length === 0 ? "the directory has no SKILL.md" : `found ${manifests.map((entry) => entry.path).join(", ")}, but the package needs SKILL.md at its root or inside its single top-level directory`);
|
|
462
|
+
}
|
|
463
463
|
return {
|
|
464
464
|
zip: buildZip(entries),
|
|
465
465
|
fileCount: entries.length,
|
|
@@ -787,6 +787,47 @@ async function publish(ctx, argv) {
|
|
|
787
787
|
items
|
|
788
788
|
});
|
|
789
789
|
}
|
|
790
|
+
const FIND_CAPABILITIES = "find-capabilities";
|
|
791
|
+
/** The follow-up question needs a yes/no prompter on both ends of a terminal. */
|
|
792
|
+
function canConfirm(ctx) {
|
|
793
|
+
return ctx.io.stdin.isTTY && ctx.io.stdout.isTTY && ctx.io.prompts?.confirm !== void 0;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Whether any scope the caller would install into already holds find-capabilities.
|
|
797
|
+
* A project install does not ask again when the global store has it, and the
|
|
798
|
+
* reverse, so the offer appears only when nothing is installed yet.
|
|
799
|
+
*/
|
|
800
|
+
async function findCapabilitiesInstalled(ctx, agents, options) {
|
|
801
|
+
const scopes = options.dir ? [options.global] : [false, true];
|
|
802
|
+
for (const global of scopes) {
|
|
803
|
+
const plan = planInstall(process.cwd(), ctx.env, FIND_CAPABILITIES, agents, {
|
|
804
|
+
...options,
|
|
805
|
+
global
|
|
806
|
+
});
|
|
807
|
+
const paths = [...plan.canonical ? [plan.canonical] : [], ...plan.targets.map((target) => target.path)];
|
|
808
|
+
for (const path of paths) if (await pathExists(path)) return true;
|
|
809
|
+
}
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Offers the catalog's capability-discovery skill after a successful install so
|
|
814
|
+
* an agent that just gained one capability can find the rest, Skills and MCP
|
|
815
|
+
* servers alike. It is a convenience, not part of the request: an
|
|
816
|
+
* already-installed copy or a failed download never makes the add itself fail
|
|
817
|
+
* (a cancelled prompt still exits 130).
|
|
818
|
+
*/
|
|
819
|
+
async function offerFindCapabilities(ctx, connection, destNames, agents, options) {
|
|
820
|
+
const confirm = ctx.io.prompts?.confirm;
|
|
821
|
+
if (!confirm || destNames.includes(FIND_CAPABILITIES)) return;
|
|
822
|
+
if (await findCapabilitiesInstalled(ctx, agents, options)) return;
|
|
823
|
+
if (!await confirm(`Also install ${FIND_CAPABILITIES} (discover more capabilities)?`, true)) return;
|
|
824
|
+
try {
|
|
825
|
+
await addOne(ctx, connection, parseSkillPath(FIND_CAPABILITIES), FIND_CAPABILITIES, agents, options.dir, options.global, options.copy, { version: null });
|
|
826
|
+
} catch (err) {
|
|
827
|
+
if (err instanceof HngrError && err.exitCode !== 1) throw err;
|
|
828
|
+
ctx.summary(`warning: could not install ${FIND_CAPABILITIES}: ${err instanceof Error ? err.message : String(err)}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
790
831
|
async function add(ctx, argv) {
|
|
791
832
|
const rawPaths = positionalPaths(argv.paths);
|
|
792
833
|
if (rawPaths.length === 0) throw usage("No skill path given", "Pass one or more `<name>` or `<namespace>/<name>` arguments.");
|
|
@@ -812,6 +853,11 @@ async function add(ctx, argv) {
|
|
|
812
853
|
failure: failureResult
|
|
813
854
|
});
|
|
814
855
|
if (ctx.flags.json || !ctx.io.stdout.isTTY) ctx.writePayload({ results });
|
|
856
|
+
if (!ctx.flags.json && !ctx.flags.dryRun && !ctx.flags.quiet && !failed && canConfirm(ctx)) await offerFindCapabilities(ctx, connection, parsedPaths.map((parsed) => as ?? parsed.name), agents, {
|
|
857
|
+
dir,
|
|
858
|
+
global,
|
|
859
|
+
copy
|
|
860
|
+
});
|
|
815
861
|
if (!ctx.flags.json && !ctx.flags.dryRun && !ctx.flags.quiet && !failed && ctx.io.stderr.isTTY) ctx.io.prompts?.outro?.("Done! Review skills before use; they run with full agent permissions.");
|
|
816
862
|
return failed ? 1 : 0;
|
|
817
863
|
}
|