a3s 0.10.0

a3s — A3S coding agent CLI; `a3s code` launches the interactive TUI
Documentation
#![cfg(target_os = "macos")]

use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

struct TestDirectory {
    path: PathBuf,
}

impl TestDirectory {
    fn new() -> Self {
        let stamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("system clock")
            .as_nanos();
        let path =
            std::env::temp_dir().join(format!("a3s-web-interrupt-{}-{stamp}", std::process::id()));
        fs::create_dir_all(&path).expect("create Web interrupt test directory");
        Self { path }
    }

    fn path(&self) -> &Path {
        &self.path
    }

    fn join(&self, path: impl AsRef<Path>) -> PathBuf {
        self.path.join(path)
    }
}

impl Drop for TestDirectory {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.path);
    }
}

#[test]
fn foreground_web_exits_on_ctrl_c_from_raw_terminal() {
    let directory = TestDirectory::new();
    let config_path = directory.join("config.acl");
    fs::write(&config_path, test_config()).expect("write Web interrupt test config");

    let expect_script = r#"
log_user 0
set timeout 45
spawn /bin/sh -c {stty raw -echo -isig; exec "$A3S_WEB_INTERRUPT_BIN" web --api-only --host 127.0.0.1 --port 0 --workspace "$A3S_WEB_INTERRUPT_ROOT" --config "$A3S_WEB_INTERRUPT_CONFIG"}
expect {
    -exact "Press Ctrl+C to stop.\n" {}
    eof {
        set result [wait]
        puts "a3s web exited before becoming ready: [lindex $result 3]"
        exit 120
    }
    timeout {
        catch {exec kill -TERM [exp_pid]}
        catch {wait}
        puts "a3s web did not become ready"
        exit 121
    }
}

send -- "\003"
set timeout 12
expect {
    eof {
        set result [wait]
        set status [lindex $result 3]
        if {$status != 0} {
            puts "a3s web exited with status $status"
            exit 122
        }
        exit 0
    }
    timeout {
        catch {exec kill -TERM [exp_pid]}
        after 500
        catch {exec kill -KILL [exp_pid]}
        catch {wait}
        puts "Ctrl+C did not stop a3s web"
        exit 123
    }
}
"#;
    let output = Command::new("/usr/bin/expect")
        .args(["-c", expect_script])
        .env("HOME", directory.path())
        .env("A3S_NO_AUTO_INSTALL", "1")
        .env("A3S_WEB_INTERRUPT_BIN", env!("CARGO_BIN_EXE_a3s"))
        .env("A3S_WEB_INTERRUPT_ROOT", directory.path())
        .env("A3S_WEB_INTERRUPT_CONFIG", &config_path)
        .output()
        .expect("run foreground Web PTY probe");

    assert!(
        output.status.success(),
        "foreground Web PTY probe failed:\nstdout: {}\nstderr: {}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr)
    );
}

fn test_config() -> &'static str {
    r#"default_model = "openai/test"
providers "openai" {
  apiKey = "test"
  baseUrl = "http://127.0.0.1:1"
  models "test" {
    name = "Test"
    toolCall = true
  }
}
memory { llmExtraction = false }
"#
}