From ccb981b0f6ddd5ded80d2d13d8b97accdd63afbf Mon Sep 17 00:00:00 2001 From: Dmitry Stoyanov Date: Fri, 13 Feb 2026 18:17:09 +0300 Subject: [PATCH 1/4] feat: Update Claude code support to new release 2.1.41 --- src/agents/plugins/claude/claude.plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/plugins/claude/claude.plugin.ts b/src/agents/plugins/claude/claude.plugin.ts index 2121b3e..a0d84f8 100644 --- a/src/agents/plugins/claude/claude.plugin.ts +++ b/src/agents/plugins/claude/claude.plugin.ts @@ -28,7 +28,7 @@ import { * * **UPDATE THIS WHEN BUMPING CLAUDE VERSION** */ -const CLAUDE_SUPPORTED_VERSION = '2.1.31'; +const CLAUDE_SUPPORTED_VERSION = '2.1.41'; /** * Claude Code installer URLs From d93c63674734359256f405d45c35a0f22b1e6aa5 Mon Sep 17 00:00:00 2001 From: Dmitry Stoyanov Date: Sat, 14 Feb 2026 16:13:51 +0300 Subject: [PATCH 2/4] fix: Error handling when claude client is not available in region --- src/agents/plugins/claude/claude.plugin.ts | 5 ++- src/utils/native-installer.ts | 43 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/agents/plugins/claude/claude.plugin.ts b/src/agents/plugins/claude/claude.plugin.ts index a0d84f8..5a737e3 100644 --- a/src/agents/plugins/claude/claude.plugin.ts +++ b/src/agents/plugins/claude/claude.plugin.ts @@ -26,9 +26,12 @@ import { * Supported Claude Code version * Latest version tested and verified with CodeMie backend * + * Latest available versions can be found here: + * https://github.com/anthropics/claude-code/releases + * * **UPDATE THIS WHEN BUMPING CLAUDE VERSION** */ -const CLAUDE_SUPPORTED_VERSION = '2.1.41'; +const CLAUDE_SUPPORTED_VERSION = '2.1.42'; /** * Claude Code installer URLs diff --git a/src/utils/native-installer.ts b/src/utils/native-installer.ts index a624aa3..072a913 100644 --- a/src/utils/native-installer.ts +++ b/src/utils/native-installer.ts @@ -194,6 +194,37 @@ async function verifyInstallation( return null; } +/** + * Detect if installer output contains HTML instead of expected script output + * This occurs when the installer URL returns an HTML page (e.g., region block, + * maintenance page) instead of the actual installer script. Bash then fails + * trying to execute HTML as shell commands. + * + * @param output - Combined stdout/stderr from the installer process + * @returns true if the output contains HTML markers + */ +function isHtmlInstallerResponse(output: string): boolean { + return /]/i.test(output); +} + +/** + * Extract a user-friendly error message from HTML installer response + * Checks for known error patterns (region block, service unavailability) + * and returns an appropriate message. + * + * @param output - Combined stdout/stderr containing HTML content + * @returns User-friendly error message + */ +function detectHtmlErrorMessage(output: string): string { + // Check for region-specific unavailability + if (/unavailable.*region|not.*available.*here|app.unavailable/i.test(output)) { + return 'Claude Code is not available in your current region. Visit https://claude.ai for information about supported regions.'; + } + + // Generic HTML response (maintenance page, unexpected redirect, etc.) + return 'Installer URL returned an HTML page instead of an installation script. The service may be temporarily unavailable or not accessible from your location. Visit https://claude.ai for more information.'; +} + /** * Install agent using native platform installer * Detects platform and executes appropriate installation script @@ -246,6 +277,18 @@ export async function installNativeAgent( // Check if installation succeeded if (result.code !== 0) { + const combinedOutput = `${result.stderr || ''} ${result.stdout || ''}`; + + // Detect HTML response instead of installer script + // This happens when the service returns an error page (e.g., region block) + // instead of the actual installer script, and bash fails trying to parse HTML + if (isHtmlInstallerResponse(combinedOutput)) { + throw new AgentInstallationError( + agentName, + detectHtmlErrorMessage(combinedOutput) + ); + } + // SECURITY: Sanitize output before including in error message // Installer scripts might echo sensitive environment variables const sanitizedOutput = sanitizeValue(result.stderr || result.stdout); From a5e3cf4b6feea515a2642abc1585e5cb698c4264 Mon Sep 17 00:00:00 2001 From: Dmitriy Stoyanov Date: Mon, 16 Feb 2026 11:20:09 +0300 Subject: [PATCH 3/4] fix: Revert claude version update --- src/agents/plugins/claude/claude.plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/plugins/claude/claude.plugin.ts b/src/agents/plugins/claude/claude.plugin.ts index 5a737e3..0b10c44 100644 --- a/src/agents/plugins/claude/claude.plugin.ts +++ b/src/agents/plugins/claude/claude.plugin.ts @@ -31,7 +31,7 @@ import { * * **UPDATE THIS WHEN BUMPING CLAUDE VERSION** */ -const CLAUDE_SUPPORTED_VERSION = '2.1.42'; +const CLAUDE_SUPPORTED_VERSION = '2.1.31'; /** * Claude Code installer URLs From d4b844bfde5a626b36a8bba490c3efa7453c4ae1 Mon Sep 17 00:00:00 2001 From: Dmitry Stoyanov Date: Mon, 16 Feb 2026 12:02:12 +0300 Subject: [PATCH 4/4] feat: added parallelization in checks: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem The codemie doctor command spawned 14-23 subprocesses sequentially. On Windows, process creation is ~10x slower than Unix, causing the command to exceed the 60s test timeout (took ~63.5s). Changes 3 parallelization layers: 1. src/agents/registry.ts — getInstalledAgents() now checks all 5 agents' isInstalled() in parallel via Promise.all() instead of a sequential loop. This was the single biggest bottleneck (4-6 process spawns serialized). 2. src/cli/commands/doctor/checks/AgentsCheck.ts — run() now fetches getVersion() + getInstallationMethod() for all installed agents in parallel instead of one at a time. 3. src/cli/commands/doctor/checks/FrameworksCheck.ts — run() now checks isInstalled() + getVersion() for all frameworks in parallel. 4. src/cli/commands/doctor/index.ts — Restructured the orchestrator into 3 parallel groups: - Group 1: Tool checks (Node, npm, Python, uv, AWS CLI) — all run concurrently - Group 2: AI Config + Provider check — sequential (provider depends on config) - Group 3: Discovery checks (Agents, Workflows, Frameworks) — all run concurrently Results are collected first, then displayed sequentially to preserve output ordering. Before: ~23 subprocess spawns executed one after another After: Same subprocess spawns but organized into parallel batches, reducing wall-clock time from O(n) to roughly O(max-per-group) --- src/agents/plugins/claude/claude.plugin.ts | 3 - src/agents/registry.ts | 17 +- src/cli/commands/doctor/checks/AgentsCheck.ts | 15 +- .../commands/doctor/checks/FrameworksCheck.ts | 14 +- src/cli/commands/doctor/index.ts | 206 +++++++++--------- 5 files changed, 129 insertions(+), 126 deletions(-) diff --git a/src/agents/plugins/claude/claude.plugin.ts b/src/agents/plugins/claude/claude.plugin.ts index 0b10c44..2121b3e 100644 --- a/src/agents/plugins/claude/claude.plugin.ts +++ b/src/agents/plugins/claude/claude.plugin.ts @@ -26,9 +26,6 @@ import { * Supported Claude Code version * Latest version tested and verified with CodeMie backend * - * Latest available versions can be found here: - * https://github.com/anthropics/claude-code/releases - * * **UPDATE THIS WHEN BUMPING CLAUDE VERSION** */ const CLAUDE_SUPPORTED_VERSION = '2.1.31'; diff --git a/src/agents/registry.ts b/src/agents/registry.ts index 5345d63..75fd82b 100644 --- a/src/agents/registry.ts +++ b/src/agents/registry.ts @@ -65,13 +65,16 @@ export class AgentRegistry { static async getInstalledAgents(): Promise { AgentRegistry.initialize(); - const agents: AgentAdapter[] = []; - for (const adapter of AgentRegistry.adapters.values()) { - if (await adapter.isInstalled()) { - agents.push(adapter); - } - } - return agents; + const allAdapters = Array.from(AgentRegistry.adapters.values()); + const installResults = await Promise.all( + allAdapters.map(async (adapter) => ({ + adapter, + installed: await adapter.isInstalled() + })) + ); + return installResults + .filter(({ installed }) => installed) + .map(({ adapter }) => adapter); } /** diff --git a/src/cli/commands/doctor/checks/AgentsCheck.ts b/src/cli/commands/doctor/checks/AgentsCheck.ts index c3aab32..e396ba9 100644 --- a/src/cli/commands/doctor/checks/AgentsCheck.ts +++ b/src/cli/commands/doctor/checks/AgentsCheck.ts @@ -36,12 +36,17 @@ export class AgentsCheck implements ItemWiseHealthCheck { const installedAgents = await AgentRegistry.getInstalledAgents(); if (installedAgents.length > 0) { - for (const agent of installedAgents) { - const version = await agent.getVersion(); - const versionStr = version ? ` (${version})` : ''; + // Parallelize version + installation method checks across all agents + const agentResults = await Promise.all( + installedAgents.map(async (agent) => { + const version = await agent.getVersion(); + const versionStr = version ? ` (${version})` : ''; + const deprecationWarning = await this.checkDeprecatedInstallation(agent, versionStr); + return { agent, versionStr, deprecationWarning }; + }) + ); - // Check for deprecated npm installation - const deprecationWarning = await this.checkDeprecatedInstallation(agent, versionStr); + for (const { agent, versionStr, deprecationWarning } of agentResults) { if (deprecationWarning) { details.push(deprecationWarning); continue; diff --git a/src/cli/commands/doctor/checks/FrameworksCheck.ts b/src/cli/commands/doctor/checks/FrameworksCheck.ts index 5e50028..85d8d77 100644 --- a/src/cli/commands/doctor/checks/FrameworksCheck.ts +++ b/src/cli/commands/doctor/checks/FrameworksCheck.ts @@ -23,10 +23,16 @@ export class FrameworksCheck implements ItemWiseHealthCheck { message: 'No frameworks registered' }); } else { - // Check each framework - for (const framework of frameworks) { - const installed = await framework.isInstalled(); - const version = installed ? await framework.getVersion() : null; + // Check all frameworks in parallel + const frameworkResults = await Promise.all( + frameworks.map(async (framework) => { + const installed = await framework.isInstalled(); + const version = installed ? await framework.getVersion() : null; + return { framework, installed, version }; + }) + ); + + for (const { framework, installed, version } of frameworkResults) { const versionStr = version ? ` (${version})` : ''; if (installed) { diff --git a/src/cli/commands/doctor/index.ts b/src/cli/commands/doctor/index.ts index c23c40a..153bccf 100644 --- a/src/cli/commands/doctor/index.ts +++ b/src/cli/commands/doctor/index.ts @@ -5,7 +5,7 @@ import { Command } from 'commander'; import chalk from 'chalk'; import os from 'os'; -import { HealthCheck, ItemWiseHealthCheck, HealthCheckResult } from './types.js'; +import { HealthCheckResult } from './types.js'; import { HealthCheckFormatter } from './formatter.js'; import { NodeVersionCheck, @@ -79,120 +79,112 @@ export function createDoctorCommand(): Command { // Display header formatter.displayHeader(); - // Define standard health checks - const checks: HealthCheck[] = [ - new NodeVersionCheck(), - new NpmCheck(), - new PythonCheck(), - new UvCheck(), - new AwsCliCheck(), - new AIConfigCheck(), - new AgentsCheck(), - new WorkflowsCheck(), - new FrameworksCheck() - ]; - - // Run and display standard checks immediately - for (const check of checks) { - logger.debug(`=== Running Check: ${check.name} ===`); - const startTime = Date.now(); - - // Check if this is an ItemWiseHealthCheck - const isItemWise = 'runWithItemDisplay' in check; - - if (isItemWise) { - // Display section header - console.log(formatter['getCheckHeader'](check.name)); - - // Run with item-by-item display - const result = await (check as ItemWiseHealthCheck).runWithItemDisplay( - (itemName) => { - logger.debug(` Checking item: ${itemName}`); - formatter.startItem(itemName); - }, - (detail) => { - logger.debug(` Result: ${detail.status} - ${detail.message}`); - formatter.displayItem(detail); - } - ); - results.push(result); - - const elapsed = Date.now() - startTime; - logger.debug(`Check completed in ${elapsed}ms: ${result.success ? 'SUCCESS' : 'FAILED'}`); - logger.debug(''); - - // Add blank line after section - console.log(); - } else { - // Regular check with section-level progress - formatter.startCheck(check.name); - const result = await check.run((message) => { - logger.debug(` Progress: ${message}`); - formatter.updateProgress(message); + // Helper to display a pre-computed check result + const displayResult = (result: HealthCheckResult): void => { + formatter.displayCheck(result); + if (result.details && result.details.length > 0) { + result.details.forEach(detail => { + logger.debug(` - ${detail.status}: ${detail.message}`); }); - results.push(result); - formatter.displayCheck(result); - - const elapsed = Date.now() - startTime; - logger.debug(`Check completed in ${elapsed}ms: ${result.success ? 'SUCCESS' : 'FAILED'}`); - if (result.details && result.details.length > 0) { - result.details.forEach(detail => { - logger.debug(` - ${detail.status}: ${detail.message}`); - }); - } - logger.debug(''); } + logger.debug(''); + }; + + // --- Group 1: Independent tool checks (run in parallel) --- + const nodeCheck = new NodeVersionCheck(); + const npmCheck = new NpmCheck(); + const pythonCheck = new PythonCheck(); + const uvCheck = new UvCheck(); + const awsCheck = new AwsCliCheck(); + + logger.debug('=== Running Tool Checks (parallel) ==='); + const toolStartTime = Date.now(); + const [nodeResult, npmResult, pythonResult, uvResult, awsResult] = await Promise.all([ + nodeCheck.run(), + npmCheck.run(), + pythonCheck.run(), + uvCheck.run(), + awsCheck.run() + ]); + logger.debug(`Tool checks completed in ${Date.now() - toolStartTime}ms`); + + // Display tool check results sequentially + for (const result of [nodeResult, npmResult, pythonResult, uvResult, awsResult]) { + results.push(result); + displayResult(result); + } - // After AIConfigCheck, immediately run provider-specific checks - if (check instanceof AIConfigCheck) { - const config = check.getConfig(); - - if (config && config.provider) { - logger.debug(`=== Running Provider Check: ${config.provider} ===`); - logger.debug(`Base URL: ${config.baseUrl}`); - logger.debug(`Model: ${config.model}`); - - // Get health check from ProviderRegistry - const healthCheck = ProviderRegistry.getHealthCheck(config.provider); - - if (healthCheck) { - formatter.startCheck('Provider'); - - try { - const providerStartTime = Date.now(); - const providerResult = await healthCheck.check(config); - const elapsed = Date.now() - providerStartTime; - - logger.debug(`Provider check completed in ${elapsed}ms`); - logger.debug(`Status: ${providerResult.status}`); - - const doctorResult = adaptProviderResult(providerResult); - results.push(doctorResult); - formatter.displayCheckWithHeader(doctorResult); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - logger.error(`Provider check failed: ${errorMessage}`); - if (error instanceof Error && error.stack) { - logger.debug(`Stack trace: ${error.stack}`); - } - - // If check throws, capture error - results.push({ - name: 'Provider Check Error', - success: false, - details: [{ - status: 'error', - message: `Check failed: ${errorMessage}` - }] - }); - } - } else { - logger.debug(`No health check available for provider: ${config.provider}`); + // --- Group 2: AI Config + Provider check (sequential, provider depends on config) --- + const aiConfigCheck = new AIConfigCheck(); + logger.debug('=== Running Check: Active Profile ==='); + const configStartTime = Date.now(); + const configResult = await aiConfigCheck.run(); + logger.debug(`Check completed in ${Date.now() - configStartTime}ms`); + results.push(configResult); + displayResult(configResult); + + // Run provider-specific checks if config is available + const config = aiConfigCheck.getConfig(); + if (config && config.provider) { + logger.debug(`=== Running Provider Check: ${config.provider} ===`); + logger.debug(`Base URL: ${config.baseUrl}`); + logger.debug(`Model: ${config.model}`); + + const healthCheck = ProviderRegistry.getHealthCheck(config.provider); + + if (healthCheck) { + formatter.startCheck('Provider'); + + try { + const providerStartTime = Date.now(); + const providerResult = await healthCheck.check(config); + logger.debug(`Provider check completed in ${Date.now() - providerStartTime}ms`); + logger.debug(`Status: ${providerResult.status}`); + + const doctorResult = adaptProviderResult(providerResult); + results.push(doctorResult); + formatter.displayCheckWithHeader(doctorResult); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(`Provider check failed: ${errorMessage}`); + if (error instanceof Error && error.stack) { + logger.debug(`Stack trace: ${error.stack}`); } + + results.push({ + name: 'Provider Check Error', + success: false, + details: [{ + status: 'error', + message: `Check failed: ${errorMessage}` + }] + }); } + } else { + logger.debug(`No health check available for provider: ${config.provider}`); } } + // --- Group 3: Discovery checks (run in parallel) --- + const agentsCheck = new AgentsCheck(); + const workflowsCheck = new WorkflowsCheck(); + const frameworksCheck = new FrameworksCheck(); + + logger.debug('=== Running Discovery Checks (parallel) ==='); + const discoveryStartTime = Date.now(); + const [agentsResult, workflowsResult, frameworksResult] = await Promise.all([ + agentsCheck.run(), + workflowsCheck.run(), + frameworksCheck.run() + ]); + logger.debug(`Discovery checks completed in ${Date.now() - discoveryStartTime}ms`); + + // Display discovery check results sequentially + for (const result of [agentsResult, workflowsResult, frameworksResult]) { + results.push(result); + displayResult(result); + } + logger.debug('=== All Checks Completed ==='); const successCount = results.filter(r => r.success).length; logger.debug(`Passed: ${successCount}/${results.length}`);