Skip to main content

resq_cli/commands/
explore.rs

1/*
2 * Copyright 2026 ResQ
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! ResQ Explore commands — Unified TUI launcher.
18
19use anyhow::{Context, Result};
20use clap::Parser;
21use std::process::Command;
22
23/// Arguments for the 'explore' (resq-perf) command
24#[derive(Parser, Debug)]
25pub struct ExploreArgs {
26    /// Service URL to monitor
27    #[arg(default_value = "http://localhost:3000/admin/status")]
28    pub url: String,
29    /// Refresh rate in milliseconds
30    #[arg(long, default_value_t = 500)]
31    pub refresh_ms: u64,
32}
33
34/// Arguments for the 'logs' (resq-logs) command
35#[derive(Parser, Debug)]
36pub struct LogsArgs {
37    /// Log source: "docker" or "file"
38    #[arg(long, default_value = "docker")]
39    pub source: String,
40    /// Filter to a specific service name
41    #[arg(long)]
42    pub service: Option<String>,
43}
44
45/// Arguments for the 'health' (resq-health) command
46#[derive(Parser, Debug)]
47pub struct HealthArgs {
48    /// Poll interval in seconds
49    #[arg(short, long, default_value_t = 5)]
50    pub interval: u64,
51}
52
53/// Arguments for the 'deploy' (resq-deploy) command
54#[derive(Parser, Debug)]
55pub struct DeployArgs {
56    /// Target environment: dev, staging, prod
57    #[arg(long, default_value = "dev")]
58    pub env: String,
59    /// Use Kubernetes instead of Docker Compose
60    #[arg(long)]
61    pub k8s: bool,
62}
63
64/// Arguments for the 'clean' (resq-clean) command
65#[derive(Parser, Debug)]
66pub struct CleanArgs {
67    /// Preview what would be deleted without removing anything
68    #[arg(long, default_value_t = false)]
69    pub dry_run: bool,
70}
71
72/// Arguments for the 'asm' (resq-bin) command
73#[derive(Parser, Debug)]
74pub struct AsmArgs {
75    /// Analyze a single binary path
76    #[arg(long, conflicts_with = "dir")]
77    pub file: Option<String>,
78    /// Analyze binaries under a directory
79    #[arg(long, conflicts_with = "file")]
80    pub dir: Option<String>,
81    /// Recursively traverse directory in batch mode
82    #[arg(long, default_value_t = false)]
83    pub recursive: bool,
84    /// Optional suffix filter in batch mode (e.g. .so, .o)
85    #[arg(long)]
86    pub ext: Option<String>,
87    /// Optional resq-bin config TOML path
88    #[arg(long)]
89    pub config: Option<String>,
90    /// Disable cache reads/writes
91    #[arg(long, default_value_t = false)]
92    pub no_cache: bool,
93    /// Force cache rebuild
94    #[arg(long, default_value_t = false)]
95    pub rebuild_cache: bool,
96    /// Disable disassembly and only collect metadata
97    #[arg(long, default_value_t = false)]
98    pub no_disasm: bool,
99    /// Maximum functions to disassemble per binary
100    #[arg(long)]
101    pub max_functions: Option<usize>,
102    /// Force interactive TUI mode
103    #[arg(long, default_value_t = false)]
104    pub tui: bool,
105    /// Use non-interactive plain output
106    #[arg(long, default_value_t = false)]
107    pub plain: bool,
108    /// Emit JSON report output
109    #[arg(long, default_value_t = false)]
110    pub json: bool,
111}
112
113/// Run resq-perf (Performance Explorer)
114pub async fn run_explore(args: ExploreArgs) -> Result<()> {
115    run_tool(
116        "resq-perf",
117        &[&args.url, "--refresh-ms", &args.refresh_ms.to_string()],
118    )
119}
120
121/// Run resq-logs (Log Explorer)
122pub async fn run_logs(args: LogsArgs) -> Result<()> {
123    let mut cmd_args = vec!["--source", &args.source];
124    if let Some(ref s) = args.service {
125        cmd_args.push("--service");
126        cmd_args.push(s);
127    }
128    run_tool("resq-logs", &cmd_args)
129}
130
131/// Run resq-health (Health Explorer)
132pub async fn run_health(args: HealthArgs) -> Result<()> {
133    run_tool("resq-health", &["--interval", &args.interval.to_string()])
134}
135
136/// Run resq-deploy (Deploy Explorer)
137pub async fn run_deploy(args: DeployArgs) -> Result<()> {
138    let mut cmd_args = vec!["--env", &args.env];
139    if args.k8s {
140        cmd_args.push("--k8s");
141    }
142    run_tool("resq-deploy", &cmd_args)
143}
144
145/// Run resq-clean (Workspace Cleaner)
146pub async fn run_clean(args: CleanArgs) -> Result<()> {
147    let mut cmd_args = Vec::new();
148    if args.dry_run {
149        cmd_args.push("--dry-run");
150    }
151    run_tool("resq-clean", &cmd_args)
152}
153
154/// Run resq-bin (Binary Explorer)
155pub async fn run_asm(args: AsmArgs) -> Result<()> {
156    let mut cmd_args = Vec::new();
157    if let Some(ref file) = args.file {
158        cmd_args.push("--file");
159        cmd_args.push(file);
160    }
161    if let Some(ref dir) = args.dir {
162        cmd_args.push("--dir");
163        cmd_args.push(dir);
164    }
165    if args.recursive {
166        cmd_args.push("--recursive");
167    }
168    if let Some(ref ext) = args.ext {
169        cmd_args.push("--ext");
170        cmd_args.push(ext);
171    }
172    if let Some(ref config) = args.config {
173        cmd_args.push("--config");
174        cmd_args.push(config);
175    }
176    if args.no_cache {
177        cmd_args.push("--no-cache");
178    }
179    if args.rebuild_cache {
180        cmd_args.push("--rebuild-cache");
181    }
182    if args.no_disasm {
183        cmd_args.push("--no-disasm");
184    }
185    let max_functions = args.max_functions.map(|v| v.to_string());
186    if let Some(ref max_functions) = max_functions {
187        cmd_args.push("--max-functions");
188        cmd_args.push(max_functions);
189    }
190    if args.tui {
191        cmd_args.push("--tui");
192    }
193    if args.plain {
194        cmd_args.push("--plain");
195    }
196    if args.json {
197        cmd_args.push("--json");
198    }
199    run_tool("resq-bin", &cmd_args)
200}
201
202fn run_tool(name: &str, args: &[&str]) -> Result<()> {
203    // We assume the tool is built and available via 'cargo run -p'
204    // or eventually as a pre-built binary in the path.
205    // For now, using 'cargo run -p' ensures we always run the latest version in dev.
206
207    let mut child = Command::new("cargo")
208        .arg("run")
209        .arg("-q")
210        .arg("-p")
211        .arg(name)
212        .arg("--")
213        .args(args)
214        .spawn()
215        .with_context(|| format!("Failed to launch tool: {name}"))?;
216
217    let status = child.wait().context("Tool crashed or was interrupted")?;
218    if !status.success() {
219        // We don't necessarily want to exit the main CLI with error if the TUI was just closed
220    }
221    Ok(())
222}