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.
The easiest way to install envsense
is via aqua
through mise:
# Install via mise (which uses aqua)
mise install aqua:technicalpickles/envsense
# Or install globally
mise use -g aqua:technicalpickles/envsense
This method automatically:
- Downloads the correct binary for your platform
- Verifies cryptographic signatures via cosign
- Handles installation and PATH management
Download the latest release for your platform from GitHub Releases:
# Get the latest version dynamically
LATEST_VERSION=$(curl -s https://api.github.com/repos/technicalpickles/envsense/releases/latest | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
# Linux x64
curl -L "https://github.com/technicalpickles/envsense/releases/latest/download/envsense-${LATEST_VERSION}-x86_64-unknown-linux-gnu" -o envsense
chmod +x envsense
# macOS Universal (Intel + Apple Silicon)
curl -L "https://github.com/technicalpickles/envsense/releases/latest/download/envsense-${LATEST_VERSION}-universal-apple-darwin" -o envsense
chmod +x envsense
Alternative: One-liner install scripts
For convenience, here are one-liner commands that handle version detection automatically:
# Linux x64
curl -s https://api.github.com/repos/technicalpickles/envsense/releases/latest \
| grep "browser_download_url.*x86_64-unknown-linux-gnu\"" \
| grep -v "\.sig\|\.sha256\|\.bundle" \
| cut -d '"' -f 4 \
| xargs curl -L -o envsense && chmod +x envsense
# macOS (Universal - works on both Intel and Apple Silicon)
curl -s https://api.github.com/repos/technicalpickles/envsense/releases/latest \
| grep "browser_download_url.*universal-apple-darwin\"" \
| grep -v "\.sig\|\.sha256\|\.bundle\|v0\." \
| cut -d '"' -f 4 \
| xargs curl -L -o envsense && chmod +x envsense
Note: Currently, Windows builds are not available. For Windows users, consider using WSL and following the Linux installation instructions.
# Install from source (requires Rust)
cargo install --git https://github.com/technicalpickles/envsense
# Detect and show if running from an agent
envsense check agent # => true
# Detect and show agent id, if present
envsense check agent.id # => cursor
# Simple check for any coding agent
envsense -q check agent && echo "Running inside a coding agent"
# Check if specifically running in Cursor
envsense -q check agent.id=cursor && echo "Cursor detected"
# Check if running in VS Code or VS Code Insiders
envsense -q check ide.id=vscode && echo "VS Code"
envsense -q check ide.id=vscode-insiders && echo "VS Code Insiders"
# Check if running in GitHub Actions
envsense -q check ci.id=github && echo "GitHub Actions"
# Check if terminal is interactive
envsense -q check terminal.interactive && echo "Interactive terminal"
# 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 terminal.interactive && echo "Interactive agent session"
# Get detailed reasoning for checks
envsense check --explain ide.id=cursor
# List all available predicates
envsense check --list
You 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
# Pipe-friendly text
envsense info --raw
# Disable color output
envsense info --no-color
Example JSON output:
{
"contexts": ["agent", "ide"],
"traits": {
"agent": {
"id": "claude-code"
},
"ide": {
"id": "cursor"
},
"terminal": {
"interactive": true,
"color_level": "truecolor",
"stdin": {
"tty": true,
"piped": false
},
"stdout": {
"tty": true,
"piped": false
},
"stderr": {
"tty": true,
"piped": false
},
"supports_hyperlinks": true
}
},
"evidence": [],
"version": "0.3.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--descriptions
- Show context descriptions in list mode (requires--list
)
--lenient
- Use lenient mode (don't error on invalid fields)
# 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 traits
envsense check --list --descriptions # Shows contexts with descriptions
# Lenient mode (for experimental usage)
envsense check --lenient unknown.field # Won't error on invalid field paths
The 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
--tree
- Use tree structure for nested display (hierarchical is default)--compact
- Compact output without extra formatting
# 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
# Display options
envsense info --tree # Tree structure display
envsense info --compact # Compact formatting
envsense info --tree --compact # Tree structure with compact formatting
--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
envsense supports optional configuration via a TOML file located at
~/.config/envsense/config.toml
(or equivalent on your platform).
[error_handling]
strict_mode = true # Enable strict validation (default: true)
show_usage_on_error = true # Show usage help on errors (default: true)
[output_formatting]
context_descriptions = true # Show descriptions in --list (default: true)
nested_display = true # Use hierarchical output (default: true)
rainbow_colors = true # Enable rainbow colors for special values (default: true)
[validation]
validate_predicates = true # Validate predicate syntax (default: true)
allowed_characters = "a-zA-Z0-9_.=-" # Valid characters in predicates
- Configuration is loaded automatically from the standard config directory
- If no config file exists, sensible defaults are used
- Partial configuration files are supported (missing sections use defaults)
- Configuration errors are silently ignored, falling back to defaults
# Create the config directory
mkdir -p ~/.config/envsense
# Create a basic configuration file
cat > ~/.config/envsense/config.toml << EOF
[error_handling]
strict_mode = true
[output_formatting]
rainbow_colors = false
[validation]
validate_predicates = true
EOF
- Contexts — broad categories of environment (
agent
,ide
,ci
). - Traits — identifiers, capabilities or properties (
terminal.interactive
,terminal.supports_hyperlinks
,terminal.color_level
). - Evidence — why envsense believes something (env vars, TTY checks, etc.), with confidence scores.
- "Running in VS Code" → Context/Trait (
ide
,ide.id=vscode
). - "Running in GitHub Actions" → Trait (
ci.id=github
). - "Running in CI at all" → Context (
ci
). - "Can print hyperlinks" → Trait (
terminal.supports_hyperlinks=true
). - "stdout is not a TTY" → Trait (
terminal.interactive=false
).
# Context: any CI
envsense check ci && echo "In CI"
# Trait: specifically GitHub Actions
envsense check ci.id=github && echo "In GitHub Actions"
# Trait: non-interactive
if ! envsense check terminal.interactive; then
echo "Running non-interactive"
fi
Predicates follow a simple syntax pattern:
# Contexts (broad categories)
envsense check agent
envsense check ide
envsense check ci
# Traits (specific identifiers and capabilities)
envsense check agent.id=cursor
envsense check ide.id=vscode
envsense check ci.id=github
envsense check terminal.interactive
envsense check terminal.supports_colors
# Negation (prefix with !)
envsense check !agent
envsense check !terminal.interactive
envsense check !ide.id=vscode
-
Rust (Primary):
use envsense::detect_environment; let env = detect_environment(); if env.contexts.contains("agent") { println!("Agent detected"); } if env.traits.agent.id.as_deref() == Some("cursor") { println!("Cursor detected"); } if !env.traits.terminal.interactive { println!("Non-interactive session"); }
-
Node.js (Planned):
import { detect } from "envsense"; const ctx = await detect(); if (ctx.contexts.includes("agent")) console.log("Agent detected"); if (ctx.traits.agent.id === "cursor") console.log("Cursor detected"); if (!ctx.traits.terminal.interactive) console.log("Non-interactive session");
- Explicit signals — documented env vars (e.g.
TERM_PROGRAM
,INSIDE_EMACS
,CI
). - 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
If you're upgrading from envsense v0.2.0, the syntax has been simplified:
Old Syntax | New Syntax | Notes |
---|---|---|
facet:agent_id=cursor |
agent.id=cursor |
Direct mapping |
facet:ide_id=vscode |
ide.id=vscode |
IDE context |
facet:ci_id=github |
ci.id=github |
CI context |
trait:is_interactive |
terminal.interactive |
Boolean field |
trait:supports_hyperlinks |
terminal.supports_hyperlinks |
Terminal capability |
For a complete migration guide, see docs/migration-guide.md.
- 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
- 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
If you encounter issues installing via aqua/mise, try these solutions:
Permission denied or signature verification failed:
# Ensure aqua policies allow the installation
mise exec aqua -- aqua policy allow 'technicalpickles/envsense'
Binary not found after installation:
# Check if binary is in PATH
mise which envsense
# Reload your shell
exec $SHELL
# Or explicitly activate mise in your shell profile
eval "$(mise activate)"
Version-specific installation:
# Install a specific version
mise install aqua:technicalpickles/[email protected]
# List available versions
mise ls-remote aqua:technicalpickles/envsense
Cosign signature verification issues: If you're in an environment that doesn't support cosign verification:
- Consider using the direct binary download method instead
- Check if your environment has the necessary cosign dependencies
- Consult the aqua documentation for signature verification troubleshooting
GitHub API rate limiting: If you see "GitHub rate limit exceeded" errors
with mise install aqua:technicalpickles/envsense
:
# Wait for rate limit to reset (shown in the error message)
# Or authenticate with GitHub to get higher rate limits
gh auth login
# Then retry the installation
mise install aqua:technicalpickles/envsense
Binary works but shows unexpected results:
# Get detailed detection information
envsense info --json | jq '.'
# Enable debug logging to see detection process
ENVSENSE_LOG=debug envsense info
False positives/negatives in environment detection:
- Check your environment variables:
printenv | grep -E "(TERM|EDITOR|CI|IDE)"
- Review process tree:
ps auxf
orpstree
- Consider using explicit overrides via environment variables
For more help, please open an issue with:
- Your operating system and version
- Installation method used
- Full error messages
- Output of
envsense info --json
MIT