supercode-cli 0.4.6

supercode — a lightweight, fully-customizable AI coding agent CLI in Rust. Any model via OpenRouter; natively continues Claude Code and Codex sessions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
//! CLI-level acceptance test for UX-29 dev/01 (soft-interrupt / steer
//! mid-turn without killing it) — the real-CLI-over-a-real-pty proof this
//! ticket's bar asks for. `soft_interrupt_cli.rs` (dev/02) proves Ctrl-C
//! hard-cancels a one-shot `run` over PIPED (non-tty) stdio, since SIGINT
//! delivery there is process-level (`kill -INT`) and needs no terminal at
//! all. dev/01 is the opposite: it only exists while a REAL terminal's
//! canonical-mode keyboard is live, so this file spawns the actual built
//! `supercode` binary with its stdin/stdout/stderr ALL wired to ONE real
//! pty (`openpty(3)`, the child made the pty's session leader/controlling
//! terminal via `setsid`+`TIOCSCTTY` in a `pre_exec` hook) — a genuine
//! terminal, not a fake or an in-process fd swap (this codebase's own
//! precedent, `picker.rs`'s doc comment, explicitly rules out repointing
//! the TEST process's own fd 0 at a pty as unsafe/global-state-stomping;
//! spawning a full CHILD PROCESS against a pty sidesteps that entirely).
//!
//! Proves, against a real local HTTP/SSE stub that serves multiple
//! requests in sequence (so this test can inspect exactly what body each
//! turn sent, not just what the terminal displayed):
//!   1. A line typed + Enter mid-turn is captured (an on-pty confirmation
//!      notice appears) WHILE the turn is still in flight, and the turn
//!      goes on to finish normally (its own reply text appears) — never
//!      aborted, never truncated.
//!   2. The captured line is then delivered, unprompted, as the very next
//!      turn's request — the stub's SECOND request body is asserted to
//!      contain the exact steer text, proving real delivery, not just a
//!      printed notice.
//!   3. A REAL Ctrl-C (byte `0x03` sent through the pty, so the KERNEL's
//!      own line discipline raises a genuine `SIGINT` — not a synthetic
//!      `kill`) during that delivered turn still hard-aborts it (exit code
//!      130, the dev/02 notice, no further "queued" notice) — distinct
//!      from soft-steer and unbroken by this ticket's changes, on the
//!      EXACT call site this ticket modified.
//!   4. After the child exits, the pty's termios are back to exactly what
//!      they were before the child ever ran — proof nothing in this REPL
//!      session (soft-steer included) leaked a raw-mode terminal.
//!
//! A second, much smaller test proves the opposite edge: a genuinely
//! non-interactive piped `run` never emits any soft-steer chrome at all.

use std::io::{Read, Write};
use std::net::TcpListener;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::mpsc;
use std::time::{Duration, Instant};

fn bin() -> PathBuf {
    PathBuf::from(env!("CARGO_BIN_EXE_supercode"))
}

