@supatest/cli 0.0.3 → 0.0.5
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/core/agent.js +0 -11
- package/dist/index.js +3 -2
- package/dist/modes/headless.js +2 -2
- package/dist/modes/interactive.js +6 -18
- package/dist/presenters/react.js +0 -3
- package/dist/ui/components/AuthBanner.js +10 -6
- package/dist/ui/components/Header.js +3 -1
- package/dist/ui/components/InputPrompt.js +4 -10
- package/dist/ui/components/MessageList.js +1 -1
- package/dist/ui/components/StatusBar.js +1 -12
- package/dist/ui/contexts/SessionContext.js +0 -2
- package/dist/version.js +6 -0
- package/package.json +1 -1
- package/dist/agent-runner.js +0 -589
- package/dist/prompts/headless.md +0 -97
- package/dist/prompts/interactive.md +0 -43
- package/dist/prompts/plan.md +0 -41
- package/dist/prompts/prompts/builder.md +0 -97
- package/dist/prompts/prompts/fixer.md +0 -100
- package/dist/prompts/prompts/plan.md +0 -41
- package/dist/prompts/prompts/planner.md +0 -41
package/dist/core/agent.js
CHANGED
|
@@ -98,9 +98,6 @@ export class CoreAgent {
|
|
|
98
98
|
let wasInterrupted = false;
|
|
99
99
|
// Capture the SDK's session_id for future resume capability
|
|
100
100
|
let providerSessionId;
|
|
101
|
-
// Track cumulative token usage
|
|
102
|
-
let totalInputTokens = config.initialTokens || 0;
|
|
103
|
-
let totalOutputTokens = 0;
|
|
104
101
|
// Helper to check if an error indicates an expired/invalid session
|
|
105
102
|
const isSessionExpiredError = (errorMsg) => {
|
|
106
103
|
const expiredPatterns = [
|
|
@@ -123,14 +120,6 @@ export class CoreAgent {
|
|
|
123
120
|
if (msg.type === "assistant") {
|
|
124
121
|
iterations++;
|
|
125
122
|
const content = msg.message.content;
|
|
126
|
-
// Extract and accumulate token usage from this turn
|
|
127
|
-
const usage = msg.message.usage;
|
|
128
|
-
if (usage) {
|
|
129
|
-
totalInputTokens += usage.input_tokens || 0;
|
|
130
|
-
totalOutputTokens += usage.output_tokens || 0;
|
|
131
|
-
// Notify presenter of updated token count
|
|
132
|
-
await this.presenter.onUsageUpdate?.(totalInputTokens + totalOutputTokens);
|
|
133
|
-
}
|
|
134
123
|
if (Array.isArray(content)) {
|
|
135
124
|
for (const block of content) {
|
|
136
125
|
if (block.type === "text") {
|
package/dist/index.js
CHANGED
|
@@ -9,19 +9,20 @@ import { logger } from "./utils/logger";
|
|
|
9
9
|
import { checkNodeVersion } from "./utils/node-version";
|
|
10
10
|
import { readStdin } from "./utils/stdin";
|
|
11
11
|
import { loadTokenAsync } from "./utils/token-storage";
|
|
12
|
+
import { CLI_VERSION } from "./version";
|
|
12
13
|
const program = new Command();
|
|
13
14
|
// Main run command (default)
|
|
14
15
|
program
|
|
15
16
|
.name("supatest")
|
|
16
17
|
.description("AI-powered task automation CLI for CI/CD - fix tests, lint issues, and more")
|
|
17
|
-
.version(
|
|
18
|
+
.version(CLI_VERSION)
|
|
18
19
|
.argument("[task]", "Task description or prompt for the AI agent")
|
|
19
20
|
.option("-l, --logs <file>", "Path to log file to analyze")
|
|
20
21
|
.option("--stdin", "Read logs from stdin")
|
|
21
22
|
.option("-C, --cwd <path>", "Working directory for the agent", process.cwd())
|
|
22
23
|
.option("-m, --max-iterations <number>", "Maximum number of iterations", "100")
|
|
23
24
|
.option("--supatest-api-key <key>", "Supatest API key (or use SUPATEST_API_KEY env)")
|
|
24
|
-
.option("--supatest-api-url <url>", "Supatest API URL (or use SUPATEST_API_URL env, defaults to https://api.supatest.ai)")
|
|
25
|
+
.option("--supatest-api-url <url>", "Supatest API URL (or use SUPATEST_API_URL env, defaults to https://code-api.supatest.ai)")
|
|
25
26
|
.option("--headless", "Run in headless mode (for CI/CD, minimal output)")
|
|
26
27
|
.option("--verbose", "Enable verbose logging")
|
|
27
28
|
.action(async (task, options) => {
|
package/dist/modes/headless.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ConsolePresenter } from "../presenters/console";
|
|
|
6
6
|
import { WebPresenter } from "../presenters/web";
|
|
7
7
|
import { ApiClient } from "../services/api-client";
|
|
8
8
|
import { logger } from "../utils/logger";
|
|
9
|
-
|
|
9
|
+
import { CLI_VERSION } from "../version";
|
|
10
10
|
export async function runAgent(config) {
|
|
11
11
|
// Configure logger
|
|
12
12
|
logger.setVerbose(config.verbose);
|
|
@@ -34,7 +34,7 @@ export async function runAgent(config) {
|
|
|
34
34
|
logger.raw(metadataParts.join(chalk.dim(" • ")));
|
|
35
35
|
logger.divider();
|
|
36
36
|
// --- Session & API Setup ---
|
|
37
|
-
const apiUrl = config.supatestApiUrl || "https://api.supatest.ai";
|
|
37
|
+
const apiUrl = config.supatestApiUrl || "https://code-api.supatest.ai";
|
|
38
38
|
const apiClient = new ApiClient(apiUrl, config.supatestApiKey);
|
|
39
39
|
let sessionId;
|
|
40
40
|
let webUrl;
|
|
@@ -16,7 +16,7 @@ import { useBracketedPaste } from "../ui/hooks/useBracketedPaste";
|
|
|
16
16
|
import { disableMouseEvents, enableMouseEvents } from "../ui/utils/mouse";
|
|
17
17
|
import { logger } from "../utils/logger";
|
|
18
18
|
import { createInkStdio, patchStdio } from "../utils/stdio";
|
|
19
|
-
|
|
19
|
+
import { CLI_VERSION } from "../version";
|
|
20
20
|
/**
|
|
21
21
|
* Get human-readable description for tool call (used when resuming sessions)
|
|
22
22
|
*/
|
|
@@ -71,7 +71,8 @@ const AgentRunner = ({ config, sessionId, apiClient, onComplete }) => {
|
|
|
71
71
|
setIsAgentRunning(true);
|
|
72
72
|
try {
|
|
73
73
|
// Set up environment for Claude Code
|
|
74
|
-
const
|
|
74
|
+
const proxyUrl = config.supatestApiUrl || "https://code-api.supatest.ai";
|
|
75
|
+
const baseUrl = `${proxyUrl}/v1/sessions/${sessionId}/anthropic`;
|
|
75
76
|
process.env.ANTHROPIC_BASE_URL = baseUrl;
|
|
76
77
|
process.env.ANTHROPIC_API_KEY = config.supatestApiKey;
|
|
77
78
|
// Create presenter with callbacks to React state
|
|
@@ -153,7 +154,7 @@ const AgentRunner = ({ config, sessionId, apiClient, onComplete }) => {
|
|
|
153
154
|
* Content component for InteractiveApp that has access to SessionContext
|
|
154
155
|
*/
|
|
155
156
|
const InteractiveAppContent = ({ config, sessionId: initialSessionId, webUrl, apiClient, onExit }) => {
|
|
156
|
-
const { addMessage, loadMessages, setSessionId: setContextSessionId,
|
|
157
|
+
const { addMessage, loadMessages, setSessionId: setContextSessionId, } = useSession();
|
|
157
158
|
const [sessionId, setSessionId] = React.useState(initialSessionId);
|
|
158
159
|
const [currentTask, setCurrentTask] = React.useState(config.task);
|
|
159
160
|
const [taskId, setTaskId] = React.useState(0);
|
|
@@ -161,8 +162,6 @@ const InteractiveAppContent = ({ config, sessionId: initialSessionId, webUrl, ap
|
|
|
161
162
|
const [taskQueue, setTaskQueue] = React.useState([]);
|
|
162
163
|
// Track provider session ID for resume capability
|
|
163
164
|
const [providerSessionId, setProviderSessionId] = React.useState();
|
|
164
|
-
// Track initial tokens for resumed sessions
|
|
165
|
-
const [initialTokens, setInitialTokens] = React.useState(0);
|
|
166
165
|
const handleSubmitTask = async (task) => {
|
|
167
166
|
// Create session on first message if it doesn't exist
|
|
168
167
|
if (!sessionId) {
|
|
@@ -312,17 +311,6 @@ const InteractiveAppContent = ({ config, sessionId: initialSessionId, webUrl, ap
|
|
|
312
311
|
if (session.providerSessionId) {
|
|
313
312
|
setProviderSessionId(session.providerSessionId);
|
|
314
313
|
}
|
|
315
|
-
// Fetch session details to get totalTokens for resume
|
|
316
|
-
try {
|
|
317
|
-
const sessionDetails = await apiClient.getSession(session.id);
|
|
318
|
-
if (sessionDetails.totalTokens) {
|
|
319
|
-
setInitialTokens(sessionDetails.totalTokens);
|
|
320
|
-
updateStats({ totalTokens: sessionDetails.totalTokens });
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
catch {
|
|
324
|
-
// Non-critical - stats will start from 0
|
|
325
|
-
}
|
|
326
314
|
uiMessages.push({
|
|
327
315
|
id: `resume-info-${Date.now()}`,
|
|
328
316
|
type: "error",
|
|
@@ -345,7 +333,7 @@ const InteractiveAppContent = ({ config, sessionId: initialSessionId, webUrl, ap
|
|
|
345
333
|
});
|
|
346
334
|
}
|
|
347
335
|
}, onSubmitTask: handleSubmitTask, queuedTasks: taskQueue, sessionId: sessionId, webUrl: webUrl }),
|
|
348
|
-
shouldRunAgent && currentTask && sessionId && (React.createElement(AgentRunner, { apiClient: apiClient, config: { ...config, task: currentTask, providerSessionId
|
|
336
|
+
shouldRunAgent && currentTask && sessionId && (React.createElement(AgentRunner, { apiClient: apiClient, config: { ...config, task: currentTask, providerSessionId }, key: `${taskId}`, onComplete: handleAgentComplete, sessionId: sessionId }))));
|
|
349
337
|
};
|
|
350
338
|
/**
|
|
351
339
|
* Main wrapper component that combines UI and agent
|
|
@@ -383,7 +371,7 @@ export async function runInteractive(config) {
|
|
|
383
371
|
process.on("SIGTERM", handleSigInt);
|
|
384
372
|
const isDev = process.env.NODE_ENV === "development";
|
|
385
373
|
logger.enableFileLogging(isDev);
|
|
386
|
-
const apiUrl = config.supatestApiUrl || "https://api.supatest.ai";
|
|
374
|
+
const apiUrl = config.supatestApiUrl || "https://code-api.supatest.ai";
|
|
387
375
|
const apiClient = new ApiClient(apiUrl, config.supatestApiKey);
|
|
388
376
|
try {
|
|
389
377
|
process.stdout.write("\x1Bc");
|
package/dist/presenters/react.js
CHANGED
|
@@ -178,9 +178,6 @@ export class ReactPresenter {
|
|
|
178
178
|
};
|
|
179
179
|
await this.apiClient.streamEvent(this.sessionId, toolResultEvent);
|
|
180
180
|
}
|
|
181
|
-
async onUsageUpdate(totalTokens) {
|
|
182
|
-
this.callbacks.updateStats({ totalTokens });
|
|
183
|
-
}
|
|
184
181
|
async onTurnComplete(content) {
|
|
185
182
|
// Stream message complete to API
|
|
186
183
|
const messageCompleteEvent = {
|
|
@@ -11,10 +11,14 @@ export const AuthBanner = ({ authState }) => {
|
|
|
11
11
|
if (authState === AuthState.Authenticated) {
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
if (authState === AuthState.Authenticating) {
|
|
15
|
+
return (React.createElement(Box, { marginBottom: 0, paddingX: 1 },
|
|
16
|
+
React.createElement(Text, { color: theme.text.info }, "Authenticating...")));
|
|
17
|
+
}
|
|
18
|
+
// Not logged in - show prominent banner
|
|
19
|
+
return (React.createElement(Box, { marginBottom: 0, paddingX: 1, paddingY: 0, borderStyle: "round", borderColor: theme.text.warning },
|
|
20
|
+
React.createElement(Text, { color: theme.text.warning, bold: true }, "Not logged in"),
|
|
21
|
+
React.createElement(Text, { color: theme.text.dim }, " - Type "),
|
|
22
|
+
React.createElement(Text, { color: theme.text.info }, "/login"),
|
|
23
|
+
React.createElement(Text, { color: theme.text.dim }, " to authenticate")));
|
|
20
24
|
};
|
|
@@ -2,8 +2,10 @@ import { Box, Text } from "ink";
|
|
|
2
2
|
import Gradient from "ink-gradient";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { getBanner } from "../../utils/banner.js";
|
|
5
|
+
import { CLI_VERSION } from "../../version.js";
|
|
5
6
|
import { theme } from "../utils/theme.js";
|
|
6
|
-
export const Header = ({
|
|
7
|
+
export const Header = ({ currentFolder, gitBranch }) => {
|
|
8
|
+
const version = CLI_VERSION;
|
|
7
9
|
const banner = getBanner();
|
|
8
10
|
// Build the info line: "v0.0.2 • ~/path/to/folder on branch"
|
|
9
11
|
const infoParts = [`v${version}`];
|
|
@@ -14,7 +14,7 @@ import { useKeypress } from "../hooks/useKeypress.js";
|
|
|
14
14
|
import { getFiles } from "../utils/file-completion.js";
|
|
15
15
|
import { theme } from "../utils/theme.js";
|
|
16
16
|
export const InputPrompt = forwardRef(({ onSubmit, placeholder = "Enter your task (press Enter to submit, Shift+Enter for new line)...", disabled = false, onHelpToggle, currentFolder, gitBranch, onInputChange, }, ref) => {
|
|
17
|
-
const { messages, agentMode
|
|
17
|
+
const { messages, agentMode } = useSession();
|
|
18
18
|
const [value, setValue] = useState("");
|
|
19
19
|
const [cursorOffset, setCursorOffset] = useState(0);
|
|
20
20
|
// Autocomplete State
|
|
@@ -279,14 +279,8 @@ export const InputPrompt = forwardRef(({ onSubmit, placeholder = "Enter your tas
|
|
|
279
279
|
return (React.createElement(Text, { color: theme.text.primary, key: idx }, line));
|
|
280
280
|
}))),
|
|
281
281
|
!hasContent && disabled && (React.createElement(Text, { color: theme.text.dim, italic: true }, "Waiting for agent to complete..."))))),
|
|
282
|
-
React.createElement(Box, { paddingX: 1
|
|
283
|
-
React.createElement(
|
|
284
|
-
|
|
285
|
-
React.createElement(Text, { color: theme.text.dim }, " (shift+tab to cycle)")),
|
|
286
|
-
React.createElement(Text, { color: theme.text.dim }, stats.totalTokens >= 1000000
|
|
287
|
-
? `${(stats.totalTokens / 1000000).toFixed(1)}M tokens`
|
|
288
|
-
: stats.totalTokens >= 1000
|
|
289
|
-
? `${(stats.totalTokens / 1000).toFixed(1)}k tokens`
|
|
290
|
-
: `${stats.totalTokens} tokens`))));
|
|
282
|
+
React.createElement(Box, { paddingX: 1 },
|
|
283
|
+
React.createElement(Text, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "⏸ plan mode on" : "▶ build mode"),
|
|
284
|
+
React.createElement(Text, { color: theme.text.dim }, " (shift+tab to cycle)"))));
|
|
291
285
|
});
|
|
292
286
|
InputPrompt.displayName = "InputPrompt";
|
|
@@ -36,7 +36,7 @@ export const MessageList = ({ terminalWidth, currentFolder, gitBranch }) => {
|
|
|
36
36
|
};
|
|
37
37
|
return (React.createElement(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden" },
|
|
38
38
|
React.createElement(Scrollable, { scrollToBottom: true },
|
|
39
|
-
React.createElement(Header, { currentFolder: currentFolder, gitBranch: gitBranch, key: "header"
|
|
39
|
+
React.createElement(Header, { currentFolder: currentFolder, gitBranch: gitBranch, key: "header" }),
|
|
40
40
|
messages.map((message) => (React.createElement(Box, { flexDirection: "column", key: message.id, width: "100%" }, renderMessage(message)))),
|
|
41
41
|
isAgentRunning && !messages.some(m => m.type === "assistant" && m.isPending) && (React.createElement(LoadingMessage, { key: "loading" })))));
|
|
42
42
|
};
|
|
@@ -6,13 +6,6 @@ import { Box, Text } from "ink";
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { useSession } from "../contexts/SessionContext.js";
|
|
8
8
|
import { theme } from "../utils/theme.js";
|
|
9
|
-
function formatTokens(tokens) {
|
|
10
|
-
if (tokens >= 1000000)
|
|
11
|
-
return `${(tokens / 1000000).toFixed(1)}M`;
|
|
12
|
-
if (tokens >= 1000)
|
|
13
|
-
return `${(tokens / 1000).toFixed(1)}k`;
|
|
14
|
-
return tokens.toString();
|
|
15
|
-
}
|
|
16
9
|
export const StatusBar = () => {
|
|
17
10
|
const { stats, webUrl, isAgentRunning } = useSession();
|
|
18
11
|
const elapsedSeconds = Math.floor((Date.now() - stats.startTime) / 1000);
|
|
@@ -32,11 +25,7 @@ export const StatusBar = () => {
|
|
|
32
25
|
React.createElement(Text, { color: theme.text.dim }, " \u2022 "),
|
|
33
26
|
React.createElement(Text, { color: theme.text.dim },
|
|
34
27
|
"Time: ",
|
|
35
|
-
React.createElement(Text, { color: theme.text.accent }, timeStr)),
|
|
36
|
-
React.createElement(Text, { color: theme.text.dim }, " \u2022 "),
|
|
37
|
-
React.createElement(Text, { color: theme.text.dim },
|
|
38
|
-
"Tokens: ",
|
|
39
|
-
React.createElement(Text, { color: theme.text.accent }, formatTokens(stats.totalTokens)))),
|
|
28
|
+
React.createElement(Text, { color: theme.text.accent }, timeStr))),
|
|
40
29
|
React.createElement(Box, null, isAgentRunning ? (React.createElement(Text, { color: theme.status.inProgress }, "\u25CF Running")) : (React.createElement(Text, { color: theme.status.completed }, "\u25CF Ready")))),
|
|
41
30
|
webUrl && (React.createElement(Box, { marginTop: 0 },
|
|
42
31
|
React.createElement(Text, { color: theme.text.dim },
|
|
@@ -12,7 +12,6 @@ export const SessionProvider = ({ children, }) => {
|
|
|
12
12
|
commandsRun: [],
|
|
13
13
|
iterations: 0,
|
|
14
14
|
startTime: Date.now(),
|
|
15
|
-
totalTokens: 0,
|
|
16
15
|
});
|
|
17
16
|
const [isAgentRunning, setIsAgentRunning] = useState(false);
|
|
18
17
|
const [shouldInterruptAgent, setShouldInterruptAgent] = useState(false);
|
|
@@ -70,7 +69,6 @@ export const SessionProvider = ({ children, }) => {
|
|
|
70
69
|
commandsRun: [],
|
|
71
70
|
iterations: 0,
|
|
72
71
|
startTime: Date.now(),
|
|
73
|
-
totalTokens: 0,
|
|
74
72
|
});
|
|
75
73
|
// Clear the terminal screen (similar to Gemini CLI behavior)
|
|
76
74
|
console.clear();
|
package/dist/version.js
ADDED