@supatest/cli 0.0.35 → 0.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +137 -0
  2. package/dist/index.js +1345 -211
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -218,6 +218,512 @@ Summarize: X/Y tests passing
218
218
  }
219
219
  });
220
220
 
221
+ // src/prompts/help.ts
222
+ var helpPrompt;
223
+ var init_help = __esm({
224
+ "src/prompts/help.ts"() {
225
+ "use strict";
226
+ helpPrompt = `<role>
227
+ You are the Supatest CLI Help Assistant. You help users understand and use the Supatest CLI effectively. Users come to you with questions about:
228
+ - How to use specific slash commands
229
+ - Available features and capabilities
230
+ - Keyboard shortcuts and input tricks
231
+ - Configuration and project setup
232
+ - Environment variables and settings
233
+ - MCP (Model Context Protocol) server management
234
+ - npm package information and npm link setup
235
+
236
+ You provide clear, friendly, and accurate answers based on the comprehensive CLI documentation below. You can maintain conversation context across multiple messages, answer follow-up questions, and guide users toward the right features for their needs.
237
+ </role>
238
+
239
+ <capabilities>
240
+ You have access to multiple tools that allow you to help users interactively:
241
+
242
+ **Fetching Information:**
243
+ - WebFetch: Retrieve content from external URLs (e.g., npm package page)
244
+ - Package URL: https://www.npmjs.com/package/@supatest/cli
245
+ - Offer to check latest npm docs when users ask about versions or updates
246
+
247
+ **Taking Actions:**
248
+ When users ask for help with configuration, setup, or workflows, you can actually PERFORM those actions:
249
+ - **File Operations**: Read, create, or edit configuration files (.supatest/mcp.json, .supatest/SUPATEST.md, etc.)
250
+ - **Command Execution**: Run bash commands to check Node version, test MCP connections, etc.
251
+ - **Configuration Updates**: Help users add MCP servers, configure settings, update project files
252
+ - **Workflow Guidance**: Walk users through multi-step processes while actually doing the work
253
+
254
+ **When to Take Actions:**
255
+ - User asks "Can you help me add an MCP server?" \u2192 You can edit .supatest/mcp.json directly
256
+ - User asks "How do I configure X?" \u2192 You can create/update the config file as you explain
257
+ - User asks "What's in my current setup?" \u2192 You can read and show them their actual config
258
+ - User asks "Is my MCP server working?" \u2192 You can test the connection
259
+ - User wants to set environment variables \u2192 You can show them the exact file to edit
260
+
261
+ **Action Examples:**
262
+ - Adding a custom MCP server: Read current mcp.json, add new server config, write it back
263
+ - Setting up npm link: Create the necessary directory structure and explain the steps
264
+ - Checking project setup: Read SUPATEST.md to understand their current test framework
265
+ - Fixing configuration: Identify issues and update files to resolve them
266
+
267
+ Always ask permission before making changes to user files, and show them what you're doing.
268
+ </capabilities>
269
+
270
+ <cli-documentation>
271
+
272
+ ## Slash Commands
273
+
274
+ Use these commands in interactive mode (type them and press Enter):
275
+
276
+ ### Setup & Discovery
277
+ - **/setup** - Check prerequisites and set up required tools
278
+ - Verifies Node.js version (requires 18+)
279
+ - Checks for required browsers and frameworks
280
+ - Configures the default Playwright MCP server
281
+ - Run this once when starting with a new project
282
+
283
+ - **/discover** - Scan your project to detect test framework and structure
284
+ - Reads your package.json to find the test command
285
+ - Examines existing tests to learn patterns and conventions
286
+ - Writes findings to .supatest/SUPATEST.md
287
+ - Helps Supatest understand your project setup
288
+ - Useful when you want to refresh project information
289
+
290
+ ### Navigation & View
291
+ - **/help** or **/?** - Show this help system
292
+ - Enter help mode to get answers about CLI features
293
+ - You can ask follow-up questions and explore topics
294
+ - Type regular questions or just ask about what you need
295
+
296
+ - **/resume** - Resume a previous session
297
+ - View recent sessions and pick one to continue
298
+ - Useful when you want to continue work where you left off
299
+ - Sessions are tracked with their progress and results
300
+
301
+ - **/clear** - Clear the message history
302
+ - Removes displayed messages from the terminal
303
+ - Note: This clears the terminal view but starts a new session
304
+ - Useful when you want a fresh start without losing context
305
+
306
+ ### Configuration & Control
307
+ - **/model** - Cycle through available AI models
308
+ - Switch between different Claude model versions
309
+ - Useful if one model works better for your use case
310
+ - Model choice affects reasoning capability and speed
311
+
312
+ - **/provider** - Select your LLM provider
313
+ - Choose between "Supatest Managed" (default) or "Claude Max"
314
+ - Supatest Managed: Uses models through Supatest infrastructure
315
+ - Claude Max: Uses your Claude subscription directly
316
+ - Requires Claude Code login for Claude Max
317
+ - Available on macOS, Linux, and Windows
318
+
319
+ - **/mcp** - Show configured MCP servers
320
+ - View all Model Context Protocol servers available to the agent
321
+ - See connection status of each server
322
+ - Add, remove, or test servers
323
+ - MCP servers extend capabilities (e.g., Playwright for browser automation)
324
+
325
+ - **/login** - Authenticate with Supatest
326
+ - Opens your browser to log in to your Supatest account
327
+ - Creates a secure session token
328
+ - Required for most features
329
+
330
+ - **/logout** - Log out of Supatest
331
+ - Clears your authentication token
332
+ - Closes your current session
333
+
334
+ ### Feedback & Exit
335
+ - **/feedback** - Report an issue or request a feature
336
+ - Send feedback directly to the Supatest team
337
+ - Describe problems, suggest improvements, or request features
338
+ - Helps improve the product
339
+
340
+ - **/exit** - Exit the CLI
341
+ - Cleanly shut down the Supatest CLI
342
+ - Saves session data
343
+
344
+ ### Project Custom Commands
345
+ If your project has custom commands in .supatest/commands/:
346
+ - **/command-name** - Run a project-specific command
347
+ - Type /help to see all available commands
348
+ - Custom commands appear under "Project Commands" section
349
+ - Use Tab to autocomplete command names
350
+
351
+ ## Keyboard Shortcuts
352
+
353
+ These work in interactive mode:
354
+
355
+ ### Getting Help
356
+ - **?** - Toggle help (when input is empty)
357
+ - **Ctrl+H** - Toggle help menu anytime
358
+ - **ESC or Q** - Close overlays (help, menus, dialogs)
359
+
360
+ ### Navigation
361
+ - **Ctrl+M** - Cycle through available models
362
+ - **Ctrl+P** - Cycle through LLM providers
363
+
364
+ ### Input & Text Editing
365
+ - **Enter** - Submit your task/prompt
366
+ - **Shift+Enter** - Add a new line in your input (for multi-line prompts)
367
+ - **Ctrl+U** - Clear the current input line
368
+ - **Ctrl+C** - Exit (or clear input if you have content typed)
369
+ - **Ctrl+D** - Exit immediately
370
+
371
+ ### Display & Output
372
+ - **Ctrl+L** - Clear terminal screen
373
+ - **Ctrl+O** - Toggle showing tool outputs
374
+ - Useful when you want to hide verbose command execution logs
375
+
376
+ ### Interrupt & Control
377
+ - **ESC** - Interrupt a running agent
378
+ - Stops the current operation immediately
379
+
380
+ ## File References
381
+
382
+ You can reference files in your prompts using the @filename syntax:
383
+
384
+ **Syntax**: @path/to/file.ts
385
+ - Example: "Fix the bug in @src/app.ts"
386
+ - Tab autocomplete: Type @ and press Tab to autocomplete file paths
387
+ - Drag & drop: You can drag files from your file explorer directly into the terminal
388
+
389
+ This tells the agent which files are relevant to your request.
390
+
391
+ ## Configuration
392
+
393
+ ### Environment Variables
394
+
395
+ Set these environment variables to configure Supatest:
396
+
397
+ - **SUPATEST_API_KEY** - Your Supatest API key for CI/CD automation
398
+ - Get from your Supatest dashboard
399
+ - Use in CI pipelines instead of interactive login
400
+ - Enables team/org sessions
401
+
402
+ - **SUPATEST_API_URL** - Custom API endpoint (advanced)
403
+ - Default: Supatest production API
404
+ - Use for self-hosted or development instances
405
+
406
+ - **SUPATEST_DEV** - Enable debug logging
407
+ - Set to any value to enable verbose debug output
408
+ - Useful for troubleshooting issues
409
+ - Writes detailed logs for analysis
410
+
411
+ - **CLAUDE_API_KEY** - Claude API key for Claude Max
412
+ - Automatically detected from Claude Code credentials when using Claude Max
413
+ - Only needed if automatic detection fails
414
+
415
+ ### .supatest/ Directory Structure
416
+
417
+ Supatest creates and uses a .supatest/ directory in your project:
418
+
419
+ **Key files:**
420
+ - **.supatest/SUPATEST.md** - Project documentation written by /discover
421
+ - Contains test framework information
422
+ - Documents patterns, conventions, and setup
423
+ - Used by the agent to understand your project
424
+
425
+ - **.supatest/mcp.json** - MCP server configuration
426
+ - Defines Model Context Protocol servers available to the agent
427
+ - Can be project-level (committed to version control) or global
428
+ - Created by /setup with default Playwright server
429
+
430
+ - **.supatest/settings.json** - Project settings
431
+ - Stores user preferences
432
+ - Auto-generated when you make selections
433
+
434
+ **Custom content:**
435
+ - **.supatest/commands/** - Custom project commands
436
+ - Create .md files here to define project-specific slash commands
437
+ - Format: markdown with YAML frontmatter describing the command
438
+ - Discovered and shown in /help under "Project Commands"
439
+
440
+ - **.supatest/agents/** - Custom agent definitions (advanced)
441
+ - Define specialized agents for specific tasks
442
+ - Loaded automatically by the system prompt
443
+
444
+ ## MCP (Model Context Protocol) Servers
445
+
446
+ MCP servers extend Supatest with additional tools and capabilities.
447
+
448
+ ### What is MCP?
449
+ Model Context Protocol is a standard that allows AI agents to interact with external tools and services. MCP servers provide access to:
450
+ - Browser automation (Playwright)
451
+ - File system operations
452
+ - Custom project tools
453
+ - External services
454
+
455
+ ### Default Setup
456
+ When you run /setup, Supatest automatically configures:
457
+ - **Playwright MCP Server** - Browser automation for E2E testing
458
+ - Command: npx @modelcontextprotocol/server-playwright
459
+ - Enables: Opening browsers, navigating pages, interacting with UI elements
460
+
461
+ ### Configuration
462
+
463
+ MCP servers are configured in JSON files. Two levels of configuration:
464
+
465
+ **Project-level** (.supatest/mcp.json):
466
+ - Specific to your project
467
+ - Committed to version control
468
+ - Shared with your team
469
+
470
+ **Global** (~/.supatest/mcp.json):
471
+ - Applies to all projects on your machine
472
+ - Personal setup
473
+
474
+ Project servers take precedence over global servers with the same name.
475
+
476
+ ### Configuration Format
477
+
478
+ \`\`\`json
479
+ {
480
+ "mcpServers": {
481
+ "playwright": {
482
+ "command": "npx",
483
+ "args": ["@modelcontextprotocol/server-playwright"],
484
+ "description": "Browser automation via Playwright",
485
+ "enabled": true
486
+ },
487
+ "custom-tool": {
488
+ "command": "node",
489
+ "args": ["/path/to/server.js"],
490
+ "env": {
491
+ "API_KEY": "value"
492
+ },
493
+ "description": "My custom tool",
494
+ "enabled": true
495
+ }
496
+ }
497
+ }
498
+ \`\`\`
499
+
500
+ **Fields:**
501
+ - **command** (required) - Executable to run (npx, node, python, etc.)
502
+ - **args** (optional) - Arguments to pass to the command
503
+ - **env** (optional) - Environment variables for the server process
504
+ - **description** (optional) - Human-readable description
505
+ - **enabled** (optional) - Whether server is active (default: true)
506
+
507
+ ### Managing Servers with /mcp
508
+
509
+ Type /mcp to open the MCP server management interface:
510
+
511
+ - **View** - See all configured servers with their status and details
512
+ - **A** - Add a new server (guided setup)
513
+ - **R** - Remove a server
514
+ - **T** - Test connection to all servers
515
+ - **ESC/Q** - Close the interface
516
+
517
+ ## npm Package Information
518
+
519
+ The Supatest CLI is published on npm as **@supatest/cli**.
520
+
521
+ ### Package Details
522
+ - **Name**: @supatest/cli
523
+ - **Registry**: https://www.npmjs.com/package/@supatest/cli
524
+ - **Installation**: npm install -g @supatest/cli (global)
525
+ - **Usage without install**: npx @supatest/cli
526
+
527
+ ### npm Link Setup
528
+
529
+ For local development of the Supatest CLI package:
530
+
531
+ **What is npm link?**
532
+ - Creates a symbolic link from your package to global node_modules
533
+ - Allows testing local changes without publishing to npm
534
+ - Perfect for development and testing
535
+
536
+ **Setup steps:**
537
+
538
+ 1. Clone the Supatest repository
539
+ 2. Navigate to cli/ directory: \`cd cli\`
540
+ 3. Link the package: \`npm link\`
541
+ 4. Now run \`supatest\` anywhere to use your local development version
542
+
543
+ **Usage:**
544
+ - Run \`supatest\` commands normally - they use your linked local version
545
+ - Changes you make are immediately reflected
546
+ - Useful for testing features before submitting pull requests
547
+
548
+ **To unlink:**
549
+ - Run \`npm unlink -g @supatest/cli\` to restore the npm version
550
+
551
+ **For team members:**
552
+ - Shared development: Other devs can link to the same directory
553
+ - Ensure you're on the same branch
554
+ - Test against each other's changes
555
+
556
+ ### Checking Latest Docs
557
+
558
+ I can fetch the latest information from the npm package page if you'd like:
559
+ - Current version and release notes
560
+ - Package size and dependencies
561
+ - Recent updates and changes
562
+ - How to report issues or contribute
563
+
564
+ Just ask: "Can you check the npm docs?" or "What's the latest version?"
565
+
566
+ ## Usage Tips & Tricks
567
+
568
+ ### Multi-line Prompts
569
+ - Use Shift+Enter to write prompts across multiple lines
570
+ - Useful for complex instructions: "Test the login flow. First, verify email validation. Then test password reset."
571
+
572
+ ### Referencing Files
573
+ - Drag files from your file explorer into the terminal to add paths
574
+ - Use @filename syntax with Tab autocomplete
575
+ - The agent will examine those files when analyzing your request
576
+
577
+ ### Terminal Integration
578
+ - Most terminals support drag-and-drop of files
579
+ - Works with logs, test output, configuration files
580
+ - Paste long outputs and the agent will analyze them
581
+
582
+ ### Running vs Reading Tests
583
+ - The agent can create, run, and fix tests automatically
584
+ - Provide clear descriptions of what to test
585
+ - The agent will handle framework-specific syntax
586
+
587
+ ### Session Persistence
588
+ - Your conversations are saved and can be resumed
589
+ - Use /resume to continue work from where you left off
590
+ - Useful for long-running projects
591
+
592
+ ### Efficient Feedback
593
+ - Be specific about what's wrong or what you need
594
+ - Include error messages when reporting issues
595
+ - Describe expected vs actual behavior
596
+
597
+ </cli-documentation>
598
+
599
+ <conversation-guidelines>
600
+ When answering questions:
601
+
602
+ 1. **Be direct**: Answer the question asked first, then offer related information
603
+ 2. **Show examples**: Include practical examples when explaining features
604
+ 3. **Cross-reference**: When relevant, mention related commands or features
605
+ 4. **Offer help**: Ask if the user needs clarification or has follow-up questions
606
+ 5. **Know your limits**: For questions outside CLI features or npm link setup, suggest appropriate next steps
607
+
608
+ When users ask about npm documentation or want latest info:
609
+ - Offer to check the npm package page
610
+ - Use WebFetch to retrieve current information
611
+ - Share relevant details (versions, changes, links)
612
+
613
+ When users mix help with actual test building:
614
+ - Answer their help question
615
+ - Then ask if they want to start building tests or need more information
616
+
617
+ </conversation-guidelines>
618
+
619
+ <action-guide>
620
+ ## How to Help Users with Actions
621
+
622
+ Beyond answering questions, you can actively help users by taking actions. Here are common scenarios and how to handle them:
623
+
624
+ ### Adding an MCP Server
625
+
626
+ **When user asks:** "Can you help me add an MCP server?" or "How do I add a custom MCP tool?"
627
+
628
+ **Action steps:**
629
+ 1. Ask what the MCP server is (command, args, optional env vars, description)
630
+ 2. Check if .supatest/mcp.json exists: \`read .supatest/mcp.json\`
631
+ 3. Parse the existing config (or create empty structure if missing)
632
+ 4. Add the new server to mcpServers object
633
+ 5. Write the updated config back
634
+ 6. Show the user what was added and test it if they want
635
+
636
+ **Example interaction:**
637
+ - User: "I want to add a custom database tool as MCP server"
638
+ - You: "Great! I can help. Tell me the command to run, any arguments, and what it does"
639
+ - User: "node /path/to/db-tool.js --debug"
640
+ - You: "I'll add that to your .supatest/mcp.json. Let me read your current config first..."
641
+ [Read file, add server, write back]
642
+ - You: "Done! I've added your 'db-tool' server. Let me test the connection..."
643
+ [Run test command]
644
+
645
+ ### Checking Current Configuration
646
+
647
+ **When user asks:** "What's my current setup?" or "Show me my MCP servers"
648
+
649
+ **Action steps:**
650
+ 1. Read .supatest/SUPATEST.md to show test framework info
651
+ 2. Read .supatest/mcp.json to show configured servers
652
+ 3. Check .supatest/settings.json for saved preferences
653
+ 4. Display the information clearly formatted
654
+ 5. Explain what each configuration means
655
+
656
+ ### Fixing Configuration Issues
657
+
658
+ **When user says:** "My MCP server isn't working" or "I think my config is wrong"
659
+
660
+ **Action steps:**
661
+ 1. Read their configuration files
662
+ 2. Test the MCP connection if it's a server
663
+ 3. Check for common issues:
664
+ - Missing .supatest/ directory
665
+ - Malformed JSON
666
+ - Invalid command paths
667
+ - Missing required fields
668
+ 4. Suggest fixes and apply them with user permission
669
+ 5. Test again to confirm resolution
670
+
671
+ ### Setting Up npm Link
672
+
673
+ **When user asks:** "How do I set up npm link for development?"
674
+
675
+ **Action steps:**
676
+ 1. Verify they're in the cli/ directory (check current location)
677
+ 2. Explain the steps
678
+ 3. Run the npm link command for them (with permission)
679
+ 4. Verify it worked by checking if supatest command is available
680
+ 5. Show them how to unlink when done
681
+
682
+ ### Environment Variable Help
683
+
684
+ **When user asks:** "How do I set SUPATEST_API_KEY?" or "Where do I put environment variables?"
685
+
686
+ **Action steps:**
687
+ 1. Explain the options (export, .env file, shell config)
688
+ 2. If they want help creating a .env file, offer to create one
689
+ 3. Show them the format they need
690
+ 4. For shell profile setup, show the exact lines to add
691
+
692
+ ### Project Instructions Update
693
+
694
+ **When user says:** "Can you help me document my project setup?"
695
+
696
+ **Action steps:**
697
+ 1. Read their current .supatest/SUPATEST.md if it exists
698
+ 2. Ask about their test framework, patterns, conventions
699
+ 3. Create/update the documentation
700
+ 4. Show them the result and ask if it looks good
701
+ 5. Write it back with their approval
702
+
703
+ ### Permission and Transparency
704
+
705
+ **Critical rules for actions:**
706
+ - ALWAYS ask permission before modifying files
707
+ - SHOW what you're about to do before doing it
708
+ - After making changes, DISPLAY what was added/modified
709
+ - Explain WHY each change was necessary
710
+ - Offer to undo or adjust if user isn't happy with the result
711
+ - When in doubt, read the file first and show it to user before editing
712
+
713
+ ### Tools You Can Use
714
+
715
+ - **Read**: Check configuration and setup files
716
+ - **Write/Edit**: Create or update config files
717
+ - **Bash**: Run commands to check Node version, test connections, verify npm link
718
+ - **WebFetch**: Get latest npm documentation when requested
719
+
720
+ Use these tools naturally when helping - not as a last resort, but as the primary way to provide hands-on assistance.
721
+
722
+ </action-guide>
723
+ `;
724
+ }
725
+ });
726
+
221
727
  // src/prompts/planner.ts
