@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.
@@ -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("1.0.0")
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) => {
@@ -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
- const CLI_VERSION = "0.0.1";
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
- const CLI_VERSION = "0.0.1";
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 baseUrl = `${config.supatestApiUrl}/v1/sessions/${sessionId}/anthropic`;
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, updateStats, } = useSession();
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, initialTokens }, key: `${taskId}`, onComplete: handleAgentComplete, sessionId: sessionId }))));
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");
@@ -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
- const message = authState === AuthState.Authenticating
15
- ? "Authenticating..."
16
- : "Not logged in. Type /login to authenticate.";
17
- const color = authState === AuthState.Authenticating ? theme.text.info : theme.text.warning;
18
- return (React.createElement(Box, { marginBottom: 0, paddingX: 1 },
19
- React.createElement(Text, { color: color }, message)));
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 = ({ version, currentFolder, gitBranch }) => {
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, stats } = useSession();
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, justifyContent: "space-between" },
283
- React.createElement(Box, null,
284
- React.createElement(Text, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "⏸ plan mode on" : "▶ build mode"),
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", version: "0.0.2" }),
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();
@@ -0,0 +1,6 @@
1
+ /**
2
+ * CLI Version - Single source of truth
3
+ * Update this when releasing a new version
4
+ * Keep in sync with package.json version
5
+ */
6
+ export const CLI_VERSION = "0.0.5";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supatest/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Supatest CLI - AI-powered task automation for CI/CD",
5
5
  "type": "module",
6
6
  "bin": {