# tirith nushell hook # LIMITATION: Cannot block execution — warn-only mode # Add to config.nu: source ~/.local/share/tirith/shell/lib/nushell-hook.nu # Interactive-only: the hook intercepts commands typed at a prompt, so it must # be a complete no-op in a non-interactive nushell (`nu -c …`, `nu script.nu`). # Nushell does not load `config.nu` for those by default, but guarding on # `$nu.is-interactive` makes the no-op explicit and survives a config sourced # another way. # # TIRITH_STATUS — note on nushell: the other tirith hooks expose a small # non-exported `TIRITH_STATUS` shell variable for an opt-in prompt segment. # Nushell has no session-persistent variable that is both readable by a later # prompt closure and *not* inherited by child processes — anything in `$env` # is exported to externals, and a non-interactive child has no tirith # protection, so an inherited status would misrepresent it. The nushell hook # therefore deliberately does NOT set a `TIRITH_STATUS` variable; # `docs/prompt-status.md` accordingly has no nushell snippet. nushell is # warn-only regardless (its `pre_execution` hook cannot abort a command). if (($nu.is-interactive) and (not ('_TIRITH_NU_LOADED' in $env))) { $env._TIRITH_NU_LOADED = true # M8 ch2 — surface "this shell is on the remote side of an SSH session" # to `tirith prompt-status` (planned for M8 ch6) and any other # downstream consumer. Set NOW so chunk 6 can read it without a # follow-up hook patch. Nushell's `$env` is auto-exported to externals, # which is exactly what we want here: a child `tirith` invocation # should see TIRITH_SSH_REMOTE. if (not ('TIRITH_SSH_REMOTE' in $env)) and ( ('SSH_CONNECTION' in $env) or ('SSH_CLIENT' in $env) or ('SSH_TTY' in $env) ) { $env.TIRITH_SSH_REMOTE = "1" } # M9 ch4 — record a shell-start environment snapshot for `tirith env diff`. # Exec a hidden tirith subcommand that reads ITS OWN inherited environment # (nushell's `$env` is auto-exported to externals) and writes ONLY variable # names + an 8-char value-hash prefix (never raw values, never a recoverable # hash) to /env_snapshot.json. No value crosses an argv boundary # or a temp file. The write is a sub-millisecond single env read; it runs # once per session (guarded above). Errors are swallowed so a missing binary # never disrupts the shell. try { ^tirith env snapshot | complete | ignore } # Defensively initialize pre_execution if absent/null (fresh configs may lack it) let existing = ($env.config.hooks.pre_execution? | default []) $env.config.hooks.pre_execution = ($existing | append {|| # Reentrancy guard (safety — pre_execution shouldn't recurse, # but guard prevents issues if Nushell behavior changes) if ('_TIRITH_NU_RUNNING' in $env) and $env._TIRITH_NU_RUNNING { return } let cmd = (commandline) if ($cmd | is-empty) { return } # Skip tirith's own commands (engine also guards via is_self_invocation, # but this avoids spawning a subprocess entirely) let first_word = ($cmd | split row ' ' | first) if ($first_word == "tirith") or ($first_word | str ends-with "/tirith") { return } $env._TIRITH_NU_RUNNING = true try { let result = (with-env {_TIRITH_HOOK: "1"} { do { ^tirith check --non-interactive --interactive --shell posix -- $cmd } | complete }) $env._TIRITH_NU_RUNNING = false if $result.exit_code != 0 { print -e $"(ansi red_bold)tirith warning:(ansi reset) security concerns detected" if ($result.stderr | is-not-empty) { print -e $result.stderr } print -e $"(ansi yellow_dimmed)Nushell cannot block execution. Review the command above.(ansi reset)" } } catch { $env._TIRITH_NU_RUNNING = false } } ) } # ── tirith output wrap (M7 ch1) ───────────────────────────────────────────── # Opt-in output-direction wrapper. Commented out by default in this embedded # hook copy; `tirith output wrap on` writes an active copy of the function # into the user's shell-profile separately. This block is kept here as the # canonical source so a user reading the hook understands the surface area. # # Scope honesty: this wraps INDIVIDUAL commands invoked via `tirith-out # `. It does NOT intercept output from anything run outside the wrapper. # # def tirith-output-guard-wrap [...cmd] { # if ($cmd | length) == 0 { # print --stderr 'tirith-output-guard-wrap: usage: tirith-out [args...]' # return 2 # } # run-external $cmd.0 ...($cmd | skip 1) | tirith view --max-bytes 16777216 - # } # alias tirith-out = tirith-output-guard-wrap