fix(cli): end the stdin envelope at its first newline
An Erlang port cannot close stdin without closing the port, so a host there never sends the EOF `advance -` waited for. The envelope is one JSON line; reading to the first newline (or EOF) lets such a host write the line and read the answer.
60 unmodified lines6162636465666765666768697071727366 unmodified lines60 unmodified lines
The `smoke` step is not decoration: it compiles `tests/c_smoke.c` with the toolchain, linksthe installed static library, exercises multi-round state, and asserts against recorded provider bodies.
`advance -` takes the whole call as one JSON envelope on stdin instead, so a credential neverlands in argv where other local processes can read it. It is as stateless as the argument form:the host keeps every round's responses and replays them.`advance -` takes the whole call as one JSON envelope line on stdin instead, so a credentialnever lands in argv where other local processes can read it. The envelope ends at the firstnewline, so a host that cannot half-close a pipe (an Erlang port) writes one line and reads theanswer. It is as stateless as the argument form: the host keeps every round's responses andreplays them.
```shecho '{"provider":"anthropic","credential":{"kind":"oauth","access":"…"},"responses":[],"nowMs":0}' \ | zig build run -- advance -66 unmodified lines6 unmodified lines7891011111213141554 unmodified lines707172737474757676777879777879808182838480 unmodified lines6 unmodified lines \\usage <command> \\ providers supported providers info \\ advance <provider> <credential.json> <responses.json>... advance feed state across rounds \\ [--now-ms N] \\ advance - the same, from one JSON envelope on stdin: \\ advance - the same, from one JSON envelope line on stdin: \\ {"provider","credential","responses":[round...],"nowMs"?} \\ claims <access-token> account id and email from an OAuth token \\;54 unmodified lines return 0;}
/// `advance -`: the whole call as one JSON envelope on stdin, so a credential never reaches argv,/// where any local process can read it./// where any local process can read it. The envelope ends at the first newline, not only at EOF:/// a host that cannot half-close a pipe (an Erlang port) writes one line and reads the answer.fn advanceEnvelope(init: std.process.Init, arena: std.mem.Allocator) !u8 { var buffer: [8192]u8 = undefined; var reader = std.Io.File.stdin().readerStreaming(init.io, &buffer); const input = reader.interface.allocRemaining(arena, std.Io.Limit.limited(8 * 1024 * 1024)) catch |e| return fail(init, @errorName(e)); const buffer = try arena.alloc(u8, 8 * 1024 * 1024); var reader = std.Io.File.stdin().readerStreaming(init.io, buffer); const input = (reader.interface.takeDelimiter('\n') catch |e| return fail(init, @errorName(e))) orelse return fail(init, "the envelope is empty");
const parsed = std.json.parseFromSliceLeaky(std.json.Value, arena, input, .{}) catch return fail(init, "the envelope is not JSON"); const root = usage.asObject(parsed) orelse return fail(init, "the envelope is not an object");80 unmodified lines