Skip to main content

harn_cli/cli/
pack.rs

1use std::path::PathBuf;
2
3use clap::{Args, Subcommand};
4
5#[derive(Debug, Args)]
6#[command(arg_required_else_help = true)]
7pub struct PackArgs {
8    #[command(subcommand)]
9    pub command: Option<PackCommand>,
10
11    /// Entrypoint `.harn` file to pack. Transitive Harn modules and
12    /// non-Harn assets referenced by import directives under the
13    /// entrypoint's directory are bundled alongside it.
14    ///
15    /// When `harn pack` is invoked without a subcommand, this positional
16    /// argument selects the build path; passing a subcommand (e.g.
17    /// `harn pack verify <bundle>`) routes to that subcommand instead.
18    #[arg(required = false)]
19    pub entrypoint: Option<PathBuf>,
20
21    /// Output `.harnpack` path. Defaults to the entrypoint stem with
22    /// the `.harnpack` extension next to the entrypoint.
23    #[arg(long, value_name = "PATH")]
24    pub out: Option<PathBuf>,
25
26    /// Read an existing `.harnpack` and re-emit it under the v2
27    /// manifest, preserving the prior bundle's id, name, version,
28    /// triggers, workflow graph, and prompt capsules. The new
29    /// `<entrypoint>` argument supplies the transitive-modules /
30    /// SBOM payload that v1 lacked.
31    #[arg(long, value_name = "OLD_BUNDLE")]
32    pub upgrade: Option<PathBuf>,
33
34    /// Sign the bundle hash and embed an Ed25519 signature in the manifest.
35    #[arg(
36        long,
37        default_value_t = false,
38        conflicts_with = "unsigned",
39        requires = "key"
40    )]
41    pub sign: bool,
42
43    /// Ed25519 private key PEM used with `--sign`.
44    #[arg(long, value_name = "PATH", requires = "sign")]
45    pub key: Option<PathBuf>,
46
47    /// Mark the bundle as unsigned. This still emits an OpenTrustGraph
48    /// release record at autonomy tier `suggest`.
49    #[arg(long, default_value_t = false)]
50    pub unsigned: bool,
51
52    /// Refuse to bundle modules whose path matches a built-in
53    /// secret-bearing glob (`.env`, `.env.*`, `*.pem`, `*.key`,
54    /// `credentials*`, anything under `secrets/`). The default behavior
55    /// matches the historical pack semantics: pack the full transitive
56    /// module set without any secret filtering. Pass `--exclude-secrets`
57    /// from CI or release pipelines that share bundles externally.
58    ///
59    /// The same gate skips imported non-Harn assets that match the
60    /// secret heuristic and reports each skipped asset as a structured
61    /// JSON warning plus `manifest.metadata.skipped_assets`.
62    #[arg(long, default_value_t = false, conflicts_with = "include_secrets")]
63    pub exclude_secrets: bool,
64
65    /// Explicitly opt in to the default behavior: bundle every
66    /// transitive module without secret filtering. Useful in scripts
67    /// that want to be explicit about the bundle's contents instead of
68    /// relying on the default.
69    #[arg(long, default_value_t = false)]
70    pub include_secrets: bool,
71
72    /// Emit a `JsonEnvelope` summary instead of a human-readable
73    /// one-liner. Schema: `harn --json-schemas --command pack`.
74    #[arg(long, default_value_t = false)]
75    pub json: bool,
76}
77
78#[derive(Debug, Subcommand)]
79pub enum PackCommand {
80    /// Expand a `.harnpack` bundle into a directory containing
81    /// `harnpack.json` plus the archive payload entries. Intended for
82    /// audit/debug workflows and tests that need to inspect or mutate a
83    /// bundle without depending on host `tar`/`zstd` binaries.
84    Unpack(PackUnpackArgs),
85
86    /// Reassemble a directory produced by `harn pack unpack` back into
87    /// a `.harnpack` bundle.
88    Repack(PackRepackArgs),
89
90    /// Verify a `.harnpack` bundle: check the embedded Ed25519
91    /// signature (if present), recompute the canonical bundle hash,
92    /// and compare each archive entry's BLAKE3 against the manifest's
93    /// recorded hashes. Exits non-zero on any mismatch.
94    Verify(PackVerifyArgs),
95}
96
97#[derive(Debug, Args)]
98#[command(arg_required_else_help = true)]
99pub struct PackUnpackArgs {
100    /// Path to the `.harnpack` archive to unpack.
101    pub bundle: PathBuf,
102
103    /// Output directory to create.
104    #[arg(long, value_name = "DIR")]
105    pub out: PathBuf,
106
107    /// Replace the output path if it already exists.
108    #[arg(long, default_value_t = false)]
109    pub force: bool,
110}
111
112#[derive(Debug, Args)]
113#[command(arg_required_else_help = true)]
114pub struct PackRepackArgs {
115    /// Directory containing `harnpack.json` and archive payload entries.
116    pub dir: PathBuf,
117
118    /// Output `.harnpack` path.
119    #[arg(long, value_name = "PATH")]
120    pub out: PathBuf,
121
122    /// Replace the output file if it already exists.
123    #[arg(long, default_value_t = false)]
124    pub force: bool,
125}
126
127#[derive(Debug, Args)]
128#[command(arg_required_else_help = true)]
129pub struct PackVerifyArgs {
130    /// Path to the `.harnpack` archive to verify.
131    pub bundle: PathBuf,
132
133    /// Accept bundles that carry no Ed25519 signature. Without this
134    /// flag, an unsigned bundle is treated as a verification failure.
135    #[arg(long, default_value_t = false)]
136    pub allow_unsigned: bool,
137
138    /// JSON trust policy describing the signer registry URL and
139    /// optional trusted signer allowlist to enforce during
140    /// verification.
141    #[arg(long, value_name = "PATH")]
142    pub trust_policy: Option<PathBuf>,
143
144    /// Require the bundle signer to resolve from the trusted signer
145    /// registry and, when `--trust-policy` supplies a
146    /// `trusted_signers` allowlist, appear in that allowlist too.
147    #[arg(long, default_value_t = false)]
148    pub require_trusted_signer: bool,
149
150    /// Cross-check SBOM package hashes against the archive payloads
151    /// they describe when the bundle format carries a corresponding
152    /// entry.
153    #[arg(long, default_value_t = false)]
154    pub strict: bool,
155
156    /// Emit a `JsonEnvelope` summary instead of a human-readable
157    /// one-liner. Schema: `harn --json-schemas --command "pack verify"`.
158    #[arg(long, default_value_t = false)]
159    pub json: bool,
160}