Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2063582
fix(usage): count a week-capped slot as burned in the Session aggregate
countzero Sep 19, 2026
a818dc2
docs(readme): correct the watch-title example to the active slot's nu…
countzero Sep 19, 2026
f97e173
docs: drop the CLAUDE.md import shim now that Claude Code reads AGENT…
countzero Sep 19, 2026
e002529
fix(usage): average the Session bar over reachable slots only
countzero Sep 19, 2026
92c30a9
feat: run the warm round-robin beside a live Claude Code
countzero Sep 20, 2026
737f816
chore(agents): give both clients a real session id for scratch paths
countzero Sep 20, 2026
33a1ba6
docs(testing): make the run's exit code the documented way to read a …
countzero Sep 20, 2026
7d27faa
refactor: drop the unreachable JSON-null branch in ConvertTo-ScaJsonS…
countzero Sep 20, 2026
13480d6
fix(watch): guard the console cursor restore like every other Console…
countzero Sep 20, 2026
bf79618
test: close the reachable coverage gaps and raise the gate to 97
countzero Sep 20, 2026
a56c7eb
fix(warmup): finish what dropping the live-client refusal left open
countzero Sep 20, 2026
b44d1d2
docs(readme): correct what a full Session bar means
countzero Sep 20, 2026
d4d083a
docs(changelog): release 4.2.0
countzero Sep 20, 2026
82941fa
feat(color): add SCA_THEME palettes with an alt-screen background
countzero Sep 20, 2026
7a33015
feat(color): ship nine base16 themes and derive palettes from data
countzero Sep 20, 2026
9697be1
docs(themes): add a rendered gallery so a theme can be chosen by eye
countzero Sep 20, 2026
f873830
feat(color): add the claude theme
countzero Sep 20, 2026
3c0e6aa
docs(themes): render the whole monitor view in every theme
countzero Sep 20, 2026
02114b4
docs(themes): give every theme its own image and anchor
countzero Sep 20, 2026
b573432
fix(themes): restore the embedded font and fill the whole panel face
countzero Sep 20, 2026
ef241da
fix(warmup): finalize the slots an aborted pass never reached
countzero Sep 21, 2026
4d1d5ef
fix(warmup): decide the abort before the repaint that can throw past it
countzero Sep 21, 2026
c3708b3
fix(warmup): hold the live-client notice until a slot will be warmed
countzero Sep 21, 2026
241db28
docs(changelog): record the theming work in the 4.2.0 release
countzero Sep 21, 2026
96b7500
test(save): restore the unreadable mode only where the file survives
countzero Sep 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume|compact",
"hooks": [
{
"type": "command",
"command": "jq -r '\"SESSION_ID=\" + .session_id'"
}
]
}
]
}
}
4 changes: 3 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ jobs:

# Coverage runs on one OS only. The gate counts lines executed in a
# single run, and each platform necessarily leaves the other's branches
# untouched, so requiring 90% on every leg would fail for the wrong reason.
# untouched, so requiring 97% on every leg would fail for the wrong reason.
# What that leaves unmeasured on this leg is `docs/testing.md` → *The
# ceiling*, which also says why the gate sits below the number a run reaches.
- name: Run tests with coverage
if: matrix.os == 'windows-latest'
shell: pwsh
Expand Down
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@
node_modules/
tests/TestResults/
*.sublime-workspace

# Local Claude Code settings; the shared SessionStart hook is tracked.
.claude/settings.local.json

# Agent git worktrees. See AGENTS.md "Multi-Agent Working Tree Discipline".
.claude/worktrees/
.tmp/

