@imdeadpool/codex-account-switcher 0.1.13 → 0.1.15
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 +2 -0
- package/dist/lib/accounts/account-service.d.ts +4 -0
- package/dist/lib/accounts/account-service.js +78 -3
- package/dist/lib/config/login-hook.js +14 -16
- package/dist/tests/login-hook.test.js +2 -0
- package/dist/tests/save-account-safety.test.js +111 -3
- package/package.json +4 -4
- package/scripts/postinstall-login-hook.cjs +65 -20
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ official `codex login`.
|
|
|
27
27
|
|
|
28
28
|
On later global updates, if the hook is already installed, `codex-auth` refreshes the
|
|
29
29
|
hook block automatically to the latest template (no manual remove/setup needed).
|
|
30
|
+
The refreshed hook always wraps `codex` in your shell so sync/restore still run even if
|
|
31
|
+
another shell config already defined `codex` as a function.
|
|
30
32
|
|
|
31
33
|
- Choose `y` to enable fully automatic login snapshot capture.
|
|
32
34
|
- Choose `n` (default) to skip.
|
|
@@ -75,10 +75,14 @@ export declare class AccountService {
|
|
|
75
75
|
private isExternalSyncForced;
|
|
76
76
|
private resolveSessionScopeKey;
|
|
77
77
|
private getSessionAccountName;
|
|
78
|
+
private getActiveSessionAccountName;
|
|
78
79
|
private setSessionAccountName;
|
|
79
80
|
private clearSessionAccountName;
|
|
80
81
|
private readSessionMap;
|
|
81
82
|
private writeSessionMap;
|
|
83
|
+
private isSessionPinnedToActiveCodex;
|
|
84
|
+
private readChildPids;
|
|
85
|
+
private isCodexProcess;
|
|
82
86
|
private snapshotsShareIdentity;
|
|
83
87
|
private renderSnapshotIdentity;
|
|
84
88
|
}
|
|
@@ -16,6 +16,7 @@ const service_manager_1 = require("./service-manager");
|
|
|
16
16
|
const ACCOUNT_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._@+-]*$/;
|
|
17
17
|
const EXTERNAL_SYNC_FORCE_ENV = "CODEX_AUTH_FORCE_EXTERNAL_SYNC";
|
|
18
18
|
const SESSION_KEY_ENV = "CODEX_AUTH_SESSION_KEY";
|
|
19
|
+
const SESSION_ACTIVE_OVERRIDE_ENV = "CODEX_AUTH_SESSION_ACTIVE_OVERRIDE";
|
|
19
20
|
class AccountService {
|
|
20
21
|
async syncExternalAuthSnapshotIfNeeded() {
|
|
21
22
|
const authPath = (0, paths_1.resolveAuthPath)();
|
|
@@ -33,7 +34,7 @@ class AccountService {
|
|
|
33
34
|
autoSwitchDisabled: false,
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
|
-
const sessionAccountName = await this.
|
|
37
|
+
const sessionAccountName = await this.getActiveSessionAccountName();
|
|
37
38
|
if (sessionAccountName) {
|
|
38
39
|
const sessionSnapshotPath = this.accountFilePath(sessionAccountName);
|
|
39
40
|
if (await this.pathExists(sessionSnapshotPath)) {
|
|
@@ -84,7 +85,7 @@ class AccountService {
|
|
|
84
85
|
};
|
|
85
86
|
}
|
|
86
87
|
async restoreSessionSnapshotIfNeeded() {
|
|
87
|
-
const sessionAccountName = await this.
|
|
88
|
+
const sessionAccountName = await this.getActiveSessionAccountName();
|
|
88
89
|
if (!sessionAccountName) {
|
|
89
90
|
return { restored: false };
|
|
90
91
|
}
|
|
@@ -191,7 +192,7 @@ class AccountService {
|
|
|
191
192
|
});
|
|
192
193
|
}
|
|
193
194
|
async getCurrentAccountName() {
|
|
194
|
-
const sessionAccountName = await this.
|
|
195
|
+
const sessionAccountName = await this.getActiveSessionAccountName();
|
|
195
196
|
if (sessionAccountName) {
|
|
196
197
|
const sessionSnapshotPath = this.accountFilePath(sessionAccountName);
|
|
197
198
|
if (await this.pathExists(sessionSnapshotPath)) {
|
|
@@ -740,6 +741,16 @@ class AccountService {
|
|
|
740
741
|
return null;
|
|
741
742
|
}
|
|
742
743
|
}
|
|
744
|
+
async getActiveSessionAccountName() {
|
|
745
|
+
const sessionAccountName = await this.getSessionAccountName();
|
|
746
|
+
if (!sessionAccountName)
|
|
747
|
+
return null;
|
|
748
|
+
const sessionIsActive = await this.isSessionPinnedToActiveCodex();
|
|
749
|
+
if (sessionIsActive)
|
|
750
|
+
return sessionAccountName;
|
|
751
|
+
await this.clearSessionAccountName();
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
743
754
|
async setSessionAccountName(accountName) {
|
|
744
755
|
const sessionKey = this.resolveSessionScopeKey();
|
|
745
756
|
if (!sessionKey)
|
|
@@ -808,6 +819,70 @@ class AccountService {
|
|
|
808
819
|
await this.ensureDir(node_path_1.default.dirname(sessionMapPath));
|
|
809
820
|
await promises_1.default.writeFile(sessionMapPath, `${JSON.stringify(sessionMap, null, 2)}\n`, "utf8");
|
|
810
821
|
}
|
|
822
|
+
async isSessionPinnedToActiveCodex() {
|
|
823
|
+
var _a;
|
|
824
|
+
const override = (_a = process.env[SESSION_ACTIVE_OVERRIDE_ENV]) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
|
|
825
|
+
if (override) {
|
|
826
|
+
if (["1", "true", "yes", "on"].includes(override))
|
|
827
|
+
return true;
|
|
828
|
+
if (["0", "false", "no", "off"].includes(override))
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
const sessionKey = this.resolveSessionScopeKey();
|
|
832
|
+
if (!sessionKey)
|
|
833
|
+
return false;
|
|
834
|
+
if (sessionKey.startsWith("session:")) {
|
|
835
|
+
return true;
|
|
836
|
+
}
|
|
837
|
+
if (process.platform !== "linux") {
|
|
838
|
+
return true;
|
|
839
|
+
}
|
|
840
|
+
const ppidMatch = sessionKey.match(/^ppid:(\d+)$/);
|
|
841
|
+
if (!ppidMatch)
|
|
842
|
+
return false;
|
|
843
|
+
const parentPid = Number(ppidMatch[1]);
|
|
844
|
+
if (!Number.isFinite(parentPid) || parentPid <= 1)
|
|
845
|
+
return false;
|
|
846
|
+
const childPids = await this.readChildPids(parentPid);
|
|
847
|
+
if (childPids.length === 0)
|
|
848
|
+
return false;
|
|
849
|
+
for (const childPid of childPids) {
|
|
850
|
+
if (await this.isCodexProcess(childPid)) {
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
async readChildPids(parentPid) {
|
|
857
|
+
try {
|
|
858
|
+
const childrenRaw = await promises_1.default.readFile(`/proc/${parentPid}/task/${parentPid}/children`, "utf8");
|
|
859
|
+
return childrenRaw
|
|
860
|
+
.split(/\s+/)
|
|
861
|
+
.map((value) => Number(value))
|
|
862
|
+
.filter((value) => Number.isInteger(value) && value > 1);
|
|
863
|
+
}
|
|
864
|
+
catch {
|
|
865
|
+
return [];
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
async isCodexProcess(pid) {
|
|
869
|
+
try {
|
|
870
|
+
const cmdline = await promises_1.default.readFile(`/proc/${pid}/cmdline`, "utf8");
|
|
871
|
+
const normalized = cmdline.replace(/\0/g, " ").trim();
|
|
872
|
+
if (!normalized)
|
|
873
|
+
return false;
|
|
874
|
+
if (/\bcodex-auth\b/.test(normalized))
|
|
875
|
+
return false;
|
|
876
|
+
if (/(^|\s|\/)codex(\s|$)/.test(normalized))
|
|
877
|
+
return true;
|
|
878
|
+
if (/(^|\s|\/)codex-linux-[^\s]*($|\s)/.test(normalized))
|
|
879
|
+
return true;
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
catch {
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
811
886
|
snapshotsShareIdentity(a, b) {
|
|
812
887
|
var _a, _b;
|
|
813
888
|
if (a.authMode !== "chatgpt" || b.authMode !== "chatgpt") {
|
|
@@ -45,22 +45,20 @@ function renderLoginHookBlock() {
|
|
|
45
45
|
" [[ -w \"$__tty_target\" ]] || __tty_target=/dev/stdout",
|
|
46
46
|
" printf '\\033[>4m\\033[<u\\033[?2026l\\033[?1004l\\033[?1l\\033[?2004l\\033[?1000l\\033[?1002l\\033[?1003l\\033[?1006l\\033[?1015l\\033[?1049l\\033[0m\\033[?25h\\033[H\\033>' >\"$__tty_target\" 2>/dev/null || true",
|
|
47
47
|
"}",
|
|
48
|
-
"
|
|
49
|
-
" codex
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
" }",
|
|
63
|
-
"fi",
|
|
48
|
+
"codex() {",
|
|
49
|
+
" if command -v codex-auth >/dev/null 2>&1; then",
|
|
50
|
+
" command codex-auth restore-session >/dev/null 2>&1 || true",
|
|
51
|
+
" fi",
|
|
52
|
+
" command codex \"$@\"",
|
|
53
|
+
" local __codex_exit=$?",
|
|
54
|
+
" if command -v codex-auth >/dev/null 2>&1; then",
|
|
55
|
+
" CODEX_AUTH_FORCE_EXTERNAL_SYNC=1 command codex-auth status >/dev/null 2>&1 || true",
|
|
56
|
+
" fi",
|
|
57
|
+
" if [[ -z \"${CODEX_AUTH_SKIP_TTY_RESTORE:-}\" ]]; then",
|
|
58
|
+
" __codex_auth_restore_tty",
|
|
59
|
+
" fi",
|
|
60
|
+
" return $__codex_exit",
|
|
61
|
+
"}",
|
|
64
62
|
exports.LOGIN_HOOK_MARK_END,
|
|
65
63
|
].join("\n");
|
|
66
64
|
}
|
|
@@ -86,9 +86,11 @@ async function withTempRcFile(t, fn) {
|
|
|
86
86
|
(0, node_test_1.default)("renderLoginHookBlock includes terminal-mode restore guard", () => {
|
|
87
87
|
const hook = (0, login_hook_1.renderLoginHookBlock)();
|
|
88
88
|
strict_1.default.ok(hook.includes("__codex_auth_restore_tty"));
|
|
89
|
+
strict_1.default.ok(hook.includes("codex() {"));
|
|
89
90
|
strict_1.default.ok(hook.includes("command codex-auth restore-session"));
|
|
90
91
|
strict_1.default.ok(hook.includes("CODEX_AUTH_FORCE_EXTERNAL_SYNC=1 command codex-auth status"));
|
|
91
92
|
strict_1.default.ok(!hook.includes("__first_non_flag"));
|
|
93
|
+
strict_1.default.ok(!hook.includes("if ! typeset -f codex"));
|
|
92
94
|
strict_1.default.ok(hook.includes("\\033[>4m"));
|
|
93
95
|
strict_1.default.ok(hook.includes("\\033[<u"));
|
|
94
96
|
strict_1.default.ok(hook.includes("\\033[?2026l"));
|
|
@@ -51,6 +51,7 @@ async function withIsolatedCodexDir(t, fn) {
|
|
|
51
51
|
CODEX_AUTH_ACCOUNTS_DIR: process.env.CODEX_AUTH_ACCOUNTS_DIR,
|
|
52
52
|
CODEX_AUTH_JSON_PATH: process.env.CODEX_AUTH_JSON_PATH,
|
|
53
53
|
CODEX_AUTH_CURRENT_PATH: process.env.CODEX_AUTH_CURRENT_PATH,
|
|
54
|
+
CODEX_AUTH_SESSION_ACTIVE_OVERRIDE: process.env.CODEX_AUTH_SESSION_ACTIVE_OVERRIDE,
|
|
54
55
|
};
|
|
55
56
|
process.env.CODEX_AUTH_CODEX_DIR = codexDir;
|
|
56
57
|
delete process.env.CODEX_AUTH_ACCOUNTS_DIR;
|
|
@@ -61,6 +62,7 @@ async function withIsolatedCodexDir(t, fn) {
|
|
|
61
62
|
process.env.CODEX_AUTH_ACCOUNTS_DIR = previousEnv.CODEX_AUTH_ACCOUNTS_DIR;
|
|
62
63
|
process.env.CODEX_AUTH_JSON_PATH = previousEnv.CODEX_AUTH_JSON_PATH;
|
|
63
64
|
process.env.CODEX_AUTH_CURRENT_PATH = previousEnv.CODEX_AUTH_CURRENT_PATH;
|
|
65
|
+
process.env.CODEX_AUTH_SESSION_ACTIVE_OVERRIDE = previousEnv.CODEX_AUTH_SESSION_ACTIVE_OVERRIDE;
|
|
64
66
|
await promises_1.default.rm(codexDir, { recursive: true, force: true });
|
|
65
67
|
});
|
|
66
68
|
await fn({ codexDir, accountsDir, authPath });
|
|
@@ -426,12 +428,42 @@ async function withIsolatedCodexDir(t, fn) {
|
|
|
426
428
|
strict_1.default.equal(authStat.isSymbolicLink(), false);
|
|
427
429
|
});
|
|
428
430
|
});
|
|
429
|
-
(0, node_test_1.default)("getCurrentAccountName
|
|
431
|
+
(0, node_test_1.default)("getCurrentAccountName falls back to global current pointer when codex is not active in this terminal", async (t) => {
|
|
430
432
|
await withIsolatedCodexDir(t, async ({ codexDir, accountsDir }) => {
|
|
431
433
|
const service = new account_service_1.AccountService();
|
|
432
434
|
const currentPath = node_path_1.default.join(codexDir, "current");
|
|
433
435
|
const sessionMapPath = node_path_1.default.join(accountsDir, "sessions.json");
|
|
434
436
|
const sessionKey = `ppid:${process.ppid}`;
|
|
437
|
+
process.env.CODEX_AUTH_SESSION_ACTIVE_OVERRIDE = "0";
|
|
438
|
+
await promises_1.default.writeFile(node_path_1.default.join(accountsDir, "odin@megkapja.hu.json"), buildAuthPayload("odin@megkapja.hu", {
|
|
439
|
+
accountId: "acct-odin",
|
|
440
|
+
userId: "user-odin",
|
|
441
|
+
}));
|
|
442
|
+
await promises_1.default.writeFile(node_path_1.default.join(accountsDir, "lajos@edix.hu.json"), buildAuthPayload("lajos@edix.hu", {
|
|
443
|
+
accountId: "acct-lajos",
|
|
444
|
+
userId: "user-lajos",
|
|
445
|
+
}));
|
|
446
|
+
await promises_1.default.writeFile(currentPath, "lajos@edix.hu\n", "utf8");
|
|
447
|
+
await promises_1.default.writeFile(sessionMapPath, `${JSON.stringify({
|
|
448
|
+
version: 1,
|
|
449
|
+
sessions: {
|
|
450
|
+
[sessionKey]: {
|
|
451
|
+
accountName: "odin@megkapja.hu",
|
|
452
|
+
updatedAt: new Date().toISOString(),
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
}, null, 2)}\n`, "utf8");
|
|
456
|
+
const active = await service.getCurrentAccountName();
|
|
457
|
+
strict_1.default.equal(active, "lajos@edix.hu");
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
(0, node_test_1.default)("getCurrentAccountName prefers session-scoped snapshot when codex is active in this terminal", async (t) => {
|
|
461
|
+
await withIsolatedCodexDir(t, async ({ codexDir, accountsDir }) => {
|
|
462
|
+
const service = new account_service_1.AccountService();
|
|
463
|
+
const currentPath = node_path_1.default.join(codexDir, "current");
|
|
464
|
+
const sessionMapPath = node_path_1.default.join(accountsDir, "sessions.json");
|
|
465
|
+
const sessionKey = `ppid:${process.ppid}`;
|
|
466
|
+
process.env.CODEX_AUTH_SESSION_ACTIVE_OVERRIDE = "1";
|
|
435
467
|
await promises_1.default.writeFile(node_path_1.default.join(accountsDir, "odin@megkapja.hu.json"), buildAuthPayload("odin@megkapja.hu", {
|
|
436
468
|
accountId: "acct-odin",
|
|
437
469
|
userId: "user-odin",
|
|
@@ -454,12 +486,49 @@ async function withIsolatedCodexDir(t, fn) {
|
|
|
454
486
|
strict_1.default.equal(active, "odin@megkapja.hu");
|
|
455
487
|
});
|
|
456
488
|
});
|
|
457
|
-
(0, node_test_1.default)("syncExternalAuthSnapshotIfNeeded
|
|
489
|
+
(0, node_test_1.default)("syncExternalAuthSnapshotIfNeeded follows global sync when codex is not active in this terminal", async (t) => {
|
|
490
|
+
await withIsolatedCodexDir(t, async ({ codexDir, accountsDir, authPath }) => {
|
|
491
|
+
const service = new account_service_1.AccountService();
|
|
492
|
+
const currentPath = node_path_1.default.join(codexDir, "current");
|
|
493
|
+
const sessionMapPath = node_path_1.default.join(accountsDir, "sessions.json");
|
|
494
|
+
const sessionKey = `ppid:${process.ppid}`;
|
|
495
|
+
process.env.CODEX_AUTH_SESSION_ACTIVE_OVERRIDE = "0";
|
|
496
|
+
await promises_1.default.writeFile(node_path_1.default.join(accountsDir, "odin@megkapja.hu.json"), buildAuthPayload("odin@megkapja.hu", {
|
|
497
|
+
accountId: "acct-odin",
|
|
498
|
+
userId: "user-odin",
|
|
499
|
+
}));
|
|
500
|
+
await promises_1.default.writeFile(currentPath, "odin@megkapja.hu\n", "utf8");
|
|
501
|
+
await promises_1.default.writeFile(sessionMapPath, `${JSON.stringify({
|
|
502
|
+
version: 1,
|
|
503
|
+
sessions: {
|
|
504
|
+
[sessionKey]: {
|
|
505
|
+
accountName: "odin@megkapja.hu",
|
|
506
|
+
updatedAt: new Date().toISOString(),
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
}, null, 2)}\n`, "utf8");
|
|
510
|
+
await promises_1.default.writeFile(authPath, buildAuthPayload("lajos@edix.hu", {
|
|
511
|
+
accountId: "acct-lajos",
|
|
512
|
+
userId: "user-lajos",
|
|
513
|
+
}), "utf8");
|
|
514
|
+
const result = await service.syncExternalAuthSnapshotIfNeeded();
|
|
515
|
+
strict_1.default.deepEqual(result, {
|
|
516
|
+
synchronized: true,
|
|
517
|
+
savedName: "lajos@edix.hu",
|
|
518
|
+
autoSwitchDisabled: false,
|
|
519
|
+
});
|
|
520
|
+
const parsed = await (0, auth_parser_1.parseAuthSnapshotFile)(node_path_1.default.join(accountsDir, "lajos@edix.hu.json"));
|
|
521
|
+
strict_1.default.equal(parsed.email, "lajos@edix.hu");
|
|
522
|
+
strict_1.default.equal((await promises_1.default.readFile(currentPath, "utf8")).trim(), "lajos@edix.hu");
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
(0, node_test_1.default)("syncExternalAuthSnapshotIfNeeded ignores external login from another terminal when codex remains active in this terminal", async (t) => {
|
|
458
526
|
await withIsolatedCodexDir(t, async ({ codexDir, accountsDir, authPath }) => {
|
|
459
527
|
const service = new account_service_1.AccountService();
|
|
460
528
|
const currentPath = node_path_1.default.join(codexDir, "current");
|
|
461
529
|
const sessionMapPath = node_path_1.default.join(accountsDir, "sessions.json");
|
|
462
530
|
const sessionKey = `ppid:${process.ppid}`;
|
|
531
|
+
process.env.CODEX_AUTH_SESSION_ACTIVE_OVERRIDE = "1";
|
|
463
532
|
await promises_1.default.writeFile(node_path_1.default.join(accountsDir, "odin@megkapja.hu.json"), buildAuthPayload("odin@megkapja.hu", {
|
|
464
533
|
accountId: "acct-odin",
|
|
465
534
|
userId: "user-odin",
|
|
@@ -525,12 +594,51 @@ async function withIsolatedCodexDir(t, fn) {
|
|
|
525
594
|
strict_1.default.equal((await promises_1.default.readFile(currentPath, "utf8")).trim(), "lajos@edix.hu");
|
|
526
595
|
});
|
|
527
596
|
});
|
|
528
|
-
(0, node_test_1.default)("restoreSessionSnapshotIfNeeded
|
|
597
|
+
(0, node_test_1.default)("restoreSessionSnapshotIfNeeded skips restore when codex is not active in this terminal", async (t) => {
|
|
598
|
+
await withIsolatedCodexDir(t, async ({ codexDir, accountsDir, authPath }) => {
|
|
599
|
+
const service = new account_service_1.AccountService();
|
|
600
|
+
const currentPath = node_path_1.default.join(codexDir, "current");
|
|
601
|
+
const sessionMapPath = node_path_1.default.join(accountsDir, "sessions.json");
|
|
602
|
+
const sessionKey = `ppid:${process.ppid}`;
|
|
603
|
+
process.env.CODEX_AUTH_SESSION_ACTIVE_OVERRIDE = "0";
|
|
604
|
+
await promises_1.default.writeFile(node_path_1.default.join(accountsDir, "odin@megkapja.hu.json"), buildAuthPayload("odin@megkapja.hu", {
|
|
605
|
+
accountId: "acct-odin",
|
|
606
|
+
userId: "user-odin",
|
|
607
|
+
}));
|
|
608
|
+
await promises_1.default.writeFile(node_path_1.default.join(accountsDir, "lajos@edix.hu.json"), buildAuthPayload("lajos@edix.hu", {
|
|
609
|
+
accountId: "acct-lajos",
|
|
610
|
+
userId: "user-lajos",
|
|
611
|
+
}));
|
|
612
|
+
await promises_1.default.writeFile(currentPath, "lajos@edix.hu\n", "utf8");
|
|
613
|
+
await promises_1.default.writeFile(sessionMapPath, `${JSON.stringify({
|
|
614
|
+
version: 1,
|
|
615
|
+
sessions: {
|
|
616
|
+
[sessionKey]: {
|
|
617
|
+
accountName: "odin@megkapja.hu",
|
|
618
|
+
updatedAt: new Date().toISOString(),
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
}, null, 2)}\n`, "utf8");
|
|
622
|
+
await promises_1.default.writeFile(authPath, buildAuthPayload("lajos@edix.hu", {
|
|
623
|
+
accountId: "acct-lajos",
|
|
624
|
+
userId: "user-lajos",
|
|
625
|
+
}), "utf8");
|
|
626
|
+
const restored = await service.restoreSessionSnapshotIfNeeded();
|
|
627
|
+
strict_1.default.deepEqual(restored, {
|
|
628
|
+
restored: false,
|
|
629
|
+
});
|
|
630
|
+
strict_1.default.equal((await promises_1.default.readFile(currentPath, "utf8")).trim(), "lajos@edix.hu");
|
|
631
|
+
const parsed = await (0, auth_parser_1.parseAuthSnapshotFile)(authPath);
|
|
632
|
+
strict_1.default.equal(parsed.email, "lajos@edix.hu");
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
(0, node_test_1.default)("restoreSessionSnapshotIfNeeded re-activates the session-pinned snapshot while codex stays active in this terminal", async (t) => {
|
|
529
636
|
await withIsolatedCodexDir(t, async ({ codexDir, accountsDir, authPath }) => {
|
|
530
637
|
const service = new account_service_1.AccountService();
|
|
531
638
|
const currentPath = node_path_1.default.join(codexDir, "current");
|
|
532
639
|
const sessionMapPath = node_path_1.default.join(accountsDir, "sessions.json");
|
|
533
640
|
const sessionKey = `ppid:${process.ppid}`;
|
|
641
|
+
process.env.CODEX_AUTH_SESSION_ACTIVE_OVERRIDE = "1";
|
|
534
642
|
await promises_1.default.writeFile(node_path_1.default.join(accountsDir, "odin@megkapja.hu.json"), buildAuthPayload("odin@megkapja.hu", {
|
|
535
643
|
accountId: "acct-odin",
|
|
536
644
|
userId: "user-odin",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imdeadpool/codex-account-switcher",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "A command-line tool that lets you manage and switch between multiple Codex accounts instantly, no more constant logins and logouts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@oclif/core": "^3.0.0",
|
|
46
46
|
"prompts": "^2.4.2",
|
|
47
|
-
"tslib": "^2.8.1"
|
|
47
|
+
"tslib": "^2.8.1",
|
|
48
|
+
"typescript": "^5.6.3"
|
|
48
49
|
},
|
|
49
50
|
"devDependencies": {
|
|
50
|
-
"@types/prompts": "^2.4.9"
|
|
51
|
-
"typescript": "^5.6.3"
|
|
51
|
+
"@types/prompts": "^2.4.9"
|
|
52
52
|
},
|
|
53
53
|
"oclif": {
|
|
54
54
|
"bin": "codex-auth",
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require("node:fs/promises");
|
|
4
|
+
const fsSync = require("node:fs");
|
|
4
5
|
const os = require("node:os");
|
|
5
6
|
const path = require("node:path");
|
|
7
|
+
const { spawnSync } = require("node:child_process");
|
|
6
8
|
const readline = require("node:readline/promises");
|
|
7
9
|
|
|
8
10
|
const MARK_START = "# >>> codex-auth-login-auto-snapshot >>>";
|
|
@@ -29,22 +31,20 @@ function renderHookBlock() {
|
|
|
29
31
|
" [[ -w \"$__tty_target\" ]] || __tty_target=/dev/stdout",
|
|
30
32
|
" printf '\\033[>4m\\033[<u\\033[?2026l\\033[?1004l\\033[?1l\\033[?2004l\\033[?1000l\\033[?1002l\\033[?1003l\\033[?1006l\\033[?1015l\\033[?1049l\\033[0m\\033[?25h\\033[H\\033>' >\"$__tty_target\" 2>/dev/null || true",
|
|
31
33
|
"}",
|
|
32
|
-
"
|
|
33
|
-
" codex
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
" }",
|
|
47
|
-
"fi",
|
|
34
|
+
"codex() {",
|
|
35
|
+
" if command -v codex-auth >/dev/null 2>&1; then",
|
|
36
|
+
" command codex-auth restore-session >/dev/null 2>&1 || true",
|
|
37
|
+
" fi",
|
|
38
|
+
" command codex \"$@\"",
|
|
39
|
+
" local __codex_exit=$?",
|
|
40
|
+
" if command -v codex-auth >/dev/null 2>&1; then",
|
|
41
|
+
" CODEX_AUTH_FORCE_EXTERNAL_SYNC=1 command codex-auth status >/dev/null 2>&1 || true",
|
|
42
|
+
" fi",
|
|
43
|
+
" if [[ -z \"${CODEX_AUTH_SKIP_TTY_RESTORE:-}\" ]]; then",
|
|
44
|
+
" __codex_auth_restore_tty",
|
|
45
|
+
" fi",
|
|
46
|
+
" return $__codex_exit",
|
|
47
|
+
"}",
|
|
48
48
|
MARK_END,
|
|
49
49
|
].join("\n");
|
|
50
50
|
}
|
|
@@ -64,6 +64,43 @@ function normalizeRcContents(contents) {
|
|
|
64
64
|
return `${collapsed.replace(/\s*$/, "")}\n`;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
function ensureBuiltDist() {
|
|
68
|
+
const projectRoot = path.resolve(__dirname, "..");
|
|
69
|
+
const distEntry = path.join(projectRoot, "dist", "index.js");
|
|
70
|
+
if (fsSync.existsSync(distEntry)) return;
|
|
71
|
+
|
|
72
|
+
const tscPath = path.join(projectRoot, "node_modules", "typescript", "bin", "tsc");
|
|
73
|
+
if (fsSync.existsSync(tscPath)) {
|
|
74
|
+
const result = spawnSync(process.execPath, [tscPath, "-p", "tsconfig.json"], {
|
|
75
|
+
cwd: projectRoot,
|
|
76
|
+
stdio: "inherit",
|
|
77
|
+
});
|
|
78
|
+
if (result.status !== 0) {
|
|
79
|
+
throw new Error(`TypeScript build failed with exit code ${result.status ?? "unknown"}.`);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const npmBinary = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
85
|
+
const fallback = spawnSync(
|
|
86
|
+
npmBinary,
|
|
87
|
+
["exec", "--yes", "--package", "typescript@5.6.3", "--", "tsc", "-p", "tsconfig.json"],
|
|
88
|
+
{
|
|
89
|
+
cwd: projectRoot,
|
|
90
|
+
stdio: "inherit",
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
if (fallback.status !== 0) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Missing TypeScript compiler for git install bootstrap (fallback exit ${fallback.status ?? "unknown"}).`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!fsSync.existsSync(distEntry)) {
|
|
100
|
+
throw new Error("TypeScript build completed but dist/index.js is still missing.");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
67
104
|
async function maybeInstallHook() {
|
|
68
105
|
if (process.env.npm_config_global !== "true") return;
|
|
69
106
|
if (isTruthy(process.env.CODEX_AUTH_SKIP_POSTINSTALL)) return;
|
|
@@ -110,7 +147,15 @@ async function maybeInstallHook() {
|
|
|
110
147
|
process.stdout.write(`\nInstalled shell hook in ${rcPath}. Restart terminal or run: source ${rcPath}\n`);
|
|
111
148
|
}
|
|
112
149
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
150
|
+
function runPostinstall() {
|
|
151
|
+
ensureBuiltDist();
|
|
152
|
+
return maybeInstallHook();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Promise.resolve()
|
|
156
|
+
.then(() => runPostinstall())
|
|
157
|
+
.catch((error) => {
|
|
158
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
159
|
+
process.stderr.write(`\n[codex-auth postinstall] Failed: ${message}\n`);
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
});
|