@supatest/cli 0.0.5 → 0.0.6

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 (69) hide show
  1. package/dist/index.js +9485 -157
  2. package/package.json +8 -5
  3. package/dist/commands/login.js +0 -392
  4. package/dist/commands/setup.js +0 -234
  5. package/dist/config.js +0 -29
  6. package/dist/core/agent.js +0 -259
  7. package/dist/modes/headless.js +0 -117
  8. package/dist/modes/interactive.js +0 -418
  9. package/dist/presenters/composite.js +0 -32
  10. package/dist/presenters/console.js +0 -163
  11. package/dist/presenters/react.js +0 -217
  12. package/dist/presenters/types.js +0 -1
  13. package/dist/presenters/web.js +0 -78
  14. package/dist/prompts/builder.js +0 -181
  15. package/dist/prompts/fixer.js +0 -148
  16. package/dist/prompts/index.js +0 -3
  17. package/dist/prompts/planner.js +0 -70
  18. package/dist/services/api-client.js +0 -244
  19. package/dist/services/event-streamer.js +0 -130
  20. package/dist/types.js +0 -1
  21. package/dist/ui/App.js +0 -322
  22. package/dist/ui/components/AuthBanner.js +0 -24
  23. package/dist/ui/components/AuthDialog.js +0 -32
  24. package/dist/ui/components/Banner.js +0 -12
  25. package/dist/ui/components/ExpandableSection.js +0 -17
  26. package/dist/ui/components/Header.js +0 -51
  27. package/dist/ui/components/HelpMenu.js +0 -89
  28. package/dist/ui/components/InputPrompt.js +0 -286
  29. package/dist/ui/components/MessageList.js +0 -42
  30. package/dist/ui/components/QueuedMessageDisplay.js +0 -31
  31. package/dist/ui/components/Scrollable.js +0 -103
  32. package/dist/ui/components/SessionSelector.js +0 -196
  33. package/dist/ui/components/StatusBar.js +0 -34
  34. package/dist/ui/components/messages/AssistantMessage.js +0 -20
  35. package/dist/ui/components/messages/ErrorMessage.js +0 -26
  36. package/dist/ui/components/messages/LoadingMessage.js +0 -28
  37. package/dist/ui/components/messages/ThinkingMessage.js +0 -17
  38. package/dist/ui/components/messages/TodoMessage.js +0 -44
  39. package/dist/ui/components/messages/ToolMessage.js +0 -218
  40. package/dist/ui/components/messages/UserMessage.js +0 -14
  41. package/dist/ui/contexts/KeypressContext.js +0 -527
  42. package/dist/ui/contexts/MouseContext.js +0 -98
  43. package/dist/ui/contexts/SessionContext.js +0 -129
  44. package/dist/ui/hooks/useAnimatedScrollbar.js +0 -83
  45. package/dist/ui/hooks/useBatchedScroll.js +0 -22
  46. package/dist/ui/hooks/useBracketedPaste.js +0 -31
  47. package/dist/ui/hooks/useFocus.js +0 -50
  48. package/dist/ui/hooks/useKeypress.js +0 -26
  49. package/dist/ui/hooks/useModeToggle.js +0 -25
  50. package/dist/ui/types/auth.js +0 -13
  51. package/dist/ui/utils/file-completion.js +0 -56
  52. package/dist/ui/utils/input.js +0 -50
  53. package/dist/ui/utils/markdown.js +0 -376
  54. package/dist/ui/utils/mouse.js +0 -189
  55. package/dist/ui/utils/theme.js +0 -59
  56. package/dist/utils/banner.js +0 -9
  57. package/dist/utils/encryption.js +0 -71
  58. package/dist/utils/events.js +0 -36
  59. package/dist/utils/keychain-storage.js +0 -120
  60. package/dist/utils/logger.js +0 -209
  61. package/dist/utils/node-version.js +0 -89
  62. package/dist/utils/plan-file.js +0 -75
  63. package/dist/utils/project-instructions.js +0 -23
  64. package/dist/utils/rich-logger.js +0 -208
  65. package/dist/utils/stdin.js +0 -25
  66. package/dist/utils/stdio.js +0 -80
  67. package/dist/utils/summary.js +0 -94
  68. package/dist/utils/token-storage.js +0 -242
  69. package/dist/version.js +0 -6
