@ralphkrauss/codex-account-switcher 0.1.0
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.
- package/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +21 -0
- package/LICENSE.md +21 -0
- package/PUBLISHING.md +116 -0
- package/README.md +112 -0
- package/SECURITY.md +17 -0
- package/SUPPORT.md +14 -0
- package/dist/accounts.d.ts +107 -0
- package/dist/accounts.js +462 -0
- package/dist/accounts.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +285 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/src/accounts.ts +645 -0
- package/src/cli.ts +343 -0
- package/src/index.ts +38 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-06-04
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Initial installable `cx` CLI package for Codex account switching.
|
|
8
|
+
- Safe account storage under `CODEX_HOME/accounts` with POSIX private permissions where supported.
|
|
9
|
+
- Commands for listing, saving, using, logging in, renaming, removing, running Codex, and diagnostics.
|
|
10
|
+
- Strict account-name validation and path traversal protection.
|
|
11
|
+
- Writeback safeguards so deleted active slots are not recreated.
|
|
12
|
+
- npm Trusted Publishing workflow, CI, tests, and package verification scripts.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Local setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
corepack enable
|
|
7
|
+
pnpm install --frozen-lockfile
|
|
8
|
+
pnpm verify
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Development rules
|
|
12
|
+
|
|
13
|
+
- Use Node.js 22+.
|
|
14
|
+
- Keep credential contents out of logs, tests, docs, and fixtures.
|
|
15
|
+
- Write tests for behavior changes before implementation.
|
|
16
|
+
- Prefer small, dependency-light TypeScript.
|
|
17
|
+
- Run `pnpm verify` before opening a PR or tagging a release.
|
|
18
|
+
|
|
19
|
+
## Release readiness
|
|
20
|
+
|
|
21
|
+
Releases are tag-driven through GitHub Actions. See `PUBLISHING.md`.
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ralph Krauss
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/PUBLISHING.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Publishing and Release Process
|
|
2
|
+
|
|
3
|
+
This package is published publicly as:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
@ralphkrauss/codex-account-switcher
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The repository uses npm Trusted Publishing. GitHub Actions publishes from git tags; no long-lived `NPM_TOKEN` is required for the normal release flow.
|
|
10
|
+
|
|
11
|
+
## Mental model
|
|
12
|
+
|
|
13
|
+
- `latest` is the stable npm dist-tag.
|
|
14
|
+
- `next` is the prerelease npm dist-tag.
|
|
15
|
+
- Stable versions look like `0.1.0` and publish to `latest`.
|
|
16
|
+
- Prerelease versions look like `0.1.1-beta.0` and publish to `next`.
|
|
17
|
+
- `npm version ...` updates `package.json`, updates `pnpm-lock.yaml`, creates a git commit, and creates a matching git tag.
|
|
18
|
+
- The `Publish npm` GitHub workflow runs when a `v*.*.*` tag is pushed.
|
|
19
|
+
- Release notes are extracted from the matching `CHANGELOG.md` section.
|
|
20
|
+
|
|
21
|
+
## One-time npm setup
|
|
22
|
+
|
|
23
|
+
The first public publish of a scoped package may need to be done manually so the package exists on npm. After that, configure Trusted Publishing on npm:
|
|
24
|
+
|
|
25
|
+
- Package: `@ralphkrauss/codex-account-switcher`
|
|
26
|
+
- Repository owner: `ralphkrauss`
|
|
27
|
+
- Repository name: `codex-account-switcher`
|
|
28
|
+
- Workflow filename: `publish-npm.yml`
|
|
29
|
+
- Environment: leave empty
|
|
30
|
+
|
|
31
|
+
The workflow has `id-token: write`, which npm uses for OIDC trusted publishing.
|
|
32
|
+
|
|
33
|
+
## Verification before any release
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pnpm install --frozen-lockfile
|
|
37
|
+
pnpm verify
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`pnpm verify` builds, tests, checks publish readiness, resolves the npm dist-tag, audits production dependencies, and runs `npm pack --dry-run`.
|
|
41
|
+
|
|
42
|
+
Before tagging, ensure `CHANGELOG.md` has a non-empty section for the exact version, for example:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
## 0.1.0 - 2026-06-04
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Beta/test release
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
node scripts/extract-changelog-release-notes.mjs <next-beta-version> >/tmp/release-notes.md
|
|
52
|
+
pnpm verify
|
|
53
|
+
npm version prerelease --preid beta
|
|
54
|
+
git push origin main --follow-tags
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Test the beta:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm view @ralphkrauss/codex-account-switcher@next version
|
|
61
|
+
npx -y @ralphkrauss/codex-account-switcher@next doctor
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Stable release
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
node scripts/extract-changelog-release-notes.mjs <next-stable-version> >/tmp/release-notes.md
|
|
68
|
+
pnpm verify
|
|
69
|
+
npm version patch
|
|
70
|
+
git push origin main --follow-tags
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For minor or major releases:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm version minor
|
|
77
|
+
npm version major
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Then push with:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git push origin main --follow-tags
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## What the GitHub Action checks
|
|
87
|
+
|
|
88
|
+
1. Installs dependencies with the lockfile.
|
|
89
|
+
2. Checks the git tag matches `package.json`.
|
|
90
|
+
3. Runs `pnpm verify`.
|
|
91
|
+
4. Chooses npm dist-tag: prerelease => `next`, stable => `latest`.
|
|
92
|
+
5. Skips publishing if the exact package version already exists.
|
|
93
|
+
6. Publishes with `npm publish --access public --provenance --tag <tag>`.
|
|
94
|
+
7. Extracts release notes from `CHANGELOG.md`.
|
|
95
|
+
8. Creates or updates the GitHub Release for the same tag.
|
|
96
|
+
|
|
97
|
+
## Installed-package smoke test
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
package_file="$(npm pack --silent | tail -n 1)"
|
|
101
|
+
temp_dir="$(mktemp -d)"
|
|
102
|
+
cd "$temp_dir"
|
|
103
|
+
npm init -y >/dev/null
|
|
104
|
+
npm install "/path/to/codex-account-switcher/$package_file"
|
|
105
|
+
CODEX_HOME="$temp_dir/codex-home" ./node_modules/.bin/cx --help
|
|
106
|
+
CODEX_HOME="$temp_dir/codex-home" ./node_modules/.bin/cx doctor
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Inspecting npm state
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm view @ralphkrauss/codex-account-switcher version
|
|
113
|
+
npm view @ralphkrauss/codex-account-switcher versions --json
|
|
114
|
+
npm dist-tag ls @ralphkrauss/codex-account-switcher
|
|
115
|
+
npm view @ralphkrauss/codex-account-switcher@next version
|
|
116
|
+
```
|
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Codex Account Switcher
|
|
2
|
+
|
|
3
|
+
`cx` is a small, installable CLI for switching native Codex CLI ChatGPT/OAuth accounts by swapping Codex's `auth.json` safely.
|
|
4
|
+
|
|
5
|
+
It keeps package code out of `~/.codex`; only account data lives there.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @ralphkrauss/codex-account-switcher
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or try without installing:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx -y @ralphkrauss/codex-account-switcher --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Node.js 22+ and the native Codex CLI on `PATH`.
|
|
20
|
+
|
|
21
|
+
## Data layout
|
|
22
|
+
|
|
23
|
+
`cx` uses `CODEX_HOME` when set, otherwise `~/.codex`:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
~/.codex/auth.json # Codex's live auth file
|
|
27
|
+
~/.codex/accounts/<name>.json # saved account slots
|
|
28
|
+
~/.codex/.current-account # active account marker
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
On POSIX, newly-created directories are `0700` and credential copies are `0600` where supported.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cx ls
|
|
37
|
+
cx save personal
|
|
38
|
+
cx save personal --force
|
|
39
|
+
cx login work
|
|
40
|
+
cx use work
|
|
41
|
+
cx run work -- exec "fix the tests"
|
|
42
|
+
cx run -- --help
|
|
43
|
+
cx rename work work-prod
|
|
44
|
+
cx rm work-prod
|
|
45
|
+
cx doctor
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Backward-friendly shortcut:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cx work exec "fix the tests"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This switches to `work`, then launches `codex exec "fix the tests"`.
|
|
55
|
+
|
|
56
|
+
`cx` with no args launches `codex` if a live `auth.json` exists. If not, it prints setup guidance.
|
|
57
|
+
|
|
58
|
+
## Account names
|
|
59
|
+
|
|
60
|
+
Names must contain only:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
A-Z a-z 0-9 . _ -
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Empty names, dot-only names, slashes, backslashes, spaces, unicode, and path traversal are rejected.
|
|
67
|
+
|
|
68
|
+
## Behavior notes
|
|
69
|
+
|
|
70
|
+
- Before switching or logging in, `cx` writes the live `auth.json` back to the current saved slot if it is valid and still exists. This preserves token refreshes.
|
|
71
|
+
- Writeback never recreates a deleted active slot.
|
|
72
|
+
- `cx rm <active>` removes the saved slot and clears `.current-account`; it leaves live `auth.json` untouched until you switch or login.
|
|
73
|
+
- `cx save` and `cx rename` refuse overwrites unless `--force` is passed.
|
|
74
|
+
- `cx doctor` never prints token contents.
|
|
75
|
+
|
|
76
|
+
## Migrating from the prototype shell function
|
|
77
|
+
|
|
78
|
+
If you previously had `/home/ubuntu/.codex/codex-acct.sh` sourced from `.bashrc`:
|
|
79
|
+
|
|
80
|
+
1. Install this package globally.
|
|
81
|
+
2. Remove the marked `.bashrc` block that sources `~/.codex/codex-acct.sh`.
|
|
82
|
+
3. Delete only the old script:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
rm -f ~/.codex/codex-acct.sh
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Keep these files/directories:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
~/.codex/auth.json
|
|
92
|
+
~/.codex/accounts/
|
|
93
|
+
~/.codex/.current-account
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
They are the data this package uses.
|
|
97
|
+
|
|
98
|
+
## Uninstall
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npm uninstall -g @ralphkrauss/codex-account-switcher
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This removes the CLI only. It does not remove your Codex credentials.
|
|
105
|
+
|
|
106
|
+
To remove stored account slots too:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
rm -rf ~/.codex/accounts ~/.codex/.current-account
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Do not delete `~/.codex/auth.json` unless you want to log Codex out.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
This package manages copies of Codex `auth.json` files. Treat all account JSON files as secrets.
|
|
4
|
+
|
|
5
|
+
## Supported versions
|
|
6
|
+
|
|
7
|
+
The latest published version receives fixes.
|
|
8
|
+
|
|
9
|
+
## Reporting vulnerabilities
|
|
10
|
+
|
|
11
|
+
Email Ralph Krauss at ralph@krauss.be. Do not open public issues containing credentials or exploit details.
|
|
12
|
+
|
|
13
|
+
## Handling secrets
|
|
14
|
+
|
|
15
|
+
- Never paste `auth.json` contents in issues, PRs, logs, or screenshots.
|
|
16
|
+
- `cx doctor` reports sizes and paths only; it should not print token contents.
|
|
17
|
+
- Stored account files are created with `0600` permissions on POSIX where supported.
|
package/SUPPORT.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Support
|
|
2
|
+
|
|
3
|
+
For usage questions, open an issue at:
|
|
4
|
+
|
|
5
|
+
https://github.com/ralphkrauss/codex-account-switcher/issues
|
|
6
|
+
|
|
7
|
+
Include:
|
|
8
|
+
|
|
9
|
+
- OS and shell
|
|
10
|
+
- Node.js version
|
|
11
|
+
- `cx --version`
|
|
12
|
+
- `cx doctor` output
|
|
13
|
+
|
|
14
|
+
Do not include `auth.json` or account JSON contents.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export declare const AUTH_NON_EMPTY_BYTES = 100;
|
|
2
|
+
export declare const ACCOUNT_NAME_PATTERN: RegExp;
|
|
3
|
+
export interface CodexPaths {
|
|
4
|
+
readonly home: string;
|
|
5
|
+
readonly accountsDir: string;
|
|
6
|
+
readonly currentFile: string;
|
|
7
|
+
readonly authFile: string;
|
|
8
|
+
}
|
|
9
|
+
export interface OperationOptions {
|
|
10
|
+
readonly paths?: CodexPaths;
|
|
11
|
+
}
|
|
12
|
+
export interface ForceOptions extends OperationOptions {
|
|
13
|
+
readonly force?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface SpawnCodexOptions {
|
|
16
|
+
readonly command?: string;
|
|
17
|
+
readonly args?: readonly string[];
|
|
18
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
19
|
+
readonly cwd?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AccountEntry {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly file: string;
|
|
24
|
+
readonly active: boolean;
|
|
25
|
+
}
|
|
26
|
+
export type CurrentMarker = {
|
|
27
|
+
readonly state: 'missing';
|
|
28
|
+
} | {
|
|
29
|
+
readonly state: 'valid';
|
|
30
|
+
readonly name: string;
|
|
31
|
+
} | {
|
|
32
|
+
readonly state: 'invalid';
|
|
33
|
+
readonly raw: string;
|
|
34
|
+
readonly reason: string;
|
|
35
|
+
};
|
|
36
|
+
export interface AccountList {
|
|
37
|
+
readonly home: string;
|
|
38
|
+
readonly accountsDir: string;
|
|
39
|
+
readonly current: string | null;
|
|
40
|
+
readonly currentMarker: CurrentMarker;
|
|
41
|
+
readonly accounts: readonly AccountEntry[];
|
|
42
|
+
}
|
|
43
|
+
export interface WritebackResult {
|
|
44
|
+
readonly performed: boolean;
|
|
45
|
+
readonly reason?: string;
|
|
46
|
+
readonly account?: string;
|
|
47
|
+
}
|
|
48
|
+
export interface RenameResult {
|
|
49
|
+
readonly oldName: string;
|
|
50
|
+
readonly newName: string;
|
|
51
|
+
readonly overwrote: boolean;
|
|
52
|
+
readonly currentUpdated: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface RemoveResult {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly wasActive: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface DoctorReport {
|
|
59
|
+
readonly packageName: string;
|
|
60
|
+
readonly version: string;
|
|
61
|
+
readonly nodeVersion: string;
|
|
62
|
+
readonly platform: string;
|
|
63
|
+
readonly codexHome: string;
|
|
64
|
+
readonly accountsDir: string;
|
|
65
|
+
readonly homeExists: boolean;
|
|
66
|
+
readonly accountsDirExists: boolean;
|
|
67
|
+
readonly authJson: {
|
|
68
|
+
readonly exists: boolean;
|
|
69
|
+
readonly size: number;
|
|
70
|
+
readonly looksNonEmpty: boolean;
|
|
71
|
+
};
|
|
72
|
+
readonly current: {
|
|
73
|
+
readonly state: CurrentMarker['state'];
|
|
74
|
+
readonly name: string | null;
|
|
75
|
+
readonly slotExists: boolean;
|
|
76
|
+
readonly reason?: string;
|
|
77
|
+
};
|
|
78
|
+
readonly accounts: readonly string[];
|
|
79
|
+
readonly codexExecutable: string | null;
|
|
80
|
+
readonly warnings: readonly string[];
|
|
81
|
+
}
|
|
82
|
+
export declare class CxError extends Error {
|
|
83
|
+
readonly exitCode: number;
|
|
84
|
+
constructor(message: string, exitCode?: number);
|
|
85
|
+
}
|
|
86
|
+
export declare function getCodexHome(env?: NodeJS.ProcessEnv): string;
|
|
87
|
+
export declare function getCodexPaths(env?: NodeJS.ProcessEnv): CodexPaths;
|
|
88
|
+
export declare function validateAccountName(name: string): string;
|
|
89
|
+
export declare function accountPathForName(paths: CodexPaths, name: string): string;
|
|
90
|
+
export declare function authFileExists(paths?: CodexPaths): Promise<boolean>;
|
|
91
|
+
export declare function authLooksNonEmpty(paths?: CodexPaths): Promise<boolean>;
|
|
92
|
+
export declare function readCurrentMarker(paths?: CodexPaths): Promise<CurrentMarker>;
|
|
93
|
+
export declare function listAccountNames(paths?: CodexPaths): Promise<string[]>;
|
|
94
|
+
export declare function listAccounts(paths?: CodexPaths): Promise<AccountList>;
|
|
95
|
+
export declare function writebackCurrentAccount(options?: OperationOptions): Promise<WritebackResult>;
|
|
96
|
+
export declare function saveAccount(name: string, options?: ForceOptions): Promise<void>;
|
|
97
|
+
export declare function useAccount(name: string, options?: OperationOptions): Promise<WritebackResult>;
|
|
98
|
+
export declare function renameAccount(oldName: string, newName: string, options?: ForceOptions): Promise<RenameResult>;
|
|
99
|
+
export declare function removeAccount(name: string, options?: OperationOptions): Promise<RemoveResult>;
|
|
100
|
+
export declare function runCodex(codexArgs?: readonly string[], options?: SpawnCodexOptions): Promise<number>;
|
|
101
|
+
export declare function loginAccount(name: string, options?: ForceOptions & SpawnCodexOptions): Promise<WritebackResult>;
|
|
102
|
+
export declare function switchAndRunCodex(name: string, codexArgs?: readonly string[], options?: OperationOptions & SpawnCodexOptions): Promise<number>;
|
|
103
|
+
export declare function resolveExecutable(command: string, env?: NodeJS.ProcessEnv): Promise<string | null>;
|
|
104
|
+
export declare function inspectDoctor(metadata: {
|
|
105
|
+
readonly packageName: string;
|
|
106
|
+
readonly version: string;
|
|
107
|
+
}, env?: NodeJS.ProcessEnv): Promise<DoctorReport>;
|