fn fresh_home(tag: &str) -> PathBuf {
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let dir = std::env::temp_dir().join(format!(
        "supercode-ux29-softsteer-{tag}-{}-{nanos}",
        std::process::id()
    ));
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

// ---- A tiny real pty (openpty + setsid/TIOCSCTTY) ----------------------

struct Pty {
    master: OwnedFd,
    /// The slave side, kept open (as a bare fd) in the parent only long
    /// enough for `spawn_chat`'s `pre_exec` hook to `ioctl(TIOCSCTTY)` it
    /// in the child; closed explicitly once the test is done with it.
    slave: RawFd,
}

impl Pty {
    fn open() -> Self {
        let mut master: libc::c_int = -1;
        let mut slave: libc::c_int = -1;
        // SAFETY: `master`/`slave` are valid out-pointers; the remaining
        // three args (name buf, termios, winsize) are optional (`NULL` is
        // documented as "use defaults") per `openpty(3)`.
        let rc = unsafe {
            libc::openpty(
                &mut master,
                &mut slave,
                std::ptr::null_mut(),
                std::ptr::null_mut(),
                std::ptr::null_mut(),
            )
        };
        assert_eq!(rc, 0, "openpty failed: {}", std::io::Error::last_os_error());
        // SAFETY: `openpty` just returned this fd as freshly opened and
        // valid; `OwnedFd` takes ownership (closes it on drop).
        let master = unsafe { OwnedFd::from_raw_fd(master) };
        Pty { master, slave }
    }

    fn termios(&self) -> libc::termios {
        let mut term = std::mem::MaybeUninit::<libc::termios>::uninit();
        // SAFETY: `self.master.as_raw_fd()` is a valid, open fd for the
        // lifetime of `self`; `term` is a correctly-sized out-pointer.
        let rc = unsafe { libc::tcgetattr(self.master.as_raw_fd(), term.as_mut_ptr()) };
        assert_eq!(
            rc,
            0,
            "tcgetattr(master) failed: {}",
            std::io::Error::last_os_error()
        );
        // SAFETY: `tcgetattr` returned success above, so `term` is init.
        unsafe { term.assume_init() }
    }

    fn write_str(&self, s: &str) {
        let fd = self.master.as_raw_fd();
        let bytes = s.as_bytes();
        let mut written = 0usize;
        while written < bytes.len() {
            // SAFETY: `fd` is open for `self`'s lifetime; the slice
            // `bytes[written..]` is a valid readable buffer.
            let n = unsafe {
                libc::write(
                    fd,
                    bytes[written..].as_ptr() as *const libc::c_void,
                    bytes.len() - written,
                )
            };
            assert!(
                n >= 0,
                "write to pty master failed: {}",
                std::io::Error::last_os_error()
            );
            written += n as usize;
        }
    }

    /// Read whatever is currently available (non-blocking) and append it
    /// to `acc`, looping until `needle` appears or `timeout` elapses.
    fn expect_contains(&self, acc: &mut String, needle: &str, timeout: Duration) {
        let fd = self.master.as_raw_fd();
        // SAFETY: `fd` is valid for `self`'s lifetime; setting O_NONBLOCK
        // only affects how *this test's* reads behave, not the child's
        // view of the terminal (a pty's O_NONBLOCK flag lives on this
        // fd-table entry, not on the shared line discipline).
        unsafe {
            let flags = libc::fcntl(fd, libc::F_GETFL);
            libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
        }
        let deadline = Instant::now() + timeout;
        let mut buf = [0u8; 4096];
        loop {
            if acc.contains(needle) {
                return;
            }
            if Instant::now() > deadline {
                panic!("timed out waiting for {needle:?} in pty output; captured so far: {acc:?}");
            }
            // SAFETY: `fd` is open, `buf` is a valid `4096`-byte buffer.
            let n = unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
            if n > 0 {
                acc.push_str(&String::from_utf8_lossy(&buf[..n as usize]));
            } else {
                std::thread::sleep(Duration::from_millis(10));
            }
        }
    }

    fn close_slave(&self) {
        // SAFETY: `self.slave` is a valid fd this struct still owns at
        // this point (only ever closed here, once, at test teardown).
        unsafe {
            libc::close(self.slave);
        }
    }
}

/// Spawn the real `supercode` binary with stdin/stdout/stderr ALL wired to
/// `pty`'s slave — one real terminal, exactly like an interactive user's
/// session (chrome on stderr, replies on stdout, keystrokes on stdin, all
/// the same tty, all interleaved the way a real terminal actually sees
/// them).
fn spawn_chat(pty: &Pty, home: &Path, base_url: &str) -> std::process::Child {
    let dup_for = |slave: RawFd| -> Stdio {
        // SAFETY: `slave` is a valid, open fd (the pty's slave side,
        // still open in the parent at this point); `dup` returns a new
        // fd the parent then hands off (via `Stdio::from`) for `Command`
        // to `dup2` into the child — standard fd-inheritance plumbing.
        let duped = unsafe { libc::dup(slave) };
        assert!(
            duped >= 0,
            "dup(slave) failed: {}",
            std::io::Error::last_os_error()
        );
        // SAFETY: `duped` was just returned by a successful `dup` above.
        let owned = unsafe { OwnedFd::from_raw_fd(duped) };
        Stdio::from(owned)
    };
    let slave = pty.slave;
    let mut cmd = Command::new(bin());
    cmd.env("SUPERCODE_HOME", home)
        // MCP auto-discovery also consults harness files below HOME. Keep a
        // PTY test from reading a developer's real Claude/Codex config and
        // blocking on an unrelated first-run import prompt.
        .env("HOME", home)
        .env("XDG_CONFIG_HOME", home.join("config"))
        .env("XDG_DATA_HOME", home.join("data"))
        .env_remove("OPENROUTER_API_KEY")
        .env_remove("OPENAI_API_KEY")
        .env_remove("ANTHROPIC_API_KEY")
        .env_remove("NO_COLOR")
        .env_remove("SUPERCODE_QUIET")
        .args(["--api-key", "x", "--base-url", base_url, "chat"])
        .stdin(dup_for(slave))
        .stdout(dup_for(slave))
        .stderr(dup_for(slave));
    // SAFETY: this closure runs in the child, after `fork()` and before
    // `exec()` — `setsid` detaches the child from any inherited
    // controlling terminal and makes it a new session/process-group
    // leader; `ioctl(.., TIOCSCTTY, 0)` on the (still-inherited, valid in
    // the child) pty slave fd then makes THIS pty the child's controlling
    // terminal, which is what makes a real Ctrl-C keystroke sent through
    // the master raise a genuine `SIGINT` at the child via the kernel's
    // own line discipline (`ISIG`) — not something this test has to
    // simulate by hand. No allocation, no async-signal-unsafe calls.
    unsafe {
        cmd.pre_exec(move || {
            if libc::setsid() == -1 {
                return Err(std::io::Error::last_os_error());
            }
            if libc::ioctl(slave, libc::TIOCSCTTY as libc::c_ulong, 0) == -1 {
                return Err(std::io::Error::last_os_error());
            }
            Ok(())
        });
    }
    cmd.spawn()
        .expect("failed to spawn supercode chat over a pty")
}

/// A local HTTP/SSE stub that serves MULTIPLE requests in sequence (unlike
/// `soft_interrupt_cli.rs`'s one-shot stub — this test needs to inspect a
/// SECOND request's body). `delays[i]` is applied before replying to the
/// i-th request (the last entry repeats for any further request). Each
/// request's raw bytes are sent on `tx` the instant they're read off the
/// wire — proof a turn genuinely left the client, never a timing guess.
fn spawn_multi_stub(
    delays: Vec<Duration>,
    reply: &'static str,
) -> (
    std::net::SocketAddr,
    std::thread::JoinHandle<()>,
    mpsc::Receiver<Vec<u8>>,
) {
    let listener = TcpListener::bind("127.0.0.1:0").expect("bind stub listener");
    let addr = listener.local_addr().unwrap();
    let (tx, rx) = mpsc::channel();
    let handle = std::thread::spawn(move || {
        for i in 0.. {
            let Ok((mut sock, _)) = listener.accept() else {
                break;
            };
            let mut buf = vec![0u8; 16384];
            let n = sock.read(&mut buf).unwrap_or(0);
            if n > 0 && tx.send(buf[..n].to_vec()).is_err() {
                break; // receiver gone — test is tearing down.
            }
            let delay = *delays.get(i).unwrap_or_else(|| delays.last().unwrap());
            std::thread::sleep(delay);
            let sse = format!(
                "data: {{\"choices\":[{{\"delta\":{{\"content\":\"{reply}\"}}}}]}}\n\ndata: [DONE]\n\n"
            );
            let resp = format!(
                "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
                sse.len(),
                sse
            );
            // The client may already have hung up (e.g. Ctrl-C cancelled
            // this exact turn) — a write error here is expected and fine.
            let _ = sock.write_all(resp.as_bytes());
            let _ = sock.flush();
        }
    });
    (addr, handle, rx)
}

#[test]
#[cfg_attr(
    target_os = "macos",
    ignore = "macOS openpty does not deliver synthetic NL/CR as a rustyline Enter key"
)]
fn steer_mid_turn_is_queued_and_delivered_without_aborting_the_turn_then_ctrl_c_still_hard_aborts()
{
    let home = fresh_home("main");
    // UX-32 onboarding (merged onto main after this test was written) fires
    // on any fresh `SUPERCODE_HOME` with no `config.toml` on a real tty —
    // exactly the setup this test's pty gives it — and would otherwise
    // block on onboarding's own "run the guided setup now? [Y/n]" prompt
    // before the REPL this test cares about ever starts. Pre-seed a
    // `config.toml` (same shape `login`/onboarding itself would produce)
    // so `should_onboard`'s `config_exists` gate is false and this test
    // exercises soft-steer against an already-configured install, same as
    // every other CLI test here.
    std::fs::write(
        home.join("config.toml"),
        "model = \"anthropic/claude-opus-4-8\"\n",
    )
    .expect("seed config.toml");
    let pty = Pty::open();
    let orig_termios = pty.termios();

    // Turn 1 replies after 1s (a real window to type a steer line into);
    // turn 2 (the auto-delivered steer) would reply after 4s, but this
    // test Ctrl-Cs it well before that — the long delay is just headroom.
    let (addr, server, rx_bodies) = spawn_multi_stub(
        vec![Duration::from_millis(1000), Duration::from_secs(4)],
        "hello from turn one",
    );

    let mut child = spawn_chat(&pty, &home, &format!("http://{addr}"));
    let mut out = String::new();

    // Wait for the REPL's own start-of-session hint so we know the prompt
    // is genuinely up before typing anything.
    pty.expect_contains(&mut out, "Ctrl-D to exit", Duration::from_secs(10));

    // ---- Turn 1: type the message, confirm it genuinely left the client --
    pty.write_str("hello there\n");
    let req0 = rx_bodies
        .recv_timeout(Duration::from_secs(10))
        .expect("stub never saw turn 1's request arrive — turn never left the client");
    assert!(
        String::from_utf8_lossy(&req0).contains("hello there"),
        "turn 1's request should carry the typed message"
    );

    // ---- Mid-turn: type a steer line + Enter WHILE turn 1 is in flight ---
    // (the stub is still asleep for most of its 1s delay at this point).
    pty.write_str("focus on unit tests next\n");

    // Proof of capture: the on-pty confirmation notice appears BEFORE the
    // turn's own reply does — i.e. captured concurrently, not after.
    pty.expect_contains(
        &mut out,
        "queued — will steer after this turn",
        Duration::from_secs(5),
    );
    assert!(
        !out.contains("hello from turn one"),
        "the steer notice must land BEFORE turn 1's reply — got: {out:?}"
    );

    // ---- Proof turn 1 was NOT aborted: its own reply still arrives ------
    pty.expect_contains(&mut out, "hello from turn one", Duration::from_secs(10));

    // ---- Proof of delivery: the queued steer becomes the NEXT request ---
    let req1 = rx_bodies
        .recv_timeout(Duration::from_secs(10))
        .expect("the queued steer was never delivered as a second request");
    let req1_str = String::from_utf8_lossy(&req1);
    assert!(
        req1_str.contains("focus on unit tests next"),
        "the second request must carry the exact queued steer text, got: {req1_str}"
    );
    assert!(
        req1_str.contains("hello there") && req1_str.contains("hello from turn one"),
        "the second request's history must still include turn 1's own exchange \
         (the steer is delivered as a NEW turn, not a replacement), got: {req1_str}"
    );

    // ---- Now prove Ctrl-C is STILL distinct and STILL hard-aborts -------
    // Turn 2 (the delivered steer) is now genuinely in flight (its request
    // just arrived above) and asleep for up to 4s — plenty of window.
    //
    // Inside the interactive `chat()` REPL (unlike the one-shot `run`/
    // `resume <file> "prompt"` call sites, which exit 130), dev/02's own
    // documented contract is: Ctrl-C hard-cancels the IN-FLIGHT TURN and
    // returns to the prompt — the REPL itself is NOT exited (see
    // `main.rs::chat`'s own comment above its `race_ctrl_c_with_steer`
    // call site, and `soft_interrupt_cli.rs`'s doc comment, which covers
    // the DIFFERENT one-shot exit-130 contract separately). So the proof
    // here is: the notice prints, the turn's own reply NEVER arrives, and
    // the process is very much still alive at an idle prompt afterward —
    // not that it exits.
    pty.write_str("\u{3}"); // a real Ctrl-C byte, through the pty's line discipline
    pty.expect_contains(&mut out, "interrupted", Duration::from_secs(5));
    drop(server); // the stub thread would otherwise block in `accept()`/`sleep` forever; detach, don't join.

    // Both stubs reply with the same text ("hello from turn one"); it must
    // appear EXACTLY ONCE in the whole transcript — turn 1's own reply —
    // never a second time, which would mean the delivered steer's turn
    // was allowed to complete despite the Ctrl-C.
    assert_eq!(
        out.matches("hello from turn one").count(),
        1,
        "the delivered steer's own turn must never complete after a hard Ctrl-C \
         (its reply text must never appear a second time), got: {out:?}"
    );
    assert_eq!(
        out.matches("queued — will steer").count(),
        1,
        "exactly one steer should ever have been queued (from turn 1) — a Ctrl-C must never \
         itself be captured as a second steer line: {out:?}"
    );

    // Proof the REPL is genuinely still alive (not exited, not hung) after
    // the hard-cancel: Ctrl-D now cleanly ends the session (rustyline's
    // own EOF path, `chat()` prints "bye." and returns `Ok(())`).
    pty.write_str("\u{4}"); // Ctrl-D / EOF on an empty line
    let status = child.wait().expect("child wait failed");
    assert!(
        status.success(),
        "the REPL must still be alive and exit cleanly via Ctrl-D after the hard-cancel: \
         status={status:?} pty output so far={out:?}"
    );

    // ---- Terminal-restore proof: the pty's termios are back to exactly
    // what they were before the child ever ran — nothing leaked raw mode.
    let after_termios = pty.termios();
    assert_eq!(
        (
            orig_termios.c_iflag,
            orig_termios.c_oflag,
            orig_termios.c_cflag,
            orig_termios.c_lflag,
        ),
        (
            after_termios.c_iflag,
            after_termios.c_oflag,
            after_termios.c_cflag,
            after_termios.c_lflag,
        ),
        "the pty's termios must be restored to their pre-session state after the child exits"
    );

    pty.close_slave();
}

