use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
use crate::core::{
contracts::scalar::LocalId,
domain::Conversation,
input::{DecimalId, Handle, Limit, NonEmpty, Rfc3339, Seconds, Text},
};
use serde::Serialize;
#[derive(Debug, Parser, Serialize)]
#[command(
name = "qq-cli",
version,
about = "QQ task interface for coding agents",
disable_help_subcommand = true
)]
pub struct Cli {
#[arg(long, global = true, value_name = "FILE")]
pub config: Option<PathBuf>,
#[arg(long, global = true, value_name = "ID")]
pub instance: Option<LocalId>,
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Command {
Status,
Doctor {
#[arg(long, value_name = "VERSION")]
version: Option<String>,
},
Schema {
#[command(subcommand)]
command: SchemaCommand,
},
Skill {
#[command(subcommand)]
command: SkillCommand,
},
Napcat {
#[command(subcommand)]
command: NapcatCommand,
},
Contact {
#[command(subcommand)]
command: ContactCommand,
},
Group {
#[command(subcommand)]
command: GroupCommand,
},
Conversation {
#[command(subcommand)]
command: ConversationCommand,
},
Message {
#[command(subcommand)]
command: MessageCommand,
},
Media {
#[command(subcommand)]
command: MediaCommand,
},
Event {
#[command(subcommand)]
command: EventCommand,
},
Request {
#[command(subcommand)]
command: RequestCommand,
},
}
impl Command {
pub fn timeout_seconds(&self) -> u16 {
match self {
Self::Contact {
command: ContactCommand::List(args),
}
| Self::Group {
command: GroupCommand::List(args),
}
| Self::Conversation {
command: ConversationCommand::List(args),
} => args.page.deadline.timeout.get(),
Self::Group {
command: GroupCommand::Members { page, .. },
} => page.deadline.timeout.get(),
Self::Message {
command: MessageCommand::History(args),
} => args.page.deadline.timeout.get(),
Self::Message {
command: MessageCommand::Search(args),
} => args.history.page.deadline.timeout.get(),
Self::Message {
command: MessageCommand::Send(args),
} => args.content.deadline.timeout.get(),
Self::Message {
command: MessageCommand::Reply(args),
} => args.content.deadline.timeout.get(),
Self::Message {
command: MessageCommand::Export(args),
} => args.deadline.timeout.get(),
Self::Media {
command: MediaCommand::Download { deadline, .. },
}
| Self::Event {
command: EventCommand::Watch { deadline, .. },
} => deadline.timeout.get(),
Self::Napcat {
command: NapcatCommand::Start(args),
} => args.deadline.timeout.get(),
Self::Napcat {
command: NapcatCommand::Update { deadline, .. },
}
| Self::Napcat {
command: NapcatCommand::Rollback { deadline },
} => deadline.timeout.get(),
_ => 30,
}
}
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SchemaCommand {
List,
Get { command: String },
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SkillCommand {
List,
Get { task: String },
Export {
#[arg(long, value_name = "DIR")]
output: PathBuf,
},
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum NapcatCommand {
Install {
#[arg(long, value_name = "VERSION")]
version: String,
#[arg(long, value_name = "ARCHIVE")]
from: Option<PathBuf>,
},
Installations,
Uninstall { installation_id: LocalId },
Start(StartArgs),
Instances,
Stop,
Restart,
Remove { instance_id: LocalId },
Update {
#[arg(long, value_name = "VERSION")]
version: String,
#[arg(long)]
verify_compatibility: bool,
#[command(flatten)]
#[serde(flatten)]
deadline: DeadlineArgs,
},
Rollback {
#[command(flatten)]
#[serde(flatten)]
deadline: DeadlineArgs,
},
}
#[derive(Debug, Args, Serialize)]
pub struct StartArgs {
#[arg(long, value_name = "VERSION")]
pub version: Option<String>,
#[arg(long, requires = "request_id")]
pub new: bool,
#[arg(long, requires = "new")]
pub verify_compatibility: bool,
#[arg(long, requires = "new")]
pub request_id: Option<LocalId>,
#[command(flatten)]
#[serde(flatten)]
pub deadline: DeadlineArgs,
}
#[derive(Debug, Args, Serialize)]
pub struct DeadlineArgs {
#[arg(long, default_value_t = Seconds::new(30))]
pub timeout: Seconds,
}
#[derive(Debug, Args, Serialize)]
pub struct PageArgs {
#[arg(long, default_value_t = Limit::new(20))]
pub limit: Limit,
#[arg(long)]
pub cursor: Option<Handle>,
#[command(flatten)]
#[serde(flatten)]
pub deadline: DeadlineArgs,
}
#[derive(Debug, Args, Serialize)]
pub struct ListArgs {
#[arg(long)]
pub query: Option<String>,
#[command(flatten)]
#[serde(flatten)]
pub page: PageArgs,
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ContactCommand {
List(ListArgs),
Get { user_id: DecimalId },
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum GroupCommand {
List(ListArgs),
Get { group_id: DecimalId },
Members {
#[arg(required_unless_present = "cursor")]
group_id: Option<DecimalId>,
#[command(flatten)]
#[serde(flatten)]
page: PageArgs,
},
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ConversationCommand {
List(ListArgs),
Get { conversation: Conversation },
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MessageCommand {
Get {
message_ref: Handle,
#[arg(long)]
expand: bool,
},
History(QueryArgs),
Search(SearchArgs),
Export(ExportArgs),
Send(SendArgs),
Reply(ReplyArgs),
}
#[derive(Debug, Args, Serialize)]
pub struct QueryArgs {
#[arg(long)]
pub conversation: Option<Conversation>,
#[arg(long, value_name = "RFC3339")]
pub since: Option<Rfc3339>,
#[arg(long, value_name = "RFC3339")]
pub until: Option<Rfc3339>,
#[command(flatten)]
#[serde(flatten)]
pub page: PageArgs,
}
#[derive(Debug, Args, Serialize)]
pub struct SearchArgs {
#[arg(long)]
pub query: Option<NonEmpty>,
#[command(flatten)]
#[serde(flatten)]
pub history: QueryArgs,
}
#[derive(Debug, Args, Serialize)]
pub struct ExportArgs {
#[arg(long)]
pub conversation: Option<Conversation>,
#[arg(long)]
pub query: Option<String>,
#[arg(long, value_name = "RFC3339")]
pub since: Option<Rfc3339>,
#[arg(long, value_name = "RFC3339")]
pub until: Option<Rfc3339>,
#[arg(long, value_name = "FILE")]
pub output: PathBuf,
#[arg(long)]
pub resume: bool,
#[arg(long, default_value_t = Limit::new(1000))]
pub limit: Limit,
#[command(flatten)]
#[serde(flatten)]
pub deadline: DeadlineArgs,
}
#[derive(Debug, Args, Serialize)]
pub struct SendArgs {
#[arg(long)]
pub conversation: Conversation,
#[command(flatten)]
#[serde(flatten)]
pub content: ContentArgs,
}
#[derive(Debug, Args, Serialize)]
pub struct ReplyArgs {
pub message_ref: Handle,
#[command(flatten)]
#[serde(flatten)]
pub content: ContentArgs,
}
#[derive(Debug, Args, Serialize)]
pub struct ContentArgs {
#[arg(long, conflicts_with_all = ["text_file", "stdin"])]
pub text: Option<Text>,
#[arg(long, conflicts_with_all = ["text", "stdin"], value_name = "FILE")]
pub text_file: Option<PathBuf>,
#[arg(long, conflicts_with_all = ["text", "text_file"])]
pub stdin: bool,
#[arg(long, value_name = "FILE")]
pub image: Vec<PathBuf>,
#[arg(long, value_name = "FILE")]
pub file: Vec<PathBuf>,
#[arg(long)]
pub mention: Vec<DecimalId>,
#[arg(long)]
pub request_id: Option<LocalId>,
#[arg(long)]
pub dry_run: bool,
#[command(flatten)]
#[serde(flatten)]
pub deadline: DeadlineArgs,
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MediaCommand {
Download {
media_ref: Handle,
#[arg(long, value_name = "FILE")]
output: PathBuf,
#[command(flatten)]
#[serde(flatten)]
deadline: DeadlineArgs,
},
Prune,
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EventCommand {
Watch {
#[arg(long)]
conversation: Option<Conversation>,
#[arg(long, requires = "conversation")]
resume: Option<Handle>,
#[arg(long, default_value_t = Limit::new(100))]
limit: Limit,
#[command(flatten)]
#[serde(flatten)]
deadline: DeadlineArgs,
},
}
#[derive(Debug, Subcommand, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum RequestCommand {
Get { request_id: LocalId },
}