tau_cli/cli.rs
1use std::path::{Path, PathBuf};
2
3use clap::{Args, Parser, Subcommand, ValueEnum};
4use tau_proto::SessionId;
5use tau_session_inspect::{
6 default_agents_dir, default_session_id, default_sessions_dir, default_state_dir,
7};
8
9#[cfg(test)]
10mod tests;
11
12#[derive(Parser)]
13#[command(
14 name = "tau",
15 about = "Unix-native LLM agent harness",
16 disable_version_flag = true
17)]
18pub struct Cli {
19 /// Print version, build revision, and build date.
20 #[arg(short = 'V', long = "version", global = true)]
21 pub version: bool,
22
23 #[command(flatten)]
24 pub harness: HarnessArgs,
25
26 #[command(flatten)]
27 pub run: RunArgs,
28
29 #[command(subcommand)]
30 pub command: Option<Command>,
31}
32
33#[derive(Args)]
34pub struct HarnessArgs {
35 #[command(flatten)]
36 pub role_overrides: RoleOverrideArgs,
37
38 #[command(flatten)]
39 pub extension_overrides: ExtensionOverrideArgs,
40
41 /// Select the startup/rendered role.
42 #[arg(short = 'r', long = "role")]
43 pub role: Option<String>,
44
45 /// Select comma-separated configuration profiles before CLI overrides.
46 #[arg(long = "profile", value_name = "PROFILE")]
47 pub profile: Option<String>,
48
49 /// Override one harness config key after all config files are loaded.
50 #[arg(
51 long = "harness-config",
52 value_name = "KEY=VALUE",
53 require_equals = true
54 )]
55 pub harness_config: Vec<tau_config::settings::HarnessConfigCliOverride>,
56
57 /// Override one provider alias for this harness startup.
58 ///
59 /// `TAU_PROVIDER_ALIASES` supplies a JSON object of lower-precedence
60 /// environment overrides.
61 #[arg(long = "provider-alias", value_name = "FROM=TO")]
62 pub provider_alias: Vec<tau_config::settings::ProviderAliasCliOverride>,
63
64 /// Override one exact model-name alias for this harness startup.
65 ///
66 /// `TAU_MODEL_ALIASES` supplies a JSON object of lower-precedence
67 /// environment overrides.
68 #[arg(long = "model-alias", value_name = "FROM=TO")]
69 pub model_alias: Vec<tau_config::settings::ModelAliasCliOverride>,
70}
71
72#[derive(Args)]
73pub struct RoleOverrideArgs {
74 /// Enable a configured role after all config files are loaded.
75 #[arg(long = "enable-role")]
76 pub enable_role: Vec<String>,
77
78 /// Disable a configured role after all config files are loaded.
79 #[arg(long = "disable-role")]
80 pub disable_role: Vec<String>,
81
82 /// Disable every configured role before later CLI role overrides.
83 #[arg(long = "disable-roles-all", action = clap::ArgAction::Count)]
84 pub disable_roles_all: u8,
85}
86
87#[derive(Args)]
88pub struct ExtensionOverrideArgs {
89 /// Enable non-test configured extensions before later CLI extension
90 /// overrides. The built-in test-dummy fixture still requires explicit
91 /// `--enable-extension test-dummy`.
92 #[arg(long = "enable-extensions-all", action = clap::ArgAction::Count)]
93 pub enable_extensions_all: u8,
94
95 /// Disable every configured extension before later CLI extension overrides.
96 #[arg(long = "disable-extensions-all", action = clap::ArgAction::Count)]
97 pub disable_extensions_all: u8,
98
99 /// Enable a configured extension after all config files are loaded.
100 ///
101 /// `TAU_ENABLE_EXTENSIONS=NAME[,NAME...]` additively enables exact
102 /// configured names before ordered CLI overrides. Space/tab around
103 /// names is allowed; malformed or unknown names fail startup. CLI
104 /// enable/disable flags win.
105 #[arg(long = "enable-extension")]
106 pub enable_extension: Vec<String>,
107
108 /// Disable a configured extension after all config files are loaded.
109 #[arg(long = "disable-extension")]
110 pub disable_extension: Vec<String>,
111}
112
113#[derive(Args)]
114/// Options shared by new, attach, and resume session startup.
115pub struct RunArgs {
116 /// Deprecated legacy extension config path; use `--harness-config`
117 /// overrides instead.
118 #[arg(long, hide = true)]
119 pub config: Option<PathBuf>,
120
121 /// Read one prompt from stdin, submit it, print final output, and exit.
122 ///
123 /// Answers go to stdout; reasoning, headers, and errors go to stderr.
124 /// Each destination's terminal state is checked independently, and dynamic
125 /// bodies are sanitized only when that destination is a terminal. Pipes and
126 /// files retain semantic UTF-8 bytes and existing framing.
127 #[arg(long = "prompt-stdin")]
128 pub prompt_stdin: bool,
129
130 /// Run without writing session membership, session metadata, session debug
131 /// events, per-session logs, session-scoped extension data, or the terminal
132 /// UI log to disk.
133 ///
134 /// Agent transcripts, provider state, credentials, user/cache extension
135 /// data, runtime sockets, and configuration state keep their normal
136 /// persistence behavior.
137 #[arg(long)]
138 pub ephemeral: bool,
139}
140
141#[derive(Subcommand)]
142pub enum Command {
143 /// Run an interactive agent session.
144 ///
145 /// `tau` spawns a new harness daemon and attaches for this process.
146 #[command(hide = true)]
147 Run(RunArgs),
148
149 /// Attach to a running session without taking daemon ownership.
150 Attach {
151 /// Running session id; omit to choose interactively.
152 session: Option<SessionId>,
153 },
154
155 /// Resume a persisted session in a new harness daemon.
156 Resume {
157 /// Persisted session id; omission auto-selects the sole unlocked
158 /// target, otherwise shows the eligible-session picker.
159 session: Option<SessionId>,
160 },
161
162 /// Serve one fixed session in the foreground without an initial UI.
163 Serve {
164 /// Exact session id to create or resume.
165 #[arg(long)]
166 session: SessionId,
167
168 /// Require the session directory to be completely absent, then create
169 /// it.
170 #[arg(
171 long,
172 required_unless_present_any = ["existing", "create_or_existing"],
173 conflicts_with_all = ["existing", "create_or_existing"]
174 )]
175 create: bool,
176
177 /// Require and strictly resume valid existing session state.
178 #[arg(
179 long,
180 required_unless_present_any = ["create", "create_or_existing"],
181 conflicts_with_all = ["create", "create_or_existing"]
182 )]
183 existing: bool,
184
185 /// Resume valid existing state or atomically create an absent session.
186 #[arg(
187 long,
188 required_unless_present_any = ["create", "existing"],
189 conflicts_with_all = ["create", "existing"]
190 )]
191 create_or_existing: bool,
192
193 /// Read one literal bootstrap prompt from this UTF-8 file after
194 /// startup.
195 ///
196 /// `-` reads stdin through EOF once. The paired bootstrap id makes this
197 /// submission durable and at-most-once across restarts.
198 #[arg(
199 long = "bootstrap-prompt-file",
200 value_name = "PATH",
201 requires = "bootstrap_id"
202 )]
203 bootstrap_prompt_file: Option<PathBuf>,
204
205 /// Durable bootstrap generation id.
206 #[arg(
207 long = "bootstrap-id",
208 value_name = "ID",
209 requires = "bootstrap_prompt_file"
210 )]
211 bootstrap_id: Option<tau_harness::BootstrapId>,
212
213 /// Mirror framed, escaped extension stderr to this process's stderr.
214 ///
215 /// Private per-session extension log files remain authoritative. Custom
216 /// extension stderr is unredacted and may reach a wider journal
217 /// audience.
218 #[arg(long)]
219 mirror_extension_stderr: bool,
220 },
221
222 /// Inspect sessions.
223 Session {
224 #[command(subcommand)]
225 command: SessionCommand,
226 },
227
228 /// Inspect agents.
229 Agent {
230 #[command(subcommand)]
231 command: AgentCommand,
232 },
233
234 /// Copy sample config files to ~/.config/tau/
235 Init {
236 /// Overwrite existing config files
237 #[arg(long)]
238 force: bool,
239 },
240
241 /// Manage LLM providers (add, remove, list)
242 Provider {
243 /// Subcommand and arguments (e.g. `add`, `remove <name>`, `list`)
244 #[arg(trailing_var_arg = true)]
245 args: Vec<String>,
246 },
247
248 /// Developer-only commands.
249 #[command(hide = true, hide_possible_values = true)]
250 Dev {
251 #[command(subcommand)]
252 command: DevCommand,
253 },
254
255 /// Run a bundled Tau component as a standalone process.
256 ///
257 /// Bundled extensions are components too, but not every component is an
258 /// extension; for example, the harness is a component.
259 Component {
260 /// Component name (harness or a bundled extension such as ext-shell,
261 /// ext-provider-builtin, ext-websearch, ext-rhai,
262 /// ext-std-notifications, or ext-test-dummy)
263 name: String,
264
265 /// Use stdin/stdout as the initial UI connection before starting
266 /// harness extensions. Only valid with `tau component harness`.
267 #[arg(long, hide = true)]
268 initial_ui_stdio: bool,
269 },
270}
271
272#[derive(Subcommand)]
273pub enum SessionCommand {
274 /// Inspect durable cache accounting and private capture coverage offline.
275 Cache(SessionCacheArgs),
276 /// Gracefully shut down one exact running session.
277 Kill {
278 /// Exact session identifier to shut down
279 session_id: tau_proto::SessionId,
280 },
281 /// List currently running sessions.
282 List(SessionListArgs),
283
284 /// Show a single session's history.
285 Show {
286 /// Session identifier
287 #[arg(
288 long,
289 default_value_t = tau_proto::SessionId::parse(default_session_id())
290 .expect("configured default session id must be valid")
291 )]
292 session_id: tau_proto::SessionId,
293
294 /// Path to per-session storage root (`<state-dir>/sessions/`)
295 #[arg(long, default_value_os_t = default_sessions_dir())]
296 sessions_dir: PathBuf,
297 },
298
299 /// Print exact durable activity accounting for one session as TOON.
300 Stats {
301 /// Session identifier to account.
302 #[arg(long)]
303 session: tau_proto::SessionId,
304
305 /// Path to per-session storage root (`<state-dir>/sessions/`).
306 #[arg(long, default_value_os_t = default_sessions_dir())]
307 sessions_dir: PathBuf,
308 },
309}
310
311/// Output and exact-directory filters for `tau session list`.
312#[derive(Args, Clone, Debug, Default)]
313pub struct SessionListArgs {
314 /// List only harnesses whose canonical startup root is this directory.
315 #[arg(long, value_name = "DIR", value_parser = parse_canonical_directory)]
316 pub dir: Option<PathBuf>,
317
318 /// Emit one JSON array with session id and canonical project root fields.
319 #[arg(long)]
320 pub json: bool,
321}
322
323/// Canonicalizes one existing directory during CLI parsing.
324fn parse_canonical_directory(value: &str) -> Result<PathBuf, String> {
325 let canonical = Path::new(value)
326 .canonicalize()
327 .map_err(|error| format!("cannot access directory `{value}`: {error}"))?;
328 if !canonical.is_dir() {
329 return Err(format!("path is not a directory: `{value}`"));
330 }
331 Ok(canonical)
332}
333
334#[derive(Subcommand)]
335pub enum AgentCommand {
336 /// Inspect durable cache accounting and private capture coverage offline.
337 Cache(AgentCacheArgs),
338 /// Export a durable agent artifact for offline use.
339 Export(AgentExportArgs),
340 /// List agents known to a running session.
341 List(AgentListArgs),
342 /// Unload one idle saved agent without deleting its transcript or session
343 /// history.
344 ///
345 /// Only durable agents are supported. After the request is sent, timeout or
346 /// disconnect is indeterminate; retrying the same session and agent is
347 /// safe.
348 Unload(AgentUnloadArgs),
349 /// Project a validated durable agent snapshot (defaults to compact TOON
350 /// lite).
351 Trace(AgentTraceArgs),
352}
353
354/// Options for `tau agent export`.
355#[derive(Args, Clone)]
356pub struct AgentExportArgs {
357 /// Artifact to export.
358 #[command(subcommand)]
359 pub command: AgentExportCommand,
360}
361
362/// Durable agent artifacts available for export.
363#[derive(Subcommand, Clone)]
364pub enum AgentExportCommand {
365 /// Export user prompts and agent responses from the selected branch.
366 Chat(AgentExportChatArgs),
367}
368
369/// Options for `tau agent export chat`.
370#[derive(Args, Clone)]
371pub struct AgentExportChatArgs {
372 /// Durable agent journal to export.
373 pub agent_id: tau_proto::AgentId,
374 /// Emit the existing strict TOON serialization instead of Markdown.
375 #[arg(long, conflicts_with = "markdown")]
376 pub toons: bool,
377 /// Explicitly select Markdown, which is already the default.
378 #[arg(long, conflicts_with = "toons")]
379 pub markdown: bool,
380 /// Durable agent journal root.
381 #[arg(long, default_value_os_t = default_agents_dir())]
382 pub agents_dir: PathBuf,
383}
384
385/// Offline cache report options shared by agent and session scopes.
386#[derive(Args, Clone)]
387pub struct CacheArgs {
388 /// Emit a compact summary or internal version-zero JSON Lines.
389 #[arg(long, value_enum, default_value_t)]
390 pub format: CacheFormat,
391 /// Restrict canonical responses to this exact local prompt.
392 #[arg(long)]
393 pub prompt: Option<tau_proto::AgentPromptId>,
394 /// Include observations at or after this absolute RFC3339 timestamp.
395 #[arg(long, value_parser = parse_cache_since)]
396 pub since: Option<u64>,
397 /// Include observations at or before this absolute RFC3339 timestamp.
398 #[arg(long, value_parser = parse_cache_until)]
399 pub until: Option<u64>,
400 /// Restrict evidence to this exact configured or effective model.
401 #[arg(long)]
402 pub model: Option<String>,
403 /// Restrict evidence to one closed provider operation.
404 #[arg(long, value_enum)]
405 pub operation: Option<CacheOperation>,
406 /// Restrict evidence to one logical/provider attempt ordinal.
407 #[arg(long)]
408 pub attempt: Option<u64>,
409 /// Exclude exact comparisons without a proven captured response-chain edge.
410 #[arg(long)]
411 pub require_exact_chain: bool,
412 /// Geometry grouping dimensions, as a comma-separated list.
413 #[arg(
414 long,
415 value_enum,
416 value_delimiter = ',',
417 default_value = "model,backend,controls"
418 )]
419 pub group_by: Vec<CacheGroup>,
420 /// Select summary, attribution, continuity, geometry, or gap evidence.
421 #[arg(long, value_enum, default_value_t)]
422 pub view: CacheView,
423 /// Existing Tau state root; inspection never creates it.
424 #[arg(long, default_value_os_t = tau_session_inspect::default_state_dir())]
425 pub state_dir: PathBuf,
426 /// Inclusive compressed byte limit per capture.
427 #[arg(long, default_value_t = 16 * 1024 * 1024)]
428 pub max_compressed_bytes: u64,
429 /// Inclusive decompressed byte limit per capture.
430 #[arg(long, default_value_t = 64 * 1024 * 1024)]
431 pub max_decompressed_bytes: u64,
432 /// Inclusive cumulative decompressed capture bytes.
433 #[arg(long, default_value_t = 1024 * 1024 * 1024)]
434 pub max_total_bytes: u64,
435 /// Capture parser and report working-memory budget in bytes.
436 #[arg(long, default_value_t = 512 * 1024 * 1024)]
437 pub max_memory_bytes: u64,
438 /// Replace one disposable owner-private index for later comparisons.
439 #[arg(long, value_name = "PATH")]
440 pub index: Option<PathBuf>,
441}
442
443/// Closed cache operation accepted by offline selection.
444#[derive(Clone, Copy, ValueEnum)]
445pub enum CacheOperation {
446 /// Ordinary provider inference.
447 Inference,
448 /// Provider-backed standalone compaction.
449 StandaloneCompaction,
450 /// Provider cache refresh or prewarm work.
451 CacheRefresh,
452}
453
454/// Closed empirical geometry grouping dimension.
455#[derive(Clone, Copy, ValueEnum)]
456pub enum CacheGroup {
457 /// Effective provider model.
458 Model,
459 /// Backend adapter and transport.
460 Backend,
461 /// Reasoning, tool, tier, and cache controls.
462 Controls,
463}
464
465/// Parses an inclusive lower RFC3339 bound, rounding toward later observations.
466pub(crate) fn parse_cache_since(value: &str) -> Result<u64, String> {
467 let nanos = parse_cache_rfc3339_nanos(value)?;
468 u64::try_from((nanos + 999) / 1_000)
469 .map_err(|_| "timestamp exceeds the supported Unix-microsecond range".to_owned())
470}
471
472/// Parses an inclusive upper RFC3339 bound, rounding toward earlier
473/// observations.
474pub(crate) fn parse_cache_until(value: &str) -> Result<u64, String> {
475 let nanos = parse_cache_rfc3339_nanos(value)?;
476 u64::try_from(nanos / 1_000)
477 .map_err(|_| "timestamp exceeds the supported Unix-microsecond range".to_owned())
478}
479
480/// Parses one nonnegative absolute RFC3339 timestamp at nanosecond precision.
481fn parse_cache_rfc3339_nanos(value: &str) -> Result<i128, String> {
482 use time::format_description::well_known::Rfc3339;
483
484 let timestamp = time::OffsetDateTime::parse(value, &Rfc3339)
485 .map_err(|error| format!("must be an absolute RFC3339 timestamp: {error}"))?;
486 let nanos = timestamp.unix_timestamp_nanos();
487 if nanos < 0 {
488 return Err("timestamp must not precede the Unix epoch".to_owned());
489 }
490 Ok(nanos)
491}
492
493/// Offline cache evidence projection.
494#[derive(Clone, Copy, Debug, Default, ValueEnum)]
495pub enum CacheView {
496 /// Canonical accounting and overall capture coverage.
497 #[default]
498 Summary,
499 /// Provider-reported per-item attribution evidence.
500 Attribution,
501 /// Attempt, dispatch, anchor, connection, and repair facts.
502 Continuity,
503 /// Empirical reported-token distributions by scalar regime.
504 Geometry,
505 /// Encountered evidence gaps only.
506 Gaps,
507}
508
509/// Available first-delivery content-free cache report encodings.
510#[derive(Clone, Copy, Default, ValueEnum)]
511pub enum CacheFormat {
512 /// Counts and explicit evidence gaps.
513 #[default]
514 Summary,
515 /// Canonical per-response facts, recorded costs, and coverage.
516 Jsonl,
517}
518
519/// Offline cache inspection rooted at one durable agent.
520#[derive(Args, Clone)]
521pub struct AgentCacheArgs {
522 /// Existing durable agent identity.
523 pub agent_id: tau_proto::AgentId,
524 /// Include authenticated creator descendants recursively.
525 #[arg(long)]
526 pub include_descendants: bool,
527 /// Shared read-only report controls.
528 #[command(flatten)]
529 pub options: CacheArgs,
530}
531
532/// Offline cache inspection of durable session membership.
533#[derive(Args, Clone)]
534pub struct SessionCacheArgs {
535 /// Existing durable session identity.
536 pub session_id: tau_proto::SessionId,
537 /// Shared read-only report controls.
538 #[command(flatten)]
539 pub options: CacheArgs,
540}
541
542/// Options for `tau agent unload`.
543#[derive(Args, Clone)]
544pub struct AgentUnloadArgs {
545 /// Running session to mutate.
546 pub session_id: SessionId,
547 /// Saved agent to unload.
548 pub agent_id: tau_proto::AgentId,
549}
550
551/// Options for `tau agent trace`.
552#[derive(Args, Clone)]
553pub struct AgentTraceArgs {
554 /// Durable agent journal to export.
555 pub agent_id: tau_proto::AgentId,
556
557 /// Recursively include agents created by the requested workflow.
558 #[arg(long)]
559 pub include_descendants: bool,
560
561 /// Machine-readable export format.
562 #[arg(long, value_enum, default_value_t)]
563 pub format: AgentTraceFormat,
564
565 /// Compact semantic text and tool-output detail.
566 #[arg(long, value_enum, default_value_t)]
567 pub mode: AgentTraceMode,
568
569 /// Durable agent journal root.
570 #[arg(long, default_value_os_t = default_agents_dir())]
571 pub agents_dir: PathBuf,
572}
573
574/// Machine-readable agent trace export format.
575#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
576pub enum AgentTraceFormat {
577 /// Complete canonical Tau JSON Lines.
578 TauJsonl,
579 /// Lossy OTLP/OpenInference JSON visualization adapter.
580 OtlpJson,
581 /// Compact assistant, message, reasoning, and tool-call timeline as JSON
582 /// Lines.
583 AgentToolsJsonl,
584 /// Compact assistant, message, reasoning, and tool-call timeline as TOON.
585 #[default]
586 AgentToolsToon,
587 /// Content-free provider, tool, wait, outer-turn, and compaction accounting
588 /// as JSON Lines.
589 AgentPerformanceJsonl,
590}
591
592/// Content detail for compact semantic trace formats.
593#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
594pub enum AgentTraceMode {
595 /// Report complete metrics and at most 4 KiB of each text/output item.
596 #[default]
597 Lite,
598 /// Report complete metrics and complete semantic text/normalized output.
599 Full,
600}
601
602/// Filters for `tau agent list`.
603#[derive(Args, Clone)]
604pub struct AgentListArgs {
605 /// Running session to query.
606 pub session_id: SessionId,
607
608 /// Include suspended live agents.
609 #[arg(long)]
610 pub include_suspended: bool,
611
612 /// Include current unavailable agents and rows with missing, invalid, or
613 /// unreadable creation facts.
614 #[arg(long)]
615 pub include_unavailable: bool,
616
617 /// Include previously loaded and now-unloaded agents.
618 #[arg(long)]
619 pub include_unloaded: bool,
620
621 /// Include every supported agent category.
622 #[arg(long)]
623 pub all: bool,
624}
625
626#[derive(Subcommand)]
627pub enum DevCommand {
628 /// Send one line to a running session.
629 Send {
630 /// Running session identifier.
631 session_id: SessionId,
632
633 /// Line to submit. Commands are interpreted like the TUI.
634 #[arg(required = true, trailing_var_arg = true)]
635 line: Vec<String>,
636 },
637
638 /// Dump the initial provider prompt built from local config.
639 DumpInitialPrompt {
640 /// Output path.
641 #[arg(long, default_value = "tmp/initial_prompt.txt")]
642 out: PathBuf,
643
644 /// Synthetic first user message.
645 #[arg(long, default_value = "hello")]
646 message: String,
647 },
648
649 /// Print the effective provider-visible prompt context.
650 ///
651 /// Configures ordinary extensions, initializes one fresh ephemeral agent,
652 /// and waits boundedly for its context without calling a provider.
653 /// Extensions retain ordinary persistent state access and side effects.
654 /// Omitting `--role` uses the configured startup role.
655 PrintPrompt {
656 /// Include harness-injected AGENTS.md context.
657 #[arg(long = "enable-agents-md", default_value_t = true, action = clap::ArgAction::Set)]
658 enable_agents_md: bool,
659 },
660
661 /// Print only the rendered system prompt for a role.
662 ///
663 /// Uses a stable fake agent id as the explicit `agent_id` input for custom
664 /// templates; built-in templates intentionally omit agent identity.
665 PrintSystemPrompt,
666
667 /// Print the effective tool definitions.
668 ///
669 /// Uses the same fresh ephemeral-agent lifecycle and effective model/tool
670 /// snapshot as `print-prompt`, without calling a provider. Extensions
671 /// retain ordinary persistent state access and side effects. Omitting
672 /// `--role` uses the configured startup role.
673 PrintTools,
674
675 /// Print the effective skills available to a role.
676 ///
677 /// Uses the same fresh ephemeral-agent lifecycle and effective context
678 /// snapshot as `print-prompt`, without calling a provider. Extensions
679 /// retain ordinary persistent state access and side effects. Omitting
680 /// `--role` uses the configured startup role.
681 PrintSkills {
682 /// Output encoding for the effective skill list.
683 #[arg(long, value_enum, default_value_t)]
684 format: SkillOutputFormat,
685 },
686
687 /// Preview config-derived declarations from explicitly opted-in extensions.
688 ///
689 /// Does not start a harness, read extension state or credentials, or
690 /// discover agent context. Unsupported or incomplete inventories exit
691 /// unsuccessfully.
692 PreviewDeclarations,
693
694 /// Inspect or clear reports recorded by the standard papercut reporter.
695 Papercut {
696 /// Papercut operation to run.
697 #[command(subcommand)]
698 command: PapercutCommand,
699 },
700
701 /// Manage a manual Tau end-to-end session in a private tmux server.
702 Tmux {
703 /// Tmux helper action to run.
704 #[command(subcommand)]
705 command: DevTmuxCommand,
706 },
707}
708
709/// Encodings supported by `tau dev print-skills`.
710#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
711pub enum SkillOutputFormat {
712 /// Human-readable Markdown document.
713 #[default]
714 Markdown,
715 /// Machine-readable JSON array.
716 Json,
717}
718
719/// Commands that inspect or clear the standard papercut reporter's records.
720#[derive(Subcommand)]
721pub enum PapercutCommand {
722 /// List recorded papercut reports.
723 List {
724 /// Render the reports as copyable Markdown.
725 #[arg(long)]
726 markdown: bool,
727
728 /// Tau state directory containing the standard reporter's records.
729 #[arg(long, default_value_os_t = default_state_dir())]
730 state_dir: PathBuf,
731 },
732
733 /// Archive every active papercut report at this command's serialized clear
734 /// boundary and print the preserved file's path.
735 Clear {
736 /// Tau state directory containing the standard reporter's records.
737 #[arg(long, default_value_os_t = default_state_dir())]
738 state_dir: PathBuf,
739 },
740}
741
742/// Hidden tmux helper subcommands for manual Tau end-to-end sessions.
743#[derive(Subcommand)]
744pub enum DevTmuxCommand {
745 /// Start Tau in an isolated scratch environment inside tmux.
746 Start(DevTmuxStartArgs),
747
748 /// Capture the current tmux pane contents.
749 Capture(DevTmuxTargetArgs),
750
751 /// Send text to the tmux pane, followed by Enter by default.
752 Send(DevTmuxSendArgs),
753
754 /// Stop the private tmux server.
755 Stop(DevTmuxStopArgs),
756}
757
758/// Shared tmux target arguments used by the manual E2E helper.
759#[derive(Args)]
760pub struct DevTmuxCommonArgs {
761 /// Scratch root containing the tmux socket and isolated Tau environment.
762 /// When omitted, `start` generates a fresh temporary root; target commands
763 /// use the historical static fallback root.
764 #[arg(long = "scratch-root", visible_alias = "root")]
765 pub scratch_root: Option<PathBuf>,
766
767 /// Private tmux session name.
768 #[arg(long, default_value = "tau-e2e")]
769 pub session: String,
770}
771
772/// Arguments for starting a new isolated Tau tmux session.
773#[derive(Args)]
774pub struct DevTmuxStartArgs {
775 /// Shared tmux socket/session selection.
776 #[command(flatten)]
777 pub common: DevTmuxCommonArgs,
778
779 /// Tau binary to run inside tmux.
780 #[arg(long)]
781 pub tau_bin: Option<PathBuf>,
782
783 /// Working directory for Tau and core-shell.
784 #[arg(long)]
785 pub workdir: Option<PathBuf>,
786
787 /// Initial tmux pane width.
788 #[arg(long, default_value_t = 120)]
789 pub width: u16,
790
791 /// Initial tmux pane height.
792 #[arg(long, default_value_t = 40)]
793 pub height: u16,
794}
795
796/// Arguments that identify an existing Tau tmux session.
797#[derive(Args)]
798pub struct DevTmuxTargetArgs {
799 /// Shared tmux socket/session selection.
800 #[command(flatten)]
801 pub common: DevTmuxCommonArgs,
802}
803
804/// Arguments for sending literal input to an existing Tau tmux session.
805#[derive(Args)]
806pub struct DevTmuxSendArgs {
807 /// Existing tmux session to receive input.
808 #[command(flatten)]
809 pub target: DevTmuxTargetArgs,
810
811 /// Do not send Enter after the text.
812 #[arg(long)]
813 pub no_enter: bool,
814
815 /// Text to send literally to the Tau prompt.
816 #[arg(required = true, trailing_var_arg = true)]
817 pub text: Vec<String>,
818}
819
820/// Arguments for stopping an existing Tau tmux session.
821#[derive(Args)]
822pub struct DevTmuxStopArgs {
823 /// Existing tmux session to stop.
824 #[command(flatten)]
825 pub target: DevTmuxTargetArgs,
826
827 /// Remove the scratch root after stopping tmux.
828 #[arg(long)]
829 pub remove_scratch: bool,
830}