mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-05 22:24:04 +00:00
fix(codex): harden dashboard config editing
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import { Route } from 'lucide-react';
|
||||
import { CodexFeaturesCard } from '@/components/compatible-cli/codex-features-card';
|
||||
import { CodexMcpServersCard } from '@/components/compatible-cli/codex-mcp-servers-card';
|
||||
import { CodexModelProvidersCard } from '@/components/compatible-cli/codex-model-providers-card';
|
||||
import { CodexProfilesCard } from '@/components/compatible-cli/codex-profiles-card';
|
||||
import { CodexProjectTrustCard } from '@/components/compatible-cli/codex-project-trust-card';
|
||||
import { CodexTopLevelControlsCard } from '@/components/compatible-cli/codex-top-level-controls-card';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import type {
|
||||
CodexConfigPatchInput,
|
||||
CodexProfilePatchValues,
|
||||
CodexTopLevelSettingsPatch,
|
||||
} from '@/hooks/use-codex-types';
|
||||
import type {
|
||||
CodexFeatureCatalogEntry,
|
||||
CodexMcpServerEntry,
|
||||
CodexModelProviderEntry,
|
||||
CodexProfileEntry,
|
||||
CodexProjectTrustEntry,
|
||||
CodexTopLevelSettingsView,
|
||||
} from '@/lib/codex-config';
|
||||
|
||||
interface CodexControlCenterTabProps {
|
||||
workspacePath: string;
|
||||
activeProfile: string | null;
|
||||
topLevelSettings: CodexTopLevelSettingsView;
|
||||
projectTrustEntries: CodexProjectTrustEntry[];
|
||||
profileEntries: CodexProfileEntry[];
|
||||
modelProviderEntries: CodexModelProviderEntry[];
|
||||
mcpServerEntries: CodexMcpServerEntry[];
|
||||
featureCatalog: CodexFeatureCatalogEntry[];
|
||||
featureState: Record<string, boolean | null>;
|
||||
disabled: boolean;
|
||||
disabledReason: string | null;
|
||||
saving: boolean;
|
||||
onPatch: (patch: CodexConfigPatchInput, successMessage: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export function CodexControlCenterTab({
|
||||
workspacePath,
|
||||
activeProfile,
|
||||
topLevelSettings,
|
||||
projectTrustEntries,
|
||||
profileEntries,
|
||||
modelProviderEntries,
|
||||
mcpServerEntries,
|
||||
featureCatalog,
|
||||
featureState,
|
||||
disabled,
|
||||
disabledReason,
|
||||
saving,
|
||||
onPatch,
|
||||
}: CodexControlCenterTabProps) {
|
||||
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">
|
||||
<Route className="h-4 w-4" />
|
||||
Structured controls boundary
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 text-sm text-muted-foreground">
|
||||
<p>
|
||||
Guided controls write only the user-layer <code>config.toml</code>. They do not model
|
||||
the full effective Codex runtime once trusted repo layers and CCS transient{' '}
|
||||
<code>-c</code> overrides are involved.
|
||||
</p>
|
||||
<p>
|
||||
Structured saves normalize TOML formatting and strip comments. Use the raw editor on
|
||||
the right when exact layout matters.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<CodexTopLevelControlsCard
|
||||
values={topLevelSettings}
|
||||
providerNames={modelProviderEntries.map((entry) => entry.name)}
|
||||
disabled={disabled}
|
||||
disabledReason={disabledReason}
|
||||
saving={saving}
|
||||
onSave={(values: CodexTopLevelSettingsPatch) =>
|
||||
onPatch({ kind: 'top-level', values }, 'Saved top-level Codex settings.')
|
||||
}
|
||||
/>
|
||||
|
||||
<CodexProjectTrustCard
|
||||
workspacePath={workspacePath}
|
||||
entries={projectTrustEntries}
|
||||
disabled={disabled}
|
||||
disabledReason={disabledReason}
|
||||
saving={saving}
|
||||
onSave={(projectPath, trustLevel) =>
|
||||
onPatch(
|
||||
{ kind: 'project-trust', path: projectPath, trustLevel },
|
||||
trustLevel ? 'Saved project trust entry.' : 'Removed project trust entry.'
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<CodexProfilesCard
|
||||
activeProfile={activeProfile}
|
||||
entries={profileEntries}
|
||||
providerNames={modelProviderEntries.map((entry) => entry.name)}
|
||||
disabled={disabled}
|
||||
disabledReason={disabledReason}
|
||||
saving={saving}
|
||||
onSave={(name, values: CodexProfilePatchValues, setAsActive) =>
|
||||
onPatch(
|
||||
{ kind: 'profile', action: 'upsert', name, values, setAsActive },
|
||||
'Saved profile.'
|
||||
)
|
||||
}
|
||||
onDelete={(name) =>
|
||||
onPatch({ kind: 'profile', action: 'delete', name }, 'Deleted profile.')
|
||||
}
|
||||
onSetActive={(name) =>
|
||||
onPatch({ kind: 'profile', action: 'set-active', name }, 'Set active profile.')
|
||||
}
|
||||
/>
|
||||
|
||||
<CodexModelProvidersCard
|
||||
entries={modelProviderEntries}
|
||||
disabled={disabled}
|
||||
disabledReason={disabledReason}
|
||||
saving={saving}
|
||||
onSave={(name, values) =>
|
||||
onPatch(
|
||||
{ kind: 'model-provider', action: 'upsert', name, values },
|
||||
'Saved model provider.'
|
||||
)
|
||||
}
|
||||
onDelete={(name) =>
|
||||
onPatch({ kind: 'model-provider', action: 'delete', name }, 'Deleted model provider.')
|
||||
}
|
||||
/>
|
||||
|
||||
<CodexMcpServersCard
|
||||
entries={mcpServerEntries}
|
||||
disabled={disabled}
|
||||
disabledReason={disabledReason}
|
||||
saving={saving}
|
||||
onSave={(name, values) =>
|
||||
onPatch({ kind: 'mcp-server', action: 'upsert', name, values }, 'Saved MCP server.')
|
||||
}
|
||||
onDelete={(name) =>
|
||||
onPatch({ kind: 'mcp-server', action: 'delete', name }, 'Deleted MCP server.')
|
||||
}
|
||||
/>
|
||||
|
||||
<CodexFeaturesCard
|
||||
catalog={featureCatalog}
|
||||
state={featureState}
|
||||
disabled={disabled}
|
||||
disabledReason={disabledReason}
|
||||
onToggle={(feature, enabled) =>
|
||||
onPatch({ kind: 'feature', feature, enabled }, 'Saved feature toggle.')
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import { type ReactNode } from 'react';
|
||||
import { ExternalLink, ShieldCheck } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import type {
|
||||
CompatibleCliProviderDocLink,
|
||||
CodexDashboardDiagnostics,
|
||||
} from '@/hooks/use-codex-types';
|
||||
|
||||
const DEFAULT_CODEX_DOC_LINKS = [
|
||||
{
|
||||
id: 'codex-config-basic',
|
||||
label: 'Codex Config Basics',
|
||||
url: 'https://developers.openai.com/codex/config-basic',
|
||||
description: 'Official user-layer setup, config location, and baseline configuration behavior.',
|
||||
},
|
||||
{
|
||||
id: 'codex-config-advanced',
|
||||
label: 'Codex Config Advanced',
|
||||
url: 'https://developers.openai.com/codex/config-advanced',
|
||||
description: 'Layering, trust, profiles, and advanced config behavior.',
|
||||
},
|
||||
{
|
||||
id: 'codex-config-reference',
|
||||
label: 'Codex Config Reference',
|
||||
url: 'https://developers.openai.com/codex/config-reference',
|
||||
description: 'Canonical upstream config surface for providers, MCP, features, and trust.',
|
||||
},
|
||||
{
|
||||
id: 'codex-releases',
|
||||
label: 'Codex GitHub Releases',
|
||||
url: 'https://github.com/openai/codex/releases',
|
||||
description: 'Track upstream release notes and fast-moving CLI changes.',
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_PROVIDER_DOCS: CompatibleCliProviderDocLink[] = [
|
||||
{
|
||||
provider: 'openai',
|
||||
label: 'OpenAI Responses API',
|
||||
apiFormat: 'Responses API',
|
||||
url: 'https://platform.openai.com/docs/api-reference/responses',
|
||||
},
|
||||
];
|
||||
|
||||
function renderTextWithLinks(text: string): ReactNode[] {
|
||||
const urlPattern = /https?:\/\/[^\s)]+/g;
|
||||
const nodes: ReactNode[] = [];
|
||||
let cursor = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = urlPattern.exec(text)) !== null) {
|
||||
const [url] = match;
|
||||
const index = match.index;
|
||||
|
||||
if (index > cursor) {
|
||||
nodes.push(text.slice(cursor, index));
|
||||
}
|
||||
|
||||
nodes.push(
|
||||
<a
|
||||
key={`${url}-${index}`}
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="underline underline-offset-2 hover:text-foreground"
|
||||
>
|
||||
{url}
|
||||
</a>
|
||||
);
|
||||
cursor = index + url.length;
|
||||
}
|
||||
|
||||
if (cursor < text.length) {
|
||||
nodes.push(text.slice(cursor));
|
||||
}
|
||||
|
||||
return nodes.length > 0 ? nodes : [text];
|
||||
}
|
||||
|
||||
interface CodexDocsTabProps {
|
||||
diagnostics: CodexDashboardDiagnostics;
|
||||
}
|
||||
|
||||
export function CodexDocsTab({ diagnostics }: CodexDocsTabProps) {
|
||||
const docsReference = diagnostics.docsReference ?? {
|
||||
notes: [],
|
||||
links: [],
|
||||
providerDocs: [],
|
||||
providerValues: [],
|
||||
settingsHierarchy: [],
|
||||
};
|
||||
const docsLinks = docsReference.links.length > 0 ? docsReference.links : DEFAULT_CODEX_DOC_LINKS;
|
||||
const providerDocs =
|
||||
docsReference.providerDocs.length > 0 ? docsReference.providerDocs : DEFAULT_PROVIDER_DOCS;
|
||||
|
||||
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" />
|
||||
Upstream notes
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 text-sm">
|
||||
{docsReference.notes.map((note, index) => (
|
||||
<p key={`${index}-${note}`} className="text-muted-foreground">
|
||||
- {renderTextWithLinks(note)}
|
||||
</p>
|
||||
))}
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs uppercase tracking-wide text-muted-foreground">Codex docs</p>
|
||||
<div className="space-y-1.5">
|
||||
{docsLinks.map((link) => (
|
||||
<a
|
||||
key={link.id}
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="block rounded-md border px-2.5 py-2 transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-medium">{link.label}</span>
|
||||
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground">{link.description}</p>
|
||||
<p className="mt-1 break-all font-mono text-[11px] text-muted-foreground/90 underline underline-offset-2">
|
||||
{link.url}
|
||||
</p>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs uppercase tracking-wide text-muted-foreground">
|
||||
Provider / bridge reference
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
{providerDocs.map((providerDoc) => (
|
||||
<a
|
||||
key={`${providerDoc.provider}-${providerDoc.url}`}
|
||||
href={providerDoc.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="block rounded-md border px-2.5 py-2 transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-medium">{providerDoc.label}</span>
|
||||
<ExternalLink className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="mt-0.5 text-[11px] text-muted-foreground">
|
||||
provider: {providerDoc.provider} | format: {providerDoc.apiFormat}
|
||||
</p>
|
||||
<p className="mt-1 break-all font-mono text-[11px] text-muted-foreground/90 underline underline-offset-2">
|
||||
{providerDoc.url}
|
||||
</p>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{docsReference.providerValues.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Provider values: {docsReference.providerValues.join(', ')}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{docsReference.settingsHierarchy.length > 0 && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Settings hierarchy: {docsReference.settingsHierarchy.join(' -> ')}
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
import {
|
||||
AlertTriangle,
|
||||
CheckCircle2,
|
||||
Folder,
|
||||
Info,
|
||||
Route,
|
||||
ShieldCheck,
|
||||
TerminalSquare,
|
||||
XCircle,
|
||||
} from 'lucide-react';
|
||||
import { QuickCommands } from '@/components/shared';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import type { CodexDashboardDiagnostics } from '@/hooks/use-codex-types';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
function formatTimestamp(value: number | null | undefined): string {
|
||||
if (!value || !Number.isFinite(value)) return 'N/A';
|
||||
return new Date(value).toLocaleString();
|
||||
}
|
||||
|
||||
function formatBytes(value: number | null | undefined): string {
|
||||
if (!value || value <= 0) return '0 B';
|
||||
if (value < 1024) return `${value} B`;
|
||||
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
|
||||
return `${(value / (1024 * 1024)).toFixed(2)} MB`;
|
||||
}
|
||||
|
||||
function DetailRow({
|
||||
label,
|
||||
value,
|
||||
mono = false,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
mono?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-start justify-between gap-3 text-sm">
|
||||
<span className="text-muted-foreground shrink-0">{label}</span>
|
||||
<span className={cn('text-right break-all', mono && 'font-mono text-xs')}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface CodexOverviewTabProps {
|
||||
diagnostics: CodexDashboardDiagnostics;
|
||||
}
|
||||
|
||||
export function CodexOverviewTab({ diagnostics }: CodexOverviewTabProps) {
|
||||
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">
|
||||
<Info className="h-4 w-4" />
|
||||
How Codex works in CCS
|
||||
</CardTitle>
|
||||
</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>
|
||||
Saved default targets for API profiles and variants still remain on Claude or Droid.
|
||||
</p>
|
||||
<p>
|
||||
CCS-backed Codex launches can apply transient <code>-c</code> overrides and inject
|
||||
<code> CCS_CODEX_API_KEY</code>, so effective runtime values may not match this file
|
||||
exactly.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<TerminalSquare className="h-4 w-4" />
|
||||
Runtime install
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm text-muted-foreground">Status</span>
|
||||
<Badge variant={diagnostics.binary.installed ? 'default' : 'secondary'}>
|
||||
{diagnostics.binary.installed ? 'Detected' : 'Not found'}
|
||||
</Badge>
|
||||
</div>
|
||||
<DetailRow label="Detection source" value={diagnostics.binary.source} mono />
|
||||
<DetailRow label="Binary path" value={diagnostics.binary.path || 'Not found'} mono />
|
||||
<DetailRow
|
||||
label="Install directory"
|
||||
value={diagnostics.binary.installDir || 'N/A'}
|
||||
mono
|
||||
/>
|
||||
<DetailRow label="Version" value={diagnostics.binary.version || 'Unknown'} mono />
|
||||
<DetailRow label="Alias commands" value="ccs-codex, ccsx" 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'}>
|
||||
{diagnostics.binary.supportsConfigOverrides ? 'Available' : 'Missing'}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Folder className="h-4 w-4" />
|
||||
Config file
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="rounded-md border p-3 space-y-1.5">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="font-medium text-sm">User config</span>
|
||||
{diagnostics.file.exists ? (
|
||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
||||
) : (
|
||||
<XCircle className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<DetailRow label="Path" value={diagnostics.file.path} mono />
|
||||
<DetailRow label="Resolved" value={diagnostics.file.resolvedPath} mono />
|
||||
<DetailRow label="Size" value={formatBytes(diagnostics.file.sizeBytes)} />
|
||||
<DetailRow label="Last modified" value={formatTimestamp(diagnostics.file.mtimeMs)} />
|
||||
{diagnostics.file.parseError && (
|
||||
<p className="text-xs text-amber-600">
|
||||
TOML warning: {diagnostics.file.parseError}
|
||||
</p>
|
||||
)}
|
||||
{diagnostics.file.readError && (
|
||||
<p className="text-xs text-destructive">
|
||||
Read warning: {diagnostics.file.readError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<ShieldCheck className="h-4 w-4" />
|
||||
Current user-layer summary
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<DetailRow label="Model" value={diagnostics.config.model || 'Not set'} mono />
|
||||
<DetailRow
|
||||
label="Model provider"
|
||||
value={diagnostics.config.modelProvider || 'Not set'}
|
||||
mono
|
||||
/>
|
||||
<DetailRow
|
||||
label="Active profile"
|
||||
value={diagnostics.config.activeProfile || 'Not set'}
|
||||
mono
|
||||
/>
|
||||
<DetailRow
|
||||
label="Approval policy"
|
||||
value={diagnostics.config.approvalPolicy || 'Not set'}
|
||||
mono
|
||||
/>
|
||||
<DetailRow
|
||||
label="Sandbox mode"
|
||||
value={diagnostics.config.sandboxMode || 'Not set'}
|
||||
mono
|
||||
/>
|
||||
<DetailRow label="Web search" value={diagnostics.config.webSearch || 'Not set'} mono />
|
||||
<Separator />
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<Badge variant="outline" className="justify-center">
|
||||
providers: {diagnostics.config.modelProviderCount}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="justify-center">
|
||||
profiles: {diagnostics.config.profileCount}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="justify-center">
|
||||
enabled features: {diagnostics.config.enabledFeatures.length}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="justify-center">
|
||||
MCP servers: {diagnostics.config.mcpServerCount}
|
||||
</Badge>
|
||||
</div>
|
||||
{diagnostics.config.topLevelKeys.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs uppercase tracking-wide text-muted-foreground">
|
||||
User-layer keys present
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{diagnostics.config.topLevelKeys.map((key) => (
|
||||
<Badge key={key} variant="secondary" className="font-mono text-[10px]">
|
||||
{key}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<QuickCommands
|
||||
snippets={[
|
||||
{
|
||||
label: 'Native Codex',
|
||||
command: 'ccs-codex',
|
||||
description: 'Launch the native Codex runtime alias.',
|
||||
},
|
||||
{
|
||||
label: 'Short alias',
|
||||
command: 'ccsx',
|
||||
description: 'Launch the short Codex runtime alias.',
|
||||
},
|
||||
{
|
||||
label: 'Target override',
|
||||
command: 'ccs codex --target codex "your prompt"',
|
||||
description: 'Run a CCS profile on the native Codex target.',
|
||||
},
|
||||
{
|
||||
label: 'Workspace trust',
|
||||
command: `codex --profile ${diagnostics.config.activeProfile || 'default'}`,
|
||||
description: 'Inspect the active profile directly in native Codex.',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Route className="h-4 w-4" />
|
||||
Runtime vs provider
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-3 md:grid-cols-2">
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-md border p-3 text-sm">
|
||||
<p className="font-medium">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.
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-base">Supported flows</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Flow</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Notes</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{diagnostics.supportMatrix.map((entry) => (
|
||||
<TableRow key={entry.id}>
|
||||
<TableCell className="font-mono text-xs">{entry.label}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={entry.supported ? 'default' : 'secondary'}>
|
||||
{entry.supported ? 'Yes' : 'No'}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">{entry.notes}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{diagnostics.warnings.length > 0 && (
|
||||
<Card className="border-amber-200 bg-amber-50/50 dark:bg-amber-950/20">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<AlertTriangle className="h-4 w-4 text-amber-600" />
|
||||
Warnings
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-1.5">
|
||||
{diagnostics.warnings.map((warning) => (
|
||||
<p key={warning} className="text-sm text-amber-800 dark:text-amber-300">
|
||||
- {warning}
|
||||
</p>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user