feat(cli-settings): support multi-provider settings for Claude, Codex, and Gemini

Decouple CLI settings architecture from Claude-only to support multiple
providers. Each provider has independent settings UI and backend handling.

- Add CliProvider type discriminator ('claude' | 'codex' | 'gemini')
- Add CodexCliSettings (profile, authJson, configToml) and GeminiCliSettings types
- Update EndpointSettings with provider field (defaults 'claude' for backward compat)
- Refactor CliSettingsModal with provider selector and provider-specific forms
- Remove includeCoAuthoredBy field across all layers
- Extend CliConfigModal to show Config Profile for all tools (not just claude)
- Add provider-aware argument injection in cli-session-manager (--settings/--profile/env)
- Rename addClaudeCustomEndpoint to addCustomEndpoint (old name kept as deprecated alias)
- Replace providerBasedCount/directCount with per-provider counts in useCliSettings hook
- Update CliSettingsList with provider badges and per-provider stat cards
- Add Codex and Gemini test cases for validateSettings and createDefaultSettings
This commit is contained in:
catlog22
2026-02-25 17:40:43 +08:00
parent c11596c038
commit d6acbaf30f
11 changed files with 927 additions and 497 deletions

View File

@@ -56,8 +56,8 @@ export async function handleCliSettingsRoutes(ctx: RouteContext): Promise<boolea
if (!request.settings || !request.settings.env) {
return { error: 'settings.env is required', status: 400 };
}
// Deep validation of settings object
if (!validateSettings(request.settings)) {
// Deep validation of settings object (provider-aware)
if (!validateSettings(request.settings, request.provider)) {
return { error: 'Invalid settings object format', status: 400 };
}

View File

@@ -15,6 +15,8 @@ import { getCliSessionPolicy } from './cli-session-policy.js';
import { appendCliSessionAudit } from './cli-session-audit.js';
import { getLaunchConfig } from './cli-launch-registry.js';
import { assembleInstruction, type InstructionType } from './cli-instruction-assembler.js';
import { loadEndpointSettings } from '../../config/cli-settings-manager.js';
import { getToolConfig } from '../../tools/claude-cli-tools.js';
export interface CliSession {
sessionKey: string;
@@ -41,6 +43,8 @@ export interface CreateCliSessionOptions {
resumeKey?: string;
/** Launch mode for native CLI sessions. */
launchMode?: 'default' | 'yolo';
/** Settings endpoint ID for injecting env vars and settings into CLI process. */
settingsEndpointId?: string;
}
export interface ExecuteInCliSessionOptions {
@@ -221,15 +225,56 @@ export class CliSessionManager {
let args: string[];
let cliTool: string | undefined;
// Load settings endpoint env vars and extra args if specified
let endpointEnv: Record<string, string> = {};
let endpointExtraArgs: string[] = [];
if (options.settingsEndpointId) {
try {
const endpoint = loadEndpointSettings(options.settingsEndpointId);
if (endpoint) {
// Merge env vars (skip undefined/empty values)
for (const [key, value] of Object.entries(endpoint.settings.env)) {
if (value !== undefined && value !== '') {
endpointEnv[key] = value;
}
}
// Provider-specific argument injection
const provider = endpoint.provider || 'claude';
if (provider === 'claude' && 'settingsFile' in endpoint.settings && endpoint.settings.settingsFile) {
endpointExtraArgs.push('--settings', endpoint.settings.settingsFile);
} else if (provider === 'codex' && 'profile' in endpoint.settings && endpoint.settings.profile) {
endpointExtraArgs.push('--profile', endpoint.settings.profile);
}
// Gemini: env vars only, no extra CLI flags
}
} catch (err) {
console.warn('[CliSessionManager] Failed to load settings endpoint:', options.settingsEndpointId, err);
}
} else if (options.tool) {
// Fallback: read settingsFile from cli-tools.json for the tool
try {
const toolConfig = getToolConfig(this.projectRoot, options.tool);
if (options.tool === 'claude' && toolConfig.settingsFile) {
endpointExtraArgs.push('--settings', toolConfig.settingsFile);
}
} catch (err) {
// Non-fatal: continue without settings file
}
}
if (options.tool) {
// Native CLI interactive session: spawn the CLI process directly
const launchMode = options.launchMode ?? 'default';
const config = getLaunchConfig(options.tool, launchMode);
cliTool = options.tool;
// Append endpoint-specific extra args (e.g., --settings for claude)
const allArgs = [...config.args, ...endpointExtraArgs];
// Build the full command string with arguments
const fullCommand = config.args.length > 0
? `${config.command} ${config.args.join(' ')}`
const fullCommand = allArgs.length > 0
? `${config.command} ${allArgs.join(' ')}`
: config.command;
// On Windows, CLI tools installed via npm are typically .cmd files.
@@ -275,7 +320,7 @@ export class CliSessionManager {
// Unix: direct spawn works for most CLI tools
shellKind = 'git-bash';
file = config.command;
args = config.args;
args = allArgs;
}
} else {
@@ -289,6 +334,12 @@ export class CliSessionManager {
args = picked.args;
}
// Merge endpoint env vars with process.env (endpoint overrides process.env)
const spawnEnv: Record<string, string> = {
...(process.env as Record<string, string>),
...endpointEnv,
};
let pty: nodePty.IPty;
try {
pty = nodePty.spawn(file, args, {
@@ -296,7 +347,7 @@ export class CliSessionManager {
cols: options.cols ?? 120,
rows: options.rows ?? 30,
cwd: workingDir,
env: process.env as Record<string, string>
env: spawnEnv
});
} catch (spawnError: unknown) {
const errorMsg = spawnError instanceof Error ? spawnError.message : String(spawnError);