@peac/cli 0.13.0 → 0.13.1

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/dist/index.mjs CHANGED
@@ -6,13 +6,14 @@ import * as fs6 from 'fs';
6
6
  import { existsSync, readFileSync } from 'fs';
7
7
  import * as path6 from 'path';
8
8
  import { join } from 'path';
9
- import { loadProfile, serializePolicyJson, serializePolicyYaml, ProfileError, createExamplePolicy, loadPolicy, PolicyValidationError, PolicyLoadError, compilePeacTxt, compileRobotsSnippet, compileAiprefTemplates, renderPolicyMarkdown, explainMatches, evaluate, listProfiles, getProfileSummary } from '@peac/policy-kit';
9
+ import { loadProfile, serializePolicyJson, serializePolicyYaml, ProfileError, createExamplePolicy, loadPolicy, PolicyValidationError, PolicyLoadError, compilePeacTxt, compileRobotsSnippet, compileAiprefTemplates, renderPolicyMarkdown, explainMatches, evaluate, listProfiles, getProfileSummary, parsePolicyDocument } from '@peac/policy-kit';
10
10
  import { execSync } from 'child_process';
11
11
  import { createHash } from 'crypto';
12
12
  import { computeReceiptRef, validateWorkflowContextOrdered, isValidPurposeToken, isCanonicalPurpose, validateObligationsExtension, validateInteractionOrdered, validateDisputeAttestation, validateAttributionAttestation, validateAgentIdentityAttestation, ReceiptClaimsSchema, parseReceiptClaims } from '@peac/schema';
13
- import { parse } from '@peac/disc';
13
+ import { POLICY } from '@peac/kernel';
14
14
  import { readDisputeBundle } from '@peac/audit';
15
15
  import { createRequire } from 'module';
16
+ import chalk from 'chalk';
16
17
 
