From f7574b0bf9bfe68fcb9d307417958f976a3aee65 Mon Sep 17 00:00:00 2001 From: danieldutu Date: Sat, 24 Oct 2026 11:46:13 +0300 Subject: [PATCH 1/2] fix: warn that --format does not change files written by agents-as-code commands --- README.md | 2 +- cli/elevenlabs/workflow/agents.rs | 6 ++++- cli/elevenlabs/workflow/tests.rs | 6 ++++- cli/elevenlabs/workflow/tools.rs | 6 ++++- cli/elevenlabs/workflow/util.rs | 44 +++++++++++++++++++++++++++++++ src/app.rs | 2 +- 6 files changed, 61 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b61551f..70c5e99 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ For production, prefer setting `webhook` on `create` and handling the webhook ev ## Agents as Code -Manage Conversational AI agents from local configuration files. `elevenlabs agents init` scaffolds a project; agent configs live as JSON on disk and sync to ElevenLabs. Pulled configs are stored as raw wire JSON and pushed back verbatim, so they round-trip losslessly. +Manage Conversational AI agents from local configuration files. `elevenlabs agents init` scaffolds a project; agent configs live as JSON on disk and sync to ElevenLabs. Pulled configs are stored as raw wire JSON and pushed back verbatim, so they round-trip losslessly. The global `--format` flag styles printed API responses only; configs are always stored as JSON. ### Project structure diff --git a/cli/elevenlabs/workflow/agents.rs b/cli/elevenlabs/workflow/agents.rs index 87f8cdf..e53fb35 100644 --- a/cli/elevenlabs/workflow/agents.rs +++ b/cli/elevenlabs/workflow/agents.rs @@ -15,7 +15,9 @@ use fern_cli_sdk::error::CliError; use fern_cli_sdk::openapi::AppContext; use serde_json::{json, Value}; -use super::util::{downcast_ctx, dry_run_flag, opt_string, plan_pull_action, PullAction}; +use super::util::{ + downcast_ctx, dry_run_flag, opt_string, plan_pull_action, warn_format_ignored, PullAction, +}; use super::{api, project, settings, templates, verify}; /// Register the `agents` command group. @@ -612,6 +614,7 @@ fn push_command() -> clap::Command { fn handle_push(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("agents.push"); + warn_format_ignored(matches, "agents push"); let agent = opt_string(matches, "agent"); let branch = opt_string(matches, "branch"); let version_description = opt_string(matches, "version-description"); @@ -842,6 +845,7 @@ fn pull_command() -> clap::Command { fn handle_pull(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("agents.pull"); + warn_format_ignored(matches, "agents pull"); let agent = opt_string(matches, "agent"); let branch = opt_string(matches, "branch"); let all_branches = matches.get_flag("all-branches"); diff --git a/cli/elevenlabs/workflow/tests.rs b/cli/elevenlabs/workflow/tests.rs index c4d9a7c..1be7360 100644 --- a/cli/elevenlabs/workflow/tests.rs +++ b/cli/elevenlabs/workflow/tests.rs @@ -10,7 +10,9 @@ use fern_cli_sdk::error::CliError; use fern_cli_sdk::openapi::AppContext; use serde_json::{json, Value}; -use super::util::{downcast_ctx, dry_run_flag, opt_string, plan_pull_action, PullAction}; +use super::util::{ + downcast_ctx, dry_run_flag, opt_string, plan_pull_action, warn_format_ignored, PullAction, +}; use super::{api, project}; /// Register the `tests` command group. @@ -446,6 +448,7 @@ fn push_command() -> clap::Command { fn handle_push(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("tests.push"); + warn_format_ignored(matches, "tests push"); let test_filter = opt_string(matches, "test"); let config_dir = opt_string(matches, "config-dir").unwrap_or_else(|| "test_configs".to_string()); @@ -592,6 +595,7 @@ fn pull_command() -> clap::Command { fn handle_pull(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("tests.pull"); + warn_format_ignored(matches, "tests pull"); let test_filter = opt_string(matches, "test"); let output_dir = opt_string(matches, "output-dir").unwrap_or_else(|| "test_configs".to_string()); diff --git a/cli/elevenlabs/workflow/tools.rs b/cli/elevenlabs/workflow/tools.rs index 58d1ff6..66ae006 100644 --- a/cli/elevenlabs/workflow/tools.rs +++ b/cli/elevenlabs/workflow/tools.rs @@ -9,7 +9,9 @@ use fern_cli_sdk::error::CliError; use fern_cli_sdk::openapi::AppContext; use serde_json::{json, Value}; -use super::util::{downcast_ctx, dry_run_flag, opt_string, plan_pull_action, PullAction}; +use super::util::{ + downcast_ctx, dry_run_flag, opt_string, plan_pull_action, warn_format_ignored, PullAction, +}; use super::{api, project, verify}; /// Register the `tools` command group. @@ -305,6 +307,7 @@ fn push_command() -> clap::Command { fn handle_push(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("tools.push"); + warn_format_ignored(matches, "tools push"); let tool_filter = opt_string(matches, "tool"); let dry_run = dry_run_flag(matches); @@ -432,6 +435,7 @@ fn pull_command() -> clap::Command { fn handle_pull(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("tools.pull"); + warn_format_ignored(matches, "tools pull"); let tool_filter = opt_string(matches, "tool"); let output_dir = opt_string(matches, "output-dir").unwrap_or_else(|| "tool_configs".to_string()); diff --git a/cli/elevenlabs/workflow/util.rs b/cli/elevenlabs/workflow/util.rs index c9ebaf7..123ef74 100644 --- a/cli/elevenlabs/workflow/util.rs +++ b/cli/elevenlabs/workflow/util.rs @@ -22,6 +22,28 @@ pub fn dry_run_flag(matches: &clap::ArgMatches) -> bool { .unwrap_or(false) } +/// Read the framework's global `--format` flag (declared `global(true)`), +/// returning the requested format when passed. +pub fn format_flag(matches: &clap::ArgMatches) -> Option { + matches + .try_get_one::("format") + .ok() + .flatten() + .cloned() +} + +/// Warn when `--format` is passed to a command that writes local files +/// instead of printing an API response. `--format` is a global flag, so clap +/// still accepts it on those commands; warn on stderr and continue rather +/// than fail. +pub fn warn_format_ignored(matches: &clap::ArgMatches, command: &str) { + if format_flag(matches).is_some() { + eprintln!( + "--format controls how API responses are printed and has no effect on {command}; agent configs are always written as JSON." + ); + } +} + /// Read an optional string argument. pub fn opt_string(matches: &clap::ArgMatches, id: &str) -> Option { matches.get_one::(id).cloned() @@ -61,6 +83,7 @@ mod tests { .long("dry-run") .action(clap::ArgAction::SetTrue), ) + .arg(clap::Arg::new("format").long("format").global(true)) .arg(clap::Arg::new("agent").long("agent")) .get_matches_from(args) } @@ -78,6 +101,27 @@ mod tests { assert!(!dry_run_flag(&m)); } + #[test] + fn format_flag_reads_the_global_flag() { + assert_eq!( + format_flag(&matches_from(&["test", "--format", "yaml"])), + Some("yaml".to_string()) + ); + assert_eq!(format_flag(&matches_from(&["test"])), None); + } + + #[test] + fn format_flag_reads_the_flag_before_the_subcommand() { + // `--format` is global, so a value placed before the subcommand still + // lands in the subcommand's matches — which is what a handler reads. + let m = clap::Command::new("test") + .arg(clap::Arg::new("format").long("format").global(true)) + .subcommand(clap::Command::new("pull")) + .get_matches_from(["test", "--format", "yaml", "pull"]); + let sub = m.subcommand_matches("pull").unwrap(); + assert_eq!(format_flag(&sub), Some("yaml".to_string())); + } + #[test] fn opt_string_reads_present_and_absent_values() { assert_eq!( diff --git a/src/app.rs b/src/app.rs index e4771a8..4a183f1 100644 --- a/src/app.rs +++ b/src/app.rs @@ -926,7 +926,7 @@ impl CliApp { .arg( clap::Arg::new("format") .long("format") - .help("Output format: json, table, yaml, csv, raw, jsonl, http. Default: table when stdout is a TTY, json when piped. Override default with _OUTPUT env var. raw emits unmodified server response bytes. jsonl emits one compact JSON value per line (NDJSON); arrays are flattened. http emits the full HTTP response (status line + headers + body) like curl -i (OpenAPI only).") + .help("Output format for printed API responses. Does not change files written to disk. Formats: json, table, yaml, csv, raw, jsonl, http. Default: table when stdout is a TTY, json when piped. Override default with _OUTPUT env var. raw emits unmodified server response bytes. jsonl emits one compact JSON value per line (NDJSON); arrays are flattened. http emits the full HTTP response (status line + headers + body) like curl -i (OpenAPI only).") .value_name("FORMAT") .global(true), ) From abe255e6a6eca4569506b7a0952520fbe627f293 Mon Sep 17 00:00:00 2001 From: danieldutu Date: Tue, 27 Oct 2026 12:49:54 +0200 Subject: [PATCH 2/2] fix: declare --format-ignoring commands on CliCommand and warn from dispatch The first version called warn_format_ignored inside six push/pull handlers. Every future handler had to remember the call. The message also said "agent configs" for tools and tests. Its two unit tests ran on a hand-built clap tree, so they never touched the real global flag. CliApp::ignores_format(path, noun, verb) now records the declaration on the registered CliCommand. Dispatch prints the warning once on stderr before it calls the handler, and only when --format was given. The agents, tools and tests push/pull commands declare it where they register. Each declaration gives its own noun ("agent configs", "tool configs", "test configs") and verb ("read" for push, "written" for pull). I removed format_flag, warn_format_ignored and their tests from util.rs. tests/format_flag_test.rs runs the built binary on five argv cases. Removing .global(true) from the --format arg in src/app.rs makes 4 of the 5 fail. --- .fernignore | 1 + cli/elevenlabs/workflow/agents.rs | 9 ++-- cli/elevenlabs/workflow/tests.rs | 9 ++-- cli/elevenlabs/workflow/tools.rs | 9 ++-- cli/elevenlabs/workflow/util.rs | 44 ---------------- src/app.rs | 50 +++++++++++++++++++ tests/format_flag_test.rs | 83 +++++++++++++++++++++++++++++++ 7 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 tests/format_flag_test.rs diff --git a/.fernignore b/.fernignore index ecbbe2c..1fbe28f 100644 --- a/.fernignore +++ b/.fernignore @@ -40,6 +40,7 @@ tests/e2e_smoke.rs tests/say_test.rs tests/onboard_test.rs tests/flows_wait_test.rs +tests/format_flag_test.rs .fern/replay.lock .fern/replay.yml .gitattributes diff --git a/cli/elevenlabs/workflow/agents.rs b/cli/elevenlabs/workflow/agents.rs index e53fb35..c35412c 100644 --- a/cli/elevenlabs/workflow/agents.rs +++ b/cli/elevenlabs/workflow/agents.rs @@ -15,9 +15,7 @@ use fern_cli_sdk::error::CliError; use fern_cli_sdk::openapi::AppContext; use serde_json::{json, Value}; -use super::util::{ - downcast_ctx, dry_run_flag, opt_string, plan_pull_action, warn_format_ignored, PullAction, -}; +use super::util::{downcast_ctx, dry_run_flag, opt_string, plan_pull_action, PullAction}; use super::{api, project, settings, templates, verify}; /// Register the `agents` command group. @@ -50,6 +48,9 @@ pub fn register(app: CliApp) -> CliApp { pull_command(), Box::new(|matches, ctx| handle_pull(matches, downcast_ctx(ctx)?)), ) + // Push and pull read and write local JSON files, not printed responses. + .ignores_format(&["agents", "push"], "agent configs", "read") + .ignores_format(&["agents", "pull"], "agent configs", "written") // Nested under the generated `agents widget` subgroup rather than // replacing it: `widget get` (config) and `widget avatar` are API // commands, and a custom leaf named `widget` would shadow the whole group. @@ -614,7 +615,6 @@ fn push_command() -> clap::Command { fn handle_push(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("agents.push"); - warn_format_ignored(matches, "agents push"); let agent = opt_string(matches, "agent"); let branch = opt_string(matches, "branch"); let version_description = opt_string(matches, "version-description"); @@ -845,7 +845,6 @@ fn pull_command() -> clap::Command { fn handle_pull(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("agents.pull"); - warn_format_ignored(matches, "agents pull"); let agent = opt_string(matches, "agent"); let branch = opt_string(matches, "branch"); let all_branches = matches.get_flag("all-branches"); diff --git a/cli/elevenlabs/workflow/tests.rs b/cli/elevenlabs/workflow/tests.rs index 1be7360..e0632b5 100644 --- a/cli/elevenlabs/workflow/tests.rs +++ b/cli/elevenlabs/workflow/tests.rs @@ -10,9 +10,7 @@ use fern_cli_sdk::error::CliError; use fern_cli_sdk::openapi::AppContext; use serde_json::{json, Value}; -use super::util::{ - downcast_ctx, dry_run_flag, opt_string, plan_pull_action, warn_format_ignored, PullAction, -}; +use super::util::{downcast_ctx, dry_run_flag, opt_string, plan_pull_action, PullAction}; use super::{api, project}; /// Register the `tests` command group. @@ -38,6 +36,9 @@ pub fn register(app: CliApp) -> CliApp { pull_command(), Box::new(|matches, ctx| handle_pull(matches, downcast_ctx(ctx)?)), ) + // Push and pull read and write local JSON files, not printed responses. + .ignores_format(&["tests", "push"], "test configs", "read") + .ignores_format(&["tests", "pull"], "test configs", "written") .command_under_typed_with( &["tests", "templates"], clap::Command::new("list").about("List available test templates"), @@ -448,7 +449,6 @@ fn push_command() -> clap::Command { fn handle_push(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("tests.push"); - warn_format_ignored(matches, "tests push"); let test_filter = opt_string(matches, "test"); let config_dir = opt_string(matches, "config-dir").unwrap_or_else(|| "test_configs".to_string()); @@ -595,7 +595,6 @@ fn pull_command() -> clap::Command { fn handle_pull(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("tests.pull"); - warn_format_ignored(matches, "tests pull"); let test_filter = opt_string(matches, "test"); let output_dir = opt_string(matches, "output-dir").unwrap_or_else(|| "test_configs".to_string()); diff --git a/cli/elevenlabs/workflow/tools.rs b/cli/elevenlabs/workflow/tools.rs index 66ae006..483d666 100644 --- a/cli/elevenlabs/workflow/tools.rs +++ b/cli/elevenlabs/workflow/tools.rs @@ -9,9 +9,7 @@ use fern_cli_sdk::error::CliError; use fern_cli_sdk::openapi::AppContext; use serde_json::{json, Value}; -use super::util::{ - downcast_ctx, dry_run_flag, opt_string, plan_pull_action, warn_format_ignored, PullAction, -}; +use super::util::{downcast_ctx, dry_run_flag, opt_string, plan_pull_action, PullAction}; use super::{api, project, verify}; /// Register the `tools` command group. @@ -37,6 +35,9 @@ pub fn register(app: CliApp) -> CliApp { pull_command(), Box::new(|matches, ctx| handle_pull(matches, downcast_ctx(ctx)?)), ) + // Push and pull read and write local JSON files, not printed responses. + .ignores_format(&["tools", "push"], "tool configs", "read") + .ignores_format(&["tools", "pull"], "tool configs", "written") } // ── shared helpers ────────────────────────────────────────────────── @@ -307,7 +308,6 @@ fn push_command() -> clap::Command { fn handle_push(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("tools.push"); - warn_format_ignored(matches, "tools push"); let tool_filter = opt_string(matches, "tool"); let dry_run = dry_run_flag(matches); @@ -435,7 +435,6 @@ fn pull_command() -> clap::Command { fn handle_pull(matches: &clap::ArgMatches, ctx: &AppContext) -> Result<(), CliError> { let _scope = api::command_scope("tools.pull"); - warn_format_ignored(matches, "tools pull"); let tool_filter = opt_string(matches, "tool"); let output_dir = opt_string(matches, "output-dir").unwrap_or_else(|| "tool_configs".to_string()); diff --git a/cli/elevenlabs/workflow/util.rs b/cli/elevenlabs/workflow/util.rs index 123ef74..c9ebaf7 100644 --- a/cli/elevenlabs/workflow/util.rs +++ b/cli/elevenlabs/workflow/util.rs @@ -22,28 +22,6 @@ pub fn dry_run_flag(matches: &clap::ArgMatches) -> bool { .unwrap_or(false) } -/// Read the framework's global `--format` flag (declared `global(true)`), -/// returning the requested format when passed. -pub fn format_flag(matches: &clap::ArgMatches) -> Option { - matches - .try_get_one::("format") - .ok() - .flatten() - .cloned() -} - -/// Warn when `--format` is passed to a command that writes local files -/// instead of printing an API response. `--format` is a global flag, so clap -/// still accepts it on those commands; warn on stderr and continue rather -/// than fail. -pub fn warn_format_ignored(matches: &clap::ArgMatches, command: &str) { - if format_flag(matches).is_some() { - eprintln!( - "--format controls how API responses are printed and has no effect on {command}; agent configs are always written as JSON." - ); - } -} - /// Read an optional string argument. pub fn opt_string(matches: &clap::ArgMatches, id: &str) -> Option { matches.get_one::(id).cloned() @@ -83,7 +61,6 @@ mod tests { .long("dry-run") .action(clap::ArgAction::SetTrue), ) - .arg(clap::Arg::new("format").long("format").global(true)) .arg(clap::Arg::new("agent").long("agent")) .get_matches_from(args) } @@ -101,27 +78,6 @@ mod tests { assert!(!dry_run_flag(&m)); } - #[test] - fn format_flag_reads_the_global_flag() { - assert_eq!( - format_flag(&matches_from(&["test", "--format", "yaml"])), - Some("yaml".to_string()) - ); - assert_eq!(format_flag(&matches_from(&["test"])), None); - } - - #[test] - fn format_flag_reads_the_flag_before_the_subcommand() { - // `--format` is global, so a value placed before the subcommand still - // lands in the subcommand's matches — which is what a handler reads. - let m = clap::Command::new("test") - .arg(clap::Arg::new("format").long("format").global(true)) - .subcommand(clap::Command::new("pull")) - .get_matches_from(["test", "--format", "yaml", "pull"]); - let sub = m.subcommand_matches("pull").unwrap(); - assert_eq!(format_flag(&sub), Some("yaml".to_string())); - } - #[test] fn opt_string_reads_present_and_absent_values() { assert_eq!( diff --git a/src/app.rs b/src/app.rs index 4a183f1..64433fc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -45,6 +45,9 @@ struct CliCommand { path: Vec, cmd: clap::Command, handler: CliCommandHandler, + /// Set by [`CliApp::ignores_format`]: `(noun, verb)` for the warning + /// dispatch prints when `--format` is passed to this command. + format_ignored: Option<(String, String)>, } /// Outcome of the dispatch pipeline — separates success from @@ -316,6 +319,7 @@ impl CliApp { path: Vec::new(), cmd, handler, + format_ignored: None, }); self } @@ -401,6 +405,7 @@ impl CliApp { path: Vec::new(), cmd: augmented, handler: erased, + format_ignored: None, }); self } @@ -431,6 +436,7 @@ impl CliApp { path: path.iter().map(|s| s.to_string()).collect(), cmd, handler, + format_ignored: None, }); self } @@ -489,10 +495,35 @@ impl CliApp { path: path.iter().map(|s| s.to_string()).collect(), cmd: augmented, handler: erased, + format_ignored: None, }); self } + /// Declare that the custom command at `path` (parent path plus its own + /// name) does not print an API response, so the global `--format` flag + /// has no effect on it. Dispatch then warns on stderr when `--format` is + /// passed: "... are always as JSON." + /// + /// ```rust,ignore + /// app.ignores_format(&["agents", "pull"], "agent configs", "written") + /// ``` + pub fn ignores_format(mut self, path: &[&str], noun: &str, verb: &str) -> Self { + let cc = self + .cli_commands + .iter_mut() + .find(|cc| { + cc.path + .iter() + .map(String::as_str) + .chain([cc.cmd.get_name()]) + .eq(path.iter().copied()) + }) + .unwrap_or_else(|| panic!("ignores_format: no custom command at `{}`", path.join(" "))); + cc.format_ignored = Some((noun.to_string(), verb.to_string())); + self + } + // ── Tier 1: Declarative ───────────────────────────────────────── /// Register an alias for a command at `path`. Invoking the alias @@ -1127,6 +1158,25 @@ impl CliApp { if let Some(target) = crate::custom_commands::walk_matches_to_custom( &matches, &cc.path, cc.cmd.get_name(), ) { + if let Some((noun, verb)) = &cc.format_ignored { + if target + .try_get_one::("format") + .ok() + .flatten() + .is_some() + { + let command: Vec<&str> = cc + .path + .iter() + .map(String::as_str) + .chain([cc.cmd.get_name()]) + .collect(); + eprintln!( + "--format controls how API responses are printed and has no effect on {}; {noun} are always {verb} as JSON.", + command.join(" "), + ); + } + } // Collect contexts from ALL bindings so the handler can // invoke operations from any binding transparently. let mut ctx: Option> = None; diff --git a/tests/format_flag_test.rs b/tests/format_flag_test.rs new file mode 100644 index 0000000..4b9e1b6 --- /dev/null +++ b/tests/format_flag_test.rs @@ -0,0 +1,83 @@ +//! `--format` on agents-as-code push/pull commands, run through the real binary. +//! +//! `--format` is a global flag declared in `src/app.rs`, so clap accepts it on +//! every command. Push/pull read and write local JSON configs and never print +//! an API response, so they declare `ignores_format` at registration and +//! `CliApp` dispatch warns on stderr. These tests drive the built binary — the +//! same command tree `main` builds — so a change to how the global flag is +//! attached, or a dropped declaration, fails here. +//! +//! No network: `ELEVENLABS_BASE_URL` points at a closed localhost port, so a +//! pull that gets as far as the API fails with a connection error, and each +//! run happens in an empty temp directory. The warning prints before the +//! handler runs, so the handler's own outcome does not matter here. + +use std::path::Path; +use std::process::{Command, Output}; + +fn cli(dir: &Path, args: &[&str]) -> Output { + Command::new(env!("CARGO_BIN_EXE_elevenlabs")) + .args(args) + .current_dir(dir) + .env("HOME", dir) + .env("ELEVENLABS_API_KEY", "test-key") + .env("NO_COLOR", "1") + .env("FERN_CLI_CREDENTIAL_STORE", "file") + .env("ELEVENLABS_BASE_URL", "http://127.0.0.1:9") + .env_remove("ELEVENLABS_VIA") + .output() + .expect("failed to spawn the elevenlabs binary") +} + +fn stderr_of(args: &[&str]) -> String { + let dir = tempfile::tempdir().expect("tempdir"); + String::from_utf8_lossy(&cli(dir.path(), args).stderr).to_string() +} + +fn warning(command: &str, noun: &str, verb: &str) -> String { + format!( + "--format controls how API responses are printed and has no effect on {command}; {noun} are always {verb} as JSON.\n" + ) +} + +#[test] +fn agents_pull_warns_with_format_after_the_command() { + let err = stderr_of(&["agents", "pull", "--format", "yaml"]); + assert!( + err.starts_with(&warning("agents pull", "agent configs", "written")), + "{err}" + ); +} + +#[test] +fn agents_pull_warns_with_format_before_the_command() { + let err = stderr_of(&["--format", "yaml", "agents", "pull"]); + assert!( + err.starts_with(&warning("agents pull", "agent configs", "written")), + "{err}" + ); +} + +#[test] +fn tools_push_warns_with_its_own_noun() { + let err = stderr_of(&["tools", "push", "--format", "json"]); + assert!( + err.starts_with(&warning("tools push", "tool configs", "read")), + "{err}" + ); +} + +#[test] +fn tests_pull_warns_with_its_own_noun() { + let err = stderr_of(&["tests", "pull", "--format", "table"]); + assert!( + err.starts_with(&warning("tests pull", "test configs", "written")), + "{err}" + ); +} + +#[test] +fn no_warning_without_format() { + let err = stderr_of(&["agents", "pull"]); + assert!(!err.contains("--format"), "{err}"); +}