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.
- vibemaxxing-0.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +39 -0
- vibemaxxing-0.2.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- vibemaxxing-0.2.0/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
- vibemaxxing-0.2.0/.github/pull_request_template.md +15 -0
- vibemaxxing-0.2.0/.github/workflows/ci.yml +52 -0
- vibemaxxing-0.2.0/.github/workflows/publish.yml +54 -0
- vibemaxxing-0.2.0/.gitignore +12 -0
- vibemaxxing-0.2.0/.gitleaks.toml +17 -0
- vibemaxxing-0.2.0/.impeccable/config.json +17 -0
- vibemaxxing-0.2.0/CHANGELOG.md +46 -0
- vibemaxxing-0.2.0/CONTRIBUTING.md +58 -0
- vibemaxxing-0.2.0/LICENSE +21 -0
- vibemaxxing-0.2.0/PKG-INFO +188 -0
- vibemaxxing-0.2.0/README.md +162 -0
- vibemaxxing-0.2.0/SECURITY.md +50 -0
- vibemaxxing-0.2.0/docs/AUDIT.md +747 -0
- vibemaxxing-0.2.0/docs/CONTRACT.md +1089 -0
- vibemaxxing-0.2.0/docs/RELEASE.md +135 -0
- vibemaxxing-0.2.0/docs/RUNBOOK.md +362 -0
- vibemaxxing-0.2.0/docs/media/dashboard-dark.png +0 -0
- vibemaxxing-0.2.0/docs/media/dashboard.png +0 -0
- vibemaxxing-0.2.0/pyproject.toml +69 -0
- vibemaxxing-0.2.0/scripts/demo_dashboard.py +152 -0
- vibemaxxing-0.2.0/scripts/verify_switch_live.py +123 -0
- vibemaxxing-0.2.0/src/vibemaxxing/__init__.py +7 -0
- vibemaxxing-0.2.0/src/vibemaxxing/__main__.py +6 -0
- vibemaxxing-0.2.0/src/vibemaxxing/cli.py +475 -0
- vibemaxxing-0.2.0/src/vibemaxxing/credentials.py +239 -0
- vibemaxxing-0.2.0/src/vibemaxxing/envelope.py +298 -0
- vibemaxxing-0.2.0/src/vibemaxxing/errors.py +55 -0
- vibemaxxing-0.2.0/src/vibemaxxing/fsutil.py +46 -0
- vibemaxxing-0.2.0/src/vibemaxxing/history.py +77 -0
- vibemaxxing-0.2.0/src/vibemaxxing/httpclient.py +124 -0
- vibemaxxing-0.2.0/src/vibemaxxing/index.html +551 -0
- vibemaxxing-0.2.0/src/vibemaxxing/keychain.py +144 -0
- vibemaxxing-0.2.0/src/vibemaxxing/models.py +20 -0
- vibemaxxing-0.2.0/src/vibemaxxing/oauth.py +276 -0
- vibemaxxing-0.2.0/src/vibemaxxing/poll.py +89 -0
- vibemaxxing-0.2.0/src/vibemaxxing/pool.py +56 -0
- vibemaxxing-0.2.0/src/vibemaxxing/redact.py +133 -0
- vibemaxxing-0.2.0/src/vibemaxxing/store.py +362 -0
- vibemaxxing-0.2.0/src/vibemaxxing/tui.py +249 -0
- vibemaxxing-0.2.0/src/vibemaxxing/usage.py +160 -0
- vibemaxxing-0.2.0/src/vibemaxxing/web.py +174 -0
- vibemaxxing-0.2.0/tests/__init__.py +0 -0
- vibemaxxing-0.2.0/tests/conftest.py +121 -0
- vibemaxxing-0.2.0/tests/fakes.py +145 -0
- vibemaxxing-0.2.0/tests/fixture_usage.json +24 -0
- vibemaxxing-0.2.0/tests/test_audit_regressions.py +276 -0
- vibemaxxing-0.2.0/tests/test_cli.py +182 -0
- vibemaxxing-0.2.0/tests/test_credentials.py +129 -0
- vibemaxxing-0.2.0/tests/test_guard.py +31 -0
- vibemaxxing-0.2.0/tests/test_history.py +76 -0
- vibemaxxing-0.2.0/tests/test_keychain.py +52 -0
- vibemaxxing-0.2.0/tests/test_leak.py +224 -0
- vibemaxxing-0.2.0/tests/test_oauth.py +279 -0
- vibemaxxing-0.2.0/tests/test_poll.py +93 -0
- vibemaxxing-0.2.0/tests/test_pool.py +74 -0
- vibemaxxing-0.2.0/tests/test_redact.py +70 -0
- vibemaxxing-0.2.0/tests/test_store.py +292 -0
- vibemaxxing-0.2.0/tests/test_tui.py +247 -0
- vibemaxxing-0.2.0/tests/test_usage.py +173 -0
- vibemaxxing-0.2.0/tests/test_version.py +19 -0
- vibemaxxing-0.2.0/tests/test_web.py +296 -0
- 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,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,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
|
+

|
|
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.
|