mod library;
mod plugin;
mod render;
mod schema;
mod workspace;
pub use library::{LibraryAddArgs, LibraryArgs, LibraryListArgs, LibraryShowArgs, LibrarySub};
pub use plugin::{
AgentFlags, PluginArgs, PluginInstallArgs, PluginSub, PluginUninstallArgs, ScopeArg,
};
pub use render::RenderArgs;
pub use schema::{SchemaArgs, SchemaSub};
pub use workspace::{
CandidateArgs, PromoteArgs, ScratchArgs, ScratchListArgs, ScratchNewArgs, ScratchShowArgs,
ScratchSub, WorkspaceArgs, WorkspaceSub,
};
use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;
#[derive(Debug, Parser)]
#[command(
name = "zenith",
version,
about = "Author, validate, and render deterministic .zen design documents (KDL → PNG/PDF).",
long_about = "Zenith turns a design into plain-text .zen source (KDL) you can read, diff, \
validate, edit with typed transactions, and render deterministically to pixel-exact PNG or \
print-ready PDF — the opposite of a flat AI image.\n\n\
The core loop: author/edit source → `validate` → `render` to inspect → iterate. Edits to \
existing documents should go through `tx` (typed, dry-run by default). Every command accepts \
`--json` for machine-readable output; run `zenith <command> --help` for exact flags.",
after_help = "QUICK START:\n \
zenith validate poster.zen --json # check for hard diagnostics\n \
zenith render poster.zen --png out.png # render a page to inspect\n \
zenith theme new acme --scheme light --primary '#3b5bdb' --out acme.zen\n \
zenith plugin install --claude # teach your AI agent to use zenith\n\n\
Run `zenith <command> --help` for details on any command."
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
New(NewArgs),
Validate(ValidateArgs),
Fmt(FmtArgs),
Tokens(TokensArgs),
Render(RenderArgs),
Tx(TxArgs),
Inspect(InspectArgs),
Merge(MergeArgs),
Library(LibraryArgs),
History(HistoryArgs),
Undo(UndoArgs),
Redo(RedoArgs),
Version(VersionArgs),
Restore(RestoreArgs),
Sync(SyncArgs),
Variant(VariantArgs),
Update(UpdateArgs),
Theme(ThemeArgs),
Plugin(PluginArgs),
Mcp(McpArgs),
#[command(after_help = "EXAMPLES:\n \
zenith fonts # human-readable, two-section listing\n \
zenith fonts --json # machine-readable JSON ({ \"schema\": \"zenith-fonts-v1\", ... })")]
Fonts(FontsArgs),
Schema(SchemaArgs),
Workspace(WorkspaceArgs),
}
#[derive(Debug, Args)]
#[command(
after_help = "Configure your MCP client to launch `zenith mcp` (command: \"zenith\", args: \
[\"mcp\"]). Logs go to stderr; stdout carries the JSON-RPC protocol."
)]
pub struct McpArgs {
#[arg(long, value_name = "ADDR")]
pub http: Option<String>,
}
#[derive(Debug, Args)]
pub struct UpdateArgs {
#[arg(long)]
pub pre: bool,
#[arg(long, value_name = "VERSION")]
pub version: Option<String>,
}
#[derive(Debug, Args)]
pub struct ThemeArgs {
#[command(subcommand)]
pub command: ThemeSub,
}
#[derive(Debug, Subcommand)]
pub enum ThemeSub {
New(ThemeNewArgs),
}
#[derive(Debug, Args)]
#[command(after_help = "EXAMPLE:\n \
zenith theme new acme --scheme light --primary '#3b5bdb' --accent '#f76707' --out acme.zen\n\n\
NOTE: quote every hex value — a bare # starts a comment in most shells, so an\n \
unquoted --primary #3b5bdb is silently dropped and reads as a missing value.")]
pub struct ThemeNewArgs {
pub name: String,
#[arg(long, value_name = "light|dark")]
pub scheme: String,
#[arg(long, value_name = "HEX")]
pub primary: String,
#[arg(long, value_name = "HEX")]
pub secondary: Option<String>,
#[arg(long, value_name = "HEX")]
pub accent: Option<String>,
#[arg(long, value_name = "HEX")]
pub neutral: Option<String>,
#[arg(long, value_name = "HEX")]
pub info: Option<String>,
#[arg(long, value_name = "HEX")]
pub success: Option<String>,
#[arg(long, value_name = "HEX")]
pub warning: Option<String>,
#[arg(long, value_name = "HEX")]
pub error: Option<String>,
#[arg(long, value_name = "PX", default_value_t = 16.0)]
pub radius_box: f64,
#[arg(long, value_name = "PX", default_value_t = 8.0)]
pub radius_field: f64,
#[arg(long, value_name = "PX", default_value_t = 8.0)]
pub radius_selector: f64,
#[arg(long, value_name = "PX", default_value_t = 1.0)]
pub border: f64,
#[arg(long)]
pub depth: bool,
#[arg(long)]
pub noise: bool,
#[arg(long, value_name = "FILE")]
pub out: Option<PathBuf>,
}
#[derive(Debug, Args)]
#[command(after_help = "EXAMPLE:\n \
zenith variant poster.zen --out-dir out/ --manifest run.json\n\n\
The document must contain a `variants { variant id=\"square\" source=\"page.main\" w=(px)1080 \
h=(px)1080 { … } }` block.")]
pub struct VariantArgs {
pub doc: PathBuf,
#[arg(long, value_name = "DIR")]
pub out_dir: PathBuf,
#[arg(long)]
pub json: bool,
#[arg(long, value_name = "PATH")]
pub manifest: Option<PathBuf>,
}
#[derive(Debug, Args)]
#[command(after_help = "EXAMPLE:\n zenith new poster.zen --name \"Launch Poster\"")]
pub struct NewArgs {
pub path: PathBuf,
#[arg(long, value_name = "NAME")]
pub name: Option<String>,
}
#[derive(Debug, Args)]
#[command(after_help = "EXAMPLE:\n zenith validate poster.zen --json")]
pub struct ValidateArgs {
pub path: PathBuf,
#[arg(long)]
pub json: bool,
#[arg(long = "allow", value_name = "CODE", action = clap::ArgAction::Append)]
pub allow: Vec<String>,
#[arg(long = "warn", value_name = "CODE", action = clap::ArgAction::Append)]
pub warn: Vec<String>,
#[arg(long = "deny", value_name = "CODE", action = clap::ArgAction::Append)]
pub deny: Vec<String>,
}
#[derive(Debug, Args)]
pub struct FmtArgs {
pub path: PathBuf,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Args)]
pub struct FontsArgs {
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Args)]
#[command(after_help = "EXAMPLE:\n zenith tokens poster.zen --json")]
pub struct TokensArgs {
pub path: PathBuf,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Args)]
#[command(after_help = "TRANSACTION FILE FORMAT:\n \
A tx file is a JSON object with a single \"ops\" array; ops are applied in order:\n\n \
{\"ops\":[\n \
{\"op\":\"set_text_align\",\"node\":\"text.hello\",\"align\":\"center\"},\n \
{\"op\":\"set_fill\",\"node\":\"hero\",\"fill\":\"color.brand\"}\n \
]}\n\n\
DISCOVERING OPS:\n \
zenith schema op set_fill # fields, types, and a working example\n \
zenith schema op add_node # how to insert a new node from .zen source\n \
zenith schema ops # list all 40 available ops with summaries\n \
See examples/*.tx.json for runnable samples.\n\n\
EXAMPLES:\n \
zenith tx poster.zen edits.json # preview the diff (dry-run)\n \
zenith tx poster.zen edits.json --apply # write the change to disk")]
pub struct TxArgs {
pub path: PathBuf,
pub tx_file: PathBuf,
#[arg(long)]
pub apply: bool,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Args)]
#[command(after_help = "EXAMPLE:\n zenith inspect poster.zen --node hero --json")]
pub struct InspectArgs {
pub path: PathBuf,
#[arg(long, value_name = "ID")]
pub node: Option<String>,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Args)]
#[command(after_help = "EXAMPLE:\n \
zenith merge card.zen people.csv --out-dir out/ --name-by name --manifest run.json")]
pub struct MergeArgs {
pub doc: PathBuf,
pub data: PathBuf,
#[arg(long, value_name = "DIR")]
pub out_dir: PathBuf,
#[arg(long, value_name = "COL")]
pub name_by: Option<String>,
#[arg(long)]
pub json: bool,
#[arg(long, value_name = "PATH")]
pub manifest: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct HistoryArgs {
pub path: PathBuf,
#[arg(long)]
pub json: bool,
}
#[derive(Debug, Args)]
pub struct UndoArgs {
pub path: PathBuf,
}
#[derive(Debug, Args)]
pub struct RedoArgs {
pub path: PathBuf,
}
#[derive(Debug, Args)]
pub struct VersionArgs {
pub path: PathBuf,
pub name: String,
}
#[derive(Debug, Args)]
pub struct RestoreArgs {
pub path: PathBuf,
pub rev: String,
}
#[derive(Debug, Args)]
pub struct SyncArgs {
pub path: PathBuf,
}