feat: 更新 stopCommand 函数以确保进程正常退出;优化 MCP 路由序列化以支持嵌套对象;添加 CSS 类以实现文本行数限制

This commit is contained in:
catlog22
2025-12-21 12:47:25 +08:00
parent 89b3475508
commit 15d5890861
4 changed files with 95 additions and 18 deletions

View File

@@ -72,7 +72,7 @@ export async function stopCommand(options: StopOptions): Promise<void> {
await new Promise(resolve => setTimeout(resolve, 500));
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
return;
process.exit(0);
}
// No CCW server responding, check if port is in use
@@ -80,7 +80,7 @@ export async function stopCommand(options: StopOptions): Promise<void> {
if (!pid) {
console.log(chalk.yellow(` No server running on port ${port}\n`));
return;
process.exit(0);
}
// Port is in use by another process
@@ -92,16 +92,20 @@ export async function stopCommand(options: StopOptions): Promise<void> {
if (killed) {
console.log(chalk.green.bold('\n Process killed successfully!\n'));
process.exit(0);
} else {
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));
process.exit(1);
}
} else {
console.log(chalk.gray(`\n This is not a CCW server. Use --force to kill it:`));
console.log(chalk.white(` ccw stop --force\n`));
process.exit(0);
}
} catch (err) {
const error = err as Error;
console.error(chalk.red(`\n Error: ${error.message}\n`));
process.exit(1);
}
}

View File

@@ -153,10 +153,18 @@ function parseTomlValue(value: string): any {
/**
* Serialize object to TOML format for Codex config
*
* Handles mixed objects containing both simple values and sub-objects.
* For example: { command: "cmd", args: [...], env: { KEY: "value" } }
* becomes:
* [section]
* command = "cmd"
* args = [...]
* [section.env]
* KEY = "value"
*/
function serializeToml(obj: Record<string, any>, prefix: string = ''): string {
let result = '';
const sections: string[] = [];
for (const [key, value] of Object.entries(obj)) {
if (value === null || value === undefined) continue;
@@ -164,23 +172,58 @@ function serializeToml(obj: Record<string, any>, prefix: string = ''): string {
if (typeof value === 'object' && !Array.isArray(value)) {
// Handle nested sections (like mcp_servers.server_name)
const sectionKey = prefix ? `${prefix}.${key}` : key;
sections.push(sectionKey);
// Check if this is a section with sub-sections or direct values
const hasSubSections = Object.values(value).some(v => typeof v === 'object' && !Array.isArray(v));
// Separate simple values from sub-objects
const simpleEntries: [string, any][] = [];
const objectEntries: [string, any][] = [];
if (hasSubSections) {
// This section has sub-sections, recurse without header
result += serializeToml(value, sectionKey);
} else {
// This section has direct values, add header and values
for (const [subKey, subValue] of Object.entries(value)) {
if (subValue === null || subValue === undefined) continue;
if (typeof subValue === 'object' && !Array.isArray(subValue)) {
objectEntries.push([subKey, subValue]);
} else {
simpleEntries.push([subKey, subValue]);
}
}
// Write section header if there are simple values
if (simpleEntries.length > 0) {
result += `\n[${sectionKey}]\n`;
for (const [subKey, subValue] of Object.entries(value)) {
if (subValue !== null && subValue !== undefined) {
result += `${subKey} = ${serializeTomlValue(subValue)}\n`;
for (const [subKey, subValue] of simpleEntries) {
result += `${subKey} = ${serializeTomlValue(subValue)}\n`;
}
}
// Recursively handle sub-objects
if (objectEntries.length > 0) {
for (const [subKey, subValue] of objectEntries) {
const subSectionKey = `${sectionKey}.${subKey}`;
// Check if sub-object has nested objects
const hasNestedObjects = Object.values(subValue).some(
v => typeof v === 'object' && v !== null && !Array.isArray(v)
);
if (hasNestedObjects) {
// Recursively process nested objects
result += serializeToml({ [subKey]: subValue }, sectionKey);
} else {
// Write sub-section with simple values
result += `\n[${subSectionKey}]\n`;
for (const [nestedKey, nestedValue] of Object.entries(subValue)) {
if (nestedValue !== null && nestedValue !== undefined) {
result += `${nestedKey} = ${serializeTomlValue(nestedValue)}\n`;
}
}
}
}
}
// If no simple values but has object entries, still need to process
if (simpleEntries.length === 0 && objectEntries.length === 0) {
// Empty section - write header only
result += `\n[${sectionKey}]\n`;
}
} else if (!prefix) {
// Top-level simple values
result += `${key} = ${serializeTomlValue(value)}\n`;

View File

@@ -514,6 +514,13 @@
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Highlight pulse effect */
.highlight-pulse {
animation: highlightPulse 0.5s ease-out 2;

View File

@@ -371,9 +371,9 @@ function renderHooksByEvent(hooks, scope) {
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">matcher</span>
<span class="text-muted-foreground">${escapeHtml(matcher)}</span>
</div>
<div class="flex items-center gap-2">
<div class="flex items-start gap-2">
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">command</span>
<span class="font-mono text-xs text-foreground">${escapeHtml(command)}</span>
<span class="font-mono text-xs text-foreground break-all line-clamp-3 overflow-hidden" title="${escapeHtml(command)}">${escapeHtml(command)}</span>
</div>
${args.length > 0 ? `
<div class="flex items-start gap-2">
@@ -570,13 +570,36 @@ function attachHookEventListeners() {
const hook = hookList[index];
if (hook) {
// Support both Claude Code format (hooks[0].command) and legacy format (command + args)
let command = '';
let args = [];
if (hook.hooks && hook.hooks[0]) {
// Claude Code format: { hooks: [{ type: "command", command: "bash -c '...'" }] }
const fullCommand = hook.hooks[0].command || '';
// Try to split command and args for bash -c commands
const bashMatch = fullCommand.match(/^(bash|sh|cmd)\s+(-c)\s+(.+)$/s);
if (bashMatch) {
command = bashMatch[1];
args = [bashMatch[2], bashMatch[3]];
} else {
// For other commands, put the whole thing as command
command = fullCommand;
args = [];
}
} else {
// Legacy format: { command: "bash", args: ["-c", "..."] }
command = hook.command || '';
args = hook.args || [];
}
openHookCreateModal({
scope: scope,
event: event,
index: index,
matcher: hook.matcher || '',
command: hook.command,
args: hook.args || []
command: command,
args: args
});
}
});