tui-runner.tsā¢14.3 kB
import React from 'react';
import { Box, Text } from 'ink';
import * as path from 'path';
import fs from 'fs';
import { createNodeDependencies } from '../adapters/nodejs';
import { CodeIndexManager } from '../code-index/manager';
import { App } from '../examples/tui/App';
import { CliOptions } from './args-parser';
import createSampleFiles from '../examples/create-sample-files';
import { CodebaseMCPServer, createMCPServer } from '../mcp/server';
import { CodebaseHTTPMCPServer } from '../mcp/http-server.js';
// Extract sample files creation from original demo
export function createTUIApp(options: CliOptions) {
const AppWithOptions: React.FC = () => {
const [codeIndexManager, setCodeIndexManager] = React.useState<any>(null);
const [dependencies, setDependencies] = React.useState<any>(null);
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
async function initialize() {
// Ensure options.path is absolute; if not, prepend process.cwd()
let resolvedPath = options.path;
if (!path.isAbsolute(resolvedPath)) {
resolvedPath = path.join(process.cwd(), resolvedPath);
}
// Create workspace path - use demo subdirectory if --demo flag is set
const workspacePath = options.demo
? path.join(resolvedPath, 'demo')
: resolvedPath;
// Use config file from workspace directory
const configPath = options.config || path.join(workspacePath, 'autodev-config.json');
// console.log('[tui-runner]š Workspace path:', workspacePath);
const deps = createNodeDependencies({
workspacePath,
storageOptions: {
globalStoragePath: options.storage || path.join(process.cwd(), '.autodev-storage'),
...(options.cache && { cacheBasePath: options.cache })
},
loggerOptions: {
name: 'Autodev-Codebase-TUI',
level: options.logLevel,
timestamps: true,
colors: true
},
configOptions: {
configPath,
cliOverrides: {
ollamaUrl: options.ollamaUrl,
model: options.model,
qdrantUrl: options.qdrantUrl
}
}
});
try {
// Log workspace path after deps are created so we can use the logger
deps.logger?.info('[tui-runner]š Workspace path:', workspacePath);
// Create demo files if requested
if (options.demo) {
const workspaceExists = await deps.fileSystem.exists(workspacePath);
if (!workspaceExists) {
fs.mkdirSync(workspacePath, { recursive: true });
await createSampleFiles(deps.fileSystem, workspacePath);
deps.logger?.info('[tui-runner]š Demo files created in:', workspacePath);
}
}
deps.logger?.info('[tui-runner]āļø Loading configuration...');
const config = await deps.configProvider.loadConfig();
deps.logger?.info('[tui-runner]š Configuration:', JSON.stringify(config, null, 2));
deps.logger?.info('[tui-runner]ā
Validating configuration...');
const validation = await deps.configProvider.validateConfig();
deps.logger?.info('[tui-runner]š Validation result:', validation);
if (!validation.isValid) {
deps.logger?.warn('[tui-runner]ā ļø Configuration validation warnings:', validation.errors);
deps.logger?.info('[tui-runner]ā ļø Continuing initialization (debug mode)');
} else {
deps.logger?.info('[tui-runner]ā
Configuration validation passed');
}
setDependencies(deps);
deps.logger?.info('Creating CodeIndexManager with dependencies:', {
hasFileSystem: !!deps.fileSystem,
hasStorage: !!deps.storage,
hasEventBus: !!deps.eventBus,
hasWorkspace: !!deps.workspace,
hasPathUtils: !!deps.pathUtils,
hasConfigProvider: !!deps.configProvider,
workspaceRootPath: deps.workspace.getRootPath()
});
const manager = CodeIndexManager.getInstance(deps);
deps.logger?.info('CodeIndexManager instance created:', !!manager);
if (!manager) {
setError('Failed to create CodeIndexManager - workspace root path may be invalid');
return;
}
deps.logger?.info('[tui-runner]āļø Initializing CodeIndexManager...');
const initResult = await manager.initialize({ force: options.force });
deps.logger?.info('[tui-runner]ā
CodeIndexManager initialization success:', initResult);
deps.logger?.info('[tui-runner]š Manager state:', {
isInitialized: manager.isInitialized,
isFeatureEnabled: manager.isFeatureEnabled,
isFeatureConfigured: manager.isFeatureConfigured,
state: manager.state
});
deps.logger?.info('[tui-runner]š Setting CodeIndexManager to state...');
setCodeIndexManager(manager);
deps.logger?.info('[tui-runner]ā
CodeIndexManager set to state');
// Start indexing in background
deps.logger?.info('[tui-runner]š Preparing to start indexing...');
manager.onProgressUpdate((progressInfo) => {
deps.logger?.info('[tui-runner]š Indexing progress:', JSON.stringify(progressInfo));
});
setTimeout(() => {
if (manager.isFeatureEnabled && manager.isInitialized) {
deps.logger?.info('[tui-runner]š Starting indexing process...');
deps.logger?.info('[tui-runner]š Current state:', manager.state);
const indexingTimeout = setTimeout(() => {
deps.logger?.warn('[tui-runner]ā ļø Indexing process timeout (30s), may be stuck');
}, 30000);
manager.startIndexing()
.then(() => {
clearTimeout(indexingTimeout);
deps.logger?.info('[tui-runner]ā
Indexing completed');
})
.catch((err: any) => {
clearTimeout(indexingTimeout);
deps.logger?.error('[tui-runner]ā Indexing failed:', err);
deps.logger?.error('[tui-runner]ā Error stack:', err.stack);
setError(`Indexing failed: ${err.message}`);
});
} else {
deps.logger?.warn('[tui-runner]ā ļø Skipping indexing - feature not enabled or not initialized');
deps.logger?.error('[tui-runner]š Feature state:', {
isFeatureEnabled: manager.isFeatureEnabled,
isInitialized: manager.isInitialized,
state: manager.state
});
}
}, 1000);
deps.logger?.info('[tui-runner]ā
Initialization completed');
} catch (err: any) {
deps.logger?.error('[tui-runner]ā Initialization failed:', err);
deps.logger?.error('[tui-runner]ā Error stack:', err.stack);
setError(`Initialization failed: ${err.message}`);
}
}
initialize();
// Cleanup function to dispose of singleton instances
return () => {
CodeIndexManager.disposeAll();
};
}, []);
if (error) {
return React.createElement(Box, { flexDirection: "column", padding: 1 },
React.createElement(Text, { bold: true, color: "red" }, "X Initialization Failed"),
React.createElement(Text, { color: "white" }, error),
React.createElement(Text, { color: "gray" }, "Please check configuration or service connection status")
);
}
const DummyApp = () => null;
return React.createElement(App, { codeIndexManager, dependencies });
};
return AppWithOptions;
}
// Helper function to create HTTP MCP server
async function createHTTPMCPServer(manager: CodeIndexManager, options?: { port?: number; host?: string }): Promise<CodebaseHTTPMCPServer> {
const server = new CodebaseHTTPMCPServer({
codeIndexManager: manager,
port: options?.port,
host: options?.host
});
await server.start();
return server;
}
export async function startStdioAdapterMode(options: CliOptions): Promise<void> {
// console.log('š Starting Stdio Adapter Mode');
// console.log(`š Connecting to server: ${options.stdioServerUrl || 'http://localhost:3001'}`);
// console.log(`ā±ļø Request timeout: ${options.stdioTimeout || 30000}ms`);
const { StdioToSSEAdapter } = await import('../mcp/stdio-adapter');
const adapter = new StdioToSSEAdapter({
serverUrl: options.stdioServerUrl || 'http://localhost:3001/sse',
timeout: options.stdioTimeout || 30000
});
try {
await adapter.start();
// Handle graceful shutdown
const handleShutdown = () => {
console.error('š Shutting down stdio adapter...');
adapter.stop();
process.exit(0);
};
process.on('SIGINT', handleShutdown);
process.on('SIGTERM', handleShutdown);
// Keep the process alive to handle stdio communication
return new Promise(() => {}); // Never resolves
} catch (error) {
console.error('ā Stdio adapter failed to start:', error);
process.exit(1);
}
}
export async function startMCPServerMode(options: CliOptions): Promise<void> {
// Ensure options.path is absolute; if not, prepend process.cwd()
let resolvedPath = options.path;
if (!path.isAbsolute(resolvedPath)) {
resolvedPath = path.join(process.cwd(), resolvedPath);
}
// Create workspace path - use demo subdirectory if --demo flag is set
const workspacePath = options.demo
? path.join(resolvedPath, 'demo')
: resolvedPath;
// Use config file from workspace directory
const configPath = options.config || path.join(workspacePath, 'autodev-config.json');
console.log('š Starting MCP Server Mode');
console.log(`š Workspace: ${workspacePath}`);
console.log(`āļø Config: ${configPath}`);
const deps = createNodeDependencies({
workspacePath,
storageOptions: {
globalStoragePath: options.storage || path.join(process.cwd(), '.autodev-storage'),
...(options.cache && { cacheBasePath: options.cache })
},
loggerOptions: {
name: 'Autodev-Codebase-MCP',
level: options.logLevel,
timestamps: true,
colors: false // Disable colors for MCP server mode
},
configOptions: {
configPath,
cliOverrides: {
ollamaUrl: options.ollamaUrl,
model: options.model,
qdrantUrl: options.qdrantUrl
}
}
});
try {
// Create demo files if requested
if (options.demo) {
const workspaceExists = await deps.fileSystem.exists(workspacePath);
if (!workspaceExists) {
fs.mkdirSync(workspacePath, { recursive: true });
await createSampleFiles(deps.fileSystem, workspacePath);
console.log(`š Demo files created in: ${workspacePath}`);
}
}
console.log('āļø Loading configuration...');
const config = await deps.configProvider.loadConfig();
console.log('ā
Validating configuration...');
const validation = await deps.configProvider.validateConfig();
if (!validation.isValid) {
console.warn('ā ļø Configuration validation warnings:', validation.errors);
console.log('ā ļø Continuing initialization (debug mode)');
} else {
console.log('ā
Configuration validation passed');
}
console.log('š§ Creating CodeIndexManager...');
const manager = CodeIndexManager.getInstance(deps);
if (!manager) {
throw new Error('Failed to create CodeIndexManager - workspace root path may be invalid');
}
console.log('āļø Initializing CodeIndexManager...');
const initResult = await manager.initialize({ force: options.force });
console.log('ā
CodeIndexManager initialization success');
// Start MCP Server
console.log('š Starting MCP Server...');
const server = await createHTTPMCPServer(manager, {
port: options.mcpPort,
host: options.mcpHost
});
console.log('ā
MCP Server started successfully');
// Display configuration instructions
console.log('\nš MCP Server is now running!');
console.log('To connect your IDE to the HTTP SSE MCP server, use the following configuration:');
console.log(JSON.stringify({
"mcpServers": {
"codebase": {
"url": `http://${options.mcpHost || 'localhost'}:${options.mcpPort || 3001}/sse`
}
}
}, null, 2));
console.log('Alternatively, to use MCP in stdio mode:');
console.log(JSON.stringify({
"mcpServers": {
"codebase": {
"command": "codebase",
"args": ["stdio-adapter", `--server-url=http://${options.mcpHost || 'localhost'}:${options.mcpPort || 3001}/sse`]
}
}
}, null, 2));
console.log('');
// Start indexing in background
console.log('š Starting indexing process...');
manager.onProgressUpdate((progressInfo) => {
console.log(`š Indexing progress: ${progressInfo.systemStatus} - ${progressInfo.message || ''}`);
});
if (manager.isFeatureEnabled && manager.isInitialized) {
manager.startIndexing()
.then(() => {
console.log('ā
Indexing completed');
})
.catch((err: any) => {
console.error('ā Indexing failed:', err.message);
});
} else {
console.warn('ā ļø Skipping indexing - feature not enabled or not initialized');
}
// Handle graceful shutdown
const handleShutdown = async () => {
console.log('\nš Shutting down MCP Server...');
await server.stop();
console.log('ā
MCP Server stopped');
process.exit(0);
};
process.on('SIGINT', handleShutdown);
process.on('SIGTERM', handleShutdown);
console.log('š” MCP Server is ready for connections. Press Ctrl+C to stop.');
// Keep the process alive
return new Promise(() => {}); // This never resolves, keeping the server running
} catch (err: any) {
console.error('ā MCP Server initialization failed:', err.message);
process.exit(1);
}
}