envsense is a cross-language library and CLI for detecting the runtime environment and exposing it in a structured way. It helps tools and scripts adapt their behavior based on where they are running.
Most developers end up writing brittle ad-hoc checks in their shell configs or tools:
- "If I’m in VS Code, set
EDITOR=code -w" - "If stdout is piped, disable color and paging"
- "If running in a coding agent, simplify my prompt"
These heuristics get duplicated across dotfiles and codebases. envsense centralizes and standardizes this detection.
# Detect and show agent status
envsense check agent
envsense check --json agent
# Simple check for any coding agent
envsense check agent && echo "Running inside a coding agent"
# Check if specifically running in Cursor
envsense check facet:agent_id=cursor && echo "Cursor detected"
# Check if running in VS Code or VS Code Insiders
envsense check facet:ide_id=vscode && echo "VS Code"
envsense check facet:ide_id=vscode-insiders && echo "VS Code Insiders"
# Combine checks in scripts
if envsense check -q facet:ide_id=cursor; then
export PAGER=cat
fi
# Check multiple conditions (any must match)
envsense check --any agent ide && echo "Running in agent or IDE"
# Check multiple conditions (all must match)
envsense check --all agent trait:is_interactive && echo "Interactive agent session"
# Get detailed reasoning for checks
envsense check --explain facet:ide_id=cursor
# List all available predicates
envsense check --listYou can also inspect the full structured output to see what envsense detects:
# Human-friendly summary
envsense info
# Print detected environment as JSON
envsense info --json
# Restrict to specific keys
envsense info --fields contexts,traits
# Pipe-friendly text
envsense info --raw
# Disable color output
envsense info --no-colorExample JSON output:
{
"contexts": ["agent", "ide"],
"traits": {
"is_interactive": true,
"is_tty_stdin": true,
"is_tty_stdout": true,
"is_tty_stderr": true,
"is_piped_stdin": false,
"is_piped_stdout": false,
"color_level": "truecolor",
"supports_hyperlinks": true
},
"facets": {
"agent_id": "cursor",
"ide_id": "cursor"
},
"evidence": [],
"version": "0.2.0"
}The check command evaluates predicates against the environment and exits with
status 0 on success, 1 on failure.
--json- Output results as JSON (stable schema)-q, --quiet- Suppress output (useful in scripts)--explain- Show reasoning for each check result
--any- Succeed if any predicate matches (default: all must match)--all- Require all predicates to match (default behavior)
--list- List all available predicates
# Basic checks
envsense check agent # Exit 0 if agent detected, 1 otherwise
envsense check -q agent # Silent check (no output)
envsense check --json agent # JSON output with result
# Multiple predicates
envsense check agent ide # Both must match (AND logic)
envsense check --any agent ide # Either can match (OR logic)
envsense check --all agent ide # Both must match (explicit AND)
# Get reasoning
envsense check --explain agent # Shows why agent was/wasn't detected
envsense check --json --explain agent # JSON with reasoning included
# List available predicates
envsense check --list # Shows all contexts, facets, and traitsThe info command shows detailed environment information.
--json- Output as JSON (stable schema)--raw- Plain text without colors or headers (pipe-friendly)--no-color- Disable color output
--fields <list>- Comma-separated keys to include:contexts,traits,facets,meta
# Different output formats
envsense info # Human-friendly with colors
envsense info --json # JSON output
envsense info --raw # Plain text, no formatting
envsense info --no-color # Human-friendly, no colors
# Field filtering
envsense info --fields contexts,traits # Only contexts and traits
envsense info --json --fields facets # JSON with only facets
envsense info --raw --fields meta # Raw output with only metadata--no-color- Disable color output (works on all commands)
- 0 - Success (predicates matched as expected)
- 1 - Failure (predicates did not match)
- 2 - Error (invalid arguments, parsing errors)
envsense check agent && echo "Agent detected" # Only runs if agent=true
envsense check !agent && echo "Not in agent" # Only runs if agent=false
envsense check --any agent ide || echo "Neither" # Runs if neither matches- Contexts — broad categories of environment (
agent,ide,ci,container,remote). - Facets — more specific identifiers (
agent_id=cursor,ide_id=vscode). - Traits — capabilities or properties (
is_interactive,supports_hyperlinks,color_level). - Evidence — why envsense believes something (env vars, TTY checks, etc.), with confidence scores.
- “Running in VS Code” → Context/Facet (
ide,ide_id=vscode). - “Running in GitHub Actions” → Facet (
ci_id=github). - “Running in CI at all” → Context (
ci=true). - “Can print hyperlinks” → Trait (
supports_hyperlinks=true). - “stdout is not a TTY” → Trait (
is_interactive=false).
# Context: any CI
envsense check ci && echo "In CI"
# Facet: specifically GitHub Actions
envsense check facet:ci_id=github && echo "In GitHub Actions"
# Trait: non-interactive
if ! envsense check trait:is_interactive; then
echo "Running non-interactive"
fiPredicates follow a simple syntax pattern:
# Contexts (broad categories)
envsense check agent
envsense check ide
envsense check ci
envsense check container
envsense check remote
# Facets (specific identifiers)
envsense check facet:agent_id=cursor
envsense check facet:ide_id=vscode
envsense check facet:ci_id=github
envsense check facet:container_id=docker
# Traits (capabilities/properties)
envsense check trait:is_interactive
envsense check trait:supports_hyperlinks
envsense check trait:is_ci
# Negation (prefix with !)
envsense check !agent
envsense check !trait:is_interactive
envsense check !facet:ide_id=vscodeDetect if envsense is running in a CI environment and inspect details:
# Human-friendly summary
envsense info --fields=facets --no-color
# JSON output
envsense info --json --fields=traits,facets
# Simple check that exits 0 on CI, 1 otherwise
envsense check ciSample cargo run -- info --fields=facets --no-color output on GitHub Actions:
Facets:
ci_id = github_actions
And the corresponding JSON fragment:
{
"traits": {
"is_ci": true,
"ci_vendor": "github_actions",
"ci_name": "GitHub Actions",
"is_pr": true,
"branch": "main"
},
"facets": {
"ci_id": "github_actions"
}
}-
Rust (Primary):
use envsense::detect_environment; let env = detect_environment(); if env.contexts.agent { println!("Agent detected"); } if env.facets.ide_id.as_deref() == Some("cursor") { println!("Cursor detected"); } if !env.traits.is_interactive { println!("Non-interactive session"); }
-
Node.js (Planned):
import { detect } from "envsense"; const ctx = await detect(); if (ctx.contexts.agent) console.log("Agent detected"); if (ctx.facets.ide_id === "cursor") console.log("Cursor detected"); if (!ctx.traits.is_interactive) console.log("Non-interactive session");
- Explicit signals — documented env vars (e.g.
TERM_PROGRAM,INSIDE_EMACS,CI). - Execution channel — SSH env vars, container cgroups, devcontainer markers.
- Process ancestry — parent process names (optional, behind a flag).
- Heuristics — last resort (file paths, working dir markers).
Precedence is: user override > explicit > channel > ancestry > heuristics.
-
Interactivity
- Shell flags (interactive mode)
- TTY checks for stdin/stdout/stderr
- Pipe/redirect detection
-
Colors
- Honors
NO_COLOR,FORCE_COLOR - Detects depth: none, basic, 256, truecolor
- Honors
-
Hyperlinks (OSC 8)
- Known supporting terminals (iTerm2, kitty, WezTerm, VS Code, etc.)
- Optional probe for fallback
- Rust implementation complete - Core library and CLI fully functional
- Declarative detection system - Environment detection using declarative patterns
- Comprehensive CI support - GitHub Actions, GitLab CI, CircleCI, and more
- Extensible architecture - Easy to add new detection patterns
- Production ready - Used in real-world environments
- Implement baseline detectors (env vars, TTY)
- CLI output in JSON + pretty modes
- Rule engine for contexts/facets/traits
- Declarative detection system
- Comprehensive CI environment support
- Node binding via napi-rs
- Additional language bindings
- Advanced value extraction patterns
- Rust 1.70+
cargo-instafor snapshot testing:cargo install cargo-insta
# Run all tests
cargo test --all
# Run snapshot tests
cargo test --test info_snapshots
# Update snapshots after schema changes
cargo insta accept
# Test specific components
cargo test --package envsense
cargo test --package envsense-macrosWhen making breaking schema changes (like removing fields):
- Bump
SCHEMA_VERSIONinsrc/schema.rs - Update tests to expect new version
- Run
cargo insta acceptto update snapshots - Verify all tests pass
# Format code (enforced by pre-commit hooks)
cargo fmt --all
# Lint and fix issues
cargo clippy --all --fix -D warnings
# Run full test suite
cargo test --all
# Build release version
cargo build --releaseSee docs/testing.md for detailed testing guidelines.
MIT
envsense: environment awareness for any tool, in any language.