# Local scratch directory for all agent-generated artifacts (screenshots,
# diffs, trace outputs, experimental scripts). See AGENTS.md "Scratch files".
.tmp/
24 changes: 24 additions & 0 deletions .opencode/plugins/session-id-injector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Puts SESSION_ID into the environment of every shell command, which the
// agent uses as <session-id> in .tmp/sessions/<session-id>/ per AGENTS.md
// "Multi-Agent Working Tree Discipline" rule 3.
//
// It goes in the environment rather than in the system prompt because the
// system prompt is the one part of the request every turn and every session
// shares, and a prompt cache keys on exactly that. A per-session value inside
// it leaves no two sessions a reusable prefix, so the whole system block
// (instructions, AGENTS.md, tool definitions) is reprocessed at every session
// start. Turn-to-turn reuse within a session was never affected, which is why
// the cost hid: it falls entirely on session starts.
//
// Claude Code keeps the literal value in context instead, from the
// SessionStart hook in .claude/settings.json. That is not an oversight to be
// tidied away: its stdout joins the conversation ahead of the first prompt
// rather than the system block, so it sits outside the cached prefix, and its
// static "env" setting cannot carry a per-session value the way this hook can.

export const SessionIdInjector = async () => ({
'shell.env': async (input, output) => {
if (!input.sessionID) return;
output.env.SESSION_ID = input.sessionID;
},
});
15 changes: 8 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AGENTS.md

This file is the canonical agent-instructions source for this repository, read natively by OpenCode and loaded by Claude Code through the `CLAUDE.md` import shim. Single-file PowerShell tool: core logic lives in `switch_claude_account.ps1`; tests live in `tests/` and use Pester 5. It carries the always-on rules as one invariant per area; the contracts behind them are the documents under `docs/`, read on demand through *Reference* at the end.
This file is the canonical agent-instructions source for this repository, read natively by both OpenCode and Claude Code (2.1.277+). Single-file PowerShell tool: core logic lives in `switch_claude_account.ps1`; tests live in `tests/` and use Pester 5. It carries the always-on rules as one invariant per area; the contracts behind them are the documents under `docs/`, read on demand through *Reference* at the end.

## Security Rules

Expand Down Expand Up @@ -40,23 +40,24 @@ The `usage` action and the identity-fallback path depend on constants extracted

## Platform gotchas

- **Hot-swapping a live client is supported.** `switch` and `monitor` run with Claude Code open; `save`, `warmup` and `monitor -KeepWarm` refuse. `Test-ClaudeRunning` owns the evidence and the exceptions.
- **Hot-swapping a live client is supported.** Every action but `save` runs with Claude Code open, the warm round-robin included; `save` alone refuses. `Test-ClaudeRunning` owns the evidence and that one exception.
- **POSIX has no mandatory locking**, so a share-mode test is `-Skip:(-not $IsWindows)` and pairs with a Unix test asserting the inode property instead.
- **`Get-SafeName` is Windows-strict on every platform**, and every credential-file operation also passes `-LiteralPath` as defense in depth.
- **Guard every `System.Console` call.** `[Console]::CursorVisible` is Windows-only to read and throws off an attached console to write; a failed capture stays `$null` so the restore is skipped rather than defaulted to a wrong value.
- **`Write-Color` takes a role, never a color**, one of `Heading` / `Warning` / `Success` / `Danger` / `Muted` / `Neutral`; `$env:SCA_THEME` picks the palette they render through. A theme's background is alt-screen chrome, never a seventh role, and truecolor is never probed for.
- The reasoning for each of these, and token expiry, are `docs/architecture.md` → *Platform behavior*.

## Testing

```powershell
pwsh -NoProfile -File tests/Invoke-Tests.ps1
pwsh -NoProfile -File tests/Invoke-Tests.ps1; "EXIT=$LASTEXITCODE"
```

