mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-25 19:48:33 +08:00
refactor: redesign cli settings export/import API with endpoint-based schema
Replace nested settings structure with flat endpoints array for export/import. Add conflict strategy options (skip/overwrite/merge) and skipInvalid/disableImported flags. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6159,28 +6159,17 @@ export async function upgradeCcwInstallation(
|
|||||||
*/
|
*/
|
||||||
export interface ExportedSettings {
|
export interface ExportedSettings {
|
||||||
version: string;
|
version: string;
|
||||||
exportedAt: string;
|
timestamp: string;
|
||||||
settings: {
|
endpoints: Array<Record<string, unknown>>;
|
||||||
cliTools?: Record<string, unknown>;
|
|
||||||
chineseResponse?: {
|
|
||||||
claudeEnabled: boolean;
|
|
||||||
codexEnabled: boolean;
|
|
||||||
};
|
|
||||||
windowsPlatform?: {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
codexCliEnhancement?: {
|
|
||||||
enabled: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import options for settings import
|
* Import options for settings import
|
||||||
*/
|
*/
|
||||||
export interface ImportOptions {
|
export interface ImportOptions {
|
||||||
overwrite?: boolean;
|
conflictStrategy?: 'skip' | 'overwrite' | 'merge';
|
||||||
dryRun?: boolean;
|
skipInvalid?: boolean;
|
||||||
|
disableImported?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -619,7 +619,7 @@ function ResponseLanguageSection() {
|
|||||||
const data = JSON.parse(text) as ExportedSettings;
|
const data = JSON.parse(text) as ExportedSettings;
|
||||||
|
|
||||||
// Validate basic structure
|
// Validate basic structure
|
||||||
if (!data.version || !data.settings) {
|
if (!data.version || !data.endpoints) {
|
||||||
toast.error(formatMessage({ id: 'settings.responseLanguage.importInvalidStructure' }));
|
toast.error(formatMessage({ id: 'settings.responseLanguage.importInvalidStructure' }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,150 +213,7 @@ export async function handleCliSettingsRoutes(ctx: RouteContext): Promise<boolea
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== GET SINGLE SETTINGS ==========
|
// ========== NAMED SUB-ROUTES (must come before :id routes) ==========
|
||||||
// GET /api/cli/settings/:id
|
|
||||||
const getMatch = pathname.match(/^\/api\/cli\/settings\/([^/]+)$/);
|
|
||||||
if (getMatch && req.method === 'GET') {
|
|
||||||
const endpointId = sanitizeEndpointId(getMatch[1]);
|
|
||||||
try {
|
|
||||||
const endpoint = loadEndpointSettings(endpointId);
|
|
||||||
|
|
||||||
if (!endpoint) {
|
|
||||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
||||||
res.end(JSON.stringify({ error: 'Endpoint not found' }));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
||||||
res.end(JSON.stringify({
|
|
||||||
endpoint,
|
|
||||||
filePath: getSettingsFilePath(endpointId)
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
|
||||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
||||||
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== UPDATE SETTINGS ==========
|
|
||||||
// PUT /api/cli/settings/:id
|
|
||||||
const putMatch = pathname.match(/^\/api\/cli\/settings\/([^/]+)$/);
|
|
||||||
if (putMatch && req.method === 'PUT') {
|
|
||||||
const endpointId = sanitizeEndpointId(putMatch[1]);
|
|
||||||
handlePostRequest(req, res, async (body: unknown) => {
|
|
||||||
try {
|
|
||||||
const request = body as Partial<SaveEndpointRequest>;
|
|
||||||
|
|
||||||
// Check if just toggling enabled status
|
|
||||||
if (Object.keys(request).length === 1 && 'enabled' in request) {
|
|
||||||
const result = toggleEndpointEnabled(endpointId, request.enabled as boolean);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
broadcastToClients({
|
|
||||||
type: 'CLI_SETTINGS_TOGGLED',
|
|
||||||
payload: {
|
|
||||||
endpointId,
|
|
||||||
enabled: request.enabled,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full update
|
|
||||||
const existing = loadEndpointSettings(endpointId);
|
|
||||||
if (!existing) {
|
|
||||||
return { error: 'Endpoint not found', status: 404 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateRequest: SaveEndpointRequest = {
|
|
||||||
id: endpointId,
|
|
||||||
name: request.name || existing.name,
|
|
||||||
description: request.description ?? existing.description,
|
|
||||||
settings: request.settings || existing.settings,
|
|
||||||
enabled: request.enabled ?? existing.enabled
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = saveEndpointSettings(updateRequest);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
broadcastToClients({
|
|
||||||
type: 'CLI_SETTINGS_UPDATED',
|
|
||||||
payload: {
|
|
||||||
endpoint: result.endpoint,
|
|
||||||
filePath: result.filePath,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (err) {
|
|
||||||
return { error: (err as Error).message, status: 500 };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== DELETE SETTINGS ==========
|
|
||||||
// DELETE /api/cli/settings/:id
|
|
||||||
const deleteMatch = pathname.match(/^\/api\/cli\/settings\/([^/]+)$/);
|
|
||||||
if (deleteMatch && req.method === 'DELETE') {
|
|
||||||
const endpointId = sanitizeEndpointId(deleteMatch[1]);
|
|
||||||
try {
|
|
||||||
const result = deleteEndpointSettings(endpointId);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
broadcastToClients({
|
|
||||||
type: 'CLI_SETTINGS_DELETED',
|
|
||||||
payload: {
|
|
||||||
endpointId,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
||||||
res.end(JSON.stringify(result));
|
|
||||||
} else {
|
|
||||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
||||||
res.end(JSON.stringify(result));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
||||||
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== GET SETTINGS FILE PATH ==========
|
|
||||||
// GET /api/cli/settings/:id/path
|
|
||||||
const pathMatch = pathname.match(/^\/api\/cli\/settings\/([^/]+)\/path$/);
|
|
||||||
if (pathMatch && req.method === 'GET') {
|
|
||||||
const endpointId = sanitizeEndpointId(pathMatch[1]);
|
|
||||||
try {
|
|
||||||
const endpoint = loadEndpointSettings(endpointId);
|
|
||||||
|
|
||||||
if (!endpoint) {
|
|
||||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
||||||
res.end(JSON.stringify({ error: 'Endpoint not found' }));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filePath = getSettingsFilePath(endpointId);
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
||||||
res.end(JSON.stringify({
|
|
||||||
endpointId,
|
|
||||||
filePath,
|
|
||||||
enabled: endpoint.enabled
|
|
||||||
}));
|
|
||||||
} catch (err) {
|
|
||||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
||||||
res.end(JSON.stringify({ error: (err as Error).message }));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== SYNC BUILTIN TOOLS AVAILABILITY ==========
|
// ========== SYNC BUILTIN TOOLS AVAILABILITY ==========
|
||||||
// POST /api/cli/settings/sync-tools
|
// POST /api/cli/settings/sync-tools
|
||||||
@@ -505,5 +362,152 @@ export async function handleCliSettingsRoutes(ctx: RouteContext): Promise<boolea
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== PARAMETERIZED :id ROUTES ==========
|
||||||
|
|
||||||
|
// ========== GET SINGLE SETTINGS ==========
|
||||||
|
// GET /api/cli/settings/:id
|
||||||
|
const getMatch = pathname.match(/^\/api\/cli\/settings\/([^/]+)$/);
|
||||||
|
if (getMatch && req.method === 'GET') {
|
||||||
|
const endpointId = sanitizeEndpointId(getMatch[1]);
|
||||||
|
try {
|
||||||
|
const endpoint = loadEndpointSettings(endpointId);
|
||||||
|
|
||||||
|
if (!endpoint) {
|
||||||
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: 'Endpoint not found' }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
endpoint,
|
||||||
|
filePath: getSettingsFilePath(endpointId)
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: (err as Error).message }));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== UPDATE SETTINGS ==========
|
||||||
|
// PUT /api/cli/settings/:id
|
||||||
|
const putMatch = pathname.match(/^\/api\/cli\/settings\/([^/]+)$/);
|
||||||
|
if (putMatch && req.method === 'PUT') {
|
||||||
|
const endpointId = sanitizeEndpointId(putMatch[1]);
|
||||||
|
handlePostRequest(req, res, async (body: unknown) => {
|
||||||
|
try {
|
||||||
|
const request = body as Partial<SaveEndpointRequest>;
|
||||||
|
|
||||||
|
// Check if just toggling enabled status
|
||||||
|
if (Object.keys(request).length === 1 && 'enabled' in request) {
|
||||||
|
const result = toggleEndpointEnabled(endpointId, request.enabled as boolean);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
broadcastToClients({
|
||||||
|
type: 'CLI_SETTINGS_TOGGLED',
|
||||||
|
payload: {
|
||||||
|
endpointId,
|
||||||
|
enabled: request.enabled,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full update
|
||||||
|
const existing = loadEndpointSettings(endpointId);
|
||||||
|
if (!existing) {
|
||||||
|
return { error: 'Endpoint not found', status: 404 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateRequest: SaveEndpointRequest = {
|
||||||
|
id: endpointId,
|
||||||
|
name: request.name || existing.name,
|
||||||
|
description: request.description ?? existing.description,
|
||||||
|
settings: request.settings || existing.settings,
|
||||||
|
enabled: request.enabled ?? existing.enabled
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = saveEndpointSettings(updateRequest);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
broadcastToClients({
|
||||||
|
type: 'CLI_SETTINGS_UPDATED',
|
||||||
|
payload: {
|
||||||
|
endpoint: result.endpoint,
|
||||||
|
filePath: result.filePath,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
return { error: (err as Error).message, status: 500 };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== DELETE SETTINGS ==========
|
||||||
|
// DELETE /api/cli/settings/:id
|
||||||
|
const deleteMatch = pathname.match(/^\/api\/cli\/settings\/([^/]+)$/);
|
||||||
|
if (deleteMatch && req.method === 'DELETE') {
|
||||||
|
const endpointId = sanitizeEndpointId(deleteMatch[1]);
|
||||||
|
try {
|
||||||
|
const result = deleteEndpointSettings(endpointId);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
broadcastToClients({
|
||||||
|
type: 'CLI_SETTINGS_DELETED',
|
||||||
|
payload: {
|
||||||
|
endpointId,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify(result));
|
||||||
|
} else {
|
||||||
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify(result));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: (err as Error).message }));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== GET SETTINGS FILE PATH ==========
|
||||||
|
// GET /api/cli/settings/:id/path
|
||||||
|
const pathMatch = pathname.match(/^\/api\/cli\/settings\/([^/]+)\/path$/);
|
||||||
|
if (pathMatch && req.method === 'GET') {
|
||||||
|
const endpointId = sanitizeEndpointId(pathMatch[1]);
|
||||||
|
try {
|
||||||
|
const endpoint = loadEndpointSettings(endpointId);
|
||||||
|
|
||||||
|
if (!endpoint) {
|
||||||
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: 'Endpoint not found' }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = getSettingsFilePath(endpointId);
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({
|
||||||
|
endpointId,
|
||||||
|
filePath,
|
||||||
|
enabled: endpoint.enabled
|
||||||
|
}));
|
||||||
|
} catch (err) {
|
||||||
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: (err as Error).message }));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user