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}