@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
265 lines • 9.39 kB
JavaScript
/**
* VSCode terminal adapter implementation
*/
import { platform } from "os";
import { TerminalError } from "../../utils/errors.js";
import { generateId, delay, timeout, createDeferred } from "../../utils/helpers.js";
/**
* VSCode terminal implementation
*/
class VSCodeTerminalWrapper {
vscodeApi;
shellType;
logger;
id;
pid;
vscodeTerminal;
outputBuffer = "";
commandMarker;
outputDeferred = createDeferred();
isDisposed = false;
constructor(vscodeApi, shellType, logger) {
this.vscodeApi = vscodeApi;
this.shellType = shellType;
this.logger = logger;
this.id = generateId("vscode-term");
this.commandMarker = `__CLAUDE_FLOW_${this.id}__`;
}
async initialize() {
try {
// Create VSCode terminal
const shellPath = this.getShellPath();
const terminalOptions = {
name: `Claude-Flow Terminal ${this.id}`,
shellArgs: this.getShellArgs(),
env: {
CLAUDE_FLOW_TERMINAL: "true",
CLAUDE_FLOW_TERMINAL_ID: this.id,
PS1: "$ ", // Simple prompt
},
};
if (shellPath !== undefined) {
terminalOptions.shellPath = shellPath;
}
this.vscodeTerminal = this.vscodeApi.window.createTerminal(terminalOptions);
// Get process ID
const processId = await this.vscodeTerminal.processId;
if (processId !== undefined) {
this.pid = processId;
}
// Show terminal (but don't steal focus)
this.vscodeTerminal.show(true);
// Wait for terminal to be ready
await this.waitForReady();
this.logger.debug("VSCode terminal initialized", { id: this.id, pid: this.pid });
}
catch (error) {
throw new TerminalError("Failed to create VSCode terminal", { error });
}
}
async executeCommand(command) {
if (!this.vscodeTerminal || !this.isAlive()) {
throw new TerminalError("Terminal is not alive");
}
try {
// Clear output buffer
this.outputBuffer = "";
this.outputDeferred = createDeferred();
// Send command with marker
const markedCommand = `${command} && echo "${this.commandMarker}"`;
this.vscodeTerminal.sendText(markedCommand, true);
// Wait for command to complete
const output = await timeout(this.outputDeferred.promise, 30000, "Command execution timeout");
return output;
}
catch (error) {
throw new TerminalError("Failed to execute command", { command, error });
}
}
async write(data) {
if (!this.vscodeTerminal || !this.isAlive()) {
throw new TerminalError("Terminal is not alive");
}
this.vscodeTerminal.sendText(data, false);
}
async read() {
if (!this.vscodeTerminal || !this.isAlive()) {
throw new TerminalError("Terminal is not alive");
}
// Return buffered output
const output = this.outputBuffer;
this.outputBuffer = "";
return output;
}
isAlive() {
return !this.isDisposed && this.vscodeTerminal !== undefined;
}
async kill() {
if (this.vscodeTerminal && !this.isDisposed) {
try {
// Try graceful shutdown first
this.vscodeTerminal.sendText("exit", true);
await delay(500);
// Dispose terminal
this.vscodeTerminal.dispose();
this.isDisposed = true;
}
catch (error) {
this.logger.warn("Error killing VSCode terminal", { id: this.id, error });
}
}
}
/**
* Process terminal output (called by extension)
*/
processOutput(data) {
this.outputBuffer += data;
// Check for command completion marker
const markerIndex = this.outputBuffer.indexOf(this.commandMarker);
if (markerIndex !== -1) {
// Extract output before marker
const output = this.outputBuffer.substring(0, markerIndex).trim();
// Clear buffer up to after marker
this.outputBuffer = this.outputBuffer.substring(markerIndex + this.commandMarker.length).trim();
// Resolve pending command
this.outputDeferred.resolve(output);
}
}
getShellPath() {
switch (this.shellType) {
case "bash":
return "/bin/bash";
case "zsh":
return "/bin/zsh";
case "powershell":
return platform() === "win32" ? "powershell.exe" : "pwsh";
case "cmd":
return platform() === "win32" ? "cmd.exe" : undefined;
default:
return undefined;
}
}
getShellArgs() {
switch (this.shellType) {
case "bash":
return ["--norc", "--noprofile"];
case "zsh":
return ["--no-rcs"];
case "powershell":
return ["-NoProfile", "-NonInteractive"];
case "cmd":
return ["/Q"];
default:
return [];
}
}
async waitForReady() {
// Send a test command to ensure terminal is ready
this.vscodeTerminal.sendText("echo \"READY\"", true);
const startTime = Date.now();
while (Date.now() - startTime < 5000) {
if (this.outputBuffer.includes("READY")) {
this.outputBuffer = "";
return;
}
await delay(100);
}
throw new TerminalError("Terminal failed to become ready");
}
}
/**
* VSCode terminal adapter
*/
export class VSCodeAdapter {
logger;
terminals = new Map();
vscodeApi;
shellType;
terminalCloseListener;
constructor(logger) {
this.logger = logger;
this.shellType = this.detectShell();
}
async initialize() {
this.logger.info("Initializing VSCode terminal adapter");
// Check if running in VSCode extension context
if (!this.isVSCodeExtensionContext()) {
throw new TerminalError("Not running in VSCode extension context");
}
// Get VSCode API from global
this.vscodeApi = globalThis.vscode;
if (!this.vscodeApi) {
throw new TerminalError("VSCode API not available");
}
// Register terminal close listener
this.terminalCloseListener = this.vscodeApi.window.onDidCloseTerminal((terminal) => {
// Find and clean up closed terminal
for (const [id, wrapper] of this.terminals.entries()) {
if (wrapper.vscodeTerminal === terminal) {
this.logger.info("VSCode terminal closed", { id });
this.terminals.delete(id);
break;
}
}
});
this.logger.info("VSCode terminal adapter initialized");
}
async shutdown() {
this.logger.info("Shutting down VSCode terminal adapter");
// Dispose listener
if (this.terminalCloseListener) {
this.terminalCloseListener.dispose();
}
// Kill all terminals
const terminals = Array.from(this.terminals.values());
await Promise.all(terminals.map(term => term.kill()));
this.terminals.clear();
}
async createTerminal() {
if (!this.vscodeApi) {
throw new TerminalError("VSCode API not initialized");
}
const terminal = new VSCodeTerminalWrapper(this.vscodeApi, this.shellType, this.logger);
await terminal.initialize();
this.terminals.set(terminal.id, terminal);
// Register output processor if extension provides it
const outputProcessor = globalThis.registerTerminalOutputProcessor;
if (outputProcessor) {
outputProcessor(terminal.id, (data) => terminal.processOutput(data));
}
return terminal;
}
async destroyTerminal(terminal) {
await terminal.kill();
this.terminals.delete(terminal.id);
}
isVSCodeExtensionContext() {
// Check for VSCode extension environment
return typeof globalThis.vscode !== "undefined" &&
typeof globalThis.vscode.window !== "undefined";
}
detectShell() {
// Get default shell from VSCode settings or environment
const osplatform = platform();
if (osplatform === "win32") {
// Windows defaults
const comspec = process.env.COMSPEC;
if (comspec?.toLowerCase().includes("powershell")) {
return "powershell";
}
return "cmd";
}
else {
// Unix-like defaults
const shell = process.env.SHELL;
if (shell) {
const shellName = shell.split("/").pop();
if (shellName && ["bash", "zsh", "fish", "sh"].includes(shellName)) {
return shellName;
}
}
return "bash";
}
}
}
//# sourceMappingURL=vscode.js.map