@supatest/cli 0.0.36 → 0.0.38

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 +1319 -201
  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
  });
@@ -5384,7 +5891,7 @@ var CLI_VERSION;
5384
5891
  var init_version = __esm({
5385
5892
  "src/version.ts"() {
5386
5893
  "use strict";
5387
- CLI_VERSION = "0.0.36";
5894
+ CLI_VERSION = "0.0.38";
5388
5895
  }
5389
5896
  });
5390
5897
 
@@ -6240,6 +6747,7 @@ var init_command_discovery = __esm({
6240
6747
 
6241
6748
  // src/utils/mcp-loader.ts
6242
6749
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
6750
+ import { homedir as homedir2 } from "os";
6243
6751
  import { join as join4 } from "path";
6244
6752
  function expandEnvVar(value) {
6245
6753
  return value.replace(/\$\{([^}]+)\}/g, (_, expr) => {
@@ -6262,8 +6770,7 @@ function expandServerConfig(config2) {
6262
6770
  }
6263
6771
  return expanded;
6264
6772
  }
6265
- function loadMcpServers(cwd) {
6266
- const mcpPath = join4(cwd, ".supatest", "mcp.json");
6773
+ function loadMcpServersFromFile(mcpPath) {
6267
6774
  if (!existsSync3(mcpPath)) {
6268
6775
  return {};
6269
6776
  }
@@ -6275,6 +6782,9 @@ function loadMcpServers(cwd) {
6275
6782
  }
6276
6783
  const expanded = {};
6277
6784
  for (const [name, serverConfig] of Object.entries(config2.mcpServers)) {
6785
+ if (serverConfig.enabled === false) {
6786
+ continue;
6787
+ }
6278
6788
  expanded[name] = expandServerConfig(serverConfig);
6279
6789
  }
6280
6790
  return expanded;
@@ -6286,6 +6796,13 @@ function loadMcpServers(cwd) {
6286
6796
  return {};
6287
6797
  }
6288
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
+ }
6289
6806
  var init_mcp_loader = __esm({
6290
6807
  "src/utils/mcp-loader.ts"() {
6291
6808
  "use strict";
@@ -6318,7 +6835,7 @@ var init_project_instructions = __esm({
6318
6835
 
6319
6836
  // src/core/agent.ts
6320
6837
  import { createRequire } from "module";
6321
- import { homedir as homedir2 } from "os";
6838
+ import { homedir as homedir3 } from "os";
6322
6839
  import { dirname, join as join6 } from "path";
6323
6840
  import { query } from "@anthropic-ai/claude-agent-sdk";
6324
6841
  var CoreAgent;
@@ -6419,7 +6936,7 @@ ${projectInstructions}`,
6419
6936
  this.presenter.onLog(`Auth: Using Claude Max (default Claude Code credentials)`);
6420
6937
  logger.debug("[agent] Claude Max mode: Using default ~/.claude/ config, cleared provider credentials");
6421
6938
  } else {
6422
- const internalConfigDir = join6(homedir2(), ".supatest", "claude-internal");
6939
+ const internalConfigDir = join6(homedir3(), ".supatest", "claude-internal");
6423
6940
  cleanEnv.CLAUDE_CONFIG_DIR = internalConfigDir;
6424
6941
  cleanEnv.ANTHROPIC_API_KEY = config2.supatestApiKey || "";
6425
6942
  cleanEnv.ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL || "";
@@ -6450,9 +6967,17 @@ ${projectInstructions}`,
6450
6967
  // MCP servers from .supatest/mcp.json
6451
6968
  // Users can add servers like Playwright if needed
6452
6969
  mcpServers: (() => {
6970
+ logger.debug("[agent] Loading MCP servers for query", { cwd });
6453
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
+ });
6454
6977
  if (Object.keys(servers).length > 0) {
6455
6978
  this.presenter.onLog(`MCP servers: ${Object.keys(servers).join(", ")}`);
6979
+ } else {
6980
+ logger.debug("[agent] No MCP servers configured");
6456
6981
  }
6457
6982
  return servers;
6458
6983
  })(),
@@ -6512,7 +7037,14 @@ ${projectInstructions}`,
6512
7037
  maxTurns: options.maxTurns,
6513
7038
  permissionMode: options.permissionMode,
6514
7039
  hasResume: !!options.resume,
6515
- 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 || {})
6516
7048
  });
6517
7049
  const queryIterator = query({ prompt, options });
6518
7050
  if (this.messageBridge) {
@@ -8425,7 +8957,7 @@ var init_encryption = __esm({
8425
8957
 
8426
8958
  // src/utils/token-storage.ts
8427
8959
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync } from "fs";
8428
- import { homedir as homedir4 } from "os";
8960
+ import { homedir as homedir5 } from "os";
8429
8961
  import { join as join7 } from "path";
8430
8962
  function getTokenFilePath() {
8431
8963
  const apiUrl = process.env.SUPATEST_API_URL || PRODUCTION_API_URL;
@@ -8500,7 +9032,7 @@ var init_token_storage = __esm({
8500
9032
  "src/utils/token-storage.ts"() {
8501
9033
  "use strict";
8502
9034
  init_encryption();
8503
- CONFIG_DIR = join7(homedir4(), ".supatest");
9035
+ CONFIG_DIR = join7(homedir5(), ".supatest");
8504
9036
  PRODUCTION_API_URL = "https://code-api.supatest.ai";
8505
9037
  STORAGE_VERSION = 2;
8506
9038
  TOKEN_FILE = join7(CONFIG_DIR, "token.json");
@@ -8625,18 +9157,22 @@ async function exchangeCodeForToken(code, state) {
8625
9157
  function openBrowser(url) {
8626
9158
  const os3 = platform();
8627
9159
  let command;
9160
+ let args;
8628
9161
  switch (os3) {
8629
9162
  case "darwin":
8630
9163
  command = "open";
9164
+ args = [url];
8631
9165
  break;
8632
9166
  case "win32":
8633
9167
  command = "start";
9168
+ args = ["", url];
8634
9169
  break;
8635
9170
  default:
8636
9171
  command = "xdg-open";
9172
+ args = [url];
8637
9173
  }
8638
9174
  const options = { detached: true, stdio: "ignore", shell: os3 === "win32" };
8639
- spawn2(command, [url], options).unref();
9175
+ spawn2(command, args, options).unref();
8640
9176
  }
8641
9177
  function startCallbackServer(port, expectedState) {
8642
9178
  return new Promise((resolve2, reject) => {
@@ -8975,7 +9511,7 @@ var init_login = __esm({
8975
9511
  // src/utils/claude-max.ts
8976
9512
  import { execSync as execSync5 } from "child_process";
8977
9513
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
8978
- import { homedir as homedir5 } from "os";
9514
+ import { homedir as homedir6 } from "os";
8979
9515
  import { join as join8 } from "path";
8980
9516
  function isClaudeMaxAvailable() {
8981
9517
  const platform2 = process.platform;
@@ -9012,7 +9548,7 @@ function checkMacOSKeychain() {
9012
9548
  }
9013
9549
  function checkCredentialsFile() {
9014
9550
  try {
9015
- const credentialsPath = join8(homedir5(), ".claude", ".credentials.json");
9551
+ const credentialsPath = join8(homedir6(), ".claude", ".credentials.json");
9016
9552
  if (!existsSync6(credentialsPath)) {
9017
9553
  logger.debug("[claude-max] Credentials file not found", { path: credentialsPath });
9018
9554
  return false;
@@ -9040,16 +9576,248 @@ var init_claude_max = __esm({
9040
9576
  }
9041
9577
  });
9042
9578
 
9043
- // src/utils/settings-loader.ts
9579
+ // src/utils/mcp-manager.ts
9044
9580
  import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "fs";
9581
+ import { homedir as homedir7 } from "os";
9045
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";
9046
9814
  function loadSupatestSettings(cwd) {
9047
- const settingsPath = join9(cwd, ".supatest", "settings.json");
9048
- if (!existsSync7(settingsPath)) {
9815
+ const settingsPath = join10(cwd, ".supatest", "settings.json");
9816
+ if (!existsSync8(settingsPath)) {
9049
9817
  return {};
9050
9818
  }
9051
9819
  try {
9052
- const content = readFileSync6(settingsPath, "utf-8");
9820
+ const content = readFileSync7(settingsPath, "utf-8");
9053
9821
  return JSON.parse(content);
9054
9822
  } catch (error) {
9055
9823
  console.warn(
@@ -9060,11 +9828,11 @@ function loadSupatestSettings(cwd) {
9060
9828
  }
9061
9829
  }
9062
9830
  function saveSupatestSettings(cwd, settings) {
9063
- const settingsDir = join9(cwd, ".supatest");
9064
- const settingsPath = join9(settingsDir, "settings.json");
9831
+ const settingsDir = join10(cwd, ".supatest");
9832
+ const settingsPath = join10(settingsDir, "settings.json");
9065
9833
  try {
9066
- if (!existsSync7(settingsDir)) {
9067
- mkdirSync3(settingsDir, { recursive: true });
9834
+ if (!existsSync8(settingsDir)) {
9835
+ mkdirSync4(settingsDir, { recursive: true });
9068
9836
  }
9069
9837
  const existingSettings = loadSupatestSettings(cwd);
9070
9838
  const mergedSettings = {
@@ -9080,7 +9848,7 @@ function saveSupatestSettings(cwd, settings) {
9080
9848
  ...settings.hooks
9081
9849
  }
9082
9850
  };
9083
- writeFileSync2(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
9851
+ writeFileSync3(settingsPath, JSON.stringify(mergedSettings, null, 2), "utf-8");
9084
9852
  } catch (error) {
9085
9853
  console.warn(
9086
9854
  `Warning: Failed to save settings to ${settingsPath}:`,
@@ -10497,56 +11265,6 @@ var init_FixFlow = __esm({
10497
11265
  }
10498
11266
  });
10499
11267
 
10500
- // src/ui/components/HelpMenu.tsx
10501
- import { Box as Box20, Text as Text18, useInput as useInput5 } from "ink";
10502
- import React23, { useEffect as useEffect9, useState as useState9 } from "react";
10503
- var HelpMenu;
10504
- var init_HelpMenu = __esm({
10505
- "src/ui/components/HelpMenu.tsx"() {
10506
- "use strict";
10507
- init_command_discovery();
10508
- init_theme();
10509
- HelpMenu = ({ isAuthenticated, onClose, cwd }) => {
10510
- const [customCommands, setCustomCommands] = useState9([]);
10511
- useEffect9(() => {
10512
- const projectDir = cwd || process.cwd();
10513
- const commands = discoverCommands(projectDir);
10514
- setCustomCommands(commands);
10515
- }, [cwd]);
10516
- useInput5((input, key) => {
10517
- if (key.escape || input === "q" || input === "?" || key.ctrl && input === "h") {
10518
- onClose();
10519
- }
10520
- });
10521
- return /* @__PURE__ */ React23.createElement(
10522
- Box20,
10523
- {
10524
- borderColor: theme.border.accent,
10525
- borderStyle: "round",
10526
- flexDirection: "column",
10527
- paddingX: 2,
10528
- paddingY: 1
10529
- },
10530
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.accent }, "\u{1F4D6} Supatest AI CLI - Help"),
10531
- /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }),
10532
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.secondary }, "Slash Commands:"),
10533
- /* @__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"))),
10534
- 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)"))),
10535
- /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }),
10536
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.secondary }, "Keyboard Shortcuts:"),
10537
- /* @__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"))),
10538
- /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }),
10539
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.secondary }, "File References:"),
10540
- /* @__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"')),
10541
- /* @__PURE__ */ React23.createElement(Box20, { marginTop: 1 }),
10542
- /* @__PURE__ */ React23.createElement(Text18, { bold: true, color: theme.text.secondary }, "Tips:"),
10543
- /* @__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")),
10544
- /* @__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"))
10545
- );
10546
- };
10547
- }
10548
- });
10549
-
10550
11268
  // src/ui/utils/file-completion.ts
10551
11269
  import fs4 from "fs";
10552
11270
  import path4 from "path";
@@ -10630,8 +11348,8 @@ var init_file_completion = __esm({
10630
11348
  });
10631
11349
 
10632
11350
  // src/ui/components/ModelSelector.tsx
10633
- import { Box as Box21, Text as Text19, useInput as useInput6 } from "ink";
10634
- 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";
10635
11353
  function getAvailableModels(isClaudeMax) {
10636
11354
  if (isClaudeMax) {
10637
11355
  return AVAILABLE_MODELS.map((m) => ({
@@ -10666,8 +11384,8 @@ var init_ModelSelector = __esm({
10666
11384
  }) => {
10667
11385
  const models = getAvailableModels(isClaudeMax);
10668
11386
  const currentIndex = models.findIndex((m) => m.id === currentModel);
10669
- const [selectedIndex, setSelectedIndex] = useState10(currentIndex >= 0 ? currentIndex : 0);
10670
- useInput6((input, key) => {
11387
+ const [selectedIndex, setSelectedIndex] = useState9(currentIndex >= 0 ? currentIndex : 0);
11388
+ useInput5((input, key) => {
10671
11389
  if (key.upArrow) {
10672
11390
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : models.length - 1);
10673
11391
  } else if (key.downArrow) {
@@ -10678,12 +11396,12 @@ var init_ModelSelector = __esm({
10678
11396
  onCancel();
10679
11397
  }
10680
11398
  });
10681
- 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) => {
10682
11400
  const isSelected = index === selectedIndex;
10683
11401
  const isCurrent = model.id === currentModel;
10684
11402
  const indicator = isSelected ? "\u25B6 " : " ";
10685
- return /* @__PURE__ */ React24.createElement(Box21, { gap: 1, key: model.id }, /* @__PURE__ */ React24.createElement(
10686
- Text19,
11403
+ return /* @__PURE__ */ React23.createElement(Box20, { gap: 1, key: model.id }, /* @__PURE__ */ React23.createElement(
11404
+ Text18,
10687
11405
  {
10688
11406
  backgroundColor: isSelected ? theme.text.accent : void 0,
10689
11407
  bold: isSelected,
@@ -10691,15 +11409,15 @@ var init_ModelSelector = __esm({
10691
11409
  },
10692
11410
  indicator,
10693
11411
  model.name.padEnd(12)
10694
- ), /* @__PURE__ */ React24.createElement(
10695
- Text19,
11412
+ ), /* @__PURE__ */ React23.createElement(
11413
+ Text18,
10696
11414
  {
10697
11415
  backgroundColor: isSelected ? theme.text.accent : void 0,
10698
11416
  color: isSelected ? "black" : theme.text.dim
10699
11417
  },
10700
11418
  model.description
10701
- ), isCurrent && /* @__PURE__ */ React24.createElement(Text19, { color: theme.text.success }, " (current)"));
10702
- })), /* @__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")));
10703
11421
  };
10704
11422
  }
10705
11423
  });
@@ -10707,8 +11425,8 @@ var init_ModelSelector = __esm({
10707
11425
  // src/ui/components/InputPrompt.tsx
10708
11426
  import path5 from "path";
10709
11427
  import chalk4 from "chalk";
10710
- import { Box as Box22, Text as Text20 } from "ink";
10711
- 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";
10712
11430
  var InputPrompt;
10713
11431
  var init_InputPrompt = __esm({
10714
11432
  "src/ui/components/InputPrompt.tsx"() {
@@ -10732,13 +11450,13 @@ var init_InputPrompt = __esm({
10732
11450
  isClaudeMax = false
10733
11451
  }, ref) => {
10734
11452
  const { messages, agentMode, selectedModel, setSelectedModel, isAgentRunning, usageStats } = useSession();
10735
- const [value, setValue] = useState11("");
10736
- const [cursorOffset, setCursorOffset] = useState11(0);
10737
- const [allFiles, setAllFiles] = useState11([]);
10738
- const [suggestions, setSuggestions] = useState11([]);
10739
- const [activeSuggestion, setActiveSuggestion] = useState11(0);
10740
- const [showSuggestions, setShowSuggestions] = useState11(false);
10741
- 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);
10742
11460
  const BUILTIN_SLASH_COMMANDS = [
10743
11461
  { name: "/help", desc: "Show help" },
10744
11462
  { name: "/resume", desc: "Resume session" },
@@ -10749,13 +11467,14 @@ var init_InputPrompt = __esm({
10749
11467
  { name: "/feedback", desc: "Report an issue" },
10750
11468
  { name: "/setup", desc: "Install Playwright browsers" },
10751
11469
  { name: "/discover", desc: "Discover test framework" },
11470
+ { name: "/mcp", desc: "Show MCP servers" },
10752
11471
  { name: "/login", desc: "Authenticate with Supatest" },
10753
11472
  { name: "/logout", desc: "Log out" },
10754
11473
  { name: "/exit", desc: "Exit CLI" }
10755
11474
  ];
10756
- const [customCommands, setCustomCommands] = useState11([]);
10757
- const [isSlashCommand, setIsSlashCommand] = useState11(false);
10758
- useEffect10(() => {
11475
+ const [customCommands, setCustomCommands] = useState10([]);
11476
+ const [isSlashCommand, setIsSlashCommand] = useState10(false);
11477
+ useEffect9(() => {
10759
11478
  try {
10760
11479
  const projectDir = cwd || process.cwd();
10761
11480
  const discovered = discoverCommands(projectDir);
@@ -10776,7 +11495,7 @@ var init_InputPrompt = __esm({
10776
11495
  onInputChange?.("");
10777
11496
  }
10778
11497
  }));
10779
- useEffect10(() => {
11498
+ useEffect9(() => {
10780
11499
  setTimeout(() => {
10781
11500
  try {
10782
11501
  const files = getFiles();
@@ -10890,8 +11609,8 @@ var init_InputPrompt = __esm({
10890
11609
  setSelectedModel(getNextModel(selectedModel, isClaudeMax));
10891
11610
  return;
10892
11611
  }
10893
- if (input === "?" && value.length === 0 && onHelpToggle) {
10894
- onHelpToggle();
11612
+ if (input === "?" && value.length === 0) {
11613
+ onSubmit("/help");
10895
11614
  return;
10896
11615
  }
10897
11616
  if (showSuggestions && !key.shift) {
@@ -10976,8 +11695,8 @@ var init_InputPrompt = __esm({
10976
11695
  }
10977
11696
  charCount += lineLength + 1;
10978
11697
  }
10979
- return /* @__PURE__ */ React25.createElement(Box22, { flexDirection: "column", width: "100%" }, showSuggestions && /* @__PURE__ */ React25.createElement(
10980
- Box22,
11698
+ return /* @__PURE__ */ React24.createElement(Box21, { flexDirection: "column", width: "100%" }, showSuggestions && /* @__PURE__ */ React24.createElement(
11699
+ Box21,
10981
11700
  {
10982
11701
  borderColor: theme.border.default,
10983
11702
  borderStyle: "round",
@@ -10988,12 +11707,12 @@ var init_InputPrompt = __esm({
10988
11707
  suggestions.map((item, idx) => {
10989
11708
  const isSeparator = item.startsWith("\u2500\u2500\u2500\u2500\u2500");
10990
11709
  if (isSeparator) {
10991
- 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);
10992
11711
  }
10993
- 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);
10994
11713
  })
10995
- ), /* @__PURE__ */ React25.createElement(
10996
- Box22,
11714
+ ), /* @__PURE__ */ React24.createElement(
11715
+ Box21,
10997
11716
  {
10998
11717
  borderColor: disabled ? theme.border.default : theme.border.accent,
10999
11718
  borderStyle: "round",
@@ -11003,24 +11722,288 @@ var init_InputPrompt = __esm({
11003
11722
  paddingX: 1,
11004
11723
  width: "100%"
11005
11724
  },
11006
- /* @__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) => {
11007
11726
  if (idx === cursorLine && !disabled) {
11008
11727
  const before = line.slice(0, cursorCol);
11009
11728
  const charAtCursor = line[cursorCol] || " ";
11010
11729
  const after = line.slice(cursorCol + 1);
11011
- 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);
11012
11731
  }
11013
- return /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.primary, key: idx }, line);
11014
- })), !hasContent && disabled && /* @__PURE__ */ React25.createElement(Text20, { color: theme.text.dim, italic: true }, "Waiting for agent to complete...")))
11015
- ), /* @__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", ")"))));
11016
11735
  });
11017
11736
  InputPrompt.displayName = "InputPrompt";
11018
11737
  }
11019
11738
  });
11020
11739
 
11021
- // 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
11022
11848
  import { Box as Box23, Text as Text21, useInput as useInput7 } from "ink";
11023
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";
11024
12007
  var PROVIDERS, ProviderSelector;
11025
12008
  var init_ProviderSelector = __esm({
11026
12009
  "src/ui/components/ProviderSelector.tsx"() {
@@ -11050,10 +12033,10 @@ var init_ProviderSelector = __esm({
11050
12033
  const currentIndex = availableProviders.findIndex(
11051
12034
  (p) => p.id === currentProvider
11052
12035
  );
11053
- const [selectedIndex, setSelectedIndex] = useState12(
12036
+ const [selectedIndex, setSelectedIndex] = useState14(
11054
12037
  currentIndex >= 0 ? currentIndex : 0
11055
12038
  );
11056
- useInput7((input, key) => {
12039
+ useInput9((input, key) => {
11057
12040
  if (key.upArrow) {
11058
12041
  setSelectedIndex(
11059
12042
  (prev) => prev > 0 ? prev - 1 : availableProviders.length - 1
@@ -11068,12 +12051,12 @@ var init_ProviderSelector = __esm({
11068
12051
  onCancel();
11069
12052
  }
11070
12053
  });
11071
- 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) => {
11072
12055
  const isSelected = index === selectedIndex;
11073
12056
  const isCurrent = provider.id === currentProvider;
11074
12057
  const indicator = isSelected ? "\u25B6 " : " ";
11075
- return /* @__PURE__ */ React26.createElement(
11076
- Text21,
12058
+ return /* @__PURE__ */ React28.createElement(
12059
+ Text23,
11077
12060
  {
11078
12061
  backgroundColor: isSelected ? theme.text.accent : void 0,
11079
12062
  bold: isSelected,
@@ -11082,17 +12065,17 @@ var init_ProviderSelector = __esm({
11082
12065
  },
11083
12066
  indicator,
11084
12067
  provider.name,
11085
- isCurrent && /* @__PURE__ */ React26.createElement(Text21, { color: isSelected ? "black" : theme.text.success }, " (current)"),
11086
- /* @__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)
11087
12070
  );
11088
- })), /* @__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")));
11089
12072
  };
11090
12073
  }
11091
12074
  });
11092
12075
 
11093
12076
  // src/ui/components/SessionSelector.tsx
11094
- import { Box as Box24, Text as Text22, useInput as useInput8 } from "ink";
11095
- 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";
11096
12079
  function getSessionPrefix(authMethod) {
11097
12080
  return authMethod === "api-key" ? "[Team]" : "[Me]";
11098
12081
  }
@@ -11107,12 +12090,12 @@ var init_SessionSelector = __esm({
11107
12090
  onSelect,
11108
12091
  onCancel
11109
12092
  }) => {
11110
- const [allSessions, setAllSessions] = useState13([]);
11111
- const [selectedIndex, setSelectedIndex] = useState13(0);
11112
- const [isLoading, setIsLoading] = useState13(false);
11113
- const [hasMore, setHasMore] = useState13(true);
11114
- const [totalSessions, setTotalSessions] = useState13(0);
11115
- 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);
11116
12099
  useEffect11(() => {
11117
12100
  loadMoreSessions();
11118
12101
  }, []);
@@ -11139,7 +12122,7 @@ var init_SessionSelector = __esm({
11139
12122
  setIsLoading(false);
11140
12123
  }
11141
12124
  };
11142
- useInput8((input, key) => {
12125
+ useInput10((input, key) => {
11143
12126
  if (allSessions.length === 0) {
11144
12127
  if (key.escape || input === "q") {
11145
12128
  onCancel();
@@ -11163,13 +12146,13 @@ var init_SessionSelector = __esm({
11163
12146
  }
11164
12147
  });
11165
12148
  if (error) {
11166
- 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")));
11167
12150
  }
11168
12151
  if (allSessions.length === 0 && isLoading) {
11169
- 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"));
11170
12153
  }
11171
12154
  if (allSessions.length === 0 && !isLoading) {
11172
- 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")));
11173
12156
  }
11174
12157
  const VISIBLE_ITEMS3 = 10;
11175
12158
  let startIndex;
@@ -11189,7 +12172,7 @@ var init_SessionSelector = __esm({
11189
12172
  const visibleSessions = allSessions.slice(startIndex, endIndex);
11190
12173
  const MAX_TITLE_WIDTH = 50;
11191
12174
  const PREFIX_WIDTH = 6;
11192
- 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) => {
11193
12176
  const actualIndex = startIndex + index;
11194
12177
  const isSelected = actualIndex === selectedIndex;
11195
12178
  const title = item.session.title || "Untitled session";
@@ -11213,8 +12196,8 @@ var init_SessionSelector = __esm({
11213
12196
  const prefixColor = item.prefix === "[Me]" ? "cyan" : "yellow";
11214
12197
  const indicator = isSelected ? "\u25B6 " : " ";
11215
12198
  const bgColor = isSelected ? theme.text.accent : void 0;
11216
- 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, ")"));
11217
- })), /* @__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..."))));
11218
12201
  };
11219
12202
  }
11220
12203
  });
@@ -11264,10 +12247,10 @@ var init_useOverlayEscapeGuard = __esm({
11264
12247
 
11265
12248
  // src/ui/App.tsx
11266
12249
  import { execSync as execSync6 } from "child_process";
11267
- import { homedir as homedir6 } from "os";
11268
- import { Box as Box25, Text as Text23, useApp as useApp2, useStdout as useStdout2 } from "ink";
11269
- import Spinner3 from "ink-spinner";
11270
- 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";
11271
12254
  var getGitBranch2, getCurrentFolder2, AppContent, App;
11272
12255
  var init_App = __esm({
11273
12256
  "src/ui/App.tsx"() {
@@ -11278,6 +12261,7 @@ var init_App = __esm({
11278
12261
  init_prompts();
11279
12262
  init_claude_max();
11280
12263
  init_command_discovery();
12264
+ init_mcp_manager();
11281
12265
  init_settings_loader();
11282
12266
  init_stdio();
11283
12267
  init_token_storage();
@@ -11286,8 +12270,10 @@ var init_App = __esm({
11286
12270
  init_AuthDialog();
11287
12271
  init_FeedbackDialog();
11288
12272
  init_FixFlow();
11289
- init_HelpMenu();
11290
12273
  init_InputPrompt();
12274
+ init_McpAddDialog();
12275
+ init_McpServerSelector();
12276
+ init_McpServersDisplay();
11291
12277
  init_MessageList();
11292
12278
  init_ModelSelector();
11293
12279
  init_ProviderSelector();
@@ -11307,7 +12293,7 @@ var init_App = __esm({
11307
12293
  };
11308
12294
  getCurrentFolder2 = (configCwd) => {
11309
12295
  const cwd = configCwd || process.cwd();
11310
- const home = homedir6();
12296
+ const home = homedir8();
11311
12297
  if (cwd.startsWith(home)) {
11312
12298
  return `~${cwd.slice(home.length)}`;
11313
12299
  }
@@ -11318,27 +12304,43 @@ var init_App = __esm({
11318
12304
  const { stdout } = useStdout2();
11319
12305
  const { addMessage, clearMessages, isAgentRunning, messages, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic, toggleToolGroups, llmProvider, setLlmProvider } = useSession();
11320
12306
  useModeToggle();
11321
- const [terminalWidth, setTerminalWidth] = useState14(process.stdout.columns || 80);
11322
- const [showHelp, setShowHelp] = useState14(false);
11323
- const [showInput, setShowInput] = useState14(true);
11324
- const [gitBranch] = useState14(() => getGitBranch2());
11325
- const [currentFolder] = useState14(() => getCurrentFolder2(config2.cwd));
11326
- const [hasInputContent, setHasInputContent] = useState14(false);
11327
- 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);
11328
12313
  const inputPromptRef = useRef5(null);
11329
- const [showSessionSelector, setShowSessionSelector] = useState14(false);
11330
- const [showModelSelector, setShowModelSelector] = useState14(false);
11331
- const [showProviderSelector, setShowProviderSelector] = useState14(false);
11332
- const [showFeedbackDialog, setShowFeedbackDialog] = useState14(false);
11333
- const [isLoadingSession, setIsLoadingSession] = useState14(false);
11334
- const [showFixFlow, setShowFixFlow] = useState14(false);
11335
- const [fixRunId, setFixRunId] = useState14(void 0);
11336
- 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(
11337
12329
  () => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
11338
12330
  );
11339
- const [showAuthDialog, setShowAuthDialog] = useState14(false);
12331
+ const [showAuthDialog, setShowAuthDialog] = useState16(false);
11340
12332
  const { isOverlayOpen, isCancelSuppressed, markOverlayClosed } = useOverlayEscapeGuard({
11341
- 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
+ ]
11342
12344
  });
11343
12345
  useEffect13(() => {
11344
12346
  if (!config2.supatestApiKey) {
@@ -11396,7 +12398,7 @@ var init_App = __esm({
11396
12398
  return;
11397
12399
  }
11398
12400
  if (command === "/help" || command === "/?") {
11399
- setShowHelp((prev) => !prev);
12401
+ onSubmitTask?.(helpPrompt);
11400
12402
  return;
11401
12403
  }
11402
12404
  if (command === "/login") {
@@ -11525,6 +12527,10 @@ var init_App = __esm({
11525
12527
  onSubmitTask?.(discoverPrompt);
11526
12528
  return;
11527
12529
  }
12530
+ if (command === "/mcp") {
12531
+ setShowMcpServers(true);
12532
+ return;
12533
+ }
11528
12534
  const projectDir = config2.cwd || process.cwd();
11529
12535
  const spaceIndex = trimmedTask.indexOf(" ");
11530
12536
  const commandName = spaceIndex > 0 ? trimmedTask.slice(1, spaceIndex) : trimmedTask.slice(1);
@@ -11677,9 +12683,112 @@ var init_App = __esm({
11677
12683
  setIsAgentRunning(true);
11678
12684
  onSubmitTask?.(prompt);
11679
12685
  };
11680
- const handleHelpClose = () => {
12686
+ const handleMcpServersClose = () => {
11681
12687
  markOverlayClosed();
11682
- 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);
11683
12792
  };
11684
12793
  const isInitialMount = useRef5(true);
11685
12794
  useEffect13(() => {
@@ -11743,9 +12852,6 @@ var init_App = __esm({
11743
12852
  exit();
11744
12853
  onExit(true);
11745
12854
  }
11746
- if (key.ctrl && key.name === "h" && !showHelp) {
11747
- setShowHelp(true);
11748
- }
11749
12855
  if (key.ctrl && key.name === "l") {
11750
12856
  clearTerminalViewportAndScrollback();
11751
12857
  }
@@ -11763,21 +12869,14 @@ var init_App = __esm({
11763
12869
  });
11764
12870
  }
11765
12871
  }, []);
11766
- return /* @__PURE__ */ React28.createElement(
11767
- Box25,
12872
+ return /* @__PURE__ */ React30.createElement(
12873
+ Box27,
11768
12874
  {
11769
12875
  flexDirection: "column",
11770
12876
  paddingX: 1
11771
12877
  },
11772
- /* @__PURE__ */ React28.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
11773
- showHelp && /* @__PURE__ */ React28.createElement(
11774
- HelpMenu,
11775
- {
11776
- isAuthenticated: authState === "authenticated" /* Authenticated */,
11777
- onClose: handleHelpClose
11778
- }
11779
- ),
11780
- showSessionSelector && apiClient && /* @__PURE__ */ React28.createElement(
12878
+ /* @__PURE__ */ React30.createElement(MessageList, { currentFolder, gitBranch, queuedTasks, terminalWidth }),
12879
+ showSessionSelector && apiClient && /* @__PURE__ */ React30.createElement(
11781
12880
  SessionSelector,
11782
12881
  {
11783
12882
  apiClient,
@@ -11785,8 +12884,8 @@ var init_App = __esm({
11785
12884
  onSelect: handleSessionSelect
11786
12885
  }
11787
12886
  ),
11788
- 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")),
11789
- 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(
11790
12889
  ModelSelector,
11791
12890
  {
11792
12891
  currentModel: selectedModel,
@@ -11795,7 +12894,7 @@ var init_App = __esm({
11795
12894
  onSelect: handleModelSelect
11796
12895
  }
11797
12896
  ),
11798
- showProviderSelector && /* @__PURE__ */ React28.createElement(
12897
+ showProviderSelector && /* @__PURE__ */ React30.createElement(
11799
12898
  ProviderSelector,
11800
12899
  {
11801
12900
  claudeMaxAvailable: isClaudeMaxAvailable(),
@@ -11804,20 +12903,20 @@ var init_App = __esm({
11804
12903
  onSelect: handleProviderSelect
11805
12904
  }
11806
12905
  ),
11807
- showAuthDialog && /* @__PURE__ */ React28.createElement(
12906
+ showAuthDialog && /* @__PURE__ */ React30.createElement(
11808
12907
  AuthDialog,
11809
12908
  {
11810
12909
  onLogin: handleLogin
11811
12910
  }
11812
12911
  ),
11813
- showFeedbackDialog && /* @__PURE__ */ React28.createElement(
12912
+ showFeedbackDialog && /* @__PURE__ */ React30.createElement(
11814
12913
  FeedbackDialog,
11815
12914
  {
11816
12915
  onCancel: handleFeedbackCancel,
11817
12916
  onSubmit: handleFeedbackSubmit
11818
12917
  }
11819
12918
  ),
11820
- showFixFlow && apiClient && /* @__PURE__ */ React28.createElement(
12919
+ showFixFlow && apiClient && /* @__PURE__ */ React30.createElement(
11821
12920
  FixFlow,
11822
12921
  {
11823
12922
  apiClient,
@@ -11827,14 +12926,33 @@ var init_App = __esm({
11827
12926
  onStartFix: handleFixStart
11828
12927
  }
11829
12928
  ),
11830
- /* @__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(
11831
12950
  InputPrompt,
11832
12951
  {
11833
12952
  currentFolder,
11834
12953
  cwd: config2.cwd,
11835
12954
  gitBranch,
11836
12955
  isClaudeMax: !!config2.oauthToken,
11837
- onHelpToggle: () => setShowHelp((prev) => !prev),
11838
12956
  onInputChange: (val) => setHasInputContent(val.trim().length > 0),
11839
12957
  onSubmit: handleSubmitTask,
11840
12958
  placeholder: "Enter your task...",
@@ -11844,7 +12962,7 @@ var init_App = __esm({
11844
12962
  );
11845
12963
  };
11846
12964
  App = (props) => {
11847
- return /* @__PURE__ */ React28.createElement(AppContent, { ...props });
12965
+ return /* @__PURE__ */ React30.createElement(AppContent, { ...props });
11848
12966
  };
11849
12967
  }
11850
12968
  });
@@ -11880,7 +12998,7 @@ __export(interactive_exports, {
11880
12998
  runInteractive: () => runInteractive
11881
12999
  });
11882
13000
  import { render as render2 } from "ink";
11883
- import React29, { useEffect as useEffect15, useRef as useRef6 } from "react";
13001
+ import React31, { useEffect as useEffect15, useRef as useRef6 } from "react";
11884
13002
  function getToolDescription2(toolName, input) {
11885
13003
  switch (toolName) {
11886
13004
  case "Read":
@@ -12013,7 +13131,7 @@ async function runInteractive(config2) {
12013
13131
  webUrl = session.webUrl;
12014
13132
  }
12015
13133
  const { unmount, waitUntilExit } = render2(
12016
- /* @__PURE__ */ React29.createElement(
13134
+ /* @__PURE__ */ React31.createElement(
12017
13135
  InteractiveApp,
12018
13136
  {
12019
13137
  apiClient,
@@ -12205,17 +13323,17 @@ var init_interactive = __esm({
12205
13323
  setIsAgentRunning,
12206
13324
  setUsageStats
12207
13325
  } = useSession();
12208
- const [sessionId, setSessionId] = React29.useState(initialSessionId);
12209
- const [currentTask, setCurrentTask] = React29.useState(config2.task);
12210
- const [taskId, setTaskId] = React29.useState(0);
12211
- const [shouldRunAgent, setShouldRunAgent] = React29.useState(!!config2.task);
12212
- const [taskQueue, setTaskQueue] = React29.useState([]);
12213
- const [providerSessionId, setProviderSessionId] = React29.useState();
12214
- const messageBridgeRef = React29.useRef(null);
12215
- const lastSubmitRef = React29.useRef(null);
12216
- const [pendingInjected, setPendingInjected] = React29.useState([]);
12217
- const pendingInjectedRef = React29.useRef([]);
12218
- 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(() => {
12219
13337
  pendingInjectedRef.current = pendingInjected;
12220
13338
  }, [pendingInjected]);
12221
13339
  const handleSubmitTask = async (task) => {
@@ -12278,7 +13396,7 @@ var init_interactive = __esm({
12278
13396
  if (shouldRunAgent && !messageBridgeRef.current) {
12279
13397
  messageBridgeRef.current = new MessageBridge(providerSessionId || "");
12280
13398
  }
12281
- React29.useEffect(() => {
13399
+ React31.useEffect(() => {
12282
13400
  if (!shouldRunAgent && taskQueue.length > 0) {
12283
13401
  const [nextTask, ...remaining] = taskQueue;
12284
13402
  setTaskQueue(remaining);
@@ -12292,14 +13410,14 @@ var init_interactive = __esm({
12292
13410
  setShouldRunAgent(true);
12293
13411
  }
12294
13412
  }, [shouldRunAgent, taskQueue, addMessage, providerSessionId]);
12295
- const handleClearSession = React29.useCallback(() => {
13413
+ const handleClearSession = React31.useCallback(() => {
12296
13414
  setSessionId(void 0);
12297
13415
  setContextSessionId(void 0);
12298
13416
  setProviderSessionId(void 0);
12299
13417
  setTaskQueue([]);
12300
13418
  setPendingInjected([]);
12301
13419
  }, [setContextSessionId]);
12302
- return /* @__PURE__ */ React29.createElement(React29.Fragment, null, /* @__PURE__ */ React29.createElement(
13420
+ return /* @__PURE__ */ React31.createElement(React31.Fragment, null, /* @__PURE__ */ React31.createElement(
12303
13421
  App,
12304
13422
  {
12305
13423
  apiClient,
@@ -12362,7 +13480,7 @@ var init_interactive = __esm({
12362
13480
  sessionId,
12363
13481
  webUrl
12364
13482
  }
12365
- ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React29.createElement(
13483
+ ), shouldRunAgent && currentTask && sessionId && messageBridgeRef.current && /* @__PURE__ */ React31.createElement(
12366
13484
  AgentRunner,
12367
13485
  {
12368
13486
  apiClient,
@@ -12387,7 +13505,7 @@ var init_interactive = __esm({
12387
13505
  useBracketedPaste();
12388
13506
  const settings = loadSupatestSettings(props.config.cwd || process.cwd());
12389
13507
  const initialProvider = settings.llmProvider || "supatest-managed";
12390
- 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 })));
12391
13509
  };
12392
13510
  }
12393
13511
  });
@@ -12411,7 +13529,7 @@ init_react();
12411
13529
  init_MessageList();
12412
13530
  init_SessionContext();
12413
13531
  import { execSync as execSync2 } from "child_process";
12414
- import { homedir as homedir3 } from "os";
13532
+ import { homedir as homedir4 } from "os";
12415
13533
  import { Box as Box13, useApp } from "ink";
12416
13534
  import React14, { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
12417
13535
  var getGitBranch = () => {
@@ -12423,7 +13541,7 @@ var getGitBranch = () => {
12423
13541
  };
12424
13542
  var getCurrentFolder = () => {
12425
13543
  const cwd = process.cwd();
12426
- const home = homedir3();
13544
+ const home = homedir4();
12427
13545
  if (cwd.startsWith(home)) {
12428
13546
  return `~${cwd.slice(home.length)}`;
12429
13547
  }
@@ -12867,7 +13985,7 @@ program.name("supatest").description(
12867
13985
  if (options.verbose) {
12868
13986
  logger.setVerbose(true);
12869
13987
  }
12870
- const isDev = process.env.NODE_ENV === "development";
13988
+ const isDev = process.env.NODE_ENV === "development" || process.env.SUPATEST_DEV === "true";
12871
13989
  logger.enableFileLogging(isDev);
12872
13990
  let prompt = task;
12873
13991
  let logs;