vibemaxxing 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. vibemaxxing-0.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +39 -0
  2. vibemaxxing-0.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  3. vibemaxxing-0.2.0/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
  4. vibemaxxing-0.2.0/.github/pull_request_template.md +15 -0
  5. vibemaxxing-0.2.0/.github/workflows/ci.yml +52 -0
  6. vibemaxxing-0.2.0/.github/workflows/publish.yml +54 -0
  7. vibemaxxing-0.2.0/.gitignore +12 -0
  8. vibemaxxing-0.2.0/.gitleaks.toml +17 -0
  9. vibemaxxing-0.2.0/.impeccable/config.json +17 -0
  10. vibemaxxing-0.2.0/CHANGELOG.md +46 -0
  11. vibemaxxing-0.2.0/CONTRIBUTING.md +58 -0
  12. vibemaxxing-0.2.0/LICENSE +21 -0
  13. vibemaxxing-0.2.0/PKG-INFO +188 -0
  14. vibemaxxing-0.2.0/README.md +162 -0
  15. vibemaxxing-0.2.0/SECURITY.md +50 -0
  16. vibemaxxing-0.2.0/docs/AUDIT.md +747 -0
  17. vibemaxxing-0.2.0/docs/CONTRACT.md +1089 -0
  18. vibemaxxing-0.2.0/docs/RELEASE.md +135 -0
  19. vibemaxxing-0.2.0/docs/RUNBOOK.md +362 -0
  20. vibemaxxing-0.2.0/docs/media/dashboard-dark.png +0 -0
  21. vibemaxxing-0.2.0/docs/media/dashboard.png +0 -0
  22. vibemaxxing-0.2.0/pyproject.toml +69 -0
  23. vibemaxxing-0.2.0/scripts/demo_dashboard.py +152 -0
  24. vibemaxxing-0.2.0/scripts/verify_switch_live.py +123 -0
  25. vibemaxxing-0.2.0/src/vibemaxxing/__init__.py +7 -0
  26. vibemaxxing-0.2.0/src/vibemaxxing/__main__.py +6 -0
  27. vibemaxxing-0.2.0/src/vibemaxxing/cli.py +475 -0
  28. vibemaxxing-0.2.0/src/vibemaxxing/credentials.py +239 -0
  29. vibemaxxing-0.2.0/src/vibemaxxing/envelope.py +298 -0
  30. vibemaxxing-0.2.0/src/vibemaxxing/errors.py +55 -0
  31. vibemaxxing-0.2.0/src/vibemaxxing/fsutil.py +46 -0
  32. vibemaxxing-0.2.0/src/vibemaxxing/history.py +77 -0
  33. vibemaxxing-0.2.0/src/vibemaxxing/httpclient.py +124 -0
  34. vibemaxxing-0.2.0/src/vibemaxxing/index.html +551 -0
  35. vibemaxxing-0.2.0/src/vibemaxxing/keychain.py +144 -0
  36. vibemaxxing-0.2.0/src/vibemaxxing/models.py +20 -0
  37. vibemaxxing-0.2.0/src/vibemaxxing/oauth.py +276 -0
  38. vibemaxxing-0.2.0/src/vibemaxxing/poll.py +89 -0
  39. vibemaxxing-0.2.0/src/vibemaxxing/pool.py +56 -0
  40. vibemaxxing-0.2.0/src/vibemaxxing/redact.py +133 -0
  41. vibemaxxing-0.2.0/src/vibemaxxing/store.py +362 -0
  42. vibemaxxing-0.2.0/src/vibemaxxing/tui.py +249 -0
  43. vibemaxxing-0.2.0/src/vibemaxxing/usage.py +160 -0
  44. vibemaxxing-0.2.0/src/vibemaxxing/web.py +174 -0
  45. vibemaxxing-0.2.0/tests/__init__.py +0 -0
  46. vibemaxxing-0.2.0/tests/conftest.py +121 -0
  47. vibemaxxing-0.2.0/tests/fakes.py +145 -0
  48. vibemaxxing-0.2.0/tests/fixture_usage.json +24 -0
  49. vibemaxxing-0.2.0/tests/test_audit_regressions.py +276 -0
  50. vibemaxxing-0.2.0/tests/test_cli.py +182 -0
  51. vibemaxxing-0.2.0/tests/test_credentials.py +129 -0
  52. vibemaxxing-0.2.0/tests/test_guard.py +31 -0
  53. vibemaxxing-0.2.0/tests/test_history.py +76 -0
  54. vibemaxxing-0.2.0/tests/test_keychain.py +52 -0
  55. vibemaxxing-0.2.0/tests/test_leak.py +224 -0
  56. vibemaxxing-0.2.0/tests/test_oauth.py +279 -0
  57. vibemaxxing-0.2.0/tests/test_poll.py +93 -0
  58. vibemaxxing-0.2.0/tests/test_pool.py +74 -0
  59. vibemaxxing-0.2.0/tests/test_redact.py +70 -0
  60. vibemaxxing-0.2.0/tests/test_store.py +292 -0
  61. vibemaxxing-0.2.0/tests/test_tui.py +247 -0
  62. vibemaxxing-0.2.0/tests/test_usage.py +173 -0
  63. vibemaxxing-0.2.0/tests/test_version.py +19 -0
  64. vibemaxxing-0.2.0/tests/test_web.py +296 -0
  65. vibemaxxing-0.2.0/uv.lock +486 -0
