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

The i-have-adhd repository provides three distinct install scripts—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 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 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 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:


# POSIX sh

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

# PowerShell

if (-not (Test-Path $flagPath)) { exit 0 }
// 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 reveals each language's strengths:

POSIX sh uses awk for state-machine parsing:

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:

$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:

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

Summary

  • Three parallel implementations exist in 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 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).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →