mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: 更新 stopCommand 函数以确保进程正常退出;优化 MCP 路由序列化以支持嵌套对象;添加 CSS 类以实现文本行数限制
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user