Skip to main content

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}