opencode-swap 0.4.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.
- opencode_swap-0.4.0/.gitignore +230 -0
- opencode_swap-0.4.0/LICENSE +21 -0
- opencode_swap-0.4.0/PKG-INFO +307 -0
- opencode_swap-0.4.0/README.md +280 -0
- opencode_swap-0.4.0/docs/architecture.md +327 -0
- opencode_swap-0.4.0/docs/opencode-auth.md +176 -0
- opencode_swap-0.4.0/docs/provider-research-prompt.md +193 -0
- opencode_swap-0.4.0/docs/provider-support.md +76 -0
- opencode_swap-0.4.0/docs/releasing.md +93 -0
- opencode_swap-0.4.0/docs/roadmap.md +61 -0
- opencode_swap-0.4.0/docs/security.md +208 -0
- opencode_swap-0.4.0/docs/testing.md +113 -0
- opencode_swap-0.4.0/integrations/opencode-tui-plugin/LICENSE +21 -0
- opencode_swap-0.4.0/integrations/opencode-tui-plugin/README.md +119 -0
- opencode_swap-0.4.0/integrations/opencode-tui-plugin/src/tui.tsx +514 -0
- opencode_swap-0.4.0/pyproject.toml +58 -0
- opencode_swap-0.4.0/src/opencode_swap/__init__.py +3 -0
- opencode_swap-0.4.0/src/opencode_swap/__main__.py +4 -0
- opencode_swap-0.4.0/src/opencode_swap/atomic.py +95 -0
- opencode_swap-0.4.0/src/opencode_swap/backup.py +128 -0
- opencode_swap-0.4.0/src/opencode_swap/cli.py +799 -0
- opencode_swap-0.4.0/src/opencode_swap/exceptions.py +60 -0
- opencode_swap-0.4.0/src/opencode_swap/locking.py +70 -0
- opencode_swap-0.4.0/src/opencode_swap/macos_keychain.py +136 -0
- opencode_swap-0.4.0/src/opencode_swap/models.py +190 -0
- opencode_swap-0.4.0/src/opencode_swap/oauth_jwt.py +66 -0
- opencode_swap-0.4.0/src/opencode_swap/oauth_refresh.py +156 -0
- opencode_swap-0.4.0/src/opencode_swap/opencode_auth.py +54 -0
- opencode_swap-0.4.0/src/opencode_swap/paths.py +83 -0
- opencode_swap-0.4.0/src/opencode_swap/process_detection.py +29 -0
- opencode_swap-0.4.0/src/opencode_swap/providers/__init__.py +26 -0
- opencode_swap-0.4.0/src/opencode_swap/providers/api.py +50 -0
- opencode_swap-0.4.0/src/opencode_swap/providers/base.py +91 -0
- opencode_swap-0.4.0/src/opencode_swap/providers/common.py +144 -0
- opencode_swap-0.4.0/src/opencode_swap/providers/github_copilot.py +46 -0
- opencode_swap-0.4.0/src/opencode_swap/providers/openai.py +186 -0
- opencode_swap-0.4.0/src/opencode_swap/providers/poe.py +64 -0
- opencode_swap-0.4.0/src/opencode_swap/providers/xai.py +72 -0
- opencode_swap-0.4.0/src/opencode_swap/providers/zai.py +31 -0
- opencode_swap-0.4.0/src/opencode_swap/sealed.py +83 -0
- opencode_swap-0.4.0/src/opencode_swap/store.py +551 -0
- opencode_swap-0.4.0/src/opencode_swap/switcher.py +952 -0
- opencode_swap-0.4.0/src/opencode_swap/transfer.py +149 -0
- opencode_swap-0.4.0/src/opencode_swap/usage.py +304 -0
- opencode_swap-0.4.0/tests/__init__.py +0 -0
- opencode_swap-0.4.0/tests/helpers.py +12 -0
- opencode_swap-0.4.0/tests/test_atomic.py +183 -0
- opencode_swap-0.4.0/tests/test_backup.py +75 -0
- opencode_swap-0.4.0/tests/test_cli.py +1050 -0
- opencode_swap-0.4.0/tests/test_concurrency.py +208 -0
- opencode_swap-0.4.0/tests/test_locking.py +77 -0
- opencode_swap-0.4.0/tests/test_macos_keychain.py +13 -0
- opencode_swap-0.4.0/tests/test_main.py +17 -0
- opencode_swap-0.4.0/tests/test_models.py +27 -0
- opencode_swap-0.4.0/tests/test_multi_provider.py +122 -0
- opencode_swap-0.4.0/tests/test_oauth_jwt.py +42 -0
- opencode_swap-0.4.0/tests/test_oauth_refresh.py +214 -0
- opencode_swap-0.4.0/tests/test_opencode_auth.py +65 -0
- opencode_swap-0.4.0/tests/test_paths.py +48 -0
- opencode_swap-0.4.0/tests/test_providers_multi.py +217 -0
- opencode_swap-0.4.0/tests/test_providers_openai.py +189 -0
- opencode_swap-0.4.0/tests/test_registry.py +205 -0
- opencode_swap-0.4.0/tests/test_store.py +430 -0
- opencode_swap-0.4.0/tests/test_switcher.py +858 -0
- opencode_swap-0.4.0/tests/test_switcher_restore.py +273 -0
- opencode_swap-0.4.0/tests/test_switcher_transfer.py +338 -0
- opencode_swap-0.4.0/tests/test_switcher_use.py +419 -0
- opencode_swap-0.4.0/tests/test_transfer.py +65 -0
- opencode_swap-0.4.0/tests/test_usage.py +471 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib64/
|
|
18
|
+
parts/
|
|
19
|
+
sdist/
|
|
20
|
+
var/
|
|
21
|
+
wheels/
|
|
22
|
+
share/python-wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
MANIFEST
|
|
27
|
+
|
|
28
|
+
# PyInstaller
|
|
29
|
+
# Usually these files are written by a python script from a template
|
|
30
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Installer logs
|
|
35
|
+
pip-log.txt
|
|
36
|
+
pip-delete-this-directory.txt
|
|
37
|
+
|
|
38
|
+
# Unit test / coverage reports
|
|
39
|
+
htmlcov/
|
|
40
|
+
.tox/
|
|
41
|
+
.nox/
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
.cache
|
|
45
|
+
nosetests.xml
|
|
46
|
+
coverage.xml
|
|
47
|
+
*.cover
|
|
48
|
+
*.py.cover
|
|
49
|
+
.hypothesis/
|
|
50
|
+
.pytest_cache/
|
|
51
|
+
cover/
|
|
52
|
+
|
|
53
|
+
# Translations
|
|
54
|
+
*.mo
|
|
55
|
+
*.pot
|
|
56
|
+
|
|
57
|
+
# Django stuff:
|
|
58
|
+
*.log
|
|
59
|
+
local_settings.py
|
|
60
|
+
db.sqlite3
|
|
61
|
+
db.sqlite3-journal
|
|
62
|
+
|
|
63
|
+
# Flask stuff:
|
|
64
|
+
instance/
|
|
65
|
+
.webassets-cache
|
|
66
|
+
|
|
67
|
+
# Scrapy stuff:
|
|
68
|
+
.scrapy
|
|
69
|
+
|
|
70
|
+
# Sphinx documentation
|
|
71
|
+
docs/_build/
|
|
72
|
+
|
|
73
|
+
# PyBuilder
|
|
74
|
+
.pybuilder/
|
|
75
|
+
target/
|
|
76
|
+
|
|
77
|
+
# Jupyter Notebook
|
|
78
|
+
.ipynb_checkpoints
|
|
79
|
+
|
|
80
|
+
# IPython
|
|
81
|
+
profile_default/
|
|
82
|
+
ipython_config.py
|
|
83
|
+
|
|
84
|
+
# pyenv
|
|
85
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
86
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
87
|
+
# .python-version
|
|
88
|
+
|
|
89
|
+
# pipenv
|
|
90
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
91
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
92
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
93
|
+
# install all needed dependencies.
|
|
94
|
+
# Pipfile.lock
|
|
95
|
+
|
|
96
|
+
# UV
|
|
97
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
98
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
99
|
+
# commonly ignored for libraries.
|
|
100
|
+
# uv.lock
|
|
101
|
+
|
|
102
|
+
# poetry
|
|
103
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
104
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
105
|
+
# commonly ignored for libraries.
|
|
106
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
107
|
+
# poetry.lock
|
|
108
|
+
# poetry.toml
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
113
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
114
|
+
# pdm.lock
|
|
115
|
+
# pdm.toml
|
|
116
|
+
.pdm-python
|
|
117
|
+
.pdm-build/
|
|
118
|
+
|
|
119
|
+
# pixi
|
|
120
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
121
|
+
# pixi.lock
|
|
122
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
123
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
124
|
+
.pixi
|
|
125
|
+
|
|
126
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
127
|
+
__pypackages__/
|
|
128
|
+
|
|
129
|
+
# Celery stuff
|
|
130
|
+
celerybeat-schedule
|
|
131
|
+
celerybeat.pid
|
|
132
|
+
|
|
133
|
+
# Redis
|
|
134
|
+
*.rdb
|
|
135
|
+
*.aof
|
|
136
|
+
*.pid
|
|
137
|
+
|
|
138
|
+
# RabbitMQ
|
|
139
|
+
mnesia/
|
|
140
|
+
rabbitmq/
|
|
141
|
+
rabbitmq-data/
|
|
142
|
+
|
|
143
|
+
# ActiveMQ
|
|
144
|
+
activemq-data/
|
|
145
|
+
|
|
146
|
+
# SageMath parsed files
|
|
147
|
+
*.sage.py
|
|
148
|
+
|
|
149
|
+
# Environments
|
|
150
|
+
.env
|
|
151
|
+
!/.gemini/.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Ruff stuff:
|
|
198
|
+
.ruff_cache/
|
|
199
|
+
|
|
200
|
+
# PyPI configuration file
|
|
201
|
+
.pypirc
|
|
202
|
+
|
|
203
|
+
# Marimo
|
|
204
|
+
marimo/_static/
|
|
205
|
+
marimo/_lsp/
|
|
206
|
+
__marimo__/
|
|
207
|
+
|
|
208
|
+
# Streamlit
|
|
209
|
+
.streamlit/secrets.toml
|
|
210
|
+
|
|
211
|
+
#IDEs
|
|
212
|
+
# IntelliJ IDEA / JetBrains
|
|
213
|
+
.idea/*
|
|
214
|
+
!.idea/codeStyles/
|
|
215
|
+
!.idea/copyright/
|
|
216
|
+
*.iml
|
|
217
|
+
out/
|
|
218
|
+
.gradle/
|
|
219
|
+
|
|
220
|
+
# VS Code
|
|
221
|
+
.vscode/
|
|
222
|
+
|
|
223
|
+
# macOS
|
|
224
|
+
.DS_Store
|
|
225
|
+
|
|
226
|
+
# Password-encrypted opencode-swap account exports
|
|
227
|
+
*.ocs
|
|
228
|
+
|
|
229
|
+
# OpenCode TUI plugin dependencies (bun.lock is committed).
|
|
230
|
+
integrations/opencode-tui-plugin/node_modules/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Roberto Leinardi
|
|
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,307 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: opencode-swap
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Multi-account switcher for OpenCode
|
|
5
|
+
Project-URL: Homepage, https://github.com/leinardi/opencode-swap
|
|
6
|
+
Project-URL: Repository, https://github.com/leinardi/opencode-swap
|
|
7
|
+
Project-URL: Issues, https://github.com/leinardi/opencode-swap/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/leinardi/opencode-swap#readme
|
|
9
|
+
Author-email: Roberto Leinardi <roberto@leinardi.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: account-switcher,cli,opencode
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: pycryptodomex>=3.19.0
|
|
25
|
+
Requires-Dist: pyzipper>=0.4.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# opencode-swap
|
|
29
|
+
|
|
30
|
+
**Multi-account switcher for [OpenCode](https://github.com/anomalyco/opencode).**
|
|
31
|
+
|
|
32
|
+
OpenCode has no built-in way to keep several accounts for one provider around and flip
|
|
33
|
+
between them. `opencode-swap` is a small standalone CLI that does exactly
|
|
34
|
+
that: it keeps a securely-stored copy of each account's OpenCode auth state
|
|
35
|
+
and swaps the one OpenCode currently considers "active."
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
opencode-swap use openai personal && opencode
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Core workflow stays standalone: no background process or proxy runs while
|
|
42
|
+
OpenCode is. An optional terminal UI plugin adds account status and commands
|
|
43
|
+
without taking ownership of credentials or swaps.
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+
|
|
47
|
+

|
|
48
|
+
|
|
49
|
+
## Status
|
|
50
|
+
|
|
51
|
+
Early. Core behavior is covered by automated tests and has been exercised
|
|
52
|
+
against a real OpenCode installation. See
|
|
53
|
+
[`docs/roadmap.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/roadmap.md)
|
|
54
|
+
for what's done and what's left.
|
|
55
|
+
|
|
56
|
+
⚠️ **OpenAI is the only provider tested end to end with real accounts.** Other
|
|
57
|
+
provider implementations were derived from OpenCode source and synthetic tests.
|
|
58
|
+
Maintainer does not have accounts for those services, so testers are wanted for
|
|
59
|
+
every non-OpenAI provider.
|
|
60
|
+
|
|
61
|
+
> 🧪 **Testers wanted.** If you use any non-OpenAI provider, please open an
|
|
62
|
+
> issue with the provider, auth method, OpenCode version, and sanitized failure
|
|
63
|
+
> details. **Never include credentials.**
|
|
64
|
+
> [`docs/provider-research-prompt.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/provider-research-prompt.md)
|
|
65
|
+
> has a copy-paste OpenCode prompt that gathers safe source evidence for one provider.
|
|
66
|
+
|
|
67
|
+
🤖 This project is built largely with agentic AI coding tools, under human
|
|
68
|
+
review. Read the code before trusting it with your credentials.
|
|
69
|
+
|
|
70
|
+
## Looking for load balancing?
|
|
71
|
+
|
|
72
|
+
`opencode-swap` activates one account at a time. If you instead want requests
|
|
73
|
+
spread across several accounts at runtime, that's a different problem, and
|
|
74
|
+
[`opencode-balancer`](https://github.com/thelioo/opencode-balancer) is the
|
|
75
|
+
project for it.
|
|
76
|
+
[`docs/security.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/security.md)
|
|
77
|
+
covers the storage trade-offs each approach makes.
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uv tool install opencode-swap # or: pipx install opencode-swap
|
|
83
|
+
opencode-swap --help
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`uvx opencode-swap --help` works too, without installing anything permanently.
|
|
87
|
+
|
|
88
|
+
### From source
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
git clone https://github.com/leinardi/opencode-swap.git
|
|
92
|
+
cd opencode-swap
|
|
93
|
+
uv sync
|
|
94
|
+
uv run opencode-swap --help
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`uv tool install .` from the checkout also works if you want `opencode-swap`
|
|
98
|
+
and `ocs` on your `PATH` from a local checkout.
|
|
99
|
+
|
|
100
|
+
## Quickstart
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# 1. Log into OpenCode normally with your first account.
|
|
104
|
+
opencode auth login
|
|
105
|
+
|
|
106
|
+
# 2. Import whichever account is currently active into opencode-swap.
|
|
107
|
+
opencode-swap add openai personal
|
|
108
|
+
|
|
109
|
+
# 3. Log into a second account.
|
|
110
|
+
opencode auth login # choose/authenticate a different ChatGPT account
|
|
111
|
+
opencode-swap add openai work
|
|
112
|
+
|
|
113
|
+
# 4. See what's saved.
|
|
114
|
+
opencode-swap list
|
|
115
|
+
# personal ...abcd -
|
|
116
|
+
# * work ...ef01 -
|
|
117
|
+
|
|
118
|
+
# 5. Switch whenever you like.
|
|
119
|
+
opencode-swap use openai personal
|
|
120
|
+
opencode
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Names are yours to choose (lowercase letters/digits/`-`/`_`/`.`). Re-running
|
|
124
|
+
`add` with the provider and name of an already-imported account refreshes its stored
|
|
125
|
+
tokens in place; it won't create a duplicate.
|
|
126
|
+
|
|
127
|
+
## Commands
|
|
128
|
+
|
|
129
|
+
| Command | Description |
|
|
130
|
+
| --- | --- |
|
|
131
|
+
| `opencode-swap add <provider> <name>` | Import the provider's currently-active account under `<name>`. |
|
|
132
|
+
| `opencode-swap list [provider] [--usage]` | List every saved account, or filter by provider; `--usage` adds a live quota line where supported (OpenAI ChatGPT OAuth, Z.AI `zai-coding-plan`; network, opt-in). |
|
|
133
|
+
| `opencode-swap current [provider]` | Show active managed accounts for every provider, or one provider. |
|
|
134
|
+
| `opencode-swap status [provider] [--json] [--usage]` | Show integration status; `--json` emits versioned secret-safe data; `--usage` as for `list`. |
|
|
135
|
+
| `opencode-swap use <provider> <name> [-y]` | Activate one saved provider account. |
|
|
136
|
+
| `opencode-swap switch <provider> [-y]` | Switch to next saved account for provider. |
|
|
137
|
+
| `opencode-swap refresh <provider> [name]` | Ensure a saved account's OAuth token is valid, refreshing over the network if expired (no-op if already valid); every saved account for the provider if `name` is omitted. Never refreshes whichever account is currently active in OpenCode — that account's own next request handles its refresh. |
|
|
138
|
+
| `opencode-swap remove <provider> <name> [-y]` | Delete a saved provider account. |
|
|
139
|
+
| `opencode-swap rename <provider> <old> <new>` | Rename a saved provider account. |
|
|
140
|
+
| `opencode-swap export <path>` | Export all saved accounts to a new password-encrypted `.ocs` archive. |
|
|
141
|
+
| `opencode-swap import <path>` | Import accounts from an encrypted archive; prompts to skip or overwrite existing names. |
|
|
142
|
+
| `opencode-swap restore [--pristine] [-y]` | Recover `auth.json` from the most recent pre-switch backup, or from the very first snapshot ever taken (`--pristine`). |
|
|
143
|
+
| `opencode-swap doctor` | Diagnose paths, schema compatibility, secret backend, and backup state. |
|
|
144
|
+
|
|
145
|
+
`use`, `switch`, `remove`, and `restore` ask for confirmation unless you pass `-y`/`--yes`
|
|
146
|
+
(and refuse to prompt at all on a non-interactive terminal, so pass `-y` in
|
|
147
|
+
scripts). `use` also warns if it detects a running `opencode` process, since
|
|
148
|
+
switching while OpenCode might be mid-token-refresh is the one real race
|
|
149
|
+
condition this tool can't fully close (see
|
|
150
|
+
[`docs/security.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/security.md)).
|
|
151
|
+
|
|
152
|
+
No command ever prints an access token, refresh token, or API key. Account
|
|
153
|
+
ids are shown truncated to their last four characters.
|
|
154
|
+
|
|
155
|
+
## OpenCode TUI integration
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
opencode plugin @leinardi/opencode-swap --global
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
[`integrations/opencode-tui-plugin`](https://github.com/leinardi/opencode-swap/blob/main/integrations/opencode-tui-plugin)
|
|
162
|
+
uses OpenCode's supported TUI-plugin API to render active account and
|
|
163
|
+
active-only usage at right side of session prompt metadata:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
Plan · GPT-5.6 Sol OpenAI · medium work · 17%
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
It only appears after session has sent a request through provider managed by
|
|
170
|
+
`opencode-swap`; it stays hidden for unrelated providers. `/swap` opens a
|
|
171
|
+
safe account picker, `/swap-next` rotates current provider, and `/swap-refresh`
|
|
172
|
+
refreshes visible status. See the
|
|
173
|
+
[plugin README](https://github.com/leinardi/opencode-swap/blob/main/integrations/opencode-tui-plugin/README.md)
|
|
174
|
+
for install and concurrency limits.
|
|
175
|
+
|
|
176
|
+
Core CLI does not require Bun. TUI plugin development and root `make verify`
|
|
177
|
+
require Bun 1.3.14; `make check` remains Python-only and offline.
|
|
178
|
+
|
|
179
|
+
### Moving accounts to another computer
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
# Source computer
|
|
183
|
+
opencode-swap export ~/opencode-accounts.ocs
|
|
184
|
+
|
|
185
|
+
# Transfer the archive, then on the destination computer
|
|
186
|
+
opencode-swap import ~/opencode-accounts.ocs
|
|
187
|
+
opencode-swap use openai personal
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`export` asks for a password twice; `import` asks for it once. Password input
|
|
191
|
+
requires an interactive terminal and is never placed in command arguments or
|
|
192
|
+
printed. The archive is AES-256 encrypted and created with `0600` permissions.
|
|
193
|
+
Delete the transfer archive after a successful import. See
|
|
194
|
+
[`docs/architecture.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/architecture.md)
|
|
195
|
+
for how import handles naming conflicts and validates accounts before writing
|
|
196
|
+
anything.
|
|
197
|
+
|
|
198
|
+
## How it works
|
|
199
|
+
|
|
200
|
+
The short version: OpenCode keeps all of its provider credentials in a
|
|
201
|
+
single JSON file, `~/.local/share/opencode/auth.json`, and re-reads it fresh
|
|
202
|
+
on every request; no restart is required to pick up a change. The entire unit
|
|
203
|
+
of "which account is active" is one top-level provider key in that file.
|
|
204
|
+
`opencode-swap` keeps a copy of each account's record in private storage, and
|
|
205
|
+
`use <provider> <name>` atomically replaces that one key.
|
|
206
|
+
|
|
207
|
+
The interesting part is what happens *between* switches: OpenCode rotates
|
|
208
|
+
the access and refresh token in place whenever it refreshes, so a naively
|
|
209
|
+
cached copy would go stale. `opencode-swap` captures that rotation back into
|
|
210
|
+
its own storage every time you switch *away* from an account, before
|
|
211
|
+
overwriting it with another. Providers using static API keys do not rotate
|
|
212
|
+
credentials, but use the same atomic switching path.
|
|
213
|
+
|
|
214
|
+
### Provider support
|
|
215
|
+
|
|
216
|
+
| Provider/auth class | Status |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| OpenAI API key and ChatGPT OAuth | ✅ Supported; only end-to-end tested provider |
|
|
219
|
+
| Any provider using OpenCode's canonical `{"type":"api","key":"..."}` record | ⚠️ Supported generically; testers wanted |
|
|
220
|
+
| GitHub Copilot OAuth | ⚠️ Supported from OpenCode source analysis; testers wanted |
|
|
221
|
+
| Poe API/OAuth | ⚠️ Supported from pinned plugin source analysis; testers wanted |
|
|
222
|
+
| xAI API/OAuth | ⚠️ API supported; OAuth requires access JWT with stable `iss`/`sub`; testers wanted |
|
|
223
|
+
| Z.AI GLM Coding Plan (`zai-coding-plan`) | ⚠️ Supported through generic API handling; `--usage` reads the GLM Coding Plan quota; testers wanted |
|
|
224
|
+
| GitLab and Snowflake API/PAT | ⚠️ Supported through generic API handling; testers wanted |
|
|
225
|
+
| GitLab and Snowflake OAuth | ❌ Not supported: OpenCode does not persist a provably stable per-user identity |
|
|
226
|
+
| Well-known URL auth and unknown OAuth plugins | ❌ Not supported |
|
|
227
|
+
|
|
228
|
+
Same account name may exist under different providers. `openai:work` and
|
|
229
|
+
`anthropic:work` are separate accounts. Provider IDs must match keys in
|
|
230
|
+
OpenCode's `auth.json` exactly.
|
|
231
|
+
|
|
232
|
+
Full details, including the exact OpenCode internals this was reverse
|
|
233
|
+
engineered from and the switch algorithm's failure-recovery guarantees, are
|
|
234
|
+
in [`docs/opencode-auth.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/opencode-auth.md)
|
|
235
|
+
and [`docs/architecture.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/architecture.md).
|
|
236
|
+
|
|
237
|
+
## Security
|
|
238
|
+
|
|
239
|
+
Credentials are stored via:
|
|
240
|
+
|
|
241
|
+
- **macOS**: the system Keychain, through the pinned `/usr/bin/security` CLI.
|
|
242
|
+
- **Linux**: atomic `chmod 0600` files under a `0700` directory, obfuscated
|
|
243
|
+
(base64) but not encrypted. This matches OpenCode's filesystem trust boundary
|
|
244
|
+
without interactive Secret Service unlock prompts. The optional TUI status
|
|
245
|
+
widget polls account usage, so every secret read must be non-interactive and
|
|
246
|
+
complete in bounded time. Linux Secret Service cannot guarantee this: an
|
|
247
|
+
unlocked keyring may prompt again, while a locked or unhealthy D-Bus service
|
|
248
|
+
can block the CLI indefinitely.
|
|
249
|
+
- **macOS fallback**: the same private-file backend when Keychain is
|
|
250
|
+
unavailable.
|
|
251
|
+
|
|
252
|
+
No custom cryptographic protocol. Portable exports use standard WinZip AES-256
|
|
253
|
+
implemented by `pyzipper`; plaintext credentials never touch a temporary file.
|
|
254
|
+
No plaintext database. Full threat model in
|
|
255
|
+
[`docs/security.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/security.md).
|
|
256
|
+
See [`SECURITY.md`](https://github.com/leinardi/opencode-swap/blob/main/SECURITY.md)
|
|
257
|
+
to report a vulnerability.
|
|
258
|
+
|
|
259
|
+
## Scope
|
|
260
|
+
|
|
261
|
+
**In scope:** standalone CLI, Linux + macOS, OpenCode provider accounts with
|
|
262
|
+
known safe identity semantics, secure multi-account storage, transactional switching with
|
|
263
|
+
rollback, backup/recovery, an architecture that doesn't need a rewrite to
|
|
264
|
+
add provider-specific behavior without changing safe I/O machinery.
|
|
265
|
+
|
|
266
|
+
**Out of scope:** load balancing / round-robin, runtime request interception,
|
|
267
|
+
a local proxy, Windows, unsupported/ambiguous OAuth providers, reimplementing
|
|
268
|
+
or modifying OpenCode, cloud sync. The core CLI stays standalone and owns
|
|
269
|
+
credentials and swaps; the optional TUI plugin is a read-only companion that
|
|
270
|
+
shells out to it rather than a separate GUI.
|
|
271
|
+
|
|
272
|
+
## Documentation
|
|
273
|
+
|
|
274
|
+
- [`docs/architecture.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/architecture.md): module map, data flow, the switch algorithm, transaction/rollback design.
|
|
275
|
+
- [`docs/opencode-auth.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/opencode-auth.md): how OpenCode stores provider credentials and refreshes OpenAI credentials.
|
|
276
|
+
- [`docs/provider-support.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/provider-support.md): provider matrix, source evidence, live-testing status, and deferred OAuth cases.
|
|
277
|
+
- [`docs/provider-research-prompt.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/provider-research-prompt.md): safe copy-paste prompt for provider-research issues.
|
|
278
|
+
- [`docs/security.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/security.md): threat model, storage mechanism comparison, what's explicitly out of scope.
|
|
279
|
+
- [`docs/testing.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/testing.md): testing strategy and how to run the suite.
|
|
280
|
+
- [`docs/roadmap.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/roadmap.md): what v1 ships and the known gaps.
|
|
281
|
+
- [`docs/releasing.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/releasing.md): how to cut a CLI (PyPI) or TUI plugin (npm) release.
|
|
282
|
+
- [`AGENTS.md`](https://github.com/leinardi/opencode-swap/blob/main/AGENTS.md): project guide for AI coding agents working in this repo.
|
|
283
|
+
- [`CONTRIBUTING.md`](https://github.com/leinardi/opencode-swap/blob/main/CONTRIBUTING.md): how to set up, test, and submit changes.
|
|
284
|
+
- [`SECURITY.md`](https://github.com/leinardi/opencode-swap/blob/main/SECURITY.md): how to report a vulnerability.
|
|
285
|
+
|
|
286
|
+
## Development
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
make python-sync
|
|
290
|
+
make python-test # No network/keychain access required
|
|
291
|
+
make doctor
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Run `make help` for all development targets. Direct `uv` commands remain
|
|
295
|
+
available when Make is not installed.
|
|
296
|
+
|
|
297
|
+
See [`docs/testing.md`](https://github.com/leinardi/opencode-swap/blob/main/docs/testing.md)
|
|
298
|
+
for what the suite covers and how it keeps real macOS Keychain data and your
|
|
299
|
+
real `auth.json` out of the loop,
|
|
300
|
+
[`CONTRIBUTING.md`](https://github.com/leinardi/opencode-swap/blob/main/CONTRIBUTING.md)
|
|
301
|
+
for the PR workflow, and
|
|
302
|
+
[`AGENTS.md`](https://github.com/leinardi/opencode-swap/blob/main/AGENTS.md)
|
|
303
|
+
for conventions to follow when changing code here.
|
|
304
|
+
|
|
305
|
+
## License
|
|
306
|
+
|
|
307
|
+
MIT
|