mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +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));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
||||||
return;
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// No CCW server responding, check if port is in use
|
// No CCW server responding, check if port is in use
|
||||||
@@ -80,7 +80,7 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
|
|
||||||
if (!pid) {
|
if (!pid) {
|
||||||
console.log(chalk.yellow(` No server running on port ${port}\n`));
|
console.log(chalk.yellow(` No server running on port ${port}\n`));
|
||||||
return;
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port is in use by another process
|
// Port is in use by another process
|
||||||
@@ -92,16 +92,20 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
|
|
||||||
if (killed) {
|
if (killed) {
|
||||||
console.log(chalk.green.bold('\n Process killed successfully!\n'));
|
console.log(chalk.green.bold('\n Process killed successfully!\n'));
|
||||||
|
process.exit(0);
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));
|
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.gray(`\n This is not a CCW server. Use --force to kill it:`));
|
console.log(chalk.gray(`\n This is not a CCW server. Use --force to kill it:`));
|
||||||
console.log(chalk.white(` ccw stop --force\n`));
|
console.log(chalk.white(` ccw stop --force\n`));
|
||||||
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = err as Error;
|
const error = err as Error;
|
||||||
console.error(chalk.red(`\n Error: ${error.message}\n`));
|
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
|
* 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 {
|
function serializeToml(obj: Record<string, any>, prefix: string = ''): string {
|
||||||
let result = '';
|
let result = '';
|
||||||
const sections: string[] = [];
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
if (value === null || value === undefined) continue;
|
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)) {
|
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||||
// Handle nested sections (like mcp_servers.server_name)
|
// Handle nested sections (like mcp_servers.server_name)
|
||||||
const sectionKey = prefix ? `${prefix}.${key}` : key;
|
const sectionKey = prefix ? `${prefix}.${key}` : key;
|
||||||
sections.push(sectionKey);
|
|
||||||
|
|
||||||
// Check if this is a section with sub-sections or direct values
|
// Separate simple values from sub-objects
|
||||||
const hasSubSections = Object.values(value).some(v => typeof v === 'object' && !Array.isArray(v));
|
const simpleEntries: [string, any][] = [];
|
||||||
|
const objectEntries: [string, any][] = [];
|
||||||
|
|
||||||
if (hasSubSections) {
|
for (const [subKey, subValue] of Object.entries(value)) {
|
||||||
// This section has sub-sections, recurse without header
|
if (subValue === null || subValue === undefined) continue;
|
||||||
result += serializeToml(value, sectionKey);
|
if (typeof subValue === 'object' && !Array.isArray(subValue)) {
|
||||||
} else {
|
objectEntries.push([subKey, subValue]);
|
||||||
// This section has direct values, add header and values
|
} else {
|
||||||
|
simpleEntries.push([subKey, subValue]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write section header if there are simple values
|
||||||
|
if (simpleEntries.length > 0) {
|
||||||
result += `\n[${sectionKey}]\n`;
|
result += `\n[${sectionKey}]\n`;
|
||||||
for (const [subKey, subValue] of Object.entries(value)) {
|
for (const [subKey, subValue] of simpleEntries) {
|
||||||
if (subValue !== null && subValue !== undefined) {
|
result += `${subKey} = ${serializeTomlValue(subValue)}\n`;
|
||||||
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) {
|
} else if (!prefix) {
|
||||||
// Top-level simple values
|
// Top-level simple values
|
||||||
result += `${key} = ${serializeTomlValue(value)}\n`;
|
result += `${key} = ${serializeTomlValue(value)}\n`;
|
||||||
|
|||||||
@@ -514,6 +514,13 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.line-clamp-3 {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
/* Highlight pulse effect */
|
/* Highlight pulse effect */
|
||||||
.highlight-pulse {
|
.highlight-pulse {
|
||||||
animation: highlightPulse 0.5s ease-out 2;
|
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="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>
|
<span class="text-muted-foreground">${escapeHtml(matcher)}</span>
|
||||||
</div>
|
</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 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>
|
</div>
|
||||||
${args.length > 0 ? `
|
${args.length > 0 ? `
|
||||||
<div class="flex items-start gap-2">
|
<div class="flex items-start gap-2">
|
||||||
@@ -570,13 +570,36 @@ function attachHookEventListeners() {
|
|||||||
const hook = hookList[index];
|
const hook = hookList[index];
|
||||||
|
|
||||||
if (hook) {
|
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({
|
openHookCreateModal({
|
||||||
scope: scope,
|
scope: scope,
|
||||||
event: event,
|
event: event,
|
||||||
index: index,
|
index: index,
|
||||||
matcher: hook.matcher || '',
|
matcher: hook.matcher || '',
|
||||||
command: hook.command,
|
command: command,
|
||||||
args: hook.args || []
|
args: args
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user