multi-codex 0.8.0__tar.gz → 0.10.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.8.0/src/multi_codex.egg-info → multi_codex-0.10.0}/PKG-INFO +33 -9
- {multi_codex-0.8.0 → multi_codex-0.10.0}/README.md +32 -8
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/__init__.py +1 -1
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/accounts.py +72 -5
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/apps.py +8 -5
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/cli.py +156 -8
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/completion.py +2 -2
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/config.py +70 -3
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/doctor.py +3 -2
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/launcher.py +7 -1
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/migrate.py +2 -2
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/platform.py +2 -1
- multi_codex-0.10.0/src/multi_codex/router.py +121 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/shared.py +4 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/switch.py +30 -6
- {multi_codex-0.8.0 → multi_codex-0.10.0/src/multi_codex.egg-info}/PKG-INFO +33 -9
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex.egg-info/SOURCES.txt +3 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_ergonomics.py +11 -8
- multi_codex-0.10.0/tests/test_isolation.py +428 -0
- multi_codex-0.10.0/tests/test_router.py +370 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_switch.py +20 -3
- {multi_codex-0.8.0 → multi_codex-0.10.0}/LICENSE +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/pyproject.toml +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/setup.cfg +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/__main__.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/actions.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/binding.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/fsutil.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/identity.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/lock.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/shellpath.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex/usage.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex.egg-info/dependency_links.txt +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex.egg-info/entry_points.txt +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/src/multi_codex.egg-info/top_level.txt +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_accounts.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_apps.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_binding.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_config.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_config_copy.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_everyday.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_fsutil.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_insight.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_install.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_migrate.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_onboarding.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_onboarding_commands.py +0 -0
- {multi_codex-0.8.0 → multi_codex-0.10.0}/tests/test_platform.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multi-codex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.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
|
|
@@ -134,7 +134,7 @@ Without a name, the account is named after the e-mail address in `~/.codex/auth.
|
|
|
134
134
|
| Open VS Code with an account | `multi-codex code work ~/src/project` | [VS Code and the desktop app](#vs-code-and-the-desktop-app-experimental) |
|
|
135
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) |
|
|
136
136
|
| Change the account that plain `codex` and the Dock apps use | `multi-codex use work` (after `migrate-default`) | [Default account](#default-account) |
|
|
137
|
-
| Always use one account inside a project | In the project directory: `multi-codex bind work`, then `
|
|
137
|
+
| Always use one account inside a project | In the project directory: `multi-codex bind work`, then `codex-auto` | [Directory bindings](#directory-bindings) |
|
|
138
138
|
| Set or change an account's proxy | `multi-codex set work --proxy 7901` | [Proxy values](#proxy-values) |
|
|
139
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) |
|
|
140
140
|
| Start a new account with another account's settings | `multi-codex add new --config-from work` | [Copying settings](#copying-settings-from-another-account) |
|
|
@@ -142,6 +142,7 @@ Without a name, the account is named after the e-mail address in `~/.codex/auth.
|
|
|
142
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) |
|
|
143
143
|
| Get tab completion | `eval "$(multi-codex completion zsh)"` | [Shell completion](#shell-completion) |
|
|
144
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) |
|
|
145
146
|
| Remove an account | `multi-codex remove work` (the directory is kept) | [Commands](#commands) |
|
|
146
147
|
|
|
147
148
|
More questions are answered in the [FAQ](#faq).
|
|
@@ -159,8 +160,9 @@ More questions are answered in the [FAQ](#faq).
|
|
|
159
160
|
| ---- | ---- |
|
|
160
161
|
| `multi-codex init [--root DIR] [--bin-dir DIR] [--shared-dir DIR] [--shared-items A,B]` | Create or change global settings. |
|
|
161
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. |
|
|
162
|
-
| `multi-codex add NAME [--proxy P] [--shared [DIR] \| --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 set NAME [
|
|
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. |
|
|
164
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`. |
|
|
165
167
|
| `multi-codex proxy NAME PORT\|URL\|off\|inherit` | Set an account's proxy. |
|
|
166
168
|
| `multi-codex remove NAME` | Unregister an account and delete its launcher. **The account directory is kept.** |
|
|
@@ -170,6 +172,7 @@ More questions are answered in the [FAQ](#faq).
|
|
|
170
172
|
| `multi-codex doctor [--json]` | Check the installation, configuration and accounts. Read-only. |
|
|
171
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. |
|
|
172
174
|
| `multi-codex bind [NAME [DIR]]` / `unbind [DIR]` | Bind a directory to an account, list bindings, or remove one. |
|
|
175
|
+
| `multi-codex which [DIR]` | Show which account a directory uses (the same one `codex-auto` and `run` pick). |
|
|
173
176
|
| `multi-codex code NAME [PATH] [-- ARGS]` | Open VS Code for an account (experimental). |
|
|
174
177
|
| `multi-codex app NAME` | Open the Codex desktop app for an account (macOS, experimental). |
|
|
175
178
|
| `multi-codex path NAME` | Print an account's directory. |
|
|
@@ -178,7 +181,7 @@ More questions are answered in the [FAQ](#faq).
|
|
|
178
181
|
| `multi-codex restore NAME [--skip-process-check] [--accept-relogin]` | Undo `migrate-default`: move the account back to `~/.codex`. |
|
|
179
182
|
| `multi-codex completion bash\|zsh\|fish` | Print a shell completion script. |
|
|
180
183
|
|
|
181
|
-
Every write command accepts `--dry-run`. `init`, `add`, `set`, `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.
|
|
184
|
+
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.
|
|
182
185
|
|
|
183
186
|
### Login and usage
|
|
184
187
|
|
|
@@ -216,7 +219,7 @@ Subcommands, options and registered account names (including e-mail addresses) a
|
|
|
216
219
|
|
|
217
220
|
### Running other commands
|
|
218
221
|
|
|
219
|
-
`multi-codex run NAME -- COMMAND ...` runs any command with exactly the environment of `codex-NAME` (`CODEX_HOME`, proxy, extra variables). Without a command it runs `codex`. Everything after the first `--` is passed through unchanged; the exit code is the command's. `multi-codex path NAME` prints the account directory.
|
|
222
|
+
`multi-codex run NAME -- COMMAND ...` runs any command with exactly the environment of `codex-NAME` (`CODEX_HOME`, proxy, extra variables). Without a command it runs `codex`. Everything after the first `--` is passed through unchanged; the exit code is the command's. `multi-codex path NAME` prints the account directory. For example, to manage an account's MCP servers: `multi-codex run work -- codex mcp list`.
|
|
220
223
|
|
|
221
224
|
### Directory bindings
|
|
222
225
|
|
|
@@ -225,14 +228,26 @@ cd ~/work/project && multi-codex bind work # this directory and everything b
|
|
|
225
228
|
multi-codex run -- codex resume # no account name needed here
|
|
226
229
|
multi-codex bind # list bindings; * marks the one in effect here
|
|
227
230
|
multi-codex unbind # remove the binding of the current directory
|
|
231
|
+
codex-auto # runs codex with the bound account
|
|
232
|
+
multi-codex which # prints the account this directory uses
|
|
228
233
|
```
|
|
229
234
|
|
|
235
|
+
While at least one binding exists, multi-codex generates `codex-auto` next to the other launchers. It looks up the nearest bound directory exactly like `run` and starts that account's launcher; outside any bound directory it runs plain `codex` (the default account, see [Default account](#default-account)) and says so on stderr. It is updated whenever bindings or accounts change and removed with the last binding. An account named `auto` cannot coexist with bindings: rename it first (`multi-codex rename auto NEW`).
|
|
236
|
+
|
|
230
237
|
`run` without an account name walks up from the current directory and uses the nearest bound directory. Bindings are stored in `config.json` (not in your project), keyed by the real path of the directory (links resolved, the on-disk letter case used on case-insensitive file systems). `apply -f` keeps the current bindings; removing an account (`remove`, `restore`, `apply -f`) also removes its bindings. Older versions of multi-codex drop the `bindings` field on their next write.
|
|
231
238
|
|
|
232
239
|
### Copying settings from another account
|
|
233
240
|
|
|
234
241
|
`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.
|
|
235
242
|
|
|
243
|
+
### Renaming accounts
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
multi-codex rename work client-a
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The account and its launcher get the new name (`codex-client-a`; `codex-work` is deleted), and directory bindings, `codex-auto` 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`.
|
|
250
|
+
|
|
236
251
|
### Per-account environment variables
|
|
237
252
|
|
|
238
253
|
```sh
|
|
@@ -242,6 +257,8 @@ multi-codex env work --unset TERM_PROGRAM
|
|
|
242
257
|
multi-codex env work --clear
|
|
243
258
|
```
|
|
244
259
|
|
|
260
|
+
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.
|
|
261
|
+
|
|
245
262
|
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.
|
|
246
263
|
|
|
247
264
|
### VS Code and the desktop app (experimental)
|
|
@@ -327,7 +344,7 @@ Progress is recorded in `~/.config/multi-codex/migrate-journal.json`. If the mig
|
|
|
327
344
|
|
|
328
345
|
Sockets and FIFOs (runtime files such as `ipc.sock`) are not copied in copy mode. On macOS, copy mode does not preserve extended attributes.
|
|
329
346
|
|
|
330
|
-
If `CODEX_HOME`, `CODEX_SQLITE_HOME`, `CODEX_API_KEY` or `CODEX_ACCESS_TOKEN` is set in your environment, multi-codex warns you:
|
|
347
|
+
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.
|
|
331
348
|
|
|
332
349
|
### Undoing a migration
|
|
333
350
|
|
|
@@ -361,6 +378,10 @@ multi-codex creates the missing links and remembers which links it created. Turn
|
|
|
361
378
|
|
|
362
379
|
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.
|
|
363
380
|
|
|
381
|
+
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.
|
|
382
|
+
|
|
383
|
+
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`.
|
|
384
|
+
|
|
364
385
|
### What can be shared
|
|
365
386
|
|
|
366
387
|
Based on the Codex source code (openai/codex at `6b4daafd`):
|
|
@@ -375,7 +396,7 @@ Based on the Codex source code (openai/codex at `6b4daafd`):
|
|
|
375
396
|
| `history.jsonl` | Prompt history | Yes | Reads and writes are file-locked; the histories of the accounts are merged. |
|
|
376
397
|
| `auth.json`, `secrets/`, `.credentials.json`, `.env` | Credentials | **No** | They are the account. |
|
|
377
398
|
| `installation_id` | Installation identifier | No | Sent with requests; sharing makes several accounts look like one installation. |
|
|
378
|
-
| `*.sqlite` (`state_5.sqlite`, …) | Threads, logs, memories | **No** | `state_5.sqlite` records account IDs. |
|
|
399
|
+
| `*.sqlite`, `*.sqlite-wal`, `*.sqlite-shm` (`state_5.sqlite`, …), `sqlite/` | Threads, logs, memories | **No** | `state_5.sqlite` records account IDs. |
|
|
379
400
|
| `sessions/`, `archived_sessions/`, `session_index.jsonl` | Session logs | No | The index has only an in-process lock; sessions record the account that created them. |
|
|
380
401
|
| `models_cache.json`, `cache/` | Caches | Not needed | Keyed by the account; a mismatch is a cache miss. |
|
|
381
402
|
| `app-server-control/`, `app-server-daemon/`, `packages/`, `tmp/`, `.tmp/`, `log/`, `shell_snapshots/` | Runtime state | No | Per process or per session. |
|
|
@@ -438,8 +459,11 @@ Run the installer again; configuration, accounts and launchers are not touched:
|
|
|
438
459
|
```sh
|
|
439
460
|
curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.sh | sh
|
|
440
461
|
multi-codex --version
|
|
462
|
+
multi-codex apply # after upgrading from 0.8 or older: rewrites the launchers
|
|
441
463
|
```
|
|
442
464
|
|
|
465
|
+
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.
|
|
466
|
+
|
|
443
467
|
## Exit codes
|
|
444
468
|
|
|
445
469
|
| Code | Meaning |
|
|
@@ -473,7 +497,7 @@ The tool goes to `~/.local/share/multi-codex` and the `multi-codex` command to `
|
|
|
473
497
|
curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.sh | sh -s -- --uninstall # without a clone
|
|
474
498
|
```
|
|
475
499
|
|
|
476
|
-
This removes the tool only. Your configuration, account directories
|
|
500
|
+
This removes the tool only. Your configuration, account directories, `codex-<name>` launchers and `codex-auto` stay; they keep working because they do not depend on multi-codex.
|
|
477
501
|
|
|
478
502
|
## Contributing
|
|
479
503
|
|
|
@@ -110,7 +110,7 @@ Without a name, the account is named after the e-mail address in `~/.codex/auth.
|
|
|
110
110
|
| Open VS Code with an account | `multi-codex code work ~/src/project` | [VS Code and the desktop app](#vs-code-and-the-desktop-app-experimental) |
|
|
111
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) |
|
|
112
112
|
| Change the account that plain `codex` and the Dock apps use | `multi-codex use work` (after `migrate-default`) | [Default account](#default-account) |
|
|
113
|
-
| Always use one account inside a project | In the project directory: `multi-codex bind work`, then `
|
|
113
|
+
| Always use one account inside a project | In the project directory: `multi-codex bind work`, then `codex-auto` | [Directory bindings](#directory-bindings) |
|
|
114
114
|
| Set or change an account's proxy | `multi-codex set work --proxy 7901` | [Proxy values](#proxy-values) |
|
|
115
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) |
|
|
116
116
|
| Start a new account with another account's settings | `multi-codex add new --config-from work` | [Copying settings](#copying-settings-from-another-account) |
|
|
@@ -118,6 +118,7 @@ Without a name, the account is named after the e-mail address in `~/.codex/auth.
|
|
|
118
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) |
|
|
119
119
|
| Get tab completion | `eval "$(multi-codex completion zsh)"` | [Shell completion](#shell-completion) |
|
|
120
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) |
|
|
121
122
|
| Remove an account | `multi-codex remove work` (the directory is kept) | [Commands](#commands) |
|
|
122
123
|
|
|
123
124
|
More questions are answered in the [FAQ](#faq).
|
|
@@ -135,8 +136,9 @@ More questions are answered in the [FAQ](#faq).
|
|
|
135
136
|
| ---- | ---- |
|
|
136
137
|
| `multi-codex init [--root DIR] [--bin-dir DIR] [--shared-dir DIR] [--shared-items A,B]` | Create or change global settings. |
|
|
137
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. |
|
|
138
|
-
| `multi-codex add NAME [--proxy P] [--shared [DIR] \| --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 set NAME [
|
|
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. |
|
|
140
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`. |
|
|
141
143
|
| `multi-codex proxy NAME PORT\|URL\|off\|inherit` | Set an account's proxy. |
|
|
142
144
|
| `multi-codex remove NAME` | Unregister an account and delete its launcher. **The account directory is kept.** |
|
|
@@ -146,6 +148,7 @@ More questions are answered in the [FAQ](#faq).
|
|
|
146
148
|
| `multi-codex doctor [--json]` | Check the installation, configuration and accounts. Read-only. |
|
|
147
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. |
|
|
148
150
|
| `multi-codex bind [NAME [DIR]]` / `unbind [DIR]` | Bind a directory to an account, list bindings, or remove one. |
|
|
151
|
+
| `multi-codex which [DIR]` | Show which account a directory uses (the same one `codex-auto` and `run` pick). |
|
|
149
152
|
| `multi-codex code NAME [PATH] [-- ARGS]` | Open VS Code for an account (experimental). |
|
|
150
153
|
| `multi-codex app NAME` | Open the Codex desktop app for an account (macOS, experimental). |
|
|
151
154
|
| `multi-codex path NAME` | Print an account's directory. |
|
|
@@ -154,7 +157,7 @@ More questions are answered in the [FAQ](#faq).
|
|
|
154
157
|
| `multi-codex restore NAME [--skip-process-check] [--accept-relogin]` | Undo `migrate-default`: move the account back to `~/.codex`. |
|
|
155
158
|
| `multi-codex completion bash\|zsh\|fish` | Print a shell completion script. |
|
|
156
159
|
|
|
157
|
-
Every write command accepts `--dry-run`. `init`, `add`, `set`, `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.
|
|
160
|
+
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.
|
|
158
161
|
|
|
159
162
|
### Login and usage
|
|
160
163
|
|
|
@@ -192,7 +195,7 @@ Subcommands, options and registered account names (including e-mail addresses) a
|
|
|
192
195
|
|
|
193
196
|
### Running other commands
|
|
194
197
|
|
|
195
|
-
`multi-codex run NAME -- COMMAND ...` runs any command with exactly the environment of `codex-NAME` (`CODEX_HOME`, proxy, extra variables). Without a command it runs `codex`. Everything after the first `--` is passed through unchanged; the exit code is the command's. `multi-codex path NAME` prints the account directory.
|
|
198
|
+
`multi-codex run NAME -- COMMAND ...` runs any command with exactly the environment of `codex-NAME` (`CODEX_HOME`, proxy, extra variables). Without a command it runs `codex`. Everything after the first `--` is passed through unchanged; the exit code is the command's. `multi-codex path NAME` prints the account directory. For example, to manage an account's MCP servers: `multi-codex run work -- codex mcp list`.
|
|
196
199
|
|
|
197
200
|
### Directory bindings
|
|
198
201
|
|
|
@@ -201,14 +204,26 @@ cd ~/work/project && multi-codex bind work # this directory and everything b
|
|
|
201
204
|
multi-codex run -- codex resume # no account name needed here
|
|
202
205
|
multi-codex bind # list bindings; * marks the one in effect here
|
|
203
206
|
multi-codex unbind # remove the binding of the current directory
|
|
207
|
+
codex-auto # runs codex with the bound account
|
|
208
|
+
multi-codex which # prints the account this directory uses
|
|
204
209
|
```
|
|
205
210
|
|
|
211
|
+
While at least one binding exists, multi-codex generates `codex-auto` next to the other launchers. It looks up the nearest bound directory exactly like `run` and starts that account's launcher; outside any bound directory it runs plain `codex` (the default account, see [Default account](#default-account)) and says so on stderr. It is updated whenever bindings or accounts change and removed with the last binding. An account named `auto` cannot coexist with bindings: rename it first (`multi-codex rename auto NEW`).
|
|
212
|
+
|
|
206
213
|
`run` without an account name walks up from the current directory and uses the nearest bound directory. Bindings are stored in `config.json` (not in your project), keyed by the real path of the directory (links resolved, the on-disk letter case used on case-insensitive file systems). `apply -f` keeps the current bindings; removing an account (`remove`, `restore`, `apply -f`) also removes its bindings. Older versions of multi-codex drop the `bindings` field on their next write.
|
|
207
214
|
|
|
208
215
|
### Copying settings from another account
|
|
209
216
|
|
|
210
217
|
`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.
|
|
211
218
|
|
|
219
|
+
### Renaming accounts
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
multi-codex rename work client-a
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The account and its launcher get the new name (`codex-client-a`; `codex-work` is deleted), and directory bindings, `codex-auto` 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`.
|
|
226
|
+
|
|
212
227
|
### Per-account environment variables
|
|
213
228
|
|
|
214
229
|
```sh
|
|
@@ -218,6 +233,8 @@ multi-codex env work --unset TERM_PROGRAM
|
|
|
218
233
|
multi-codex env work --clear
|
|
219
234
|
```
|
|
220
235
|
|
|
236
|
+
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.
|
|
237
|
+
|
|
221
238
|
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.
|
|
222
239
|
|
|
223
240
|
### VS Code and the desktop app (experimental)
|
|
@@ -303,7 +320,7 @@ Progress is recorded in `~/.config/multi-codex/migrate-journal.json`. If the mig
|
|
|
303
320
|
|
|
304
321
|
Sockets and FIFOs (runtime files such as `ipc.sock`) are not copied in copy mode. On macOS, copy mode does not preserve extended attributes.
|
|
305
322
|
|
|
306
|
-
If `CODEX_HOME`, `CODEX_SQLITE_HOME`, `CODEX_API_KEY` or `CODEX_ACCESS_TOKEN` is set in your environment, multi-codex warns you:
|
|
323
|
+
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.
|
|
307
324
|
|
|
308
325
|
### Undoing a migration
|
|
309
326
|
|
|
@@ -337,6 +354,10 @@ multi-codex creates the missing links and remembers which links it created. Turn
|
|
|
337
354
|
|
|
338
355
|
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.
|
|
339
356
|
|
|
357
|
+
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.
|
|
358
|
+
|
|
359
|
+
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`.
|
|
360
|
+
|
|
340
361
|
### What can be shared
|
|
341
362
|
|
|
342
363
|
Based on the Codex source code (openai/codex at `6b4daafd`):
|
|
@@ -351,7 +372,7 @@ Based on the Codex source code (openai/codex at `6b4daafd`):
|
|
|
351
372
|
| `history.jsonl` | Prompt history | Yes | Reads and writes are file-locked; the histories of the accounts are merged. |
|
|
352
373
|
| `auth.json`, `secrets/`, `.credentials.json`, `.env` | Credentials | **No** | They are the account. |
|
|
353
374
|
| `installation_id` | Installation identifier | No | Sent with requests; sharing makes several accounts look like one installation. |
|
|
354
|
-
| `*.sqlite` (`state_5.sqlite`, …) | Threads, logs, memories | **No** | `state_5.sqlite` records account IDs. |
|
|
375
|
+
| `*.sqlite`, `*.sqlite-wal`, `*.sqlite-shm` (`state_5.sqlite`, …), `sqlite/` | Threads, logs, memories | **No** | `state_5.sqlite` records account IDs. |
|
|
355
376
|
| `sessions/`, `archived_sessions/`, `session_index.jsonl` | Session logs | No | The index has only an in-process lock; sessions record the account that created them. |
|
|
356
377
|
| `models_cache.json`, `cache/` | Caches | Not needed | Keyed by the account; a mismatch is a cache miss. |
|
|
357
378
|
| `app-server-control/`, `app-server-daemon/`, `packages/`, `tmp/`, `.tmp/`, `log/`, `shell_snapshots/` | Runtime state | No | Per process or per session. |
|
|
@@ -414,8 +435,11 @@ Run the installer again; configuration, accounts and launchers are not touched:
|
|
|
414
435
|
```sh
|
|
415
436
|
curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.sh | sh
|
|
416
437
|
multi-codex --version
|
|
438
|
+
multi-codex apply # after upgrading from 0.8 or older: rewrites the launchers
|
|
417
439
|
```
|
|
418
440
|
|
|
441
|
+
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.
|
|
442
|
+
|
|
419
443
|
## Exit codes
|
|
420
444
|
|
|
421
445
|
| Code | Meaning |
|
|
@@ -449,7 +473,7 @@ The tool goes to `~/.local/share/multi-codex` and the `multi-codex` command to `
|
|
|
449
473
|
curl -fsSL https://raw.githubusercontent.com/jakoes-wu/multi-codex/main/install.sh | sh -s -- --uninstall # without a clone
|
|
450
474
|
```
|
|
451
475
|
|
|
452
|
-
This removes the tool only. Your configuration, account directories
|
|
476
|
+
This removes the tool only. Your configuration, account directories, `codex-<name>` launchers and `codex-auto` stay; they keep working because they do not depend on multi-codex.
|
|
453
477
|
|
|
454
478
|
## Contributing
|
|
455
479
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import os
|
|
15
15
|
from typing import Dict, FrozenSet, Iterable, List, Optional, Sequence, Set, Union
|
|
16
16
|
|
|
17
|
-
from . import launcher, shared
|
|
17
|
+
from . import launcher, router, shared
|
|
18
18
|
from .actions import (CONFLICT, CREATE, DELETE, SKIP, UNCHANGED, UPDATE, Action, error,
|
|
19
19
|
has_conflict, info, print_action)
|
|
20
20
|
from .config import Account, Config, config_path, dump_config, save_config
|
|
@@ -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:
|
|
@@ -94,6 +121,8 @@ def plan(old: Config, new: Config, *, config_exists: bool = True,
|
|
|
94
121
|
actions.extend(_plan_launcher_delete(new_bin, name, planned_deletes,
|
|
95
122
|
"orphan launcher", path=path))
|
|
96
123
|
|
|
124
|
+
actions.extend(_plan_router(old_bin, new_bin, new, planned_deletes))
|
|
125
|
+
|
|
97
126
|
config_changed = not config_exists or dump_config(old) != dump_config(new)
|
|
98
127
|
config_action = Action(CREATE if not config_exists else (UPDATE if config_changed else UNCHANGED),
|
|
99
128
|
"config", config_path())
|
|
@@ -121,7 +150,7 @@ def plan_config_copy(config: Config, account: Account, content: str, source_name
|
|
|
121
150
|
已有不同内容时不覆盖:那可能是用户已经改过的配置。
|
|
122
151
|
"""
|
|
123
152
|
target = os.path.join(account_dir(config, account.name), "config.toml")
|
|
124
|
-
if account.shared and "config.toml" in config.shared_items:
|
|
153
|
+
if account.shared and "config.toml" in config.shared_items and "config.toml" not in account.shared_exclude:
|
|
125
154
|
return [Action(CONFLICT, "config-file", target,
|
|
126
155
|
"config.toml is shared for this account and does not need copying")]
|
|
127
156
|
kind = entry_kind(target)
|
|
@@ -164,6 +193,44 @@ def _plan_launcher(bin_dir: str, name: str, directory: str, proxy: str,
|
|
|
164
193
|
return [Action(UPDATE, "launcher", path, run=_write_launcher(path, content, mode))]
|
|
165
194
|
|
|
166
195
|
|
|
196
|
+
def _plan_router(old_bin: str, new_bin: str, new: Config, planned_deletes: Set[str]) -> List[Action]:
|
|
197
|
+
"""codex-auto 入口(feature-auto-launcher §5.1.2):有目录绑定时生成或更新,没有时删除。
|
|
198
|
+
|
|
199
|
+
必须放在账号启动命令的删除(含孤儿清理)之后计算:rename / remove / restore 名为 auto 的账号时,
|
|
200
|
+
codex-auto 原本是它的启动命令,正被删除,这里要把它当作不存在、重建成入口。
|
|
201
|
+
"""
|
|
202
|
+
actions: List[Action] = []
|
|
203
|
+
path = router.router_path(new_bin)
|
|
204
|
+
if old_bin != new_bin:
|
|
205
|
+
old_path = router.router_path(old_bin)
|
|
206
|
+
if router.is_router(old_path):
|
|
207
|
+
actions.append(Action(DELETE, "launcher", old_path, "bin_dir changed", _unlink_file(old_path)))
|
|
208
|
+
if not new.bindings:
|
|
209
|
+
if router.is_router(path):
|
|
210
|
+
actions.append(Action(DELETE, "launcher", path, "no directory bindings", _unlink_file(path)))
|
|
211
|
+
return actions
|
|
212
|
+
owner = new.find(router.ROUTER_NAME)
|
|
213
|
+
if owner is not None:
|
|
214
|
+
return actions + [Action(CONFLICT, "launcher", path,
|
|
215
|
+
"codex-auto is generated from directory bindings; rename the account named {!r} "
|
|
216
|
+
"(multi-codex rename {} NEW) or remove the bindings".format(owner.name, owner.name))]
|
|
217
|
+
content = router.render(new)
|
|
218
|
+
write = _write_launcher(path, content, router.ROUTER_MODE)
|
|
219
|
+
if entry_kind(path) == KIND_MISSING or router.same_file(path, planned_deletes):
|
|
220
|
+
return actions + [Action(CREATE, "launcher", path, run=write)]
|
|
221
|
+
if not router.is_router(path):
|
|
222
|
+
return actions + [Action(CONFLICT, "launcher", path, "already exists and is not the multi-codex router")]
|
|
223
|
+
if launcher.launcher_file_ok(path, content, None):
|
|
224
|
+
return actions + [Action(UNCHANGED, "launcher", path)]
|
|
225
|
+
return actions + [Action(UPDATE, "launcher", path, run=write)]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _unlink_file(path: str):
|
|
229
|
+
def run() -> None:
|
|
230
|
+
os.unlink(path)
|
|
231
|
+
return run
|
|
232
|
+
|
|
233
|
+
|
|
167
234
|
def _write_launcher(path: str, content: str, mode: int):
|
|
168
235
|
def run() -> None:
|
|
169
236
|
atomic_write(path, content, mode=mode)
|
|
@@ -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)
|