feat(ui): clarify image-analysis target status

This commit is contained in:
Tam Nhu Tran
2026-04-01 15:32:37 -04:00
parent 3b61673ad2
commit 0246e327fe
10 changed files with 495 additions and 84 deletions
@@ -30,7 +30,7 @@ const singleProviderSchema = z.object({
provider: z.enum(CLIPROXY_PROVIDERS, { message: 'Provider is required' }),
model: z.string().optional(),
account: z.string().optional(),
target: z.enum(['claude', 'droid']),
target: z.enum(['claude', 'droid', 'codex']),
});
const compositeSchema = z.object({
@@ -39,7 +39,7 @@ const compositeSchema = z.object({
.min(1, 'Name is required')
.regex(/^[a-zA-Z][a-zA-Z0-9._-]*$/, 'Invalid variant name'),
default_tier: z.enum(['opus', 'sonnet', 'haiku'], { message: 'Default tier is required' }),
target: z.enum(['claude', 'droid']),
target: z.enum(['claude', 'droid', 'codex']),
tiers: z.object({
opus: z.object({
provider: z.enum(CLIPROXY_PROVIDERS, { message: 'Provider is required' }),
@@ -249,6 +249,7 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
>
<option value="claude">{t('cliproxyDialog.claudeCode')}</option>
<option value="droid">{t('cliproxyDialog.factoryDroid')}</option>
<option value="codex">Codex CLI</option>
</select>
</div>
@@ -353,6 +354,7 @@ export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
>
<option value="claude">{t('cliproxyDialog.claudeCode')}</option>
<option value="droid">{t('cliproxyDialog.factoryDroid')}</option>
<option value="codex">Codex CLI</option>
</select>
</div>
@@ -23,12 +23,12 @@ const singleProviderSchema = z.object({
provider: z.enum(CLIPROXY_PROVIDERS, { message: 'Provider is required' }),
model: z.string().optional(),
account: z.string().optional(),
target: z.enum(['claude', 'droid']),
target: z.enum(['claude', 'droid', 'codex']),
});
const compositeSchema = z.object({
default_tier: z.enum(['opus', 'sonnet', 'haiku'], { message: 'Default tier is required' }),
target: z.enum(['claude', 'droid']),
target: z.enum(['claude', 'droid', 'codex']),
tiers: z.object({
opus: z.object({
provider: z.enum(CLIPROXY_PROVIDERS, { message: 'Provider is required' }),
@@ -375,6 +375,7 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit
>
<option value="claude">{t('cliproxyDialog.claudeCode')}</option>
<option value="droid">{t('cliproxyDialog.factoryDroid')}</option>
<option value="codex">Codex CLI</option>
</select>
</div>
@@ -433,6 +434,7 @@ export function CliproxyEditDialog({ variant, open, onOpenChange }: CliproxyEdit
>
<option value="claude">{t('cliproxyDialog.claudeCode')}</option>
<option value="droid">{t('cliproxyDialog.factoryDroid')}</option>
<option value="codex">Codex CLI</option>
</select>
</div>
@@ -85,6 +85,7 @@ export function HeaderSection({
<SelectContent>
<SelectItem value="claude">Claude Code</SelectItem>
<SelectItem value="droid">Factory Droid</SelectItem>
<SelectItem value="codex">Codex CLI</SelectItem>
</SelectContent>
</Select>
{isTargetSaving && <Loader2 className="w-3.5 h-3.5 animate-spin text-muted-foreground" />}
@@ -1,16 +1,64 @@
import { AlertTriangle, Image as ImageIcon, Route } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { cn } from '@/lib/utils';
import type { ImageAnalysisStatus } from '@/lib/api-client';
import type { CliTarget, ImageAnalysisStatus } from '@/lib/api-client';
interface ImageAnalysisStatusSectionProps {
status?: ImageAnalysisStatus | null;
target?: CliTarget;
source?: 'saved' | 'editor';
previewState?: 'saved' | 'preview' | 'refreshing' | 'invalid';
}
function getBadge(status: ImageAnalysisStatus | null | undefined) {
const RESOLUTION_SOURCE_LABELS: Record<ImageAnalysisStatus['resolutionSource'], string> = {
'cliproxy-provider': 'Direct provider route',
'cliproxy-variant': 'Variant route',
'cliproxy-composite': 'Composite route',
'copilot-alias': 'Copilot alias',
'cliproxy-bridge': 'Derived from profile API route',
'profile-backend': 'Saved Image Analysis mapping',
'fallback-backend': 'Fallback backend',
disabled: 'Disabled globally',
'unsupported-profile': 'Unsupported profile type',
unresolved: 'No backend mapped',
'missing-model': 'Missing model',
};
const TARGET_LABELS: Record<CliTarget, string> = {
claude: 'Claude Code',
droid: 'Factory Droid',
codex: 'Codex CLI',
};
function getPreviewBadge(
source: 'saved' | 'editor',
previewState: ImageAnalysisStatusSectionProps['previewState']
) {
if (previewState === 'refreshing') {
return { label: 'Refreshing', variant: 'outline' as const };
}
if (source === 'editor') {
return { label: 'Live Preview', variant: 'secondary' as const };
}
return { label: 'Saved', variant: 'outline' as const };
}
function getRuntimeBadge(status: ImageAnalysisStatus | null | undefined, target: CliTarget) {
if (!status) return { label: 'Checking', variant: 'outline' as const };
if (target !== 'claude') {
if (status.status === 'disabled') return { label: 'Disabled', variant: 'outline' as const };
if (status.status === 'hook-missing')
return { label: 'Setup needed', variant: 'destructive' as const };
if (status.authReadiness === 'missing')
return { label: 'Needs auth', variant: 'destructive' as const };
if (status.proxyReadiness === 'unavailable')
return { label: 'Needs proxy', variant: 'destructive' as const };
if (status.effectiveRuntimeMode === 'native-read' && status.backendId) {
return { label: 'Saved fallback', variant: 'outline' as const };
}
return { label: 'Target bypassed', variant: 'outline' as const };
}
if (status.status === 'disabled') return { label: 'Disabled', variant: 'outline' as const };
if (status.status === 'hook-missing')
return { label: 'Setup needed', variant: 'destructive' as const };
@@ -25,10 +73,7 @@ function getBadge(status: ImageAnalysisStatus | null | undefined) {
return { label: 'Starts on launch', variant: 'secondary' as const };
}
if (status.effectiveRuntimeMode === 'cliproxy-image-analysis') {
return {
label: status.resolutionSource === 'profile-backend' ? 'Ready via mapping' : 'Ready',
variant: 'default' as const,
};
return { label: 'CLIProxy Active', variant: 'default' as const };
}
if (
status.authReadiness === 'unknown' ||
@@ -37,47 +82,72 @@ function getBadge(status: ImageAnalysisStatus | null | undefined) {
) {
return { label: 'Needs review', variant: 'outline' as const };
}
if (status.status === 'skipped' && status.reason?.includes('native file access')) {
return { label: 'Native Claude', variant: 'secondary' as const };
}
return { label: 'Native Read', variant: 'outline' as const };
return { label: 'Native Access', variant: 'outline' as const };
}
function getSummary(status: ImageAnalysisStatus): string {
function getSummary(status: ImageAnalysisStatus, target: CliTarget): string {
const backendName = status.backendDisplayName || status.backendId || 'this backend';
const currentTarget = TARGET_LABELS[target];
if (target !== 'claude') {
if (!status.backendId) {
return (
status.reason ||
`Current default target: ${currentTarget}. This launch path bypasses the Claude Read hook, and no Claude-side Image Analysis backend is currently mapped.`
);
}
if (status.status === 'disabled') {
return `Current default target: ${currentTarget}. This launch path bypasses the Claude Read hook, and saved Claude-side Image Analysis is disabled for this profile.`;
}
if (status.status === 'hook-missing') {
return `Current default target: ${currentTarget}. This launch path bypasses the Claude Read hook, and the saved Claude-side setup for ${backendName} still falls back to native file access because ${status.reason || 'the profile hook is not fully installed yet.'}`;
}
if (status.effectiveRuntimeMode === 'native-read') {
return `Current default target: ${currentTarget}. This launch path bypasses the Claude Read hook, and the saved Claude-side setup for ${backendName} still falls back to native file access. ${status.effectiveRuntimeReason || status.reason || `Image Analysis via ${backendName} could not be confirmed.`}`;
}
return `Images and PDFs for this profile are configured to resolve through ${backendName} when the profile runs on Claude Code.`;
}
if (status.status === 'disabled') {
return "Disabled globally. This profile uses Claude's built-in file reading because CCS image analysis is turned off.";
return 'Image Analysis is disabled globally, so images and PDFs use built-in file access for this profile.';
}
if (!status.backendId) {
return status.reason || "This profile uses Claude's built-in file reading.";
return status.reason || 'This profile currently uses built-in file access for images and PDFs.';
}
if (status.status === 'hook-missing') {
return `Configured for ${backendName}, but ${status.reason || 'the image-analysis hook is not fully installed yet.'}`;
return `Images and PDFs are configured for ${backendName}, but ${status.reason || 'the profile hook is not fully installed yet.'}`;
}
if (status.effectiveRuntimeMode === 'native-read') {
return `Configured via ${backendName}, but ${status.effectiveRuntimeReason || status.reason || 'runtime readiness could not be confirmed.'}`;
return `This profile currently falls back to native file access. ${status.effectiveRuntimeReason || status.reason || `Image Analysis via ${backendName} could not be confirmed.`}`;
}
if (status.proxyReadiness === 'stopped') {
return `Configured via ${backendName}. Auth is ready and CCS will start the local CLIProxy runtime on launch, so image and PDF reads still use CLIProxy.`;
return `Images and PDFs for this profile resolve through ${backendName}. Auth is ready and CCS will start the local CLIProxy runtime when needed.`;
}
if (status.resolutionSource === 'profile-backend') {
return `Configured via saved ${backendName} mapping. Auth and runtime are ready, so image and PDF reads use CLIProxy.`;
return `Images and PDFs for this profile resolve through ${backendName} via a saved Image Analysis mapping.`;
}
if (status.status === 'attention' && status.reason) {
return `Configured via ${backendName}. Image and PDF reads use CLIProxy, but ${status.reason}`;
return `Images and PDFs for this profile resolve through ${backendName} via CLIProxy, but ${status.reason}`;
}
return `Configured via ${backendName}. Image and PDF reads use CLIProxy for this profile.`;
return `Images and PDFs for this profile resolve through ${backendName} via CLIProxy.`;
}
function getRuntimeLine(status: ImageAnalysisStatus): string {
function getRuntimeLine(status: ImageAnalysisStatus, target: CliTarget): string {
if (target !== 'claude') {
return `${TARGET_LABELS[target]} launch -> no Claude Read hook`;
}
if (status.effectiveRuntimeMode === 'native-read') {
return 'Read -> native file access';
}
@@ -93,6 +163,69 @@ function getRuntimeLine(status: ImageAnalysisStatus): string {
return `Read -> image-analysis hook -> ${status.runtimePath || 'CLIProxy'}`;
}
function getConfiguredBackendDetail(status: ImageAnalysisStatus): string {
if (!status.backendId) {
return status.reason || 'No backend mapped';
}
return RESOLUTION_SOURCE_LABELS[status.resolutionSource] || 'Configured';
}
function getEffectiveRuntimeValue(status: ImageAnalysisStatus, target: CliTarget): string {
if (target !== 'claude') {
return `Not active on ${TARGET_LABELS[target]}`;
}
if (status.effectiveRuntimeMode === 'native-read') {
return 'Native file access';
}
return 'CLIProxy image analysis';
}
function getEffectiveRuntimeDetail(status: ImageAnalysisStatus, target: CliTarget): string {
if (target !== 'claude') {
if (status.status === 'hook-missing') {
return (
status.reason ||
`${TARGET_LABELS[target]} bypasses the Claude Read hook, and the saved Claude-side profile hook is still missing.`
);
}
if (status.effectiveRuntimeMode === 'native-read') {
return (
status.effectiveRuntimeReason ||
status.reason ||
`${TARGET_LABELS[target]} bypasses the Claude Read hook, and the saved Claude-side setup currently falls back to native file access.`
);
}
return `${TARGET_LABELS[target]} bypasses the Claude Read hook. Switch the target back to Claude Code to use the saved backend shown here.`;
}
if (status.effectiveRuntimeMode === 'native-read') {
return (
status.effectiveRuntimeReason ||
status.reason ||
'Uses built-in file access for this profile.'
);
}
if (status.proxyReadiness === 'stopped') {
return 'Auth ready • Local CLIProxy starts on demand';
}
if (status.proxyReadiness === 'remote') {
return 'Auth ready • Remote CLIProxy reachable';
}
if (status.authReadiness === 'ready' && status.proxyReadiness === 'ready') {
return 'Auth ready • Local CLIProxy reachable';
}
return status.proxyReason || status.authReason || 'Runtime verified';
}
function getAuthLine(status: ImageAnalysisStatus): string {
if (status.authReadiness === 'not-needed') return 'Not required';
if (status.authReadiness === 'ready')
@@ -124,16 +257,25 @@ function getStatusContext(
return 'Saved runtime status for this profile. Config stays in the JSON editor above; auth and proxy readiness are derived at runtime.';
}
function getPersistenceLine(status: ImageAnalysisStatus): string {
if (!status.shouldPersistHook || !status.persistencePath)
return 'Not persisted for this profile type';
return status.hookInstalled
? `${status.persistencePath} hook`
: `${status.persistencePath} hook missing`;
function getPersistenceValue(status: ImageAnalysisStatus): string {
if (!status.shouldPersistHook || !status.persistencePath) {
return 'Not persisted';
}
return status.hookInstalled ? 'Hook saved to profile' : 'Hook missing from profile';
}
function getPersistenceDetail(status: ImageAnalysisStatus): string {
if (!status.shouldPersistHook || !status.persistencePath) {
return 'Not required for this profile type';
}
return status.persistencePath;
}
export function ImageAnalysisStatusSection({
status,
target = 'claude',
source = 'saved',
previewState = 'saved',
}: ImageAnalysisStatusSectionProps) {
@@ -147,9 +289,12 @@ export function ImageAnalysisStatusSection({
);
}
const badge = getBadge(status);
const notice =
status.effectiveRuntimeMode === 'native-read'
const previewBadge = getPreviewBadge(source, previewState);
const runtimeBadge = getRuntimeBadge(status, target);
const bypassedOnCurrentTarget = target !== 'claude';
const notice = bypassedOnCurrentTarget
? null
: status.effectiveRuntimeMode === 'native-read'
? status.effectiveRuntimeReason
: status.status === 'attention' || status.status === 'hook-missing'
? status.reason
@@ -157,43 +302,82 @@ export function ImageAnalysisStatusSection({
return (
<section className="rounded-md border bg-muted/20 p-4">
<div className="flex items-start justify-between gap-3">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<div className="flex items-center gap-2">
<ImageIcon className="h-4 w-4 text-sky-600" />
<h3 className="text-sm font-semibold">Image-analysis backend</h3>
<h3 className="text-sm font-semibold">Image Analysis</h3>
</div>
<p className="mt-1 text-xs text-muted-foreground">
{getStatusContext(source, previewState)}
</p>
</div>
<Badge variant={badge.variant} className="shrink-0 text-[11px]">
{badge.label}
</Badge>
<div className="flex flex-wrap items-center gap-2">
<Badge variant={previewBadge.variant} className="h-5 shrink-0 px-1.5 text-[10px]">
{previewBadge.label}
</Badge>
<Badge variant={runtimeBadge.variant} className="h-5 shrink-0 px-1.5 text-[10px]">
{runtimeBadge.label}
</Badge>
</div>
</div>
<p aria-live="polite" className="mt-3 text-sm leading-6 text-muted-foreground">
{getSummary(status)}
{getSummary(status, target)}
</p>
<dl className="mt-4 grid gap-x-4 gap-y-3 sm:grid-cols-2">
<div className="space-y-1">
<dt className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
Backend
</dt>
<dd className="text-sm font-medium">{status.backendDisplayName || 'Unresolved'}</dd>
{bypassedOnCurrentTarget && (
<div className="mt-4 flex items-start gap-2 rounded-md border border-sky-500/20 bg-sky-500/10 px-3 py-2 text-sm text-sky-900 dark:text-sky-200">
<Route className="mt-0.5 h-4 w-4 shrink-0 text-sky-600 dark:text-sky-300" />
<span>
Current default target: {TARGET_LABELS[target]}. The diagnostics below describe the
saved Claude-side Image Analysis hook and apply again if you switch back to Claude Code.
</span>
</div>
<div className="space-y-1">
<dt className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
Runtime
</dt>
<dd
className="font-mono text-xs leading-5 text-foreground"
title={getRuntimeLine(status)}
)}
<div className="mt-4 grid gap-3 md:grid-cols-3">
<div className="rounded-md border bg-background/70 p-3">
<div className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
Configured backend
</div>
<div className="mt-2 text-sm font-medium text-foreground">
{status.backendDisplayName || status.backendId || 'No backend mapped'}
</div>
<p className="mt-1 text-xs leading-5 text-muted-foreground">
{getConfiguredBackendDetail(status)}
</p>
</div>
<div className="rounded-md border bg-background/70 p-3">
<div className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
Effective runtime
</div>
<div className="mt-2 text-sm font-medium text-foreground">
{getEffectiveRuntimeValue(status, target)}
</div>
<p className="mt-1 text-xs leading-5 text-muted-foreground">
{getEffectiveRuntimeDetail(status, target)}
</p>
</div>
<div className="rounded-md border bg-background/70 p-3">
<div className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
Hook persistence
</div>
<div className="mt-2 text-sm font-medium text-foreground">
{getPersistenceValue(status)}
</div>
<p
className="mt-1 text-xs leading-5 text-muted-foreground"
title={status.persistencePath || 'Not persisted'}
>
{getRuntimeLine(status)}
</dd>
{getPersistenceDetail(status)}
</p>
</div>
</div>
<dl className="mt-4 grid gap-x-4 gap-y-3 sm:grid-cols-3">
<div className="space-y-1">
<dt className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
Auth
@@ -206,17 +390,6 @@ export function ImageAnalysisStatusSection({
</dt>
<dd className="text-sm text-foreground">{getProxyLine(status)}</dd>
</div>
<div className="space-y-1">
<dt className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
Persistence
</dt>
<dd
className="font-mono text-xs leading-5 text-foreground"
title={status.persistencePath || 'Not persisted'}
>
{getPersistenceLine(status)}
</dd>
</div>
<div className="space-y-1">
<dt className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
Model
@@ -227,6 +400,18 @@ export function ImageAnalysisStatusSection({
</div>
</dl>
<div className="mt-4 rounded-md border bg-background/60 px-3 py-2">
<div className="text-[11px] font-medium uppercase tracking-[0.14em] text-muted-foreground">
Runtime path
</div>
<div
className="mt-1 font-mono text-xs leading-5 text-foreground"
title={getRuntimeLine(status, target)}
>
{getRuntimeLine(status, target)}
</div>
</div>
{notice && (
<div className="mt-4 flex items-start gap-2 rounded-md border border-amber-500/20 bg-amber-500/10 px-3 py-2 text-sm text-amber-900 dark:text-amber-200">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-amber-500" />
@@ -237,7 +422,8 @@ export function ImageAnalysisStatusSection({
<div className="mt-4 flex items-center gap-2 border-t border-border/60 pt-3 text-xs text-muted-foreground">
<Route className="h-3.5 w-3.5" />
<span>
WebSearch stays managed separately and is not controlled by this backend status.
This panel covers the Image Analysis hook only. WebSearch stays managed separately and is
not controlled here.
</span>
</div>
</section>
+6 -2
View File
@@ -124,6 +124,7 @@ export function ProfileEditor({
data: previewStatusResponse,
isFetching: isPreviewStatusFetching,
isError: isPreviewStatusError,
isPlaceholderData: isPreviewStatusPlaceholderData,
} = useQuery<{ imageAnalysisStatus: SettingsResponse['imageAnalysisStatus'] }>({
queryKey: ['settings', profileName, 'image-analysis-status-preview', deferredPreviewJson],
enabled: previewSettings !== null,
@@ -160,7 +161,8 @@ export function ProfileEditor({
? 'invalid'
: isPreviewStatusError
? 'saved'
: isPreviewStatusFetching && !previewStatusResponse?.imageAnalysisStatus
: isPreviewStatusFetching &&
(!previewStatusResponse?.imageAnalysisStatus || isPreviewStatusPlaceholderData)
? 'refreshing'
: 'preview';
@@ -219,7 +221,8 @@ export function ProfileEditor({
toast.success(i18n.t('commonToast.defaultTargetUpdated'));
},
onError: (error: Error, target: CliTarget) => {
const targetLabel = target === 'droid' ? 'Factory Droid' : 'Claude Code';
const targetLabel =
target === 'droid' ? 'Factory Droid' : target === 'codex' ? 'Codex CLI' : 'Claude Code';
const suffix = error.message.trim() ? `: ${error.message}` : '';
toast.error(i18n.t('commonToast.failedUpdateDefaultTarget', { target: targetLabel, suffix }));
},
@@ -311,6 +314,7 @@ export function ProfileEditor({
isRawJsonValid={computedIsRawJsonValid}
rawJsonEdits={rawJsonEdits}
settings={settings}
profileTarget={resolvedTarget}
imageAnalysisStatus={imageAnalysisStatus}
imageAnalysisStatusSource={imageAnalysisStatusSource}
imageAnalysisStatusPreviewState={imageAnalysisStatusPreviewState}
@@ -8,7 +8,7 @@ import { Loader2, X, AlertTriangle } from 'lucide-react';
import { GlobalEnvIndicator } from '@/components/shared/global-env-indicator';
import { ImageAnalysisStatusSection } from './image-analysis-status-section';
import type { Settings } from './types';
import type { ImageAnalysisStatus } from '@/lib/api-client';
import type { CliTarget, ImageAnalysisStatus } from '@/lib/api-client';
// Lazy load CodeEditor
const CodeEditor = lazy(() =>
@@ -20,6 +20,7 @@ interface RawEditorSectionProps {
isRawJsonValid: boolean;
rawJsonEdits: string | null;
settings: Settings | undefined;
profileTarget?: CliTarget;
imageAnalysisStatus?: ImageAnalysisStatus | null;
imageAnalysisStatusSource?: 'saved' | 'editor';
imageAnalysisStatusPreviewState?: 'saved' | 'preview' | 'refreshing' | 'invalid';
@@ -32,6 +33,7 @@ export function RawEditorSection({
isRawJsonValid,
rawJsonEdits,
settings,
profileTarget = 'claude',
imageAnalysisStatus,
imageAnalysisStatusSource = 'saved',
imageAnalysisStatusPreviewState = 'saved',
@@ -86,6 +88,7 @@ export function RawEditorSection({
<div className="mx-6 mb-4">
<ImageAnalysisStatusSection
status={imageAnalysisStatus}
target={profileTarget}
source={imageAnalysisStatusSource}
previewState={imageAnalysisStatusPreviewState}
/>
@@ -80,7 +80,7 @@ const schema = z.object({
opusModel: z.string().optional(),
sonnetModel: z.string().optional(),
haikuModel: z.string().optional(),
target: z.enum(['claude', 'droid']),
target: z.enum(['claude', 'droid', 'codex']),
});
type FormData = z.infer<typeof schema>;
@@ -521,6 +521,7 @@ export function ProfileCreateDialog({
<SelectContent>
<SelectItem value="claude">Claude Code (default)</SelectItem>
<SelectItem value="droid">Factory Droid</SelectItem>
<SelectItem value="codex">Codex CLI</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
@@ -529,6 +530,11 @@ export function ProfileCreateDialog({
{t('profileEditor.targetHintPreferredAlias')}{' '}
<code className="bg-muted px-1 rounded text-[10px]">ccs-droid</code>.
</>
) : targetValue === 'codex' ? (
<>
{t('profileEditor.targetHintPreferredAlias')}{' '}
<code className="bg-muted px-1 rounded text-[10px]">ccsx</code>.
</>
) : (
<>
{t('profileEditor.targetHintClaudeDefault')}{' '}
@@ -541,6 +547,12 @@ export function ProfileCreateDialog({
{t('profileEditor.targetHintLegacyAlias')}{' '}
<code className="bg-muted px-1 rounded text-[10px]">ccsd</code>.
</>
) : targetValue === 'codex' ? (
<>
{' '}
{t('profileEditor.targetHintLegacyAlias')}{' '}
<code className="bg-muted px-1 rounded text-[10px]">ccs-codex</code>.
</>
) : null}{' '}
{t('profileEditor.targetHintOverride')}{' '}
<code className="bg-muted px-1 rounded text-[10px]">--target</code>.
+1 -1
View File
@@ -99,7 +99,7 @@ async function request<T>(url: string, options?: RequestInit): Promise<T> {
}
// Types
export type CliTarget = 'claude' | 'droid';
export type CliTarget = 'claude' | 'droid' | 'codex';
export interface CliproxyBridgeMetadata {
provider: CLIProxyProvider;
+2 -2
View File
@@ -70,7 +70,7 @@ export const SUPPORT_NOTICES: SupportNotice[] = [
'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.',
'Saved default targets for API profiles and variants can now be claude, droid, or codex.',
],
actions: [
{
@@ -256,7 +256,7 @@ export const CLI_SUPPORT_ENTRIES: CliSupportEntry[] = [
routes: [{ label: 'Codex CLI', path: '/codex' }],
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.',
'Saved default targets for API profiles and CLIProxy variants can now be claude, droid, or codex.',
},
{
id: 'codex-cliproxy',