MCPcopy Index your code
hub / github.com/Shopify/shadowenv

github.com/Shopify/shadowenv @3.5.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release 3.5.1 ↗ · + Follow
185 symbols 414 edges 18 files 14 documented · 8%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Shadowenv

Shadowenv provides a way to perform a set of manipulations to the process environment upon entering a directory in a shell. These manipulations are reversed when leaving the directory, and there is some limited ability to make the manipulations dynamic.

shadowenv in action

In order to use shadowenv, add a line to your shell profile (.zshrc, .bash_profile, or config.fish) reading:

eval "$(shadowenv init bash)" # for bash
eval "$(shadowenv init zsh)"  # for zsh
shadowenv init fish | source  # for fish

For nushell, run shadowenv init nushell which will install a hook into nushell's autoload directory.

With this code loaded, upon entering a directory containing a .shadowenv.d directory, any *.lisp files in that directory will be executed and you will see "activated shadowenv." in your shell.

The syntax for the .shadowenv.d/*.lisp files is Shadowlisp, a minimal Scheme-like language. Unlike other tools like direnv, this has the interesting property of allowing us to do things like simulate chruby reset upon entry into a directory without the user having chruby installed (and undo these changes to the environment when cd'ing back out):

(provide "ruby")

(when-let ((ruby-root (env/get "RUBY_ROOT")))
  (env/remove-from-pathlist "PATH" (path-concat ruby-root "bin"))
  (when-let ((gem-root (env/get "GEM_ROOT")))
    (env/remove-from-pathlist "PATH" (path-concat gem-root "bin"))
    (env/remove-from-pathlist "GEM_PATH" gem-root))
  (when-let ((gem-home (env/get "GEM_HOME")))
    (env/remove-from-pathlist "PATH" (path-concat gem-home "bin"))
    (env/remove-from-pathlist "GEM_PATH" gem-home)))

The intention isn't really for users to write these files directly, nor to commit them to repositories , but for other tool authors to generate configuration on the user's machine. Here's an example of a generated Shadowlisp file for activating ruby 2.7.1:

(provide "ruby" "2.7.1")

(when-let ((ruby-root (env/get "RUBY_ROOT")))
 (env/remove-from-pathlist "PATH" (path-concat ruby-root "bin"))
 (when-let ((gem-root (env/get "GEM_ROOT")))
   (env/remove-from-pathlist "PATH" (path-concat gem-root "bin")))
 (when-let ((gem-home (env/get "GEM_HOME")))
   (env/remove-from-pathlist "PATH" (path-concat gem-home "bin"))))

(env/set "GEM_PATH" ())
(env/set "GEM_HOME" ())
(env/set "RUBYOPT" ())

(env/set "RUBY_ROOT" "/opt/rubies/2.7.1")
(env/prepend-to-pathlist "PATH" "/opt/rubies/2.7.1/bin")
(env/set "RUBY_ENGINE" "ruby")
(env/set "RUBY_VERSION" "2.7.1")
(env/set "GEM_ROOT" "/opt/rubies/2.7.1/lib/ruby/gems/2.7.0")

(when-let ((gem-root (env/get "GEM_ROOT")))
  (env/prepend-to-pathlist "GEM_PATH" gem-root)
  (env/prepend-to-pathlist "PATH" (path-concat gem-root "bin")))

(let ((gem-home
      (path-concat (env/get "HOME") ".gem" (env/get "RUBY_ENGINE") (env/get "RUBY_VERSION"))))
  (do
    (env/set "GEM_HOME" gem-home)
    (env/prepend-to-pathlist "GEM_PATH" gem-home)
    (env/prepend-to-pathlist "PATH" (path-concat gem-home "bin"))))

.shadowenv.d

The .shadowenv.d directory will generally exist at the root of your repository (in the same directory as .git.

We strongly recommend creating gitignore'ing everything under .shadowenv.d (echo '*' > .shadowenv.d/.gitignore).

A .shadowenv.d will contain any number of *.lisp files. These are evaluated in the order in which the OS returns when reading the directory: generally alphabetically. We strongly recommend using a prefix like 090_something.lisp to make it easy to maintain ordering.

.shadowenv.d will also contain a .trust-<fingerprint> file if it has been marked as trusted. (see the trust section).

Language

See https://shopify.github.io/shadowenv/ for Shadowlisp documentation.

Integrations

Shadowenv has plugins for multiple editors and/or IDEs:

Trust

If you cd into a directory containing .shadowenv.d/*.lisp files, they will not be run and you will see a message indicating so. This is for security reasons: we don't want to enable random stuff downloaded from the internet to modify your PATH, for example.

You can run shadowenv trust to mark a directory as trusted.

Technically, running shadowenv trust will create a file at .shadowenv.d/.trust-<fingerprint>, indicating that it's okay for shadowenv to run this code. The .shadowenv.d/.trust-* file contains a cryptographic signature of the directory path. The key is generated the first time shadowenv is run, and the fingerprint is an identifier for the key.

Extension points exported contracts — how you extend this code

Logger (Interface)
(no doc) [2 implementers]
src/diff.rs

Core symbols most depended-on inside this repo

is_empty
called by 22
src/hash.rs
get
called by 13
src/shadowenv.rs
exports
called by 10
src/shadowenv.rs
create_all
called by 9
src/loader.rs
set
called by 7
src/shadowenv.rs
get_value
called by 7
src/lang.rs
build_source
called by 7
src/lang.rs
run
called by 7
src/hook.rs

Shape

Function 106
Method 49
Class 25
Enum 4
Interface 1

Languages

Rust95%
TypeScript5%

Modules by API surface

src/shadowenv.rs32 symbols
src/hash.rs22 symbols
src/lang.rs19 symbols
src/loader.rs17 symbols
src/init.rs13 symbols
src/hook.rs13 symbols
src/output.rs12 symbols
src/diff.rs12 symbols
src/trust.rs10 symbols
src/cli.rs9 symbols
src/undo.rs7 symbols
docs/doks-theme/assets/js/scripts.min.js5 symbols

For agents

$ claude mcp add shadowenv \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact