Skip to content

Commit b4d3426

Browse files
author
catlog22
committed
feat: add CLI config preview API for Codex and Gemini
- Implemented `fetchCodexConfigPreview` and `fetchGeminiConfigPreview` functions in the API layer to retrieve masked configuration files. - Added new interfaces `CodexConfigPreviewResponse` and `GeminiConfigPreviewResponse` to define the structure of the API responses. - Created utility functions to read and mask sensitive values from `config.toml` and `auth.json` for Codex, and `settings.json` for Gemini. - Updated CLI settings routes to handle new preview endpoints. - Enhanced session content parser to support Claude JSONL format. - Updated UI components to reflect changes in history page and navigation, including new tabs for observability. - Localized changes for English and Chinese languages to reflect "CLI History" terminology.
1 parent c927545 commit b4d3426

15 files changed

Lines changed: 1133 additions & 159 deletions

File tree

ccw/frontend/src/components/api-settings/CliSettingsModal.tsx

Lines changed: 139 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { useState, useEffect } from 'react';
77
import { useIntl } from 'react-intl';
8-
import { Check, Eye, EyeOff, X, Plus } from 'lucide-react';
8+
import { Check, Eye, EyeOff, X, Plus, Loader2, Download } from 'lucide-react';
99
import {
1010
Dialog,
1111
DialogContent,
@@ -23,6 +23,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
2323
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/Tabs';
2424
import { useCreateCliSettings, useUpdateCliSettings, useProviders } from '@/hooks/useApiSettings';
2525
import { useNotifications } from '@/hooks/useNotifications';
26+
import { fetchCodexConfigPreview, fetchGeminiConfigPreview } from '@/lib/api';
2627
import type { CliSettingsEndpoint, CliProvider } from '@/lib/api';
2728

2829
// ========== Types ==========
@@ -101,6 +102,8 @@ export function CliSettingsModal({ open, onClose, cliSettings, defaultProvider }
101102
// Gemini specific
102103
const [geminiApiKey, setGeminiApiKey] = useState('');
103104
const [showGeminiKey, setShowGeminiKey] = useState(false);
105+
const [geminiSettingsJson, setGeminiSettingsJson] = useState('');
106+
const [isLoadingGeminiConfig, setIsLoadingGeminiConfig] = useState(false);
104107

105108
// Shared
106109
const [model, setModel] = useState('');
@@ -112,6 +115,9 @@ export function CliSettingsModal({ open, onClose, cliSettings, defaultProvider }
112115
const [showJsonInput, setShowJsonInput] = useState(false);
113116
const [errors, setErrors] = useState<Record<string, string>>({});
114117

118+
// Codex config preview loading state
119+
const [isLoadingCodexConfig, setIsLoadingCodexConfig] = useState(false);
120+
115121
// Initialize form
116122
useEffect(() => {
117123
if (cliSettings) {
@@ -171,6 +177,7 @@ export function CliSettingsModal({ open, onClose, cliSettings, defaultProvider }
171177
setWriteCommonConfig(false);
172178
setGeminiApiKey('');
173179
setShowGeminiKey(false);
180+
setGeminiSettingsJson('');
174181
setAvailableModels([]);
175182
setModelInput('');
176183
setTags([]);
@@ -224,6 +231,47 @@ export function CliSettingsModal({ open, onClose, cliSettings, defaultProvider }
224231
return Object.keys(newErrors).length === 0;
225232
};
226233

234+
// Handle load Codex config preview
235+
const handleLoadCodexConfig = async () => {
236+
setIsLoadingCodexConfig(true);
237+
try {
238+
const result = await fetchCodexConfigPreview();
239+
if (result.success) {
240+
if (result.configToml) {
241+
setConfigToml(result.configToml);
242+
}
243+
if (result.authJson) {
244+
setAuthJson(result.authJson);
245+
}
246+
} else {
247+
error(formatMessage({ id: 'apiSettings.cliSettings.loadConfigError' }) || 'Failed to load config');
248+
}
249+
} catch (err) {
250+
error(formatMessage({ id: 'apiSettings.cliSettings.loadConfigError' }) || 'Failed to load config');
251+
} finally {
252+
setIsLoadingCodexConfig(false);
253+
}
254+
};
255+
256+
// Handle load Gemini config preview
257+
const handleLoadGeminiConfig = async () => {
258+
setIsLoadingGeminiConfig(true);
259+
try {
260+
const result = await fetchGeminiConfigPreview();
261+
if (result.success) {
262+
if (result.settingsJson) {
263+
setGeminiSettingsJson(result.settingsJson);
264+
}
265+
} else {
266+
error(formatMessage({ id: 'apiSettings.cliSettings.loadConfigError' }) || 'Failed to load config');
267+
}
268+
} catch (err) {
269+
error(formatMessage({ id: 'apiSettings.cliSettings.loadConfigError' }) || 'Failed to load config');
270+
} finally {
271+
setIsLoadingGeminiConfig(false);
272+
}
273+
};
274+
227275
// Handle save
228276
const handleSave = async () => {
229277
if (!validateForm()) return;
@@ -521,6 +569,35 @@ export function CliSettingsModal({ open, onClose, cliSettings, defaultProvider }
521569
{/* ========== Codex Settings ========== */}
522570
{cliProvider === 'codex' && (
523571
<div className="space-y-4">
572+
{/* Load Global Config Button */}
573+
<div className="flex items-center justify-between p-3 bg-muted/30 rounded-lg">
574+
<div>
575+
<p className="text-sm font-medium">Load Global Config</p>
576+
<p className="text-xs text-muted-foreground">
577+
Load config.toml and auth.json from global Codex config directory
578+
</p>
579+
</div>
580+
<Button
581+
type="button"
582+
variant="outline"
583+
size="sm"
584+
onClick={handleLoadCodexConfig}
585+
disabled={isLoadingCodexConfig}
586+
>
587+
{isLoadingCodexConfig ? (
588+
<>
589+
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
590+
Loading...
591+
</>
592+
) : (
593+
<>
594+
<Download className="w-4 h-4 mr-2" />
595+
Load Config
596+
</>
597+
)}
598+
</Button>
599+
</div>
600+
524601
{/* API Key */}
525602
<div className="space-y-2">
526603
<Label htmlFor="codex-apikey">API Key</Label>
@@ -645,6 +722,35 @@ export function CliSettingsModal({ open, onClose, cliSettings, defaultProvider }
645722
{/* ========== Gemini Settings ========== */}
646723
{cliProvider === 'gemini' && (
647724
<div className="space-y-4">
725+
{/* Load Global Config Button */}
726+
<div className="flex items-center justify-between p-3 bg-muted/30 rounded-lg">
727+
<div>
728+
<p className="text-sm font-medium">Load Global Config</p>
729+
<p className="text-xs text-muted-foreground">
730+
Load settings.json from global Gemini config directory
731+
</p>
732+
</div>
733+
<Button
734+
type="button"
735+
variant="outline"
736+
size="sm"
737+
onClick={handleLoadGeminiConfig}
738+
disabled={isLoadingGeminiConfig}
739+
>
740+
{isLoadingGeminiConfig ? (
741+
<>
742+
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
743+
Loading...
744+
</>
745+
) : (
746+
<>
747+
<Download className="w-4 h-4 mr-2" />
748+
Load Config
749+
</>
750+
)}
751+
</Button>
752+
</div>
753+
648754
<div className="space-y-2">
649755
<Label htmlFor="gemini-apikey">API Key</Label>
650756
<div className="relative">
@@ -675,6 +781,38 @@ export function CliSettingsModal({ open, onClose, cliSettings, defaultProvider }
675781
placeholder="gemini-2.5-flash"
676782
/>
677783
</div>
784+
785+
{/* settings.json Editor */}
786+
{geminiSettingsJson && (
787+
<div className="space-y-2">
788+
<div className="flex items-center justify-between">
789+
<Label htmlFor="gemini-settingsjson">settings.json (Preview)</Label>
790+
<Button
791+
type="button" variant="ghost" size="sm"
792+
onClick={() => {
793+
try {
794+
const formatted = JSON.stringify(JSON.parse(geminiSettingsJson), null, 2);
795+
setGeminiSettingsJson(formatted);
796+
} catch { /* skip */ }
797+
}}
798+
>
799+
Format
800+
</Button>
801+
</div>
802+
<Textarea
803+
id="gemini-settingsjson"
804+
value={geminiSettingsJson}
805+
onChange={(e) => setGeminiSettingsJson(e.target.value)}
806+
placeholder='{"model": "gemini-2.5-flash", ...}'
807+
className="font-mono text-sm"
808+
rows={8}
809+
readOnly
810+
/>
811+
<p className="text-xs text-muted-foreground">
812+
Gemini settings.json content (read-only preview)
813+
</p>
814+
</div>
815+
)}
678816
</div>
679817
)}
680818

ccw/frontend/src/components/issue/hub/IssueHubHeader.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
// Dynamic header component for IssueHub
55

66
import { useIntl } from 'react-intl';
7-
import { AlertCircle, Radar, ListTodo, LayoutGrid, Activity, Terminal } from 'lucide-react';
8-
9-
type IssueTab = 'issues' | 'board' | 'queue' | 'discovery' | 'observability' | 'executions';
7+
import { AlertCircle, Radar, ListTodo, LayoutGrid, Play } from 'lucide-react';
8+
import type { IssueTab } from './IssueHubTabs';
109

1110
interface IssueHubHeaderProps {
1211
currentTab: IssueTab;
@@ -37,19 +36,14 @@ export function IssueHubHeader({ currentTab }: IssueHubHeaderProps) {
3736
title: formatMessage({ id: 'issues.discovery.pageTitle' }),
3837
description: formatMessage({ id: 'issues.discovery.description' }),
3938
},
40-
observability: {
41-
icon: <Activity className="w-6 h-6 text-primary" />,
42-
title: formatMessage({ id: 'issues.observability.pageTitle' }),
43-
description: formatMessage({ id: 'issues.observability.description' }),
44-
},
4539
executions: {
46-
icon: <Terminal className="w-6 h-6 text-primary" />,
40+
icon: <Play className="w-6 h-6 text-primary" />,
4741
title: formatMessage({ id: 'issues.executions.pageTitle' }),
4842
description: formatMessage({ id: 'issues.executions.description' }),
4943
},
5044
};
5145

52-
const config = tabConfig[currentTab];
46+
const config = tabConfig[currentTab] || tabConfig.issues;
5347

5448
return (
5549
<div className="flex items-center gap-2">

ccw/frontend/src/components/issue/hub/IssueHubTabs.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Button } from '@/components/ui/Button';
88
import { cn } from '@/lib/utils';
99

1010
// Keep in sync with IssueHubHeader/IssueHubPage
11-
export type IssueTab = 'issues' | 'board' | 'queue' | 'discovery' | 'observability' | 'executions';
11+
export type IssueTab = 'issues' | 'board' | 'queue' | 'discovery' | 'executions';
1212

1313
interface IssueHubTabsProps {
1414
currentTab: IssueTab;
@@ -23,7 +23,6 @@ export function IssueHubTabs({ currentTab, onTabChange }: IssueHubTabsProps) {
2323
{ value: 'board', label: formatMessage({ id: 'issues.hub.tabs.board' }) },
2424
{ value: 'queue', label: formatMessage({ id: 'issues.hub.tabs.queue' }) },
2525
{ value: 'discovery', label: formatMessage({ id: 'issues.hub.tabs.discovery' }) },
26-
{ value: 'observability', label: formatMessage({ id: 'issues.hub.tabs.observability' }) },
2726
{ value: 'executions', label: formatMessage({ id: 'issues.hub.tabs.executions' }) },
2827
];
2928

ccw/frontend/src/components/layout/Sidebar.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
PanelLeftClose,
1818
PanelLeftOpen,
1919
LayoutDashboard,
20-
Clock,
2120
Zap,
2221
GitFork,
2322
Shield,
@@ -78,7 +77,6 @@ const navGroupDefinitions: NavGroupDef[] = [
7877
{ path: '/sessions', labelKey: 'navigation.main.sessions', icon: FolderKanban },
7978
{ path: '/lite-tasks', labelKey: 'navigation.main.liteTasks', icon: Zap },
8079
{ path: '/orchestrator', labelKey: 'navigation.main.orchestrator', icon: Workflow },
81-
{ path: '/history', labelKey: 'navigation.main.history', icon: Clock },
8280
{ path: '/issues', labelKey: 'navigation.main.issues', icon: AlertCircle },
8381
{ path: '/teams', labelKey: 'navigation.main.teams', icon: Users },
8482
{ path: '/terminal-dashboard', labelKey: 'navigation.main.terminalDashboard', icon: Terminal },

ccw/frontend/src/lib/api.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6150,6 +6150,54 @@ export async function getCliSettingsPath(endpointId: string): Promise<{ endpoint
61506150
return fetchApi(`/api/cli/settings/${encodeURIComponent(endpointId)}/path`);
61516151
}
61526152

6153+
// ========== CLI Config Preview API ==========
6154+
6155+
/**
6156+
* Codex config preview response
6157+
*/
6158+
export interface CodexConfigPreviewResponse {
6159+
/** Whether preview was successful */
6160+
success: boolean;
6161+
/** Path to config.toml */
6162+
configPath: string;
6163+
/** Path to auth.json */
6164+
authPath: string;
6165+
/** config.toml content with sensitive values masked */
6166+
configToml: string | null;
6167+
/** auth.json content with API keys masked */
6168+
authJson: string | null;
6169+
/** Error messages if any files could not be read */
6170+
errors?: string[];
6171+
}
6172+
6173+
/**
6174+
* Gemini config preview response
6175+
*/
6176+
export interface GeminiConfigPreviewResponse {
6177+
/** Whether preview was successful */
6178+
success: boolean;
6179+
/** Path to settings.json */
6180+
settingsPath: string;
6181+
/** settings.json content with sensitive values masked */
6182+
settingsJson: string | null;
6183+
/** Error messages if file could not be read */
6184+
errors?: string[];
6185+
}
6186+
6187+
/**
6188+
* Fetch Codex config files preview (config.toml and auth.json)
6189+
*/
6190+
export async function fetchCodexConfigPreview(): Promise<CodexConfigPreviewResponse> {
6191+
return fetchApi('/api/cli/settings/codex/preview');
6192+
}
6193+
6194+
/**
6195+
* Fetch Gemini settings file preview (settings.json)
6196+
*/
6197+
export async function fetchGeminiConfigPreview(): Promise<GeminiConfigPreviewResponse> {
6198+
return fetchApi('/api/cli/settings/gemini/preview');
6199+
}
6200+
61536201
// ========== Orchestrator Execution Monitoring API ==========
61546202

61556203
/**

ccw/frontend/src/locales/en/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@
245245
"coordinator": "Coordinator",
246246
"executions": "Executions",
247247
"loops": "Loops",
248-
"history": "History",
248+
"history": "CLI History",
249249
"memory": "Memory",
250250
"prompts": "Prompts",
251251
"skills": "Skills",

ccw/frontend/src/locales/en/navigation.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"sessions": "Sessions",
1313
"liteTasks": "Lite Tasks",
1414
"project": "Project",
15-
"history": "History",
15+
"history": "CLI History",
1616
"orchestrator": "Orchestrator",
1717
"coordinator": "Coordinator",
1818
"loops": "Loop Monitor",

ccw/frontend/src/locales/zh/common.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@
245245
"coordinator": "协调器",
246246
"executions": "执行监控",
247247
"loops": "循环",
248-
"history": "历史",
248+
"history": "CLI执行历史",
249249
"memory": "记忆",
250250
"prompts": "提示词",
251251
"skills": "技能",

ccw/frontend/src/locales/zh/navigation.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"sessions": "会话",
1313
"liteTasks": "轻量任务",
1414
"project": "项目",
15-
"history": "历史",
15+
"history": "CLI执行历史",
1616
"orchestrator": "编排器",
1717
"coordinator": "协调器",
1818
"loops": "循环监控",

0 commit comments

Comments
 (0)