feat(profiles): expose codex runtime across surfaces

- update CLI, API, route, and dashboard surfaces to recognize the Codex runtime target

- normalize persisted codex targets back to claude so runtime-only behavior stays truthful

- add regression coverage for help text, route parsing, and profile storage normalization

Refs #773
This commit is contained in:
Tam Nhu Tran
2026-03-29 13:14:15 -04:00
parent 8f60820f33
commit f9c1238483
19 changed files with 332 additions and 26 deletions
@@ -8,6 +8,7 @@ import * as fs from 'fs';
import * as path from 'path';
import type { Config, Settings } from '../../types';
import type { TargetType } from '../../targets/target-adapter';
import { getPersistedTargetChoices, isPersistedTargetType } from '../../targets/target-metadata';
import { getCcsDir, getConfigPath, loadConfigSafe } from '../../utils/config-manager';
import { ensureProfileHooksOrThrow } from '../../utils/websearch/profile-hook-injector';
import { isSensitiveKey } from '../../utils/sensitive-keys';
@@ -29,7 +30,7 @@ const SETTINGS_FILE_SUFFIX = '.settings.json';
const REDACTED_TOKEN_SENTINEL = '__CCS_REDACTED__';
function parseTargetValue(value: unknown): TargetType | null {
if (value === 'claude' || value === 'droid') {
if (isPersistedTargetType(value)) {
return value;
}
return null;
@@ -359,7 +360,7 @@ export function importApiProfileBundle(
if (input.profile.target !== undefined && bundleTarget === null) {
return {
success: false,
error: 'Invalid bundle profile target. Expected: claude or droid.',
error: `Invalid bundle profile target. Expected: ${getPersistedTargetChoices()}.`,
};
}
+2 -3
View File
@@ -10,14 +10,13 @@ import { loadConfigSafe } from '../../utils/config-manager';
import { loadOrCreateUnifiedConfig, isUnifiedMode } from '../../config/unified-config-loader';
import { expandPath } from '../../utils/helpers';
import type { TargetType } from '../../targets/target-adapter';
import { isPersistedTargetType } from '../../targets/target-metadata';
import type { Settings } from '../../types/config';
import type { ApiProfileInfo, CliproxyVariantInfo, ApiListResult } from './profile-types';
import { resolveCliproxyBridgeMetadata } from './cliproxy-profile-bridge';
const VALID_TARGETS: ReadonlySet<TargetType> = new Set<TargetType>(['claude', 'droid']);
function sanitizeTarget(target: unknown): TargetType {
if (typeof target === 'string' && VALID_TARGETS.has(target as TargetType)) {
if (isPersistedTargetType(target)) {
return target as TargetType;
}
return 'claude';
+1 -1
View File
@@ -292,7 +292,7 @@ export function getOfficialChannelsStateScopeMessage(): string {
}
export function getOfficialChannelsSupportMessage(): string {
return 'Works only for native Claude default/account sessions. It does not apply to API, OAuth, or Droid targets such as `ccs glm`, `ccs gemini`, `ccs codex`, or `ccs --target droid`.';
return 'Works only for native Claude default/account sessions. It does not apply to API, OAuth, or non-Claude targets such as `ccs glm`, `ccs gemini`, `ccs codex`, `ccs --target droid`, or `ccs --target codex`.';
}
export function getOfficialChannelsAccountStatusCaveat(): string {
@@ -417,6 +417,17 @@ export async function handleApiCreateCommand(args: string[]): Promise<void> {
` ${color(`ccs ${result.name} --target droid "your prompt"`, 'command')} ${dim('# target flag alternative')}`
);
}
if (cliproxyProvider === 'codex') {
console.log(
` ${color(`ccs ${result.name} --target codex "your prompt"`, 'command')} ${dim('# native Codex runtime')}`
);
console.log(
` ${color(`ccs-codex ${result.name} "your prompt"`, 'command')} ${dim('# explicit Codex alias')}`
);
console.log(
` ${color(`ccsx ${result.name} "your prompt"`, 'command')} ${dim('# short alias')}`
);
}
console.log('');
console.log(dim('Manage provider accounts, keys, and models in: ccs cliproxy'));
return;
+4
View File
@@ -107,6 +107,10 @@ export async function showApiCommandHelp(writeLine: HelpWriter = console.log): P
writeLine(
` ${color('ccs api create gemini-droid --cliproxy-provider gemini --target droid', 'command')}`
);
writeLine(` ${color('ccs api create codex-api --cliproxy-provider codex', 'command')}`);
writeLine(
` ${color('ccs codex-api --target codex', 'command')} ${dim('# runtime-only native Codex launch')}`
);
writeLine('');
writeLine(` ${dim('# Create with name')}`);
writeLine(` ${color('ccs api create myapi', 'command')}`);
+6 -3
View File
@@ -1,5 +1,6 @@
import type { ModelMapping } from '../../api/services';
import type { TargetType } from '../../targets/target-adapter';
import { getPersistedTargetChoices, isPersistedTargetType } from '../../targets/target-metadata';
import {
applyExtendedContextSuffix,
hasExtendedContextSuffix,
@@ -108,7 +109,7 @@ export function extractPositionalArgs(args: string[]): string[] {
function parseTargetValue(value: string): TargetType | null {
const normalized = value.trim().toLowerCase();
if (normalized === 'claude' || normalized === 'droid') {
if (isPersistedTargetType(normalized)) {
return normalized;
}
return null;
@@ -132,7 +133,7 @@ export function parseOptionalTargetFlag(
if (!target) {
return {
remainingArgs: extracted.remainingArgs,
errors: [`Invalid --target value "${extracted.value}". Use: claude or droid`],
errors: [`Invalid --target value "${extracted.value}". Use: ${getPersistedTargetChoices()}`],
};
}
@@ -251,7 +252,9 @@ export function parseApiCommandArgs(
(value) => {
const target = parseTargetValue(value);
if (!target) {
result.errors.push(`Invalid --target value "${value}". Use: claude or droid`);
result.errors.push(
`Invalid --target value "${value}". Use: ${getPersistedTargetChoices()}`
);
return;
}
result.target = target;
+8 -3
View File
@@ -13,6 +13,7 @@ import { CLIProxyProfileName, CLIPROXY_PROFILES } from '../../auth/profile-detec
import { supportsModelConfig, getProviderCatalog, ModelEntry } from '../../cliproxy/model-catalog';
import { CLIProxyProvider, CLIProxyBackend } from '../../cliproxy/types';
import type { TargetType } from '../../targets/target-adapter';
import { getPersistedTargetChoices, isPersistedTargetType } from '../../targets/target-metadata';
import { isUnifiedMode } from '../../config/unified-config-loader';
import { initUI, header, color, ok, fail, warn, info, infoBox, dim } from '../../utils/ui';
import { InteractivePrompt } from '../../utils/prompt';
@@ -42,7 +43,7 @@ interface CliproxyProfileArgs {
function parseTargetValue(rawValue: string): TargetType | null {
const normalized = rawValue.trim().toLowerCase();
if (normalized === 'claude' || normalized === 'droid') {
if (isPersistedTargetType(normalized)) {
return normalized;
}
return null;
@@ -73,7 +74,9 @@ export function parseProfileArgs(args: string[]): CliproxyProfileArgs {
i += 1;
const parsedTarget = parseTargetValue(rawValue);
if (!parsedTarget) {
result.errors.push(`Invalid --target value "${rawValue}". Use: claude or droid`);
result.errors.push(
`Invalid --target value "${rawValue}". Use: ${getPersistedTargetChoices()}`
);
} else {
result.target = parsedTarget;
}
@@ -82,7 +85,9 @@ export function parseProfileArgs(args: string[]): CliproxyProfileArgs {
const rawValue = arg.slice('--target='.length);
const parsedTarget = parseTargetValue(rawValue);
if (!parsedTarget) {
result.errors.push(`Invalid --target value "${rawValue}". Use: claude or droid`);
result.errors.push(
`Invalid --target value "${rawValue}". Use: ${getPersistedTargetChoices()}`
);
} else {
result.target = parsedTarget;
}
+12 -1
View File
@@ -391,7 +391,7 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
'Flags',
[
['--config-dir <path>', 'Use custom CCS config directory'],
['--target <cli>', 'Target CLI: claude (default), droid'],
['--target <cli>', 'Target CLI: claude (default), droid, codex (runtime-only)'],
['-h, --help', 'Show this help message'],
['-v, --version', 'Show version and installation info'],
['-sc, --shell-completion', 'Install shell auto-completion'],
@@ -405,6 +405,8 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
[
['ccs-droid <profile> [args]', 'Explicit Droid runtime alias'],
['ccsd <profile> [args]', 'Legacy shortcut for: ccs-droid <profile> [args]'],
['ccs-codex <profile> [args]', 'Explicit Codex runtime alias'],
['ccsx <profile> [args]', 'Short alias for: ccs-codex <profile> [args]'],
],
writeLine
);
@@ -416,6 +418,15 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim();
['ccs glm --target droid', 'Run GLM profile on Droid CLI'],
['ccs-droid glm', 'Same as above (explicit alias)'],
['ccsd glm', 'Legacy shortcut for ccs-droid'],
['ccs --target codex', 'Open a native Codex session with your existing ~/.codex setup'],
['ccs-codex', 'Same as above (explicit Codex alias)'],
['ccsx', 'Short alias for ccs-codex'],
['ccs codex --target codex', 'Run built-in CLIProxy Codex on native Codex CLI'],
[
'ccs api create codex-api --cliproxy-provider codex',
'Create a routed API bridge that can also run on Codex',
],
['ccs codex-api --target codex', 'Run a Codex bridge profile on native Codex CLI'],
['ccs-droid codex', 'Run built-in CLIProxy Codex profile on Droid'],
['ccs-droid agy', 'Run built-in CLIProxy Antigravity profile on Droid'],
[
+10 -7
View File
@@ -23,6 +23,7 @@ import {
validateApiName,
} from '../../api/services';
import { normalizeDroidProvider } from '../../targets/droid-provider';
import { getPersistedTargetChoices } from '../../targets/target-metadata';
import { isCLIProxyProvider } from '../../cliproxy/provider-capabilities';
import { isAnthropicDirectProfile, updateSettingsFile, parseTarget } from './route-helpers';
@@ -100,7 +101,7 @@ router.post('/cliproxy-bridge', (req: Request, res: Response): void => {
const target = parseTarget(shape.payload.target);
if (shape.payload.target !== undefined && target === null) {
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
res.status(400).json({ error: `Invalid target. Expected: ${getPersistedTargetChoices()}` });
return;
}
@@ -155,7 +156,7 @@ router.post('/', (req: Request, res: Response): void => {
const parsedTarget = parseTarget(target);
if (target !== undefined && parsedTarget === null) {
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
res.status(400).json({ error: `Invalid target. Expected: ${getPersistedTargetChoices()}` });
return;
}
if (providerHint !== undefined && parsedProvider === null) {
@@ -265,7 +266,7 @@ router.post('/orphans/register', (req: Request, res: Response): void => {
const force = payload.force === true;
if (payload.target !== undefined && target === null) {
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
res.status(400).json({ error: `Invalid target. Expected: ${getPersistedTargetChoices()}` });
return;
}
@@ -306,7 +307,7 @@ router.post('/:name/copy', (req: Request, res: Response): void => {
return;
}
if (shape.payload.target !== undefined && target === null) {
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
res.status(400).json({ error: `Invalid target. Expected: ${getPersistedTargetChoices()}` });
return;
}
@@ -357,7 +358,7 @@ router.post('/import', (req: Request, res: Response): void => {
const target = parseTarget(shape.payload.target);
if (shape.payload.target !== undefined && target === null) {
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
res.status(400).json({ error: `Invalid target. Expected: ${getPersistedTargetChoices()}` });
return;
}
@@ -368,7 +369,9 @@ router.post('/import', (req: Request, res: Response): void => {
}
const bundleTarget = (bundle as { profile?: { target?: unknown } }).profile?.target;
if (bundleTarget !== undefined && parseTarget(bundleTarget) === null) {
res.status(400).json({ error: 'Invalid bundle profile target. Expected: claude or droid' });
res.status(400).json({
error: `Invalid bundle profile target. Expected: ${getPersistedTargetChoices()}`,
});
return;
}
@@ -418,7 +421,7 @@ router.put('/:name', (req: Request, res: Response): void => {
const parsedTarget = parseTarget(target);
if (target !== undefined && parsedTarget === null) {
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
res.status(400).json({ error: `Invalid target. Expected: ${getPersistedTargetChoices()}` });
return;
}
if (providerHint !== undefined && parsedProvider === null) {
+3 -2
View File
@@ -18,6 +18,7 @@ import {
import type { CLIProxyProvider } from '../../cliproxy/types';
import type { Config, Settings } from '../../types/config';
import type { TargetType } from '../../targets/target-adapter';
import { isPersistedTargetType } from '../../targets/target-metadata';
import { ValidationError } from '../../errors/error-types';
/** Model mapping for API profiles */
@@ -438,7 +439,7 @@ export function validateFilePath(filePath: string): {
}
/**
* Parse and validate a target param (claude/droid). Returns null if invalid/absent.
* Parse and validate a persisted target param. Returns null if invalid/absent.
* Shared by profile-routes and variant-routes.
*/
export function parseTarget(rawTarget: unknown): TargetType | null {
@@ -451,7 +452,7 @@ export function parseTarget(rawTarget: unknown): TargetType | null {
}
const normalized = rawTarget.trim().toLowerCase();
if (normalized === 'claude' || normalized === 'droid') {
if (isPersistedTargetType(normalized)) {
return normalized;
}
+3 -2
View File
@@ -7,6 +7,7 @@
import { Router, Request, Response } from 'express';
import { isReservedName, RESERVED_PROFILE_NAMES } from '../../config/reserved-names';
import type { CLIProxyProvider } from '../../cliproxy/types';
import { getPersistedTargetChoices } from '../../targets/target-metadata';
import { parseTarget } from './route-helpers';
import {
createVariant,
@@ -55,7 +56,7 @@ router.post('/', (req: Request, res: Response): void => {
const parsedTarget = parseTarget(req.body.target);
if (req.body.target !== undefined && parsedTarget === null) {
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
res.status(400).json({ error: `Invalid target. Expected: ${getPersistedTargetChoices()}` });
return;
}
@@ -178,7 +179,7 @@ router.put('/:name', (req: Request, res: Response): void => {
const parsedTarget = parseTarget(req.body.target);
if (req.body.target !== undefined && parsedTarget === null) {
res.status(400).json({ error: 'Invalid target. Expected: claude or droid' });
res.status(400).json({ error: `Invalid target. Expected: ${getPersistedTargetChoices()}` });
return;
}
+138
View File
@@ -0,0 +1,138 @@
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { listApiProfiles } from '../../../src/api/services/profile-reader';
import { runWithScopedConfigDir, setGlobalConfigDir } from '../../../src/utils/config-manager';
describe('profile reader target sanitization', () => {
let tempHome = '';
let originalCcsHome: string | undefined;
let originalCcsDir: string | undefined;
let originalUnifiedMode: string | undefined;
function getScopedCcsDir(): string {
return path.join(tempHome, '.ccs');
}
async function runInScopedCcsDir<T>(fn: () => T): Promise<T> {
return await runWithScopedConfigDir(getScopedCcsDir(), fn);
}
beforeEach(() => {
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-profile-reader-'));
originalCcsHome = process.env.CCS_HOME;
originalCcsDir = process.env.CCS_DIR;
originalUnifiedMode = process.env.CCS_UNIFIED_CONFIG;
process.env.CCS_HOME = tempHome;
delete process.env.CCS_DIR;
delete process.env.CCS_UNIFIED_CONFIG;
setGlobalConfigDir(undefined);
});
afterEach(() => {
if (originalCcsHome === undefined) {
delete process.env.CCS_HOME;
} else {
process.env.CCS_HOME = originalCcsHome;
}
if (originalCcsDir === undefined) {
delete process.env.CCS_DIR;
} else {
process.env.CCS_DIR = originalCcsDir;
}
if (originalUnifiedMode === undefined) {
delete process.env.CCS_UNIFIED_CONFIG;
} else {
process.env.CCS_UNIFIED_CONFIG = originalUnifiedMode;
}
setGlobalConfigDir(undefined);
if (tempHome && fs.existsSync(tempHome)) {
fs.rmSync(tempHome, { recursive: true, force: true });
}
});
it('normalizes legacy stored codex targets back to claude for profiles and variants', async () => {
const ccsDir = getScopedCcsDir();
fs.mkdirSync(ccsDir, { recursive: true });
fs.writeFileSync(
path.join(ccsDir, 'config.json'),
JSON.stringify(
{
profiles: { demo: '~/.ccs/demo.settings.json' },
profile_targets: { demo: 'codex' },
cliproxy: {
routed: {
provider: 'codex',
settings: '~/.ccs/routed.settings.json',
target: 'codex',
},
},
},
null,
2
) + '\n'
);
fs.writeFileSync(
path.join(ccsDir, 'demo.settings.json'),
JSON.stringify(
{ env: { ANTHROPIC_BASE_URL: 'https://api.example.com', ANTHROPIC_AUTH_TOKEN: 'token' } },
null,
2
) + '\n'
);
const result = await runInScopedCcsDir(() => listApiProfiles());
expect(result.profiles).toHaveLength(1);
expect(result.profiles[0]?.target).toBe('claude');
expect(result.variants).toHaveLength(1);
expect(result.variants[0]?.target).toBe('claude');
});
it('normalizes unified stored codex targets back to claude for profiles and variants', async () => {
const ccsDir = getScopedCcsDir();
fs.mkdirSync(ccsDir, { recursive: true });
process.env.CCS_UNIFIED_CONFIG = '1';
fs.writeFileSync(
path.join(ccsDir, 'config.yaml'),
[
'version: 12',
'profiles:',
' demo:',
' type: api',
' settings: ~/.ccs/demo.settings.json',
' target: codex',
'cliproxy:',
' oauth_accounts: {}',
' providers: []',
' variants:',
' routed:',
' provider: codex',
' settings: ~/.ccs/routed.settings.json',
' target: codex',
'',
].join('\n'),
'utf8'
);
fs.writeFileSync(
path.join(ccsDir, 'demo.settings.json'),
JSON.stringify(
{ env: { ANTHROPIC_BASE_URL: 'https://api.example.com', ANTHROPIC_AUTH_TOKEN: 'token' } },
null,
2
) + '\n'
);
const result = await runInScopedCcsDir(() => listApiProfiles());
expect(result.profiles).toHaveLength(1);
expect(result.profiles[0]?.target).toBe('claude');
expect(result.variants).toHaveLength(1);
expect(result.variants[0]?.target).toBe('claude');
});
});
@@ -75,6 +75,13 @@ describe('api-command arg parser', () => {
]);
});
test('rejects runtime-only codex as a persisted API target value', () => {
const parsed = parseApiCommandArgs(['my-api', '--target', 'codex']);
expect(parsed.target).toBeUndefined();
expect(parsed.errors).toEqual(['Invalid --target value "codex". Use: claude or droid']);
});
test('collects missing-value error for --target with no value', () => {
const parsed = parseApiCommandArgs(['my-api', '--target']);
@@ -26,6 +26,13 @@ describe('cliproxy variant arg parser', () => {
expect(parsed.errors).toEqual(['Missing value for --target']);
});
test('rejects runtime-only codex as a persisted variant target value', () => {
const parsed = parseProfileArgs(['variant-a', '--target', 'codex']);
expect(parsed.target).toBeUndefined();
expect(parsed.errors).toEqual(['Invalid --target value "codex". Use: claude or droid']);
});
test('uses last --target value when repeated', () => {
const parsed = parseProfileArgs(['variant-a', '--target', 'claude', '--target=droid']);
@@ -99,6 +99,18 @@ describe('help command parity', () => {
expect(rendered.includes('return 429 extra-usage errors for long-context requests')).toBe(true);
});
test('root help documents native Codex runtime alias and runtime-only scope', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs-codex <profile> [args]')).toBe(true);
expect(rendered.includes('ccsx <profile> [args]')).toBe(true);
expect(rendered.includes('ccs --target codex')).toBe(true);
expect(rendered.includes('ccs codex-api --target codex')).toBe(true);
expect(rendered.includes('codex (runtime-only)')).toBe(true);
});
test('api help documents create-time Claude [1m] flags and entitlement warning', async () => {
const lines: string[] = [];
await showApiCommandHelp((line) => lines.push(line));
@@ -113,4 +125,14 @@ describe('help command parity', () => {
true
);
});
test('api help documents Codex bridge runtime launch separately from persisted targets', async () => {
const lines: string[] = [];
await showApiCommandHelp((line) => lines.push(line));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs api create codex-api --cliproxy-provider codex')).toBe(true);
expect(rendered.includes('ccs codex-api --target codex')).toBe(true);
expect(rendered.includes('Default target: claude or droid (create)')).toBe(true);
});
});
@@ -13,8 +13,10 @@ describe('route target parsing', () => {
it('returns null for invalid target values', () => {
expect(parseProfileTarget('glm')).toBeNull();
expect(parseProfileTarget('codex')).toBeNull();
expect(parseProfileTarget('')).toBeNull();
expect(parseVariantTarget('factory')).toBeNull();
expect(parseVariantTarget('codex')).toBeNull();
expect(parseVariantTarget(' ')).toBeNull();
});
@@ -28,6 +28,7 @@ export function ProviderInfoTab({
}: ProviderInfoTabProps) {
const resolvedTarget = defaultTarget || 'claude';
const isDroidTarget = resolvedTarget === 'droid';
const isCodexProvider = provider === 'codex';
return (
<ScrollArea className="h-full">
@@ -88,6 +89,22 @@ export function ProviderInfoTab({
<h3 className="text-sm font-medium mb-3">Quick Usage</h3>
<div className="space-y-3 bg-card rounded-lg border p-4 shadow-sm">
<UsageCommand label="Run with prompt" command={`ccs ${provider} "your prompt"`} />
{isCodexProvider && (
<>
<UsageCommand
label="Run on native Codex (--target)"
command={`ccs ${provider} --target codex "your prompt"`}
/>
<UsageCommand
label="Codex alias (explicit)"
command={`ccs-codex ${provider} "your prompt"`}
/>
<UsageCommand
label="Codex alias (short)"
command={`ccsx ${provider} "your prompt"`}
/>
</>
)}
<UsageCommand
label={isDroidTarget ? 'Droid alias (explicit)' : 'Run on Droid'}
command={`ccs-droid ${provider} "your prompt"`}
+4 -1
View File
@@ -689,7 +689,10 @@ export function findCatalogModel(provider: string, modelId: string) {
.sort((left, right) => compareGeminiVersions(right.info.version, left.info.version))[0]?.model;
}
export function resolveCatalogModelId(modelId: string, availableModels: CatalogAvailableModel[] = []): string {
export function resolveCatalogModelId(
modelId: string,
availableModels: CatalogAvailableModel[] = []
): string {
const normalizedModelId = normalizeModelId(modelId);
const liveGeminiModelId = resolveGeminiPreviewModelId(normalizedModelId, availableModels);
if (liveGeminiModelId) return liveGeminiModelId;
+72 -1
View File
@@ -55,6 +55,51 @@ export const SUPPORT_SCOPE_LABELS: Record<SupportScope, string> = {
};
export const SUPPORT_NOTICES: SupportNotice[] = [
{
id: 'codex-target-runtime-support',
title: 'Native Codex runtime support is live',
summary:
'Codex now participates as a first-class runtime target through ccs-codex, ccsx, or --target codex.',
primaryAction:
'Use Codex as a runtime target for native Codex sessions and Codex-routed CLIProxy flows.',
publishedAt: '2026-03-28',
status: 'new',
scopes: ['target', 'cliproxy', 'api-profiles'],
entryIds: ['codex-target', 'codex-cliproxy'],
highlights: [
'Use ccs-codex or ccsx for native Codex runs.',
'Built-in Codex and Codex bridge profiles can run on native Codex with --target codex.',
'Saved default targets for API profiles and variants remain claude or droid.',
],
actions: [
{
id: 'copy-codex-alias-command',
label: 'Open native Codex',
description: 'Launch Codex through the explicit CCS runtime alias.',
type: 'command',
command: 'ccs-codex',
},
{
id: 'copy-codex-provider-command',
label: 'Run built-in Codex on Codex',
description: 'Use the built-in Codex provider with native Codex runtime.',
type: 'command',
command: 'ccs codex --target codex "your prompt"',
},
{
id: 'open-cliproxy-codex',
label: 'Open Codex provider settings',
description: 'Review Codex provider and bridge flows in the dashboard.',
type: 'route',
path: '/cliproxy',
},
],
routes: [
{ label: 'CLIProxy', path: '/cliproxy' },
{ label: 'API Profiles', path: '/providers' },
],
commands: ['ccs-codex', 'ccsx', 'ccs codex --target codex "your prompt"'],
},
{
id: 'droid-target-support',
title: 'Factory Droid support is live',
@@ -185,6 +230,27 @@ export const CLI_SUPPORT_ENTRIES: CliSupportEntry[] = [
commands: ['ccs-droid glm', 'ccs km --target droid', 'ccs codex --target droid'],
notes: 'Use ccs-droid as the explicit runtime alias. Legacy ccsd still works.',
},
{
id: 'codex-target',
name: 'Codex CLI',
scope: 'target',
status: 'new',
summary:
'First-class runtime target for native Codex sessions and Codex-routed CLIProxy flows.',
pillars: {
baseUrl:
'Native ~/.codex config for default mode, transient -c overrides for CCS-backed routes',
auth: 'Native Codex auth for default mode, env_key injection for CCS-backed routes',
model: 'Native Codex config or routed Codex model mapping from CLIProxy',
},
routes: [
{ label: 'CLIProxy', path: '/cliproxy' },
{ label: 'API Profiles', path: '/providers' },
],
commands: ['ccs-codex', 'ccsx', 'ccs codex --target codex', 'ccs codex-api --target codex'],
notes:
'Runtime-only in v1. Saved default targets for API profiles and CLIProxy variants remain claude or droid.',
},
{
id: 'codex-cliproxy',
name: 'Codex via CLIProxy',
@@ -200,7 +266,12 @@ export const CLI_SUPPORT_ENTRIES: CliSupportEntry[] = [
{ label: 'CLIProxy', path: '/cliproxy' },
{ label: 'Control Panel', path: '/cliproxy/control-panel' },
],
commands: ['ccs codex', 'ccs cliproxy create mycodex --provider codex'],
commands: [
'ccs codex',
'ccs codex --target codex',
'ccs cliproxy create mycodex --provider codex',
'ccs api create codex-api --cliproxy-provider codex',
],
},
{
id: 'gemini-cliproxy',