multi-codex 0.7.0__tar.gz → 0.9.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.
- {multi_codex-0.7.0 → multi_codex-0.9.0}/PKG-INFO +73 -18
- multi_codex-0.7.0/src/multi_codex.egg-info/PKG-INFO → multi_codex-0.9.0/README.md +64 -33
- {multi_codex-0.7.0 → multi_codex-0.9.0}/pyproject.toml +8 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/__init__.py +1 -1
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/accounts.py +31 -4
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/apps.py +8 -5
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/cli.py +326 -29
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/completion.py +2 -2
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/config.py +72 -3
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/doctor.py +5 -4
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/launcher.py +7 -1
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/migrate.py +2 -2
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/platform.py +2 -1
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/shared.py +4 -0
- multi_codex-0.9.0/src/multi_codex/shellpath.py +52 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/switch.py +5 -1
- multi_codex-0.7.0/README.md → multi_codex-0.9.0/src/multi_codex.egg-info/PKG-INFO +88 -17
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex.egg-info/SOURCES.txt +3 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_ergonomics.py +12 -9
- multi_codex-0.9.0/tests/test_everyday.py +400 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_insight.py +3 -3
- multi_codex-0.9.0/tests/test_isolation.py +428 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_onboarding.py +1 -1
- {multi_codex-0.7.0 → multi_codex-0.9.0}/LICENSE +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/setup.cfg +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/__main__.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/actions.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/binding.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/fsutil.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/identity.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/lock.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/usage.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex.egg-info/dependency_links.txt +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex.egg-info/entry_points.txt +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex.egg-info/top_level.txt +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_accounts.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_apps.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_binding.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_config.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_config_copy.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_fsutil.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_install.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_migrate.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_onboarding_commands.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_platform.py +0 -0
- {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_switch.py +0 -0
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multi-codex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Run several Codex CLI accounts side by side: separate CODEX_HOME directories, launchers and proxies.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/jakoes-wu/multi-codex
|
|
7
|
+
Project-URL: Changelog, https://github.com/jakoes-wu/multi-codex/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/jakoes-wu/multi-codex/issues
|
|
9
|
+
Keywords: codex,codex-cli,openai,multi-account,account-switcher,proxy,cli
|
|
7
10
|
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
8
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
13
|
Classifier: Operating System :: MacOS
|
|
10
14
|
Classifier: Operating System :: POSIX :: Linux
|
|
11
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
12
20
|
Requires-Python: >=3.8
|
|
13
21
|
Description-Content-Type: text/markdown
|
|
14
22
|
License-File: LICENSE
|
|
@@ -18,6 +26,12 @@ Dynamic: license-file
|
|
|
18
26
|
|
|
19
27
|
**English** | [简体中文](README.zh-CN.md)
|
|
20
28
|
|
|
29
|
+
[](https://github.com/jakoes-wu/multi-codex/releases)
|
|
30
|
+
[](https://github.com/jakoes-wu/multi-codex/actions/workflows/ci.yml)
|
|
31
|
+
[](https://pypi.org/project/multi-codex/)
|
|
32
|
+

|
|
33
|
+
[](LICENSE)
|
|
34
|
+
|
|
21
35
|
Use several [Codex CLI](https://github.com/openai/codex) accounts on one machine, at the same time. Each account keeps its own login, settings, history and, if you like, its own proxy. No more logging out and in again.
|
|
22
36
|
|
|
23
37
|
```sh
|
|
@@ -26,6 +40,10 @@ codex-personal # Codex, logged in with your personal account, in another te
|
|
|
26
40
|
multi-codex list # which account is logged in as whom
|
|
27
41
|
```
|
|
28
42
|
|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
<sub>The accounts in the demo are examples.</sub>
|
|
46
|
+
|
|
29
47
|
## How it works
|
|
30
48
|
|
|
31
49
|
Codex keeps everything (settings, credentials, sessions) in one directory, `CODEX_HOME`, which is `~/.codex` by default. multi-codex gives every account its own directory and a small launcher command, `codex-<name>`, that starts Codex with that directory:
|
|
@@ -47,10 +65,12 @@ curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.
|
|
|
47
65
|
multi-codex --version
|
|
48
66
|
```
|
|
49
67
|
|
|
50
|
-
|
|
68
|
+
Other ways: `pipx install multi-codex` (from PyPI), or on macOS `brew install jakoes-wu/tap/multi-codex`. Either way, the `codex-<name>` launchers still go to `~/.local/bin`.
|
|
69
|
+
|
|
70
|
+
The `multi-codex` command and the `codex-<name>` launchers go to `~/.local/bin`. If your shell says `command not found`, that directory is not on your `PATH` yet. The installer, `multi-codex add` and `multi-codex doctor` then print the exact command for your shell (zsh, bash or fish), but never edit your shell profile themselves. In zsh, for example, run this once (bash on macOS uses `~/.bash_profile`, bash on Linux `~/.bashrc`) and open a new terminal:
|
|
51
71
|
|
|
52
72
|
```sh
|
|
53
|
-
export PATH="$HOME/.local/bin:$PATH"
|
|
73
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
|
|
54
74
|
```
|
|
55
75
|
|
|
56
76
|
In fish, run `fish_add_path ~/.local/bin` once instead.
|
|
@@ -70,7 +90,7 @@ Each command creates a directory (`~/.cx/work`) and a launcher (`codex-work`). A
|
|
|
70
90
|
|
|
71
91
|
After `add`, multi-codex prints the next step: the login command, and a warning if `~/.local/bin` is not on your `PATH` yet. Running `multi-codex` without arguments shows these steps again.
|
|
72
92
|
|
|
73
|
-
If an account should go through a proxy, give it a local port or a URL, for example `multi-codex add work --proxy 7901` (the same as `http://127.0.0.1:7901`). See [Proxy values](#proxy-values).
|
|
93
|
+
If an account should go through a proxy, give it a local port or a URL, for example `multi-codex add work --proxy 7901` (the same as `http://127.0.0.1:7901`). See [Proxy values](#proxy-values). To change an existing account later, use `multi-codex set`, for example `multi-codex set work --proxy 7902`.
|
|
74
94
|
|
|
75
95
|
### 2. Log in once per account
|
|
76
96
|
|
|
@@ -91,7 +111,7 @@ codex-personal resume
|
|
|
91
111
|
### 4. Check that everything is right
|
|
92
112
|
|
|
93
113
|
```sh
|
|
94
|
-
multi-codex list #
|
|
114
|
+
multi-codex list # who is logged in, proxy, sharing, usage and anything that needs fixing
|
|
95
115
|
multi-codex usage # 5-hour and weekly usage; empty until you have used an account (--live asks right away)
|
|
96
116
|
multi-codex doctor # finds problems and prints the command that fixes each one
|
|
97
117
|
```
|
|
@@ -115,13 +135,14 @@ Without a name, the account is named after the e-mail address in `~/.codex/auth.
|
|
|
115
135
|
| Open the desktop app with an account (macOS) | `multi-codex app work` | [VS Code and the desktop app](#vs-code-and-the-desktop-app-experimental) |
|
|
116
136
|
| Change the account that plain `codex` and the Dock apps use | `multi-codex use work` (after `migrate-default`) | [Default account](#default-account) |
|
|
117
137
|
| Always use one account inside a project | In the project directory: `multi-codex bind work`, then `multi-codex run` | [Directory bindings](#directory-bindings) |
|
|
118
|
-
| Set or change an account's proxy | `multi-codex
|
|
119
|
-
| Share `AGENTS.md`, skills and rules between accounts | Put them in `~/.codex-shared`, run `multi-codex
|
|
138
|
+
| Set or change an account's proxy | `multi-codex set work --proxy 7901` | [Proxy values](#proxy-values) |
|
|
139
|
+
| Share `AGENTS.md`, skills and rules between accounts | Put them in `~/.codex-shared`, then run `multi-codex set work --shared` | [Shared resources](#shared-resources) |
|
|
120
140
|
| Start a new account with another account's settings | `multi-codex add new --config-from work` | [Copying settings](#copying-settings-from-another-account) |
|
|
121
141
|
| Give an account extra environment variables | `multi-codex env work KEY=VALUE` | [Environment variables](#per-account-environment-variables) |
|
|
122
142
|
| Set up all accounts on a new machine | `curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.sh \| sh -s -- --config accounts.json` | [Declarative setup](#declarative-setup-with-apply) |
|
|
123
143
|
| Get tab completion | `eval "$(multi-codex completion zsh)"` | [Shell completion](#shell-completion) |
|
|
124
144
|
| See what a command would change | add `--dry-run` | [Commands](#commands) |
|
|
145
|
+
| Rename an account | `multi-codex rename work client-a` (the directory and login stay) | [Renaming accounts](#renaming-accounts) |
|
|
125
146
|
| Remove an account | `multi-codex remove work` (the directory is kept) | [Commands](#commands) |
|
|
126
147
|
|
|
127
148
|
More questions are answered in the [FAQ](#faq).
|
|
@@ -139,12 +160,14 @@ More questions are answered in the [FAQ](#faq).
|
|
|
139
160
|
| ---- | ---- |
|
|
140
161
|
| `multi-codex init [--root DIR] [--bin-dir DIR] [--shared-dir DIR] [--shared-items A,B]` | Create or change global settings. |
|
|
141
162
|
| `multi-codex migrate-default [NAME] [--source DIR] [--copy] [--keep-backup] [--proxy P] [--skip-process-check] [--accept-relogin]` | Turn the default directory into an account. Without NAME, the e-mail address in its `auth.json` is used. |
|
|
142
|
-
| `multi-codex add NAME [--proxy P] [--shared \| --no-shared] [--adopt] [--config-from OTHER]` | Add an account, adopt an existing directory, or change its options. `--config-from` copies `config.toml` from another account once. |
|
|
163
|
+
| `multi-codex add NAME [--proxy P] [--shared [DIR] \| --no-shared] [--shared-exclude ITEM] [--shared-include ITEM] [--adopt] [--config-from OTHER]` | Add an account, adopt an existing directory, or change its options. `--config-from` copies `config.toml` from another account once. |
|
|
164
|
+
| `multi-codex set NAME [same options as add]` | Change an existing account; same options as `add`, but never creates one. |
|
|
165
|
+
| `multi-codex rename OLD NEW` | Rename an account and its launcher; the directory, login and links stay. |
|
|
143
166
|
| `multi-codex login NAME [-- ARGS]` | Run `codex login` with an account's environment; arguments after `--` go to `codex login`. Does not need `~/.local/bin` on `PATH`. |
|
|
144
167
|
| `multi-codex proxy NAME PORT\|URL\|off\|inherit` | Set an account's proxy. |
|
|
145
168
|
| `multi-codex remove NAME` | Unregister an account and delete its launcher. **The account directory is kept.** |
|
|
146
169
|
| `multi-codex apply [-f FILE]` | Converge everything to the configuration (or to `FILE`). |
|
|
147
|
-
| `multi-codex list [--json]` | Show accounts
|
|
170
|
+
| `multi-codex list [-v] [--json]` | Show accounts: who is logged in, proxy, sharing, usage and status. `-v` shows the full table. |
|
|
148
171
|
| `multi-codex usage [NAME ...] [--live] [--timeout SEC] [--json]` | Show rate-limit usage. |
|
|
149
172
|
| `multi-codex doctor [--json]` | Check the installation, configuration and accounts. Read-only. |
|
|
150
173
|
| `multi-codex run [NAME] [-- COMMAND ...]` | Run a command (default: `codex`) with an account's environment. Without NAME, the account bound to the current directory is used. |
|
|
@@ -157,11 +180,23 @@ More questions are answered in the [FAQ](#faq).
|
|
|
157
180
|
| `multi-codex restore NAME [--skip-process-check] [--accept-relogin]` | Undo `migrate-default`: move the account back to `~/.codex`. |
|
|
158
181
|
| `multi-codex completion bash\|zsh\|fish` | Print a shell completion script. |
|
|
159
182
|
|
|
160
|
-
Every write command accepts `--dry-run`. `init`, `add`, `proxy`, `remove`, `apply`, `bind`, `unbind` and `env` print only the items they change (or `already up to date`); `-v` / `--verbose` also prints unchanged items. `list`, `usage` and `doctor` never change anything; with `--json` they print a single JSON object on stdout (with a `"version": 1` field) and keep warnings on stderr. Use `--json` in scripts: the table layout is not guaranteed to stay the same.
|
|
183
|
+
Every write command accepts `--dry-run`. `init`, `add`, `set`, `rename`, `proxy`, `remove`, `apply`, `bind`, `unbind` and `env` print only the items they change (or `already up to date`); `-v` / `--verbose` also prints unchanged items. For `list`, `-v` means the full table instead. `list`, `usage` and `doctor` never change anything; with `--json` they print a single JSON object on stdout (with a `"version": 1` field) and keep warnings on stderr. Use `--json` in scripts: the table layout is not guaranteed to stay the same.
|
|
161
184
|
|
|
162
185
|
### Login and usage
|
|
163
186
|
|
|
164
|
-
`list`
|
|
187
|
+
`list` shows one line per account:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
default: work
|
|
191
|
+
NAME LOGIN PROXY SHARED USAGE STATUS
|
|
192
|
+
work w@example.com http://127.0.0.1:7901 yes 5h 23%, 7d 41% ok
|
|
193
|
+
home - inherit no - not logged in
|
|
194
|
+
run `multi-codex doctor` for details
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
LOGIN is read from each account's local `auth.json`: the e-mail address, `api-key`, `-` (not logged in), `keyring` (credentials are in the system keyring and cannot be read from files) or `unreadable`. No token is ever printed, and nothing is sent anywhere. USAGE is the last usage snapshot in the account's local session logs, the same data as `multi-codex usage` (`reset` means the window has reset since; `*` means `sessions` is shared with other accounts, so the numbers may belong to another one). STATUS lists what needs fixing (`missing-dir`, `launcher missing`/`stale`/`conflict`, `not logged in`); `doctor` explains each problem. If two accounts are logged in as the same ChatGPT user and workspace, `list` warns you: they share one quota.
|
|
198
|
+
|
|
199
|
+
`list -v` prints the full table of earlier versions: the root, launcher and shared directories, and the DIR, LAUNCHER and PLAN columns. PLAN is the plan recorded when the current token was issued; it is updated the next time Codex refreshes the token.
|
|
165
200
|
|
|
166
201
|
`multi-codex usage` reads the most recent rate-limit snapshot from each account's session logs (`sessions/` and `archived_sessions/`). It is offline but can be out of date; a window that has reset since the snapshot is shown as `reset since snapshot`.
|
|
167
202
|
|
|
@@ -200,6 +235,14 @@ multi-codex unbind # remove the binding of the curre
|
|
|
200
235
|
|
|
201
236
|
`multi-codex add new --config-from work` copies `config.toml` from `work` into `new` once; afterwards the two files are independent. An existing `config.toml` with different content is a conflict (nothing is written), and so is copying into an account that shares `config.toml`. The copy includes everything in the file, such as `cli_auth_credentials_store` or absolute paths that point into the other account.
|
|
202
237
|
|
|
238
|
+
### Renaming accounts
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
multi-codex rename work client-a
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The account and its launcher get the new name (`codex-client-a`; `codex-work` is deleted), and directory bindings and the default account follow. The directory stays where it is, so the login, sessions and shared links are kept; `config.json` records it as `"dir": "work"` (in `list --json`, `dir` is the full path instead). A name that is still used as another account's directory, such as `work` after this rename, cannot be given to a new account. Renaming only the letter case is not supported. Before downgrading to a version older than 0.9, rename the account back: older versions ignore `dir` and would use `<root>/client-a`.
|
|
245
|
+
|
|
203
246
|
### Per-account environment variables
|
|
204
247
|
|
|
205
248
|
```sh
|
|
@@ -209,6 +252,8 @@ multi-codex env work --unset TERM_PROGRAM
|
|
|
209
252
|
multi-codex env work --clear
|
|
210
253
|
```
|
|
211
254
|
|
|
255
|
+
Launchers (and `run`, `login`, `code` and `usage --live`, which use the same environment) clear `CODEX_API_KEY`, `CODEX_ACCESS_TOKEN` and `CODEX_SQLITE_HOME` inherited from your shell, so one exported API key or database location does not leak into every account. An account that needs an API key sets it here, for example `multi-codex env work CODEX_API_KEY=sk-…`; per-account variables are applied after the clearing.
|
|
256
|
+
|
|
212
257
|
The variables are stored in `config.json` (`accounts.<name>.env`) and written into the launcher. Values are used literally (no `$VAR` expansion). `CODEX_HOME` and the proxy variables are reserved: use `multi-codex proxy` for proxies. A launcher with environment variables is readable only by you (mode 0700), and `list --json` shows only the variable names; still, the values are stored in plain text, so do not put secrets there that need stronger protection. Older versions of multi-codex ignore the `env` field and drop it on their next write; run `multi-codex env NAME --clear` before downgrading.
|
|
213
258
|
|
|
214
259
|
### VS Code and the desktop app (experimental)
|
|
@@ -294,7 +339,7 @@ Progress is recorded in `~/.config/multi-codex/migrate-journal.json`. If the mig
|
|
|
294
339
|
|
|
295
340
|
Sockets and FIFOs (runtime files such as `ipc.sock`) are not copied in copy mode. On macOS, copy mode does not preserve extended attributes.
|
|
296
341
|
|
|
297
|
-
If `CODEX_HOME`, `CODEX_SQLITE_HOME`, `CODEX_API_KEY` or `CODEX_ACCESS_TOKEN` is set in your environment, multi-codex warns you:
|
|
342
|
+
If `CODEX_HOME`, `CODEX_SQLITE_HOME`, `CODEX_API_KEY` or `CODEX_ACCESS_TOKEN` is set in your environment, multi-codex warns you: launchers set or clear them, but plain `codex` still uses them.
|
|
298
343
|
|
|
299
344
|
### Undoing a migration
|
|
300
345
|
|
|
@@ -317,13 +362,20 @@ If a migration stops with an error and you want to abandon it: the error message
|
|
|
317
362
|
## Shared resources
|
|
318
363
|
|
|
319
364
|
```sh
|
|
320
|
-
multi-codex
|
|
321
|
-
multi-codex add work --shared
|
|
365
|
+
multi-codex set work --shared
|
|
322
366
|
```
|
|
323
367
|
|
|
324
|
-
Put the items you want to share
|
|
368
|
+
Without a directory, `--shared` uses the shared directory already configured, or `~/.codex-shared` if none is set yet. Put the items you want to share there first: items missing from it are skipped (`skip`), and nothing is linked for them; if none of them is there, multi-codex says so. To choose which items are shared, use `multi-codex init --shared-items AGENTS.md,skills,rules,agents`.
|
|
325
369
|
|
|
326
|
-
|
|
370
|
+
`--shared DIR` (for example `multi-codex set work --shared ~/my-shared`) changes the shared directory for **every** shared account, not only this one: their links move to the new directory, and links to items missing there are removed. Write the account name before `--shared`; `add --shared work` would take `work` as the directory.
|
|
371
|
+
|
|
372
|
+
multi-codex creates the missing links and remembers which links it created. Turning sharing off removes only those links; links you made yourself are left alone. A real file or directory at a link location is a conflict and is never overwritten.
|
|
373
|
+
|
|
374
|
+
If you already linked an account to the shared directory by hand, `multi-codex set NAME --shared --adopt` takes those links over without recreating them: from then on, turning sharing off removes them as well. Only links that already point to the matching shared item are adopted.
|
|
375
|
+
|
|
376
|
+
One account can opt out of some shared items: `multi-codex set work --shared-exclude skills` removes the `skills` link multi-codex created in `work` (as if sharing were off for that item) and no longer creates it; `--shared-include skills` undoes that. Both options can be repeated, names match `shared.items` exactly, and `list` shows the exclusions as `yes (not: skills)`. To exclude `config.toml` and then copy another account's settings into it, run `--shared-exclude config.toml` and `--config-from` as two separate commands.
|
|
377
|
+
|
|
378
|
+
Items that hold one account's own state can never be shared: `shared.items` must not contain the entries marked "No" in the table below. A configuration that lists one fails to load and names the item; remove it from `config.json`.
|
|
327
379
|
|
|
328
380
|
### What can be shared
|
|
329
381
|
|
|
@@ -339,7 +391,7 @@ Based on the Codex source code (openai/codex at `6b4daafd`):
|
|
|
339
391
|
| `history.jsonl` | Prompt history | Yes | Reads and writes are file-locked; the histories of the accounts are merged. |
|
|
340
392
|
| `auth.json`, `secrets/`, `.credentials.json`, `.env` | Credentials | **No** | They are the account. |
|
|
341
393
|
| `installation_id` | Installation identifier | No | Sent with requests; sharing makes several accounts look like one installation. |
|
|
342
|
-
| `*.sqlite` (`state_5.sqlite`, …) | Threads, logs, memories | **No** | `state_5.sqlite` records account IDs. |
|
|
394
|
+
| `*.sqlite`, `*.sqlite-wal`, `*.sqlite-shm` (`state_5.sqlite`, …), `sqlite/` | Threads, logs, memories | **No** | `state_5.sqlite` records account IDs. |
|
|
343
395
|
| `sessions/`, `archived_sessions/`, `session_index.jsonl` | Session logs | No | The index has only an in-process lock; sessions record the account that created them. |
|
|
344
396
|
| `models_cache.json`, `cache/` | Caches | Not needed | Keyed by the account; a mismatch is a cache miss. |
|
|
345
397
|
| `app-server-control/`, `app-server-daemon/`, `packages/`, `tmp/`, `.tmp/`, `log/`, `shell_snapshots/` | Runtime state | No | Per process or per session. |
|
|
@@ -402,8 +454,11 @@ Run the installer again; configuration, accounts and launchers are not touched:
|
|
|
402
454
|
```sh
|
|
403
455
|
curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.sh | sh
|
|
404
456
|
multi-codex --version
|
|
457
|
+
multi-codex apply # after upgrading from 0.8 or older: rewrites the launchers
|
|
405
458
|
```
|
|
406
459
|
|
|
460
|
+
0.9 changed what the launchers contain. Until `multi-codex apply` (or any other write command) rewrites them, `list` shows `launcher stale` and `usage --live` refuses to run; the old launchers keep working.
|
|
461
|
+
|
|
407
462
|
## Exit codes
|
|
408
463
|
|
|
409
464
|
| Code | Meaning |
|
|
@@ -424,7 +479,7 @@ cd multi-codex
|
|
|
424
479
|
./install.sh
|
|
425
480
|
```
|
|
426
481
|
|
|
427
|
-
With pipx: `pipx install git+https://github.com/jakoes-wu/multi-codex
|
|
482
|
+
With pipx: `pipx install multi-codex` (the latest release from PyPI), or `pipx install git+https://github.com/jakoes-wu/multi-codex` for the current `main` branch.
|
|
428
483
|
|
|
429
484
|
The tool goes to `~/.local/share/multi-codex` and the `multi-codex` command to `~/.local/bin`. Use `--prefix DIR` to install somewhere else. Run `./install.sh --help` for all options.
|
|
430
485
|
|
|
@@ -1,23 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: multi-codex
|
|
3
|
-
Version: 0.7.0
|
|
4
|
-
Summary: Run several Codex CLI accounts side by side: separate CODEX_HOME directories, launchers and proxies.
|
|
5
|
-
License: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/jakoes-wu/multi-codex
|
|
7
|
-
Classifier: Environment :: Console
|
|
8
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
-
Classifier: Operating System :: MacOS
|
|
10
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Requires-Python: >=3.8
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
|
-
Dynamic: license-file
|
|
16
|
-
|
|
17
1
|
# multi-codex
|
|
18
2
|
|
|
19
3
|
**English** | [简体中文](README.zh-CN.md)
|
|
20
4
|
|
|
5
|
+
[](https://github.com/jakoes-wu/multi-codex/releases)
|
|
6
|
+
[](https://github.com/jakoes-wu/multi-codex/actions/workflows/ci.yml)
|
|
7
|
+
[](https://pypi.org/project/multi-codex/)
|
|
8
|
+

|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
21
11
|
Use several [Codex CLI](https://github.com/openai/codex) accounts on one machine, at the same time. Each account keeps its own login, settings, history and, if you like, its own proxy. No more logging out and in again.
|
|
22
12
|
|
|
23
13
|
```sh
|
|
@@ -26,6 +16,10 @@ codex-personal # Codex, logged in with your personal account, in another te
|
|
|
26
16
|
multi-codex list # which account is logged in as whom
|
|
27
17
|
```
|
|
28
18
|
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
<sub>The accounts in the demo are examples.</sub>
|
|
22
|
+
|
|
29
23
|
## How it works
|
|
30
24
|
|
|
31
25
|
Codex keeps everything (settings, credentials, sessions) in one directory, `CODEX_HOME`, which is `~/.codex` by default. multi-codex gives every account its own directory and a small launcher command, `codex-<name>`, that starts Codex with that directory:
|
|
@@ -47,10 +41,12 @@ curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.
|
|
|
47
41
|
multi-codex --version
|
|
48
42
|
```
|
|
49
43
|
|
|
50
|
-
|
|
44
|
+
Other ways: `pipx install multi-codex` (from PyPI), or on macOS `brew install jakoes-wu/tap/multi-codex`. Either way, the `codex-<name>` launchers still go to `~/.local/bin`.
|
|
45
|
+
|
|
46
|
+
The `multi-codex` command and the `codex-<name>` launchers go to `~/.local/bin`. If your shell says `command not found`, that directory is not on your `PATH` yet. The installer, `multi-codex add` and `multi-codex doctor` then print the exact command for your shell (zsh, bash or fish), but never edit your shell profile themselves. In zsh, for example, run this once (bash on macOS uses `~/.bash_profile`, bash on Linux `~/.bashrc`) and open a new terminal:
|
|
51
47
|
|
|
52
48
|
```sh
|
|
53
|
-
export PATH="$HOME/.local/bin:$PATH"
|
|
49
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
|
|
54
50
|
```
|
|
55
51
|
|
|
56
52
|
In fish, run `fish_add_path ~/.local/bin` once instead.
|
|
@@ -70,7 +66,7 @@ Each command creates a directory (`~/.cx/work`) and a launcher (`codex-work`). A
|
|
|
70
66
|
|
|
71
67
|
After `add`, multi-codex prints the next step: the login command, and a warning if `~/.local/bin` is not on your `PATH` yet. Running `multi-codex` without arguments shows these steps again.
|
|
72
68
|
|
|
73
|
-
If an account should go through a proxy, give it a local port or a URL, for example `multi-codex add work --proxy 7901` (the same as `http://127.0.0.1:7901`). See [Proxy values](#proxy-values).
|
|
69
|
+
If an account should go through a proxy, give it a local port or a URL, for example `multi-codex add work --proxy 7901` (the same as `http://127.0.0.1:7901`). See [Proxy values](#proxy-values). To change an existing account later, use `multi-codex set`, for example `multi-codex set work --proxy 7902`.
|
|
74
70
|
|
|
75
71
|
### 2. Log in once per account
|
|
76
72
|
|
|
@@ -91,7 +87,7 @@ codex-personal resume
|
|
|
91
87
|
### 4. Check that everything is right
|
|
92
88
|
|
|
93
89
|
```sh
|
|
94
|
-
multi-codex list #
|
|
90
|
+
multi-codex list # who is logged in, proxy, sharing, usage and anything that needs fixing
|
|
95
91
|
multi-codex usage # 5-hour and weekly usage; empty until you have used an account (--live asks right away)
|
|
96
92
|
multi-codex doctor # finds problems and prints the command that fixes each one
|
|
97
93
|
```
|
|
@@ -115,13 +111,14 @@ Without a name, the account is named after the e-mail address in `~/.codex/auth.
|
|
|
115
111
|
| Open the desktop app with an account (macOS) | `multi-codex app work` | [VS Code and the desktop app](#vs-code-and-the-desktop-app-experimental) |
|
|
116
112
|
| Change the account that plain `codex` and the Dock apps use | `multi-codex use work` (after `migrate-default`) | [Default account](#default-account) |
|
|
117
113
|
| Always use one account inside a project | In the project directory: `multi-codex bind work`, then `multi-codex run` | [Directory bindings](#directory-bindings) |
|
|
118
|
-
| Set or change an account's proxy | `multi-codex
|
|
119
|
-
| Share `AGENTS.md`, skills and rules between accounts | Put them in `~/.codex-shared`, run `multi-codex
|
|
114
|
+
| Set or change an account's proxy | `multi-codex set work --proxy 7901` | [Proxy values](#proxy-values) |
|
|
115
|
+
| Share `AGENTS.md`, skills and rules between accounts | Put them in `~/.codex-shared`, then run `multi-codex set work --shared` | [Shared resources](#shared-resources) |
|
|
120
116
|
| Start a new account with another account's settings | `multi-codex add new --config-from work` | [Copying settings](#copying-settings-from-another-account) |
|
|
121
117
|
| Give an account extra environment variables | `multi-codex env work KEY=VALUE` | [Environment variables](#per-account-environment-variables) |
|
|
122
118
|
| Set up all accounts on a new machine | `curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.sh \| sh -s -- --config accounts.json` | [Declarative setup](#declarative-setup-with-apply) |
|
|
123
119
|
| Get tab completion | `eval "$(multi-codex completion zsh)"` | [Shell completion](#shell-completion) |
|
|
124
120
|
| See what a command would change | add `--dry-run` | [Commands](#commands) |
|
|
121
|
+
| Rename an account | `multi-codex rename work client-a` (the directory and login stay) | [Renaming accounts](#renaming-accounts) |
|
|
125
122
|
| Remove an account | `multi-codex remove work` (the directory is kept) | [Commands](#commands) |
|
|
126
123
|
|
|
127
124
|
More questions are answered in the [FAQ](#faq).
|
|
@@ -139,12 +136,14 @@ More questions are answered in the [FAQ](#faq).
|
|
|
139
136
|
| ---- | ---- |
|
|
140
137
|
| `multi-codex init [--root DIR] [--bin-dir DIR] [--shared-dir DIR] [--shared-items A,B]` | Create or change global settings. |
|
|
141
138
|
| `multi-codex migrate-default [NAME] [--source DIR] [--copy] [--keep-backup] [--proxy P] [--skip-process-check] [--accept-relogin]` | Turn the default directory into an account. Without NAME, the e-mail address in its `auth.json` is used. |
|
|
142
|
-
| `multi-codex add NAME [--proxy P] [--shared \| --no-shared] [--adopt] [--config-from OTHER]` | Add an account, adopt an existing directory, or change its options. `--config-from` copies `config.toml` from another account once. |
|
|
139
|
+
| `multi-codex add NAME [--proxy P] [--shared [DIR] \| --no-shared] [--shared-exclude ITEM] [--shared-include ITEM] [--adopt] [--config-from OTHER]` | Add an account, adopt an existing directory, or change its options. `--config-from` copies `config.toml` from another account once. |
|
|
140
|
+
| `multi-codex set NAME [same options as add]` | Change an existing account; same options as `add`, but never creates one. |
|
|
141
|
+
| `multi-codex rename OLD NEW` | Rename an account and its launcher; the directory, login and links stay. |
|
|
143
142
|
| `multi-codex login NAME [-- ARGS]` | Run `codex login` with an account's environment; arguments after `--` go to `codex login`. Does not need `~/.local/bin` on `PATH`. |
|
|
144
143
|
| `multi-codex proxy NAME PORT\|URL\|off\|inherit` | Set an account's proxy. |
|
|
145
144
|
| `multi-codex remove NAME` | Unregister an account and delete its launcher. **The account directory is kept.** |
|
|
146
145
|
| `multi-codex apply [-f FILE]` | Converge everything to the configuration (or to `FILE`). |
|
|
147
|
-
| `multi-codex list [--json]` | Show accounts
|
|
146
|
+
| `multi-codex list [-v] [--json]` | Show accounts: who is logged in, proxy, sharing, usage and status. `-v` shows the full table. |
|
|
148
147
|
| `multi-codex usage [NAME ...] [--live] [--timeout SEC] [--json]` | Show rate-limit usage. |
|
|
149
148
|
| `multi-codex doctor [--json]` | Check the installation, configuration and accounts. Read-only. |
|
|
150
149
|
| `multi-codex run [NAME] [-- COMMAND ...]` | Run a command (default: `codex`) with an account's environment. Without NAME, the account bound to the current directory is used. |
|
|
@@ -157,11 +156,23 @@ More questions are answered in the [FAQ](#faq).
|
|
|
157
156
|
| `multi-codex restore NAME [--skip-process-check] [--accept-relogin]` | Undo `migrate-default`: move the account back to `~/.codex`. |
|
|
158
157
|
| `multi-codex completion bash\|zsh\|fish` | Print a shell completion script. |
|
|
159
158
|
|
|
160
|
-
Every write command accepts `--dry-run`. `init`, `add`, `proxy`, `remove`, `apply`, `bind`, `unbind` and `env` print only the items they change (or `already up to date`); `-v` / `--verbose` also prints unchanged items. `list`, `usage` and `doctor` never change anything; with `--json` they print a single JSON object on stdout (with a `"version": 1` field) and keep warnings on stderr. Use `--json` in scripts: the table layout is not guaranteed to stay the same.
|
|
159
|
+
Every write command accepts `--dry-run`. `init`, `add`, `set`, `rename`, `proxy`, `remove`, `apply`, `bind`, `unbind` and `env` print only the items they change (or `already up to date`); `-v` / `--verbose` also prints unchanged items. For `list`, `-v` means the full table instead. `list`, `usage` and `doctor` never change anything; with `--json` they print a single JSON object on stdout (with a `"version": 1` field) and keep warnings on stderr. Use `--json` in scripts: the table layout is not guaranteed to stay the same.
|
|
161
160
|
|
|
162
161
|
### Login and usage
|
|
163
162
|
|
|
164
|
-
`list`
|
|
163
|
+
`list` shows one line per account:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
default: work
|
|
167
|
+
NAME LOGIN PROXY SHARED USAGE STATUS
|
|
168
|
+
work w@example.com http://127.0.0.1:7901 yes 5h 23%, 7d 41% ok
|
|
169
|
+
home - inherit no - not logged in
|
|
170
|
+
run `multi-codex doctor` for details
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
LOGIN is read from each account's local `auth.json`: the e-mail address, `api-key`, `-` (not logged in), `keyring` (credentials are in the system keyring and cannot be read from files) or `unreadable`. No token is ever printed, and nothing is sent anywhere. USAGE is the last usage snapshot in the account's local session logs, the same data as `multi-codex usage` (`reset` means the window has reset since; `*` means `sessions` is shared with other accounts, so the numbers may belong to another one). STATUS lists what needs fixing (`missing-dir`, `launcher missing`/`stale`/`conflict`, `not logged in`); `doctor` explains each problem. If two accounts are logged in as the same ChatGPT user and workspace, `list` warns you: they share one quota.
|
|
174
|
+
|
|
175
|
+
`list -v` prints the full table of earlier versions: the root, launcher and shared directories, and the DIR, LAUNCHER and PLAN columns. PLAN is the plan recorded when the current token was issued; it is updated the next time Codex refreshes the token.
|
|
165
176
|
|
|
166
177
|
`multi-codex usage` reads the most recent rate-limit snapshot from each account's session logs (`sessions/` and `archived_sessions/`). It is offline but can be out of date; a window that has reset since the snapshot is shown as `reset since snapshot`.
|
|
167
178
|
|
|
@@ -200,6 +211,14 @@ multi-codex unbind # remove the binding of the curre
|
|
|
200
211
|
|
|
201
212
|
`multi-codex add new --config-from work` copies `config.toml` from `work` into `new` once; afterwards the two files are independent. An existing `config.toml` with different content is a conflict (nothing is written), and so is copying into an account that shares `config.toml`. The copy includes everything in the file, such as `cli_auth_credentials_store` or absolute paths that point into the other account.
|
|
202
213
|
|
|
214
|
+
### Renaming accounts
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
multi-codex rename work client-a
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The account and its launcher get the new name (`codex-client-a`; `codex-work` is deleted), and directory bindings and the default account follow. The directory stays where it is, so the login, sessions and shared links are kept; `config.json` records it as `"dir": "work"` (in `list --json`, `dir` is the full path instead). A name that is still used as another account's directory, such as `work` after this rename, cannot be given to a new account. Renaming only the letter case is not supported. Before downgrading to a version older than 0.9, rename the account back: older versions ignore `dir` and would use `<root>/client-a`.
|
|
221
|
+
|
|
203
222
|
### Per-account environment variables
|
|
204
223
|
|
|
205
224
|
```sh
|
|
@@ -209,6 +228,8 @@ multi-codex env work --unset TERM_PROGRAM
|
|
|
209
228
|
multi-codex env work --clear
|
|
210
229
|
```
|
|
211
230
|
|
|
231
|
+
Launchers (and `run`, `login`, `code` and `usage --live`, which use the same environment) clear `CODEX_API_KEY`, `CODEX_ACCESS_TOKEN` and `CODEX_SQLITE_HOME` inherited from your shell, so one exported API key or database location does not leak into every account. An account that needs an API key sets it here, for example `multi-codex env work CODEX_API_KEY=sk-…`; per-account variables are applied after the clearing.
|
|
232
|
+
|
|
212
233
|
The variables are stored in `config.json` (`accounts.<name>.env`) and written into the launcher. Values are used literally (no `$VAR` expansion). `CODEX_HOME` and the proxy variables are reserved: use `multi-codex proxy` for proxies. A launcher with environment variables is readable only by you (mode 0700), and `list --json` shows only the variable names; still, the values are stored in plain text, so do not put secrets there that need stronger protection. Older versions of multi-codex ignore the `env` field and drop it on their next write; run `multi-codex env NAME --clear` before downgrading.
|
|
213
234
|
|
|
214
235
|
### VS Code and the desktop app (experimental)
|
|
@@ -294,7 +315,7 @@ Progress is recorded in `~/.config/multi-codex/migrate-journal.json`. If the mig
|
|
|
294
315
|
|
|
295
316
|
Sockets and FIFOs (runtime files such as `ipc.sock`) are not copied in copy mode. On macOS, copy mode does not preserve extended attributes.
|
|
296
317
|
|
|
297
|
-
If `CODEX_HOME`, `CODEX_SQLITE_HOME`, `CODEX_API_KEY` or `CODEX_ACCESS_TOKEN` is set in your environment, multi-codex warns you:
|
|
318
|
+
If `CODEX_HOME`, `CODEX_SQLITE_HOME`, `CODEX_API_KEY` or `CODEX_ACCESS_TOKEN` is set in your environment, multi-codex warns you: launchers set or clear them, but plain `codex` still uses them.
|
|
298
319
|
|
|
299
320
|
### Undoing a migration
|
|
300
321
|
|
|
@@ -317,13 +338,20 @@ If a migration stops with an error and you want to abandon it: the error message
|
|
|
317
338
|
## Shared resources
|
|
318
339
|
|
|
319
340
|
```sh
|
|
320
|
-
multi-codex
|
|
321
|
-
multi-codex add work --shared
|
|
341
|
+
multi-codex set work --shared
|
|
322
342
|
```
|
|
323
343
|
|
|
324
|
-
Put the items you want to share
|
|
344
|
+
Without a directory, `--shared` uses the shared directory already configured, or `~/.codex-shared` if none is set yet. Put the items you want to share there first: items missing from it are skipped (`skip`), and nothing is linked for them; if none of them is there, multi-codex says so. To choose which items are shared, use `multi-codex init --shared-items AGENTS.md,skills,rules,agents`.
|
|
325
345
|
|
|
326
|
-
|
|
346
|
+
`--shared DIR` (for example `multi-codex set work --shared ~/my-shared`) changes the shared directory for **every** shared account, not only this one: their links move to the new directory, and links to items missing there are removed. Write the account name before `--shared`; `add --shared work` would take `work` as the directory.
|
|
347
|
+
|
|
348
|
+
multi-codex creates the missing links and remembers which links it created. Turning sharing off removes only those links; links you made yourself are left alone. A real file or directory at a link location is a conflict and is never overwritten.
|
|
349
|
+
|
|
350
|
+
If you already linked an account to the shared directory by hand, `multi-codex set NAME --shared --adopt` takes those links over without recreating them: from then on, turning sharing off removes them as well. Only links that already point to the matching shared item are adopted.
|
|
351
|
+
|
|
352
|
+
One account can opt out of some shared items: `multi-codex set work --shared-exclude skills` removes the `skills` link multi-codex created in `work` (as if sharing were off for that item) and no longer creates it; `--shared-include skills` undoes that. Both options can be repeated, names match `shared.items` exactly, and `list` shows the exclusions as `yes (not: skills)`. To exclude `config.toml` and then copy another account's settings into it, run `--shared-exclude config.toml` and `--config-from` as two separate commands.
|
|
353
|
+
|
|
354
|
+
Items that hold one account's own state can never be shared: `shared.items` must not contain the entries marked "No" in the table below. A configuration that lists one fails to load and names the item; remove it from `config.json`.
|
|
327
355
|
|
|
328
356
|
### What can be shared
|
|
329
357
|
|
|
@@ -339,7 +367,7 @@ Based on the Codex source code (openai/codex at `6b4daafd`):
|
|
|
339
367
|
| `history.jsonl` | Prompt history | Yes | Reads and writes are file-locked; the histories of the accounts are merged. |
|
|
340
368
|
| `auth.json`, `secrets/`, `.credentials.json`, `.env` | Credentials | **No** | They are the account. |
|
|
341
369
|
| `installation_id` | Installation identifier | No | Sent with requests; sharing makes several accounts look like one installation. |
|
|
342
|
-
| `*.sqlite` (`state_5.sqlite`, …) | Threads, logs, memories | **No** | `state_5.sqlite` records account IDs. |
|
|
370
|
+
| `*.sqlite`, `*.sqlite-wal`, `*.sqlite-shm` (`state_5.sqlite`, …), `sqlite/` | Threads, logs, memories | **No** | `state_5.sqlite` records account IDs. |
|
|
343
371
|
| `sessions/`, `archived_sessions/`, `session_index.jsonl` | Session logs | No | The index has only an in-process lock; sessions record the account that created them. |
|
|
344
372
|
| `models_cache.json`, `cache/` | Caches | Not needed | Keyed by the account; a mismatch is a cache miss. |
|
|
345
373
|
| `app-server-control/`, `app-server-daemon/`, `packages/`, `tmp/`, `.tmp/`, `log/`, `shell_snapshots/` | Runtime state | No | Per process or per session. |
|
|
@@ -402,8 +430,11 @@ Run the installer again; configuration, accounts and launchers are not touched:
|
|
|
402
430
|
```sh
|
|
403
431
|
curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.sh | sh
|
|
404
432
|
multi-codex --version
|
|
433
|
+
multi-codex apply # after upgrading from 0.8 or older: rewrites the launchers
|
|
405
434
|
```
|
|
406
435
|
|
|
436
|
+
0.9 changed what the launchers contain. Until `multi-codex apply` (or any other write command) rewrites them, `list` shows `launcher stale` and `usage --live` refuses to run; the old launchers keep working.
|
|
437
|
+
|
|
407
438
|
## Exit codes
|
|
408
439
|
|
|
409
440
|
| Code | Meaning |
|
|
@@ -424,7 +455,7 @@ cd multi-codex
|
|
|
424
455
|
./install.sh
|
|
425
456
|
```
|
|
426
457
|
|
|
427
|
-
With pipx: `pipx install git+https://github.com/jakoes-wu/multi-codex
|
|
458
|
+
With pipx: `pipx install multi-codex` (the latest release from PyPI), or `pipx install git+https://github.com/jakoes-wu/multi-codex` for the current `main` branch.
|
|
428
459
|
|
|
429
460
|
The tool goes to `~/.local/share/multi-codex` and the `multi-codex` command to `~/.local/bin`. Use `--prefix DIR` to install somewhere else. Run `./install.sh --help` for all options.
|
|
430
461
|
|
|
@@ -9,12 +9,18 @@ description = "Run several Codex CLI accounts side by side: separate CODEX_HOME
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
11
11
|
requires-python = ">=3.8"
|
|
12
|
+
keywords = ["codex", "codex-cli", "openai", "multi-account", "account-switcher", "proxy", "cli"]
|
|
12
13
|
classifiers = [
|
|
13
14
|
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
14
16
|
"License :: OSI Approved :: MIT License",
|
|
15
17
|
"Operating System :: MacOS",
|
|
16
18
|
"Operating System :: POSIX :: Linux",
|
|
17
19
|
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.8",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Utilities",
|
|
18
24
|
]
|
|
19
25
|
|
|
20
26
|
[project.scripts]
|
|
@@ -22,6 +28,8 @@ multi-codex = "multi_codex.cli:main"
|
|
|
22
28
|
|
|
23
29
|
[project.urls]
|
|
24
30
|
Homepage = "https://github.com/jakoes-wu/multi-codex"
|
|
31
|
+
Changelog = "https://github.com/jakoes-wu/multi-codex/blob/main/CHANGELOG.md"
|
|
32
|
+
Issues = "https://github.com/jakoes-wu/multi-codex/issues"
|
|
25
33
|
|
|
26
34
|
[tool.setuptools.dynamic]
|
|
27
35
|
version = {attr = "multi_codex.__version__"}
|
|
@@ -32,7 +32,27 @@ NO_ORPHANS: FrozenSet[str] = frozenset()
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def account_dir(config: Config, name: str) -> str:
|
|
35
|
-
|
|
35
|
+
"""账号目录。用账号记录的目录名(rename 后与账号名不同);未登记的名字按名字本身拼。"""
|
|
36
|
+
account = config.find(name)
|
|
37
|
+
return os.path.join(expand(config.root), account.dir_name if account is not None else name)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def dir_conflicts(config: Config) -> List[str]:
|
|
41
|
+
"""两个账号会落到同一个目录时的说明文字(规则同 config._check_dir_names)。
|
|
42
|
+
|
|
43
|
+
parse_config 只检查读入的文件;这里检查命令算出的新配置,典型是 rename work job 之后 add work。
|
|
44
|
+
"""
|
|
45
|
+
problems = []
|
|
46
|
+
for account in config.accounts.values():
|
|
47
|
+
for other in config.accounts.values():
|
|
48
|
+
if other is account:
|
|
49
|
+
continue
|
|
50
|
+
if account.dir_name.casefold() == other.dir_name.casefold() or \
|
|
51
|
+
account.name.casefold() == other.dir_name.casefold():
|
|
52
|
+
problems.append("account {!r} would use the directory of account {!r}".format(
|
|
53
|
+
account.name, other.name))
|
|
54
|
+
break
|
|
55
|
+
return problems
|
|
36
56
|
|
|
37
57
|
|
|
38
58
|
def plan(old: Config, new: Config, *, config_exists: bool = True,
|
|
@@ -60,6 +80,9 @@ def plan(old: Config, new: Config, *, config_exists: bool = True,
|
|
|
60
80
|
"cannot change root from {} to {} while accounts are registered".format(
|
|
61
81
|
old_root, new_root)))
|
|
62
82
|
|
|
83
|
+
for problem in dir_conflicts(new):
|
|
84
|
+
actions.append(Action(CONFLICT, "config", config_path(), problem))
|
|
85
|
+
|
|
63
86
|
planned_deletes: Set[str] = set()
|
|
64
87
|
for account in new.accounts.values():
|
|
65
88
|
old_account = old.find(account.name)
|
|
@@ -70,7 +93,7 @@ def plan(old: Config, new: Config, *, config_exists: bool = True,
|
|
|
70
93
|
"account {!r} is registered as {!r}; renaming is not supported".format(
|
|
71
94
|
account.name, old_account.name)))
|
|
72
95
|
continue
|
|
73
|
-
directory = os.path.join(new_root, account.
|
|
96
|
+
directory = os.path.join(new_root, account.dir_name)
|
|
74
97
|
actions.extend(_plan_account_dir(directory, assumed))
|
|
75
98
|
actions.extend(_plan_launcher(new_bin, account.name, directory, account.proxy, account.env))
|
|
76
99
|
if old_account is not None and old_bin != new_bin:
|
|
@@ -79,11 +102,15 @@ def plan(old: Config, new: Config, *, config_exists: bool = True,
|
|
|
79
102
|
actions.extend(shared.plan_shared(new, account, directory, old.shared_dir,
|
|
80
103
|
adopt=account.name.casefold() in adopt_accounts))
|
|
81
104
|
|
|
105
|
+
new_dirs = {account.dir_name.casefold() for account in new.accounts.values()}
|
|
82
106
|
for old_account in old.accounts.values():
|
|
83
107
|
if new.find(old_account.name) is None:
|
|
84
108
|
actions.extend(_plan_launcher_delete(old_bin, old_account.name, planned_deletes,
|
|
85
109
|
"account removed"))
|
|
86
|
-
|
|
110
|
+
# rename:旧名字从配置里消失,但目录由新名字接着用,共享链接必须留着。
|
|
111
|
+
if old_account.dir_name.casefold() in new_dirs:
|
|
112
|
+
continue
|
|
113
|
+
actions.extend(shared.plan_remove_links(old_account, os.path.join(old_root, old_account.dir_name),
|
|
87
114
|
old.shared_dir))
|
|
88
115
|
|
|
89
116
|
if orphan_scope:
|
|
@@ -121,7 +148,7 @@ def plan_config_copy(config: Config, account: Account, content: str, source_name
|
|
|
121
148
|
已有不同内容时不覆盖:那可能是用户已经改过的配置。
|
|
122
149
|
"""
|
|
123
150
|
target = os.path.join(account_dir(config, account.name), "config.toml")
|
|
124
|
-
if account.shared and "config.toml" in config.shared_items:
|
|
151
|
+
if account.shared and "config.toml" in config.shared_items and "config.toml" not in account.shared_exclude:
|
|
125
152
|
return [Action(CONFLICT, "config-file", target,
|
|
126
153
|
"config.toml is shared for this account and does not need copying")]
|
|
127
154
|
kind = entry_kind(target)
|
|
@@ -33,15 +33,18 @@ def _private_dir(path: str) -> str:
|
|
|
33
33
|
return path
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def gui_data_dir(config: Config,
|
|
37
|
-
"""返回并创建 <root>/.apps
|
|
36
|
+
def gui_data_dir(config: Config, dir_name: str, kind: str) -> str:
|
|
37
|
+
"""返回并创建 <root>/.apps/<账号目录名>/<kind>(kind 为 vscode 或 desktop),各级目录都只允许本人访问。
|
|
38
|
+
|
|
39
|
+
按目录名而不是账号名存放:rename 后 VS Code 与桌面端的登录状态、窗口与扩展数据都还在原处。
|
|
40
|
+
"""
|
|
38
41
|
apps_root = _private_dir(os.path.join(expand(config.root), ".apps"))
|
|
39
|
-
account_root = _private_dir(os.path.join(apps_root,
|
|
42
|
+
account_root = _private_dir(os.path.join(apps_root, dir_name))
|
|
40
43
|
return _private_dir(os.path.join(account_root, kind))
|
|
41
44
|
|
|
42
45
|
|
|
43
|
-
def desktop_log(config: Config,
|
|
44
|
-
path = os.path.join(expand(config.root), ".apps",
|
|
46
|
+
def desktop_log(config: Config, dir_name: str) -> str:
|
|
47
|
+
path = os.path.join(expand(config.root), ".apps", dir_name, "desktop.log")
|
|
45
48
|
fd = os.open(path, os.O_CREAT | os.O_APPEND | os.O_WRONLY, 0o600)
|
|
46
49
|
os.close(fd)
|
|
47
50
|
os.chmod(path, 0o600)
|