@peac/cli 0.12.10 → 0.12.11

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.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `peac doctor`: installability diagnostics.
3
+ *
4
+ * Runs a fixed set of offline-first checks that flag common PEAC
5
+ * installation problems (Node version, required package resolution,
6
+ * tracked config presence). Remote checks (JWKS reachability, reference
7
+ * verifier reachability) are opt-in via the --online flag; by default
8
+ * the command performs zero network calls.
9
+ *
10
+ * Each check reports green / yellow / red. Exit code is 0 when all
11
+ * required checks are green or yellow; 1 when any required check is
12
+ * red.
13
+ */
14
+ import { Command } from 'commander';
15
+ export declare function runDoctor(options: {
16
+ online: boolean;
17
+ issuer?: string;
18
+ timeout: number;
19
+ cwd: string;
20
+ }): Promise<number>;
21
+ export declare function doctor(program: Command): void;
22
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkLpC,wBAAsB,SAAS,CAAC,OAAO,EAAE;IACvC,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAAC,MAAM,CAAC,CAkClB;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqB7C"}
package/dist/index.cjs CHANGED
@@ -12,6 +12,7 @@ var crypto$1 = require('crypto');
12
12
  var schema = require('@peac/schema');
13
13
  var disc = require('@peac/disc');
14
14
  var audit = require('@peac/audit');
15
+ var module$1 = require('module');
15
16
 
