1use tau_config::settings as path_tau_config_settings;
8
9pub mod cli;
10
11mod action_commands;
12mod agent_activity;
13mod agent_navigation;
14mod chat;
15mod daemon;
16mod dev_tmux;
17mod estimated_cost;
18mod event_renderer;
19mod kill_session;
20mod line_output;
21mod list_agents;
22mod list_sessions;
23mod markdown_render;
24mod message_fact_render;
25mod papercut;
26mod peer_exit;
27mod preview_declarations;
28mod print_prompt;
29mod print_tools;
30mod prompt_history;
31mod prompt_stdin;
32mod provider_quota;
33mod render_request;
34mod renderer_handle;
35mod send;
36mod settings_registry;
37mod skill_commands;
38mod terminal_text;
39mod theme;
40mod tool_render;
41mod transcript_markers;
42mod turn_stats_projection;
43mod ui_client;
44mod ui_commands;
45mod ui_events;
46mod ui_logging;
47mod ui_prompt;
48mod unload_agent;
49mod watch_activity;
50
51use std::process::ExitCode;
52use std::sync::{Mutex, MutexGuard};
53use std::{fmt, io};
54
55use tau_harness::SessionLaunchStatus;
56
57use crate::chat::run_chat;
58use crate::daemon::resolve_resume_session_id;
59
60pub(crate) const MUTEX_POISONED: &str = "mutex poisoned";
64
65pub(crate) fn locked<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
69 mutex.lock().expect(MUTEX_POISONED)
70}
71
72mod built_info;
73
74const STARTUP_PUNS: &[&str] = &[
75 "Tau is like Pi, but twice as much.",
76 "A whole new angle on coding agents.",
77 "Tau day is every day if you care about circles enough.",
78 "Come for the agent, stay for the circumference discourse.",
79 "Tau is the irrational choice for rational Unix hackers.",
80 "Small tools, loosely joined — that’s the Tau of Unix.",
81 "In Tau, what goes around comes around over stdio.",
82 "We’ve come full τurn.",
83 "Tau keeps the loop tight and the pipes honest.",
84 "Every extension gets its turn in Tau.",
85 "Tau speaks fluent stdio with a circular accent.",
86 "Agents, tools, sockets, loops: a well-rounded lineup.",
87 "Ready, set, Tau!",
88 "Tau day to code.",
89 "Tau-tau control.",
90 "Tau-tally operational.",
91 "Tau much power in one terminal.",
92 "Tau infinity and beyond.",
93 "Tau the line between human and agent.",
94 "Tau’s what I’m talking about.",
95 "One shell to Tau them all.",
96 "Tau-powered, Unix-native.",
97 "Complete revolution.",
98 "Wrapping around nicely.",
99 "Continuous on S¹, probably.",
100 "Cohomology remains left as exercise.",
101];
102
103pub(crate) fn random_startup_pun() -> &'static str {
104 use rand::Rng;
105 let idx = rand::thread_rng().gen_range(0..STARTUP_PUNS.len());
106 STARTUP_PUNS[idx]
107}
108
109#[derive(Debug)]
115pub enum CliError {
116 CachePartial,
118 CacheInvalid,
120 Io(io::Error),
121 Encode(tau_proto::EncodeError),
122 Harness(tau_harness::HarnessError),
123 Inspect(tau_session_inspect::InspectError),
124 DaemonExited(String),
125 NoRunningDaemon,
126 Participant(String),
127 PromptStdin(PromptStdinError),
128 SessionNotFound(String),
129 ForegroundOwnershipUnconfirmed {
132 message: String,
134 diagnostic: tau_cli_term::ForegroundRestorationDiagnostic,
136 },
137 TerminalOutputFailed(String),
139}
140
141impl fmt::Display for CliError {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 match self {
144 Self::CachePartial => f.write_str("cache analysis is partial; see report coverage"),
145 Self::CacheInvalid => f.write_str("cache source unavailable, unsupported, or invalid; use a matching-build inspector and check the selected scope and limits"),
146 Self::Io(source) => write!(f, "I/O error: {source}"),
147 Self::Encode(source) => write!(f, "encode error: {source}"),
148 Self::Harness(source) => write!(f, "harness error: {source}"),
149 Self::Inspect(source) => write!(f, "inspect error: {source}"),
150 Self::DaemonExited(msg) => write!(f, "harness daemon exited: {msg}"),
151 Self::NoRunningDaemon => {
152 f.write_str("no running session is available; start one with `tau`")
153 }
154 Self::Participant(msg) => write!(f, "participant error: {msg}"),
155 Self::PromptStdin(error) => error.fmt(f),
156 Self::SessionNotFound(id) => write!(f, "session not found: `{id}`"),
157 Self::ForegroundOwnershipUnconfirmed { message, .. } => f.write_str(message),
158 Self::TerminalOutputFailed(message) => f.write_str(message),
159 }
160 }
161}
162
163impl CliError {
164 fn exit_code(&self) -> ExitCode {
167 match self {
168 Self::CachePartial => ExitCode::from(3),
169 Self::CacheInvalid => ExitCode::from(2),
170 _ => ExitCode::FAILURE,
171 }
172 }
173
174 fn should_report_to_terminal(&self) -> bool {
176 !matches!(
177 self,
178 Self::ForegroundOwnershipUnconfirmed { .. } | Self::TerminalOutputFailed(_)
179 )
180 }
181}
182
183#[derive(Debug)]
185pub enum PromptStdinError {
186 AdmissionTimeout {
188 timeout: std::time::Duration,
190 },
191 Rejected {
193 reason: tau_proto::UiCreateAgentRejection,
195 message: String,
197 },
198 PromptFailed {
200 stage: tau_proto::AgentPromptFailureStage,
202 message: String,
204 },
205 ExecutionFailed {
207 message: String,
209 },
210}
211
212impl fmt::Display for PromptStdinError {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 match self {
215 Self::AdmissionTimeout { timeout } => write!(
216 f,
217 "timed out after {}s waiting for create-agent admission",
218 timeout.as_secs()
219 ),
220 Self::Rejected { reason, message } => {
221 write!(f, "create-agent request failed ({}): {message}", reason)
222 }
223 Self::PromptFailed { stage, message } => {
224 write!(f, "initial prompt failed ({}): {message}", stage)
225 }
226 Self::ExecutionFailed { message } => {
227 write!(f, "initial prompt failed (execution): {message}")
228 }
229 }
230 }
231}
232
233impl std::error::Error for PromptStdinError {}
234impl std::error::Error for CliError {}
235
236impl From<io::Error> for CliError {
237 fn from(source: io::Error) -> Self {
238 Self::Io(source)
239 }
240}
241
242impl From<tau_harness::HarnessError> for CliError {
243 fn from(source: tau_harness::HarnessError) -> Self {
244 Self::Harness(source)
245 }
246}
247
248impl From<tau_session_inspect::InspectError> for CliError {
249 fn from(source: tau_session_inspect::InspectError) -> Self {
250 Self::Inspect(source)
251 }
252}
253
254fn run_harness_component_default() -> Result<(), Box<dyn std::error::Error>> {
260 run_harness_component(false)
261}
262
263fn run_harness_component(initial_ui_stdio: bool) -> Result<(), Box<dyn std::error::Error>> {
264 if initial_ui_stdio {
265 tau_harness::run_component_with_internal_tools_and_initial_ui_stdio(
266 tau_harness_tools::builtin_handlers(),
267 )
268 } else {
269 tau_harness::run_component_with_internal_tools(tau_harness_tools::builtin_handlers())
270 }
271}
272
273fn build_revision() -> String {
274 tau_harness::version::build_revision()
275}
276
277fn build_last_modified() -> Option<String> {
278 tau_harness::version::build_last_modified()
283 .or_else(|| short_built_time(built_info::BUILT_TIME_UTC))
284 .filter(|date| date != "1980-01-01 00:00")
285}
286
287fn short_built_time(time: &str) -> Option<String> {
288 let input_format = time::macros::format_description!(
289 "[weekday repr:short], [day padding:none] [month repr:short] [year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"
290 );
291 let output_format = time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]");
292 time::OffsetDateTime::parse(time, input_format)
293 .ok()?
294 .format(output_format)
295 .ok()
296}
297
298pub(crate) fn build_label_parts() -> (String, String) {
299 let version = format!("tau {}", env!("CARGO_PKG_VERSION"));
300 let build = match build_last_modified() {
301 Some(date) => format!("({}, {})", build_revision(), date),
302 None => format!("({})", build_revision()),
303 };
304 (version, build)
305}
306
307fn version_label() -> String {
308 let (version, build) = build_label_parts();
309 format!("{version} {build}")
310}
311
312pub(crate) fn build_banner(theme: &tau_themes::Theme) -> tau_cli_term::StyledText {
315 use tau_themes::names;
316 let logo = tau_cli_term::resolve::resolve(theme, names::BANNER_LOGO);
317 let name = tau_cli_term::resolve::resolve(theme, names::BANNER_NAME);
318 let version_style = tau_cli_term::resolve::resolve(theme, names::BANNER_VERSION);
319 let build_style = tau_cli_term::resolve::resolve(theme, names::BANNER_BUILD);
320 let pun_style = tau_cli_term::resolve::resolve(theme, names::BANNER_PUN);
321 let pun = random_startup_pun();
322 let (version, build) = build_label_parts();
323 tau_cli_term::StyledText::from(vec![
324 tau_cli_term::Span::new("▝▜▛▀ ", logo),
325 tau_cli_term::Span::new("tau", name),
326 tau_cli_term::Span::new(version.trim_start_matches("tau"), version_style),
327 tau_cli_term::Span::new(" ", Default::default()),
328 tau_cli_term::Span::new(build, build_style),
329 tau_cli_term::Span::new("\n", Default::default()),
330 tau_cli_term::Span::new(" ▐▙▖ ", logo),
331 tau_cli_term::Span::new(pun, pun_style),
332 ])
333}
334
335pub(crate) fn mint_short_id(prefix: &str) -> String {
342 use rand::distributions::Distribution;
343
344 struct Base36;
345 impl Distribution<char> for Base36 {
346 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> char {
347 let n: u8 = rng.gen_range(0..36);
348 if n < 10 {
349 (b'0' + n) as char
350 } else {
351 (b'a' + (n - 10)) as char
352 }
353 }
354 }
355
356 let suffix: String = Base36.sample_iter(rand::thread_rng()).take(6).collect();
357 format!("{prefix}-{suffix}")
358}
359
360const SAMPLE_CLI: &str = include_str!("../release-resources/cli.yaml");
365const SAMPLE_HARNESS: &str = include_str!("../release-resources/harness.yaml");
366
367fn run_init(force: bool) -> Result<(), CliError> {
368 let Some(dir) = tau_config::settings::config_dir() else {
369 return Err(CliError::Io(io::Error::new(
370 io::ErrorKind::NotFound,
371 "could not determine config directory",
372 )));
373 };
374 std::fs::create_dir_all(&dir)?;
375
376 let files = [("cli.yaml", SAMPLE_CLI), ("harness.yaml", SAMPLE_HARNESS)];
377
378 for (name, content) in &files {
379 let path = dir.join(name);
380 if path.exists() && !force {
381 eprintln!(
382 "skip: {} (exists, use --force to overwrite)",
383 path.display()
384 );
385 } else {
386 std::fs::write(&path, content)?;
387 eprintln!("wrote: {}", path.display());
388 }
389 }
390
391 eprintln!("next: use `tau provider add` to log in to a hosted LLM provider");
392
393 Ok(())
394}
395
396pub type ComponentRunner = fn() -> Result<(), Box<dyn std::error::Error>>;
401
402fn parse_role_cli_overrides<I, S>(args: I) -> Vec<tau_config::settings::RoleCliOverride>
403where
404 I: IntoIterator<Item = S>,
405 S: Into<std::ffi::OsString>,
406{
407 let mut overrides = Vec::new();
408 let mut args = args.into_iter().map(Into::into);
409 let _program = args.next();
410 while let Some(arg) = args.next() {
411 let arg = arg.to_string_lossy();
412 if arg == "--" {
413 break;
414 }
415 if arg == "--disable-roles-all" {
416 overrides.push(path_tau_config_settings::RoleCliOverride::DisableAll);
417 } else if let Some(role) = arg.strip_prefix("--enable-role=") {
418 overrides.push(path_tau_config_settings::RoleCliOverride::Enable(
419 role.to_owned(),
420 ));
421 } else if arg == "--enable-role" {
422 if let Some(role) = args.next() {
423 overrides.push(path_tau_config_settings::RoleCliOverride::Enable(
424 role.to_string_lossy().into_owned(),
425 ));
426 }
427 } else if let Some(role) = arg.strip_prefix("--disable-role=") {
428 overrides.push(path_tau_config_settings::RoleCliOverride::Disable(
429 role.to_owned(),
430 ));
431 } else if arg == "--disable-role"
432 && let Some(role) = args.next()
433 {
434 overrides.push(path_tau_config_settings::RoleCliOverride::Disable(
435 role.to_string_lossy().into_owned(),
436 ));
437 }
438 }
439 overrides
440}
441
442fn reject_harness_config_overrides(
443 overrides: &[tau_config::settings::HarnessConfigCliOverride],
444 command: &str,
445) -> Result<(), CliError> {
446 if overrides.is_empty() {
447 return Ok(());
448 }
449 Err(CliError::Participant(format!(
450 "--harness-config can only be used when starting a new harness instance; `{command}` cannot apply it to an existing or absent harness"
451 )))
452}
453
454#[derive(Clone, Copy, Default)]
456struct ModelReferenceAliasInputPresence {
457 provider_flag: bool,
459 model_flag: bool,
461 provider_environment: bool,
463 model_environment: bool,
465}
466
467fn reject_model_reference_alias_inputs(
470 presence: ModelReferenceAliasInputPresence,
471 command: &str,
472) -> Result<(), CliError> {
473 let source = if presence.provider_flag {
474 "--provider-alias"
475 } else if presence.model_flag {
476 "--model-alias"
477 } else if presence.provider_environment {
478 tau_config::settings::TAU_PROVIDER_ALIASES_ENV
479 } else if presence.model_environment {
480 tau_config::settings::TAU_MODEL_ALIASES_ENV
481 } else {
482 return Ok(());
483 };
484 Err(CliError::Participant(format!(
485 "{source} can only be used when starting a new harness instance; `{command}` cannot apply it to an existing or absent harness"
486 )))
487}
488
489fn validate_agent_trace_mode(
490 format: cli::AgentTraceFormat,
491 mode: cli::AgentTraceMode,
492) -> Result<(), CliError> {
493 if mode == cli::AgentTraceMode::Full
494 && !matches!(
495 format,
496 cli::AgentTraceFormat::AgentToolsJsonl | cli::AgentTraceFormat::AgentToolsToon
497 )
498 {
499 return Err(CliError::Participant(
500 "`agent trace --mode full` requires `--format agent-tools-jsonl` or \
501 `--format agent-tools-toon`"
502 .to_owned(),
503 ));
504 }
505 Ok(())
506}
507
508fn reject_dev_tmux_startup_overrides(
509 profile: Option<&str>,
510 startup_role: Option<&str>,
511 role_cli_overrides: &[tau_config::settings::RoleCliOverride],
512 extension_cli_overrides: &[tau_config::settings::ExtensionCliOverride],
513 harness_config_overrides: &[tau_config::settings::HarnessConfigCliOverride],
514) -> Result<(), CliError> {
515 if profile.is_some() {
516 return Err(CliError::Participant(
517 "`tau dev tmux` cannot use a configuration profile because the outer helper must not load normal user harness config before spawning the scratch Tau".to_owned(),
518 ));
519 }
520 if startup_role.is_some() {
521 return Err(CliError::Participant(
522 "`tau dev tmux` cannot use --role because the outer helper must not load normal user harness config before spawning the scratch Tau".to_owned(),
523 ));
524 }
525 if !role_cli_overrides.is_empty() {
526 return Err(CliError::Participant(
527 "`tau dev tmux` cannot use role enable/disable overrides because the outer helper must not load normal user harness config before spawning the scratch Tau".to_owned(),
528 ));
529 }
530 if !extension_cli_overrides.is_empty() {
531 return Err(CliError::Participant(
532 "`tau dev tmux` cannot use extension enable/disable overrides because the outer helper must not load normal user harness config before spawning the scratch Tau".to_owned(),
533 ));
534 }
535 reject_harness_config_overrides(harness_config_overrides, "dev tmux")
536}
537
538fn reject_legacy_config_path(config: Option<&std::path::Path>) -> Result<(), CliError> {
539 if let Some(path) = config {
540 return Err(CliError::Participant(format!(
541 "--config is no longer supported (got `{}`); use harness.yaml or --harness-config KEY=VALUE overrides",
542 path.display()
543 )));
544 }
545 Ok(())
546}
547
548fn reject_attach_startup_overrides(
549 prompt_stdin: bool,
550 profile_selected: bool,
551 startup_role: Option<&str>,
552 role_cli_overrides: &[tau_config::settings::RoleCliOverride],
553 extension_cli_overrides: &[tau_config::settings::ExtensionCliOverride],
554) -> Result<(), CliError> {
555 if profile_selected {
556 return Err(CliError::Participant(
557 "`tau attach` cannot apply --profile to an already-running daemon".to_owned(),
558 ));
559 }
560 if startup_role.is_some() && !prompt_stdin {
561 return Err(CliError::Participant(
562 "`tau attach` cannot apply --role to an already-running interactive daemon; use --prompt-stdin if you want --role for the submitted prompt".to_owned(),
563 ));
564 }
565 if !role_cli_overrides.is_empty() {
566 return Err(CliError::Participant(
567 "`tau attach` cannot apply role enable/disable overrides to an already-running daemon"
568 .to_owned(),
569 ));
570 }
571 if !extension_cli_overrides.is_empty() {
572 return Err(CliError::Participant(
573 "`tau attach` cannot apply extension enable/disable overrides to an already-running daemon"
574 .to_owned(),
575 ));
576 }
577 Ok(())
578}
579
580fn reject_ephemeral_incompatible(
581 ephemeral: bool,
582 startup_mode: &StartupMode,
583) -> Result<(), CliError> {
584 if ephemeral {
585 let command = match startup_mode {
586 StartupMode::New => return Ok(()),
587 StartupMode::Attach(_) => "attach",
588 StartupMode::Resume(_) => "resume",
589 };
590 return Err(CliError::Participant(format!(
591 "--ephemeral cannot be combined with `tau {command}`"
592 )));
593 }
594 Ok(())
595}
596
597fn required_harness_role<'a>(role: Option<&'a str>, command: &str) -> Result<&'a str, CliError> {
598 role.ok_or_else(|| CliError::Participant(format!("tau dev {command} requires --role <role>")))
599}
600
601fn parse_extension_cli_overrides<I, S>(args: I) -> Vec<tau_config::settings::ExtensionCliOverride>
602where
603 I: IntoIterator<Item = S>,
604 S: Into<std::ffi::OsString>,
605{
606 let mut overrides = Vec::new();
607 let mut args = args.into_iter().map(Into::into);
608 let _program = args.next();
609 while let Some(arg) = args.next() {
610 let arg = arg.to_string_lossy();
611 if arg == "--" {
612 break;
613 }
614 if arg == "--enable-extensions-all" {
615 overrides.push(path_tau_config_settings::ExtensionCliOverride::EnableAll);
616 } else if arg == "--disable-extensions-all" {
617 overrides.push(path_tau_config_settings::ExtensionCliOverride::DisableAll);
618 } else if let Some(extension) = arg.strip_prefix("--enable-extension=") {
619 overrides.push(path_tau_config_settings::ExtensionCliOverride::Enable(
620 extension.to_owned(),
621 ));
622 } else if arg == "--enable-extension" {
623 if let Some(extension) = args.next() {
624 overrides.push(path_tau_config_settings::ExtensionCliOverride::Enable(
625 extension.to_string_lossy().into_owned(),
626 ));
627 }
628 } else if let Some(extension) = arg.strip_prefix("--disable-extension=") {
629 overrides.push(path_tau_config_settings::ExtensionCliOverride::Disable(
630 extension.to_owned(),
631 ));
632 } else if arg == "--disable-extension"
633 && let Some(extension) = args.next()
634 {
635 overrides.push(path_tau_config_settings::ExtensionCliOverride::Disable(
636 extension.to_string_lossy().into_owned(),
637 ));
638 }
639 }
640 overrides
641}
642
643fn parse_harness_config_cli_overrides<I, S>(
644 args: I,
645) -> Result<Vec<tau_config::settings::HarnessConfigCliOverride>, String>
646where
647 I: IntoIterator<Item = S>,
648 S: Into<std::ffi::OsString>,
649{
650 let mut overrides = Vec::new();
651 let mut args = args.into_iter().map(Into::into);
652 let _program = args.next();
653 for arg in args {
654 let arg = arg.to_string_lossy();
655 if arg == "--" {
656 break;
657 }
658 if let Some(value) = arg.strip_prefix("--harness-config=") {
659 overrides.push(value.parse()?);
660 }
661 }
662 Ok(overrides)
663}
664
665#[derive(Clone, Copy, Debug, Eq, PartialEq)]
667pub enum ComponentLogging {
668 CliStderr,
670 RunnerManaged,
673}
674
675#[derive(Clone, Debug, Eq, PartialEq)]
677enum StartupMode {
678 New,
680 Attach(Option<tau_proto::SessionId>),
682 Resume(Option<tau_proto::SessionId>),
684}
685
686enum DispatchCommand {
688 Startup {
689 args: cli::RunArgs,
690 mode: StartupMode,
691 },
692 Other(cli::Command),
693}
694
695fn consumes_harness_settings(command: &DispatchCommand) -> bool {
701 match command {
702 DispatchCommand::Startup {
703 mode: StartupMode::Attach(_),
704 ..
705 } => false,
706 DispatchCommand::Startup { .. } => true,
707 DispatchCommand::Other(cli::Command::Dev {
708 command:
709 cli::DevCommand::DumpInitialPrompt { .. }
710 | cli::DevCommand::PrintPrompt { .. }
711 | cli::DevCommand::PrintSystemPrompt
712 | cli::DevCommand::PrintTools
713 | cli::DevCommand::PreviewDeclarations,
714 }) => true,
715 DispatchCommand::Other(cli::Command::Component { name, .. }) => name == "harness",
716 DispatchCommand::Other(cli::Command::Serve { .. }) => true,
717 _ => false,
718 }
719}
720
721fn rejects_model_reference_alias_inputs(command: &DispatchCommand) -> bool {
724 !consumes_harness_settings(command)
725 || matches!(
726 command,
727 DispatchCommand::Other(cli::Command::Dev {
728 command: cli::DevCommand::DumpInitialPrompt { .. }
729 })
730 )
731}
732
733pub struct Component {
734 pub name: &'static str,
736 pub runner: ComponentRunner,
738 pub logging: ComponentLogging,
740}
741
742pub fn main_with_args() -> std::process::ExitCode {
745 main_with_args_and_components(&[])
746}
747
748pub fn main_with_args_and_components(components: &[Component]) -> std::process::ExitCode {
752 tau_ext_provider_builtin::initialize_cache_diagnostic_build(build_revision());
753 use std::process::ExitCode;
754
755 use clap::{CommandFactory, FromArgMatches};
756
757 let run = || -> Result<(), CliError> {
758 let role_cli_overrides = parse_role_cli_overrides(std::env::args_os());
759 let extension_cli_overrides = parse_extension_cli_overrides(std::env::args_os());
760 let mut harness_config_overrides = parse_harness_config_cli_overrides(std::env::args_os())
761 .map_err(CliError::Participant)?;
762 let clap_command = cli::Cli::command();
763 let clap_matches = clap_command.get_matches();
764 let profile_was_explicitly_selected = matches!(
765 clap_matches.value_source("profile"),
766 Some(clap::parser::ValueSource::CommandLine)
767 );
768 let cli::Cli {
769 version,
770 harness,
771 run,
772 command,
773 } = cli::Cli::from_arg_matches(&clap_matches)
774 .expect("arguments accepted by Cli::command must parse as Cli");
775 let provider_alias_environment =
776 std::env::var_os(tau_config::settings::TAU_PROVIDER_ALIASES_ENV);
777 let model_alias_environment = std::env::var_os(tau_config::settings::TAU_MODEL_ALIASES_ENV);
778 let alias_input_presence = ModelReferenceAliasInputPresence {
779 provider_flag: !harness.provider_alias.is_empty(),
780 model_flag: !harness.model_alias.is_empty(),
781 provider_environment: provider_alias_environment.is_some(),
782 model_environment: model_alias_environment.is_some(),
783 };
784 if version {
785 reject_model_reference_alias_inputs(alias_input_presence, "version")?;
786 println!("{}", version_label());
787 return Ok(());
788 }
789 reject_legacy_config_path(run.config.as_deref())?;
790 if matches!(&command, Some(cli::Command::Serve { .. }))
791 && (run.ephemeral || run.prompt_stdin)
792 {
793 return Err(CliError::Participant(
794 "`tau serve` does not accept --ephemeral or --prompt-stdin".to_owned(),
795 ));
796 }
797 let command = match command {
798 Some(cli::Command::Run(args)) => DispatchCommand::Startup {
799 args,
800 mode: StartupMode::New,
801 },
802 Some(cli::Command::Attach { session }) => DispatchCommand::Startup {
803 args: run,
804 mode: StartupMode::Attach(session),
805 },
806 Some(cli::Command::Resume { session }) => DispatchCommand::Startup {
807 args: run,
808 mode: StartupMode::Resume(session),
809 },
810 Some(command) => DispatchCommand::Other(command),
811 None => DispatchCommand::Startup {
812 args: run,
813 mode: StartupMode::New,
814 },
815 };
816 let direct_harness_component = matches!(
817 &command,
818 DispatchCommand::Other(cli::Command::Component { name, .. }) if name == "harness"
819 );
820 let attaching = matches!(
821 &command,
822 DispatchCommand::Startup {
823 mode: StartupMode::Attach(_),
824 ..
825 }
826 );
827 let dump_initial_prompt = matches!(
828 &command,
829 DispatchCommand::Other(cli::Command::Dev {
830 command: cli::DevCommand::DumpInitialPrompt { .. }
831 })
832 );
833 if rejects_model_reference_alias_inputs(&command) || direct_harness_component {
834 reject_model_reference_alias_inputs(
835 ModelReferenceAliasInputPresence {
836 provider_environment: !direct_harness_component
837 && !attaching
838 && alias_input_presence.provider_environment,
839 model_environment: !direct_harness_component
840 && !attaching
841 && alias_input_presence.model_environment,
842 ..alias_input_presence
843 },
844 if direct_harness_component {
845 "component harness"
846 } else if dump_initial_prompt {
847 "dev dump-initial-prompt"
848 } else {
849 "this command"
850 },
851 )?;
852 }
853 let alias_config_overrides = tau_config::settings::model_reference_alias_config_overrides(
854 tau_config::settings::ModelReferenceAliasSources {
855 provider_environment: (!direct_harness_component && !attaching)
856 .then_some(provider_alias_environment)
857 .flatten(),
858 model_environment: (!direct_harness_component && !attaching)
859 .then_some(model_alias_environment)
860 .flatten(),
861 provider_cli: &harness.provider_alias,
862 model_cli: &harness.model_alias,
863 },
864 )
865 .map_err(|error| CliError::Participant(error.to_string()))?;
866 harness_config_overrides.extend(alias_config_overrides);
867 if let DispatchCommand::Startup {
868 args,
869 mode: StartupMode::Attach(_),
870 } = &command
871 {
872 reject_harness_config_overrides(&harness_config_overrides, "attach")?;
873 reject_attach_startup_overrides(
874 args.prompt_stdin,
875 profile_was_explicitly_selected,
876 harness.role.as_deref(),
877 &role_cli_overrides,
878 &extension_cli_overrides,
879 )?;
880 }
881 let profile_was_selected = profile_was_explicitly_selected
882 || std::env::var_os(tau_config::settings::TAU_PROFILE_ENV).is_some();
883 let reads_extension_environment = match &command {
884 DispatchCommand::Startup {
885 mode: StartupMode::New | StartupMode::Resume(_),
886 ..
887 } => true,
888 DispatchCommand::Other(cli::Command::Dev {
889 command:
890 cli::DevCommand::PrintPrompt { .. }
891 | cli::DevCommand::PrintSystemPrompt
892 | cli::DevCommand::PrintTools
893 | cli::DevCommand::PreviewDeclarations
894 | cli::DevCommand::Tmux { .. },
895 }) => true,
896 DispatchCommand::Other(cli::Command::Component { name, .. }) => name == "harness",
897 DispatchCommand::Other(cli::Command::Serve { .. }) => true,
898 _ => false,
899 };
900 let environment_extension_names = if reads_extension_environment {
901 tau_config::settings::parse_enable_extensions_env(std::env::var_os(
902 tau_config::settings::TAU_ENABLE_EXTENSIONS_ENV,
903 ))
904 .map_err(|error| CliError::Participant(error.to_string()))?
905 } else {
906 Vec::new()
907 };
908 if reads_extension_environment {
909 tau_config::settings::parse_tau_state_access_env(std::env::var_os(
910 tau_config::settings::TAU_EXTENSION_TAU_STATE_ACCESS_ENV,
911 ))
912 .map_err(|error| CliError::Participant(error.to_string()))?;
913 }
914 match &command {
915 DispatchCommand::Startup { .. }
916 | DispatchCommand::Other(cli::Command::Dev {
917 command:
918 cli::DevCommand::PrintPrompt { .. }
919 | cli::DevCommand::PrintSystemPrompt
920 | cli::DevCommand::PrintTools
921 | cli::DevCommand::PreviewDeclarations,
922 }) => {}
923 DispatchCommand::Other(cli::Command::Session { command }) => {
924 let command_name = match command {
925 cli::SessionCommand::List(_) => "session list",
926 cli::SessionCommand::Kill { .. } => "session kill",
927 cli::SessionCommand::Show { .. } => "session show",
928 cli::SessionCommand::Stats { .. } => "session stats",
929 cli::SessionCommand::Cache(_) => "session cache",
930 };
931 reject_harness_config_overrides(&harness_config_overrides, command_name)?;
932 }
933 DispatchCommand::Other(cli::Command::Agent { command }) => {
934 let command_name = match command {
935 cli::AgentCommand::List(_) => "agent list",
936 cli::AgentCommand::Unload(_) => "agent unload",
937 cli::AgentCommand::Trace(_) => "agent trace",
938 cli::AgentCommand::Cache(_) => "agent cache",
939 cli::AgentCommand::Export(_) => "agent export",
940 };
941 reject_harness_config_overrides(&harness_config_overrides, command_name)?;
942 }
943 DispatchCommand::Other(cli::Command::Init { .. }) => {
944 reject_harness_config_overrides(&harness_config_overrides, "init")?;
945 }
946 DispatchCommand::Other(cli::Command::Provider { .. }) => {
947 reject_harness_config_overrides(&harness_config_overrides, "provider")?;
948 }
949 DispatchCommand::Other(cli::Command::Dev {
950 command: cli::DevCommand::Send { .. },
951 }) => {
952 reject_harness_config_overrides(&harness_config_overrides, "dev send")?;
953 }
954 DispatchCommand::Other(cli::Command::Dev {
955 command: cli::DevCommand::Papercut { .. },
956 }) => {
957 reject_harness_config_overrides(&harness_config_overrides, "dev papercut")?;
958 }
959 DispatchCommand::Other(cli::Command::Dev {
960 command: cli::DevCommand::Tmux { .. },
961 }) => {
962 reject_dev_tmux_startup_overrides(
963 profile_was_selected.then_some("selected"),
964 harness.role.as_deref(),
965 &role_cli_overrides,
966 &extension_cli_overrides,
967 &harness_config_overrides,
968 )?;
969 }
970 DispatchCommand::Other(cli::Command::Dev {
971 command: cli::DevCommand::DumpInitialPrompt { .. },
972 }) => {
973 reject_harness_config_overrides(
974 &harness_config_overrides,
975 "dev dump-initial-prompt",
976 )?;
977 }
978 DispatchCommand::Other(cli::Command::Component { .. }) => {
979 reject_harness_config_overrides(&harness_config_overrides, "component")?;
980 }
981 DispatchCommand::Other(cli::Command::Serve { .. }) => {}
982 DispatchCommand::Other(cli::Command::Run(_))
983 | DispatchCommand::Other(cli::Command::Attach { .. })
984 | DispatchCommand::Other(cli::Command::Resume { .. }) => {
985 unreachable!("startup variants normalize to DispatchCommand::Startup")
986 }
987 }
988
989 if let DispatchCommand::Other(cli::Command::Dev {
990 command: cli::DevCommand::Tmux { command },
991 }) = command
992 {
993 return dev_tmux::run(command);
994 }
995
996 if matches!(
999 &command,
1000 DispatchCommand::Other(cli::Command::Dev {
1001 command: cli::DevCommand::PreviewDeclarations,
1002 })
1003 ) {
1004 return preview_declarations::run(
1005 harness.profile.as_deref(),
1006 &role_cli_overrides,
1007 &extension_cli_overrides,
1008 &environment_extension_names,
1009 &harness_config_overrides,
1010 );
1011 }
1012 let consumes_harness_settings = consumes_harness_settings(&command);
1013 let selected_profile = if consumes_harness_settings {
1014 tau_config::settings::selected_profile(harness.profile.as_deref())
1015 .map_err(|error| CliError::Participant(error.to_string()))?
1016 } else {
1017 None
1018 };
1019 if consumes_harness_settings {
1020 tau_harness::validate_cli_overrides_with_profile(
1021 selected_profile.as_ref(),
1022 &role_cli_overrides,
1023 &extension_cli_overrides,
1024 &harness_config_overrides,
1025 )
1026 .map_err(|error| CliError::Participant(error.to_string()))?;
1027 }
1028 if reads_extension_environment && consumes_harness_settings {
1029 tau_harness::validate_extension_environment_and_cli_overrides_with_profile(
1030 selected_profile.as_ref(),
1031 &environment_extension_names,
1032 &extension_cli_overrides,
1033 &role_cli_overrides,
1034 &harness_config_overrides,
1035 )
1036 .map_err(|error| CliError::Participant(error.to_string()))?;
1037 }
1038 match command {
1039 DispatchCommand::Startup {
1040 args:
1041 cli::RunArgs {
1042 config,
1043 prompt_stdin,
1044 ephemeral,
1045 },
1046 mode: startup_mode,
1047 } => {
1048 reject_legacy_config_path(config.as_deref())?;
1049 reject_ephemeral_incompatible(ephemeral, &startup_mode)?;
1050 let (session_id, session_status) = match &startup_mode {
1051 StartupMode::Attach(session) => (
1052 crate::daemon::resolve_attach_session_id(session.as_ref())?,
1053 SessionLaunchStatus::Resumed,
1054 ),
1055 StartupMode::Resume(session) => (
1056 resolve_resume_session_id(session.as_ref())?,
1057 SessionLaunchStatus::Resumed,
1058 ),
1059 StartupMode::New => (
1060 crate::daemon::mint_session_id(&std::env::current_dir()?),
1061 SessionLaunchStatus::New,
1062 ),
1063 };
1064 let attach = matches!(startup_mode, StartupMode::Attach(_));
1065 if prompt_stdin {
1066 prompt_stdin::run_prompt_stdin(
1067 &session_id,
1068 attach,
1069 session_status,
1070 harness.role.as_deref(),
1071 crate::daemon::DaemonCliOverrides {
1072 profile: selected_profile.as_ref(),
1073 role: &role_cli_overrides,
1074 extension: &extension_cli_overrides,
1075 extension_environment: None,
1076 harness_config: &harness_config_overrides,
1077 memory_only_agent_store: false,
1078 },
1079 ephemeral,
1080 )
1081 } else {
1082 run_chat(
1083 &session_id,
1084 attach,
1085 session_status,
1086 harness.role.as_deref(),
1087 crate::daemon::DaemonCliOverrides {
1088 profile: selected_profile.as_ref(),
1089 role: &role_cli_overrides,
1090 extension: &extension_cli_overrides,
1091 extension_environment: None,
1092 harness_config: &harness_config_overrides,
1093 memory_only_agent_store: false,
1094 },
1095 ephemeral,
1096 )
1097 }
1098 }
1099
1100 DispatchCommand::Other(cli::Command::Session {
1101 command: cli::SessionCommand::Kill { session_id },
1102 }) => {
1103 reject_harness_config_overrides(&harness_config_overrides, "session kill")?;
1104 kill_session::run(&session_id)
1105 }
1106
1107 DispatchCommand::Other(cli::Command::Session {
1108 command: cli::SessionCommand::List(args),
1109 }) => {
1110 reject_harness_config_overrides(&harness_config_overrides, "session list")?;
1111 list_sessions::run(&args)
1112 }
1113
1114 DispatchCommand::Other(cli::Command::Session {
1115 command:
1116 cli::SessionCommand::Show {
1117 session_id,
1118 sessions_dir,
1119 },
1120 }) => {
1121 reject_harness_config_overrides(&harness_config_overrides, "session show")?;
1122 for line in tau_session_inspect::session_lines(sessions_dir, &session_id)? {
1123 println!("{line}");
1124 }
1125 Ok(())
1126 }
1127
1128 DispatchCommand::Other(cli::Command::Session {
1129 command:
1130 cli::SessionCommand::Stats {
1131 session,
1132 sessions_dir,
1133 },
1134 }) => {
1135 reject_harness_config_overrides(&harness_config_overrides, "session stats")?;
1136 let stats = tau_session_inspect::read_session_stats(&sessions_dir, &session)?
1137 .ok_or_else(|| {
1138 CliError::Participant(format!("session `{session}` not found"))
1139 })?;
1140 let output = serde_toon::to_string(&stats).map_err(|error| {
1141 CliError::Participant(format!("failed to serialize session stats: {error}"))
1142 })?;
1143 println!("{output}");
1144 Ok(())
1145 }
1146
1147 DispatchCommand::Other(cli::Command::Agent {
1148 command: cli::AgentCommand::List(args),
1149 }) => {
1150 reject_harness_config_overrides(&harness_config_overrides, "agent list")?;
1151 list_agents::run(&args)
1152 }
1153 DispatchCommand::Other(cli::Command::Agent {
1154 command: cli::AgentCommand::Unload(args),
1155 }) => {
1156 reject_harness_config_overrides(&harness_config_overrides, "agent unload")?;
1157 unload_agent::run(&args)
1158 }
1159 DispatchCommand::Other(cli::Command::Agent {
1160 command: cli::AgentCommand::Cache(args),
1161 }) => run_cache_report(
1162 args.options,
1163 tau_session_inspect::CacheScope::Agent {
1164 agent_id: args.agent_id,
1165 include_descendants: args.include_descendants,
1166 },
1167 ),
1168 DispatchCommand::Other(cli::Command::Agent {
1169 command:
1170 cli::AgentCommand::Export(cli::AgentExportArgs {
1171 command: cli::AgentExportCommand::Chat(args),
1172 }),
1173 }) => {
1174 reject_harness_config_overrides(&harness_config_overrides, "agent export chat")?;
1175 let chat = tau_session_inspect::load_agent_chat(&args.agents_dir, &args.agent_id)?;
1176 let output = if args.toons {
1177 chat.to_toon()?
1178 } else {
1179 chat.to_markdown()
1180 };
1181 line_output::stream_stdout(|writer| writer.write_all(output.as_bytes()))
1182 }
1183 DispatchCommand::Other(cli::Command::Session {
1184 command: cli::SessionCommand::Cache(args),
1185 }) => run_cache_report(
1186 args.options,
1187 tau_session_inspect::CacheScope::Session(args.session_id),
1188 ),
1189 DispatchCommand::Other(cli::Command::Agent {
1190 command: cli::AgentCommand::Trace(args),
1191 }) => {
1192 reject_harness_config_overrides(&harness_config_overrides, "agent trace")?;
1193 let mode = match args.mode {
1194 cli::AgentTraceMode::Lite => tau_session_inspect::AgentTraceMode::Lite,
1195 cli::AgentTraceMode::Full => tau_session_inspect::AgentTraceMode::Full,
1196 };
1197 let format = match args.format {
1198 cli::AgentTraceFormat::TauJsonl => {
1199 tau_session_inspect::AgentTraceFormat::TauJsonl
1200 }
1201 cli::AgentTraceFormat::OtlpJson => {
1202 tau_session_inspect::AgentTraceFormat::OtlpJson
1203 }
1204 cli::AgentTraceFormat::AgentToolsJsonl => {
1205 tau_session_inspect::AgentTraceFormat::AgentToolsJsonl(mode)
1206 }
1207 cli::AgentTraceFormat::AgentToolsToon => {
1208 tau_session_inspect::AgentTraceFormat::AgentToolsToon(mode)
1209 }
1210 cli::AgentTraceFormat::AgentPerformanceJsonl => {
1211 tau_session_inspect::AgentTraceFormat::AgentPerformanceJsonl
1212 }
1213 };
1214 validate_agent_trace_mode(args.format, args.mode)?;
1215 let mut output = tau_session_inspect::prepare_agent_trace(
1216 &args.agents_dir,
1217 &args.agent_id,
1218 if args.include_descendants {
1219 tau_session_inspect::DescendantSelection::Include
1220 } else {
1221 tau_session_inspect::DescendantSelection::RootOnly
1222 },
1223 format,
1224 )?;
1225 line_output::stream_stdout(|writer| output.copy_to(writer).map(|_| ()))
1226 }
1227
1228 DispatchCommand::Other(cli::Command::Init { force }) => {
1229 reject_harness_config_overrides(&harness_config_overrides, "init")?;
1230 run_init(force)
1231 }
1232
1233 DispatchCommand::Other(cli::Command::Provider { args }) => {
1234 reject_harness_config_overrides(&harness_config_overrides, "provider")?;
1235 tau_ext_provider_builtin::run_provider_cli(&args)
1236 .map_err(|e| CliError::Participant(e.to_string()))
1237 }
1238
1239 DispatchCommand::Other(cli::Command::Dev { command }) => match command {
1240 cli::DevCommand::Send { session_id, line } => {
1241 reject_harness_config_overrides(&harness_config_overrides, "dev send")?;
1242 send::run_send(&session_id, &line.join(" "))
1243 }
1244 cli::DevCommand::DumpInitialPrompt { out, message } => {
1245 reject_harness_config_overrides(
1246 &harness_config_overrides,
1247 "dev dump-initial-prompt",
1248 )?;
1249 tau_harness::dump_initial_prompt(&out, &message)?;
1250 println!("wrote {}", out.display());
1251 Ok(())
1252 }
1253 cli::DevCommand::PrintPrompt { enable_agents_md } => {
1254 print_prompt::run_print_prompt(
1255 harness.role.as_deref(),
1256 enable_agents_md,
1257 selected_profile.as_ref(),
1258 &role_cli_overrides,
1259 &extension_cli_overrides,
1260 &environment_extension_names,
1261 &harness_config_overrides,
1262 )
1263 }
1264 cli::DevCommand::PrintSystemPrompt => {
1265 let role =
1266 required_harness_role(harness.role.as_deref(), "print-system-prompt")?;
1267 print_prompt::run_print_system_prompt(
1268 role,
1269 selected_profile.as_ref(),
1270 &role_cli_overrides,
1271 &extension_cli_overrides,
1272 &environment_extension_names,
1273 &harness_config_overrides,
1274 )
1275 }
1276 cli::DevCommand::PrintTools => print_tools::run_print_tools(
1277 harness.role.as_deref(),
1278 selected_profile.as_ref(),
1279 &role_cli_overrides,
1280 &extension_cli_overrides,
1281 &environment_extension_names,
1282 &harness_config_overrides,
1283 ),
1284 cli::DevCommand::Papercut { command } => papercut::run(command),
1285 cli::DevCommand::PreviewDeclarations => {
1286 unreachable!("declaration inspection returns before runtime validation")
1287 }
1288 cli::DevCommand::Tmux { command } => {
1289 let _ = command;
1290 unreachable!("dev tmux dispatch returns before harness config validation")
1291 }
1292 },
1293
1294 DispatchCommand::Other(cli::Command::Component {
1295 name,
1296 initial_ui_stdio,
1297 }) => {
1298 reject_harness_config_overrides(&harness_config_overrides, "component")?;
1299 if harness.profile.is_some() {
1300 return Err(CliError::Participant(
1301 "`tau component` cannot apply --profile in-process; set TAU_PROFILE before starting the component"
1302 .to_owned(),
1303 ));
1304 }
1305 if initial_ui_stdio && name != "harness" {
1306 return Err(CliError::Participant(
1307 "--initial-ui-stdio is only valid for `tau component harness`".to_owned(),
1308 ));
1309 }
1310 if name == "harness" && initial_ui_stdio {
1311 ui_logging::init_stderr_from_env("tau_harness=info,tau_cli=info,warn");
1312 return run_harness_component(true)
1313 .map_err(|e| CliError::Participant(e.to_string()));
1314 }
1315 if name == "harness" {
1316 ui_logging::init_stderr_from_env("tau_harness=info,tau_cli=info,warn");
1317 return tau_harness::run_component_with_internal_tools_and_extension_cli_overrides(
1318 tau_harness_tools::builtin_handlers(),
1319 extension_cli_overrides.clone(),
1320 )
1321 .map_err(|error| CliError::Participant(error.to_string()));
1322 }
1323 let built_in_components = [Component {
1324 name: "harness",
1325 runner: run_harness_component_default,
1326 logging: ComponentLogging::CliStderr,
1327 }];
1328 let component = built_in_components
1329 .iter()
1330 .chain(components)
1331 .find(|component| component.name == name)
1332 .ok_or_else(|| {
1333 let available = built_in_components
1334 .iter()
1335 .chain(components)
1336 .map(|component| component.name)
1337 .collect::<Vec<_>>()
1338 .join(", ");
1339 CliError::Participant(format!(
1340 "unknown component: {name}\navailable: {available}"
1341 ))
1342 })?;
1343 match component.logging {
1344 ComponentLogging::CliStderr => {
1345 ui_logging::init_stderr_from_env("tau_harness=info,tau_cli=info,warn")
1346 }
1347 ComponentLogging::RunnerManaged => {}
1348 }
1349 (component.runner)().map_err(|e| CliError::Participant(e.to_string()))
1350 }
1351 DispatchCommand::Other(cli::Command::Serve {
1352 session,
1353 create,
1354 existing,
1355 create_or_existing: _,
1356 bootstrap_prompt_file,
1357 bootstrap_id,
1358 mirror_extension_stderr,
1359 }) => {
1360 ui_logging::init_stderr_from_env("tau_harness=info,tau_cli=info,warn");
1361 let options = tau_harness::FixedSessionServeOptions {
1362 session_id: &session,
1363 profile_selection: selected_profile.as_ref(),
1364 startup_role: harness.role.as_deref(),
1365 environment_extension_names: &environment_extension_names,
1366 extension_cli_overrides: &extension_cli_overrides,
1367 role_cli_overrides: &role_cli_overrides,
1368 harness_config_overrides: &harness_config_overrides,
1369 internal_tool_handlers: tau_harness_tools::builtin_handlers(),
1370 bootstrap: bootstrap_prompt_file
1371 .as_deref()
1372 .zip(bootstrap_id.as_ref())
1373 .map(|(prompt_file, id)| tau_harness::BootstrapPromptOptions {
1374 prompt_file,
1375 id,
1376 }),
1377 mirror_extension_stderr,
1378 };
1379 if create {
1380 tau_harness::run_create_session_component_with_internal_tools(options)
1381 } else if existing {
1382 tau_harness::run_existing_session_component_with_internal_tools(options)
1383 } else {
1384 tau_harness::run_create_or_existing_session_component_with_internal_tools(
1385 options,
1386 )
1387 }
1388 .map_err(|error| CliError::Participant(error.to_string()))
1389 }
1390 DispatchCommand::Other(
1391 cli::Command::Run(_) | cli::Command::Attach { .. } | cli::Command::Resume { .. },
1392 ) => {
1393 unreachable!("startup variants normalize to DispatchCommand::Startup")
1394 }
1395 }
1396 };
1397
1398 match run() {
1399 Ok(()) => ExitCode::SUCCESS,
1400 Err(error) => {
1401 if error.should_report_to_terminal() {
1402 eprintln!("error: {error}");
1403 }
1404 error.exit_code()
1405 }
1406 }
1407}
1408
1409fn run_cache_report(
1411 args: cli::CacheArgs,
1412 scope: tau_session_inspect::CacheScope,
1413) -> Result<(), CliError> {
1414 let options = tau_session_inspect::CacheOptions {
1415 state_dir: args.state_dir,
1416 scope,
1417 prompt: args.prompt,
1418 selection: tau_session_inspect::CacheSelection {
1419 since_unix_micros: args.since,
1420 until_unix_micros: args.until,
1421 model: args.model,
1422 operation: args.operation.map(|operation| match operation {
1423 cli::CacheOperation::Inference => tau_session_inspect::CacheOperation::Inference,
1424 cli::CacheOperation::StandaloneCompaction => {
1425 tau_session_inspect::CacheOperation::StandaloneCompaction
1426 }
1427 cli::CacheOperation::CacheRefresh => {
1428 tau_session_inspect::CacheOperation::CacheRefresh
1429 }
1430 }),
1431 attempt: args.attempt,
1432 require_exact_chain: args.require_exact_chain,
1433 group_by: args
1434 .group_by
1435 .into_iter()
1436 .map(|group| match group {
1437 cli::CacheGroup::Model => tau_session_inspect::CacheGroup::Model,
1438 cli::CacheGroup::Backend => tau_session_inspect::CacheGroup::Backend,
1439 cli::CacheGroup::Controls => tau_session_inspect::CacheGroup::Controls,
1440 })
1441 .collect(),
1442 },
1443 view: match args.view {
1444 cli::CacheView::Summary => tau_session_inspect::CacheView::Summary,
1445 cli::CacheView::Attribution => tau_session_inspect::CacheView::Attribution,
1446 cli::CacheView::Continuity => tau_session_inspect::CacheView::Continuity,
1447 cli::CacheView::Geometry => tau_session_inspect::CacheView::Geometry,
1448 cli::CacheView::Gaps => tau_session_inspect::CacheView::Gaps,
1449 },
1450 limits: tau_session_inspect::CacheScanLimits {
1451 compressed_file_bytes: args.max_compressed_bytes,
1452 decompressed_file_bytes: args.max_decompressed_bytes,
1453 total_decompressed_bytes: args.max_total_bytes,
1454 working_memory_bytes: args.max_memory_bytes,
1455 },
1456 producer_build: build_revision(),
1457 index: args.index,
1458 };
1459 let report =
1460 tau_session_inspect::read_cache_report(&options).map_err(|_| CliError::CacheInvalid)?;
1461 line_output::stream_stdout(|writer| match (args.format, args.view) {
1462 (cli::CacheFormat::Summary, cli::CacheView::Summary) => report.write_summary(writer),
1463 (cli::CacheFormat::Summary, _) | (cli::CacheFormat::Jsonl, _) => report.write_jsonl(writer),
1464 })?;
1465 if report.index_written() {
1466 eprintln!(
1467 "Wrote a private disposable cache index; it contains linkable evidence and must be deleted manually when no longer needed."
1468 );
1469 }
1470 if report.is_partial() {
1471 Err(CliError::CachePartial)
1472 } else {
1473 Ok(())
1474 }
1475}
1476
1477#[cfg(test)]
1482mod test_support;
1483#[cfg(test)]
1484mod tests;