use serde_json::{Value, json};
pub struct Tool {
pub name: &'static str,
pub description: &'static str,
pub schema: Value,
}
fn doc_arg() -> Value {
json!({ "type": "string", "description": "Document path or 26-char doc-id." })
}
pub fn catalog() -> Vec<Tool> {
vec![
Tool {
name: "zenith_schema",
description: "Look up the document schema ON DEMAND: node kinds, a node's attributes, \
tx ops, or one op's fields + a ready-to-edit JSON example. Use surface=variant to discover the \
variants block and override entry structure (override/variant are NOT node kinds). \
Call this before building a zenith_tx instead of guessing.",
schema: json!({
"type": "object",
"properties": {
"surface": {
"type": "string",
"enum": ["overview", "nodes", "node", "ops", "op", "tokens", "token", "page", "asset", "document", "variant", "diagnostics"],
"description": "Which schema surface to fetch."
},
"name": { "type": "string", "description": "Node kind (surface=node), op name (surface=op), or token type e.g. \"gradient\" (surface=token)." }
},
"required": ["surface"]
}),
},
Tool {
name: "zenith_fonts",
description: "List the font families available to the renderer: Bundled (portable, \
deterministic across machines) vs Local/system (this machine only — using one emits a `font.local` \
advisory). Use a bundled family for reproducible output.",
schema: json!({
"type": "object",
"properties": {}
}),
},
Tool {
name: "zenith_validate",
description: "Validate a .zen document. Returns counts + the blocking errors only; \
hard (Error) diagnostics block rendering — fix them first. Set severity to also see warnings/advisories.",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"severity": {
"type": "string",
"enum": ["error", "warning", "advisory"],
"description": "Lowest severity to include in the diagnostics array (default: error)."
}
},
"required": ["doc"]
}),
},
Tool {
name: "zenith_inspect",
description: "Discover node ids and structure (read-only). Returns a shallow tree by \
default (deeper levels collapse to child_count). Use node/depth to drill in, detail for geometry. \
Large trees are returned as a resource link.",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"node": { "type": "string", "description": "Only the subtree at this node id." },
"depth": { "type": "integer", "minimum": 0, "description": "Levels to expand (default 1)." },
"detail": { "type": "boolean", "description": "Include geometry/visible/locked per node." }
},
"required": ["doc"]
}),
},
Tool {
name: "zenith_tokens",
description: "List every design token and its resolved value. Visual properties must \
reference tokens, so this reveals the palette/type/spacing a document exposes.",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"diagnostics": { "type": "boolean", "description": "Include token diagnostics (default false)." }
},
"required": ["doc"]
}),
},
Tool {
name: "zenith_tx",
description: "Apply a typed transaction (JSON edit script) to a document. Dry-run by \
default (returns status + affected ids); set apply=true to write. Set diff=true to also get the \
resulting (after) source as a resource link. Enforces id-uniqueness and referential integrity. \
Use zenith_schema op to learn an op's shape.",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"transaction": {
"description": "The transaction as a JSON object, array, or string.",
"type": ["object", "string", "array"]
},
"apply": { "type": "boolean", "description": "Write the result to disk." },
"diff": { "type": "boolean", "description": "Return a resource link to the before→after diff." }
},
"required": ["doc", "transaction"]
}),
},
Tool {
name: "zenith_render",
description: "Render a document deterministically to png, pdf, or scene (display-list \
JSON). Returns a resource link to the artifact (never inlines bytes); blocked by hard diagnostics \
— validate first. Pass out to also write the file to a path you choose.",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"format": { "type": "string", "enum": ["png", "pdf", "scene"] },
"page": { "type": "integer", "minimum": 1, "description": "1-based page (default 1)." },
"locked": { "type": "boolean", "description": "Verify asset sha256 and fail on mismatch." },
"out": { "type": "string", "description": "Optional path to also write the artifact to." },
"diagnostics": { "type": "boolean", "description": "Include soft diagnostics (default false)." }
},
"required": ["doc", "format"]
}),
},
Tool {
name: "zenith_fmt",
description: "Canonicalize a document in place (idempotent). Returns whether the file \
changed and its content hash.",
schema: json!({
"type": "object",
"properties": { "doc": doc_arg() },
"required": ["doc"]
}),
},
Tool {
name: "zenith_merge",
description: "Mail-merge a .zen template with a CSV, writing one PNG per row. Mark \
variable nodes with role=\"data.<column>\". Use for localized/personalized/batch variants.",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"data": { "type": "string", "description": "CSV data file path." },
"out_dir": { "type": "string", "description": "Directory for the output PNGs." },
"name_by": { "type": "string", "description": "CSV column to name files by." },
"manifest": { "type": "string", "description": "Write a reproducibility manifest here." }
},
"required": ["doc", "data", "out_dir"]
}),
},
Tool {
name: "zenith_theme_new",
description: "Synthesize a complete token-only theme pack (.zen) from brand colours, \
with APCA-correct content pairings for WCAG 3 contrast. Returns the generated source; pass out to \
write it to a file instead.",
schema: theme_schema(),
},
Tool {
name: "zenith_workspace_scratch",
description: "Manage scratch candidates — point-in-time .zen snapshots that keep design \
iteration out of the deliverable file. op=new snapshots the current doc; op=list/show enumerate. \
Each candidate is addressable as a resource.",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"op": { "type": "string", "enum": ["new", "list", "show"] },
"page": { "type": "string", "description": "Page id this candidate captures (default whole doc)." },
"candidate_id": { "type": "string", "description": "Candidate id (for op=show)." },
"status": { "type": "string", "enum": ["draft", "selected", "rejected"], "description": "Initial status for op=new." },
"notes": { "type": "string" },
"workspace_role": { "type": "string", "description": "e.g. hero, fallback." },
"promotion_target": { "type": "string" },
"cleanup_policy": { "type": "string", "description": "e.g. delete." }
},
"required": ["doc", "op"]
}),
},
Tool {
name: "zenith_workspace_candidate",
description: "Transition a scratch candidate's lifecycle: draft → selected | rejected. \
Only a selected candidate can be promoted.",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"candidate_id": { "type": "string" },
"status": { "type": "string", "enum": ["draft", "selected", "rejected"] }
},
"required": ["doc", "candidate_id", "status"]
}),
},
Tool {
name: "zenith_workspace_promote",
description: "Promote a selected candidate's page into a target page of the deliverable \
document (deep-copies, suffixes ids, validates, writes in place).",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"candidate_id": { "type": "string" },
"target_page": { "type": "string", "description": "Destination page id." },
"id_suffix": { "type": "string", "description": "Suffix appended to cloned ids (default .promoted)." }
},
"required": ["doc", "candidate_id", "target_page"]
}),
},
Tool {
name: "zenith_workspace_finalize",
description: "op=finalize cleans up rejected candidates per cleanup-policy. op=bundle \
packs the doc's whole session store into a portable .zenithbundle (returns a resource link). \
op=unbundle restores one from a bundle path. Read any preview resources BEFORE finalizing.",
schema: json!({
"type": "object",
"properties": {
"doc": doc_arg(),
"op": { "type": "string", "enum": ["finalize", "bundle", "unbundle"] },
"bundle": { "type": "string", "description": "Bundle file path (out for op=bundle, in for op=unbundle)." }
},
"required": ["op"]
}),
},
]
}
fn theme_schema() -> Value {
json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"scheme": { "type": "string", "enum": ["light", "dark"] },
"primary": { "type": "string", "description": "#rrggbb" },
"secondary": { "type": "string" },
"accent": { "type": "string" },
"neutral": { "type": "string" },
"info": { "type": "string" },
"success": { "type": "string" },
"warning": { "type": "string" },
"error": { "type": "string" },
"radius_box": { "type": "number" },
"radius_field": { "type": "number" },
"radius_selector": { "type": "number" },
"border": { "type": "number" },
"depth": { "type": "boolean" },
"noise": { "type": "boolean" },
"out": { "type": "string", "description": "Write the theme here instead of returning the source." }
},
"required": ["name", "scheme", "primary"]
})
}
pub fn list_payload() -> Value {
let tools: Vec<Value> = catalog()
.into_iter()
.map(|t| {
json!({
"name": t.name,
"description": t.description,
"inputSchema": t.schema,
})
})
.collect();
json!({ "tools": tools })
}