feat: update usage recommendations across multiple workflow commands to require user confirmation and improve clarity

This commit is contained in:
catlog22
2026-02-01 22:04:26 +08:00
parent 5fb910610a
commit 7dcc0a1c05
70 changed files with 4420 additions and 1108 deletions

View File

@@ -88,6 +88,7 @@ export function run(argv: string[]): void {
.option('--host <host>', 'Server host to bind', '127.0.0.1')
.option('--no-browser', 'Start server without opening browser')
.option('--frontend <type>', 'Frontend type: js, react, both', 'both')
.option('--new', 'Launch React frontend (shorthand for --frontend react)')
.action(viewCommand);
// Serve command (alias for view)
@@ -99,6 +100,7 @@ export function run(argv: string[]): void {
.option('--host <host>', 'Server host to bind', '127.0.0.1')
.option('--no-browser', 'Start server without opening browser')
.option('--frontend <type>', 'Frontend type: js, react, both', 'both')
.option('--new', 'Launch React frontend (shorthand for --frontend react)')
.action(serveCommand);
// Stop command

View File

@@ -11,6 +11,7 @@ interface ServeOptions {
host?: string;
browser?: boolean;
frontend?: 'js' | 'react' | 'both';
new?: boolean;
}
/**
@@ -20,7 +21,8 @@ interface ServeOptions {
export async function serveCommand(options: ServeOptions): Promise<void> {
const port = Number(options.port) || 3456;
const host = options.host || '127.0.0.1';
const frontend = options.frontend || 'js';
// --new flag is shorthand for --frontend react
const frontend = options.new ? 'react' : (options.frontend || 'js');
// Validate project path
let initialPath = process.cwd();

View File

@@ -10,6 +10,7 @@ interface ViewOptions {
host?: string;
browser?: boolean;
frontend?: 'js' | 'react' | 'both';
new?: boolean;
}
interface SwitchWorkspaceResult {
@@ -76,7 +77,8 @@ export async function viewCommand(options: ViewOptions): Promise<void> {
const port = Number(options.port) || 3456;
const host = options.host || '127.0.0.1';
const browserHost = host === '0.0.0.0' || host === '::' ? 'localhost' : host;
const frontend = options.frontend || 'both';
// --new flag is shorthand for --frontend react
const frontend = options.new ? 'react' : (options.frontend || 'both');
// Resolve workspace path
let workspacePath = process.cwd();

View File

@@ -5,6 +5,7 @@
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { homedir } from 'os';
import { spawn } from 'child_process';
import type { RouteContext } from './types.js';
@@ -606,18 +607,18 @@ async function executeCliCommand(
});
if (child.stdout) {
child.stdout.on('data', (data) => {
child.stdout.on('data', (data: Buffer) => {
output += data.toString();
});
}
if (child.stderr) {
child.stderr.on('data', (data) => {
child.stderr.on('data', (data: Buffer) => {
errorOutput += data.toString();
});
}
child.on('close', (code) => {
child.on('close', (code: number | null) => {
if (code === 0) {
resolve({
success: true,
@@ -632,11 +633,11 @@ async function executeCliCommand(
}
});
child.on('error', (err) => {
child.on('error', (err: Error) => {
resolve({
success: false,
output: '',
error: (err as Error).message
error: err.message
});
});
});

View File

@@ -275,6 +275,75 @@ const HOOK_TEMPLATES = {
description: 'Confirm before changing file permissions (chmod, chown, icacls)',
category: 'danger',
timeout: 5000
},
// ========== Session Start Hooks ==========
'session-start-notify': {
event: 'SessionStart',
matcher: '',
command: 'node',
args: ['-e', 'const cp=require("child_process");const payload=JSON.stringify({type:"SESSION_CREATED",timestamp:Date.now(),project:process.env.CLAUDE_PROJECT_DIR||process.cwd()});cp.spawnSync("curl",["-s","-X","POST","-H","Content-Type: application/json","-d",payload,"http://localhost:3456/api/hook"],{stdio:"inherit",shell:true})'],
description: 'Notify dashboard when session starts or resumes',
category: 'session',
timeout: 5000
},
'session-list-sync': {
event: 'UserPromptSubmit',
matcher: '',
command: 'node',
args: ['-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const prompt=(p.user_prompt||"").toLowerCase();if(prompt.includes("session")&&(prompt.includes("list")||prompt==="sessions")){const cp=require("child_process");cp.spawnSync("ccw",["session","list","--metadata"],{stdio:"inherit"})}'],
description: 'Auto-sync session list when user views sessions',
category: 'session',
timeout: 10000
},
'session-state-watch': {
event: 'PostToolUse',
matcher: 'Write|Edit',
command: 'node',
args: ['-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const file=(p.tool_input&&p.tool_input.file_path)||"";if(/workflow-session\\.json$|session-metadata\\.json$/.test(file)){const fs=require("fs");try{const content=fs.readFileSync(file,"utf8");const data=JSON.parse(content);const cp=require("child_process");const payload=JSON.stringify({type:"SESSION_STATE_CHANGED",file:file,sessionId:data.session_id||"",status:data.status||"unknown",project:process.env.CLAUDE_PROJECT_DIR||process.cwd(),timestamp:Date.now()});cp.spawnSync("curl",["-s","-X","POST","-H","Content-Type: application/json","-d",payload,"http://localhost:3456/api/hook"],{stdio:"inherit",shell:true})}catch(e){}}'],
description: 'Watch for session metadata file changes (workflow-session.json, session-metadata.json)',
category: 'session',
timeout: 5000
},
// ========== CCW Status Hooks ==========
'ccw-status-monitor': {
event: 'UserPromptSubmit',
matcher: '',
command: 'node',
args: ['-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const prompt=(p.user_prompt||"").toLowerCase();if(prompt==="status"||prompt==="ccw status"||prompt.startsWith("/status")){const cp=require("child_process");cp.spawnSync("curl",["-s","http://localhost:3456/api/status/all"],{stdio:"inherit"})}'],
description: 'Monitor CCW service status on status-related commands',
category: 'monitoring',
timeout: 10000
},
'ccw-health-check': {
event: 'UserPromptSubmit',
matcher: '',
command: 'node',
args: ['-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const prompt=(p.user_prompt||"").toLowerCase();if(prompt.includes("health")||prompt.includes("check")){const cp=require("child_process");const urls=["http://localhost:3456/api/status/all","http://localhost:3456/api/cli/active"];urls.forEach(url=>{cp.spawnSync("curl",["-s",url],{stdio:"inherit"})})}'],
description: 'Health check for CCW services (status API, active CLI executions)',
category: 'monitoring',
timeout: 15000
},
'ccw-cli-active-sync': {
event: 'UserPromptSubmit',
matcher: '',
command: 'node',
args: ['-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const prompt=(p.user_prompt||"").toLowerCase();if(prompt.includes("cli")&&(prompt.includes("active")||prompt.includes("running")||prompt.includes("status"))){const cp=require("child_process");cp.spawnSync("curl",["-s","http://localhost:3456/api/cli/active"],{stdio:"inherit"})}'],
description: 'Sync active CLI executions when requested',
category: 'monitoring',
timeout: 10000
},
// ========== WebSocket Connection Hook ==========
'ccw-websocket-notify': {
event: 'UserPromptSubmit',
matcher: '',
command: 'node',
args: ['-e', 'const p=JSON.parse(process.env.HOOK_INPUT||"{}");const WebSocket=require("ws");if(process.platform==="win32"){console.log("WebSocket notification: Skip on Windows");return}const ws=new WebSocket("ws://localhost:3456/ws");ws.on("open",()=>{ws.send(JSON.stringify({type:"CLIENT_HELLO",timestamp:Date.now(),client:"hook-manager"}));ws.close()});ws.on("error",(e)=>{console.log("WebSocket connection failed (dashboard may not be running)")})'],
description: 'Test WebSocket connection to CCW dashboard (Unix only)',
category: 'monitoring',
timeout: 5000
}
};

View File

@@ -1126,6 +1126,7 @@ const i18n = {
'hook.createTitle': 'Create Hook',
'hook.event': 'Hook Event',
'hook.selectEvent': 'Select an event...',
'hook.sessionStart': 'SessionStart - When session starts or resumes',
'hook.preToolUse': 'PreToolUse - Before a tool is executed',
'hook.postToolUse': 'PostToolUse - After a tool completes',
'hook.notification': 'Notification - On notifications',
@@ -1154,6 +1155,16 @@ const i18n = {
// Hook Quick Install Templates
'hook.tpl.sessionContext': 'Session Context',
'hook.tpl.sessionContextDesc': 'Load cluster overview once at session start',
'hook.tpl.sessionStart': 'Session Start Notification',
'hook.tpl.sessionStartDesc': 'Notify dashboard when a new workflow session is created',
'hook.tpl.sessionState': 'Session State Watcher',
'hook.tpl.sessionStateDesc': 'Watch for session metadata file changes',
'hook.tpl.ccwStatus': 'CCW Status Monitor',
'hook.tpl.ccwStatusDesc': 'Monitor CCW service status on status commands',
'hook.tpl.cliActive': 'CLI Active Sync',
'hook.tpl.cliActiveDesc': 'Sync active CLI executions when requested',
'hook.tpl.healthCheck': 'CCW Health Check',
'hook.tpl.healthCheckDesc': 'Health check for CCW services (status, CLI)',
'hook.tpl.codexlensSync': 'CodexLens Auto-Sync',
'hook.tpl.codexlensSyncDesc': 'Auto-update code index when files are written or edited',
'hook.tpl.ccwDashboardNotify': 'CCW Dashboard Notify',
@@ -1174,6 +1185,9 @@ const i18n = {
'hook.category.memory': 'memory',
'hook.category.skill': 'skill',
'hook.category.context': 'context',
'hook.category.session': 'session',
'hook.category.monitoring': 'monitoring',
'hook.category.danger': 'danger',
// Hook Wizard Templates
'hook.wizard.memoryUpdate': 'Memory Update Hook',
@@ -3808,6 +3822,7 @@ const i18n = {
'hook.createTitle': '创建钩子',
'hook.event': '钩子事件',
'hook.selectEvent': '选择事件...',
'hook.sessionStart': 'SessionStart - 会话启动或恢复时',
'hook.preToolUse': 'PreToolUse - 工具执行前',
'hook.postToolUse': 'PostToolUse - 工具完成后',
'hook.notification': 'Notification - 通知时',
@@ -3836,6 +3851,16 @@ const i18n = {
// Hook Quick Install Templates
'hook.tpl.sessionContext': 'Session 上下文',
'hook.tpl.sessionContextDesc': '会话启动时加载集群概览(仅触发一次)',
'hook.tpl.sessionStart': 'Session 启动通知',
'hook.tpl.sessionStartDesc': '创建新的工作流会话时通知控制面板',
'hook.tpl.sessionState': 'Session 状态监听',
'hook.tpl.sessionStateDesc': '监听 session 元数据文件变更',
'hook.tpl.ccwStatus': 'CCW 状态监控',
'hook.tpl.ccwStatusDesc': '在状态相关命令下监控 CCW 服务状态',
'hook.tpl.cliActive': 'CLI 活跃同步',
'hook.tpl.cliActiveDesc': '请求时同步活跃的 CLI 执行',
'hook.tpl.healthCheck': 'CCW 健康检查',
'hook.tpl.healthCheckDesc': 'CCW 服务健康检查状态、CLI',
'hook.tpl.codexlensSync': 'CodexLens 自动同步',
'hook.tpl.codexlensSyncDesc': '文件写入或编辑时自动更新代码索引',
'hook.tpl.ccwDashboardNotify': 'CCW 控制面板通知',
@@ -3856,6 +3881,9 @@ const i18n = {
'hook.category.memory': '记忆',
'hook.category.skill': '技能',
'hook.category.context': '上下文',
'hook.category.session': '会话',
'hook.category.monitoring': '监控',
'hook.category.danger': '危险防护',
// Hook Wizard Templates
'hook.wizard.memoryUpdate': '记忆更新钩子',

View File

@@ -100,12 +100,9 @@ async function renderHookManager() {
</div>
<div class="hook-templates-grid grid grid-cols-1 md:grid-cols-2 gap-4">
${renderQuickInstallCard('session-context', t('hook.tpl.sessionContext'), t('hook.tpl.sessionContextDesc'), 'UserPromptSubmit', '')}
${renderQuickInstallCard('codexlens-update', t('hook.tpl.codexlensSync'), t('hook.tpl.codexlensSyncDesc'), 'PostToolUse', 'Write|Edit')}
${renderQuickInstallCard('ccw-notify', t('hook.tpl.ccwDashboardNotify'), t('hook.tpl.ccwDashboardNotifyDesc'), 'PostToolUse', 'Write')}
${renderQuickInstallCard('log-tool', t('hook.tpl.toolLogger'), t('hook.tpl.toolLoggerDesc'), 'PostToolUse', 'All')}
${renderQuickInstallCard('lint-check', t('hook.tpl.autoLint'), t('hook.tpl.autoLintDesc'), 'PostToolUse', 'Write')}
${renderQuickInstallCard('git-add', t('hook.tpl.autoGitStage'), t('hook.tpl.autoGitStageDesc'), 'PostToolUse', 'Write')}
<!-- Session Hooks -->
${renderQuickInstallCard('session-start-notify', t('hook.tpl.sessionStart'), t('hook.tpl.sessionStartDesc'), 'SessionStart', '')}
${renderQuickInstallCard('session-state-watch', t('hook.tpl.sessionState'), t('hook.tpl.sessionStateDesc'), 'PostToolUse', 'Write|Edit')}
</div>
</div>