use std::net::SocketAddr;
use clap::{Parser, Subcommand};
mod actions;
pub(crate) use actions::*;
pub(crate) use crate::cli_manager::ManagerAction;
#[cfg(test)]
pub(crate) const DEFAULT_URL: &str = trusty_mpm::core::DEFAULT_DAEMON_URL;
pub(crate) const DEFAULT_ADDR: &str = trusty_mpm::core::DEFAULT_DAEMON_ADDR;
#[derive(Debug, Parser)]
#[command(
name = "trusty-mpm",
version,
about = "trusty-mpm — unified binary",
// #6666: `--help` is where an operator looks after bare `tm` did something
// they did not expect, so the outside-a-work-tree behaviour is stated here.
long_about = "trusty-mpm — unified binary\n\n\
Bare `tm` outside a git work tree lists managed sessions and offers a new \
untracked session (`tm sessions start`) or a managed one (`tm sessions new`).",
subcommand_required = false,
arg_required_else_help = false,
infer_subcommands = true
)]
pub(crate) struct Cli {
#[arg(long, env = "TRUSTY_MPM_URL", global = true)]
pub(crate) url: Option<String>,
#[arg(long, visible_alias = "user", global = true, value_parser = non_blank_login)]
pub(crate) account: Option<String>,
#[command(subcommand)]
pub(crate) command: Option<Command>,
}
fn non_blank_login(value: &str) -> Result<String, String> {
if value.trim().is_empty() {
return Err("needs a gh login — e.g. `--user bob-duetto`.".to_string());
}
Ok(value.to_string())
}
#[derive(Debug, Subcommand)]
pub(crate) enum Command {
Status,
Start,
Serve {
#[arg(long)]
stdio: bool,
},
Stop,
Restart,
Project {
#[command(subcommand)]
action: ProjectAction,
},
Sessions {
#[command(subcommand)]
action: SessionAction,
},
#[command(name = "session", hide = true)]
Session {
#[command(subcommand)]
action: SessionAction,
},
#[command(name = "internal-spawn-disclaimed", hide = true)]
InternalSpawnDisclaimed {
#[arg(long = "launch-spec", value_name = "PATH")]
launch_spec: Option<std::path::PathBuf>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
argv: Vec<String>,
},
Projects {
#[command(subcommand)]
action: ProjectsAction,
},
Manager {
#[command(subcommand)]
action: ManagerAction,
},
Events,
Doctor {
#[command(flatten)]
flags: DoctorFlags,
},
Validate {
#[arg(long)]
path: Option<std::path::PathBuf>,
#[arg(long)]
repair: bool,
},
Hooks {
#[command(subcommand)]
action: HooksAction,
},
Agent {
#[command(subcommand)]
action: AgentAction,
},
Generate {
#[command(subcommand)]
action: GenerateAction,
},
Health,
Tui {
#[arg(long, env = "TRUSTY_MPM_URL")]
url: Option<String>,
#[arg(long, default_value_t = 1000)]
interval_ms: u64,
#[arg(long)]
single_pane: bool,
},
Gui,
Telegram {
#[command(subcommand)]
cmd: TelegramCmd,
},
Slack {
#[command(subcommand)]
cmd: SlackCmd,
},
Install {
#[arg(long)]
force: bool,
#[arg(long, num_args = 0.., value_delimiter = ',')]
reset_agents: Option<Vec<String>>,
#[arg(long, requires = "reset_agents")]
reset_agents_workspaces: bool,
#[arg(long)]
reconcile_skills: bool,
},
Reinstall(ReinstallArgs),
Hook {
#[arg(long)]
pm_guard: bool,
#[arg(long)]
divert_check: bool,
#[arg(long)]
prompt_feedback: bool,
},
PromptFeedback(crate::commands::prompt_feedback_cli::PromptFeedbackArgs),
Divert {
#[command(subcommand)]
action: DivertAction,
},
Memory {
#[command(subcommand)]
action: MemoryAction,
},
Compress {
#[arg(long)]
tool: String,
},
Wait(WaitArgs),
Daemon {
#[arg(long, env = "TRUSTY_MPM_ADDR", default_value = DEFAULT_ADDR)]
addr: SocketAddr,
#[arg(long, env = "TRUSTY_MPM_TAILSCALE")]
tailscale: bool,
#[arg(long)]
mcp: bool,
#[arg(long)]
force: bool,
},
Supervisor {
#[arg(long)]
interval: Option<u64>,
#[arg(long)]
auto_resume: bool,
#[arg(long)]
no_classify: bool,
},
Launch {
dir: Option<String>,
#[arg(long)]
style: Option<String>,
#[arg(long)]
worktree: bool,
},
Connect {
dir: Option<String>,
},
Attach {
target: String,
#[arg(long)]
json: bool,
#[arg(long)]
single_pane: bool,
},
Optimizer {
#[command(subcommand)]
action: OptimizerAction,
},
Overseer {
#[command(subcommand)]
action: OverseerAction,
},
#[command(
visible_alias = "sm",
visible_alias = "session-manager",
alias = "coord"
)]
Coordinator {
message: Option<String>,
#[command(subcommand)]
action: Option<CoordinatorAction>,
},
Services {
#[command(subcommand)]
action: ServicesAction,
},
Repair {
#[command(subcommand)]
action: RepairAction,
},
Auth {
#[command(subcommand)]
action: AuthAction,
},
Catalog {
#[command(subcommand)]
action: CatalogAction,
},
Ticket {
issue: String,
#[arg(default_value = "gh", value_enum)]
system: crate::commands::ticket::system::TicketSystemKind,
#[arg(long = "note", short = 'm')]
notes: Vec<String>,
#[arg(long, default_value = "claude-code", value_enum)]
runtime: trusty_mpm::runtime::RuntimeKind,
},
Issue {
#[command(subcommand)]
cmd: IssueCmd,
#[arg(long, default_value = "gh", value_enum, global = true)]
system: crate::commands::ticket::system::TicketSystemKind,
},
Pr {
#[command(subcommand)]
cmd: PrCmd,
},
Watch {
#[command(subcommand)]
cmd: WatchCmd,
},
Register {
url: String,
alias: Option<String>,
#[arg(long)]
force: bool,
#[arg(long)]
root: Option<String>,
},
#[command(name = "ls")]
Ls {
#[arg(value_name = "SORT|FILTER", num_args = 0..)]
terms: Vec<String>,
#[arg(long, short = 'p')]
projects: bool,
#[arg(long)]
json: bool,
#[arg(long)]
source_id: Option<String>,
#[arg(long, conflicts_with = "source_id")]
current: bool,
#[arg(long)]
all: bool,
#[arg(long, short = 'a')]
attached: bool,
#[arg(long)]
no_prune: bool,
#[arg(long)]
plain: bool,
#[arg(long)]
root: Option<String>,
},
#[command(name = "f")]
F(FindArgs),
Load {
alias: String,
#[arg(long)]
root: Option<String>,
},
Run {
target: String,
#[arg(long)]
task: Option<String>,
#[arg(long)]
root: Option<String>,
},
Path {
alias: String,
#[arg(long)]
root: Option<String>,
},
ShellInit {
#[arg(value_enum)]
shell: ShellArg,
},
Login {
#[arg(long)]
root: Option<String>,
},
#[command(name = "rm")]
Rm {
alias: String,
#[arg(long)]
root: Option<String>,
},
Update {
alias: Option<String>,
#[arg(long)]
root: Option<String>,
},
#[command(name = "sessctl")]
Sessctl {
#[command(subcommand)]
action: SessctlAction,
},
Meta {
#[command(subcommand)]
action: MetaAction,
},
Statusline,
CommitTrailers(CommitTrailersArgs),
Banner {
#[arg(long)]
reconnecting: bool,
},
Mcp {
#[command(subcommand)]
cmd: McpCmd,
},
Config(trusty_common::inference::config::ConfigCommand),
#[command(external_subcommand)]
External(Vec<String>),
}
#[derive(clap::Args, Debug, Clone, PartialEq, Eq)]
#[command(group(
clap::ArgGroup::new("repair")
.args(["fix", "fix_skills"])
.multiple(true)
.required(false)
))]
#[command(group(
clap::ArgGroup::new("writes")
.args(["fix", "fix_skills", "fix_agents", "quarantine_mcp"])
.multiple(true)
.required(false)
))]
pub struct DoctorFlags {
#[arg(long, hide = true)]
pub prune_stale_skills: bool,
#[arg(long)]
pub fix_skills: bool,
#[arg(long)]
pub fix_agents: bool,
#[arg(long)]
pub fix: bool,
#[arg(long, requires = "writes")]
pub yes: bool,
#[arg(long, value_name = "PATH")]
pub quarantine_mcp: Option<std::path::PathBuf>,
#[arg(long, requires = "repair")]
pub include_frozen: bool,
}
#[derive(Debug, Clone, clap::Args)]
pub(crate) struct CommitTrailersArgs {
#[arg(long, value_name = "PATH")]
pub(crate) message_file: Option<std::path::PathBuf>,
#[arg(long, value_name = "SOURCE")]
pub(crate) commit_source: Option<String>,
}
#[derive(Debug, Clone, clap::Args)]
pub(crate) struct ReinstallArgs {
#[arg(long)]
pub(crate) force: bool,
#[arg(long)]
pub(crate) binary: bool,
#[arg(long, requires = "binary")]
pub(crate) yes: bool,
}
#[derive(Debug, Clone, clap::Args)]
pub(crate) struct FindArgs {
#[arg(value_name = "PATTERN", num_args = 0..)]
pub(crate) pattern: Vec<String>,
#[arg(long)]
pub(crate) json: bool,
#[arg(long)]
pub(crate) source_id: Option<String>,
#[arg(long, conflicts_with = "source_id")]
pub(crate) current: bool,
#[arg(long)]
pub(crate) all: bool,
}