mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat(cli-tools): add effort level configuration for Claude CLI
- Introduced effort level options (low, medium, high) in the CLI tool settings. - Updated the SettingsPage and CliToolCard components to handle effort level updates. - Enhanced CLI command options to accept effort level via --effort parameter. - Modified backend routes to support effort level updates in tool configurations. - Created a new CliViewerToolbar component for improved CLI viewer interactions. - Implemented logic to manage and display execution statuses and layouts in the CLI viewer.
This commit is contained in:
@@ -192,6 +192,8 @@ export function run(argv: string[]): void {
|
||||
.option('--inject-mode <mode>', 'Inject mode: none, full, progressive (default: codex=full, others=none)')
|
||||
// Template/Rules options
|
||||
.option('--rule <template>', 'Template name for auto-discovery (defines $PROTO and $TMPL env vars)')
|
||||
// Claude-specific options
|
||||
.option('--effort <level>', 'Effort level for claude session (low, medium, high)')
|
||||
// Codex review options
|
||||
.option('--uncommitted', 'Review uncommitted changes (codex review)')
|
||||
.option('--base <branch>', 'Review changes against base branch (codex review)')
|
||||
|
||||
@@ -140,6 +140,8 @@ interface CliExecOptions {
|
||||
title?: string; // Optional title for review summary
|
||||
// Template/Rules options
|
||||
rule?: string; // Template name for auto-discovery (defines $PROTO and $TMPL env vars)
|
||||
// Claude-specific options
|
||||
effort?: string; // Effort level for claude: low, medium, high
|
||||
// Output options
|
||||
raw?: boolean; // Raw output only (best for piping)
|
||||
final?: boolean; // Final agent result only (best for piping)
|
||||
@@ -612,6 +614,7 @@ async function execAction(positionalPrompt: string | undefined, options: CliExec
|
||||
commit,
|
||||
title,
|
||||
rule,
|
||||
effort,
|
||||
toFile,
|
||||
raw,
|
||||
final: finalOnly,
|
||||
@@ -1044,7 +1047,8 @@ async function execAction(positionalPrompt: string | undefined, options: CliExec
|
||||
uncommitted,
|
||||
base,
|
||||
commit,
|
||||
title
|
||||
title,
|
||||
effort
|
||||
// Rules are now concatenated directly into prompt (no env vars)
|
||||
}, onOutput); // Always pass onOutput for real-time dashboard streaming
|
||||
|
||||
@@ -1497,6 +1501,7 @@ export async function cliCommand(
|
||||
console.log(chalk.gray(' --mode <mode> Mode: analysis, write, auto, review (default: analysis)'));
|
||||
console.log(chalk.gray(' -d, --debug Enable debug logging for troubleshooting'));
|
||||
console.log(chalk.gray(' --model <model> Model override (supports PRIMARY_MODEL, SECONDARY_MODEL aliases)'));
|
||||
console.log(chalk.gray(' --effort <level> Effort level for claude (low, medium, high)'));
|
||||
console.log(chalk.gray(' --cd <path> Working directory'));
|
||||
console.log(chalk.gray(' --includeDirs <dirs> Additional directories'));
|
||||
// --timeout removed - controlled by external caller (bash timeout)
|
||||
|
||||
@@ -320,7 +320,7 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
if (req.method === 'PUT') {
|
||||
handlePostRequest(req, res, async (body: unknown) => {
|
||||
try {
|
||||
const updates = body as { enabled?: boolean; primaryModel?: string; secondaryModel?: string; availableModels?: string[]; tags?: string[]; envFile?: string | null; settingsFile?: string | null };
|
||||
const updates = body as { enabled?: boolean; primaryModel?: string; secondaryModel?: string; availableModels?: string[]; tags?: string[]; envFile?: string | null; settingsFile?: string | null; effort?: string | null };
|
||||
const updated = updateToolConfig(initialPath, tool, updates);
|
||||
|
||||
// Broadcast config updated event
|
||||
|
||||
@@ -186,12 +186,9 @@ async function buildFileTree(
|
||||
for (const entry of entries) {
|
||||
const isDirectory = entry.isDirectory();
|
||||
|
||||
// Check if should be ignored
|
||||
if (shouldIgnore(entry.name, gitignorePatterns, isDirectory)) {
|
||||
// Allow hidden files if includeHidden is true and it's .claude or .workflow
|
||||
if (!includeHidden || (!entry.name.startsWith('.claude') && !entry.name.startsWith('.workflow'))) {
|
||||
continue;
|
||||
}
|
||||
// Check if should be ignored (pass includeHidden as showAll to skip all filtering)
|
||||
if (shouldIgnore(entry.name, gitignorePatterns, isDirectory, includeHidden)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entryPath = join(normalizedPath, entry.name);
|
||||
@@ -326,17 +323,21 @@ function parseGitignore(gitignorePath: string): string[] {
|
||||
* @param {string} name - File or directory name
|
||||
* @param {string[]} patterns - Gitignore patterns
|
||||
* @param {boolean} isDirectory - Whether the entry is a directory
|
||||
* @param {boolean} showAll - When true, skip hardcoded excludes and hidden file filtering (only apply gitignore)
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function shouldIgnore(name: string, patterns: string[], isDirectory: boolean): boolean {
|
||||
// Always exclude certain directories
|
||||
if (isDirectory && EXPLORER_EXCLUDE_DIRS.includes(name)) {
|
||||
return true;
|
||||
}
|
||||
function shouldIgnore(name: string, patterns: string[], isDirectory: boolean, showAll: boolean = false): boolean {
|
||||
// When showAll is true, only apply gitignore patterns (skip hardcoded excludes and hidden files)
|
||||
if (!showAll) {
|
||||
// Always exclude certain directories
|
||||
if (isDirectory && EXPLORER_EXCLUDE_DIRS.includes(name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip hidden files/directories (starting with .)
|
||||
if (name.startsWith('.') && name !== '.claude' && name !== '.workflow') {
|
||||
return true;
|
||||
// Skip hidden files/directories (starting with .)
|
||||
if (name.startsWith('.')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const pattern of patterns) {
|
||||
@@ -626,6 +627,8 @@ export async function handleFilesRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
const maxDepth = parseInt(url.searchParams.get('maxDepth') || '6', 10);
|
||||
const includeHidden = url.searchParams.get('includeHidden') === 'true';
|
||||
|
||||
console.log(`[Explorer] Tree request - rootPath: ${rootPath}, includeHidden: ${includeHidden}`);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { remoteNotificationService } from '../core/services/remote-notification-
|
||||
import {
|
||||
addPendingQuestion,
|
||||
getPendingQuestion,
|
||||
updatePendingQuestion,
|
||||
removePendingQuestion,
|
||||
getAllPendingQuestions,
|
||||
clearAllPendingQuestions,
|
||||
@@ -451,19 +452,30 @@ export async function execute(params: AskQuestionParams): Promise<ToolResult<Ask
|
||||
// Generate surface ID
|
||||
const surfaceId = params.surfaceId || `question-${question.id}-${Date.now()}`;
|
||||
|
||||
// Check if this question was restored from disk (e.g., after MCP restart)
|
||||
const existingPending = getPendingQuestion(question.id);
|
||||
|
||||
// Create promise for answer
|
||||
const resultPromise = new Promise<AskQuestionResult>((resolve, reject) => {
|
||||
// Store pending question
|
||||
// Store pending question with real resolve/reject
|
||||
const pendingQuestion: PendingQuestion = {
|
||||
id: question.id,
|
||||
surfaceId,
|
||||
question,
|
||||
timestamp: Date.now(),
|
||||
timestamp: existingPending?.timestamp || Date.now(),
|
||||
timeout: params.timeout || DEFAULT_TIMEOUT_MS,
|
||||
resolve,
|
||||
reject,
|
||||
};
|
||||
addPendingQuestion(pendingQuestion);
|
||||
|
||||
// If question exists (restored from disk), update it with real resolve/reject
|
||||
// This fixes the "no promise attached" issue when MCP restarts
|
||||
if (existingPending) {
|
||||
updatePendingQuestion(question.id, pendingQuestion);
|
||||
console.log(`[AskQuestion] Updated restored question "${question.id}" with real resolve/reject`);
|
||||
} else {
|
||||
addPendingQuestion(pendingQuestion);
|
||||
}
|
||||
|
||||
// Set timeout
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -62,6 +62,12 @@ export interface ClaudeCliTool {
|
||||
* Supports ~, absolute, relative, and Windows paths
|
||||
*/
|
||||
settingsFile?: string;
|
||||
/**
|
||||
* Default effort level for Claude CLI (builtin claude only)
|
||||
* Passed to Claude CLI via --effort parameter
|
||||
* Valid values: 'low', 'medium', 'high'
|
||||
*/
|
||||
effort?: string;
|
||||
}
|
||||
|
||||
export type CliToolName = 'gemini' | 'qwen' | 'codex' | 'claude' | 'opencode' | string;
|
||||
@@ -1030,6 +1036,7 @@ export function getToolConfig(projectDir: string, tool: string): {
|
||||
tags?: string[];
|
||||
envFile?: string;
|
||||
settingsFile?: string;
|
||||
effort?: string;
|
||||
} {
|
||||
const config = loadClaudeCliTools(projectDir);
|
||||
const toolConfig = config.tools[tool];
|
||||
@@ -1050,7 +1057,8 @@ export function getToolConfig(projectDir: string, tool: string): {
|
||||
secondaryModel: toolConfig.secondaryModel ?? '',
|
||||
tags: toolConfig.tags,
|
||||
envFile: toolConfig.envFile,
|
||||
settingsFile: toolConfig.settingsFile
|
||||
settingsFile: toolConfig.settingsFile,
|
||||
effort: toolConfig.effort
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1068,6 +1076,7 @@ export function updateToolConfig(
|
||||
tags: string[];
|
||||
envFile: string | null;
|
||||
settingsFile: string | null;
|
||||
effort: string | null;
|
||||
}>
|
||||
): ClaudeCliToolsConfig {
|
||||
const config = loadClaudeCliTools(projectDir);
|
||||
@@ -1104,6 +1113,14 @@ export function updateToolConfig(
|
||||
config.tools[tool].settingsFile = updates.settingsFile;
|
||||
}
|
||||
}
|
||||
// Handle effort: set to undefined if null/empty, otherwise set value
|
||||
if (updates.effort !== undefined) {
|
||||
if (updates.effort === null || updates.effort === '') {
|
||||
delete config.tools[tool].effort;
|
||||
} else {
|
||||
config.tools[tool].effort = updates.effort;
|
||||
}
|
||||
}
|
||||
saveClaudeCliTools(projectDir, config);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface CliToolConfig {
|
||||
envFile?: string | null;
|
||||
type?: 'builtin' | 'cli-wrapper' | 'api-endpoint'; // Tool type for frontend routing
|
||||
settingsFile?: string | null; // Claude CLI settings file path
|
||||
effort?: string | null; // Effort level for Claude CLI (low, medium, high)
|
||||
}
|
||||
|
||||
export interface CliConfig {
|
||||
@@ -160,7 +161,8 @@ export function getFullConfigResponse(baseDir: string): {
|
||||
tags: tool.tags,
|
||||
envFile: tool.envFile,
|
||||
type: tool.type, // Preserve type field for frontend routing
|
||||
settingsFile: tool.settingsFile // Preserve settingsFile for Claude CLI
|
||||
settingsFile: tool.settingsFile, // Preserve settingsFile for Claude CLI
|
||||
effort: tool.effort // Preserve effort level for Claude CLI
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -429,6 +429,8 @@ const ParamsSchema = z.object({
|
||||
base: z.string().optional(), // Review changes against base branch
|
||||
commit: z.string().optional(), // Review changes from specific commit
|
||||
title: z.string().optional(), // Optional title for review summary
|
||||
// Claude-specific options
|
||||
effort: z.enum(['low', 'medium', 'high']).optional(), // Effort level for claude
|
||||
// Rules env vars (PROTO, TMPL) - will be passed to subprocess environment
|
||||
rulesEnv: z.object({
|
||||
PROTO: z.string().optional(),
|
||||
@@ -458,7 +460,7 @@ async function executeCliTool(
|
||||
throw new Error(`Invalid params: ${parsed.error.message}`);
|
||||
}
|
||||
|
||||
const { tool, prompt, mode, format, model, cd, includeDirs, resume, id: customId, noNative, category, parentExecutionId, outputFormat, uncommitted, base, commit, title, rulesEnv } = parsed.data;
|
||||
const { tool, prompt, mode, format, model, cd, includeDirs, resume, id: customId, noNative, category, parentExecutionId, outputFormat, uncommitted, base, commit, title, effort, rulesEnv } = parsed.data;
|
||||
|
||||
// Validate and determine working directory early (needed for conversation lookup)
|
||||
let workingDir: string;
|
||||
@@ -881,6 +883,7 @@ async function executeCliTool(
|
||||
|
||||
// Load and validate settings file for Claude tool (builtin only)
|
||||
let settingsFilePath: string | undefined;
|
||||
let effectiveEffort = effort;
|
||||
if (tool === 'claude') {
|
||||
const toolConfig = getToolConfig(workingDir, tool);
|
||||
if (toolConfig.settingsFile) {
|
||||
@@ -896,6 +899,11 @@ async function executeCliTool(
|
||||
errorLog('SETTINGS_FILE', `Failed to resolve Claude settings file`, { configured: toolConfig.settingsFile, error: (err as Error).message });
|
||||
}
|
||||
}
|
||||
// Use default effort from config if not explicitly provided, fallback to 'high'
|
||||
if (!effectiveEffort) {
|
||||
effectiveEffort = toolConfig.effort || 'high';
|
||||
debugLog('EFFORT', `Using effort level`, { effort: effectiveEffort, source: toolConfig.effort ? 'config' : 'default' });
|
||||
}
|
||||
}
|
||||
|
||||
// Build command
|
||||
@@ -908,7 +916,8 @@ async function executeCliTool(
|
||||
include: includeDirs,
|
||||
nativeResume: nativeResumeConfig,
|
||||
settingsFile: settingsFilePath,
|
||||
reviewOptions: mode === 'review' ? { uncommitted, base, commit, title } : undefined
|
||||
reviewOptions: mode === 'review' ? { uncommitted, base, commit, title } : undefined,
|
||||
effort: effectiveEffort
|
||||
});
|
||||
|
||||
// Use auto-detected format (from buildCommand) if available, otherwise use passed outputFormat
|
||||
|
||||
@@ -166,8 +166,10 @@ export function buildCommand(params: {
|
||||
commit?: string;
|
||||
title?: string;
|
||||
};
|
||||
/** Effort level for claude (low, medium, high) */
|
||||
effort?: string;
|
||||
}): { command: string; args: string[]; useStdin: boolean; outputFormat?: 'text' | 'json-lines' } {
|
||||
const { tool, prompt, mode = 'analysis', model, dir, include, nativeResume, settingsFile, reviewOptions } = params;
|
||||
const { tool, prompt, mode = 'analysis', model, dir, include, nativeResume, settingsFile, reviewOptions, effort } = params;
|
||||
|
||||
debugLog('BUILD_CMD', `Building command for tool: ${tool}`, {
|
||||
mode,
|
||||
@@ -331,6 +333,10 @@ export function buildCommand(params: {
|
||||
if (model) {
|
||||
args.push('--model', model);
|
||||
}
|
||||
// Effort level: claude --effort <low|medium|high>
|
||||
if (effort) {
|
||||
args.push('--effort', effort);
|
||||
}
|
||||
// Permission modes: write/auto → bypassPermissions, analysis → default
|
||||
if (mode === 'write' || mode === 'auto') {
|
||||
args.push('--permission-mode', 'bypassPermissions');
|
||||
|
||||
Reference in New Issue
Block a user