@fasttest-ai/qa-agent 1.0.1 → 1.0.3
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/README.md +5 -5
- package/commands/ftest.md +19 -4
- package/commands/qa.md +33 -18
- package/dist/cli.js +22 -22
- package/dist/index.js +55 -41
- package/dist/install.js +31 -33
- package/package.json +6 -8
package/dist/install.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import{execFileSync as y}from"node:child_process";import{createInterface as M}from"node:readline";import{existsSync as
|
|
2
|
-
`),v.forEach((
|
|
3
|
-
`),"claude-code")}function U(){try{return y("claude",["--version"],{stdio:"pipe",shell:C}),!0}catch{return!1}}function W(e){let
|
|
4
|
-
`),{added:!0,alreadyExists:!1})}function H(){if(!
|
|
5
|
-
`),!0)}catch{return!1}}var B=O(T(import.meta.url)),w=
|
|
6
|
-
`),!0}function Q(e){let
|
|
7
|
-
`),!0}function X(e){let
|
|
8
|
-
`)&&(
|
|
9
|
-
`),
|
|
10
|
-
`+
|
|
11
|
-
`;return f(e,
|
|
1
|
+
import{execFileSync as y}from"node:child_process";import{createInterface as M}from"node:readline";import{existsSync as c,mkdirSync as h,readFileSync as m,writeFileSync as f,unlinkSync as N,copyFileSync as _}from"node:fs";import{homedir as g,platform as j}from"node:os";import{join as l,dirname as O}from"node:path";import{fileURLToPath as T}from"node:url";var C=j()==="win32",D=j()==="darwin",a="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:l(g(),".cursor","mcp.json"),format:"json-mcpServers",hasPermissions:!1},{id:"windsurf",label:"Windsurf",globalConfigPath:l(g(),".codeium","windsurf","mcp_config.json"),format:"json-mcpServers",hasPermissions:!1},{id:"vscode",label:"VS Code / Copilot",globalConfigPath:D?l(g(),"Library","Application Support","Code","User","mcp.json"):C?l(process.env.APPDATA??l(g(),"AppData","Roaming"),"Code","User","mcp.json"):l(g(),".config","Code","User","mcp.json"),format:"json-servers",hasPermissions:!1},{id:"codex",label:"Codex",globalConfigPath:l(g(),".codex","config.toml"),format:"toml",hasPermissions:!1},{id:"antigravity",label:"Antigravity",globalConfigPath:l(g(),".gemini","antigravity","mcp_config.json"),format:"json-mcpServers",hasPermissions:!1}];function J(){let e=process.argv[2],s=process.argv.slice(3),n=null,o="user",r=!1,i=null;for(let t=0;t<s.length;t++)s[t]==="--ide"&&s[t+1]?n=s[++t]:s[t]==="--scope"&&s[t+1]?o=s[++t]:s[t]==="--api-key"&&s[t+1]?i=s[++t]:s[t]==="--skip-permissions"&&(r=!0);return{action:e,ide:n,scope:o,skipPermissions:r,apiKey:i}}function L(e){let s=M({input:process.stdin,output:process.stdout});return new Promise(n=>{s.question(e,o=>{s.close(),n(o.trim())})})}async function R(){console.log(` Which IDE do you use?
|
|
2
|
+
`),v.forEach((o,r)=>{console.log(` ${r+1}. ${o.label}`)}),console.log();let e=await L(" > "),s=parseInt(e,10)-1;if(s>=0&&s<v.length)return v[s].id;let n=v.find(o=>o.id===e.toLowerCase()||o.label.toLowerCase()===e.toLowerCase());return n?n.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,s=[]){let n=["mcp","add","--scope",e,a,"--",$,...I,...s];try{return y("claude",n,{stdio:"inherit",shell:C}),!0}catch{try{return y("claude",["mcp","remove","--scope",e,a],{stdio:"pipe",shell:C}),y("claude",n,{stdio:"inherit",shell:C}),!0}catch{return!1}}}function G(e){try{return y("claude",["mcp","remove","--scope",e,a],{stdio:"inherit",shell:C}),!0}catch{return!1}}var P=l(g(),".claude"),u=l(P,"settings.json"),b="mcp__fasttest";function q(){c(P)||h(P,{recursive:!0});let e={};if(c(u))try{e=JSON.parse(m(u,"utf-8"))}catch{let s=u+".bak";f(s,m(u)),console.log(` Warning: ${u} was corrupted. Backed up to ${s}`),e={}}return e.permissions||(e.permissions={}),Array.isArray(e.permissions.allow)||(e.permissions.allow=[]),e.permissions.allow.includes(b)?{added:!1,alreadyExists:!0}:(e.permissions.allow.push(b),f(u,JSON.stringify(e,null,2)+`
|
|
4
|
+
`),{added:!0,alreadyExists:!1})}function H(){if(!c(u))return!1;try{let e=JSON.parse(m(u,"utf-8")),s=e.permissions?.allow;if(!Array.isArray(s))return!1;let n=s.indexOf(b);return n===-1?!1:(s.splice(n,1),f(u,JSON.stringify(e,null,2)+`
|
|
5
|
+
`),!0)}catch{return!1}}var B=O(T(import.meta.url)),w=l(g(),".claude","commands"),E=["ftest.md","qa.md"];function K(){let e=0,s=0;for(let n of E){let o=l(B,"..","commands",n),r=l(w,n);c(o)&&(c(r)?(_(o,r),s++):(h(w,{recursive:!0}),_(o,r),e++))}return{installed:e,updated:s}}function V(){let e=0;for(let s of E){let n=l(w,s);if(c(n))try{N(n),e++}catch{}}return e}function Y(e,s=[]){let n=l(e,"..");c(n)||h(n,{recursive:!0});let o={};if(c(e))try{o=JSON.parse(m(e,"utf-8"))}catch{let r=e+".bak";f(r,m(e)),console.log(` Warning: ${e} was corrupted. Backed up to ${r}`),o={}}return(!o.mcpServers||typeof o.mcpServers!="object")&&(o.mcpServers={}),o.mcpServers[a]={command:$,args:[...I,...s]},f(e,JSON.stringify(o,null,2)+`
|
|
6
|
+
`),!0}function Q(e,s=[]){let n=l(e,"..");c(n)||h(n,{recursive:!0});let o={};if(c(e))try{o=JSON.parse(m(e,"utf-8"))}catch{let r=e+".bak";f(r,m(e)),console.log(` Warning: ${e} was corrupted. Backed up to ${r}`),o={}}return(!o.servers||typeof o.servers!="object")&&(o.servers={}),o.servers[a]={type:"stdio",command:$,args:[...I,...s]},f(e,JSON.stringify(o,null,2)+`
|
|
7
|
+
`),!0}function X(e,s=[]){let n=l(e,"..");c(n)||h(n,{recursive:!0});let o="";c(e)&&(o=m(e,"utf-8"));let r=`[mcp_servers.${a}]`;if(o.includes(r)){let i=new RegExp(`\\[mcp_servers\\.${a}\\][\\s\\S]*?(?=\\n\\[|$)`);o=o.replace(i,x(s))}else o.length>0&&!o.endsWith(`
|
|
8
|
+
`)&&(o+=`
|
|
9
|
+
`),o+=`
|
|
10
|
+
`+x(s)+`
|
|
11
|
+
`;return f(e,o),!0}function x(e=[]){return`[mcp_servers.${a}]
|
|
12
12
|
command = "${$}"
|
|
13
|
-
args = [${I.map(
|
|
14
|
-
`),!0)}catch{return!1}}function Z(e){if(!
|
|
15
|
-
`),!0)}catch{return!1}}function ee(e){if(!
|
|
13
|
+
args = [${[...I,...e].map(s=>`"${s}"`).join(", ")}]`}function z(e){if(!c(e))return!1;try{let s=JSON.parse(m(e,"utf-8")),n=s.mcpServers;return!n||!(a in n)?!1:(delete n[a],f(e,JSON.stringify(s,null,2)+`
|
|
14
|
+
`),!0)}catch{return!1}}function Z(e){if(!c(e))return!1;try{let s=JSON.parse(m(e,"utf-8")),n=s.servers;return!n||!(a in n)?!1:(delete n[a],f(e,JSON.stringify(s,null,2)+`
|
|
15
|
+
`),!0)}catch{return!1}}function ee(e){if(!c(e))return!1;try{let s=m(e,"utf-8"),n=`[mcp_servers.${a}]`;if(!s.includes(n))return!1;let o=new RegExp(`\\n?\\[mcp_servers\\.${a}\\][\\s\\S]*?(?=\\n\\[|$)`);return s=s.replace(o,""),f(e,s),!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 ne(e,s,n){let o=n?["--api-key",n]:[];switch(e.format){case"cli":return U()?W(s,o):(console.log(`
|
|
16
16
|
Claude Code CLI not found in PATH.
|
|
17
17
|
|
|
18
18
|
Install Claude Code first:
|
|
@@ -27,33 +27,31 @@ args = [${I.map(e=>`"${e}"`).join(", ")}]`}function z(e){if(!a(e))return!1;try{l
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
`),!1);case"json-mcpServers":return
|
|
30
|
+
`),!1);case"json-mcpServers":return Y(e.globalConfigPath,o);case"json-servers":return Q(e.globalConfigPath,o);case"toml":return X(e.globalConfigPath,o)}}function oe(e,s){switch(e.format){case"cli":return G(s);case"json-mcpServers":return z(e.globalConfigPath);case"json-servers":return Z(e.globalConfigPath);case"toml":return ee(e.globalConfigPath)}}async function re(e){console.log(`
|
|
31
31
|
FastTest Agent Installer
|
|
32
|
-
`);let
|
|
33
|
-
Installing for ${
|
|
34
|
-
`);let n
|
|
32
|
+
`);let s=e.ide??await R(),n=v.find(d=>d.id===s);n||(console.log(` Unknown IDE: ${s}`),console.log(` Supported: ${v.map(d=>d.id).join(", ")}`),process.exit(1)),console.log(`
|
|
33
|
+
Installing for ${n.label}...
|
|
34
|
+
`);let o=n.id==="claude-code",r=n.hasPermissions&&!e.skipPermissions,i=2;r&&i++,o&&i++;let t=1;if(console.log(` [${t}/${i}] Registering MCP server...`),ne(n,e.scope,e.apiKey)){let d=n.format==="cli"?`(scope: ${e.scope})`:n.globalConfigPath;console.log(` MCP server "${a}" registered ${d}`)}else n.format==="cli"&&process.exit(1),console.log(" Warning: Could not register MCP server.");if(t++,r){console.log(` [${t}/${i}] Pre-approving tools...`);let{added:d,alreadyExists:S}=q();d?console.log(` Added ${b} to ${u}`):S&&console.log(` Already configured (${b})`),t++}if(o){console.log(` [${t}/${i}] Installing /ftest and /qa commands...`);let{installed:d,updated:S}=K();d>0&&console.log(` Added ${d} command(s) to ${w}`),S>0&&console.log(` Updated ${S} command(s)`),d===0&&S===0&&console.log(" Warning: Could not install commands (source files missing)"),t++}console.log(` [${t}/${i}] Installing Playwright browsers...`),se();let k=` 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"}[n.id]} to auto-test after building features:
|
|
35
35
|
|
|
36
36
|
## Testing
|
|
37
37
|
After implementing a feature, verify it works by running:
|
|
38
38
|
\`ftest <app-url> <what to test>\`
|
|
39
|
-
or use \`vibe shield <app-url>\` to generate a full regression suite.`;console.log(
|
|
40
|
-
Done! Open ${
|
|
39
|
+
or use \`vibe shield <app-url>\` to generate a full regression suite.`;console.log(o?`
|
|
40
|
+
Done! Open ${n.label} and try:
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
"break my app"
|
|
43
43
|
|
|
44
|
-
${
|
|
44
|
+
${k}
|
|
45
45
|
`:`
|
|
46
|
-
Done! Open ${
|
|
46
|
+
Done! Open ${n.label} and try:
|
|
47
47
|
|
|
48
|
-
"
|
|
49
|
-
"ftest explore http://localhost:3000"
|
|
50
|
-
"ftest chaos http://localhost:3000"
|
|
48
|
+
"break my app"
|
|
51
49
|
|
|
52
|
-
${
|
|
53
|
-
`)}async function
|
|
50
|
+
${k}
|
|
51
|
+
`)}async function te(e){console.log(`
|
|
54
52
|
FastTest Agent Uninstaller
|
|
55
|
-
`);let
|
|
56
|
-
Uninstalling from ${
|
|
57
|
-
`);let n
|
|
58
|
-
FastTest has been uninstalled from ${
|
|
59
|
-
`)}var A=J();A.action==="uninstall"?await
|
|
53
|
+
`);let s=e.ide??await R(),n=v.find(p=>p.id===s);n||(console.log(` Unknown IDE: ${s}`),process.exit(1)),console.log(`
|
|
54
|
+
Uninstalling from ${n.label}...
|
|
55
|
+
`);let o=n.id==="claude-code",r=1;n.hasPermissions&&r++,o&&r++;let i=1;console.log(` [${i}/${r}] Removing MCP server...`);let t=oe(n,e.scope);if(console.log(t?` MCP server "${a}" removed`:" MCP server was not registered (nothing to remove)"),i++,n.hasPermissions){console.log(` [${i}/${r}] Removing tool permissions...`);let p=H();console.log(p?` Removed ${b} from ${u}`:" Permission was not present (nothing to remove)"),i++}if(o){console.log(` [${i}/${r}] Removing /ftest and /qa commands...`);let p=V();p>0?console.log(` Removed ${p} command(s)`):console.log(" Commands were not installed (nothing to remove)")}console.log(`
|
|
56
|
+
FastTest has been uninstalled from ${n.label}.
|
|
57
|
+
`)}var A=J();A.action==="uninstall"?await te(A):await re(A);
|
package/package.json
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasttest-ai/qa-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
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
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "https://github.com/fasttest-ai/agent.git"
|
|
9
|
-
},
|
|
10
6
|
"homepage": "https://fasttest.ai",
|
|
11
7
|
"bugs": {
|
|
12
|
-
"url": "https://
|
|
8
|
+
"url": "https://fasttest.ai"
|
|
13
9
|
},
|
|
14
10
|
"author": "FastTest AI <hello@fasttest.ai>",
|
|
15
11
|
"keywords": [
|
|
@@ -38,11 +34,13 @@
|
|
|
38
34
|
"dev": "tsc --watch",
|
|
39
35
|
"typecheck": "tsc --noEmit",
|
|
40
36
|
"start": "node dist/index.js",
|
|
41
|
-
"ci": "node dist/cli.js"
|
|
37
|
+
"ci": "node dist/cli.js",
|
|
38
|
+
"prepublishOnly": "npm run build"
|
|
42
39
|
},
|
|
43
40
|
"dependencies": {
|
|
44
41
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
45
|
-
"playwright": "^1.52.0"
|
|
42
|
+
"playwright": "^1.52.0",
|
|
43
|
+
"zod": "^3.25.0"
|
|
46
44
|
},
|
|
47
45
|
"devDependencies": {
|
|
48
46
|
"@types/node": "^22.0.0",
|