222
728
  var plannerPrompt;
223
729
  var init_planner = __esm({
@@ -290,6 +796,7 @@ var init_prompts = __esm({
290
796
  init_builder();
291
797
  init_discover();
292
798
  init_fixer();
799
+ init_help();
293
800
  init_planner();
294
801
  }
295
802
  });
@@ -610,14 +1117,30 @@ function getToolDisplayName(toolName) {
610
1117
  }
611
1118
  function getToolCategory(toolName) {
612
1119
  const name = toolName.toLowerCase();
613
- if (name.includes("read")) return "Read";
614
- if (name.includes("write")) return "Write";
615
- if (name.includes("edit")) return "Edit";
616
- if (name.includes("bash") || name.includes("command")) return "Bash";
617
- if (name.includes("glob")) return "Glob";
618
- if (name.includes("grep")) return "Grep";
619
- if (name.includes("task")) return "Task";
620
- if (name.includes("todo")) return "Todo";
1120
+ if (name.includes("read")) {
1121
+ return "Read";
1122
+ }
1123
+ if (name.includes("write")) {
1124
+ return "Write";
1125
+ }
1126
+ if (name.includes("edit")) {
1127
+ return "Edit";
1128
+ }
1129
+ if (name.includes("bash") || name.includes("command")) {
1130
+ return "Command";
1131
+ }
1132
+ if (name.includes("glob")) {
1133
+ return "Glob";
1134
+ }
1135
+ if (name.includes("grep")) {
1136
+ return "Grep";
1137
+ }
1138
+ if (name.includes("task")) {
1139
+ return "Task";
1140
+ }
1141
+ if (name.includes("todo")) {
1142
+ return "Todo";
1143
+ }
621
1144
  return getToolDisplayName(toolName);
622
1145
  }
623
1146
  function getToolGroupCounts(tools) {
@@ -5368,7 +5891,7 @@ var CLI_VERSION;
5368
5891
  var init_version = __esm({
5369
5892
  "src/version.ts"() {
5370
5893
  "use strict";
5371
- CLI_VERSION = "0.0.35";
5894
+ CLI_VERSION = "0.0.37";
5372
5895
  }
5373
5896
  });
5374
5897
 
@@ -6224,6 +6747,7 @@ var init_command_discovery = __esm({
6224
6747
 
6225
6748
  // src/utils/mcp-loader.ts
6226
6749
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
6750
+ import { homedir as homedir2 } from "os";
6227
6751
  import { join as join4 } from "path";
6228
6752
  function expandEnvVar(value) {
6229
6753
  return value.replace(/\$\{([^}]+)\}/g, (_, expr) => {
@@ -6246,8 +6770,7 @@ function expandServerConfig(config2) {
6246
6770
  }
6247
6771
  return expanded;
6248
6772
  }
6249
- function loadMcpServers(cwd) {
6250
- const mcpPath = join4(cwd, ".supatest", "mcp.json");
6773
+ function loadMcpServersFromFile(mcpPath) {
6251
6774
  if (!existsSync3(mcpPath)) {
6252
6775
  return {};
6253
6776
  }
@@ -6259,6 +6782,9 @@ function loadMcpServers(cwd) {
6259
6782
  }
6260
6783
  const expanded = {};
6261
6784
  for (const [name, serverConfig] of Object.entries(config2.mcpServers)) {
6785
+ if (serverConfig.enabled === false) {
6786
+ continue;
6787
+ }
6262
6788
  expanded[name] = expandServerConfig(serverConfig);
6263
6789
  }
6264
6790
  return expanded;
@@ -6270,6 +6796,13 @@ function loadMcpServers(cwd) {
6270
6796
  return {};
6271
6797
  }
6272
6798
  }
6799
+ function loadMcpServers(cwd) {
6800
+ const globalMcpPath = join4(homedir2(), ".supatest", "mcp.json");
6801
+ const globalServers = loadMcpServersFromFile(globalMcpPath);
6802
+ const projectMcpPath = join4(cwd, ".supatest", "mcp.json");
6803
+ const projectServers = loadMcpServersFromFile(projectMcpPath);
6804
+ return { ...globalServers, ...projectServers };
6805
+ }
6273
6806
  var init_mcp_loader = __esm({
6274
6807
  "src/utils/mcp-loader.ts"() {
6275
6808
  "use strict";
@@ -6302,7 +6835,7 @@ var init_project_instructions = __esm({
6302
6835
 
6303
6836
  // src/core/agent.ts
6304
6837
  import { createRequire } from "module";
6305
- import { homedir as homedir2 } from "os";
6838
+ import { homedir as homedir3 } from "os";
6306
6839
  import { dirname, join as join6 } from "path";
6307
6840
  import { query } from "@anthropic-ai/claude-agent-sdk";
6308
6841
  var CoreAgent;
@@ -6403,7 +6936,7 @@ ${projectInstructions}`,
6403
6936
  this.presenter.onLog(`Auth: Using Claude Max (default Claude Code credentials)`);
6404
6937
  logger.debug("[agent] Claude Max mode: Using default ~/.claude/ config, cleared provider credentials");
6405
6938
  } else {
6406
- const internalConfigDir = join6(homedir2(), ".supatest", "claude-internal");
6939
+ const internalConfigDir = join6(homedir3(), ".supatest", "claude-internal");
6407
6940
  cleanEnv.CLAUDE_CONFIG_DIR = internalConfigDir;
6408
6941
  cleanEnv.ANTHROPIC_API_KEY = config2.supatestApiKey || "";
6409
6942
  cleanEnv.ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL || "";
@@ -6434,9 +6967,17 @@ ${projectInstructions}`,
6434
6967
  // MCP servers from .supatest/mcp.json
6435
6968
  // Users can add servers like Playwright if needed
6436
6969
  mcpServers: (() => {
6970
+ logger.debug("[agent] Loading MCP servers for query", { cwd });
6437
6971
  const servers = loadMcpServers(cwd);
6972
+ logger.debug("[agent] MCP servers loaded", {
6973
+ count: Object.keys(servers).length,
6974
+ serverNames: Object.keys(servers),
6975
+ fullConfig: servers
6976
+ });
6438
6977
  if (Object.keys(servers).length > 0) {
6439
6978
  this.presenter.onLog(`MCP servers: ${Object.keys(servers).join(", ")}`);
6979
+ } else {
6980
+ logger.debug("[agent] No MCP servers configured");
6440
6981
  }
6441
6982
  return servers;
6442
6983
  })(),
@@ -6496,7 +7037,14 @@ ${projectInstructions}`,
6496
7037
  maxTurns: options.maxTurns,
6497
7038
  permissionMode: options.permissionMode,
6498
7039
  hasResume: !!options.resume,
6499
- cwd: options.cwd
7040
+ cwd: options.cwd,
7041
+ mcpServerCount: Object.keys(options.mcpServers || {}).length,
7042
+ mcpServerNames: Object.keys(options.mcpServers || {})
7043
+ });
7044
+ logger.debug("[agent] Full query options", {
7045
+ ...options,
7046
+ // Don't log sensitive env vars
7047
+ env: Object.keys(options.env || {})
6500
7048
  });
6501
7049
  const queryIterator = query({ prompt, options });
6502
7050
  if (this.messageBridge) {
@@ -8259,7 +8807,7 @@ var init_MessageList = __esm({
8259
8807
  return { ...group.messages[0], _isMessage: true };
8260
8808
  })
8261
8809
  ], [completedGroups]);
8262
- return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column" }, /* @__PURE__ */ React13.createElement(Static, { items: staticItems, key: staticRemountKey }, (item) => {
8810
+ return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column" }, /* @__PURE__ */ React13.createElement(Static, { items: staticItems, key: `${staticRemountKey}-${toolGroupsExpanded ? "expanded" : "collapsed"}` }, (item) => {
8263
8811
  if (item.type === "header") {
8264
8812
  return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", key: "header" }, /* @__PURE__ */ React13.createElement(Header, { currentFolder, gitBranch, headless }));
8265
8813
  }
@@ -8281,7 +8829,7 @@ var init_MessageList = __esm({
8281
8829
  return null;
8282
8830
  }
8283
8831
  return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", key: group.type === "group" ? `current-group-${idx}` : group.messages[0].id, width: "100%" }, content);
8284
- }), /* @__PURE__ */ React13.createElement(QueuedMessageDisplay, { messageQueue: queuedTasks }), isAgentRunning && !hasPendingAssistant && /* @__PURE__ */ React13.createElement(LoadingMessage, { headless, key: "loading" }));
8832
+ }), /* @__PURE__ */ React13.createElement(QueuedMessageDisplay, { messageQueue: queuedTasks }), isAgentRunning && !hasPendingAssistant && /* @__PURE__ */ React13.createElement(LoadingMessage, { headless, key: "loading" }), /* @__PURE__ */ React13.createElement(Box12, { height: 1 }));
8285
8833
  };
8286
8834
  }
8287
8835
  });
@@ -8409,7 +8957,7 @@ var init_encryption = __esm({
8409
8957
 
8410
8958
  // src/utils/token-storage.ts
8411
8959
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync } from "fs";
8412
- import { homedir as homedir4 } from "os";
8960
+ import { homedir as homedir5 } from "os";
8413
8961
  import { join as join7 } from "path";
8414
8962
  function getTokenFilePath() {
8415
8963
  const apiUrl = process.env.SUPATEST_API_URL || PRODUCTION_API_URL;
@@ -8484,7 +9032,7 @@ var init_token_storage = __esm({
8484
9032
  "src/utils/token-storage.ts"() {
8485
9033
  "use strict";
8486
9034
  init_encryption();
8487
- CONFIG_DIR = join7(homedir4(), ".supatest");
9035
+ CONFIG_DIR = join7(homedir5(), ".supatest");
8488
9036
  PRODUCTION_API_URL = "https://code-api.supatest.ai";
8489
9037
  STORAGE_VERSION = 2;
8490
9038
  TOKEN_FILE = join7(CONFIG_DIR, "token.json");
@@ -8609,18 +9157,22 @@ async function exchangeCodeForToken(code, state) {
8609
9157
  function openBrowser(url) {
8610
9158
  const os3 = platform();
8611
9159
  let command;
9160
+ let args;
8612
9161
  switch (os3) {
8613
9162
  case "darwin":
8614
9163
  command = "open";
9164
+ args = [url];
8615
9165
  break;
8616
9166
  case "win32":
8617
9167
  command = "start";
9168
+ args = ["", url];
8618
9169
  break;
8619
9170
  default:
8620
9171
  command = "xdg-open";
9172
+ args = [url];
8621
9173
  }
8622
9174
  const options = { detached: true, stdio: "ignore", shell: os3 === "win32" };
8623
- spawn2(command, [url], options).unref();
9175
+ spawn2(command, args, options).unref();
8624
9176
  }
8625
9177
  function startCallbackServer(port, expectedState) {
8626
9178
  return new Promise((resolve2, reject) => {
@@ -8959,7 +9511,7 @@ var init_login = __esm({
8959
9511
  // src/utils/claude-max.ts
8960
9512
  import { execSync as execSync5 } from "child_process";
8961
9513
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
8962
- import { homedir as homedir5 } from "os";
9514
+ import { homedir as homedir6 } from "os";
8963
9515
  import { join as join8 } from "path";
8964
9516
  function isClaudeMaxAvailable() {
8965
9517
  const platform2 = process.platform;
@@ -8996,7 +9548,7 @@ function checkMacOSKeychain() {
8996
9548
  }
8997
9549
  function checkCredentialsFile() {
8998
9550
  try {
8999
- const credentialsPath = join8(homedir5(), ".claude", ".credentials.json");
9551
+ const credentialsPath = join8(homedir6(), ".claude", ".credentials.json");
9000
9552
  if (!existsSync6(credentialsPath)) {
9001
9553
  logger.debug("[claude-max] Credentials file not found", { path: credentialsPath });
9002
9554
  return false;
@@ -9024,16 +9576,248 @@ var init_claude_max = __esm({
9024
9576
  }
9025
9577
  });
9026
9578
 
9027
- // src/utils/settings-loader.ts
9579
+ // src/utils/mcp-manager.ts
9028
9580
  import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
9581
+ import { homedir as homedir7 } from "os";
9029
9582
  import { join as join9 } from "path";
9583
+ function getGlobalMcpPath() {
9584
+ return join9(homedir7(), ".supatest", "mcp.json");
9585
+ }
9586
+ function getProjectMcpPath(cwd) {
9587
+ return join9(cwd, ".supatest", "mcp.json");
9588
+ }
9589
+ function loadMcpConfigFromFile(mcpPath, scope) {
9590
+ if (!existsSync7(mcpPath)) {
9591
+ return {};
9592
+ }
9593
+ try {
9594
+ const content = readFileSync6(mcpPath, "utf-8");
9595
+ const config2 = JSON.parse(content);
9596
+ if (!config2.mcpServers) {
9597
+ return {};
9598
+ }
9599
+ const servers = {};
9600
+ for (const [name, serverConfig] of Object.entries(config2.mcpServers)) {
9601
+ servers[name] = {
9602
+ name,
9603
+ command: serverConfig.command,
9604
+ args: serverConfig.args,
9605
+ env: serverConfig.env,
9606
+ enabled: serverConfig.enabled !== false,
9607
+ // Default to enabled
9608
+ description: serverConfig.description,
9609
+ scope
9610
+ };
9611
+ }
9612
+ return servers;
9613
+ } catch (error) {
9614
+ console.warn(
9615
+ `Warning: Failed to load MCP config from ${mcpPath}:`,
9616
+ error instanceof Error ? error.message : String(error)
9617
+ );
9618
+ return {};
9619
+ }
9620
+ }
9621
+ function loadMcpConfig(cwd) {
9622
+ const globalServers = loadMcpConfigFromFile(getGlobalMcpPath(), "global");
9623
+ const projectServers = loadMcpConfigFromFile(getProjectMcpPath(cwd), "project");
9624
+ return { ...globalServers, ...projectServers };
9625
+ }
9626
+ function saveMcpConfigToFile(mcpPath, servers) {
9627
+ const mcpDir = join9(mcpPath, "..");
9628
+ if (!existsSync7(mcpDir)) {
9629
+ mkdirSync3(mcpDir, { recursive: true });
9630
+ }
9631
+ const config2 = {
9632
+ mcpServers: {}
9633
+ };
9634
+ for (const [name, server] of Object.entries(servers)) {
9635
+ config2.mcpServers[name] = {
9636
+ command: server.command,
9637
+ args: server.args,
9638
+ env: server.env,
9639
+ enabled: server.enabled,
9640
+ description: server.description
9641
+ };
9642
+ }
9643
+ writeFileSync2(mcpPath, JSON.stringify(config2, null, 2), "utf-8");
9644
+ }
9645
+ function saveMcpConfig(cwd, servers) {
9646
+ const globalServers = {};
9647
+ const projectServers = {};
9648
+ for (const [name, server] of Object.entries(servers)) {
9649
+ if (server.scope === "global") {
9650
+ globalServers[name] = server;
9651
+ } else {
9652
+ projectServers[name] = server;
9653
+ }
9654
+ }
9655
+ if (Object.keys(globalServers).length > 0) {
9656
+ saveMcpConfigToFile(getGlobalMcpPath(), globalServers);
9657
+ }
9658
+ if (Object.keys(projectServers).length > 0) {
9659
+ saveMcpConfigToFile(getProjectMcpPath(cwd), projectServers);
9660
+ }
9661
+ }
9662
+ function addMcpServer(cwd, name, command, args, env, description, scope = "project") {
9663
+ const servers = loadMcpConfig(cwd);
9664
+ const isUpdate = !!servers[name];
9665
+ servers[name] = {
9666
+ name,
9667
+ command,
9668
+ args,
9669
+ env,
9670
+ enabled: true,
9671
+ description,
9672
+ scope
9673
+ };
9674
+ try {
9675
+ saveMcpConfig(cwd, servers);
9676
+ const action = isUpdate ? "updated" : "added";
9677
+ const scopeLabel = scope === "global" ? "(global)" : "(project)";
9678
+ return {
9679
+ success: true,
9680
+ message: `MCP server "${name}" ${action} successfully ${scopeLabel}`,
9681
+ isUpdate
9682
+ };
9683
+ } catch (error) {
9684
+ return {
9685
+ success: false,
9686
+ message: `Failed to add MCP server: ${error instanceof Error ? error.message : String(error)}`,
9687
+ isUpdate
9688
+ };
9689
+ }
9690
+ }
9691
+ function removeMcpServer(cwd, name) {
9692
+ const servers = loadMcpConfig(cwd);
9693
+ if (!servers[name]) {
9694
+ return {
9695
+ success: false,
9696
+ message: `MCP server "${name}" not found`
9697
+ };
9698
+ }
9699
+ delete servers[name];
9700
+ try {
9701
+ saveMcpConfig(cwd, servers);
9702
+ return {
9703
+ success: true,
9704
+ message: `MCP server "${name}" removed successfully`
9705
+ };
9706
+ } catch (error) {
9707
+ return {
9708
+ success: false,
9709
+ message: `Failed to remove MCP server: ${error instanceof Error ? error.message : String(error)}`
9710
+ };
9711
+ }
9712
+ }
9713
+ function enableMcpServer(cwd, name) {
9714
+ const servers = loadMcpConfig(cwd);
9715
+ if (!servers[name]) {
9716
+ return {
9717
+ success: false,
9718
+ message: `MCP server "${name}" not found`
9719
+ };
9720
+ }
9721
+ servers[name].enabled = true;
9722
+ try {
9723
+ saveMcpConfig(cwd, servers);
9724
+ return {
9725
+ success: true,
9726
+ message: `MCP server "${name}" enabled`
9727
+ };
9728
+ } catch (error) {
9729
+ return {
9730
+ success: false,
9731
+ message: `Failed to enable MCP server: ${error instanceof Error ? error.message : String(error)}`
9732
+ };
9733
+ }
9734
+ }
9735
+ function disableMcpServer(cwd, name) {
9736
+ const servers = loadMcpConfig(cwd);
9737
+ if (!servers[name]) {
9738
+ return {
9739
+ success: false,
9740
+ message: `MCP server "${name}" not found`
9741
+ };
9742
+ }
9743
+ servers[name].enabled = false;
9744
+ try {
9745
+ saveMcpConfig(cwd, servers);
9746
+ return {
9747
+ success: true,
9748
+ message: `MCP server "${name}" disabled`
9749
+ };
9750
+ } catch (error) {
9751
+ return {
9752
+ success: false,
9753
+ message: `Failed to disable MCP server: ${error instanceof Error ? error.message : String(error)}`
9754
+ };
9755
+ }
9756
+ }
9757
+ async function testMcpConnection(name, server) {
9758
+ try {
9759
+ let Client;
9760
+ let StdioClientTransport;
9761
+ try {
9762
+ const clientModule = await import("@modelcontextprotocol/sdk/client");
9763
+ const stdioModule = await import("@modelcontextprotocol/sdk/client/stdio.js");
9764
+ Client = clientModule.Client;
9765
+ StdioClientTransport = stdioModule.StdioClientTransport;
9766
+ } catch (error) {
9767
+ return {
9768
+ status: "failed",
9769
+ message: `MCP SDK not installed for server "${name}"`,
9770
+ error: `Install @modelcontextprotocol/sdk to enable connection testing: ${error instanceof Error ? error.message : String(error)}`
9771
+ };
9772
+ }
9773
+ const client = new Client({
9774
+ name: "supatest-mcp-test",
9775
+ version: "1.0.0"
9776
+ });
9777
+ const transport = new StdioClientTransport({
9778
+ command: server.command,
9779
+ args: server.args || [],
9780
+ env: { ...process.env, ...server.env }
9781
+ });
9782
+ const timeoutPromise = new Promise((_, reject) => {
9783
+ setTimeout(() => reject(new Error("Connection timeout (5s)")), 5e3);
9784
+ });
9785
+ await Promise.race([
9786
+ (async () => {
9787
+ await client.connect(transport);
9788
+ await client.close();
9789
+ })(),
9790
+ timeoutPromise
9791
+ ]);
9792
+ return {
9793
+ status: "connected",
9794
+ message: `MCP server "${name}" connected successfully`
9795
+ };
9796
+ } catch (error) {
9797
+ const errorMessage = error instanceof Error ? error.message : String(error);
9798
+ return {
9799
+ status: "failed",
9800
+ message: `Failed to connect to MCP server "${name}"`,
9801
+ error: errorMessage
9802
+ };
9803
+ }
9804
+ }
9805
+ var init_mcp_manager = __esm({
9806
+ "src/utils/mcp-manager.ts"() {
9807
+ "use strict";
9808
+ }
9809
+ });
9810
+
9811
+ // src/utils/settings-loader.ts
9812
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
9813
+ import { join as join10 } from "path";
9030
9814
  function loadSupatestSettings(cwd) {
9031
- const settingsPath = join9(cwd, ".supatest", "settings.json");
9032
- if (!existsSync7(settingsPath)) {
9815
+ const settingsPath = join10(cwd, ".supatest", "settings.json");
9816
+ if (!existsSync8(settingsPath)) {
9033
9817
  return {};
9034
9818
  }
9035
9819
  try {
9036
- const content = readFileSync6(settingsPath, "utf-8");
9820
+ const content = readFileSync7(settingsPath, "utf-8");
9037
9821
  return JSON.parse(content);
9038
9822
  } catch (error) {
9039
9823
  console.warn(
@@ -9044,11 +9828,11 @@ function loadSupatestSettings(cwd) {
9044
9828
  }
9045
9829
  }
9046
9830
  function saveSupatestSettings(cwd, settings) {
9047
- const settingsDir = join9(cwd, ".supatest");
9048
- const settingsPath = join9(settingsDir, "settings.json");
9831
+ const settingsDir = join10(cwd, ".supatest");
9832
+ const settingsPath = join10(settingsDir, "settings.json");
9049
9833
  try {
9050
- if (!existsSync7(settingsDir)) {
9051
- mkdirSync3(settingsDir, { recursive: true });
9834
+ if (!existsSync8(settingsDir)) {
9835
+ mkdirSync4(settingsDir, { recursive: true });
9052
9836
  }
9053
9837
  const existingSettings = loadSupatestSettings(cwd);
9054
9838
  const mergedSettings = {
@@ -9064,7 +9848,7 @@ function saveSupatestSettings(cwd, settings) {
9064
9848
  ...settings.hooks
9065
9849
  }
9066
9850
  };
9067
- writeFileSync2(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
9851
+ writeFileSync3(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
9068
9852
  } catch (error) {
9069
9853
  console.warn(
9070
9854
  `Warning: Failed to save settings to ${settingsPath}:`,
@@ -10481,56 +11265,6 @@ var init_FixFlow = __esm({
10481
11265
  }
10482
11266
  });
10483
11267
 
10484
- // src/ui/components/HelpMenu.tsx
10485
- import { Box as Box20, Text as Text18, useInput as useInput5 } from "ink";
10486
- import React23, { useEffect as useEffect9, useState as useState9 } from "react";
10487
- var HelpMenu;
10488
- var init_HelpMenu = __esm({
10489
- "src/ui/components/HelpMenu.tsx"() {
10490
- "use strict";
10491
- init_command_discovery();
10492
- init_theme();
10493
- HelpMenu = ({ isAuthenticated, onClose, cwd }) => {
10494
- const [customCommands, setCustomCommands] = useState9([]);
10495
- useEffect9(() => {
10496
- const projectDir = cwd || process.cwd();
10497
- const commands = discoverCommands(projectDir);
10498
- setCustomCommands(commands);
10499
- }, [cwd]);
10500
- useInput5((input, key) => {
10501
- if (key.escape || input === "q" || input === "?" || key.ctrl && input === "h") {
10502
- onClose();
10503
- }
10504
- });
10505
- return /* @__PURE__ */ React23.createElement(
10506
- Box20,
10507
- {
10508
- borderColor: theme.border.accent,
10509
- borderStyle: "round",
10510
- flexDirection: "column",
10511
- paddingX: 2,
10512
- paddingY: 1
10513
- },
10514
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.accent }, "\u{1F4D6} Supatest AI CLI - Help"),
10515
- /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }),
10516
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.secondary }, "Slash Commands:"),
10517
- /* @__PURE__ */ React23.createElement(Box20, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/help"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " or "), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/?"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Toggle this help menu")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/resume"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Resume a previous session")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/clear"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Clear message history")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/model"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Cycle through available models")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/provider"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Select LLM provider (Supatest Managed or Claude Max)")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/setup"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Initial setup for Supatest CLI")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/discover"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Discover test framework and write to SUPATEST.md")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/feedback"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Report an issue or request a feature")), isAuthenticated ? /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/logout"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Log out of Supatest")) : /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/login"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Authenticate with Supatest")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/exit"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Exit the CLI"))),
10518
- customCommands.length > 0 && /* @__PURE__ */ React23.createElement(React23.Fragment, null, /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }), /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.secondary }, "Project Commands:"), /* @__PURE__ */ React23.createElement(Box20, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, customCommands.slice(0, 5).map((cmd) => /* @__PURE__ */ React23.createElement(Text18, { key: cmd.name }, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "/", cmd.name), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, cmd.description ? ` - ${cmd.description}` : ""))), customCommands.length > 5 && /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, "...and ", customCommands.length - 5, " more (use Tab to autocomplete)"))),
10519
- /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }),
10520
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.secondary }, "Keyboard Shortcuts:"),
10521
- /* @__PURE__ */ React23.createElement(Box20, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "?"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " ", "- Toggle help (when input is empty)")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "Ctrl+H"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Toggle help")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "Ctrl+C"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " ", "- Exit (or clear input if not empty)")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "Ctrl+D"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Exit immediately")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "Ctrl+L"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Clear terminal screen")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "Ctrl+U"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Clear current input line")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "ESC"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Interrupt running agent")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "Shift+Enter"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Add new line in input")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "ctrl+o"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Toggle tool outputs")), /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "Ctrl+M"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " - Cycle through models"))),
10522
- /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }),
10523
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.secondary }, "File References:"),
10524
- /* @__PURE__ */ React23.createElement(Box20, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React23.createElement(Text18, null, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.accent }, "@filename"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, " ", "- Reference a file (autocomplete with Tab)")), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, 'Example: "Fix the bug in @src/app.ts"')),
10525
- /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }),
10526
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.secondary }, "Tips:"),
10527
- /* @__PURE__ */ React23.createElement(Box20, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, "\u2022 Press Enter to submit your task"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, "\u2022 Use Shift+Enter to write multi-line prompts"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, "\u2022 Drag and drop files into the terminal to add file paths"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, "\u2022 The agent will automatically run tools and fix issues"), /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, "\u2022 Use Ctrl+L to clear the terminal screen without clearing the messages history")),
10528
- /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React23.createElement(Text18, { bold: true }, "ESC"), " or ", /* @__PURE__ */ React23.createElement(Text18, { bold: true }, "?"), " to close"))
10529
- );
10530
- };
10531
- }
10532
- });
10533
-
10534
11268
  // src/ui/utils/file-completion.ts
10535
11269
  import fs4 from "fs";
10536
11270
  import path4 from "path";
@@ -10614,8 +11348,8 @@ var init_file_completion = __esm({
10614
11348
  });
10615
11349
 
10616
11350
  // src/ui/components/ModelSelector.tsx
10617
- import { Box as Box21, Text as Text19, useInput as useInput6 } from "ink";
10618
- import React24, { useState as useState10 } from "react";
11351
+ import { Box as Box20, Text as Text18, useInput as useInput5 } from "ink";
11352
+ import React23, { useState as useState9 } from "react";
10619
11353
  function getAvailableModels(isClaudeMax) {
10620
11354
  if (isClaudeMax) {
10621
11355
  return AVAILABLE_MODELS.map((m) => ({
@@ -10650,8 +11384,8 @@ var init_ModelSelector = __esm({
10650
11384
  }) => {
10651
11385
  const models = getAvailableModels(isClaudeMax);
10652
11386
  const currentIndex = models.findIndex((m) => m.id === currentModel);
10653
- const [selectedIndex, setSelectedIndex] = useState10(currentIndex >= 0 ? currentIndex : 0);
10654
- useInput6((input, key) => {
11387
+ const [selectedIndex, setSelectedIndex] = useState9(currentIndex >= 0 ? currentIndex : 0);
11388
+ useInput5((input, key) => {
10655
11389
  if (key.upArrow) {
10656
11390
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : models.length - 1);
10657
11391
  } else if (key.downArrow) {
@@ -10662,12 +11396,12 @@ var init_ModelSelector = __esm({
10662
11396
  onCancel();
10663
11397
  }
10664
11398
  });
10665
- return /* @__PURE__ */ React24.createElement(Box21, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React24.createElement(Box21, { marginBottom: 1 }, /* @__PURE__ */ React24.createElement(Text19, { bold: true, color: theme.text.accent }, "Select Model")), /* @__PURE__ */ React24.createElement(Box21, { marginBottom: 1 }, /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "Cost: ", /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, "0.5x"), " (Small) \u2022 ", /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, "1x"), " (Medium) \u2022 ", /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, "2x"), " (Premium)")), /* @__PURE__ */ React24.createElement(Box21, { flexDirection: "column" }, models.map((model, index) => {
11399
+ return /* @__PURE__ */ React23.createElement(Box20, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React23.createElement(Box20, { marginBottom: 1 }, /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.accent }, "Select Model")), /* @__PURE__ */ React23.createElement(Box20, { marginBottom: 1 }, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, "Cost: ", /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.info }, "0.5x"), " (Small) \u2022 ", /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.info }, "1x"), " (Medium) \u2022 ", /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.info }, "2x"), " (Premium)")), /* @__PURE__ */ React23.createElement(Box20, { flexDirection: "column" }, models.map((model, index) => {
10666
11400
  const isSelected = index === selectedIndex;
10667
11401
  const isCurrent = model.id === currentModel;
10668
11402
  const indicator = isSelected ? "\u25B6 " : " ";
10669
- return /* @__PURE__ */ React24.createElement(Box21, { gap: 1, key: model.id }, /* @__PURE__ */ React24.createElement(
10670
- Text19,
11403
+ return /* @__PURE__ */ React23.createElement(Box20, { gap: 1, key: model.id }, /* @__PURE__ */ React23.createElement(
11404
+ Text18,
10671
11405
  {
10672
11406
  backgroundColor: isSelected ? theme.text.accent : void 0,
10673
11407
  bold: isSelected,
@@ -10675,15 +11409,15 @@ var init_ModelSelector = __esm({
10675
11409
  },
10676
11410
  indicator,
10677
11411
  model.name.padEnd(12)
10678
- ), /* @__PURE__ */ React24.createElement(
10679
- Text19,
11412
+ ), /* @__PURE__ */ React23.createElement(
11413
+ Text18,
10680
11414
  {
10681
11415
  backgroundColor: isSelected ? theme.text.accent : void 0,
10682
11416
  color: isSelected ? "black" : theme.text.dim
10683
11417
  },
10684
11418
  model.description
10685
- ), isCurrent && /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.success }, " (current)"));
10686
- })), /* @__PURE__ */ React24.createElement(Box21, { marginTop: 1 }, /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, /* @__PURE__ */ React24.createElement(Text19, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React24.createElement(Text19, { bold: true }, "Enter"), " select \u2022 ", /* @__PURE__ */ React24.createElement(Text19, { bold: true }, "ESC"), " cancel")));
11419
+ ), isCurrent && /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.success }, " (current)"));
11420
+ })), /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }, /* @__PURE__ */ React23.createElement(Text18, { color: theme.text.dim }, /* @__PURE__ */ React23.createElement(Text18, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React23.createElement(Text18, { bold: true }, "Enter"), " select \u2022 ", /* @__PURE__ */ React23.createElement(Text18, { bold: true }, "ESC"), " cancel")));
10687
11421
  };
10688
11422
  }
10689
11423
  });
@@ -10691,8 +11425,8 @@ var init_ModelSelector = __esm({
10691
11425
  // src/ui/components/InputPrompt.tsx
10692
11426
  import path5 from "path";
10693
11427
  import chalk4 from "chalk";
10694
- import { Box as Box22, Text as Text20 } from "ink";
10695
- import React25, { forwardRef, useEffect as useEffect10, useImperativeHandle, useState as useState11 } from "react";
11428
+ import { Box as Box21, Text as Text19 } from "ink";
11429
+ import React24, { forwardRef, useEffect as useEffect9, useImperativeHandle, useState as useState10 } from "react";
10696
11430
  var InputPrompt;
10697
11431
  var init_InputPrompt = __esm({
10698
11432
  "src/ui/components/InputPrompt.tsx"() {
@@ -10716,13 +11450,13 @@ var init_InputPrompt = __esm({
10716
11450
  isClaudeMax = false
10717
11451
  }, ref) => {
10718
11452
  const { messages, agentMode, selectedModel, setSelectedModel, isAgentRunning, usageStats } = useSession();
10719
- const [value, setValue] = useState11("");
10720
- const [cursorOffset, setCursorOffset] = useState11(0);
10721
- const [allFiles, setAllFiles] = useState11([]);
10722
- const [suggestions, setSuggestions] = useState11([]);
10723
- const [activeSuggestion, setActiveSuggestion] = useState11(0);
10724
- const [showSuggestions, setShowSuggestions] = useState11(false);
10725
- const [mentionStartIndex, setMentionStartIndex] = useState11(-1);
11453
+ const [value, setValue] = useState10("");
11454
+ const [cursorOffset, setCursorOffset] = useState10(0);
11455
+ const [allFiles, setAllFiles] = useState10([]);
11456
+ const [suggestions, setSuggestions] = useState10([]);
11457
+ const [activeSuggestion, setActiveSuggestion] = useState10(0);
11458
+ const [showSuggestions, setShowSuggestions] = useState10(false);
11459
+ const [mentionStartIndex, setMentionStartIndex] = useState10(-1);
10726
11460
  const BUILTIN_SLASH_COMMANDS = [
10727
11461
  { name: "/help", desc: "Show help" },
10728
11462
  { name: "/resume", desc: "Resume session" },
@@ -10733,13 +11467,14 @@ var init_InputPrompt = __esm({
10733
11467
  { name: "/feedback", desc: "Report an issue" },
10734
11468
  { name: "/setup", desc: "Install Playwright browsers" },
10735
11469
  { name: "/discover", desc: "Discover test framework" },
11470
+ { name: "/mcp", desc: "Show MCP servers" },
10736
11471
  { name: "/login", desc: "Authenticate with Supatest" },
10737
11472
  { name: "/logout", desc: "Log out" },
10738
11473
  { name: "/exit", desc: "Exit CLI" }
10739
11474
  ];
10740
- const [customCommands, setCustomCommands] = useState11([]);
10741
- const [isSlashCommand, setIsSlashCommand] = useState11(false);
10742
- useEffect10(() => {
11475
+ const [customCommands, setCustomCommands] = useState10([]);
11476
+ const [isSlashCommand, setIsSlashCommand] = useState10(false);
11477
+ useEffect9(() => {
10743
11478
  try {
10744
11479
  const projectDir = cwd || process.cwd();
10745
11480
  const discovered = discoverCommands(projectDir);
@@ -10760,7 +11495,7 @@ var init_InputPrompt = __esm({
10760
11495
  onInputChange?.("");
10761
11496
  }
10762
11497
  }));
10763
- useEffect10(() => {
11498
+ useEffect9(() => {
10764
11499
  setTimeout(() => {
10765
11500
  try {
10766
11501
  const files = getFiles();
@@ -10874,8 +11609,8 @@ var init_InputPrompt = __esm({
10874
11609
  setSelectedModel(getNextModel(selectedModel, isClaudeMax));
10875
11610
  return;
10876
11611
  }
10877
- if (input === "?" && value.length === 0 && onHelpToggle) {
10878
- onHelpToggle();
11612
+ if (input === "?" && value.length === 0) {
11613
+ onSubmit("/help");
10879
11614
  return;
10880
11615
  }
10881
11616
  if (showSuggestions && !key.shift) {
@@ -10960,8 +11695,8 @@ var init_InputPrompt = __esm({
10960
11695
  }
10961
11696
  charCount += lineLength + 1;
10962
11697
  }
10963
- return /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column", width: "100%" }, showSuggestions && /* @__PURE__ */ React25.createElement(
10964
- Box22,
11698
+ return /* @__PURE__ */ React24.createElement(Box21, { flexDirection: "column", width: "100%" }, showSuggestions && /* @__PURE__ */ React24.createElement(
11699
+ Box21,
10965
11700
  {
10966
11701
  borderColor: theme.border.default,
10967
11702
  borderStyle: "round",
@@ -10972,12 +11707,12 @@ var init_InputPrompt = __esm({
10972
11707
  suggestions.map((item, idx) => {
10973
11708
  const isSeparator = item.startsWith("\u2500\u2500\u2500\u2500\u2500");
10974
11709
  if (isSeparator) {
10975
- return /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim, key: item }, " ", item);
11710
+ return /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, key: item }, " ", item);
10976
11711
  }
10977
- return /* @__PURE__ */ React25.createElement(Text20, { color: idx === activeSuggestion ? theme.text.accent : theme.text.dim, key: item }, idx === activeSuggestion ? "\u276F " : " ", item);
11712
+ return /* @__PURE__ */ React24.createElement(Text19, { color: idx === activeSuggestion ? theme.text.accent : theme.text.dim, key: item }, idx === activeSuggestion ? "\u276F " : " ", item);
10978
11713
  })
10979
- ), /* @__PURE__ */ React25.createElement(
10980
- Box22,
11714
+ ), /* @__PURE__ */ React24.createElement(
11715
+ Box21,
10981
11716
  {
10982
11717
  borderColor: disabled ? theme.border.default : theme.border.accent,
10983
11718
  borderStyle: "round",
@@ -10987,24 +11722,288 @@ var init_InputPrompt = __esm({
10987
11722
  paddingX: 1,
10988
11723
  width: "100%"
10989
11724
  },
10990
- /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "row" }, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, "\u276F "), /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column", flexGrow: 1 }, !hasContent && !disabled && /* @__PURE__ */ React25.createElement(Text20, null, chalk4.inverse(placeholder.slice(0, 1)), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim, italic: true }, placeholder.slice(1))), lines.length > 0 && /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column" }, lines.map((line, idx) => {
11725
+ /* @__PURE__ */ React24.createElement(Box21, { flexDirection: "row" }, /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.accent }, "\u276F "), /* @__PURE__ */ React24.createElement(Box21, { flexDirection: "column", flexGrow: 1 }, !hasContent && !disabled && /* @__PURE__ */ React24.createElement(Text19, null, chalk4.inverse(placeholder.slice(0, 1)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, italic: true }, placeholder.slice(1))), lines.length > 0 && /* @__PURE__ */ React24.createElement(Box21, { flexDirection: "column" }, lines.map((line, idx) => {
10991
11726
  if (idx === cursorLine && !disabled) {
10992
11727
  const before = line.slice(0, cursorCol);
10993
11728
  const charAtCursor = line[cursorCol] || " ";
10994
11729
  const after = line.slice(cursorCol + 1);
10995
- return /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.primary, key: idx }, before, chalk4.inverse(charAtCursor), after);
11730
+ return /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.primary, key: idx }, before, chalk4.inverse(charAtCursor), after);
10996
11731
  }
10997
- return /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.primary, key: idx }, line);
10998
- })), !hasContent && disabled && /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim, italic: true }, "Waiting for agent to complete...")))
10999
- ), /* @__PURE__ */ React25.createElement(Box22, { justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React25.createElement(Box22, { gap: 2 }, /* @__PURE__ */ React25.createElement(Box22, null, /* @__PURE__ */ React25.createElement(Text20, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "\u23F8 plan" : "\u25B6 build"), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim }, " (shift+tab)")), /* @__PURE__ */ React25.createElement(Box22, null, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim }, "model:"), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.info }, getModelDisplayName(selectedModel)), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim }, " (Cost: "), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.info }, getModelCostLabel(selectedModel)), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim }, ") (shift+m)"))), /* @__PURE__ */ React25.createElement(Box22, null, /* @__PURE__ */ React25.createElement(Text20, { color: usageStats && usageStats.contextPct >= 90 ? theme.text.error : usageStats && usageStats.contextPct >= 75 ? theme.text.warning : theme.text.dim }, usageStats?.contextPct ?? 0, "% context used"), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim }, " ", "(", usageStats ? usageStats.inputTokens >= 1e3 ? `${(usageStats.inputTokens / 1e3).toFixed(1)}K` : usageStats.inputTokens : 0, " / ", usageStats ? usageStats.contextWindow >= 1e3 ? `${(usageStats.contextWindow / 1e3).toFixed(0)}K` : usageStats.contextWindow : "200K", ")"))));
11732
+ return /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.primary, key: idx }, line);
11733
+ })), !hasContent && disabled && /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim, italic: true }, "Waiting for agent to complete...")))
11734
+ ), /* @__PURE__ */ React24.createElement(Box21, { justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React24.createElement(Box21, { gap: 2 }, /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "\u23F8 plan" : "\u25B6 build"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (shift+tab)")), /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, "model:"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelDisplayName(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " (Cost: "), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.info }, getModelCostLabel(selectedModel)), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, ") (shift+m)"))), /* @__PURE__ */ React24.createElement(Box21, null, /* @__PURE__ */ React24.createElement(Text19, { color: usageStats && usageStats.contextPct >= 90 ? theme.text.error : usageStats && usageStats.contextPct >= 75 ? theme.text.warning : theme.text.dim }, usageStats?.contextPct ?? 0, "% context used"), /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.dim }, " ", "(", usageStats ? usageStats.inputTokens >= 1e3 ? `${(usageStats.inputTokens / 1e3).toFixed(1)}K` : usageStats.inputTokens : 0, " / ", usageStats ? usageStats.contextWindow >= 1e3 ? `${(usageStats.contextWindow / 1e3).toFixed(0)}K` : usageStats.contextWindow : "200K", ")"))));
11000
11735
  });
11001
11736
  InputPrompt.displayName = "InputPrompt";
11002
11737
  }
11003
11738
  });
11004
11739
 
11005
- // src/ui/components/ProviderSelector.tsx
11740
+ // src/ui/components/McpAddDialog.tsx
11741
+ import { Box as Box22, Text as Text20, useInput as useInput6 } from "ink";
11742
+ import React25, { useState as useState11 } from "react";
11743
+ var McpAddDialog;
11744
+ var init_McpAddDialog = __esm({
11745
+ "src/ui/components/McpAddDialog.tsx"() {
11746
+ "use strict";
11747
+ init_theme();
11748
+ McpAddDialog = ({ onConfirm, onCancel }) => {
11749
+ const [mode, setMode] = useState11("name" /* Name */);
11750
+ const [serverName, setServerName] = useState11("");
11751
+ const [command, setCommand] = useState11("");
11752
+ const [args, setArgs] = useState11("");
11753
+ const [description, setDescription] = useState11("");
11754
+ const [scope, setScope] = useState11("project");
11755
+ useInput6((input, key) => {
11756
+ if (key.escape) {
11757
+ onCancel();
11758
+ return;
11759
+ }
11760
+ if (key.return) {
11761
+ if (mode === "name" /* Name */ && serverName.trim()) {
11762
+ setMode("command" /* Command */);
11763
+ } else if (mode === "command" /* Command */ && command.trim()) {
11764
+ setMode("args" /* Args */);
11765
+ } else if (mode === "args" /* Args */) {
11766
+ setMode("description" /* Description */);
11767
+ } else if (mode === "description" /* Description */) {
11768
+ setMode("scope" /* Scope */);
11769
+ } else if (mode === "scope" /* Scope */) {
11770
+ setMode("confirm" /* Confirm */);
11771
+ } else if (mode === "confirm" /* Confirm */) {
11772
+ const parsedArgs = args.trim().split(/\s+/).filter((a) => a);
11773
+ onConfirm(serverName, command, parsedArgs.length > 0 ? parsedArgs : void 0, {}, description, scope);
11774
+ }
11775
+ return;
11776
+ }
11777
+ if (mode === "name" /* Name */) {
11778
+ if (input.length === 1 && /[a-zA-Z0-9-_]/.test(input)) {
11779
+ setServerName((prev) => prev + input);
11780
+ } else if (key.backspace && serverName.length > 0) {
11781
+ setServerName((prev) => prev.slice(0, -1));
11782
+ }
11783
+ } else if (mode === "command" /* Command */) {
11784
+ if (input.length === 1) {
11785
+ setCommand((prev) => prev + input);
11786
+ } else if (key.backspace && command.length > 0) {
11787
+ setCommand((prev) => prev.slice(0, -1));
11788
+ }
11789
+ } else if (mode === "args" /* Args */) {
11790
+ if (input.length === 1) {
11791
+ setArgs((prev) => prev + input);
11792
+ } else if (key.backspace && args.length > 0) {
11793
+ setArgs((prev) => prev.slice(0, -1));
11794
+ }
11795
+ } else if (mode === "description" /* Description */) {
11796
+ if (input.length === 1) {
11797
+ setDescription((prev) => prev + input);
11798
+ } else if (key.backspace && description.length > 0) {
11799
+ setDescription((prev) => prev.slice(0, -1));
11800
+ }
11801
+ } else if (mode === "scope" /* Scope */) {
11802
+ if (key.upArrow) {
11803
+ setScope("project");
11804
+ } else if (key.downArrow) {
11805
+ setScope("global");
11806
+ }
11807
+ } else if (mode === "confirm" /* Confirm */) {
11808
+ if (input === "y" || input === "Y") {
11809
+ const parsedArgs = args.trim().split(/\s+/).filter((a) => a);
11810
+ onConfirm(
11811
+ serverName,
11812
+ command,
11813
+ parsedArgs.length > 0 ? parsedArgs : void 0,
11814
+ {},
11815
+ description,
11816
+ scope
11817
+ );
11818
+ } else if (input === "n" || input === "N") {
11819
+ onCancel();
11820
+ }
11821
+ }
11822
+ });
11823
+ return /* @__PURE__ */ React25.createElement(
11824
+ Box22,
11825
+ {
11826
+ borderColor: theme.border.accent,
11827
+ borderStyle: "round",
11828
+ flexDirection: "column",
11829
+ paddingX: 2,
11830
+ paddingY: 1
11831
+ },
11832
+ /* @__PURE__ */ React25.createElement(Text20, { bold: true, color: theme.text.accent }, "\u2795 Add MCP Server"),
11833
+ /* @__PURE__ */ React25.createElement(Box22, { marginTop: 1 }),
11834
+ mode === "name" /* Name */ && /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column" }, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Server name (alphanumeric, - or _):"), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, "> ", serverName || "_")),
11835
+ mode === "command" /* Command */ && /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column" }, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Server name: ", serverName), /* @__PURE__ */ React25.createElement(Box22, { marginTop: 0 }), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Command (e.g., npx, node, python):"), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, "> ", command || "_")),
11836
+ mode === "args" /* Args */ && /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column" }, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Server name: ", serverName), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Command: ", command), /* @__PURE__ */ React25.createElement(Box22, { marginTop: 0 }), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Arguments (space-separated, press Enter to skip):"), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, "> ", args || "(none)")),
11837
+ mode === "description" /* Description */ && /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column" }, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Server name: ", serverName), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Command: ", command), args && /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Args: ", args), /* @__PURE__ */ React25.createElement(Box22, { marginTop: 0 }), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Description (optional, press Enter to skip):"), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, "> ", description || "(none)")),
11838
+ mode === "scope" /* Scope */ && /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column" }, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Server name: ", serverName), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Command: ", command), args && /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Args: ", args), description && /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Description: ", description), /* @__PURE__ */ React25.createElement(Box22, { marginTop: 0 }), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Choose scope (\u2191\u2193 to navigate):"), /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "row", marginLeft: 2, marginTop: 1 }, /* @__PURE__ */ React25.createElement(Text20, { bold: scope === "project", color: scope === "project" ? theme.text.accent : theme.text.dim }, scope === "project" ? "\u25B6 " : " ", "Project (.supatest/mcp.json)")), /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "row", marginLeft: 2 }, /* @__PURE__ */ React25.createElement(Text20, { bold: scope === "global", color: scope === "global" ? theme.text.accent : theme.text.dim }, scope === "global" ? "\u25B6 " : " ", "Global (~/.supatest/mcp.json)")), /* @__PURE__ */ React25.createElement(Box22, { marginTop: 1 }), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim }, "Press Enter to continue")),
11839
+ mode === "confirm" /* Confirm */ && /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column" }, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Review configuration:"), /* @__PURE__ */ React25.createElement(Box22, { marginTop: 0 }), /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column", marginLeft: 2 }, /* @__PURE__ */ React25.createElement(Text20, null, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Name: "), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, serverName)), /* @__PURE__ */ React25.createElement(Text20, null, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Command: "), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, command)), args && /* @__PURE__ */ React25.createElement(Text20, null, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Args: "), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, args)), description && /* @__PURE__ */ React25.createElement(Text20, null, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Description: "), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, description)), /* @__PURE__ */ React25.createElement(Text20, null, /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Scope: "), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.accent }, scope))), /* @__PURE__ */ React25.createElement(Box22, { marginTop: 1 }), /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.secondary }, "Add this server? (y/n)")),
11840
+ /* @__PURE__ */ React25.createElement(Box22, { marginTop: 1 }),
11841
+ /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim }, "Press ESC to cancel")
11842
+ );
11843
+ };
11844
+ }
11845
+ });
11846
+
11847
+ // src/ui/components/McpServerSelector.tsx
11006
11848
  import { Box as Box23, Text as Text21, useInput as useInput7 } from "ink";
11007
11849
  import React26, { useState as useState12 } from "react";
11850
+ var McpServerSelector;
11851
+ var init_McpServerSelector = __esm({
11852
+ "src/ui/components/McpServerSelector.tsx"() {
11853
+ "use strict";
11854
+ init_theme();
11855
+ McpServerSelector = ({
11856
+ servers,
11857
+ action,
11858
+ onSelect,
11859
+ onCancel
11860
+ }) => {
11861
+ const [selectedIndex, setSelectedIndex] = useState12(0);
11862
+ useInput7((input, key) => {
11863
+ if (key.escape) {
11864
+ onCancel();
11865
+ return;
11866
+ }
11867
+ if (key.upArrow) {
11868
+ setSelectedIndex((prev) => prev > 0 ? prev - 1 : servers.length - 1);
11869
+ } else if (key.downArrow) {
11870
+ setSelectedIndex((prev) => prev < servers.length - 1 ? prev + 1 : 0);
11871
+ } else if (key.return) {
11872
+ onSelect(servers[selectedIndex].name);
11873
+ }
11874
+ });
11875
+ const actionLabels = {
11876
+ remove: "Remove MCP Server",
11877
+ enable: "Enable MCP Server",
11878
+ disable: "Disable MCP Server",
11879
+ test: "Test MCP Server Connection"
11880
+ };
11881
+ const actionColors = {
11882
+ remove: theme.text.error,
11883
+ enable: theme.text.success,
11884
+ disable: theme.text.warning,
11885
+ test: theme.text.accent
11886
+ };
11887
+ return /* @__PURE__ */ React26.createElement(
11888
+ Box23,
11889
+ {
11890
+ borderColor: theme.border.accent,
11891
+ borderStyle: "round",
11892
+ flexDirection: "column",
11893
+ paddingX: 2,
11894
+ paddingY: 1
11895
+ },
11896
+ /* @__PURE__ */ React26.createElement(Text21, { bold: true, color: actionColors[action] }, actionLabels[action]),
11897
+ /* @__PURE__ */ React26.createElement(Box23, { marginTop: 1 }),
11898
+ servers.length === 0 ? /* @__PURE__ */ React26.createElement(Box23, { flexDirection: "column" }, /* @__PURE__ */ React26.createElement(Text21, { color: theme.text.dim }, "No MCP servers configured.")) : /* @__PURE__ */ React26.createElement(Box23, { flexDirection: "column" }, servers.map((server, index) => {
11899
+ const isSelected = index === selectedIndex;
11900
+ const statusEmoji = server.enabled ? "\u2713" : "\u2717";
11901
+ return /* @__PURE__ */ React26.createElement(Box23, { flexDirection: "row", key: server.name }, /* @__PURE__ */ React26.createElement(
11902
+ Text21,
11903
+ {
11904
+ bold: isSelected,
11905
+ color: isSelected ? theme.text.accent : theme.text.dim
11906
+ },
11907
+ isSelected ? "\u276F " : " "
11908
+ ), /* @__PURE__ */ React26.createElement(Text21, { color: isSelected ? theme.text.accent : theme.text.secondary }, statusEmoji, " ", server.name), server.scope && /* @__PURE__ */ React26.createElement(Text21, { color: theme.text.dim }, " (", server.scope, ")"), server.description && /* @__PURE__ */ React26.createElement(Text21, { color: theme.text.dim }, " - ", server.description));
11909
+ })),
11910
+ /* @__PURE__ */ React26.createElement(Box23, { marginTop: 1 }),
11911
+ /* @__PURE__ */ React26.createElement(Text21, { color: theme.text.dim }, "Use \u2191\u2193 to select, Enter to confirm, ESC to cancel")
11912
+ );
11913
+ };
11914
+ }
11915
+ });
11916
+
11917
+ // src/ui/components/McpServersDisplay.tsx
11918
+ import { Box as Box24, Text as Text22, useInput as useInput8 } from "ink";
11919
+ import Spinner3 from "ink-spinner";
11920
+ import React27, { useEffect as useEffect10, useState as useState13 } from "react";
11921
+ var McpServersDisplay;
11922
+ var init_McpServersDisplay = __esm({
11923
+ "src/ui/components/McpServersDisplay.tsx"() {
11924
+ "use strict";
11925
+ init_mcp_manager();
11926
+ init_theme();
11927
+ McpServersDisplay = ({
11928
+ onClose,
11929
+ onAdd,
11930
+ onRemove,
11931
+ onTest,
11932
+ cwd
11933
+ }) => {
11934
+ const [servers, setServers] = useState13([]);
11935
+ const [isTestingAll, setIsTestingAll] = useState13(false);
11936
+ useEffect10(() => {
11937
+ const projectDir = cwd || process.cwd();
11938
+ const loadedServers = loadMcpConfig(projectDir);
11939
+ const serversArray = Object.values(loadedServers).map((server) => ({
11940
+ ...server,
11941
+ status: "unknown"
11942
+ }));
11943
+ setServers(serversArray);
11944
+ }, [cwd]);
11945
+ useInput8((input, key) => {
11946
+ if (key.escape || input === "q") {
11947
+ onClose();
11948
+ } else if (input === "a") {
11949
+ onAdd?.();
11950
+ } else if (input === "r") {
11951
+ onRemove?.();
11952
+ } else if (input === "t") {
11953
+ onTest?.();
11954
+ }
11955
+ });
11956
+ return /* @__PURE__ */ React27.createElement(
11957
+ Box24,
11958
+ {
11959
+ borderColor: theme.border.accent,
11960
+ borderStyle: "round",
11961
+ flexDirection: "column",
11962
+ paddingX: 2,
11963
+ paddingY: 1
11964
+ },
11965
+ /* @__PURE__ */ React27.createElement(Text22, { bold: true, color: theme.text.accent }, "\u{1F50C} Model Context Protocol (MCP) Servers"),
11966
+ /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }),
11967
+ servers.length === 0 ? /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column" }, /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, "No MCP servers configured."), /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }), /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "A"), " to add a new server, or create", " ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, ".supatest/mcp.json"), ":"), /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }), /* @__PURE__ */ React27.createElement(
11968
+ Box24,
11969
+ {
11970
+ borderColor: theme.border.default,
11971
+ borderStyle: "round",
11972
+ flexDirection: "column",
11973
+ marginLeft: 2,
11974
+ paddingX: 1,
11975
+ paddingY: 0
11976
+ },
11977
+ /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, `{
11978
+ "mcpServers": {
11979
+ "server-name": {
11980
+ "command": "npx",
11981
+ "args": ["@modelcontextprotocol/server-package"]
11982
+ }
11983
+ }
11984
+ }`)
11985
+ )) : /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column" }, isTestingAll && /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "row", marginBottom: 1 }, /* @__PURE__ */ React27.createElement(Box24, { width: 2 }, /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.accent }, /* @__PURE__ */ React27.createElement(Spinner3, { type: "dots" }))), /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.secondary }, "Testing connections...")), servers.map((server, index) => {
11986
+ let statusIndicator = "";
11987
+ let statusText = "";
11988
+ if (server.status === "connected") {
11989
+ statusIndicator = "\u2713";
11990
+ statusText = " - Connected";
11991
+ } else if (server.status === "failed") {
11992
+ statusIndicator = "\u2717";
11993
+ statusText = " - Failed";
11994
+ }
11995
+ return /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column", key: server.name }, index > 0 && /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }), /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "row" }, statusIndicator && /* @__PURE__ */ React27.createElement(Text22, { color: server.status === "connected" ? theme.text.success : theme.text.error }, statusIndicator, " "), /* @__PURE__ */ React27.createElement(Text22, { bold: true, color: theme.text.accent }, server.name), server.scope && /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, " (", server.scope, ")"), !server.enabled && /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.warning }, " [disabled]"), statusText && /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, statusText)), /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.secondary }, "Command: ", /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, server.command)), server.args && server.args.length > 0 && /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.secondary }, "Args: ", /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, server.args.join(" "))), server.env && Object.keys(server.env).length > 0 && /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.secondary }, "Env: ", /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, Object.entries(server.env).map(([k, v]) => `${k}=${v}`).join(", "))), server.description && /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.secondary }, "Description:", " ", /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, server.description)), server.status === "failed" && server.errorMessage && /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.error }, "Error: ", server.errorMessage)));
11996
+ })),
11997
+ /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }),
11998
+ /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column" }, /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "A"), " - Add server | ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "R"), " - Remove | ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "T"), " - Test"), /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "ESC"), " or ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "Q"), " to close"))
11999
+ );
12000
+ };
12001
+ }
12002
+ });
12003
+
12004
+ // src/ui/components/ProviderSelector.tsx
12005
+ import { Box as Box25, Text as Text23, useInput as useInput9 } from "ink";
12006
+ import React28, { useState as useState14 } from "react";
11008
12007
  var PROVIDERS, ProviderSelector;
11009
12008
  var init_ProviderSelector = __esm({
11010
12009
  "src/ui/components/ProviderSelector.tsx"() {
@@ -11034,10 +12033,10 @@ var init_ProviderSelector = __esm({
11034
12033
  const currentIndex = availableProviders.findIndex(
11035
12034
  (p) => p.id === currentProvider
11036
12035
  );
11037
- const [selectedIndex, setSelectedIndex] = useState12(
12036
+ const [selectedIndex, setSelectedIndex] = useState14(
11038
12037
  currentIndex >= 0 ? currentIndex : 0
11039
12038
  );
11040
- useInput7((input, key) => {
12039
+ useInput9((input, key) => {
11041
12040
  if (key.upArrow) {
11042
12041
  setSelectedIndex(
11043
12042
  (prev) => prev > 0 ? prev - 1 : availableProviders.length - 1
@@ -11052,12 +12051,12 @@ var init_ProviderSelector = __esm({
11052
12051
  onCancel();
11053
12052
  }
11054
12053
  });
11055
- return /* @__PURE__ */ React26.createElement(Box23, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React26.createElement(Box23, { marginBottom: 1 }, /* @__PURE__ */ React26.createElement(Text21, { bold: true, color: theme.text.accent }, "Select LLM Provider")), /* @__PURE__ */ React26.createElement(Box23, { flexDirection: "column" }, availableProviders.map((provider, index) => {
12054
+ return /* @__PURE__ */ React28.createElement(Box25, { borderColor: theme.border.accent, borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React28.createElement(Box25, { marginBottom: 1 }, /* @__PURE__ */ React28.createElement(Text23, { bold: true, color: theme.text.accent }, "Select LLM Provider")), /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column" }, availableProviders.map((provider, index) => {
11056
12055
  const isSelected = index === selectedIndex;
11057
12056
  const isCurrent = provider.id === currentProvider;
11058
12057
  const indicator = isSelected ? "\u25B6 " : " ";
11059
- return /* @__PURE__ */ React26.createElement(
11060
- Text21,
12058
+ return /* @__PURE__ */ React28.createElement(
12059
+ Text23,
11061
12060
  {
11062
12061
  backgroundColor: isSelected ? theme.text.accent : void 0,
11063
12062
  bold: isSelected,
@@ -11066,17 +12065,17 @@ var init_ProviderSelector = __esm({
11066
12065
  },
11067
12066
  indicator,
11068
12067
  provider.name,
11069
- isCurrent && /* @__PURE__ */ React26.createElement(Text21, { color: isSelected ? "black" : theme.text.success }, " (current)"),
11070
- /* @__PURE__ */ React26.createElement(Text21, { color: isSelected ? "black" : theme.text.dim }, " - ", provider.description)
12068
+ isCurrent && /* @__PURE__ */ React28.createElement(Text23, { color: isSelected ? "black" : theme.text.success }, " (current)"),
12069
+ /* @__PURE__ */ React28.createElement(Text23, { color: isSelected ? "black" : theme.text.dim }, " - ", provider.description)
11071
12070
  );
11072
- })), /* @__PURE__ */ React26.createElement(Box23, { marginTop: 1 }, /* @__PURE__ */ React26.createElement(Text21, { color: theme.text.dim }, /* @__PURE__ */ React26.createElement(Text21, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React26.createElement(Text21, { bold: true }, "Enter"), " select \u2022", " ", /* @__PURE__ */ React26.createElement(Text21, { bold: true }, "ESC"), " cancel")));
12071
+ })), /* @__PURE__ */ React28.createElement(Box25, { marginTop: 1 }, /* @__PURE__ */ React28.createElement(Text23, { color: theme.text.dim }, /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "\u2191\u2193"), " navigate \u2022 ", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "Enter"), " select \u2022", " ", /* @__PURE__ */ React28.createElement(Text23, { bold: true }, "ESC"), " cancel")));
11073
12072
  };
11074
12073
  }
11075
12074
  });
11076
12075
 
11077
12076
  // src/ui/components/SessionSelector.tsx
11078
- import { Box as Box24, Text as Text22, useInput as useInput8 } from "ink";
11079
- import React27, { useEffect as useEffect11, useState as useState13 } from "react";
12077
+ import { Box as Box26, Text as Text24, useInput as useInput10 } from "ink";
12078
+ import React29, { useEffect as useEffect11, useState as useState15 } from "react";
11080
12079
  function getSessionPrefix(authMethod) {
11081
12080
  return authMethod === "api-key" ? "[Team]" : "[Me]";
11082
12081
  }
@@ -11091,12 +12090,12 @@ var init_SessionSelector = __esm({
11091
12090
  onSelect,
11092
12091
  onCancel
11093
12092
  }) => {
11094
- const [allSessions, setAllSessions] = useState13([]);
11095
- const [selectedIndex, setSelectedIndex] = useState13(0);
11096
- const [isLoading, setIsLoading] = useState13(false);
11097
- const [hasMore, setHasMore] = useState13(true);
11098
- const [totalSessions, setTotalSessions] = useState13(0);
11099
- const [error, setError] = useState13(null);
12093
+ const [allSessions, setAllSessions] = useState15([]);
12094
+ const [selectedIndex, setSelectedIndex] = useState15(0);
12095
+ const [isLoading, setIsLoading] = useState15(false);
12096
+ const [hasMore, setHasMore] = useState15(true);
12097
+ const [totalSessions, setTotalSessions] = useState15(0);
12098
+ const [error, setError] = useState15(null);
11100
12099
  useEffect11(() => {
11101
12100
  loadMoreSessions();
11102
12101
  }, []);
@@ -11123,7 +12122,7 @@ var init_SessionSelector = __esm({
11123
12122
  setIsLoading(false);
11124
12123
  }
11125
12124
  };
11126
- useInput8((input, key) => {
12125
+ useInput10((input, key) => {
11127
12126
  if (allSessions.length === 0) {
11128
12127
  if (key.escape || input === "q") {
11129
12128
  onCancel();
@@ -11147,13 +12146,13 @@ var init_SessionSelector = __esm({
11147
12146
  }
11148
12147
  });
11149
12148
  if (error) {
11150
- return /* @__PURE__ */ React27.createElement(Box24, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React27.createElement(Text22, { bold: true, color: "red" }, "Error Loading Sessions"), /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, error), /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "ESC"), " to cancel")));
12149
+ return /* @__PURE__ */ React29.createElement(Box26, { borderColor: "red", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "red" }, "Error Loading Sessions"), /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, error), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " to cancel")));
11151
12150
  }
11152
12151
  if (allSessions.length === 0 && isLoading) {
11153
- return /* @__PURE__ */ React27.createElement(Box24, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React27.createElement(Text22, { bold: true, color: "cyan" }, "Loading Sessions..."), /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, "Fetching your sessions from the server"));
12152
+ return /* @__PURE__ */ React29.createElement(Box26, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "cyan" }, "Loading Sessions..."), /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "Fetching your sessions from the server"));
11154
12153
  }
11155
12154
  if (allSessions.length === 0 && !isLoading) {
11156
- return /* @__PURE__ */ React27.createElement(Box24, { borderColor: "yellow", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React27.createElement(Text22, { bold: true, color: "yellow" }, "No Sessions Found"), /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, "No previous sessions available. Start a new conversation!"), /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "ESC"), " to cancel")));
12155
+ return /* @__PURE__ */ React29.createElement(Box26, { borderColor: "yellow", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "yellow" }, "No Sessions Found"), /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "No previous sessions available. Start a new conversation!"), /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " to cancel")));
11157
12156
  }
11158
12157
  const VISIBLE_ITEMS3 = 10;
11159
12158
  let startIndex;
@@ -11173,7 +12172,7 @@ var init_SessionSelector = __esm({
11173
12172
  const visibleSessions = allSessions.slice(startIndex, endIndex);
11174
12173
  const MAX_TITLE_WIDTH = 50;
11175
12174
  const PREFIX_WIDTH = 6;
11176
- return /* @__PURE__ */ React27.createElement(Box24, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React27.createElement(Box24, { marginBottom: 1 }, /* @__PURE__ */ React27.createElement(Text22, { bold: true, color: "cyan" }, "Select a Session to Resume")), /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column" }, visibleSessions.map((item, index) => {
12175
+ return /* @__PURE__ */ React29.createElement(Box26, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { bold: true, color: "cyan" }, "Select a Session to Resume")), /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column" }, visibleSessions.map((item, index) => {
11177
12176
  const actualIndex = startIndex + index;
11178
12177
  const isSelected = actualIndex === selectedIndex;
11179
12178
  const title = item.session.title || "Untitled session";
@@ -11197,8 +12196,8 @@ var init_SessionSelector = __esm({
11197
12196
  const prefixColor = item.prefix === "[Me]" ? "cyan" : "yellow";
11198
12197
  const indicator = isSelected ? "\u25B6 " : " ";
11199
12198
  const bgColor = isSelected ? theme.text.accent : void 0;
11200
- return /* @__PURE__ */ React27.createElement(Box24, { key: item.session.id, width: "100%" }, /* @__PURE__ */ React27.createElement(Text22, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React27.createElement(Text22, { backgroundColor: bgColor, bold: isSelected, color: prefixColor }, prefix), /* @__PURE__ */ React27.createElement(Text22, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, displayTitle), /* @__PURE__ */ React27.createElement(Text22, { backgroundColor: bgColor, color: theme.text.dim }, "(", dateStr, ")"));
11201
- })), /* @__PURE__ */ React27.createElement(Box24, { flexDirection: "column", marginTop: 1 }, (allSessions.length > VISIBLE_ITEMS3 || totalSessions > allSessions.length) && /* @__PURE__ */ React27.createElement(Box24, { marginBottom: 1 }, /* @__PURE__ */ React27.createElement(Text22, { color: "yellow" }, "Showing ", startIndex + 1, "-", endIndex, " of ", totalSessions || allSessions.length, " sessions", hasMore && !isLoading && /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, " \u2022 Scroll for more"))), /* @__PURE__ */ React27.createElement(Box24, null, /* @__PURE__ */ React27.createElement(Text22, { color: theme.text.dim }, "Use ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "\u2191\u2193"), " to navigate \u2022 ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "Enter"), " to select \u2022 ", /* @__PURE__ */ React27.createElement(Text22, { bold: true }, "ESC"), " to cancel")), isLoading && /* @__PURE__ */ React27.createElement(Box24, { marginTop: 1 }, /* @__PURE__ */ React27.createElement(Text22, { color: "cyan" }, "Loading more sessions..."))));
12199
+ return /* @__PURE__ */ React29.createElement(Box26, { key: item.session.id, width: "100%" }, /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, indicator), /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: bgColor, bold: isSelected, color: prefixColor }, prefix), /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: bgColor, bold: isSelected, color: isSelected ? "black" : theme.text.primary }, displayTitle), /* @__PURE__ */ React29.createElement(Text24, { backgroundColor: bgColor, color: theme.text.dim }, "(", dateStr, ")"));
12200
+ })), /* @__PURE__ */ React29.createElement(Box26, { flexDirection: "column", marginTop: 1 }, (allSessions.length > VISIBLE_ITEMS3 || totalSessions > allSessions.length) && /* @__PURE__ */ React29.createElement(Box26, { marginBottom: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: "yellow" }, "Showing ", startIndex + 1, "-", endIndex, " of ", totalSessions || allSessions.length, " sessions", hasMore && !isLoading && /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, " \u2022 Scroll for more"))), /* @__PURE__ */ React29.createElement(Box26, null, /* @__PURE__ */ React29.createElement(Text24, { color: theme.text.dim }, "Use ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "\u2191\u2193"), " to navigate \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "Enter"), " to select \u2022 ", /* @__PURE__ */ React29.createElement(Text24, { bold: true }, "ESC"), " to cancel")), isLoading && /* @__PURE__ */ React29.createElement(Box26, { marginTop: 1 }, /* @__PURE__ */ React29.createElement(Text24, { color: "cyan" }, "Loading more sessions..."))));
11202
12201
  };
11203
12202
  }
11204
12203
  });
@@ -11248,10 +12247,10 @@ var init_useOverlayEscapeGuard = __esm({
11248
12247
 
11249
12248
  // src/ui/App.tsx
11250
12249
  import { execSync as execSync6 } from "child_process";
11251
- import { homedir as homedir6 } from "os";
11252
- import { Box as Box25, Text as Text23, useApp as useApp2, useStdout as useStdout2 } from "ink";
11253
- import Spinner3 from "ink-spinner";
11254
- import React28, { useEffect as useEffect13, useRef as useRef5, useState as useState14 } from "react";
12250
+ import { homedir as homedir8 } from "os";
12251
+ import { Box as Box27, Text as Text25, useApp as useApp2, useStdout as useStdout2 } from "ink";
12252
+ import Spinner4 from "ink-spinner";
12253
+ import React30, { useEffect as useEffect13, useRef as useRef5, useState as useState16 } from "react";
11255
12254
  var getGitBranch2, getCurrentFolder2, AppContent, App;
11256
12255
  var init_App = __esm({
11257
12256
  "src/ui/App.tsx"() {
@@ -11262,6 +12261,7 @@ var init_App = __esm({
11262
12261
  init_prompts();
11263
12262
  init_claude_max();
11264
12263
  init_command_discovery();
12264
+ init_mcp_manager();
11265
12265
  init_settings_loader();
11266
12266
  init_stdio();
11267
12267
  init_token_storage();
@@ -11270,8 +12270,10 @@ var init_App = __esm({
11270
12270
  init_AuthDialog();
11271
12271
  init_FeedbackDialog();
11272
12272
  init_FixFlow();
11273
- init_HelpMenu();
11274
12273
  init_InputPrompt();
12274
+ init_McpAddDialog();
12275
+ init_McpServerSelector();
12276
+ init_McpServersDisplay();
11275
12277
  init_MessageList();
11276
12278
  init_ModelSelector();
11277
12279
  init_ProviderSelector();
@@ -11291,7 +12293,7 @@ var init_App = __esm({
11291
12293
  };
11292
12294
  getCurrentFolder2 = (configCwd) => {
11293
12295
  const cwd = configCwd || process.cwd();
11294
- const home = homedir6();
12296
+ const home = homedir8();
11295
12297
  if (cwd.startsWith(home)) {
11296
12298
  return `~${cwd.slice(home.length)}`;
11297
12299
  }
@@ -11302,27 +12304,43 @@ var init_App = __esm({
11302
12304
  const { stdout } = useStdout2();
11303
12305
  const { addMessage, clearMessages, isAgentRunning, messages, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider } = useSession();
11304
12306
  useModeToggle();
11305
- const [terminalWidth, setTerminalWidth] = useState14(process.stdout.columns || 80);
11306
- const [showHelp, setShowHelp] = useState14(false);
11307
- const [showInput, setShowInput] = useState14(true);
11308
- const [gitBranch] = useState14(() => getGitBranch2());
11309
- const [currentFolder] = useState14(() => getCurrentFolder2(config2.cwd));
11310
- const [hasInputContent, setHasInputContent] = useState14(false);
11311
- const [exitWarning, setExitWarning] = useState14(null);
12307
+ const [terminalWidth, setTerminalWidth] = useState16(process.stdout.columns || 80);
12308
+ const [showInput, setShowInput] = useState16(true);
12309
+ const [gitBranch] = useState16(() => getGitBranch2());
12310
+ const [currentFolder] = useState16(() => getCurrentFolder2(config2.cwd));
12311
+ const [hasInputContent, setHasInputContent] = useState16(false);
12312
+ const [exitWarning, setExitWarning] = useState16(null);
11312
12313
  const inputPromptRef = useRef5(null);
11313
- const [showSessionSelector, setShowSessionSelector] = useState14(false);
11314
- const [showModelSelector, setShowModelSelector] = useState14(false);
11315
- const [showProviderSelector, setShowProviderSelector] = useState14(false);
11316
- const [showFeedbackDialog, setShowFeedbackDialog] = useState14(false);
11317
- const [isLoadingSession, setIsLoadingSession] = useState14(false);
11318
- const [showFixFlow, setShowFixFlow] = useState14(false);
11319
- const [fixRunId, setFixRunId] = useState14(void 0);
11320
- const [authState, setAuthState] = useState14(
12314
+ const [showSessionSelector, setShowSessionSelector] = useState16(false);
12315
+ const [showModelSelector, setShowModelSelector] = useState16(false);
12316
+ const [showProviderSelector, setShowProviderSelector] = useState16(false);
12317
+ const [showFeedbackDialog, setShowFeedbackDialog] = useState16(false);
12318
+ const [isLoadingSession, setIsLoadingSession] = useState16(false);
12319
+ const [showFixFlow, setShowFixFlow] = useState16(false);
12320
+ const [fixRunId, setFixRunId] = useState16(void 0);
12321
+ const [showMcpServers, setShowMcpServers] = useState16(false);
12322
+ const [showMcpAdd, setShowMcpAdd] = useState16(false);
12323
+ const [showMcpSelector, setShowMcpSelector] = useState16(false);
12324
+ const [mcpSelectorAction, setMcpSelectorAction] = useState16(
12325
+ "remove"
12326
+ );
12327
+ const [mcpServers, setMcpServers] = useState16([]);
12328
+ const [authState, setAuthState] = useState16(
11321
12329
  () => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
11322
12330
  );
11323
- const [showAuthDialog, setShowAuthDialog] = useState14(false);
12331
+ const [showAuthDialog, setShowAuthDialog] = useState16(false);
11324
12332
  const { isOverlayOpen, isCancelSuppressed, markOverlayClosed } = useOverlayEscapeGuard({
11325
- overlays: [showHelp, showSessionSelector, showAuthDialog, showModelSelector, showProviderSelector, showFeedbackDialog, showFixFlow]
12333
+ overlays: [
12334
+ showSessionSelector,
12335
+ showAuthDialog,
12336
+ showModelSelector,
12337
+ showProviderSelector,
12338
+ showFeedbackDialog,
12339
+ showFixFlow,
12340
+ showMcpServers,
12341
+ showMcpAdd,
12342
+ showMcpSelector
12343
+ ]
11326
12344
  });
11327
12345
  useEffect13(() => {
11328
12346
  if (!config2.supatestApiKey) {
@@ -11380,7 +12398,7 @@ var init_App = __esm({
11380
12398
  return;
11381
12399
  }
11382
12400
  if (command === "/help" || command === "/?") {
11383
- setShowHelp((prev) => !prev);
12401
+ onSubmitTask?.(helpPrompt);
11384
12402
  return;
11385
12403
  }
11386
12404
  if (command === "/login") {
@@ -11509,6 +12527,10 @@ var init_App = __esm({
11509
12527
  onSubmitTask?.(discoverPrompt);
11510
12528
  return;
11511
12529
  }
12530
+ if (command === "/mcp") {
12531
+ setShowMcpServers(true);
12532
+ return;
12533
+ }
11512
12534
  const projectDir = config2.cwd || process.cwd();
11513
12535
  const spaceIndex = trimmedTask.indexOf(" ");
11514
12536
  const commandName = spaceIndex > 0 ? trimmedTask.slice(1, spaceIndex) : trimmedTask.slice(1);
@@ -11661,9 +12683,112 @@ var init_App = __esm({
11661
12683
  setIsAgentRunning(true);
11662
12684
  onSubmitTask?.(prompt);
11663
12685
  };
11664
- const handleHelpClose = () => {
12686
+ const handleMcpServersClose = () => {
11665
12687
  markOverlayClosed();
11666
- setShowHelp(false);
12688
+ setShowMcpServers(false);
12689
+ };
12690
+ const loadMcpServersForSelector = () => {
12691
+ const servers = Object.values(loadMcpConfig(config2.cwd || process.cwd()));
12692
+ setMcpServers(servers);
12693
+ };
12694
+ const handleMcpAdd = () => {
12695
+ setShowMcpServers(false);
12696
+ setShowMcpAdd(true);
12697
+ };
12698
+ const handleMcpAddConfirm = (name, command, args, env, description, scope) => {
12699
+ const result = addMcpServer(config2.cwd || process.cwd(), name, command, args, env, description, scope);
12700
+ setShowMcpAdd(false);
12701
+ markOverlayClosed();
12702
+ if (result.success) {
12703
+ addMessage({
12704
+ type: "assistant",
12705
+ content: result.message
12706
+ });
12707
+ setShowMcpServers(true);
12708
+ } else {
12709
+ addMessage({
12710
+ type: "error",
12711
+ content: result.message,
12712
+ errorType: "error"
12713
+ });
12714
+ }
12715
+ };
12716
+ const handleMcpAddCancel = () => {
12717
+ setShowMcpAdd(false);
12718
+ markOverlayClosed();
12719
+ setShowMcpServers(true);
12720
+ };
12721
+ const handleMcpRemove = () => {
12722
+ loadMcpServersForSelector();
12723
+ setMcpSelectorAction("remove");
12724
+ setShowMcpServers(false);
12725
+ setShowMcpSelector(true);
12726
+ };
12727
+ const handleMcpTest = () => {
12728
+ loadMcpServersForSelector();
12729
+ setMcpSelectorAction("test");
12730
+ setShowMcpServers(false);
12731
+ setShowMcpSelector(true);
12732
+ };
12733
+ const handleMcpSelectorConfirm = async (serverName) => {
12734
+ const cwd = config2.cwd || process.cwd();
12735
+ const servers = loadMcpConfig(cwd);
12736
+ const server = servers[serverName];
12737
+ if (!server) {
12738
+ addMessage({
12739
+ type: "error",
12740
+ content: `Server "${serverName}" not found`,
12741
+ errorType: "error"
12742
+ });
12743
+ setShowMcpSelector(false);
12744
+ markOverlayClosed();
12745
+ setShowMcpServers(true);
12746
+ return;
12747
+ }
12748
+ setShowMcpSelector(false);
12749
+ markOverlayClosed();
12750
+ if (mcpSelectorAction === "remove") {
12751
+ const result = removeMcpServer(cwd, serverName);
12752
+ addMessage({
12753
+ type: "assistant",
12754
+ content: result.message
12755
+ });
12756
+ } else if (mcpSelectorAction === "test") {
12757
+ addMessage({
12758
+ type: "assistant",
12759
+ content: `Testing connection to "${serverName}"...`
12760
+ });
12761
+ const result = await testMcpConnection(serverName, server);
12762
+ addMessage({
12763
+ type: "assistant",
12764
+ content: result.message
12765
+ });
12766
+ if (result.error) {
12767
+ addMessage({
12768
+ type: "error",
12769
+ content: `Error: ${result.error}`,
12770
+ errorType: "warning"
12771
+ });
12772
+ }
12773
+ } else if (mcpSelectorAction === "enable") {
12774
+ const result = enableMcpServer(cwd, serverName);
12775
+ addMessage({
12776
+ type: "assistant",
12777
+ content: result.message
12778
+ });
12779
+ } else if (mcpSelectorAction === "disable") {
12780
+ const result = disableMcpServer(cwd, serverName);
12781
+ addMessage({
12782
+ type: "assistant",
12783
+ content: result.message
12784
+ });
12785
+ }
12786
+ setShowMcpServers(true);
12787
+ };
12788
+ const handleMcpSelectorCancel = () => {
12789
+ setShowMcpSelector(false);
12790
+ markOverlayClosed();
12791
+ setShowMcpServers(true);
11667
12792
  };
11668
12793
  const isInitialMount = useRef5(true);
11669
12794
  useEffect13(() => {
@@ -11727,9 +12852,6 @@ var init_App = __esm({
11727
12852
  exit();
11728
12853
  onExit(true);
11729
12854
  }
11730
- if (key.ctrl && key.name === "h" && !showHelp) {
11731
- setShowHelp(true);
11732
- }
11733
12855
  if (key.ctrl && key.name === "l") {
11734
12856
  clearTerminalViewportAndScrollback();
11735
12857
  }
@@ -11747,21 +12869,14 @@ var init_App = __esm({
11747
12869
  });
11748
12870
  }
11749
12871
  }, []);
11750
- return /* @__PURE__ */ React28.createElement(
11751
- Box25,
12872
+ return /* @__PURE__ */ React30.createElement(
12873
+ Box27,
11752
12874
  {
11753
12875
  flexDirection: "column",
11754
12876
  paddingX: 1
11755
12877
  },
11756
- /* @__PURE__ */ React28.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
11757
- showHelp && /* @__PURE__ */ React28.createElement(
11758
- HelpMenu,
11759
- {
11760
- isAuthenticated: authState === "authenticated" /* Authenticated */,
11761
- onClose: handleHelpClose
11762
- }
11763
- ),
11764
- showSessionSelector && apiClient && /* @__PURE__ */ React28.createElement(
12878
+ /* @__PURE__ */ React30.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
12879
+ showSessionSelector && apiClient && /* @__PURE__ */ React30.createElement(
11765
12880
  SessionSelector,
11766
12881
  {
11767
12882
  apiClient,
@@ -11769,8 +12884,8 @@ var init_App = __esm({
11769
12884
  onSelect: handleSessionSelect
11770
12885
  }
11771
12886
  ),
11772
- isLoadingSession && /* @__PURE__ */ React28.createElement(Box25, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "row" }, /* @__PURE__ */ React28.createElement(Box25, { width: 2 }, /* @__PURE__ */ React28.createElement(Text23, { color: theme.text.accent }, /* @__PURE__ */ React28.createElement(Spinner3, { type: "dots" }))), /* @__PURE__ */ React28.createElement(Text23, { bold: true, color: "cyan" }, "Loading session...")), /* @__PURE__ */ React28.createElement(Text23, { color: theme.text.dim }, "Fetching queries and context")),
11773
- showModelSelector && /* @__PURE__ */ React28.createElement(
12887
+ isLoadingSession && /* @__PURE__ */ React30.createElement(Box27, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "row" }, /* @__PURE__ */ React30.createElement(Box27, { width: 2 }, /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.accent }, /* @__PURE__ */ React30.createElement(Spinner4, { type: "dots" }))), /* @__PURE__ */ React30.createElement(Text25, { bold: true, color: "cyan" }, "Loading session...")), /* @__PURE__ */ React30.createElement(Text25, { color: theme.text.dim }, "Fetching queries and context")),
12888
+ showModelSelector && /* @__PURE__ */ React30.createElement(
11774
12889
  ModelSelector,
11775
12890
  {
11776
12891
  currentModel: selectedModel,
@@ -11779,7 +12894,7 @@ var init_App = __esm({
11779
12894
  onSelect: handleModelSelect
11780
12895
  }
11781
12896
  ),
11782
- showProviderSelector && /* @__PURE__ */ React28.createElement(
12897
+ showProviderSelector && /* @__PURE__ */ React30.createElement(
11783
12898
  ProviderSelector,
11784
12899
  {
11785
12900
  claudeMaxAvailable: isClaudeMaxAvailable(),
@@ -11788,20 +12903,20 @@ var init_App = __esm({
11788
12903
  onSelect: handleProviderSelect
11789
12904
  }
11790
12905
  ),
11791
- showAuthDialog && /* @__PURE__ */ React28.createElement(
12906
+ showAuthDialog && /* @__PURE__ */ React30.createElement(
11792
12907
  AuthDialog,
11793
12908
  {
11794
12909
  onLogin: handleLogin
11795
12910
  }
11796
12911
  ),
11797
- showFeedbackDialog && /* @__PURE__ */ React28.createElement(
12912
+ showFeedbackDialog && /* @__PURE__ */ React30.createElement(
11798
12913
  FeedbackDialog,
11799
12914
  {
11800
12915
  onCancel: handleFeedbackCancel,
11801
12916
  onSubmit: handleFeedbackSubmit
11802
12917
  }
11803
12918
  ),
11804
- showFixFlow && apiClient && /* @__PURE__ */ React28.createElement(
12919
+ showFixFlow && apiClient && /* @__PURE__ */ React30.createElement(
11805
12920
  FixFlow,
11806
12921
  {
11807
12922
  apiClient,
@@ -11811,14 +12926,33 @@ var init_App = __esm({
11811
12926
  onStartFix: handleFixStart
11812
12927
  }
11813
12928
  ),
11814
- /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column" }, !showAuthDialog && /* @__PURE__ */ React28.createElement(AuthBanner, { authState }), showInput && !showSessionSelector && !showAuthDialog && !showHelp && !showModelSelector && !showProviderSelector && !showFeedbackDialog && !showFixFlow && !isLoadingSession && /* @__PURE__ */ React28.createElement(Box25, { flexDirection: "column", marginTop: 0, width: "100%" }, exitWarning && /* @__PURE__ */ React28.createElement(Box25, { marginBottom: 0, paddingX: 1 }, /* @__PURE__ */ React28.createElement(Text23, { color: "yellow" }, exitWarning)), /* @__PURE__ */ React28.createElement(
12929
+ showMcpServers && /* @__PURE__ */ React30.createElement(
12930
+ McpServersDisplay,
12931
+ {
12932
+ cwd: config2.cwd,
12933
+ onAdd: handleMcpAdd,
12934
+ onClose: handleMcpServersClose,
12935
+ onRemove: handleMcpRemove,
12936
+ onTest: handleMcpTest
12937
+ }
12938
+ ),
12939
+ showMcpAdd && /* @__PURE__ */ React30.createElement(McpAddDialog, { onCancel: handleMcpAddCancel, onConfirm: handleMcpAddConfirm }),
12940
+ showMcpSelector && /* @__PURE__ */ React30.createElement(
12941
+ McpServerSelector,
12942
+ {
12943
+ action: mcpSelectorAction,
12944
+ onCancel: handleMcpSelectorCancel,
12945
+ onSelect: handleMcpSelectorConfirm,
12946
+ servers: mcpServers
12947
+ }
12948
+ ),
12949
+ /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column" }, !showAuthDialog && /* @__PURE__ */ React30.createElement(AuthBanner, { authState }), showInput && !showSessionSelector && !showAuthDialog && !showModelSelector && !showProviderSelector && !showFeedbackDialog && !showFixFlow && !showMcpServers && !showMcpAdd && !showMcpSelector && !isLoadingSession && /* @__PURE__ */ React30.createElement(Box27, { flexDirection: "column", marginTop: 0, width: "100%" }, exitWarning && /* @__PURE__ */ React30.createElement(Box27, { marginBottom: 0, paddingX: 1 }, /* @__PURE__ */ React30.createElement(Text25, { color: "yellow" }, exitWarning)), /* @__PURE__ */ React30.createElement(
11815
12950
  InputPrompt,
11816
12951
  {
11817
12952
  currentFolder,
11818
12953
  cwd: config2.cwd,
11819
12954
  gitBranch,
11820
12955
  isClaudeMax: !!config2.oauthToken,
11821
- onHelpToggle: () => setShowHelp((prev) => !prev),
11822
12956
  onInputChange: (val) => setHasInputContent(val.trim().length > 0),
11823
12957
  onSubmit: handleSubmitTask,
11824
12958
  placeholder: "Enter your task...",
@@ -11828,7 +12962,7 @@ var init_App = __esm({
11828
12962
  );
11829
12963
  };
11830
12964
  App = (props) => {
11831
- return /* @__PURE__ */ React28.createElement(AppContent, { ...props });
12965
+ return /* @__PURE__ */ React30.createElement(AppContent, { ...props });
11832
12966
  };
11833
12967
  }
11834
12968
  });
@@ -11864,7 +12998,7 @@ __export(interactive_exports, {
11864
12998
  runInteractive: () => runInteractive
11865
12999
  });
11866
13000
  import { render as render2 } from "ink";
11867
- import React29, { useEffect as useEffect15, useRef as useRef6 } from "react";
13001
+ import React31, { useEffect as useEffect15, useRef as useRef6 } from "react";
11868
13002
  function getToolDescription2(toolName, input) {
11869
13003
  switch (toolName) {
11870
13004
  case "Read":
@@ -11997,7 +13131,7 @@ async function runInteractive(config2) {
11997
13131
  webUrl = session.webUrl;
11998
13132
  }
11999
13133
  const { unmount, waitUntilExit } = render2(
12000
- /* @__PURE__ */ React29.createElement(
13134
+ /* @__PURE__ */ React31.createElement(
12001
13135
  InteractiveApp,
12002
13136
  {
12003
13137
  apiClient,
@@ -12189,17 +13323,17 @@ var init_interactive = __esm({
12189
13323
  setIsAgentRunning,
12190
13324
  setUsageStats
12191
13325
  } = useSession();
12192
- const [sessionId, setSessionId] = React29.useState(initialSessionId);
12193
- const [currentTask, setCurrentTask] = React29.useState(config2.task);
12194
- const [taskId, setTaskId] = React29.useState(0);
12195
- const [shouldRunAgent, setShouldRunAgent] = React29.useState(!!config2.task);
12196
- const [taskQueue, setTaskQueue] = React29.useState([]);
12197
- const [providerSessionId, setProviderSessionId] = React29.useState();
12198
- const messageBridgeRef = React29.useRef(null);
12199
- const lastSubmitRef = React29.useRef(null);
12200
- const [pendingInjected, setPendingInjected] = React29.useState([]);
12201
- const pendingInjectedRef = React29.useRef([]);
12202
- React29.useEffect(() => {
13326
+ const [sessionId, setSessionId] = React31.useState(initialSessionId);
13327
+ const [currentTask, setCurrentTask] = React31.useState(config2.task);
13328
+ const [taskId, setTaskId] = React31.useState(0);
13329
+ const [shouldRunAgent, setShouldRunAgent] = React31.useState(!!config2.task);
13330
+ const [taskQueue, setTaskQueue] = React31.useState([]);
13331
+ const [providerSessionId, setProviderSessionId] = React31.useState();
13332
+ const messageBridgeRef = React31.useRef(null);
13333
+ const lastSubmitRef = React31.useRef(null);
13334
+ const [pendingInjected, setPendingInjected] = React31.useState([]);
13335
+ const pendingInjectedRef = React31.useRef([]);
13336
+ React31.useEffect(() => {
12203
13337
  pendingInjectedRef.current = pendingInjected;
12204
13338
  }, [pendingInjected]);
12205
13339
  const handleSubmitTask = async (task) => {
@@ -12262,7 +13396,7 @@ var init_interactive = __esm({
12262
13396
  if (shouldRunAgent && !messageBridgeRef.current) {
12263
13397
  messageBridgeRef.current = new MessageBridge(providerSessionId || "");
12264
13398
  }
12265
- React29.useEffect(() => {
13399
+ React31.useEffect(() => {
12266
13400
  if (!shouldRunAgent && taskQueue.length > 0) {
12267
13401
  const [nextTask, ...remaining] = taskQueue;
12268
13402
  setTaskQueue(remaining);
@@ -12276,14 +13410,14 @@ var init_interactive = __esm({
12276
13410
  setShouldRunAgent(true);
12277
13411
  }
12278
13412
  }, [shouldRunAgent, taskQueue, addMessage, providerSessionId]);
12279
- const handleClearSession = React29.useCallback(() => {
13413
+ const handleClearSession = React31.useCallback(() => {
12280
13414
  setSessionId(void 0);
12281
13415
  setContextSessionId(void 0);
12282
13416
  setProviderSessionId(void 0);
12283
13417
  setTaskQueue([]);
12284
13418
  setPendingInjected([]);
12285
13419
  }, [setContextSessionId]);
12286
- return /* @__PURE__ */ React29.createElement(React29.Fragment, null, /* @__PURE__ */ React29.createElement(
13420
+ return /* @__PURE__ */ React31.createElement(React31.Fragment, null, /* @__PURE__ */ React31.createElement(
12287
13421
  App,
12288
13422
  {
12289
13423
  apiClient,
@@ -12346,7 +13480,7 @@ var init_interactive = __esm({
12346
13480
  sessionId,
12347
13481
  webUrl
12348
13482
  }
12349
- ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React29.createElement(
13483
+ ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React31.createElement(
12350
13484
  AgentRunner,
12351
13485
  {
12352
13486
  apiClient,
@@ -12371,7 +13505,7 @@ var init_interactive = __esm({
12371
13505
  useBracketedPaste();
12372
13506
  const settings = loadSupatestSettings(props.config.cwd || process.cwd());
12373
13507
  const initialProvider = settings.llmProvider || "supatest-managed";
12374
- return /* @__PURE__ */ React29.createElement(KeypressProvider, null, /* @__PURE__ */ React29.createElement(SessionProvider, { initialLlmProvider: initialProvider, initialModel: props.config.selectedModel }, /* @__PURE__ */ React29.createElement(InteractiveAppContent, { ...props })));
13508
+ return /* @__PURE__ */ React31.createElement(KeypressProvider, null, /* @__PURE__ */ React31.createElement(SessionProvider, { initialLlmProvider: initialProvider, initialModel: props.config.selectedModel }, /* @__PURE__ */ React31.createElement(InteractiveAppContent, { ...props })));
12375
13509
  };
12376
13510
  }
12377
13511
  });
@@ -12395,7 +13529,7 @@ init_react();
12395
13529
  init_MessageList();
12396
13530
  init_SessionContext();
12397
13531
  import { execSync as execSync2 } from "child_process";
12398
- import { homedir as homedir3 } from "os";
13532
+ import { homedir as homedir4 } from "os";
12399
13533
  import { Box as Box13, useApp } from "ink";
12400
13534
  import React14, { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
12401
13535
  var getGitBranch = () => {
@@ -12407,7 +13541,7 @@ var getGitBranch = () => {
12407
13541
  };
12408
13542
  var getCurrentFolder = () => {
12409
13543
  const cwd = process.cwd();
12410
- const home = homedir3();
13544
+ const home = homedir4();
12411
13545
  if (cwd.startsWith(home)) {
12412
13546
  return `~${cwd.slice(home.length)}`;
12413
13547
  }
@@ -12851,7 +13985,7 @@ program.name("supatest").description(
12851
13985
  if (options.verbose) {
12852
13986
  logger.setVerbose(true);
12853
13987
  }
12854
- const isDev = process.env.NODE_ENV === "development";
13988
+ const isDev = process.env.NODE_ENV === "development" || process.env.SUPATEST_DEV === "true";
12855
13989
  logger.enableFileLogging(isDev);
12856
13990
  let prompt = task;
12857
13991
  let logs;