@supatest/cli 0.0.21 → 0.0.23

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 (2) hide show
  1. package/dist/index.js +206 -86
  2. package/package.json +6 -2
package/dist/index.js CHANGED
@@ -3817,6 +3817,34 @@ function installChromium() {
3817
3817
  };
3818
3818
  }
3819
3819
  }
3820
+ function createSupatestConfig(cwd) {
3821
+ const supatestDir = path.join(cwd, ".supatest");
3822
+ const mcpJsonPath = path.join(supatestDir, "mcp.json");
3823
+ try {
3824
+ if (fs.existsSync(mcpJsonPath)) {
3825
+ return {
3826
+ ok: true,
3827
+ message: "mcp.json already exists",
3828
+ created: false
3829
+ };
3830
+ }
3831
+ if (!fs.existsSync(supatestDir)) {
3832
+ fs.mkdirSync(supatestDir, { recursive: true });
3833
+ }
3834
+ fs.writeFileSync(mcpJsonPath, JSON.stringify(DEFAULT_MCP_CONFIG, null, 2) + "\n", "utf-8");
3835
+ return {
3836
+ ok: true,
3837
+ message: "Created .supatest/mcp.json with Playwright MCP server configuration",
3838
+ created: true
3839
+ };
3840
+ } catch (error) {
3841
+ return {
3842
+ ok: false,
3843
+ message: `Failed to create mcp.json: ${error instanceof Error ? error.message : String(error)}`,
3844
+ created: false
3845
+ };
3846
+ }
3847
+ }
3820
3848
  function getVersionSummary() {
3821
3849
  const nodeVersion = getNodeVersion();
3822
3850
  const playwrightVersion = getPlaywrightVersion();
@@ -3828,7 +3856,7 @@ function getVersionSummary() {
3828
3856
  lines.push(` Chromium: ${chromiumVersion ? `build ${chromiumVersion}` : "Not installed"}`);
3829
3857
  return lines.join("\n");
3830
3858
  }
3831
- async function setupCommand() {
3859
+ async function setupCommand(options) {
3832
3860
  const output = [];
3833
3861
  const log = (msg) => {
3834
3862
  output.push(msg);
@@ -3876,6 +3904,18 @@ async function setupCommand() {
3876
3904
  result.errors.push(chromiumResult.message);
3877
3905
  }
3878
3906
  }
3907
+ log("\n3. Setting up MCP configuration...");
3908
+ const configResult = createSupatestConfig(options.cwd);
3909
+ if (configResult.ok) {
3910
+ if (configResult.created) {
3911
+ log(` \u2705 ${configResult.message}`);
3912
+ } else {
3913
+ log(` \u2705 ${configResult.message}`);
3914
+ }
3915
+ } else {
3916
+ log(` \u274C ${configResult.message}`);
3917
+ result.errors.push(configResult.message);
3918
+ }
3879
3919
  const versionSummary = getVersionSummary();
3880
3920
  log(versionSummary);
3881
3921
  log("\n" + "\u2500".repeat(50));
@@ -3891,11 +3931,19 @@ async function setupCommand() {
3891
3931
  result.output = output.join("\n");
3892
3932
  return result;
3893
3933
  }
3894
- var MINIMUM_NODE_VERSION;
3934
+ var MINIMUM_NODE_VERSION, DEFAULT_MCP_CONFIG;
3895
3935
  var init_setup = __esm({
3896
3936
  "src/commands/setup.ts"() {
3897
3937
  "use strict";
3898
3938
  MINIMUM_NODE_VERSION = 18;
3939
+ DEFAULT_MCP_CONFIG = {
3940
+ mcpServers: {
3941
+ playwright: {
3942
+ command: "npx",
3943
+ args: ["@playwright/mcp@latest"]
3944
+ }
3945
+ }
3946
+ };
3899
3947
  }
3900
3948
  });
3901
3949
 
@@ -3904,7 +3952,7 @@ var CLI_VERSION;
3904
3952
  var init_version = __esm({
3905
3953
  "src/version.ts"() {
3906
3954
  "use strict";
3907
- CLI_VERSION = "0.0.21";
3955
+ CLI_VERSION = "0.0.23";
3908
3956
  }
3909
3957
  });
3910
3958
 
@@ -4195,12 +4243,24 @@ ${"=".repeat(80)}
4195
4243
  });
4196
4244
 
4197
4245
  // src/services/api-client.ts
4198
- var ApiError, ApiClient;
4246
+ import { Agent, setGlobalDispatcher } from "undici";
4247
+ var agent, ApiError, ApiClient;
4199
4248
  var init_api_client = __esm({
4200
4249
  "src/services/api-client.ts"() {
4201
4250
  "use strict";
4202
4251
  init_error_logger();
4203
4252
  init_logger();
4253
+ agent = new Agent({
4254
+ keepAliveTimeout: 3e4,
4255
+ // Keep connections alive for 30s
4256
+ keepAliveMaxTimeout: 6e4,
4257
+ // Max time to keep connection alive
4258
+ connections: 10,
4259
+ // Max connections per origin
4260
+ pipelining: 1
4261
+ // Enable HTTP pipelining
4262
+ });
4263
+ setGlobalDispatcher(agent);
4204
4264
  ApiError = class extends Error {
4205
4265
  status;
4206
4266
  isAuthError;
@@ -4729,7 +4789,7 @@ var init_agent = __esm({
4729
4789
  }
4730
4790
  async run(config2) {
4731
4791
  this.abortController = new AbortController();
4732
- await this.presenter.onStart(config2);
4792
+ this.presenter.onStart(config2);
4733
4793
  const claudeCodePath = await this.resolveClaudeCodePath();
4734
4794
  let prompt = config2.task;
4735
4795
  if (config2.logs) {
@@ -4746,9 +4806,9 @@ ${config2.logs}
4746
4806
  const customAgents = discoverAgents(cwd);
4747
4807
  let customAgentsPrompt;
4748
4808
  if (customAgents.length > 0) {
4749
- const agentList = customAgents.map((agent) => {
4750
- const modelInfo = agent.model ? ` (model: ${agent.model})` : "";
4751
- return `- **${agent.name}**${modelInfo}: ${agent.description || "No description"}`;
4809
+ const agentList = customAgents.map((agent2) => {
4810
+ const modelInfo = agent2.model ? ` (model: ${agent2.model})` : "";
4811
+ return `- **${agent2.name}**${modelInfo}: ${agent2.description || "No description"}`;
4752
4812
  }).join("\n");
4753
4813
  customAgentsPrompt = `
4754
4814
 
@@ -4798,21 +4858,14 @@ ${projectInstructions}`,
4798
4858
  pathToClaudeCodeExecutable: claudeCodePath,
4799
4859
  includePartialMessages: true,
4800
4860
  executable: "node",
4801
- // MCP servers for enhanced capabilities
4802
- // User-defined servers from .supatest/mcp.json can override defaults
4861
+ // MCP servers from .supatest/mcp.json
4862
+ // Users can add servers like Playwright if needed
4803
4863
  mcpServers: (() => {
4804
- const userServers = loadMcpServers(cwd);
4805
- const allServers = {
4806
- // Default Playwright MCP server for browser automation
4807
- playwright: {
4808
- command: "npx",
4809
- args: ["-y", "@playwright/mcp@latest"]
4810
- },
4811
- // User-defined servers override defaults (spread after)
4812
- ...userServers
4813
- };
4814
- this.presenter.onLog(`MCP servers loaded: ${Object.keys(allServers).join(", ")}`);
4815
- return allServers;
4864
+ const servers = loadMcpServers(cwd);
4865
+ if (Object.keys(servers).length > 0) {
4866
+ this.presenter.onLog(`MCP servers: ${Object.keys(servers).join(", ")}`);
4867
+ }
4868
+ return servers;
4816
4869
  })(),
4817
4870
  // Resume from previous session if providerSessionId is provided
4818
4871
  // This allows the agent to continue conversations with full context
@@ -4882,16 +4935,16 @@ ${projectInstructions}`,
4882
4935
  for (const block of content) {
4883
4936
  if (block.type === "text") {
4884
4937
  resultText += block.text + "\n";
4885
- await this.presenter.onAssistantText(block.text);
4938
+ this.presenter.onAssistantText(block.text);
4886
4939
  } else if (block.type === "thinking") {
4887
- await this.presenter.onThinking(block.thinking);
4940
+ this.presenter.onThinking(block.thinking);
4888
4941
  } else if (block.type === "tool_use") {
4889
4942
  const toolName = block.name;
4890
4943
  const input = block.input;
4891
4944
  if ((toolName === "Write" || toolName === "Edit") && input?.file_path) {
4892
4945
  filesModified.add(input.file_path);
4893
4946
  }
4894
- await this.presenter.onToolUse(toolName, input, block.id);
4947
+ this.presenter.onToolUse(toolName, input, block.id);
4895
4948
  }
4896
4949
  }
4897
4950
  }
@@ -4909,7 +4962,7 @@ ${projectInstructions}`,
4909
4962
  cacheReadTokens: cumulativeCacheReadTokens,
4910
4963
  cacheCreationTokens: cumulativeCacheCreationTokens
4911
4964
  };
4912
- await this.presenter.onUsageUpdate?.({
4965
+ this.presenter.onUsageUpdate?.({
4913
4966
  model: currentModel,
4914
4967
  inputTokens: cumulativeInputTokens,
4915
4968
  outputTokens: cumulativeOutputTokens,
@@ -4917,7 +4970,7 @@ ${projectInstructions}`,
4917
4970
  cacheCreationTokens: cumulativeCacheCreationTokens
4918
4971
  });
4919
4972
  }
4920
- await this.presenter.onTurnComplete(content);
4973
+ this.presenter.onTurnComplete(content);
4921
4974
  } else if (msg.type === "result") {
4922
4975
  iterations = msg.num_turns;
4923
4976
  const modelName = Object.keys(msg.modelUsage || {})[0] || "unknown";
@@ -4954,7 +5007,7 @@ ${projectInstructions}`,
4954
5007
  if ("errors" in msg && Array.isArray(msg.errors)) {
4955
5008
  errors.push(...msg.errors);
4956
5009
  for (const error of msg.errors) {
4957
- await this.presenter.onError(error);
5010
+ this.presenter.onError(error);
4958
5011
  }
4959
5012
  }
4960
5013
  }
@@ -4965,7 +5018,7 @@ ${projectInstructions}`,
4965
5018
  if (block.type === "tool_result" && block.tool_use_id) {
4966
5019
  if (this.presenter.onToolResult) {
4967
5020
  const resultContent = Array.isArray(block.content) ? block.content.map((c2) => c2.text || "").join("\n") : typeof block.content === "string" ? block.content : "";
4968
- await this.presenter.onToolResult(block.tool_use_id, resultContent);
5021
+ this.presenter.onToolResult(block.tool_use_id, resultContent);
4969
5022
  }
4970
5023
  }
4971
5024
  }
@@ -4982,7 +5035,7 @@ ${projectInstructions}`,
4982
5035
  wasInterrupted = true;
4983
5036
  } else if (config2.providerSessionId && isSessionExpiredError(errorMessage)) {
4984
5037
  const expiredMessage = "Can't continue conversation older than 30 days. Please start a new session.";
4985
- await this.presenter.onError(expiredMessage);
5038
+ this.presenter.onError(expiredMessage);
4986
5039
  hasError = true;
4987
5040
  errors.push(expiredMessage);
4988
5041
  } else {
@@ -4993,7 +5046,7 @@ ${projectInstructions}`,
4993
5046
  // Truncate for log size
4994
5047
  iteration: iterations
4995
5048
  });
4996
- await this.presenter.onError(errorMessage);
5049
+ this.presenter.onError(errorMessage);
4997
5050
  hasError = true;
4998
5051
  errors.push(errorMessage);
4999
5052
  }
@@ -5010,7 +5063,7 @@ ${projectInstructions}`,
5010
5063
  providerSessionId,
5011
5064
  usage: queryUsage
5012
5065
  };
5013
- await this.presenter.onComplete(result);
5066
+ this.presenter.onComplete(result);
5014
5067
  return result;
5015
5068
  }
5016
5069
  async resolveClaudeCodePath() {
@@ -5039,7 +5092,7 @@ ${projectInstructions}`,
5039
5092
  } catch {
5040
5093
  const error = `Claude Code executable not found at: ${claudeCodePath}
5041
5094
  Set SUPATEST_CLAUDE_CODE_PATH environment variable to override.`;
5042
- await this.presenter.onError(error);
5095
+ this.presenter.onError(error);
5043
5096
  throw new Error(error);
5044
5097
  }
5045
5098
  return claudeCodePath;
@@ -5079,6 +5132,10 @@ function getToolDescription(toolName, input) {
5079
5132
  return toolName;
5080
5133
  }
5081
5134
  }
5135
+ function streamEventAsync(apiClient, sessionId, event) {
5136
+ apiClient.streamEvent(sessionId, event).catch(() => {
5137
+ });
5138
+ }
5082
5139
  var CONTEXT_WINDOWS, ReactPresenter;
5083
5140
  var init_react = __esm({
5084
5141
  "src/presenters/react.ts"() {
@@ -5109,19 +5166,18 @@ var init_react = __esm({
5109
5166
  this.sessionId = sessionId;
5110
5167
  this.verbose = verbose;
5111
5168
  }
5112
- async onStart(config2) {
5113
- const event = {
5169
+ onStart(config2) {
5170
+ streamEventAsync(this.apiClient, this.sessionId, {
5114
5171
  type: "query_start",
5115
5172
  prompt: config2.task
5116
- };
5117
- await this.apiClient.streamEvent(this.sessionId, event);
5173
+ });
5118
5174
  }
5119
5175
  onLog(message) {
5120
5176
  if (this.verbose) {
5121
5177
  console.error(message);
5122
5178
  }
5123
5179
  }
5124
- async onAssistantText(text) {
5180
+ onAssistantText(text) {
5125
5181
  if (!this.hasAssistantMessage) {
5126
5182
  this.callbacks.addMessage({
5127
5183
  type: "assistant",
@@ -5136,13 +5192,12 @@ var init_react = __esm({
5136
5192
  content: this.currentAssistantText
5137
5193
  });
5138
5194
  }
5139
- const textEvent = {
5195
+ streamEventAsync(this.apiClient, this.sessionId, {
5140
5196
  type: "assistant_text",
5141
5197
  delta: text
5142
- };
5143
- await this.apiClient.streamEvent(this.sessionId, textEvent);
5198
+ });
5144
5199
  }
5145
- async onThinking(text) {
5200
+ onThinking(text) {
5146
5201
  if (!this.hasThinkingMessage) {
5147
5202
  this.callbacks.addMessage({
5148
5203
  type: "thinking",
@@ -5157,13 +5212,12 @@ var init_react = __esm({
5157
5212
  content: this.currentThinkingText
5158
5213
  });
5159
5214
  }
5160
- const thinkingEvent = {
5215
+ streamEventAsync(this.apiClient, this.sessionId, {
5161
5216
  type: "assistant_thinking",
5162
5217
  delta: text
5163
- };
5164
- await this.apiClient.streamEvent(this.sessionId, thinkingEvent);
5218
+ });
5165
5219
  }
5166
- async onToolUse(tool, input, toolId) {
5220
+ onToolUse(tool, input, toolId) {
5167
5221
  const message = {
5168
5222
  type: "tool",
5169
5223
  content: getToolDescription(tool, input),
@@ -5207,48 +5261,45 @@ var init_react = __esm({
5207
5261
  } else if (tool === "EnterPlanMode") {
5208
5262
  this.callbacks.onEnterPlanMode?.();
5209
5263
  }
5210
- const toolUseEvent = {
5264
+ streamEventAsync(this.apiClient, this.sessionId, {
5211
5265
  type: "tool_use",
5212
5266
  id: toolId,
5213
5267
  name: tool,
5214
5268
  input: input || {}
5215
- };
5216
- await this.apiClient.streamEvent(this.sessionId, toolUseEvent);
5269
+ });
5217
5270
  }
5218
- async onToolResult(toolId, result) {
5271
+ onToolResult(toolId, result) {
5219
5272
  this.callbacks.updateMessageByToolId(toolId, {
5220
5273
  toolResult: result
5221
5274
  });
5222
- const toolResultEvent = {
5275
+ streamEventAsync(this.apiClient, this.sessionId, {
5223
5276
  type: "tool_result",
5224
5277
  tool_use_id: toolId,
5225
5278
  content: result
5226
- };
5227
- await this.apiClient.streamEvent(this.sessionId, toolResultEvent);
5279
+ });
5228
5280
  }
5229
- async onTurnComplete(content) {
5230
- const messageCompleteEvent = {
5281
+ onTurnComplete(content) {
5282
+ streamEventAsync(this.apiClient, this.sessionId, {
5231
5283
  type: "message_complete",
5232
5284
  message: {
5233
5285
  role: "assistant",
5234
5286
  content
5235
5287
  }
5236
- };
5237
- await this.apiClient.streamEvent(this.sessionId, messageCompleteEvent);
5288
+ });
5238
5289
  this.callbacks.onTurnComplete?.();
5239
5290
  }
5240
- async onError(error) {
5291
+ onError(error) {
5241
5292
  this.callbacks.addMessage({
5242
5293
  type: "error",
5243
5294
  content: error,
5244
5295
  errorType: "error"
5245
5296
  });
5246
- await this.apiClient.streamEvent(this.sessionId, {
5297
+ streamEventAsync(this.apiClient, this.sessionId, {
5247
5298
  type: "session_error",
5248
5299
  error
5249
5300
  });
5250
5301
  }
5251
- async onUsageUpdate(usage) {
5302
+ onUsageUpdate(usage) {
5252
5303
  const contextWindow = CONTEXT_WINDOWS[usage.model] || 2e5;
5253
5304
  const contextTokens = usage.inputTokens + usage.cacheReadTokens + usage.cacheCreationTokens;
5254
5305
  const contextPct = contextTokens / contextWindow * 100;
@@ -5269,10 +5320,10 @@ var init_react = __esm({
5269
5320
  };
5270
5321
  });
5271
5322
  }
5272
- async onComplete(result) {
5323
+ onComplete(result) {
5273
5324
  if (result.usage) {
5274
5325
  const queryStatus = result.success ? "success" : result.error?.toLowerCase().includes("interrupt") ? "interrupted" : "error";
5275
- await this.apiClient.streamEvent(this.sessionId, {
5326
+ streamEventAsync(this.apiClient, this.sessionId, {
5276
5327
  type: "query_complete",
5277
5328
  usage: result.usage,
5278
5329
  result: {
@@ -5283,11 +5334,11 @@ var init_react = __esm({
5283
5334
  });
5284
5335
  }
5285
5336
  if (result.success) {
5286
- await this.apiClient.streamEvent(this.sessionId, {
5337
+ streamEventAsync(this.apiClient, this.sessionId, {
5287
5338
  type: "session_complete"
5288
5339
  });
5289
5340
  } else {
5290
- await this.apiClient.streamEvent(this.sessionId, {
5341
+ streamEventAsync(this.apiClient, this.sessionId, {
5291
5342
  type: "session_error",
5292
5343
  error: result.error || "Unknown error"
5293
5344
  });
@@ -5412,6 +5463,9 @@ var init_SessionContext = __esm({
5412
5463
  });
5413
5464
  setStaticRemountKey((prev) => prev + 1);
5414
5465
  }, []);
5466
+ const refreshStatic = useCallback(() => {
5467
+ setStaticRemountKey((prev) => prev + 1);
5468
+ }, []);
5415
5469
  const updateStats = useCallback((updates) => {
5416
5470
  setStats((prev) => ({ ...prev, ...updates }));
5417
5471
  }, []);
@@ -5457,7 +5511,8 @@ var init_SessionContext = __esm({
5457
5511
  setPlanFilePath,
5458
5512
  selectedModel,
5459
5513
  setSelectedModel,
5460
- staticRemountKey
5514
+ staticRemountKey,
5515
+ refreshStatic
5461
5516
  };
5462
5517
  return /* @__PURE__ */ React.createElement(SessionContext.Provider, { value }, children);
5463
5518
  };
@@ -6731,7 +6786,7 @@ var init_message_bridge = __esm({
6731
6786
  });
6732
6787
 
6733
6788
  // src/commands/login.ts
6734
- import { spawn } from "child_process";
6789
+ import { spawn as spawn2 } from "child_process";
6735
6790
  import crypto2 from "crypto";
6736
6791
  import http from "http";
6737
6792
  import { platform } from "os";
@@ -6767,7 +6822,7 @@ function openBrowser(url) {
6767
6822
  default:
6768
6823
  command = "xdg-open";
6769
6824
  }
6770
- spawn(command, [url], { detached: true, stdio: "ignore" }).unref();
6825
+ spawn2(command, [url], { detached: true, stdio: "ignore" }).unref();
6771
6826
  }
6772
6827
  function startCallbackServer(port, expectedState) {
6773
6828
  return new Promise((resolve2, reject) => {
@@ -8678,9 +8733,9 @@ var init_useOverlayEscapeGuard = __esm({
8678
8733
  });
8679
8734
 
8680
8735
  // src/ui/App.tsx
8681
- import { execSync as execSync4 } from "child_process";
8736
+ import { execSync as execSync5 } from "child_process";
8682
8737
  import { homedir as homedir5 } from "os";
8683
- import { Box as Box20, Text as Text18, useApp as useApp2 } from "ink";
8738
+ import { Box as Box20, Text as Text18, useApp as useApp2, useStdout as useStdout2 } from "ink";
8684
8739
  import Spinner3 from "ink-spinner";
8685
8740
  import React23, { useEffect as useEffect10, useRef as useRef4, useState as useState10 } from "react";
8686
8741
  var getGitBranch2, getCurrentFolder2, AppContent, App;
@@ -8710,7 +8765,7 @@ var init_App = __esm({
8710
8765
  init_theme();
8711
8766
  getGitBranch2 = () => {
8712
8767
  try {
8713
- return execSync4("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
8768
+ return execSync5("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
8714
8769
  } catch {
8715
8770
  return "";
8716
8771
  }
@@ -8725,7 +8780,8 @@ var init_App = __esm({
8725
8780
  };
8726
8781
  AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession }) => {
8727
8782
  const { exit } = useApp2();
8728
- const { addMessage, clearMessages, isAgentRunning, messages, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel } = useSession();
8783
+ const { stdout } = useStdout2();
8784
+ const { addMessage, clearMessages, isAgentRunning, messages, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel, refreshStatic } = useSession();
8729
8785
  useModeToggle();
8730
8786
  const [terminalWidth, setTerminalWidth] = useState10(process.stdout.columns || 80);
8731
8787
  const [showHelp, setShowHelp] = useState10(false);
@@ -8878,7 +8934,7 @@ var init_App = __esm({
8878
8934
  content: "Running setup..."
8879
8935
  });
8880
8936
  try {
8881
- const result = await setupCommand();
8937
+ const result = await setupCommand({ cwd: config2.cwd || process.cwd() });
8882
8938
  addMessage({
8883
8939
  type: "assistant",
8884
8940
  content: result.output
@@ -9002,6 +9058,7 @@ var init_App = __esm({
9002
9058
  markOverlayClosed();
9003
9059
  setShowHelp(false);
9004
9060
  };
9061
+ const isInitialMount = useRef4(true);
9005
9062
  useEffect10(() => {
9006
9063
  const handleResize = () => {
9007
9064
  setTerminalWidth(process.stdout.columns || 80);
@@ -9011,6 +9068,19 @@ var init_App = __esm({
9011
9068
  process.stdout.off("resize", handleResize);
9012
9069
  };
9013
9070
  }, []);
9071
+ useEffect10(() => {
9072
+ if (isInitialMount.current) {
9073
+ isInitialMount.current = false;
9074
+ return;
9075
+ }
9076
+ const handler = setTimeout(() => {
9077
+ stdout?.write("\x1B[3J\x1B[H\x1B[2J");
9078
+ refreshStatic();
9079
+ }, 300);
9080
+ return () => {
9081
+ clearTimeout(handler);
9082
+ };
9083
+ }, [terminalWidth, refreshStatic, stdout]);
9014
9084
  useKeypress(
9015
9085
  (key) => {
9016
9086
  if (key.name === "escape" && isAgentRunning && !isOverlayOpen) {
@@ -9436,9 +9506,9 @@ var init_interactive = __esm({
9436
9506
  selectedModel,
9437
9507
  systemPromptAppend: agentMode === "plan" ? config.planSystemPrompt : config2.systemPromptAppend
9438
9508
  };
9439
- const agent = new CoreAgent(presenter, messageBridge);
9440
- agentRef.current = agent;
9441
- const result = await agent.run(runConfig);
9509
+ const agent2 = new CoreAgent(presenter, messageBridge);
9510
+ agentRef.current = agent2;
9511
+ const result = await agent2.run(runConfig);
9442
9512
  if (isMounted) {
9443
9513
  onComplete(result.success, result.providerSessionId);
9444
9514
  }
@@ -9752,9 +9822,9 @@ var HeadlessAgentRunner = ({ config: config2, sessionId, apiClient, onComplete }
9752
9822
  sessionId,
9753
9823
  config2.verbose
9754
9824
  );
9755
- const agent = new CoreAgent(presenter);
9756
- agentRef.current = agent;
9757
- const result = await agent.run(config2);
9825
+ const agent2 = new CoreAgent(presenter);
9826
+ agentRef.current = agent2;
9827
+ const result = await agent2.run(config2);
9758
9828
  if (isMounted) {
9759
9829
  onComplete(result.success, result.providerSessionId);
9760
9830
  }
@@ -9825,10 +9895,8 @@ var HeadlessAppContent = ({
9825
9895
  const handleComplete = (success, providerSessionId) => {
9826
9896
  if (hasCompletedRef.current) return;
9827
9897
  hasCompletedRef.current = true;
9828
- setTimeout(() => {
9829
- onComplete(success, providerSessionId);
9830
- exit();
9831
- }, 100);
9898
+ onComplete(success, providerSessionId);
9899
+ exit();
9832
9900
  };
9833
9901
  return /* @__PURE__ */ React13.createElement(Box12, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React13.createElement(
9834
9902
  MessageList,
@@ -9940,6 +10008,57 @@ async function runAgent(config2) {
9940
10008
  });
9941
10009
  }
9942
10010
 
10011
+ // src/utils/auto-update.ts
10012
+ init_error_logger();
10013
+ init_version();
10014
+ import { execSync as execSync3, spawn } from "child_process";
10015
+ import latestVersion from "latest-version";
10016
+ import { gt as gt2 } from "semver";
10017
+ var UPDATE_CHECK_TIMEOUT = 3e3;
10018
+ var INSTALL_TIMEOUT = 6e4;
10019
+ async function checkAndAutoUpdate() {
10020
+ if (process.env.NODE_ENV === "development") {
10021
+ return;
10022
+ }
10023
+ if (process.env.SUPATEST_SKIP_UPDATE === "1") {
10024
+ return;
10025
+ }
10026
+ try {
10027
+ const latest = await Promise.race([
10028
+ latestVersion("@supatest/cli"),
10029
+ new Promise(
10030
+ (_2, reject) => setTimeout(() => reject(new Error("timeout")), UPDATE_CHECK_TIMEOUT)
10031
+ )
10032
+ ]);
10033
+ if (!gt2(latest, CLI_VERSION)) {
10034
+ return;
10035
+ }
10036
+ console.log(`
10037
+ Updating Supatest CLI ${CLI_VERSION} \u2192 ${latest}...`);
10038
+ try {
10039
+ execSync3("npm install -g @supatest/cli@latest", {
10040
+ stdio: "inherit",
10041
+ timeout: INSTALL_TIMEOUT
10042
+ });
10043
+ console.log("\u2713 Updated successfully\n");
10044
+ const child = spawn(process.argv[0], process.argv.slice(1), {
10045
+ stdio: "inherit",
10046
+ detached: false
10047
+ });
10048
+ child.on("exit", (code) => process.exit(code ?? 0));
10049
+ await new Promise(() => {
10050
+ });
10051
+ } catch (installError) {
10052
+ logError(installError, { source: "auto-update", action: "install" });
10053
+ const errMsg = installError instanceof Error ? installError.message : String(installError);
10054
+ console.error("Failed to update:", errMsg);
10055
+ console.log("Continuing with current version...\n");
10056
+ }
10057
+ } catch (error) {
10058
+ logError(error, { source: "auto-update", action: "check" });
10059
+ }
10060
+ }
10061
+
9943
10062
  // src/index.ts
9944
10063
  init_banner();
9945
10064
  init_error_logger();
@@ -9947,7 +10066,7 @@ init_logger();
9947
10066
 
9948
10067
  // src/utils/node-version.ts
9949
10068
  init_logger();
9950
- import { execSync as execSync3 } from "child_process";
10069
+ import { execSync as execSync4 } from "child_process";
9951
10070
  var MINIMUM_NODE_VERSION2 = 18;
9952
10071
  function parseVersion2(versionString) {
9953
10072
  const cleaned = versionString.trim().replace(/^v/, "");
@@ -9964,7 +10083,7 @@ function parseVersion2(versionString) {
9964
10083
  }
9965
10084
  function getNodeVersion2() {
9966
10085
  try {
9967
- const versionOutput = execSync3("node --version", {
10086
+ const versionOutput = execSync4("node --version", {
9968
10087
  encoding: "utf-8",
9969
10088
  stdio: ["ignore", "pipe", "ignore"]
9970
10089
  });
@@ -10084,6 +10203,7 @@ program.name("supatest").description(
10084
10203
  ).option("--supatest-api-key <key>", "Supatest API key (or use SUPATEST_API_KEY env)").option("--supatest-api-url <url>", "Supatest API URL (or use SUPATEST_API_URL env, defaults to https://code-api.supatest.ai)").option("--headless", "Run in headless mode (for CI/CD, minimal output)").option("--verbose", "Enable verbose logging").option("--model <model>", "Claude model to use (or use ANTHROPIC_MODEL_NAME env)").action(async (task, options) => {
10085
10204
  try {
10086
10205
  checkNodeVersion2();
10206
+ await checkAndAutoUpdate();
10087
10207
  const isHeadlessMode = options.headless || process.stdin.isTTY === false;
10088
10208
  if (options.verbose) {
10089
10209
  logger.setVerbose(true);
@@ -10192,9 +10312,9 @@ program.name("supatest").description(
10192
10312
  process.exit(1);
10193
10313
  }
10194
10314
  });
10195
- program.command("setup").description("Check prerequisites and set up required tools (Node.js, Playwright MCP)").action(async () => {
10315
+ program.command("setup").description("Check prerequisites and set up required tools (Node.js, Playwright MCP)").option("-C, --cwd <path>", "Working directory for setup", process.cwd()).action(async (options) => {
10196
10316
  try {
10197
- const result = await setupCommand();
10317
+ const result = await setupCommand({ cwd: options.cwd });
10198
10318
  process.exit(result.errors.length === 0 ? 0 : 1);
10199
10319
  } catch (error) {
10200
10320
  logError(error, { source: "setup" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supatest/cli",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "Supatest CLI - AI-powered task automation for CI/CD",
5
5
  "type": "module",
6
6
  "bin": {
@@ -63,10 +63,14 @@
63
63
  "react": "^19.0.0",
64
64
  "string-width": "^8.1.0",
65
65
  "strip-ansi": "^7.1.2",
66
- "wrap-ansi": "^9.0.2"
66
+ "undici": "^7.16.0",
67
+ "wrap-ansi": "^9.0.2",
68
+ "latest-version": "^9.0.0",
69
+ "semver": "^7.6.0"
67
70
  },
68
71
  "devDependencies": {
69
72
  "@types/node": "^20.12.12",
73
+ "@types/semver": "^7.5.8",
70
74
  "@types/react": "^19.0.0",
71
75
  "nodemon": "^3.1.11",
72
76
  "tsup": "^8.5.1",