Coverage on `switch_claude_account.ps1` runs by default behind a **90% gate**; `-SkipCoverage` for the fastest local loop. One file per action at `tests/Invoke-<Action>Action.Tests.ps1`, every outer `Describe` named `'switch_claude_account'`, and `tests/Common.ps1` dot-sourced from each `BeforeEach` to sandbox both home variables, `CLAUDE_CONFIG_DIR` and `$PROFILE.CurrentUserAllHosts` into `$TestDrive`. The filter recipes, the direct-call pattern, the output-capture rule and the complexity diagnostic are `docs/testing.md`.
The exit code is the verdict, so never narrow the run to find one: a filter that fits the output to a terminal drops the summary and costs a second full run. Coverage on `switch_claude_account.ps1` runs by default behind a **97% gate**, measured on one OS, so 100% is unreachable by construction and the residue is `docs/testing.md` → *The ceiling*; `-SkipCoverage` for the fastest local loop. One file per action at `tests/Invoke-<Action>Action.Tests.ps1`, every outer `Describe` named `'switch_claude_account'`, and `tests/Common.ps1` dot-sourced from each `BeforeEach` to sandbox both home variables, `CLAUDE_CONFIG_DIR` and `$PROFILE.CurrentUserAllHosts` into `$TestDrive`. The filter recipes, the direct-call pattern, the output-capture rule, reading the result and the complexity diagnostic are `docs/testing.md`.

## README image regeneration

`pwsh -NoProfile -File tools/Render-ReadmeImages.ps1` re-renders the four SVGs in `docs/images/` via `charmbracelet/freeze`. Re-run when a README example number changes, or when a `Write-Color` / `Get-StatusColor` / `Get-AggregateBarColor` mapping changes. That script's header owns the palette, the truecolor rationale and the README `width` contract.
`pwsh -NoProfile -File tools/Render-ReadmeImages.ps1` re-renders every SVG in `docs/images/` via `charmbracelet/freeze`: four README scenes plus one `theme-<name>.svg` per selectable theme, which is every entry in `$Script:Base16Schemes`, read by dot-sourcing the script, and `default` besides. Re-run when a README example number changes, when a `Write-Color` / `Get-StatusColor` / `Get-AggregateBarColor` mapping changes, or when a theme is added; a new theme's image appears on its own, but its heading and alt text in `docs/themes.md` are hand-maintained. A theme panel takes its canvas from freeze's `--background`, not an SGR behind each row, so the color reaches the window padding too. Every image embeds its font and must: freeze emits no per-glyph positions, so a substituted face moves the text off the geometry and the usage bars stop filling their cells. That script's header owns the palette, the font and truecolor rationale, and the README `width` contract.

## Default Change Workflow

Expand All @@ -68,15 +69,15 @@ Comments explain **why**, not **what**. Default to no comment; prefer a clearer

## Scratch files

Ad-hoc agent artifacts (screenshots, diffs, scratch scripts, traces) go under `.tmp/sessions/<session-id>/`. `.tmp/` is gitignored. Never write scratch files to `.claude/`, the repo root, or `tests/`.
Every ad-hoc artifact of an agent session (screenshots, diffs, scratch scripts, traces: anything not meant to be committed) goes under `.tmp/sessions/<session-id>/` at the repo root, `<session-id>` per rule 3 in *Multi-Agent Working Tree Discipline*; `.tmp/` is gitignored. Nowhere else: not `.claude/`, not the repo root, not `tests/` or `tools/`, and not the operating-system temp directory under any name or helper (`$env:TEMP`, `os.tmpdir()`), which sits outside the workspace.

## Multi-Agent Working Tree Discipline

Multiple agents may share this directory; foreign uncommitted changes and untracked files are untouchable.

