mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
fix: Resolve MCP installation issues and enhance path resolution
- Fixed API endpoint mismatches in mcp-manager.js to ensure global install/update buttons function correctly. - Corrected undefined function references in mcp-manager.js for project installation. - Refactored event handling to eliminate global scope pollution in mcp-manager.js. - Added comprehensive debugging guide for MCP installation issues. - Implemented a session path resolver to infer content types from filenames and paths, improving usability. - Introduced tests for embeddings improvements in init and status commands to verify functionality.
This commit is contained in:
@@ -135,7 +135,7 @@ export function run(argv: string[]): void {
|
||||
program
|
||||
.command('session [subcommand] [args...]')
|
||||
.description('Workflow session lifecycle management')
|
||||
.option('--location <loc>', 'Location filter: active|archived|both')
|
||||
.option('--location <loc>', 'Session location: active|lite-plan|lite-fix (init); Filter: active|archived|both (list)')
|
||||
.option('--type <type>', 'Content type or session type')
|
||||
.option('--content <json>', 'Content for write/update')
|
||||
.option('--task-id <id>', 'Task ID for task content')
|
||||
|
||||
372
ccw/src/commands/session-path-resolver.ts
Normal file
372
ccw/src/commands/session-path-resolver.ts
Normal file
@@ -0,0 +1,372 @@
|
||||
/**
|
||||
* Session Path Resolver - Smart file path to content_type resolution
|
||||
* Eliminates need for --type parameter by inferring from filenames and paths
|
||||
*/
|
||||
|
||||
import { basename, dirname, join } from 'path';
|
||||
|
||||
// Supported content types (from session-manager.ts)
|
||||
type ContentType =
|
||||
| 'session' | 'plan' | 'task' | 'summary' | 'process' | 'chat' | 'brainstorm'
|
||||
| 'review-dim' | 'review-iter' | 'review-fix' | 'todo' | 'context'
|
||||
| 'lite-plan' | 'lite-fix-plan' | 'exploration' | 'explorations-manifest'
|
||||
| 'diagnosis' | 'diagnoses-manifest' | 'clarifications' | 'execution-context' | 'session-metadata';
|
||||
|
||||
export interface ResolverResult {
|
||||
contentType: ContentType;
|
||||
pathParams?: Record<string, string>;
|
||||
resolvedPath: string; // Relative path within session directory
|
||||
}
|
||||
|
||||
export interface ResolverContext {
|
||||
sessionPath: string;
|
||||
sessionLocation: 'active' | 'archived' | 'lite-plan' | 'lite-fix';
|
||||
}
|
||||
|
||||
export class PathResolutionError extends Error {
|
||||
code: 'NOT_FOUND' | 'INVALID_PATH';
|
||||
suggestions: string[];
|
||||
|
||||
constructor(code: 'NOT_FOUND' | 'INVALID_PATH', message: string, suggestions: string[] = []) {
|
||||
super(message);
|
||||
this.name = 'PathResolutionError';
|
||||
this.code = code;
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task ID patterns (IMPL-*, TEST-*, DOC-*, REFACTOR-*, TASK-*)
|
||||
*/
|
||||
const TASK_ID_PATTERNS = [
|
||||
/^(IMPL|TEST|DOC|REFACTOR|TASK)-\d+\.json$/i,
|
||||
];
|
||||
|
||||
/**
|
||||
* Summary filename pattern (*-summary.md)
|
||||
*/
|
||||
const SUMMARY_PATTERN = /^(.+)-summary\.md$/i;
|
||||
|
||||
/**
|
||||
* Path prefix to content_type mapping
|
||||
*/
|
||||
const PATH_PREFIX_TO_CONTENT_TYPE: Record<string, ContentType> = {
|
||||
'.task/': 'task',
|
||||
'.summaries/': 'summary',
|
||||
'.process/': 'process',
|
||||
'.chat/': 'chat',
|
||||
'.brainstorming/': 'brainstorm',
|
||||
'.review/dimensions/': 'review-dim',
|
||||
'.review/iterations/': 'review-iter',
|
||||
'.review/fixes/': 'review-fix',
|
||||
};
|
||||
|
||||
/**
|
||||
* Exact filename to content_type mapping
|
||||
*/
|
||||
const EXACT_FILENAME_TO_CONTENT_TYPE: Record<string, ContentType> = {
|
||||
'IMPL_PLAN.md': 'plan',
|
||||
'TODO_LIST.md': 'todo',
|
||||
'workflow-session.json': 'session',
|
||||
'context-package.json': 'context',
|
||||
'plan.json': 'lite-plan', // Will be overridden by session location
|
||||
'fix-plan.json': 'lite-fix-plan', // Specific to lite-fix sessions
|
||||
'session-metadata.json': 'session-metadata',
|
||||
'clarifications.json': 'clarifications',
|
||||
'execution-context.json': 'execution-context',
|
||||
'explorations-manifest.json': 'explorations-manifest',
|
||||
'diagnoses-manifest.json': 'diagnoses-manifest',
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract task ID from filename
|
||||
* Examples: IMPL-001.json → IMPL-001
|
||||
*/
|
||||
function extractTaskId(filename: string): string | null {
|
||||
const match = filename.match(/^([A-Z]+-\d+)\.json$/i);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract summary task ID from filename
|
||||
* Examples: IMPL-001-summary.md → IMPL-001
|
||||
*/
|
||||
function extractSummaryTaskId(filename: string): string | null {
|
||||
const match = filename.match(SUMMARY_PATTERN);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract review dimension from path
|
||||
* Examples: .review/dimensions/security.json → security
|
||||
*/
|
||||
function extractReviewDimension(filepath: string): string | null {
|
||||
const match = filepath.match(/\.review\/dimensions\/(.+)\.json$/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract review iteration from path
|
||||
* Examples: .review/iterations/1.json → 1
|
||||
*/
|
||||
function extractReviewIteration(filepath: string): string | null {
|
||||
const match = filepath.match(/\.review\/iterations\/(\d+)\.json$/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract filename from relative path within review fixes
|
||||
* Examples: .review/fixes/finding-001.json → finding-001.json
|
||||
*/
|
||||
function extractReviewFixFilename(filepath: string): string | null {
|
||||
const match = filepath.match(/\.review\/fixes\/(.+)$/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract filename from process/chat/brainstorm paths
|
||||
* Examples: .process/archiving.json → archiving.json
|
||||
*/
|
||||
function extractProcessFilename(filepath: string, prefix: string): string | null {
|
||||
const pattern = new RegExp(`^${prefix.replace(/\//g, '\\/')}(.+)$`);
|
||||
const match = filepath.match(pattern);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve relative path (contains '/') to content_type
|
||||
*/
|
||||
function resolveRelativePath(filepath: string): ResolverResult | null {
|
||||
// Security: Reject path traversal attempts
|
||||
if (filepath.includes('..') || filepath.startsWith('/')) {
|
||||
throw new PathResolutionError(
|
||||
'INVALID_PATH',
|
||||
'Path traversal and absolute paths are not allowed',
|
||||
['Use relative paths within the session directory']
|
||||
);
|
||||
}
|
||||
|
||||
// Check path prefix matches
|
||||
for (const [prefix, contentType] of Object.entries(PATH_PREFIX_TO_CONTENT_TYPE)) {
|
||||
if (filepath.startsWith(prefix)) {
|
||||
const pathParams: Record<string, string> = {};
|
||||
|
||||
// Extract specific parameters based on content type
|
||||
if (contentType === 'task') {
|
||||
const taskId = extractTaskId(basename(filepath));
|
||||
if (taskId) {
|
||||
pathParams.task_id = taskId;
|
||||
}
|
||||
} else if (contentType === 'summary') {
|
||||
const taskId = extractSummaryTaskId(basename(filepath));
|
||||
if (taskId) {
|
||||
pathParams.task_id = taskId;
|
||||
}
|
||||
} else if (contentType === 'review-dim') {
|
||||
const dimension = extractReviewDimension(filepath);
|
||||
if (dimension) {
|
||||
pathParams.dimension = dimension;
|
||||
}
|
||||
} else if (contentType === 'review-iter') {
|
||||
const iteration = extractReviewIteration(filepath);
|
||||
if (iteration) {
|
||||
pathParams.iteration = iteration;
|
||||
}
|
||||
} else if (contentType === 'review-fix') {
|
||||
const filename = extractReviewFixFilename(filepath);
|
||||
if (filename) {
|
||||
pathParams.filename = filename;
|
||||
}
|
||||
} else if (contentType === 'process' || contentType === 'chat' || contentType === 'brainstorm') {
|
||||
const filename = extractProcessFilename(filepath, prefix);
|
||||
if (filename) {
|
||||
pathParams.filename = filename;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
contentType,
|
||||
pathParams: Object.keys(pathParams).length > 0 ? pathParams : undefined,
|
||||
resolvedPath: filepath,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve simple filename (no '/') to content_type
|
||||
*/
|
||||
function resolveFilename(
|
||||
filename: string,
|
||||
context: ResolverContext
|
||||
): ResolverResult | null {
|
||||
// Check exact filename matches first
|
||||
if (EXACT_FILENAME_TO_CONTENT_TYPE[filename]) {
|
||||
let contentType = EXACT_FILENAME_TO_CONTENT_TYPE[filename];
|
||||
|
||||
// Override plan.json based on session location
|
||||
if (filename === 'plan.json') {
|
||||
if (context.sessionLocation === 'lite-plan') {
|
||||
contentType = 'lite-plan';
|
||||
} else if (context.sessionLocation === 'lite-fix') {
|
||||
contentType = 'lite-fix-plan';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
contentType,
|
||||
resolvedPath: filename,
|
||||
};
|
||||
}
|
||||
|
||||
// Check task ID patterns (IMPL-001.json, TEST-042.json, etc.)
|
||||
for (const pattern of TASK_ID_PATTERNS) {
|
||||
if (pattern.test(filename)) {
|
||||
const taskId = extractTaskId(filename);
|
||||
if (taskId) {
|
||||
return {
|
||||
contentType: 'task',
|
||||
pathParams: { task_id: taskId },
|
||||
resolvedPath: `.task/${filename}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check summary pattern (*-summary.md)
|
||||
const summaryTaskId = extractSummaryTaskId(filename);
|
||||
if (summaryTaskId) {
|
||||
return {
|
||||
contentType: 'summary',
|
||||
pathParams: { task_id: summaryTaskId },
|
||||
resolvedPath: `.summaries/${filename}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Check exploration pattern (exploration-{angle}.json)
|
||||
const explorationMatch = filename.match(/^exploration-(.+)\.json$/);
|
||||
if (explorationMatch) {
|
||||
return {
|
||||
contentType: 'exploration',
|
||||
pathParams: { angle: explorationMatch[1] },
|
||||
resolvedPath: filename,
|
||||
};
|
||||
}
|
||||
|
||||
// Check diagnosis pattern (diagnosis-{angle}.json)
|
||||
const diagnosisMatch = filename.match(/^diagnosis-(.+)\.json$/);
|
||||
if (diagnosisMatch) {
|
||||
return {
|
||||
contentType: 'diagnosis',
|
||||
pathParams: { angle: diagnosisMatch[1] },
|
||||
resolvedPath: filename,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main resolver function - resolves filename/path to content_type
|
||||
*
|
||||
* @param filename - Filename or relative path
|
||||
* @param context - Session context (path and location)
|
||||
* @returns Resolver result with content type, path params, and resolved path
|
||||
* @throws PathResolutionError if path is invalid or cannot be resolved
|
||||
*
|
||||
* @example
|
||||
* // Filename examples
|
||||
* resolveFilePath('IMPL-001.json', context)
|
||||
* // → { contentType: 'task', pathParams: {task_id: 'IMPL-001'}, resolvedPath: '.task/IMPL-001.json' }
|
||||
*
|
||||
* resolveFilePath('IMPL_PLAN.md', context)
|
||||
* // → { contentType: 'plan', resolvedPath: 'IMPL_PLAN.md' }
|
||||
*
|
||||
* // Relative path examples
|
||||
* resolveFilePath('.task/IMPL-001.json', context)
|
||||
* // → { contentType: 'task', pathParams: {task_id: 'IMPL-001'}, resolvedPath: '.task/IMPL-001.json' }
|
||||
*
|
||||
* resolveFilePath('.review/dimensions/security.json', context)
|
||||
* // → { contentType: 'review-dim', pathParams: {dimension: 'security'}, resolvedPath: '...' }
|
||||
*/
|
||||
export function resolveFilePath(
|
||||
filename: string,
|
||||
context: ResolverContext
|
||||
): ResolverResult {
|
||||
if (!filename) {
|
||||
throw new PathResolutionError(
|
||||
'INVALID_PATH',
|
||||
'Filename is required',
|
||||
['Usage: ccw session <session-id> read <filename|path>']
|
||||
);
|
||||
}
|
||||
|
||||
// Normalize path separators (handle Windows paths)
|
||||
const normalizedFilename = filename.replace(/\\/g, '/');
|
||||
|
||||
// Security: Validate filename
|
||||
if (normalizedFilename.includes('\0') || normalizedFilename.trim() === '') {
|
||||
throw new PathResolutionError(
|
||||
'INVALID_PATH',
|
||||
'Invalid filename or path',
|
||||
['Use valid filename or relative path']
|
||||
);
|
||||
}
|
||||
|
||||
// RULE 1: Relative path (contains '/')
|
||||
if (normalizedFilename.includes('/')) {
|
||||
const result = resolveRelativePath(normalizedFilename);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new PathResolutionError(
|
||||
'NOT_FOUND',
|
||||
`Unknown path pattern: ${normalizedFilename}`,
|
||||
[
|
||||
'Supported paths:',
|
||||
' .task/*.json (tasks)',
|
||||
' .summaries/*.md (summaries)',
|
||||
' .process/* (process files)',
|
||||
' .chat/* (chat files)',
|
||||
' .brainstorming/* (brainstorm files)',
|
||||
' .review/dimensions/*.json (review dimensions)',
|
||||
' .review/iterations/*.json (review iterations)',
|
||||
' .review/fixes/* (review fixes)',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// RULE 2: Simple filename
|
||||
const result = resolveFilename(normalizedFilename, context);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Resolution failed
|
||||
throw new PathResolutionError(
|
||||
'NOT_FOUND',
|
||||
`Unknown file: ${normalizedFilename}`,
|
||||
[
|
||||
'Supported filenames:',
|
||||
' IMPL-*.json, TEST-*.json, DOC-*.json, REFACTOR-*.json, TASK-*.json (tasks)',
|
||||
' IMPL_PLAN.md (plan)',
|
||||
' TODO_LIST.md (todo)',
|
||||
' workflow-session.json (session metadata)',
|
||||
' context-package.json (context)',
|
||||
' session-metadata.json (session metadata for lite sessions)',
|
||||
' plan.json (lite-plan or lite-fix-plan, depending on session location)',
|
||||
' fix-plan.json (lite-fix-plan)',
|
||||
' *-summary.md (summaries)',
|
||||
' exploration-{angle}.json (explorations)',
|
||||
' diagnosis-{angle}.json (diagnoses)',
|
||||
' explorations-manifest.json (exploration manifest)',
|
||||
' diagnoses-manifest.json (diagnosis manifest)',
|
||||
' clarifications.json (clarifications)',
|
||||
' execution-context.json (execution context)',
|
||||
'Or use a relative path: .task/IMPL-001.json',
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -53,10 +53,13 @@ interface SessionInput {
|
||||
session_id?: string;
|
||||
id?: string;
|
||||
project?: string;
|
||||
description?: string;
|
||||
status?: string;
|
||||
type?: string;
|
||||
workflow_type?: string | null;
|
||||
created_at?: string | null;
|
||||
created_at?: string | null; // For backward compatibility
|
||||
created?: string; // From SessionMetadata
|
||||
updated?: string; // From SessionMetadata
|
||||
archived_at?: string | null;
|
||||
path: string;
|
||||
}
|
||||
@@ -249,11 +252,11 @@ export async function aggregateData(sessions: ScanSessionsResult, workflowDir: s
|
||||
async function processSession(session: SessionInput, isActive: boolean): Promise<SessionData> {
|
||||
const result: SessionData = {
|
||||
session_id: session.session_id || session.id || '',
|
||||
project: session.project || session.session_id || session.id || '',
|
||||
project: session.project || session.description || session.session_id || session.id || '',
|
||||
status: session.status || (isActive ? 'active' : 'archived'),
|
||||
type: session.type || 'workflow', // Session type (workflow, review, test, docs)
|
||||
workflow_type: session.workflow_type || null, // Original workflow_type for reference
|
||||
created_at: session.created_at || null, // Raw ISO string - let frontend format
|
||||
created_at: session.created || session.created_at || null, // Prefer 'created' from SessionMetadata, fallback to 'created_at'
|
||||
archived_at: session.archived_at || null, // Raw ISO string - let frontend format
|
||||
path: session.path,
|
||||
tasks: [],
|
||||
|
||||
@@ -192,9 +192,9 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
|
||||
const args = ['clean'];
|
||||
if (all) {
|
||||
args.push('--all');
|
||||
}
|
||||
if (path) {
|
||||
args.push('--path', path);
|
||||
} else if (path) {
|
||||
// Path is passed as a positional argument, not as a flag
|
||||
args.push(path);
|
||||
}
|
||||
args.push('--json');
|
||||
|
||||
|
||||
@@ -771,7 +771,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
}
|
||||
|
||||
try {
|
||||
const configPath = join(projectPath, '.claude', 'CLAUDE.md');
|
||||
const rulesDir = join(projectPath, '.claude', 'rules');
|
||||
const configPath = join(rulesDir, 'active_memory.md');
|
||||
const configJsonPath = join(projectPath, '.claude', 'active_memory_config.json');
|
||||
const enabled = existsSync(configPath);
|
||||
let lastSync: string | null = null;
|
||||
@@ -823,7 +824,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
}
|
||||
|
||||
const claudeDir = join(projectPath, '.claude');
|
||||
const configPath = join(claudeDir, 'CLAUDE.md');
|
||||
const rulesDir = join(claudeDir, 'rules');
|
||||
const configPath = join(rulesDir, 'active_memory.md');
|
||||
const configJsonPath = join(claudeDir, 'active_memory_config.json');
|
||||
|
||||
if (enabled) {
|
||||
@@ -831,14 +833,17 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
if (!existsSync(claudeDir)) {
|
||||
mkdirSync(claudeDir, { recursive: true });
|
||||
}
|
||||
if (!existsSync(rulesDir)) {
|
||||
mkdirSync(rulesDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Save config
|
||||
if (config) {
|
||||
writeFileSync(configJsonPath, JSON.stringify(config, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
// Create initial CLAUDE.md with header
|
||||
const initialContent = `# CLAUDE.md - Project Memory
|
||||
// Create initial active_memory.md with header
|
||||
const initialContent = `# Active Memory - Project Context
|
||||
|
||||
> Auto-generated understanding of frequently accessed files.
|
||||
> Last updated: ${new Date().toISOString()}
|
||||
@@ -901,7 +906,7 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Active Memory - Sync (analyze hot files using CLI and update CLAUDE.md)
|
||||
// API: Active Memory - Sync (analyze hot files using CLI and update active_memory.md)
|
||||
if (pathname === '/api/memory/active/sync' && req.method === 'POST') {
|
||||
let body = '';
|
||||
req.on('data', (chunk: Buffer) => { body += chunk.toString(); });
|
||||
@@ -917,7 +922,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
}
|
||||
|
||||
const claudeDir = join(projectPath, '.claude');
|
||||
const configPath = join(claudeDir, 'CLAUDE.md');
|
||||
const rulesDir = join(claudeDir, 'rules');
|
||||
const configPath = join(rulesDir, 'active_memory.md');
|
||||
|
||||
// Get hot files from memory store - with fallback
|
||||
let hotFiles: any[] = [];
|
||||
@@ -937,8 +943,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
return isAbsolute(filePath) ? filePath : join(projectPath, filePath);
|
||||
}).filter((p: string) => existsSync(p));
|
||||
|
||||
// Build the CLAUDE.md content header
|
||||
let content = `# CLAUDE.md - Project Memory
|
||||
// Build the active_memory.md content header
|
||||
let content = `# Active Memory - Project Context
|
||||
|
||||
> Auto-generated understanding of frequently accessed files using ${tool.toUpperCase()}.
|
||||
> Last updated: ${new Date().toISOString()}
|
||||
@@ -1055,10 +1061,13 @@ RULES: Be concise. Focus on practical understanding. Include function signatures
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
// Ensure directories exist
|
||||
if (!existsSync(claudeDir)) {
|
||||
mkdirSync(claudeDir, { recursive: true });
|
||||
}
|
||||
if (!existsSync(rulesDir)) {
|
||||
mkdirSync(rulesDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Write the file
|
||||
writeFileSync(configPath, content, 'utf-8');
|
||||
|
||||
@@ -17,7 +17,7 @@ const SERVER_NAME = 'ccw-tools';
|
||||
const SERVER_VERSION = '6.1.4';
|
||||
|
||||
// Default enabled tools (core set)
|
||||
const DEFAULT_TOOLS: string[] = ['write_file', 'edit_file', 'read_file', 'codex_lens', 'smart_search'];
|
||||
const DEFAULT_TOOLS: string[] = ['write_file', 'edit_file', 'read_file', 'smart_search'];
|
||||
|
||||
/**
|
||||
* Get list of enabled tools from environment or defaults
|
||||
|
||||
@@ -20,6 +20,11 @@ let currentCliMode = 'claude'; // 'claude' or 'codex'
|
||||
let codexMcpConfig = null;
|
||||
let codexMcpServers = {};
|
||||
|
||||
// ========== Project Config Type Preference ==========
|
||||
// 'mcp' = .mcp.json (project root file, recommended)
|
||||
// 'claude' = claude.json projects[path].mcpServers (shared config)
|
||||
let preferredProjectConfigType = 'mcp';
|
||||
|
||||
// ========== Initialization ==========
|
||||
function initMcpManager() {
|
||||
// Initialize MCP navigation
|
||||
@@ -229,11 +234,9 @@ async function toggleMcpServer(serverName, enable) {
|
||||
|
||||
async function copyMcpServerToProject(serverName, serverConfig, configType = null) {
|
||||
try {
|
||||
// If configType not specified, ask user to choose
|
||||
// If configType not specified, use the preferred config type (toggle setting)
|
||||
if (!configType) {
|
||||
const choice = await showConfigTypeDialog();
|
||||
if (!choice) return null; // User cancelled
|
||||
configType = choice;
|
||||
configType = preferredProjectConfigType;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/mcp-copy-server', {
|
||||
@@ -982,14 +985,16 @@ async function installCcwToolsMcp(scope = 'workspace') {
|
||||
showRefreshToast(result.error || 'Failed to install CCW Tools MCP globally', 'error');
|
||||
}
|
||||
} else {
|
||||
// Install to workspace (.mcp.json)
|
||||
// Install to workspace (use preferredProjectConfigType)
|
||||
const configType = preferredProjectConfigType;
|
||||
const response = await fetch('/api/mcp-copy-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: 'ccw-tools',
|
||||
serverConfig: ccwToolsConfig
|
||||
serverConfig: ccwToolsConfig,
|
||||
configType: configType
|
||||
})
|
||||
});
|
||||
|
||||
@@ -999,7 +1004,8 @@ async function installCcwToolsMcp(scope = 'workspace') {
|
||||
if (result.success) {
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast(`CCW Tools installed to workspace (${selectedTools.length} tools)`, 'success');
|
||||
const location = configType === 'mcp' ? '.mcp.json' : 'claude.json';
|
||||
showRefreshToast(`CCW Tools installed to ${location} (${selectedTools.length} tools)`, 'success');
|
||||
} else {
|
||||
showRefreshToast(result.error || 'Failed to install CCW Tools MCP to workspace', 'error');
|
||||
}
|
||||
@@ -1046,14 +1052,16 @@ async function updateCcwToolsMcp(scope = 'workspace') {
|
||||
showRefreshToast(result.error || 'Failed to update CCW Tools MCP globally', 'error');
|
||||
}
|
||||
} else {
|
||||
// Update workspace (.mcp.json)
|
||||
// Update workspace (use preferredProjectConfigType)
|
||||
const configType = preferredProjectConfigType;
|
||||
const response = await fetch('/api/mcp-copy-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: 'ccw-tools',
|
||||
serverConfig: ccwToolsConfig
|
||||
serverConfig: ccwToolsConfig,
|
||||
configType: configType
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1063,7 +1071,8 @@ async function updateCcwToolsMcp(scope = 'workspace') {
|
||||
if (result.success) {
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast(`CCW Tools updated in workspace (${selectedTools.length} tools)`, 'success');
|
||||
const location = configType === 'mcp' ? '.mcp.json' : 'claude.json';
|
||||
showRefreshToast(`CCW Tools updated in ${location} (${selectedTools.length} tools)`, 'success');
|
||||
} else {
|
||||
showRefreshToast(result.error || 'Failed to update CCW Tools MCP in workspace', 'error');
|
||||
}
|
||||
@@ -1130,6 +1139,25 @@ async function installCcwToolsMcpToCodex() {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Project Config Type Toggle ==========
|
||||
function toggleProjectConfigType() {
|
||||
preferredProjectConfigType = preferredProjectConfigType === 'mcp' ? 'claude' : 'mcp';
|
||||
console.log('[MCP] Preferred project config type changed to:', preferredProjectConfigType);
|
||||
// Re-render to update toggle display
|
||||
renderMcpManager();
|
||||
}
|
||||
|
||||
function getPreferredProjectConfigType() {
|
||||
return preferredProjectConfigType;
|
||||
}
|
||||
|
||||
function setPreferredProjectConfigType(type) {
|
||||
if (type === 'mcp' || type === 'claude') {
|
||||
preferredProjectConfigType = type;
|
||||
console.log('[MCP] Preferred project config type set to:', preferredProjectConfigType);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Global Exports for onclick handlers ==========
|
||||
// Expose functions to global scope to support inline onclick handlers
|
||||
window.setCliMode = setCliMode;
|
||||
@@ -1137,3 +1165,6 @@ window.getCliMode = getCliMode;
|
||||
window.selectCcwTools = selectCcwTools;
|
||||
window.selectCcwToolsCodex = selectCcwToolsCodex;
|
||||
window.openMcpCreateModal = openMcpCreateModal;
|
||||
window.toggleProjectConfigType = toggleProjectConfigType;
|
||||
window.getPreferredProjectConfigType = getPreferredProjectConfigType;
|
||||
window.setPreferredProjectConfigType = setPreferredProjectConfigType;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// Internationalization for help page (Chinese translations)
|
||||
// ==========================================
|
||||
|
||||
console.log('[Help i18n] File loading started');
|
||||
|
||||
var helpI18n = {
|
||||
zh: {
|
||||
// Page Headers
|
||||
@@ -30,10 +32,13 @@ var helpI18n = {
|
||||
|
||||
// Workflow Diagrams
|
||||
'help.diagrams.title': '常见工作流场景',
|
||||
'help.diagrams.tdd': 'TDD 开发',
|
||||
'help.diagrams.feature': '功能开发',
|
||||
'help.diagrams.bugfix': 'Bug 调查',
|
||||
'help.diagrams.review': '代码审查',
|
||||
'help.diagrams.decision': '决策流程:选择规划方式',
|
||||
'help.diagrams.brainstorm': '头脑风暴',
|
||||
'help.diagrams.cliResume': 'CLI Resume机制',
|
||||
'help.diagrams.bugFix': 'Bug修复流程',
|
||||
'help.diagrams.lite': 'Lite轻量工作流',
|
||||
'help.diagrams.planFull': 'Plan完整规划',
|
||||
'help.diagrams.tdd': 'TDD测试驱动',
|
||||
'help.diagrams.fit': '适应视图',
|
||||
'help.diagrams.zoomIn': '放大',
|
||||
'help.diagrams.zoomOut': '缩小',
|
||||
@@ -43,6 +48,59 @@ var helpI18n = {
|
||||
'help.diagrams.legend.alternatives': '替代方案',
|
||||
'help.diagrams.notLoaded': 'Cytoscape.js 未加载',
|
||||
|
||||
// Workflow Steps - Decision
|
||||
'help.workflows.decision.start': '任务开始',
|
||||
'help.workflows.decision.cliAnalyze': 'CLI分析理解项目',
|
||||
'help.workflows.decision.understand': '充分理解蓝图',
|
||||
'help.workflows.decision.simple': '简单任务',
|
||||
'help.workflows.decision.medium': '中等任务',
|
||||
'help.workflows.decision.complex': '复杂任务',
|
||||
'help.workflows.decision.claudeExec': 'Claude执行(优先)',
|
||||
'help.workflows.decision.cliExec': 'CLI执行',
|
||||
'help.workflows.decision.claudePlan': 'Claude自带Plan',
|
||||
|
||||
// Workflow Steps - Brainstorm
|
||||
'help.workflows.brainstorm.start': '不确定方向',
|
||||
'help.workflows.brainstorm.question': '知道做什么吗?',
|
||||
'help.workflows.brainstorm.product': '不知道:探索产品',
|
||||
'help.workflows.brainstorm.design': '知道但不知怎么做',
|
||||
'help.workflows.brainstorm.next': '进入规划阶段',
|
||||
|
||||
// Workflow Steps - CLI Resume
|
||||
'help.workflows.cliResume.firstExec': 'ccw cli exec "分析..."',
|
||||
'help.workflows.cliResume.saveContext': '保存会话上下文',
|
||||
'help.workflows.cliResume.resumeCmd': 'ccw cli exec --resume',
|
||||
'help.workflows.cliResume.merge': '合并历史对话',
|
||||
'help.workflows.cliResume.continue': '继续执行任务',
|
||||
'help.workflows.cliResume.splitOutput': '拆分结果存储',
|
||||
'help.workflows.cliResume.complete': '完成',
|
||||
|
||||
// Workflow Steps - Bug Fix
|
||||
'help.workflows.bugFix.start': '发现Bug',
|
||||
'help.workflows.bugFix.cliAnalyze': 'CLI分析定位Bug',
|
||||
'help.workflows.bugFix.diagnosis': '诊断根因',
|
||||
'help.workflows.bugFix.impact': '影响评估',
|
||||
'help.workflows.bugFix.strategy': '修复策略',
|
||||
'help.workflows.bugFix.execute': '执行修复',
|
||||
'help.workflows.bugFix.complete': '完成',
|
||||
|
||||
// Workflow Steps - Plan Full
|
||||
'help.workflows.planFull.start': '复杂项目开始',
|
||||
'help.workflows.planFull.cliAnalyze': 'CLI深度分析项目',
|
||||
'help.workflows.planFull.complete': '会话完成',
|
||||
|
||||
// Workflow Steps - Lite
|
||||
'help.workflows.lite.start': '开始',
|
||||
'help.workflows.lite.confirm': '三维确认',
|
||||
'help.workflows.lite.complete': '完成',
|
||||
|
||||
// Workflow Steps - TDD
|
||||
'help.workflows.tdd.start': '开始',
|
||||
'help.workflows.tdd.red': 'Red: 编写失败测试',
|
||||
'help.workflows.tdd.green': 'Green: 实现代码',
|
||||
'help.workflows.tdd.refactor': 'Refactor: 重构优化',
|
||||
'help.workflows.tdd.complete': '完成',
|
||||
|
||||
// CodexLens
|
||||
'help.codexlens.title': 'CodexLens 快速入门',
|
||||
'help.codexlens.subtitle': '强大的代码索引和语义搜索工具',
|
||||
@@ -95,10 +153,13 @@ var helpI18n = {
|
||||
|
||||
// Workflow Diagrams
|
||||
'help.diagrams.title': 'Common Workflow Scenarios',
|
||||
'help.diagrams.decision': 'Decision: Choose Planning Approach',
|
||||
'help.diagrams.brainstorm': 'Brainstorming',
|
||||
'help.diagrams.cliResume': 'CLI Resume Mechanism',
|
||||
'help.diagrams.bugFix': 'Bug Fix Workflow',
|
||||
'help.diagrams.lite': 'Lite Workflow',
|
||||
'help.diagrams.planFull': 'Full Planning',
|
||||
'help.diagrams.tdd': 'TDD Development',
|
||||
'help.diagrams.feature': 'Feature Development',
|
||||
'help.diagrams.bugfix': 'Bug Investigation',
|
||||
'help.diagrams.review': 'Code Review',
|
||||
'help.diagrams.fit': 'Fit to View',
|
||||
'help.diagrams.zoomIn': 'Zoom In',
|
||||
'help.diagrams.zoomOut': 'Zoom Out',
|
||||
@@ -108,6 +169,59 @@ var helpI18n = {
|
||||
'help.diagrams.legend.alternatives': 'Alternatives',
|
||||
'help.diagrams.notLoaded': 'Cytoscape.js not loaded',
|
||||
|
||||
// Workflow Steps - Decision
|
||||
'help.workflows.decision.start': 'Task Start',
|
||||
'help.workflows.decision.cliAnalyze': 'CLI Analyze Project',
|
||||
'help.workflows.decision.understand': 'Understand Blueprint',
|
||||
'help.workflows.decision.simple': 'Simple Task',
|
||||
'help.workflows.decision.medium': 'Medium Task',
|
||||
'help.workflows.decision.complex': 'Complex Task',
|
||||
'help.workflows.decision.claudeExec': 'Claude Execute (Preferred)',
|
||||
'help.workflows.decision.cliExec': 'CLI Execute',
|
||||
'help.workflows.decision.claudePlan': 'Claude Built-in Plan',
|
||||
|
||||
// Workflow Steps - Brainstorm
|
||||
'help.workflows.brainstorm.start': 'Uncertain Direction',
|
||||
'help.workflows.brainstorm.question': 'Know What to Build?',
|
||||
'help.workflows.brainstorm.product': 'No: Explore Product',
|
||||
'help.workflows.brainstorm.design': 'Yes but Not How',
|
||||
'help.workflows.brainstorm.next': 'Enter Planning Phase',
|
||||
|
||||
// Workflow Steps - CLI Resume
|
||||
'help.workflows.cliResume.firstExec': 'ccw cli exec "analyze..."',
|
||||
'help.workflows.cliResume.saveContext': 'Save Session Context',
|
||||
'help.workflows.cliResume.resumeCmd': 'ccw cli exec --resume',
|
||||
'help.workflows.cliResume.merge': 'Merge Conversation History',
|
||||
'help.workflows.cliResume.continue': 'Continue Execution',
|
||||
'help.workflows.cliResume.splitOutput': 'Split & Store Results',
|
||||
'help.workflows.cliResume.complete': 'Complete',
|
||||
|
||||
// Workflow Steps - Bug Fix
|
||||
'help.workflows.bugFix.start': 'Bug Discovered',
|
||||
'help.workflows.bugFix.cliAnalyze': 'CLI Analyze & Locate Bug',
|
||||
'help.workflows.bugFix.diagnosis': 'Root Cause Analysis',
|
||||
'help.workflows.bugFix.impact': 'Impact Assessment',
|
||||
'help.workflows.bugFix.strategy': 'Fix Strategy',
|
||||
'help.workflows.bugFix.execute': 'Execute Fix',
|
||||
'help.workflows.bugFix.complete': 'Complete',
|
||||
|
||||
// Workflow Steps - Plan Full
|
||||
'help.workflows.planFull.start': 'Complex Project Start',
|
||||
'help.workflows.planFull.cliAnalyze': 'CLI Deep Analysis',
|
||||
'help.workflows.planFull.complete': 'Session Complete',
|
||||
|
||||
// Workflow Steps - Lite
|
||||
'help.workflows.lite.start': 'Start',
|
||||
'help.workflows.lite.confirm': '3D Confirmation',
|
||||
'help.workflows.lite.complete': 'Complete',
|
||||
|
||||
// Workflow Steps - TDD
|
||||
'help.workflows.tdd.start': 'Start',
|
||||
'help.workflows.tdd.red': 'Red: Write Failing Test',
|
||||
'help.workflows.tdd.green': 'Green: Implement Code',
|
||||
'help.workflows.tdd.refactor': 'Refactor: Optimize',
|
||||
'help.workflows.tdd.complete': 'Complete',
|
||||
|
||||
// CodexLens
|
||||
'help.codexlens.title': 'CodexLens Quick Start',
|
||||
'help.codexlens.subtitle': 'Powerful code indexing and semantic search tool',
|
||||
|
||||
@@ -226,6 +226,7 @@ const i18n = {
|
||||
'codexlens.migrationWarning': 'After changing the path, existing indexes will need to be re-initialized for each workspace.',
|
||||
'codexlens.actions': 'Actions',
|
||||
'codexlens.initializeIndex': 'Initialize Index',
|
||||
'codexlens.cleanCurrentWorkspace': 'Clean Current Workspace',
|
||||
'codexlens.cleanAllIndexes': 'Clean All Indexes',
|
||||
'codexlens.installCodexLens': 'Install CodexLens',
|
||||
'codexlens.testSearch': 'Test Search',
|
||||
@@ -249,8 +250,10 @@ const i18n = {
|
||||
'codexlens.configSaved': 'Configuration saved successfully',
|
||||
'codexlens.pathEmpty': 'Index directory path cannot be empty',
|
||||
'codexlens.cleanConfirm': 'Are you sure you want to clean all CodexLens indexes? This cannot be undone.',
|
||||
'codexlens.cleanCurrentWorkspaceConfirm': 'Are you sure you want to clean the current workspace index? This cannot be undone.',
|
||||
'codexlens.cleaning': 'Cleaning indexes...',
|
||||
'codexlens.cleanSuccess': 'All indexes cleaned successfully',
|
||||
'codexlens.cleanCurrentWorkspaceSuccess': 'Current workspace index cleaned successfully',
|
||||
'codexlens.cleanFailed': 'Failed to clean indexes',
|
||||
'codexlens.loadingConfig': 'Loading configuration...',
|
||||
|
||||
@@ -484,6 +487,10 @@ const i18n = {
|
||||
'mcp.description': 'Description',
|
||||
'mcp.category': 'Category',
|
||||
'mcp.installTo': 'Install To',
|
||||
'mcp.configTarget': 'Config Target',
|
||||
'mcp.clickToSwitch': 'Click to switch',
|
||||
'mcp.usingMcpJson': 'Using .mcp.json',
|
||||
'mcp.usingClaudeJson': 'Using claude.json',
|
||||
'mcp.cwd': 'Working Directory',
|
||||
'mcp.httpHeaders': 'HTTP Headers',
|
||||
'mcp.error': 'Error',
|
||||
@@ -1335,6 +1342,7 @@ const i18n = {
|
||||
'codexlens.migrationWarning': '更改路径后,需要为每个工作区重新初始化索引。',
|
||||
'codexlens.actions': '操作',
|
||||
'codexlens.initializeIndex': '初始化索引',
|
||||
'codexlens.cleanCurrentWorkspace': '清理当前工作空间',
|
||||
'codexlens.cleanAllIndexes': '清理所有索引',
|
||||
'codexlens.installCodexLens': '安装 CodexLens',
|
||||
'codexlens.testSearch': '测试搜索',
|
||||
@@ -1358,8 +1366,10 @@ const i18n = {
|
||||
'codexlens.configSaved': '配置保存成功',
|
||||
'codexlens.pathEmpty': '索引目录路径不能为空',
|
||||
'codexlens.cleanConfirm': '确定要清理所有 CodexLens 索引吗?此操作无法撤销。',
|
||||
'codexlens.cleanCurrentWorkspaceConfirm': '确定要清理当前工作空间的索引吗?此操作无法撤销。',
|
||||
'codexlens.cleaning': '清理索引中...',
|
||||
'codexlens.cleanSuccess': '所有索引已成功清理',
|
||||
'codexlens.cleanCurrentWorkspaceSuccess': '当前工作空间索引已成功清理',
|
||||
'codexlens.cleanFailed': '清理索引失败',
|
||||
'codexlens.loadingConfig': '加载配置中...',
|
||||
|
||||
@@ -1590,6 +1600,10 @@ const i18n = {
|
||||
'mcp.description': '描述',
|
||||
'mcp.category': '分类',
|
||||
'mcp.installTo': '安装到',
|
||||
'mcp.configTarget': '配置目标',
|
||||
'mcp.clickToSwitch': '点击切换',
|
||||
'mcp.usingMcpJson': '使用 .mcp.json',
|
||||
'mcp.usingClaudeJson': '使用 claude.json',
|
||||
'mcp.cwd': '工作目录',
|
||||
'mcp.httpHeaders': 'HTTP 头',
|
||||
'mcp.error': '错误',
|
||||
|
||||
@@ -1613,6 +1613,9 @@ function buildCodexLensConfigContent(config) {
|
||||
? '<button class="btn-sm btn-outline" onclick="event.stopPropagation(); initCodexLensIndex()">' +
|
||||
'<i data-lucide="database" class="w-3 h-3"></i> ' + t('codexlens.initializeIndex') +
|
||||
'</button>' +
|
||||
'<button class="btn-sm btn-outline" onclick="event.stopPropagation(); cleanCurrentWorkspaceIndex()">' +
|
||||
'<i data-lucide="folder-x" class="w-3 h-3"></i> ' + t('codexlens.cleanCurrentWorkspace') +
|
||||
'</button>' +
|
||||
'<button class="btn-sm btn-outline" onclick="event.stopPropagation(); cleanCodexLensIndexes()">' +
|
||||
'<i data-lucide="trash" class="w-3 h-3"></i> ' + t('codexlens.cleanAllIndexes') +
|
||||
'</button>' +
|
||||
@@ -2014,7 +2017,48 @@ async function deleteModel(profile) {
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clean current workspace index
|
||||
*/
|
||||
async function cleanCurrentWorkspaceIndex() {
|
||||
if (!confirm(t('codexlens.cleanCurrentWorkspaceConfirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
showRefreshToast(t('codexlens.cleaning'), 'info');
|
||||
|
||||
// Get current workspace path (projectPath is a global variable from state.js)
|
||||
var workspacePath = projectPath;
|
||||
|
||||
var response = await fetch('/api/codexlens/clean', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: workspacePath })
|
||||
});
|
||||
|
||||
var result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showRefreshToast(t('codexlens.cleanCurrentWorkspaceSuccess'), 'success');
|
||||
|
||||
// Refresh status
|
||||
if (typeof loadCodexLensStatus === 'function') {
|
||||
await loadCodexLensStatus();
|
||||
renderToolsSection();
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
} else {
|
||||
showRefreshToast(t('codexlens.cleanFailed') + ': ' + result.error, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean all CodexLens indexes
|
||||
*/
|
||||
async function cleanCodexLensIndexes() {
|
||||
if (!confirm(t('codexlens.cleanConfirm'))) {
|
||||
return;
|
||||
|
||||
@@ -109,6 +109,9 @@ function buildCodexLensConfigContent(config) {
|
||||
? '<button class="btn-sm btn-outline" onclick="initCodexLensIndex()">' +
|
||||
'<i data-lucide="database" class="w-3 h-3"></i> ' + t('codexlens.initializeIndex') +
|
||||
'</button>' +
|
||||
'<button class="btn-sm btn-outline" onclick="cleanCurrentWorkspaceIndex()">' +
|
||||
'<i data-lucide="folder-x" class="w-3 h-3"></i> ' + t('codexlens.cleanCurrentWorkspace') +
|
||||
'</button>' +
|
||||
'<button class="btn-sm btn-outline" onclick="cleanCodexLensIndexes()">' +
|
||||
'<i data-lucide="trash" class="w-3 h-3"></i> ' + t('codexlens.cleanAllIndexes') +
|
||||
'</button>' +
|
||||
@@ -559,6 +562,45 @@ function uninstallCodexLens() {
|
||||
openCliUninstallWizard('codexlens');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean current workspace index
|
||||
*/
|
||||
async function cleanCurrentWorkspaceIndex() {
|
||||
if (!confirm(t('codexlens.cleanCurrentWorkspaceConfirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
showRefreshToast(t('codexlens.cleaning'), 'info');
|
||||
|
||||
// Get current workspace path (projectPath is a global variable from state.js)
|
||||
var workspacePath = projectPath;
|
||||
|
||||
var response = await fetch('/api/codexlens/clean', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: workspacePath })
|
||||
});
|
||||
|
||||
var result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showRefreshToast(t('codexlens.cleanCurrentWorkspaceSuccess'), 'success');
|
||||
|
||||
// Refresh status
|
||||
if (typeof loadCodexLensStatus === 'function') {
|
||||
await loadCodexLensStatus();
|
||||
renderToolsSection();
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
} else {
|
||||
showRefreshToast(t('codexlens.cleanFailed') + ': ' + result.error, 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean all CodexLens indexes
|
||||
*/
|
||||
|
||||
@@ -14,7 +14,7 @@ var activeHelpTab = 'cli';
|
||||
var helpSearchQuery = '';
|
||||
var helpSearchTimeout = null;
|
||||
var cytoscapeInstance = null;
|
||||
var activeWorkflowDiagram = 'tdd';
|
||||
var activeWorkflowDiagram = 'decision';
|
||||
|
||||
// ========== Main Render Function ==========
|
||||
async function renderHelpView() {
|
||||
@@ -377,18 +377,27 @@ function renderWorkflowDiagrams() {
|
||||
<div class="mb-4">
|
||||
<h3 class="text-lg font-semibold text-foreground mb-3">${ht('help.diagrams.title')}</h3>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="decision">
|
||||
${ht('help.diagrams.decision')}
|
||||
</button>
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="brainstorm">
|
||||
${ht('help.diagrams.brainstorm')}
|
||||
</button>
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="cli-resume">
|
||||
${ht('help.diagrams.cliResume')}
|
||||
</button>
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="bug-fix">
|
||||
${ht('help.diagrams.bugFix')}
|
||||
</button>
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="lite">
|
||||
${ht('help.diagrams.lite')}
|
||||
</button>
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="plan-full">
|
||||
${ht('help.diagrams.planFull')}
|
||||
</button>
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="tdd">
|
||||
${ht('help.diagrams.tdd')}
|
||||
</button>
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="feature">
|
||||
${ht('help.diagrams.feature')}
|
||||
</button>
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="bugfix">
|
||||
${ht('help.diagrams.bugfix')}
|
||||
</button>
|
||||
<button class="workflow-diagram-btn px-4 py-2 rounded-lg text-sm font-medium transition-colors" data-workflow="review">
|
||||
${ht('help.diagrams.review')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -507,6 +516,17 @@ function initializeCytoscapeDiagram(workflow) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get computed CSS variable values
|
||||
var rootStyles = getComputedStyle(document.documentElement);
|
||||
var primaryColor = rootStyles.getPropertyValue('--primary').trim();
|
||||
var foregroundColor = rootStyles.getPropertyValue('--foreground').trim();
|
||||
var mutedColor = rootStyles.getPropertyValue('--muted-foreground').trim();
|
||||
|
||||
// Convert HSL values to usable format
|
||||
var primaryHsl = primaryColor ? 'hsl(' + primaryColor + ')' : '#3B82F6';
|
||||
var foregroundHsl = foregroundColor ? 'hsl(' + foregroundColor + ')' : '#1F2937';
|
||||
var mutedHsl = mutedColor ? 'hsl(' + mutedColor + ')' : '#6B7280';
|
||||
|
||||
// Initialize Cytoscape
|
||||
cytoscapeInstance = cytoscape({
|
||||
container: container,
|
||||
@@ -515,43 +535,68 @@ function initializeCytoscapeDiagram(workflow) {
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'background-color': 'hsl(var(--primary))',
|
||||
'shape': 'roundrectangle',
|
||||
'background-color': primaryHsl,
|
||||
'background-opacity': 0.9,
|
||||
'border-width': 2,
|
||||
'border-color': primaryHsl,
|
||||
'border-opacity': 1,
|
||||
'label': 'data(label)',
|
||||
'color': 'hsl(var(--foreground))',
|
||||
'color': '#FFFFFF',
|
||||
'text-valign': 'center',
|
||||
'text-halign': 'center',
|
||||
'font-size': '12px',
|
||||
'width': '80px',
|
||||
'height': '80px',
|
||||
'font-size': '14px',
|
||||
'font-weight': '600',
|
||||
'width': '140px',
|
||||
'height': '60px',
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': '70px'
|
||||
'text-max-width': '130px',
|
||||
'padding': '8px',
|
||||
'shadow-blur': 10,
|
||||
'shadow-color': '#000000',
|
||||
'shadow-opacity': 0.2,
|
||||
'shadow-offset-x': 0,
|
||||
'shadow-offset-y': 2
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'width': 2,
|
||||
'line-color': 'hsl(var(--muted-foreground))',
|
||||
'target-arrow-color': 'hsl(var(--muted-foreground))',
|
||||
'width': 3,
|
||||
'line-color': mutedHsl,
|
||||
'target-arrow-color': mutedHsl,
|
||||
'target-arrow-shape': 'triangle',
|
||||
'target-arrow-fill': 'filled',
|
||||
'arrow-scale': 1.5,
|
||||
'curve-style': 'bezier',
|
||||
'label': 'data(label)',
|
||||
'font-size': '10px',
|
||||
'color': 'hsl(var(--muted-foreground))'
|
||||
'font-size': '12px',
|
||||
'font-weight': '500',
|
||||
'color': foregroundHsl,
|
||||
'text-background-color': '#FFFFFF',
|
||||
'text-background-opacity': 0.9,
|
||||
'text-background-padding': '4px',
|
||||
'text-background-shape': 'roundrectangle',
|
||||
'text-border-width': 1,
|
||||
'text-border-color': mutedHsl,
|
||||
'text-border-opacity': 0.3
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge.prerequisite',
|
||||
style: {
|
||||
'line-color': 'hsl(var(--primary))',
|
||||
'target-arrow-color': 'hsl(var(--primary))'
|
||||
'line-color': primaryHsl,
|
||||
'target-arrow-color': primaryHsl,
|
||||
'width': 3
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge.next-step',
|
||||
style: {
|
||||
'line-color': '#10B981',
|
||||
'target-arrow-color': '#10B981'
|
||||
'target-arrow-color': '#10B981',
|
||||
'width': 3,
|
||||
'line-style': 'solid'
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -559,15 +604,20 @@ function initializeCytoscapeDiagram(workflow) {
|
||||
style: {
|
||||
'line-color': '#F59E0B',
|
||||
'target-arrow-color': '#F59E0B',
|
||||
'line-style': 'dashed'
|
||||
'line-style': 'dashed',
|
||||
'line-dash-pattern': [10, 5],
|
||||
'width': 2.5
|
||||
}
|
||||
}
|
||||
],
|
||||
layout: {
|
||||
name: 'dagre',
|
||||
rankDir: 'TB',
|
||||
nodeSep: 50,
|
||||
rankSep: 80
|
||||
name: 'breadthfirst',
|
||||
directed: true,
|
||||
padding: 80,
|
||||
spacingFactor: 2,
|
||||
avoidOverlap: true,
|
||||
nodeDimensionsIncludeLabels: true,
|
||||
animate: false
|
||||
}
|
||||
});
|
||||
|
||||
@@ -583,83 +633,156 @@ function initializeCytoscapeDiagram(workflow) {
|
||||
}
|
||||
|
||||
function getWorkflowGraphData(workflow) {
|
||||
var nodes = [];
|
||||
var edges = [];
|
||||
|
||||
var workflows = {
|
||||
'tdd': ['workflow:tdd-plan', 'workflow:execute', 'workflow:tdd-verify'],
|
||||
'feature': ['workflow:plan', 'workflow:action-plan-verify', 'workflow:execute', 'workflow:review'],
|
||||
'bugfix': ['workflow:lite-fix', 'workflow:lite-execute', 'workflow:test-cycle-execute'],
|
||||
'review': ['workflow:review-session-cycle', 'workflow:review-fix', 'workflow:test-cycle-execute']
|
||||
'decision': {
|
||||
nodes: [
|
||||
{ data: { id: 'start', label: ht('help.workflows.decision.start') } },
|
||||
{ data: { id: 'cli-analyze', label: ht('help.workflows.decision.cliAnalyze') } },
|
||||
{ data: { id: 'understand', label: ht('help.workflows.decision.understand') } },
|
||||
{ data: { id: 'simple', label: ht('help.workflows.decision.simple') } },
|
||||
{ data: { id: 'medium', label: ht('help.workflows.decision.medium') } },
|
||||
{ data: { id: 'complex', label: ht('help.workflows.decision.complex') } },
|
||||
{ data: { id: 'claude-exec', label: ht('help.workflows.decision.claudeExec') } },
|
||||
{ data: { id: 'cli-exec', label: ht('help.workflows.decision.cliExec') } },
|
||||
{ data: { id: 'claude-plan', label: ht('help.workflows.decision.claudePlan') } },
|
||||
{ data: { id: 'lite-plan', label: '/workflow:lite-plan' } },
|
||||
{ data: { id: 'full-plan', label: '/workflow:plan' } }
|
||||
],
|
||||
edges: [
|
||||
{ data: { source: 'start', target: 'cli-analyze' }, classes: 'next-step' },
|
||||
{ data: { source: 'cli-analyze', target: 'understand' }, classes: 'next-step' },
|
||||
{ data: { source: 'understand', target: 'simple' }, classes: 'alternative' },
|
||||
{ data: { source: 'understand', target: 'medium' }, classes: 'alternative' },
|
||||
{ data: { source: 'understand', target: 'complex' }, classes: 'alternative' },
|
||||
{ data: { source: 'simple', target: 'claude-exec', label: '优先' }, classes: 'next-step' },
|
||||
{ data: { source: 'simple', target: 'cli-exec' }, classes: 'alternative' },
|
||||
{ data: { source: 'medium', target: 'claude-plan' }, classes: 'next-step' },
|
||||
{ data: { source: 'medium', target: 'lite-plan' }, classes: 'alternative' },
|
||||
{ data: { source: 'complex', target: 'full-plan' }, classes: 'next-step' }
|
||||
]
|
||||
},
|
||||
'brainstorm': {
|
||||
nodes: [
|
||||
{ data: { id: 'start', label: ht('help.workflows.brainstorm.start') } },
|
||||
{ data: { id: 'question', label: ht('help.workflows.brainstorm.question') } },
|
||||
{ data: { id: 'product', label: ht('help.workflows.brainstorm.product') } },
|
||||
{ data: { id: 'design', label: ht('help.workflows.brainstorm.design') } },
|
||||
{ data: { id: 'brainstorm-product', label: '/workflow:brainstorm:auto-parallel' } },
|
||||
{ data: { id: 'brainstorm-design', label: '/workflow:brainstorm:auto-parallel' } },
|
||||
{ data: { id: 'next', label: ht('help.workflows.brainstorm.next') } }
|
||||
],
|
||||
edges: [
|
||||
{ data: { source: 'start', target: 'question' }, classes: 'next-step' },
|
||||
{ data: { source: 'question', target: 'product' }, classes: 'alternative' },
|
||||
{ data: { source: 'question', target: 'design' }, classes: 'alternative' },
|
||||
{ data: { source: 'product', target: 'brainstorm-product' }, classes: 'next-step' },
|
||||
{ data: { source: 'design', target: 'brainstorm-design' }, classes: 'next-step' },
|
||||
{ data: { source: 'brainstorm-product', target: 'next' }, classes: 'next-step' },
|
||||
{ data: { source: 'brainstorm-design', target: 'next' }, classes: 'next-step' }
|
||||
]
|
||||
},
|
||||
'cli-resume': {
|
||||
nodes: [
|
||||
{ data: { id: 'first-exec', label: ht('help.workflows.cliResume.firstExec') } },
|
||||
{ data: { id: 'save-context', label: ht('help.workflows.cliResume.saveContext') } },
|
||||
{ data: { id: 'resume-cmd', label: ht('help.workflows.cliResume.resumeCmd') } },
|
||||
{ data: { id: 'merge', label: ht('help.workflows.cliResume.merge') } },
|
||||
{ data: { id: 'continue', label: ht('help.workflows.cliResume.continue') } },
|
||||
{ data: { id: 'split-output', label: ht('help.workflows.cliResume.splitOutput') } },
|
||||
{ data: { id: 'complete', label: ht('help.workflows.cliResume.complete') } }
|
||||
],
|
||||
edges: [
|
||||
{ data: { source: 'first-exec', target: 'save-context' }, classes: 'next-step' },
|
||||
{ data: { source: 'save-context', target: 'resume-cmd' }, classes: 'next-step' },
|
||||
{ data: { source: 'resume-cmd', target: 'merge' }, classes: 'next-step' },
|
||||
{ data: { source: 'merge', target: 'continue' }, classes: 'next-step' },
|
||||
{ data: { source: 'continue', target: 'split-output' }, classes: 'next-step' },
|
||||
{ data: { source: 'split-output', target: 'complete' }, classes: 'next-step' }
|
||||
]
|
||||
},
|
||||
'bug-fix': {
|
||||
nodes: [
|
||||
{ data: { id: 'start', label: ht('help.workflows.bugFix.start') } },
|
||||
{ data: { id: 'cli-analyze', label: ht('help.workflows.bugFix.cliAnalyze') } },
|
||||
{ data: { id: 'lite-fix', label: '/workflow:lite-fix' } },
|
||||
{ data: { id: 'diagnosis', label: ht('help.workflows.bugFix.diagnosis') } },
|
||||
{ data: { id: 'impact', label: ht('help.workflows.bugFix.impact') } },
|
||||
{ data: { id: 'strategy', label: ht('help.workflows.bugFix.strategy') } },
|
||||
{ data: { id: 'execute', label: ht('help.workflows.bugFix.execute') } },
|
||||
{ data: { id: 'complete', label: ht('help.workflows.bugFix.complete') } }
|
||||
],
|
||||
edges: [
|
||||
{ data: { source: 'start', target: 'cli-analyze' }, classes: 'next-step' },
|
||||
{ data: { source: 'cli-analyze', target: 'lite-fix' }, classes: 'next-step' },
|
||||
{ data: { source: 'lite-fix', target: 'diagnosis' }, classes: 'next-step' },
|
||||
{ data: { source: 'diagnosis', target: 'impact' }, classes: 'next-step' },
|
||||
{ data: { source: 'impact', target: 'strategy' }, classes: 'next-step' },
|
||||
{ data: { source: 'strategy', target: 'execute' }, classes: 'next-step' },
|
||||
{ data: { source: 'execute', target: 'complete' }, classes: 'next-step' }
|
||||
]
|
||||
},
|
||||
'plan-full': {
|
||||
nodes: [
|
||||
{ data: { id: 'start', label: ht('help.workflows.planFull.start') } },
|
||||
{ data: { id: 'cli-analyze', label: ht('help.workflows.planFull.cliAnalyze') } },
|
||||
{ data: { id: 'plan', label: '/workflow:plan' } },
|
||||
{ data: { id: 'verify', label: '/workflow:action-plan-verify' } },
|
||||
{ data: { id: 'execute', label: '/workflow:execute' } },
|
||||
{ data: { id: 'test', label: '/workflow:test-gen' } },
|
||||
{ data: { id: 'review', label: '/workflow:review' } },
|
||||
{ data: { id: 'complete', label: '/workflow:session:complete' } }
|
||||
],
|
||||
edges: [
|
||||
{ data: { source: 'start', target: 'cli-analyze' }, classes: 'next-step' },
|
||||
{ data: { source: 'cli-analyze', target: 'plan' }, classes: 'next-step' },
|
||||
{ data: { source: 'plan', target: 'verify' }, classes: 'next-step' },
|
||||
{ data: { source: 'verify', target: 'execute' }, classes: 'next-step' },
|
||||
{ data: { source: 'execute', target: 'test' }, classes: 'next-step' },
|
||||
{ data: { source: 'test', target: 'review' }, classes: 'next-step' },
|
||||
{ data: { source: 'review', target: 'complete' }, classes: 'next-step' }
|
||||
]
|
||||
},
|
||||
'lite': {
|
||||
nodes: [
|
||||
{ data: { id: 'start', label: ht('help.workflows.lite.start') } },
|
||||
{ data: { id: 'lite-plan', label: '/workflow:lite-plan' } },
|
||||
{ data: { id: 'confirm', label: ht('help.workflows.lite.confirm') } },
|
||||
{ data: { id: 'lite-execute', label: '/workflow:lite-execute' } },
|
||||
{ data: { id: 'complete', label: ht('help.workflows.lite.complete') } }
|
||||
],
|
||||
edges: [
|
||||
{ data: { source: 'start', target: 'lite-plan' }, classes: 'next-step' },
|
||||
{ data: { source: 'lite-plan', target: 'confirm' }, classes: 'next-step' },
|
||||
{ data: { source: 'confirm', target: 'lite-execute' }, classes: 'next-step' },
|
||||
{ data: { source: 'lite-execute', target: 'complete' }, classes: 'next-step' }
|
||||
]
|
||||
},
|
||||
'tdd': {
|
||||
nodes: [
|
||||
{ data: { id: 'start', label: ht('help.workflows.tdd.start') } },
|
||||
{ data: { id: 'tdd-plan', label: '/workflow:tdd-plan' } },
|
||||
{ data: { id: 'red', label: ht('help.workflows.tdd.red') } },
|
||||
{ data: { id: 'green', label: ht('help.workflows.tdd.green') } },
|
||||
{ data: { id: 'refactor', label: ht('help.workflows.tdd.refactor') } },
|
||||
{ data: { id: 'verify', label: '/workflow:tdd-verify' } },
|
||||
{ data: { id: 'complete', label: ht('help.workflows.tdd.complete') } }
|
||||
],
|
||||
edges: [
|
||||
{ data: { source: 'start', target: 'tdd-plan' }, classes: 'next-step' },
|
||||
{ data: { source: 'tdd-plan', target: 'red' }, classes: 'next-step' },
|
||||
{ data: { source: 'red', target: 'green' }, classes: 'next-step' },
|
||||
{ data: { source: 'green', target: 'refactor' }, classes: 'next-step' },
|
||||
{ data: { source: 'refactor', target: 'verify' }, classes: 'next-step' },
|
||||
{ data: { source: 'verify', target: 'complete' }, classes: 'next-step' }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var workflowCommands = workflows[workflow] || workflows['tdd'];
|
||||
|
||||
var workflowData = workflows[workflow] || workflows['decision'];
|
||||
console.log('Building workflow diagram for:', workflow);
|
||||
console.log('Commands:', workflowCommands);
|
||||
console.log('Available workflows data:', helpData.workflows ? Object.keys(helpData.workflows).length + ' commands' : 'no data');
|
||||
console.log('Generated graph:', workflowData.nodes.length, 'nodes,', workflowData.edges.length, 'edges');
|
||||
|
||||
// Build graph from workflow relationships
|
||||
workflowCommands.forEach(function(cmd) {
|
||||
nodes.push({ data: { id: cmd, label: cmd.replace('workflow:', '').replace('task:', '') } });
|
||||
|
||||
var relationships = helpData.workflows ? helpData.workflows[cmd] : null;
|
||||
if (relationships) {
|
||||
// Add prerequisites
|
||||
if (relationships.prerequisites) {
|
||||
relationships.prerequisites.forEach(function(prereq) {
|
||||
if (!nodes.find(n => n.data.id === prereq)) {
|
||||
nodes.push({ data: { id: prereq, label: prereq.replace('workflow:', '').replace('task:', '') } });
|
||||
}
|
||||
edges.push({
|
||||
data: { source: prereq, target: cmd, label: 'requires' },
|
||||
classes: 'prerequisite'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add next steps
|
||||
if (relationships.next_steps) {
|
||||
relationships.next_steps.forEach(function(next) {
|
||||
if (!nodes.find(n => n.data.id === next)) {
|
||||
nodes.push({ data: { id: next, label: next.replace('workflow:', '').replace('task:', '') } });
|
||||
}
|
||||
edges.push({
|
||||
data: { source: cmd, target: next, label: 'then' },
|
||||
classes: 'next-step'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add alternatives
|
||||
if (relationships.alternatives) {
|
||||
relationships.alternatives.forEach(function(alt) {
|
||||
if (!nodes.find(n => n.data.id === alt)) {
|
||||
nodes.push({ data: { id: alt, label: alt.replace('workflow:', '').replace('task:', '') } });
|
||||
}
|
||||
edges.push({
|
||||
data: { source: cmd, target: alt, label: 'or' },
|
||||
classes: 'alternative'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Generated graph:', nodes.length, 'nodes,', edges.length, 'edges');
|
||||
|
||||
// If no edges but we have nodes, create a simple chain
|
||||
if (edges.length === 0 && nodes.length > 1) {
|
||||
console.log('No relationships found, creating simple chain');
|
||||
for (var i = 0; i < nodes.length - 1; i++) {
|
||||
edges.push({
|
||||
data: { source: nodes[i].data.id, target: nodes[i + 1].data.id },
|
||||
classes: 'next-step'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return nodes.concat(edges);
|
||||
return workflowData.nodes.concat(workflowData.edges);
|
||||
}
|
||||
|
||||
function showCommandTooltip(commandName, node) {
|
||||
|
||||
@@ -461,17 +461,23 @@ async function renderMcpManager() {
|
||||
onclick="openMcpCreateModal('project')">
|
||||
<span>+</span> ${t('mcp.newProjectServer')}
|
||||
</button>
|
||||
<!-- Project Config Type Toggle -->
|
||||
<button class="project-config-toggle inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg border cursor-pointer transition-all hover:shadow-md"
|
||||
onclick="toggleProjectConfigType()"
|
||||
title="${t('mcp.clickToSwitch')}"
|
||||
style="${getPreferredProjectConfigType() === 'mcp'
|
||||
? 'background: rgba(34, 197, 94, 0.1); border-color: rgba(34, 197, 94, 0.3); color: rgb(34, 197, 94);'
|
||||
: 'background: rgba(59, 130, 246, 0.1); border-color: rgba(59, 130, 246, 0.3); color: rgb(59, 130, 246);'}">
|
||||
<i data-lucide="${getPreferredProjectConfigType() === 'mcp' ? 'file-json' : 'settings'}" class="w-3.5 h-3.5"></i>
|
||||
<span>${getPreferredProjectConfigType() === 'mcp' ? '.mcp.json' : 'claude.json'}</span>
|
||||
<i data-lucide="chevrons-up-down" class="w-3 h-3 opacity-50"></i>
|
||||
</button>
|
||||
${hasMcpJson ? `
|
||||
<span class="inline-flex items-center gap-1.5 px-2 py-1 text-xs bg-success/10 text-success rounded-md border border-success/20">
|
||||
<i data-lucide="file-check" class="w-3.5 h-3.5"></i>
|
||||
.mcp.json
|
||||
<span class="inline-flex items-center gap-1 px-1.5 py-0.5 text-[10px] bg-success/10 text-success rounded border border-success/20">
|
||||
<i data-lucide="check" class="w-2.5 h-2.5"></i>
|
||||
exists
|
||||
</span>
|
||||
` : `
|
||||
<span class="inline-flex items-center gap-1.5 px-2 py-1 text-xs bg-muted text-muted-foreground rounded-md border border-border" title="New servers will create .mcp.json">
|
||||
<i data-lucide="file-plus" class="w-3.5 h-3.5"></i>
|
||||
Will use .mcp.json
|
||||
</span>
|
||||
`}
|
||||
` : ''}
|
||||
</div>
|
||||
<span class="text-sm text-muted-foreground">${projectAvailableEntries.length} ${t('mcp.serversAvailable')}</span>
|
||||
</div>
|
||||
@@ -569,30 +575,46 @@ async function renderMcpManager() {
|
||||
<span class="text-sm text-muted-foreground">${mcpTemplates.length} ${t('mcp.savedTemplates')}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div class="mcp-server-grid grid gap-3">
|
||||
${mcpTemplates.map(template => `
|
||||
<div class="mcp-template-card bg-card border border-border rounded-lg p-4 hover:shadow-md transition-all">
|
||||
<div class="mcp-template-card mcp-server-card bg-card border border-border border-dashed rounded-lg p-4 hover:shadow-md hover:border-solid transition-all">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h4 class="font-semibold text-foreground truncate flex items-center gap-2">
|
||||
<i data-lucide="layout-template" class="w-4 h-4 shrink-0"></i>
|
||||
<span class="truncate">${escapeHtml(template.name)}</span>
|
||||
</h4>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<span><i data-lucide="layout-template" class="w-5 h-5 text-muted-foreground"></i></span>
|
||||
<h4 class="font-semibold text-foreground">${escapeHtml(template.name)}</h4>
|
||||
${template.description ? `
|
||||
<p class="text-xs text-muted-foreground mt-1 line-clamp-2">${escapeHtml(template.description)}</p>
|
||||
<span class="text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded-full truncate max-w-32" title="${escapeHtml(template.description)}">
|
||||
${escapeHtml(template.description)}
|
||||
</span>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="px-3 py-1 text-xs bg-primary text-primary-foreground rounded hover:opacity-90 transition-opacity"
|
||||
data-template-name="${escapeHtml(template.name)}"
|
||||
data-scope="project"
|
||||
data-action="install-template"
|
||||
title="${t('mcp.installToProject')}">
|
||||
<i data-lucide="folder-plus" class="w-3.5 h-3.5 inline"></i>
|
||||
</button>
|
||||
<button class="px-3 py-1 text-xs bg-success text-success-foreground rounded hover:opacity-90 transition-opacity"
|
||||
data-template-name="${escapeHtml(template.name)}"
|
||||
data-scope="global"
|
||||
data-action="install-template"
|
||||
title="${t('mcp.installToGlobal')}">
|
||||
<i data-lucide="globe" class="w-3.5 h-3.5 inline"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mcp-server-details text-sm space-y-1 mb-3">
|
||||
<div class="mcp-server-details text-sm space-y-1">
|
||||
<div class="flex items-center gap-2 text-muted-foreground">
|
||||
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded">${t('mcp.cmd')}</span>
|
||||
<span class="truncate text-xs" title="${escapeHtml(template.serverConfig.command)}">${escapeHtml(template.serverConfig.command)}</span>
|
||||
<span class="truncate" title="${escapeHtml(template.serverConfig.command)}">${escapeHtml(template.serverConfig.command)}</span>
|
||||
</div>
|
||||
${template.serverConfig.args && template.serverConfig.args.length > 0 ? `
|
||||
<div class="flex items-start gap-2 text-muted-foreground">
|
||||
<span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0">${t('mcp.args')}</span>
|
||||
<span class="text-xs font-mono truncate" title="${escapeHtml(template.serverConfig.args.join(' '))}">${escapeHtml(template.serverConfig.args.slice(0, 2).join(' '))}${template.serverConfig.args.length > 2 ? '...' : ''}</span>
|
||||
<span class="text-xs font-mono truncate" title="${escapeHtml(template.serverConfig.args.join(' '))}">${escapeHtml(template.serverConfig.args.slice(0, 3).join(' '))}${template.serverConfig.args.length > 3 ? '...' : ''}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
@@ -45,6 +45,12 @@ const ParamsSchema = z.object({
|
||||
mode: z.enum(['auto', 'text', 'semantic', 'exact', 'fuzzy', 'hybrid', 'vector', 'pure-vector']).default('auto'),
|
||||
languages: z.array(z.string()).optional(),
|
||||
limit: z.number().default(20),
|
||||
// Additional fields for internal functions
|
||||
file: z.string().optional(),
|
||||
key: z.string().optional(),
|
||||
value: z.string().optional(),
|
||||
newPath: z.string().optional(),
|
||||
all: z.boolean().optional(),
|
||||
});
|
||||
|
||||
type Params = z.infer<typeof ParamsSchema>;
|
||||
|
||||
@@ -18,7 +18,7 @@ import * as convertTokensToCssMod from './convert-tokens-to-css.js';
|
||||
import * as sessionManagerMod from './session-manager.js';
|
||||
import * as cliExecutorMod from './cli-executor.js';
|
||||
import * as smartSearchMod from './smart-search.js';
|
||||
import * as codexLensMod from './codex-lens.js';
|
||||
// codex_lens removed - functionality integrated into smart_search
|
||||
import * as readFileMod from './read-file.js';
|
||||
|
||||
// Import legacy JS tools
|
||||
@@ -297,7 +297,7 @@ registerTool(toLegacyTool(convertTokensToCssMod));
|
||||
registerTool(toLegacyTool(sessionManagerMod));
|
||||
registerTool(toLegacyTool(cliExecutorMod));
|
||||
registerTool(toLegacyTool(smartSearchMod));
|
||||
registerTool(toLegacyTool(codexLensMod));
|
||||
// codex_lens removed - functionality integrated into smart_search
|
||||
registerTool(toLegacyTool(readFileMod));
|
||||
|
||||
// Register legacy JS tools
|
||||
|
||||
Reference in New Issue
Block a user