UNPKG

@autifyhq/muon

Version:

Muon - AI-Powered Playwright Test Coding Agent with Advanced Test Fixing Capabilities

143 lines (142 loc) 5.27 kB
#!/usr/bin/env node import { resolve } from 'node:path'; import { Command } from 'commander'; import { render } from 'ink'; import React from 'react'; import { MuonAuth } from './auth.js'; import { isTrusted, saveTrustConfig } from './trust-config.js'; import { MuonApp } from './ui/app.js'; import { TrustConfirmation } from './ui/components.js'; const program = new Command(); program .name('muon') .description('Muon - AI-Powered Playwright Test Agent with Advanced Test Fixing Capabilities') .version('0.0.8'); function validateApiKey(apiKey) { if (!apiKey) { return null; } if (!apiKey.startsWith('muon_live_') && !apiKey.startsWith('muon_test_')) { throw new Error('Invalid API key format. API keys should start with "muon_live_" or "muon_test_"'); } return apiKey; } async function getAuthCredentials(cliApiKey, serverUrl) { // First, try to get valid access tokens (prioritize OAuth) const auth = new MuonAuth(serverUrl); const tokens = await auth.getValidTokens(); if (tokens) { return { accessToken: tokens.accessToken, auth }; } // Fallback to API key if no valid access tokens const envApiKey = process.env['MUON_API_KEY']; const apiKey = cliApiKey || envApiKey; if (apiKey) { try { const validatedKey = validateApiKey(apiKey) || apiKey; return { apiKey: validatedKey }; } catch (error) { console.error('❌ Invalid API key format:', error.message); console.error('Get a valid API key from:', `${serverUrl}/keys`); } } console.error('❌ Authentication is required to use Muon CLI'); console.error(''); console.error('Setup instructions:'); console.error('Option 1 - OAuth Authentication (Recommended):'); console.error(' muon login'); console.error(''); console.error('Option 2 - API Key:'); console.error(`1. Get your API key from: ${serverUrl}/keys`); console.error('2. Set the environment variable:'); console.error(' export MUON_API_KEY=your_api_key_here'); console.error(''); console.error('Alternatively, you can pass it directly:'); console.error(' muon --api-key your_api_key_here'); console.error(''); process.exit(1); } async function showTrustConfirmation(projectPath) { return new Promise((resolve) => { const app = render(React.createElement(TrustConfirmation, { projectPath, onConfirm: () => { // Save trust configuration when user confirms const saved = saveTrustConfig(projectPath); if (!saved) { console.warn('Warning: Could not save trust configuration'); } resolve(true); }, onExit: () => { resolve(false); }, })); app.waitUntilExit().then(() => { // Cleanup will happen when the app exits }); }); } program .command('start [projectPath]') .description('Start Muon AI Test Agent with interactive terminal UI') .option('-s, --server-url <url>', 'Server URL', 'https://muon.autify.com') .option('-t, --agent-type <type>', 'Agent type (general)', 'general') .option('-k, --api-key <key>', 'API key (overrides MUON_API_KEY environment variable)') .option('--nlstep', 'Enable natural language step mode for complex/dynamic locators') .action(async (projectPath = '.', options) => { // Resolve the project path to absolute path to handle relative paths like "." const resolvedProjectPath = resolve(projectPath); // Authenticate first const authCredentials = await getAuthCredentials(options.apiKey, options.serverUrl); // Check if directory is already trusted let trusted = isTrusted(resolvedProjectPath); // If not trusted, show trust confirmation dialog after authentication if (!trusted) { trusted = await showTrustConfirmation(resolvedProjectPath); if (!trusted) { console.log('Trust declined. Exiting.'); process.exit(0); } } const app = render(React.createElement(MuonApp, { serverUrl: options.serverUrl, agentType: options.agentType, projectPath: resolvedProjectPath, apiKey: authCredentials.apiKey, accessToken: authCredentials.accessToken, auth: authCredentials.auth, nlstepMode: options.nlstep, })); try { await app.waitUntilExit(); } catch (error) { console.error('Application error:', error); process.exit(1); } }); program .command('login') .description('Authenticate with Muon using OAuth device flow') .option('-s, --server-url <url>', 'Server URL', 'https://muon.autify.com') .action(async (options) => { const auth = new MuonAuth(options.serverUrl); await auth.login(); }); program .command('logout') .description('Sign out from Muon') .action(async () => { const auth = new MuonAuth(''); await auth.logout(); }); program .command('status') .description('Check authentication status') .action(async () => { const auth = new MuonAuth(''); await auth.status(); }); program.parse();