@supatest/cli 0.0.12 → 0.0.14

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.
package/dist/index.js CHANGED
@@ -3872,6 +3872,7 @@ var init_project_instructions = __esm({
3872
3872
 
3873
3873
  // src/core/agent.ts
3874
3874
  import { createRequire } from "module";
3875
+ import { homedir } from "os";
3875
3876
  import { dirname, join as join2 } from "path";
3876
3877
  import { query } from "@anthropic-ai/claude-agent-sdk";
3877
3878
  var CoreAgent;
@@ -3932,6 +3933,8 @@ ${projectInstructions}`
3932
3933
  cleanEnv[key] = value;
3933
3934
  }
3934
3935
  }
3936
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join2(homedir(), ".supatest", "claude-config");
3937
+ cleanEnv.CLAUDE_CONFIG_DIR = claudeConfigDir;
3935
3938
  cleanEnv.ANTHROPIC_API_KEY = config2.supatestApiKey || "";
3936
3939
  cleanEnv.ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL || "";
3937
3940
  cleanEnv.ANTHROPIC_AUTH_TOKEN = "";
@@ -4447,6 +4450,12 @@ var init_api_client = __esm({
4447
4450
  hasApiKey() {
4448
4451
  return !!this.apiKey;
4449
4452
  }
4453
+ /**
4454
+ * Get the current API key (used to refresh auth during a session)
4455
+ */
4456
+ getApiKey() {
4457
+ return this.apiKey;
4458
+ }
4450
4459
  /**
4451
4460
  * Create a new CLI session on the backend
4452
4461
  * @param title - The session title
@@ -4570,13 +4579,13 @@ var init_api_client = __esm({
4570
4579
  return data;
4571
4580
  }
4572
4581
  /**
4573
- * Get messages for a session
4582
+ * Get queries for a session
4574
4583
  * @param sessionId - The session ID
4575
- * @returns Messages with pagination info
4584
+ * @returns Queries for the session
4576
4585
  */
4577
- async getSessionMessages(sessionId) {
4578
- const url = `${this.apiUrl}/v1/sessions/${sessionId}/messages`;
4579
- logger.debug(`Fetching messages for session: ${sessionId}`);
4586
+ async getSessionQueries(sessionId) {
4587
+ const url = `${this.apiUrl}/v1/sessions/${sessionId}/queries`;
4588
+ logger.debug(`Fetching queries for session: ${sessionId}`);
4580
4589
  const response = await fetch(url, {
4581
4590
  method: "GET",
4582
4591
  headers: {
@@ -4589,7 +4598,7 @@ var init_api_client = __esm({
4589
4598
  }
4590
4599
  const data = await response.json();
4591
4600
  logger.debug(
4592
- `Fetched ${data.messages.length} messages for session: ${sessionId}`
4601
+ `Fetched ${data.queries.length} queries for session: ${sessionId}`
4593
4602
  );
4594
4603
  return data;
4595
4604
  }
@@ -4658,7 +4667,7 @@ var CLI_VERSION;
4658
4667
  var init_version = __esm({
4659
4668
  "src/version.ts"() {
4660
4669
  "use strict";
4661
- CLI_VERSION = "0.0.12";
4670
+ CLI_VERSION = "0.0.14";
4662
4671
  }
4663
4672
  });
4664
4673
 
@@ -4733,7 +4742,7 @@ var init_encryption = __esm({
4733
4742
 
4734
4743
  // src/utils/token-storage.ts
4735
4744
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
4736
- import { homedir } from "os";
4745
+ import { homedir as homedir2 } from "os";
4737
4746
  import { join as join4 } from "path";
4738
4747
  function getTokenFilePath() {
4739
4748
  const apiUrl = process.env.SUPATEST_API_URL || PRODUCTION_API_URL;
@@ -4808,7 +4817,7 @@ var init_token_storage = __esm({
4808
4817
  "src/utils/token-storage.ts"() {
4809
4818
  "use strict";
4810
4819
  init_encryption();
4811
- CONFIG_DIR = join4(homedir(), ".supatest");
4820
+ CONFIG_DIR = join4(homedir2(), ".supatest");
4812
4821
  PRODUCTION_API_URL = "https://code-api.supatest.ai";
4813
4822
  STORAGE_VERSION = 2;
4814
4823
  TOKEN_FILE = join4(CONFIG_DIR, "token.json");
@@ -5170,7 +5179,15 @@ function startCallbackServer(port, expectedState) {
5170
5179
  }
5171
5180
  });
5172
5181
  server.on("error", (error) => {
5173
- reject(error);
5182
+ if (error.code === "EADDRINUSE") {
5183
+ const portError = new Error(
5184
+ "Something went wrong. Please restart the CLI and try again."
5185
+ );
5186
+ portError.code = "EADDRINUSE";
5187
+ reject(portError);
5188
+ } else {
5189
+ reject(error);
5190
+ }
5174
5191
  });
5175
5192
  const timeout = setTimeout(() => {
5176
5193
  server.close();
@@ -5424,7 +5441,13 @@ ${loginUrl}
5424
5441
  console.log("\n\u2705 Login successful!\n");
5425
5442
  return result;
5426
5443
  } catch (error) {
5427
- console.error("\n\u274C Login failed:", error.message, "\n");
5444
+ const err = error;
5445
+ if (err.code === "EADDRINUSE") {
5446
+ console.error("\n\u274C Login failed: Something went wrong.");
5447
+ console.error(" Please restart the CLI and try again.\n");
5448
+ } else {
5449
+ console.error("\n\u274C Login failed:", error.message, "\n");
5450
+ }
5428
5451
  throw error;
5429
5452
  }
5430
5453
  }
@@ -7066,6 +7089,9 @@ function highlightTreeToString(node) {
7066
7089
  if (node.type === "text") {
7067
7090
  return node.value;
7068
7091
  }
7092
+ if (node.type === "root" && node.children) {
7093
+ return node.children.map(highlightTreeToString).join("");
7094
+ }
7069
7095
  if (node.type === "element") {
7070
7096
  const className = node.properties?.className?.[0] || "";
7071
7097
  let content = "";
@@ -7352,7 +7378,8 @@ var init_ErrorMessage = __esm({
7352
7378
  type = "error"
7353
7379
  }) => {
7354
7380
  const { icon, color } = getErrorStyle(type);
7355
- return /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text9, { color }, icon, " ", message));
7381
+ const displayMessage = type === "error" ? "Tool call failed" : message;
7382
+ return /* @__PURE__ */ React11.createElement(Box9, { flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React11.createElement(Text9, { color }, icon, " ", displayMessage));
7356
7383
  };
7357
7384
  }
7358
7385
  });
@@ -7913,8 +7940,9 @@ var init_useOverlayEscapeGuard = __esm({
7913
7940
 
7914
7941
  // src/ui/App.tsx
7915
7942
  import { execSync as execSync3 } from "child_process";
7916
- import { homedir as homedir2 } from "os";
7943
+ import { homedir as homedir3 } from "os";
7917
7944
  import { Box as Box18, Text as Text17, useApp } from "ink";
7945
+ import Spinner3 from "ink-spinner";
7918
7946
  import React20, { useEffect as useEffect7, useRef as useRef3, useState as useState6 } from "react";
7919
7947
  var getGitBranch, getCurrentFolder, AppContent, App;
7920
7948
  var init_App = __esm({
@@ -7938,6 +7966,7 @@ var init_App = __esm({
7938
7966
  init_useModeToggle();
7939
7967
  init_useOverlayEscapeGuard();
7940
7968
  init_auth();
7969
+ init_theme();
7941
7970
  getGitBranch = () => {
7942
7971
  try {
7943
7972
  return execSync3("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
@@ -7947,7 +7976,7 @@ var init_App = __esm({
7947
7976
  };
7948
7977
  getCurrentFolder = () => {
7949
7978
  const cwd = process.cwd();
7950
- const home = homedir2();
7979
+ const home = homedir3();
7951
7980
  if (cwd.startsWith(home)) {
7952
7981
  return `~${cwd.slice(home.length)}`;
7953
7982
  }
@@ -7967,6 +7996,7 @@ var init_App = __esm({
7967
7996
  const inputPromptRef = useRef3(null);
7968
7997
  const [showSessionSelector, setShowSessionSelector] = useState6(false);
7969
7998
  const [showModelSelector, setShowModelSelector] = useState6(false);
7999
+ const [isLoadingSession, setIsLoadingSession] = useState6(false);
7970
8000
  const [authState, setAuthState] = useState6(
7971
8001
  () => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
7972
8002
  );
@@ -8127,15 +8157,13 @@ var init_App = __esm({
8127
8157
  onSubmitTask(task);
8128
8158
  }
8129
8159
  };
8130
- const handleSessionSelect = (session) => {
8160
+ const handleSessionSelect = async (session) => {
8131
8161
  setShowSessionSelector(false);
8132
- addMessage({
8133
- type: "assistant",
8134
- content: `Switching to session: ${session.title || "Untitled"} (${session.id})`
8135
- });
8162
+ setIsLoadingSession(true);
8136
8163
  if (onResumeSession) {
8137
- onResumeSession(session);
8164
+ await onResumeSession(session);
8138
8165
  }
8166
+ setIsLoadingSession(false);
8139
8167
  };
8140
8168
  const handleSessionSelectorCancel = () => {
8141
8169
  markOverlayClosed();
@@ -8248,6 +8276,7 @@ var init_App = __esm({
8248
8276
  onSelect: handleSessionSelect
8249
8277
  }
8250
8278
  ),
8279
+ isLoadingSession && /* @__PURE__ */ React20.createElement(Box18, { borderColor: "cyan", borderStyle: "round", flexDirection: "column", padding: 1 }, /* @__PURE__ */ React20.createElement(Box18, { flexDirection: "row" }, /* @__PURE__ */ React20.createElement(Box18, { width: 2 }, /* @__PURE__ */ React20.createElement(Text17, { color: theme.text.accent }, /* @__PURE__ */ React20.createElement(Spinner3, { type: "dots" }))), /* @__PURE__ */ React20.createElement(Text17, { bold: true, color: "cyan" }, "Loading session...")), /* @__PURE__ */ React20.createElement(Text17, { color: theme.text.dim }, "Fetching queries and context")),
8251
8280
  showModelSelector && /* @__PURE__ */ React20.createElement(
8252
8281
  ModelSelector,
8253
8282
  {
@@ -8262,7 +8291,7 @@ var init_App = __esm({
8262
8291
  onLogin: handleLogin
8263
8292
  }
8264
8293
  ),
8265
- /* @__PURE__ */ React20.createElement(Box18, { flexDirection: "column" }, /* @__PURE__ */ React20.createElement(QueuedMessageDisplay, { messageQueue: queuedTasks }), !showAuthDialog && /* @__PURE__ */ React20.createElement(AuthBanner, { authState }), showInput && !showSessionSelector && !showAuthDialog && !showHelp && !showModelSelector && /* @__PURE__ */ React20.createElement(Box18, { flexDirection: "column", marginTop: 0, width: "100%" }, exitWarning && /* @__PURE__ */ React20.createElement(Box18, { marginBottom: 0, paddingX: 1 }, /* @__PURE__ */ React20.createElement(Text17, { color: "yellow" }, exitWarning)), /* @__PURE__ */ React20.createElement(
8294
+ /* @__PURE__ */ React20.createElement(Box18, { flexDirection: "column" }, /* @__PURE__ */ React20.createElement(QueuedMessageDisplay, { messageQueue: queuedTasks }), !showAuthDialog && /* @__PURE__ */ React20.createElement(AuthBanner, { authState }), showInput && !showSessionSelector && !showAuthDialog && !showHelp && !showModelSelector && !isLoadingSession && /* @__PURE__ */ React20.createElement(Box18, { flexDirection: "column", marginTop: 0, width: "100%" }, exitWarning && /* @__PURE__ */ React20.createElement(Box18, { marginBottom: 0, paddingX: 1 }, /* @__PURE__ */ React20.createElement(Text17, { color: "yellow" }, exitWarning)), /* @__PURE__ */ React20.createElement(
8266
8295
  InputPrompt,
8267
8296
  {
8268
8297
  currentFolder,
@@ -8344,6 +8373,69 @@ function getToolDescription2(toolName, input) {
8344
8373
  return toolName;
8345
8374
  }
8346
8375
  }
8376
+ function convertQueriesToUIMessages(queries) {
8377
+ const uiMessages = [];
8378
+ const sortedQueries = [...queries].sort((a, b2) => a.sequence - b2.sequence);
8379
+ for (const query2 of sortedQueries) {
8380
+ const timestamp = new Date(query2.startedAt).getTime();
8381
+ if (query2.content?.userMessage) {
8382
+ for (const block of query2.content.userMessage) {
8383
+ if (block.type === "text") {
8384
+ uiMessages.push({
8385
+ id: `${query2.id}-user-${uiMessages.length}`,
8386
+ type: "user",
8387
+ content: block.text,
8388
+ timestamp
8389
+ });
8390
+ }
8391
+ }
8392
+ }
8393
+ if (query2.content?.turns) {
8394
+ const toolResults = /* @__PURE__ */ new Map();
8395
+ for (const turn of query2.content.turns) {
8396
+ if (turn.toolResults) {
8397
+ for (const block of turn.toolResults) {
8398
+ if (block.type === "tool_result" && block.tool_use_id) {
8399
+ toolResults.set(block.tool_use_id, block.content);
8400
+ }
8401
+ }
8402
+ }
8403
+ }
8404
+ for (const turn of query2.content.turns) {
8405
+ for (const block of turn.assistant) {
8406
+ if (block.type === "text") {
8407
+ uiMessages.push({
8408
+ id: `${query2.id}-assistant-${uiMessages.length}`,
8409
+ type: "assistant",
8410
+ content: block.text,
8411
+ timestamp
8412
+ });
8413
+ } else if (block.type === "thinking") {
8414
+ uiMessages.push({
8415
+ id: `${query2.id}-thinking-${uiMessages.length}`,
8416
+ type: "thinking",
8417
+ content: block.thinking,
8418
+ timestamp
8419
+ });
8420
+ } else if (block.type === "tool_use") {
8421
+ const toolResult = toolResults.get(block.id);
8422
+ uiMessages.push({
8423
+ id: `${query2.id}-tool-${uiMessages.length}`,
8424
+ type: "tool",
8425
+ content: getToolDescription2(block.name, block.input),
8426
+ toolName: block.name,
8427
+ toolInput: block.input,
8428
+ toolResult,
8429
+ toolUseId: block.id,
8430
+ timestamp
8431
+ });
8432
+ }
8433
+ }
8434
+ }
8435
+ }
8436
+ }
8437
+ return uiMessages;
8438
+ }
8347
8439
  async function runInteractive(config2) {
8348
8440
  let success = false;
8349
8441
  let unmountFn = null;
@@ -8467,10 +8559,11 @@ var init_interactive = __esm({
8467
8559
  const runAgent2 = async () => {
8468
8560
  setIsAgentRunning(true);
8469
8561
  try {
8562
+ const supatestApiKey = apiClient.getApiKey?.() || config2.supatestApiKey || "";
8470
8563
  const proxyUrl = config2.supatestApiUrl || "https://code-api.supatest.ai";
8471
8564
  const baseUrl = `${proxyUrl}/v1/sessions/${sessionId}/anthropic`;
8472
8565
  process.env.ANTHROPIC_BASE_URL = baseUrl;
8473
- process.env.ANTHROPIC_API_KEY = config2.supatestApiKey;
8566
+ process.env.ANTHROPIC_API_KEY = supatestApiKey;
8474
8567
  const presenter = new ReactPresenter(
8475
8568
  {
8476
8569
  addMessage: (msg) => {
@@ -8502,6 +8595,7 @@ var init_interactive = __esm({
8502
8595
  );
8503
8596
  const runConfig = {
8504
8597
  ...config2,
8598
+ supatestApiKey,
8505
8599
  mode: agentMode,
8506
8600
  planFilePath,
8507
8601
  selectedModel,
@@ -8625,61 +8719,9 @@ var init_interactive = __esm({
8625
8719
  });
8626
8720
  return;
8627
8721
  }
8628
- const response = await apiClient.getSessionMessages(session.id);
8629
- const apiMessages = response.messages;
8630
- const toolResults = /* @__PURE__ */ new Map();
8631
- for (const msg of apiMessages) {
8632
- const contentBlocks = msg.content;
8633
- if (!contentBlocks) continue;
8634
- for (const block of contentBlocks) {
8635
- if (block.type === "tool_result" && block.tool_use_id) {
8636
- let resultText = "";
8637
- if (typeof block.content === "string") {
8638
- resultText = block.content;
8639
- } else if (Array.isArray(block.content)) {
8640
- resultText = block.content.map((c2) => c2.text || "").join("\n");
8641
- }
8642
- toolResults.set(block.tool_use_id, resultText);
8643
- }
8644
- }
8645
- }
8646
- const uiMessages = apiMessages.flatMap((msg) => {
8647
- const messages = [];
8648
- const contentBlocks = msg.content;
8649
- if (!contentBlocks || contentBlocks.length === 0) {
8650
- return [];
8651
- }
8652
- for (const block of contentBlocks) {
8653
- if (block.type === "text") {
8654
- messages.push({
8655
- id: `${msg.id}-${messages.length}`,
8656
- type: msg.role,
8657
- content: block.text,
8658
- timestamp: new Date(msg.createdAt).getTime()
8659
- });
8660
- } else if (block.type === "thinking") {
8661
- messages.push({
8662
- id: `${msg.id}-${messages.length}`,
8663
- type: "thinking",
8664
- content: block.thinking,
8665
- timestamp: new Date(msg.createdAt).getTime()
8666
- });
8667
- } else if (block.type === "tool_use") {
8668
- const toolResult = toolResults.get(block.id);
8669
- messages.push({
8670
- id: `${msg.id}-${messages.length}`,
8671
- type: "tool",
8672
- content: getToolDescription2(block.name, block.input),
8673
- toolName: block.name,
8674
- toolInput: block.input,
8675
- toolResult,
8676
- toolUseId: block.id,
8677
- timestamp: new Date(msg.createdAt).getTime()
8678
- });
8679
- }
8680
- }
8681
- return messages;
8682
- });
8722
+ const response = await apiClient.getSessionQueries(session.id);
8723
+ const queries = response.queries;
8724
+ const uiMessages = convertQueriesToUIMessages(queries);
8683
8725
  setSessionId(session.id);
8684
8726
  setContextSessionId(session.id);
8685
8727
  if (session.providerSessionId) {
@@ -8705,7 +8747,7 @@ var init_interactive = __esm({
8705
8747
  uiMessages.push({
8706
8748
  id: `resume-info-${Date.now()}`,
8707
8749
  type: "error",
8708
- content: `Resumed session: ${session.title || "Untitled"} (${apiMessages.length} messages loaded)`,
8750
+ content: `Resumed session: ${session.title || "Untitled"} (${queries.length} queries loaded)`,
8709
8751
  errorType: "info",
8710
8752
  timestamp: Date.now()
8711
8753
  });
@@ -9432,6 +9474,36 @@ async function readStdin() {
9432
9474
  // src/index.ts
9433
9475
  init_token_storage();
9434
9476
  init_version();
9477
+ var looksLikeAnthropicKey = (key) => !!key && key.startsWith("sk-ant-");
9478
+ var normalizeSupatestKey = (key, { isHeadless }) => {
9479
+ if (!key) return void 0;
9480
+ if (looksLikeAnthropicKey(key)) {
9481
+ const message = "Detected an Anthropic API key. Use a Supatest API key (sk_test_/sk_live_) or run `supatest login` for a CLI token.";
9482
+ if (isHeadless) {
9483
+ logger.error(message);
9484
+ process.exit(1);
9485
+ } else {
9486
+ logger.warn(`${message} Ignoring the provided key and prompting for login.`);
9487
+ return void 0;
9488
+ }
9489
+ }
9490
+ return key;
9491
+ };
9492
+ process.on("uncaughtException", (error) => {
9493
+ logger.error(`Uncaught exception: ${error.message}`);
9494
+ if (error.stack) {
9495
+ console.error(error.stack);
9496
+ }
9497
+ process.exit(1);
9498
+ });
9499
+ process.on("unhandledRejection", (reason, promise) => {
9500
+ const errorMessage = reason instanceof Error ? reason.message : String(reason);
9501
+ logger.error(`Unhandled promise rejection: ${errorMessage}`);
9502
+ if (reason instanceof Error && reason.stack) {
9503
+ console.error(reason.stack);
9504
+ }
9505
+ process.exit(1);
9506
+ });
9435
9507
  var program = new Command();
9436
9508
  program.name("supatest").description(
9437
9509
  "AI-powered task automation CLI for CI/CD - fix tests, lint issues, and more"
@@ -9475,7 +9547,10 @@ program.name("supatest").description(
9475
9547
  let supatestApiKey;
9476
9548
  const supatestApiUrl = options.supatestApiUrl || config.supatestApiUrl;
9477
9549
  if (isHeadlessMode) {
9478
- supatestApiKey = options.supatestApiKey || config.supatestApiKey;
9550
+ supatestApiKey = normalizeSupatestKey(
9551
+ options.supatestApiKey || config.supatestApiKey,
9552
+ { isHeadless: true }
9553
+ );
9479
9554
  if (!supatestApiKey) {
9480
9555
  logger.error(
9481
9556
  "API key required in CI/headless mode. Please either:"
@@ -9489,7 +9564,10 @@ program.name("supatest").description(
9489
9564
  if (cliToken) {
9490
9565
  supatestApiKey = cliToken;
9491
9566
  } else {
9492
- supatestApiKey = options.supatestApiKey || config.supatestApiKey;
9567
+ supatestApiKey = normalizeSupatestKey(
9568
+ options.supatestApiKey || config.supatestApiKey,
9569
+ { isHeadless: false }
9570
+ );
9493
9571
  }
9494
9572
  }
9495
9573
  let selectedModel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supatest/cli",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Supatest CLI - AI-powered task automation for CI/CD",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,8 +39,8 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@anthropic-ai/claude-agent-sdk": "^0.1.58",
43
- "@anthropic-ai/sdk": "^0.71.0",
42
+ "@anthropic-ai/claude-agent-sdk": "^0.1.61",
43
+ "@anthropic-ai/sdk": "^0.71.2",
44
44
  "@types/inquirer": "^9.0.0",
45
45
  "ansi-escapes": "^7.0.0",
46
46
  "boxen": "^8.0.1",