@fasttest-ai/qa-agent 0.4.3 → 1.0.0

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/install.js CHANGED
@@ -1,411 +1,18 @@
1
- /**
2
- * FastTest Agent installer supports Claude Code, Cursor, Windsurf, VS Code, and Codex.
3
- * Registers the MCP server, sets up permissions (where supported), and installs Playwright.
4
- *
5
- * Usage:
6
- * npx @fasttest-ai/qa-agent install # Interactive IDE picker
7
- * npx @fasttest-ai/qa-agent install --ide cursor # Skip prompt
8
- * npx @fasttest-ai/qa-agent install --ide claude-code --scope project
9
- * npx @fasttest-ai/qa-agent uninstall
10
- */
11
- import { execFileSync } from "node:child_process";
12
- import { createInterface } from "node:readline";
13
- import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, copyFileSync } from "node:fs";
14
- import { homedir, platform } from "node:os";
15
- import { join, dirname } from "node:path";
16
- import { fileURLToPath } from "node:url";
17
- const IS_WINDOWS = platform() === "win32";
18
- const IS_MAC = platform() === "darwin";
19
- const MCP_SERVER_NAME = "fasttest";
20
- const NPX_CMD = IS_WINDOWS ? "npx.cmd" : "npx";
21
- const MCP_COMMAND = "npx";
22
- const MCP_ARGS = ["-y", "@fasttest-ai/qa-agent@latest"];
23
- const IDE_CONFIGS = [
24
- {
25
- id: "claude-code",
26
- label: "Claude Code",
27
- globalConfigPath: "", // Uses CLI, not direct file write
28
- format: "cli",
29
- hasPermissions: true,
30
- },
31
- {
32
- id: "cursor",
33
- label: "Cursor",
34
- globalConfigPath: join(homedir(), ".cursor", "mcp.json"),
35
- format: "json-mcpServers",
36
- hasPermissions: false,
37
- },
38
- {
39
- id: "windsurf",
40
- label: "Windsurf",
41
- globalConfigPath: join(homedir(), ".codeium", "windsurf", "mcp_config.json"),
42
- format: "json-mcpServers",
43
- hasPermissions: false,
44
- },
45
- {
46
- id: "vscode",
47
- label: "VS Code / Copilot",
48
- globalConfigPath: IS_MAC
49
- ? join(homedir(), "Library", "Application Support", "Code", "User", "mcp.json")
50
- : IS_WINDOWS
51
- ? join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "Code", "User", "mcp.json")
52
- : join(homedir(), ".config", "Code", "User", "mcp.json"),
53
- format: "json-servers",
54
- hasPermissions: false,
55
- },
56
- {
57
- id: "codex",
58
- label: "Codex",
59
- globalConfigPath: join(homedir(), ".codex", "config.toml"),
60
- format: "toml",
61
- hasPermissions: false,
62
- },
63
- ];
64
- function parseArgs() {
65
- const action = process.argv[2];
66
- const rest = process.argv.slice(3);
67
- let ide = null;
68
- let scope = "user";
69
- let skipPermissions = false;
70
- for (let i = 0; i < rest.length; i++) {
71
- if (rest[i] === "--ide" && rest[i + 1]) {
72
- ide = rest[++i];
73
- }
74
- else if (rest[i] === "--scope" && rest[i + 1]) {
75
- scope = rest[++i];
76
- }
77
- else if (rest[i] === "--skip-permissions") {
78
- skipPermissions = true;
79
- }
80
- }
81
- return { action, ide, scope, skipPermissions };
82
- }
83
- // ---------------------------------------------------------------------------
84
- // Interactive IDE picker
85
- // ---------------------------------------------------------------------------
86
- function askQuestion(prompt) {
87
- const rl = createInterface({ input: process.stdin, output: process.stdout });
88
- return new Promise((resolve) => {
89
- rl.question(prompt, (answer) => {
90
- rl.close();
91
- resolve(answer.trim());
92
- });
93
- });
94
- }
95
- async function pickIde() {
96
- console.log(" Which IDE do you use?\n");
97
- IDE_CONFIGS.forEach((ide, i) => {
98
- console.log(` ${i + 1}. ${ide.label}`);
99
- });
100
- console.log();
101
- const answer = await askQuestion(" > ");
102
- const idx = parseInt(answer, 10) - 1;
103
- if (idx >= 0 && idx < IDE_CONFIGS.length) {
104
- return IDE_CONFIGS[idx].id;
105
- }
106
- // Try matching by name
107
- const match = IDE_CONFIGS.find((c) => c.id === answer.toLowerCase() || c.label.toLowerCase() === answer.toLowerCase());
108
- if (match)
109
- return match.id;
110
- console.log(" Invalid selection, defaulting to Claude Code.\n");
111
- return "claude-code";
112
- }
113
- // ---------------------------------------------------------------------------
114
- // Claude Code — CLI-based registration + permission management
115
- // ---------------------------------------------------------------------------
116
- function isClaudeCliAvailable() {
117
- try {
118
- execFileSync("claude", ["--version"], { stdio: "pipe", shell: IS_WINDOWS });
119
- return true;
120
- }
121
- catch {
122
- return false;
123
- }
124
- }
125
- function registerClaudeCode(scope) {
126
- const args = [
127
- "mcp", "add", "--scope", scope,
128
- MCP_SERVER_NAME, "--",
129
- MCP_COMMAND, ...MCP_ARGS,
130
- ];
131
- try {
132
- execFileSync("claude", args, { stdio: "inherit", shell: IS_WINDOWS });
133
- return true;
134
- }
135
- catch {
136
- try {
137
- execFileSync("claude", ["mcp", "remove", "--scope", scope, MCP_SERVER_NAME], {
138
- stdio: "pipe", shell: IS_WINDOWS,
139
- });
140
- execFileSync("claude", args, { stdio: "inherit", shell: IS_WINDOWS });
141
- return true;
142
- }
143
- catch {
144
- return false;
145
- }
146
- }
147
- }
148
- function removeClaudeCode(scope) {
149
- try {
150
- execFileSync("claude", ["mcp", "remove", "--scope", scope, MCP_SERVER_NAME], {
151
- stdio: "inherit", shell: IS_WINDOWS,
152
- });
153
- return true;
154
- }
155
- catch {
156
- return false;
157
- }
158
- }
159
- const CLAUDE_DIR = join(homedir(), ".claude");
160
- const CLAUDE_SETTINGS_PATH = join(CLAUDE_DIR, "settings.json");
161
- const PERMISSION_ENTRY = "mcp__fasttest";
162
- function addClaudePermissions() {
163
- if (!existsSync(CLAUDE_DIR))
164
- mkdirSync(CLAUDE_DIR, { recursive: true });
165
- let settings = {};
166
- if (existsSync(CLAUDE_SETTINGS_PATH)) {
167
- try {
168
- settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
169
- }
170
- catch {
171
- const backup = CLAUDE_SETTINGS_PATH + ".bak";
172
- writeFileSync(backup, readFileSync(CLAUDE_SETTINGS_PATH));
173
- console.log(` Warning: ${CLAUDE_SETTINGS_PATH} was corrupted. Backed up to ${backup}`);
174
- settings = {};
175
- }
176
- }
177
- if (!settings.permissions)
178
- settings.permissions = {};
179
- if (!Array.isArray(settings.permissions.allow))
180
- settings.permissions.allow = [];
181
- if (settings.permissions.allow.includes(PERMISSION_ENTRY)) {
182
- return { added: false, alreadyExists: true };
183
- }
184
- settings.permissions.allow.push(PERMISSION_ENTRY);
185
- writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
186
- return { added: true, alreadyExists: false };
187
- }
188
- function removeClaudePermissions() {
189
- if (!existsSync(CLAUDE_SETTINGS_PATH))
190
- return false;
191
- try {
192
- const settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
193
- const allow = settings.permissions?.allow;
194
- if (!Array.isArray(allow))
195
- return false;
196
- const idx = allow.indexOf(PERMISSION_ENTRY);
197
- if (idx === -1)
198
- return false;
199
- allow.splice(idx, 1);
200
- writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
201
- return true;
202
- }
203
- catch {
204
- return false;
205
- }
206
- }
207
- // ---------------------------------------------------------------------------
208
- // Claude Code — /ftest slash command
209
- // ---------------------------------------------------------------------------
210
- const __dirname = dirname(fileURLToPath(import.meta.url));
211
- const CLAUDE_COMMANDS_DIR = join(process.cwd(), ".claude", "commands");
212
- const COMMAND_FILES = ["ftest.md", "qa.md"];
213
- function installClaudeCommands() {
214
- let installed = 0;
215
- let updated = 0;
216
- for (const file of COMMAND_FILES) {
217
- const src = join(__dirname, "..", "commands", file);
218
- const dest = join(CLAUDE_COMMANDS_DIR, file);
219
- if (!existsSync(src))
220
- continue;
221
- if (existsSync(dest)) {
222
- copyFileSync(src, dest);
223
- updated++;
224
- }
225
- else {
226
- mkdirSync(CLAUDE_COMMANDS_DIR, { recursive: true });
227
- copyFileSync(src, dest);
228
- installed++;
229
- }
230
- }
231
- return { installed, updated };
232
- }
233
- function removeClaudeCommands() {
234
- let removed = 0;
235
- for (const file of COMMAND_FILES) {
236
- const dest = join(CLAUDE_COMMANDS_DIR, file);
237
- if (!existsSync(dest))
238
- continue;
239
- try {
240
- unlinkSync(dest);
241
- removed++;
242
- }
243
- catch { /* ignore */ }
244
- }
245
- return removed;
246
- }
247
- // ---------------------------------------------------------------------------
248
- // JSON config writers (Cursor, Windsurf, VS Code)
249
- // ---------------------------------------------------------------------------
250
- /** Cursor & Windsurf: { "mcpServers": { "fasttest": { "command": ..., "args": ... } } } */
251
- function writeMcpServersJson(configPath) {
252
- const dir = join(configPath, "..");
253
- if (!existsSync(dir))
254
- mkdirSync(dir, { recursive: true });
255
- let config = {};
256
- if (existsSync(configPath)) {
257
- try {
258
- config = JSON.parse(readFileSync(configPath, "utf-8"));
259
- }
260
- catch {
261
- const backup = configPath + ".bak";
262
- writeFileSync(backup, readFileSync(configPath));
263
- console.log(` Warning: ${configPath} was corrupted. Backed up to ${backup}`);
264
- config = {};
265
- }
266
- }
267
- if (!config.mcpServers || typeof config.mcpServers !== "object") {
268
- config.mcpServers = {};
269
- }
270
- config.mcpServers[MCP_SERVER_NAME] = {
271
- command: MCP_COMMAND,
272
- args: [...MCP_ARGS],
273
- };
274
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
275
- return true;
276
- }
277
- /** VS Code: { "servers": { "fasttest": { "type": "stdio", "command": ..., "args": ... } } } */
278
- function writeVscodeJson(configPath) {
279
- const dir = join(configPath, "..");
280
- if (!existsSync(dir))
281
- mkdirSync(dir, { recursive: true });
282
- let config = {};
283
- if (existsSync(configPath)) {
284
- try {
285
- config = JSON.parse(readFileSync(configPath, "utf-8"));
286
- }
287
- catch {
288
- const backup = configPath + ".bak";
289
- writeFileSync(backup, readFileSync(configPath));
290
- console.log(` Warning: ${configPath} was corrupted. Backed up to ${backup}`);
291
- config = {};
292
- }
293
- }
294
- if (!config.servers || typeof config.servers !== "object") {
295
- config.servers = {};
296
- }
297
- config.servers[MCP_SERVER_NAME] = {
298
- type: "stdio",
299
- command: MCP_COMMAND,
300
- args: [...MCP_ARGS],
301
- };
302
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
303
- return true;
304
- }
305
- // ---------------------------------------------------------------------------
306
- // Codex TOML config writer
307
- // ---------------------------------------------------------------------------
308
- function writeCodexToml(configPath) {
309
- const dir = join(configPath, "..");
310
- if (!existsSync(dir))
311
- mkdirSync(dir, { recursive: true });
312
- let content = "";
313
- if (existsSync(configPath)) {
314
- content = readFileSync(configPath, "utf-8");
315
- }
316
- const sectionHeader = `[mcp_servers.${MCP_SERVER_NAME}]`;
317
- if (content.includes(sectionHeader)) {
318
- // Replace existing section — find from header to next section or EOF
319
- const regex = new RegExp(`\\[mcp_servers\\.${MCP_SERVER_NAME}\\][\\s\\S]*?(?=\\n\\[|$)`);
320
- content = content.replace(regex, buildCodexSection());
321
- }
322
- else {
323
- // Append
324
- if (content.length > 0 && !content.endsWith("\n"))
325
- content += "\n";
326
- content += "\n" + buildCodexSection() + "\n";
327
- }
328
- writeFileSync(configPath, content);
329
- return true;
330
- }
331
- function buildCodexSection() {
332
- return `[mcp_servers.${MCP_SERVER_NAME}]\ncommand = "${MCP_COMMAND}"\nargs = [${MCP_ARGS.map((a) => `"${a}"`).join(", ")}]`;
333
- }
334
- // ---------------------------------------------------------------------------
335
- // Remove from JSON configs
336
- // ---------------------------------------------------------------------------
337
- function removeFromMcpServersJson(configPath) {
338
- if (!existsSync(configPath))
339
- return false;
340
- try {
341
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
342
- const servers = config.mcpServers;
343
- if (!servers || !(MCP_SERVER_NAME in servers))
344
- return false;
345
- delete servers[MCP_SERVER_NAME];
346
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
347
- return true;
348
- }
349
- catch {
350
- return false;
351
- }
352
- }
353
- function removeFromVscodeJson(configPath) {
354
- if (!existsSync(configPath))
355
- return false;
356
- try {
357
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
358
- const servers = config.servers;
359
- if (!servers || !(MCP_SERVER_NAME in servers))
360
- return false;
361
- delete servers[MCP_SERVER_NAME];
362
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
363
- return true;
364
- }
365
- catch {
366
- return false;
367
- }
368
- }
369
- function removeFromCodexToml(configPath) {
370
- if (!existsSync(configPath))
371
- return false;
372
- try {
373
- let content = readFileSync(configPath, "utf-8");
374
- const sectionHeader = `[mcp_servers.${MCP_SERVER_NAME}]`;
375
- if (!content.includes(sectionHeader))
376
- return false;
377
- const regex = new RegExp(`\\n?\\[mcp_servers\\.${MCP_SERVER_NAME}\\][\\s\\S]*?(?=\\n\\[|$)`);
378
- content = content.replace(regex, "");
379
- writeFileSync(configPath, content);
380
- return true;
381
- }
382
- catch {
383
- return false;
384
- }
385
- }
386
- // ---------------------------------------------------------------------------
387
- // Playwright browser installation
388
- // ---------------------------------------------------------------------------
389
- function installPlaywrightBrowsers() {
390
- try {
391
- execFileSync(NPX_CMD, ["playwright", "install", "--with-deps", "chromium"], {
392
- stdio: "inherit",
393
- });
394
- console.log(" Chromium installed");
395
- }
396
- catch {
397
- console.log(" Warning: Could not install Playwright browsers automatically.");
398
- console.log(" Run manually: npx playwright install --with-deps chromium");
399
- }
400
- }
401
- // ---------------------------------------------------------------------------
402
- // Install
403
- // ---------------------------------------------------------------------------
404
- function registerMcpServer(ide, scope) {
405
- switch (ide.format) {
406
- case "cli":
407
- if (!isClaudeCliAvailable()) {
408
- console.log(`
1
+ import{execFileSync as y}from"node:child_process";import{createInterface as M}from"node:readline";import{existsSync as a,mkdirSync as b,readFileSync as u,writeFileSync as f,unlinkSync as N,copyFileSync as _}from"node:fs";import{homedir as g,platform as j}from"node:os";import{join as i,dirname as O}from"node:path";import{fileURLToPath as T}from"node:url";var C=j()==="win32",D=j()==="darwin",l="fasttest",F=C?"npx.cmd":"npx",$="npx",I=["-y","@fasttest-ai/qa-agent@latest"],v=[{id:"claude-code",label:"Claude Code",globalConfigPath:"",format:"cli",hasPermissions:!0},{id:"cursor",label:"Cursor",globalConfigPath:i(g(),".cursor","mcp.json"),format:"json-mcpServers",hasPermissions:!1},{id:"windsurf",label:"Windsurf",globalConfigPath:i(g(),".codeium","windsurf","mcp_config.json"),format:"json-mcpServers",hasPermissions:!1},{id:"vscode",label:"VS Code / Copilot",globalConfigPath:D?i(g(),"Library","Application Support","Code","User","mcp.json"):C?i(process.env.APPDATA??i(g(),"AppData","Roaming"),"Code","User","mcp.json"):i(g(),".config","Code","User","mcp.json"),format:"json-servers",hasPermissions:!1},{id:"codex",label:"Codex",globalConfigPath:i(g(),".codex","config.toml"),format:"toml",hasPermissions:!1},{id:"antigravity",label:"Antigravity",globalConfigPath:i(g(),".gemini","antigravity","mcp_config.json"),format:"json-mcpServers",hasPermissions:!1}];function J(){let e=process.argv[2],o=process.argv.slice(3),s=null,n="user",r=!1;for(let t=0;t<o.length;t++)o[t]==="--ide"&&o[t+1]?s=o[++t]:o[t]==="--scope"&&o[t+1]?n=o[++t]:o[t]==="--skip-permissions"&&(r=!0);return{action:e,ide:s,scope:n,skipPermissions:r}}function L(e){let o=M({input:process.stdin,output:process.stdout});return new Promise(s=>{o.question(e,n=>{o.close(),s(n.trim())})})}async function R(){console.log(` Which IDE do you use?
2
+ `),v.forEach((n,r)=>{console.log(` ${r+1}. ${n.label}`)}),console.log();let e=await L(" > "),o=parseInt(e,10)-1;if(o>=0&&o<v.length)return v[o].id;let s=v.find(n=>n.id===e.toLowerCase()||n.label.toLowerCase()===e.toLowerCase());return s?s.id:(console.log(` Invalid selection, defaulting to Claude Code.
3
+ `),"claude-code")}function U(){try{return y("claude",["--version"],{stdio:"pipe",shell:C}),!0}catch{return!1}}function W(e){let o=["mcp","add","--scope",e,l,"--",$,...I];try{return y("claude",o,{stdio:"inherit",shell:C}),!0}catch{try{return y("claude",["mcp","remove","--scope",e,l],{stdio:"pipe",shell:C}),y("claude",o,{stdio:"inherit",shell:C}),!0}catch{return!1}}}function G(e){try{return y("claude",["mcp","remove","--scope",e,l],{stdio:"inherit",shell:C}),!0}catch{return!1}}var P=i(g(),".claude"),d=i(P,"settings.json"),h="mcp__fasttest";function q(){a(P)||b(P,{recursive:!0});let e={};if(a(d))try{e=JSON.parse(u(d,"utf-8"))}catch{let o=d+".bak";f(o,u(d)),console.log(` Warning: ${d} was corrupted. Backed up to ${o}`),e={}}return e.permissions||(e.permissions={}),Array.isArray(e.permissions.allow)||(e.permissions.allow=[]),e.permissions.allow.includes(h)?{added:!1,alreadyExists:!0}:(e.permissions.allow.push(h),f(d,JSON.stringify(e,null,2)+`
4
+ `),{added:!0,alreadyExists:!1})}function H(){if(!a(d))return!1;try{let e=JSON.parse(u(d,"utf-8")),o=e.permissions?.allow;if(!Array.isArray(o))return!1;let s=o.indexOf(h);return s===-1?!1:(o.splice(s,1),f(d,JSON.stringify(e,null,2)+`
5
+ `),!0)}catch{return!1}}var B=O(T(import.meta.url)),w=i(process.cwd(),".claude","commands"),E=["ftest.md","qa.md"];function V(){let e=0,o=0;for(let s of E){let n=i(B,"..","commands",s),r=i(w,s);a(n)&&(a(r)?(_(n,r),o++):(b(w,{recursive:!0}),_(n,r),e++))}return{installed:e,updated:o}}function Y(){let e=0;for(let o of E){let s=i(w,o);if(a(s))try{N(s),e++}catch{}}return e}function K(e){let o=i(e,"..");a(o)||b(o,{recursive:!0});let s={};if(a(e))try{s=JSON.parse(u(e,"utf-8"))}catch{let n=e+".bak";f(n,u(e)),console.log(` Warning: ${e} was corrupted. Backed up to ${n}`),s={}}return(!s.mcpServers||typeof s.mcpServers!="object")&&(s.mcpServers={}),s.mcpServers[l]={command:$,args:[...I]},f(e,JSON.stringify(s,null,2)+`
6
+ `),!0}function Q(e){let o=i(e,"..");a(o)||b(o,{recursive:!0});let s={};if(a(e))try{s=JSON.parse(u(e,"utf-8"))}catch{let n=e+".bak";f(n,u(e)),console.log(` Warning: ${e} was corrupted. Backed up to ${n}`),s={}}return(!s.servers||typeof s.servers!="object")&&(s.servers={}),s.servers[l]={type:"stdio",command:$,args:[...I]},f(e,JSON.stringify(s,null,2)+`
7
+ `),!0}function X(e){let o=i(e,"..");a(o)||b(o,{recursive:!0});let s="";a(e)&&(s=u(e,"utf-8"));let n=`[mcp_servers.${l}]`;if(s.includes(n)){let r=new RegExp(`\\[mcp_servers\\.${l}\\][\\s\\S]*?(?=\\n\\[|$)`);s=s.replace(r,k())}else s.length>0&&!s.endsWith(`
8
+ `)&&(s+=`
9
+ `),s+=`
10
+ `+k()+`
11
+ `;return f(e,s),!0}function k(){return`[mcp_servers.${l}]
12
+ command = "${$}"
13
+ args = [${I.map(e=>`"${e}"`).join(", ")}]`}function z(e){if(!a(e))return!1;try{let o=JSON.parse(u(e,"utf-8")),s=o.mcpServers;return!s||!(l in s)?!1:(delete s[l],f(e,JSON.stringify(o,null,2)+`
14
+ `),!0)}catch{return!1}}function Z(e){if(!a(e))return!1;try{let o=JSON.parse(u(e,"utf-8")),s=o.servers;return!s||!(l in s)?!1:(delete s[l],f(e,JSON.stringify(o,null,2)+`
15
+ `),!0)}catch{return!1}}function ee(e){if(!a(e))return!1;try{let o=u(e,"utf-8"),s=`[mcp_servers.${l}]`;if(!o.includes(s))return!1;let n=new RegExp(`\\n?\\[mcp_servers\\.${l}\\][\\s\\S]*?(?=\\n\\[|$)`);return o=o.replace(n,""),f(e,o),!0}catch{return!1}}function se(){if(process.env.FASTTEST_SKIP_PLAYWRIGHT==="1"){console.log(" Skipping Playwright install (FASTTEST_SKIP_PLAYWRIGHT=1)");return}try{y(F,["playwright","install","--with-deps","chromium"],{stdio:"inherit"}),console.log(" Chromium installed")}catch{console.log(" Warning: Could not install Playwright browsers automatically."),console.log(" Run manually: npx playwright install --with-deps chromium")}}function oe(e,o){switch(e.format){case"cli":return U()?W(o):(console.log(`
409
16
  Claude Code CLI not found in PATH.
410
17
 
411
18
  Install Claude Code first:
@@ -420,171 +27,33 @@ function registerMcpServer(ide, scope) {
420
27
  }
421
28
  }
422
29
  }
423
- `);
424
- return false;
425
- }
426
- return registerClaudeCode(scope);
427
- case "json-mcpServers":
428
- return writeMcpServersJson(ide.globalConfigPath);
429
- case "json-servers":
430
- return writeVscodeJson(ide.globalConfigPath);
431
- case "toml":
432
- return writeCodexToml(ide.globalConfigPath);
433
- }
434
- }
435
- function removeMcpServer(ide, scope) {
436
- switch (ide.format) {
437
- case "cli":
438
- return removeClaudeCode(scope);
439
- case "json-mcpServers":
440
- return removeFromMcpServersJson(ide.globalConfigPath);
441
- case "json-servers":
442
- return removeFromVscodeJson(ide.globalConfigPath);
443
- case "toml":
444
- return removeFromCodexToml(ide.globalConfigPath);
445
- }
446
- }
447
- async function install(config) {
448
- console.log("\n FastTest Agent Installer\n");
449
- const ideId = config.ide ?? (await pickIde());
450
- const ide = IDE_CONFIGS.find((c) => c.id === ideId);
451
- if (!ide) {
452
- console.log(` Unknown IDE: ${ideId}`);
453
- console.log(` Supported: ${IDE_CONFIGS.map((c) => c.id).join(", ")}`);
454
- process.exit(1);
455
- }
456
- console.log(`\n Installing for ${ide.label}...\n`);
457
- // Calculate total steps
458
- const isClaudeCode = ide.id === "claude-code";
459
- const hasPermissions = ide.hasPermissions && !config.skipPermissions;
460
- let totalSteps = 2; // MCP registration + Playwright
461
- if (hasPermissions)
462
- totalSteps++;
463
- if (isClaudeCode)
464
- totalSteps++; // /ftest command
465
- let step = 1;
466
- // Step 1: Register MCP server
467
- console.log(` [${step}/${totalSteps}] Registering MCP server...`);
468
- const registered = registerMcpServer(ide, config.scope);
469
- if (registered) {
470
- const location = ide.format === "cli"
471
- ? `(scope: ${config.scope})`
472
- : ide.globalConfigPath;
473
- console.log(` MCP server "${MCP_SERVER_NAME}" registered ${location}`);
474
- }
475
- else {
476
- if (ide.format === "cli") {
477
- process.exit(1);
478
- }
479
- console.log(" Warning: Could not register MCP server.");
480
- }
481
- step++;
482
- // Step 2: Pre-approve tools (Claude Code only)
483
- if (hasPermissions) {
484
- console.log(` [${step}/${totalSteps}] Pre-approving tools...`);
485
- const { added, alreadyExists } = addClaudePermissions();
486
- if (added) {
487
- console.log(` Added ${PERMISSION_ENTRY} to ${CLAUDE_SETTINGS_PATH}`);
488
- }
489
- else if (alreadyExists) {
490
- console.log(` Already configured (${PERMISSION_ENTRY})`);
491
- }
492
- step++;
493
- }
494
- // Step 3: Install /ftest slash command (Claude Code only)
495
- if (isClaudeCode) {
496
- console.log(` [${step}/${totalSteps}] Installing /ftest and /qa commands...`);
497
- const { installed, updated } = installClaudeCommands();
498
- if (installed > 0) {
499
- console.log(` Added ${installed} command(s) to ${CLAUDE_COMMANDS_DIR}`);
500
- }
501
- if (updated > 0) {
502
- console.log(` Updated ${updated} command(s)`);
503
- }
504
- if (installed === 0 && updated === 0) {
505
- console.log(" Warning: Could not install commands (source files missing)");
506
- }
507
- step++;
508
- }
509
- // Step 4: Install Playwright browsers
510
- console.log(` [${step}/${totalSteps}] Installing Playwright browsers...`);
511
- installPlaywrightBrowsers();
512
- if (isClaudeCode) {
513
- console.log(`
514
- Done! Open ${ide.label} and try:
30
+ `),!1);case"json-mcpServers":return K(e.globalConfigPath);case"json-servers":return Q(e.globalConfigPath);case"toml":return X(e.globalConfigPath)}}function ne(e,o){switch(e.format){case"cli":return G(o);case"json-mcpServers":return z(e.globalConfigPath);case"json-servers":return Z(e.globalConfigPath);case"toml":return ee(e.globalConfigPath)}}async function te(e){console.log(`
31
+ FastTest Agent Installer
32
+ `);let o=e.ide??await R(),s=v.find(c=>c.id===o);s||(console.log(` Unknown IDE: ${o}`),console.log(` Supported: ${v.map(c=>c.id).join(", ")}`),process.exit(1)),console.log(`
33
+ Installing for ${s.label}...
34
+ `);let n=s.id==="claude-code",r=s.hasPermissions&&!e.skipPermissions,t=2;r&&t++,n&&t++;let m=1;if(console.log(` [${m}/${t}] Registering MCP server...`),oe(s,e.scope)){let c=s.format==="cli"?`(scope: ${e.scope})`:s.globalConfigPath;console.log(` MCP server "${l}" registered ${c}`)}else s.format==="cli"&&process.exit(1),console.log(" Warning: Could not register MCP server.");if(m++,r){console.log(` [${m}/${t}] Pre-approving tools...`);let{added:c,alreadyExists:S}=q();c?console.log(` Added ${h} to ${d}`):S&&console.log(` Already configured (${h})`),m++}if(n){console.log(` [${m}/${t}] Installing /ftest and /qa commands...`);let{installed:c,updated:S}=V();c>0&&console.log(` Added ${c} command(s) to ${w}`),S>0&&console.log(` Updated ${S} command(s)`),c===0&&S===0&&console.log(" Warning: Could not install commands (source files missing)"),m++}console.log(` [${m}/${t}] Installing Playwright browsers...`),se();let x=` Optional: Add this to your project's ${{"claude-code":"CLAUDE.md",cursor:".cursor/rules",windsurf:".windsurfrules",vscode:".github/copilot-instructions.md",codex:"AGENTS.md",antigravity:"GEMINI.md"}[s.id]} to auto-test after building features:
35
+
36
+ ## Testing
37
+ After implementing a feature, verify it works by running:
38
+ \`ftest <app-url> <what to test>\`
39
+ or use \`vibe shield <app-url>\` to generate a full regression suite.`;console.log(n?`
40
+ Done! Open ${s.label} and try:
515
41
 
516
42
  /ftest http://localhost:3000 login flow
517
- `);
518
- }
519
- else {
520
- console.log(`
521
- Done! Open ${ide.label} and try:
43
+
44
+ ${x}
45
+ `:`
46
+ Done! Open ${s.label} and try:
522
47
 
523
48
  "ftest my app at http://localhost:3000"
524
49
  "ftest explore http://localhost:3000"
525
50
  "ftest chaos http://localhost:3000"
526
- `);
527
- }
528
- }
529
- // ---------------------------------------------------------------------------
530
- // Uninstall
531
- // ---------------------------------------------------------------------------
532
- async function uninstall(config) {
533
- console.log("\n FastTest Agent Uninstaller\n");
534
- const ideId = config.ide ?? (await pickIde());
535
- const ide = IDE_CONFIGS.find((c) => c.id === ideId);
536
- if (!ide) {
537
- console.log(` Unknown IDE: ${ideId}`);
538
- process.exit(1);
539
- }
540
- console.log(`\n Uninstalling from ${ide.label}...\n`);
541
- const isClaudeCode = ide.id === "claude-code";
542
- let totalSteps = 1; // MCP removal
543
- if (ide.hasPermissions)
544
- totalSteps++;
545
- if (isClaudeCode)
546
- totalSteps++;
547
- let step = 1;
548
- console.log(` [${step}/${totalSteps}] Removing MCP server...`);
549
- const removed = removeMcpServer(ide, config.scope);
550
- if (removed) {
551
- console.log(` MCP server "${MCP_SERVER_NAME}" removed`);
552
- }
553
- else {
554
- console.log(" MCP server was not registered (nothing to remove)");
555
- }
556
- step++;
557
- if (ide.hasPermissions) {
558
- console.log(` [${step}/${totalSteps}] Removing tool permissions...`);
559
- const permRemoved = removeClaudePermissions();
560
- if (permRemoved) {
561
- console.log(` Removed ${PERMISSION_ENTRY} from ${CLAUDE_SETTINGS_PATH}`);
562
- }
563
- else {
564
- console.log(" Permission was not present (nothing to remove)");
565
- }
566
- step++;
567
- }
568
- if (isClaudeCode) {
569
- console.log(` [${step}/${totalSteps}] Removing /ftest and /qa commands...`);
570
- const cmdRemoved = removeClaudeCommands();
571
- if (cmdRemoved > 0) {
572
- console.log(` Removed ${cmdRemoved} command(s)`);
573
- }
574
- else {
575
- console.log(" Commands were not installed (nothing to remove)");
576
- }
577
- }
578
- console.log(`\n FastTest has been uninstalled from ${ide.label}.\n`);
579
- }
580
- // ---------------------------------------------------------------------------
581
- // Main
582
- // ---------------------------------------------------------------------------
583
- const config = parseArgs();
584
- if (config.action === "uninstall") {
585
- await uninstall(config);
586
- }
587
- else {
588
- await install(config);
589
- }
590
- //# sourceMappingURL=install.js.map
51
+
52
+ ${x}
53
+ `)}async function re(e){console.log(`
54
+ FastTest Agent Uninstaller
55
+ `);let o=e.ide??await R(),s=v.find(p=>p.id===o);s||(console.log(` Unknown IDE: ${o}`),process.exit(1)),console.log(`
56
+ Uninstalling from ${s.label}...
57
+ `);let n=s.id==="claude-code",r=1;s.hasPermissions&&r++,n&&r++;let t=1;console.log(` [${t}/${r}] Removing MCP server...`);let m=ne(s,e.scope);if(console.log(m?` MCP server "${l}" removed`:" MCP server was not registered (nothing to remove)"),t++,s.hasPermissions){console.log(` [${t}/${r}] Removing tool permissions...`);let p=H();console.log(p?` Removed ${h} from ${d}`:" Permission was not present (nothing to remove)"),t++}if(n){console.log(` [${t}/${r}] Removing /ftest and /qa commands...`);let p=Y();p>0?console.log(` Removed ${p} command(s)`):console.log(" Commands were not installed (nothing to remove)")}console.log(`
58
+ FastTest has been uninstalled from ${s.label}.
59
+ `)}var A=J();A.action==="uninstall"?await re(A):await te(A);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fasttest-ai/qa-agent",
3
- "version": "0.4.3",
3
+ "version": "1.0.0",
4
4
  "description": "FastTest Agent — MCP server that turns your coding agent into a QA engineer. Test, explore, and break web apps using Playwright.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -33,8 +33,10 @@
33
33
  },
34
34
  "main": "./dist/index.js",
35
35
  "scripts": {
36
- "build": "tsc",
36
+ "build": "rm -rf dist && node esbuild.config.mjs",
37
+ "build:dev": "tsc",
37
38
  "dev": "tsc --watch",
39
+ "typecheck": "tsc --noEmit",
38
40
  "start": "node dist/index.js",
39
41
  "ci": "node dist/cli.js"
40
42
  },
@@ -44,6 +46,7 @@
44
46
  },
45
47
  "devDependencies": {
46
48
  "@types/node": "^22.0.0",
49
+ "esbuild": "^0.24.0",
47
50
  "typescript": "^5.7.0"
48
51
  },
49
52
  "engines": {