diff --git a/packages/cli/README.md b/packages/cli/README.md index 49a268b..e87b280 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -82,17 +82,19 @@ mycoder config set modelName gpt-4o-2024-05-13 ### Model Selection +NOTE: Anthropic Claude 3.7 works the best by far in our testing. + MyCoder supports Anthropic, OpenAI, xAI/Grok, Mistral AI, and Ollama models. You can configure which model provider and model name to use with the following commands: ```bash +# Use Anthropic models [These work the best at this time] +mycoder config set modelProvider anthropic +mycoder config set modelName claude-3-7-sonnet-20250219 # or any other Anthropic model + # Use OpenAI models mycoder config set modelProvider openai mycoder config set modelName gpt-4o-2024-05-13 # or any other OpenAI model -# Use Anthropic models -mycoder config set modelProvider anthropic -mycoder config set modelName claude-3-7-sonnet-20250219 # or any other Anthropic model - # Use xAI/Grok models mycoder config set modelProvider xai mycoder config set modelName grok-1 # or any other xAI model @@ -117,7 +119,7 @@ mycoder --modelProvider openai --modelName gpt-4o-2024-05-13 "Your prompt here" ### Available Configuration Options -- `githubMode`: Enable GitHub mode for working with issues and PRs (default: `false`) +- `githubMode`: Enable GitHub mode (requires "gh" cli to be installed) for working with issues and PRs (default: `false`) - `headless`: Run browser in headless mode with no UI showing (default: `true`) - `userSession`: Use user's existing browser session instead of sandboxed session (default: `false`) - `pageFilter`: Method to process webpage content: 'simple', 'none', or 'readability' (default: `none`) diff --git a/packages/cli/src/commands/test-profile.ts b/packages/cli/src/commands/test-profile.ts new file mode 100644 index 0000000..50b54e3 --- /dev/null +++ b/packages/cli/src/commands/test-profile.ts @@ -0,0 +1,15 @@ +import { CommandModule } from 'yargs'; + +import { SharedOptions } from '../options.js'; + +export const command: CommandModule = { + command: 'test-profile', + describe: 'Test the profiling feature', + handler: async () => { + console.log('Profile test completed successfully'); + // Profiling report will be automatically displayed by the main function + + // Force a delay to simulate some processing + await new Promise((resolve) => setTimeout(resolve, 100)); + }, +}; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 7a61e28..ead4f8e 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -7,26 +7,49 @@ import { hideBin } from 'yargs/helpers'; import { command as defaultCommand } from './commands/$default.js'; import { command as configCommand } from './commands/config.js'; +import { command as testProfileCommand } from './commands/test-profile.js'; import { command as testSentryCommand } from './commands/test-sentry.js'; import { command as toolsCommand } from './commands/tools.js'; import { sharedOptions } from './options.js'; import { initSentry, captureException } from './sentry/index.js'; -initSentry(); +import { getConfig } from './settings/config.js'; +import { enableProfiling, mark, reportTimings } from './utils/performance.js'; + +mark('After imports'); import type { PackageJson } from 'type-fest'; // Add global declaration for our patched toolAgent +mark('Before sourceMapSupport install'); sourceMapSupport.install(); +mark('After sourceMapSupport install'); const main = async () => { + mark('Main function start'); + dotenv.config(); + mark('After dotenv config'); + + // Only initialize Sentry if needed + if ( + process.env.NODE_ENV !== 'development' || + process.env.ENABLE_SENTRY === 'true' + ) { + initSentry(); + mark('After Sentry init'); + } + mark('Before package.json load'); const require = createRequire(import.meta.url); const packageInfo = require('../package.json') as PackageJson; + mark('After package.json load'); + + console.log('packageInfo', packageInfo); // Set up yargs with the new CLI interface - await yargs(hideBin(process.argv)) + mark('Before yargs setup'); + const argv = await yargs(hideBin(process.argv)) .scriptName(packageInfo.name!) .version(packageInfo.version!) .options(sharedOptions) @@ -35,17 +58,30 @@ const main = async () => { .command([ defaultCommand, testSentryCommand, + testProfileCommand, toolsCommand, configCommand, ] as CommandModule[]) .strict() .showHelpOnFail(true) .help().argv; + + // Get config to check for profile setting + const config = getConfig(); + + // Enable profiling if --profile flag is set or if enabled in config + enableProfiling(Boolean(argv.profile) || Boolean(config.profile)); + mark('After yargs setup'); }; -await main().catch((error) => { - console.error(error); - // Capture the error with Sentry - captureException(error); - process.exit(1); -}); +await main() + .catch(async (error) => { + console.error(error); + // Capture the error with Sentry + captureException(error); + process.exit(1); + }) + .finally(async () => { + // Report timings if profiling is enabled + await reportTimings(); + }); diff --git a/packages/cli/src/options.ts b/packages/cli/src/options.ts index cd875a5..d03f5b3 100644 --- a/packages/cli/src/options.ts +++ b/packages/cli/src/options.ts @@ -9,6 +9,7 @@ export type SharedOptions = { readonly sentryDsn?: string; readonly modelProvider?: string; readonly modelName?: string; + readonly profile?: boolean; }; export const sharedOptions = { @@ -19,6 +20,11 @@ export const sharedOptions = { default: 'info', choices: ['debug', 'verbose', 'info', 'warn', 'error'], } as const, + profile: { + type: 'boolean', + description: 'Enable performance profiling of CLI startup', + default: false, + } as const, modelProvider: { type: 'string', description: 'AI model provider to use', diff --git a/packages/cli/src/settings/config.ts b/packages/cli/src/settings/config.ts index 7f131ac..796e037 100644 --- a/packages/cli/src/settings/config.ts +++ b/packages/cli/src/settings/config.ts @@ -16,6 +16,7 @@ const defaultConfig = { modelName: 'claude-3-7-sonnet-20250219', ollamaBaseUrl: 'http://localhost:11434/api', customPrompt: '', + profile: false, }; export type Config = typeof defaultConfig; diff --git a/packages/cli/src/utils/performance.ts b/packages/cli/src/utils/performance.ts new file mode 100644 index 0000000..97646f6 --- /dev/null +++ b/packages/cli/src/utils/performance.ts @@ -0,0 +1,96 @@ +import { performance } from 'perf_hooks'; + +// Store start time as soon as this module is imported +const cliStartTime = performance.now(); +const timings: Record = {}; +let isEnabled = false; + +/** + * Enable or disable performance tracking + */ +export function enableProfiling(enabled: boolean): void { + isEnabled = enabled; +} + +/** + * Mark a timing point in the application + * Always collect data, but only report if profiling is enabled + */ +export function mark(label: string): void { + // Always collect timing data regardless of whether profiling is enabled + timings[label] = performance.now() - cliStartTime; +} + +/** + * Log all collected performance metrics + */ +export async function reportTimings(): Promise { + if (!isEnabled) return; + + console.log('\n📊 Performance Profile Results'); + console.log('======================='); + console.log( + `${'Label'.padEnd(40, ' ')}${'Time'.padStart(10, ' ')}${'Duration'.padStart(10, ' ')}`, + ); + + // Sort timings by time value + const sortedTimings = Object.entries(timings).sort((a, b) => a[1] - b[1]); + + // Calculate durations between steps + let previousTime = 0; + for (const [label, time] of sortedTimings) { + const duration = time - previousTime; + console.log( + `${label.padEnd(40, ' ')}${`${time.toFixed(2)}ms`.padStart(10, ' ')}${`${duration.toFixed(2)}ms`.padStart(10, ' ')}`, + ); + previousTime = time; + } + + console.log(`Total startup time: ${previousTime.toFixed(2)}ms`); + console.log('=======================\n'); + + // Report platform-specific information if on Windows + if (process.platform === 'win32') { + await reportPlatformInfo(); + } +} + +/** + * Collect and report platform-specific information + */ +async function reportPlatformInfo(): Promise { + if (!isEnabled) return; + + console.log('\n🖥️ Platform Information:'); + console.log('======================='); + console.log(`Platform: ${process.platform}`); + console.log(`Architecture: ${process.arch}`); + console.log(`Node.js version: ${process.version}`); + + // Windows-specific information + if (process.platform === 'win32') { + console.log('Windows-specific details:'); + console.log(`- Current working directory: ${process.cwd()}`); + console.log(`- Path length: ${process.cwd().length} characters`); + + // Check for antivirus markers by measuring file read time + try { + // Using dynamic import to avoid require + const fs = await import('fs'); + const startTime = performance.now(); + fs.readFileSync(process.execPath); + console.log( + `- Time to read Node.js executable: ${(performance.now() - startTime).toFixed(2)}ms`, + ); + } catch (error: unknown) { + const errorMessage = + error instanceof Error ? error.message : String(error); + console.log(`- Error reading Node.js executable: ${errorMessage}`); + } + } + + console.log('=======================\n'); +} + +// Initial mark for module load time +mark('Module initialization'); diff --git a/packages/cli/tests/settings/config.test.ts b/packages/cli/tests/settings/config.test.ts index 0ada2c2..9af4894 100644 --- a/packages/cli/tests/settings/config.test.ts +++ b/packages/cli/tests/settings/config.test.ts @@ -44,6 +44,7 @@ describe('Config', () => { modelProvider: 'anthropic', modelName: 'claude-3-7-sonnet-20250219', ollamaBaseUrl: 'http://localhost:11434/api', + profile: false, customPrompt: '', }); expect(fs.existsSync).toHaveBeenCalledWith(mockConfigFile); @@ -77,6 +78,7 @@ describe('Config', () => { modelProvider: 'anthropic', modelName: 'claude-3-7-sonnet-20250219', ollamaBaseUrl: 'http://localhost:11434/api', + profile: false, customPrompt: '', }); });