Skip to main content

zenith_cli/
cli.rs

1//! Clap argument types for the Zenith CLI.
2//!
3//! This module defines the top-level [`Cli`] struct and the [`Command`]
4//! subcommand enum. No business logic lives here — just argument shapes.
5//!
6//! Per-command-group arg structs live in submodules and are re-exported here
7//! so that all existing `crate::cli::*` paths continue to resolve unchanged.
8//!
9//! Submodules:
10//! - `library` — `LibraryArgs`, `LibrarySub`, and library item arg types.
11//! - `plugin` — `PluginArgs`, `PluginSub`, `ScopeArg`, `AgentFlags`, and install/uninstall args.
12//! - `render` — `RenderArgs`.
13//! - `schema` — `SchemaArgs`, `SchemaSub`.
14//! - `workspace` — `WorkspaceArgs`, `WorkspaceSub`, scratch, candidate, and promote arg types.
15
16mod library;
17mod plugin;
18mod render;
19mod schema;
20mod workspace;
21
22pub use library::{LibraryAddArgs, LibraryArgs, LibraryListArgs, LibraryShowArgs, LibrarySub};
23pub use plugin::{
24    AgentFlags, PluginArgs, PluginInstallArgs, PluginSub, PluginUninstallArgs, ScopeArg,
25};
26pub use render::RenderArgs;
27pub use schema::{SchemaArgs, SchemaSub};
28pub use workspace::{
29    CandidateArgs, PromoteArgs, ScratchArgs, ScratchListArgs, ScratchNewArgs, ScratchShowArgs,
30    ScratchSub, WorkspaceArgs, WorkspaceSub,
31};
32
33use clap::{Args, Parser, Subcommand};
34use std::path::PathBuf;
35
36/// Zenith — design-document toolchain.
37#[derive(Debug, Parser)]
38#[command(
39    name = "zenith",
40    version,
41    about = "Author, validate, and render deterministic .zen design documents (KDL → PNG/PDF).",
42    long_about = "Zenith turns a design into plain-text .zen source (KDL) you can read, diff, \
43validate, edit with typed transactions, and render deterministically to pixel-exact PNG or \
44print-ready PDF — the opposite of a flat AI image.\n\n\
45The core loop: author/edit source → `validate` → `render` to inspect → iterate. Edits to \
46existing documents should go through `tx` (typed, dry-run by default). Every command accepts \
47`--json` for machine-readable output; run `zenith <command> --help` for exact flags.",
48    after_help = "QUICK START:\n  \
49zenith validate poster.zen --json     # check for hard diagnostics\n  \
50zenith render poster.zen --png out.png # render a page to inspect\n  \
51zenith theme new acme --scheme light --primary '#3b5bdb' --out acme.zen\n  \
52zenith plugin install --claude         # teach your AI agent to use zenith\n\n\
53Run `zenith <command> --help` for details on any command."
54)]
55pub struct Cli {
56    #[command(subcommand)]
57    pub command: Command,
58}
59
60/// Top-level subcommands.
61#[derive(Debug, Subcommand)]
62pub enum Command {
63    /// Scaffold a new minimal, valid `.zen` document at a fresh path.
64    ///
65    /// Writes a minimal valid document (one page on a white background) at the
66    /// given path, with a `doc-id` already minted + stamped and its first
67    /// version recorded in history — a ready-to-edit "File > New" starting point.
68    /// A `.zen` extension is appended when the path has none, and any missing
69    /// parent directories are created. Refuses to overwrite an existing file.
70    /// The id slug is derived from `--name` when given, otherwise from the
71    /// path's file stem; the default name is "Untitled".
72    New(NewArgs),
73
74    /// Validate a `.zen` document and report diagnostics.
75    ///
76    /// Validate a .zen document and report diagnostics. Hard (Error) diagnostics
77    /// block rendering — always validate and fix them before `render`. Exit code is non-zero when
78    /// hard diagnostics are present.
79    Validate(ValidateArgs),
80
81    /// Format a `.zen` document in-place (idempotent).
82    Fmt(FmtArgs),
83
84    /// List all design tokens and their resolved values.
85    ///
86    /// List every design token and its resolved value. Visual properties must
87    /// reference tokens, so this is how you discover the palette/type/spacing a document exposes
88    /// before authoring or editing nodes.
89    Tokens(TokensArgs),
90
91    /// Compile and render a `.zen` document.
92    ///
93    /// Compile and render a .zen document to PNG, PDF, or a scene display-list.
94    /// Rendering is deterministic (same source + backend → same bytes) and is blocked by hard
95    /// diagnostics, so `validate` first. Use `--all-pages <DIR>` for a contact sheet and `--spread
96    /// A-B` for facing pages.
97    Render(RenderArgs),
98
99    /// Apply a transaction to a `.zen` document (dry-run by default).
100    ///
101    /// Apply a typed transaction (a JSON edit script) to a .zen document. This is
102    /// the preferred way to edit existing documents: it is dry-run by default (shows a source + scene
103    /// diff), enforces id-uniqueness and referential integrity, and only writes with `--apply`.
104    Tx(TxArgs),
105
106    /// Print the node tree of a `.zen` document (read-only).
107    ///
108    /// Print the structure of a .zen document (read-only): the node tree plus
109    /// document-level blocks such as the `recipes` provenance block. Use it to discover node ids before
110    /// writing a `tx` edit, to see which recipes a document declares, or to confirm what it contains.
111    Inspect(InspectArgs),
112
113    /// Mail-merge a `.zen` template with a CSV data file, writing one PNG per row.
114    ///
115    /// Mail-merge a .zen template with a CSV, writing one PNG per row. Mark variable
116    /// nodes with `role="data.<column>"` (text nodes substitute their text; image nodes substitute
117    /// their asset path) where `<column>` matches a CSV header. Use this for localized posts,
118    /// personalized graphics, certificates, badges, and campaign variants. For a SINGLE
119    /// data-bound render (first object/row, via `(data)"field.path"` references) use
120    /// `zenith render --data` instead.
121    Merge(MergeArgs),
122
123    /// Discover and materialize reusable design assets (tokens, components, actions).
124    ///
125    /// Manages library packs: embedded `@zenith/*` presets and project-local packs
126    /// in `libraries/*.zen`. Run `zenith library list` to discover, `zenith library
127    /// show <pkg>#<item>` to inspect, and `zenith library add <pkg>#<item> --into
128    /// <doc.zen>` to materialize a token, component, or action into a document.
129    Library(LibraryArgs),
130
131    /// List a document's version history.
132    ///
133    /// History is automatic: every `tx --apply` and edit is recorded in a durable
134    /// per-document version store kept beside the file (content-addressed; off the
135    /// render path). This lists those revisions with their version ids. The related
136    /// commands operate on that same store: `undo`/`redo` step the session, `version`
137    /// names the current state, `restore <rev>` rewinds to a past one, and `sync`
138    /// records an out-of-band external edit. Use the version ids shown here as the
139    /// `<rev>` argument to `restore`.
140    History(HistoryArgs),
141
142    /// Undo the last edit, rewriting the document in place.
143    Undo(UndoArgs),
144
145    /// Redo the last undone edit, rewriting the document in place.
146    Redo(RedoArgs),
147
148    /// Save the current document as a named version.
149    Version(VersionArgs),
150
151    /// Restore the document to a past version.
152    ///
153    /// Rewinds the document to a past revision and rewrites it in place. The `<rev>`
154    /// argument accepts: a version id as listed by `zenith history` (e.g. `v2`);
155    /// `@head` or `@head~N` (the current head, or N steps back); `@latest:<name>`
156    /// (the most recent version saved under that name via `zenith version`); or a
157    /// bare version name. Run `zenith history` first to see the available revisions.
158    Restore(RestoreArgs),
159
160    /// Capture the document's current on-disk state into history as an external
161    /// change (e.g. after a GUI edit, hand-edit, or `git checkout`).
162    Sync(SyncArgs),
163
164    /// Generate size/format variants of a document (one design → many sizes).
165    ///
166    /// Expands the `variants` block: one canonical page becomes N named target sizes (square,
167    /// story, banner), each written as a native `.zen` page plus a rendered PNG. Per-variant
168    /// `override`s can hide/show nodes, swap text, or change a fill; source token edits propagate
169    /// to every variant. This varies DIMENSIONS — distinct from `merge`, which varies CONTENT
170    /// across CSV rows. Deterministic: same source → byte-identical outputs.
171    Variant(VariantArgs),
172
173    /// Update the installed `zenith` binary to a published release.
174    Update(UpdateArgs),
175
176    /// Generate design themes (token packs) from brand colours.
177    Theme(ThemeArgs),
178
179    /// Install the Zenith agent skill into AI coding tools (Claude Code, Codex, OpenCode, …).
180    Plugin(PluginArgs),
181
182    /// Run Zenith as an MCP server over stdio (for remote/CI/server agents).
183    ///
184    /// Run Zenith as a Model Context Protocol (MCP) server over stdio, exposing the
185    /// command surface (validate, inspect, tokens, fmt, render, tx, merge, theme) as MCP tools for any
186    /// MCP-aware client.
187    ///
188    /// For a LOCAL agent, prefer installing the CLI and the skill
189    /// (`zenith plugin install`) and running commands directly. This MCP server is for
190    /// environments where a local binary is not suitable (remote, CI, sandboxed, hosted agents) —
191    /// and it is a first-class surface there: tools return trimmed structured results, fetch schema
192    /// detail on demand (`zenith_schema`), hand back large/binary artifacts as resource links, and
193    /// drive the full scratch/candidate/promote/finalize workspace loop by doc-id.
194    /// Defaults to the stdio transport; pass `--http <ADDR>` for native Streamable-HTTP
195    /// (requires the `http` build feature).
196    Mcp(McpArgs),
197
198    /// List fonts available to the renderer — bundled (portable) and local/system.
199    ///
200    /// Discovers fonts in two clearly-separated sections:
201    ///
202    ///   Bundled (portable) — fonts shipped in the binary. Using these keeps
203    ///   renders byte-identical across machines.
204    ///
205    ///   Local / system (this machine only) — fonts in OS font directories.
206    ///   Using these is NOT portable: renders may differ on another machine,
207    ///   and they trip a `font.local` advisory.
208    ///
209    /// Uses the same discovery code as the renderer so there is no drift.
210    /// Scanning reads every system font file on disk, so the command may take a
211    /// moment on machines with many fonts installed (similar to `fc-list`).
212    #[command(after_help = "EXAMPLES:\n  \
213zenith fonts            # human-readable, two-section listing\n  \
214zenith fonts --json     # machine-readable JSON ({ \"schema\": \"zenith-fonts-v1\", ... })")]
215    Fonts(FontsArgs),
216
217    /// Describe the Zenith document schema (node kinds, attributes, tx ops, and non-node surfaces).
218    ///
219    /// Self-describing source of truth for agents and tooling. Reports every
220    /// authorable node kind with its one-line summary and recognized attribute
221    /// names, every transaction op with its summary, and the recognized
222    /// attributes for the non-node authorable surfaces (page, asset, document).
223    /// Attribute types, required-ness, and valid values are enforced at
224    /// document-level by `zenith validate` — run that command for the full
225    /// diagnostic loop.
226    ///
227    /// Subcommands: `nodes` (all kinds), `node <kind>` (one kind + its
228    /// attributes), `ops` (all tx ops), `op <name>` (one op: summary,
229    /// fields, and a working JSON example), `page`, `asset`, `document`
230    /// (non-node surface attributes).
231    /// Bare `zenith schema` prints a short overview with counts and drill-in hints.
232    Schema(SchemaArgs),
233
234    /// Manage workspace-level process state: scratch candidates and their lifecycle.
235    ///
236    /// The workspace subsystem persists design scratch candidates — point-in-time
237    /// `.zen` snapshots that are evaluated and promoted or rejected — alongside
238    /// the durable version history. Use `zenith workspace scratch` to record and
239    /// inspect candidates; use `zenith workspace candidate` to transition their
240    /// lifecycle status (draft → selected | rejected).
241    Workspace(WorkspaceArgs),
242}
243
244/// Arguments for `zenith mcp`.
245#[derive(Debug, Args)]
246#[command(
247    after_help = "Configure your MCP client to launch `zenith mcp` (command: \"zenith\", args: \
248[\"mcp\"]). Logs go to stderr; stdout carries the JSON-RPC protocol."
249)]
250pub struct McpArgs {
251    /// Serve over native Streamable-HTTP at this address (e.g. 127.0.0.1:8080)
252    /// instead of stdio. Requires a build with the `http` feature.
253    #[arg(long, value_name = "ADDR")]
254    pub http: Option<String>,
255}
256
257/// Arguments for `zenith update`.
258#[derive(Debug, Args)]
259pub struct UpdateArgs {
260    /// Install the latest prerelease instead of the latest stable release.
261    #[arg(long)]
262    pub pre: bool,
263
264    /// Install a specific version (e.g. `v0.1.0` or `0.1.0`) instead of the latest.
265    #[arg(long, value_name = "VERSION")]
266    pub version: Option<String>,
267}
268
269/// Arguments for `zenith theme`.
270#[derive(Debug, Args)]
271pub struct ThemeArgs {
272    #[command(subcommand)]
273    pub command: ThemeSub,
274}
275
276/// Subcommands of `zenith theme`.
277#[derive(Debug, Subcommand)]
278pub enum ThemeSub {
279    /// Synthesize a complete theme pack from a primary colour (+ optional roles).
280    ///
281    /// Synthesize a complete theme pack (a token-only .zen) from brand colours.
282    /// Surfaces are tinted toward the primary; each role gets an APCA-correct `.content` pairing for
283    /// WCAG 3 contrast. Captures radius, border, spacing, type, and optional depth/noise — not just
284    /// colour. The output validates clean and can be merged into a document or used as a starting palette.
285    New(ThemeNewArgs),
286}
287
288/// Arguments for `zenith theme new`.
289#[derive(Debug, Args)]
290#[command(after_help = "EXAMPLE:\n  \
291zenith theme new acme --scheme light --primary '#3b5bdb' --accent '#f76707' --out acme.zen\n\n\
292NOTE: quote every hex value — a bare # starts a comment in most shells, so an\n  \
293unquoted --primary #3b5bdb is silently dropped and reads as a missing value.")]
294pub struct ThemeNewArgs {
295    /// Theme name (used in ids and the preview title), e.g. `acme`.
296    pub name: String,
297
298    /// Base scheme: `light` or `dark`.
299    #[arg(long, value_name = "light|dark")]
300    pub scheme: String,
301
302    /// Primary brand colour as `#rrggbb`. Quote it: most shells treat a bare
303    /// `#` as the start of a comment, so write `--primary '#3b5bdb'` (or
304    /// `"#3b5bdb"`) — an unquoted `#3b5bdb` is dropped and this flag will
305    /// appear to have no value.
306    #[arg(long, value_name = "HEX")]
307    pub primary: String,
308
309    /// Secondary colour (default: same as primary).
310    #[arg(long, value_name = "HEX")]
311    pub secondary: Option<String>,
312
313    /// Accent colour (default: same as secondary).
314    #[arg(long, value_name = "HEX")]
315    pub accent: Option<String>,
316
317    /// Neutral colour (default: a tinted grey).
318    #[arg(long, value_name = "HEX")]
319    pub neutral: Option<String>,
320
321    /// Override the info status colour.
322    #[arg(long, value_name = "HEX")]
323    pub info: Option<String>,
324
325    /// Override the success status colour.
326    #[arg(long, value_name = "HEX")]
327    pub success: Option<String>,
328
329    /// Override the warning status colour.
330    #[arg(long, value_name = "HEX")]
331    pub warning: Option<String>,
332
333    /// Override the error status colour.
334    #[arg(long, value_name = "HEX")]
335    pub error: Option<String>,
336
337    /// Box/card corner radius in px (default 16).
338    #[arg(long, value_name = "PX", default_value_t = 16.0)]
339    pub radius_box: f64,
340
341    /// Field/button corner radius in px (default 8).
342    #[arg(long, value_name = "PX", default_value_t = 8.0)]
343    pub radius_field: f64,
344
345    /// Selector/badge corner radius in px (default 8).
346    #[arg(long, value_name = "PX", default_value_t = 8.0)]
347    pub radius_selector: f64,
348
349    /// Default border width in px (default 1).
350    #[arg(long, value_name = "PX", default_value_t = 1.0)]
351    pub border: f64,
352
353    /// Emit a `shadow.depth` elevation token (raised look).
354    #[arg(long)]
355    pub depth: bool,
356
357    /// Mark the theme as wanting a grain overlay (recorded in the header).
358    #[arg(long)]
359    pub noise: bool,
360
361    /// Write to this path instead of stdout.
362    #[arg(long, value_name = "FILE")]
363    pub out: Option<PathBuf>,
364}
365
366/// Arguments for `zenith variant`.
367#[derive(Debug, Args)]
368#[command(after_help = "EXAMPLE:\n  \
369zenith variant poster.zen --out-dir out/ --manifest run.json\n\n\
370The document must contain a `variants { variant id=\"square\" source=\"page.main\" w=(px)1080 \
371h=(px)1080 { … } }` block.")]
372pub struct VariantArgs {
373    /// Input `.zen` document containing a `variants` block.
374    pub doc: PathBuf,
375
376    /// Directory to write one `.zen` + one `.png` per generated variant into.
377    #[arg(long, value_name = "DIR")]
378    pub out_dir: PathBuf,
379
380    /// Emit a machine-readable JSON batch report (per-variant provenance).
381    #[arg(long)]
382    pub json: bool,
383
384    /// Write a deterministic generation manifest (JSON) to this path for CI
385    /// reproducibility.  Independent of --json.
386    #[arg(long, value_name = "PATH")]
387    pub manifest: Option<PathBuf>,
388}
389
390/// Arguments for `zenith new`.
391#[derive(Debug, Args)]
392#[command(after_help = "EXAMPLE:\n  zenith new poster.zen --name \"Launch Poster\"")]
393pub struct NewArgs {
394    /// Path to create the new document at (must not already exist). A `.zen`
395    /// extension is appended if absent, and missing parent directories are created.
396    pub path: PathBuf,
397
398    /// Display name for the document (used in ids and the title). Defaults to
399    /// "Untitled"; the id slug is derived from this, else from the file stem.
400    #[arg(long, value_name = "NAME")]
401    pub name: Option<String>,
402}
403
404/// Arguments for `zenith validate`.
405#[derive(Debug, Args)]
406#[command(after_help = "EXAMPLE:\n  zenith validate poster.zen --json")]
407pub struct ValidateArgs {
408    /// Path to the `.zen` document.
409    pub path: PathBuf,
410
411    /// Emit machine-readable JSON instead of a human-readable table.
412    #[arg(long)]
413    pub json: bool,
414
415    /// Suppress a diagnostic code (downgrade Warning/Advisory to nothing).
416    ///
417    /// Repeatable. Overrides the document's in-file `diagnostics` block and any
418    /// global/local config policy for this code. Error-severity diagnostics are
419    /// immutable and never suppressed.
420    #[arg(long = "allow", value_name = "CODE", action = clap::ArgAction::Append)]
421    pub allow: Vec<String>,
422
423    /// Force a diagnostic code to Warning severity.
424    ///
425    /// Repeatable. Overrides the document's in-file `diagnostics` block and any
426    /// global/local config policy for this code.
427    #[arg(long = "warn", value_name = "CODE", action = clap::ArgAction::Append)]
428    pub warn: Vec<String>,
429
430    /// Elevate a diagnostic code to a blocking Error (CI gate).
431    ///
432    /// Repeatable. Overrides the document's in-file `diagnostics` block and any
433    /// global/local config policy for this code.
434    #[arg(long = "deny", value_name = "CODE", action = clap::ArgAction::Append)]
435    pub deny: Vec<String>,
436}
437
438/// Arguments for `zenith fmt`.
439#[derive(Debug, Args)]
440pub struct FmtArgs {
441    /// Path to the `.zen` document (written in-place).
442    pub path: PathBuf,
443
444    /// Emit machine-readable JSON reporting `changed` and `hash`.
445    #[arg(long)]
446    pub json: bool,
447}
448
449/// Arguments for `zenith fonts`.
450#[derive(Debug, Args)]
451pub struct FontsArgs {
452    /// Emit machine-readable JSON instead of a human-readable listing.
453    #[arg(long)]
454    pub json: bool,
455}
456
457/// Arguments for `zenith tokens`.
458#[derive(Debug, Args)]
459#[command(after_help = "EXAMPLE:\n  zenith tokens poster.zen --json")]
460pub struct TokensArgs {
461    /// Path to the `.zen` document.
462    pub path: PathBuf,
463
464    /// Emit machine-readable JSON instead of a human-readable table.
465    #[arg(long)]
466    pub json: bool,
467}
468
469/// Arguments for `zenith tx`.
470#[derive(Debug, Args)]
471#[command(after_help = "TRANSACTION FILE FORMAT:\n  \
472A tx file is a JSON object with a single \"ops\" array; ops are applied in order:\n\n  \
473    {\"ops\":[\n      \
474{\"op\":\"set_text_align\",\"node\":\"text.hello\",\"align\":\"center\"},\n      \
475{\"op\":\"set_fill\",\"node\":\"hero\",\"fill\":\"color.brand\"}\n    \
476]}\n\n\
477DISCOVERING OPS:\n  \
478zenith schema op set_fill          # fields, types, and a working example\n  \
479zenith schema op add_node          # how to insert a new node from .zen source\n  \
480zenith schema ops                  # list all 40 available ops with summaries\n  \
481See examples/*.tx.json for runnable samples.\n\n\
482EXAMPLES:\n  \
483zenith tx poster.zen edits.json            # preview the diff (dry-run)\n  \
484zenith tx poster.zen edits.json --apply    # write the change to disk")]
485pub struct TxArgs {
486    /// Path to the `.zen` document.
487    pub path: PathBuf,
488
489    /// Path to the transaction JSON file.
490    pub tx_file: PathBuf,
491
492    /// Apply the result back to disk (dry-run by default).
493    #[arg(long)]
494    pub apply: bool,
495
496    /// Emit machine-readable JSON instead of a human-readable summary.
497    #[arg(long)]
498    pub json: bool,
499}
500
501/// Arguments for `zenith inspect`.
502#[derive(Debug, Args)]
503#[command(after_help = "EXAMPLE:\n  zenith inspect poster.zen --node hero --json")]
504pub struct InspectArgs {
505    /// Path to the `.zen` document.
506    pub path: PathBuf,
507
508    /// Inspect only the subtree rooted at this node id.
509    #[arg(long, value_name = "ID")]
510    pub node: Option<String>,
511
512    /// Emit machine-readable JSON instead of a human-readable tree.
513    #[arg(long)]
514    pub json: bool,
515}
516
517/// Arguments for `zenith merge`.
518#[derive(Debug, Args)]
519#[command(after_help = "EXAMPLE:\n  \
520zenith merge card.zen people.csv --out-dir out/ --name-by name --manifest run.json")]
521pub struct MergeArgs {
522    /// Template `.zen` document with `role="data.<column>"` text nodes.
523    pub doc: PathBuf,
524
525    /// CSV data file; header row names the columns.
526    pub data: PathBuf,
527
528    /// Directory to write one PNG per row into.
529    #[arg(long, value_name = "DIR")]
530    pub out_dir: PathBuf,
531
532    /// CSV column to name each output file by (default: row-NNNN.png).
533    #[arg(long, value_name = "COL")]
534    pub name_by: Option<String>,
535
536    /// Emit a machine-readable JSON batch report (per-row provenance).
537    #[arg(long)]
538    pub json: bool,
539
540    /// Write a deterministic generation manifest (JSON) to this path for CI
541    /// reproducibility. Independent of --json.
542    #[arg(long, value_name = "PATH")]
543    pub manifest: Option<PathBuf>,
544}
545
546/// Arguments for `zenith history`.
547#[derive(Debug, Args)]
548pub struct HistoryArgs {
549    /// Path to the `.zen` document.
550    pub path: PathBuf,
551
552    /// Emit machine-readable JSON instead of a human-readable listing.
553    #[arg(long)]
554    pub json: bool,
555}
556
557/// Arguments for `zenith undo`.
558#[derive(Debug, Args)]
559pub struct UndoArgs {
560    /// Path to the `.zen` document (rewritten in place).
561    pub path: PathBuf,
562}
563
564/// Arguments for `zenith redo`.
565#[derive(Debug, Args)]
566pub struct RedoArgs {
567    /// Path to the `.zen` document (rewritten in place).
568    pub path: PathBuf,
569}
570
571/// Arguments for `zenith version`.
572#[derive(Debug, Args)]
573pub struct VersionArgs {
574    /// Path to the `.zen` document.
575    pub path: PathBuf,
576    /// Name for this version (a named version is retained indefinitely).
577    pub name: String,
578}
579
580/// Arguments for `zenith restore`.
581#[derive(Debug, Args)]
582pub struct RestoreArgs {
583    /// Path to the `.zen` document.
584    pub path: PathBuf,
585    /// Revision spec (e.g. a version id `v2`, `@head~1`, `@latest:named`, or a name).
586    pub rev: String,
587}
588
589/// Arguments for `zenith sync`.
590#[derive(Debug, Args)]
591pub struct SyncArgs {
592    /// Path to the `.zen` document.
593    pub path: PathBuf,
594}