@bjesuiter/codex-switcher 1.1.0 → 1.2.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/README.md +8 -2
- package/cdx.mjs +71 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,10 @@ Opens your browser to authenticate with OpenAI. After successful login, your cre
|
|
|
40
40
|
cdx switch
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Interactive picker to select an account. Writes credentials to
|
|
43
|
+
Interactive picker to select an account. Writes credentials to:
|
|
44
|
+
- `~/.local/share/opencode/auth.json` (OpenCode)
|
|
45
|
+
- `~/.pi/agent/auth.json` (Pi agent, or `$PI_CODING_AGENT_DIR/auth.json` when `PI_CODING_AGENT_DIR` is set)
|
|
46
|
+
- `~/.codex/auth.json` (Codex CLI; requires `id_token`)
|
|
44
47
|
|
|
45
48
|
```bash
|
|
46
49
|
cdx switch --next
|
|
@@ -101,7 +104,10 @@ Running `cdx` without arguments opens an interactive menu to:
|
|
|
101
104
|
|
|
102
105
|
- OAuth credentials are stored securely in macOS Keychain
|
|
103
106
|
- Account list is stored in `~/.config/cdx/accounts.json`
|
|
104
|
-
- Active account credentials are written to
|
|
107
|
+
- Active account credentials are written to:
|
|
108
|
+
- `~/.local/share/opencode/auth.json`
|
|
109
|
+
- `~/.pi/agent/auth.json` (or `$PI_CODING_AGENT_DIR/auth.json`)
|
|
110
|
+
- `~/.codex/auth.json` (when `id_token` exists)
|
|
105
111
|
|
|
106
112
|
## For Developers
|
|
107
113
|
|
package/cdx.mjs
CHANGED
|
@@ -11,18 +11,24 @@ import { randomBytes } from "node:crypto";
|
|
|
11
11
|
import http from "node:http";
|
|
12
12
|
|
|
13
13
|
//#region package.json
|
|
14
|
-
var version = "1.
|
|
14
|
+
var version = "1.2.0";
|
|
15
15
|
|
|
16
16
|
//#endregion
|
|
17
17
|
//#region lib/paths.ts
|
|
18
18
|
const defaultConfigDir = path.join(os.homedir(), ".config", "cdx");
|
|
19
|
-
const
|
|
19
|
+
const resolvePiAuthPath = () => {
|
|
20
|
+
const piAgentDir = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
21
|
+
if (piAgentDir) return path.join(piAgentDir, "auth.json");
|
|
22
|
+
return path.join(os.homedir(), ".pi", "agent", "auth.json");
|
|
23
|
+
};
|
|
24
|
+
const createDefaultPaths = () => ({
|
|
20
25
|
configDir: defaultConfigDir,
|
|
21
26
|
configPath: path.join(defaultConfigDir, "accounts.json"),
|
|
22
27
|
authPath: path.join(os.homedir(), ".local", "share", "opencode", "auth.json"),
|
|
23
|
-
codexAuthPath: path.join(os.homedir(), ".codex", "auth.json")
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
codexAuthPath: path.join(os.homedir(), ".codex", "auth.json"),
|
|
29
|
+
piAuthPath: resolvePiAuthPath()
|
|
30
|
+
});
|
|
31
|
+
let currentPaths = createDefaultPaths();
|
|
26
32
|
const getPaths = () => currentPaths;
|
|
27
33
|
const setPaths = (paths) => {
|
|
28
34
|
currentPaths = {
|
|
@@ -32,13 +38,14 @@ const setPaths = (paths) => {
|
|
|
32
38
|
if (paths.configDir && !paths.configPath) currentPaths.configPath = path.join(paths.configDir, "accounts.json");
|
|
33
39
|
};
|
|
34
40
|
const resetPaths = () => {
|
|
35
|
-
currentPaths =
|
|
41
|
+
currentPaths = createDefaultPaths();
|
|
36
42
|
};
|
|
37
43
|
const createTestPaths = (testDir) => ({
|
|
38
44
|
configDir: path.join(testDir, "config"),
|
|
39
45
|
configPath: path.join(testDir, "config", "accounts.json"),
|
|
40
46
|
authPath: path.join(testDir, "auth", "auth.json"),
|
|
41
|
-
codexAuthPath: path.join(testDir, "codex", "auth.json")
|
|
47
|
+
codexAuthPath: path.join(testDir, "codex", "auth.json"),
|
|
48
|
+
piAuthPath: path.join(testDir, "pi", "auth.json")
|
|
42
49
|
});
|
|
43
50
|
|
|
44
51
|
//#endregion
|
|
@@ -80,11 +87,26 @@ const writeCodexAuthFile = async (payload) => {
|
|
|
80
87
|
existing.last_refresh = (/* @__PURE__ */ new Date()).toISOString();
|
|
81
88
|
await writeFile(codexAuthPath, JSON.stringify(existing, null, 2), "utf8");
|
|
82
89
|
};
|
|
90
|
+
const writePiAuthFile = async (payload) => {
|
|
91
|
+
const { piAuthPath } = getPaths();
|
|
92
|
+
await mkdir(path.dirname(piAuthPath), { recursive: true });
|
|
93
|
+
const existing = await readExistingJson(piAuthPath);
|
|
94
|
+
existing["openai-codex"] = {
|
|
95
|
+
type: "oauth",
|
|
96
|
+
access: payload.access,
|
|
97
|
+
refresh: payload.refresh,
|
|
98
|
+
expires: payload.expires,
|
|
99
|
+
accountId: payload.accountId
|
|
100
|
+
};
|
|
101
|
+
await writeFile(piAuthPath, JSON.stringify(existing, null, 2), "utf8");
|
|
102
|
+
};
|
|
83
103
|
const writeAllAuthFiles = async (payload) => {
|
|
84
104
|
await writeAuthFile(payload);
|
|
105
|
+
await writePiAuthFile(payload);
|
|
85
106
|
if (payload.idToken) {
|
|
86
107
|
await writeCodexAuthFile(payload);
|
|
87
108
|
return {
|
|
109
|
+
piWritten: true,
|
|
88
110
|
codexWritten: true,
|
|
89
111
|
codexMissingIdToken: false,
|
|
90
112
|
codexCleared: false
|
|
@@ -99,6 +121,7 @@ const writeAllAuthFiles = async (payload) => {
|
|
|
99
121
|
codexCleared = false;
|
|
100
122
|
}
|
|
101
123
|
return {
|
|
124
|
+
piWritten: true,
|
|
102
125
|
codexWritten: false,
|
|
103
126
|
codexMissingIdToken: true,
|
|
104
127
|
codexCleared
|
|
@@ -608,6 +631,25 @@ const readCodexAuthAccount = async () => {
|
|
|
608
631
|
};
|
|
609
632
|
}
|
|
610
633
|
};
|
|
634
|
+
const readPiAuthAccount = async () => {
|
|
635
|
+
const { piAuthPath } = getPaths();
|
|
636
|
+
if (!existsSync(piAuthPath)) return {
|
|
637
|
+
exists: false,
|
|
638
|
+
accountId: null
|
|
639
|
+
};
|
|
640
|
+
try {
|
|
641
|
+
const raw = await readFile(piAuthPath, "utf8");
|
|
642
|
+
return {
|
|
643
|
+
exists: true,
|
|
644
|
+
accountId: JSON.parse(raw)["openai-codex"]?.accountId ?? null
|
|
645
|
+
};
|
|
646
|
+
} catch {
|
|
647
|
+
return {
|
|
648
|
+
exists: true,
|
|
649
|
+
accountId: null
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
};
|
|
611
653
|
const getAccountStatus = (accountId, isCurrent, label) => {
|
|
612
654
|
const keychainExists = keychainPayloadExists(accountId);
|
|
613
655
|
let expiresAt = null;
|
|
@@ -636,11 +678,16 @@ const getStatus = async () => {
|
|
|
636
678
|
accounts.push(getAccountStatus(account.accountId, i === config.current, account.label));
|
|
637
679
|
}
|
|
638
680
|
}
|
|
639
|
-
const [opencodeAuth, codexAuth] = await Promise.all([
|
|
681
|
+
const [opencodeAuth, codexAuth, piAuth] = await Promise.all([
|
|
682
|
+
readOpenCodeAuthAccount(),
|
|
683
|
+
readCodexAuthAccount(),
|
|
684
|
+
readPiAuthAccount()
|
|
685
|
+
]);
|
|
640
686
|
return {
|
|
641
687
|
accounts,
|
|
642
688
|
opencodeAuth,
|
|
643
|
-
codexAuth
|
|
689
|
+
codexAuth,
|
|
690
|
+
piAuth
|
|
644
691
|
};
|
|
645
692
|
};
|
|
646
693
|
|
|
@@ -709,9 +756,11 @@ const handleSwitchAccount = async () => {
|
|
|
709
756
|
await saveConfig(config);
|
|
710
757
|
const displayName = selectedAccount.label ?? selectedAccount.accountId;
|
|
711
758
|
const opencodeMark = "✓";
|
|
759
|
+
const piMark = result.piWritten ? "✓" : "✗";
|
|
712
760
|
const codexMark = result.codexWritten ? "✓" : result.codexCleared ? "⚠ missing id_token (cleared)" : "⚠ missing id_token";
|
|
713
761
|
p.log.success(`Switched to account ${displayName}`);
|
|
714
762
|
p.log.message(` OpenCode: ${opencodeMark}`);
|
|
763
|
+
p.log.message(` Pi Agent: ${piMark}`);
|
|
715
764
|
p.log.message(` Codex CLI: ${codexMark}`);
|
|
716
765
|
};
|
|
717
766
|
const handleAddAccount = async () => {
|
|
@@ -748,9 +797,11 @@ const handleRefreshAccount = async () => {
|
|
|
748
797
|
else {
|
|
749
798
|
const authResult = await writeActiveAuthFilesIfCurrent(result.accountId);
|
|
750
799
|
if (authResult) {
|
|
800
|
+
const piMark = authResult.piWritten ? "✓" : "✗";
|
|
751
801
|
const codexMark = authResult.codexWritten ? "✓" : authResult.codexCleared ? "⚠ missing id_token (cleared)" : "⚠ missing id_token";
|
|
752
802
|
p.log.message("Updated active auth files:");
|
|
753
803
|
p.log.message(" OpenCode: ✓");
|
|
804
|
+
p.log.message(` Pi Agent: ${piMark}`);
|
|
754
805
|
p.log.message(` Codex CLI: ${codexMark}`);
|
|
755
806
|
}
|
|
756
807
|
}
|
|
@@ -862,9 +913,11 @@ const handleStatus = async () => {
|
|
|
862
913
|
}
|
|
863
914
|
const ocStatus = status.opencodeAuth.exists ? `active: ${status.opencodeAuth.accountId ?? "unknown"}` : "not found";
|
|
864
915
|
const cxStatus = status.codexAuth.exists ? `active: ${status.codexAuth.accountId ?? "unknown"}` : "not found";
|
|
916
|
+
const piStatus = status.piAuth.exists ? `active: ${status.piAuth.accountId ?? "unknown"}` : "not found";
|
|
865
917
|
p.log.info(`Auth files:`);
|
|
866
918
|
p.log.message(` OpenCode: ${ocStatus}`);
|
|
867
919
|
p.log.message(` Codex CLI: ${cxStatus}`);
|
|
920
|
+
p.log.message(` Pi Agent: ${piStatus}`);
|
|
868
921
|
};
|
|
869
922
|
const runInteractiveMode = async () => {
|
|
870
923
|
p.intro("cdx - OpenAI Account Switcher");
|
|
@@ -1118,9 +1171,11 @@ const switchNext = async () => {
|
|
|
1118
1171
|
await saveConfig(config);
|
|
1119
1172
|
const displayName = nextAccount.label ?? payload.accountId;
|
|
1120
1173
|
const opencodeMark = "✓";
|
|
1174
|
+
const piMark = result.piWritten ? "✓" : "✗";
|
|
1121
1175
|
const codexMark = result.codexWritten ? "✓" : result.codexCleared ? "⚠ missing id_token (cleared)" : "⚠ missing id_token";
|
|
1122
1176
|
process.stdout.write(`Switched to account ${displayName}\n`);
|
|
1123
1177
|
process.stdout.write(` OpenCode: ${opencodeMark}\n`);
|
|
1178
|
+
process.stdout.write(` Pi Agent: ${piMark}\n`);
|
|
1124
1179
|
process.stdout.write(` Codex CLI: ${codexMark}\n`);
|
|
1125
1180
|
};
|
|
1126
1181
|
const switchToAccount = async (identifier) => {
|
|
@@ -1133,9 +1188,11 @@ const switchToAccount = async (identifier) => {
|
|
|
1133
1188
|
await saveConfig(config);
|
|
1134
1189
|
const displayName = account.label ?? account.accountId;
|
|
1135
1190
|
const opencodeMark = "✓";
|
|
1191
|
+
const piMark = result.piWritten ? "✓" : "✗";
|
|
1136
1192
|
const codexMark = result.codexWritten ? "✓" : result.codexCleared ? "⚠ missing id_token (cleared)" : "⚠ missing id_token";
|
|
1137
1193
|
process.stdout.write(`Switched to account ${displayName}\n`);
|
|
1138
1194
|
process.stdout.write(` OpenCode: ${opencodeMark}\n`);
|
|
1195
|
+
process.stdout.write(` Pi Agent: ${piMark}\n`);
|
|
1139
1196
|
process.stdout.write(` Codex CLI: ${codexMark}\n`);
|
|
1140
1197
|
};
|
|
1141
1198
|
const interactiveMode = runInteractiveMode;
|
|
@@ -1167,9 +1224,11 @@ const createProgram = (deps = {}) => {
|
|
|
1167
1224
|
}
|
|
1168
1225
|
const authResult = await writeActiveAuthFilesIfCurrent(result.accountId);
|
|
1169
1226
|
if (authResult) {
|
|
1227
|
+
const piMark = authResult.piWritten ? "✓" : "✗";
|
|
1170
1228
|
const codexMark = authResult.codexWritten ? "✓" : authResult.codexCleared ? "⚠ missing id_token (cleared)" : "⚠ missing id_token";
|
|
1171
1229
|
process.stdout.write("Updated active auth files:\n");
|
|
1172
1230
|
process.stdout.write(" OpenCode: ✓\n");
|
|
1231
|
+
process.stdout.write(` Pi Agent: ${piMark}\n`);
|
|
1173
1232
|
process.stdout.write(` Codex CLI: ${codexMark}\n`);
|
|
1174
1233
|
}
|
|
1175
1234
|
} else await handleRefreshAccount();
|
|
@@ -1241,6 +1300,8 @@ const createProgram = (deps = {}) => {
|
|
|
1241
1300
|
process.stdout.write(` OpenCode: ${ocStatus}\n`);
|
|
1242
1301
|
const cxStatus = status.codexAuth.exists ? `active: ${resolveLabel(status.codexAuth.accountId)}` : "not found";
|
|
1243
1302
|
process.stdout.write(` Codex CLI: ${cxStatus}\n`);
|
|
1303
|
+
const piStatus = status.piAuth.exists ? `active: ${resolveLabel(status.piAuth.accountId)}` : "not found";
|
|
1304
|
+
process.stdout.write(` Pi Agent: ${piStatus}\n`);
|
|
1244
1305
|
process.stdout.write("\n");
|
|
1245
1306
|
} catch (error) {
|
|
1246
1307
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1320,4 +1381,4 @@ if (import.meta.main) main().catch((error) => {
|
|
|
1320
1381
|
});
|
|
1321
1382
|
|
|
1322
1383
|
//#endregion
|
|
1323
|
-
export { createProgram, createTestPaths, getPaths, interactiveMode, loadConfig, resetPaths, runInteractiveMode, saveConfig, setPaths, switchNext, switchToAccount, writeAllAuthFiles, writeAuthFile, writeCodexAuthFile };
|
|
1384
|
+
export { createProgram, createTestPaths, getPaths, interactiveMode, loadConfig, resetPaths, runInteractiveMode, saveConfig, setPaths, switchNext, switchToAccount, writeAllAuthFiles, writeAuthFile, writeCodexAuthFile, writePiAuthFile };
|