feat(cli): add support for NO_DNA agent mode (full no-dna.org complia…#685
feat(cli): add support for NO_DNA agent mode (full no-dna.org complia…#685rit3sh-x wants to merge 5 commits into
Conversation
…nce) Detect NO_DNA per https://no-dna.org — env var set AND non-empty, NO_COLOR-style semantics (NO_DNA=0 activates). NO_COLOR honored independently for color choice. AgentMode is a struct with five fields (active, output_format, color, timestamp_format, log_verbosity_floor) so consumers branch on data rather than re-reading env. Under NO_DNA: - start → no_tui + skip_runbook_generation_prompts - run → unsupervised - update → skip_confirm - runbook → snapshot-diff Confirm auto-yes (plus the pre-existing .unwrap() on dialoguer replaced with proper error propagation) - spinners + progress bars NOT constructed (download bar, runbook exec spinners, simnet active_spinners). - fern console branch routes to stderr (never stdout); format becomes JSONL {"ts","level","target","msg"} with RFC3339 UTC milliseconds; ANSI color escapes stripped; filter widens to pass all targets so agents see every log record. - direct print sites (cli/update, cli/simnet, cli/mod, runbook persist_log + handle_log_event, ls subcommand) all go through cli_info!/cli_warn!/cli_error! macros which emit JSONL on stderr under NO_DNA and plain stdout/stderr text otherwise. - ls emits one JSON object per runbook with structured `data` field (name, description, location). - setup_logger called for Update + List subcommand arms under NO_DNA so log!() calls in those paths flow through the JSON formatter. - --ci stays orthogonal: apply_ci_mode_to_simnet is a sibling helper to apply_agent_mode_to_simnet so both apply when both are set. --help long_about documents NO_DNA, suppressed surfaces, the JSONL/ RFC3339/no-color additions, NO_COLOR honoring, and --ci orthogonality. Supersedes solana-foundation#593 (stale: cloud-crate rebase rot + truthy-parse deviation from the NO_DNA standard). Closes solana-foundation#569.
Greptile SummaryThis PR adds full NO_DNA agent-mode support to the surfpool CLI, implementing the no-dna.org spec so that setting
Confidence Score: 5/5This PR is safe to merge; no changed code path introduces incorrect behavior in human or agent mode. The NO_DNA detection and routing logic is cleanly separated and well-tested. The OnceLock ensures consistent state across all call sites. The color macro guard correctly prevents ANSI leakage before strip_ansi is even needed. The fern dispatch routing and suppress_cli logic in persist_log are correct and avoid double-emitting. No pre-existing behavior is regressed for human-mode users. agent_io.rs — the strip_ansi helper leaves non-CSI escape sequence bodies in the output, which is a minor gap given the libraries in use only emit CSI sequences. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
Start([surfpool invoked]) --> Init[AgentMode from_env - OnceLock cached]
Init --> Check{NO_DNA set and non-empty?}
Check -- No --> Human[Human mode - Human format, Auto color]
Check -- Yes --> Agent[Agent mode - JSON format, Never color]
Human --> HumanLog[fern dispatch: console to stdout, colored + local timestamp]
Human --> HumanSpinner[ProgressBar and spinners enabled]
Agent --> AgentLog[fern dispatch: console to stderr, JSONL + RFC3339 UTC]
Agent --> AgentPrint[cli_emit macros: agent_emit to stderr]
Agent --> AgentNoSpinner[ProgressBar skipped, no spinners]
AgentPrint --> StripANSI[strip_ansi defense-in-depth]
StripANSI --> JsonOut[JSONL record on stderr]
AgentLog --> JsonOut
HumanLog --> HumanOut[colored lines on stdout or stderr]
Reviews (5): Last reviewed commit: "fix(cli): dedupe NO_DNA JSONL emission f..." | Re-trigger Greptile |
|
@lgalabru @MicaiahReid Deliberately left for follow-ups: static enforcement of NO_DNA gating (every invariant is by-convention today one careless PR away from regression), Lower priority: structured |
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…n prefixes
Greptile review caught two issues in the NO_DNA pipeline:
1. ANSI in JSON `msg`: color macros (purple!/yellow!/red! etc. in
crates/cli/src/macros.rs) gated only on `atty::is(Stream::Stdout)`. Under
NO_DNA with a TTY stdout, the resulting ANSI bytes passed through
persist_log into cli_info!/cli_warn!/cli_error! and ended up inside the
JSON `msg` field. Fix: color macros now also gate on
`AgentMode::from_env().color != ColorChoice::Never`, so they emit plain
text under NO_DNA or NO_COLOR regardless of TTY.
2. Defense-in-depth: format_agent_json_with_data_at now strips ANSI CSI
escape sequences from `msg` before serialization, catching any future
leakage from third-party libs or new code that bypasses the color macros.
3. Restored "Error: "/"Warning: " prefix in human mode: the cli_warn!/
cli_error! migration silently dropped the level prefixes from the
eprintln! sites it replaced. cli_emit now prepends "Error: "/"Warning: "
in human mode based on level; duplicate prefixes in cli_error! callers
(cli/mod.rs:917, cli/simnet/mod.rs:238) were stripped to avoid
"Error: Error: ..." doubling. Agent JSON unchanged (level field carries
the same info).
Greptile flagged: under NO_DNA, persist_log fired both the log-crate macros (routed via fern → stderr JSON) and cli_* (direct stderr JSON), producing two JSONL lines per event. handle_log_event's Transient agent branch had the same problem (cli_emit + persist_log). Fix: - persist_log: gate cli_* on `!AgentMode::is_active()`. Log-crate macros still fire so the file dispatch keeps the audit trail; fern's console branch is the single stderr source under NO_DNA. - handle_log_event Transient agent branch: drop the redundant cli_emit, fold the [start]/[ok]/[fail] tag into summary, override level from status so Failure surfaces as level="error" for agent filtering. Verified: 52/52 cli unit tests pass; agent_io ANSI/JSON tests unchanged.
|
@lgalabru @MicaiahReid does the implementation look good, or would you like to steer the direction? |
@rit3sh-x we'll need some patience on this review 🙏 |
Summary
Detect
NO_DNAper https://no-dna.org and adapt CLI output for AI agents andautomation. Full compliance with all 6 no-dna.org behavioral bullets.
Under
NO_DNAsurfpool:no_tui,skip_runbook_generation_prompts,unsupervised,skip_confirmforced)NO_COLORhonored independently)surfpool ls--ci(orthogonal)Test plan
cargo test -p surfpool-cli --bins— 50/50 passingcargo +nightly fmt --allcleancargo check -p surfpool-clicleanNO_DNA=1 surfpool start --offline→ stdout clean, JSON lines on stderrNO_DNA=1 surfpool ls→ JSON-per-runbook on stderrsurfpool start(no NO_DNA) → unchanged human behaviorNO_DNA=1 surfpool start --ci→ both flag sets applyCloses #569. Supersedes #593.