v0.1.1 - unified project storage
cctts v0.1.1
Patch release. Project settings move into the central state file, and bare cctts -p becomes a project-scope toggle that matches bare cctts and cctts -g.
In 0.1.0 each repo could carry a .claude/cctts.json file with a voice, rate, pause, or chime preference scoped to that project. The design assumed those settings might be useful to share with collaborators — commit the file to the repo, your teammates inherit the same TTS preferences. In practice cctts is a personal Claude Code plugin and no one was using it that way; the file-per-repo layer was just paying complexity cost and producing one small UX wart: bare cctts toggled the terminal on or off, bare cctts -g toggled global on or off, but bare cctts -p printed the project config instead of toggling. Same prefix, different verb.
0.1.1 unifies project storage into ~/.claude/hooks/.cctts-state.json under a new projects map keyed by git root, and gives cctts -p the toggle semantics it always should have had. Bare cctts -p now flips state.projects[<git-root>].enabled between true and false — first call disables every terminal whose cwd resolves to this repo, second call re-enables it. The previous read-and-print behavior moves to cctts -p -status, which prints the project's current entry as JSON. Everything else — cctts -p -3, cctts -p %150, cctts -p -pause, cctts -p -clear, the broadcast that clears matching per-terminal overrides — behaves the same from the user's point of view; the only difference is that values land in the central state file instead of a per-repo JSON file.
Project key resolution is pure-Python — a walk-up looking for .git, falling back to the resolved cwd when no git is found. Git worktrees (where .git is a file containing a gitdir pointer, not a directory) resolve to the main repo's project key, so settings follow the repo rather than splitting per worktree. The walk runs on every hook fire that involves project-scope state, so it has to be cheap; no subprocess to git, no caching layer, just Path.exists() walking up parents. On Windows that's microseconds; the previous file-read-per-fire was a similar cost.
Lazy migration handles existing v0.1.0 project files invisibly. On the first hook fire (or first cctts -p invocation) in a directory that still has a .claude/cctts.json file, the contents get ingested into state.projects[<git-root>] and the file is deleted. The hook logs one line on a successful migration so post-upgrade verification is possible. Failure modes are conservative: a half-written file (mid-edit) is left alone; a garbage payload is deleted; valid content with mixed valid/invalid fields keeps the valid ones and drops the rest. Once a project entry lives in state, the migration short-circuits even if the user manually re-creates the file later — the state entry is authoritative.
One bug fix tagged along. _valid_voice_index in cli/state.py was accepting True as a valid voice index because Python booleans satisfy isinstance(int). The validator now excludes bools explicitly, matching the same guard _sanitize_sid_int_map already had for kill_seq counters. In practice the bug would have produced voice_index=1 (since True == 1) on a malformed state file — annoying rather than catastrophic, but worth fixing now that 0.1.1's _sanitize_project exposed it.
If you're upgrading from v0.1.0 and you had .claude/cctts.json files in your repos, you don't need to do anything — they'll migrate on the next hook fire in each project. If you want to inspect what migrated, cat ~/.claude/hooks/.cctts-state.json | jq .projects shows the full map after the upgrade settles.
Upgrading
In any Claude Code session, run /plugin update cctts@quigleybits to pick up v0.1.1 — /plugin install is for fresh installs and won't upgrade an already-installed plugin. The bare /plugin slash command opens the management UI as an alternative. After the upgrade, fully restart Claude Code (close + reopen, not /reload-plugins) so the new hook code registers. Migration of any leftover .claude/cctts.json files happens automatically on the next hook fire — no manual cleanup needed.
The CLI surface that changed:
cctts -p(no other flags) → toggle the project's enabled flag (was: print project config)cctts -p -status→ print the project's entry as JSON (new)cctts -p -clear→ remove the entry fromstate.projects(was: delete the.claude/cctts.jsonfile)- Every other
cctts -p ...form behaves the same from the user's POV