diff --git a/package.json b/package.json index 7ccabfc97..bffae2ae4 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,14 @@ "packages/*" ], "scripts": { - "dev": "npm run dev --workspace=web", - "dev:mcp": "npm run dev --workspace=mcp-server", + "dev": "npm run dev --workspace=@voice-dev/web", + "dev:mcp": "npm run dev --workspace=@voice-dev/mcp-server", "build": "npm run build --workspaces --if-present", - "build:mcp": "npm run build --workspace=mcp-server", - "build:web": "npm run build --workspace=web", + "build:mcp": "npm run build --workspace=@voice-dev/mcp-server", + "build:web": "npm run build --workspace=@voice-dev/web", "typecheck": "npm run typecheck --workspaces --if-present", - "start": "npm run start --workspace=web", - "start:mcp": "npm run start --workspace=mcp-server" + "start": "npm run start --workspace=@voice-dev/web", + "start:mcp": "npm run start --workspace=@voice-dev/mcp-server" }, "devDependencies": { "typescript": "^5.9.3" diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 2a76e4cb7..ad06ce858 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -1,7 +1,7 @@ { "name": "@voice-dev/mcp-server", "version": "0.3.0", - "description": "MCP Server for interfacing with tmux sessions", + "description": "MCP Server for voice-controlled terminal development", "type": "module", "main": "build/index.js", "scripts": { @@ -14,7 +14,7 @@ "release": "npm run build && npm publish" }, "bin": { - "tmux-mcp": "build/index.js" + "voice-dev-mcp": "build/index.js" }, "files": [ "build" diff --git a/packages/mcp-server/src/http-server.ts b/packages/mcp-server/src/http-server.ts index 4e23b8e81..e6062aa23 100644 --- a/packages/mcp-server/src/http-server.ts +++ b/packages/mcp-server/src/http-server.ts @@ -34,7 +34,7 @@ export function startHttpServer({ port, password, server }: HttpServerOptions): app.get('/', (req: Request, res: Response) => { res.json({ status: 'ok', - service: 'tmux-mcp', + service: 'voice-dev-mcp', transport: 'streamable-http' }); }); diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index d5efc4b47..bc3ba4860 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -13,7 +13,7 @@ import { startHttpServer } from "./http-server.js"; // Create MCP server const server = new McpServer( { - name: "tmux-mcp", + name: "voice-dev-mcp", version: "0.3.0", }, { @@ -30,10 +30,10 @@ const server = new McpServer( } ); -// List tmux hierarchy - Tool +// List hierarchy - Tool server.tool( "list", - "List tmux sessions, windows, and panes. Use scope='all' for full hierarchy, or drill down with specific scopes. Examples: scope='all' returns nested tree, scope='sessions' returns all sessions, scope='session' with target='$0' returns windows in that session, scope='window' with target='@1' returns panes in that window.", + "List sessions, windows, and panes. Use scope='all' for full hierarchy, or drill down with specific scopes. Examples: scope='all' returns nested tree, scope='sessions' returns all sessions, scope='session' with target='$0' returns windows in that session, scope='window' with target='@1' returns panes in that window.", { scope: z .enum(["all", "sessions", "session", "window", "pane"]) @@ -75,9 +75,9 @@ server.tool( // Capture pane content - Tool server.tool( "capture-pane", - "Capture content from a tmux pane with configurable lines count and optional color preservation. Optionally wait before capturing to allow commands to complete.", + "Capture content from a pane with configurable lines count and optional color preservation. Optionally wait before capturing to allow commands to complete.", { - paneId: z.string().describe("ID of the tmux pane"), + paneId: z.string().describe("ID of the pane"), lines: z.string().optional().describe("Number of lines to capture"), colors: z .boolean() @@ -132,9 +132,9 @@ server.tool( // Create new session - Tool server.tool( "create-session", - "Create a new tmux session", + "Create a new session", { - name: z.string().describe("Name for the new tmux session"), + name: z.string().describe("Name for the new session"), }, async ({ name }) => { try { @@ -166,9 +166,9 @@ server.tool( // Create new window - Tool server.tool( "create-window", - "Create a new window in a tmux session. Returns the window ID and the default pane ID. Optionally execute a command in the new window immediately after creation - the command can use any bash operators (&&, ||, |, ;, etc.).", + "Create a new window in a session. Returns the window ID and the default pane ID. Optionally execute a command in the new window immediately after creation - the command can use any bash operators (&&, ||, |, ;, etc.).", { - sessionId: z.string().describe("ID of the tmux session"), + sessionId: z.string().describe("ID of the session"), name: z.string().describe("Name for the new window"), command: z .string() @@ -237,9 +237,9 @@ server.tool( // Rename window - Tool server.tool( "rename-window", - "Rename a tmux window by its window ID (e.g., @380)", + "Rename a window by its window ID (e.g., @380)", { - windowId: z.string().describe("ID of the tmux window (e.g., '@380')"), + windowId: z.string().describe("ID of the window (e.g., '@380')"), name: z.string().describe("New name for the window"), }, async ({ windowId, name }) => { @@ -267,10 +267,10 @@ server.tool( } ); -// Kill tmux resources - Tool +// Kill resources - Tool server.tool( "kill", - "Kill a tmux session, window, or pane. Examples: kill(scope='session', target='$0'), kill(scope='window', target='@1'), kill(scope='pane', target='%2'). No 'all' scope for safety.", + "Kill a session, window, or pane. Examples: kill(scope='session', target='$0'), kill(scope='window', target='@1'), kill(scope='pane', target='%2'). No 'all' scope for safety.", { scope: z .enum(["session", "window", "pane"]) @@ -311,9 +311,9 @@ server.tool( // Split pane - Tool server.tool( "split-pane", - "Split a tmux pane horizontally or vertically", + "Split a pane horizontally or vertically", { - paneId: z.string().describe("ID of the tmux pane to split"), + paneId: z.string().describe("ID of the pane to split"), direction: z .enum(["horizontal", "vertical"]) .optional() @@ -365,9 +365,9 @@ server.tool( // Execute shell command - Tool server.tool( "execute-shell-command", - "Execute a shell command in a tmux pane synchronously and return results immediately. Waits for command completion (default 30s timeout). Returns output, exit code, and status. Use for quick commands (ls, grep, npm test, etc.). Avoid heredoc syntax (cat << EOF) and multi-line constructs. IMPORTANT: For long-running commands (npm start, servers, watch processes), use send-text with pressEnter=true instead, then monitor output with capture-pane. For interactive apps or special keys, use send-keys.", + "Execute a shell command in a pane synchronously and return results immediately. Waits for command completion (default 30s timeout). Returns output, exit code, and status. Use for quick commands (ls, grep, npm test, etc.). Avoid heredoc syntax (cat << EOF) and multi-line constructs. IMPORTANT: For long-running commands (npm start, servers, watch processes), use send-text with pressEnter=true instead, then monitor output with capture-pane. For interactive apps or special keys, use send-keys.", { - paneId: z.string().describe("ID of the tmux pane"), + paneId: z.string().describe("ID of the pane"), command: z.string().describe("Shell command to execute"), timeout: z .number() @@ -412,9 +412,9 @@ server.tool( // Send keys - Tool server.tool( "send-keys", - "Send special keys or key combinations to a tmux pane. Use for TUI navigation and control sequences. Examples: 'Up', 'Down', 'Enter', 'Escape', 'C-c' (Ctrl+C), 'M-x' (Alt+X), 'C-b' (tmux prefix). For typing regular text, use send-text instead. Supports repeating key presses and optionally capturing output after sending keys.", + "Send special keys or key combinations to a pane. Use for TUI navigation and control sequences. Examples: 'Up', 'Down', 'Enter', 'Escape', 'C-c' (Ctrl+C), 'M-x' (Alt+X). For typing regular text, use send-text instead. Supports repeating key presses and optionally capturing output after sending keys.", { - paneId: z.string().describe("ID of the tmux pane"), + paneId: z.string().describe("ID of the pane"), keys: z .string() .describe( @@ -490,9 +490,9 @@ server.tool( // Send text - Tool server.tool( "send-text", - "Type text into a tmux pane. This is the PRIMARY way to execute shell commands with bash operators (&&, ||, |, ;, etc.) - set pressEnter=true to run the command. Also use for interactive applications, REPLs, forms, and text entry. For special keys or control sequences, use send-keys instead.", + "Type text into a pane. This is the PRIMARY way to execute shell commands with bash operators (&&, ||, |, ;, etc.) - set pressEnter=true to run the command. Also use for interactive applications, REPLs, forms, and text entry. For special keys or control sequences, use send-keys instead.", { - paneId: z.string().describe("ID of the tmux pane"), + paneId: z.string().describe("ID of the pane"), text: z .string() .describe( @@ -566,8 +566,8 @@ server.tool( } ); -// Expose tmux session list as a resource -server.resource("Tmux Sessions", "tmux://sessions", async () => { +// Expose session list as a resource +server.resource("Sessions", "tmux://sessions", async () => { try { const sessions = await tmux.listSessions(); return { @@ -592,7 +592,7 @@ server.resource("Tmux Sessions", "tmux://sessions", async () => { contents: [ { uri: "tmux://sessions", - text: `Error listing tmux sessions: ${error}`, + text: `Error listing sessions: ${error}`, }, ], }; @@ -601,7 +601,7 @@ server.resource("Tmux Sessions", "tmux://sessions", async () => { // Expose pane content as a resource server.resource( - "Tmux Pane Content", + "Pane Content", new ResourceTemplate("tmux://pane/{paneId}", { list: async () => { try { @@ -702,7 +702,7 @@ async function main() { process.exit(1); } - const port = parseInt(values.port || process.env.PORT || "3000", 10); + const port = Number(values.port || process.env.PORT || "6767"); // Start HTTP server startHttpServer({ diff --git a/packages/web/app/api/session/route.ts b/packages/web/app/api/session/route.ts index a5b7ef6a0..0ca1a8bb1 100644 --- a/packages/web/app/api/session/route.ts +++ b/packages/web/app/api/session/route.ts @@ -38,7 +38,7 @@ export async function POST() { tools: [ { type: 'mcp', - server_label: 'tmux-mcp', + server_label: 'voice-dev-mcp', server_url: mcpServerUrl, require_approval: 'never', }, diff --git a/packages/web/app/api/session/system-prompt.ts b/packages/web/app/api/session/system-prompt.ts index 4c4294404..e8854bfd9 100644 --- a/packages/web/app/api/session/system-prompt.ts +++ b/packages/web/app/api/session/system-prompt.ts @@ -2,11 +2,11 @@ export const SYSTEM_PROMPT = `# Virtual Assistant Instructions ## Your Role -You are a virtual assistant with direct access to the user's tmux environment on their laptop. Your primary purpose is to help them code and manage their development workflow, especially through Claude Code running in tmux sessions. +You are a virtual assistant with direct access to the user's terminal environment on their laptop. Your primary purpose is to help them code and manage their development workflow, especially through Claude Code running in terminal sessions. ## Connection Setup -- **Environment**: You connect remotely to the user's laptop running tmux +- **Environment**: You connect remotely to the user's laptop terminal environment - **User's device**: They typically code from their **phone** - **Key implication**: Be mindful of voice-to-text issues, autocorrect problems, and typos - **Projects location**: All projects are in ~/dev @@ -44,6 +44,39 @@ You: "Starting Claude Code and switching to plan mode." - Prevents awkward silence while tools execute - Builds trust through responsiveness +### Tool Results Reporting + +**CRITICAL**: After ANY tool execution completes, you MUST verbally report the results. + +**Complete tool execution cycle:** +1. Acknowledge request verbally +2. Execute tool call +3. **Wait for tool result** +4. **Report the results in your verbal response** - NEVER stop after tool execution without explaining what happened + +**Examples:** + +User: "List the sessions" +You: "I'll list the sessions for you." +[Tool executes and returns results] +You: "I found 3 active tmux sessions: session-1 with 2 windows, session-2 with 5 windows, and dev with 1 window. The dev session is currently attached." + +User: "Check if there are any failing tests" +You: "Let me run the test suite." +[Tool executes] +You: "The tests are passing! All 47 tests completed successfully in 3.2 seconds." + +User: "What's the git status?" +You: "Checking git status now." +[Tool executes] +You: "You have 3 modified files: app.ts, routes.ts, and README.md. There are also 2 untracked files in the test directory." + +**Why this is critical:** +- **NEVER leave the user hanging** - silence after tool execution is confusing +- Tool results are useless if not communicated back to the user +- Voice users cannot see tool output, they depend on your verbal summary +- The conversation should flow naturally: request → acknowledgment → execution → results report + ### Communication Style - **Confirm commands** before executing, especially destructive operations - **Be patient** with spelling errors and voice-related mistakes @@ -209,7 +242,7 @@ Use send-keys for: - C-c (interrupt) - Up, Down, Left, Right (navigation) -## Tmux MCP Tools Available +## Terminal Control Tools ### Discovery & Navigation - **list** - Flexible listing with scopes @@ -220,7 +253,7 @@ Use send-keys for: - scope="pane", target="%557" - Pane details ### Creation -- **create-session** - Create new tmux session +- **create-session** - Create new session - **create-window** - Create new window in session - **split-pane** - Split pane horizontally/vertically