feat(codex-ui): surface cliproxy setup guidance

This commit is contained in:
Tam Nhu Tran
2026-03-29 19:44:02 -04:00
parent 318b57bedc
commit 0ffde4f3a2
7 changed files with 196 additions and 30 deletions
@@ -702,6 +702,7 @@ export async function getCodexDashboardDiagnostics(): Promise<CodexDashboardDiag
? Object.keys(config).sort((left, right) => left.localeCompare(right))
: [];
const activeProfile = asString(config?.profile);
const activeModelProvider = asString(config?.model_provider);
const profileNames = Object.keys(asObject(config?.profiles) ?? {}).sort((left, right) =>
left.localeCompare(right)
);
@@ -726,6 +727,21 @@ export async function getCodexDashboardDiagnostics(): Promise<CodexDashboardDiag
if (activeProfile && !profileNames.includes(activeProfile)) {
warnings.push(`Active profile "${activeProfile}" is selected but missing from [profiles].`);
}
if (activeModelProvider) {
const activeProvider = modelProviders.find((provider) => provider.name === activeModelProvider);
if (!activeProvider) {
warnings.push(
`Active model provider "${activeModelProvider}" is selected but missing from [model_providers].`
);
} else {
if (!activeProvider.baseUrl) {
warnings.push(`Active model provider "${activeProvider.name}" is missing base_url.`);
}
if (!activeProvider.envKey) {
warnings.push(`Active model provider "${activeProvider.name}" is missing env_key.`);
}
}
}
if (modelProviders.some((provider) => provider.usesExperimentalBearerToken)) {
warnings.push(
'One or more model_providers entries use experimental_bearer_token; prefer env_key-backed auth.'
@@ -218,6 +218,22 @@ model = "gpt-5.4"
expect(diagnostics.supportMatrix.some((entry) => entry.id === 'default')).toBe(true);
});
it('warns when the active model provider is selected without base_url or env_key', async () => {
fs.writeFileSync(
path.join(codexHome, 'config.toml'),
`model_provider = "cliproxy"
[model_providers.cliproxy]
wire_api = "responses"
`
);
const diagnostics = await getCodexDashboardDiagnostics();
expect(diagnostics.warnings.some((warning) => warning.includes('missing base_url'))).toBe(true);
expect(diagnostics.warnings.some((warning) => warning.includes('missing env_key'))).toBe(true);
});
it('summarizes granular approval policies without flattening them to null', async () => {
fs.writeFileSync(
path.join(codexHome, 'config.toml'),
@@ -91,18 +91,14 @@ export function ProviderInfoTab({
<UsageCommand label="Run with prompt" command={`ccs ${provider} "your prompt"`} />
{isCodexProvider && (
<>
<UsageCommand
label="Run on native Codex (shortcut)"
command={`ccsxp "your prompt"`}
/>
<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
@@ -44,6 +44,13 @@ const DEFAULT_PROVIDER_DOCS: CompatibleCliProviderDocLink[] = [
},
];
const CLIPROXY_NATIVE_CODEX_RECIPE = `model_provider = "cliproxy"
[model_providers.cliproxy]
base_url = "http://127.0.0.1:8317/api/provider/codex"
env_key = "CLIPROXY_API_KEY"
wire_api = "responses"`;
function renderTextWithLinks(text: string): ReactNode[] {
const urlPattern = /https?:\/\/[^\s)]+/g;
const nodes: ReactNode[] = [];
@@ -98,6 +105,36 @@ export function CodexDocsTab({ diagnostics }: CodexDocsTabProps) {
return (
<ScrollArea className="h-full">
<div className="space-y-4 pr-1">
<Card>
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-base">
<ShieldCheck className="h-4 w-4" />
CCS bridge recipe
</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<p>
Use <code>ccsxp</code> if you want the built-in CCS Codex provider shortcut on native
Codex. Use the saved recipe below if you want plain <code>codex</code> or a personal
alias like <code>cxp</code> to default to CLIProxy.
</p>
<pre className="overflow-x-auto rounded-md border bg-muted/20 p-3 text-xs text-foreground">
{CLIPROXY_NATIVE_CODEX_RECIPE}
</pre>
<div className="space-y-1">
<p>
1. Save the <code>cliproxy</code> provider in your user config.
</p>
<p>
2. Set top-level <code>model_provider</code> to <code>cliproxy</code>.
</p>
<p>
3. Export <code>CLIPROXY_API_KEY</code> in your shell before launching native Codex.
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-base">
@@ -33,6 +33,16 @@ const EMPTY_MODEL_PROVIDER_DRAFT: CodexModelProviderEntry = {
supportsWebsockets: false,
};
const CLIPROXY_CODEX_PROVIDER_PRESET: CodexModelProviderEntry = {
name: 'cliproxy',
displayName: 'CLIProxy Codex',
baseUrl: 'http://127.0.0.1:8317/api/provider/codex',
envKey: 'CLIPROXY_API_KEY',
wireApi: 'responses',
requiresOpenaiAuth: false,
supportsWebsockets: false,
};
interface ModelProviderEditorProps {
initialDraft: CodexModelProviderEntry;
isNew: boolean;
@@ -56,6 +66,23 @@ function ModelProviderEditor({
return (
<>
{isNew && (
<div className="flex flex-wrap items-center justify-between gap-2 rounded-md border border-dashed px-3 py-2 text-xs text-muted-foreground">
<p>
Quick start: apply the CLIProxy Codex preset here, then set{' '}
<strong>Default provider</strong> to <code>cliproxy</code> in Top-level settings.
</p>
<Button
variant="outline"
size="sm"
onClick={() => setDraft(CLIPROXY_CODEX_PROVIDER_PRESET)}
disabled={disabled}
>
Use CLIProxy Codex preset
</Button>
</div>
)}
<div className="grid gap-3 sm:grid-cols-2">
<Input
value={draft.name}
@@ -146,6 +173,12 @@ function ModelProviderEditor({
Save provider
</Button>
</div>
<p className="text-xs text-muted-foreground">
If you want plain native <code>codex</code> to default to CLIProxy, save a provider named{' '}
<code>cliproxy</code> with <code>CLIPROXY_API_KEY</code> here, then pick{' '}
<code>cliproxy</code> in the <strong>Default provider</strong> control above.
</p>
</>
);
}
@@ -24,6 +24,13 @@ import {
import type { CodexDashboardDiagnostics } from '@/hooks/use-codex-types';
import { cn } from '@/lib/utils';
const CLIPROXY_NATIVE_CODEX_RECIPE = `model_provider = "cliproxy"
[model_providers.cliproxy]
base_url = "http://127.0.0.1:8317/api/provider/codex"
env_key = "CLIPROXY_API_KEY"
wire_api = "responses"`;
function formatTimestamp(value: number | null | undefined): string {
if (!value || !Number.isFinite(value)) return 'N/A';
return new Date(value).toLocaleString();
@@ -70,6 +77,17 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
</CardHeader>
<CardContent className="space-y-2 text-sm text-muted-foreground">
<p>Codex is a first-class runtime target in CCS, but it stays runtime-only in v1.</p>
<p>
<code>ccs-codex</code> and <code>ccsx</code> launch native Codex against your saved
native config, while <code>ccsxp</code> is the opinionated shortcut for{' '}
<code>ccs codex --target codex</code>.
</p>
<p>
Plain <code>codex</code> or a personal alias like <code>cxp</code> needs{' '}
<code>model_provider = "cliproxy"</code> plus a matching{' '}
<code>[model_providers.cliproxy]</code> entry if you want CLIProxy as the saved native
default.
</p>
<p>
Saved default targets for API profiles and variants still remain on Claude or Droid.
</p>
@@ -103,7 +121,8 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
mono
/>
<DetailRow label="Version" value={diagnostics.binary.version || 'Unknown'} mono />
<DetailRow label="Alias commands" value="ccs-codex, ccsx" mono />
<DetailRow label="Native aliases" value="ccs-codex, ccsx" mono />
<DetailRow label="CCS provider shortcut" value="ccsxp" mono />
<div className="flex items-center justify-between rounded-md border px-3 py-2">
<span className="text-sm text-muted-foreground">--config override support</span>
<Badge variant={diagnostics.binary.supportsConfigOverrides ? 'default' : 'secondary'}>
@@ -113,6 +132,40 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-base">
<Route className="h-4 w-4" />
CLIProxy-backed native Codex
</CardTitle>
</CardHeader>
<CardContent className="space-y-3 text-sm text-muted-foreground">
<p>
There are two supported paths. Use <code>ccsxp</code> if you want the built-in CCS
Codex provider shortcut. Use the saved recipe below if you want plain{' '}
<code>codex</code> or a personal alias like <code>cxp</code> to default to CLIProxy.
</p>
<div className="rounded-md border bg-muted/20 p-3">
<p className="font-medium text-foreground">Saved native Codex recipe</p>
<pre className="mt-2 overflow-x-auto rounded-md bg-background p-3 text-xs text-foreground">
{CLIPROXY_NATIVE_CODEX_RECIPE}
</pre>
</div>
<div className="space-y-1">
<p>
1. Save a provider named <code>cliproxy</code> with the base URL and env key above.
</p>
<p>
2. In <strong>Top-level settings</strong>, set <strong>Default provider</strong> to{' '}
<code>cliproxy</code>.
</p>
<p>
3. Export <code>CLIPROXY_API_KEY</code> in your shell before launching native Codex.
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="flex items-center gap-2 text-base">
@@ -213,19 +266,19 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
<QuickCommands
snippets={[
{
label: 'Native Codex',
command: 'ccs-codex',
description: 'Launch the native Codex runtime alias.',
},
{
label: 'Short alias',
label: 'Native short alias',
command: 'ccsx',
description: 'Launch the short Codex runtime alias.',
description: 'Launch the short native Codex runtime alias.',
},
{
label: 'Target override',
label: 'CCS Codex shortcut',
command: 'ccsxp "your prompt"',
description: 'Run the built-in CCS Codex provider on native Codex.',
},
{
label: 'Explicit provider route',
command: 'ccs codex --target codex "your prompt"',
description: 'Run a CCS profile on the native Codex target.',
description: 'Use the explicit built-in Codex provider route.',
},
{
label: 'Workspace trust',
@@ -246,17 +299,16 @@ export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
<div className="rounded-md border p-3 text-sm">
<p className="font-medium">Native Codex runtime</p>
<p className="mt-1 text-muted-foreground">
Use <code>ccs-codex</code>, <code>ccsx</code>, or <code>--target codex</code>. CCS
launches the local Codex CLI and depends on native Codex capabilities such as{' '}
<code>--config</code> overrides.
Use <code>ccs-codex</code>, <code>ccsx</code>, or <code>--target codex</code> when
you want the local Codex CLI to honor your saved native user config.
</p>
</div>
<div className="rounded-md border p-3 text-sm">
<p className="font-medium">Codex provider / bridge</p>
<p className="font-medium">CCS Codex provider / bridge</p>
<p className="mt-1 text-muted-foreground">
CCS can route provider credentials transiently through CLIProxy. That is not the
same as editing local <code>config.toml</code>, and some routed values may never
persist here.
Use <code>ccsxp</code> or <code>ccs codex --target codex</code> when you want the
built-in CCS Codex provider on native Codex. That path uses transient CCS-managed
overrides and is separate from the saved <code>cliproxy</code> recipe above.
</p>
</div>
</CardContent>
+21 -5
View File
@@ -59,7 +59,7 @@ 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.',
'Codex now participates as a first-class runtime target through ccs-codex, ccsx, ccsxp, or --target codex.',
primaryAction:
'Use Codex as a runtime target for native Codex sessions and Codex-routed CLIProxy flows.',
publishedAt: '2026-03-28',
@@ -68,6 +68,7 @@ export const SUPPORT_NOTICES: SupportNotice[] = [
entryIds: ['codex-target', 'codex-cliproxy'],
highlights: [
'Use ccs-codex or ccsx for native Codex runs.',
'Use ccsxp for the built-in CCS Codex provider shortcut on native Codex.',
'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.',
],
@@ -82,7 +83,14 @@ export const SUPPORT_NOTICES: SupportNotice[] = [
{
id: 'copy-codex-provider-command',
label: 'Run built-in Codex on Codex',
description: 'Use the built-in Codex provider with native Codex runtime.',
description: 'Use the built-in Codex provider shortcut on native Codex.',
type: 'command',
command: 'ccsxp "your prompt"',
},
{
id: 'copy-codex-provider-command-explicit',
label: 'Run built-in Codex on Codex (explicit)',
description: 'Use the explicit built-in Codex provider route on native Codex.',
type: 'command',
command: 'ccs codex --target codex "your prompt"',
},
@@ -95,7 +103,12 @@ export const SUPPORT_NOTICES: SupportNotice[] = [
},
],
routes: [{ label: 'Codex CLI', path: '/codex' }],
commands: ['ccs-codex', 'ccsx', 'ccs codex --target codex "your prompt"'],
commands: [
'ccs-codex',
'ccsx',
'ccsxp "your prompt"',
'ccs codex --target codex "your prompt"',
],
},
{
id: 'droid-target-support',
@@ -250,7 +263,7 @@ export const CLI_SUPPORT_ENTRIES: CliSupportEntry[] = [
name: 'Codex via CLIProxy',
scope: 'cliproxy',
status: 'stable',
summary: 'OAuth-backed provider with configurable variant model and target.',
summary: 'OAuth-backed provider with configurable variant model and a native Codex shortcut.',
pillars: {
baseUrl: 'Managed by CLIProxy backend',
auth: 'OAuth account via CLIProxy auth flow',
@@ -261,11 +274,14 @@ export const CLI_SUPPORT_ENTRIES: CliSupportEntry[] = [
{ label: 'Control Panel', path: '/cliproxy/control-panel' },
],
commands: [
'ccs codex',
'ccsxp "your prompt"',
'ccs codex --target codex',
'ccs codex',
'ccs cliproxy create mycodex --provider codex',
'ccs api create codex-api --cliproxy-provider codex',
],
notes:
'Use ccsxp when you want the built-in Codex provider on native Codex without retyping --target codex.',
},
{
id: 'gemini-cliproxy',