schrod-cli
Version:
AI-driven application framework CLI that displays hierarchical Schrödinger project structure for systematic development workflow
366 lines (319 loc) • 11.6 kB
text/typescript
import * as vscode from 'vscode';
import { SchrodService } from '../services/schrodService';
import { SchrodExplorerProvider } from '../providers/schrodExplorerProvider';
// SchrodTreeItem is defined in the provider file
import { SchrodDomain } from '../types';
export function registerCommands(
context: vscode.ExtensionContext,
schrodService: SchrodService,
explorerProvider: SchrodExplorerProvider
) {
// Initialize Project
context.subscriptions.push(
vscode.commands.registerCommand('schrod.initializeProject', async () => {
const projectName = await vscode.window.showInputBox({
prompt: 'Enter project name',
placeHolder: 'my-app',
validateInput: (value) => {
if (!value || value.trim() === '') {
return 'Project name is required';
}
if (!/^[a-z0-9-]+$/.test(value)) {
return 'Project name must contain only lowercase letters, numbers, and hyphens';
}
return null;
}
});
if (projectName) {
try {
await schrodService.initializeProject(projectName);
explorerProvider.refresh();
vscode.window.showInformationMessage(`Schröd project '${projectName}' initialized`);
} catch (error) {
vscode.window.showErrorMessage(`Failed to initialize project: ${error}`);
}
}
})
);
// Create App Spec
context.subscriptions.push(
vscode.commands.registerCommand('schrod.createAppSpec', async () => {
const idea = await vscode.window.showInputBox({
prompt: 'Describe your application idea',
placeHolder: 'A blog application with authentication and comments'
});
if (idea) {
try {
await schrodService.createAppSpec(idea);
explorerProvider.refresh();
vscode.window.showInformationMessage('App specification created');
} catch (error) {
vscode.window.showErrorMessage(`Failed to create app spec: ${error}`);
}
}
})
);
// Decompose to UI/Logic
context.subscriptions.push(
vscode.commands.registerCommand('schrod.decomposeToUILogic', async () => {
try {
await schrodService.decomposeToUILogic();
explorerProvider.refresh();
vscode.window.showInformationMessage('Decomposed to UI/Logic/Test domains');
} catch (error) {
vscode.window.showErrorMessage(`Failed to decompose: ${error}`);
}
})
);
// Add Feature
context.subscriptions.push(
vscode.commands.registerCommand('schrod.addFeature', async () => {
const featureName = await vscode.window.showInputBox({
prompt: 'Enter feature name',
placeHolder: 'auth, dashboard, profile',
validateInput: (value) => {
if (!value || value.trim() === '') {
return 'Feature name is required';
}
if (!/^[a-z0-9-]+$/.test(value)) {
return 'Feature name must contain only lowercase letters, numbers, and hyphens';
}
return null;
}
});
if (!featureName) {
return;
}
const domain = await vscode.window.showQuickPick(
['ui', 'logic', 'test'],
{
placeHolder: 'Select domain for the feature'
}
);
if (domain) {
try {
await schrodService.addFeature(featureName, domain as SchrodDomain);
explorerProvider.refresh();
vscode.window.showInformationMessage(`Feature '${featureName}' added to ${domain} domain`);
} catch (error) {
vscode.window.showErrorMessage(`Failed to add feature: ${error}`);
}
}
})
);
// Create Tickets
context.subscriptions.push(
vscode.commands.registerCommand('schrod.createTickets', async (item?: any) => {
let nodePath: string | undefined;
if (item) {
nodePath = item.node.path;
} else {
const nodes = schrodService.getNodes();
const featureNodes = Array.from(nodes.values())
.filter(node => node.level === 3)
.map(node => ({
label: node.name,
description: node.path,
path: node.path
}));
const selected = await vscode.window.showQuickPick(featureNodes, {
placeHolder: 'Select a feature to create tickets for'
});
nodePath = selected?.path;
}
if (nodePath) {
try {
await schrodService.createTickets(nodePath);
explorerProvider.refresh();
vscode.window.showInformationMessage('Tickets created');
} catch (error) {
vscode.window.showErrorMessage(`Failed to create tickets: ${error}`);
}
}
})
);
// Run Node
context.subscriptions.push(
vscode.commands.registerCommand('schrod.run', async (item?: any) => {
let nodePath: string | undefined;
if (item) {
nodePath = item.node.path;
} else {
const nodes = schrodService.getNodes();
const runnableNodes = Array.from(nodes.values())
.filter(node => node.type === 'ticket')
.map(node => ({
label: node.name,
description: node.path,
path: node.path
}));
const selected = await vscode.window.showQuickPick(runnableNodes, {
placeHolder: 'Select a node to run'
});
nodePath = selected?.path;
}
if (nodePath) {
try {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `Running ${nodePath}`,
cancellable: false
}, async (progress) => {
progress.report({ increment: 0 });
await schrodService.runNode(nodePath);
progress.report({ increment: 100 });
});
explorerProvider.refresh();
vscode.window.showInformationMessage(`Successfully ran ${nodePath}`);
} catch (error) {
vscode.window.showErrorMessage(`Failed to run node: ${error}`);
}
}
})
);
// Run All Tickets
context.subscriptions.push(
vscode.commands.registerCommand('schrod.runAllTickets', async () => {
const nodes = schrodService.getNodes();
const tickets = Array.from(nodes.values()).filter(node => node.type === 'ticket');
if (tickets.length === 0) {
vscode.window.showInformationMessage('No tickets found to run');
return;
}
const answer = await vscode.window.showWarningMessage(
`Run all ${tickets.length} tickets?`,
'Yes',
'No'
);
if (answer === 'Yes') {
try {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: 'Running all tickets',
cancellable: false
}, async (progress) => {
for (let i = 0; i < tickets.length; i++) {
const ticket = tickets[i];
progress.report({
increment: (100 / tickets.length),
message: `Running ${ticket.name} (${i + 1}/${tickets.length})`
});
try {
await schrodService.runNode(ticket.path);
} catch (error) {
console.error(`Failed to run ${ticket.path}:`, error);
}
}
});
explorerProvider.refresh();
vscode.window.showInformationMessage('All tickets completed');
} catch (error) {
vscode.window.showErrorMessage(`Failed to run tickets: ${error}`);
}
}
})
);
// Show Status
context.subscriptions.push(
vscode.commands.registerCommand('schrod.status', async () => {
const config = schrodService.getConfig();
const nodes = schrodService.getNodes();
if (!config) {
vscode.window.showInformationMessage('No Schröd project initialized');
return;
}
const totalNodes = nodes.size;
const completedNodes = Array.from(nodes.values()).filter(n => n.status === 'completed').length;
const runningNodes = Array.from(nodes.values()).filter(n => n.status === 'running').length;
const failedNodes = Array.from(nodes.values()).filter(n => n.status === 'failed').length;
const message = `
Schröd Project: ${config.projectName}
Total Nodes: ${totalNodes}
Completed: ${completedNodes}
Running: ${runningNodes}
Failed: ${failedNodes}
Pending: ${totalNodes - completedNodes - runningNodes - failedNodes}
Progress: ${Math.round((completedNodes / totalNodes) * 100)}%
`.trim();
vscode.window.showInformationMessage(message, { modal: true });
})
);
// Plan
context.subscriptions.push(
vscode.commands.registerCommand('schrod.plan', async (item?: any) => {
if (!item) {
vscode.window.showErrorMessage('Please select a node to plan');
return;
}
// This would show execution plan
vscode.window.showInformationMessage(`Planning execution for: ${item.node.path}`);
})
);
// View Dependencies
context.subscriptions.push(
vscode.commands.registerCommand('schrod.viewDependencies', async (item?: any) => {
if (!item) {
vscode.window.showErrorMessage('Please select a node to view dependencies');
return;
}
// This would show dependency graph
vscode.window.showInformationMessage(`Dependencies for: ${item.node.path}`);
})
);
// Show Dependency Graph (placeholder)
context.subscriptions.push(
vscode.commands.registerCommand('schrod.showDependencyGraph', async () => {
vscode.window.showInformationMessage('Dependency graph visualization coming soon!');
})
);
// Run with AI Selection (placeholder)
context.subscriptions.push(
vscode.commands.registerCommand('schrod.runWithAISelection', async () => {
const ai = await vscode.window.showQuickPick(
['claude-sonnet-4', 'claude-haiku', 'gpt-4', 'claude-opus'],
{ placeHolder: 'Select AI model' }
);
if (ai) {
vscode.window.showInformationMessage(`Running with ${ai}`);
}
})
);
// Run Specific Path
context.subscriptions.push(
vscode.commands.registerCommand('schrod.runSpecificPath', async () => {
const path = await vscode.window.showInputBox({
prompt: 'Enter Schröd path pattern',
placeHolder: '**/*@Schröd.ticket',
value: '**/*@Schröd.ticket'
});
if (path) {
vscode.window.showInformationMessage(`Running pattern: ${path}`);
}
})
);
// Resume Failed
context.subscriptions.push(
vscode.commands.registerCommand('schrod.resumeFailed', async () => {
const nodes = schrodService.getNodes();
const failedNodes = Array.from(nodes.values()).filter(n => n.status === 'failed');
if (failedNodes.length === 0) {
vscode.window.showInformationMessage('No failed nodes to resume');
return;
}
vscode.window.showInformationMessage(`Resuming ${failedNodes.length} failed nodes`);
})
);
// Clean and Restart
context.subscriptions.push(
vscode.commands.registerCommand('schrod.cleanAndRestart', async () => {
const answer = await vscode.window.showWarningMessage(
'This will clean all generated files and reset status. Continue?',
'Yes',
'No'
);
if (answer === 'Yes') {
vscode.window.showInformationMessage('Cleaning and restarting...');
}
})
);
}