17
18
  function getGlobalOptions(cmd) {
18
19
  const parent = cmd.parent;
@@ -682,7 +683,7 @@ var PROFILE_CAPABILITIES = {
682
683
  discovery: {
683
684
  profile: "discovery.config",
684
685
  level: "semantic",
685
- validator: "@peac/disc:parse"
686
+ validator: "@peac/policy-kit:parsePolicyDocument (via cli compat helper)"
686
687
  },
687
688
  dispute: {
688
689
  profile: "receipt.verify.dispute",
@@ -762,6 +763,124 @@ function shouldRunAtLevel(fixtureVersion, level) {
762
763
  const levelOrder = ["basic", "standard", "full"];
763
764
  return levelOrder.indexOf(fixtureLevel) <= levelOrder.indexOf(level);
764
765
  }
766
+ var DISCOVER_TIMEOUT_MS = 5e3;
767
+ var DISCOVER_MAX_BYTES = POLICY.maxBytes;
768
+ var PEAC_TXT_PATH = POLICY.manifestPath;
769
+ var DEFAULT_USER_AGENT = "peac-cli";
770
+ var LEGACY_KEY_LINE = /^\s*(verify|public_keys|jwks)\s*:/m;
771
+ var LEGACY_KEY_FULL_LINE_GLOBAL = /^\s*(verify|public_keys|jwks)\s*:.*\r?\n?/gm;
772
+ var legacyWarningFired = false;
773
+ function fireLegacyWarning(field) {
774
+ if (legacyWarningFired) return;
775
+ legacyWarningFired = true;
776
+ if (typeof process !== "undefined" && typeof process.emitWarning === "function") {
777
+ process.emitWarning(
778
+ `peac.txt legacy key-discovery field "${field}" is deprecated and ignored. peac.txt is a policy-document surface (docs/specs/PEAC-TXT.md). Key resolution uses iss -> /.well-known/peac-issuer.json -> jwks_uri -> JWKS.`,
779
+ { code: "PEAC_LEGACY_PEAC_TXT_KEY_FIELD", type: "DeprecationWarning" }
780
+ );
781
+ }
782
+ }
783
+ function collectLegacyWarnings(text, warnings) {
784
+ const lines = text.split(/\r?\n/);
785
+ let firstField = null;
786
+ lines.forEach((line, idx) => {
787
+ const m = line.match(/^\s*(verify|public_keys|jwks)\s*:/);
788
+ if (m) {
789
+ if (firstField === null) firstField = m[1];
790
+ warnings.push(
791
+ `Line ${idx + 1}: legacy key-discovery field "${m[1]}" ignored (peac.txt is policy-only; use peac-issuer.json for keys)`
792
+ );
793
+ }
794
+ });
795
+ if (firstField) fireLegacyWarning(firstField);
796
+ }
797
+ function failureFrom(err, warnings) {
798
+ const errors = [];
799
+ if (err instanceof PolicyValidationError) {
800
+ const issues = err.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
801
+ errors.push(`Policy validation failed: ${issues}`);
802
+ } else if (err instanceof PolicyLoadError) {
803
+ errors.push(err.message);
804
+ } else {
805
+ errors.push(`Unexpected parse error: ${err instanceof Error ? err.message : String(err)}`);
806
+ }
807
+ return {
808
+ valid: false,
809
+ errors,
810
+ warnings: warnings.length > 0 ? warnings : void 0
811
+ };
812
+ }
813
+ function parsePolicyDocumentCompat(text) {
814
+ if (text.trim().length === 0) {
815
+ return {
816
+ valid: false,
817
+ errors: ["Empty policy document. Expected peac-policy/0.1 YAML or JSON."]
818
+ };
819
+ }
820
+ const hasLegacyLines = LEGACY_KEY_LINE.test(text);
821
+ const warnings = [];
822
+ try {
823
+ const data = parsePolicyDocument(text);
824
+ if (hasLegacyLines) collectLegacyWarnings(text, warnings);
825
+ return {
826
+ valid: true,
827
+ data,
828
+ warnings: warnings.length > 0 ? warnings : void 0
829
+ };
830
+ } catch (firstErr) {
831
+ if (!hasLegacyLines) return failureFrom(firstErr, warnings);
832
+ collectLegacyWarnings(text, warnings);
833
+ const stripped = text.replace(LEGACY_KEY_FULL_LINE_GLOBAL, "");
834
+ try {
835
+ const data = parsePolicyDocument(stripped);
836
+ return { valid: true, data, warnings };
837
+ } catch (secondErr) {
838
+ return failureFrom(secondErr, warnings);
839
+ }
840
+ }
841
+ }
842
+ async function fetchPolicyDocumentText(origin, options = {}) {
843
+ let target;
844
+ try {
845
+ target = new URL(PEAC_TXT_PATH, origin);
846
+ } catch (err) {
847
+ return {
848
+ ok: false,
849
+ code: "INVALID_URL",
850
+ error: `Invalid origin: ${err instanceof Error ? err.message : String(err)}`
851
+ };
852
+ }
853
+ const fetchOptions = {
854
+ timeoutMs: options.timeoutMs ?? DISCOVER_TIMEOUT_MS,
855
+ maxResponseBytes: options.maxBytes ?? DISCOVER_MAX_BYTES,
856
+ headers: { "user-agent": options.userAgent ?? DEFAULT_USER_AGENT },
857
+ allowedMethods: ["GET"]
858
+ };
859
+ const { safeFetchRaw } = await import('@peac/net-node');
860
+ const raw = await safeFetchRaw(target.toString(), fetchOptions);
861
+ if (!raw.ok) {
862
+ return {
863
+ ok: false,
864
+ code: raw.code,
865
+ error: `${raw.code}: ${raw.error}`
866
+ };
867
+ }
868
+ try {
869
+ if (!raw.response.ok) {
870
+ return {
871
+ ok: false,
872
+ code: `HTTP_${raw.response.status}`,
873
+ error: `HTTP ${raw.response.status}: ${raw.response.statusText}`
874
+ };
875
+ }
876
+ const text = await raw.response.text();
877
+ return { ok: true, text, warnings: raw.warnings };
878
+ } finally {
879
+ await raw.close();
880
+ }
881
+ }
882
+
883
+ // src/lib/conformance/validators.ts
765
884
  function zodCodeToKeyword(code) {
766
885
  const mapping = {
767
886
  invalid_type: "type",
@@ -816,7 +935,7 @@ function validateParseReceiptClaims(payload) {
816
935
  }
817
936
  function validateDiscoveryInput(input) {
818
937
  if (typeof input === "string") {
819
- const result = parse(input);
938
+ const result = parsePolicyDocumentCompat(input);
820
939
  if (result.valid) {
821
940
  return { valid: true };
822
941
  }
@@ -2572,6 +2691,118 @@ function doctor(program2) {
2572
2691
  process.exit(exitCode);
2573
2692
  });
2574
2693
  }
2694
+ function formatOutput(result, json = false) {
2695
+ if (json) {
2696
+ return JSON.stringify(result, null, 2);
2697
+ }
2698
+ if (!result.success) {
2699
+ return chalk.red(`Error: ${result.error}`);
2700
+ }
2701
+ if (result.data && typeof result.data === "object") {
2702
+ if (result.data.sources) {
2703
+ return formatDiscoveryResult(result.data);
2704
+ } else if (result.data.digest) {
2705
+ return formatHashResult(result.data);
2706
+ } else if (result.data.valid !== void 0) {
2707
+ return formatVerifyResult(result.data);
2708
+ }
2709
+ }
2710
+ return JSON.stringify(result.data, null, 2);
2711
+ }
2712
+ function formatDiscoveryResult(data) {
2713
+ const lines = [`Discovery for: ${chalk.blue(data.url)}`, ""];
2714
+ for (const source of data.sources) {
2715
+ const status = source.status === "found" ? chalk.green("[found]") : source.status === "not_found" ? chalk.yellow("[not found]") : chalk.red("[error]");
2716
+ lines.push(`${status} ${source.type}: ${source.url}`);
2717
+ if (source.etag) {
2718
+ lines.push(` ETag: ${source.etag}`);
2719
+ }
2720
+ }
2721
+ return lines.join("\\n");
2722
+ }
2723
+ function formatHashResult(data) {
2724
+ return [
2725
+ `Algorithm: ${data.algorithm}`,
2726
+ `Format: ${data.format}`,
2727
+ `Digest: ${chalk.green(data.digest)}`,
2728
+ `Input size: ${data.input_size} bytes`
2729
+ ].join("\\n");
2730
+ }
2731
+ function formatVerifyResult(data) {
2732
+ if (data.valid) {
2733
+ const lines = [chalk.green("Receipt verified successfully"), ""];
2734
+ if (data.policy_hash) {
2735
+ lines.push(`Policy hash: ${data.policy_hash}`);
2736
+ }
2737
+ if (data.resource) {
2738
+ lines.push(`Resource: ${data.resource}`);
2739
+ }
2740
+ return lines.join("\\n");
2741
+ } else {
2742
+ return chalk.red(`Verification failed: ${data.error || "Unknown error"}`);
2743
+ }
2744
+ }
2745
+ function handleError(error) {
2746
+ return {
2747
+ success: false,
2748
+ error: error.message
2749
+ };
2750
+ }
2751
+ function timing() {
2752
+ const started = Date.now();
2753
+ return {
2754
+ started,
2755
+ end: () => {
2756
+ const completed = Date.now();
2757
+ return {
2758
+ started,
2759
+ completed,
2760
+ duration: completed - started
2761
+ };
2762
+ }
2763
+ };
2764
+ }
2765
+
2766
+ // src/commands/discover.ts
2767
+ var DiscoverCommand = class {
2768
+ async execute(url, _options = {}) {
2769
+ const timer = timing();
2770
+ try {
2771
+ const parsedUrl = new URL(url);
2772
+ if (!["http:", "https:"].includes(parsedUrl.protocol)) {
2773
+ throw new Error("URL must use http: or https: protocol");
2774
+ }
2775
+ const fetched = await fetchPolicyDocumentText(url);
2776
+ if (!fetched.ok) {
2777
+ return {
2778
+ success: false,
2779
+ error: fetched.error,
2780
+ timing: timer.end()
2781
+ };
2782
+ }
2783
+ const parsed = parsePolicyDocumentCompat(fetched.text);
2784
+ const data = { ...parsed };
2785
+ if (fetched.warnings && fetched.warnings.length > 0) {
2786
+ data.warnings = [...parsed.warnings ?? [], ...fetched.warnings];
2787
+ }
2788
+ if (data.warnings && data.warnings.length > 0) {
2789
+ data.hints = [
2790
+ "peac.txt is a policy-document surface (docs/specs/PEAC-TXT.md). For key discovery, use /.well-known/peac-issuer.json (docs/specs/PEAC-ISSUER.md)."
2791
+ ];
2792
+ }
2793
+ return {
2794
+ success: true,
2795
+ data,
2796
+ timing: timer.end()
2797
+ };
2798
+ } catch (error) {
2799
+ return {
2800
+ ...handleError(error),
2801
+ timing: timer.end()
2802
+ };
2803
+ }
2804
+ }
2805
+ };
2575
2806
 
2576
2807
  // src/index.ts
2577
2808
  var program = new Command();
@@ -2709,6 +2940,12 @@ program.addCommand(conformance);
2709
2940
  program.addCommand(samples);
2710
2941
  program.addCommand(reconcileCommand());
2711
2942
  doctor(program);
2943
+ program.command("discover").description("Discover and parse a remote /.well-known/peac.txt policy document").argument("<url>", "Origin URL (http/https) whose /.well-known/peac.txt to fetch").option("-j, --json", "output in JSON format").action(async (url, opts) => {
2944
+ const cmd = new DiscoverCommand();
2945
+ const result = await cmd.execute(url, { json: opts.json });
2946
+ console.log(formatOutput(result, opts.json));
2947
+ process.exitCode = result.success ? 0 : 1;
2948
+ });
2712
2949
  try {
2713
2950
  program.parse();
2714
2951
  } catch (err) {