@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 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.
@@ -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>;