/// Real-CLI proof (companion to the above): a genuinely non-interactive,
/// piped invocation of the one-shot `run` subcommand never touches
/// soft-steer at all — no capture, no notice text, byte-identical to
/// `run`'s pre-UX-29-dev/01 behavior. `soft_interrupt_cli.rs` already
/// proves `run`'s Ctrl-C/session-consistency behavior end-to-end; this
/// adds the one assertion specific to THIS ticket: zero soft-steer chrome
/// leaks into a machine/piped path.
#[test]
fn non_tty_run_never_emits_soft_steer_chrome() {
    let home = fresh_home("nontty");
    let (addr, server, _rx) = spawn_multi_stub(vec![Duration::from_millis(0)], "hi");
    let out = Command::new(bin())
        .env("SUPERCODE_HOME", &home)
        .env_remove("OPENROUTER_API_KEY")
        .env_remove("OPENAI_API_KEY")
        .env_remove("ANTHROPIC_API_KEY")
        .env_remove("NO_COLOR")
        .env_remove("SUPERCODE_QUIET")
        .args([
            "--api-key",
            "x",
            "--base-url",
            &format!("http://{addr}"),
            "run",
            "a piped one-shot prompt",
        ])
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .expect("failed to run supercode");
    drop(server); // one-shot use; the stub thread's still-looping accept() would block forever otherwise.
    assert!(
        out.status.success(),
        "piped run should succeed: status={:?} stderr={}",
        out.status,
        String::from_utf8_lossy(&out.stderr)
    );
    let stderr = String::from_utf8_lossy(&out.stderr);
    let stdout = String::from_utf8_lossy(&out.stdout);
    assert!(
        !stderr.contains("queued — will steer") && !stdout.contains("queued — will steer"),
        "a piped/non-tty run must never emit soft-steer chrome, stderr={stderr} stdout={stdout}"
    );
}