1. **Foreign changes off-limits.** Never run `git checkout --`, `restore --`, `reset --hard`, `clean`, `rm`, `mv`, or `git stash pop/apply` on a path another agent modified or an untracked file another agent created. "Commit and push" does NOT authorize destructive cleanup of foreign paths.
2. **Preflight.** `git status --porcelain -u` at task start and again before `git commit`.
3. **Session-scoped scratch.** Use `<session-id>` from your runtime's session metadata if exposed; otherwise mint `YYYYMMDD-HHMMSS-<random6>`.
3. **Session-scoped scratch.** At task start take `SESSION_ID` from your session-start context (Claude Code) or the shell environment (OpenCode, where it is spent unread in a command and read once with `Write-Output $env:SESSION_ID` for a Write or Edit path; `.opencode/plugins/session-id-injector.js` has why it is not in the prompt), use it as `<session-id>` and write every scratch artifact into `.tmp/sessions/<session-id>/` under a readable name (`foreign-baseline.diff`). A resumed session gets the same id; unset, it collapses the path to `.tmp/sessions/`, so without one mint `YYYYMMDD-HHMMSS-<random6>` and lose resume support.
4. **Stashes session-scoped.** Only with explicit pathspec and tagged message: `git stash push --message "session-<id>: <reason>" -- <files>`. Bare `git stash`, `-u`, `--all`, and pop/apply of foreign stashes are forbidden.
5. **Edit and shell writes are mutually exclusive per file.** If a file was written outside the Edit tool, the cached content is stale. Re-Read before the next Edit. If Edit fails with "oldString not found", assume concurrent foreign write: surface to the user, do not guess.
6. **Worktrees.** `.claude/worktrees/<branch-name>/` is gitignored. Cleanup with `git worktree remove <path>`; no `--force`.
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,30 @@
This changelog follows [Common Changelog](https://common-changelog.org) and this
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [4.2.0] - 2026-09-21

_Upgrading is replacing one file. `sca warmup` and `sca monitor -KeepWarm` now run beside an open Claude Code instead of refusing, the `Session` aggregate bar reports a different number wherever a slot has capped its week, and colors are unchanged unless you set the new `SCA_THEME`._

### Changed
- Run the warm round-robin of `sca warmup` and `sca monitor -KeepWarm` beside a live Claude Code.
- Average the `Session` aggregate bar over reachable slots only, dropping any slot whose week has capped.
- Pause `sca warmup` for five seconds before the first billable activation when Claude Code is running.

### Added
- Add `SCA_THEME`, which pins output to an exact palette instead of the terminal's own ANSI colors.
- Add ten themes: the base16 schemes `dracula`, `everforest`, `flexoki`, `gruvbox`, `kanagawa`, `material`, `monokai`, `nord` and `onedark`, plus an original `claude`.
- Paint the watch's alternate screen in the active theme's background, erases and window padding included.
- Add `docs/themes.md`, showing every theme as a full `sca monitor` view under a heading of its own.
- List the available theme names in `sca help` under a new `ENVIRONMENT` section.

### Fixed
- Mirror the active credentials after every activation, including one whose `claude -p` then threw.
- Stop the warm pass, and skip its restore, when nothing could capture the credentials left active.
- Show the warm pass's restore failure instead of discarding it into a suppressed stream.
- Carry the live-client warning into `sca monitor -KeepWarm`, at startup and at every re-warm.
- Guard the watch's console cursor restore so a failure there cannot unwind the terminal restore.
- Correct the README's claim that a full `Session` bar means the week has capped every slot.

## [4.1.0] - 2026-09-19

_Upgrading is replacing one file. A hot swap is only followed without a restart by Claude Code >= 2.1.274 or opencode-claude-auth >= 1.5.4, and `sca switch` can now refuse, and exit non-zero, where it previously always succeeded._
Expand Down Expand Up @@ -295,6 +319,7 @@ _Upgrading migrates active-slot tracking from hardlinks to a state file on first
- Add a README with installation, usage, workflow, Windows notes and testing sections.
- Add `CLAUDE.md` with agent guidance for the repo structure, gotchas and script-shape conventions.

[4.2.0]: https://github.com/countzero/switch_claude_account/releases/tag/v4.2.0
[4.1.0]: https://github.com/countzero/switch_claude_account/releases/tag/v4.1.0
[4.0.0]: https://github.com/countzero/switch_claude_account/releases/tag/v4.0.0
[3.0.1]: https://github.com/countzero/switch_claude_account/releases/tag/v3.0.1
Expand Down
9 changes: 0 additions & 9 deletions CLAUDE.md

This file was deleted.

Loading
Loading