feat: -d/--detach flag for non-interactive use
All checks were successful
CI / Check, test, lint (push) Successful in 27s
All checks were successful
CI / Check, test, lint (push) Successful in 27s
This commit is contained in:
@@ -14,7 +14,10 @@ Run on a specific worker:
|
|||||||
p <worker> -- <command>
|
p <worker> -- <command>
|
||||||
|
|
||||||
Skip directory sync:
|
Skip directory sync:
|
||||||
p -n -- <command>"
|
p -n -- <command>
|
||||||
|
|
||||||
|
Detach immediately and print the job ID (for scripting / AI agents):
|
||||||
|
p -d -- <command>"
|
||||||
)]
|
)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use base64::{engine::general_purpose::STANDARD as B64, Engine};
|
use base64::{engine::general_purpose::STANDARD as B64, Engine};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use std::io::IsTerminal;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -9,10 +10,19 @@ use crate::{
|
|||||||
ssh, sync,
|
ssh, sync,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn execute(worker_name: Option<&str>, cmd: Vec<String>, no_sync: bool) -> Result<()> {
|
pub fn execute(
|
||||||
|
worker_name: Option<&str>,
|
||||||
|
cmd: Vec<String>,
|
||||||
|
no_sync: bool,
|
||||||
|
detach: bool,
|
||||||
|
) -> Result<()> {
|
||||||
let cfg = config::load()?;
|
let cfg = config::load()?;
|
||||||
let worker = cfg.resolve_worker(worker_name)?.clone();
|
let worker = cfg.resolve_worker(worker_name)?.clone();
|
||||||
|
|
||||||
|
// Auto-detach when stdout is not a terminal (pipe, script, AI agent, etc.).
|
||||||
|
// Explicit -d also sets detach=true before we get here.
|
||||||
|
let detach = detach || !std::io::stdout().is_terminal();
|
||||||
|
|
||||||
let id = Uuid::new_v4().to_string();
|
let id = Uuid::new_v4().to_string();
|
||||||
let short_id = &id[..8];
|
let short_id = &id[..8];
|
||||||
let session = format!("p-{}", short_id);
|
let session = format!("p-{}", short_id);
|
||||||
@@ -41,9 +51,13 @@ pub fn execute(worker_name: Option<&str>, cmd: Vec<String>, no_sync: bool) -> Re
|
|||||||
// ── 2. Sync directory ─────────────────────────────────────────────────────
|
// ── 2. Sync directory ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
if no_sync {
|
if no_sync {
|
||||||
|
if !detach {
|
||||||
eprintln!("Skipping sync (--no-sync).");
|
eprintln!("Skipping sync (--no-sync).");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if !detach {
|
||||||
eprintln!("Syncing to {}...", worker.name);
|
eprintln!("Syncing to {}...", worker.name);
|
||||||
|
}
|
||||||
sync::push_dir(&worker, &std::env::current_dir()?, &work_dir)
|
sync::push_dir(&worker, &std::env::current_dir()?, &work_dir)
|
||||||
.context("directory sync failed")?;
|
.context("directory sync failed")?;
|
||||||
}
|
}
|
||||||
@@ -84,7 +98,17 @@ pub fn execute(worker_name: Option<&str>, cmd: Vec<String>, no_sync: bool) -> Re
|
|||||||
|
|
||||||
ssh::run_capture(&worker, &setup).context("failed to set up job on worker")?;
|
ssh::run_capture(&worker, &setup).context("failed to set up job on worker")?;
|
||||||
|
|
||||||
// ── 4. Attach to the tmux session ─────────────────────────────────────────
|
// ── 4. Attach or detach ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
if detach {
|
||||||
|
// Non-interactive mode: print only the job UUID to stdout and exit.
|
||||||
|
// stderr is kept clean so the caller can capture just the ID:
|
||||||
|
// p logs $(p -d -- make)
|
||||||
|
println!("{}", id);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interactive mode: attach to the tmux session.
|
||||||
// run.sh keeps the session alive after the job finishes (via `read`),
|
// run.sh keeps the session alive after the job finishes (via `read`),
|
||||||
// so there is no race between job completion and our attach call.
|
// so there is no race between job completion and our attach call.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -20,19 +20,21 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
if cmd.is_empty() {
|
if cmd.is_empty() {
|
||||||
eprintln!("error: no command specified after --");
|
eprintln!("error: no command specified after --");
|
||||||
eprintln!("usage: p [-n] [<worker>] -- <command>");
|
eprintln!("usage: p [-n] [-d] [<worker>] -- <command>");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut no_sync = false;
|
let mut no_sync = false;
|
||||||
|
let mut detach = false;
|
||||||
let mut worker: Option<String> = None;
|
let mut worker: Option<String> = None;
|
||||||
|
|
||||||
for arg in pre {
|
for arg in pre {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"-n" | "--no-sync" => no_sync = true,
|
"-n" | "--no-sync" => no_sync = true,
|
||||||
|
"-d" | "--detach" => detach = true,
|
||||||
flag if flag.starts_with('-') => {
|
flag if flag.starts_with('-') => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"error: unknown flag '{}'\nusage: p [-n] [<worker>] -- <command>",
|
"error: unknown flag '{}'\nusage: p [-n] [-d] [<worker>] -- <command>",
|
||||||
flag
|
flag
|
||||||
);
|
);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
@@ -47,7 +49,7 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return commands::run::execute(worker.as_deref(), cmd, no_sync);
|
return commands::run::execute(worker.as_deref(), cmd, no_sync, detach);
|
||||||
}
|
}
|
||||||
|
|
||||||
// All other subcommands go through clap.
|
// All other subcommands go through clap.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// Directory sync via rsync over SSH.
|
// Directory sync via rsync over SSH.
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use std::io::IsTerminal;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -13,7 +14,12 @@ pub fn push_dir(worker: &WorkerConfig, local_dir: &Path, remote_path: &str) -> R
|
|||||||
let (user_host, port) = parse_connection(&worker.connection);
|
let (user_host, port) = parse_connection(&worker.connection);
|
||||||
|
|
||||||
let mut cmd = Command::new("rsync");
|
let mut cmd = Command::new("rsync");
|
||||||
|
// Only show progress when there is a human watching.
|
||||||
|
if std::io::stdout().is_terminal() {
|
||||||
cmd.args(["-az", "--info=progress2", "--filter=:- .gitignore"]);
|
cmd.args(["-az", "--info=progress2", "--filter=:- .gitignore"]);
|
||||||
|
} else {
|
||||||
|
cmd.args(["-az", "--filter=:- .gitignore"]);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(p) = port {
|
if let Some(p) = port {
|
||||||
cmd.arg(format!("-e=ssh -p {}", p));
|
cmd.arg(format!("-e=ssh -p {}", p));
|
||||||
|
|||||||
Reference in New Issue
Block a user