@supatest/cli 0.0.30 → 0.0.32
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 +144 -27
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5522,7 +5522,7 @@ var CLI_VERSION;
|
|
|
5522
5522
|
var init_version = __esm({
|
|
5523
5523
|
"src/version.ts"() {
|
|
5524
5524
|
"use strict";
|
|
5525
|
-
CLI_VERSION = "0.0.
|
|
5525
|
+
CLI_VERSION = "0.0.32";
|
|
5526
5526
|
}
|
|
5527
5527
|
});
|
|
5528
5528
|
|
|
@@ -6466,6 +6466,7 @@ var init_agent = __esm({
|
|
|
6466
6466
|
await init_config();
|
|
6467
6467
|
init_command_discovery();
|
|
6468
6468
|
init_error_logger();
|
|
6469
|
+
init_logger();
|
|
6469
6470
|
init_mcp_loader();
|
|
6470
6471
|
init_project_instructions();
|
|
6471
6472
|
CoreAgent = class {
|
|
@@ -6531,6 +6532,7 @@ ${projectInstructions}`,
|
|
|
6531
6532
|
"ANTHROPIC_BASE_URL",
|
|
6532
6533
|
"ANTHROPIC_AUTH_TOKEN",
|
|
6533
6534
|
"CLAUDE_CODE_AUTH_TOKEN",
|
|
6535
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
6534
6536
|
"CLAUDE_API_KEY"
|
|
6535
6537
|
]);
|
|
6536
6538
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -6538,12 +6540,32 @@ ${projectInstructions}`,
|
|
|
6538
6540
|
cleanEnv[key] = value;
|
|
6539
6541
|
}
|
|
6540
6542
|
}
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6543
|
+
logger.debug("[agent] Setting up authentication", {
|
|
6544
|
+
useClaudeMax: !!config2.oauthToken,
|
|
6545
|
+
hasSupatestApiKey: !!config2.supatestApiKey,
|
|
6546
|
+
supatestApiKeyPrefix: config2.supatestApiKey ? config2.supatestApiKey.slice(0, 15) + "..." : null,
|
|
6547
|
+
anthropicBaseUrl: process.env.ANTHROPIC_BASE_URL
|
|
6548
|
+
});
|
|
6549
|
+
if (config2.oauthToken) {
|
|
6550
|
+
cleanEnv.ANTHROPIC_API_KEY = "";
|
|
6551
|
+
cleanEnv.ANTHROPIC_BASE_URL = "";
|
|
6552
|
+
this.presenter.onLog(`Auth: Using Claude Max (default Claude Code credentials)`);
|
|
6553
|
+
logger.debug("[agent] Claude Max mode: Using default ~/.claude/ config, cleared ANTHROPIC_API_KEY and ANTHROPIC_BASE_URL");
|
|
6554
|
+
} else {
|
|
6555
|
+
const internalConfigDir = join6(homedir2(), ".supatest", "claude-internal");
|
|
6556
|
+
cleanEnv.CLAUDE_CONFIG_DIR = internalConfigDir;
|
|
6557
|
+
cleanEnv.ANTHROPIC_API_KEY = config2.supatestApiKey || "";
|
|
6558
|
+
cleanEnv.ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL || "";
|
|
6559
|
+
this.presenter.onLog(`Auth: Using Supatest API key${process.env.ANTHROPIC_BASE_URL ? ` (base: ${process.env.ANTHROPIC_BASE_URL})` : ""}`);
|
|
6560
|
+
logger.debug("[agent] API key mode: Set ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, and CLAUDE_CONFIG_DIR");
|
|
6561
|
+
}
|
|
6545
6562
|
cleanEnv.ANTHROPIC_AUTH_TOKEN = "";
|
|
6546
6563
|
cleanEnv.CLAUDE_CODE_AUTH_TOKEN = "";
|
|
6564
|
+
logger.debug("[agent] Final auth env vars", {
|
|
6565
|
+
ANTHROPIC_API_KEY: cleanEnv.ANTHROPIC_API_KEY ? cleanEnv.ANTHROPIC_API_KEY.slice(0, 15) + "..." : "(not set)",
|
|
6566
|
+
ANTHROPIC_BASE_URL: cleanEnv.ANTHROPIC_BASE_URL || "(not set)",
|
|
6567
|
+
CLAUDE_CONFIG_DIR: cleanEnv.CLAUDE_CONFIG_DIR || "(using default ~/.claude/)"
|
|
6568
|
+
});
|
|
6547
6569
|
cleanEnv.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = "1";
|
|
6548
6570
|
const queryOptions = {
|
|
6549
6571
|
// AbortController for cancellation support
|
|
@@ -6616,6 +6638,13 @@ ${projectInstructions}`,
|
|
|
6616
6638
|
return expiredPatterns.some((pattern) => lowerError.includes(pattern));
|
|
6617
6639
|
};
|
|
6618
6640
|
const runQuery = async (options) => {
|
|
6641
|
+
logger.debug("[agent] Starting query", {
|
|
6642
|
+
model: options.model,
|
|
6643
|
+
maxTurns: options.maxTurns,
|
|
6644
|
+
permissionMode: options.permissionMode,
|
|
6645
|
+
hasResume: !!options.resume,
|
|
6646
|
+
cwd: options.cwd
|
|
6647
|
+
});
|
|
6619
6648
|
const queryIterator = query({ prompt, options });
|
|
6620
6649
|
if (this.messageBridge) {
|
|
6621
6650
|
queryIterator.streamInput(this.messageBridge).catch((err) => {
|
|
@@ -6738,16 +6767,24 @@ ${projectInstructions}`,
|
|
|
6738
6767
|
};
|
|
6739
6768
|
try {
|
|
6740
6769
|
await runQuery(queryOptions);
|
|
6770
|
+
logger.debug("[agent] Query completed successfully");
|
|
6741
6771
|
} catch (error) {
|
|
6742
6772
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6773
|
+
logger.debug("[agent] Query error caught", {
|
|
6774
|
+
errorName: error instanceof Error ? error.name : "unknown",
|
|
6775
|
+
errorMessage,
|
|
6776
|
+
stack: error instanceof Error ? error.stack?.slice(0, 500) : void 0
|
|
6777
|
+
});
|
|
6743
6778
|
const isAbortError = error instanceof Error && error.name === "AbortError" || errorMessage.toLowerCase().includes("aborted");
|
|
6744
6779
|
if (isAbortError) {
|
|
6745
6780
|
wasInterrupted = true;
|
|
6781
|
+
logger.debug("[agent] Query was aborted by user");
|
|
6746
6782
|
} else if (config2.providerSessionId && isSessionExpiredError(errorMessage)) {
|
|
6747
6783
|
const expiredMessage = "Can't continue conversation older than 30 days. Please start a new session.";
|
|
6748
6784
|
this.presenter.onError(expiredMessage, true);
|
|
6749
6785
|
hasError = true;
|
|
6750
6786
|
errors.push(expiredMessage);
|
|
6787
|
+
logger.debug("[agent] Session expired error");
|
|
6751
6788
|
} else {
|
|
6752
6789
|
logError(error, {
|
|
6753
6790
|
source: "CoreAgent.run",
|
|
@@ -6759,6 +6796,7 @@ ${projectInstructions}`,
|
|
|
6759
6796
|
this.presenter.onError(errorMessage, true);
|
|
6760
6797
|
hasError = true;
|
|
6761
6798
|
errors.push(errorMessage);
|
|
6799
|
+
logger.debug("[agent] General error, logged to error-logger");
|
|
6762
6800
|
}
|
|
6763
6801
|
} finally {
|
|
6764
6802
|
this.messageBridge?.close();
|
|
@@ -8334,21 +8372,21 @@ var init_encryption = __esm({
|
|
|
8334
8372
|
});
|
|
8335
8373
|
|
|
8336
8374
|
// src/utils/token-storage.ts
|
|
8337
|
-
import { existsSync as
|
|
8338
|
-
import { homedir as
|
|
8339
|
-
import { join as
|
|
8375
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync } from "fs";
|
|
8376
|
+
import { homedir as homedir5 } from "os";
|
|
8377
|
+
import { join as join8 } from "path";
|
|
8340
8378
|
function getTokenFilePath() {
|
|
8341
8379
|
const apiUrl = process.env.SUPATEST_API_URL || PRODUCTION_API_URL;
|
|
8342
8380
|
if (apiUrl === PRODUCTION_API_URL) {
|
|
8343
|
-
return
|
|
8381
|
+
return join8(CONFIG_DIR, "token.json");
|
|
8344
8382
|
}
|
|
8345
|
-
return
|
|
8383
|
+
return join8(CONFIG_DIR, "token.local.json");
|
|
8346
8384
|
}
|
|
8347
8385
|
function isV2Format(stored) {
|
|
8348
8386
|
return "version" in stored && stored.version === 2;
|
|
8349
8387
|
}
|
|
8350
8388
|
function ensureConfigDir() {
|
|
8351
|
-
if (!
|
|
8389
|
+
if (!existsSync6(CONFIG_DIR)) {
|
|
8352
8390
|
mkdirSync2(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
8353
8391
|
}
|
|
8354
8392
|
}
|
|
@@ -8368,11 +8406,11 @@ function saveToken(token, expiresAt) {
|
|
|
8368
8406
|
}
|
|
8369
8407
|
function loadToken() {
|
|
8370
8408
|
const tokenFile = getTokenFilePath();
|
|
8371
|
-
if (!
|
|
8409
|
+
if (!existsSync6(tokenFile)) {
|
|
8372
8410
|
return null;
|
|
8373
8411
|
}
|
|
8374
8412
|
try {
|
|
8375
|
-
const data =
|
|
8413
|
+
const data = readFileSync5(tokenFile, "utf8");
|
|
8376
8414
|
const stored = JSON.parse(data);
|
|
8377
8415
|
let payload;
|
|
8378
8416
|
if (isV2Format(stored)) {
|
|
@@ -8401,7 +8439,7 @@ function loadToken() {
|
|
|
8401
8439
|
}
|
|
8402
8440
|
function removeToken() {
|
|
8403
8441
|
const tokenFile = getTokenFilePath();
|
|
8404
|
-
if (
|
|
8442
|
+
if (existsSync6(tokenFile)) {
|
|
8405
8443
|
unlinkSync2(tokenFile);
|
|
8406
8444
|
}
|
|
8407
8445
|
}
|
|
@@ -8410,10 +8448,10 @@ var init_token_storage = __esm({
|
|
|
8410
8448
|
"src/utils/token-storage.ts"() {
|
|
8411
8449
|
"use strict";
|
|
8412
8450
|
init_encryption();
|
|
8413
|
-
CONFIG_DIR =
|
|
8451
|
+
CONFIG_DIR = join8(homedir5(), ".supatest");
|
|
8414
8452
|
PRODUCTION_API_URL = "https://code-api.supatest.ai";
|
|
8415
8453
|
STORAGE_VERSION = 2;
|
|
8416
|
-
TOKEN_FILE =
|
|
8454
|
+
TOKEN_FILE = join8(CONFIG_DIR, "token.json");
|
|
8417
8455
|
}
|
|
8418
8456
|
});
|
|
8419
8457
|
|
|
@@ -10960,8 +10998,8 @@ var init_useOverlayEscapeGuard = __esm({
|
|
|
10960
10998
|
});
|
|
10961
10999
|
|
|
10962
11000
|
// src/ui/App.tsx
|
|
10963
|
-
import { execSync as
|
|
10964
|
-
import { homedir as
|
|
11001
|
+
import { execSync as execSync6 } from "child_process";
|
|
11002
|
+
import { homedir as homedir6 } from "os";
|
|
10965
11003
|
import { Box as Box23, Text as Text21, useApp as useApp2, useStdout as useStdout2 } from "ink";
|
|
10966
11004
|
import Spinner3 from "ink-spinner";
|
|
10967
11005
|
import React26, { useEffect as useEffect13, useRef as useRef4, useState as useState13 } from "react";
|
|
@@ -10994,14 +11032,14 @@ var init_App = __esm({
|
|
|
10994
11032
|
init_theme();
|
|
10995
11033
|
getGitBranch2 = () => {
|
|
10996
11034
|
try {
|
|
10997
|
-
return
|
|
11035
|
+
return execSync6("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
|
|
10998
11036
|
} catch {
|
|
10999
11037
|
return "";
|
|
11000
11038
|
}
|
|
11001
11039
|
};
|
|
11002
11040
|
getCurrentFolder2 = (configCwd) => {
|
|
11003
11041
|
const cwd = configCwd || process.cwd();
|
|
11004
|
-
const home =
|
|
11042
|
+
const home = homedir6();
|
|
11005
11043
|
if (cwd.startsWith(home)) {
|
|
11006
11044
|
return `~${cwd.slice(home.length)}`;
|
|
11007
11045
|
}
|
|
@@ -12369,12 +12407,77 @@ Updating Supatest CLI ${CLI_VERSION} \u2192 ${latest}...`);
|
|
|
12369
12407
|
|
|
12370
12408
|
// src/index.ts
|
|
12371
12409
|
init_banner();
|
|
12410
|
+
|
|
12411
|
+
// src/utils/claude-max.ts
|
|
12412
|
+
init_logger();
|
|
12413
|
+
import { execSync as execSync4 } from "child_process";
|
|
12414
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
12415
|
+
import { homedir as homedir4 } from "os";
|
|
12416
|
+
import { join as join7 } from "path";
|
|
12417
|
+
function isClaudeMaxAvailable() {
|
|
12418
|
+
const platform2 = process.platform;
|
|
12419
|
+
logger.debug("[claude-max] Checking Claude Code credentials", { platform: platform2 });
|
|
12420
|
+
if (platform2 === "darwin") {
|
|
12421
|
+
return checkMacOSKeychain();
|
|
12422
|
+
} else {
|
|
12423
|
+
return checkCredentialsFile();
|
|
12424
|
+
}
|
|
12425
|
+
}
|
|
12426
|
+
function checkMacOSKeychain() {
|
|
12427
|
+
try {
|
|
12428
|
+
const credentialsJson = execSync4(
|
|
12429
|
+
'security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null',
|
|
12430
|
+
{ encoding: "utf-8" }
|
|
12431
|
+
).trim();
|
|
12432
|
+
if (!credentialsJson) {
|
|
12433
|
+
logger.debug("[claude-max] No credentials found in macOS keychain");
|
|
12434
|
+
return false;
|
|
12435
|
+
}
|
|
12436
|
+
const credentials = JSON.parse(credentialsJson);
|
|
12437
|
+
const hasOauth = !!credentials.claudeAiOauth?.accessToken;
|
|
12438
|
+
logger.debug("[claude-max] macOS keychain credentials check", {
|
|
12439
|
+
hasOauth,
|
|
12440
|
+
hasRefreshToken: !!credentials.claudeAiOauth?.refreshToken
|
|
12441
|
+
});
|
|
12442
|
+
return hasOauth;
|
|
12443
|
+
} catch (error) {
|
|
12444
|
+
logger.debug("[claude-max] Error checking macOS keychain", {
|
|
12445
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12446
|
+
});
|
|
12447
|
+
return false;
|
|
12448
|
+
}
|
|
12449
|
+
}
|
|
12450
|
+
function checkCredentialsFile() {
|
|
12451
|
+
try {
|
|
12452
|
+
const credentialsPath = join7(homedir4(), ".claude", ".credentials.json");
|
|
12453
|
+
if (!existsSync5(credentialsPath)) {
|
|
12454
|
+
logger.debug("[claude-max] Credentials file not found", { path: credentialsPath });
|
|
12455
|
+
return false;
|
|
12456
|
+
}
|
|
12457
|
+
const credentialsJson = readFileSync4(credentialsPath, "utf-8");
|
|
12458
|
+
const credentials = JSON.parse(credentialsJson);
|
|
12459
|
+
const hasOauth = !!credentials.claudeAiOauth?.accessToken;
|
|
12460
|
+
logger.debug("[claude-max] Credentials file check", {
|
|
12461
|
+
path: credentialsPath,
|
|
12462
|
+
hasOauth,
|
|
12463
|
+
hasRefreshToken: !!credentials.claudeAiOauth?.refreshToken
|
|
12464
|
+
});
|
|
12465
|
+
return hasOauth;
|
|
12466
|
+
} catch (error) {
|
|
12467
|
+
logger.debug("[claude-max] Error checking credentials file", {
|
|
12468
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12469
|
+
});
|
|
12470
|
+
return false;
|
|
12471
|
+
}
|
|
12472
|
+
}
|
|
12473
|
+
|
|
12474
|
+
// src/index.ts
|
|
12372
12475
|
init_error_logger();
|
|
12373
12476
|
init_logger();
|
|
12374
12477
|
|
|
12375
12478
|
// src/utils/node-version.ts
|
|
12376
12479
|
init_logger();
|
|
12377
|
-
import { execSync as
|
|
12480
|
+
import { execSync as execSync5 } from "child_process";
|
|
12378
12481
|
var MINIMUM_NODE_VERSION2 = 18;
|
|
12379
12482
|
function parseVersion2(versionString) {
|
|
12380
12483
|
const cleaned = versionString.trim().replace(/^v/, "");
|
|
@@ -12391,7 +12494,7 @@ function parseVersion2(versionString) {
|
|
|
12391
12494
|
}
|
|
12392
12495
|
function getNodeVersion2() {
|
|
12393
12496
|
try {
|
|
12394
|
-
const versionOutput =
|
|
12497
|
+
const versionOutput = execSync5("node --version", {
|
|
12395
12498
|
encoding: "utf-8",
|
|
12396
12499
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12397
12500
|
});
|
|
@@ -12505,10 +12608,10 @@ var program = new Command();
|
|
|
12505
12608
|
program.name("supatest").description(
|
|
12506
12609
|
"AI-powered task automation CLI for CI/CD - fix tests, lint issues, and more"
|
|
12507
12610
|
).version(CLI_VERSION).argument("[task]", "Task description or prompt for the AI agent").option("-l, --logs <file>", "Path to log file to analyze").option("--stdin", "Read logs from stdin").option("-C, --cwd <path>", "Working directory for the agent", process.cwd()).option(
|
|
12508
|
-
"-m, --max-iterations <number>",
|
|
12611
|
+
"-m, --claude-max-iterations <number>",
|
|
12509
12612
|
"Maximum number of iterations",
|
|
12510
12613
|
"100"
|
|
12511
|
-
).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) => {
|
|
12614
|
+
).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)").option("--claude-max", "Use Claude Max subscription (requires Claude Code to be logged in)").action(async (task, options) => {
|
|
12512
12615
|
try {
|
|
12513
12616
|
checkNodeVersion2();
|
|
12514
12617
|
await checkAndAutoUpdate();
|
|
@@ -12543,7 +12646,19 @@ program.name("supatest").description(
|
|
|
12543
12646
|
process.exit(1);
|
|
12544
12647
|
}
|
|
12545
12648
|
let supatestApiKey;
|
|
12649
|
+
let oauthToken;
|
|
12546
12650
|
const supatestApiUrl = options.supatestApiUrl || config.supatestApiUrl;
|
|
12651
|
+
if (options.claudeMax) {
|
|
12652
|
+
if (!isClaudeMaxAvailable()) {
|
|
12653
|
+
logger.error("Claude Max not available. Please ensure:");
|
|
12654
|
+
logger.error(" 1. Claude Code is installed");
|
|
12655
|
+
logger.error(" 2. You are logged in with a Claude Max subscription");
|
|
12656
|
+
logger.error(" 3. Run 'claude' and authenticate if needed");
|
|
12657
|
+
process.exit(1);
|
|
12658
|
+
}
|
|
12659
|
+
oauthToken = "use-claude-max";
|
|
12660
|
+
logger.info("Using Claude Max subscription for LLM calls");
|
|
12661
|
+
}
|
|
12547
12662
|
if (isHeadlessMode) {
|
|
12548
12663
|
supatestApiKey = normalizeSupatestKey(
|
|
12549
12664
|
options.supatestApiKey || config.supatestApiKey,
|
|
@@ -12551,7 +12666,7 @@ program.name("supatest").description(
|
|
|
12551
12666
|
);
|
|
12552
12667
|
if (!supatestApiKey) {
|
|
12553
12668
|
logger.error(
|
|
12554
|
-
"API key required in CI/headless mode. Please either:"
|
|
12669
|
+
"Supatest API key required in CI/headless mode. Please either:"
|
|
12555
12670
|
);
|
|
12556
12671
|
logger.error(" 1. Set SUPATEST_API_KEY environment variable");
|
|
12557
12672
|
logger.error(" 2. Use --supatest-api-key option");
|
|
@@ -12593,7 +12708,8 @@ program.name("supatest").description(
|
|
|
12593
12708
|
verbose: options.verbose || false,
|
|
12594
12709
|
cwd: options.cwd,
|
|
12595
12710
|
systemPromptAppend: config.headlessSystemPrompt,
|
|
12596
|
-
selectedModel
|
|
12711
|
+
selectedModel,
|
|
12712
|
+
oauthToken
|
|
12597
12713
|
});
|
|
12598
12714
|
process.exit(result.success ? 0 : 1);
|
|
12599
12715
|
} else {
|
|
@@ -12608,7 +12724,8 @@ program.name("supatest").description(
|
|
|
12608
12724
|
verbose: options.verbose || false,
|
|
12609
12725
|
cwd: options.cwd,
|
|
12610
12726
|
systemPromptAppend: config.interactiveSystemPrompt,
|
|
12611
|
-
selectedModel
|
|
12727
|
+
selectedModel,
|
|
12728
|
+
oauthToken
|
|
12612
12729
|
});
|
|
12613
12730
|
}
|
|
12614
12731
|
} catch (error) {
|