1mod bisect;
21mod drift;
22mod merge;
23mod naming;
24mod regen;
25mod typify_probe;
26
27use anyhow::{bail, Context, Result};
28use serde_json::{Map, Value};
29use std::path::{Path, PathBuf};
30
31pub(crate) const PROTOCOL_SCHEMA_PATH: &str =
33 "crates/shared/codex-app-server-client/schema/protocol.schema.json";
34pub(crate) const METHODS_JSON_PATH: &str =
36 "crates/shared/codex-app-server-client/schema/methods.json";
37pub(crate) const CODEX_VERSION_PATH: &str =
40 "crates/shared/codex-app-server-client/schema/CODEX_VERSION.txt";
41
42pub(crate) const MASTER_BUNDLE_FILE: &str = "codex_app_server_protocol.schemas.json";
48pub(crate) const V2_BUNDLE_FILE: &str = "codex_app_server_protocol.v2.schemas.json";
51
52pub fn run(args: &[String]) -> Result<()> {
53 match args.first().map(String::as_str) {
54 Some("regen") => regen::run(&args[1..]),
55 Some("bisect") => bisect::run(&args[1..]),
56 Some("drift") => drift::run(&args[1..]),
57 Some("--help") | Some("-h") | Some("help") | None => {
58 print_help();
59 Ok(())
60 }
61 Some(unknown) => bail!(
62 "Unknown codex-schema subcommand: {unknown:?}\nRun `cargo xtask codex-schema --help` for usage."
63 ),
64 }
65}
66
67fn print_help() {
68 eprintln!(
69 "cargo xtask codex-schema — codex-app-server-client schema tooling
70
71USAGE:
72 cargo xtask codex-schema <subcommand> <path-to-codex-generate-json-schema-output-dir>
73
74SUBCOMMANDS:
75 regen <dir> Rebuild schema/protocol.schema.json + schema/methods.json from
76 a `codex app-server generate-json-schema --out <dir> --experimental`
77 dump, and stamp schema/CODEX_VERSION.txt with `codex --version`.
78 bisect <dir> Binary-search a fresh schema dump for the definition(s) that
79 panic typify's schema-merge logic (see README.md).
80 drift [--dir <dir>] [--json] [--strict]
81 Diff the vendored schema/methods.json against a fresh (or
82 --dir'd) `codex app-server generate-json-schema` dump and
83 report added/removed/changed methods per section. Missing
84 `codex` on PATH (with no --dir) is never a hard failure - it
85 prints \"skipped\" and exits 0. Run `cargo xtask codex-schema
86 drift --help` for the full flag reference.
87 help Show this help
88
89Typical workflow after upgrading the installed `codex` CLI:
90 codex app-server generate-json-schema --out /tmp/codex-schema --experimental
91 cargo xtask codex-schema drift --dir /tmp/codex-schema # see what changed
92 cargo xtask codex-schema regen /tmp/codex-schema
93 cargo build -p codex-app-server-client --all-targets
94 cargo clippy -p codex-app-server-client --all-targets -- -D warnings
95 cargo test -p codex-app-server-client
96
97If that panics inside typify, bisect it:
98 cargo xtask codex-schema bisect /tmp/codex-schema"
99 );
100}
101
102pub(crate) fn read_json(path: &Path) -> Result<Value> {
104 let text = std::fs::read_to_string(path)
105 .with_context(|| format!("failed to read {}", path.display()))?;
106 serde_json::from_str(&text)
107 .with_context(|| format!("failed to parse {} as JSON", path.display()))
108}
109
110pub(crate) fn parse_gen_dir(args: &[String], usage: &str) -> Result<PathBuf> {
117 let mut gen_dir = None;
118 for arg in args {
119 match arg.as_str() {
120 "--help" | "-h" => {
121 println!("{usage}");
122 std::process::exit(0);
123 }
124 other if gen_dir.is_none() => gen_dir = Some(PathBuf::from(other)),
125 other => bail!("unexpected argument: {other}"),
126 }
127 }
128 gen_dir.context(usage.to_string())
129}
130
131pub(crate) fn load_combined_defs(gen_dir: &Path) -> Result<(Value, Map<String, Value>)> {
136 let master = read_json(&gen_dir.join(MASTER_BUNDLE_FILE))?;
137 let v2 = read_json(&gen_dir.join(V2_BUNDLE_FILE))?;
138
139 let combined = merge::build_combined(&master, &v2)?;
140 let combined_defs = combined
141 .get("definitions")
142 .and_then(Value::as_object)
143 .context("combined schema missing \"definitions\"")?
144 .clone();
145
146 Ok((combined, combined_defs))
147}