@supatest/cli 0.0.3 → 0.0.4

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 (76) hide show
  1. package/dist/index.js +6586 -153
  2. package/package.json +4 -3
  3. package/dist/agent-runner.js +0 -589
  4. package/dist/commands/login.js +0 -392
  5. package/dist/commands/setup.js +0 -234
  6. package/dist/config.js +0 -29
  7. package/dist/core/agent.js +0 -270
  8. package/dist/modes/headless.js +0 -117
  9. package/dist/modes/interactive.js +0 -430
  10. package/dist/presenters/composite.js +0 -32
  11. package/dist/presenters/console.js +0 -163
  12. package/dist/presenters/react.js +0 -220
  13. package/dist/presenters/types.js +0 -1
  14. package/dist/presenters/web.js +0 -78
  15. package/dist/prompts/builder.js +0 -181
  16. package/dist/prompts/fixer.js +0 -148
  17. package/dist/prompts/headless.md +0 -97
  18. package/dist/prompts/index.js +0 -3
  19. package/dist/prompts/interactive.md +0 -43
  20. package/dist/prompts/plan.md +0 -41
  21. package/dist/prompts/planner.js +0 -70
  22. package/dist/prompts/prompts/builder.md +0 -97
  23. package/dist/prompts/prompts/fixer.md +0 -100
  24. package/dist/prompts/prompts/plan.md +0 -41
  25. package/dist/prompts/prompts/planner.md +0 -41
  26. package/dist/services/api-client.js +0 -244
  27. package/dist/services/event-streamer.js +0 -130
  28. package/dist/types.js +0 -1
  29. package/dist/ui/App.js +0 -322
  30. package/dist/ui/components/AuthBanner.js +0 -20
  31. package/dist/ui/components/AuthDialog.js +0 -32
  32. package/dist/ui/components/Banner.js +0 -12
  33. package/dist/ui/components/ExpandableSection.js +0 -17
  34. package/dist/ui/components/Header.js +0 -49
  35. package/dist/ui/components/HelpMenu.js +0 -89
  36. package/dist/ui/components/InputPrompt.js +0 -292
  37. package/dist/ui/components/MessageList.js +0 -42
  38. package/dist/ui/components/QueuedMessageDisplay.js +0 -31
  39. package/dist/ui/components/Scrollable.js +0 -103
  40. package/dist/ui/components/SessionSelector.js +0 -196
  41. package/dist/ui/components/StatusBar.js +0 -45
  42. package/dist/ui/components/messages/AssistantMessage.js +0 -20
  43. package/dist/ui/components/messages/ErrorMessage.js +0 -26
  44. package/dist/ui/components/messages/LoadingMessage.js +0 -28
  45. package/dist/ui/components/messages/ThinkingMessage.js +0 -17
  46. package/dist/ui/components/messages/TodoMessage.js +0 -44
  47. package/dist/ui/components/messages/ToolMessage.js +0 -218
  48. package/dist/ui/components/messages/UserMessage.js +0 -14
  49. package/dist/ui/contexts/KeypressContext.js +0 -527
  50. package/dist/ui/contexts/MouseContext.js +0 -98
  51. package/dist/ui/contexts/SessionContext.js +0 -131
  52. package/dist/ui/hooks/useAnimatedScrollbar.js +0 -83
  53. package/dist/ui/hooks/useBatchedScroll.js +0 -22
  54. package/dist/ui/hooks/useBracketedPaste.js +0 -31
  55. package/dist/ui/hooks/useFocus.js +0 -50
  56. package/dist/ui/hooks/useKeypress.js +0 -26
  57. package/dist/ui/hooks/useModeToggle.js +0 -25
  58. package/dist/ui/types/auth.js +0 -13
  59. package/dist/ui/utils/file-completion.js +0 -56
  60. package/dist/ui/utils/input.js +0 -50
  61. package/dist/ui/utils/markdown.js +0 -376
  62. package/dist/ui/utils/mouse.js +0 -189
  63. package/dist/ui/utils/theme.js +0 -59
  64. package/dist/utils/banner.js +0 -9
  65. package/dist/utils/encryption.js +0 -71
  66. package/dist/utils/events.js +0 -36
  67. package/dist/utils/keychain-storage.js +0 -120
  68. package/dist/utils/logger.js +0 -209
  69. package/dist/utils/node-version.js +0 -89
  70. package/dist/utils/plan-file.js +0 -75
  71. package/dist/utils/project-instructions.js +0 -23
  72. package/dist/utils/rich-logger.js +0 -208
  73. package/dist/utils/stdin.js +0 -25
  74. package/dist/utils/stdio.js +0 -80
  75. package/dist/utils/summary.js +0 -94
  76. package/dist/utils/token-storage.js +0 -242
