use clap::{CommandFactory, Parser, Subcommand, ValueEnum};
use crate::parse_temperature;
use vw_agent::channels::ChannelCommands;
use vw_agent::cron::CronCommands;
use vw_agent::integrations::IntegrationCommands;
use vw_agent::memory::cli::MemoryCommands;
use vw_agent::security;
use vw_agent::service::ServiceCommands;
use vw_agent::skill::SkillCommands;
#[path = "cli/mod.rs"]
mod runtime;
pub(crate) use runtime::run;
pub(crate) use runtime::{processor, session, setup, tui_v2};
pub(crate) mod legacy_runtime {
pub(crate) use super::runtime::{logo_text_lines, render_execution_indicator};
pub(crate) mod theme {
pub(crate) use super::super::runtime::theme::*;
}
pub(crate) mod transcript {
pub(crate) use super::super::runtime::transcript::*;
}
pub(crate) mod tui_utils {
pub(crate) use super::super::runtime::tui_utils::*;
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum CompletionShell {
#[value(name = "bash")]
Bash,
#[value(name = "fish")]
Fish,
#[value(name = "zsh")]
Zsh,
#[value(name = "powershell")]
PowerShell,
#[value(name = "elvish")]
Elvish,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum EstopLevelArg {
#[value(name = "kill-all")]
KillAll,
#[value(name = "network-kill")]
NetworkKill,
#[value(name = "domain-block")]
DomainBlock,
#[value(name = "tool-freeze")]
ToolFreeze,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub(crate) enum AgentTuiMode {
#[value(name = "legacy")]
Legacy,
#[value(name = "v2")]
V2,
#[value(name = "v2-shadow")]
V2Shadow,
}
#[derive(Parser, Debug)]
#[command(name = "vibewindow")]
#[command(author = "theonlyhennygod")]
#[command(version)]
#[command(about = "The fastest, smallest AI assistant.", long_about = None)]
pub(crate) struct Cli {
#[arg(long, global = true)]
pub(crate) config_dir: Option<String>,
#[command(subcommand)]
pub(crate) command: Commands,
}
#[derive(Subcommand, Debug)]
pub(crate) enum Commands {
#[command(long_about = "\
Start the AI agent loop.
Launches an interactive chat session with the configured AI provider. \
Use --message for single-shot queries without entering interactive mode.
Examples:
vibewindow agent # interactive session
vibewindow agent -m \"Summarize today's logs\" # single message
vibewindow agent -p anthropic --model claude-sonnet-4-20250514
vibewindow agent --peripheral nucleo-f401re:/dev/ttyACM0
vibewindow agent --autonomy-level full --max-actions-per-hour 100
vibewindow agent -m \"quick task\" --memory-backend none --compact-context")]
Agent {
#[arg(short, long)]
message: Option<String>,
#[arg(long, value_enum, default_value_t = AgentTuiMode::Legacy)]
tui_mode: AgentTuiMode,
#[arg(short, long)]
provider: Option<String>,
#[arg(long)]
model: Option<String>,
#[arg(short, long, default_value = "0.7", value_parser = parse_temperature)]
temperature: f64,
#[arg(long)]
peripheral: Vec<String>,
#[arg(long, value_parser = clap::value_parser!(security::AutonomyLevel))]
autonomy_level: Option<security::AutonomyLevel>,
#[arg(long)]
max_actions_per_hour: Option<u32>,
#[arg(long)]
max_tool_iterations: Option<usize>,
#[arg(long)]
max_history_messages: Option<usize>,
#[arg(long)]
compact_context: bool,
#[arg(long)]
memory_backend: Option<String>,
},
#[command(long_about = "\
Start the gateway server (webhooks, websockets).
Runs the HTTP/WebSocket gateway that accepts incoming webhook events \
and WebSocket connections. Bind address defaults to the values in \
your config file (gateway.host / gateway.port).
Examples:
vibewindow gateway # use config defaults
vibewindow gateway -p 8080 # listen on port 8080
vibewindow gateway --host 0.0.0.0 # bind to all interfaces
vibewindow gateway -p 0 # random available port
vibewindow gateway --new-pairing # clear tokens and generate fresh pairing code")]
Gateway {
#[arg(short, long)]
port: Option<u16>,
#[arg(long)]
host: Option<String>,
#[arg(long)]
new_pairing: bool,
},
#[command(long_about = "\
Start the long-running autonomous daemon.
Launches the full VibeWindow runtime: gateway server, all configured \
channels (Telegram, Discord, Slack, etc.), heartbeat monitor, and \
the cron scheduler. This is the recommended way to run VibeWindow in \
production or as an always-on assistant.
Use 'vibewindow service install' to register the daemon as an OS \
service (systemd/launchd) for auto-start on boot.
Examples:
vibewindow daemon # use config defaults
vibewindow daemon -p 9090 # gateway on port 9090
vibewindow daemon --host 127.0.0.1 # localhost only")]
Daemon {
#[arg(short, long)]
port: Option<u16>,
#[arg(long)]
host: Option<String>,
},
Service {
#[arg(long, default_value = "auto", value_parser = ["auto", "systemd", "openrc"])]
service_init: String,
#[command(subcommand)]
service_command: ServiceCommands,
},
Doctor {
#[command(subcommand)]
doctor_command: Option<DoctorCommands>,
},
Status,
Estop {
#[command(subcommand)]
estop_command: Option<EstopSubcommands>,
#[arg(long, value_enum)]
level: Option<EstopLevelArg>,
#[arg(long = "domain")]
domains: Vec<String>,
#[arg(long = "tool")]
tools: Vec<String>,
},
#[command(long_about = "\
Manage security maintenance tasks.
Commands in this group maintain security-related data stores used at runtime.
Examples:
vibewindow security update-guard-corpus
vibewindow security update-guard-corpus --source builtin
vibewindow security update-guard-corpus --source ./assets/security/attack-corpus-v1.jsonl
vibewindow security update-guard-corpus --source https://example.com/guard-corpus.jsonl --checksum <sha256>")]
Security {
#[command(subcommand)]
security_command: SecurityCommands,
},
#[command(long_about = "\
Configure and manage scheduled tasks.
Schedule recurring, one-shot, or interval-based tasks using cron \
expressions, RFC 3339 timestamps, durations, or fixed intervals.
Cron expressions use the standard 5-field format: \
'min hour day month weekday'. Timezones default to UTC; \
override with --tz and an IANA timezone name.
Examples:
vibewindow cron list
vibewindow cron add '0 9 * * 1-5' 'Good morning' --tz America/New_York
vibewindow cron add '*/30 * * * *' 'Check system health'
vibewindow cron add-at 2025-01-15T14:00:00Z 'Send reminder'
vibewindow cron add-every 60000 'Ping heartbeat'
vibewindow cron once 30m 'Run backup in 30 minutes'
vibewindow cron pause <task-id>
vibewindow cron update <task-id> --expression '0 8 * * *' --tz Europe/London")]
Cron {
#[command(subcommand)]
cron_command: CronCommands,
},
Providers,
#[command(long_about = "\
Manage communication channels.
Add, remove, list, and health-check channels that connect VibeWindow \
to messaging platforms. Supported channel types: telegram, discord, \
slack, whatsapp, matrix, imessage, email.
Examples:
vibewindow channel list
vibewindow channel doctor
vibewindow channel add telegram '{\"bot_token\":\"...\",\"name\":\"my-bot\"}'
vibewindow channel remove my-bot
vibewindow channel bind-telegram vibewindow_user")]
Channel {
#[command(subcommand)]
channel_command: ChannelCommands,
},
Integrations {
#[command(subcommand)]
integration_command: IntegrationCommands,
},
Skills {
#[command(subcommand)]
skill_command: SkillCommands,
},
#[command(long_about = "\
Create and read project tasks stored in .vibewindow/tasks.
Use --project-dir to target a specific project path. Defaults to the \
current working directory.
Examples:
vibewindow task create --prompt \"检查 OAuth 回调并修复\"
vibewindow task create --project-dir /path/to/repo --description \"补充测试\" --subtask \"单元测试\" --subtask \"集成测试\"
vibewindow task read
vibewindow task read --id T20260317.0001
vibewindow task read --project-dir /path/to/repo --status pending --limit 20")]
Task {
#[arg(long, default_value = ".")]
project_dir: String,
#[command(subcommand)]
task_command: TaskCommands,
},
#[command(long_about = "\
Manage agent memory entries.
List, inspect, and clear memory entries stored by the agent. \
Supports filtering by category and session, pagination, and \
batch clearing with confirmation.
Examples:
vibewindow memory stats
vibewindow memory list
vibewindow memory list --category core --limit 10
vibewindow memory get <key>
vibewindow memory clear --category conversation --yes")]
Memory {
#[command(subcommand)]
memory_command: MemoryCommands,
},
#[command(long_about = "\
Manage VibeWindow configuration.
Inspect and export configuration settings. Use 'schema' to dump \
the full JSON Schema for the config file, which documents every \
available key, type, and default value.
Examples:
vibewindow config schema # print JSON Schema to stdout
vibewindow config schema > schema.json")]
Config {
#[command(subcommand)]
config_command: ConfigCommands,
},
#[command(long_about = "\
Generate shell completion scripts for `vibewindow`.
The script is printed to stdout so it can be sourced directly:
Examples:
source <(vibewindow completions bash)
vibewindow completions zsh > ~/.zfunc/_vibewindow
vibewindow completions fish > ~/.config/fish/completions/vibewindow.fish")]
Completions {
#[arg(value_enum)]
shell: CompletionShell,
},
}
#[derive(Subcommand, Debug)]
pub(crate) enum ConfigCommands {
Schema,
}
#[derive(Subcommand, Debug)]
pub(crate) enum TaskCommands {
Create {
#[arg(long, default_value = "999")]
priority: u32,
#[arg(long)]
prompt: Option<String>,
#[arg(long)]
description: Option<String>,
#[arg(long)]
assignee: Option<String>,
#[arg(long)]
model: Option<String>,
#[arg(long)]
executor: Option<String>,
#[arg(long = "subtask")]
subtasks: Vec<String>,
},
Read {
#[arg(long)]
id: Option<String>,
#[arg(long)]
status: Option<String>,
#[arg(long)]
include_archived: bool,
#[arg(long)]
include_deleted: bool,
#[arg(long, default_value = "50")]
limit: usize,
},
}
#[derive(Subcommand, Debug)]
pub(crate) enum EstopSubcommands {
Status,
Resume {
#[arg(long)]
network: bool,
#[arg(long = "domain")]
domains: Vec<String>,
#[arg(long = "tool")]
tools: Vec<String>,
#[arg(long)]
otp: Option<String>,
},
}
#[derive(Subcommand, Debug)]
pub(crate) enum SecurityCommands {
UpdateGuardCorpus {
#[arg(long)]
source: Option<String>,
#[arg(long)]
checksum: Option<String>,
},
}
#[derive(Subcommand, Debug)]
pub(crate) enum DoctorCommands {
Models {
#[arg(long)]
provider: Option<String>,
#[arg(long)]
use_cache: bool,
},
Traces {
#[arg(long)]
id: Option<String>,
#[arg(long)]
event: Option<String>,
#[arg(long)]
contains: Option<String>,
#[arg(long, default_value = "20")]
limit: usize,
},
}
pub(crate) fn write_shell_completion<W: std::io::Write>(
shell: CompletionShell,
writer: &mut W,
) -> anyhow::Result<()> {
use clap_complete::generate;
use clap_complete::shells;
let mut cmd = Cli::command();
let bin_name = cmd.get_name().to_string();
match shell {
CompletionShell::Bash => generate(shells::Bash, &mut cmd, bin_name.clone(), writer),
CompletionShell::Fish => generate(shells::Fish, &mut cmd, bin_name.clone(), writer),
CompletionShell::Zsh => generate(shells::Zsh, &mut cmd, bin_name.clone(), writer),
CompletionShell::PowerShell => {
generate(shells::PowerShell, &mut cmd, bin_name.clone(), writer);
}
CompletionShell::Elvish => generate(shells::Elvish, &mut cmd, bin_name, writer),
}
writer.flush()?;
Ok(())
}