Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion crates/cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,15 @@ impl Context {
}

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None, name = "surfpool", bin_name = "surfpool")]
#[clap(
author,
version,
about,
long_about = "Where you train before surfing Solana\n\nAutomation mode:\nSet NO_DNA=1 to disable interactive prompts and
TUI-oriented UX for agent/CI execution.",
name = "surfpool",
bin_name = "surfpool"
)]
struct Opts {
#[clap(subcommand)]
command: Command,
Expand Down Expand Up @@ -585,6 +593,10 @@ pub async fn handle_mcp_command(_ctx: &Context) -> Result<(), String> {
fn handle_command(opts: Opts, ctx: &Context) -> Result<(), String> {
match opts.command {
Command::Simnet(mut cmd) => {
if crate::no_dna::is_no_dna() {
cmd.no_tui = true;
}

if cmd.ci {
cmd.disable_instruction_profiling = true;
cmd.no_studio = true;
Expand Down
1 change: 1 addition & 0 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[macro_use]
mod macros;
mod no_dna;

extern crate hiro_system_kit;

Expand Down
42 changes: 42 additions & 0 deletions crates/cli/src/no_dna.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use std::env;

/// Returns true when NO_DNA requests non-interactive agent mode.
/// Truthy values (case-insensitive)
pub fn is_no_dna() -> bool {
parse_truthy(env::var("NO_DNA").ok().as_deref())
}

fn parse_truthy(value: Option<&str>) -> bool {
match value {
Some(v) => {
let v = v.trim().to_ascii_lowercase();
matches!(v.as_str(), "1" | "true" | "yes" | "on")
}
None => false,
}
}

#[cfg(test)]
mod tests {
use super::parse_truthy;

#[test]
fn parse_truthy_values() {
assert!(parse_truthy(Some("1")));
assert!(parse_truthy(Some("true")));
assert!(parse_truthy(Some("TRUE")));
assert!(parse_truthy(Some("yes")));
assert!(parse_truthy(Some("on")));
assert!(parse_truthy(Some(" On ")));
}

#[test]
fn parse_falsey_values() {
assert!(!parse_truthy(None));
assert!(!parse_truthy(Some("0")));
assert!(!parse_truthy(Some("fals e")));
assert!(!parse_truthy(Some("no")));
assert!(!parse_truthy(Some("off")));
assert!(!parse_truthy(Some("")));
}
}
18 changes: 14 additions & 4 deletions crates/cli/src/runbook/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,20 @@ pub async fn execute_runbook(
..ColorfulTheme::default()
};

let confirm = Confirm::with_theme(&theme)
.with_prompt("Do you want to continue?")
.interact()
.unwrap();
let confirm = if crate::no_dna::is_no_dna() {
// non-interactive mode should not block on prompt
// safest behavior: require explicit unsupervised/yes-style intent if available
if cmd.unsupervised {
true
} else {
return Err("NO_DNA=1 disables interactive confirmation. Re-run with non-interactive intent (e.g. --unsupervised or equivalent explicit approval flag).".to_string());
}
} else {
Confirm::with_theme(&theme)
.with_prompt("Do you want to continue?")
.interact()
.map_err(|e| format!("failed to read confirmation input: {e}"))?
};

if !confirm {
return Ok(());
Expand Down
34 changes: 19 additions & 15 deletions crates/cli/src/scaffold/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,21 +223,25 @@ pub fn scaffold_iac_layout(
let selected_programs = match auto_generate_runbooks {
true => programs,
false => {
let selection = MultiSelect::with_theme(&theme)
.with_prompt("Select the programs to deploy (all by default):")
.items_checked(
&programs
.iter()
.map(|p| (p.name.as_str(), true))
.collect::<Vec<_>>(),
)
.interact()
.map_err(|e| format!("unable to select programs to deploy: {e}"))?;

&selection
.iter()
.map(|i| programs[*i].clone())
.collect::<Vec<_>>()
if crate::no_dna::is_no_dna() {
programs
} else {
let selection = MultiSelect::with_theme(&theme)
.with_prompt("Select the programs to deploy (all by default):")
.items_checked(
&programs
.iter()
.map(|p| (p.name.as_str(), true))
.collect::<Vec<_>>(),
)
.interact()
.map_err(|e| format!("unable to select programs to deploy: {e}"))?;

&selection
.iter()
.map(|i| programs[*i].clone())
.collect::<Vec<_>>()
}
}
};

Expand Down