hngr 0.7.0 → 0.9.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 +18 -3
- package/dist/{auth-ByGgd_Yn.mjs → auth-CQkOtVxv.mjs} +5 -5
- package/dist/{catalog-yVsOzNKU.mjs → catalog-CmtREMFr.mjs} +19 -4
- package/dist/{commands-KKAq0m6z.mjs → commands-Bjui8-Nt.mjs} +1 -1
- package/dist/{http-DgHX7Fux.mjs → http-CLG2jrIE.mjs} +2 -2
- package/dist/index.mjs +171 -14
- package/dist/{mcp-DjSU9C8z.mjs → mcp-C-oJ6J5B.mjs} +2 -2
- package/dist/{pagination-BXyFxGYA.mjs → pagination-DtoXmj2F.mjs} +1 -1
- package/dist/{scope-D8k2oa_W.mjs → scope-8BZjgIXy.mjs} +47 -3
- package/dist/{skills-BH59pgRw.mjs → skills-CqHGxo_M.mjs} +265 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,13 +13,13 @@ hngr --version
|
|
|
13
13
|
## Authenticate
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
# Interactive: username and password against
|
|
16
|
+
# Interactive: username and password against https://hangar.paykka.com (the
|
|
17
17
|
# built-in default), then an access token is written to
|
|
18
18
|
# ~/.config/hngr/config.json (0600).
|
|
19
19
|
hngr auth login
|
|
20
20
|
|
|
21
|
-
# A different deployment.
|
|
22
|
-
#
|
|
21
|
+
# A different deployment. Any non-local host needs https:// (or an explicit
|
|
22
|
+
# --insecure).
|
|
23
23
|
hngr auth login --server https://hangar.example.com
|
|
24
24
|
|
|
25
25
|
# CI or containers: read an existing access token from stdin.
|
|
@@ -43,10 +43,25 @@ hngr mcp add pdf-tools --agent claude-code
|
|
|
43
43
|
# another namespace
|
|
44
44
|
hngr skills add team-a/pdf-tools --agent claude-code
|
|
45
45
|
|
|
46
|
+
# choose several skills from a namespace in an interactive terminal
|
|
47
|
+
hngr skills add team-a
|
|
48
|
+
|
|
49
|
+
# expand a namespace without prompting; the trailing slash or /* is explicit
|
|
50
|
+
hngr skills add team-a/ --all-skills --agent claude-code
|
|
51
|
+
hngr skills add 'team-a/*' --all-skills --agent claude-code
|
|
52
|
+
|
|
46
53
|
# pin a version
|
|
47
54
|
hngr skills add pdf-tools@2026.10.25.1 --agent claude-code
|
|
48
55
|
```
|
|
49
56
|
|
|
57
|
+
A bare name such as `team-a` first looks for `system/team-a`; only when that skill
|
|
58
|
+
does not exist is the argument treated as a namespace to choose from. Write `team-a/`
|
|
59
|
+
or `team-a/*` to mean the namespace unconditionally. In an interactive terminal a
|
|
60
|
+
namespace opens a searchable multi-select, including when it holds only one skill;
|
|
61
|
+
without a terminal, pass `--all-skills` to install every skill in it, or name each
|
|
62
|
+
skill as `<namespace>/<name>`. A namespace cannot be version-pinned, and every
|
|
63
|
+
selected skill still installs into its own `<name>` directory.
|
|
64
|
+
|
|
50
65
|
The shared `.agents/skills` store is always installed, so every universal agent reads it
|
|
51
66
|
without extra work. In an interactive terminal, omitting `--global` first asks whether to
|
|
52
67
|
install for the current project or the user, then shows a multi-select of the agents that
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as HngrError, d as configFilePath, f as deleteStoredConfig, g as resolveConnection, h as redactUrl, m as readStoredConfig, n as apiGet, p as normalizeServerUrl, t as apiFetch, v as storedAudience, y as writeStoredConfig } from "./http-
|
|
1
|
+
import { C as HngrError, d as configFilePath, f as deleteStoredConfig, g as resolveConnection, h as redactUrl, m as readStoredConfig, n as apiGet, p as normalizeServerUrl, t as apiFetch, v as storedAudience, y as writeStoredConfig } from "./http-CLG2jrIE.mjs";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
@@ -60,8 +60,8 @@ function canPrompt(ctx) {
|
|
|
60
60
|
return ctx.io.stdin.isTTY && ctx.io.stdout.isTTY && ctx.io.prompts !== void 0;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* Enforces https everywhere; http only for localhost, 127.0.0.1, [::1],
|
|
64
|
-
*
|
|
63
|
+
* Enforces https everywhere; http only for localhost, 127.0.0.1, [::1], or
|
|
64
|
+
* with an explicit --insecure.
|
|
65
65
|
*/
|
|
66
66
|
function assertServerUrlAllowed(raw, insecure) {
|
|
67
67
|
const server = normalizeServerUrl(raw);
|
|
@@ -72,7 +72,7 @@ function assertServerUrlAllowed(raw, insecure) {
|
|
|
72
72
|
"localhost",
|
|
73
73
|
"127.0.0.1",
|
|
74
74
|
"[::1]"
|
|
75
|
-
].includes(url.hostname) || insecure
|
|
75
|
+
].includes(url.hostname) || insecure) return server;
|
|
76
76
|
throw new HngrError({
|
|
77
77
|
type: "config",
|
|
78
78
|
title: `Refusing insecure server URL ${redactUrl(server)}`,
|
|
@@ -114,7 +114,7 @@ async function revokePreviousToken(ctx, stored) {
|
|
|
114
114
|
async function login(ctx, argv) {
|
|
115
115
|
if (ctx.env.HANGAR_TOKEN) throw envTokenRefusal();
|
|
116
116
|
const stored = await readStoredConfig(ctx.env);
|
|
117
|
-
const server = assertServerUrlAllowed(ctx.flags.server ?? ctx.env.HANGAR_URL ?? stored?.server ?? "
|
|
117
|
+
const server = assertServerUrlAllowed(ctx.flags.server ?? ctx.env.HANGAR_URL ?? stored?.server ?? "https://hangar.paykka.com", ctx.flags.insecure);
|
|
118
118
|
if (stored?.token && !ctx.flags.force) try {
|
|
119
119
|
const me = asStoredPrincipal(await apiGet({
|
|
120
120
|
server,
|
|
@@ -1242,7 +1242,7 @@ const COMMANDS = deriveCommands({
|
|
|
1242
1242
|
"get": {
|
|
1243
1243
|
"operationId": "listCapabilities",
|
|
1244
1244
|
"summary": "List the Capabilities the caller can read, newest first.",
|
|
1245
|
-
"description": "Reads follow the rules of `lookupCapability`. Filters combine with\nAND. The list is ordered by `created_at` then `id`, both descending,\nand paged by cursor
|
|
1245
|
+
"description": "Reads follow the rules of `lookupCapability`. Filters combine with\nAND. The list is ordered by `created_at` then `id`, both descending,\nand paged by cursor. `total` counts every Capability matching the\nfilters, independent of the cursor.\n",
|
|
1246
1246
|
"security": [
|
|
1247
1247
|
{},
|
|
1248
1248
|
{ "sessionCookie": [] },
|
|
@@ -3660,13 +3660,23 @@ const COMMANDS = deriveCommands({
|
|
|
3660
3660
|
},
|
|
3661
3661
|
"CapabilityList": {
|
|
3662
3662
|
"type": "object",
|
|
3663
|
-
"required": [
|
|
3663
|
+
"required": [
|
|
3664
|
+
"items",
|
|
3665
|
+
"next_cursor",
|
|
3666
|
+
"total"
|
|
3667
|
+
],
|
|
3664
3668
|
"properties": {
|
|
3665
3669
|
"items": {
|
|
3666
3670
|
"type": "array",
|
|
3667
3671
|
"items": { "$ref": "#/components/schemas/Capability" }
|
|
3668
3672
|
},
|
|
3669
|
-
"next_cursor": { "$ref": "#/components/schemas/NextCursor" }
|
|
3673
|
+
"next_cursor": { "$ref": "#/components/schemas/NextCursor" },
|
|
3674
|
+
"total": {
|
|
3675
|
+
"type": "integer",
|
|
3676
|
+
"format": "int64",
|
|
3677
|
+
"minimum": 0,
|
|
3678
|
+
"description": "The number of Capabilities matching the filters, independent of the cursor."
|
|
3679
|
+
}
|
|
3670
3680
|
}
|
|
3671
3681
|
},
|
|
3672
3682
|
"VersionSummary": {
|
|
@@ -4329,7 +4339,7 @@ function workflowCatalog() {
|
|
|
4329
4339
|
name: "path",
|
|
4330
4340
|
required: true,
|
|
4331
4341
|
variadic: true,
|
|
4332
|
-
description: "<name
|
|
4342
|
+
description: "<name>, <namespace>/<name>, or <namespace>/ and <namespace>/* to choose from a namespace; optionally @<version>"
|
|
4333
4343
|
}],
|
|
4334
4344
|
flags: [
|
|
4335
4345
|
{
|
|
@@ -4343,6 +4353,11 @@ function workflowCatalog() {
|
|
|
4343
4353
|
type: "boolean",
|
|
4344
4354
|
description: "Install to all known agents"
|
|
4345
4355
|
},
|
|
4356
|
+
{
|
|
4357
|
+
name: "all-skills",
|
|
4358
|
+
type: "boolean",
|
|
4359
|
+
description: "Install every skill in a namespace argument instead of prompting"
|
|
4360
|
+
},
|
|
4346
4361
|
{
|
|
4347
4362
|
name: "global",
|
|
4348
4363
|
type: "boolean",
|
|
@@ -171,7 +171,7 @@ async function readStoredConfig(env) {
|
|
|
171
171
|
* The instance the CLI talks to when `--server`, `HANGAR_URL` and the config
|
|
172
172
|
* file are all absent. Point a deployment elsewhere with any of those.
|
|
173
173
|
*/
|
|
174
|
-
const DEFAULT_SERVER_URL = "
|
|
174
|
+
const DEFAULT_SERVER_URL = "https://hangar.paykka.com";
|
|
175
175
|
const INVALID_SERVER_DETAIL = "Pass --server <url>, set HANGAR_URL, or run `hngr auth login` to store one in the config file.";
|
|
176
176
|
/** Parses and normalizes a server URL (no trailing slash); invalid input is a config error. */
|
|
177
177
|
function normalizeServerUrl(server) {
|
|
@@ -225,7 +225,7 @@ async function deleteStoredConfig(env) {
|
|
|
225
225
|
*/
|
|
226
226
|
async function resolveConnection(flags, env) {
|
|
227
227
|
const config = await readStoredConfig(env);
|
|
228
|
-
const server = flags.server ?? env.HANGAR_URL ?? config?.server ?? "
|
|
228
|
+
const server = flags.server ?? env.HANGAR_URL ?? config?.server ?? "https://hangar.paykka.com";
|
|
229
229
|
const token = env.HANGAR_TOKEN ?? config?.token;
|
|
230
230
|
return {
|
|
231
231
|
server: normalizeServerUrl(server),
|
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 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-
|
|
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-CmtREMFr.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-CLG2jrIE.mjs";
|
|
3
|
+
import { t as collectPages } from "./pagination-DtoXmj2F.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.9.0";
|
|
5283
5283
|
// src/generated/completion.ts
|
|
5284
5284
|
function isCompletionShell(value) {
|
|
5285
5285
|
return value === "bash" || value === "zsh" || value === "fish";
|
|
@@ -6429,6 +6429,86 @@ var y = class {
|
|
|
6429
6429
|
}
|
|
6430
6430
|
}
|
|
6431
6431
|
};
|
|
6432
|
+
function p$1(l, e) {
|
|
6433
|
+
if (l === void 0 || e.length === 0) return 0;
|
|
6434
|
+
const i = e.findIndex((s) => s.value === l);
|
|
6435
|
+
return i !== -1 ? i : 0;
|
|
6436
|
+
}
|
|
6437
|
+
function m$1(l, e) {
|
|
6438
|
+
return (e.label ?? String(e.value)).toLowerCase().includes(l.toLowerCase());
|
|
6439
|
+
}
|
|
6440
|
+
function g(l, e) {
|
|
6441
|
+
if (e) return l ? e : e[0];
|
|
6442
|
+
}
|
|
6443
|
+
let T$1 = class T extends y {
|
|
6444
|
+
filteredOptions;
|
|
6445
|
+
multiple;
|
|
6446
|
+
isNavigating = false;
|
|
6447
|
+
selectedValues = [];
|
|
6448
|
+
focusedValue;
|
|
6449
|
+
#e = 0;
|
|
6450
|
+
#s = "";
|
|
6451
|
+
#t;
|
|
6452
|
+
#i;
|
|
6453
|
+
#n;
|
|
6454
|
+
#l;
|
|
6455
|
+
get cursor() {
|
|
6456
|
+
return this.#e;
|
|
6457
|
+
}
|
|
6458
|
+
get userInputWithCursor() {
|
|
6459
|
+
if (!this.userInput) return styleText(["inverse", "hidden"], "_");
|
|
6460
|
+
if (this._cursor >= this.userInput.length) return `${this.userInput}\u2588`;
|
|
6461
|
+
const e = this.userInput.slice(0, this.cursor), t = this.userInput.slice(this.cursor, this.cursor + 1), i = this.userInput.slice(this.cursor + 1);
|
|
6462
|
+
return `${e}${styleText("inverse", t)}${i}`;
|
|
6463
|
+
}
|
|
6464
|
+
get options() {
|
|
6465
|
+
return typeof this.#i == "function" ? this.#i() : this.#i;
|
|
6466
|
+
}
|
|
6467
|
+
constructor(e) {
|
|
6468
|
+
super(e), this.#i = e.options, this.#n = e.placeholder, this.#l = e.completeOnTab === true;
|
|
6469
|
+
const t = this.options;
|
|
6470
|
+
this.filteredOptions = [...t], this.multiple = e.multiple === true, this.#t = typeof e.options == "function" ? e.filter : e.filter ?? m$1;
|
|
6471
|
+
let i;
|
|
6472
|
+
if (e.initialValue && Array.isArray(e.initialValue) ? this.multiple ? i = e.initialValue : i = e.initialValue.slice(0, 1) : !this.multiple && this.options.length > 0 && (i = [this.options[0]?.value]), i) for (const s of i) {
|
|
6473
|
+
const n = t.findIndex((r) => r.value === s);
|
|
6474
|
+
n !== -1 && (this.toggleSelected(s), this.#e = n);
|
|
6475
|
+
}
|
|
6476
|
+
this.focusedValue = this.options[this.#e]?.value, this.on("key", (s, n) => this.#u(s, n)), this.on("userInput", (s) => this.#o(s));
|
|
6477
|
+
}
|
|
6478
|
+
_isActionKey(e, t) {
|
|
6479
|
+
return e === " " || this.multiple && this.isNavigating && t.name === "space" && e !== void 0 && e !== "";
|
|
6480
|
+
}
|
|
6481
|
+
#u(e, t) {
|
|
6482
|
+
const i = t.name === "up", s = t.name === "down", n = t.name === "return", r = this.userInput === "" || this.userInput === " ", u = this.#n, d = this.options, c = u !== void 0 && u !== "" && d.some((o) => !o.disabled && (this.#t ? this.#t(u, o) : true));
|
|
6483
|
+
if (t.name === "tab" && r && c) {
|
|
6484
|
+
this.userInput === " " && this._clearUserInput(), this._setUserInput(u, true), this.isNavigating = false;
|
|
6485
|
+
return;
|
|
6486
|
+
}
|
|
6487
|
+
if (t.name === "tab" && this.#l && !this.multiple && this.focusedValue !== void 0) {
|
|
6488
|
+
const o = String(this.focusedValue);
|
|
6489
|
+
this._clearUserInput(), this._setUserInput(o, true), this.isNavigating = false;
|
|
6490
|
+
return;
|
|
6491
|
+
}
|
|
6492
|
+
i || s ? (this.#e = findCursor(this.#e, i ? -1 : 1, this.filteredOptions), this.focusedValue = this.filteredOptions[this.#e]?.value, this.multiple || (this.selectedValues = [this.focusedValue]), this.isNavigating = true) : n ? this.value = g(this.multiple, this.selectedValues) : this.multiple ? this.focusedValue !== void 0 && (t.name === "tab" || this.isNavigating && t.name === "space") ? this.toggleSelected(this.focusedValue) : this.isNavigating = false : (this.focusedValue && (this.selectedValues = [this.focusedValue]), this.isNavigating = false);
|
|
6493
|
+
}
|
|
6494
|
+
deselectAll() {
|
|
6495
|
+
this.selectedValues = [];
|
|
6496
|
+
}
|
|
6497
|
+
toggleSelected(e) {
|
|
6498
|
+
this.filteredOptions.length !== 0 && (this.multiple ? this.selectedValues.includes(e) ? this.selectedValues = this.selectedValues.filter((t) => t !== e) : this.selectedValues = [...this.selectedValues, e] : this.selectedValues = [e]);
|
|
6499
|
+
}
|
|
6500
|
+
#o(e) {
|
|
6501
|
+
if (e !== this.#s) {
|
|
6502
|
+
this.#s = e;
|
|
6503
|
+
const t = this.options;
|
|
6504
|
+
e && this.#t ? this.filteredOptions = t.filter((n) => this.#t?.(e, n)) : this.filteredOptions = [...t];
|
|
6505
|
+
const i = p$1(this.focusedValue, this.filteredOptions);
|
|
6506
|
+
this.#e = findCursor(i, 0, this.filteredOptions);
|
|
6507
|
+
const s = this.filteredOptions[this.#e];
|
|
6508
|
+
s && !s.disabled ? this.focusedValue = s.value : this.focusedValue = void 0, this.multiple || (this.focusedValue !== void 0 ? this.toggleSelected(this.focusedValue) : this.deselectAll());
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
};
|
|
6432
6512
|
var r = class extends y {
|
|
6433
6513
|
get cursor() {
|
|
6434
6514
|
return this.value ? 0 : 1;
|
|
@@ -6650,6 +6730,66 @@ const limitOptions = ({ cursor: l, options: e, style: w, output: p = process.std
|
|
|
6650
6730
|
for (const t of s) for (const n of t) x.push(n);
|
|
6651
6731
|
return c && x.push(M), x;
|
|
6652
6732
|
};
|
|
6733
|
+
function P(t, c) {
|
|
6734
|
+
if (!t) return true;
|
|
6735
|
+
const n = (c.label ?? String(c.value ?? "")).toLowerCase(), i = (c.hint ?? "").toLowerCase(), r = String(c.value).toLowerCase(), o = t.toLowerCase();
|
|
6736
|
+
return n.includes(o) || i.includes(o) || r.includes(o);
|
|
6737
|
+
}
|
|
6738
|
+
const autocompleteMultiselect = (t) => {
|
|
6739
|
+
const c = (i, r, o, m) => {
|
|
6740
|
+
const p = o.includes(i.value), $ = i.label ?? String(i.value ?? ""), l = i.hint && m !== void 0 && i.value === m ? styleText("dim", ` (${i.hint})`) : "", s = p ? styleText("green", S_CHECKBOX_SELECTED) : styleText("dim", S_CHECKBOX_INACTIVE);
|
|
6741
|
+
return i.disabled ? `${styleText("gray", S_CHECKBOX_INACTIVE)} ${styleText(["strikethrough", "gray"], $)}` : r ? `${s} ${$}${l}` : `${s} ${styleText("dim", $)}`;
|
|
6742
|
+
}, n = new T$1({
|
|
6743
|
+
options: t.options,
|
|
6744
|
+
multiple: true,
|
|
6745
|
+
placeholder: t.placeholder,
|
|
6746
|
+
filter: t.filter ?? ((i, r) => P(i, r)),
|
|
6747
|
+
validate: () => {
|
|
6748
|
+
if (t.required && n.selectedValues.length === 0) return "Please select at least one item";
|
|
6749
|
+
},
|
|
6750
|
+
initialValue: t.initialValues,
|
|
6751
|
+
signal: t.signal,
|
|
6752
|
+
input: t.input,
|
|
6753
|
+
output: t.output,
|
|
6754
|
+
render() {
|
|
6755
|
+
const i = t.withGuide ?? settings.withGuide, r = `${i ? `${styleText("gray", S_BAR)}
|
|
6756
|
+
` : ""}${symbol(this.state)} ${t.message}
|
|
6757
|
+
`, o = this.userInput, m = t.placeholder, p = o === "" && m !== void 0, $ = this.isNavigating || p ? styleText("dim", p ? m : o) : this.userInputWithCursor, l = this.options, s = this.filteredOptions.length !== l.length ? styleText("dim", ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? "" : "es"})`) : "";
|
|
6758
|
+
switch (this.state) {
|
|
6759
|
+
case "submit": return `${r}${i ? `${styleText("gray", S_BAR)} ` : ""}${styleText("dim", `${this.selectedValues.length} items selected`)}`;
|
|
6760
|
+
case "cancel": return `${r}${i ? `${styleText("gray", S_BAR)} ` : ""}${styleText(["strikethrough", "dim"], o)}`;
|
|
6761
|
+
default: {
|
|
6762
|
+
const a = this.state === "error" ? "yellow" : "cyan", u = i ? `${styleText(a, S_BAR)} ` : "", V = i ? styleText(a, S_BAR_END) : "", b = [
|
|
6763
|
+
`${styleText("dim", "↑/↓")} to navigate`,
|
|
6764
|
+
`${styleText("dim", this.isNavigating ? "Space/Tab:" : "Tab:")} select`,
|
|
6765
|
+
`${styleText("dim", "Enter:")} confirm`,
|
|
6766
|
+
`${styleText("dim", "Type:")} to search`
|
|
6767
|
+
], y = this.filteredOptions.length === 0 && o ? [`${u}${styleText("yellow", "No matches found")}`] : [], S = this.state === "error" ? [`${u}${styleText("yellow", this.error)}`] : [], g = [
|
|
6768
|
+
...`${r}${i ? styleText(a, S_BAR) : ""}`.split(`
|
|
6769
|
+
`),
|
|
6770
|
+
`${u}${styleText("dim", "Search:")} ${$}${s}`,
|
|
6771
|
+
...y,
|
|
6772
|
+
...S
|
|
6773
|
+
], O = [`${u}${b.join(" • ")}`, V], d = limitOptions({
|
|
6774
|
+
cursor: this.cursor,
|
|
6775
|
+
options: this.filteredOptions,
|
|
6776
|
+
style: (f, L) => c(f, L, this.selectedValues, this.focusedValue),
|
|
6777
|
+
maxItems: t.maxItems,
|
|
6778
|
+
output: t.output,
|
|
6779
|
+
rowPadding: g.length + O.length
|
|
6780
|
+
});
|
|
6781
|
+
return [
|
|
6782
|
+
...g,
|
|
6783
|
+
...d.map((f) => `${u}${f}`),
|
|
6784
|
+
...O
|
|
6785
|
+
].join(`
|
|
6786
|
+
`);
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6789
|
+
}
|
|
6790
|
+
});
|
|
6791
|
+
return n.prompt();
|
|
6792
|
+
};
|
|
6653
6793
|
const confirm = (e) => {
|
|
6654
6794
|
const a = e.active ?? "Yes", o = e.inactive ?? "No";
|
|
6655
6795
|
return new r({
|
|
@@ -6970,6 +7110,19 @@ function clackPrompter(streams = {}) {
|
|
|
6970
7110
|
required: false
|
|
6971
7111
|
}));
|
|
6972
7112
|
},
|
|
7113
|
+
async searchableMultiselect(message, options, initialValues = [], hint) {
|
|
7114
|
+
if (hint) output.write(`${import_picocolors.default.dim(hint)}\n`);
|
|
7115
|
+
return unwrapPrompt(await autocompleteMultiselect({
|
|
7116
|
+
message,
|
|
7117
|
+
input,
|
|
7118
|
+
output,
|
|
7119
|
+
options,
|
|
7120
|
+
initialValues,
|
|
7121
|
+
required: false,
|
|
7122
|
+
placeholder: "Type to search",
|
|
7123
|
+
maxItems: 12
|
|
7124
|
+
}));
|
|
7125
|
+
},
|
|
6973
7126
|
note(message, title) {
|
|
6974
7127
|
output.write(`${import_picocolors.default.green("◇")} ${title}\n`);
|
|
6975
7128
|
output.write(`${import_picocolors.default.dim("│")}\n`);
|
|
@@ -7075,7 +7228,7 @@ function buildParser(onMatched) {
|
|
|
7075
7228
|
parser.command("commands", "Print the command catalog", () => {}, (argv) => {
|
|
7076
7229
|
onMatched({
|
|
7077
7230
|
argv,
|
|
7078
|
-
run: async (ctx) => (await import("./commands-
|
|
7231
|
+
run: async (ctx) => (await import("./commands-Bjui8-Nt.mjs")).run(ctx)
|
|
7079
7232
|
});
|
|
7080
7233
|
});
|
|
7081
7234
|
parser.command("auth", "Manage credentials", (auth) => {
|
|
@@ -7088,13 +7241,13 @@ function buildParser(onMatched) {
|
|
|
7088
7241
|
}).conflicts("password", "token"), (argv) => {
|
|
7089
7242
|
onMatched({
|
|
7090
7243
|
argv,
|
|
7091
|
-
run: async (ctx, matchedArgv) => (await import("./auth-
|
|
7244
|
+
run: async (ctx, matchedArgv) => (await import("./auth-CQkOtVxv.mjs")).login(ctx, matchedArgv)
|
|
7092
7245
|
});
|
|
7093
7246
|
});
|
|
7094
7247
|
auth.command("logout", "Revoke the stored token and delete the local config", () => {}, (argv) => {
|
|
7095
7248
|
onMatched({
|
|
7096
7249
|
argv,
|
|
7097
|
-
run: async (ctx) => (await import("./auth-
|
|
7250
|
+
run: async (ctx) => (await import("./auth-CQkOtVxv.mjs")).logout(ctx)
|
|
7098
7251
|
});
|
|
7099
7252
|
});
|
|
7100
7253
|
auth.command("status", "Show credential source, server, principal, token prefix and audience", (status) => status.option("offline", {
|
|
@@ -7104,7 +7257,7 @@ function buildParser(onMatched) {
|
|
|
7104
7257
|
}), (argv) => {
|
|
7105
7258
|
onMatched({
|
|
7106
7259
|
argv,
|
|
7107
|
-
run: async (ctx, matchedArgv) => (await import("./auth-
|
|
7260
|
+
run: async (ctx, matchedArgv) => (await import("./auth-CQkOtVxv.mjs")).status(ctx, matchedArgv)
|
|
7108
7261
|
});
|
|
7109
7262
|
});
|
|
7110
7263
|
auth.command("token", "Print the access token to stdout and nothing else", (token) => token.option("audience", {
|
|
@@ -7115,13 +7268,13 @@ function buildParser(onMatched) {
|
|
|
7115
7268
|
}), (argv) => {
|
|
7116
7269
|
onMatched({
|
|
7117
7270
|
argv,
|
|
7118
|
-
run: async (ctx, matchedArgv) => (await import("./auth-
|
|
7271
|
+
run: async (ctx, matchedArgv) => (await import("./auth-CQkOtVxv.mjs")).token(ctx, matchedArgv)
|
|
7119
7272
|
});
|
|
7120
7273
|
});
|
|
7121
7274
|
auth.command("whoami", "Print the GET /me payload", () => {}, (argv) => {
|
|
7122
7275
|
onMatched({
|
|
7123
7276
|
argv,
|
|
7124
|
-
run: async (ctx) => (await import("./auth-
|
|
7277
|
+
run: async (ctx) => (await import("./auth-CQkOtVxv.mjs")).whoami(ctx)
|
|
7125
7278
|
});
|
|
7126
7279
|
});
|
|
7127
7280
|
registerGeneratedAuth(auth, onMatched);
|
|
@@ -7138,6 +7291,10 @@ function buildParser(onMatched) {
|
|
|
7138
7291
|
type: "boolean",
|
|
7139
7292
|
default: false,
|
|
7140
7293
|
describe: "Install to every known agent target"
|
|
7294
|
+
}).option("all-skills", {
|
|
7295
|
+
type: "boolean",
|
|
7296
|
+
default: false,
|
|
7297
|
+
describe: "Install every skill in a namespace argument instead of prompting"
|
|
7141
7298
|
}).option("global", {
|
|
7142
7299
|
type: "boolean",
|
|
7143
7300
|
default: false,
|
|
@@ -7155,7 +7312,7 @@ function buildParser(onMatched) {
|
|
|
7155
7312
|
}), (argv) => {
|
|
7156
7313
|
onMatched({
|
|
7157
7314
|
argv,
|
|
7158
|
-
run: async (ctx, matchedArgv) => (await import("./skills-
|
|
7315
|
+
run: async (ctx, matchedArgv) => (await import("./skills-CqHGxo_M.mjs")).add(ctx, matchedArgv)
|
|
7159
7316
|
});
|
|
7160
7317
|
});
|
|
7161
7318
|
skills.command("publish <paths..>", "Publish a local skill directory as a capability or a new version", (publish) => publish.option("namespace", {
|
|
@@ -7183,7 +7340,7 @@ function buildParser(onMatched) {
|
|
|
7183
7340
|
}), (argv) => {
|
|
7184
7341
|
onMatched({
|
|
7185
7342
|
argv,
|
|
7186
|
-
run: async (ctx, matchedArgv) => (await import("./skills-
|
|
7343
|
+
run: async (ctx, matchedArgv) => (await import("./skills-CqHGxo_M.mjs")).publish(ctx, matchedArgv)
|
|
7187
7344
|
});
|
|
7188
7345
|
});
|
|
7189
7346
|
return skills.demandCommand(1);
|
|
@@ -7205,7 +7362,7 @@ function buildParser(onMatched) {
|
|
|
7205
7362
|
}), (argv) => {
|
|
7206
7363
|
onMatched({
|
|
7207
7364
|
argv,
|
|
7208
|
-
run: async (ctx, matchedArgv) => (await import("./mcp-
|
|
7365
|
+
run: async (ctx, matchedArgv) => (await import("./mcp-C-oJ6J5B.mjs")).add(ctx, matchedArgv)
|
|
7209
7366
|
});
|
|
7210
7367
|
});
|
|
7211
7368
|
mcp.command("publish <paths..>", "Publish an MCP snapshot as a capability or a new version", (publish) => publish.option("namespace", {
|
|
@@ -7233,7 +7390,7 @@ function buildParser(onMatched) {
|
|
|
7233
7390
|
}), (argv) => {
|
|
7234
7391
|
onMatched({
|
|
7235
7392
|
argv,
|
|
7236
|
-
run: async (ctx, matchedArgv) => (await import("./mcp-
|
|
7393
|
+
run: async (ctx, matchedArgv) => (await import("./mcp-C-oJ6J5B.mjs")).publish(ctx, matchedArgv)
|
|
7237
7394
|
});
|
|
7238
7395
|
});
|
|
7239
7396
|
return mcp.demandCommand(1);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as HngrError, g as resolveConnection, s as redactSensitive, t as apiFetch, x as writeFileAtomically } from "./http-
|
|
2
|
-
import { S as resolveCapabilityVersion, _ as downloadContent, a as errorBody,
|
|
1
|
+
import { C as HngrError, g as resolveConnection, s as redactSensitive, t as apiFetch, x as writeFileAtomically } from "./http-CLG2jrIE.mjs";
|
|
2
|
+
import { S as parseCapabilityPath, T as resolveCapabilityVersion, _ as downloadContent, a as errorBody, c as runBatch, h as findAgentDefinition, i as runPublish, l as stringValues, m as AGENTS, n as explicitName, o as failedResult, p as pathExists, r as readPublishFlags, s as positionalPaths, t as resolveInstallScope, v as MCP_KIND, w as resolveCapability } from "./scope-8BZjgIXy.mjs";
|
|
3
3
|
import { constants } from "node:fs";
|
|
4
4
|
import { access, readFile, stat } from "node:fs/promises";
|
|
5
5
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as HngrError } from "./http-
|
|
1
|
+
import { C as HngrError } from "./http-CLG2jrIE.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 { C as HngrError, _ as scrubCredentials, a as toHngrError, b as writeDirectoryAtomically, g as resolveConnection, h as redactUrl, i as buildRequestUrl, l as apiErrorFromResponse, n as apiGet } from "./http-
|
|
2
|
-
import { t as collectPages } from "./pagination-
|
|
1
|
+
import { C as HngrError, _ as scrubCredentials, a as toHngrError, b as writeDirectoryAtomically, g as resolveConnection, h as redactUrl, i as buildRequestUrl, l as apiErrorFromResponse, n as apiGet } from "./http-CLG2jrIE.mjs";
|
|
2
|
+
import { t as collectPages } from "./pagination-DtoXmj2F.mjs";
|
|
3
3
|
import { lstat, mkdir, readFile, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
@@ -76,6 +76,37 @@ function parseCapabilityPath(raw, kind) {
|
|
|
76
76
|
version
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Parses a position argument into a concrete path, a bare name to probe, or an
|
|
81
|
+
* explicit Namespace. `<namespace>/` and `<namespace>/*` always mean the
|
|
82
|
+
* Namespace; a bare name is resolved by the caller because today it means
|
|
83
|
+
* `system/<name>` while a miss may be a Namespace.
|
|
84
|
+
*/
|
|
85
|
+
function parseCapabilityTarget(raw, kind) {
|
|
86
|
+
const value = raw.trim();
|
|
87
|
+
if (value === "") throw usage$1(`A ${kind.noun} path is required`, "Pass `<name>`, `<namespace>/<name>`, `<namespace>/` or `<namespace>/*`.");
|
|
88
|
+
const at = value.lastIndexOf("@");
|
|
89
|
+
const base = at === -1 ? value : value.slice(0, at);
|
|
90
|
+
if (base.endsWith("/") || base.endsWith("/*")) {
|
|
91
|
+
if (at !== -1) throw usage$1(`Invalid ${kind.noun} path ${JSON.stringify(raw)}`, "A version cannot be pinned on a namespace; name each one as `<namespace>/<name>@<version>`.");
|
|
92
|
+
const namespace = base.endsWith("/*") ? base.slice(0, -2) : base.slice(0, -1);
|
|
93
|
+
if (namespace === "" || namespace.includes("/")) throw usage$1(`Invalid ${kind.noun} path ${JSON.stringify(raw)}`, "Use `<namespace>/` or `<namespace>/*`.");
|
|
94
|
+
checkNamePart(namespace, raw, kind.noun);
|
|
95
|
+
return {
|
|
96
|
+
kind: "namespace",
|
|
97
|
+
namespace
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const parsed = parseCapabilityPath(value, kind);
|
|
101
|
+
if (!value.includes("/") && parsed.version === void 0) return {
|
|
102
|
+
kind: "probe",
|
|
103
|
+
parsed
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
kind: "capability",
|
|
107
|
+
parsed
|
|
108
|
+
};
|
|
109
|
+
}
|
|
79
110
|
function candidateLabel(capability) {
|
|
80
111
|
return `${capability.id} (${capability.namespace}/${capability.name}, ${capability.kind})`;
|
|
81
112
|
}
|
|
@@ -99,6 +130,19 @@ async function findCapability(connection, parsed, kind, signal) {
|
|
|
99
130
|
});
|
|
100
131
|
return matches[0] ?? null;
|
|
101
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Lists every readable Capability of one kind in one Namespace, newest-first
|
|
135
|
+
* pages included, then sorted by name so a picker is stable across requests.
|
|
136
|
+
* The server has no total; an empty array means nothing is readable there.
|
|
137
|
+
*/
|
|
138
|
+
async function listCapabilityNamespace(connection, namespace, kind, signal) {
|
|
139
|
+
return (await collectPages(CAPABILITIES_PATH, (cursor) => apiGet(connection, CAPABILITIES_PATH, {
|
|
140
|
+
kind: kind.kind,
|
|
141
|
+
namespace,
|
|
142
|
+
limit: 100,
|
|
143
|
+
cursor
|
|
144
|
+
}, { signal }))).filter((item) => item !== null && typeof item === "object" && item.kind === kind.kind && item.namespace === namespace && typeof item.name === "string").sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
145
|
+
}
|
|
102
146
|
/** Resolves a path to exactly one Capability, or reports that it does not exist. */
|
|
103
147
|
async function resolveCapability(connection, parsed, kind, signal) {
|
|
104
148
|
const capability = await findCapability(connection, parsed, kind, signal);
|
|
@@ -637,4 +681,4 @@ async function resolveInstallScope(ctx, global) {
|
|
|
637
681
|
if (!select || ctx.flags.json || !ctx.io.stdin.isTTY || !ctx.io.stdout.isTTY) return "project";
|
|
638
682
|
return await select("Install scope", SCOPE_OPTIONS, "project") === "global" ? "global" : "project";
|
|
639
683
|
}
|
|
640
|
-
export {
|
|
684
|
+
export { parseCapabilityTarget as C, parseCapabilityPath as S, resolveCapabilityVersion as T, downloadContent as _, errorBody as a, findCapability as b, runBatch as c, installAtomically as d, linkAtomically as f, homeDir as g, findAgentDefinition as h, runPublish as i, stringValues as l, AGENTS as m, explicitName as n, failedResult as o, pathExists as p, readPublishFlags as r, positionalPaths as s, resolveInstallScope as t, copyAtomically as u, MCP_KIND as v, resolveCapability as w, listCapabilityNamespace as x, SKILL_KIND as y };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as HngrError, g as resolveConnection, r as apiRequest } from "./http-
|
|
2
|
-
import { S as resolveCapabilityVersion, _ as downloadContent, a as errorBody, b as
|
|
1
|
+
import { C as HngrError, g as resolveConnection, r as apiRequest } from "./http-CLG2jrIE.mjs";
|
|
2
|
+
import { C as parseCapabilityTarget, S as parseCapabilityPath, T as resolveCapabilityVersion, _ as downloadContent, a as errorBody, b as findCapability, c as runBatch, d as installAtomically, f as linkAtomically, g as homeDir, i as runPublish, l as stringValues, m as AGENTS, n as explicitName, o as failedResult, p as pathExists, r as readPublishFlags, s as positionalPaths, t as resolveInstallScope, u as copyAtomically, w as resolveCapability, x as listCapabilityNamespace, y as SKILL_KIND } from "./scope-8BZjgIXy.mjs";
|
|
3
3
|
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
@@ -466,6 +466,19 @@ async function packageSkillDirectory(dir) {
|
|
|
466
466
|
byteCount: totals.bytes
|
|
467
467
|
};
|
|
468
468
|
}
|
|
469
|
+
// src/terminal-text.ts
|
|
470
|
+
/**
|
|
471
|
+
* Catalog text is publisher-controlled. Keep a single line safe to draw in a
|
|
472
|
+
* terminal: drop ANSI escape sequences and remaining control characters,
|
|
473
|
+
* collapse whitespace, and cap the length. Skill names are already validated,
|
|
474
|
+
* but descriptions are not.
|
|
475
|
+
*/
|
|
476
|
+
const ESCAPE_SEQUENCE = /\u001b(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001b\\)|[P^_][\s\S]*?\u001b\\|[@-_])/g;
|
|
477
|
+
const CONTROL_CHARACTER = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g;
|
|
478
|
+
function terminalText(value, maxLength = 80) {
|
|
479
|
+
const cleaned = value.replace(ESCAPE_SEQUENCE, "").replace(CONTROL_CHARACTER, " ").replace(/\s+/g, " ").trim();
|
|
480
|
+
return Array.from(cleaned).slice(0, maxLength).join("");
|
|
481
|
+
}
|
|
469
482
|
// src/commands/skills.ts
|
|
470
483
|
function usage(title, detail) {
|
|
471
484
|
return new HngrError({
|
|
@@ -498,12 +511,11 @@ function noAgentSelectionError(examplePath, global) {
|
|
|
498
511
|
}
|
|
499
512
|
const ALL_AGENTS = "__all__";
|
|
500
513
|
/**
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
* filesystem and must pass --agent or --all.
|
|
514
|
+
* The agents named by flags, or null when a terminal must choose. Resolving
|
|
515
|
+
* this without prompting lets a non-TTY invocation fail before any network
|
|
516
|
+
* call, even when the arguments also include a namespace to expand.
|
|
505
517
|
*/
|
|
506
|
-
|
|
518
|
+
function requestedAgents(argv) {
|
|
507
519
|
const requested = stringValues(argv.agent);
|
|
508
520
|
if (requested.length > 0) {
|
|
509
521
|
const unknown = requested.filter((id) => findAgent(id) === void 0);
|
|
@@ -511,6 +523,16 @@ async function selectAgents(ctx, argv, examplePath, global) {
|
|
|
511
523
|
return AGENT_TARGETS.filter((agent) => requested.includes(agent.id));
|
|
512
524
|
}
|
|
513
525
|
if (argv.all === true) return [...AGENT_TARGETS];
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* One multi-select for the agents that keep their own skills directory.
|
|
530
|
+
* `.agents` is always installed, so universal agents never appear here.
|
|
531
|
+
* Detection only preselects; a non-TTY invocation never consults the
|
|
532
|
+
* filesystem and must pass --agent or --all.
|
|
533
|
+
*/
|
|
534
|
+
async function selectAgents(ctx, requested, examplePath, global) {
|
|
535
|
+
if (requested !== null) return requested;
|
|
514
536
|
const multiselect = ctx.io.prompts?.multiselect;
|
|
515
537
|
if (!canPrompt(ctx) || !multiselect) throw noAgentSelectionError(examplePath, global);
|
|
516
538
|
const cwd = process.cwd();
|
|
@@ -546,6 +568,217 @@ function frontmatterName(manifest) {
|
|
|
546
568
|
function assertNotReserved(name) {
|
|
547
569
|
if (isReservedDirName(name)) throw usage(`Reserved directory name: ${name}`, "The names `synced` and `anthropic-skills` are reserved for agent-owned skill stores.");
|
|
548
570
|
}
|
|
571
|
+
function capabilityRef(namespace, name) {
|
|
572
|
+
return `${namespace}/${name}`;
|
|
573
|
+
}
|
|
574
|
+
function skillHint(capability) {
|
|
575
|
+
const description = typeof capability.description === "string" ? terminalText(capability.description) : "";
|
|
576
|
+
if (description !== "") return description;
|
|
577
|
+
return capability.current_version ? `v${capability.current_version}` : "";
|
|
578
|
+
}
|
|
579
|
+
/** A namespace picker needs a TTY and either multi-select implementation. */
|
|
580
|
+
function canSelectSkills(ctx) {
|
|
581
|
+
return !ctx.flags.json && ctx.io.stdin.isTTY && ctx.io.stdout.isTTY && (ctx.io.prompts?.searchableMultiselect !== void 0 || ctx.io.prompts?.multiselect !== void 0);
|
|
582
|
+
}
|
|
583
|
+
async function promptForSkills(ctx, candidates) {
|
|
584
|
+
const namespaces = new Set(candidates.map((capability) => capability.namespace));
|
|
585
|
+
const options = candidates.map((capability) => ({
|
|
586
|
+
value: capability.id,
|
|
587
|
+
label: namespaces.size === 1 ? capability.name : capabilityRef(capability.namespace, capability.name),
|
|
588
|
+
hint: skillHint(capability)
|
|
589
|
+
}));
|
|
590
|
+
const prompt = ctx.io.prompts;
|
|
591
|
+
const message = "Select skills to install";
|
|
592
|
+
const hint = "space to select · enter to install · nothing selected installs nothing";
|
|
593
|
+
let selected = [];
|
|
594
|
+
if (prompt?.searchableMultiselect) selected = await prompt.searchableMultiselect(message, options, [], hint);
|
|
595
|
+
else if (prompt?.multiselect) selected = await prompt.multiselect(message, options, [], hint);
|
|
596
|
+
const byId = new Map(candidates.map((capability) => [capability.id, capability]));
|
|
597
|
+
const chosen = selected.flatMap((id) => {
|
|
598
|
+
const capability = byId.get(id);
|
|
599
|
+
return capability ? [capability] : [];
|
|
600
|
+
});
|
|
601
|
+
if (chosen.length === 0) ctx.summary("No skills selected.");
|
|
602
|
+
return chosen;
|
|
603
|
+
}
|
|
604
|
+
function emptyExpansionError(expansion) {
|
|
605
|
+
if (expansion.origin !== void 0) return new HngrError({
|
|
606
|
+
type: "api",
|
|
607
|
+
title: `No skill named ${expansion.origin.namespace}/${expansion.origin.name}`,
|
|
608
|
+
detail: `There are also no readable skills in the ${JSON.stringify(expansion.namespace)} namespace.`
|
|
609
|
+
});
|
|
610
|
+
return new HngrError({
|
|
611
|
+
type: "api",
|
|
612
|
+
title: `No readable skills in namespace ${expansion.namespace}`,
|
|
613
|
+
detail: "Check the Namespace name, or list the catalog with `hngr capabilities list`."
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
/** Keeps the first occurrence, but an explicitly pinned version outranks a bare one. */
|
|
617
|
+
function dedupeUnits(units) {
|
|
618
|
+
const index = /* @__PURE__ */ new Map();
|
|
619
|
+
const result = [];
|
|
620
|
+
for (const unit of units) {
|
|
621
|
+
const key = capabilityRef(unit.parsed.namespace, unit.parsed.name);
|
|
622
|
+
const existing = index.get(key);
|
|
623
|
+
if (existing === void 0) {
|
|
624
|
+
index.set(key, result.length);
|
|
625
|
+
result.push(unit);
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const current = result[existing];
|
|
629
|
+
if (current === void 0) continue;
|
|
630
|
+
if (unit.kind === "skill" && unit.parsed.version !== void 0 && current.kind === "skill" && current.parsed.version === void 0) result[existing] = unit;
|
|
631
|
+
}
|
|
632
|
+
return result;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Resolves position arguments into install units. Explicit paths stay in input
|
|
636
|
+
* order; each namespace's selection is inserted at its first position. A bare
|
|
637
|
+
* name probes `system/<name>` first, so today's meaning always wins.
|
|
638
|
+
*/
|
|
639
|
+
async function resolveAddUnits(ctx, connection, targets, allSkills) {
|
|
640
|
+
const pending = [];
|
|
641
|
+
const expansions = /* @__PURE__ */ new Map();
|
|
642
|
+
const expand = (namespace, origin) => {
|
|
643
|
+
const existing = expansions.get(namespace);
|
|
644
|
+
if (existing === void 0) expansions.set(namespace, {
|
|
645
|
+
namespace,
|
|
646
|
+
origin,
|
|
647
|
+
candidates: []
|
|
648
|
+
});
|
|
649
|
+
else if (existing.origin === void 0) existing.origin = origin;
|
|
650
|
+
pending.push({
|
|
651
|
+
kind: "namespace",
|
|
652
|
+
namespace
|
|
653
|
+
});
|
|
654
|
+
};
|
|
655
|
+
for (const target of targets) {
|
|
656
|
+
if (target.kind === "capability") {
|
|
657
|
+
pending.push({
|
|
658
|
+
kind: "unit",
|
|
659
|
+
unit: {
|
|
660
|
+
kind: "skill",
|
|
661
|
+
parsed: target.parsed
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (target.kind === "probe") {
|
|
667
|
+
try {
|
|
668
|
+
const found = await findCapability(connection, target.parsed, SKILL_KIND);
|
|
669
|
+
if (found !== null) {
|
|
670
|
+
pending.push({
|
|
671
|
+
kind: "unit",
|
|
672
|
+
unit: {
|
|
673
|
+
kind: "skill",
|
|
674
|
+
parsed: target.parsed,
|
|
675
|
+
capability: found
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
} catch (err) {
|
|
681
|
+
if (err instanceof HngrError && err.exitCode !== 1) throw err;
|
|
682
|
+
pending.push({
|
|
683
|
+
kind: "unit",
|
|
684
|
+
unit: {
|
|
685
|
+
kind: "failure",
|
|
686
|
+
parsed: target.parsed,
|
|
687
|
+
error: err
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
expand(target.parsed.name, target.parsed);
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
expand(target.namespace);
|
|
696
|
+
}
|
|
697
|
+
const candidates = [];
|
|
698
|
+
for (const expansion of expansions.values()) {
|
|
699
|
+
try {
|
|
700
|
+
expansion.candidates = await listCapabilityNamespace(connection, expansion.namespace, SKILL_KIND);
|
|
701
|
+
} catch (err) {
|
|
702
|
+
if (err instanceof HngrError && err.exitCode !== 1) throw err;
|
|
703
|
+
expansion.error = err;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (expansion.candidates.length > 0) candidates.push(...expansion.candidates);
|
|
707
|
+
}
|
|
708
|
+
const unique = /* @__PURE__ */ new Map();
|
|
709
|
+
for (const capability of candidates) unique.set(capabilityRef(capability.namespace, capability.name), capability);
|
|
710
|
+
const allCandidates = [...unique.values()].sort((a, b) => {
|
|
711
|
+
const left = capabilityRef(a.namespace, a.name);
|
|
712
|
+
const right = capabilityRef(b.namespace, b.name);
|
|
713
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
714
|
+
});
|
|
715
|
+
let chosen;
|
|
716
|
+
if (allCandidates.length === 0) chosen = [];
|
|
717
|
+
else if (allSkills) chosen = allCandidates;
|
|
718
|
+
else if (!canSelectSkills(ctx)) throw usage("Cannot choose skills from a namespace without a terminal", "Pass --all-skills to install every skill in the namespace, or name each skill as `<namespace>/<name>`.");
|
|
719
|
+
else chosen = await promptForSkills(ctx, allCandidates);
|
|
720
|
+
const chosenRefs = new Set(chosen.map((capability) => capabilityRef(capability.namespace, capability.name)));
|
|
721
|
+
const units = [];
|
|
722
|
+
for (const entry of pending) {
|
|
723
|
+
if (entry.kind === "unit") {
|
|
724
|
+
units.push(entry.unit);
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
const expansion = expansions.get(entry.namespace);
|
|
728
|
+
if (expansion === void 0) continue;
|
|
729
|
+
if (expansion.error !== void 0) {
|
|
730
|
+
units.push({
|
|
731
|
+
kind: "failure",
|
|
732
|
+
parsed: expansion.origin ?? {
|
|
733
|
+
namespace: expansion.namespace,
|
|
734
|
+
name: "*"
|
|
735
|
+
},
|
|
736
|
+
error: expansion.error
|
|
737
|
+
});
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
if (expansion.candidates.length === 0) {
|
|
741
|
+
units.push({
|
|
742
|
+
kind: "failure",
|
|
743
|
+
parsed: expansion.origin ?? {
|
|
744
|
+
namespace: expansion.namespace,
|
|
745
|
+
name: "*"
|
|
746
|
+
},
|
|
747
|
+
error: emptyExpansionError(expansion)
|
|
748
|
+
});
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
for (const capability of expansion.candidates) {
|
|
752
|
+
if (!chosenRefs.has(capabilityRef(capability.namespace, capability.name))) continue;
|
|
753
|
+
units.push({
|
|
754
|
+
kind: "skill",
|
|
755
|
+
parsed: {
|
|
756
|
+
namespace: capability.namespace,
|
|
757
|
+
name: capability.name
|
|
758
|
+
},
|
|
759
|
+
capability
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
units: dedupeUnits(units),
|
|
765
|
+
expanded: expansions.size > 0
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function assertAddPlan(units, as, expanded) {
|
|
769
|
+
if (as !== void 0 && expanded) throw usage("--as cannot be used with a namespace", "Pass one explicit `<namespace>/<name>` instead.");
|
|
770
|
+
const skills = units.filter((unit) => unit.kind === "skill");
|
|
771
|
+
if (as !== void 0 && skills.length !== 1) throw usage("--as requires exactly one skill", `This command resolves to ${skills.length} skills; install them without --as.`);
|
|
772
|
+
const destinations = /* @__PURE__ */ new Map();
|
|
773
|
+
for (const unit of skills) {
|
|
774
|
+
const destName = as ?? unit.parsed.name;
|
|
775
|
+
assertNotReserved(destName);
|
|
776
|
+
const ref = capabilityRef(unit.parsed.namespace, unit.parsed.name);
|
|
777
|
+
const existing = destinations.get(destName);
|
|
778
|
+
if (existing !== void 0 && existing !== ref) throw usage(`Two skills would install as ${JSON.stringify(destName)}`, `${existing} and ${ref} both use that directory name; install them separately.`);
|
|
779
|
+
destinations.set(destName, ref);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
549
782
|
function planInstall(cwd, env, destName, agents, options) {
|
|
550
783
|
if (options.dir) {
|
|
551
784
|
const base = resolve(cwd, options.dir);
|
|
@@ -644,8 +877,8 @@ function installSummary(outcome, canonicalPath) {
|
|
|
644
877
|
if (failed.length > 0) lines.push(` failed: ${failed.map((target) => target.path).join(", ")}`);
|
|
645
878
|
return lines.join("\n");
|
|
646
879
|
}
|
|
647
|
-
async function addOne(ctx, connection, parsed, destName, agents, dir, global, copy, state) {
|
|
648
|
-
const capability = await resolveSkill(connection, parsed);
|
|
880
|
+
async function addOne(ctx, connection, parsed, destName, agents, dir, global, copy, state, resolved) {
|
|
881
|
+
const capability = resolved ?? await resolveSkill(connection, parsed);
|
|
649
882
|
const version = resolveVersion(capability, parsed);
|
|
650
883
|
state.version = version;
|
|
651
884
|
const plan = planInstall(process.cwd(), ctx.env, destName, agents, {
|
|
@@ -830,30 +1063,45 @@ async function offerFindCapabilities(ctx, connection, destNames, agents, options
|
|
|
830
1063
|
}
|
|
831
1064
|
async function add(ctx, argv) {
|
|
832
1065
|
const rawPaths = positionalPaths(argv.paths);
|
|
833
|
-
if (rawPaths.length === 0) throw usage("No skill path given", "Pass one or more `<name
|
|
834
|
-
const
|
|
1066
|
+
if (rawPaths.length === 0) throw usage("No skill path given", "Pass one or more `<name>`, `<namespace>/<name>`, `<namespace>/` or `<namespace>/*` arguments.");
|
|
1067
|
+
const targets = rawPaths.map((raw) => parseCapabilityTarget(raw, SKILL_KIND));
|
|
835
1068
|
const dir = stringFlag(argv, "dir");
|
|
836
1069
|
const as = stringFlag(argv, "as");
|
|
837
1070
|
if (as !== void 0 && (as.includes("/") || as.includes("\\") || as === "." || as === "..")) throw usage(`Invalid --as name ${JSON.stringify(as)}`, "Pass a single directory name.");
|
|
838
1071
|
const requestedGlobal = argv.global === true;
|
|
839
1072
|
const copy = argv.copy === true;
|
|
1073
|
+
const allSkills = argv.allSkills === true;
|
|
840
1074
|
if (dir !== void 0 && requestedGlobal) throw usage("--dir cannot be combined with --global");
|
|
841
1075
|
if (dir !== void 0 && (stringValues(argv.agent).length > 0 || argv.all === true)) throw usage("--dir cannot be combined with --agent or --all");
|
|
842
1076
|
if (as !== void 0) assertNotReserved(as);
|
|
843
|
-
|
|
844
|
-
const
|
|
845
|
-
|
|
1077
|
+
if (as !== void 0 && targets.some((target) => target.kind === "namespace")) throw usage("--as cannot be used with a namespace", "Pass one explicit `<namespace>/<name>` instead.");
|
|
1078
|
+
for (const target of targets) {
|
|
1079
|
+
if (target.kind === "namespace") continue;
|
|
1080
|
+
assertNotReserved(as ?? target.parsed.name);
|
|
1081
|
+
}
|
|
1082
|
+
const requested = dir === void 0 ? requestedAgents(argv) : [];
|
|
1083
|
+
if (dir === void 0 && requested === null && !canPrompt(ctx)) throw noAgentSelectionError(rawPaths[0] ?? "", requestedGlobal);
|
|
846
1084
|
const connection = await resolveConnection(ctx.flags, ctx.env);
|
|
1085
|
+
const { units, expanded } = await resolveAddUnits(ctx, connection, targets, allSkills);
|
|
1086
|
+
if (units.length === 0) return 0;
|
|
1087
|
+
assertAddPlan(units, as, expanded);
|
|
1088
|
+
const installable = units.some((unit) => unit.kind === "skill");
|
|
1089
|
+
const global = dir === void 0 && installable ? await resolveInstallScope(ctx, requestedGlobal) === "global" : false;
|
|
1090
|
+
const agents = dir === void 0 && installable ? await selectAgents(ctx, requested, rawPaths[0] ?? "", global) : [];
|
|
1091
|
+
const unitByRef = new Map(units.map((unit) => [capabilityRef(unit.parsed.namespace, unit.parsed.name), unit]));
|
|
847
1092
|
const { results, failed } = await runBatch({
|
|
848
1093
|
ctx,
|
|
849
|
-
paths:
|
|
1094
|
+
paths: units.map((unit) => unit.parsed),
|
|
850
1095
|
run: async (parsed, state) => {
|
|
851
|
-
|
|
1096
|
+
const unit = unitByRef.get(capabilityRef(parsed.namespace, parsed.name));
|
|
1097
|
+
if (unit?.kind === "failure") throw unit.error;
|
|
1098
|
+
return (await addOne(ctx, connection, parsed, as ?? parsed.name, agents, dir, global, copy, state, unit?.capability)).result;
|
|
852
1099
|
},
|
|
853
1100
|
failure: failureResult
|
|
854
1101
|
});
|
|
855
1102
|
if (ctx.flags.json || !ctx.io.stdout.isTTY) ctx.writePayload({ results });
|
|
856
|
-
|
|
1103
|
+
const destNames = units.filter((unit) => unit.kind === "skill").map((unit) => as ?? unit.parsed.name);
|
|
1104
|
+
if (!ctx.flags.json && !ctx.flags.dryRun && !ctx.flags.quiet && !failed && canConfirm(ctx)) await offerFindCapabilities(ctx, connection, destNames, agents, {
|
|
857
1105
|
dir,
|
|
858
1106
|
global,
|
|
859
1107
|
copy
|