@@ -1,36 +0,0 @@
1
- /**
2
- * Simple event emitter for app-level events
3
- */
4
- export var AppEvent;
5
- (function (AppEvent) {
6
- AppEvent["PasteTimeout"] = "paste-timeout";
7
- AppEvent["OpenDebugConsole"] = "open-debug-console";
8
- AppEvent["SelectionWarning"] = "selection-warning";
9
- })(AppEvent || (AppEvent = {}));
10
- class EventEmitter {
11
- events = new Map();
12
- on(event, callback) {
13
- if (!this.events.has(event)) {
14
- this.events.set(event, []);
15
- }
16
- this.events.get(event).push(callback);
17
- }
18
- emit(event, ...args) {
19
- const callbacks = this.events.get(event);
20
- if (callbacks) {
21
- for (const callback of callbacks) {
22
- callback(...args);
23
- }
24
- }
25
- }
26
- off(event, callback) {
27
- const callbacks = this.events.get(event);
28
- if (callbacks) {
29
- const index = callbacks.indexOf(callback);
30
- if (index > -1) {
31
- callbacks.splice(index, 1);
32
- }
33
- }
34
- }
35
- }
36
- export const appEvents = new EventEmitter();
@@ -1,120 +0,0 @@
1
- /**
2
- * OS Keychain token storage using keytar.
3
- * Inspired by Gemini CLI's keychain-token-storage implementation.
4
- */
5
- import crypto from "node:crypto";
6
- const SERVICE_NAME = "supatest-cli";
7
- const ACCOUNT_NAME = "cli-token";
8
- const KEYCHAIN_TEST_PREFIX = "__keychain_test__";
9
- let keytarModule = null;
10
- let keytarLoadAttempted = false;
11
- let keychainAvailable = null;
12
- /**
13
- * Dynamically load the keytar module.
14
- * Returns null if keytar is not available (not installed or failed to load).
15
- */
16
- async function getKeytar() {
17
- if (keytarLoadAttempted) {
18
- return keytarModule;
19
- }
20
- keytarLoadAttempted = true;
21
- try {
22
- const moduleName = "keytar";
23
- const mod = await import(moduleName);
24
- keytarModule = mod.default || mod;
25
- }
26
- catch {
27
- // Keytar is optional, so we silently fall back to file storage
28
- keytarModule = null;
29
- }
30
- return keytarModule;
31
- }
32
- /**
33
- * Check if OS keychain is available via a set/get/delete test cycle.
34
- * Respects SUPATEST_FORCE_FILE_STORAGE environment variable.
35
- */
36
- export async function isKeychainAvailable() {
37
- if (keychainAvailable !== null) {
38
- return keychainAvailable;
39
- }
40
- if (process.env.SUPATEST_FORCE_FILE_STORAGE === "true") {
41
- keychainAvailable = false;
42
- return false;
43
- }
44
- try {
45
- const keytar = await getKeytar();
46
- if (!keytar) {
47
- keychainAvailable = false;
48
- return false;
49
- }
50
- // Test with a set/get/delete cycle
51
- const testAccount = `${KEYCHAIN_TEST_PREFIX}${crypto.randomBytes(8).toString("hex")}`;
52
- const testPassword = "test";
53
- await keytar.setPassword(SERVICE_NAME, testAccount, testPassword);
54
- const retrieved = await keytar.getPassword(SERVICE_NAME, testAccount);
55
- const deleted = await keytar.deletePassword(SERVICE_NAME, testAccount);
56
- keychainAvailable = deleted && retrieved === testPassword;
57
- }
58
- catch {
59
- keychainAvailable = false;
60
- }
61
- return keychainAvailable;
62
- }
63
- /**
64
- * Save token payload to OS keychain.
65
- * Returns true if successful, false otherwise.
66
- */
67
- export async function saveToKeychain(token, expiresAt) {
68
- const keytar = await getKeytar();
69
- if (!keytar) {
70
- return false;
71
- }
72
- const payload = {
73
- token,
74
- expiresAt,
75
- createdAt: new Date().toISOString(),
76
- };
77
- try {
78
- await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(payload));
79
- return true;
80
- }
81
- catch {
82
- return false;
83
- }
84
- }
85
- /**
86
- * Load token payload from OS keychain.
87
- * Returns null if not found or keychain unavailable.
88
- */
89
- export async function loadFromKeychain() {
90
- const keytar = await getKeytar();
91
- if (!keytar) {
92
- return null;
93
- }
94
- try {
95
- const data = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
96
- if (!data) {
97
- return null;
98
- }
99
- return JSON.parse(data);
100
- }
101
- catch {
102
- return null;
103
- }
104
- }
105
- /**
106
- * Remove token from OS keychain.
107
- * Returns true if successfully removed, false otherwise.
108
- */
109
- export async function removeFromKeychain() {
110
- const keytar = await getKeytar();
111
- if (!keytar) {
112
- return false;
113
- }
114
- try {
115
- return await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
116
- }
117
- catch {
118
- return false;
119
- }
120
- }
@@ -1,209 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import chalk from "chalk";
4
- class Logger {
5
- verbose = false;
6
- silent = false;
7
- logFile = null;
8
- isDev = false;
9
- setVerbose(enabled) {
10
- this.verbose = enabled;
11
- }
12
- setSilent(enabled) {
13
- this.silent = enabled;
14
- }
15
- isSilent() {
16
- return this.silent;
17
- }
18
- /**
19
- * Enable file logging (dev mode only)
20
- */
21
- enableFileLogging(isDev = false) {
22
- this.isDev = isDev;
23
- if (!isDev)
24
- return;
25
- // Write directly to cli.log in current directory
26
- this.logFile = path.join(process.cwd(), "cli.log");
27
- // Add session separator to log file
28
- const separator = `\n${"=".repeat(80)}\n[${new Date().toISOString()}] New CLI session started\n${"=".repeat(80)}\n`;
29
- try {
30
- fs.appendFileSync(this.logFile, separator);
31
- }
32
- catch (error) {
33
- // Silently fail
34
- }
35
- }
36
- /**
37
- * Write to log file (dev mode only)
38
- */
39
- writeToFile(level, message, data) {
40
- if (!this.isDev || !this.logFile)
41
- return;
42
- const timestamp = new Date().toISOString();
43
- const logEntry = data
44
- ? `[${timestamp}] [${level}] ${message} ${JSON.stringify(data, null, 2)}\n`
45
- : `[${timestamp}] [${level}] ${message}\n`;
46
- try {
47
- fs.appendFileSync(this.logFile, logEntry);
48
- }
49
- catch (error) {
50
- // Silently fail - don't disrupt CLI operation
51
- }
52
- }
53
- /**
54
- * Check if an error message is critical and should bypass silent mode
55
- * Critical errors are those that prevent the CLI from starting or executing
56
- */
57
- isCriticalError(message) {
58
- const criticalPatterns = [
59
- /api key/i,
60
- /authentication/i,
61
- /node.*version/i,
62
- /missing.*required/i,
63
- /failed to install/i,
64
- /fatal/i,
65
- ];
66
- return criticalPatterns.some((pattern) => pattern.test(message));
67
- }
68
- info(message) {
69
- if (this.silent)
70
- return;
71
- console.log(chalk.blue("ℹ"), message);
72
- }
73
- success(message) {
74
- if (this.silent)
75
- return;
76
- console.log(chalk.green("✓"), message);
77
- }
78
- error(message) {
79
- // Allow critical errors through even in silent mode
80
- if (this.silent && !this.isCriticalError(message))
81
- return;
82
- console.error(chalk.red("✗"), message);
83
- }
84
- warn(message) {
85
- if (this.silent)
86
- return;
87
- console.warn(chalk.yellow("⚠"), message);
88
- }
89
- debug(message, data) {
90
- this.writeToFile("DEBUG", message, data);
91
- if (this.silent)
92
- return;
93
- if (this.verbose) {
94
- console.log(chalk.gray("→"), message);
95
- if (data) {
96
- console.log(chalk.gray(JSON.stringify(data, null, 2)));
97
- }
98
- }
99
- }
100
- section(title) {
101
- if (this.silent)
102
- return;
103
- console.log("\n" + chalk.bold.red(`━━━ ${title} ━━━`));
104
- }
105
- summary(title) {
106
- if (this.silent)
107
- return;
108
- console.log("\n" + chalk.bold.cyan(`╔═══ ${title} ═══╗`));
109
- }
110
- raw(message) {
111
- if (this.silent)
112
- return;
113
- console.log(message);
114
- }
115
- stream(chunk) {
116
- if (this.silent)
117
- return;
118
- process.stdout.write(chalk.dim(chunk));
119
- }
120
- toolRead(filePath) {
121
- if (this.silent)
122
- return;
123
- console.log("");
124
- console.log(chalk.blue("📖"), chalk.dim("Reading:"), chalk.white(filePath));
125
- }
126
- toolWrite(filePath) {
127
- if (this.silent)
128
- return;
129
- console.log("");
130
- console.log(chalk.green("✏️"), chalk.dim("Writing:"), chalk.white(filePath));
131
- }
132
- toolEdit(filePath) {
133
- if (this.silent)
134
- return;
135
- console.log("");
136
- console.log(chalk.yellow("✏️"), chalk.dim("Editing:"), chalk.white(filePath));
137
- }
138
- toolBash(command) {
139
- if (this.silent)
140
- return;
141
- console.log("");
142
- console.log(chalk.cyan("🔨"), chalk.dim("Running:"), chalk.white(command));
143
- }
144
- toolSearch(type, pattern) {
145
- if (this.silent)
146
- return;
147
- console.log("");
148
- console.log(chalk.cyan("🔍"), chalk.dim(`Searching ${type}:`), chalk.white(pattern));
149
- }
150
- toolAgent(agentType) {
151
- if (this.silent)
152
- return;
153
- console.log("");
154
- console.log(chalk.cyan("🤖"), chalk.dim("Launching agent:"), chalk.white(agentType));
155
- }
156
- todoUpdate(todos) {
157
- if (this.silent)
158
- return;
159
- const completed = todos.filter((t) => t.status === "completed");
160
- const inProgress = todos.filter((t) => t.status === "in_progress");
161
- const pending = todos.filter((t) => t.status === "pending");
162
- const total = todos.length;
163
- const completedCount = completed.length;
164
- const progress = total > 0 ? Math.round((completedCount / total) * 100) : 0;
165
- // Progress bar
166
- const barLength = 20;
167
- const filledLength = Math.round((barLength * completedCount) / total);
168
- const bar = "█".repeat(filledLength) + "░".repeat(barLength - filledLength);
169
- console.log("");
170
- console.log(chalk.blue("📝"), chalk.dim("Todo Progress:"), chalk.cyan(bar), chalk.white(`${progress}%`), chalk.gray(`(${completedCount}/${total})`));
171
- // Show individual todos with status indicators
172
- if (inProgress.length > 0) {
173
- for (const todo of inProgress) {
174
- console.log(" ", chalk.yellow("→"), chalk.white(todo.content), chalk.gray("[in progress]"));
175
- }
176
- }
177
- if (completed.length > 0 && completed.length <= 3) {
178
- for (const todo of completed) {
179
- console.log(" ", chalk.green("✓"), chalk.gray(todo.content), chalk.dim("[completed]"));
180
- }
181
- }
182
- if (pending.length > 0 && pending.length <= 3) {
183
- for (const todo of pending) {
184
- console.log(" ", chalk.dim("⏳"), chalk.white(todo.content), chalk.gray("[pending]"));
185
- }
186
- }
187
- }
188
- divider() {
189
- if (this.silent)
190
- return;
191
- console.log(chalk.gray("─".repeat(60)));
192
- }
193
- box(title) {
194
- if (this.silent)
195
- return;
196
- const width = 60;
197
- const padding = Math.max(0, width - title.length - 2);
198
- const leftPad = Math.floor(padding / 2);
199
- const rightPad = Math.ceil(padding / 2);
200
- console.log(chalk.cyan("╔" + "═".repeat(width) + "╗"));
201
- console.log(chalk.cyan("║") +
202
- " ".repeat(leftPad) +
203
- chalk.bold.white(title) +
204
- " ".repeat(rightPad) +
205
- chalk.cyan("║"));
206
- console.log(chalk.cyan("╚" + "═".repeat(width) + "╝"));
207
- }
208
- }
209
- export const logger = new Logger();
@@ -1,89 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import { logger } from "./logger";
3
- const MINIMUM_NODE_VERSION = 18;
4
- /**
5
- * Parse a version string like "v18.17.0" or "18.17.0" into components
6
- */
7
- function parseVersion(versionString) {
8
- const cleaned = versionString.trim().replace(/^v/, "");
9
- const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)/);
10
- if (!match) {
11
- return null;
12
- }
13
- return {
14
- major: Number.parseInt(match[1], 10),
15
- minor: Number.parseInt(match[2], 10),
16
- patch: Number.parseInt(match[3], 10),
17
- raw: versionString.trim(),
18
- };
19
- }
20
- /**
21
- * Get the installed Node.js version
22
- */
23
- function getNodeVersion() {
24
- try {
25
- // Try to get version from child process (works when Node.js is available)
26
- const versionOutput = execSync("node --version", {
27
- encoding: "utf-8",
28
- stdio: ["ignore", "pipe", "ignore"],
29
- });
30
- return parseVersion(versionOutput);
31
- }
32
- catch (error) {
33
- // Node.js not found or not accessible
34
- return null;
35
- }
36
- }
37
- /**
38
- * Check if Node.js is installed and meets minimum version requirements
39
- * @throws Error with helpful message if requirements not met
40
- */
41
- export function checkNodeVersion() {
42
- const nodeVersion = getNodeVersion();
43
- if (!nodeVersion) {
44
- logger.error("Node.js is not installed or not accessible");
45
- logger.error("");
46
- logger.error("Supatest AI requires Node.js 18 or higher to run.");
47
- logger.error("");
48
- logger.error("Please install Node.js:");
49
- logger.error(" • macOS: brew install node");
50
- logger.error(" • Linux: Use your package manager or nvm");
51
- logger.error(" • Windows: Download from https://nodejs.org/");
52
- logger.error("");
53
- logger.error("Or use nvm (Node Version Manager):");
54
- logger.error(" nvm install 18");
55
- logger.error(" nvm use 18");
56
- logger.error("");
57
- logger.error("Verify installation: node --version");
58
- process.exit(1);
59
- }
60
- if (nodeVersion.major < MINIMUM_NODE_VERSION) {
61
- logger.error(`Node.js ${nodeVersion.raw} is installed, but version ${MINIMUM_NODE_VERSION} or higher is required`);
62
- logger.error("");
63
- logger.error(`Current version: ${nodeVersion.raw}`);
64
- logger.error(`Required version: ${MINIMUM_NODE_VERSION}.0.0 or higher`);
65
- logger.error("");
66
- logger.error("Please upgrade Node.js:");
67
- logger.error(" • macOS: brew upgrade node");
68
- logger.error(" • Linux: Use your package manager or nvm");
69
- logger.error(" • Windows: Download from https://nodejs.org/");
70
- logger.error("");
71
- logger.error("Or use nvm to install a newer version:");
72
- logger.error(` nvm install ${MINIMUM_NODE_VERSION}`);
73
- logger.error(` nvm use ${MINIMUM_NODE_VERSION}`);
74
- logger.error("");
75
- logger.error("Verify upgrade: node --version");
76
- process.exit(1);
77
- }
78
- // Success - version is adequate
79
- }
80
- /**
81
- * Get Node.js version info for display purposes
82
- */
83
- export function getNodeVersionInfo() {
84
- const version = getNodeVersion();
85
- if (!version) {
86
- return "Not installed";
87
- }
88
- return version.raw;
89
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * Plan file utilities
3
- * Handles creation and management of plan files for plan mode
4
- */
5
- import { mkdir, readFile, writeFile } from "node:fs/promises";
6
- import { join } from "node:path";
7
- const PLAN_DIR = ".supatest/plans";
8
- /**
9
- * Generate a unique plan file name with timestamp
10
- */
11
- function generatePlanFileName() {
12
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
13
- return `plan-${timestamp}.md`;
14
- }
15
- /**
16
- * Create a new plan file in the .supatest/plans directory
17
- * @param cwd - The current working directory (project root)
18
- * @returns The path to the created plan file
19
- */
20
- export async function createPlanFile(cwd) {
21
- const planDir = join(cwd, PLAN_DIR);
22
- // Ensure the plan directory exists
23
- await mkdir(planDir, { recursive: true });
24
- const planFileName = generatePlanFileName();
25
- const planPath = join(planDir, planFileName);
26
- // Create the initial plan file with a placeholder
27
- const initialContent = `# Plan
28
-
29
- _Planning in progress..._
30
-
31
- ---
32
-
33
- ## Summary
34
-
35
- _To be filled by the agent_
36
-
37
- ## Tasks
38
-
39
- _To be filled by the agent_
40
-
41
- ## Files to Modify
42
-
43
- _To be filled by the agent_
44
-
45
- ---
46
-
47
- _Generated by Supatest AI_
48
- `;
49
- await writeFile(planPath, initialContent, "utf-8");
50
- return planPath;
51
- }
52
- /**
53
- * Update the contents of an existing plan file
54
- * @param planPath - The path to the plan file
55
- * @param content - The new content to write
56
- */
57
- export async function updatePlanFile(planPath, content) {
58
- await writeFile(planPath, content, "utf-8");
59
- }
60
- /**
61
- * Read the contents of a plan file
62
- * @param planPath - The path to the plan file
63
- * @returns The contents of the plan file
64
- */
65
- export async function readPlanFile(planPath) {
66
- return await readFile(planPath, "utf-8");
67
- }
68
- /**
69
- * Get the plan directory path for a given project
70
- * @param cwd - The current working directory (project root)
71
- * @returns The path to the plan directory
72
- */
73
- export function getPlanDirectory(cwd) {
74
- return join(cwd, PLAN_DIR);
75
- }
@@ -1,23 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- /**
4
- * Load project-specific instructions from SUPATEST.md
5
- * Checks multiple locations in order of precedence
6
- */
7
- export function loadProjectInstructions(cwd) {
8
- const paths = [
9
- join(cwd, "SUPATEST.md"),
10
- join(cwd, ".supatest", "SUPATEST.md"),
11
- ];
12
- for (const path of paths) {
13
- if (existsSync(path)) {
14
- try {
15
- return readFileSync(path, "utf-8");
16
- }
17
- catch {
18
- // Skip if can't read
19
- }
20
- }
21
- }
22
- return undefined;
23
- }