hngr 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/{auth-CgBUQUQn.mjs → auth-ByGgd_Yn.mjs} +49 -11
- package/dist/{catalog-DbGLqQ8a.mjs → catalog-Bv304SXJ.mjs} +412 -20
- package/dist/{commands-BHXSZ04F.mjs → commands-CLqLwQHY.mjs} +1 -1
- package/dist/{http-2HWxLIOq.mjs → http-DgHX7Fux.mjs} +13 -7
- package/dist/index.mjs +136 -21
- package/dist/{mcp-b6bE3ZRt.mjs → mcp-B0sbQABp.mjs} +27 -50
- package/dist/{pagination-BbKLKSw3.mjs → pagination-BXyFxGYA.mjs} +1 -1
- package/dist/{publish-EUqgdp0G.mjs → scope-DVfDeQi2.mjs} +37 -3
- package/dist/{skills-XDbBCC5f.mjs → skills-ZFmFyZtw.mjs} +80 -70
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,9 +47,19 @@ hngr skills add team-a/pdf-tools --agent claude-code
|
|
|
47
47
|
hngr skills add pdf-tools@2026.10.25.1 --agent claude-code
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
The shared `.agents/skills` store is always installed, so every universal agent reads it
|
|
51
|
+
without extra work. In an interactive terminal, omitting `--global` first asks whether to
|
|
52
|
+
install for the current project or the user, then shows a multi-select of the agents that
|
|
53
|
+
need their own directory (`claude-code`, `cline`, `junie` or `kiro-cli`), with an
|
|
54
|
+
`All agents` entry; detected agents are preselected, and pressing Enter with none selected
|
|
55
|
+
installs only `.agents/skills`. Without a terminal, pass `--agent` (repeatable) or `--all`,
|
|
56
|
+
and the scope defaults to the project. Use `--copy` to write copies instead of the
|
|
57
|
+
canonical store plus links, and `--dry-run` to preview without writing. Re-running an
|
|
58
|
+
install replaces the existing target; `--force` is not needed.
|
|
59
|
+
|
|
60
|
+
Interactive runs end with a borderless summary on stderr (canonical path, universal
|
|
61
|
+
agents, symlinked/copied agents); stdout carries the JSON payload only when piped or when
|
|
62
|
+
`--json` is passed.
|
|
53
63
|
|
|
54
64
|
## Publish capabilities
|
|
55
65
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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-DgHX7Fux.mjs";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
@@ -28,12 +28,33 @@ async function activeToken(ctx) {
|
|
|
28
28
|
source: "config"
|
|
29
29
|
} : void 0;
|
|
30
30
|
}
|
|
31
|
-
async function token(ctx) {
|
|
31
|
+
async function token(ctx, argv) {
|
|
32
|
+
if (argv.audience === "mcp") return mintMCPToken(ctx);
|
|
32
33
|
const active = await activeToken(ctx);
|
|
33
34
|
if (!active) throw notLoggedInError();
|
|
34
35
|
ctx.io.stdout.write(`${active.token}\n`);
|
|
35
36
|
return 0;
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Mints a static MCP token with the active credential and prints it once.
|
|
40
|
+
* The stored config is never rewritten, so the same machine keeps its
|
|
41
|
+
* management login while handing out MCP tokens.
|
|
42
|
+
*
|
|
43
|
+
* The caller's kind is deliberately not checked here: the server owns the
|
|
44
|
+
* owner rule (a Principal can manage its own tokens), so the request must go
|
|
45
|
+
* out and its Problem, if any, reaches the user through the usual mapping.
|
|
46
|
+
*/
|
|
47
|
+
async function mintMCPToken(ctx) {
|
|
48
|
+
const connection = await resolveConnection(ctx.flags, ctx.env);
|
|
49
|
+
if (!connection.token) throw notLoggedInError();
|
|
50
|
+
const minted = await mintAccessToken(connection.server, {
|
|
51
|
+
name: cliMCPTokenName(),
|
|
52
|
+
audience: "mcp",
|
|
53
|
+
headers: { Authorization: `Bearer ${connection.token}` }
|
|
54
|
+
});
|
|
55
|
+
ctx.io.stdout.write(`${minted.token}\n`);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
37
58
|
/** Prompts only happen with both stdin and stdout on a TTY and a prompter available. */
|
|
38
59
|
function canPrompt(ctx) {
|
|
39
60
|
return ctx.io.stdin.isTTY && ctx.io.stdout.isTTY && ctx.io.prompts !== void 0;
|
|
@@ -130,6 +151,7 @@ async function loginWithToken(ctx, server, stored) {
|
|
|
130
151
|
token_name: matched.name
|
|
131
152
|
} : {},
|
|
132
153
|
token_prefix: prefix,
|
|
154
|
+
audience: "management",
|
|
133
155
|
principal,
|
|
134
156
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
135
157
|
});
|
|
@@ -158,7 +180,11 @@ async function loginWithPassword(ctx, server, stored) {
|
|
|
158
180
|
}
|
|
159
181
|
}));
|
|
160
182
|
if (ctx.flags.force && stored) await revokePreviousToken(ctx, stored);
|
|
161
|
-
const minted = await mintAccessToken(server,
|
|
183
|
+
const minted = await mintAccessToken(server, {
|
|
184
|
+
name: cliTokenName(),
|
|
185
|
+
audience: "management",
|
|
186
|
+
headers: { Cookie: cookie }
|
|
187
|
+
});
|
|
162
188
|
const principal = asStoredPrincipal(await apiGet({
|
|
163
189
|
server,
|
|
164
190
|
token: minted.token
|
|
@@ -171,6 +197,7 @@ async function loginWithPassword(ctx, server, stored) {
|
|
|
171
197
|
token_id: minted.id,
|
|
172
198
|
token_name: minted.name,
|
|
173
199
|
token_prefix: minted.token_prefix,
|
|
200
|
+
audience: "management",
|
|
174
201
|
principal,
|
|
175
202
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
176
203
|
});
|
|
@@ -308,6 +335,7 @@ async function loginDevice(ctx, server, stored) {
|
|
|
308
335
|
token_id: minted.token_id,
|
|
309
336
|
token_name: minted.name,
|
|
310
337
|
token_prefix: minted.token_prefix,
|
|
338
|
+
audience: "management",
|
|
311
339
|
principal,
|
|
312
340
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
313
341
|
});
|
|
@@ -332,6 +360,10 @@ function sessionCookieOf(response) {
|
|
|
332
360
|
function cliTokenName() {
|
|
333
361
|
return `hngr-cli@${hostname()}`.slice(0, 64);
|
|
334
362
|
}
|
|
363
|
+
/** `hngr-mcp@<hostname>`, so MCP tokens are recognisable in the token list. */
|
|
364
|
+
function cliMCPTokenName() {
|
|
365
|
+
return `hngr-mcp@${hostname()}`.slice(0, 64);
|
|
366
|
+
}
|
|
335
367
|
/**
|
|
336
368
|
* Finds the stored token's id so logout can revoke it. A token list that is
|
|
337
369
|
* unavailable, malformed, or ambiguous leaves the id unknown; login itself
|
|
@@ -360,20 +392,24 @@ async function lookupTokenMetadata(server, token, prefix) {
|
|
|
360
392
|
} : { id: match.id };
|
|
361
393
|
}
|
|
362
394
|
/**
|
|
363
|
-
* Mints an access token with the
|
|
364
|
-
*
|
|
365
|
-
*
|
|
395
|
+
* Mints an access token with the caller's credentials and a random
|
|
396
|
+
* Idempotency-Key. The caller passes either a session cookie (login) or a
|
|
397
|
+
* Bearer token (minting for an audience). A replay response carries no token
|
|
398
|
+
* value: delete the replayed id and mint again under a fresh key.
|
|
366
399
|
*/
|
|
367
|
-
async function mintAccessToken(server,
|
|
400
|
+
async function mintAccessToken(server, options) {
|
|
368
401
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
369
402
|
const response = await apiFetch({ server }, {
|
|
370
403
|
method: "POST",
|
|
371
404
|
path: "/api/v1/me/tokens",
|
|
372
405
|
headers: {
|
|
373
|
-
|
|
406
|
+
...options.headers,
|
|
374
407
|
"Idempotency-Key": randomUUID()
|
|
375
408
|
},
|
|
376
|
-
body: {
|
|
409
|
+
body: {
|
|
410
|
+
name: options.name,
|
|
411
|
+
audience: options.audience
|
|
412
|
+
}
|
|
377
413
|
});
|
|
378
414
|
const body = await response.json();
|
|
379
415
|
if (response.status === 201) {
|
|
@@ -397,7 +433,7 @@ async function mintAccessToken(server, cookie, name) {
|
|
|
397
433
|
await apiFetch({ server }, {
|
|
398
434
|
method: "DELETE",
|
|
399
435
|
path: `/api/v1/me/tokens/${encodeURIComponent(body.id)}`,
|
|
400
|
-
headers:
|
|
436
|
+
headers: options.headers
|
|
401
437
|
});
|
|
402
438
|
}
|
|
403
439
|
throw new HngrError({
|
|
@@ -447,6 +483,7 @@ async function status(ctx, argv) {
|
|
|
447
483
|
const config = await readStoredConfig(ctx.env);
|
|
448
484
|
const source = active.source === "env" ? "env:HANGAR_TOKEN" : configFilePath(ctx.env);
|
|
449
485
|
const tokenPrefix = active.source === "config" && config?.token_prefix ? config.token_prefix : tokenPrefixOf(active.token);
|
|
486
|
+
const audience = active.source === "config" ? storedAudience(config) : "management";
|
|
450
487
|
let principal = config?.principal ?? null;
|
|
451
488
|
if (argv.offline !== true) try {
|
|
452
489
|
principal = await apiGet(connection, "/api/v1/me", void 0, { signal: AbortSignal.timeout(5e3) });
|
|
@@ -458,7 +495,8 @@ async function status(ctx, argv) {
|
|
|
458
495
|
source,
|
|
459
496
|
server: connection.server,
|
|
460
497
|
principal,
|
|
461
|
-
token_prefix: tokenPrefix
|
|
498
|
+
token_prefix: tokenPrefix,
|
|
499
|
+
audience
|
|
462
500
|
});
|
|
463
501
|
return 0;
|
|
464
502
|
}
|