Skip to content

Commit 15d5890

Browse files
author
catlog22
committed
feat: 更新 stopCommand 函数以确保进程正常退出;优化 MCP 路由序列化以支持嵌套对象;添加 CSS 类以实现文本行数限制
1 parent 89b3475 commit 15d5890

File tree

4 files changed

+95
-18
lines changed

4 files changed

+95
-18
lines changed

ccw/src/commands/stop.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,15 @@ export async function stopCommand(options: StopOptions): Promise<void> {
7272
await new Promise(resolve => setTimeout(resolve, 500));
7373

7474
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
75-
return;
75+
process.exit(0);
7676
}
7777

7878
// No CCW server responding, check if port is in use
7979
const pid = await findProcessOnPort(port);
8080

8181
if (!pid) {
8282
console.log(chalk.yellow(` No server running on port ${port}\n`));
83-
return;
83+
process.exit(0);
8484
}
8585

8686
// Port is in use by another process
@@ -92,16 +92,20 @@ export async function stopCommand(options: StopOptions): Promise<void> {
9292

9393
if (killed) {
9494
console.log(chalk.green.bold('\n Process killed successfully!\n'));
95+
process.exit(0);
9596
} else {
9697
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));
98+
process.exit(1);
9799
}
98100
} else {
99101
console.log(chalk.gray(`\n This is not a CCW server. Use --force to kill it:`));
100102
console.log(chalk.white(` ccw stop --force\n`));
103+
process.exit(0);
101104
}
102105

103106
} catch (err) {
104107
const error = err as Error;
105108
console.error(chalk.red(`\n Error: ${error.message}\n`));
109+
process.exit(1);
106110
}
107111
}

