mirror of
https://github.com/openai/codex.git
synced 2026-11-02 19:43:24 +00:00
hooks: add rust-owned schema fixtures
This commit is contained in:
Generated
+1
@@ -2040,6 +2040,7 @@ dependencies = [
|
||||
"codex-protocol",
|
||||
"futures",
|
||||
"pretty_assertions",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
|
||||
@@ -322,6 +322,9 @@
|
||||
"codex_git_commit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"codex_hooks": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"collab": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -1712,6 +1715,9 @@
|
||||
"codex_git_commit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"codex_hooks": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"collab": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -90,6 +90,8 @@ pub enum Feature {
|
||||
ApplyPatchFreeform,
|
||||
/// Allow requesting additional filesystem permissions while staying sandboxed.
|
||||
RequestPermissions,
|
||||
/// Enable Claude-style lifecycle hooks loaded from hooks.json files.
|
||||
CodexHooks,
|
||||
/// Allow the model to request web searches that fetch live content.
|
||||
WebSearchRequest,
|
||||
/// Allow the model to request web searches that fetch cached content.
|
||||
@@ -541,6 +543,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::CodexHooks,
|
||||
key: "codex_hooks",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::UseLinuxSandboxBwrap,
|
||||
key: "use_linux_sandbox_bwrap",
|
||||
|
||||
@@ -13,15 +13,16 @@ path = "src/lib.rs"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-protocol = { workspace = true }
|
||||
futures = { workspace = true, features = ["alloc"] }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = ["process"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"NullableString": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"hook_event_name": {
|
||||
"const": "SessionStart",
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"permission_mode": {
|
||||
"enum": [
|
||||
"default",
|
||||
"acceptEdits",
|
||||
"plan",
|
||||
"dontAsk",
|
||||
"bypassPermissions"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"enum": [
|
||||
"startup",
|
||||
"resume",
|
||||
"clear"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"transcript_path": {
|
||||
"$ref": "#/definitions/NullableString"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"hook_event_name",
|
||||
"model",
|
||||
"permission_mode",
|
||||
"session_id",
|
||||
"source",
|
||||
"transcript_path"
|
||||
],
|
||||
"title": "session-start.command.input",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"HookEventNameWire": {
|
||||
"enum": [
|
||||
"SessionStart",
|
||||
"Stop"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SessionStartHookSpecificOutputWire": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"additionalContext": {
|
||||
"default": null,
|
||||
"type": "string"
|
||||
},
|
||||
"hookEventName": {
|
||||
"$ref": "#/definitions/HookEventNameWire"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"hookEventName"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"continue": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"hookSpecificOutput": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/SessionStartHookSpecificOutputWire"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"stopReason": {
|
||||
"default": null,
|
||||
"type": "string"
|
||||
},
|
||||
"suppressOutput": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"systemMessage": {
|
||||
"default": null,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "session-start.command.output",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"NullableString": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"hook_event_name": {
|
||||
"const": "Stop",
|
||||
"type": "string"
|
||||
},
|
||||
"last_assistant_message": {
|
||||
"$ref": "#/definitions/NullableString"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"permission_mode": {
|
||||
"enum": [
|
||||
"default",
|
||||
"acceptEdits",
|
||||
"plan",
|
||||
"dontAsk",
|
||||
"bypassPermissions"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"stop_hook_active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"transcript_path": {
|
||||
"$ref": "#/definitions/NullableString"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"cwd",
|
||||
"hook_event_name",
|
||||
"last_assistant_message",
|
||||
"model",
|
||||
"permission_mode",
|
||||
"session_id",
|
||||
"stop_hook_active",
|
||||
"transcript_path"
|
||||
],
|
||||
"title": "stop.command.input",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"additionalProperties": false,
|
||||
"definitions": {
|
||||
"StopDecisionWire": {
|
||||
"enum": [
|
||||
"block"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"continue": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"decision": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/StopDecisionWire"
|
||||
}
|
||||
],
|
||||
"default": null
|
||||
},
|
||||
"reason": {
|
||||
"default": null,
|
||||
"type": "string"
|
||||
},
|
||||
"stopReason": {
|
||||
"default": null,
|
||||
"type": "string"
|
||||
},
|
||||
"suppressOutput": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"systemMessage": {
|
||||
"default": null,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "stop.command.output",
|
||||
"type": "object"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
let schema_root = std::env::args_os()
|
||||
.nth(1)
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("schema"));
|
||||
codex_hooks::write_schema_fixtures(&schema_root)
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
mod registry;
|
||||
mod schema;
|
||||
mod types;
|
||||
mod user_notification;
|
||||
|
||||
pub use registry::Hooks;
|
||||
pub use registry::HooksConfig;
|
||||
pub use registry::command_from_argv;
|
||||
pub use schema::write_schema_fixtures;
|
||||
pub use types::Hook;
|
||||
pub use types::HookEvent;
|
||||
pub use types::HookEventAfterAgent;
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
use schemars::JsonSchema;
|
||||
use schemars::r#gen::SchemaGenerator;
|
||||
use schemars::r#gen::SchemaSettings;
|
||||
use schemars::schema::InstanceType;
|
||||
use schemars::schema::RootSchema;
|
||||
use schemars::schema::Schema;
|
||||
use schemars::schema::SchemaObject;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Map;
|
||||
use serde_json::Value;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
const GENERATED_DIR: &str = "generated";
|
||||
const SESSION_START_INPUT_FIXTURE: &str = "session-start.command.input.schema.json";
|
||||
const SESSION_START_OUTPUT_FIXTURE: &str = "session-start.command.output.schema.json";
|
||||
const STOP_INPUT_FIXTURE: &str = "stop.command.input.schema.json";
|
||||
const STOP_OUTPUT_FIXTURE: &str = "stop.command.output.schema.json";
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub(crate) struct NullableString(Option<String>);
|
||||
|
||||
impl NullableString {
|
||||
fn from_path(path: Option<PathBuf>) -> Self {
|
||||
Self(path.map(|path| path.display().to_string()))
|
||||
}
|
||||
|
||||
fn from_string(value: Option<String>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for NullableString {
|
||||
fn schema_name() -> String {
|
||||
"NullableString".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut SchemaGenerator) -> Schema {
|
||||
Schema::Object(SchemaObject {
|
||||
instance_type: Some(vec![InstanceType::String, InstanceType::Null].into()),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub(crate) struct HookUniversalOutputWire {
|
||||
#[serde(default = "default_continue")]
|
||||
pub r#continue: bool,
|
||||
#[serde(default)]
|
||||
pub stop_reason: Option<String>,
|
||||
#[serde(default)]
|
||||
pub suppress_output: bool,
|
||||
#[serde(default)]
|
||||
pub system_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub(crate) enum HookEventNameWire {
|
||||
#[serde(rename = "SessionStart")]
|
||||
SessionStart,
|
||||
#[serde(rename = "Stop")]
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(rename = "session-start.command.output")]
|
||||
pub(crate) struct SessionStartCommandOutputWire {
|
||||
#[serde(flatten)]
|
||||
pub universal: HookUniversalOutputWire,
|
||||
#[serde(default)]
|
||||
pub hook_specific_output: Option<SessionStartHookSpecificOutputWire>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub(crate) struct SessionStartHookSpecificOutputWire {
|
||||
pub hook_event_name: HookEventNameWire,
|
||||
#[serde(default)]
|
||||
pub additional_context: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(rename = "stop.command.output")]
|
||||
pub(crate) struct StopCommandOutputWire {
|
||||
#[serde(flatten)]
|
||||
pub universal: HookUniversalOutputWire,
|
||||
#[serde(default)]
|
||||
pub decision: Option<StopDecisionWire>,
|
||||
#[serde(default)]
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub(crate) enum StopDecisionWire {
|
||||
#[serde(rename = "block")]
|
||||
Block,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(rename = "session-start.command.input")]
|
||||
pub(crate) struct SessionStartCommandInput {
|
||||
pub session_id: String,
|
||||
pub transcript_path: NullableString,
|
||||
pub cwd: String,
|
||||
#[schemars(schema_with = "session_start_hook_event_name_schema")]
|
||||
pub hook_event_name: String,
|
||||
pub model: String,
|
||||
#[schemars(schema_with = "permission_mode_schema")]
|
||||
pub permission_mode: String,
|
||||
#[schemars(schema_with = "session_start_source_schema")]
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
impl SessionStartCommandInput {
|
||||
pub(crate) fn new(
|
||||
session_id: impl Into<String>,
|
||||
transcript_path: Option<PathBuf>,
|
||||
cwd: impl Into<String>,
|
||||
model: impl Into<String>,
|
||||
permission_mode: impl Into<String>,
|
||||
source: impl Into<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
session_id: session_id.into(),
|
||||
transcript_path: NullableString::from_path(transcript_path),
|
||||
cwd: cwd.into(),
|
||||
hook_event_name: "SessionStart".to_string(),
|
||||
model: model.into(),
|
||||
permission_mode: permission_mode.into(),
|
||||
source: source.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[schemars(rename = "stop.command.input")]
|
||||
pub(crate) struct StopCommandInput {
|
||||
pub session_id: String,
|
||||
pub transcript_path: NullableString,
|
||||
pub cwd: String,
|
||||
#[schemars(schema_with = "stop_hook_event_name_schema")]
|
||||
pub hook_event_name: String,
|
||||
pub model: String,
|
||||
#[schemars(schema_with = "permission_mode_schema")]
|
||||
pub permission_mode: String,
|
||||
pub stop_hook_active: bool,
|
||||
pub last_assistant_message: NullableString,
|
||||
}
|
||||
|
||||
impl StopCommandInput {
|
||||
pub(crate) fn new(
|
||||
session_id: impl Into<String>,
|
||||
transcript_path: Option<PathBuf>,
|
||||
cwd: impl Into<String>,
|
||||
model: impl Into<String>,
|
||||
permission_mode: impl Into<String>,
|
||||
stop_hook_active: bool,
|
||||
last_assistant_message: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
session_id: session_id.into(),
|
||||
transcript_path: NullableString::from_path(transcript_path),
|
||||
cwd: cwd.into(),
|
||||
hook_event_name: "Stop".to_string(),
|
||||
model: model.into(),
|
||||
permission_mode: permission_mode.into(),
|
||||
stop_hook_active,
|
||||
last_assistant_message: NullableString::from_string(last_assistant_message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_schema_fixtures(schema_root: &Path) -> anyhow::Result<()> {
|
||||
let generated_dir = schema_root.join(GENERATED_DIR);
|
||||
ensure_empty_dir(&generated_dir)?;
|
||||
|
||||
write_schema(
|
||||
&generated_dir.join(SESSION_START_INPUT_FIXTURE),
|
||||
schema_json::<SessionStartCommandInput>()?,
|
||||
)?;
|
||||
write_schema(
|
||||
&generated_dir.join(SESSION_START_OUTPUT_FIXTURE),
|
||||
schema_json::<SessionStartCommandOutputWire>()?,
|
||||
)?;
|
||||
write_schema(
|
||||
&generated_dir.join(STOP_INPUT_FIXTURE),
|
||||
schema_json::<StopCommandInput>()?,
|
||||
)?;
|
||||
write_schema(
|
||||
&generated_dir.join(STOP_OUTPUT_FIXTURE),
|
||||
schema_json::<StopCommandOutputWire>()?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_schema(path: &Path, json: Vec<u8>) -> anyhow::Result<()> {
|
||||
std::fs::write(path, json)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_empty_dir(dir: &Path) -> anyhow::Result<()> {
|
||||
if dir.exists() {
|
||||
std::fs::remove_dir_all(dir)?;
|
||||
}
|
||||
std::fs::create_dir_all(dir)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn schema_json<T>() -> anyhow::Result<Vec<u8>>
|
||||
where
|
||||
T: JsonSchema,
|
||||
{
|
||||
let schema = schema_for_type::<T>();
|
||||
let value = serde_json::to_value(schema)?;
|
||||
let value = canonicalize_json(&value);
|
||||
Ok(serde_json::to_vec_pretty(&value)?)
|
||||
}
|
||||
|
||||
fn schema_for_type<T>() -> RootSchema
|
||||
where
|
||||
T: JsonSchema,
|
||||
{
|
||||
SchemaSettings::draft07()
|
||||
.with(|settings| {
|
||||
settings.option_add_null_type = false;
|
||||
})
|
||||
.into_generator()
|
||||
.into_root_schema_for::<T>()
|
||||
}
|
||||
|
||||
fn canonicalize_json(value: &Value) -> Value {
|
||||
match value {
|
||||
Value::Array(items) => Value::Array(items.iter().map(canonicalize_json).collect()),
|
||||
Value::Object(map) => {
|
||||
let mut entries: Vec<_> = map.iter().collect();
|
||||
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||
let mut sorted = Map::with_capacity(map.len());
|
||||
for (key, child) in entries {
|
||||
sorted.insert(key.clone(), canonicalize_json(child));
|
||||
}
|
||||
Value::Object(sorted)
|
||||
}
|
||||
_ => value.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn session_start_hook_event_name_schema(_gen: &mut SchemaGenerator) -> Schema {
|
||||
string_const_schema("SessionStart")
|
||||
}
|
||||
|
||||
fn stop_hook_event_name_schema(_gen: &mut SchemaGenerator) -> Schema {
|
||||
string_const_schema("Stop")
|
||||
}
|
||||
|
||||
fn permission_mode_schema(_gen: &mut SchemaGenerator) -> Schema {
|
||||
string_enum_schema(&[
|
||||
"default",
|
||||
"acceptEdits",
|
||||
"plan",
|
||||
"dontAsk",
|
||||
"bypassPermissions",
|
||||
])
|
||||
}
|
||||
|
||||
fn session_start_source_schema(_gen: &mut SchemaGenerator) -> Schema {
|
||||
string_enum_schema(&["startup", "resume", "clear"])
|
||||
}
|
||||
|
||||
fn string_const_schema(value: &str) -> Schema {
|
||||
let mut schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
..Default::default()
|
||||
};
|
||||
schema.const_value = Some(Value::String(value.to_string()));
|
||||
Schema::Object(schema)
|
||||
}
|
||||
|
||||
fn string_enum_schema(values: &[&str]) -> Schema {
|
||||
let mut schema = SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
..Default::default()
|
||||
};
|
||||
schema.enum_values = Some(
|
||||
values
|
||||
.iter()
|
||||
.map(|value| Value::String((*value).to_string()))
|
||||
.collect(),
|
||||
);
|
||||
Schema::Object(schema)
|
||||
}
|
||||
|
||||
fn default_continue() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::SESSION_START_INPUT_FIXTURE;
|
||||
use super::SESSION_START_OUTPUT_FIXTURE;
|
||||
use super::STOP_INPUT_FIXTURE;
|
||||
use super::STOP_OUTPUT_FIXTURE;
|
||||
use super::write_schema_fixtures;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn generated_hook_schemas_match_fixtures() {
|
||||
let temp_dir = TempDir::new().expect("create temp dir");
|
||||
let schema_root = temp_dir.path().join("schema");
|
||||
write_schema_fixtures(&schema_root).expect("write generated hook schemas");
|
||||
|
||||
for fixture in [
|
||||
SESSION_START_INPUT_FIXTURE,
|
||||
SESSION_START_OUTPUT_FIXTURE,
|
||||
STOP_INPUT_FIXTURE,
|
||||
STOP_OUTPUT_FIXTURE,
|
||||
] {
|
||||
let expected = std::fs::read_to_string(
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("schema")
|
||||
.join("generated")
|
||||
.join(fixture),
|
||||
)
|
||||
.unwrap_or_else(|err| panic!("read fixture {fixture}: {err}"));
|
||||
let actual = std::fs::read_to_string(schema_root.join("generated").join(fixture))
|
||||
.unwrap_or_else(|err| panic!("read generated schema {fixture}: {err}"));
|
||||
assert_eq!(expected, actual, "fixture should match generated schema");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,10 @@ write-config-schema:
|
||||
write-app-server-schema *args:
|
||||
cargo run -p codex-app-server-protocol --bin write_schema_fixtures -- "$@"
|
||||
|
||||
[no-cd]
|
||||
write-hooks-schema:
|
||||
cargo run --manifest-path ./codex-rs/Cargo.toml -p codex-hooks --bin write_schema_fixtures
|
||||
|
||||
# Tail logs from the state SQLite database
|
||||
log *args:
|
||||
if [ "${1:-}" = "--" ]; then shift; fi; cargo run -p codex-state --bin logs_client -- "$@"
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@
|
||||
"description": "Tools for repo-wide maintenance.",
|
||||
"scripts": {
|
||||
"format": "prettier --check *.json *.md docs/*.md .github/workflows/*.yml **/*.js",
|
||||
"format:fix": "prettier --write *.json *.md docs/*.md .github/workflows/*.yml **/*.js"
|
||||
"format:fix": "prettier --write *.json *.md docs/*.md .github/workflows/*.yml **/*.js",
|
||||
"write-hooks-schema": "cargo run --manifest-path ./codex-rs/Cargo.toml -p codex-hooks --bin write_schema_fixtures"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.3"
|
||||
|
||||
Reference in New Issue
Block a user