diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 876b019..7615291 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ on: - "config.fish" - "completions/**" - "integrations/**" + - "templates/**" - "tests/**" - "scripts/**" pull_request: @@ -82,7 +83,7 @@ jobs: fi git fetch origin "${{ github.event.pull_request.base.ref }}" if git diff --name-only "origin/${{ github.event.pull_request.base.ref }}...HEAD" \ - | grep -qE '^(docs/manual/|docs/build-manual\.py$|docs/manualtools\.py$|docs/verify-manual\.py$|docs/site/|functions/|conf\.d/|config\.fish$|completions/|integrations/|tests/|scripts/)'; then + | grep -qE '^(docs/manual/|docs/build-manual\.py$|docs/manualtools\.py$|docs/verify-manual\.py$|docs/site/|functions/|conf\.d/|config\.fish$|completions/|integrations/|templates/|tests/|scripts/)'; then echo "run=true" >>"$GITHUB_OUTPUT" else echo "run=false" >>"$GITHUB_OUTPUT" diff --git a/README.md b/README.md index 54d67db..0726537 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,14 @@ To opt out, set `__fish_user_dots_symlink` to a falsy value (or toggle **Dots li `fish_variables` (which fish auto-manages and may contain universal variable state) is excluded from this repo via `.gitignore`. +### Function templates + +`templates/` holds starting points for your own functions. Each one has a short settings block at the top; edit it and save the file as `.fish` in a directory on `$fish_function_path`. + +| Template | Purpose | +|---|---| +| `allow-telemetry.fish` | Wrap one command and remove environment variables (default: `DO_NOT_TRACK`, `DISABLE_TELEMETRY`) from that command's process only | + --- ## Minimal Mode diff --git a/templates/allow-telemetry.fish b/templates/allow-telemetry.fish new file mode 100644 index 0000000..b6f546f --- /dev/null +++ b/templates/allow-telemetry.fish @@ -0,0 +1,60 @@ +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later + +# ╭──────────────────────────────────────────────────────────────────────╮ +# │ TEMPLATE: allow-telemetry wrapper │ +# ╰──────────────────────────────────────────────────────────────────────╯ +# +# Wraps one command and removes environment variables from that command's +# process only. Every other tool still sees them. +# +# Use it when a global opt-out (DO_NOT_TRACK, DISABLE_TELEMETRY) breaks a +# feature you want in one tool. Example: claude-code needs feature flags for +# Remote Control, and both variables turn them off. +# +# To use: +# 1. Set wrap_cmd and wrap_unset below. +# 2. Save the file as .fish in a directory on $fish_function_path, +# e.g. $__fish_user_dots_path/functions/ or $__fish_config_dir/functions/. +# Fish autoloads a function by its file name. +# 3. Update the documentation header below for the new command. + +# ─────────────────────────────── Settings ─────────────────────────────── + +# Command to wrap. Must match the file name (claude -> claude.fish). +set -l wrap_cmd claude + +# Variables removed from the wrapped command's environment. +set -l wrap_unset DO_NOT_TRACK DISABLE_TELEMETRY + +# ─────────────────────────────── Function ─────────────────────────────── + +# SYNOPSIS +# claude [ARGS...] +# +# DESCRIPTION +# Runs the claude binary without DO_NOT_TRACK and DISABLE_TELEMETRY in its +# environment. The variables stay set in the calling shell and in every +# other command. All arguments are forwarded verbatim. +# +# ARGUMENTS +# ARGS Any arguments forwarded verbatim to the underlying binary +# +# EXIT STATUS +# Exit status of the underlying binary; 127 if it is not installed +# +# EXAMPLE +# claude --remote-control +# +# NOTES +# The function name and settings are fixed when the file loads (-V +# snapshots them), so the file-scope variables above do not leak into the +# shell. `env` runs the external binary, never this function, so the +# wrapper cannot recurse. +function $wrap_cmd --wraps=$wrap_cmd -V wrap_cmd -V wrap_unset --description "$wrap_cmd with $wrap_unset removed from its environment" + set -l env_args + for var in $wrap_unset + set -a env_args -u $var + end + env $env_args $wrap_cmd $argv +end diff --git a/tests/run-tests.fish b/tests/run-tests.fish index 4d48938..9a5c4a3 100755 --- a/tests/run-tests.fish +++ b/tests/run-tests.fish @@ -27,7 +27,7 @@ set -l overall_failed 0 # ---- Phase 1: syntax & indent lint --------------------------------------- echo "== Syntax & indent lint ==" set -l lint_files $repo_root/config.fish -for dir in functions conf.d completions integrations tests +for dir in functions conf.d completions integrations templates tests set -a lint_files (find $repo_root/$dir -name '*.fish' | sort) end diff --git a/tests/test-templates.fish b/tests/test-templates.fish new file mode 100644 index 0000000..b743e95 --- /dev/null +++ b/tests/test-templates.fish @@ -0,0 +1,39 @@ +#!/usr/bin/env fish +# Copyright (C) 2026 Rootiest +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Coverage for templates/*.fish: each template is instantiated the way a +# user would (edit the settings, save as .fish on the function path) +# and exercised against a stub binary in a --no-config fish. + +source (realpath (dirname (status filename)))/lib.fish + +# Instantiate allow-telemetry.fish for a stub command named tpl_probe. The +# stub prints what it received and exits 7 so status passthrough is visible. +# $unset replaces the wrap_unset value verbatim (may be empty). +function _allow_telemetry_probe --argument-names unset + set -l tmp (mktemp -d) + mkdir $tmp/bin $tmp/functions + printf '%s\n' '#!/bin/sh' \ + 'echo "DNT=[$DO_NOT_TRACK] DT=[$DISABLE_TELEMETRY] KEEP=[$KEEP] args=$*"' \ + 'exit 7' >$tmp/bin/tpl_probe + chmod +x $tmp/bin/tpl_probe + string replace -r '^set -l wrap_cmd .*' 'set -l wrap_cmd tpl_probe' <$repo_root/templates/allow-telemetry.fish \ + | string replace -r '^set -l wrap_unset .*' "set -l wrap_unset $unset" >$tmp/functions/tpl_probe.fish + env PATH=(string join : $tmp/bin $PATH) DO_NOT_TRACK=1 DISABLE_TELEMETRY=1 KEEP=1 fish --no-config -c \ + "set -g fish_function_path $tmp/functions \$fish_function_path + tpl_probe a 'b c'; echo status=\$status + echo parent=\$DO_NOT_TRACK leak=(set -q wrap_cmd; and echo yes; or echo no)" + rm -rf $tmp +end + +section "templates: allow-telemetry" +set -l out (_allow_telemetry_probe "DO_NOT_TRACK DISABLE_TELEMETRY") +check "listed vars removed, others kept, args forwarded" "DNT=[] DT=[] KEEP=[1] args=a b c" "$out[1]" +check "exit status passes through" status=7 "$out[2]" +check "parent keeps vars, settings do not leak" "parent=1 leak=no" "$out[3]" + +set -l out (_allow_telemetry_probe "") +check "empty unset list passes environment through" "DNT=[1] DT=[1] KEEP=[1] args=a b c" "$out[1]" + +report