ccw/src/core/routes/mcp-routes.ts

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -153,34 +153,77 @@ function parseTomlValue(value: string): any {
153153

154154
/**
155155
* Serialize object to TOML format for Codex config
156+
*
157+
* Handles mixed objects containing both simple values and sub-objects.
158+
* For example: { command: "cmd", args: [...], env: { KEY: "value" } }
159+
* becomes:
160+
* [section]
161+
* command = "cmd"
162+
* args = [...]
163+
* [section.env]
164+
* KEY = "value"
156165
*/
157166
function serializeToml(obj: Record<string, any>, prefix: string = ''): string {
158167
let result = '';
159-
const sections: string[] = [];
160168

161169
for (const [key, value] of Object.entries(obj)) {
162170
if (value === null || value === undefined) continue;
163171

164172
if (typeof value === 'object' && !Array.isArray(value)) {
165173
// Handle nested sections (like mcp_servers.server_name)
166174
const sectionKey = prefix ? `${prefix}.${key}` : key;
167-
sections.push(sectionKey);
168175

169-
// Check if this is a section with sub-sections or direct values
170-
const hasSubSections = Object.values(value).some(v => typeof v === 'object' && !Array.isArray(v));
176+
// Separate simple values from sub-objects
177+
const simpleEntries: [string, any][] = [];
178+
const objectEntries: [string, any][] = [];
171179

172-
if (hasSubSections) {
173-
// This section has sub-sections, recurse without header
174-
result += serializeToml(value, sectionKey);
175-
} else {
176-
// This section has direct values, add header and values
180+
for (const [subKey, subValue] of Object.entries(value)) {
181+
if (subValue === null || subValue === undefined) continue;
182+
if (typeof subValue === 'object' && !Array.isArray(subValue)) {
183+
objectEntries.push([subKey, subValue]);
184+
} else {
185+
simpleEntries.push([subKey, subValue]);
186+
}
187+
}
188+
189+
// Write section header if there are simple values
190+
if (simpleEntries.length > 0) {
177191
result += `\n[${sectionKey}]\n`;
178-
for (const [subKey, subValue] of Object.entries(value)) {
179-
if (subValue !== null && subValue !== undefined) {
180-
result += `${subKey} = ${serializeTomlValue(subValue)}\n`;
192+
for (const [subKey, subValue] of simpleEntries) {
193+
result += `${subKey} = ${serializeTomlValue(subValue)}\n`;
194+
}
195+
}
196+
197+
// Recursively handle sub-objects
198+
if (objectEntries.length > 0) {
199+
for (const [subKey, subValue] of objectEntries) {
200+
const subSectionKey = `${sectionKey}.${subKey}`;
201+
202+
// Check if sub-object has nested objects
203+
const hasNestedObjects = Object.values(subValue).some(
204+
v => typeof v === 'object' && v !== null && !Array.isArray(v)
205+
);
206+
207+
if (hasNestedObjects) {
208+
// Recursively process nested objects
209+
result += serializeToml({ [subKey]: subValue }, sectionKey);
210+
} else {
211+
// Write sub-section with simple values
212+
result += `\n[${subSectionKey}]\n`;
213+
for (const [nestedKey, nestedValue] of Object.entries(subValue)) {
214+
if (nestedValue !== null && nestedValue !== undefined) {
215+
result += `${nestedKey} = ${serializeTomlValue(nestedValue)}\n`;
216+
}
217+
}
181218
}
182219
}
183220
}
221+
222+
// If no simple values but has object entries, still need to process
223+
if (simpleEntries.length === 0 && objectEntries.length === 0) {
224+
// Empty section - write header only
225+
result += `\n[${sectionKey}]\n`;
226+
}
184227
} else if (!prefix) {
185228
// Top-level simple values
186229
result += `${key} = ${serializeTomlValue(value)}\n`;

ccw/src/templates/dashboard-css/07-managers.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,13 @@
514514
overflow: hidden;
515515
}
516516

517+
.line-clamp-3 {
518+
display: -webkit-box;
519+
-webkit-line-clamp: 3;
520+
-webkit-box-orient: vertical;
521+
overflow: hidden;
522+
}
523+
517524
/* Highlight pulse effect */
518525
.highlight-pulse {
519526
animation: highlightPulse 0.5s ease-out 2;

ccw/src/templates/dashboard-js/views/hook-manager.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -371,9 +371,9 @@ function renderHooksByEvent(hooks, scope) {
371371
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">matcher</span>
372372
<span class="text-muted-foreground">${escapeHtml(matcher)}</span>
373373
</div>
374-
<div class="flex items-center gap-2">
374+
<div class="flex items-start gap-2">
375375
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">command</span>
376-
<span class="font-mono text-xs text-foreground">${escapeHtml(command)}</span>
376+
<span class="font-mono text-xs text-foreground break-all line-clamp-3 overflow-hidden" title="${escapeHtml(command)}">${escapeHtml(command)}</span>
377377
</div>
378378
${args.length > 0 ? `
379379
<div class="flex items-start gap-2">
@@ -570,13 +570,36 @@ function attachHookEventListeners() {
570570
const hook = hookList[index];
571571

572572
if (hook) {
573+
// Support both Claude Code format (hooks[0].command) and legacy format (command + args)
574+
let command = '';
575+
let args = [];
576+
577+
if (hook.hooks && hook.hooks[0]) {
578+
// Claude Code format: { hooks: [{ type: "command", command: "bash -c '...'" }] }
579+
const fullCommand = hook.hooks[0].command || '';
580+
// Try to split command and args for bash -c commands
581+
const bashMatch = fullCommand.match(/^(bash|sh|cmd)\s+(-c)\s+(.+)$/s);
582+
if (bashMatch) {
583+
command = bashMatch[1];
584+
args = [bashMatch[2], bashMatch[3]];
585+
} else {
586+
// For other commands, put the whole thing as command
587+
command = fullCommand;
588+
args = [];
589+
}
590+
} else {
591+
// Legacy format: { command: "bash", args: ["-c", "..."] }
592+
command = hook.command || '';
593+
args = hook.args || [];
594+
}
595+
573596
openHookCreateModal({
574597
scope: scope,
575598
event: event,
576599
index: index,
577600
matcher: hook.matcher || '',
578-
command: hook.command,
579-
args: hook.args || []
601+
command: command,
602+
args: args
580603
});
581604
}
582605
});

0 commit comments

Comments
 (0)