@@ -0,0 +1,39 @@
1
+ name: Bug report
2
+ description: Something does not work the way the README says it does
3
+ labels: [bug]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ **Never paste a token**, a `~/.vibemaxxing/accounts/*.json` file, or an
9
+ unredacted `~/.claude.json`. Output printed by `vibe` is scrubbed and is safe
10
+ to paste; the files it reads are not.
11
+
12
+ Found a security issue? Do not file it here. See
13
+ [SECURITY.md](https://github.com/jskarbecki/vibemaxxing/blob/main/SECURITY.md).
14
+ - type: input
15
+ id: command
16
+ attributes:
17
+ label: The exact command you ran
18
+ placeholder: vibe switch work
19
+ validations:
20
+ required: true
21
+ - type: textarea
22
+ id: what
23
+ attributes:
24
+ label: What happened, and what you expected instead
25
+ validations:
26
+ required: true
27
+ - type: input
28
+ id: version
29
+ attributes:
30
+ label: Output of `vibe --version`
31
+ validations:
32
+ required: true
33
+ - type: input
34
+ id: os
35
+ attributes:
36
+ label: OS and Python version
37
+ placeholder: macOS 15.5, Python 3.13
38
+ validations:
39
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/jskarbecki/vibemaxxing/security/advisories/new
5
+ about: Report privately. Never in a public issue.
@@ -0,0 +1,22 @@
1
+ name: Feature request
2
+ description: Suggest something it should do
3
+ labels: [enhancement]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ `docs/CONTRACT.md` is the frozen design and explains why several obvious-looking
9
+ things are deliberately absent. Worth a skim before you write.
10
+ - type: textarea
11
+ id: problem
12
+ attributes:
13
+ label: The problem you hit
14
+ description: What you were trying to do when you wanted this.
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: idea
19
+ attributes:
20
+ label: What you would like it to do
21
+ validations:
22
+ required: true
@@ -0,0 +1,15 @@
1
+ ## What changed, and why
2
+
3
+ <!-- Readable without opening the diff. -->
4
+
5
+ ## Checks
6
+
7
+ <!-- CI runs this; paste what it printed, or say which parts you skipped and why. -->
8
+
9
+ ```
10
+ uv sync --locked && uv run ruff check . && uv run ruff format --check . && uv run mypy src && uv run pytest -q
11
+ ```
12
+
13
+ - [ ] I read `docs/CONTRACT.md` for the area I touched
14
+ - [ ] Behaviour a doc describes changed, and I updated that doc here
15
+ - [ ] No new runtime dependency (or I opened an issue first)
@@ -0,0 +1,52 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ check:
11
+ name: ${{ matrix.os }} · py${{ matrix.python }}
12
+ runs-on: ${{ matrix.os }}
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ # macOS is the first-class platform; Linux must pass. Windows is
17
+ # unsupported in v1 and has no job, deliberately.
18
+ os: [macos-latest, ubuntu-latest]
19
+ python: ['3.11', '3.13']
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v6
26
+ with:
27
+ python-version: ${{ matrix.python }}
28
+
29
+ # The five clauses of the check command in docs/CONTRACT.md, in order and
30
+ # unchanged. CI has no credentials; every test runs offline.
31
+ - run: uv sync --locked
32
+ - run: uv run ruff check .
33
+ - run: uv run ruff format --check .
34
+ - run: uv run mypy src
35
+ - run: uv run pytest -q
36
+
37
+ gitleaks:
38
+ name: gitleaks
39
+ runs-on: ubuntu-latest
40
+
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+ with:
44
+ fetch-depth: 0 # scan the whole history, not just the tip
45
+
46
+ - name: Scan history for secrets
47
+ run: |
48
+ curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
49
+ | tar -xz gitleaks
50
+ ./gitleaks git . --config .gitleaks.toml --redact --no-banner --exit-code 1
51
+ env:
52
+ GITLEAKS_VERSION: 8.30.1
@@ -0,0 +1,54 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ testpypi:
9
+ name: TestPyPI
10
+ runs-on: ubuntu-latest
11
+ environment: testpypi
12
+ permissions:
13
+ # An explicit permissions block sets every unlisted scope to `none`, so
14
+ # contents: read has to be named or actions/checkout cannot read a private
15
+ # repo -- which is exactly how the first v0.0.1 publish failed.
16
+ contents: read
17
+ id-token: write # trusted publishing; no API token is stored anywhere
18
+
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v6
24
+ with:
25
+ python-version: '3.11'
26
+
27
+ - run: uv build
28
+
29
+ - uses: pypa/gh-action-pypi-publish@release/v1
30
+ with:
31
+ repository-url: https://test.pypi.org/legacy/
32
+
33
+ pypi:
34
+ name: PyPI
35
+ runs-on: ubuntu-latest
36
+ # Real PyPI only for a full release, never a prerelease. Nothing reached it
37
+ # before v0.1.0 by design: the pipeline was proven on TestPyPI first.
38
+ if: github.event.release.prerelease == false
39
+ environment: pypi
40
+ permissions:
41
+ contents: read
42
+ id-token: write
43
+
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+
47
+ - name: Install uv
48
+ uses: astral-sh/setup-uv@v6
49
+ with:
50
+ python-version: '3.11'
51
+
52
+ - run: uv build
53
+
54
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .mypy_cache/
10
+ .coverage
11
+ .claude/
12
+ .playwright-mcp/
@@ -0,0 +1,17 @@
1
+ # Default rules plus one narrow exception. Never a path-scoped exclusion of
2
+ # tests/: that is exactly where the sentinel tokens live and where a real leak
3
+ # would show. The exception is the literal string and nothing else.
4
+ [extend]
5
+ useDefault = true
6
+
7
+ [[allowlists]]
8
+ description = """
9
+ `default_claude_max_20x` is the rateLimitTier string a live /api/oauth/usage
10
+ credential carries. It is a plan-tier name, not a credential, and the test that
11
+ asserts it is the one proving parse_blob reads the field at all. gitleaks'
12
+ generic-api-key rule fires on its entropy (3.57). Allowlisted by the exact
13
+ matched text, with no path clause -- a path clause suppressed unrelated
14
+ high-entropy strings in the same file, which was verified and rejected.
15
+ """
16
+ regexTarget = "match"
17
+ regexes = ['''rate_limit_tier == "default_claude_max_20x"''']
@@ -0,0 +1,17 @@
1
+ {
2
+ "detector": {
3
+ "ignoreRules": [],
4
+ "ignoreFiles": [],
5
+ "ignoreValues": [
6
+ {
7
+ "rule": "layout-transition",
8
+ "value": "*",
9
+ "files": [
10
+ "src/vibemaxxing/index.html"
11
+ ],
12
+ "createdAt": "2026-09-16T10:34:54.667Z",
13
+ "reason": "docs/CONTRACT.md s11 pins 'transition: width 240ms cubic-bezier(0.23, 1, 0.32, 1)' as the page's only motion; tests/test_web.py asserts exactly one occurrence. Leaf span in a fixed-height track, repaints once per 60s poll. scaleX would distort the 2px border-radius."
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ Notable changes per release. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
4
+ versions follow [semantic versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [0.2.0] - 2026-09-16
7
+
8
+ ### Added
9
+
10
+ - `vibe help`: a command that explains what each command is for and when to use it.
11
+ Before it existed, `vibe help` answered `invalid choice: 'help'` and then listed the
12
+ commands it had just refused to explain.
13
+ - The web dashboard shows **the week ahead** beside the account ledger: one lane per
14
+ account, a marker where that account's weekly limit resets, seven days from right now.
15
+ Times render in the reader's own timezone, and day boundaries are local midnights, so
16
+ the grid does not drift across a DST change.
17
+ - Every account's plan (`max 20x`, `pro`) is shown in the dashboard's ledger.
18
+ - A top-level `resets` array in the `--json` envelope, listing each account's weekly
19
+ rollover soonest first. Documented in `docs/CONTRACT.md` §10.
20
+
21
+ ### Fixed
22
+
23
+ - **Login failed at the token exchange with HTTP 403.** Cloudflare fronts all three
24
+ allowed hosts and answers urllib's default `Python-urllib/3.x` user agent with
25
+ `error code: 1010` before the request reaches the API. Every request now names a real
26
+ user agent.
27
+ - **Login failed at the Authorize click with "Invalid request format".** The PKCE
28
+ `state` was 22 characters; the authorize endpoint accepts the consent page but rejects
29
+ the submission below 43. The failure only surfaced at the last step, which is what made
30
+ it look like a redirect problem.
31
+ - **`vibe switch` changed the credential but not the account.** Claude Code caches the
32
+ profile in `~/.claude.json` under `oauthAccount` and trusts it for 24 h, so it ran on
33
+ the new token while still showing the previous account's email, org and limits. The
34
+ switch now repoints that object and drops `profileFetchedAt`, which makes Claude Code
35
+ refetch the rest itself. No priming needed on a machine that has never run it.
36
+
37
+ ## [0.1.0] - 2026-09-15
38
+
39
+ First release. Multi-account store, OAuth login with PKCE, credential switching through
40
+ the macOS Keychain or `~/.claude/.credentials.json`, `vibe run` for per-command accounts,
41
+ a Textual dashboard, a loopback web dashboard, and a documented `--json` envelope.
42
+
43
+ Published to TestPyPI only, to prove the release pipeline.
44
+
45
+ [0.2.0]: https://github.com/jskarbecki/vibemaxxing/compare/v0.1.0...v0.2.0
46
+ [0.1.0]: https://github.com/jskarbecki/vibemaxxing/releases/tag/v0.1.0
@@ -0,0 +1,58 @@
1
+ # Contributing
2
+
3
+ Thanks for looking. Issues and pull requests are both welcome.
4
+
5
+ ## Setup
6
+
7
+ ```
8
+ git clone https://github.com/jskarbecki/vibemaxxing
9
+ cd vibemaxxing
10
+ uv sync
11
+ ```
12
+
13
+ ## The one command
14
+
15
+ CI runs exactly this, in this order. Run it before you open a pull request:
16
+
17
+ ```
18
+ uv sync --locked && uv run ruff check . && uv run ruff format --check . && uv run mypy src && uv run pytest -q
19
+ ```
20
+
21
+ Every test runs offline. There are no credentials in CI and none are needed: the HTTP
22
+ client and the keychain are both injectable ports, and the suite passes fakes.
23
+
24
+ ## Before you write
25
+
26
+ Read [`docs/CONTRACT.md`](docs/CONTRACT.md). It is the frozen design, and it is the
27
+ authority on any question of intent — not preference, and not this file. It explains why
28
+ things that look odd are the way they are, which will save you writing a patch that gets
29
+ turned down for a reason nobody wrote down anywhere else.
30
+
31
+ A few rules from it that catch people out:
32
+
33
+ - **A token value lives only inside a `Secret`.** `.reveal()` may be called in exactly
34
+ five places, listed in §5. A sixth fails the leak test.
35
+ - **The `--json` envelope is a contract.** `vibe list --json` and `GET /api/usage` emit
36
+ the same bytes because they call the same builder. Adding a key means updating §10.
37
+ - **No new dependencies** without asking first. The runtime set is `textual` and the
38
+ standard library, deliberately — §1.
39
+ - **`mypy --strict`, and no silencing.** No `Any`, no `type: ignore` to get a build
40
+ green.
41
+
42
+ ## Pull requests
43
+
44
+ Small and focused beats large and comprehensive. Say what changed and why in the
45
+ description; if the change touches behaviour a doc describes, update that doc in the same
46
+ pull request.
47
+
48
+ If you are unsure whether something is wanted, open an issue first and ask. That is
49
+ cheaper for both of us than a rejected branch.
50
+
51
+ ## Reporting a bug
52
+
53
+ Include your OS, your Python version, `vibe --version`, and the exact command you ran.
54
+ **Never paste a token, a `~/.vibemaxxing/accounts/*.json` file, or an unredacted
55
+ `~/.claude.json`.** Output from `vibe` is scrubbed before it is printed, so pasting what
56
+ it showed you is safe; pasting the files it reads is not.
57
+
58
+ Security issues go to [`SECURITY.md`](SECURITY.md) instead, not to the issue tracker.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jan Skarbecki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.5
2
+ Name: vibemaxxing
3
+ Version: 0.2.0
4
+ Summary: Manage several Claude Code accounts and see pooled plan usage across all of them
5
+ Project-URL: Homepage, https://github.com/jskarbecki/vibemaxxing
6
+ Project-URL: Repository, https://github.com/jskarbecki/vibemaxxing
7
+ Project-URL: Issues, https://github.com/jskarbecki/vibemaxxing/issues
8
+ Author-email: Jan Skarbecki <jan@intra-ai.de>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: account-switcher,claude,claude-code,cli,oauth,tui
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: textual<8.3,>=8.2.8
25
+ Description-Content-Type: text/markdown
26
+
27
+ # vibemaxxing
28
+
29
+ Run several Claude Code accounts from one machine, switch between them in a second, and
30
+ see all their plan limits in one place.
31
+
32
+ If you have more than one Claude subscription, you already know the problem: the only way
33
+ to change account is `/logout`, `/login`, browser, paste. And nothing anywhere tells you
34
+ how much weekly headroom you have left across all of them.
35
+
36
+ ```
37
+ vibe switch work
38
+ ```
39
+
40
+ ![The dashboard: every account's limits on the left, the week ahead on the right](https://raw.githubusercontent.com/jskarbecki/vibemaxxing/main/docs/media/dashboard.png)
41
+
42
+ ## Install
43
+
44
+ ```
45
+ uv tool install vibemaxxing # or: pipx install vibemaxxing
46
+ ```
47
+
48
+ macOS and Linux. Python 3.11+. Two commands, same program: `vibe` and `vibemaxxing`.
49
+
50
+ ## Start
51
+
52
+ ```
53
+ vibe add # store the account you are already logged in to
54
+ vibe add side # log a second one in, in the browser
55
+ vibe list # see both, and what is left on them
56
+ vibe switch side # Claude Code is now that account
57
+ ```
58
+
59
+ `vibe add` with no name takes the login already in Claude Code, no browser and no
60
+ network. That is the fastest way in: one command and you have your first account stored.
61
+
62
+ ## Every day
63
+
64
+ ```
65
+ vibe open the dashboard in the terminal
66
+ vibe usage web open it in a browser at http://127.0.0.1:8787
67
+ vibe list every account, its limits, the pooled total
68
+ vibe switch <name> change the active account
69
+ vibe run <name> -- <cmd> run one command as one account
70
+ vibe remove <name> forget an account
71
+ vibe alias <old> <new> rename one
72
+ vibe help what each command is for
73
+ ```
74
+
75
+ ### switch, or run?
76
+
77
+ `vibe switch work` changes the account **globally**, the same as logging out and back in.
78
+ Claude Code sessions already running keep the old one until they restart.
79
+
80
+ `vibe run work -- claude` pins **one command** to one account and leaves everything else
81
+ alone. Two terminals can hold two different accounts at once:
82
+
83
+ ```
84
+ vibe run work -- claude # this window is "work"
85
+ vibe run side -- claude # that window is "side"
86
+ ```
87
+
88
+ ### See what is left
89
+
90
+ ```
91
+ $ vibe list
92
+ * work you@example.com max 20x
93
+ Session 34%
94
+ Weekly · all models 61%
95
+ Weekly · Fable 12%
96
+
97
+ side side@example.com max
98
+ Session 2%
99
+ Weekly · all models 9%
100
+
101
+ pool 1.36 account-weeks across 2 accounts
102
+ ```
103
+
104
+ **account-weeks** is the pooled number: one account with a fresh weekly limit is `1.00`.
105
+ Two accounts each 50% spent is also `1.00`. It answers "how much Claude do I have left in
106
+ total", which no single account's percentage can.
107
+
108
+ The web dashboard adds the week ahead: one lane per account, a marker where that
109
+ account's weekly limit resets, in your own timezone. So you can see whether a reset lands
110
+ before your work does.
111
+
112
+ ### Scripting
113
+
114
+ `--json` works on every command and prints one documented envelope, the same bytes the
115
+ dashboard serves at `/api/usage`:
116
+
117
+ ```
118
+ vibe list --json | jq '.pool.remaining_account_weeks'
119
+ vibe list --json | jq -r '.resets[] | "\(.alias) \(.at)"'
120
+ ```
121
+
122
+ The schema is frozen in [`docs/CONTRACT.md`](docs/CONTRACT.md) §10.
123
+
124
+ ## How it works
125
+
126
+ It swaps **credentials**, plus the one line of config that names them. One `~/.claude`
127
+ history and one MCP config, shared by every account, exactly as if you had logged in and
128
+ out by hand. On macOS the active credential is Claude Code's own Keychain item; on Linux
129
+ it is `~/.claude/.credentials.json`.
130
+
131
+ `vibe switch` also repoints `oauthAccount` in `~/.claude.json` and clears its
132
+ `profileFetchedAt`, because Claude Code caches that profile for 24 h and would otherwise
133
+ run on the new token while still showing the old account's email, org and limits. It
134
+ refetches the rest itself on the next start, so a fresh machine needs no setup beyond
135
+ `vibe add` and `vibe switch`.
136
+
137
+ Tokens refresh on demand about five minutes before expiry, behind a consume gate: a
138
+ refresh rotates the refresh token, so the successor is written to disk **before** the
139
+ predecessor is treated as spent. A crash mid-refresh leaves a recoverable successor
140
+ rather than a dead account.
141
+
142
+ It talks to three hosts and no others — `claude.ai`, `platform.claude.com`,
143
+ `api.anthropic.com` — and refuses to contact anything else, redirects included. No
144
+ version pings, no analytics, no telemetry. The web dashboard binds `127.0.0.1`, and
145
+ `--host` with anything else is a hard error.
146
+
147
+ ## Known limitations
148
+
149
+ Real and deliberate. None of these is a bug report.
150
+
151
+ **Your tokens sit in plaintext JSON at rest.** `~/.vibemaxxing/accounts/*.json`, mode
152
+ `0600`, directories `0700`. Anyone who can read your files can read your tokens — which
153
+ is equally true of `~/.claude` itself.
154
+
155
+ **The macOS Keychain write puts the credential in `argv`.** `vibe switch` shells out to
156
+ `/usr/bin/security add-generic-password -w <blob>`, and macOS shows any local user a
157
+ process's full argv through `ps`. For the length of one `exec`, the credential is
158
+ readable cross-uid. The alternatives were measured and are worse: `security -i` silently
159
+ truncates at ~4005 bytes and stores the truncated value, and the Security framework
160
+ refuses to read Claude Code's item from a process outside its ACL, which would put an
161
+ authorization prompt in front of every switch. Multi-user macOS is out of scope for v1.
162
+
163
+ **`vibe run` cannot refresh its own token.** It sets `CLAUDE_CODE_OAUTH_TOKEN` for the
164
+ child only, which is what lets two shells hold two accounts, but a session outliving that
165
+ access token needs a re-run. Every grandchild inherits the variable.
166
+
167
+ **Switching does not reach a running session.** `vibe switch` makes no attempt to signal
168
+ a live Claude Code process. Restart it, or use `vibe run`.
169
+
170
+ **Windows is unsupported.** It says so rather than half-working.
171
+
172
+ ## Uninstall
173
+
174
+ ```
175
+ uv tool uninstall vibemaxxing
176
+ rm -rf ~/.vibemaxxing
177
+ ```
178
+
179
+ That is everything it creates. Your Claude Code login is untouched; whichever account was
180
+ active stays active.
181
+
182
+ ## Contributing
183
+
184
+ Issues and pull requests welcome. [`CONTRIBUTING.md`](CONTRIBUTING.md) has the setup and
185
+ the one command CI runs. [`docs/CONTRACT.md`](docs/CONTRACT.md) is the frozen design and
186
+ the authority on any question of intent.
187
+
188
+ MIT licensed. Not affiliated with Anthropic.