{"id":51413983,"url":"https://github.com/spuddydev/claudetoggle","last_synced_at":"2026-07-04T17:30:24.738Z","repository":{"id":353897665,"uuid":"1220910388","full_name":"spuddydev/claudetoggle","owner":"spuddydev","description":"Tiny Bash framework for toggleable Claude Code hooks. One declaration file per toggle.","archived":false,"fork":false,"pushed_at":"2026-04-26T06:10:59.000Z","size":106,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-26T06:25:20.598Z","etag":null,"topics":["bash","claude-code","developer-tools","hooks"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spuddydev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-25T13:56:15.000Z","updated_at":"2026-04-26T06:10:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/spuddydev/claudetoggle","commit_stats":null,"previous_names":["spuddydev/claudetoggle"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/spuddydev/claudetoggle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spuddydev%2Fclaudetoggle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spuddydev%2Fclaudetoggle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spuddydev%2Fclaudetoggle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spuddydev%2Fclaudetoggle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spuddydev","download_url":"https://codeload.github.com/spuddydev/claudetoggle/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spuddydev%2Fclaudetoggle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35130722,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","claude-code","developer-tools","hooks"],"created_at":"2026-07-04T17:30:24.174Z","updated_at":"2026-07-04T17:30:24.726Z","avatar_url":"https://github.com/spuddydev.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# claudetoggle\n\n[![ci](https://github.com/spuddydev/claudetoggle/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/spuddydev/claudetoggle/actions/workflows/ci.yml)\n[![licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)\n\nAdd `/yourtoggle` slash commands to Claude Code with one file. Each toggle flips a rule on or off — for the project, the session, or globally — and tells the model the new state.\n\n\u003e Writing your own toggle? See [docs/AUTHORING.md](docs/AUTHORING.md). Hacking on the framework? See [CONTRIBUTING.md](CONTRIBUTING.md). Release log: [CHANGELOG.md](CHANGELOG.md).\n\n## Why you'd use this\n\nA toggle is a one-liner for a behaviour you want to switch on and off mid-conversation. Real examples:\n\n- **`/coauth`** — flip the `Co-Authored-By: Claude` trailer policy for this repo.\n- **`/devlog`** — keep a running journal in `.claude/devlog/` for this session.\n- **`/precommit`** — turn pre-commit hooks off for an emergency hotfix without sprinkling `--no-verify`.\n- **`/safetynet`** — refuse `git push --force`, `rm -rf` and friends until you flip it back off.\n\nWithout a framework, each of these means writing a hook script, editing `settings.json`, adding a slash-command markdown, telling the model the rule changed, plus reannouncing it every few prompts. claudetoggle does all of that from one short metadata file per toggle.\n\n## Install\n\n```sh\ncurl -sSfL https://raw.githubusercontent.com/spuddydev/claudetoggle/main/setup.sh | sh\n```\n\nThat fetches the latest release, places framework files under `$XDG_DATA_HOME/claudetoggle/` (defaulting to `~/.local/share/claudetoggle/`), installs the `claudetoggle` CLI to `~/.local/bin/`, and wires the dispatcher into `~/.claude/settings.json`.\n\nIf `~/.local/bin` isn't on your `$PATH`, the installer prints the exact line to add to your shell config.\n\nTagged releases ship with a `SHA256SUMS` asset and the installer verifies the tarball against it before unpacking. Set `CLAUDETOGGLE_SKIP_VERIFY=1` (or pass `--skip-verify`) only if you really need to bypass the check.\n\n**From a clone, for development or audit-first install:**\n\n```sh\ngit clone https://github.com/spuddydev/claudetoggle\ncd claudetoggle\n./setup.sh --local\n```\n\n## Add a toggle in 60 seconds\n\nPick a shipped example:\n\n```sh\nclaudetoggle add coauth\nclaudetoggle list\n```\n\nOr roll your own. Drop a directory anywhere, then `claudetoggle add` it:\n\n```sh\nmkdir -p ~/projects/safetynet\ncat \u003e~/projects/safetynet/toggle.sh \u003c\u003c'EOF'\nTOGGLE_API=1\nTOGGLE_NAME=safetynet\nTOGGLE_SCOPE=session\nTOGGLE_ON_MSG=\"safetynet is ON: refuse git push --force, rm -rf and any irreversible filesystem operation. Ask the user to flip it off if they really want it.\"\nTOGGLE_OFF_MSG=\"safetynet is OFF.\"\nTOGGLE_MARKER=\"\u003c!-- safetynet-marker --\u003e\"\nEOF\n\ncat \u003e~/projects/safetynet/safetynet.md \u003c\u003c'EOF'\n---\ndescription: Toggle the safety net for this session. User-invokable only.\n---\n\u003c!-- safetynet-marker --\u003e\nThe user just typed `/safetynet`. The dispatcher already flipped state and announced. Acknowledge in one short line.\nEOF\n\nclaudetoggle add ~/projects/safetynet\n```\n\nNow in Claude Code, type `/safetynet`. The toggle flips on, the model sees the new rule, the statusline shows `safetynet`. Type it again to flip off.\n\n## CLI reference\n\n```\nclaudetoggle add \u003cname|path\u003e      register a shipped example or a local directory\n                                  (--dry-run to preview without writing)\nclaudetoggle remove \u003cname\u003e        unregister and delete a toggle (--keep-state to preserve,\n                                  --dry-run to preview)\nclaudetoggle list                 show registered toggles and current state\nclaudetoggle on \u003cname\u003e            flip a toggle ON in the current scope\nclaudetoggle off \u003cname\u003e           flip a toggle OFF in the current scope\nclaudetoggle update               re-run setup.sh against the latest release\nclaudetoggle uninstall            unwire claudetoggle (--purge to also delete data and CLI)\nclaudetoggle doctor               diagnostic dump\nclaudetoggle version              print the installed version\nclaudetoggle help                 full reference\n```\n\nTip: `claudetoggle on \u003cname\u003e` and `claudetoggle off \u003cname\u003e` work outside Claude Code too — handy for scripting or flipping a toggle from your shell without touching the chat.\n\n## How a toggle works\n\nEvery toggle lives at `$XDG_DATA_HOME/claudetoggle/toggles/\u003cname\u003e/` with at minimum:\n\n- `toggle.sh` — six lines of metadata (name, scope, the on and off messages).\n- `\u003cname\u003e.md` — the slash-command body Claude Code parses; tiny.\n\nWhen the user types `/\u003cname\u003e`:\n\n1. A single shared dispatcher (set up by `setup.sh`) intercepts the prompt.\n2. It flips a sentinel file under `\u003cdata\u003e/state/\u003cname\u003e/`.\n3. It injects `ON_MSG` (or `OFF_MSG`) into the model's context for this turn — that's how the rule \"lands\" without needing the model to read disk.\n4. From this turn on, the model knows the rule is on (or off).\n\nYou don't write the dispatcher. You don't edit `settings.json`. You write the metadata file (and optionally peer enforcement scripts) and `claudetoggle add` does the rest.\n\n## The metadata file in detail\n\nEvery toggle declares these:\n\n| Variable | Required | Default | What it does |\n|---|---|---|---|\n| `TOGGLE_API` | yes | — | Schema version. Set to `1`. |\n| `TOGGLE_NAME` | yes | — | Short name. Must match the directory name. |\n| `TOGGLE_SCOPE` | yes | — | `global`, `project`, or `session`. |\n| `TOGGLE_ON_MSG` | yes | — | Text injected to the model when flipped on or reannounced. |\n| `TOGGLE_OFF_MSG` | yes | — | Text shown when flipped off. |\n| `TOGGLE_MARKER` | optional | none | Substring in the slash-command markdown body for forward-compatible detection. Recommended. |\n| `TOGGLE_REANNOUNCE_EVERY` | optional | `0` | Reinject `ON_MSG` every N prompts. `0` = announce once on flip, never again. |\n| `TOGGLE_ANNOUNCE_ON_SESSION_START` | optional | `1` | Print `ON_MSG` at session start when the toggle is on. |\n| `TOGGLE_ANNOUNCE_ON_TOGGLE` | optional | `1` | Inject the on/off message into the model's context on flip. Set to `0` for silent toggles whose effect is purely behind-the-scenes. |\n| `TOGGLE_STATUSLINE` | optional | `1` | Show this toggle on the statusline when on. |\n| `TOGGLE_EXTRA_HOOKS` | optional | empty | One entry per extra event hook (see below). |\n\nA toggle may also define a function `toggle_\u003cname\u003e_statusline` to override the default statusline fragment. The function is called per redraw inside a subshell, so it must be fast and side-effect-free.\n\n## Adding extra enforcement scripts\n\nSometimes a toggle needs to **enforce** a rule, not just tell the model. Example: `/precommit` should make `git commit` fail when off.\n\nDrop a peer script alongside `toggle.sh` and register it via `TOGGLE_EXTRA_HOOKS`:\n\n```sh\n# ~/projects/precommit/toggle.sh\nTOGGLE_API=1\nTOGGLE_NAME=precommit\nTOGGLE_SCOPE=project\nTOGGLE_ON_MSG=\"precommit is ON: pre-commit hooks must run.\"\nTOGGLE_OFF_MSG=\"precommit is OFF: skipping pre-commit hooks for this turn.\"\nTOGGLE_MARKER=\"\u003c!-- precommit-marker --\u003e\"\n\nTOGGLE_EXTRA_HOOKS=()\nTOGGLE_EXTRA_HOOKS+=(\"PreToolUse\"$'\\x1f'\"Bash\"$'\\x1f'\"Bash(git commit *)\"$'\\x1f'\"check.sh\")\n```\n\nThen `claudetoggle add ~/projects/precommit`. The four pipe-separated fields are: **event** (`PreToolUse`), **matcher** (`Bash`), **if-clause** (`Bash(git commit *)`), **script path** (relative to the toggle's directory). The separator `$'\\x1f'` is the ASCII unit-separator, chosen because it never appears inside an if-clause.\n\nYour peer script reads the hook's stdin JSON and decides what to do. Source the framework helpers like this:\n\n```sh\n#!/usr/bin/env bash\nCLAUDETOGGLE_LIB=${CLAUDETOGGLE_LIB:-$(dirname \"$(readlink -f \"$0\")\")/../../lib}\n. \"$CLAUDETOGGLE_LIB/hook_io.sh\"\n. \"$CLAUDETOGGLE_LIB/scope.sh\"\n\n# Read input, decide, then either exit 0 (allow) or:\n#   deny_pretooluse \"your reason here\"\n```\n\nTwo `..` because peer scripts live at `\u003cdata\u003e/toggles/\u003cname\u003e/\u003cpeer\u003e.sh`, two levels below the framework lib at `\u003cdata\u003e/lib/`.\n\nAfter editing a registered toggle's metadata, run `claudetoggle remove \u003cname\u003e` then `claudetoggle add \u003cpath\u003e` to re-register with the changes.\n\nSee [`examples/coauth/`](examples/coauth/) for a complete worked example. The full schema reference, scope semantics, idempotency rules and peer-script patterns live in [docs/AUTHORING.md](docs/AUTHORING.md).\n\n### Scaffolding skill\n\nIf you want Claude to draft a new toggle for you, ask it: *\"create a toggle that ...\"*. The installer ships a `create-claudetoggle` skill into `~/.claude/skills/` that walks the model through the schema, generates the files into a directory of your choice, and prints the exact `claudetoggle add` command for you to run. It will not register the toggle for you — review it first.\n\n## Statusline integration\n\nPipe Claude Code's statusline JSON into `claudetoggle statusline`. It works regardless of what your statusline is written in — bash, fish, node, python, a one-liner in `settings.json`:\n\n```sh\ninput=$(cat)\n# ...your existing statusline rendering...\nfragment=$(printf '%s' \"$input\" | claudetoggle statusline)\nprintf '%s%s\\n' \"$your_status\" \"$fragment\"\n```\n\nThe fragment is empty when no toggle is on, so you append unconditionally. `cwd` and `session_id` are read from the JSON; pass `--cwd` or `--session` to override, or set `CLAUDE_CWD` / `CLAUDE_SESSION_ID` in the environment.\n\nIf your statusline is already a bash script with `$cwd` and `$session` parsed and a `left+=` accumulator, you can source the helper directly instead:\n\n```sh\n. \"$HOME/.local/share/claudetoggle/bin/statusline.sh\"\nexport CLAUDE_CWD=\"$cwd\" CLAUDE_SESSION_ID=\"$session\"\nleft+=\"$(claudetoggle_statusline)\"\n```\n\n`setup.sh` prints both options for you. It does not mutate your existing statusline script — that's yours.\n\n## Upgrade\n\n```sh\nclaudetoggle update\n```\n\nRe-runs `setup.sh` against the latest release, replaces framework files, and re-wires the dispatcher idempotently. Your registered toggles and state are untouched.\n\n## Uninstall\n\n```sh\nclaudetoggle uninstall              # unwire from settings.json; preserves data, state, CLI\nclaudetoggle uninstall --purge      # also remove $XDG_DATA_HOME/claudetoggle and the CLI\n```\n\n## Troubleshooting\n\nSet `CLAUDETOGGLE_DEBUG=1` in your shell. The dispatcher and helpers append timestamped lines to `~/.local/share/claudetoggle/debug.log`. Tail it while you reproduce the issue.\n\nYou can drive the dispatcher directly to inspect its behaviour:\n\n```sh\nprintf '{\"hook_event_name\":\"UserPromptSubmit\",\"prompt\":\"/coauth\",\"cwd\":\"'\"$PWD\"'\",\"session_id\":\"x\"}' \\\n  | bash ~/.local/share/claudetoggle/bin/dispatch.sh UserPromptSubmit\n```\n\n`claudetoggle doctor` prints resolved paths, settings.json sanity, the registry, and the last few debug lines — start there for any \"is this set up right?\" question.\n\n## Known limits\n\n- The statusline forks one subshell per registered toggle on every redraw. Fine at five toggles, sluggish at twenty. A cache will land if anyone reports it.\n- The CLI is bash for v0.1.0. A native binary rewrite (Go) is on the roadmap; the CLI surface won't change.\n\n## Licence\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspuddydev%2Fclaudetoggle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspuddydev%2Fclaudetoggle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspuddydev%2Fclaudetoggle/lists"}