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.
Files changed (46) hide show
  1. {multi_codex-0.7.0 → multi_codex-0.9.0}/PKG-INFO +73 -18
  2. multi_codex-0.7.0/src/multi_codex.egg-info/PKG-INFO → multi_codex-0.9.0/README.md +64 -33
  3. {multi_codex-0.7.0 → multi_codex-0.9.0}/pyproject.toml +8 -0
  4. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/__init__.py +1 -1
  5. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/accounts.py +31 -4
  6. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/apps.py +8 -5
  7. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/cli.py +326 -29
  8. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/completion.py +2 -2
  9. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/config.py +72 -3
  10. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/doctor.py +5 -4
  11. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/launcher.py +7 -1
  12. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/migrate.py +2 -2
  13. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/platform.py +2 -1
  14. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/shared.py +4 -0
  15. multi_codex-0.9.0/src/multi_codex/shellpath.py +52 -0
  16. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/switch.py +5 -1
  17. multi_codex-0.7.0/README.md → multi_codex-0.9.0/src/multi_codex.egg-info/PKG-INFO +88 -17
  18. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex.egg-info/SOURCES.txt +3 -0
  19. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_ergonomics.py +12 -9
  20. multi_codex-0.9.0/tests/test_everyday.py +400 -0
  21. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_insight.py +3 -3
  22. multi_codex-0.9.0/tests/test_isolation.py +428 -0
  23. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_onboarding.py +1 -1
  24. {multi_codex-0.7.0 → multi_codex-0.9.0}/LICENSE +0 -0
  25. {multi_codex-0.7.0 → multi_codex-0.9.0}/setup.cfg +0 -0
  26. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/__main__.py +0 -0
  27. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/actions.py +0 -0
  28. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/binding.py +0 -0
  29. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/fsutil.py +0 -0
  30. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/identity.py +0 -0
  31. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/lock.py +0 -0
  32. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex/usage.py +0 -0
  33. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex.egg-info/dependency_links.txt +0 -0
  34. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex.egg-info/entry_points.txt +0 -0
  35. {multi_codex-0.7.0 → multi_codex-0.9.0}/src/multi_codex.egg-info/top_level.txt +0 -0
  36. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_accounts.py +0 -0
  37. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_apps.py +0 -0
  38. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_binding.py +0 -0
  39. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_config.py +0 -0
  40. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_config_copy.py +0 -0
  41. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_fsutil.py +0 -0
  42. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_install.py +0 -0
  43. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_migrate.py +0 -0
  44. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_onboarding_commands.py +0 -0
  45. {multi_codex-0.7.0 → multi_codex-0.9.0}/tests/test_platform.py +0 -0
  46. {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.7.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
+ [![Release](https://img.shields.io/github/v/release/jakoes-wu/multi-codex)](https://github.com/jakoes-wu/multi-codex/releases)
30
+ [![CI](https://github.com/jakoes-wu/multi-codex/actions/workflows/ci.yml/badge.svg)](https://github.com/jakoes-wu/multi-codex/actions/workflows/ci.yml)
31
+ [![PyPI](https://img.shields.io/pypi/v/multi-codex)](https://pypi.org/project/multi-codex/)
32
+ ![Python](https://img.shields.io/badge/python-3.8%2B-blue)
33
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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
+ ![multi-codex demo: add two accounts and list them](https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/docs/assets/demo.gif)
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
- 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 prints a hint but never edits your shell profile. Add this line to `~/.zshrc` or `~/.bashrc` and open a new terminal:
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 # accounts, launcher state, logged-in e-mail and plan
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 proxy work 7901` | [Proxy values](#proxy-values) |
119
- | Share `AGENTS.md`, skills and rules between accounts | Put them in `~/.codex-shared`, run `multi-codex init --shared-dir ~/.codex-shared`, then `multi-codex add work --shared` | [Shared resources](#shared-resources) |
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, the state of their launchers, and who is logged in. |
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` adds two columns, LOGIN and PLAN, read from each account's local `auth.json`. No token is ever printed, and nothing is sent anywhere. LOGIN is the e-mail address, `api-key`, `-` (not logged in), `keyring` (credentials are in the system keyring and cannot be read from files) or `unreadable`. PLAN is the plan recorded when the current token was issued; it is updated the next time Codex refreshes the token. If two accounts are logged in as the same ChatGPT user and workspace, `list` warns you: they share one quota.
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: these variables override or bypass per-account isolation.
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 init --shared-dir ~/.codex-shared --shared-items AGENTS.md,skills,rules,agents
321
- multi-codex add work --shared
365
+ multi-codex set work --shared
322
366
  ```
323
367
 
324
- Put the items you want to share into the shared directory first: items missing from it are skipped (`skip`), and nothing is linked for them. 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.
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
- If you already linked an account to the shared directory by hand, `multi-codex add 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.
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
+ [![Release](https://img.shields.io/github/v/release/jakoes-wu/multi-codex)](https://github.com/jakoes-wu/multi-codex/releases)
6
+ [![CI](https://github.com/jakoes-wu/multi-codex/actions/workflows/ci.yml/badge.svg)](https://github.com/jakoes-wu/multi-codex/actions/workflows/ci.yml)
7
+ [![PyPI](https://img.shields.io/pypi/v/multi-codex)](https://pypi.org/project/multi-codex/)
8
+ ![Python](https://img.shields.io/badge/python-3.8%2B-blue)
9
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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
+ ![multi-codex demo: add two accounts and list them](https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/docs/assets/demo.gif)
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
- 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 prints a hint but never edits your shell profile. Add this line to `~/.zshrc` or `~/.bashrc` and open a new terminal:
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 # accounts, launcher state, logged-in e-mail and plan
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 proxy work 7901` | [Proxy values](#proxy-values) |
119
- | Share `AGENTS.md`, skills and rules between accounts | Put them in `~/.codex-shared`, run `multi-codex init --shared-dir ~/.codex-shared`, then `multi-codex add work --shared` | [Shared resources](#shared-resources) |
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, the state of their launchers, and who is logged in. |
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` adds two columns, LOGIN and PLAN, read from each account's local `auth.json`. No token is ever printed, and nothing is sent anywhere. LOGIN is the e-mail address, `api-key`, `-` (not logged in), `keyring` (credentials are in the system keyring and cannot be read from files) or `unreadable`. PLAN is the plan recorded when the current token was issued; it is updated the next time Codex refreshes the token. If two accounts are logged in as the same ChatGPT user and workspace, `list` warns you: they share one quota.
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: these variables override or bypass per-account isolation.
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 init --shared-dir ~/.codex-shared --shared-items AGENTS.md,skills,rules,agents
321
- multi-codex add work --shared
341
+ multi-codex set work --shared
322
342
  ```
323
343
 
324
- Put the items you want to share into the shared directory first: items missing from it are skipped (`skip`), and nothing is linked for them. 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.
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
- If you already linked an account to the shared directory by hand, `multi-codex add 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.
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__"}
@@ -1,3 +1,3 @@
1
1
  """multi-codex:管理多个 Codex CLI 账号目录、启动命令与代理设置。"""
2
2
 
3
- __version__ = "0.7.0"
3
+ __version__ = "0.9.0"
@@ -32,7 +32,27 @@ NO_ORPHANS: FrozenSet[str] = frozenset()
32
32
 
33
33
 
34
34
  def account_dir(config: Config, name: str) -> str:
35
- return os.path.join(expand(config.root), name)
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.name)
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
- actions.extend(shared.plan_remove_links(old_account, os.path.join(old_root, old_account.name),
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, name: str, kind: str) -> str:
37
- """返回并创建 <root>/.apps/<名>/<kind>(kind 为 vscode 或 desktop),各级目录都只允许本人访问。"""
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, name))
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, name: str) -> str:
44
- path = os.path.join(expand(config.root), ".apps", name, "desktop.log")
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)