@fasttest-ai/qa-agent 1.0.4-staging.2 → 1.0.4-staging.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 +3 -0
- package/commands/ftest.md +2 -2
- package/commands/qa.md +2 -2
- package/dist/cli.js +20 -20
- package/dist/index.js +70 -66
- package/dist/install.js +49 -34
- package/package.json +1 -1
package/dist/install.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import{execFileSync as b}from"node:child_process";import{createInterface as
|
|
2
|
-
`),v.forEach((
|
|
3
|
-
`),"claude-code")}function q(){try{return b("claude",["--version"],{stdio:"pipe",shell:C}),!0}catch{return!1}}function H(e,s=[]){try{b("claude",["mcp","remove","--scope",e,a],{stdio:"pipe",shell:C})}catch{}let
|
|
4
|
-
`),{added:!0,alreadyExists:!1})}function B(){if(!c(m))return!1;try{let e=JSON.parse(d(m,"utf-8")),s=e.permissions?.allow;if(!Array.isArray(s))return!1;let
|
|
5
|
-
`),!0)}catch{return!1}}var
|
|
6
|
-
`),!0}function z(e,s=[]){let
|
|
7
|
-
`),!0}function Z(e,s=[]){let
|
|
8
|
-
`)&&(
|
|
9
|
-
`),
|
|
10
|
-
`+
|
|
11
|
-
`;return f(e,
|
|
1
|
+
import{execFileSync as b}from"node:child_process";import{createInterface as j}from"node:readline";import{existsSync as c,mkdirSync as h,readFileSync as d,writeFileSync as f,unlinkSync as O,copyFileSync as x}from"node:fs";import{homedir as g,platform as _}from"node:os";import{join as l,dirname as D}from"node:path";import{fileURLToPath as F}from"node:url";var C=_()==="win32",J=_()==="darwin",a="fasttest",L=C?"npx.cmd":"npx",$="npx",M=D(F(import.meta.url)),U=(()=>{try{return JSON.parse(d(l(M,"..","package.json"),"utf-8")).version??""}catch{return""}})(),T=U.includes("-staging")?"staging":"latest",I=["-y",`@fasttest-ai/qa-agent@${T}`],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:"vscode",label:"VS Code / Copilot",globalConfigPath:J?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}];function W(){let e=process.argv[2],s=process.argv.slice(3),o=null,n="user",t=!1,i=null;for(let r=0;r<s.length;r++)s[r]==="--ide"&&s[r+1]?o=s[++r]:s[r]==="--scope"&&s[r+1]?n=s[++r]:s[r]==="--api-key"&&s[r+1]?i=s[++r]:s[r]==="--skip-permissions"&&(t=!0);return{action:e,ide:o,scope:n,skipPermissions:t,apiKey:i}}function G(e){let s=j({input:process.stdin,output:process.stdout});return new Promise(o=>{s.question(e,n=>{s.close(),o(n.trim())})})}async function E(){console.log(` Which IDE do you use?
|
|
2
|
+
`),v.forEach((n,t)=>{console.log(` ${t+1}. ${n.label}`)}),console.log();let e=await G(" > "),s=parseInt(e,10)-1;if(s>=0&&s<v.length)return v[s].id;let o=v.find(n=>n.id===e.toLowerCase()||n.label.toLowerCase()===e.toLowerCase());return o?o.id:(console.log(` Invalid selection, defaulting to Claude Code.
|
|
3
|
+
`),"claude-code")}function q(){try{return b("claude",["--version"],{stdio:"pipe",shell:C}),!0}catch{return!1}}function H(e,s=[]){try{b("claude",["mcp","remove","--scope",e,a],{stdio:"pipe",shell:C})}catch{}let o=["mcp","add","--scope",e,a,"--",$,...I,...s];try{return b("claude",o,{stdio:"inherit",shell:C}),!0}catch{return!1}}function K(e){try{return b("claude",["mcp","remove","--scope",e,a],{stdio:"inherit",shell:C}),!0}catch{return!1}}var P=l(g(),".claude"),m=l(P,"settings.json"),y="mcp__fasttest";function V(){c(P)||h(P,{recursive:!0});let e={};if(c(m))try{e=JSON.parse(d(m,"utf-8"))}catch{let s=m+".bak";f(s,d(m)),console.log(` Warning: ${m} was corrupted. Backed up to ${s}`),e={}}return e.permissions||(e.permissions={}),Array.isArray(e.permissions.allow)||(e.permissions.allow=[]),e.permissions.allow.includes(y)?{added:!1,alreadyExists:!0}:(e.permissions.allow.push(y),f(m,JSON.stringify(e,null,2)+`
|
|
4
|
+
`),{added:!0,alreadyExists:!1})}function B(){if(!c(m))return!1;try{let e=JSON.parse(d(m,"utf-8")),s=e.permissions?.allow;if(!Array.isArray(s))return!1;let o=s.indexOf(y);return o===-1?!1:(s.splice(o,1),f(m,JSON.stringify(e,null,2)+`
|
|
5
|
+
`),!0)}catch{return!1}}var w=l(g(),".claude","commands"),N=["ftest.md","qa.md"];function Y(){let e=0,s=0;for(let o of N){let n=l(M,"..","commands",o),t=l(w,o);c(n)&&(c(t)?(x(n,t),s++):(h(w,{recursive:!0}),x(n,t),e++))}return{installed:e,updated:s}}function Q(){let e=0;for(let s of N){let o=l(w,s);if(c(o))try{O(o),e++}catch{}}return e}function X(e,s=[]){let o=l(e,"..");c(o)||h(o,{recursive:!0});let n={};if(c(e))try{n=JSON.parse(d(e,"utf-8"))}catch{let t=e+".bak";f(t,d(e)),console.log(` Warning: ${e} was corrupted. Backed up to ${t}`),n={}}return(!n.mcpServers||typeof n.mcpServers!="object")&&(n.mcpServers={}),n.mcpServers[a]={command:$,args:[...I,...s]},f(e,JSON.stringify(n,null,2)+`
|
|
6
|
+
`),!0}function z(e,s=[]){let o=l(e,"..");c(o)||h(o,{recursive:!0});let n={};if(c(e))try{n=JSON.parse(d(e,"utf-8"))}catch{let t=e+".bak";f(t,d(e)),console.log(` Warning: ${e} was corrupted. Backed up to ${t}`),n={}}return(!n.servers||typeof n.servers!="object")&&(n.servers={}),n.servers[a]={type:"stdio",command:$,args:[...I,...s]},f(e,JSON.stringify(n,null,2)+`
|
|
7
|
+
`),!0}function Z(e,s=[]){let o=l(e,"..");c(o)||h(o,{recursive:!0});let n="";c(e)&&(n=d(e,"utf-8"));let t=`[mcp_servers.${a}]`;if(n.includes(t)){let i=new RegExp(`\\[mcp_servers\\.${a}\\][\\s\\S]*?(?=\\n\\[|$)`);n=n.replace(i,R(s))}else n.length>0&&!n.endsWith(`
|
|
8
|
+
`)&&(n+=`
|
|
9
|
+
`),n+=`
|
|
10
|
+
`+R(s)+`
|
|
11
|
+
`;return f(e,n),!0}function R(e=[]){return`[mcp_servers.${a}]
|
|
12
12
|
command = "${$}"
|
|
13
|
-
args = [${[...I,...e].map(s=>`"${s}"`).join(", ")}]`}function ee(e){if(!c(e))return!1;try{let s=JSON.parse(d(e,"utf-8")),
|
|
14
|
-
`),!0)}catch{return!1}}function se(e){if(!c(e))return!1;try{let s=JSON.parse(d(e,"utf-8")),
|
|
15
|
-
`),!0)}catch{return!1}}function
|
|
13
|
+
args = [${[...I,...e].map(s=>`"${s}"`).join(", ")}]`}function ee(e){if(!c(e))return!1;try{let s=JSON.parse(d(e,"utf-8")),o=s.mcpServers;return!o||!(a in o)?!1:(delete o[a],f(e,JSON.stringify(s,null,2)+`
|
|
14
|
+
`),!0)}catch{return!1}}function se(e){if(!c(e))return!1;try{let s=JSON.parse(d(e,"utf-8")),o=s.servers;return!o||!(a in o)?!1:(delete o[a],f(e,JSON.stringify(s,null,2)+`
|
|
15
|
+
`),!0)}catch{return!1}}function oe(e){if(!c(e))return!1;try{let s=d(e,"utf-8"),o=`[mcp_servers.${a}]`;if(!s.includes(o))return!1;let n=new RegExp(`\\n?\\[mcp_servers\\.${a}\\][\\s\\S]*?(?=\\n\\[|$)`);return s=s.replace(n,""),f(e,s),!0}catch{return!1}}function ne(){if(process.env.FASTTEST_SKIP_PLAYWRIGHT==="1"){console.log(" Skipping Playwright install (FASTTEST_SKIP_PLAYWRIGHT=1)");return}try{b(L,["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 te(e,s,o){let n=o?["--api-key",o]:[];switch(e.format){case"cli":return q()?H(s,n):(console.log(`
|
|
16
16
|
Claude Code CLI not found in PATH.
|
|
17
17
|
|
|
18
18
|
Install Claude Code first:
|
|
@@ -23,35 +23,50 @@ args = [${[...I,...e].map(s=>`"${s}"`).join(", ")}]`}function ee(e){if(!c(e))ret
|
|
|
23
23
|
"mcpServers": {
|
|
24
24
|
"fasttest": {
|
|
25
25
|
"command": "npx",
|
|
26
|
-
"args": ["-y", "@fasttest-ai/qa-agent@${
|
|
26
|
+
"args": ["-y", "@fasttest-ai/qa-agent@${T}"]
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
-
`),!1);case"json-mcpServers":return X(e.globalConfigPath,
|
|
30
|
+
`),!1);case"json-mcpServers":return X(e.globalConfigPath,n);case"json-servers":return z(e.globalConfigPath,n);case"toml":return Z(e.globalConfigPath,n)}}function re(e,s){switch(e.format){case"cli":return K(s);case"json-mcpServers":return ee(e.globalConfigPath);case"json-servers":return se(e.globalConfigPath);case"toml":return oe(e.globalConfigPath)}}async function ie(e){console.log(`
|
|
31
31
|
FastTest Agent Installer
|
|
32
|
-
`);let s=e.ide??await
|
|
33
|
-
Installing for ${
|
|
34
|
-
`);let o
|
|
32
|
+
`);let s=e.ide??await E(),o=v.find(u=>u.id===s);o||(console.log(` Unknown IDE: ${s}`),console.log(` Supported: ${v.map(u=>u.id).join(", ")}`),process.exit(1)),console.log(`
|
|
33
|
+
Installing for ${o.label}...
|
|
34
|
+
`);let n=o.id==="claude-code",t=o.hasPermissions&&!e.skipPermissions,i=2;t&&i++,n&&i++;let r=1;if(console.log(` [${r}/${i}] Registering MCP server...`),te(o,e.scope,e.apiKey)){let u=o.format==="cli"?`(scope: ${e.scope})`:o.globalConfigPath;console.log(` MCP server "${a}" registered ${u}`)}else o.format==="cli"&&process.exit(1),console.log(" Warning: Could not register MCP server.");if(r++,t){console.log(` [${r}/${i}] Pre-approving tools...`);let{added:u,alreadyExists:S}=V();u?console.log(` Added ${y} to ${m}`):S&&console.log(` Already configured (${y})`),r++}if(n){console.log(` [${r}/${i}] Installing /ftest and /qa commands...`);let{installed:u,updated:S}=Y();u>0&&console.log(` Added ${u} command(s) to ${w}`),S>0&&console.log(` Updated ${S} command(s)`),u===0&&S===0&&console.log(" Warning: Could not install commands (source files missing)"),r++}console.log(` [${r}/${i}] Installing Playwright browsers...`),ne();let k={"claude-code":"CLAUDE.md",cursor:".cursor/rules",vscode:".github/copilot-instructions.md",codex:"AGENTS.md"};console.log(n?`
|
|
35
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
36
|
+
FastTest installed! Try it now \u2014 paste into ${o.label}:
|
|
37
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
35
38
|
|
|
36
|
-
|
|
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(o?`
|
|
40
|
-
Done! Open ${n.label} and try:
|
|
39
|
+
/ftest test the login flow
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
More things to try:
|
|
42
|
+
/ftest explore my app Map out pages and flows
|
|
43
|
+
/ftest break my app Adversarial security testing
|
|
44
|
+
/ftest shield my app Generate a full regression suite
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
Set up an environment so you never have to pass a URL:
|
|
47
|
+
/ftest setup environment "dev" for my project
|
|
48
|
+
|
|
49
|
+
Tip: Add testing rules to ${k[o.id]} \u2014 https://docs.fasttest.ai
|
|
45
50
|
`:`
|
|
46
|
-
|
|
51
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
52
|
+
FastTest installed! Try it now \u2014 paste into ${o.label}:
|
|
53
|
+
\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
54
|
+
|
|
55
|
+
test the login flow
|
|
56
|
+
|
|
57
|
+
More things to try:
|
|
58
|
+
explore my app Map out pages and flows
|
|
59
|
+
break my app Adversarial security testing
|
|
60
|
+
vibe shield my app Generate a full regression suite
|
|
47
61
|
|
|
48
|
-
|
|
62
|
+
Set up an environment so you never have to pass a URL:
|
|
63
|
+
setup a "dev" environment for my project
|
|
49
64
|
|
|
50
|
-
${k}
|
|
65
|
+
Tip: Add testing rules to ${k[o.id]} \u2014 https://docs.fasttest.ai
|
|
51
66
|
`)}async function le(e){console.log(`
|
|
52
67
|
FastTest Agent Uninstaller
|
|
53
|
-
`);let s=e.ide??await
|
|
54
|
-
Uninstalling from ${
|
|
55
|
-
`);let o
|
|
56
|
-
FastTest has been uninstalled from ${
|
|
57
|
-
`)}var
|
|
68
|
+
`);let s=e.ide??await E(),o=v.find(p=>p.id===s);o||(console.log(` Unknown IDE: ${s}`),process.exit(1)),console.log(`
|
|
69
|
+
Uninstalling from ${o.label}...
|
|
70
|
+
`);let n=o.id==="claude-code",t=1;o.hasPermissions&&t++,n&&t++;let i=1;console.log(` [${i}/${t}] Removing MCP server...`);let r=re(o,e.scope);if(console.log(r?` MCP server "${a}" removed`:" MCP server was not registered (nothing to remove)"),i++,o.hasPermissions){console.log(` [${i}/${t}] Removing tool permissions...`);let p=B();console.log(p?` Removed ${y} from ${m}`:" Permission was not present (nothing to remove)"),i++}if(n){console.log(` [${i}/${t}] Removing /ftest and /qa commands...`);let p=Q();p>0?console.log(` Removed ${p} command(s)`):console.log(" Commands were not installed (nothing to remove)")}console.log(`
|
|
71
|
+
FastTest has been uninstalled from ${o.label}.
|
|
72
|
+
`)}var A=W();A.action==="uninstall"?await le(A):await ie(A);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fasttest-ai/qa-agent",
|
|
3
|
-
"version": "1.0.4-staging.
|
|
3
|
+
"version": "1.0.4-staging.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
6
|
"homepage": "https://fasttest.ai",
|