mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 04:17:54 +00:00
feat(codex-auth): wire ccsx bin router + ccsxp scope notice
Upgrades the previously-stub ccsx binary entry (src/bin/codex-runtime.ts) into an argv router: `ccsx auth <cmd>` dispatches to the Phase 2 router; any other argv resolves the active codex-auth profile and spawns codex with CODEX_HOME pointed at the profile dir. - resolve-active-profile: sync, hot-path-safe (<5ms), reads YAML registry via Phase 1 helpers; precedence is CODEX_HOME (explicit) > CCS_CODEX_PROFILE (env) > registry default > null (legacy ~/.codex fallback); fails open on any error (silent for missing registry, stderr warn for corrupt/missing-profile) - codex-runtime-router: extracted main() for testability; entry script is a thin 3-line wrapper; returns -1 sentinel for the CCS branch so the spawn lifecycle isn't terminated - ccsxp-runtime: H5 defensive stderr notice when CCS_CODEX_PROFILE is set, surfacing the boundary between codex-auth (native codex) and ccsxp (cliproxy pool) without changing functional behavior; CLIProxyAPI does not read CODEX_HOME so no pool contamination possible - 14 unit tests (8 resolver + 6 router); ccsxp regression suite (5 tests) untouched and still green
This commit is contained in:
@@ -68,6 +68,16 @@ function resolveCcsxpCodexHome() {
|
||||
return path.join(os.homedir(), '.codex');
|
||||
}
|
||||
|
||||
// H5: CCS_CODEX_PROFILE is ignored by ccsxp. The ccsx auth profile system
|
||||
// (src/codex-auth/) is intentionally NOT consulted here — ccsxp serves the
|
||||
// cliproxy round-robin pool, not per-user-account profiles. Emit a one-line
|
||||
// notice so users who set CCS_CODEX_PROFILE in their shell don't get confused
|
||||
// when ccsxp silently ignores it and overwrites CODEX_HOME below.
|
||||
if (process.env.CCS_CODEX_PROFILE) {
|
||||
process.stderr.write(
|
||||
"[i] CCS_CODEX_PROFILE is ignored by ccsxp; profile applies to native 'codex' only.\n"
|
||||
);
|
||||
}
|
||||
process.env.CODEX_HOME = resolveCcsxpCodexHome();
|
||||
|
||||
// ccsxp is the Codex + cliproxy shortcut. Keep the native Codex history root,
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Codex runtime router — testable logic for src/bin/codex-runtime.ts.
|
||||
*
|
||||
* All inter-module deps are resolved via require() at call-time so tests can
|
||||
* inject stubs via require.cache before calling main().
|
||||
*
|
||||
* Routing:
|
||||
* argv[2] === 'auth' → delegate to runCodexAuth(argv.slice(3)), exit with code
|
||||
* else → resolve active profile, set CODEX_HOME, load ccs
|
||||
* CCS manages the process lifecycle; entry MUST NOT
|
||||
* call process.exit() when main returns -1.
|
||||
*
|
||||
* Return value contract:
|
||||
* ≥ 0 → auth branch: caller should process.exit(code)
|
||||
* -1 → CCS branch: CCS has taken over the process; caller must NOT exit
|
||||
*/
|
||||
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'codex';
|
||||
|
||||
/**
|
||||
* Main entry-point for the ccsx / codex-runtime binary.
|
||||
*
|
||||
* @param argv - process.argv (or test-supplied equivalent)
|
||||
* @returns ≥0 exit code for auth branch; -1 for CCS branch (no exit needed)
|
||||
*/
|
||||
export async function main(argv: string[]): Promise<number> {
|
||||
const subcommand = argv[2];
|
||||
|
||||
// ── auth branch ─────────────────────────────────────────────────────────
|
||||
if (subcommand === 'auth') {
|
||||
const { runCodexAuth } = require('../codex-auth/codex-auth-router') as {
|
||||
runCodexAuth: (args: string[]) => Promise<number>;
|
||||
};
|
||||
return runCodexAuth(argv.slice(3));
|
||||
}
|
||||
|
||||
// ── non-auth branch: profile resolution ─────────────────────────────────
|
||||
|
||||
// F1: respect an explicit CODEX_HOME — ccsxp, user export, CI override, etc.
|
||||
const explicit = (process.env.CODEX_HOME ?? '').trim();
|
||||
if (!explicit) {
|
||||
try {
|
||||
const { resolveActiveProfile } = require('../codex-auth/resolve-active-profile') as {
|
||||
resolveActiveProfile: (
|
||||
env: NodeJS.ProcessEnv
|
||||
) => { name: string; dir: string; source: string } | null;
|
||||
};
|
||||
const resolved = resolveActiveProfile(process.env);
|
||||
if (resolved) {
|
||||
process.env.CODEX_HOME = resolved.dir;
|
||||
|
||||
try {
|
||||
const { ensureSharedConfigSymlink } = require('../codex-auth/codex-config-symlink') as {
|
||||
ensureSharedConfigSymlink: (dir: string) => void;
|
||||
};
|
||||
ensureSharedConfigSymlink(resolved.dir);
|
||||
} catch (symlinkErr) {
|
||||
const msg = symlinkErr instanceof Error ? symlinkErr.message : String(symlinkErr);
|
||||
process.stderr.write(
|
||||
`[!] codex-auth: shared config symlink failed (${msg}), continuing\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (resolverErr) {
|
||||
// Resolver module threw unexpectedly — degrade silently to legacy mode
|
||||
const msg = resolverErr instanceof Error ? resolverErr.message : String(resolverErr);
|
||||
process.stderr.write(`[!] codex-auth: profile resolution skipped (${msg})\n`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── delegate to CCS ─────────────────────────────────────────────────────
|
||||
// require() is evaluated AFTER env mutations above. CCS manages its own
|
||||
// process lifecycle (spawns codex, pipes stdio, calls process.exit).
|
||||
// Return -1 so the entry script knows NOT to call process.exit().
|
||||
require('../ccs');
|
||||
|
||||
return -1; // CCS is in control — entry must not call process.exit()
|
||||
}
|
||||
@@ -1,2 +1,5 @@
|
||||
process.env.CCS_INTERNAL_ENTRY_TARGET = 'codex';
|
||||
require('../ccs');
|
||||
import { main } from './codex-runtime-router';
|
||||
// -1 means CCS has taken over the process lifecycle; do not exit.
|
||||
main(process.argv).then((code) => {
|
||||
if (code >= 0) process.exit(code);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user