@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.
Files changed (3) hide show
  1. package/README.md +8 -2
  2. package/cdx.mjs +71 -10
  3. 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 `~/.local/share/opencode/auth.json`.
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 `~/.local/share/opencode/auth.json`
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.1.0";
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 defaultPaths = {
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
- let currentPaths = { ...defaultPaths };
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 = { ...defaultPaths };
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([readOpenCodeAuthAccount(), readCodexAuthAccount()]);
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bjesuiter/codex-switcher",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "CLI tool to switch between multiple OpenAI accounts for OpenCode",
6
6
  "bin": {