Files
claude-task-master/apps/extension/src/extension.ts
DavidMaliglowka 64302dc191 feat(extension): complete VS Code extension with kanban board interface (#997)
---------
Co-authored-by: DavidMaliglowka <13022280+DavidMaliglowka@users.noreply.github.com>
Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-08-01 14:04:22 +02:00

220 lines
6.0 KiB
TypeScript

/**
* TaskMaster Extension - Simplified Architecture
* Only using patterns where they add real value
*/
import * as vscode from 'vscode';
import { ConfigService } from './services/config-service';
import { PollingService } from './services/polling-service';
import { createPollingStrategy } from './services/polling-strategies';
import { TaskRepository } from './services/task-repository';
import { WebviewManager } from './services/webview-manager';
import { EventEmitter } from './utils/event-emitter';
import { ExtensionLogger } from './utils/logger';
import {
MCPClientManager,
createMCPConfigFromSettings
} from './utils/mcpClient';
import { TaskMasterApi } from './utils/task-master-api';
import { SidebarWebviewManager } from './services/sidebar-webview-manager';
let logger: ExtensionLogger;
let mcpClient: MCPClientManager;
let api: TaskMasterApi;
let repository: TaskRepository;
let pollingService: PollingService;
let webviewManager: WebviewManager;
let events: EventEmitter;
let configService: ConfigService;
let sidebarManager: SidebarWebviewManager;
export async function activate(context: vscode.ExtensionContext) {
try {
// Initialize logger (needed to prevent MCP stdio issues)
logger = ExtensionLogger.getInstance();
logger.log('🎉 TaskMaster Extension activating...');
// Simple event emitter for webview communication
events = new EventEmitter();
// Initialize MCP client
mcpClient = new MCPClientManager(createMCPConfigFromSettings());
// Initialize API
api = new TaskMasterApi(mcpClient);
// Repository with caching (actually useful for performance)
repository = new TaskRepository(api, logger);
// Config service for TaskMaster config.json
configService = new ConfigService(logger);
// Polling service with strategy pattern (makes sense for different polling behaviors)
const strategy = createPollingStrategy(
vscode.workspace.getConfiguration('taskmaster')
);
pollingService = new PollingService(repository, strategy, logger);
// Webview manager (cleaner than global panel array) - create before connection
webviewManager = new WebviewManager(context, repository, events, logger);
webviewManager.setConfigService(configService);
// Sidebar webview manager
sidebarManager = new SidebarWebviewManager(context.extensionUri);
// Initialize connection
await initializeConnection();
// Set MCP client and API after connection
webviewManager.setMCPClient(mcpClient);
webviewManager.setApi(api);
sidebarManager.setApi(api);
// Register commands
registerCommands(context);
// Handle polling lifecycle
events.on('webview:opened', () => {
if (webviewManager.getPanelCount() === 1) {
pollingService.start();
}
});
events.on('webview:closed', () => {
if (webviewManager.getPanelCount() === 0) {
pollingService.stop();
}
});
// Forward repository updates to webviews
repository.on('tasks:updated', (tasks) => {
webviewManager.broadcast('tasksUpdated', { tasks, source: 'polling' });
});
logger.log('✅ TaskMaster Extension activated');
} catch (error) {
logger?.error('Failed to activate', error);
vscode.window.showErrorMessage(
`Failed to activate TaskMaster: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
async function initializeConnection() {
try {
logger.log('🔗 Connecting to TaskMaster...');
// Notify webviews that we're connecting
if (webviewManager) {
webviewManager.broadcast('connectionStatus', {
isConnected: false,
status: 'Connecting...'
});
}
await mcpClient.connect();
const testResult = await api.testConnection();
if (testResult.success) {
logger.log('✅ Connected to TaskMaster');
vscode.window.showInformationMessage('TaskMaster connected!');
// Notify webviews that we're connected
if (webviewManager) {
webviewManager.broadcast('connectionStatus', {
isConnected: true,
status: 'Connected'
});
}
if (sidebarManager) {
sidebarManager.updateConnectionStatus();
}
} else {
throw new Error(testResult.error || 'Connection test failed');
}
} catch (error) {
logger.error('Connection failed', error);
// Notify webviews that connection failed
if (webviewManager) {
webviewManager.broadcast('connectionStatus', {
isConnected: false,
status: 'Disconnected'
});
}
if (sidebarManager) {
sidebarManager.updateConnectionStatus();
}
handleConnectionError(error);
}
}
function handleConnectionError(error: any) {
const message = error instanceof Error ? error.message : 'Unknown error';
if (message.includes('ENOENT') && message.includes('npx')) {
vscode.window
.showWarningMessage(
'TaskMaster: npx not found. Please ensure Node.js is installed.',
'Open Settings'
)
.then((action) => {
if (action === 'Open Settings') {
vscode.commands.executeCommand(
'workbench.action.openSettings',
'@ext:Hamster.task-master-hamster taskmaster'
);
}
});
} else {
vscode.window.showWarningMessage(
`TaskMaster connection failed: ${message}`
);
}
}
function registerCommands(context: vscode.ExtensionContext) {
// Main command
context.subscriptions.push(
vscode.commands.registerCommand('tm.showKanbanBoard', async () => {
await webviewManager.createOrShowPanel();
})
);
// Utility commands
context.subscriptions.push(
vscode.commands.registerCommand('tm.refreshTasks', async () => {
await repository.refresh();
vscode.window.showInformationMessage('Tasks refreshed!');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('tm.openSettings', () => {
vscode.commands.executeCommand(
'workbench.action.openSettings',
'@ext:Hamster.task-master-hamster taskmaster'
);
})
);
// Register sidebar view provider
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
'taskmaster.welcome',
sidebarManager
)
);
}
export function deactivate() {
logger?.log('👋 TaskMaster Extension deactivating...');
pollingService?.stop();
webviewManager?.dispose();
api?.destroy();
mcpClient?.disconnect();
}