runhuman 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2024, Volter AI
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/runhuman.svg)](https://www.npmjs.com/package/runhuman)
4
4
  [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
5
5
 
6
- > **🚧 Coming Soon:** The Runhuman CLI is currently under development.
6
+ > AI-orchestrated human QA testing from your terminal
7
7
 
8
8
  ## What is Runhuman?
9
9
 
@@ -15,66 +15,624 @@ Runhuman is an AI-orchestrated human QA testing service. Get real human testing
15
15
  - Visual/UX testing requiring real human judgment
16
16
  - On-demand QA without hiring/managing teams
17
17
 
18
- ## Available Now: MCP Server for AI Agents
19
-
20
- If you're using AI agents (Claude, GPT, etc.), use our MCP server package:
18
+ ## Installation
21
19
 
22
20
  ```bash
23
21
  # Install globally
24
- npm install -g @runhuman/mcp
22
+ npm install -g runhuman
25
23
 
26
24
  # Or use with npx
27
- npx @runhuman/mcp --api-key=qa_live_xxxxxxxxxxxxx
25
+ npx runhuman --help
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```bash
31
+ # 1. Login with your API key
32
+ runhuman login
33
+
34
+ # 2. Create your first test
35
+ runhuman create https://myapp.com -d "Test the checkout flow"
36
+
37
+ # 3. Check the status
38
+ runhuman status <jobId>
39
+
40
+ # 4. Get results
41
+ runhuman results <jobId>
42
+ ```
43
+
44
+ ## Commands
45
+
46
+ ### Jobs
47
+
48
+ #### `create [url]`
49
+
50
+ Create a new QA test job.
51
+
52
+ ```bash
53
+ # Basic usage
54
+ runhuman create https://myapp.com -d "Test checkout flow"
55
+
56
+ # With template
57
+ runhuman create https://myapp.com --template tmpl_abc123
58
+
59
+ # Synchronous (wait for result)
60
+ runhuman create https://myapp.com -d "Test" --sync
61
+
62
+ # With custom schema
63
+ runhuman create https://myapp.com -d "Test" --schema ./schema.json
64
+ ```
65
+
66
+ **Options:**
67
+ - `-d, --description <text>` - Test instructions for the human tester
68
+ - `--template <id>` - Use a pre-defined template
69
+ - `--duration <seconds>` - Target test duration
70
+ - `--screen-size <size>` - Screen size (desktop/mobile/tablet)
71
+ - `--schema <path>` - Path to JSON schema for structured output
72
+ - `--sync` - Wait for result before exiting
73
+ - `--json` - Output as JSON
74
+
75
+ #### `status <jobId>`
76
+
77
+ Check the status of a test job.
78
+
79
+ ```bash
80
+ runhuman status job_abc123
81
+ ```
82
+
83
+ #### `wait <jobId>`
84
+
85
+ Wait for a job to complete and display results.
86
+
87
+ ```bash
88
+ runhuman wait job_abc123
89
+ runhuman wait job_abc123 --timeout 300
90
+ ```
91
+
92
+ **Options:**
93
+ - `--timeout <seconds>` - Maximum wait time (default: 600)
94
+
95
+ #### `results <jobId>`
96
+
97
+ Display detailed test results.
98
+
99
+ ```bash
100
+ runhuman results job_abc123
101
+ runhuman results job_abc123 --json
102
+ ```
103
+
104
+ #### `list`
105
+
106
+ List all your test jobs.
107
+
108
+ ```bash
109
+ # List all jobs
110
+ runhuman list
111
+
112
+ # Filter by status
113
+ runhuman list --status completed
114
+
115
+ # Filter by project
116
+ runhuman list --project proj_abc123
117
+
118
+ # Limit results
119
+ runhuman list --limit 20
120
+ ```
121
+
122
+ **Options:**
123
+ - `--status <status>` - Filter by status (pending/claimed/in_progress/completed/failed)
124
+ - `--project <id>` - Filter by project
125
+ - `--limit <number>` - Maximum number of jobs to return
126
+ - `--json` - Output as JSON
127
+
128
+ ### Projects
129
+
130
+ #### `projects list`
131
+
132
+ List all your projects.
133
+
134
+ ```bash
135
+ runhuman projects list
136
+ runhuman projects ls # alias
137
+ ```
138
+
139
+ #### `projects create <name>`
140
+
141
+ Create a new project.
142
+
143
+ ```bash
144
+ runhuman projects create "My App"
145
+ runhuman projects create "My App" -d "Production QA testing"
146
+ ```
147
+
148
+ **Options:**
149
+ - `-d, --description <text>` - Project description
150
+ - `--default-url <url>` - Default URL to test
151
+ - `--github-repo <owner/repo>` - Link GitHub repository
152
+
153
+ #### `projects show <projectId>`
154
+
155
+ Show details of a specific project.
156
+
157
+ ```bash
158
+ runhuman projects show proj_abc123
159
+ runhuman projects info proj_abc123 # alias
160
+ ```
161
+
162
+ #### `projects update <projectId>`
163
+
164
+ Update a project.
165
+
166
+ ```bash
167
+ runhuman projects update proj_abc123 --name "New Name"
168
+ runhuman projects update proj_abc123 --description "New description"
169
+ ```
170
+
171
+ **Options:**
172
+ - `--name <name>` - New project name
173
+ - `-d, --description <text>` - New description
174
+ - `--default-url <url>` - New default URL
175
+ - `--github-repo <owner/repo>` - New GitHub repository
176
+
177
+ #### `projects delete <projectId>`
178
+
179
+ Delete a project permanently.
180
+
181
+ ```bash
182
+ runhuman projects delete proj_abc123 --force
183
+ ```
184
+
185
+ **Options:**
186
+ - `--force` - Skip confirmation prompt
187
+
188
+ ### API Keys
189
+
190
+ #### `keys list`
191
+
192
+ List all API keys for a project.
193
+
194
+ ```bash
195
+ runhuman keys list --project proj_abc123
196
+ runhuman keys ls --project proj_abc123 # alias
197
+ ```
198
+
199
+ **Options:**
200
+ - `--project <id>` - Project ID (required)
201
+ - `--show-keys` - Show full API keys (default: masked)
202
+
203
+ #### `keys create <name>`
204
+
205
+ Create a new API key.
206
+
207
+ ```bash
208
+ runhuman keys create "CI/CD Key" --project proj_abc123
209
+ runhuman keys new "CI/CD Key" --project proj_abc123 # alias
210
+ ```
211
+
212
+ **Options:**
213
+ - `--project <id>` - Project ID (required)
214
+ - `--copy` - Copy key to clipboard
215
+
216
+ #### `keys delete <keyId>`
217
+
218
+ Delete an API key permanently.
219
+
220
+ ```bash
221
+ runhuman keys delete key_abc123 --force
222
+ runhuman keys rm key_abc123 --force # alias
223
+ ```
224
+
225
+ **Options:**
226
+ - `--force` - Skip confirmation prompt
227
+
228
+ ### Templates
229
+
230
+ #### `templates list`
231
+
232
+ List all test templates for a project.
233
+
234
+ ```bash
235
+ runhuman templates list --project proj_abc123
236
+ runhuman templates ls --project proj_abc123 # alias
237
+ ```
238
+
239
+ **Options:**
240
+ - `--project <id>` - Project ID (required)
241
+
242
+ #### `templates create <name>`
243
+
244
+ Create a new test template.
245
+
246
+ ```bash
247
+ runhuman templates create "Checkout Flow" --project proj_abc123
248
+ runhuman templates create "Login Test" --project proj_abc123 \
249
+ -d "Test login functionality" \
250
+ --duration 180 \
251
+ --screen-size mobile \
252
+ --schema ./schema.json
253
+ ```
254
+
255
+ **Options:**
256
+ - `--project <id>` - Project ID (required)
257
+ - `-d, --description <text>` - Template description
258
+ - `--duration <seconds>` - Target test duration
259
+ - `--screen-size <size>` - Default screen size
260
+ - `--schema <path>` - Path to JSON schema file
261
+
262
+ #### `templates show <templateId>`
263
+
264
+ Show details of a specific template.
265
+
266
+ ```bash
267
+ runhuman templates show tmpl_abc123
268
+ runhuman templates info tmpl_abc123 # alias
269
+ ```
270
+
271
+ #### `templates update <templateId>`
272
+
273
+ Update a template.
274
+
275
+ ```bash
276
+ runhuman templates update tmpl_abc123 --name "New Name"
277
+ runhuman templates update tmpl_abc123 --description "New description"
278
+ runhuman templates update tmpl_abc123 --schema ./new-schema.json
279
+ ```
280
+
281
+ **Options:**
282
+ - `--name <name>` - New template name
283
+ - `-d, --description <text>` - New description
284
+ - `--duration <seconds>` - New target duration
285
+ - `--screen-size <size>` - New default screen size
286
+ - `--schema <path>` - Path to new JSON schema file
287
+
288
+ #### `templates delete <templateId>`
289
+
290
+ Delete a template permanently.
291
+
292
+ ```bash
293
+ runhuman templates delete tmpl_abc123 --force
294
+ runhuman templates rm tmpl_abc123 --force # alias
295
+ ```
296
+
297
+ **Options:**
298
+ - `--force` - Skip confirmation prompt
299
+
300
+ ### GitHub Integration
301
+
302
+ #### `github link <repo>`
303
+
304
+ Link a GitHub repository to your project.
305
+
306
+ ```bash
307
+ runhuman github link owner/repo --project proj_abc123
308
+ runhuman gh link volter-ai/runhuman --project proj_abc123 # alias
28
309
  ```
29
310
 
30
- ### Claude Desktop Setup
311
+ **Options:**
312
+ - `--project <id>` - Project ID (required)
31
313
 
32
- Add to your Claude Desktop config:
314
+ #### `github repos`
315
+
316
+ List linked GitHub repositories.
317
+
318
+ ```bash
319
+ runhuman github repos
320
+ runhuman github repos --project proj_abc123 # filter by project
321
+ ```
322
+
323
+ **Options:**
324
+ - `--project <id>` - Filter by project
325
+
326
+ #### `github issues <repo>`
327
+
328
+ List GitHub issues for a repository.
329
+
330
+ ```bash
331
+ runhuman github issues owner/repo
332
+ runhuman github issues owner/repo --state open
333
+ runhuman github issues owner/repo --labels "bug,needs-qa"
334
+ ```
335
+
336
+ **Options:**
337
+ - `--state <state>` - Filter by state (open/closed/all, default: open)
338
+ - `--labels <labels>` - Filter by comma-separated labels
339
+
340
+ #### `github test <issueNumber>`
341
+
342
+ Create a QA test job for a GitHub issue.
343
+
344
+ ```bash
345
+ runhuman github test 123 --repo owner/repo --url https://myapp.com
346
+ runhuman github test 123 --url https://myapp.com --sync # wait for result
347
+ ```
348
+
349
+ **Options:**
350
+ - `--repo <owner/repo>` - Repository (or use default from config)
351
+ - `--url <url>` - URL to test (required)
352
+ - `--template <id>` - Template ID to use
353
+ - `--sync` - Wait for result before exiting
354
+
355
+ #### `github bulk-test`
356
+
357
+ Create QA test jobs for multiple GitHub issues.
358
+
359
+ ```bash
360
+ # Test all open issues
361
+ runhuman github bulk-test --repo owner/repo --url https://myapp.com
362
+
363
+ # Test issues with specific labels
364
+ runhuman github bulk-test --repo owner/repo --url https://myapp.com \
365
+ --labels "bug,needs-qa" \
366
+ --limit 5
367
+ ```
368
+
369
+ **Options:**
370
+ - `--repo <owner/repo>` - Repository (or use default from config)
371
+ - `--url <url>` - URL to test (required)
372
+ - `--labels <labels>` - Filter issues by comma-separated labels
373
+ - `--state <state>` - Filter by state (open/closed/all, default: open)
374
+ - `--template <id>` - Template ID to use
375
+ - `--limit <number>` - Maximum number of jobs to create (default: 10)
376
+
377
+ ### Authentication
378
+
379
+ #### `login`
380
+
381
+ Login to Runhuman and save your API key.
382
+
383
+ ```bash
384
+ runhuman login
385
+ # Prompts for API key
386
+
387
+ # Or provide directly
388
+ runhuman login --api-key qa_live_xxxxxxxxxxxxx
389
+ ```
390
+
391
+ Get your API key at: [runhuman.com/dashboard](https://runhuman.com/dashboard)
392
+
393
+ #### `logout`
394
+
395
+ Logout and clear saved credentials.
396
+
397
+ ```bash
398
+ runhuman logout
399
+ ```
400
+
401
+ #### `whoami`
402
+
403
+ Display current user info and account balance.
404
+
405
+ ```bash
406
+ runhuman whoami
407
+ ```
408
+
409
+ ### Configuration
410
+
411
+ #### `config get <key>`
412
+
413
+ Get a configuration value.
414
+
415
+ ```bash
416
+ runhuman config get apiUrl
417
+ runhuman config get project
418
+ ```
419
+
420
+ #### `config set <key> <value>`
421
+
422
+ Set a configuration value.
423
+
424
+ ```bash
425
+ # Set globally
426
+ runhuman config set apiUrl https://api.runhuman.com --global
427
+
428
+ # Set for current project
429
+ runhuman config set project proj_abc123
430
+ runhuman config set defaultUrl https://myapp.com
431
+ ```
432
+
433
+ **Options:**
434
+ - `--global` - Save to global config instead of project config
435
+
436
+ #### `config list`
437
+
438
+ List all configuration values.
439
+
440
+ ```bash
441
+ runhuman config list
442
+ ```
443
+
444
+ Shows configuration from all sources:
445
+ - Global config (`~/.config/runhuman/config.json`)
446
+ - Project config (`.runhumanrc`)
447
+ - Environment variables
448
+ - Effective (merged) config
449
+
450
+ #### `config reset`
451
+
452
+ Reset configuration to defaults.
453
+
454
+ ```bash
455
+ # Reset project config
456
+ runhuman config reset project
457
+
458
+ # Reset global config
459
+ runhuman config reset global
460
+
461
+ # Reset all
462
+ runhuman config reset all
463
+ ```
464
+
465
+ #### `init`
466
+
467
+ Initialize a new Runhuman project with interactive prompts.
468
+
469
+ ```bash
470
+ runhuman init
471
+ ```
472
+
473
+ Creates a `.runhumanrc` file in the current directory with your project configuration.
474
+
475
+ ## Configuration
476
+
477
+ ### Configuration Hierarchy
478
+
479
+ Configuration is loaded from multiple sources with the following priority (highest to lowest):
480
+
481
+ 1. **CLI flags** - `--api-key`, `--api-url`, etc.
482
+ 2. **Environment variables** - `RUNHUMAN_API_KEY`, `RUNHUMAN_API_URL`, etc.
483
+ 3. **Project config** - `.runhumanrc` in current directory
484
+ 4. **Global config** - `~/.config/runhuman/config.json`
485
+ 5. **Defaults**
486
+
487
+ ### Environment Variables
488
+
489
+ ```bash
490
+ RUNHUMAN_API_KEY # API key for authentication
491
+ RUNHUMAN_API_URL # API base URL (default: https://runhuman.com)
492
+ RUNHUMAN_PROJECT # Default project ID
493
+ RUNHUMAN_DEFAULT_URL # Default URL to test
494
+ RUNHUMAN_DEFAULT_DURATION # Default test duration in seconds
495
+ RUNHUMAN_DEFAULT_SCREEN_SIZE # Default screen size (desktop/mobile/tablet)
496
+ RUNHUMAN_OUTPUT_FORMAT # Output format (pretty/json/compact)
497
+ RUNHUMAN_NO_COLOR=1 # Disable colored output
498
+ ```
499
+
500
+ ### Project Config (`.runhumanrc`)
501
+
502
+ Create a `.runhumanrc` file in your project root:
33
503
 
34
504
  ```json
35
505
  {
36
- "mcpServers": {
37
- "runhuman": {
38
- "command": "npx",
39
- "args": ["-y", "@runhuman/mcp", "--api-key=qa_live_xxxxxxxxxxxxx"]
40
- }
41
- }
506
+ "project": "proj_abc123",
507
+ "defaultUrl": "https://myapp.com",
508
+ "defaultDuration": 300,
509
+ "defaultScreenSize": "desktop",
510
+ "githubRepo": "owner/repo",
511
+ "outputFormat": "pretty",
512
+ "color": true
42
513
  }
43
514
  ```
44
515
 
45
- **Config locations:**
46
- - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
47
- - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
516
+ ### Global Config
48
517
 
49
- Get your API key at: [runhuman.com/dashboard](https://runhuman.com/dashboard)
518
+ Stored at `~/.config/runhuman/config.json`:
519
+
520
+ ```json
521
+ {
522
+ "apiUrl": "https://runhuman.com",
523
+ "apiKey": "qa_live_xxxxxxxxxxxxx",
524
+ "color": true
525
+ }
526
+ ```
50
527
 
51
- ## Coming Soon: Full CLI
528
+ ## JSON Output
52
529
 
53
- The full `runhuman` CLI will include commands like:
530
+ All commands support `--json` flag for machine-readable output:
54
531
 
55
532
  ```bash
56
- # Create a test job
57
- runhuman create --url https://myapp.com --description "Test checkout flow"
533
+ runhuman create https://myapp.com -d "Test" --json
534
+ runhuman status job_abc123 --json
535
+ runhuman list --json
536
+ ```
58
537
 
59
- # List jobs
60
- runhuman list
538
+ JSON output format:
61
539
 
62
- # Get job status
63
- runhuman status <jobId>
540
+ ```json
541
+ {
542
+ "success": true,
543
+ "data": { ... },
544
+ "error": null
545
+ }
546
+ ```
64
547
 
65
- # View results
66
- runhuman results <jobId>
548
+ ## Exit Codes
67
549
 
68
- # And more...
550
+ - `0` - Success
551
+ - `1` - General error
552
+ - `2` - Authentication error
553
+ - `3` - Not found error
554
+ - `4` - Validation error
555
+ - `5` - Timeout error
556
+
557
+ ## Examples
558
+
559
+ ### Basic Workflow
560
+
561
+ ```bash
562
+ # 1. Login
563
+ runhuman login
564
+
565
+ # 2. Initialize project
566
+ runhuman init
567
+
568
+ # 3. Create a test
569
+ runhuman create https://myapp.com -d "Test the login flow"
570
+
571
+ # 4. Wait for results
572
+ runhuman wait job_abc123
573
+
574
+ # 5. View detailed results
575
+ runhuman results job_abc123
576
+ ```
577
+
578
+ ### CI/CD Integration
579
+
580
+ ```bash
581
+ # Create test and wait for result (fail on error)
582
+ runhuman create https://staging.myapp.com \
583
+ -d "Smoke test on staging" \
584
+ --sync \
585
+ --json > result.json
586
+
587
+ # Exit code will be non-zero if test fails
588
+ ```
589
+
590
+ ### GitHub Integration Workflow
591
+
592
+ ```bash
593
+ # 1. Link your repo
594
+ runhuman github link owner/repo --project proj_abc123
595
+
596
+ # 2. List issues needing QA
597
+ runhuman github issues owner/repo --labels "needs-qa"
598
+
599
+ # 3. Test a specific issue
600
+ runhuman github test 42 --url https://myapp.com --sync
601
+
602
+ # 4. Bulk test multiple issues
603
+ runhuman github bulk-test --url https://myapp.com --labels "needs-qa" --limit 5
604
+ ```
605
+
606
+ ### Using Templates
607
+
608
+ ```bash
609
+ # 1. Create a template
610
+ runhuman templates create "Login Flow Test" --project proj_abc123 \
611
+ -d "Test login with valid and invalid credentials" \
612
+ --duration 180 \
613
+ --schema ./schemas/login-test.json
614
+
615
+ # 2. Use the template
616
+ runhuman create https://myapp.com --template tmpl_abc123
617
+
618
+ # 3. List all templates
619
+ runhuman templates list --project proj_abc123
620
+ ```
621
+
622
+ ## MCP Server for AI Agents
623
+
624
+ If you're using AI agents (Claude, GPT, etc.), check out our MCP server package:
625
+
626
+ ```bash
627
+ npm install -g @runhuman/mcp
69
628
  ```
70
629
 
71
- **Stay tuned!** We'll announce when the CLI is ready.
630
+ See [@runhuman/mcp](https://www.npmjs.com/package/@runhuman/mcp) for details.
72
631
 
73
632
  ## Learn More
74
633
 
75
634
  - **Website:** [runhuman.com](https://runhuman.com)
76
635
  - **Documentation:** [runhuman.com/docs](https://runhuman.com/docs)
77
- - **MCP Server:** [@runhuman/mcp](https://www.npmjs.com/package/@runhuman/mcp)
78
636
  - **GitHub:** [github.com/volter-ai/runhuman](https://github.com/volter-ai/runhuman)
79
637
 
80
638
  ## Support
@@ -84,11 +642,11 @@ runhuman results <jobId>
84
642
 
85
643
  ## How It Works
86
644
 
87
- 1. **AI creates job** - Define test instructions and output schema
645
+ 1. **You create a job** - Define test instructions and optional output schema
88
646
  2. **Job posted to testers** - Request goes to human tester pool
89
647
  3. **Human performs test** - Real person tests with video/screenshot recording
90
648
  4. **AI extracts data** - GPT-4o processes feedback into structured JSON
91
- 5. **AI gets results** - Clean, typed data ready for automation
649
+ 5. **You get results** - Clean, typed data ready for automation
92
650
 
93
651
  **Pricing:** Pay-per-second ($0.0018/sec of tester time). No monthly fees, no minimums.
94
652
 
package/dist/index.js ADDED
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+ var Oe=Object.defineProperty;var De=(r=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(r,{get:(t,e)=>(typeof require<"u"?require:t)[e]}):r)(function(r){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+r+'" is not supported')});var H=(r,t)=>()=>(r&&(t=r(r=0)),t);var Ee=(r,t)=>{for(var e in t)Oe(r,e,{get:t[e],enumerable:!0})};function Ne(r){return r instanceof S}function p(r){return Ne(r)?{message:r.message,exitCode:r.exitCode,details:r.details}:r instanceof Error?{message:r.message,exitCode:1}:{message:String(r),exitCode:1}}var S,q,_,N,z,w=H(()=>{"use strict";S=class extends Error{constructor(e,n=1,o){super(e);this.exitCode=n;this.details=o;this.name="CliError"}},q=class extends S{constructor(t="Authentication failed",e){super(t,2,e),this.name="AuthenticationError"}},_=class extends S{constructor(t="Resource not found",e){super(t,3,e),this.name="NotFoundError"}},N=class extends S{constructor(t="Validation failed",e){super(t,4,e),this.name="ValidationError"}},z=class extends S{constructor(t="Operation timed out",e){super(t,5,e),this.name="TimeoutError"}}});import ve from"axios";var d,I=H(()=>{"use strict";w();d=class{client;config;constructor(t){this.config=t,this.client=ve.create({baseURL:t.apiUrl||"https://runhuman.com",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 q(o,n);case 404:return new _(o,n);case 400:case 422:return new N(o,n);default:return new S(o,1,n)}}return t.code==="ECONNABORTED"?new S("Request timeout",5):t.code==="ENOTFOUND"||t.code==="ECONNREFUSED"?new S("Cannot connect to Runhuman API",1):new S(t.message,1)}async createJob(t){return(await this.client.post("/jobs",t)).data}async getJob(t){return(await this.client.get(`/job/${t}`)).data}async listJobs(t){return(await this.client.get("/jobs",{params:t})).data}async cancelJob(t){await this.client.post(`/job/${t}/cancel`)}async deleteJob(t){await this.client.delete(`/job/${t}`)}async listProjects(t){return(await this.client.get("/projects",{params:t})).data}async getProject(t){return(await this.client.get(`/projects/${t}`)).data}async createProject(t){return(await this.client.post("/projects",t)).data}async updateProject(t,e){return(await this.client.put(`/projects/${t}`,e)).data}async deleteProject(t){await this.client.delete(`/projects/${t}`)}async listApiKeys(t){return(await this.client.get(`/projects/${t}/keys`)).data}async createApiKey(t,e){return(await this.client.post(`/projects/${t}/keys`,{name:e})).data}async deleteApiKey(t){await this.client.delete(`/keys/${t}`)}async listTemplates(t){return(await this.client.get(`/projects/${t}/templates`)).data}async getTemplate(t){return(await this.client.get(`/templates/${t}`)).data}async createTemplate(t,e){return(await this.client.post(`/projects/${t}/templates`,e)).data}async updateTemplate(t,e){return(await this.client.put(`/templates/${t}`,e)).data}async deleteTemplate(t){await this.client.delete(`/templates/${t}`)}async getCurrentUser(){return(await this.client.get("/auth/me")).data}async getTokenBalance(){return(await this.client.get("/auth/balance")).data}async linkGithubRepo(t,e,n){return(await this.client.post(`/projects/${t}/github/link`,{owner:e,repo:n})).data}async listGithubRepos(t){let e=t?{projectId:t}:void 0;return(await this.client.get("/github/repos",{params:e})).data}async listGithubIssues(t,e,n){return(await this.client.get(`/github/${t}/${e}/issues`,{params:n})).data}async getGithubIssue(t,e,n){return(await this.client.get(`/github/${t}/${e}/issues/${n}`)).data}}});import{cosmiconfig as Te}from"cosmiconfig";import{homedir as Me}from"os";import{join as O}from"path";import{readFileSync as $,writeFileSync as x,existsSync as A,mkdirSync as Y,chmodSync as Je}from"fs";function Ke(r){return Le.includes(r)}var $e,R,v,L,Le,m,b=H(()=>{"use strict";$e="runhuman",R=O(Me(),".config","runhuman"),v=O(R,"config.json"),L=O(R,"credentials.json"),Le=["pretty","json","compact"];m=class{constructor(t=process.cwd()){this.cwd=t}projectConfig=null;globalConfig=null;envConfig=null;async loadConfig(t={}){return this.envConfig=this.loadEnvConfig(),this.projectConfig=await this.loadProjectConfig(),this.globalConfig=this.loadGlobalConfig(),{...this.getDefaults(),...this.globalConfig,...this.projectConfig,...this.envConfig,...t}}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&&Ke(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 Te($e).search(this.cwd))?.config||null}catch{return null}}loadGlobalConfig(){try{if(!A(v))return null;let t=$(v,"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){A(R)||Y(R,{recursive:!0});let n={};if(A(v)){let o=$(v,"utf-8");n=JSON.parse(o)}n[t]=e,x(v,JSON.stringify(n,null,2))}async setProjectConfig(t,e){let n=O(this.cwd,".runhumanrc"),o={};if(A(n)){let s=$(n,"utf-8");o=JSON.parse(s)}o[t]=e,x(n,JSON.stringify(o,null,2))}async saveProjectConfig(t){let e=O(this.cwd,".runhumanrc"),n={};if(A(e)){let s=$(e,"utf-8");n=JSON.parse(s)}let o={...n,...t};x(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")&&A(v)&&x(v,"{}"),t==="project"||t==="all"){let e=O(this.cwd,".runhumanrc");A(e)&&x(e,"{}")}}saveCredentials(t){A(R)||Y(R,{recursive:!0}),x(L,JSON.stringify(t,null,2));try{process.platform!=="win32"&&Je(L,384)}catch{}}loadCredentials(){try{if(!A(L))return null;let t=$(L,"utf-8");return JSON.parse(t)}catch{return null}}clearCredentials(){A(L)&&x(L,"{}")}saveUserInfo(t){let e=O(R,"user.json");A(R)||Y(R,{recursive:!0}),x(e,JSON.stringify(t,null,2))}loadUserInfo(){try{let t=O(R,"user.json");if(!A(t))return null;let e=$(t,"utf-8");return JSON.parse(e)}catch{return null}}clearUserInfo(){let t=O(R,"user.json");A(t)&&x(t,"{}")}}});import T from"chalk";import Fe from"cli-table3";var a,j=H(()=>{"use strict";a=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 Fe({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),s=e%60;return`${o}h ${s}m ${n}s`}formatDate(t){let e=new Date(t),o=new Date().getTime()-e.getTime(),s=Math.floor(o/1e3),u=Math.floor(s/60),i=Math.floor(u/60),c=Math.floor(i/24);return s<60?`${s}s ago`:u<60?`${u}m ago`:i<24?`${i}h ago`:c<30?`${c}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:T.red,green:T.green,blue:T.blue,yellow:T.yellow,cyan:T.cyan,gray:T.gray,white:T.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 X={};Ee(X,{waitCommand:()=>Z,waitForJob:()=>oe});import{Command as He}from"commander";import Ge from"ora";async function oe(r,t,e,n=600){let o=Date.now(),s=n*1e3,u=1e4,i=null;for(e.options.json||(i=Ge("\u23F3 Waiting for job completion...").start());;){let c=Date.now()-o;if(c>=s)throw i&&i.fail("Timeout waiting for job completion"),new z(`Job did not complete within ${n} seconds`);let l=await t.getJob(r);if(i){let g=e.formatDuration(Math.floor(c/1e3));i.text=`\u23F3 Waiting for job completion... (${g} elapsed, status: ${l.status})`}if(l.status==="completed"){if(i&&i.succeed("\u2705 Test Completed!"),!e.options.json){if(console.log(`
8
+ Duration: `+(l.testDurationSeconds?e.formatDuration(l.testDurationSeconds):"N/A")),console.log(" Cost: $"+(l.costUsd||0).toFixed(3)),l.testerName&&console.log(" Tester: "+l.testerName),l.result){console.log(`
9
+ \u{1F4CB} Results Summary:`);for(let[g,f]of Object.entries(l.result))console.log(` ${g}: ${f}`)}console.log(`
10
+ \u{1F4BE} Full results:`),console.log(` runhuman results ${r}
11
+ `)}return}if(l.status==="error"||l.status==="incomplete"||l.status==="abandoned")throw i&&i.fail(`Test ${l.status}`),new Error(`Job ${l.status}: ${l.testerResponse||"No details available"}`);if(l.status==="rejected")throw i&&i.fail("Test was rejected"),new Error("Job was rejected");await new Promise(g=>setTimeout(g,u))}}function Z(){let r=new He("wait");return r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color}),u=new d(o);if(await oe(t,u,s,e.timeout||600),e.json){let i=await u.getJob(t),c=a.result(i);s.output(c)}}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r}var W=H(()=>{"use strict";I();b();j();w()});import{Command as vt}from"commander";I();b();j();w();import{Command as qe}from"commander";import _e from"ora";function ne(){let r=new qe("create");return r.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 m().loadConfig({apiKey:e.apiKey,project:e.project}),s=new a({json:e.json,quiet:e.quiet,color:o.color}),u=new d(o);if(!t&&!e.template&&!o.defaultUrl)throw new N("URL is required (provide as argument, via --template, or set defaultUrl in config)");if(!e.description&&!e.template)throw new N("Description is required (use -d flag or --template)");let i={url:t||o.defaultUrl||"",description:e.description||"",duration:e.duration||o.defaultDuration,screenSize:e.screenSize||o.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 c=e.json?null:_e("Creating QA test job...").start(),l=await u.createJob(i);if(c&&c.succeed("Job created successfully!"),e.json){let g=a.result({jobId:l.jobId,status:l.status,message:l.message,dashboardUrl:`${o.apiUrl}/jobs/${l.jobId}`,estimatedCompletionTime:l.estimatedCompletionTime});s.output(g)}else e.quiet?console.log(l.jobId):(console.log(`
12
+ `+"=".repeat(60)),console.log(" Job ID: "+l.jobId),console.log(" Status: "+l.status),console.log(` Dashboard: ${o.apiUrl}/jobs/${l.jobId}`),console.log("=".repeat(60)+`
13
+ `),console.log("\u{1F4A1} Track progress:"),console.log(` runhuman status ${l.jobId}`),console.log(` runhuman wait ${l.jobId}
14
+ `));if(e.sync){let{waitForJob:g}=await Promise.resolve().then(()=>(W(),X));await g(l.jobId,u,s,e.wait||300)}}catch(n){let o=p(n);new a({json:e.json,quiet:!1}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r}I();b();j();w();import{Command as ze}from"commander";function re(){let r=new ze("status");return r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color}),i=await new d(o).getJob(t);if(e.json){let c=a.result(i);s.output(c)}else console.log(`
15
+ \u{1F4CA} Job Status: `+t+`
16
+ `),console.log(" Status: "+s.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: "+s.formatDuration(i.testDurationSeconds)),i.costUsd&&console.log(" Cost: $"+i.costUsd.toFixed(3)),console.log(`
17
+ Dashboard: ${o.apiUrl}/jobs/${t}
18
+ `),i.status==="pending"||i.status==="waiting"||i.status==="working"?(console.log("\u{1F4A1} Wait for completion:"),console.log(` runhuman wait ${t}
19
+ `)):i.status==="completed"&&(console.log("\u{1F4A1} View results:"),console.log(` runhuman results ${t}
20
+ `))}catch(n){let o=new a({json:e.json}),s=p(n);o.outputError(s.message,s.details),process.exit(s.exitCode)}}),r}W();I();b();j();w();import{Command as We}from"commander";function se(){let r=new We("results");return r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color}),i=await new d(o).getJob(t);if(e.json){let c=a.result(i);s.output(c)}else{if(console.log(`
21
+ \u{1F4CA} Test Results: `+t+`
22
+ `),console.log("=".repeat(80)),console.log("Job Information"),console.log("=".repeat(80)+`
23
+ `),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: "+s.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(`
24
+ `+"=".repeat(80)),console.log("Structured Results (Extracted)"),console.log("=".repeat(80)+`
25
+ `);for(let[c,l]of Object.entries(i.result)){let g=typeof l=="object"?JSON.stringify(l,null,2):String(l);console.log(` ${c}:`.padEnd(30)+g)}}i.testerResponse&&!e.schemaOnly&&(console.log(`
26
+ `+"=".repeat(80)),console.log("Tester Feedback"),console.log("=".repeat(80)+`
27
+ `),console.log(" "+i.testerResponse.split(`
28
+ `).join(`
29
+ `))),console.log(`
30
+ `+"=".repeat(80)+`
31
+ `)}}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r}I();b();j();w();import{Command as Be}from"commander";function ie(){let r=new Be("list");return r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,format:e.format,color:o.color}),u=new d(o),i={limit:e.limit||20,offset:e.offset||0};t&&t!=="all"&&(i.status=t),e.project&&(i.projectId=e.project);let{jobs:c,total:l}=await u.listJobs(i);if(e.json){let g=a.result({jobs:c,total:l});s.output(g)}else console.log(`
32
+ \u{1F4CB} Recent Jobs (${c.length} of ${l})
33
+ `),console.log(s.formatJobList(c)),console.log(`
34
+ \u{1F4A1} View details: runhuman status <jobId>`),console.log(` View results: runhuman results <jobId>
35
+ `)}catch(n){let o=new a({json:e.json}),s=p(n);o.outputError(s.message,s.details),process.exit(s.exitCode)}}),r}I();b();j();w();import{Command as Ve}from"commander";function ae(){let r=new Ve("delete");return r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color});if(!e.json&&!e.confirm)throw console.log(`
36
+ \u26A0\uFE0F Warning: This will permanently delete job ${t}`),console.log(` This action cannot be undone.
37
+ `),new Error("Please use --confirm flag to delete the job");if(await new d(o).deleteJob(t),e.json){let i=a.result({success:!0,message:"Job deleted successfully",jobId:t});s.output(i)}else console.log(`
38
+ \u2705 Job deleted successfully
39
+ `),console.log(` Job ID: ${t}`),console.log(` Status: Permanently deleted
40
+ `)}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r}I();b();j();w();import{Command as Qe}from"commander";import Ye from"chokidar";import{existsSync as B,readFileSync as ee,writeFileSync as Ze,unlinkSync as Xe}from"fs";import{join as ce}from"path";import{homedir as le}from"os";var E=ce(le(),".config","runhuman","watch.pid");function ue(){let r=new Qe("watch");return r.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 tt();return}if(e.status){ot();return}pe()&&(console.log(`
41
+ \u26A0\uFE0F Watch mode is already running`),console.log(` Use --stop to stop it first
42
+ `),process.exit(1));let o=await new m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=o.watch||{},u=t.length>0?t:s.patterns||["src/**/*"],i=e.ignore||s.ignore||["**/node_modules/**","**/dist/**","**/build/**","**/.git/**"],c=e.debounce||s.debounce||2e3,l=e.url||s.url||o.defaultUrl,g=e.description||s.description||"Auto-test from watch mode",f=e.template||s.template;l||(console.log(`
43
+ \u274C Error: URL required
44
+ `),console.log(`Provide via --url flag or set in .runhumanrc:
45
+ `),console.log(' runhuman watch --url "https://myapp.com"'),console.log(` OR add to .runhumanrc: { "watch": { "url": "..." } }
46
+ `),process.exit(1));let h=new a({color:o.color});console.log(`
47
+ \u{1F440} Starting watch mode
48
+ `),console.log(` Watching: ${u.join(", ")}`),console.log(` Ignoring: ${i.join(", ")}`),console.log(` Debounce: ${c}ms`),console.log(` URL: ${l}`),f&&console.log(` Template: ${f}`),console.log(`
49
+ Press Ctrl+C to stop
50
+ `),et(process.pid);let U=new d(o),y=null,D=new Set,P=Ye.watch(u,{ignored:i,persistent:!0,ignoreInitial:!0});P.on("change",J=>{D.add(J),console.log(` \u{1F4DD} Changed: ${J}`),y&&clearTimeout(y),y=setTimeout(async()=>{let te=Array.from(D);D.clear(),console.log(`
51
+ \u{1F680} Creating test job for ${te.length} file(s)...
52
+ `);try{let Q={url:l,description:`${g}
53
+
54
+ Changed files:
55
+ ${te.map(xe=>`- ${xe}`).join(`
56
+ `)}`,templateId:f},F=await U.createJob(Q);console.log(` \u2705 Job created: ${F.jobId}`),console.log(` \u{1F4CA} Dashboard: https://runhuman.com/jobs/${F.jobId}
57
+ `),console.log(` Watching for more changes...
58
+ `)}catch(Q){let F=p(Q);h.outputError(F.message,F.details),console.log(`
59
+ Watching for more changes...
60
+ `)}},c)}),P.on("error",J=>{console.error(`
61
+ \u274C Watch error: ${J instanceof Error?J.message:String(J)}
62
+ `)});let K=()=>{console.log(`
63
+
64
+ \u{1F44B} Stopping watch mode...
65
+ `),P.close(),G(),process.exit(0)};process.on("SIGINT",K),process.on("SIGTERM",K)}catch(n){let o=p(n);new a({}).outputError(o.message,o.details),G(),process.exit(o.exitCode)}}),r}function et(r){let t=ce(le(),".config","runhuman");B(t)||De("fs").mkdirSync(t,{recursive:!0}),Ze(E,r.toString())}function G(){B(E)&&Xe(E)}function pe(){if(!B(E))return!1;try{let r=parseInt(ee(E,"utf-8"));return process.kill(r,0),!0}catch{return G(),!1}}async function tt(){if(!B(E)){console.log(`
66
+ \u2139\uFE0F Watch mode is not running
67
+ `);return}try{let r=parseInt(ee(E,"utf-8"));process.kill(r,"SIGTERM"),G(),console.log(`
68
+ \u2705 Watch mode stopped
69
+ `)}catch{G(),console.log(`
70
+ \u2139\uFE0F Watch process not found (already stopped)
71
+ `)}}function ot(){if(pe()){let r=parseInt(ee(E,"utf-8"));console.log(`
72
+ \u2705 Watch mode is running`),console.log(` \u{1F4DD} PID: ${r}`),console.log(`
73
+ Stop with: runhuman watch --stop
74
+ `)}else console.log(`
75
+ \u2139\uFE0F Watch mode is not running
76
+ `)}I();b();w();j();import{Command as at}from"commander";import nt from"http";import{URL as rt}from"url";import st from"open";async function V(r){let{apiUrl:t,autoOpenBrowser:e=!0}=r;return new Promise((n,o)=>{let s=nt.createServer((i,c)=>{if(!i.url){c.writeHead(400),c.end("Bad request");return}let l=new rt(i.url,"http://localhost");if(l.pathname==="/callback"){let g=l.searchParams.get("token"),f=l.searchParams.get("email"),h=l.searchParams.get("error");if(h){c.writeHead(200,{"Content-Type":"text/html"}),c.end(me(h)),s.close(),o(new Error(h));return}if(g&&f){c.writeHead(200,{"Content-Type":"text/html"}),c.end(it(f)),s.close(),n({token:g,email:f});return}c.writeHead(400,{"Content-Type":"text/html"}),c.end(me("Missing token or email in callback")),s.close(),o(new Error("Missing token or email in callback"));return}c.writeHead(404),c.end("Not found")});s.listen(0,"127.0.0.1",async()=>{let i=s.address();if(!i||typeof i=="string"){o(new Error("Failed to start local server"));return}let l=`http://127.0.0.1:${i.port}/callback`,g=`${t}/cli/auth?callback=${encodeURIComponent(l)}`;if(console.log(""),e){console.log("Opening browser for authentication...");try{await st(g)}catch{console.log("Could not open browser automatically."),console.log(`
77
+ Please visit: ${g}`)}}else console.log(`Please visit: ${g}`);console.log(""),console.log("Waiting for authentication...")});let u=setTimeout(()=>{s.close(),o(new Error("Authentication timed out. Please try again."))},300*1e3);s.on("close",()=>clearTimeout(u)),s.on("error",i=>{clearTimeout(u),o(new Error(`Failed to start local server: ${i.message}`))})})}function it(r){return`<!DOCTYPE html>
78
+ <html>
79
+ <head>
80
+ <title>Runhuman CLI - Authenticated</title>
81
+ <style>
82
+ body {
83
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
84
+ display: flex;
85
+ justify-content: center;
86
+ align-items: center;
87
+ min-height: 100vh;
88
+ margin: 0;
89
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
90
+ color: white;
91
+ }
92
+ .container {
93
+ text-align: center;
94
+ padding: 40px;
95
+ background: rgba(255,255,255,0.1);
96
+ border-radius: 16px;
97
+ backdrop-filter: blur(10px);
98
+ }
99
+ h1 { margin-bottom: 16px; }
100
+ p { opacity: 0.9; }
101
+ .email { font-weight: bold; }
102
+ .close-note { margin-top: 24px; font-size: 14px; opacity: 0.7; }
103
+ </style>
104
+ </head>
105
+ <body>
106
+ <div class="container">
107
+ <h1>Successfully authenticated!</h1>
108
+ <p>Logged in as <span class="email">${r}</span></p>
109
+ <p class="close-note">You can close this tab and return to the terminal.</p>
110
+ </div>
111
+ </body>
112
+ </html>`}function me(r){return`<!DOCTYPE html>
113
+ <html>
114
+ <head>
115
+ <title>Runhuman CLI - Authentication Failed</title>
116
+ <style>
117
+ body {
118
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
119
+ display: flex;
120
+ justify-content: center;
121
+ align-items: center;
122
+ min-height: 100vh;
123
+ margin: 0;
124
+ background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%);
125
+ color: white;
126
+ }
127
+ .container {
128
+ text-align: center;
129
+ padding: 40px;
130
+ background: rgba(255,255,255,0.1);
131
+ border-radius: 16px;
132
+ backdrop-filter: blur(10px);
133
+ }
134
+ h1 { margin-bottom: 16px; }
135
+ .error { background: rgba(0,0,0,0.2); padding: 12px; border-radius: 8px; margin-top: 16px; }
136
+ </style>
137
+ </head>
138
+ <body>
139
+ <div class="container">
140
+ <h1>Authentication failed</h1>
141
+ <p>Please try again from the terminal.</p>
142
+ <div class="error">${r}</div>
143
+ </div>
144
+ </body>
145
+ </html>`}function ge(){let r=new at("login");return r.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 m,n=await e.loadConfig(),o=new a({json:t.json,color:n.color});if(t.token)await ct(e,o,t.token,t.json);else{let s=t.browser!==!1&&n.autoOpenBrowser!==!1;await lt(e,o,n.apiUrl,s,t.json)}}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r}async function ct(r,t,e,n){r.saveCredentials({accessToken:e});let o=await r.loadConfig({apiKey:e}),u=await new d(o).getCurrentUser();r.saveUserInfo(u),n?t.output(a.result({success:!0,user:u})):(t.success("Successfully logged in!"),console.log(` User: ${u.email}`),console.log(` Account ID: ${u.accountId}
146
+ `))}async function lt(r,t,e,n,o){o||console.log("Logging in to Runhuman...");let s=await V({apiUrl:e||"https://runhuman.com",autoOpenBrowser:n});r.saveCredentials({accessToken:s.token});let u=await r.loadConfig({apiKey:s.token}),c=await new d(u).getCurrentUser();r.saveUserInfo(c),o?t.output(a.result({success:!0,user:c})):(console.log(""),t.success("Successfully logged in!"),console.log(` User: ${c.email}`),console.log(` Account ID: ${c.accountId}
147
+ `))}b();w();j();import{Command as ut}from"commander";function de(){let r=new ut("logout");return r.description("Log out and clear stored credentials").option("--force","Skip confirmation prompt").option("--json","Output as JSON").action(async t=>{try{let e=new m,n=new a({json:t.json});if(e.clearCredentials(),e.clearUserInfo(),t.json){let o=a.result({success:!0,message:"Logged out successfully"});n.output(o)}else n.success("Logged out successfully!"),console.log(`Credentials have been cleared.
148
+ `)}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r}I();b();w();j();import{Command as pt}from"commander";function fe(){let r=new pt("whoami");return r.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 m,n=await e.loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=new a({json:t.json}),s=new d(n),u=await s.getCurrentUser(),i;try{i=(await s.getTokenBalance()).balance}catch{i=null}let c=e.loadUserInfo();if(t.json){let l=a.result({user:u,balance:i});o.output(l)}else console.log(`
149
+ \u{1F464} Current User
150
+ `),console.log(" Email: "+u.email),console.log(" Account ID: "+u.accountId),i!==null&&console.log(" Token Balance: $"+i.toFixed(2)),n.project&&console.log(`
151
+ Default Project: `+n.project),console.log()}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r}I();b();w();j();import{Command as mt}from"commander";function he(){let r=new mt("tokens");return r.description("View token balance and usage"),r.command("balance").description("Check remaining token/credit 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 m().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=new a({json:t.json,color:n.color}),s=new d(n),{balance:u}=await s.getTokenBalance();if(t.json){let i=a.result({balance:u});o.output(i)}else console.log(`
152
+ \u{1F4B0} Token Balance
153
+ `),console.log(` Current Balance: ${u.toLocaleString()} tokens`),console.log(` Estimated Tests: ~${Math.floor(u/100)} tests
154
+ `),console.log(`\u{1F4A1} Purchase more tokens: https://runhuman.com/billing
155
+ `)}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r.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 m().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=new a({json:t.json,color:n.color});if(t.json){let s=a.result({message:"Token history endpoint not yet available",history:[]});o.output(s)}else console.log(`
156
+ \u{1F4CA} Token Usage History
157
+ `),console.log(` This feature is coming soon!
158
+ `),console.log(" For now, view usage in the dashboard:"),console.log(` https://runhuman.com/dashboard/usage
159
+ `)}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r}b();w();j();import{Command as gt}from"commander";function we(){let r=new gt("config");return r.description("Manage CLI configuration"),r.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 m().get(t),s=new a({json:e.json});if(e.json){let u=a.result({[t]:o});s.output(u)}else console.log(o!==void 0?o:"(not set)")}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.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 m().set(t,e,n.global);let s=new a({json:n.json});if(n.json){let u=a.result({success:!0,key:t,value:e,scope:n.global?"global":"project"});s.output(u)}else s.success(`Set ${t} = ${e}`+(n.global?" (global)":" (project)"))}catch(o){let s=p(o);new a({json:n.json}).outputError(s.message,s.details),process.exit(s.exitCode)}}),r.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 m().list(),o=new a({json:t.json});if(t.json){let s=a.result(n);o.output(s)}else{if(console.log(`
160
+ \u2699\uFE0F Configuration
161
+ `),n.global&&Object.keys(n.global).length>0){console.log("Global (~/.config/runhuman/config.json):");for(let[s,u]of Object.entries(n.global)){let i=s==="apiKey"&&!t.showSecrets?ye(String(u)):u;console.log(` ${s}:`.padEnd(20)+i)}console.log()}if(n.project&&Object.keys(n.project).length>0){console.log("Project (.runhumanrc):");for(let[s,u]of Object.entries(n.project)){let i=s==="apiKey"&&!t.showSecrets?ye(String(u)):u;console.log(` ${s}:`.padEnd(20)+i)}console.log()}console.log("\u{1F4A1} Set value: runhuman config set <key> <value>"),console.log(` Get value: runhuman config get <key>
162
+ `)}}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r.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.
163
+ `),process.exit(0)),await new m().reset(e);let o=new a({json:t.json});if(t.json){let s=a.result({success:!0,scope:e});o.output(s)}else o.success(`Reset ${e} configuration`)}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r}function ye(r){return r.length<=8?"****":r.substring(0,4)+"*".repeat(r.length-8)+r.substring(r.length-4)}w();j();import{Command as dt}from"commander";import ft from"inquirer";import{writeFileSync as ht}from"fs";import{join as yt}from"path";function je(){let r=new dt("init");return r.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 a({json:t.json});!t.json&&!t.yes&&(console.log(`\u{1F680} Welcome to Runhuman!
164
+ `),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
165
+ `),console.log("=".repeat(60)+`
166
+ `));let n=t.name,o=t.url,s=t.githubRepo;if(!t.yes&&!t.json){let c=await ft.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:!s}]);n=n||c.projectName,o=o||c.defaultUrl,s=s||c.githubRepo}let u={defaultUrl:o||void 0,githubRepo:s||void 0,defaultDuration:5,defaultScreenSize:"desktop"},i=yt(process.cwd(),".runhumanrc");if(ht(i,JSON.stringify(u,null,2)),t.json){let c=a.result({success:!0,configPath:i,config:u});e.output(c)}else console.log(`
167
+ `+"=".repeat(60)),e.success("Project initialized successfully!"),console.log(`\u{1F4C1} Configuration saved to: .runhumanrc
168
+ `),console.log(`\u{1F389} All set! Try creating your first test:
169
+ `),console.log(` runhuman create https://example.com -d "Test homepage"
170
+ `),console.log(`\u{1F4DA} Learn more: https://runhuman.com/docs/cli
171
+ `)}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r}I();b();w();j();import{Command as wt}from"commander";import jt from"cli-table3";function Ce(){let r=new wt("projects");return r.description("Manage projects"),r.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 m().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=new a({json:t.json,color:n.color}),s=new d(n),{projects:u,total:i}=await s.listProjects({limit:t.limit||20});if(t.json){let c=a.result({projects:u,total:i});o.output(c)}else{console.log(`
172
+ \u{1F4C1} Your Projects (${u.length})
173
+ `);let c=new jt({head:["Project ID","Name","Created"].map(l=>l),colWidths:[20,30,20]});u.forEach(l=>{c.push([l.id,l.name,new Date(l.createdAt).toLocaleDateString()])}),console.log(c.toString()),console.log(`
174
+ \u{1F4A1} Set default project: runhuman config set project <id>
175
+ `)}}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r.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 m,o=await n.loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color}),i=await new d(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 c=a.result(i);s.output(c)}else s.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=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color}),i=await new d(o).getProject(t);if(e.json){let c=a.result(i);s.output(c)}else console.log(`
176
+ \u{1F4C1} Project Details
177
+ `),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=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color}),u={name:e.name,description:e.description,defaultUrl:e.defaultUrl,githubRepo:e.githubRepo},c=await new d(o).updateProject(t,u);if(e.json){let l=a.result(c);s.output(l)}else s.success("Project updated successfully!")}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.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.
178
+ `),process.exit(0));let o=await new m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color});if(await new d(o).deleteProject(t),e.json){let i=a.result({success:!0});s.output(i)}else s.success("Project deleted successfully!")}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r}I();b();w();j();import{Command as Ct}from"commander";import bt from"cli-table3";function be(){let r=new Ct("keys");return r.description("Manage API keys"),r.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 m().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 s=new a({json:t.json,color:n.color}),i=await new d(n).listApiKeys(o);if(t.json){let c=a.result({keys:i});s.output(c)}else{console.log(`
179
+ \u{1F511} API Keys (${i.length})
180
+ `);let c=new bt({head:["Key ID","Name","Key","Last Used","Created"].map(l=>l),colWidths:[20,25,20,20,20]});i.forEach(l=>{let g=t.showKeys?l.key:Ut(l.key),f=l.lastUsedAt?new Date(l.lastUsedAt).toLocaleDateString():"Never";c.push([l.id,l.name,g,f,new Date(l.createdAt).toLocaleDateString()])}),console.log(c.toString()),console.log(`
181
+ \u{1F4A1} Create new key: runhuman keys create "Key Name" --project <id>`),console.log(` Show full key: runhuman keys show <keyId>
182
+ `)}}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=e.project||o.project;if(!s)throw new Error("Project ID required (use --project or set default project)");let u=new a({json:e.json,color:o.color}),c=await new d(o).createApiKey(s,t);if(e.json){let l=a.result(c);u.output(l)}else u.success("API Key created successfully!"),console.log(`
183
+ Key ID: `+c.id),console.log(" Name: "+c.name),console.log(`
184
+ API Key: `+c.key),console.log(" "+"^".repeat(c.key.length)),console.log(` \u26A0\uFE0F Save this key! It won't be shown again.
185
+ `),console.log("\u{1F4A1} Use this key:"),console.log(" export RUNHUMAN_API_KEY="+c.key),console.log(` runhuman create https://myapp.com -d "Test"
186
+ `),console.log("\u{1F512} Store securely:"),console.log(" - Use environment variables (recommended)"),console.log(" - Use secret management tools"),console.log(` - Never commit to git!
187
+ `)}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl});new a({json:e.json,color:o.color}).warn("Note: keys show requires listing all keys first"),console.log(`Use: runhuman keys list --show-keys
188
+ `)}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.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.
189
+ `),process.exit(0));let o=await new m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color});if(await new d(o).deleteApiKey(t),e.json){let i=a.result({success:!0});s.output(i)}else s.success("API key deleted successfully!")}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r}function Ut(r){return r.length<=12?"****":r.substring(0,8)+"*".repeat(r.length-12)+r.substring(r.length-4)}I();b();w();j();import{Command as kt}from"commander";import It from"cli-table3";function Ue(){let r=new kt("templates");return r.description("Manage test templates"),r.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 m().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 s=new a({json:t.json,color:n.color}),i=await new d(n).listTemplates(o);if(t.json){let c=a.result({templates:i});s.output(c)}else{console.log(`
190
+ \u{1F4CB} Test Templates (${i.length})
191
+ `);let c=new It({head:["ID","Name","Description","Created"].map(l=>l),colWidths:[30,25,35,20]});i.forEach(l=>{let g=l.description&&l.description.length>30?l.description.substring(0,27)+"...":l.description||"-";c.push([l.id,l.name,g,new Date(l.createdAt).toLocaleDateString()])}),console.log(c.toString()),console.log(`
192
+ \u{1F4A1} Create new template: runhuman templates create "Template Name" --project <id>`),console.log(` View template: runhuman templates show <templateId>
193
+ `)}}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=e.project||o.project;if(!s)throw new Error("Project ID required (use --project or set default project)");let u=new a({json:e.json,color:o.color}),i;if(e.schema){let{readFileSync:f}=await import("fs"),h=f(e.schema,"utf-8");i=JSON.parse(h)}let c={name:t,description:e.description,targetDurationMinutes:e.duration?Math.floor(parseInt(e.duration)/60):void 0,defaultScreenSize:e.screenSize,outputSchema:i},g=await new d(o).createTemplate(s,c);if(e.json){let f=a.result(g);u.output(f)}else u.success("Template created successfully!"),console.log(`
194
+ Template ID: `+g.id),console.log(" Name: "+g.name),g.description&&console.log(" Description: "+g.description),console.log(`
195
+ \u{1F4A1} Use this template:`),console.log(" runhuman create https://myapp.com --template "+g.id+`
196
+ `)}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.command("show").alias("info").alias("get").description("Show details of a specific template").argument("<templateId>","Template 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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color}),i=await new d(o).getTemplate(t);if(e.json){let c=a.result(i);s.output(c)}else console.log(`
197
+ \u{1F4CB} Template Details
198
+ `),console.log("ID: "+i.id),console.log("Name: "+i.name),console.log("Description: "+(i.description||"-")),console.log("Project: "+i.projectId),i.targetDurationMinutes&&console.log("Duration: "+i.targetDurationMinutes+" minutes"),i.defaultScreenSize&&console.log("Screen Size: "+i.defaultScreenSize),console.log("Created: "+new Date(i.createdAt).toLocaleString()),i.outputSchema&&(console.log(`
199
+ Output Schema:`),console.log(JSON.stringify(i.outputSchema,null,2))),console.log(`
200
+ \u{1F4A1} Use this template:`),console.log(" runhuman create https://myapp.com --template "+i.id+`
201
+ `)}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.command("update").alias("edit").description("Update a template").argument("<templateId>","Template ID to update").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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color}),u;if(e.schema){let{readFileSync:f}=await import("fs"),h=f(e.schema,"utf-8");u=JSON.parse(h)}let i={name:e.name,description:e.description,targetDurationMinutes:e.duration?Math.floor(parseInt(e.duration)/60):void 0,defaultScreenSize:e.screenSize,outputSchema:u},c=Object.fromEntries(Object.entries(i).filter(([,f])=>f!==void 0));if(Object.keys(c).length===0)throw new Error("No updates provided. Use --name, --description, --duration, --screen-size, or --schema");let g=await new d(o).updateTemplate(t,i);if(e.json){let f=a.result(g);s.output(f)}else s.success("Template updated successfully!"),console.log(`
202
+ Template ID: `+g.id),console.log(" Name: "+g.name+`
203
+ `)}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.command("delete").alias("rm").description("Delete a template permanently").argument("<templateId>","Template 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 template."),console.log(`Use --force to skip this prompt.
204
+ `),process.exit(0));let o=await new m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color});if(await new d(o).deleteTemplate(t),e.json){let i=a.result({success:!0});s.output(i)}else s.success("Template deleted successfully!")}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r}I();b();w();j();import{Command as At}from"commander";import ke from"cli-table3";function Ie(){let r=new At("github");return r.alias("gh"),r.description("GitHub integration commands"),r.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 m,o=await n.loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=e.project||o.project;if(!s)throw new Error("Project ID required (use --project or set default project)");let u=new a({json:e.json,color:o.color}),i=t.match(/^([^/]+)\/([^/]+)$/);if(!i)throw new Error("Invalid repository format. Use: owner/repo");let[,c,l]=i,f=await new d(o).linkGithubRepo(s,c,l);if(e.json){let h=a.result(f);u.output(h)}else u.success("GitHub repository linked successfully!"),console.log(`
205
+ Repository: `+t),console.log(" Project: "+s),console.log(`
206
+ \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+`
207
+ `),await n.saveProjectConfig({githubRepo:t}),console.log(`\u2713 Repository saved to project config (.runhumanrc)
208
+ `)}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.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 m().loadConfig({apiKey:t.apiKey,apiUrl:t.apiUrl}),o=t.project||n.project,s=new a({json:t.json,color:n.color}),i=await new d(n).listGithubRepos(o);if(t.json){let c=a.result({repositories:i});s.output(c)}else{console.log(`
209
+ \u{1F517} Linked GitHub Repositories (${i.length})
210
+ `);let c=new ke({head:["Repository","Project","Linked"].map(l=>l),colWidths:[35,30,20]});i.forEach(l=>{c.push([l.fullName,l.projectId,new Date(l.linkedAt).toLocaleDateString()])}),console.log(c.toString()),console.log(`
211
+ \u{1F4A1} Test an issue: runhuman github test <issueNumber> --repo owner/repo
212
+ `)}}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=new a({json:e.json,color:o.color}),u=t.match(/^([^/]+)\/([^/]+)$/);if(!u)throw new Error("Invalid repository format. Use: owner/repo");let[,i,c]=u,g=await new d(o).listGithubIssues(i,c,{state:e.state,labels:e.labels?.split(",")});if(e.json){let f=a.result({issues:g});s.output(f)}else{console.log(`
213
+ \u{1F41B} GitHub Issues for ${t} (${g.length})
214
+ `);let f=new ke({head:["#","Title","State","Labels","Created"].map(h=>h),colWidths:[8,40,10,20,15]});g.forEach(h=>{let U=h.labels?.join(", ")||"-",y=U.length>18?U.substring(0,15)+"...":U;f.push(["#"+h.number,h.title.length>38?h.title.substring(0,35)+"...":h.title,h.state,y,new Date(h.createdAt).toLocaleDateString()])}),console.log(f.toString()),console.log(`
215
+ \u{1F4A1} Test an issue: runhuman github test <issueNumber> --repo `+t+`
216
+ `)}}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.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 m().loadConfig({apiKey:e.apiKey,apiUrl:e.apiUrl}),s=e.repo||o.githubRepo;if(!s)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 a({json:e.json,color:o.color}),i=s.match(/^([^/]+)\/([^/]+)$/);if(!i)throw new Error("Invalid repository format. Use: owner/repo");let[,c,l]=i,g=new d(o),f=await g.getGithubIssue(c,l,parseInt(t)),h={url:e.url,description:`Test GitHub issue #${t}: ${f.title}
217
+
218
+ ${f.body}`,metadata:{githubIssue:{owner:c,repo:l,number:parseInt(t),url:f.url}},templateId:e.template},U=await g.createJob(h);if(e.json){let y=a.result(U);u.output(y)}else if(u.success("QA test job created for issue #"+t),console.log(`
219
+ Job ID: `+U.jobId),console.log(" Issue: #"+t+" - "+f.title),console.log(" Status: "+U.status),console.log(" URL: "+e.url),console.log(`
220
+ \u{1F4A1} Check status: runhuman status `+U.jobId+`
221
+ `),e.sync){let{waitForJob:y}=await Promise.resolve().then(()=>(W(),X));await y(U.jobId,g,u,600)}}catch(n){let o=p(n);new a({json:e.json}).outputError(o.message,o.details),process.exit(o.exitCode)}}),r.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 m().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 s=new a({json:t.json,color:n.color}),u=o.match(/^([^/]+)\/([^/]+)$/);if(!u)throw new Error("Invalid repository format. Use: owner/repo");let[,i,c]=u,l=new d(n),g=await l.listGithubIssues(i,c,{state:t.state,labels:t.labels?.split(",")}),f=parseInt(t.limit),h=g.slice(0,f);if(h.length===0){console.log(`No issues found matching the criteria.
222
+ `);return}console.log(`
223
+ \u{1F680} Creating ${h.length} test jobs...
224
+ `);let U=[];for(let y of h){let D={url:t.url,description:`Test GitHub issue #${y.number}: ${y.title}
225
+
226
+ ${y.body}`,metadata:{githubIssue:{owner:i,repo:c,number:y.number,url:y.url}},templateId:t.template};try{let P=await l.createJob(D);U.push({issue:y.number,jobId:P.jobId,status:"created"}),console.log(` \u2713 Issue #${y.number} \u2192 Job ${P.jobId}`)}catch(P){let K=p(P);U.push({issue:y.number,error:K.message,status:"failed"}),console.log(` \u2717 Issue #${y.number} \u2192 Failed: ${K.message}`)}}if(t.json){let y=a.result({jobs:U});s.output(y)}else{let y=U.filter(P=>P.status==="created").length,D=U.filter(P=>P.status==="failed").length;console.log(`
227
+ \u2713 Created ${y} jobs`),D>0&&console.log(`\u2717 Failed ${D} jobs`),console.log(`
228
+ \u{1F4A1} Check all jobs: runhuman list
229
+ `)}}catch(e){let n=p(e);new a({json:t.json}).outputError(n.message,n.details),process.exit(n.exitCode)}}),r}b();I();import{existsSync as Pt}from"fs";import{join as St}from"path";import{execSync as Rt}from"child_process";import M from"inquirer";import k from"chalk";import Pe from"ora";j();async function Se(){let r=new m,t=new a({color:!0});console.log(""),console.log(k.bold("Runhuman")+" - Human QA testing for your apps"),console.log("");let e=await Ae(r);if(!e.isAuthenticated){let{shouldLogin:n}=await M.prompt([{type:"confirm",name:"shouldLogin",message:"You're not logged in. Would you like to sign in?",default:!0}]);if(!n){console.log(`
230
+ Run `+k.cyan("runhuman login")+` when you're ready to sign in.
231
+ `);return}await xt(r,t),Object.assign(e,await Ae(r))}console.log(k.green("Logged in as "+e.userEmail)+`
232
+ `),e.isRunhumanProject?await Ot(r,e):await Dt(r,e)}async function Ae(r){let t={isAuthenticated:!1,isGitRepo:!1,isRunhumanProject:!1},e=r.loadCredentials();if(e?.accessToken)try{let o=await r.loadConfig({apiKey:e.accessToken}),u=await new d(o).getCurrentUser();t.isAuthenticated=!0,t.userEmail=u.email}catch{t.isAuthenticated=!1}try{Rt("git rev-parse --is-inside-work-tree",{stdio:"ignore"}),t.isGitRepo=!0}catch{t.isGitRepo=!1}let n=St(process.cwd(),".runhumanrc");if(Pt(n)){t.isRunhumanProject=!0;try{let o=await r.loadConfig();t.projectConfig={defaultUrl:o.defaultUrl,githubRepo:o.githubRepo}}catch{}}return t}async function xt(r,t){let e=await r.loadConfig(),n=e.apiUrl||"https://runhuman.com";console.log("");let o=Pe("Opening browser for authentication...").start();try{let s=await V({apiUrl:n,autoOpenBrowser:e.autoOpenBrowser!==!1});o.stop(),r.saveCredentials({accessToken:s.token});let u=await r.loadConfig({apiKey:s.token}),c=await new d(u).getCurrentUser();r.saveUserInfo(c),t.success("Successfully logged in!"),console.log("")}catch(s){throw o.stop(),s}}async function Ot(r,t){console.log(k.dim("Runhuman project detected")),t.projectConfig?.defaultUrl&&console.log(k.dim("URL: "+t.projectConfig.defaultUrl)),console.log("");let{action:e}=await M.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 M.Separator,{name:"Exit",value:"exit"}]}]);switch(e){case"quick-test":await Re(r,t);break;case"run-template":console.log(`
233
+ Run `+k.cyan("runhuman templates")+" to see available templates."),console.log("Then run "+k.cyan("runhuman create --template <name>")+` to use one.
234
+ `);break;case"list-jobs":console.log(`
235
+ Run `+k.cyan("runhuman list")+` to see your recent jobs.
236
+ `);break;case"exit":break}}async function Dt(r,t){t.isGitRepo?console.log(k.dim("Git repository detected (not yet set up with Runhuman)")):console.log(k.dim("Not in a git repository")),console.log("");let{action:e}=await M.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 M.Separator,{name:"Exit",value:"exit"}]}]);switch(e){case"quick-test":await Re(r,t);break;case"setup-repo":await Et(r);break;case"connect-github":await Nt(r);break;case"exit":break}}async function Re(r,t){let e=t.projectConfig?.defaultUrl||"",n=await M.prompt([{type:"input",name:"url",message:"URL to test:",default:e||void 0,validate:s=>{if(!s.trim())return"URL is required";try{return new URL(s),!0}catch{return"Please enter a valid URL"}}},{type:"input",name:"description",message:"What should we test? (describe in plain English):",validate:s=>s.trim()?!0:"Description is required"}]),o=Pe("Creating test job...").start();try{let s=r.loadCredentials(),u=await r.loadConfig({apiKey:s?.accessToken}),c=await new d(u).createJob({url:n.url,description:n.description});o.succeed("Test job created!"),console.log(""),console.log(" Job ID: "+k.cyan(c.jobId)),console.log(" Status: "+c.status),console.log(""),console.log("A human tester will test your app shortly."),console.log("Run "+k.cyan(`runhuman wait ${c.jobId}`)+" to wait for results."),console.log("")}catch(s){throw o.fail("Failed to create test job"),s}}async function Et(r){console.log(""),console.log("Setting up Runhuman in the current repository..."),console.log("");let t=await M.prompt([{type:"input",name:"defaultUrl",message:"Default URL to test (optional):"}]);await r.saveProjectConfig({defaultUrl:t.defaultUrl||void 0,defaultDuration:5,defaultScreenSize:"desktop"}),console.log(""),console.log(k.green("Created .runhumanrc")),console.log(""),console.log("Next steps:"),console.log(" 1. Install the GitHub App to enable @runhuman comments"),console.log(" "+k.cyan("runhuman github connect")),console.log(""),console.log(" 2. Create your first test:"),console.log(" "+k.cyan('runhuman create https://your-app.com -d "Test login flow"')),console.log("")}async function Nt(r){let n=`${(await r.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(" "+k.cyan(n)),console.log(""),console.log("After installation, you can comment "+k.bold("@runhuman")+" on any issue"),console.log("to trigger a QA test."),console.log("")}import{readFileSync as Tt}from"fs";import{join as Mt,dirname as Jt}from"path";import{fileURLToPath as $t}from"url";var Lt=$t(import.meta.url),Kt=Jt(Lt),Ft=Mt(Kt,"../package.json"),Ht=JSON.parse(Tt(Ft,"utf-8")),Gt=Ht.version,C=new vt;C.name("runhuman").description("CLI for Runhuman - AI-orchestrated human QA testing").version(Gt);C.addCommand(ne());C.addCommand(re());C.addCommand(Z());C.addCommand(se());C.addCommand(ie());C.addCommand(ae());C.addCommand(ue());C.addCommand(ge());C.addCommand(de());C.addCommand(fe());C.addCommand(he());C.addCommand(we());C.addCommand(je());C.addCommand(Ce());C.addCommand(be());C.addCommand(Ue());C.addCommand(Ie());process.argv.slice(2).length?C.parse(process.argv):Se().catch(r=>{console.error(r.message),process.exit(1)});
package/package.json CHANGED
@@ -1,16 +1,33 @@
1
1
  {
2
2
  "name": "runhuman",
3
- "version": "0.1.0",
4
- "description": "CLI for Runhuman - AI-orchestrated human QA testing (Coming Soon)",
5
- "main": "index.js",
3
+ "version": "1.0.1",
4
+ "description": "CLI for Runhuman - AI-orchestrated human QA testing",
5
+ "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
- "runhuman": "index.js"
8
+ "runhuman": "dist/index.js"
9
9
  },
10
10
  "files": [
11
- "index.js",
12
- "README.md"
11
+ "dist/index.js",
12
+ "README.md",
13
+ "LICENSE"
13
14
  ],
15
+ "publishConfig": {
16
+ "access": "public",
17
+ "registry": "https://registry.npmjs.org/"
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "build:dev": "tsc",
22
+ "dev": "tsc --watch",
23
+ "test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest",
24
+ "test:watch": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest --watch",
25
+ "test:coverage": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest --coverage",
26
+ "test:unit": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest tests/unit",
27
+ "test:integration": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest tests/integration",
28
+ "type-check": "tsc --noEmit",
29
+ "prepublishOnly": "npm run build"
30
+ },
14
31
  "keywords": [
15
32
  "runhuman",
16
33
  "qa",
@@ -18,7 +35,9 @@
18
35
  "human-in-the-loop",
19
36
  "cli",
20
37
  "qa-testing",
21
- "manual-testing"
38
+ "manual-testing",
39
+ "test-automation",
40
+ "quality-assurance"
22
41
  ],
23
42
  "author": "Runhuman <hey@runhuman.com>",
24
43
  "repository": {
@@ -33,5 +52,27 @@
33
52
  "license": "ISC",
34
53
  "engines": {
35
54
  "node": ">=18.0.0"
55
+ },
56
+ "dependencies": {
57
+ "axios": "^1.7.9",
58
+ "boxen": "^8.0.1",
59
+ "chalk": "^5.3.0",
60
+ "chokidar": "^5.0.0",
61
+ "cli-table3": "^0.6.5",
62
+ "commander": "^12.1.0",
63
+ "cosmiconfig": "^9.0.0",
64
+ "inquirer": "^10.2.2",
65
+ "open": "^10.1.0",
66
+ "ora": "^8.1.0",
67
+ "zod": "^3.23.8"
68
+ },
69
+ "devDependencies": {
70
+ "@jest/globals": "^29.7.0",
71
+ "@types/inquirer": "^9.0.7",
72
+ "@types/node": "^22.10.2",
73
+ "jest": "^29.7.0",
74
+ "ts-jest": "^29.2.5",
75
+ "tsup": "^8.3.5",
76
+ "typescript": "^5.7.2"
36
77
  }
37
78
  }
package/index.js DELETED
@@ -1,53 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Runhuman CLI - Coming Soon
5
- *
6
- * This is a placeholder package that reserves the `runhuman` npm package name.
7
- * The full CLI is currently in development.
8
- *
9
- * For now, if you're looking to use Runhuman with AI agents (Claude, GPT, etc.),
10
- * please use the MCP server package instead:
11
- *
12
- * npm install -g @runhuman/mcp
13
- *
14
- * Or use with npx:
15
- *
16
- * npx @runhuman/mcp --api-key=qa_live_xxxxx
17
- *
18
- * Documentation: https://runhuman.com
19
- */
20
-
21
- console.log(`
22
- ╔═══════════════════════════════════════════════════════════════════════════╗
23
- ║ ║
24
- ║ 🚧 Runhuman CLI - Coming Soon 🚧 ║
25
- ║ ║
26
- ╚═══════════════════════════════════════════════════════════════════════════╝
27
-
28
- The Runhuman CLI is currently under development.
29
-
30
- For AI agents (Claude, GPT, etc.), use the MCP server:
31
-
32
- 📦 Install: npm install -g @runhuman/mcp
33
- 🚀 Run: npx @runhuman/mcp --api-key=qa_live_xxxxx
34
-
35
- For Claude Desktop, add to your config:
36
- {
37
- "mcpServers": {
38
- "runhuman": {
39
- "command": "npx",
40
- "args": ["-y", "@runhuman/mcp", "--api-key=YOUR_KEY"]
41
- }
42
- }
43
- }
44
-
45
- 📚 Documentation: https://runhuman.com
46
- 🔑 Get API Key: https://runhuman.com/dashboard
47
- 💬 Support: hey@runhuman.com
48
-
49
- The full CLI with commands like 'runhuman create', 'runhuman list', etc.
50
- will be available soon!
51
- `);
52
-
53
- process.exit(0);