dejavu/env.rs
1//! `DEJAVU_*` environment variable names and the ambient session view.
2
3use std::path::PathBuf;
4
5pub const ACTIVE: &str = "DEJAVU_ACTIVE";
6pub const BIN: &str = "DEJAVU_BIN";
7pub const REPO_ROOT: &str = "DEJAVU_REPO_ROOT";
8pub const CACHE_DIR: &str = "DEJAVU_CACHE_DIR";
9pub const SESSION_ID: &str = "DEJAVU_SESSION_ID";
10pub const SHIM_DIR: &str = "DEJAVU_SHIM_DIR";
11pub const MODE: &str = "DEJAVU";
12pub const DISABLED: &str = "DEJAVU_DISABLED";
13/// Force reduction even outside a session / agent context (`DEJAVU_FORCE=1`).
14pub const FORCE: &str = "DEJAVU_FORCE";
15/// The user's original ZDOTDIR (or $HOME), sourced by the wrapper zdot files.
16pub const ORIG_ZDOTDIR: &str = "DEJAVU_ORIG_ZDOTDIR";
17
18/// Agents that capture command output through a **pipe** rather than a pty:
19/// Claude Code, the Codex CLI, Cursor's agent. Each of these vars is set only
20/// in the shell the agent drives for its command tool, and the reader on the
21/// other end of the pipe IS the agent — so reduction does not require stdout to
22/// be a terminal (with a pipe reader, it never will be).
23pub const PIPE_AGENT_MARKERS: &[&str] = &["CLAUDECODE", "CODEX_SANDBOX", "CURSOR_AGENT"];
24
25/// Agents that run commands in a real **pty** (VS Code Copilot). `AI_AGENT` is
26/// the cross-vendor convention; VS Code sets `AI_AGENT` + `COPILOT_AGENT` in
27/// agent-tool terminals only, never in user terminals. For these markers
28/// reduction additionally requires stdout to be a terminal — the pty is the
29/// tell that an agent, not a pipe-reading parser (`$(git …)`, IDE SCM), is on
30/// the far end.
31pub const PTY_AGENT_MARKERS: &[&str] = &["AI_AGENT", "COPILOT_AGENT"];
32
33/// True when the user asks Dejavu to force passthrough everywhere.
34pub fn is_disabled() -> bool {
35 let legacy_flag = std::env::var_os(DISABLED).is_some_and(|v| v == "1");
36 let mode_off = std::env::var_os(MODE).is_some_and(|v| {
37 matches!(
38 v.to_string_lossy().to_ascii_lowercase().as_str(),
39 "off" | "0" | "false" | "disabled"
40 )
41 });
42 legacy_flag || mode_off
43}
44
45/// True when running inside a `dejavu start` session.
46pub fn is_active() -> bool {
47 std::env::var_os(ACTIVE).is_some_and(|v| v == "1")
48}
49
50/// True when `DEJAVU_FORCE=1` overrides the global-mode reduction gates.
51pub fn is_forced() -> bool {
52 std::env::var_os(FORCE).is_some_and(|v| v == "1")
53}
54
55fn any_marker_set(markers: &[&str]) -> bool {
56 markers
57 .iter()
58 .any(|m| std::env::var_os(m).is_some_and(|v| !v.is_empty()))
59}
60
61/// True when a pipe-capturing agent (Claude Code, Codex CLI, Cursor) is driving
62/// this shell.
63pub fn pipe_agent_marker_present() -> bool {
64 any_marker_set(PIPE_AGENT_MARKERS)
65}
66
67/// True when a pty-based agent (VS Code Copilot) is driving this shell.
68pub fn pty_agent_marker_present() -> bool {
69 any_marker_set(PTY_AGENT_MARKERS)
70}
71
72/// Whether output reduction is allowed for this invocation.
73///
74/// - Inside a `dejavu start` session: always (the agent captures via pipes).
75/// - `DEJAVU_FORCE=1`: always (explicit human election).
76/// - Global activation (shims on PATH, no session):
77/// - a **pipe-capturing** agent marker (Claude Code, Codex CLI, Cursor) →
78/// always. The agent itself reads the pipe, so stdout is never a terminal;
79/// the marker is set only in the shell the agent drives. This matches how
80/// `dejavu start` already behaves for the same agents.
81/// - a **pty-based** agent marker (Copilot) → only when stdout is a terminal.
82/// Copilot runs commands in a real pty, while parsers (`$(git …)`,
83/// pipelines, the IDE SCM) read through pipes — so the tty gate keeps a
84/// reduced envelope from ever landing in front of a parser.
85pub fn reduction_allowed(stdout_is_tty: bool) -> bool {
86 is_active()
87 || is_forced()
88 || pipe_agent_marker_present()
89 || (pty_agent_marker_present() && stdout_is_tty)
90}
91
92/// The ambient session, reconstructed from `DEJAVU_*` env vars. `None` when a
93/// shim is somehow invoked outside a session.
94#[derive(Debug, Clone)]
95pub struct AgentEnv {
96 pub bin: PathBuf,
97 pub repo_root: PathBuf,
98 pub cache_dir: PathBuf,
99 pub session_id: String,
100 pub shim_dir: PathBuf,
101}
102
103impl AgentEnv {
104 pub fn from_current() -> Option<AgentEnv> {
105 Some(AgentEnv {
106 bin: std::env::var_os(BIN)?.into(),
107 repo_root: std::env::var_os(REPO_ROOT)?.into(),
108 cache_dir: std::env::var_os(CACHE_DIR)?.into(),
109 session_id: std::env::var_os(SESSION_ID)?.to_string_lossy().into_owned(),
110 shim_dir: std::env::var_os(SHIM_DIR)?.into(),
111 })
112 }
113}