runhuman 1.0.4 → 1.1.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.
Files changed (2) hide show
  1. package/dist/index.js +144 -122
  2. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -1,85 +1,89 @@
1
1
  #!/usr/bin/env node
2
- var lt=Object.defineProperty;var ut=(s=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(s,{get:(t,e)=>(typeof require<"u"?require:t)[e]}):s)(function(s){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+s+'" is not supported')});var b=(s,t)=>()=>(s&&(t=s(s=0)),t);var pt=(s,t)=>{for(var e in t)lt(s,e,{get:t[e],enumerable:!0})};function mt(s){return s instanceof R}function m(s){return mt(s)?{message:s.message,exitCode:s.exitCode,details:s.details}:s instanceof Error?{message:s.message,exitCode:1}:{message:String(s),exitCode:1}}var R,W,q,D,V,I=b(()=>{"use strict";R=class extends Error{constructor(e,n=1,o){super(e);this.exitCode=n;this.details=o;this.name="CliError"}},W=class extends R{constructor(t="Authentication failed",e){super(t,2,e),this.name="AuthenticationError"}},q=class extends R{constructor(t="Resource not found",e){super(t,3,e),this.name="NotFoundError"}},D=class extends R{constructor(t="Validation failed",e){super(t,4,e),this.name="ValidationError"}},V=class extends R{constructor(t="Operation timed out",e){super(t,5,e),this.name="TimeoutError"}}});var ie=b(()=>{"use strict"});var ee=b(()=>{"use strict"});var ae=b(()=>{"use strict"});var pe=b(()=>{"use strict";ie();ee();ae()});var me=b(()=>{"use strict"});var de=b(()=>{"use strict";me()});var ge=b(()=>{"use strict"});var fe=b(()=>{"use strict";ee()});var he=b(()=>{"use strict";ge();fe()});function a(s){return{pattern:s,build:e=>{if(!e)return s;let n=s;for(let[o,r]of Object.entries(e))r!==void 0&&(n=n.replace(`:${o}?`,r),n=n.replace(`:${o}`,r));return n=n.replace(/\/:[^/]+\?/g,""),n=n.replace(/\/+/g,"/"),n.length>1&&n.endsWith("/")&&(n=n.slice(0,-1)),n}}}var B=b(()=>{"use strict"});var be,ye=b(()=>{"use strict";B();be={dashboard:a("/dashboard"),onboarding:a("/dashboard/onboarding"),projects:a("/dashboard/projects"),usage:a("/dashboard/usage"),managePlan:a("/dashboard/manage-plan"),jobSimple:a("/dashboard/jobs/:jobId"),admin:a("/dashboard/admin"),organizations:a("/dashboard/organizations"),organization:a("/dashboard/organizations/:organizationId"),organizationMembers:a("/dashboard/organizations/:organizationId/members"),organizationSettings:a("/dashboard/organizations/:organizationId/settings"),organizationUsage:a("/dashboard/organizations/:organizationId/usage"),organizationProjects:a("/dashboard/organizations/:organizationId/projects"),settings:a("/dashboard/settings"),settingsAccount:a("/dashboard/settings/account"),settingsGitHub:a("/dashboard/settings/github"),settingsApiKeys:a("/dashboard/settings/api-keys"),tester:a("/tester"),testerJobs:a("/tester/jobs"),testerSettings:a("/tester/settings"),project:a("/dashboard/:projectId"),playground:a("/dashboard/:projectId/playground"),jobs:a("/dashboard/:projectId/jobs"),job:a("/dashboard/:projectId/jobs/:jobId"),templates:a("/dashboard/:projectId/templates"),template:a("/dashboard/:projectId/templates/:templateId"),issues:a("/dashboard/:projectId/issues"),issue:a("/dashboard/:projectId/issues/:issueNumber"),issueSessions:a("/dashboard/:projectId/issue-sessions"),issueSession:a("/dashboard/:projectId/issue-sessions/:sessionId"),projectSettings:a("/dashboard/:projectId/settings"),flowCharts:a("/dashboard/:projectId/flowcharts"),flowChart:a("/dashboard/:projectId/flowcharts/:flowchartId"),flowChartView:a("/view/flowchart/:projectId/:flowchartId"),invite:a("/invite/:token"),quickStart:a("/start"),publicJob:a("/j/:jobId/:token"),statuspage:a("/statuspage"),docs:a("/docs/quick-start"),pricing:a("/pricing"),home:a("/")}});var j,je=b(()=>{"use strict";B();j={jobs:a("/jobs"),job:a("/jobs/:jobId"),jobStatus:a("/jobs/:jobId/status"),jobFeedback:a("/jobs/:jobId/feedback"),jobClaim:a("/jobs/:jobId/claim"),run:a("/run"),execute:a("/execute"),publicJob:a("/public/jobs/:jobId/:token"),testerJob:a("/tester/jobs/:jobId"),testerJobUploadUrls:a("/tester/jobs/:jobId/upload-urls"),testerJobProcessResults:a("/tester/jobs/:jobId/process-results"),testerJobProcessingStatus:a("/tester/jobs/:jobId/processing/:processingJobId"),testerJobExtractFrames:a("/tester/jobs/:jobId/extract-frames"),testerJobAbort:a("/tester/jobs/:jobId/abort"),testerJobInvalid:a("/tester/jobs/:jobId/invalid"),keys:a("/keys"),key:a("/keys/:keyId"),keyRevoke:a("/keys/:keyId/revoke"),keyInfo:a("/key-info"),projects:a("/projects"),project:a("/projects/:projectId"),projectJobs:a("/projects/:projectId/jobs"),projectApiKeys:a("/projects/:projectId/api-keys"),projectTransfer:a("/projects/:projectId/transfer"),projectTemplates:a("/projects/:projectId/templates"),projectTemplate:a("/projects/:projectId/templates/:templateId"),projectFlowCharts:a("/projects/:projectId/flowcharts"),projectFlowChartsUpload:a("/projects/:projectId/flowcharts/upload"),projectFlowChart:a("/projects/:projectId/flowcharts/:flowchartId"),projectFlowChartData:a("/projects/:projectId/flowcharts/:flowchartId/data"),projectFlowChartChat:a("/projects/:projectId/flowcharts/:flowchartId/chat"),bulkCreateProjects:a("/projects/bulk"),githubOAuthAuthorize:a("/github/oauth/authorize"),githubCallback:a("/github/oauth/callback"),githubLink:a("/github/link"),githubInstallations:a("/github/installations"),githubInstallation:a("/github/installations/:installationId"),githubInstallationRefresh:a("/github/installations/:installationId/refresh"),githubRepos:a("/github/repos"),githubRepoCheckAccess:a("/github/repos/check-access"),githubRepoFindUrl:a("/github/repos/find-url"),githubIssuesByRepo:a("/github/issues/:owner/:repo"),githubIssues:a("/github/issues"),githubIssue:a("/github/issues/:issueNumber"),githubIssueComments:a("/github/issues/:issueNumber/comments"),githubIssueLabels:a("/github/issues/labels"),githubIssueAssignees:a("/github/issues/assignees"),githubIssueTest:a("/github/issues/test"),githubIssuesBulkTest:a("/github/issues/bulk-test"),githubTestSessions:a("/github/issues/test-sessions"),githubTestSession:a("/github/issues/test-sessions/:sessionId"),githubTestSessionSeen:a("/github/issues/test-sessions/:sessionId/seen"),githubTestSessionsCounts:a("/github/issues/test-sessions/counts"),githubBulkTest:a("/github/bulk-test"),githubWebhooks:a("/github/webhooks"),authSync:a("/auth/sync"),authMe:a("/auth/me"),authStartup:a("/auth/startup"),authDeleteAccount:a("/auth/account"),tokensBalance:a("/tokens/balance"),tokensTransactions:a("/tokens/transactions"),tokensContact:a("/tokens/contact"),health:a("/health"),status:a("/status"),templates:a("/templates"),issueAnalyzer:a("/issue-analyzer"),prAnalyzer:a("/pr-analyzer"),logs:a("/logs"),relevantIssuesDiscover:a("/relevant-issues/discover"),relevantIssuesLatest:a("/relevant-issues/latest"),onboarding:a("/onboarding"),onboardingComplete:a("/onboarding/complete"),onboardingCheck:a("/onboarding/check"),search:a("/search"),testerApply:a("/tester/apply"),testerProfile:a("/tester/profile"),testerPublicProfile:a("/testers/:testerId/profile"),testerDownloadApp:a("/tester/download-app"),testerAvatarUploadUrl:a("/tester/profile/avatar-upload-url"),testerAppVersion:a("/tester/app-version"),repoTemplates:a("/repos/:owner/:repo/templates"),repoTemplate:a("/repos/:owner/:repo/templates/:templateName"),organizations:a("/organizations"),organization:a("/organizations/:organizationId"),organizationMembers:a("/organizations/:organizationId/members"),organizationInvite:a("/organizations/:organizationId/invite"),organizationMember:a("/organizations/:organizationId/members/:userId"),organizationProjects:a("/organizations/:organizationId/projects"),organizationTokens:a("/organizations/:organizationId/tokens"),organizationTokenTransactions:a("/organizations/:organizationId/tokens/transactions"),organizationJobs:a("/organizations/:organizationId/jobs"),organizationTransferOwnership:a("/organizations/:organizationId/transfer-ownership"),organizationApiKeys:a("/organizations/:organizationId/api-keys"),transfersPending:a("/transfers/pending"),transfersOutgoing:a("/transfers/outgoing"),transferAccept:a("/transfers/:transferId/accept"),transferReject:a("/transfers/:transferId/reject"),transferCancel:a("/transfers/:transferId/cancel"),invite:a("/invites/:token"),inviteRedeem:a("/invites/:token/redeem")}});var we=b(()=>{"use strict";B();ye();je()});var Ce=b(()=>{"use strict"});var Ie=b(()=>{"use strict"});var Se=b(()=>{"use strict";Ce();Ie()});var Ae=b(()=>{"use strict"});var ke=b(()=>{"use strict"});var xe=b(()=>{"use strict";Ae();ke()});var Ue=b(()=>{"use strict"});var Pe=b(()=>{"use strict"});var Te=b(()=>{"use strict";Ue();Pe()});var Re=b(()=>{"use strict"});var Ee=b(()=>{"use strict";Re()});var Oe=b(()=>{"use strict"});var Ne=b(()=>{"use strict";Oe()});var ve=b(()=>{"use strict";pe();de();he();we();Se();xe();Te();Ee();Ne()});import Bt from"axios";var g,U=b(()=>{"use strict";I();ve();g=class{client;config;constructor(t){if(this.config=t,!t.apiUrl)throw new Error("apiUrl is required in CLI config");this.client=Bt.create({baseURL:t.apiUrl+"/api",timeout:3e4,headers:{"Content-Type":"application/json"}}),this.client.interceptors.request.use(e=>(this.config.apiKey&&(e.headers.Authorization=`Bearer ${this.config.apiKey}`),e)),this.client.interceptors.response.use(e=>e,e=>{throw this.handleError(e)})}handleError(t){if(t.response){let e=t.response.status,n=t.response.data,o=n?.error||n?.message||t.message;switch(e){case 401:case 403:return new W(o,n);case 404:return new q(o,n);case 400:case 422:return new D(o,n);default:return new R(o,1,n)}}return t.code==="ECONNABORTED"?new R("Request timeout",5):t.code==="ENOTFOUND"||t.code==="ECONNREFUSED"?new R("Cannot connect to Runhuman API",1):new R(t.message,1)}async createJob(t){return(await this.client.post(j.jobs.build(),t)).data}async getJob(t){return(await this.client.get(j.jobStatus.build({jobId:t}))).data}async listJobs(t){return(await this.client.get(j.jobs.build(),{params:t})).data}async cancelJob(t){await this.client.post(`${j.job.build({jobId:t})}/cancel`)}async deleteJob(t){await this.client.delete(j.job.build({jobId:t}))}async listProjects(t){return(await this.client.get(j.projects.build(),{params:t})).data}async getProject(t){return(await this.client.get(j.project.build({projectId:t}))).data}async createProject(t){return(await this.client.post(j.projects.build(),t)).data}async updateProject(t,e){return(await this.client.put(j.project.build({projectId:t}),e)).data}async deleteProject(t){await this.client.delete(j.project.build({projectId:t}))}async listApiKeys(t){return(await this.client.get(j.projectApiKeys.build({projectId:t}))).data}async createApiKey(t,e){return(await this.client.post(j.projectApiKeys.build({projectId:t}),{name:e})).data}async deleteApiKey(t){await this.client.delete(j.key.build({keyId:t}))}async listTemplates(t){return(await this.client.get(j.projectTemplates.build({projectId:t}))).data}async getTemplate(t,e){return(await this.client.get(j.projectTemplate.build({projectId:t,templateId:e}))).data}async createTemplate(t,e){return(await this.client.post(j.projectTemplates.build({projectId:t}),e)).data}async updateTemplate(t,e,n){return(await this.client.put(j.projectTemplate.build({projectId:t,templateId:e}),n)).data}async deleteTemplate(t,e){await this.client.delete(j.projectTemplate.build({projectId:t,templateId:e}))}async getCurrentUser(){return(await this.client.get(j.authMe.build())).data}async getTokenBalance(){return(await this.client.get(j.tokensBalance.build())).data}async linkGithubRepo(t,e,n){return(await this.client.post(`${j.project.build({projectId:t})}/github/link`,{owner:e,repo:n})).data}async listGithubRepos(t){let e=t?{projectId:t}:void 0;return(await this.client.get(j.githubRepos.build(),{params:e})).data}async listGithubIssues(t,e,n){return(await this.client.get(j.githubIssuesByRepo.build({owner:t,repo:e}),{params:n})).data}async getGithubIssue(t,e,n){return(await this.client.get(`${j.githubIssuesByRepo.build({owner:t,repo:e})}/${n}`)).data}}});import{cosmiconfig as Zt}from"cosmiconfig";import{homedir as Qt}from"os";import{join as N}from"path";import{readFileSync as F,writeFileSync as O,existsSync as P,mkdirSync as te,chmodSync as Xt}from"fs";function to(s){return eo.includes(s)}var Yt,E,M,z,eo,d,k=b(()=>{"use strict";Yt="runhuman",E=N(Qt(),".config","runhuman"),M=N(E,"config.json"),z=N(E,"credentials.json"),eo=["pretty","json","compact"];d=class{constructor(t=process.cwd()){this.cwd=t}projectConfig=null;globalConfig=null;envConfig=null;async loadConfig(t={}){this.envConfig=this.loadEnvConfig(),this.projectConfig=await this.loadProjectConfig(),this.globalConfig=this.loadGlobalConfig();let e=this.loadCredentials(),n=e?.accessToken?{apiKey:e.accessToken}:{},o=Object.fromEntries(Object.entries(t).filter(([,u])=>u!==void 0));return{...this.getDefaults(),...this.globalConfig,...this.projectConfig,...n,...this.envConfig,...o}}getDefaults(){return{apiUrl:"https://runhuman.com",outputFormat:"pretty",color:!0,autoOpenBrowser:!0,defaultDuration:5,defaultScreenSize:"desktop"}}loadEnvConfig(){let t={};return process.env.RUNHUMAN_API_KEY&&(t.apiKey=process.env.RUNHUMAN_API_KEY),process.env.RUNHUMAN_API_URL&&(t.apiUrl=process.env.RUNHUMAN_API_URL),process.env.RUNHUMAN_PROJECT&&(t.project=process.env.RUNHUMAN_PROJECT),process.env.RUNHUMAN_DEFAULT_URL&&(t.defaultUrl=process.env.RUNHUMAN_DEFAULT_URL),process.env.RUNHUMAN_DEFAULT_DURATION&&(t.defaultDuration=parseInt(process.env.RUNHUMAN_DEFAULT_DURATION,10)),process.env.RUNHUMAN_DEFAULT_SCREEN_SIZE&&(t.defaultScreenSize=process.env.RUNHUMAN_DEFAULT_SCREEN_SIZE),process.env.RUNHUMAN_OUTPUT_FORMAT&&to(process.env.RUNHUMAN_OUTPUT_FORMAT)&&(t.outputFormat=process.env.RUNHUMAN_OUTPUT_FORMAT),process.env.RUNHUMAN_NO_COLOR==="1"&&(t.color=!1),t}async loadProjectConfig(){try{return(await Zt(Yt).search(this.cwd))?.config||null}catch{return null}}loadGlobalConfig(){try{if(!P(M))return null;let t=F(M,"utf-8");return JSON.parse(t)}catch{return null}}async get(t){return(await this.loadConfig())[t]}async set(t,e,n=!1){n?await this.setGlobalConfig(t,e):await this.setProjectConfig(t,e)}async setGlobalConfig(t,e){P(E)||te(E,{recursive:!0});let n={};if(P(M)){let o=F(M,"utf-8");n=JSON.parse(o)}n[t]=e,O(M,JSON.stringify(n,null,2))}async setProjectConfig(t,e){let n=N(this.cwd,".runhumanrc"),o={};if(P(n)){let r=F(n,"utf-8");o=JSON.parse(r)}o[t]=e,O(n,JSON.stringify(o,null,2))}async saveProjectConfig(t){let e=N(this.cwd,".runhumanrc"),n={};if(P(e)){let r=F(e,"utf-8");n=JSON.parse(r)}let o={...n,...t};O(e,JSON.stringify(o,null,2))}async list(){let t=await this.loadConfig();return{global:this.globalConfig,project:this.projectConfig,env:this.envConfig,effective:t}}async reset(t){if((t==="global"||t==="all")&&P(M)&&O(M,"{}"),t==="project"||t==="all"){let e=N(this.cwd,".runhumanrc");P(e)&&O(e,"{}")}}saveCredentials(t){P(E)||te(E,{recursive:!0}),O(z,JSON.stringify(t,null,2));try{process.platform!=="win32"&&Xt(z,384)}catch{}}loadCredentials(){try{if(!P(z))return null;let t=F(z,"utf-8");return JSON.parse(t)}catch{return null}}clearCredentials(){P(z)&&O(z,"{}")}saveUserInfo(t){let e=N(E,"user.json");P(E)||te(E,{recursive:!0}),O(e,JSON.stringify(t,null,2))}loadUserInfo(){try{let t=N(E,"user.json");if(!P(t))return null;let e=F(t,"utf-8");return JSON.parse(e)}catch{return null}}clearUserInfo(){let t=N(E,"user.json");P(t)&&O(t,"{}")}}});import L from"chalk";import oo from"cli-table3";var c,S=b(()=>{"use strict";c=class{constructor(t={}){this.options=t}output(t){this.options.json?console.log(JSON.stringify(t,null,2)):t.success?t.data&&!this.options.quiet&&this.outputPretty(t.data):this.outputError(t.error?.message||"An error occurred")}outputPretty(t){typeof t=="string"?console.log(t):(Array.isArray(t),console.log(JSON.stringify(t,null,2)))}outputError(t,e){if(this.options.json){let n={success:!1,error:{message:t,details:e},timestamp:new Date().toISOString()};console.error(JSON.stringify(n,null,2))}else console.error(this.color("red",`
3
- Error: ${t}
4
- `)),e&&console.error(this.color("gray",JSON.stringify(e,null,2)))}success(t){!this.options.json&&!this.options.quiet&&console.log(this.color("green",`
5
- ${t}
6
- `))}info(t){!this.options.json&&!this.options.quiet&&console.log(this.color("blue",t))}warn(t){!this.options.json&&!this.options.quiet&&console.warn(this.color("yellow",`Warning: ${t}`))}formatJobList(t){if(this.options.format==="compact")return t.map(n=>`${n.id} ${n.status} ${n.url}`).join(`
7
- `);let e=new oo({head:["Job ID","Status","URL","Created","Duration","Cost"].map(n=>this.color("cyan",n)),colWidths:[15,12,30,14,10,10]});return t.forEach(n=>{e.push([n.id,this.formatStatus(n.status),this.truncate(n.url,28),this.formatDate(n.createdAt),n.testDurationSeconds?this.formatDuration(n.testDurationSeconds):"-",n.costUsd?`$${n.costUsd.toFixed(3)}`:"-"])}),e.toString()}formatStatus(t){let e={pending:"yellow",waiting:"blue",working:"blue",creating_issues:"cyan",completed:"green",incomplete:"yellow",abandoned:"red",rejected:"gray",error:"red",failed:"red"},n=t.replace("_"," ");return this.color(e[t]||"white",n)}formatDuration(t){if(t<60)return`${t}s`;let e=Math.floor(t/60),n=t%60;if(e<60)return`${e}m ${n}s`;let o=Math.floor(e/60),r=e%60;return`${o}h ${r}m ${n}s`}formatDate(t){let e=new Date(t),o=new Date().getTime()-e.getTime(),r=Math.floor(o/1e3),u=Math.floor(r/60),i=Math.floor(u/60),l=Math.floor(i/24);return r<60?`${r}s ago`:u<60?`${u}m ago`:i<24?`${i}h ago`:l<30?`${l}d ago`:e.toLocaleDateString()}truncate(t,e){return t.length<=e?t:t.substring(0,e-3)+"..."}color(t,e){if(this.options.color===!1)return e;let o={red:L.red,green:L.green,blue:L.blue,yellow:L.yellow,cyan:L.cyan,gray:L.gray,white:L.white}[t];return o?o(e):e}static result(t){return{success:!0,data:t,timestamp:new Date().toISOString()}}static error(t,e,n){return{success:!1,error:{message:t,code:e,details:n},timestamp:new Date().toISOString()}}}});var ne={};pt(ne,{waitCommand:()=>oe,waitForJob:()=>De});import{Command as no}from"commander";import ro from"ora";async function De(s,t,e,n=600){let o=Date.now(),r=n*1e3,u=1e4,i=null;for(e.options.json||(i=ro("\u23F3 Waiting for job completion...").start());;){let l=Date.now()-o;if(l>=r)throw i&&i.fail("Timeout waiting for job completion"),new V(`Job did not complete within ${n} seconds`);let p=await t.getJob(s);if(i){let f=e.formatDuration(Math.floor(l/1e3));i.text=`\u23F3 Waiting for job completion... (${f} elapsed, status: ${p.status})`}if(p.status==="completed"){if(i&&i.succeed("\u2705 Test Completed!"),!e.options.json){if(console.log(`
8
- Duration: `+(p.testDurationSeconds?e.formatDuration(p.testDurationSeconds):"N/A")),console.log(" Cost: $"+(p.costUsd||0).toFixed(3)),p.testerName&&console.log(" Tester: "+p.testerName),p.result){console.log(`
9
- \u{1F4CB} Results Summary:`);for(let[f,h]of Object.entries(p.result))console.log(` ${f}: ${h}`)}console.log(`
2
+ var Ee=Object.defineProperty;var Te=(s=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(s,{get:(o,e)=>(typeof require<"u"?require:o)[e]}):s)(function(s){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+s+'" is not supported')});var q=(s,o)=>()=>(s&&(o=s(s=0)),o);var Me=(s,o)=>{for(var e in o)Ee(s,e,{get:o[e],enumerable:!0})};function ve(s){return s instanceof O}function m(s){return ve(s)?{message:s.message,exitCode:s.exitCode,details:s.details}:s instanceof Error?{message:s.message,exitCode:1}:{message:String(s),exitCode:1}}var O,G,_,E,W,B,b=q(()=>{"use strict";O=class extends Error{constructor(e,n=1,t){super(e);this.exitCode=n;this.details=t;this.name="CliError"}},G=class extends O{constructor(o="Authentication failed",e){super(o,2,e),this.name="AuthenticationError"}},_=class extends O{constructor(o="Resource not found",e){super(o,3,e),this.name="NotFoundError"}},E=class extends O{constructor(o="Validation failed",e){super(o,4,e),this.name="ValidationError"}},W=class extends O{constructor(o="Operation timed out",e){super(o,5,e),this.name="TimeoutError"}},B=class extends O{constructor(o="Insufficient balance",e){super(o,6,e),this.name="InsufficientBalanceError"}}});import ze from"axios";import{apiRoutes as w}from"@runhuman/shared";var d,k=q(()=>{"use strict";b();d=class{client;config;constructor(o){if(this.config=o,!o.apiUrl)throw new Error("apiUrl is required in CLI config");this.client=ze.create({baseURL:o.apiUrl+"/api",timeout:3e4,headers:{"Content-Type":"application/json"}}),this.client.interceptors.request.use(e=>(this.config.apiKey&&(e.headers.Authorization=`Bearer ${this.config.apiKey}`),e)),this.client.interceptors.response.use(e=>e,e=>{throw this.handleError(e)})}handleError(o){if(o.response){let e=o.response.status,n=o.response.data,t=n?.error||n?.message||o.message;switch(e){case 401:case 403:return new G(t,n);case 402:return new B(t,n);case 404:return new _(t,n);case 400:case 422:return new E(t,n);default:return new O(t,1,n)}}return o.code==="ECONNABORTED"?new O("Request timeout",5):o.code==="ENOTFOUND"||o.code==="ECONNREFUSED"?new O("Cannot connect to Runhuman API",1):new O(o.message,1)}async createJob(o){return(await this.client.post(w.jobs.build(),o)).data}async getJob(o){return(await this.client.get(w.jobStatus.build({jobId:o}))).data}async listJobs(o){return(await this.client.get(w.jobs.build(),{params:o})).data}async cancelJob(o){await this.client.post(`${w.job.build({jobId:o})}/cancel`)}async deleteJob(o){await this.client.delete(w.job.build({jobId:o}))}async listProjects(o){return(await this.client.get(w.projects.build(),{params:o})).data}async getProject(o){return(await this.client.get(w.project.build({projectId:o}))).data}async createProject(o){return(await this.client.post(w.projects.build(),o)).data}async updateProject(o,e){return(await this.client.put(w.project.build({projectId:o}),e)).data}async deleteProject(o){await this.client.delete(w.project.build({projectId:o}))}async listApiKeys(o){return(await this.client.get(w.organizationApiKeys.build({organizationId:o}))).data}async createApiKey(o,e){return(await this.client.post(w.organizationApiKeys.build({organizationId:o}),{name:e})).data}async deleteApiKey(o){await this.client.delete(w.key.build({keyId:o}))}async listOrganizations(o){return(await this.client.get(w.organizations.build(),{params:o})).data}async getOrganization(o){return(await this.client.get(w.organization.build({organizationId:o}))).data}async getOrganizationBilling(o){return(await this.client.get(w.organizationBilling.build({organizationId:o}))).data}async listOrganizationProjects(o,e){return(await this.client.get(w.organizationProjects.build({organizationId:o}),{params:e})).data}async listTemplates(o){return(await this.client.get(w.projectTemplates.build({projectId:o}))).data}async getTemplate(o,e){return(await this.client.get(w.projectTemplate.build({projectId:o,templateId:e}))).data}async createTemplate(o,e){return(await this.client.post(w.projectTemplates.build({projectId:o}),e)).data}async updateTemplate(o,e,n){return(await this.client.put(w.projectTemplate.build({projectId:o,templateId:e}),n)).data}async deleteTemplate(o,e){await this.client.delete(w.projectTemplate.build({projectId:o,templateId:e}))}async getCurrentUser(){return(await this.client.get(w.authMe.build())).data}async linkGithubRepo(o,e,n){return(await this.client.post(`${w.project.build({projectId:o})}/github/link`,{owner:e,repo:n})).data}async listGithubRepos(o,e){return(await this.client.get(w.organizationGitHubRepos.build({organizationId:o}),{params:e})).data}async listGithubIssues(o,e,n){return(await this.client.get(w.githubIssuesByRepo.build({owner:o,repo:e}),{params:n})).data}async getGithubIssue(o,e,n){return(await this.client.get(`${w.githubIssuesByRepo.build({owner:o,repo:e})}/${n}`)).data}}});import{cosmiconfig as Je}from"cosmiconfig";import{homedir as Le}from"os";import{join as N}from"path";import{readFileSync as L,writeFileSync as D,existsSync as R,mkdirSync as X,chmodSync as $e}from"fs";function qe(s){return Fe.includes(s)}var Ke,x,M,$,Fe,g,U=q(()=>{"use strict";Ke="runhuman",x=N(Le(),".config","runhuman"),M=N(x,"config.json"),$=N(x,"credentials.json"),Fe=["pretty","json","compact"];g=class{constructor(o=process.cwd()){this.cwd=o}projectConfig=null;globalConfig=null;envConfig=null;async loadConfig(o={}){this.envConfig=this.loadEnvConfig(),this.projectConfig=await this.loadProjectConfig(),this.globalConfig=this.loadGlobalConfig();let e=this.loadCredentials(),n=e?.accessToken?{apiKey:e.accessToken}:{},t=Object.fromEntries(Object.entries(o).filter(([,c])=>c!==void 0));return{...this.getDefaults(),...this.globalConfig,...this.projectConfig,...n,...this.envConfig,...t}}getDefaults(){return{apiUrl:"https://runhuman.com",outputFormat:"pretty",color:!0,autoOpenBrowser:!0,defaultDuration:5,defaultScreenSize:"desktop"}}loadEnvConfig(){let o={};return process.env.RUNHUMAN_API_KEY&&(o.apiKey=process.env.RUNHUMAN_API_KEY),process.env.RUNHUMAN_API_URL&&(o.apiUrl=process.env.RUNHUMAN_API_URL),process.env.RUNHUMAN_PROJECT&&(o.project=process.env.RUNHUMAN_PROJECT),process.env.RUNHUMAN_DEFAULT_URL&&(o.defaultUrl=process.env.RUNHUMAN_DEFAULT_URL),process.env.RUNHUMAN_DEFAULT_DURATION&&(o.defaultDuration=parseInt(process.env.RUNHUMAN_DEFAULT_DURATION,10)),process.env.RUNHUMAN_DEFAULT_SCREEN_SIZE&&(o.defaultScreenSize=process.env.RUNHUMAN_DEFAULT_SCREEN_SIZE),process.env.RUNHUMAN_OUTPUT_FORMAT&&qe(process.env.RUNHUMAN_OUTPUT_FORMAT)&&(o.outputFormat=process.env.RUNHUMAN_OUTPUT_FORMAT),process.env.RUNHUMAN_NO_COLOR==="1"&&(o.color=!1),o}async loadProjectConfig(){try{return(await Je(Ke).search(this.cwd))?.config||null}catch{return null}}loadGlobalConfig(){try{if(!R(M))return null;let o=L(M,"utf-8");return JSON.parse(o)}catch{return null}}async get(o){return(await this.loadConfig())[o]}async set(o,e,n=!1){n?await this.setGlobalConfig(o,e):await this.setProjectConfig(o,e)}async setGlobalConfig(o,e){R(x)||X(x,{recursive:!0});let n={};if(R(M)){let t=L(M,"utf-8");n=JSON.parse(t)}n[o]=e,D(M,JSON.stringify(n,null,2))}async setProjectConfig(o,e){let n=N(this.cwd,".runhumanrc"),t={};if(R(n)){let r=L(n,"utf-8");t=JSON.parse(r)}t[o]=e,D(n,JSON.stringify(t,null,2))}async saveProjectConfig(o){let e=N(this.cwd,".runhumanrc"),n={};if(R(e)){let r=L(e,"utf-8");n=JSON.parse(r)}let t={...n,...o};D(e,JSON.stringify(t,null,2))}async list(){let o=await this.loadConfig();return{global:this.globalConfig,project:this.projectConfig,env:this.envConfig,effective:o}}async reset(o){if((o==="global"||o==="all")&&R(M)&&D(M,"{}"),o==="project"||o==="all"){let e=N(this.cwd,".runhumanrc");R(e)&&D(e,"{}")}}saveCredentials(o){R(x)||X(x,{recursive:!0}),D($,JSON.stringify(o,null,2));try{process.platform!=="win32"&&$e($,384)}catch{}}loadCredentials(){try{if(!R($))return null;let o=L($,"utf-8");return JSON.parse(o)}catch{return null}}clearCredentials(){R($)&&D($,"{}")}saveUserInfo(o){let e=N(x,"user.json");R(x)||X(x,{recursive:!0}),D(e,JSON.stringify(o,null,2))}loadUserInfo(){try{let o=N(x,"user.json");if(!R(o))return null;let e=L(o,"utf-8");return JSON.parse(e)}catch{return null}}clearUserInfo(){let o=N(x,"user.json");R(o)&&D(o,"{}")}}});import v from"chalk";import He from"cli-table3";var l,C=q(()=>{"use strict";l=class{constructor(o={}){this.options=o}output(o){this.options.json?console.log(JSON.stringify(o,null,2)):o.success?o.data&&!this.options.quiet&&this.outputPretty(o.data):this.outputError(o.error?.message||"An error occurred")}outputPretty(o){typeof o=="string"?console.log(o):(Array.isArray(o),console.log(JSON.stringify(o,null,2)))}outputError(o,e){if(this.options.json){let n={success:!1,error:{message:o,details:e},timestamp:new Date().toISOString()};console.error(JSON.stringify(n,null,2))}else console.error(this.color("red",`
3
+ Error: ${o}
4
+ `)),e?.code==="INSUFFICIENT_TOKENS"&&e?.link?(console.error(this.color("yellow","Add funds to continue:")),console.error(this.color("cyan",e.link)),console.error("")):e&&console.error(this.color("gray",JSON.stringify(e,null,2)))}success(o){!this.options.json&&!this.options.quiet&&console.log(this.color("green",`
5
+ ${o}
6
+ `))}info(o){!this.options.json&&!this.options.quiet&&console.log(this.color("blue",o))}warn(o){!this.options.json&&!this.options.quiet&&console.warn(this.color("yellow",`Warning: ${o}`))}formatJobList(o){if(this.options.format==="compact")return o.map(n=>`${n.id} ${n.status} ${n.url}`).join(`
7
+ `);let e=new He({head:["Job ID","Status","URL","Created","Duration","Cost"].map(n=>this.color("cyan",n)),colWidths:[15,12,30,14,10,10]});return o.forEach(n=>{e.push([n.id,this.formatStatus(n.status),this.truncate(n.url,28),this.formatDate(n.createdAt),n.testDurationSeconds?this.formatDuration(n.testDurationSeconds):"-",n.costUsd?`$${n.costUsd.toFixed(3)}`:"-"])}),e.toString()}formatStatus(o){let e={pending:"yellow",waiting:"blue",working:"blue",creating_issues:"cyan",completed:"green",incomplete:"yellow",abandoned:"red",rejected:"gray",error:"red",failed:"red"},n=o.replace("_"," ");return this.color(e[o]||"white",n)}formatDuration(o){if(o<60)return`${o}s`;let e=Math.floor(o/60),n=o%60;if(e<60)return`${e}m ${n}s`;let t=Math.floor(e/60),r=e%60;return`${t}h ${r}m ${n}s`}formatDate(o){let e=new Date(o),t=new Date().getTime()-e.getTime(),r=Math.floor(t/1e3),c=Math.floor(r/60),i=Math.floor(c/60),a=Math.floor(i/24);return r<60?`${r}s ago`:c<60?`${c}m ago`:i<24?`${i}h ago`:a<30?`${a}d ago`:e.toLocaleDateString()}truncate(o,e){return o.length<=e?o:o.substring(0,e-3)+"..."}color(o,e){if(this.options.color===!1)return e;let t={red:v.red,green:v.green,blue:v.blue,yellow:v.yellow,cyan:v.cyan,gray:v.gray,white:v.white}[o];return t?t(e):e}static result(o){return{success:!0,data:o,timestamp:new Date().toISOString()}}static error(o,e,n){return{success:!1,error:{message:o,code:e,details:n},timestamp:new Date().toISOString()}}}});var oe={};Me(oe,{waitCommand:()=>ee,waitForJob:()=>re});import{Command as Ge}from"commander";import _e from"ora";async function re(s,o,e,n=600){let t=Date.now(),r=n*1e3,c=1e4,i=null;for(e.options.json||(i=_e("\u23F3 Waiting for job completion...").start());;){let a=Date.now()-t;if(a>=r)throw i&&i.fail("Timeout waiting for job completion"),new W(`Job did not complete within ${n} seconds`);let u=await o.getJob(s);if(i){let p=e.formatDuration(Math.floor(a/1e3));i.text=`\u23F3 Waiting for job completion... (${p} elapsed, status: ${u.status})`}if(u.status==="completed"){if(i&&i.succeed("\u2705 Test Completed!"),!e.options.json){if(console.log(`
8
+ Duration: `+(u.testDurationSeconds?e.formatDuration(u.testDurationSeconds):"N/A")),console.log(" Cost: $"+(u.costUsd||0).toFixed(3)),u.testerName&&console.log(" Tester: "+u.testerName),u.result){console.log(`
9
+ \u{1F4CB} Results Summary:`);for(let[p,f]of Object.entries(u.result))console.log(` ${p}: ${f}`)}console.log(`
10
10
  \u{1F4BE} Full results:`),console.log(` runhuman results ${s}
11
- `)}return}if(p.status==="error"||p.status==="incomplete"||p.status==="abandoned")throw i&&i.fail(`Test ${p.status}`),new Error(`Job ${p.status}: ${p.testerResponse||"No details available"}`);if(p.status==="rejected")throw i&&i.fail("Test was rejected"),new Error("Job was rejected");await new Promise(f=>setTimeout(f,u))}}function oe(){let s=new no("wait");return s.description("Wait for a job to complete and display results").argument("<jobId>","Job ID to wait for").option("--timeout <seconds>","Max wait time (default: 600)",parseInt).option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color}),u=new g(o);if(await De(t,u,r,e.timeout||600),e.json){let i=await u.getJob(t),l=c.result(i);r.output(l)}}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s}var Z=b(()=>{"use strict";U();k();S();I()});import{Command as Zo}from"commander";U();k();S();I();import{Command as so}from"commander";import io from"ora";function $e(){let s=new so("create");return s.description("Create a new QA test job").argument("[url]","URL to test").option("-d, --description <text>","Test instructions for the human tester").option("-t, --template <name>","Use a template as base configuration").option("--duration <minutes>","Target duration (1-60 minutes)",parseInt).option("--screen-size <preset>","Screen size: desktop|laptop|tablet|mobile").option("--schema <file>","Path to JSON schema file for structured output").option("--schema-inline <json>","Inline JSON schema").option("--metadata <json>","Metadata for tracking (JSON string)").option("--github-repo <owner/repo>","GitHub repo for context").option("--create-issues","Auto-create GitHub issues from findings").option("--sync","Wait for result before exiting (synchronous mode)").option("--wait <seconds>","Max wait time in sync mode (default: 300)",parseInt).option("--project <id>","Project ID (or use default from config)").option("--api-key <key>","API key (or use from config/env)").option("--json","Output as JSON (for scripting)").option("--quiet","Minimal output (only job ID)").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,project:e.project}),r=new c({json:e.json,quiet:e.quiet,color:o.color}),u=new g(o);if(!t&&!e.template&&!o.defaultUrl)throw new D("URL is required (provide as argument, via --template, or set defaultUrl in config)");if(!e.description&&!e.template)throw new D("Description is required (use -d flag or --template)");if(!o.project)throw new D(`Project ID is required. Set a default project with:
11
+ `)}return}if(u.status==="error"||u.status==="incomplete"||u.status==="abandoned")throw i&&i.fail(`Test ${u.status}`),new Error(`Job ${u.status}: ${u.testerResponse||"No details available"}`);if(u.status==="rejected")throw i&&i.fail("Test was rejected"),new Error("Job was rejected");await new Promise(p=>setTimeout(p,c))}}function ee(){let s=new Ge("wait");return s.description("Wait for a job to complete and display results").argument("<jobId>","Job ID to wait for").option("--timeout <seconds>","Max wait time (default: 600)",parseInt).option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),c=new d(t);if(await re(o,c,r,e.timeout||600),e.json){let i=await c.getJob(o),a=l.result(i);r.output(a)}}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s}var V=q(()=>{"use strict";k();U();C();b()});import{Command as Jo}from"commander";k();U();C();b();import{Command as We}from"commander";import Be from"ora";function se(){let s=new We("create");return s.description("Create a new QA test job").argument("[url]","URL to test").option("-d, --description <text>","Test instructions for the human tester").option("-t, --template <name>","Use a template as base configuration").option("--duration <minutes>","Target duration (1-60 minutes)",parseInt).option("--screen-size <preset>","Screen size: desktop|laptop|tablet|mobile").option("--schema <file>","Path to JSON schema file for structured output").option("--schema-inline <json>","Inline JSON schema").option("--metadata <json>","Metadata for tracking (JSON string)").option("--github-repo <owner/repo>","GitHub repo for context").option("--create-issues","Auto-create GitHub issues from findings").option("--sync","Wait for result before exiting (synchronous mode)").option("--wait <seconds>","Max wait time in sync mode (default: 300)",parseInt).option("--project <id>","Project ID (or use default from config)").option("--api-key <key>","API key (or use from config/env)").option("--json","Output as JSON (for scripting)").option("--quiet","Minimal output (only job ID)").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,project:e.project}),r=new l({json:e.json,quiet:e.quiet,color:t.color}),c=new d(t);if(!o&&!e.template&&!t.defaultUrl)throw new E("URL is required (provide as argument, via --template, or set defaultUrl in config)");if(!e.description&&!e.template)throw new E("Description is required (use -d flag or --template)");if(!t.project)throw new E(`Project ID is required. Set a default project with:
12
12
  runhuman projects switch <project-id>
13
- Or use the --project flag`);let i={projectId:o.project,url:t||o.defaultUrl||"",description:e.description||"",duration:e.duration||o.defaultDuration,screenSize:e.screenSize||o.defaultScreenSize};if(e.schema){let h=await(await import("fs/promises")).readFile(e.schema,"utf-8");i.schema=JSON.parse(h)}else e.schemaInline&&(i.schema=JSON.parse(e.schemaInline));e.metadata&&(i.metadata=JSON.parse(e.metadata)),e.githubRepo&&(i.githubRepo=e.githubRepo),e.createIssues&&(i.createIssues=!0),e.template&&(i.templateId=e.template);let l=e.json?null:io("Creating QA test job...").start(),p=await u.createJob(i);if(l&&l.succeed("Job created successfully!"),e.json){let f=c.result({jobId:p.jobId,status:p.status,message:p.message,dashboardUrl:`${o.apiUrl}/dashboard/jobs/${p.jobId}`,estimatedCompletionTime:p.estimatedCompletionTime});r.output(f)}else e.quiet?console.log(p.jobId):(console.log(`
14
- `+"=".repeat(60)),console.log(" Job ID: "+p.jobId),console.log(" Status: "+p.status),console.log(` Dashboard: ${o.apiUrl}/dashboard/jobs/${p.jobId}`),console.log("=".repeat(60)+`
15
- `),console.log("\u{1F4A1} Track progress:"),console.log(` runhuman status ${p.jobId}`),console.log(` runhuman wait ${p.jobId}
16
- `));if(e.sync){let{waitForJob:f}=await Promise.resolve().then(()=>(Z(),ne));await f(p.jobId,u,r,e.wait||300)}}catch(n){let o=m(n);new c({json:e.json,quiet:!1}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s}U();k();S();I();import{Command as ao}from"commander";function Me(){let s=new ao("status");return s.description("Check the current status of a job").argument("<jobId>","Job ID to check").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color}),i=await new g(o).getJob(t);if(e.json){let l=c.result(i);r.output(l)}else console.log(`
17
- \u{1F4CA} Job Status: `+t+`
13
+ Or use the --project flag`);let i={projectId:t.project,url:o||t.defaultUrl||"",description:e.description||"",duration:e.duration||t.defaultDuration,screenSize:e.screenSize||t.defaultScreenSize};if(e.schema){let f=await(await import("fs/promises")).readFile(e.schema,"utf-8");i.schema=JSON.parse(f)}else e.schemaInline&&(i.schema=JSON.parse(e.schemaInline));e.metadata&&(i.metadata=JSON.parse(e.metadata)),e.githubRepo&&(i.githubRepo=e.githubRepo),e.createIssues&&(i.createIssues=!0),e.template&&(i.templateId=e.template);let a=e.json?null:Be("Creating QA test job...").start(),u=await c.createJob(i);if(a&&a.succeed("Job created successfully!"),e.json){let p=l.result({jobId:u.jobId,status:u.status,message:u.message,dashboardUrl:`${t.apiUrl}/dashboard/jobs/${u.jobId}`,estimatedCompletionTime:u.estimatedCompletionTime});r.output(p)}else e.quiet?console.log(u.jobId):(console.log(`
14
+ `+"=".repeat(60)),console.log(" Job ID: "+u.jobId),console.log(" Status: "+u.status),console.log(` Dashboard: ${t.apiUrl}/dashboard/jobs/${u.jobId}`),console.log("=".repeat(60)+`
15
+ `),console.log("\u{1F4A1} Track progress:"),console.log(` runhuman status ${u.jobId}`),console.log(` runhuman wait ${u.jobId}
16
+ `));if(e.sync){let{waitForJob:p}=await Promise.resolve().then(()=>(V(),oe));await p(u.jobId,c,r,e.wait||300)}}catch(n){let t=m(n);new l({json:e.json,quiet:!1}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s}k();U();C();b();import{Command as Ve}from"commander";function ie(){let s=new Ve("status");return s.description("Check the current status of a job").argument("<jobId>","Job ID to check").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),i=await new d(t).getJob(o);if(e.json){let a=l.result(i);r.output(a)}else console.log(`
17
+ \u{1F4CA} Job Status: `+o+`
18
18
  `),console.log(" Status: "+r.formatStatus(i.status)),i.testerName&&console.log(" Tester: "+i.testerName),console.log(" URL: "+i.url),console.log(" Description: "+i.description),console.log(" Created: "+i.createdAt),i.completedAt&&console.log(" Completed: "+i.completedAt),i.testDurationSeconds&&console.log(" Duration: "+r.formatDuration(i.testDurationSeconds)),i.costUsd&&console.log(" Cost: $"+i.costUsd.toFixed(3)),console.log(`
19
- Dashboard: ${o.apiUrl}/dashboard/jobs/${t}
20
- `),i.status==="pending"||i.status==="waiting"||i.status==="working"?(console.log("\u{1F4A1} Wait for completion:"),console.log(` runhuman wait ${t}
21
- `)):i.status==="completed"&&(console.log("\u{1F4A1} View results:"),console.log(` runhuman results ${t}
22
- `))}catch(n){let o=new c({json:e.json}),r=m(n);o.outputError(r.message,r.details),process.exit(r.exitCode)}}),s}Z();U();k();S();I();import{Command as co}from"commander";function Le(){let s=new co("results");return s.description("Display detailed results for a completed job").argument("<jobId>","Job ID to show results for").option("--json","Output as JSON").option("--schema-only","Show only extracted schema data").option("--raw","Show raw tester response (no processing)").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color}),i=await new g(o).getJob(t);if(e.json){let l=c.result(i);r.output(l)}else{if(console.log(`
23
- \u{1F4CA} Test Results: `+t+`
19
+ Dashboard: ${t.apiUrl}/dashboard/jobs/${o}
20
+ `),i.status==="pending"||i.status==="waiting"||i.status==="working"?(console.log("\u{1F4A1} Wait for completion:"),console.log(` runhuman wait ${o}
21
+ `)):i.status==="completed"&&(console.log("\u{1F4A1} View results:"),console.log(` runhuman results ${o}
22
+ `))}catch(n){let t=new l({json:e.json}),r=m(n);t.outputError(r.message,r.details),process.exit(r.exitCode)}}),s}V();k();U();C();b();import{Command as Qe}from"commander";function ae(){let s=new Qe("results");return s.description("Display detailed results for a completed job").argument("<jobId>","Job ID to show results for").option("--json","Output as JSON").option("--schema-only","Show only extracted schema data").option("--raw","Show raw tester response (no processing)").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),i=await new d(t).getJob(o);if(e.json){let a=l.result(i);r.output(a)}else{if(console.log(`
23
+ \u{1F4CA} Test Results: `+o+`
24
24
  `),console.log("=".repeat(80)),console.log("Job Information"),console.log("=".repeat(80)+`
25
25
  `),console.log(" Job ID: "+i.id),console.log(" Status: "+i.status),console.log(" URL: "+i.url),console.log(" Description: "+i.description),console.log(" Started: "+i.createdAt),i.completedAt&&console.log(" Completed: "+i.completedAt),i.testDurationSeconds&&console.log(" Duration: "+r.formatDuration(i.testDurationSeconds)),i.costUsd&&console.log(" Cost: $"+i.costUsd.toFixed(3)),i.testerName&&console.log(" Tester: "+i.testerName),i.result&&Object.keys(i.result).length>0&&!e.raw){console.log(`
26
26
  `+"=".repeat(80)),console.log("Structured Results (Extracted)"),console.log("=".repeat(80)+`
27
- `);for(let[l,p]of Object.entries(i.result)){let f=typeof p=="object"?JSON.stringify(p,null,2):String(p);console.log(` ${l}:`.padEnd(30)+f)}}i.testerResponse&&!e.schemaOnly&&(console.log(`
27
+ `);for(let[a,u]of Object.entries(i.result)){let p=typeof u=="object"?JSON.stringify(u,null,2):String(u);console.log(` ${a}:`.padEnd(30)+p)}}i.testerResponse&&!e.schemaOnly&&(console.log(`
28
28
  `+"=".repeat(80)),console.log("Tester Feedback"),console.log("=".repeat(80)+`
29
29
  `),console.log(" "+i.testerResponse.split(`
30
30
  `).join(`
31
31
  `))),console.log(`
32
32
  `+"=".repeat(80)+`
33
- `)}}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s}U();k();S();I();import{Command as lo}from"commander";function Je(){let s=new lo("list");return s.description("List all jobs with optional filtering").argument("[filter]","Status filter: all|pending|claimed|in_progress|completed|failed|timeout").option("--project <id>","Filter by project").option("--limit <number>","Number of results (default: 20)",parseInt).option("--offset <number>","Pagination offset (default: 0)",parseInt).option("--json","Output as JSON").option("--format <type>","Output format: table|json|compact").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,format:e.format,color:o.color}),u=new g(o),i={limit:e.limit||20,offset:e.offset||0};t&&t!=="all"&&(i.status=t),e.project&&(i.projectId=e.project);let{jobs:l,total:p}=await u.listJobs(i);if(e.json){let f=c.result({jobs:l,total:p});r.output(f)}else console.log(`
34
- \u{1F4CB} Recent Jobs (${l.length} of ${p})
35
- `),console.log(r.formatJobList(l)),console.log(`
33
+ `)}}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s}k();U();C();b();import{Command as Ye}from"commander";function le(){let s=new Ye("list");return s.description("List all jobs with optional filtering").argument("[filter]","Status filter: all|pending|claimed|in_progress|completed|failed|timeout").option("--project <id>","Filter by project").option("--limit <number>","Number of results (default: 20)",parseInt).option("--offset <number>","Pagination offset (default: 0)",parseInt).option("--json","Output as JSON").option("--format <type>","Output format: table|json|compact").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,format:e.format,color:t.color}),c=new d(t),i=e.project||t.project;if(!i)throw new Error("Project ID required (use --project or set default project)");let a={limit:e.limit||20,offset:e.offset||0,projectId:i};o&&o!=="all"&&(a.status=o);let{items:u,pagination:p}=await c.listJobs(a);if(e.json){let f=l.result({jobs:u,pagination:p});r.output(f)}else{if(p.total===0){console.log(`
34
+ \u{1F4CB} Recent Jobs (0)
35
+ `),console.log(`No jobs found.
36
+ `),console.log(`\u{1F4A1} Create a job: runhuman create <url> -d "Test description"
37
+ `);return}console.log(`
38
+ \u{1F4CB} Recent Jobs (${u.length} of ${p.total})
39
+ `),console.log(r.formatJobList(u)),console.log(`
36
40
  \u{1F4A1} View details: runhuman status <jobId>`),console.log(` View results: runhuman results <jobId>
37
- `)}catch(n){let o=new c({json:e.json}),r=m(n);o.outputError(r.message,r.details),process.exit(r.exitCode)}}),s}U();k();S();I();import{Command as uo}from"commander";function _e(){let s=new uo("delete");return s.description("Delete a job permanently").argument("<jobId>","Job ID to delete").option("--confirm","Skip confirmation prompt").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color});if(!e.json&&!e.confirm)throw console.log(`
38
- \u26A0\uFE0F Warning: This will permanently delete job ${t}`),console.log(` This action cannot be undone.
39
- `),new Error("Please use --confirm flag to delete the job");if(await new g(o).deleteJob(t),e.json){let i=c.result({success:!0,message:"Job deleted successfully",jobId:t});r.output(i)}else console.log(`
41
+ `)}}catch(n){let t=new l({json:e.json}),r=m(n);t.outputError(r.message,r.details),process.exit(r.exitCode)}}),s}k();U();C();b();import{Command as Ze}from"commander";function ce(){let s=new Ze("delete");return s.description("Delete a job permanently").argument("<jobId>","Job ID to delete").option("--confirm","Skip confirmation prompt").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color});if(!e.json&&!e.confirm)throw console.log(`
42
+ \u26A0\uFE0F Warning: This will permanently delete job ${o}`),console.log(` This action cannot be undone.
43
+ `),new Error("Please use --confirm flag to delete the job");if(await new d(t).deleteJob(o),e.json){let i=l.result({success:!0,message:"Job deleted successfully",jobId:o});r.output(i)}else console.log(`
40
44
  \u2705 Job deleted successfully
41
- `),console.log(` Job ID: ${t}`),console.log(` Status: Permanently deleted
42
- `)}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s}U();k();S();I();import{Command as po}from"commander";import mo from"chokidar";import{existsSync as Q,readFileSync as re,writeFileSync as go,unlinkSync as fo}from"fs";import{join as Fe}from"path";import{homedir as ze}from"os";var $=Fe(ze(),".config","runhuman","watch.pid");function Ke(){let s=new po("watch");return s.description("Watch files and auto-create QA test jobs on changes").argument("[patterns...]",'File patterns to watch (e.g., "src/**/*.tsx")').option("--url <url>","URL to test (required)").option("--template <name>","Template to use for tests").option("--description <text>","Test description").option("--debounce <ms>","Debounce delay in milliseconds",parseInt).option("--ignore <patterns...>","Patterns to ignore").option("--stop","Stop existing watch process").option("--status","Check if watch is running").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{if(e.stop){await bo();return}if(e.status){yo();return}Ge()&&(console.log(`
45
+ `),console.log(` Job ID: ${o}`),console.log(` Status: Permanently deleted
46
+ `)}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s}k();U();C();b();import{Command as Xe}from"commander";import eo from"chokidar";import{existsSync as Q,readFileSync as te,writeFileSync as oo,unlinkSync as to}from"fs";import{join as ue}from"path";import{homedir as pe}from"os";var T=ue(pe(),".config","runhuman","watch.pid");function me(){let s=new Xe("watch");return s.description("Watch files and auto-create QA test jobs on changes").argument("[patterns...]",'File patterns to watch (e.g., "src/**/*.tsx")').option("--url <url>","URL to test (required)").option("--template <name>","Template to use for tests").option("--description <text>","Test description").option("--debounce <ms>","Debounce delay in milliseconds",parseInt).option("--ignore <patterns...>","Patterns to ignore").option("--stop","Stop existing watch process").option("--status","Check if watch is running").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{if(e.stop){await ro();return}if(e.status){so();return}ge()&&(console.log(`
43
47
  \u26A0\uFE0F Watch mode is already running`),console.log(` Use --stop to stop it first
44
- `),process.exit(1));let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=o.watch||{},u=t.length>0?t:r.patterns||["src/**/*"],i=e.ignore||r.ignore||["**/node_modules/**","**/dist/**","**/build/**","**/.git/**"],l=e.debounce||r.debounce||2e3,p=e.url||r.url||o.defaultUrl,f=e.description||r.description||"Auto-test from watch mode",h=e.template||r.template;p||(console.log(`
48
+ `),process.exit(1));let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=t.watch||{},c=o.length>0?o:r.patterns||["src/**/*"],i=e.ignore||r.ignore||["**/node_modules/**","**/dist/**","**/build/**","**/.git/**"],a=e.debounce||r.debounce||2e3,u=e.url||r.url||t.defaultUrl,p=e.description||r.description||"Auto-test from watch mode",f=e.template||r.template;u||(console.log(`
45
49
  \u274C Error: URL required
46
50
  `),console.log(`Provide via --url flag or set in .runhumanrc:
47
51
  `),console.log(' runhuman watch --url "https://myapp.com"'),console.log(` OR add to .runhumanrc: { "watch": { "url": "..." } }
48
- `),process.exit(1)),o.project||(console.log(`
52
+ `),process.exit(1)),t.project||(console.log(`
49
53
  \u274C Error: No project selected
50
54
  `),console.log(`Run: runhuman projects switch <project-id>
51
- `),process.exit(1));let y=new c({color:o.color});console.log(`
55
+ `),process.exit(1));let h=new l({color:t.color});console.log(`
52
56
  \u{1F440} Starting watch mode
53
- `),console.log(` Watching: ${u.join(", ")}`),console.log(` Ignoring: ${i.join(", ")}`),console.log(` Debounce: ${l}ms`),console.log(` URL: ${p}`),h&&console.log(` Template: ${h}`),console.log(`
57
+ `),console.log(` Watching: ${c.join(", ")}`),console.log(` Ignoring: ${i.join(", ")}`),console.log(` Debounce: ${a}ms`),console.log(` URL: ${u}`),f&&console.log(` Template: ${f}`),console.log(`
54
58
  Press Ctrl+C to stop
55
- `),ho(process.pid);let w=new g(o),C=null,v=new Set,T=mo.watch(u,{ignored:i,persistent:!0,ignoreInitial:!0});T.on("change",_=>{v.add(_),console.log(` \u{1F4DD} Changed: ${_}`),C&&clearTimeout(C),C=setTimeout(async()=>{let se=Array.from(v);v.clear(),console.log(`
56
- \u{1F680} Creating test job for ${se.length} file(s)...
57
- `);try{let Y={projectId:o.project,url:p,description:`${f}
59
+ `),no(process.pid);let y=new d(t),j=null,A=new Set,S=eo.watch(c,{ignored:i,persistent:!0,ignoreInitial:!0});S.on("change",J=>{A.add(J),console.log(` \u{1F4DD} Changed: ${J}`),j&&clearTimeout(j),j=setTimeout(async()=>{let ne=Array.from(A);A.clear(),console.log(`
60
+ \u{1F680} Creating test job for ${ne.length} file(s)...
61
+ `);try{let Z={projectId:t.project,url:u,description:`${p}
58
62
 
59
63
  Changed files:
60
- ${se.map(ct=>`- ${ct}`).join(`
61
- `)}`,templateId:h},G=await w.createJob(Y);console.log(` \u2705 Job created: ${G.jobId}`),console.log(` \u{1F4CA} Dashboard: https://runhuman.com/dashboard/jobs/${G.jobId}
64
+ ${ne.map(Ne=>`- ${Ne}`).join(`
65
+ `)}`,templateId:f},F=await y.createJob(Z);console.log(` \u2705 Job created: ${F.jobId}`),console.log(` \u{1F4CA} Dashboard: https://runhuman.com/dashboard/jobs/${F.jobId}
62
66
  `),console.log(` Watching for more changes...
63
- `)}catch(Y){let G=m(Y);y.outputError(G.message,G.details),console.log(`
67
+ `)}catch(Z){let F=m(Z);h.outputError(F.message,F.details),console.log(`
64
68
  Watching for more changes...
65
- `)}},l)}),T.on("error",_=>{console.error(`
66
- \u274C Watch error: ${_ instanceof Error?_.message:String(_)}
69
+ `)}},a)}),S.on("error",J=>{console.error(`
70
+ \u274C Watch error: ${J instanceof Error?J.message:String(J)}
67
71
  `)});let K=()=>{console.log(`
68
72
 
69
73
  \u{1F44B} Stopping watch mode...
70
- `),T.close(),H(),process.exit(0)};process.on("SIGINT",K),process.on("SIGTERM",K)}catch(n){let o=m(n);new c({}).outputError(o.message,o.details),H(),process.exit(o.exitCode)}}),s}function ho(s){let t=Fe(ze(),".config","runhuman");Q(t)||ut("fs").mkdirSync(t,{recursive:!0}),go($,s.toString())}function H(){Q($)&&fo($)}function Ge(){if(!Q($))return!1;try{let s=parseInt(re($,"utf-8"));return process.kill(s,0),!0}catch{return H(),!1}}async function bo(){if(!Q($)){console.log(`
74
+ `),S.close(),H(),process.exit(0)};process.on("SIGINT",K),process.on("SIGTERM",K)}catch(n){let t=m(n);new l({}).outputError(t.message,t.details),H(),process.exit(t.exitCode)}}),s}function no(s){let o=ue(pe(),".config","runhuman");Q(o)||Te("fs").mkdirSync(o,{recursive:!0}),oo(T,s.toString())}function H(){Q(T)&&to(T)}function ge(){if(!Q(T))return!1;try{let s=parseInt(te(T,"utf-8"));return process.kill(s,0),!0}catch{return H(),!1}}async function ro(){if(!Q(T)){console.log(`
71
75
  \u2139\uFE0F Watch mode is not running
72
- `);return}try{let s=parseInt(re($,"utf-8"));process.kill(s,"SIGTERM"),H(),console.log(`
76
+ `);return}try{let s=parseInt(te(T,"utf-8"));process.kill(s,"SIGTERM"),H(),console.log(`
73
77
  \u2705 Watch mode stopped
74
78
  `)}catch{H(),console.log(`
75
79
  \u2139\uFE0F Watch process not found (already stopped)
76
- `)}}function yo(){if(Ge()){let s=parseInt(re($,"utf-8"));console.log(`
80
+ `)}}function so(){if(ge()){let s=parseInt(te(T,"utf-8"));console.log(`
77
81
  \u2705 Watch mode is running`),console.log(` \u{1F4DD} PID: ${s}`),console.log(`
78
82
  Stop with: runhuman watch --stop
79
83
  `)}else console.log(`
80
84
  \u2139\uFE0F Watch mode is not running
81
- `)}U();k();I();S();import{Command as So}from"commander";import jo from"http";import{URL as wo}from"url";import Co from"open";async function X(s){let{apiUrl:t,autoOpenBrowser:e=!0}=s;return new Promise((n,o)=>{let r=jo.createServer((i,l)=>{if(!i.url){l.writeHead(400),l.end("Bad request");return}let p=new wo(i.url,"http://localhost");if(p.pathname==="/callback"){let f=p.searchParams.get("token"),h=p.searchParams.get("email"),y=p.searchParams.get("projectId"),w=p.searchParams.get("error");if(w){l.writeHead(200,{"Content-Type":"text/html"}),l.end(He(w)),r.close(),o(new Error(w));return}if(f&&h&&y){l.writeHead(200,{"Content-Type":"text/html"}),l.end(Io(h)),r.close(),n({token:f,email:h,projectId:y});return}l.writeHead(400,{"Content-Type":"text/html"}),l.end(He("Missing token, email, or projectId in callback")),r.close(),o(new Error("Missing token, email, or projectId in callback"));return}l.writeHead(404),l.end("Not found")});r.listen(0,"127.0.0.1",async()=>{let i=r.address();if(!i||typeof i=="string"){o(new Error("Failed to start local server"));return}let p=`http://127.0.0.1:${i.port}/callback`,f=`${t}/cli/auth?callback=${encodeURIComponent(p)}`;if(console.log(""),e){console.log("Opening browser for authentication...");try{await Co(f)}catch{console.log("Could not open browser automatically."),console.log(`
82
- Please visit: ${f}`)}}else console.log(`Please visit: ${f}`);console.log(""),console.log("Waiting for authentication...")});let u=setTimeout(()=>{r.close(),o(new Error("Authentication timed out. Please try again."))},60*1e3);r.on("close",()=>clearTimeout(u)),r.on("error",i=>{clearTimeout(u),o(new Error(`Failed to start local server: ${i.message}`))})})}function Io(s){return`<!DOCTYPE html>
85
+ `)}k();U();b();C();import{Command as uo}from"commander";import io from"http";import{URL as ao}from"url";import lo from"open";async function Y(s){let{apiUrl:o,autoOpenBrowser:e=!0}=s;return new Promise((n,t)=>{let r=io.createServer((i,a)=>{if(!i.url){a.writeHead(400),a.end("Bad request");return}let u=new ao(i.url,"http://localhost");if(u.pathname==="/callback"){let p=u.searchParams.get("token"),f=u.searchParams.get("email"),h=u.searchParams.get("projectId")||"",y=u.searchParams.get("error");if(y){a.writeHead(200,{"Content-Type":"text/html"}),a.end(de(y)),r.close(),t(new Error(y));return}if(p&&f){a.writeHead(200,{"Content-Type":"text/html"}),a.end(co(f)),r.close(),n({token:p,email:f,projectId:h});return}a.writeHead(400,{"Content-Type":"text/html"}),a.end(de("Missing token or email in callback")),r.close(),t(new Error("Missing token or email in callback"));return}a.writeHead(404),a.end("Not found")});r.listen(0,"127.0.0.1",async()=>{let i=r.address();if(!i||typeof i=="string"){t(new Error("Failed to start local server"));return}let u=`http://127.0.0.1:${i.port}/callback`,p=`${o}/cli/auth?callback=${encodeURIComponent(u)}`;if(console.log(""),e){console.log("Opening browser for authentication...");try{await lo(p)}catch{console.log("Could not open browser automatically."),console.log(`
86
+ Please visit: ${p}`)}}else console.log(`Please visit: ${p}`);console.log(""),console.log("Waiting for authentication...")});let c=setTimeout(()=>{r.close(),t(new Error("Authentication timed out. Please try again."))},60*1e3);r.on("close",()=>clearTimeout(c)),r.on("error",i=>{clearTimeout(c),t(new Error(`Failed to start local server: ${i.message}`))})})}function co(s){return`<!DOCTYPE html>
83
87
  <html>
84
88
  <head>
85
89
  <title>Runhuman CLI - Authenticated</title>
@@ -121,7 +125,7 @@ Please visit: ${f}`)}}else console.log(`Please visit: ${f}`);console.log(""),con
121
125
  <p class="close-note">You can close this tab and return to the terminal.</p>
122
126
  </div>
123
127
  </body>
124
- </html>`}function He(s){return`<!DOCTYPE html>
128
+ </html>`}function de(s){return`<!DOCTYPE html>
125
129
  <html>
126
130
  <head>
127
131
  <title>Runhuman CLI - Authentication Failed</title>
@@ -164,96 +168,114 @@ Please visit: ${f}`)}}else console.log(`Please visit: ${f}`);console.log(""),con
164
168
  <div class="error">${s}</div>
165
169
  </div>
166
170
  </body>
167
- </html>`}function We(){let s=new So("login");return s.description("Authenticate with Runhuman").option("--token <token>","Login with API key (skip browser)").option("--no-browser","Print auth URL instead of opening browser").option("--json","Output as JSON").action(async t=>{try{let e=new d,n=await e.loadConfig(),o=new c({json:t.json,color:n.color});if(t.token)await Ao(e,o,t.token,t.json);else{let r=t.browser!==!1&&n.autoOpenBrowser!==!1;await ko(e,o,n.apiUrl,r,t.json)}}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}async function Ao(s,t,e,n){s.saveCredentials({accessToken:e});let o=await s.loadConfig({apiKey:e}),u=await new g(o).getCurrentUser();s.saveUserInfo(u),n?t.output(c.result({success:!0,user:u})):(t.success("Successfully logged in!"),console.log(` User: ${u.email}`),console.log(` Account ID: ${u.accountId}
168
- `))}async function ko(s,t,e,n,o){o||console.log("Logging in to Runhuman...");let r=await X({apiUrl:e||"https://runhuman.com",autoOpenBrowser:n});s.saveCredentials({accessToken:r.token}),await s.set("project",r.projectId,!0);let u=await s.loadConfig({apiKey:r.token}),l=await new g(u).getCurrentUser();s.saveUserInfo(l),o?t.output(c.result({success:!0,user:l,projectId:r.projectId})):(console.log(""),t.success("Successfully logged in!"),console.log(` User: ${l.email}`),console.log(` Account ID: ${l.accountId}`),console.log(` Default Project: ${r.projectId}
169
- `)),process.exit(0)}k();I();S();import{Command as xo}from"commander";function qe(){let s=new xo("logout");return s.description("Log out and clear stored credentials").option("--force","Skip confirmation prompt").option("--json","Output as JSON").action(async t=>{try{let e=new d,n=new c({json:t.json});if(e.clearCredentials(),e.clearUserInfo(),t.json){let o=c.result({success:!0,message:"Logged out successfully"});n.output(o)}else n.success("Logged out successfully!"),console.log(`Credentials have been cleared.
170
- `)}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}U();k();I();S();import{Command as Uo}from"commander";function Ve(){let s=new Uo("whoami");return s.description("Display current user information").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async t=>{try{let e=new d,n=await e.loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=new c({json:t.json}),r=new g(n),u=await r.getCurrentUser(),i;try{i=(await r.getTokenBalance()).balance}catch{i=null}let l=e.loadUserInfo();if(t.json){let p=c.result({user:u,balance:i});o.output(p)}else console.log(`
171
+ </html>`}function fe(){let s=new uo("login");return s.description("Authenticate with Runhuman").option("--token <token>","Login with API key (skip browser)").option("--no-browser","Print auth URL instead of opening browser").option("--json","Output as JSON").action(async o=>{try{let e=new g,n=await e.loadConfig(),t=new l({json:o.json,color:n.color});if(o.token)await po(e,t,o.token,o.json);else{let r=o.browser!==!1&&n.autoOpenBrowser!==!1;await mo(e,t,n.apiUrl,r,o.json)}}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}async function po(s,o,e,n){s.saveCredentials({accessToken:e});let t=await s.loadConfig({apiKey:e}),c=await new d(t).getCurrentUser();s.saveUserInfo(c),n?o.output(l.result({success:!0,user:c})):(o.success("Successfully logged in!"),console.log(` User: ${c.email}`),console.log(` Account ID: ${c.accountId}
172
+ `))}async function mo(s,o,e,n,t){t||console.log("Logging in to Runhuman...");let r=await Y({apiUrl:e||"https://runhuman.com",autoOpenBrowser:n});s.saveCredentials({accessToken:r.token}),await s.set("project",r.projectId,!0);let c=await s.loadConfig({apiKey:r.token}),a=await new d(c).getCurrentUser();s.saveUserInfo(a),t?o.output(l.result({success:!0,user:a,projectId:r.projectId})):(console.log(""),o.success("Successfully logged in!"),console.log(` User: ${a.email}`),console.log(` Account ID: ${a.accountId}`),console.log(` Default Project: ${r.projectId}
173
+ `)),process.exit(0)}U();b();C();import{Command as go}from"commander";function he(){let s=new go("logout");return s.description("Log out and clear stored credentials").option("--force","Skip confirmation prompt").option("--json","Output as JSON").action(async o=>{try{let e=new g,n=new l({json:o.json});if(e.clearCredentials(),e.clearUserInfo(),o.json){let t=l.result({success:!0,message:"Logged out successfully"});n.output(t)}else n.success("Logged out successfully!"),console.log(`Credentials have been cleared.
174
+ `)}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}k();U();b();C();import{Command as fo}from"commander";function we(){let s=new fo("whoami");return s.description("Display current user information").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async o=>{try{let n=await new g().loadConfig({apiKey:o.apiKey,apiUrl:o.apiUrl}),t=new l({json:o.json}),c=await new d(n).getCurrentUser();if(o.json){let i=l.result({user:c});t.output(i)}else console.log(`
171
175
  \u{1F464} Current User
172
- `),console.log(" Email: "+u.email),console.log(" Account ID: "+u.accountId),i!==null&&console.log(" Token Balance: $"+i.toFixed(2)),n.project&&console.log(`
173
- Default Project: `+n.project),console.log()}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}U();k();I();S();import{Command as Po}from"commander";function Be(){let s=new Po("tokens");return s.description("View token balance and usage"),s.command("balance").description("Check account balance").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async t=>{try{let n=await new d().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=new c({json:t.json,color:n.color}),r=new g(n),{balance:u}=await r.getTokenBalance();if(t.json){let i=c.result({balance:u});o.output(i)}else{let i=(u/100).toFixed(2),l=Math.floor(u/100);console.log(`
174
- \u{1F4B0} Account Balance
175
- `),console.log(` Current Balance: $${i}`),console.log(` Estimated Tests: ~${l} tests at $1.00 per test
176
- `),console.log(`\u{1F4A1} Monitor your Usage: https://runhuman.com/dashboard/usage
177
- `)}}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("history").description("View token usage history").option("--limit <number>","Number of records (default: 20)",parseInt).option("--offset <number>","Pagination offset (default: 0)",parseInt).option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async t=>{try{let n=await new d().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=new c({json:t.json,color:n.color});if(t.json){let r=c.result({message:"Token history endpoint not yet available",history:[]});o.output(r)}else console.log(`
178
- \u{1F4CA} Token Usage History
179
- `),console.log(` This feature is coming soon!
180
- `),console.log(" For now, view usage in the dashboard:"),console.log(` https://runhuman.com/dashboard/usage
181
- `)}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}k();I();S();import{Command as To}from"commander";function Qe(){let s=new To("config");return s.description("Manage CLI configuration"),s.command("get").description("Get a configuration value").argument("<key>","Configuration key").option("--json","Output as JSON").action(async(t,e)=>{try{let o=await new d().get(t),r=new c({json:e.json});if(e.json){let u=c.result({[t]:o});r.output(u)}else console.log(o!==void 0?o:"(not set)")}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("set").description("Set a configuration value").argument("<key>","Configuration key").argument("<value>","Configuration value").option("--global","Set globally (not project-specific)").option("--json","Output as JSON").action(async(t,e,n)=>{try{await new d().set(t,e,n.global);let r=new c({json:n.json});if(n.json){let u=c.result({success:!0,key:t,value:e,scope:n.global?"global":"project"});r.output(u)}else r.success(`Set ${t} = ${e}`+(n.global?" (global)":" (project)"))}catch(o){let r=m(o);new c({json:n.json}).outputError(r.message,r.details),process.exit(r.exitCode)}}),s.command("list").description("List all configuration values").option("--show-secrets","Show API keys (default: masked)").option("--json","Output as JSON").action(async t=>{try{let n=await new d().list(),o=new c({json:t.json});if(t.json){let r=c.result(n);o.output(r)}else{if(console.log(`
176
+ `),console.log(" Email: "+c.email),console.log(" Account ID: "+c.accountId),n.project&&console.log(`
177
+ Default Project: `+n.project),console.log()}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}U();b();C();import{Command as ho}from"commander";function je(){let s=new ho("config");return s.description("Manage CLI configuration"),s.command("get").description("Get a configuration value").argument("<key>","Configuration key").option("--json","Output as JSON").action(async(o,e)=>{try{let t=await new g().get(o),r=new l({json:e.json});if(e.json){let c=l.result({[o]:t});r.output(c)}else console.log(t!==void 0?t:"(not set)")}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("set").description("Set a configuration value").argument("<key>","Configuration key").argument("<value>","Configuration value").option("--global","Set globally (not project-specific)").option("--json","Output as JSON").action(async(o,e,n)=>{try{await new g().set(o,e,n.global);let r=new l({json:n.json});if(n.json){let c=l.result({success:!0,key:o,value:e,scope:n.global?"global":"project"});r.output(c)}else r.success(`Set ${o} = ${e}`+(n.global?" (global)":" (project)"))}catch(t){let r=m(t);new l({json:n.json}).outputError(r.message,r.details),process.exit(r.exitCode)}}),s.command("list").description("List all configuration values").option("--show-secrets","Show API keys (default: masked)").option("--json","Output as JSON").action(async o=>{try{let n=await new g().list(),t=new l({json:o.json});if(o.json){let r=l.result(n);t.output(r)}else{if(console.log(`
182
178
  \u2699\uFE0F Configuration
183
- `),n.global&&Object.keys(n.global).length>0){console.log("Global (~/.config/runhuman/config.json):");for(let[r,u]of Object.entries(n.global)){let i=r==="apiKey"&&!t.showSecrets?Ze(String(u)):u;console.log(` ${r}:`.padEnd(20)+i)}console.log()}if(n.project&&Object.keys(n.project).length>0){console.log("Project (.runhumanrc):");for(let[r,u]of Object.entries(n.project)){let i=r==="apiKey"&&!t.showSecrets?Ze(String(u)):u;console.log(` ${r}:`.padEnd(20)+i)}console.log()}console.log("\u{1F4A1} Set value: runhuman config set <key> <value>"),console.log(` Get value: runhuman config get <key>
184
- `)}}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("reset").description("Reset configuration to defaults").option("--global","Reset global config").option("--project","Reset project config").option("--all","Reset all config").option("--force","Skip confirmation prompt").option("--json","Output as JSON").action(async t=>{try{if(!t.global&&!t.project&&!t.all)throw new Error("Specify --global, --project, or --all");let e=t.all?"all":t.global?"global":"project";t.force||(console.log(`\u26A0\uFE0F This will reset ${e} configuration to defaults.`),console.log(`Use --force to skip this prompt.
185
- `),process.exit(0)),await new d().reset(e);let o=new c({json:t.json});if(t.json){let r=c.result({success:!0,scope:e});o.output(r)}else o.success(`Reset ${e} configuration`)}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}function Ze(s){return s.length<=8?"****":s.substring(0,4)+"*".repeat(s.length-8)+s.substring(s.length-4)}I();S();import{Command as Ro}from"commander";import Eo from"inquirer";import{writeFileSync as Oo}from"fs";import{join as No}from"path";function Xe(){let s=new Ro("init");return s.description("Initialize a new Runhuman project with configuration").option("--name <text>","Project name").option("--url <url>","Default URL").option("--github-repo <owner/repo>","GitHub repository").option("--yes","Skip all prompts (use defaults)").option("--json","Output as JSON").action(async t=>{try{let e=new c({json:t.json});!t.json&&!t.yes&&(console.log(`\u{1F680} Welcome to Runhuman!
179
+ `),n.global&&Object.keys(n.global).length>0){console.log("Global (~/.config/runhuman/config.json):");for(let[r,c]of Object.entries(n.global)){let i=r==="apiKey"&&!o.showSecrets?ye(String(c)):c;console.log(` ${r}:`.padEnd(20)+i)}console.log()}if(n.project&&Object.keys(n.project).length>0){console.log("Project (.runhumanrc):");for(let[r,c]of Object.entries(n.project)){let i=r==="apiKey"&&!o.showSecrets?ye(String(c)):c;console.log(` ${r}:`.padEnd(20)+i)}console.log()}console.log("\u{1F4A1} Set value: runhuman config set <key> <value>"),console.log(` Get value: runhuman config get <key>
180
+ `)}}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("reset").description("Reset configuration to defaults").option("--global","Reset global config").option("--project","Reset project config").option("--all","Reset all config").option("--force","Skip confirmation prompt").option("--json","Output as JSON").action(async o=>{try{if(!o.global&&!o.project&&!o.all)throw new Error("Specify --global, --project, or --all");let e=o.all?"all":o.global?"global":"project";o.force||(console.log(`\u26A0\uFE0F This will reset ${e} configuration to defaults.`),console.log(`Use --force to skip this prompt.
181
+ `),process.exit(0)),await new g().reset(e);let t=new l({json:o.json});if(o.json){let r=l.result({success:!0,scope:e});t.output(r)}else t.success(`Reset ${e} configuration`)}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}function ye(s){return s.length<=8?"****":s.substring(0,4)+"*".repeat(s.length-8)+s.substring(s.length-4)}b();C();import{Command as wo}from"commander";import yo from"inquirer";import{writeFileSync as jo}from"fs";import{join as bo}from"path";function be(){let s=new wo("init");return s.description("Initialize a new Runhuman project with configuration").option("--name <text>","Project name").option("--url <url>","Default URL").option("--github-repo <owner/repo>","GitHub repository").option("--yes","Skip all prompts (use defaults)").option("--json","Output as JSON").action(async o=>{try{let e=new l({json:o.json});!o.json&&!o.yes&&(console.log(`\u{1F680} Welcome to Runhuman!
186
182
  `),console.log("Let's set up your project. This will create:"),console.log(" \u2022 A configuration file (.runhumanrc)"),console.log(` \u2022 Setup your project defaults
187
183
  `),console.log("=".repeat(60)+`
188
- `));let n=t.name,o=t.url,r=t.githubRepo;if(!t.yes&&!t.json){let l=await Eo.prompt([{type:"input",name:"projectName",message:"Project name:",default:"My Project",when:!n},{type:"input",name:"defaultUrl",message:"Default URL (optional):",when:!o},{type:"input",name:"githubRepo",message:"Link to GitHub repository (optional, e.g., owner/repo):",when:!r}]);n=n||l.projectName,o=o||l.defaultUrl,r=r||l.githubRepo}let u={defaultUrl:o||void 0,githubRepo:r||void 0,defaultDuration:5,defaultScreenSize:"desktop"},i=No(process.cwd(),".runhumanrc");if(Oo(i,JSON.stringify(u,null,2)),t.json){let l=c.result({success:!0,configPath:i,config:u});e.output(l)}else console.log(`
184
+ `));let n=o.name,t=o.url,r=o.githubRepo;if(!o.yes&&!o.json){let a=await yo.prompt([{type:"input",name:"projectName",message:"Project name:",default:"My Project",when:!n},{type:"input",name:"defaultUrl",message:"Default URL (optional):",when:!t},{type:"input",name:"githubRepo",message:"Link to GitHub repository (optional, e.g., owner/repo):",when:!r}]);n=n||a.projectName,t=t||a.defaultUrl,r=r||a.githubRepo}let c={defaultUrl:t||void 0,githubRepo:r||void 0,defaultDuration:5,defaultScreenSize:"desktop"},i=bo(process.cwd(),".runhumanrc");if(jo(i,JSON.stringify(c,null,2)),o.json){let a=l.result({success:!0,configPath:i,config:c});e.output(a)}else console.log(`
189
185
  `+"=".repeat(60)),e.success("Project initialized successfully!"),console.log(`\u{1F4C1} Configuration saved to: .runhumanrc
190
186
  `),console.log(`\u{1F389} All set! Try creating your first test:
191
187
  `),console.log(` runhuman create https://example.com -d "Test homepage"
192
188
  `),console.log(`\u{1F4DA} Learn more: https://runhuman.com/docs/cli
193
- `)}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}U();k();I();S();import{Command as vo}from"commander";import Do from"cli-table3";function Ye(){let s=new vo("projects");return s.description("Manage projects"),s.command("list").alias("ls").description("List all projects").option("--limit <number>","Number of results (default: 20)",parseInt).option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async t=>{try{let n=await new d().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=new c({json:t.json,color:n.color}),r=new g(n),{items:u,pagination:i}=await r.listProjects({limit:t.limit||20});if(t.json){let l=c.result({projects:u,pagination:i});o.output(l)}else{console.log(`
194
- \u{1F4C1} Your Projects (${u.length} of ${i.total})
195
- `);let l=new Do({head:["Project ID","Name","Created"].map(p=>p),colWidths:[20,30,20]});u.forEach(p=>{l.push([p.id,p.name,new Date(p.createdAt).toLocaleDateString()])}),console.log(l.toString()),console.log(`
196
- \u{1F4A1} Set default project: runhuman config set project <id>
197
- `)}}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("create").alias("new").description("Create a new project").argument("<name>","Project name").option("--description <text>","Project description").option("--default-url <url>","Default URL for tests").option("--github-repo <owner/repo>","Link to GitHub repository").option("--set-default","Set as default project").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let n=new d,o=await n.loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color}),i=await new g(o).createProject({name:t,description:e.description,defaultUrl:e.defaultUrl,githubRepo:e.githubRepo});if(e.setDefault&&await n.set("project",i.id,!1),e.json){let l=c.result(i);r.output(l)}else r.success("Project created successfully!"),console.log(" Project ID: "+i.id),console.log(" Name: "+i.name),e.setDefault&&console.log(" Set as default project"),console.log()}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("show").alias("info").alias("get").description("Show detailed project information").argument("<projectId>","Project ID to show").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color}),i=await new g(o).getProject(t);if(e.json){let l=c.result(i);r.output(l)}else console.log(`
189
+ `)}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}k();U();b();C();import{Command as Co}from"commander";import Io from"cli-table3";function Ce(){let s=new Co("projects");return s.description("Manage projects"),s.command("list").alias("ls").description("List all projects").option("--limit <number>","Number of results (default: 20)",parseInt).option("--offset <number>","Pagination offset (default: 0)",parseInt).option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async o=>{try{let n=await new g().loadConfig({apiKey:o.apiKey,apiUrl:o.apiUrl}),t=new l({json:o.json,color:n.color}),r=new d(n),c=o.limit||20,i=o.offset||0,[{items:a,pagination:u},{items:p}]=await Promise.all([r.listProjects({limit:c,offset:i}),r.listOrganizations({})]),f=new Map(p.map(h=>[h.id,h.name]));if(o.json){let h=l.result({projects:a,pagination:u});t.output(h)}else{if(u.total===0){console.log(`
190
+ \u{1F4C1} Your Projects (0)
191
+ `),console.log(`No projects found.
192
+ `),console.log(`\u{1F4A1} Create a project: runhuman projects create <name> --organization <org-id>
193
+ `);return}console.log(`
194
+ \u{1F4C1} Your Projects (${a.length} of ${u.total})
195
+ `);let h=new Io({head:["Name","Organization","Project ID","Created"].map(A=>A),colWidths:[25,25,25,15]});a.forEach(A=>{let S=f.get(A.organizationId)||"(unknown)";h.push([A.name,S,A.id,new Date(A.createdAt).toLocaleDateString()])}),console.log(h.toString());let y=i+1,j=i+a.length;console.log(`
196
+ Showing ${y}-${j} of ${u.total} projects`),console.log(`
197
+ \u{1F4A1} View project: runhuman projects show <id>`),console.log("\u{1F4A1} Filter by org: runhuman orgs projects <org-id>"),u.hasMore&&console.log(`\u{1F4A1} Next page: runhuman projects list --offset ${i+c}`),console.log()}}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("create").alias("new").description("Create a new project in an organization").argument("<name>","Project name").option("--organization <id>","Organization ID (required)").option("--default-url <url>","Default URL for tests").option("--github-repo <owner/repo>","Link to GitHub repository").option("--set-default","Set as default project").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let n=new g,t=await n.loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),c=e.organization;if(!c)throw new Error("Organization ID required (use --organization)");let a=await new d(t).createProject({name:o,organizationId:c,defaultUrl:e.defaultUrl,githubRepo:e.githubRepo});if(e.setDefault&&await n.set("project",a.id,!1),e.json){let u=l.result(a);r.output(u)}else r.success("Project created successfully!"),console.log(" Project ID: "+a.id),console.log(" Name: "+a.name),e.setDefault&&console.log(" Set as default project"),console.log()}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("show").alias("info").alias("get").description("Show detailed project information").argument("<projectId>","Project ID to show").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),c=new d(t),i=await c.getProject(o),[a,u]=await Promise.all([c.getOrganization(i.organizationId),c.getOrganizationBilling(i.organizationId)]);if(e.json){let p=l.result({project:i,organization:a,balance:{balance:u.balance,balanceUsd:u.balanceUsd}});r.output(p)}else console.log(`
198
198
  \u{1F4C1} Project Details
199
- `),console.log(" Project ID: "+i.id),console.log(" Name: "+i.name),i.description&&console.log(" Description: "+i.description),i.defaultUrl&&console.log(" Default URL: "+i.defaultUrl),i.githubRepo&&console.log(" GitHub Repo: "+i.githubRepo),console.log(" Created: "+new Date(i.createdAt).toLocaleString()),console.log()}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("switch").alias("use").description("Set default project for CLI commands").argument("<projectId>","Project ID to use as default").option("--global","Set as global default instead of local").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let n=new d,o=await n.loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color}),i=await new g(o).getProject(t);if(await n.set("project",t,e.global!==!1),e.json){let l=c.result({success:!0,projectId:t,projectName:i.name,scope:e.global!==!1?"global":"local"});r.output(l)}else r.success(`Default project set to: ${i.name} (${t})`),e.global!==!1?console.log(" Scope: global (all directories)"):console.log(" Scope: local (current directory only)"),console.log()}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("update").description("Update project settings").argument("<projectId>","Project ID to update").option("--name <text>","Update name").option("--description <text>","Update description").option("--default-url <url>","Update default URL").option("--github-repo <owner/repo>","Update GitHub repo").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color}),u={name:e.name,description:e.description,defaultUrl:e.defaultUrl,githubRepo:e.githubRepo},l=await new g(o).updateProject(t,u);if(e.json){let p=c.result(l);r.output(p)}else r.success("Project updated successfully!")}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("delete").alias("rm").description("Delete a project permanently").argument("<projectId>","Project ID to delete").option("--force","Skip confirmation prompt").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{e.force||(console.log("\u26A0\uFE0F This will permanently delete the project and all its data."),console.log(`Use --force to skip this prompt.
200
- `),process.exit(0));let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color});if(await new g(o).deleteProject(t),e.json){let i=c.result({success:!0});r.output(i)}else r.success("Project deleted successfully!")}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s}U();k();I();S();import{Command as $o}from"commander";import Mo from"cli-table3";function et(){let s=new $o("keys");return s.description("Manage API keys"),s.command("list").alias("ls").description("List all API keys").option("--project <id>","Filter by project (required)").option("--show-keys","Show full API keys (default: masked)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async t=>{try{let n=await new d().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=t.project||n.project;if(!o)throw new Error("Project ID required (use --project or set default project)");let r=new c({json:t.json,color:n.color}),i=await new g(n).listApiKeys(o);if(t.json){let l=c.result({keys:i});r.output(l)}else{console.log(`
201
- \u{1F511} API Keys (${i.length})
202
- `);let l=new Mo({head:["Key ID","Name","Key","Last Used","Created"].map(p=>p),colWidths:[20,25,20,20,20]});i.forEach(p=>{let f=t.showKeys?p.key:Lo(p.key),h=p.lastUsedAt?new Date(p.lastUsedAt).toLocaleDateString():"Never";l.push([p.id,p.name,f,h,new Date(p.createdAt).toLocaleDateString()])}),console.log(l.toString()),console.log(`
203
- \u{1F4A1} Create new key: runhuman keys create "Key Name" --project <id>`),console.log(` Show full key: runhuman keys show <keyId>
204
- `)}}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("create").alias("new").description("Create a new API key").argument("<name>","API key name").option("--project <id>","Project ID (required)").option("--copy","Copy key to clipboard").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||o.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let u=new c({json:e.json,color:o.color}),l=await new g(o).createApiKey(r,t);if(e.json){let p=c.result(l);u.output(p)}else u.success("API Key created successfully!"),console.log(`
205
- Key ID: `+l.id),console.log(" Name: "+l.name),console.log(`
206
- API Key: `+l.key),console.log(" "+"^".repeat(l.key.length)),console.log(` \u26A0\uFE0F Save this key! It won't be shown again.
207
- `),console.log("\u{1F4A1} Use this key:"),console.log(" export RUNHUMAN_API_KEY="+l.key),console.log(` runhuman create https://myapp.com -d "Test"
199
+ `),console.log(" Project ID: "+i.id),console.log(" Name: "+i.name),console.log(" Organization: "+a.name+" ("+a.id+")"),console.log(" Org Balance: "+u.balanceUsd),i.defaultUrl&&console.log(" Default URL: "+i.defaultUrl),i.githubRepo&&console.log(" GitHub Repo: "+i.githubRepo),console.log(" Created: "+new Date(i.createdAt).toLocaleString()),console.log(" Last Updated: "+new Date(i.updatedAt).toLocaleString()),console.log(),console.log("\u{1F4A1} View organization: runhuman orgs show "+a.id),console.log("\u{1F4A1} List org projects: runhuman orgs projects "+a.id),console.log("\u{1F4A1} Check org balance: runhuman orgs balance "+a.id),console.log()}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("switch").alias("use").description("Set default project for CLI commands").argument("<projectId>","Project ID to use as default").option("--global","Set as global default instead of local").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let n=new g,t=await n.loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),i=await new d(t).getProject(o);if(await n.set("project",o,e.global!==!1),e.json){let a=l.result({success:!0,projectId:o,projectName:i.name,scope:e.global!==!1?"global":"local"});r.output(a)}else r.success(`Default project set to: ${i.name} (${o})`),e.global!==!1?console.log(" Scope: global (all directories)"):console.log(" Scope: local (current directory only)"),console.log()}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("update").description("Update project settings").argument("<projectId>","Project ID to update").option("--name <text>","Update name").option("--default-url <url>","Update default URL").option("--github-repo <owner/repo>","Update GitHub repo").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),c={name:e.name,defaultUrl:e.defaultUrl,githubRepo:e.githubRepo},a=await new d(t).updateProject(o,c);if(e.json){let u=l.result(a);r.output(u)}else r.success("Project updated successfully!")}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("delete").alias("rm").description("Delete a project permanently").argument("<projectId>","Project ID to delete").option("--force","Skip confirmation prompt").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{e.force||(console.log("\u26A0\uFE0F This will permanently delete the project and all its data."),console.log(`Use --force to skip this prompt.
200
+ `),process.exit(0));let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color});if(await new d(t).deleteProject(o),e.json){let i=l.result({success:!0});r.output(i)}else r.success("Project deleted successfully!")}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s}k();U();b();C();import{Command as Uo}from"commander";import Ie from"cli-table3";function Ue(){let s=new Uo("orgs");return s.alias("organizations"),s.description("Manage organizations"),s.command("list").alias("ls").description("List all organizations you belong to").option("--limit <number>","Number of results (default: 20)",parseInt).option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async o=>{try{let n=await new g().loadConfig({apiKey:o.apiKey,apiUrl:o.apiUrl}),t=new l({json:o.json,color:n.color}),r=new d(n),{items:c,total:i}=await r.listOrganizations({limit:o.limit||20});if(o.json){let a=l.result({organizations:c,pagination:{total:i,limit:o.limit||20,offset:0,hasMore:c.length<i}});t.output(a)}else{console.log(`
201
+ \u{1F3E2} Your Organizations (${c.length} of ${i})
202
+ `);let a=new Ie({head:["Organization ID","Name","Projects","Created"].map(u=>u),colWidths:[25,30,12,15]});c.forEach(u=>{a.push([u.id,u.name,u.projectCount?.toString()||"0",new Date(u.createdAt).toLocaleDateString()])}),console.log(a.toString()),console.log(`
203
+ \u{1F4A1} View org details: runhuman orgs show <id>`),console.log(`\u{1F4A1} Check org balance: runhuman orgs balance <id>
204
+ `)}}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("show").alias("info").alias("get").description("Show detailed organization information").argument("<organizationId>","Organization ID to show").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),i=await new d(t).getOrganization(o);if(e.json){let a=l.result(i);r.output(a)}else console.log(`
205
+ \u{1F3E2} Organization Details
206
+ `),console.log(" Organization ID: "+i.id),console.log(" Name: "+i.name),console.log(" Slug: "+i.slug),console.log(" Created: "+new Date(i.createdAt).toLocaleString()),console.log(),console.log("\u{1F4A1} List projects: runhuman orgs projects "+i.id),console.log("\u{1F4A1} Check balance: runhuman orgs balance "+i.id),console.log()}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("balance").description("Show organization billing balance").argument("<organizationId>","Organization ID").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),c=new d(t),[i,a]=await Promise.all([c.getOrganization(o),c.getOrganizationBilling(o)]);if(e.json){let u=l.result({balance:a});r.output(u)}else console.log(`
207
+ \u{1F4B0} Organization Balance: ${i.name}
208
+ `),console.log(" Current Balance: "+a.balanceUsd),console.log(" Billing Active: "+(a.hasBilling?"Yes":"No")),console.log(),console.log(" Estimated Tests: ~"+Math.floor(a.balance)+" tests at $1.00 per test"),console.log(),a.hasBilling?console.log("\u{1F4A1} Manage billing: https://runhuman.com/dashboard/organizations/"+o+"/billing"):console.log("\u{1F4A1} Set up billing: https://runhuman.com/dashboard/organizations/"+o+"/billing"),console.log()}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("projects").description("List all projects in an organization").argument("<organizationId>","Organization ID").option("--limit <number>","Number of results (default: 50)",parseInt).option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),c=new d(t),[i,{items:a,total:u}]=await Promise.all([c.getOrganization(o),c.listOrganizationProjects(o,{limit:e.limit||50})]);if(e.json){let p=l.result({organizationId:o,projects:a,total:u});r.output(p)}else{if(u===0){console.log(`
209
+ \u{1F4C1} Projects in ${i.name} (0 projects)
210
+ `),console.log(`No projects found in this organization.
211
+ `),console.log("\u{1F4A1} Create a project: runhuman projects create <name> --organization "+o),console.log("\u{1F4A1} View organization: runhuman orgs show "+o),console.log();return}console.log(`
212
+ \u{1F4C1} Projects in ${i.name} (${a.length} of ${u})
213
+ `);let p=new Ie({head:["Name","Project ID","Default URL","Created"].map(f=>f),colWidths:[25,25,30,15]});a.forEach(f=>{let h=f.defaultUrl?f.defaultUrl.length>27?f.defaultUrl.substring(0,24)+"...":f.defaultUrl:"-";p.push([f.name,f.id,h,new Date(f.createdAt).toLocaleDateString()])}),console.log(p.toString()),console.log(`
214
+ \u{1F4A1} View project details: runhuman projects show <project-id>`),console.log("\u{1F4A1} Create new project: runhuman projects create <name> --organization "+o),console.log("\u{1F4A1} View organization: runhuman orgs show "+o),console.log()}}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s}k();U();b();C();import{Command as Po}from"commander";import ko from"cli-table3";function Pe(){let s=new Po("keys");return s.description("Manage API keys"),s.command("list").alias("ls").description("List all API keys for an organization").option("--organization <id>","Organization ID (required)").option("--show-keys","Show full API keys (default: masked)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async o=>{try{let n=await new g().loadConfig({apiKey:o.apiKey,apiUrl:o.apiUrl}),t=o.organization;if(!t)throw new Error("Organization ID required (use --organization)");let r=new l({json:o.json,color:n.color}),c=new d(n),{items:i,total:a}=await c.listApiKeys(t);if(o.json){let u=l.result({keys:i,total:a});r.output(u)}else{if(a===0){console.log(`
215
+ \u{1F511} API Keys (0)
216
+ `),console.log(`No API keys found for this organization.
217
+ `),console.log('\u{1F4A1} Create a key: runhuman keys create "Key Name" --organization '+t+`
218
+ `);return}console.log(`
219
+ \u{1F511} API Keys (${i.length} of ${a})
220
+ `);let u=new ko({head:["Key ID","Name","Key","Last Used","Created"].map(p=>p),colWidths:[20,25,20,20,20]});i.forEach(p=>{let f=o.showKeys?p.key:Ao(p.key),h=p.lastUsedAt?new Date(p.lastUsedAt).toLocaleDateString():"Never";u.push([p.id,p.name,f,h,new Date(p.createdAt).toLocaleDateString()])}),console.log(u.toString()),console.log(`
221
+ \u{1F4A1} Create new key: runhuman keys create "Key Name" --organization `+t),console.log(` Show full key: runhuman keys show <keyId>
222
+ `)}}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("create").alias("new").description("Create a new API key for an organization").argument("<name>","API key name").option("--organization <id>","Organization ID (required)").option("--copy","Copy key to clipboard").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.organization;if(!r)throw new Error("Organization ID required (use --organization)");let c=new l({json:e.json,color:t.color}),a=await new d(t).createApiKey(r,o);if(e.json){let u=l.result(a);c.output(u)}else c.success("API Key created successfully!"),console.log(`
223
+ Key ID: `+a.id),console.log(" Name: "+a.name),console.log(`
224
+ API Key: `+a.key),console.log(" "+"^".repeat(a.key.length)),console.log(` \u26A0\uFE0F Save this key! It won't be shown again.
225
+ `),console.log("\u{1F4A1} Use this key:"),console.log(" export RUNHUMAN_API_KEY="+a.key),console.log(` runhuman create https://myapp.com -d "Test"
208
226
  `),console.log("\u{1F512} Store securely:"),console.log(" - Use environment variables (recommended)"),console.log(" - Use secret management tools"),console.log(` - Never commit to git!
209
- `)}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("show").alias("info").alias("get").description("Show details of a specific API key").argument("<keyId>","Key ID to show").option("--show-key","Show full API key (default: masked)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl});new c({json:e.json,color:o.color}).warn("Note: keys show requires listing all keys first"),console.log(`Use: runhuman keys list --show-keys
210
- `)}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("delete").aliases(["rm","revoke"]).description("Delete an API key permanently").argument("<keyId>","Key ID to delete").option("--force","Skip confirmation prompt").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{e.force||(console.log("\u26A0\uFE0F This will permanently delete the API key."),console.log(`Use --force to skip this prompt.
211
- `),process.exit(0));let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color});if(await new g(o).deleteApiKey(t),e.json){let i=c.result({success:!0});r.output(i)}else r.success("API key deleted successfully!")}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s}function Lo(s){return s.length<=12?"****":s.substring(0,8)+"*".repeat(s.length-12)+s.substring(s.length-4)}U();k();I();S();import{Command as Jo}from"commander";import _o from"cli-table3";function tt(){let s=new Jo("templates");return s.description("Manage test templates"),s.command("list").alias("ls").description("List all test templates").option("--project <id>","Filter by project (required)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async t=>{try{let n=await new d().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=t.project||n.project;if(!o)throw new Error("Project ID required (use --project or set default project)");let r=new c({json:t.json,color:n.color}),i=await new g(n).listTemplates(o);if(t.json){let l=c.result({templates:i});r.output(l)}else{console.log(`
212
- \u{1F4CB} Test Templates (${i.length})
213
- `);let l=new _o({head:["ID","Name","Description","Created"].map(p=>p),colWidths:[30,25,35,20]});i.forEach(p=>{let f=p.description&&p.description.length>30?p.description.substring(0,27)+"...":p.description||"-";l.push([p.id,p.name,f,new Date(p.createdAt).toLocaleDateString()])}),console.log(l.toString()),console.log(`
214
- \u{1F4A1} Create new template: runhuman templates create "Template Name" --project <id>`),console.log(` View template: runhuman templates show <templateId>
215
- `)}}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("create").alias("new").description("Create a new test template").argument("<name>","Template name").option("--project <id>","Project ID (required)").option("-d, --description <text>","Template description").option("--duration <seconds>","Target test duration in seconds").option("--screen-size <size>","Default screen size (desktop/mobile/tablet)").option("--schema <path>","Path to JSON schema file").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||o.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let u=new c({json:e.json,color:o.color}),i;if(e.schema){let{readFileSync:h}=await import("fs"),y=h(e.schema,"utf-8");i=JSON.parse(y)}let l={name:t,description:e.description,targetDurationMinutes:e.duration?Math.floor(parseInt(e.duration)/60):void 0,defaultScreenSize:e.screenSize,outputSchema:i},f=await new g(o).createTemplate(r,l);if(e.json){let h=c.result(f);u.output(h)}else u.success("Template created successfully!"),console.log(`
216
- Template ID: `+f.id),console.log(" Name: "+f.name),f.description&&console.log(" Description: "+f.description),console.log(`
217
- \u{1F4A1} Use this template:`),console.log(" runhuman create https://myapp.com --template "+f.id+`
218
- `)}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("show").alias("info").alias("get").description("Show details of a specific template").argument("<templateId>","Template ID to show").option("--project <id>","Project ID (required)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||o.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let u=new c({json:e.json,color:o.color}),l=await new g(o).getTemplate(r,t);if(e.json){let p=c.result(l);u.output(p)}else console.log(`
227
+ `)}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("show").alias("info").alias("get").description("Show details of a specific API key").argument("<keyId>","Key ID to show").option("--show-key","Show full API key (default: masked)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl});new l({json:e.json,color:t.color}).warn("Note: keys show requires listing all keys first"),console.log(`Use: runhuman keys list --show-keys
228
+ `)}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("delete").aliases(["rm","revoke"]).description("Delete an API key permanently").argument("<keyId>","Key ID to delete").option("--force","Skip confirmation prompt").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{e.force||(console.log("\u26A0\uFE0F This will permanently delete the API key."),console.log(`Use --force to skip this prompt.
229
+ `),process.exit(0));let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color});if(await new d(t).deleteApiKey(o),e.json){let i=l.result({success:!0});r.output(i)}else r.success("API key deleted successfully!")}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s}function Ao(s){return s.length<=12?"****":s.substring(0,8)+"*".repeat(s.length-12)+s.substring(s.length-4)}k();U();b();C();import{Command as So}from"commander";import Oo from"cli-table3";function ke(){let s=new So("templates");return s.description("Manage test templates"),s.command("list").alias("ls").description("List all test templates").option("--project <id>","Filter by project (required)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async o=>{try{let n=await new g().loadConfig({apiKey:o.apiKey,apiUrl:o.apiUrl}),t=o.project||n.project;if(!t)throw new Error("Project ID required (use --project or set default project)");let r=new l({json:o.json,color:n.color}),c=new d(n),{items:i,pagination:a}=await c.listTemplates(t);if(o.json){let u=l.result({templates:i,pagination:a});r.output(u)}else{if(a.total===0){console.log(`
230
+ \u{1F4CB} Test Templates (0)
231
+ `),console.log(`No templates found for this project.
232
+ `),console.log('\u{1F4A1} Create a template: runhuman templates create "Template Name" --project '+t+`
233
+ `);return}console.log(`
234
+ \u{1F4CB} Test Templates (${i.length} of ${a.total})
235
+ `);let u=new Oo({head:["ID","Name","Description","Created"].map(p=>p),colWidths:[30,25,35,20]});i.forEach(p=>{let f=p.description&&p.description.length>30?p.description.substring(0,27)+"...":p.description||"-";u.push([p.id,p.name,f,new Date(p.createdAt).toLocaleDateString()])}),console.log(u.toString()),console.log(`
236
+ \u{1F4A1} Create new template: runhuman templates create "Template Name" --project `+t),console.log(` View template: runhuman templates show <templateId>
237
+ `)}}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("create").alias("new").description("Create a new test template").argument("<name>","Template name").option("--project <id>","Project ID (required)").option("-d, --description <text>","Template description").option("--duration <seconds>","Target test duration in seconds").option("--screen-size <size>","Default screen size (desktop/mobile/tablet)").option("--schema <path>","Path to JSON schema file").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||t.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let c=new l({json:e.json,color:t.color}),i;if(e.schema){let{readFileSync:f}=await import("fs"),h=f(e.schema,"utf-8");i=JSON.parse(h)}let a={name:o,description:e.description,targetDurationMinutes:e.duration?Math.floor(parseInt(e.duration)/60):void 0,defaultScreenSize:e.screenSize,outputSchema:i},p=await new d(t).createTemplate(r,a);if(e.json){let f=l.result(p);c.output(f)}else c.success("Template created successfully!"),console.log(`
238
+ Template ID: `+p.id),console.log(" Name: "+p.name),p.description&&console.log(" Description: "+p.description),console.log(`
239
+ \u{1F4A1} Use this template:`),console.log(" runhuman create https://myapp.com --template "+p.id+`
240
+ `)}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("show").alias("info").alias("get").description("Show details of a specific template").argument("<templateId>","Template ID to show").option("--project <id>","Project ID (required)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||t.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let c=new l({json:e.json,color:t.color}),a=await new d(t).getTemplate(r,o);if(e.json){let u=l.result(a);c.output(u)}else console.log(`
219
241
  \u{1F4CB} Template Details
220
- `),console.log("ID: "+l.id),console.log("Name: "+l.name),console.log("Description: "+(l.description||"-")),console.log("Project: "+l.projectId),l.targetDurationMinutes&&console.log("Duration: "+l.targetDurationMinutes+" minutes"),l.defaultScreenSize&&console.log("Screen Size: "+l.defaultScreenSize),console.log("Created: "+new Date(l.createdAt).toLocaleString()),l.outputSchema&&(console.log(`
221
- Output Schema:`),console.log(JSON.stringify(l.outputSchema,null,2))),console.log(`
222
- \u{1F4A1} Use this template:`),console.log(" runhuman create https://myapp.com --template "+l.id+`
223
- `)}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("update").alias("edit").description("Update a template").argument("<templateId>","Template ID to update").option("--project <id>","Project ID (required)").option("--name <name>","New template name").option("-d, --description <text>","New description").option("--duration <seconds>","New target duration in seconds").option("--screen-size <size>","New default screen size").option("--schema <path>","Path to new JSON schema file").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||o.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let u=new c({json:e.json,color:o.color}),i;if(e.schema){let{readFileSync:y}=await import("fs"),w=y(e.schema,"utf-8");i=JSON.parse(w)}let l={name:e.name,description:e.description,targetDurationMinutes:e.duration?Math.floor(parseInt(e.duration)/60):void 0,defaultScreenSize:e.screenSize,outputSchema:i},p=Object.fromEntries(Object.entries(l).filter(([,y])=>y!==void 0));if(Object.keys(p).length===0)throw new Error("No updates provided. Use --name, --description, --duration, --screen-size, or --schema");let h=await new g(o).updateTemplate(r,t,l);if(e.json){let y=c.result(h);u.output(y)}else u.success("Template updated successfully!"),console.log(`
224
- Template ID: `+h.id),console.log(" Name: "+h.name+`
225
- `)}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("delete").alias("rm").description("Delete a template permanently").argument("<templateId>","Template ID to delete").option("--project <id>","Project ID (required)").option("--force","Skip confirmation prompt").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{e.force||(console.log("\u26A0\uFE0F This will permanently delete the template."),console.log(`Use --force to skip this prompt.
226
- `),process.exit(0));let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||o.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let u=new c({json:e.json,color:o.color});if(await new g(o).deleteTemplate(r,t),e.json){let l=c.result({success:!0});u.output(l)}else u.success("Template deleted successfully!")}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s}U();k();I();S();import{Command as Fo}from"commander";import ot from"cli-table3";function nt(){let s=new Fo("github");return s.alias("gh"),s.description("GitHub integration commands"),s.command("link").description("Link a GitHub repository to your project").argument("<repo>","Repository in format owner/repo").option("--project <id>","Project ID (required)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let n=new d,o=await n.loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||o.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let u=new c({json:e.json,color:o.color}),i=t.match(/^([^/]+)\/([^/]+)$/);if(!i)throw new Error("Invalid repository format. Use: owner/repo");let[,l,p]=i,h=await new g(o).linkGithubRepo(r,l,p);if(e.json){let y=c.result(h);u.output(y)}else u.success("GitHub repository linked successfully!"),console.log(`
227
- Repository: `+t),console.log(" Project: "+r),console.log(`
228
- \u{1F4A1} Now you can:`),console.log(" - List issues: runhuman github issues "+t),console.log(" - Test an issue: runhuman github test <issueNumber> --repo "+t),console.log(" - Bulk test: runhuman github bulk-test --repo "+t+`
229
- `),await n.saveProjectConfig({githubRepo:t}),console.log(`\u2713 Repository saved to project config (.runhumanrc)
230
- `)}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("repos").alias("repositories").description("List linked GitHub repositories").option("--project <id>","Filter by project").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async t=>{try{let n=await new d().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=t.project||n.project,r=new c({json:t.json,color:n.color}),i=await new g(n).listGithubRepos(o);if(t.json){let l=c.result({repositories:i});r.output(l)}else{console.log(`
231
- \u{1F517} Linked GitHub Repositories (${i.length})
232
- `);let l=new ot({head:["Repository","Project","Linked"].map(p=>p),colWidths:[35,30,20]});i.forEach(p=>{l.push([p.fullName,p.projectId,new Date(p.linkedAt).toLocaleDateString()])}),console.log(l.toString()),console.log(`
242
+ `),console.log("ID: "+a.id),console.log("Name: "+a.name),console.log("Description: "+(a.description||"-")),console.log("Project: "+a.projectId),a.targetDurationMinutes&&console.log("Duration: "+a.targetDurationMinutes+" minutes"),a.defaultScreenSize&&console.log("Screen Size: "+a.defaultScreenSize),console.log("Created: "+new Date(a.createdAt).toLocaleString()),a.outputSchema&&(console.log(`
243
+ Output Schema:`),console.log(JSON.stringify(a.outputSchema,null,2))),console.log(`
244
+ \u{1F4A1} Use this template:`),console.log(" runhuman create https://myapp.com --template "+a.id+`
245
+ `)}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("update").alias("edit").description("Update a template").argument("<templateId>","Template ID to update").option("--project <id>","Project ID (required)").option("--name <name>","New template name").option("-d, --description <text>","New description").option("--duration <seconds>","New target duration in seconds").option("--screen-size <size>","New default screen size").option("--schema <path>","Path to new JSON schema file").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||t.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let c=new l({json:e.json,color:t.color}),i;if(e.schema){let{readFileSync:h}=await import("fs"),y=h(e.schema,"utf-8");i=JSON.parse(y)}let a={name:e.name,description:e.description,targetDurationMinutes:e.duration?Math.floor(parseInt(e.duration)/60):void 0,defaultScreenSize:e.screenSize,outputSchema:i},u=Object.fromEntries(Object.entries(a).filter(([,h])=>h!==void 0));if(Object.keys(u).length===0)throw new Error("No updates provided. Use --name, --description, --duration, --screen-size, or --schema");let f=await new d(t).updateTemplate(r,o,a);if(e.json){let h=l.result(f);c.output(h)}else c.success("Template updated successfully!"),console.log(`
246
+ Template ID: `+f.id),console.log(" Name: "+f.name+`
247
+ `)}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("delete").alias("rm").description("Delete a template permanently").argument("<templateId>","Template ID to delete").option("--project <id>","Project ID (required)").option("--force","Skip confirmation prompt").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{e.force||(console.log("\u26A0\uFE0F This will permanently delete the template."),console.log(`Use --force to skip this prompt.
248
+ `),process.exit(0));let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||t.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let c=new l({json:e.json,color:t.color});if(await new d(t).deleteTemplate(r,o),e.json){let a=l.result({success:!0});c.output(a)}else c.success("Template deleted successfully!")}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s}k();U();b();C();import{Command as Ro}from"commander";import Ae from"cli-table3";function Se(){let s=new Ro("github");return s.alias("gh"),s.description("GitHub integration commands"),s.command("link").description("Link a GitHub repository to your project").argument("<repo>","Repository in format owner/repo").option("--project <id>","Project ID (required)").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let n=new g,t=await n.loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.project||t.project;if(!r)throw new Error("Project ID required (use --project or set default project)");let c=new l({json:e.json,color:t.color}),i=o.match(/^([^/]+)\/([^/]+)$/);if(!i)throw new Error("Invalid repository format. Use: owner/repo");let[,a,u]=i,f=await new d(t).linkGithubRepo(r,a,u);if(e.json){let h=l.result(f);c.output(h)}else c.success("GitHub repository linked successfully!"),console.log(`
249
+ Repository: `+o),console.log(" Project: "+r),console.log(`
250
+ \u{1F4A1} Now you can:`),console.log(" - List issues: runhuman github issues "+o),console.log(" - Test an issue: runhuman github test <issueNumber> --repo "+o),console.log(" - Bulk test: runhuman github bulk-test --repo "+o+`
251
+ `),await n.saveProjectConfig({githubRepo:o}),console.log(`\u2713 Repository saved to project config (.runhumanrc)
252
+ `)}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("repos").alias("repositories").description("List GitHub repositories accessible to an organization").option("--organization <id>","Organization ID (required)").option("--search <query>","Filter by repository name").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async o=>{try{let n=await new g().loadConfig({apiKey:o.apiKey,apiUrl:o.apiUrl}),t=o.organization;if(!t)throw new Error("Organization ID required (use --organization)");let r=new l({json:o.json,color:n.color}),i=await new d(n).listGithubRepos(t,{search:o.search});if(o.json)r.output(l.result(i));else{console.log(`
253
+ \u{1F517} GitHub Repositories (${i.items.length})
254
+ `);let a=new Ae({head:["Repository","Added"].map(u=>u),colWidths:[45,20]});i.items.forEach(u=>{a.push([u.fullName,new Date(u.createdAt).toLocaleDateString()])}),console.log(a.toString()),console.log(`
233
255
  \u{1F4A1} Test an issue: runhuman github test <issueNumber> --repo owner/repo
234
- `)}}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("issues").description("List GitHub issues for a repository").argument("<repo>","Repository in format owner/repo").option("--state <state>","Filter by state (open/closed/all)","open").option("--labels <labels>","Filter by comma-separated labels").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new c({json:e.json,color:o.color}),u=t.match(/^([^/]+)\/([^/]+)$/);if(!u)throw new Error("Invalid repository format. Use: owner/repo");let[,i,l]=u,f=await new g(o).listGithubIssues(i,l,{state:e.state,labels:e.labels?.split(",")});if(e.json){let h=c.result({issues:f});r.output(h)}else{console.log(`
235
- \u{1F41B} GitHub Issues for ${t} (${f.length})
236
- `);let h=new ot({head:["#","Title","State","Labels","Created"].map(y=>y),colWidths:[8,40,10,20,15]});f.forEach(y=>{let w=y.labels?.join(", ")||"-",C=w.length>18?w.substring(0,15)+"...":w;h.push(["#"+y.number,y.title.length>38?y.title.substring(0,35)+"...":y.title,y.state,C,new Date(y.createdAt).toLocaleDateString()])}),console.log(h.toString()),console.log(`
237
- \u{1F4A1} Test an issue: runhuman github test <issueNumber> --repo `+t+`
238
- `)}}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("test").description("Create a QA test job for a GitHub issue").argument("<issueNumber>","Issue number to test").option("--repo <owner/repo>","Repository (or use default from config)").option("--url <url>","URL to test (required)").option("--template <id>","Template ID to use").option("--sync","Wait for result before exiting").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(t,e)=>{try{let o=await new d().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.repo||o.githubRepo;if(!r)throw new Error("Repository required (use --repo or set default with: runhuman github link)");if(!e.url)throw new Error("URL required (use --url)");let u=new c({json:e.json,color:o.color}),i=r.match(/^([^/]+)\/([^/]+)$/);if(!i)throw new Error("Invalid repository format. Use: owner/repo");let[,l,p]=i,f=new g(o),h=await f.getGithubIssue(l,p,parseInt(t));if(!o.project)throw new Error("No project selected. Run: runhuman projects switch <project-id>");let y={projectId:o.project,url:e.url,description:`Test GitHub issue #${t}: ${h.title}
256
+ `)}}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s.command("issues").description("List GitHub issues for a repository").argument("<repo>","Repository in format owner/repo").option("--state <state>","Filter by state (open/closed/all)","open").option("--labels <labels>","Filter by comma-separated labels").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=new l({json:e.json,color:t.color}),c=o.match(/^([^/]+)\/([^/]+)$/);if(!c)throw new Error("Invalid repository format. Use: owner/repo");let[,i,a]=c,p=await new d(t).listGithubIssues(i,a,{state:e.state,labels:e.labels?.split(",")});if(e.json){let f=l.result({issues:p});r.output(f)}else{console.log(`
257
+ \u{1F41B} GitHub Issues for ${o} (${p.length})
258
+ `);let f=new Ae({head:["#","Title","State","Labels","Created"].map(h=>h),colWidths:[8,40,10,20,15]});p.forEach(h=>{let y=h.labels?.join(", ")||"-",j=y.length>18?y.substring(0,15)+"...":y;f.push(["#"+h.number,h.title.length>38?h.title.substring(0,35)+"...":h.title,h.state,j,new Date(h.createdAt).toLocaleDateString()])}),console.log(f.toString()),console.log(`
259
+ \u{1F4A1} Test an issue: runhuman github test <issueNumber> --repo `+o+`
260
+ `)}}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("test").description("Create a QA test job for a GitHub issue").argument("<issueNumber>","Issue number to test").option("--repo <owner/repo>","Repository (or use default from config)").option("--url <url>","URL to test (required)").option("--template <id>","Template ID to use").option("--sync","Wait for result before exiting").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async(o,e)=>{try{let t=await new g().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),r=e.repo||t.githubRepo;if(!r)throw new Error("Repository required (use --repo or set default with: runhuman github link)");if(!e.url)throw new Error("URL required (use --url)");let c=new l({json:e.json,color:t.color}),i=r.match(/^([^/]+)\/([^/]+)$/);if(!i)throw new Error("Invalid repository format. Use: owner/repo");let[,a,u]=i,p=new d(t),f=await p.getGithubIssue(a,u,parseInt(o));if(!t.project)throw new Error("No project selected. Run: runhuman projects switch <project-id>");let h={projectId:t.project,url:e.url,description:`Test GitHub issue #${o}: ${f.title}
239
261
 
240
- ${h.body}`,metadata:{githubIssue:{owner:l,repo:p,number:parseInt(t),url:h.url}},templateId:e.template},w=await f.createJob(y);if(e.json){let C=c.result(w);u.output(C)}else if(u.success("QA test job created for issue #"+t),console.log(`
241
- Job ID: `+w.jobId),console.log(" Issue: #"+t+" - "+h.title),console.log(" Status: "+w.status),console.log(" URL: "+e.url),console.log(`
242
- \u{1F4A1} Check status: runhuman status `+w.jobId+`
243
- `),e.sync){let{waitForJob:C}=await Promise.resolve().then(()=>(Z(),ne));await C(w.jobId,f,u,600)}}catch(n){let o=m(n);new c({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),s.command("bulk-test").description("Create QA test jobs for multiple GitHub issues").option("--repo <owner/repo>","Repository (or use default from config)").option("--url <url>","URL to test (required)").option("--labels <labels>","Filter issues by comma-separated labels").option("--state <state>","Filter by state (open/closed/all)","open").option("--template <id>","Template ID to use").option("--limit <number>","Maximum number of jobs to create","10").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async t=>{try{let n=await new d().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=t.repo||n.githubRepo;if(!o)throw new Error("Repository required (use --repo or set default with: runhuman github link)");if(!t.url)throw new Error("URL required (use --url)");let r=new c({json:t.json,color:n.color}),u=o.match(/^([^/]+)\/([^/]+)$/);if(!u)throw new Error("Invalid repository format. Use: owner/repo");let[,i,l]=u,p=new g(n),f=await p.listGithubIssues(i,l,{state:t.state,labels:t.labels?.split(",")}),h=parseInt(t.limit),y=f.slice(0,h);if(y.length===0){console.log(`No issues found matching the criteria.
262
+ ${f.body}`,metadata:{githubIssue:{owner:a,repo:u,number:parseInt(o),url:f.url}},templateId:e.template},y=await p.createJob(h);if(e.json){let j=l.result(y);c.output(j)}else if(c.success("QA test job created for issue #"+o),console.log(`
263
+ Job ID: `+y.jobId),console.log(" Issue: #"+o+" - "+f.title),console.log(" Status: "+y.status),console.log(" URL: "+e.url),console.log(`
264
+ \u{1F4A1} Check status: runhuman status `+y.jobId+`
265
+ `),e.sync){let{waitForJob:j}=await Promise.resolve().then(()=>(V(),oe));await j(y.jobId,p,c,600)}}catch(n){let t=m(n);new l({json:e.json}).outputError(t.message,t.details),process.exit(t.exitCode)}}),s.command("bulk-test").description("Create QA test jobs for multiple GitHub issues").option("--repo <owner/repo>","Repository (or use default from config)").option("--url <url>","URL to test (required)").option("--labels <labels>","Filter issues by comma-separated labels").option("--state <state>","Filter by state (open/closed/all)","open").option("--template <id>","Template ID to use").option("--limit <number>","Maximum number of jobs to create","10").option("--json","Output as JSON").option("--api-key <key>","API key").option("--api-url <url>","API URL").action(async o=>{try{let n=await new g().loadConfig({apiKey:o.apiKey,apiUrl:o.apiUrl}),t=o.repo||n.githubRepo;if(!t)throw new Error("Repository required (use --repo or set default with: runhuman github link)");if(!o.url)throw new Error("URL required (use --url)");let r=new l({json:o.json,color:n.color}),c=t.match(/^([^/]+)\/([^/]+)$/);if(!c)throw new Error("Invalid repository format. Use: owner/repo");let[,i,a]=c,u=new d(n),p=await u.listGithubIssues(i,a,{state:o.state,labels:o.labels?.split(",")}),f=parseInt(o.limit),h=p.slice(0,f);if(h.length===0){console.log(`No issues found matching the criteria.
244
266
  `);return}if(!n.project)throw new Error("No project selected. Run: runhuman projects switch <project-id>");console.log(`
245
- \u{1F680} Creating ${y.length} test jobs...
246
- `);let w=[];for(let C of y){let v={projectId:n.project,url:t.url,description:`Test GitHub issue #${C.number}: ${C.title}
267
+ \u{1F680} Creating ${h.length} test jobs...
268
+ `);let y=[];for(let j of h){let A={projectId:n.project,url:o.url,description:`Test GitHub issue #${j.number}: ${j.title}
247
269
 
248
- ${C.body}`,metadata:{githubIssue:{owner:i,repo:l,number:C.number,url:C.url}},templateId:t.template};try{let T=await p.createJob(v);w.push({issue:C.number,jobId:T.jobId,status:"created"}),console.log(` \u2713 Issue #${C.number} \u2192 Job ${T.jobId}`)}catch(T){let K=m(T);w.push({issue:C.number,error:K.message,status:"failed"}),console.log(` \u2717 Issue #${C.number} \u2192 Failed: ${K.message}`)}}if(t.json){let C=c.result({jobs:w});r.output(C)}else{let C=w.filter(T=>T.status==="created").length,v=w.filter(T=>T.status==="failed").length;console.log(`
249
- \u2713 Created ${C} jobs`),v>0&&console.log(`\u2717 Failed ${v} jobs`),console.log(`
270
+ ${j.body}`,metadata:{githubIssue:{owner:i,repo:a,number:j.number,url:j.url}},templateId:o.template};try{let S=await u.createJob(A);y.push({issue:j.number,jobId:S.jobId,status:"created"}),console.log(` \u2713 Issue #${j.number} \u2192 Job ${S.jobId}`)}catch(S){let K=m(S);y.push({issue:j.number,error:K.message,status:"failed"}),console.log(` \u2717 Issue #${j.number} \u2192 Failed: ${K.message}`)}}if(o.json){let j=l.result({jobs:y});r.output(j)}else{let j=y.filter(S=>S.status==="created").length,A=y.filter(S=>S.status==="failed").length;console.log(`
271
+ \u2713 Created ${j} jobs`),A>0&&console.log(`\u2717 Failed ${A} jobs`),console.log(`
250
272
  \u{1F4A1} Check all jobs: runhuman list
251
- `)}}catch(e){let n=m(e);new c({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}k();U();import{existsSync as zo}from"fs";import{join as Ko}from"path";import{execSync as Go}from"child_process";import J from"inquirer";import x from"chalk";import st from"ora";S();async function it(){let s=new d,t=new c({color:!0});console.log(""),console.log(x.bold("Runhuman")+" - Human QA testing for your apps"),console.log("");let e=await rt(s);if(!e.isAuthenticated){let{shouldLogin:n}=await J.prompt([{type:"confirm",name:"shouldLogin",message:"You're not logged in. Would you like to sign in?",default:!0}]);if(!n){console.log(`
252
- Run `+x.cyan("runhuman login")+` when you're ready to sign in.
253
- `);return}await Ho(s,t),Object.assign(e,await rt(s))}console.log(x.green("Logged in as "+e.userEmail)+`
254
- `),e.isRunhumanProject?await Wo(s,e):await qo(s,e)}async function rt(s){let t={isAuthenticated:!1,isGitRepo:!1,isRunhumanProject:!1},e=s.loadCredentials();if(e?.accessToken)try{let o=await s.loadConfig({apiKey:e.accessToken}),u=await new g(o).getCurrentUser();t.isAuthenticated=!0,t.userEmail=u.email}catch{t.isAuthenticated=!1}try{Go("git rev-parse --is-inside-work-tree",{stdio:"ignore"}),t.isGitRepo=!0}catch{t.isGitRepo=!1}let n=Ko(process.cwd(),".runhumanrc");if(zo(n)){t.isRunhumanProject=!0;try{let o=await s.loadConfig();t.projectConfig={defaultUrl:o.defaultUrl,githubRepo:o.githubRepo}}catch{}}return t}async function Ho(s,t){let e=await s.loadConfig(),n=e.apiUrl||"https://runhuman.com";console.log("");let o=st("Opening browser for authentication...").start();try{let r=await X({apiUrl:n,autoOpenBrowser:e.autoOpenBrowser!==!1});o.stop(),s.saveCredentials({accessToken:r.token});let u=await s.loadConfig({apiKey:r.token}),l=await new g(u).getCurrentUser();s.saveUserInfo(l),t.success("Successfully logged in!"),console.log("")}catch(r){throw o.stop(),r}}async function Wo(s,t){console.log(x.dim("Runhuman project detected")),t.projectConfig?.defaultUrl&&console.log(x.dim("URL: "+t.projectConfig.defaultUrl)),console.log("");let{action:e}=await J.prompt([{type:"list",name:"action",message:"What would you like to do?",choices:[{name:"Quick test a URL",value:"quick-test"},{name:"Run a template",value:"run-template"},{name:"View recent jobs",value:"list-jobs"},new J.Separator,{name:"Exit",value:"exit"}]}]);switch(e){case"quick-test":await at(s,t);break;case"run-template":console.log(`
255
- Run `+x.cyan("runhuman templates")+" to see available templates."),console.log("Then run "+x.cyan("runhuman create --template <name>")+` to use one.
273
+ `)}}catch(e){let n=m(e);new l({json:o.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),s}U();k();import{existsSync as xo}from"fs";import{join as Do}from"path";import{execSync as No}from"child_process";import z from"inquirer";import P from"chalk";import Re from"ora";C();async function xe(){let s=new g,o=new l({color:!0});console.log(""),console.log(P.bold("Runhuman")+" - Human QA testing for your apps"),console.log("");let e=await Oe(s);if(!e.isAuthenticated){let{shouldLogin:n}=await z.prompt([{type:"confirm",name:"shouldLogin",message:"You're not logged in. Would you like to sign in?",default:!0}]);if(!n){console.log(`
274
+ Run `+P.cyan("runhuman login")+` when you're ready to sign in.
275
+ `);return}await Eo(s,o),Object.assign(e,await Oe(s))}console.log(P.green("Logged in as "+e.userEmail)+`
276
+ `),e.isRunhumanProject?await To(s,e):await Mo(s,e)}async function Oe(s){let o={isAuthenticated:!1,isGitRepo:!1,isRunhumanProject:!1},e=s.loadCredentials();if(e?.accessToken)try{let t=await s.loadConfig({apiKey:e.accessToken}),c=await new d(t).getCurrentUser();o.isAuthenticated=!0,o.userEmail=c.email}catch{o.isAuthenticated=!1}try{No("git rev-parse --is-inside-work-tree",{stdio:"ignore"}),o.isGitRepo=!0}catch{o.isGitRepo=!1}let n=Do(process.cwd(),".runhumanrc");if(xo(n)){o.isRunhumanProject=!0;try{let t=await s.loadConfig();o.projectConfig={defaultUrl:t.defaultUrl,githubRepo:t.githubRepo}}catch{}}return o}async function Eo(s,o){let e=await s.loadConfig(),n=e.apiUrl||"https://runhuman.com";console.log("");let t=Re("Opening browser for authentication...").start();try{let r=await Y({apiUrl:n,autoOpenBrowser:e.autoOpenBrowser!==!1});t.stop(),s.saveCredentials({accessToken:r.token});let c=await s.loadConfig({apiKey:r.token}),a=await new d(c).getCurrentUser();s.saveUserInfo(a),o.success("Successfully logged in!"),console.log("")}catch(r){throw t.stop(),r}}async function To(s,o){console.log(P.dim("Runhuman project detected")),o.projectConfig?.defaultUrl&&console.log(P.dim("URL: "+o.projectConfig.defaultUrl)),console.log("");let{action:e}=await z.prompt([{type:"list",name:"action",message:"What would you like to do?",choices:[{name:"Quick test a URL",value:"quick-test"},{name:"Run a template",value:"run-template"},{name:"View recent jobs",value:"list-jobs"},new z.Separator,{name:"Exit",value:"exit"}]}]);switch(e){case"quick-test":await De(s,o);break;case"run-template":console.log(`
277
+ Run `+P.cyan("runhuman templates")+" to see available templates."),console.log("Then run "+P.cyan("runhuman create --template <name>")+` to use one.
256
278
  `);break;case"list-jobs":console.log(`
257
- Run `+x.cyan("runhuman list")+` to see your recent jobs.
258
- `);break;case"exit":break}}async function qo(s,t){t.isGitRepo?console.log(x.dim("Git repository detected (not yet set up with Runhuman)")):console.log(x.dim("Not in a git repository")),console.log("");let{action:e}=await J.prompt([{type:"list",name:"action",message:"What would you like to do?",choices:[{name:"Quick test a URL",value:"quick-test"},{name:"Set up this repo with Runhuman",value:"setup-repo",disabled:!t.isGitRepo},{name:"Connect a GitHub repo",value:"connect-github"},new J.Separator,{name:"Exit",value:"exit"}]}]);switch(e){case"quick-test":await at(s,t);break;case"setup-repo":await Vo(s);break;case"connect-github":await Bo(s);break;case"exit":break}}async function at(s,t){let e=t.projectConfig?.defaultUrl||"",n=await J.prompt([{type:"input",name:"url",message:"URL to test:",default:e||void 0,validate:r=>{if(!r.trim())return"URL is required";try{return new URL(r),!0}catch{return"Please enter a valid URL"}}},{type:"input",name:"description",message:"What should we test? (describe in plain English):",validate:r=>r.trim()?!0:"Description is required"}]),o=st("Creating test job...").start();try{let r=s.loadCredentials(),u=await s.loadConfig({apiKey:r?.accessToken});if(!u.project){o.fail("No project selected"),console.log(`
259
- Run: runhuman projects switch <project-id>`);return}let l=await new g(u).createJob({projectId:u.project,url:n.url,description:n.description});o.succeed("Test job created!"),console.log(""),console.log(" Job ID: "+x.cyan(l.jobId)),console.log(" Status: "+l.status),console.log(""),console.log("A human tester will test your app shortly."),console.log("Run "+x.cyan(`runhuman wait ${l.jobId}`)+" to wait for results."),console.log("")}catch(r){throw o.fail("Failed to create test job"),r}}async function Vo(s){console.log(""),console.log("Setting up Runhuman in the current repository..."),console.log("");let t=await J.prompt([{type:"input",name:"defaultUrl",message:"Default URL to test (optional):"}]);await s.saveProjectConfig({defaultUrl:t.defaultUrl||void 0,defaultDuration:5,defaultScreenSize:"desktop"}),console.log(""),console.log(x.green("Created .runhumanrc")),console.log(""),console.log("Next steps:"),console.log(" 1. Install the GitHub App to enable @runhuman comments"),console.log(" "+x.cyan("runhuman github connect")),console.log(""),console.log(" 2. Create your first test:"),console.log(" "+x.cyan('runhuman create https://your-app.com -d "Test login flow"')),console.log("")}async function Bo(s){let n=`${(await s.loadConfig()).apiUrl||"https://runhuman.com"}/dashboard/settings/github`;console.log(""),console.log("To connect your GitHub repos, install the Runhuman GitHub App:"),console.log(""),console.log(" "+x.cyan(n)),console.log(""),console.log("After installation, you can comment "+x.bold("@runhuman")+" on any issue"),console.log("to trigger a QA test."),console.log("")}import{readFileSync as Qo}from"fs";import{join as Xo,dirname as Yo}from"path";import{fileURLToPath as en}from"url";var tn=en(import.meta.url),on=Yo(tn),nn=Xo(on,"../package.json"),rn=JSON.parse(Qo(nn,"utf-8")),sn=rn.version,A=new Zo;A.name("runhuman").description("CLI for Runhuman - AI-orchestrated human QA testing").version(sn);A.addCommand($e());A.addCommand(Me());A.addCommand(oe());A.addCommand(Le());A.addCommand(Je());A.addCommand(_e());A.addCommand(Ke());A.addCommand(We());A.addCommand(qe());A.addCommand(Ve());A.addCommand(Be());A.addCommand(Qe());A.addCommand(Xe());A.addCommand(Ye());A.addCommand(et());A.addCommand(tt());A.addCommand(nt());process.argv.slice(2).length?A.parse(process.argv):it().catch(s=>{console.error(s.message),process.exit(1)});
279
+ Run `+P.cyan("runhuman list")+` to see your recent jobs.
280
+ `);break;case"exit":break}}async function Mo(s,o){o.isGitRepo?console.log(P.dim("Git repository detected (not yet set up with Runhuman)")):console.log(P.dim("Not in a git repository")),console.log("");let{action:e}=await z.prompt([{type:"list",name:"action",message:"What would you like to do?",choices:[{name:"Quick test a URL",value:"quick-test"},{name:"Set up this repo with Runhuman",value:"setup-repo",disabled:!o.isGitRepo},{name:"Connect a GitHub repo",value:"connect-github"},new z.Separator,{name:"Exit",value:"exit"}]}]);switch(e){case"quick-test":await De(s,o);break;case"setup-repo":await vo(s);break;case"connect-github":await zo(s);break;case"exit":break}}async function De(s,o){let e=o.projectConfig?.defaultUrl||"",n=await z.prompt([{type:"input",name:"url",message:"URL to test:",default:e||void 0,validate:r=>{if(!r.trim())return"URL is required";try{return new URL(r),!0}catch{return"Please enter a valid URL"}}},{type:"input",name:"description",message:"What should we test? (describe in plain English):",validate:r=>r.trim()?!0:"Description is required"}]),t=Re("Creating test job...").start();try{let r=s.loadCredentials(),c=await s.loadConfig({apiKey:r?.accessToken});if(!c.project){t.fail("No project selected"),console.log(`
281
+ Run: runhuman projects switch <project-id>`);return}let a=await new d(c).createJob({projectId:c.project,url:n.url,description:n.description});t.succeed("Test job created!"),console.log(""),console.log(" Job ID: "+P.cyan(a.jobId)),console.log(" Status: "+a.status),console.log(""),console.log("A human tester will test your app shortly."),console.log("Run "+P.cyan(`runhuman wait ${a.jobId}`)+" to wait for results."),console.log("")}catch(r){throw t.fail("Failed to create test job"),r}}async function vo(s){console.log(""),console.log("Setting up Runhuman in the current repository..."),console.log("");let o=await z.prompt([{type:"input",name:"defaultUrl",message:"Default URL to test (optional):"}]);await s.saveProjectConfig({defaultUrl:o.defaultUrl||void 0,defaultDuration:5,defaultScreenSize:"desktop"}),console.log(""),console.log(P.green("Created .runhumanrc")),console.log(""),console.log("Next steps:"),console.log(" 1. Install the GitHub App to enable @runhuman comments"),console.log(" "+P.cyan("runhuman github connect")),console.log(""),console.log(" 2. Create your first test:"),console.log(" "+P.cyan('runhuman create https://your-app.com -d "Test login flow"')),console.log("")}async function zo(s){let n=`${(await s.loadConfig()).apiUrl||"https://runhuman.com"}/dashboard/settings/github`;console.log(""),console.log("To connect your GitHub repos, install the Runhuman GitHub App:"),console.log(""),console.log(" "+P.cyan(n)),console.log(""),console.log("After installation, you can comment "+P.bold("@runhuman")+" on any issue"),console.log("to trigger a QA test."),console.log("")}import{readFileSync as Lo}from"fs";import{join as $o,dirname as Ko}from"path";import{fileURLToPath as Fo}from"url";var qo=Fo(import.meta.url),Ho=Ko(qo),Go=$o(Ho,"../package.json"),_o=JSON.parse(Lo(Go,"utf-8")),Wo=_o.version,I=new Jo;I.name("runhuman").description("CLI for Runhuman - AI-orchestrated human QA testing").version(Wo);I.addCommand(se());I.addCommand(ie());I.addCommand(ee());I.addCommand(ae());I.addCommand(le());I.addCommand(ce());I.addCommand(me());I.addCommand(fe());I.addCommand(he());I.addCommand(we());I.addCommand(je());I.addCommand(be());I.addCommand(Ce());I.addCommand(Ue());I.addCommand(Pe());I.addCommand(ke());I.addCommand(Se());process.argv.slice(2).length?I.parse(process.argv):xe().catch(s=>{console.error(s.message),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runhuman",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "CLI for Runhuman - AI-orchestrated human QA testing",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -21,6 +21,7 @@
21
21
  "build:dev": "tsc",
22
22
  "dev": "tsc --watch",
23
23
  "test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest",
24
+ "test:locked": "../../scripts/with-test-lock.sh npm test",
24
25
  "test:watch": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest --watch",
25
26
  "test:coverage": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest --coverage",
26
27
  "test:unit": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest tests/unit",
@@ -54,6 +55,7 @@
54
55
  "node": ">=18.0.0"
55
56
  },
56
57
  "dependencies": {
58
+ "@runhuman/shared": "*",
57
59
  "axios": "^1.7.9",
58
60
  "boxen": "^8.0.1",
59
61
  "chalk": "^5.3.0",