# How Platform-Specific Install Scripts Vary Between Shell Environments in i-have-adhd

> Explore how i-have-adhd install scripts differ across shell environments. Understand POSIX, PowerShell, and Node.js script variations for seamless cross-platform rule injection.

- Repository: [Ayoub Ghriss/i-have-adhd](https://github.com/ayghri/i-have-adhd)
- Tags: how-to-guide
- Published: 2026-08-29

---

**The i-have-adhd repository provides three distinct install scripts—[`always-on.sh`](https://github.com/ayghri/i-have-adhd/blob/main/always-on.sh) for POSIX shells, `always-on.ps1` for PowerShell, and `always-on.mjs` for Node.js—each architected to leverage native path resolution, file handling, and text processing capabilities while ensuring identical ADHD-friendly rule injection across Linux, macOS, and Windows.**

When adopting the always-on hook system from the `ayghri/i-have-adhd` repository, users encounter **platform-specific install scripts** tailored to three distinct runtime environments. Rather than forcing a universal solution, the project ships separate implementations that respect each shell’s idioms for directory resolution, file existence checks, and YAML front-matter stripping. This polyglot approach ensures the ADHD mode activates reliably whether Claude Code executes within Git Bash, Windows Terminal, or a pure Node.js context.

## The Three Shell Implementations

The repository maintains parallel hook scripts in the `hooks/` directory, each targeting a specific execution context:

### POSIX sh for Unix-like Systems

The **[`hooks/always-on.sh`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/always-on.sh)** script targets Linux, macOS, and Git Bash on Windows using strictly POSIX-compliant shell built-ins. It avoids bash-specific extensions to maximize compatibility across minimal container environments and legacy Unix systems.

Path resolution relies on `${CLAUDE_CONFIG_DIR:-$HOME/.claude}` to locate the activation flag, while the script directory derives from `dirname -- "$0"`. For YAML front-matter removal—necessary because [`SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/SKILL.md) contains metadata delimiters before the actual rules—the script employs a two-pass `awk` pipeline that tracks state between opening and closing `---` markers.

### PowerShell for Native Windows

Windows environments execute **`hooks/always-on.ps1`**, which leverages .NET's `System.IO` abstractions through PowerShell cmdlets. Rather than parsing `$HOME` manually, the script uses `[Environment]::GetFolderPath("UserProfile")` or `$env:CLAUDE_CONFIG_DIR` for directory resolution, constructing paths via `Join-Path` to handle backslash separators correctly.

The front-matter stripping logic reads the file into an array with `Get-Content`, checks `$lines[0]` for the opening delimiter, then iterates to find the closing `---` before assembling the body text. This approach aligns with PowerShell's object-stream philosophy rather than treating the file as a raw byte stream.

### Node.js ESM for Cross-Platform Consistency

The **`hooks/always-on.mjs`** script serves as the default for Claude Code and Codex hooks, running under Node.js regardless of the underlying operating system. It imports core modules (`fs`, `os`, `path`, `url`) and resolves the script's location using `fileURLToPath(import.meta.url)`, eliminating ambiguity about relative path resolution.

Unlike the iterative approaches in sh and PowerShell, the Node implementation strips YAML front-matter using a single regular expression: `replace(/^---[\s\S]*?---\s*/, "")`. This regex matches the opening delimiter, consumes all characters including newlines until the closing delimiter, and removes trailing whitespace in one operation.

## Core Architectural Differences

### Path Resolution Strategies

Each script respects its environment's canonical method for discovering user directories:

- **POSIX sh**: Constructs `claude_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"` using shell parameter expansion, manually building the flag path with string interpolation.
- **PowerShell**: Uses `$env:CLAUDE_CONFIG_DIR` with fallback to .NET's `[Environment]::GetFolderPath("UserProfile")`, ensuring correct handling of Windows roaming profiles.
- **Node**: Calls `process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude")`, leveraging Node's cross-platform `os.homedir()` which abstracts Windows `%USERPROFILE%` and Unix `$HOME` differences.

All three implementations locate [`SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/SKILL.md) relative to the script's own directory using runtime-specific variables: `$0` in sh, `$MyInvocation.MyCommand.Path` in PowerShell, and `import.meta.url` in Node. This self-referential approach prevents tampering through external environment manipulation.

### Error Handling and Safety Guarantees

The scripts share a critical safety contract: **non-blocking execution**. If the flag file `.i-have-adhd-always` is absent or any operation fails, each script returns exit status 0 immediately:

```sh

# POSIX sh

[ -f "$flag_path" ] || exit 0

```

```powershell

# PowerShell

if (-not (Test-Path $flagPath)) { exit 0 }

```

```javascript
// Node.js
if (!fs.existsSync(flagPath)) process.exit(0);

```

This guarantees that hook failures never hang the host IDE or terminal session, aligning with the principle that editor extensions should fail silently rather than block user workflow.

### Front-Matter Stripping Techniques

The method for removing YAML front-matter from [`skills/i-have-adhd/SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md) reveals each language's strengths:

**POSIX sh** uses `awk` for state-machine parsing:

```sh
body=$(awk '
  NR==1 && /^---/ {in_fm=1; next}
  in_fm && /^---/ {in_fm=0; closed=1; next}
  !in_fm {print}
' "$skill_path")

```

**PowerShell** implements an explicit loop:

```powershell
$lines = Get-Content $skillPath
$bodyStart = 0
if ($lines[0] -match '^---') {
  for ($i=1; $i -lt $lines.Length; $i++) {
    if ($lines[$i] -match '^---') { $bodyStart = $i + 1; break }
  }
}
$body = $lines[$bodyStart..($lines.Length-1)] -join "`n"

```

**Node.js** applies regex substitution:

```javascript
const body = fs.readFileSync(skillPath, "utf8")
               .replace(/^---[\s\S]*?---\s*/, "")
               .trimEnd();

```

## Summary

- **Three parallel implementations** exist in [`hooks/always-on.sh`](https://github.com/ayghri/i-have-adhd/blob/main/hooks/always-on.sh), `hooks/always-on.ps1`, and `hooks/always-on.mjs` to support POSIX shells, PowerShell, and Node.js respectively.
- **Path resolution** uses environment-specific APIs: shell parameter expansion in sh, .NET's `Environment` class in PowerShell, and Node's `os.homedir()` in the ESM script.
- **Front-matter stripping** adapts to language capabilities—stream processing with `awk`, array manipulation in PowerShell, and regular expressions in JavaScript—while producing identical output.
- **Non-blocking safety** is enforced through immediate `exit 0` or `process.exit(0)` calls if activation flags or skill files are missing.
- **Self-referential paths** prevent external manipulation by deriving [`SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/SKILL.md) locations from each script's own execution context rather than environment variables.

## Frequently Asked Questions

### Why doesn't the repository use a single cross-platform script?

A universal script would require external dependencies like Node.js or Python on every platform, violating the project's goal of "zero-dependency" activation for POSIX and PowerShell environments. By providing native implementations, the hooks execute immediately without requiring users to install additional runtimes beyond what their shell already provides.

### How does the Node.js version handle Windows paths differently than the POSIX version?

The Node.js version uses `path.join()` and `fileURLToPath(import.meta.url)`, which automatically convert between forward slashes and backslashes based on the operating system. In contrast, the POSIX version assumes forward slashes and relies on Git Bash or WSL to handle path translation on Windows, while the PowerShell version explicitly uses `Join-Path` to ensure Windows-native path separators.

### What happens if the SKILL.md file contains malformed YAML front-matter?

All three scripts implement defensive parsing. The POSIX `awk` script only enters front-matter stripping mode if line 1 matches `^---` and exits that mode only upon finding a subsequent `^---`. PowerShell similarly checks the first line before searching for the closing delimiter. The Node.js regex is non-greedy (`[\s\S]*?`), preventing it from consuming the entire file if the closing delimiter is missing. In all cases, malformed front-matter results in either partial stripping or no modification, never script failure.

### Can users customize the activation flag location across all three scripts?

Yes. All implementations check for the `CLAUDE_CONFIG_DIR` environment variable before falling back to the default `.claude` directory in the user's home folder. This variable must be exported before the hook executes to override the path consistently across POSIX sh (`$CLAUDE_CONFIG_DIR`), PowerShell (`$env:CLAUDE_CONFIG_DIR`), and Node.js (`process.env.CLAUDE_CONFIG_DIR`).