How ponytail-mode-tracker.js Handles Command Detection and Mode Switching

The ponytail-mode-tracker.js hook parses JSON input from stdin, detects commands using the regex /^[/@$]ponytail/, and orchestrates mode transitions via discrete state management functions while emitting formatted output to the active AI runtime.

The hooks/ponytail-mode-tracker.js file serves as the core UserPromptSubmit handler in the DietrichGebert/ponytail repository, bridging user input with the framework's multi-mode architecture. This lightweight JavaScript module determines whether a prompt contains valid Ponytail directives, manages the lifecycle of active modes, and ensures consistent behavior across Claude Code, Codex, and Qoder environments.

Input Parsing and Payload Extraction

The hook begins by consuming raw JSON from stdin, applying defensive preprocessing to ensure cross-platform reliability.

BOM stripping occurs first via input.replace(/^\uFEFF/, ''), eliminating UTF-8 byte-order marks that some shells prepend to piped input. After sanitization, the script parses the JSON payload and extracts the prompt field, converting it to lowercase for case-insensitive command matching.

// Standard input processing pipeline
const input = fs.readFileSync(0, 'utf8').replace(/^\uFEFF/, '');
const payload = JSON.parse(input);
const prompt = (payload.prompt || '').toLowerCase();

On Windows environments, the implementation includes a timed fallback using setTimeout(...).unref() to prevent process hangs when stdin never emits an end event.

Command Detection and Validation

The module employs a two-tier detection strategy to distinguish Ponytail commands from ordinary prompts.

Primary command detection uses the regular expression /^[/@$]ponytail/ to identify strings beginning with /ponytail, @ponytail, or $ponytail. Upon match, the hook splits the prompt into constituent parts: the trigger symbol, the sub-command (arg), and optional additional parameters.

If the primary pattern fails, the system falls back to the deactivation detection layer, invoking isDeactivationCommand from ponytail-config.js. This utility matches natural language phrases like "stop ponytail" or "normal mode" against the full prompt text, ensuring whole-message matching to prevent accidental toggling when these phrases appear embedded within conversational text.

// Command parsing logic (simplified representation)
const ponytailRegex = /^[/@$]ponytail/;
if (ponytailRegex.test(prompt)) {
    const parts = prompt.split(/\s+/);
    const cmd = parts[0];  // e.g., "/ponytail"
    const arg = parts[1];  // e.g., "full", "lite", "off"
}

Mode Switching and State Management

Once validated, commands trigger specific state transitions through the ponytail-runtime.js abstraction layer, which manages the .ponytail-active flag file and configuration persistence.

Session-Scoped Mode Changes

Direct mode switches (lite, full, ultra, off, review) invoke setMode(mode), setting modeSwitched = true and writing a confirmation via writeHookOutput. These changes affect only the current session, stored temporarily in the runtime state.

Persisted Default Configuration

The /ponytail default <mode> syntax triggers writeDefaultMode(dmode), persisting the selection to $XDG_CONFIG_HOME/ponytail/config.json (or the OS-specific fallback). This establishes the baseline mode for future sessions.

Review and Deactivation Paths

The review sub-command activates a special session-only mode that never persists to disk, while deactivation commands invoke clearMode() to remove the active flag entirely.

// Example: Persisting a default mode
if (arg === 'default' && dmode) {
    writeDefaultMode(dmode);
    writeHookOutput(`Default mode set to ${dmode}`);
}

Runtime Integration and Qoder Specialization

For standard runtimes (Claude Code, Codex), the hook outputs simple status messages like PONYTAIL MODE CHANGED — level: full.

Qoder environments receive enhanced handling: when isQoder evaluates true, the hook injects the complete instruction set generated by getPonytailInstructions from ponytail-instructions.js. This prepends the full Ponytail rule set to every prompt, folding mode-change confirmations into the ruleset header rather than emitting separate status lines.

Error Handling and Edge Cases

The implementation includes several defensive mechanisms:

  • Invalid command fallback: Unrecognized arguments default to getDefaultMode() from ponytail-config.js
  • Report-only queries: A bare /ponytail command (no arguments) triggers readMode() to display the current active level without mutation
  • Stateless architecture: All persistent state resides in external files (.ponytail-active and config.json), making the hook resilient to process restarts

Summary

  • ponytail-mode-tracker.js serves as the central command interpreter, reading JSON from stdin and detecting directives via /^[/@$]ponytail/
  • Command parsing separates triggers from sub-commands (full, lite, off, default, review) and handles natural language deactivation phrases
  • Mode switching relies on ponytail-runtime.js functions (setMode, clearMode, readMode) to manage the .ponytail-active state file
  • Persistence occurs only when users explicitly set defaults via writeDefaultMode, while session changes remain transient
  • Qoder integration differs from standard runtimes by injecting full instruction sets rather than emitting simple status messages

Frequently Asked Questions

How does ponytail-mode-tracker.js detect if a prompt contains a Ponytail command?

The hook applies the regular expression /^[/@$]ponytail/ to the lowercase prompt string to identify commands prefixed with /ponytail, @ponytail, or $ponytail. If this fails, it checks for deactivation phrases like "stop ponytail" using the isDeactivationCommand utility from ponytail-config.js, which requires whole-message matches to prevent accidental triggers.

What is the difference between session-scoped and persisted mode switching in Ponytail?

Session-scoped changes (e.g., /ponytail full) call setMode() and affect only the current conversation, storing state in the transient .ponytail-active file. Persisted changes (e.g., /ponytail default ultra) invoke writeDefaultMode() to update config.json, establishing a default that survives across restarts and new sessions.

How does the hook handle different AI runtimes like Claude, Codex, and Qoder?

For Claude and Codex, the hook emits simple text confirmations via writeHookOutput. For Qoder (isQoder === true), it prepends the complete instruction set from ponytail-instructions.js to every prompt, integrating mode-change notifications into the ruleset header rather than generating separate output lines.

What happens if a user enters an invalid Ponytail sub-command?

Unrecognized arguments fall back to the system's default mode resolution via getDefaultMode(). The hook treats invalid inputs as report-only queries, displaying the current active mode without performing state transitions, ensuring the system remains stable despite malformed commands.

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 →