@@ -1,270 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import { dirname, join } from "node:path";
3
- import { query } from "@anthropic-ai/claude-agent-sdk";
4
- import { config as envConfig } from "../config";
5
- import { loadProjectInstructions } from "../utils/project-instructions";
6
- export class CoreAgent {
7
- presenter;
8
- abortController = null;
9
- constructor(presenter) {
10
- this.presenter = presenter;
11
- }
12
- /**
13
- * Abort the current query execution.
14
- * This will cancel any running operations including LLM calls and tool executions.
15
- */
16
- abort() {
17
- if (this.abortController) {
18
- this.abortController.abort();
19
- }
20
- }
21
- async run(config) {
22
- // Create a fresh AbortController for this run
23
- this.abortController = new AbortController();
24
- await this.presenter.onStart(config);
25
- // Resolve path to Claude Code executable
26
- const claudeCodePath = await this.resolveClaudeCodePath();
27
- // Build the prompt
28
- let prompt = config.task;
29
- if (config.logs) {
30
- prompt = `${config.task}\n\nHere are the logs to analyze:\n\`\`\`\n${config.logs}\n\`\`\``;
31
- }
32
- // Apply permission mode based on agent mode
33
- // Plan mode uses 'plan' permission which restricts to read-only tools
34
- // Build mode uses 'bypassPermissions' for full tool access
35
- const isPlanMode = config.mode === 'plan';
36
- const cwd = config.cwd || process.cwd();
37
- // Only load system prompt for new sessions - resumed sessions already have it
38
- // This avoids duplicating system prompt tokens on every continuation
39
- const isResumingSession = !!config.providerSessionId;
40
- let systemPromptAppend;
41
- if (!isResumingSession) {
42
- // Load project instructions from SUPATEST.md
43
- const projectInstructions = loadProjectInstructions(cwd);
44
- // Combine system prompts: base prompt + project instructions
45
- systemPromptAppend = [
46
- config.systemPromptAppend,
47
- projectInstructions && `\n\n# Project Instructions (from SUPATEST.md)\n\n${projectInstructions}`,
48
- ].filter(Boolean).join("\n") || undefined;
49
- }
50
- const queryOptions = {
51
- // AbortController for cancellation support
52
- abortController: this.abortController,
53
- maxTurns: config.maxIterations,
54
- cwd,
55
- model: envConfig.anthropicModelName,
56
- permissionMode: isPlanMode ? "plan" : "bypassPermissions",
57
- allowDangerouslySkipPermissions: !isPlanMode,
58
- pathToClaudeCodeExecutable: claudeCodePath,
59
- includePartialMessages: true,
60
- executable: "node",
61
- // MCP servers for enhanced capabilities
62
- mcpServers: {
63
- playwright: {
64
- command: "npx",
65
- args: ["-y", "@playwright/mcp@latest"],
66
- },
67
- },
68
- // Resume from previous session if providerSessionId is provided
69
- // This allows the agent to continue conversations with full context
70
- // Note: Sessions expire after ~30 days due to Anthropic's data retention policy
71
- ...(config.providerSessionId && {
72
- resume: config.providerSessionId,
73
- }),
74
- // Only append system prompt for new sessions - resumed sessions already have context
75
- ...(systemPromptAppend && {
76
- systemPrompt: {
77
- type: "preset",
78
- preset: "claude_code",
79
- append: systemPromptAppend,
80
- },
81
- }),
82
- env: {
83
- ...process.env,
84
- ANTHROPIC_API_KEY: config.supatestApiKey,
85
- ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL || "",
86
- ANTHROPIC_AUTH_TOKEN: "",
87
- CLAUDE_CODE_AUTH_TOKEN: "",
88
- },
89
- stderr: (msg) => {
90
- this.presenter.onLog(`[Claude Code stderr] ${msg}`);
91
- },
92
- };
93
- let resultText = "";
94
- let hasError = false;
95
- const errors = [];
96
- let iterations = 0;
97
- const filesModified = new Set();
98
- let wasInterrupted = false;
99
- // Capture the SDK's session_id for future resume capability
100
- let providerSessionId;
101
- // Track cumulative token usage
102
- let totalInputTokens = config.initialTokens || 0;
103
- let totalOutputTokens = 0;
104
- // Helper to check if an error indicates an expired/invalid session
105
- const isSessionExpiredError = (errorMsg) => {
106
- const expiredPatterns = [
107
- "no conversation found",
108
- "session not found",
109
- "session expired",
110
- "invalid session",
111
- ];
112
- const lowerError = errorMsg.toLowerCase();
113
- return expiredPatterns.some((pattern) => lowerError.includes(pattern));
114
- };
115
- // Helper to run the query and process messages
116
- const runQuery = async (options) => {
117
- for await (const msg of query({ prompt, options })) {
118
- // Capture session_id from any message that has it
119
- // All SDK messages include session_id which we need for resuming
120
- if ("session_id" in msg && msg.session_id) {
121
- providerSessionId = msg.session_id;
122
- }
123
- if (msg.type === "assistant") {
124
- iterations++;
125
- 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
- if (Array.isArray(content)) {
135
- for (const block of content) {
136
- if (block.type === "text") {
137
- resultText += block.text + "\n";
138
- await this.presenter.onAssistantText(block.text);
139
- }
140
- else if (block.type === "thinking") {
141
- await this.presenter.onThinking(block.thinking);
142
- }
143
- else if (block.type === "tool_use") {
144
- const toolName = block.name;
145
- const input = block.input;
146
- // Track file modifications
147
- if ((toolName === "Write" || toolName === "Edit") &&
148
- input?.file_path) {
149
- filesModified.add(input.file_path);
150
- }
151
- await this.presenter.onToolUse(toolName, input, block.id);
152
- }
153
- }
154
- }
155
- // Notify presenter that the turn is complete
156
- await this.presenter.onTurnComplete(content);
157
- }
158
- else if (msg.type === "result") {
159
- iterations = msg.num_turns;
160
- if (msg.subtype === "success") {
161
- resultText = msg.result || resultText;
162
- }
163
- else {
164
- hasError = true;
165
- if ("errors" in msg && Array.isArray(msg.errors)) {
166
- errors.push(...msg.errors);
167
- for (const error of msg.errors) {
168
- await this.presenter.onError(error);
169
- }
170
- }
171
- }
172
- }
173
- else if (msg.type === "user") {
174
- // User message contains tool results - end tool timing
175
- const userContent = msg.message?.content;
176
- if (Array.isArray(userContent)) {
177
- for (const block of userContent) {
178
- if (block.type === "tool_result" && block.tool_use_id) {
179
- // Notify presenter of tool result
180
- if (this.presenter.onToolResult) {
181
- const resultContent = Array.isArray(block.content)
182
- ? block.content.map((c) => c.text || "").join("\n")
183
- : typeof block.content === "string"
184
- ? block.content
185
- : "";
186
- await this.presenter.onToolResult(block.tool_use_id, resultContent);
187
- }
188
- }
189
- }
190
- }
191
- }
192
- }
193
- };
194
- try {
195
- await runQuery(queryOptions);
196
- }
197
- catch (error) {
198
- const errorMessage = error instanceof Error ? error.message : String(error);
199
- // Check if this was an abort (user interrupt)
200
- // The SDK may throw AbortError or a message containing "aborted"
201
- const isAbortError = (error instanceof Error && error.name === "AbortError") ||
202
- errorMessage.toLowerCase().includes("aborted");
203
- if (isAbortError) {
204
- wasInterrupted = true;
205
- }
206
- else if (config.providerSessionId && isSessionExpiredError(errorMessage)) {
207
- // If the error indicates an expired session and we were trying to resume,
208
- // show a user-friendly error message
209
- const expiredMessage = "Can't continue conversation older than 30 days. Please start a new session.";
210
- await this.presenter.onError(expiredMessage);
211
- hasError = true;
212
- errors.push(expiredMessage);
213
- }
214
- else {
215
- await this.presenter.onError(errorMessage);
216
- hasError = true;
217
- errors.push(errorMessage);
218
- }
219
- }
220
- const result = {
221
- success: !hasError && errors.length === 0 && !wasInterrupted,
222
- summary: wasInterrupted ? "Interrupted by user" : resultText || "Task completed",
223
- filesModified: Array.from(filesModified),
224
- iterations,
225
- error: wasInterrupted
226
- ? "Interrupted by user"
227
- : errors.length > 0
228
- ? errors.join("; ")
229
- : undefined,
230
- // Include the provider session ID for resume capability
231
- providerSessionId,
232
- };
233
- await this.presenter.onComplete(result);
234
- return result;
235
- }
236
- async resolveClaudeCodePath() {
237
- // Allow override via environment variable
238
- if (envConfig.claudeCodeExecutablePath) {
239
- this.presenter.onLog(`Using CLAUDE_CODE_EXECUTABLE_PATH: ${envConfig.claudeCodeExecutablePath}`);
240
- return envConfig.claudeCodeExecutablePath;
241
- }
242
- // Determine binary directory
243
- const isCompiledBinary = process.execPath && !process.execPath.includes("node");
244
- let claudeCodePath;
245
- if (isCompiledBinary) {
246
- claudeCodePath = join(dirname(process.execPath), "claude-code-cli.js");
247
- this.presenter.onLog(`Production mode: ${claudeCodePath}`);
248
- }
249
- else {
250
- const require = createRequire(import.meta.url);
251
- const sdkPath = require.resolve("@anthropic-ai/claude-agent-sdk/sdk.mjs");
252
- claudeCodePath = join(dirname(sdkPath), "cli.js");
253
- this.presenter.onLog(`Development mode: ${claudeCodePath}`);
254
- }
255
- // Verify the file exists
256
- const fs = await import("node:fs/promises");
257
- try {
258
- await fs.access(claudeCodePath);
259
- this.presenter.onLog(`✓ Claude Code CLI found: ${claudeCodePath}`);
260
- }
261
- catch {
262
- const error = `Claude Code executable not found at: ${claudeCodePath}\n` +
263
- "For compiled binaries, ensure claude-code-cli.js is in the same directory as the binary.\n" +
264
- "Set CLAUDE_CODE_EXECUTABLE_PATH environment variable to override.";
265
- await this.presenter.onError(error);
266
- throw new Error(error);
267
- }
268
- return claudeCodePath;
269
- }
270
- }
@@ -1,117 +0,0 @@
1
- import chalk from "chalk";
2
- import { config as envConfig } from "../config";
3
- import { CoreAgent } from "../core/agent";
4
- import { CompositePresenter } from "../presenters/composite";
5
- import { ConsolePresenter } from "../presenters/console";
6
- import { WebPresenter } from "../presenters/web";
7
- import { ApiClient } from "../services/api-client";
8
- import { logger } from "../utils/logger";
9
- const CLI_VERSION = "0.0.1";
10
- export async function runAgent(config) {
11
- // Configure logger
12
- logger.setVerbose(config.verbose);
13
- // --- Metadata Display (CLI only) ---
14
- logger.raw("");
15
- // Get git branch if available
16
- let gitBranch = "";
17
- try {
18
- const { execSync } = await import("node:child_process");
19
- gitBranch = execSync("git rev-parse --abbrev-ref HEAD", {
20
- encoding: "utf8",
21
- stdio: ["pipe", "pipe", "ignore"]
22
- }).trim();
23
- }
24
- catch {
25
- // Not in a git repo or git not available
26
- }
27
- const metadataParts = [
28
- chalk.dim("Supatest AI ") + chalk.cyan(`v${CLI_VERSION}`),
29
- chalk.dim("Model: ") + chalk.cyan(envConfig.anthropicModelName),
30
- ];
31
- if (gitBranch) {
32
- metadataParts.push(chalk.dim("Branch: ") + chalk.cyan(gitBranch));
33
- }
34
- logger.raw(metadataParts.join(chalk.dim(" • ")));
35
- logger.divider();
36
- // --- Session & API Setup ---
37
- const apiUrl = config.supatestApiUrl || "https://api.supatest.ai";
38
- const apiClient = new ApiClient(apiUrl, config.supatestApiKey);
39
- let sessionId;
40
- let webUrl;
41
- try {
42
- // Truncate title to 50 characters (backend will auto-generate a better title later)
43
- const truncatedTitle = config.task.length > 50 ? config.task.slice(0, 50) + "..." : config.task;
44
- const session = await apiClient.createSession(truncatedTitle, {
45
- cliVersion: CLI_VERSION,
46
- cwd: config.cwd || process.cwd(),
47
- });
48
- sessionId = session.sessionId;
49
- webUrl = session.webUrl;
50
- logger.raw("");
51
- logger.divider();
52
- logger.raw(chalk.white.bold("View session live: ") +
53
- chalk.cyan.underline(webUrl));
54
- logger.divider();
55
- logger.raw("");
56
- }
57
- catch (error) {
58
- logger.warn(`Failed to create session on backend: ${error.message}`);
59
- logger.warn("Continuing without web streaming...");
60
- }
61
- // --- Environment Setup ---
62
- // Build base URL with session ID embedded for the proxy
63
- let baseUrl = `${apiUrl}/public`;
64
- if (sessionId) {
65
- baseUrl = `${apiUrl}/v1/sessions/${sessionId}/anthropic`;
66
- }
67
- // Set environment variables for the SDK to pick up (via CoreAgent)
68
- process.env.ANTHROPIC_BASE_URL = baseUrl;
69
- process.env.ANTHROPIC_API_KEY = config.supatestApiKey;
70
- // --- Agent Execution ---
71
- const presenters = [];
72
- // 1. Console Presenter (stdout)
73
- presenters.push(new ConsolePresenter({ verbose: config.verbose }));
74
- // 2. Web Presenter (streaming)
75
- if (sessionId) {
76
- presenters.push(new WebPresenter(apiClient, sessionId));
77
- }
78
- const compositePresenter = new CompositePresenter(presenters);
79
- const agent = new CoreAgent(compositePresenter);
80
- try {
81
- const result = await agent.run(config);
82
- // Store the provider session ID for future resume capability
83
- // This allows follow-up messages to continue the conversation with full context
84
- if (sessionId && result.providerSessionId) {
85
- try {
86
- await apiClient.updateSession(sessionId, {
87
- providerSessionId: result.providerSessionId,
88
- });
89
- logger.debug(`Stored provider session ID for resume capability`);
90
- }
91
- catch (updateError) {
92
- // Non-critical - log but don't fail
93
- logger.warn(`Failed to store provider session ID: ${updateError.message}`);
94
- }
95
- }
96
- // Display web URL again at completion
97
- if (webUrl) {
98
- logger.raw("");
99
- logger.divider();
100
- logger.raw(chalk.white.bold("View session: ") +
101
- chalk.cyan.underline(webUrl));
102
- logger.divider();
103
- }
104
- return result;
105
- }
106
- catch (error) {
107
- const errorMessage = error instanceof Error ? error.message : String(error);
108
- // Error is already logged by presenter.onError
109
- return {
110
- success: false,
111
- summary: `Failed: ${errorMessage}`,
112
- filesModified: [],
113
- iterations: 0,
114
- error: errorMessage,
115
- };
116
- }
117
- }