16
17
  function _interopNamespace(e) {
17
18
  if (e && e.__esModule) return e;
@@ -586,9 +587,9 @@ policy.command("show-profile").description("Show details of a specific policy pr
586
587
  function sha256(content) {
587
588
  return crypto$1.createHash("sha256").update(content, "utf8").digest("hex");
588
589
  }
589
- function zodPathToJsonPointer(path8) {
590
- if (path8.length === 0) return "";
591
- return "/" + path8.map((segment) => {
590
+ function zodPathToJsonPointer(path9) {
591
+ if (path9.length === 0) return "";
592
+ return "/" + path9.map((segment) => {
592
593
  const str = String(segment);
593
594
  return str.replace(/~/g, "~0").replace(/\//g, "~1");
594
595
  }).join("/");
@@ -2389,6 +2390,209 @@ function reconcileCommand() {
2389
2390
  );
2390
2391
  return reconcile;
2391
2392
  }
2393
+ var requireFrom = module$1.createRequire(path6__namespace.join(process.cwd(), "package.json"));
2394
+ function paint(status, msg) {
2395
+ const labels = {
2396
+ green: "PASS",
2397
+ yellow: "WARN",
2398
+ red: "FAIL"
2399
+ };
2400
+ return `[${labels[status]}] ${msg}`;
2401
+ }
2402
+ function parseMajor(nodeVersion) {
2403
+ const m = /^v(\d+)\./.exec(nodeVersion);
2404
+ return m ? parseInt(m[1], 10) : 0;
2405
+ }
2406
+ function checkNodeVersion() {
2407
+ const major = parseMajor(process.version);
2408
+ if (major >= 22) {
2409
+ return {
2410
+ name: "Node version",
2411
+ status: "green",
2412
+ detail: `${process.version} (>= 22 required)`
2413
+ };
2414
+ }
2415
+ return {
2416
+ name: "Node version",
2417
+ status: "red",
2418
+ detail: `${process.version} is below the required Node >= 22`
2419
+ };
2420
+ }
2421
+ function checkPackageResolution() {
2422
+ const required = ["@peac/protocol", "@peac/crypto", "@peac/schema", "@peac/kernel"];
2423
+ const missing = [];
2424
+ for (const pkg of required) {
2425
+ try {
2426
+ requireFrom.resolve(pkg);
2427
+ } catch {
2428
+ missing.push(pkg);
2429
+ }
2430
+ }
2431
+ if (missing.length === 0) {
2432
+ return {
2433
+ name: "Package resolution",
2434
+ status: "green",
2435
+ detail: `${required.length} required packages resolvable`
2436
+ };
2437
+ }
2438
+ return {
2439
+ name: "Package resolution",
2440
+ status: "red",
2441
+ detail: `${missing.length} missing: ${missing.join(", ")}`
2442
+ };
2443
+ }
2444
+ function checkKeyMaterial(cwd) {
2445
+ const env = process.env.PEAC_ISSUER_KEY || "";
2446
+ if (env) {
2447
+ if (env.startsWith("env:") || env.startsWith("file:")) {
2448
+ return {
2449
+ name: "Issuer key material",
2450
+ status: "green",
2451
+ detail: `PEAC_ISSUER_KEY set via ${env.split(":")[0]}:... scheme`
2452
+ };
2453
+ }
2454
+ return {
2455
+ name: "Issuer key material",
2456
+ status: "yellow",
2457
+ detail: "PEAC_ISSUER_KEY set but not using env: or file: scheme"
2458
+ };
2459
+ }
2460
+ const candidatePaths = [
2461
+ path6__namespace.join(cwd, "issuer.jwk.json"),
2462
+ path6__namespace.join(cwd, ".peac/issuer.jwk.json")
2463
+ ];
2464
+ for (const p of candidatePaths) {
2465
+ if (fs6__namespace.existsSync(p)) {
2466
+ return {
2467
+ name: "Issuer key material",
2468
+ status: "yellow",
2469
+ detail: `found ${path6__namespace.relative(cwd, p)}; prefer PEAC_ISSUER_KEY env var`
2470
+ };
2471
+ }
2472
+ }
2473
+ return {
2474
+ name: "Issuer key material",
2475
+ status: "yellow",
2476
+ detail: "not found (issuance disabled; read-only tools still work)"
2477
+ };
2478
+ }
2479
+ function checkPeacTxt(cwd) {
2480
+ const p = path6__namespace.join(cwd, ".well-known/peac.txt");
2481
+ if (fs6__namespace.existsSync(p)) {
2482
+ return {
2483
+ name: "peac.txt presence",
2484
+ status: "green",
2485
+ detail: `.well-known/peac.txt present at ${p}`
2486
+ };
2487
+ }
2488
+ return {
2489
+ name: "peac.txt presence",
2490
+ status: "yellow",
2491
+ detail: "no .well-known/peac.txt (discovery-layer check; optional for tool callers)"
2492
+ };
2493
+ }
2494
+ async function checkOnlineJwks(issuerUrl, timeoutMs) {
2495
+ const ctrl = new AbortController();
2496
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
2497
+ try {
2498
+ const cfgUrl = new URL("/.well-known/peac-issuer.json", issuerUrl).toString();
2499
+ const res = await fetch(cfgUrl, { signal: ctrl.signal });
2500
+ if (res.status !== 200) {
2501
+ return {
2502
+ name: "Issuer configuration",
2503
+ status: "red",
2504
+ detail: `${cfgUrl} returned ${res.status}`
2505
+ };
2506
+ }
2507
+ const cfg = await res.json();
2508
+ const jwksUri = cfg.jwks_uri;
2509
+ if (!jwksUri) {
2510
+ return {
2511
+ name: "Issuer configuration",
2512
+ status: "red",
2513
+ detail: `${cfgUrl} missing jwks_uri`
2514
+ };
2515
+ }
2516
+ const jwksRes = await fetch(jwksUri, { signal: ctrl.signal });
2517
+ if (jwksRes.status !== 200) {
2518
+ return {
2519
+ name: "Issuer configuration",
2520
+ status: "red",
2521
+ detail: `jwks_uri ${jwksUri} returned ${jwksRes.status}`
2522
+ };
2523
+ }
2524
+ const jwks = await jwksRes.json();
2525
+ const keyCount = Array.isArray(jwks.keys) ? jwks.keys.length : 0;
2526
+ if (keyCount === 0) {
2527
+ return {
2528
+ name: "Issuer configuration",
2529
+ status: "red",
2530
+ detail: `jwks_uri ${jwksUri} returned empty keys array`
2531
+ };
2532
+ }
2533
+ return {
2534
+ name: "Issuer configuration",
2535
+ status: "green",
2536
+ detail: `${cfgUrl} + jwks_uri resolvable with ${keyCount} key(s)`
2537
+ };
2538
+ } catch (err) {
2539
+ return {
2540
+ name: "Issuer configuration",
2541
+ status: "red",
2542
+ detail: `fetch failed: ${err instanceof Error ? err.message : String(err)}`
2543
+ };
2544
+ } finally {
2545
+ clearTimeout(timer);
2546
+ }
2547
+ }
2548
+ async function runDoctor(options) {
2549
+ const results = [];
2550
+ results.push(checkNodeVersion());
2551
+ results.push(checkPackageResolution());
2552
+ results.push(checkKeyMaterial(options.cwd));
2553
+ results.push(checkPeacTxt(options.cwd));
2554
+ if (options.online) {
2555
+ if (!options.issuer) {
2556
+ results.push({
2557
+ name: "Issuer configuration",
2558
+ status: "yellow",
2559
+ detail: "--online set but --issuer <url> not provided; skipping remote check"
2560
+ });
2561
+ } else {
2562
+ results.push(await checkOnlineJwks(options.issuer, options.timeout));
2563
+ }
2564
+ } else {
2565
+ results.push({
2566
+ name: "Issuer configuration",
2567
+ status: "yellow",
2568
+ detail: "offline mode (default); pass --online --issuer <url> to enable remote check"
2569
+ });
2570
+ }
2571
+ console.log("\npeac doctor");
2572
+ console.log("-----------");
2573
+ for (const r of results) {
2574
+ console.log(paint(r.status, `${r.name}: ${r.detail}`));
2575
+ }
2576
+ const hasRed = results.some((r) => r.status === "red");
2577
+ console.log(hasRed ? "\nFAIL: one or more checks are red." : "\nOK: no red checks.");
2578
+ return hasRed ? 1 : 0;
2579
+ }
2580
+ function doctor(program2) {
2581
+ program2.command("doctor").description("Run PEAC installability diagnostics (offline by default)").option("--online", "enable remote checks (issuer configuration, JWKS)", false).option("--issuer <url>", "issuer URL for --online checks (https://...)").option(
2582
+ "--timeout <ms>",
2583
+ "timeout for remote checks in milliseconds",
2584
+ (v) => parseInt(v, 10),
2585
+ 5e3
2586
+ ).action(async (opts) => {
2587
+ const exitCode = await runDoctor({
2588
+ online: !!opts.online,
2589
+ issuer: opts.issuer,
2590
+ timeout: opts.timeout,
2591
+ cwd: process.cwd()
2592
+ });
2593
+ process.exit(exitCode);
2594
+ });
2595
+ }
2392
2596
 
2393
2597
  // src/index.ts
2394
2598
  var program = new commander.Command();
@@ -2525,6 +2729,7 @@ program.addCommand(policy);
2525
2729
  program.addCommand(conformance);
2526
2730
  program.addCommand(samples);
2527
2731
  program.addCommand(reconcileCommand());
2732
+ doctor(program);
2528
2733
  try {
2529
2734
  program.parse();
2530
2735
  } catch (err) {