argusqa-os 9.2.9 → 9.3.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/README.md +224 -77
- package/package.json +1 -1
- package/src/mcp-server.js +53 -9
- package/src/orchestration/watch-mode.js +163 -2
package/README.md
CHANGED
|
@@ -52,14 +52,16 @@ Then ask Claude (or any MCP client):
|
|
|
52
52
|
Run argus_audit on http://localhost:3000
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
**
|
|
55
|
+
**Six tools are exposed:**
|
|
56
56
|
|
|
57
57
|
| Tool | What it does |
|
|
58
58
|
| --- | --- |
|
|
59
59
|
| `argus_audit` | Fast QA pass — JS errors, network failures, accessibility, SEO, security, CSS, content |
|
|
60
|
-
| `argus_audit_full` | Deep QA pass — adds Lighthouse scoring, responsive layout checks across 4 viewports, memory leak detection
|
|
60
|
+
| `argus_audit_full` | Deep QA pass — adds Lighthouse scoring, responsive layout checks across 4 viewports, memory leak detection, hover-state bug detection, and accessibility tree snapshot |
|
|
61
61
|
| `argus_compare` | Diff dev vs staging side-by-side — screenshots, findings delta, environment regressions |
|
|
62
|
-
| `argus_last_report` | Return the last saved
|
|
62
|
+
| `argus_last_report` | Return the last saved JSON report without re-running a scan |
|
|
63
|
+
| `argus_watch_snapshot` | Snapshot the currently open Chrome tab without navigating — raw console + network capture |
|
|
64
|
+
| `argus_get_context` | Capture everything broken on the open tab, formatted as a diagnostic context for Claude to diagnose and suggest fixes |
|
|
63
65
|
|
|
64
66
|
> **Requires**: Node.js ≥ 20.19, Chrome (desktop or headless), and the `chrome-devtools-mcp` server registered alongside Argus (shown above).
|
|
65
67
|
|
|
@@ -77,7 +79,7 @@ The `landing/` directory contains the product landing page (React + Vite + Tailw
|
|
|
77
79
|
|
|
78
80
|
| 🔴 Critical / 🟡 Warning / 🔵 Info | ⚙️ | 🧪 | 📋 |
|
|
79
81
|
| :---: | :---: | :---: | :---: |
|
|
80
|
-
| **114 distinct issue types detected** | **24 analysis engines** | **
|
|
82
|
+
| **114 distinct issue types detected** | **24 analysis engines** | **360 test assertions** | **83 test blocks** |
|
|
81
83
|
|
|
82
84
|
</div>
|
|
83
85
|
|
|
@@ -339,13 +341,13 @@ Argus watches your running application and automatically surfaces issues that te
|
|
|
339
341
|
| **GitHub PR Integration** | Posts a structured Markdown findings table as a PR comment (updates in-place — one comment per PR, no spam); sets an `argus-qa` commit status check (`failure` when new criticals exist, `success` otherwise) — blocks merge via branch protection when regressions are introduced. Requires `GITHUB_TOKEN` + `GITHUB_REPOSITORY` env vars |
|
|
340
342
|
| **Auto Route Discovery** | Augments manual `routes[]` with paths from three sources: fetches `/sitemap.xml` (follows one sitemap-index level, 10s timeout), scans Next.js `pages/` (Next 12) and `app/` (Next 13+) directories stripping route groups `(auth)`, and greps JS/TS source for React Router `<Route path>` declarations. Dynamic `[param]` segments are skipped — no concrete URL to crawl. Manual route config (`critical`, `waitFor`) always takes precedence. |
|
|
341
343
|
| **`argus init` Setup Wizard** | `npm run init` (or `npx argus init`) guides first-time setup: collects target URLs, detects the app framework (Next.js / React Router / unknown) from the source directory's `package.json`, runs C3 route discovery against the dev URL, prompts for optional Slack tokens and GitHub credentials, then writes a populated `.env` and a pre-filled `src/config/targets.js` — zero manual config editing required. |
|
|
342
|
-
| **Watch Mode** | `npm run watch` attaches to whatever Chrome tab is open and polls `list_console_messages` + `list_network_requests` every
|
|
344
|
+
| **Watch Mode** | `npm run watch` attaches to whatever Chrome tab is open and polls `list_console_messages` + `list_network_requests` every 1 s (configurable via `ARGUS_WATCH_INTERVAL_MS`). Reports new console errors, network failures (4xx/5xx), CORS blocks, and auth failures in real time — without navigating. Starts a live web dashboard at `http://localhost:3002` (configurable via `ARGUS_WATCH_UI_PORT`). On `Ctrl+C`, generates a final `reports/report.html`. No route config needed. |
|
|
343
345
|
| **Full Lighthouse Suite** | All 4 Lighthouse categories (performance, SEO, best-practices, accessibility) with per-audit items |
|
|
344
346
|
| **Performance Budgets** | Enforces LCP < 2500ms, CLS < 0.1, FID < 100ms, TTFB < 800ms per route |
|
|
345
347
|
| **Slack Notifications** | Rich Block Kit reports with inline screenshots routed to `#bugs-critical`, `#bugs-warnings`, `#bugs-digest` |
|
|
346
348
|
| **Slash Command** | `/argus-retest <url>` triggers an on-demand test from any Slack channel |
|
|
347
349
|
| **CI Integration** | GitHub Actions workflow runs daily at 6 AM UTC and on every push to `main` |
|
|
348
|
-
| **MCP Server (AI-callable Argus)** | Register Argus as an MCP server via `.mcp.json`; Claude (or any MCP client) can call `argus_audit`, `argus_audit_full`, `argus_compare`, `argus_last_report` directly from a conversation — no CLI, no terminal required. Published to npm as **[argusqa-os](https://www.npmjs.com/package/argusqa-os)** — add via `{ "command": "npx", "args": ["-y", "argusqa-os"] }` in `.mcp.json` |
|
|
350
|
+
| **MCP Server (AI-callable Argus)** | Register Argus as an MCP server via `.mcp.json`; Claude (or any MCP client) can call `argus_audit`, `argus_audit_full`, `argus_compare`, `argus_last_report`, `argus_watch_snapshot`, and `argus_get_context` directly from a conversation — no CLI, no terminal required. Published to npm as **[argusqa-os](https://www.npmjs.com/package/argusqa-os)** — add via `{ "command": "npx", "args": ["-y", "argusqa-os"] }` in `.mcp.json` |
|
|
349
351
|
|
|
350
352
|
Works with **React + SCSS**, CSS Modules, CSS-in-JS (styled-components / emotion), and plain HTML/CSS apps.
|
|
351
353
|
|
|
@@ -382,26 +384,126 @@ In interactive mode (running from Claude Code), MCP tools are called natively. I
|
|
|
382
384
|
|
|
383
385
|
## One-Time Setup
|
|
384
386
|
|
|
385
|
-
###
|
|
387
|
+
### Option A — MCP Server (Claude Code / any MCP client)
|
|
388
|
+
|
|
389
|
+
No local install required. `npx` auto-downloads `argusqa-os` on first use.
|
|
390
|
+
|
|
391
|
+
#### 1. Register both MCP servers
|
|
392
|
+
|
|
393
|
+
Add to `.mcp.json` in your project root:
|
|
394
|
+
|
|
395
|
+
```json
|
|
396
|
+
{
|
|
397
|
+
"mcpServers": {
|
|
398
|
+
"chrome-devtools": {
|
|
399
|
+
"command": "npx",
|
|
400
|
+
"args": ["-y", "chrome-devtools-mcp@latest"]
|
|
401
|
+
},
|
|
402
|
+
"argus": {
|
|
403
|
+
"command": "npx",
|
|
404
|
+
"args": ["-y", "argusqa-os"]
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Or via Claude Code CLI:
|
|
386
411
|
|
|
387
412
|
```bash
|
|
388
|
-
|
|
389
|
-
|
|
413
|
+
claude mcp add chrome-devtools -- npx -y chrome-devtools-mcp@latest
|
|
414
|
+
claude mcp add argus -- npx -y argusqa-os
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
#### 2. Environment variables
|
|
418
|
+
|
|
419
|
+
Create a `.env` file in your project root:
|
|
420
|
+
|
|
421
|
+
```env
|
|
422
|
+
TARGET_DEV_URL=http://localhost:3000
|
|
423
|
+
TARGET_STAGING_URL=https://staging.yourapp.com # optional — enables argus_compare
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
#### 3. Start Chrome with remote debugging
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
# macOS
|
|
430
|
+
open -a "Google Chrome" --args --remote-debugging-port=9222 --headless=new
|
|
431
|
+
|
|
432
|
+
# Windows
|
|
433
|
+
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --headless=new --no-sandbox --disable-gpu
|
|
434
|
+
|
|
435
|
+
# Linux
|
|
436
|
+
google-chrome --remote-debugging-port=9222 --headless=new --no-sandbox
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### 4. Slack notifications (optional)
|
|
440
|
+
|
|
441
|
+
> Skip to use local `report.html` mode — Argus generates a self-contained HTML report when Slack is not configured.
|
|
442
|
+
|
|
443
|
+
1. [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → name it **BugBot**
|
|
444
|
+
2. **OAuth & Permissions** → Bot Token Scopes: `chat:write`, `files:write`, `files:read`
|
|
445
|
+
3. Install to workspace → copy **Bot User OAuth Token** (`xoxb-...`) to `.env` as `SLACK_BOT_TOKEN`
|
|
446
|
+
4. Create `#bugs-critical`, `#bugs-warnings`, `#bugs-digest` and `/invite @BugBot` in each
|
|
447
|
+
|
|
448
|
+
```env
|
|
449
|
+
SLACK_BOT_TOKEN=xoxb-...
|
|
450
|
+
SLACK_CHANNEL_CRITICAL=C0000000000
|
|
451
|
+
SLACK_CHANNEL_WARNINGS=C0000000001
|
|
452
|
+
SLACK_CHANNEL_DIGEST=C0000000002
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
### Option B — npm Package (dev dependency / CI/CD)
|
|
458
|
+
|
|
459
|
+
#### 1. Install
|
|
460
|
+
|
|
461
|
+
```bash
|
|
462
|
+
npm install --save-dev argusqa-os
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### 2. Environment variables
|
|
466
|
+
|
|
467
|
+
Run the interactive wizard to auto-generate `.env` and `src/config/targets.js`:
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
npx argus
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
The wizard detects your framework (Next.js / React Router), discovers routes from `sitemap.xml` and your file structure, and optionally collects Slack and GitHub credentials.
|
|
474
|
+
|
|
475
|
+
**Alternative — manual setup:** Create a `.env` with `TARGET_DEV_URL` and optionally `TARGET_STAGING_URL`.
|
|
476
|
+
|
|
477
|
+
#### 3. Start Chrome with remote debugging
|
|
478
|
+
|
|
479
|
+
Same as Option A — see above.
|
|
480
|
+
|
|
481
|
+
#### 4. Slack notifications (optional)
|
|
482
|
+
|
|
483
|
+
Same as Option A — see above.
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
### Option C — Clone the Repository (full source / contributors)
|
|
488
|
+
|
|
489
|
+
#### 1. Clone and install
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
git clone https://github.com/ironclawdevs27/Argus.git
|
|
493
|
+
cd Argus
|
|
390
494
|
npm install
|
|
391
495
|
npm run setup # creates reports/ directory
|
|
392
496
|
```
|
|
393
497
|
|
|
394
|
-
|
|
498
|
+
#### 2. Environment variables
|
|
395
499
|
|
|
396
|
-
**Recommended
|
|
500
|
+
**Recommended — use the interactive setup wizard:**
|
|
397
501
|
|
|
398
502
|
```bash
|
|
399
503
|
npm run init
|
|
400
504
|
```
|
|
401
505
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
**Alternative: manual setup**
|
|
506
|
+
**Alternative — manual setup:**
|
|
405
507
|
|
|
406
508
|
```bash
|
|
407
509
|
cp .env.example .env
|
|
@@ -410,26 +512,22 @@ cp .env.example .env
|
|
|
410
512
|
Open `.env` and fill in:
|
|
411
513
|
|
|
412
514
|
```env
|
|
413
|
-
# Your app URLs (required)
|
|
414
515
|
TARGET_DEV_URL=http://localhost:3000
|
|
415
516
|
TARGET_STAGING_URL=https://staging.yourapp.com # leave blank → CSS-only analysis mode
|
|
416
517
|
|
|
417
|
-
# Slack — OPTIONAL. Omit to get a local report.html instead
|
|
418
|
-
# Get from: api.slack.com/apps → BugBot → OAuth & Permissions
|
|
518
|
+
# Slack — OPTIONAL. Omit to get a local report.html instead.
|
|
419
519
|
# SLACK_BOT_TOKEN=xoxb-...
|
|
420
520
|
# SLACK_SIGNING_SECRET=...
|
|
421
|
-
|
|
422
|
-
# Channel IDs — only needed when SLACK_BOT_TOKEN is set
|
|
423
521
|
# SLACK_CHANNEL_CRITICAL=C0000000000
|
|
424
522
|
# SLACK_CHANNEL_WARNINGS=C0000000001
|
|
425
523
|
# SLACK_CHANNEL_DIGEST=C0000000002
|
|
426
524
|
```
|
|
427
525
|
|
|
428
|
-
|
|
526
|
+
#### 3. Configure routes
|
|
429
527
|
|
|
430
|
-
If you
|
|
528
|
+
If you ran `npm run init` — skip this step.
|
|
431
529
|
|
|
432
|
-
Otherwise, edit [src/config/targets.js](src/config/targets.js)
|
|
530
|
+
Otherwise, edit [src/config/targets.js](src/config/targets.js):
|
|
433
531
|
|
|
434
532
|
```js
|
|
435
533
|
export const routes = [
|
|
@@ -440,111 +538,160 @@ export const routes = [
|
|
|
440
538
|
];
|
|
441
539
|
```
|
|
442
540
|
|
|
443
|
-
- `critical: true` —
|
|
541
|
+
- `critical: true` — errors on this route go to `#bugs-critical`
|
|
444
542
|
- `waitFor` — CSS selector Argus waits for before capturing (signals the page is ready)
|
|
445
543
|
|
|
446
|
-
|
|
544
|
+
#### 4. Connect Chrome DevTools MCP to Claude Code
|
|
447
545
|
|
|
448
546
|
```bash
|
|
449
547
|
claude mcp add chrome-devtools -- npx chrome-devtools-mcp@latest
|
|
450
548
|
```
|
|
451
549
|
|
|
452
|
-
Verify
|
|
453
|
-
> "List all open Chrome pages"
|
|
550
|
+
Verify — ask Claude: *"List all open Chrome pages"* — you should see your tabs.
|
|
454
551
|
|
|
455
|
-
|
|
552
|
+
#### 5. Start Chrome with remote debugging
|
|
456
553
|
|
|
457
|
-
|
|
554
|
+
Same as Option A — see above.
|
|
458
555
|
|
|
459
|
-
|
|
556
|
+
#### 6. Slack notifications (optional)
|
|
460
557
|
|
|
461
|
-
|
|
462
|
-
2. **OAuth & Permissions** → Bot Token Scopes: add `chat:write`, `files:write`, `files:read`
|
|
463
|
-
3. Click **Install to Workspace** → Authorize
|
|
464
|
-
4. Copy the **Bot User OAuth Token** (`xoxb-...`) into `.env` as `SLACK_BOT_TOKEN`
|
|
465
|
-
5. **Basic Information** → copy **Signing Secret** into `.env` as `SLACK_SIGNING_SECRET`
|
|
466
|
-
6. Create channels: `#bugs-critical`, `#bugs-warnings`, `#bugs-digest`
|
|
467
|
-
7. In each channel: `/invite @BugBot`
|
|
558
|
+
Same as Option A — see above.
|
|
468
559
|
|
|
469
560
|
---
|
|
470
561
|
|
|
471
562
|
## Running Argus
|
|
472
563
|
|
|
473
|
-
### Option A
|
|
564
|
+
### Option A — Via MCP (Claude Code / any MCP client)
|
|
565
|
+
|
|
566
|
+
Ask Claude directly — no terminal needed.
|
|
567
|
+
|
|
568
|
+
**Available tools:**
|
|
569
|
+
|
|
570
|
+
| Tool | What it does |
|
|
571
|
+
| --- | --- |
|
|
572
|
+
| `argus_audit` | Fast QA pass — JS errors, network failures, accessibility, SEO, security, CSS, content |
|
|
573
|
+
| `argus_audit_full` | Deep QA pass — adds Lighthouse, responsive layout checks across 4 viewports, memory leak detection, hover-state bug detection, and accessibility tree snapshot |
|
|
574
|
+
| `argus_compare` | Diff dev vs staging — screenshots, findings delta, environment regressions |
|
|
575
|
+
| `argus_last_report` | Return the last saved JSON report without re-running a scan |
|
|
576
|
+
| `argus_watch_snapshot` | Snapshot the currently open Chrome tab without navigating — raw console + network capture |
|
|
577
|
+
| `argus_get_context` | Capture everything broken on the open tab, formatted as a diagnostic context for Claude to diagnose and suggest fixes |
|
|
578
|
+
|
|
579
|
+
**`argus_audit`** — fast audit of any URL:
|
|
474
580
|
|
|
475
|
-
|
|
581
|
+
```text
|
|
582
|
+
Run argus_audit on http://localhost:3000/checkout
|
|
583
|
+
Run argus_audit on http://localhost:3000/login with critical: true
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**`argus_audit_full`** — deep audit with Lighthouse + memory + responsive checks:
|
|
476
587
|
|
|
477
588
|
```text
|
|
478
|
-
Run
|
|
589
|
+
Run argus_audit_full on http://localhost:3000/dashboard
|
|
479
590
|
```
|
|
480
591
|
|
|
481
|
-
|
|
592
|
+
**`argus_compare`** — dev vs staging diff (reads `TARGET_DEV_URL` and `TARGET_STAGING_URL` from `.env`):
|
|
482
593
|
|
|
483
594
|
```text
|
|
484
|
-
Run
|
|
595
|
+
Run argus_compare
|
|
485
596
|
```
|
|
486
597
|
|
|
487
|
-
|
|
598
|
+
**`argus_last_report`** — retrieve last audit without re-running Chrome:
|
|
488
599
|
|
|
489
|
-
|
|
600
|
+
```text
|
|
601
|
+
Run argus_last_report
|
|
602
|
+
```
|
|
490
603
|
|
|
491
|
-
|
|
492
|
-
# Error detection crawl
|
|
493
|
-
npm run crawl
|
|
604
|
+
**`argus_watch_snapshot`** — snapshot the currently open tab without navigating. Useful when the page is in an authenticated or post-interaction state that navigation would reset:
|
|
494
605
|
|
|
495
|
-
|
|
496
|
-
|
|
606
|
+
```text
|
|
607
|
+
Run argus_watch_snapshot
|
|
608
|
+
Run argus_watch_snapshot with url: http://localhost:3000
|
|
609
|
+
```
|
|
497
610
|
|
|
498
|
-
|
|
499
|
-
npm run compare
|
|
611
|
+
**`argus_get_context`** — when your app is stuck or throwing errors, run this to capture everything that's broken and feed it to Claude for diagnosis:
|
|
500
612
|
|
|
501
|
-
|
|
502
|
-
|
|
613
|
+
```text
|
|
614
|
+
Run argus_get_context
|
|
503
615
|
```
|
|
504
616
|
|
|
505
|
-
|
|
617
|
+
Then follow with: *"Here's the context — what's causing these errors and how do I fix them?"*
|
|
506
618
|
|
|
507
|
-
|
|
619
|
+
---
|
|
508
620
|
|
|
509
|
-
|
|
621
|
+
### Option B & C — Via CLI / npm scripts
|
|
510
622
|
|
|
511
|
-
**
|
|
623
|
+
**Available commands:**
|
|
512
624
|
|
|
513
|
-
|
|
|
514
|
-
| --- | --- |
|
|
515
|
-
|
|
|
516
|
-
|
|
|
625
|
+
| Command | What it does |
|
|
626
|
+
| --- | --- |
|
|
627
|
+
| `npm run crawl` | Multi-page batch audit of all routes in `targets.js` |
|
|
628
|
+
| `npm run compare` | Dev vs staging diff (or CSS analysis if no `TARGET_STAGING_URL`) |
|
|
629
|
+
| `npm run watch` | Passive monitor — polls the open Chrome tab every 1s, no navigation |
|
|
630
|
+
| `npm run report:html` | Generate `reports/report.html` from the latest JSON audit |
|
|
631
|
+
| `npm run server` | Start the Slack slash command + interaction server (port 3001) |
|
|
632
|
+
| `npm run init` | Interactive setup wizard — generates `.env` + `targets.js` |
|
|
633
|
+
| `npm run test:unit` | Run 61 unit tests (no Chrome required) |
|
|
634
|
+
| `npm run test:harness` | Run 82-block correctness harness (requires Chrome) |
|
|
635
|
+
|
|
636
|
+
**`npm run crawl`** — full audit of all configured routes:
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
npm run crawl
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
Reports are saved to `reports/` as JSON files. Run `npm run report:html` after any crawl for a portable `reports/report.html` with all screenshots inlined — useful for sharing with designers or reviewing offline.
|
|
643
|
+
|
|
644
|
+
**`npm run compare`** — dev vs staging diff:
|
|
645
|
+
|
|
646
|
+
```bash
|
|
647
|
+
npm run compare
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
When `TARGET_STAGING_URL` is not set, automatically switches to **CSS analysis mode** — cascade overrides, component style leaks, unused rules, and React inline style conflicts on the dev environment only.
|
|
517
651
|
|
|
518
|
-
|
|
652
|
+
**`npm run watch`** — passive monitoring (polls every 1s, no navigation):
|
|
519
653
|
|
|
520
|
-
|
|
654
|
+
Attaches to whatever Chrome tab is open and reports new issues in real time without navigating anywhere. Use this while developing.
|
|
655
|
+
|
|
656
|
+
```text
|
|
657
|
+
Requires 2 terminals:
|
|
658
|
+
Terminal 1 — your app (npm start / npm run dev)
|
|
659
|
+
Terminal 2 — npm run watch
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Steps:
|
|
663
|
+
1. Open Chrome and navigate to your app
|
|
521
664
|
2. Terminal 1: start your application
|
|
522
665
|
3. Terminal 2: `npm run watch` — Argus begins polling
|
|
523
|
-
4. Develop normally —
|
|
524
|
-
5. `Ctrl+C`
|
|
525
|
-
|
|
526
|
-
**To target a specific URL:**
|
|
666
|
+
4. Develop normally — console errors, network failures (4xx/5xx), CORS blocks, and auth failures print in real time
|
|
667
|
+
5. `Ctrl+C` — stops the monitor and writes `reports/report.html`
|
|
527
668
|
|
|
528
669
|
```bash
|
|
670
|
+
# Attribute findings to a specific URL:
|
|
529
671
|
npm run watch http://localhost:4000
|
|
530
672
|
```
|
|
531
673
|
|
|
532
|
-
**Environment variables:**
|
|
533
|
-
|
|
534
674
|
| Variable | Default | Description |
|
|
535
675
|
| --- | --- | --- |
|
|
536
|
-
| `ARGUS_WATCH_INTERVAL_MS` | `
|
|
537
|
-
| `TARGET_DEV_URL` | `http://localhost:3000` | URL attributed to findings when none passed
|
|
676
|
+
| `ARGUS_WATCH_INTERVAL_MS` | `1000` | Poll interval in milliseconds |
|
|
677
|
+
| `TARGET_DEV_URL` | `http://localhost:3000` | URL attributed to findings when none passed |
|
|
678
|
+
|
|
679
|
+
**`npm run report:html`** — generate HTML dashboard from last audit:
|
|
538
680
|
|
|
539
|
-
|
|
681
|
+
```bash
|
|
682
|
+
npm run report:html
|
|
683
|
+
# → reports/report.html (all findings + inline screenshots, portable, no server needed)
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
---
|
|
540
687
|
|
|
541
|
-
### Option D
|
|
688
|
+
### Option D — From Slack (on-demand)
|
|
542
689
|
|
|
543
690
|
```text
|
|
544
691
|
/argus-retest https://staging.yourapp.com/checkout
|
|
545
692
|
```
|
|
546
693
|
|
|
547
|
-
BugBot responds immediately, runs the test, and posts results back
|
|
694
|
+
BugBot responds immediately, runs the test, and posts results back. Detailed bug reports go to `#bugs-critical`. See [Slack Slash Command Setup](#slack-slash-command-setup) for configuration.
|
|
548
695
|
|
|
549
696
|
---
|
|
550
697
|
|
|
@@ -796,11 +943,11 @@ argus/
|
|
|
796
943
|
│ └── README.md # Setup guide, Supabase SQL schema, env vars, deployment
|
|
797
944
|
├── scripts/
|
|
798
945
|
│ └── dispatch-report.js # Standalone Slack re-dispatch script (re-posts last report.json to Slack)
|
|
799
|
-
├── test-harness/ # Fixture server + test runner (
|
|
946
|
+
├── test-harness/ # Fixture server + test runner (83 blocks, 360 hard assertions, 54 fixture pages)
|
|
800
947
|
│ ├── README.md
|
|
801
948
|
│ ├── server.js # Express fixture server (ports 3100 dev / 3101 staging)
|
|
802
949
|
│ ├── harness-config.js # Route definitions + expected findings
|
|
803
|
-
│ ├── validate.js # Test runner —
|
|
950
|
+
│ ├── validate.js # Test runner — 83 numbered blocks ([80] MCP server, [81] createFinding, [82] withRetry, [83] watch dashboard)
|
|
804
951
|
│ ├── pages/ # 54 fixture pages (one per detection category)
|
|
805
952
|
│ ├── nextjs-fixture/ # Next.js app structure for C3 discovery tests (10 files)
|
|
806
953
|
│ ├── source-fixture/ # Minimal app.js for C1 codebase-analyzer tests (env var audit)
|
|
@@ -841,7 +988,7 @@ argus/
|
|
|
841
988
|
|
|
842
989
|
## Known MCP Tool Limitations
|
|
843
990
|
|
|
844
|
-
The Chrome DevTools MCP behavioral constraints below cause **3 permanent test failures** in the harness (`
|
|
991
|
+
The Chrome DevTools MCP behavioral constraints below cause **3 permanent test failures** in the harness (`357/360` pass). These are MCP-layer restrictions — they cannot be fixed in Argus code.
|
|
845
992
|
|
|
846
993
|
> **`type_text` clarification**: `type_text` does fire DOM `input` events when the element is properly focused first with `mcp.click({ uid })`. Always use uid-based focus — passing `{ selector }` to `mcp.click` silently does nothing.
|
|
847
994
|
|
|
@@ -874,7 +1021,7 @@ These constraints are documented with workarounds in [SKILL.md §10](SKILL.md).
|
|
|
874
1021
|
| `ARGUS_RETRY_ATTEMPTS` | No | Max retry attempts for `navigate`/`fill` MCP calls (default: `3`) |
|
|
875
1022
|
| `OTEL_EXPORTER_OTLP_ENDPOINT` | No | OTLP collector endpoint — enables span/metric export to Jaeger, Grafana Tempo, Datadog, etc. |
|
|
876
1023
|
| `ARGUS_OTEL_CONSOLE` | No | Set to `1` to print OTel spans to stdout without an OTLP endpoint (dev tracing) |
|
|
877
|
-
| `ARGUS_WATCH_INTERVAL_MS` | No | Watch mode poll interval in milliseconds (default: `
|
|
1024
|
+
| `ARGUS_WATCH_INTERVAL_MS` | No | Watch mode poll interval in milliseconds (default: `1000`) |
|
|
878
1025
|
| `ARGUS_SOURCE_DIR` | No | Path to your app's source directory — enables codebase cross-reference (env var detection, feature flag leakage, dead routes) |
|
|
879
1026
|
| `ARGUS_ENV_FILE` | No | Path to your app's `.env` file — C1 cross-references env vars used in source code against this file to detect missing declarations |
|
|
880
1027
|
| `GITHUB_TOKEN` | No | GitHub personal access token — required for PR comment + commit status integration |
|
package/package.json
CHANGED
package/src/mcp-server.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Argus MCP Server (v9.
|
|
3
|
+
* Argus MCP Server (v9.3.1)
|
|
4
4
|
*
|
|
5
5
|
* Exposes Argus as an MCP server so Claude (or any MCP client) can call
|
|
6
|
-
* argus_audit, argus_audit_full, argus_compare, and
|
|
7
|
-
* directly from a conversation without using the CLI.
|
|
6
|
+
* argus_audit, argus_audit_full, argus_compare, argus_last_report, and
|
|
7
|
+
* argus_get_context (fix loop) directly from a conversation without using the CLI.
|
|
8
8
|
*
|
|
9
9
|
* Architecture: MCP-inside-MCP
|
|
10
10
|
* Claude (MCP client)
|
|
@@ -33,6 +33,18 @@ import { CdpBrowserAdapter } from './adapters/browser.js';
|
|
|
33
33
|
|
|
34
34
|
const REPORTS_DIR = path.resolve(process.cwd(), 'reports');
|
|
35
35
|
|
|
36
|
+
// Fix loop: stores up to 20 snapshots keyed by snapshot_id so argus_get_context
|
|
37
|
+
// can diff before/after a fix. Keys are evicted oldest-first when the limit is hit.
|
|
38
|
+
const snapshotStore = new Map();
|
|
39
|
+
const MAX_SNAPSHOTS = 20;
|
|
40
|
+
|
|
41
|
+
function storeSnapshot(id, findings) {
|
|
42
|
+
snapshotStore.set(id, findings);
|
|
43
|
+
if (snapshotStore.size > MAX_SNAPSHOTS) {
|
|
44
|
+
snapshotStore.delete(snapshotStore.keys().next().value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
// ── Tool definitions ─────────────────────────────────────────────────────────
|
|
37
49
|
|
|
38
50
|
const TOOLS = [
|
|
@@ -82,11 +94,12 @@ const TOOLS = [
|
|
|
82
94
|
},
|
|
83
95
|
{
|
|
84
96
|
name: 'argus_get_context',
|
|
85
|
-
description: 'Captures everything currently broken on the open Chrome tab and formats it as a diagnostic context for Claude to read and suggest fixes. Does NOT navigate — reads the live tab state after user interactions, in authenticated sessions, or mid-flow. Returns { summary, url, timestamp, critical_issues, warnings, js_errors, network_failures, console_errors, recent_requests }
|
|
97
|
+
description: 'Captures everything currently broken on the open Chrome tab and formats it as a diagnostic context for Claude to read and suggest fixes. Does NOT navigate — reads the live tab state after user interactions, in authenticated sessions, or mid-flow. Returns { snapshot_id, summary, url, timestamp, critical_issues, warnings, js_errors, network_failures, console_errors, recent_requests }. Fix loop: pass the snapshot_id from a previous call as snapshot_id to get a diff — the response will include resolved (cleared since last snapshot), new_issues (appeared since last snapshot), and persisting (unchanged). Workflow: call argus_get_context → Claude suggests fix → apply fix → call argus_get_context with snapshot_id → verify resolved array is non-empty. Requires Chrome on --remote-debugging-port=9222.',
|
|
86
98
|
inputSchema: {
|
|
87
99
|
type: 'object',
|
|
88
100
|
properties: {
|
|
89
101
|
url: { type: 'string', description: 'Optional base URL to attribute findings to (default: TARGET_DEV_URL env var). Does not navigate — inspects the currently open Chrome tab.' },
|
|
102
|
+
snapshot_id: { type: 'string', description: 'Optional snapshot_id from a previous argus_get_context call. When provided, the response includes resolved/new_issues/persisting arrays showing what changed since that snapshot.' },
|
|
90
103
|
},
|
|
91
104
|
},
|
|
92
105
|
},
|
|
@@ -143,26 +156,56 @@ async function handleWatchSnapshot({ url } = {}) {
|
|
|
143
156
|
});
|
|
144
157
|
}
|
|
145
158
|
|
|
146
|
-
async function handleGetContext({ url } = {}) {
|
|
159
|
+
async function handleGetContext({ url, snapshot_id: prevId } = {}) {
|
|
147
160
|
return withMcp(async (mcp) => {
|
|
148
161
|
const browser = new CdpBrowserAdapter(mcp);
|
|
149
162
|
const baseUrl = url ?? process.env.TARGET_DEV_URL ?? 'http://localhost:3000';
|
|
150
163
|
const session = new WatchSession(browser, baseUrl);
|
|
151
164
|
const { findings, newConsole, newNetwork } = await session.poll();
|
|
152
165
|
|
|
166
|
+
const newId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
|
167
|
+
storeSnapshot(newId, findings);
|
|
168
|
+
|
|
153
169
|
const critical = findings.filter(f => f.severity === 'critical');
|
|
154
170
|
const warnings = findings.filter(f => f.severity === 'warning');
|
|
155
171
|
|
|
172
|
+
const findingKey = (f) => `${f.type}::${(f.message ?? '').slice(0, 120)}`;
|
|
173
|
+
|
|
174
|
+
let resolved = [];
|
|
175
|
+
let persisting = [];
|
|
176
|
+
let new_issues = findings;
|
|
177
|
+
const isDiff = prevId && snapshotStore.has(prevId);
|
|
178
|
+
|
|
179
|
+
if (isDiff) {
|
|
180
|
+
const prev = snapshotStore.get(prevId);
|
|
181
|
+
const prevKeys = new Set(prev.map(findingKey));
|
|
182
|
+
const curKeys = new Set(findings.map(findingKey));
|
|
183
|
+
resolved = prev.filter(f => !curKeys.has(findingKey(f)));
|
|
184
|
+
persisting = findings.filter(f => prevKeys.has(findingKey(f)));
|
|
185
|
+
new_issues = findings.filter(f => !prevKeys.has(findingKey(f)));
|
|
186
|
+
}
|
|
187
|
+
|
|
156
188
|
let summary;
|
|
157
|
-
if (
|
|
189
|
+
if (isDiff) {
|
|
190
|
+
if (resolved.length > 0 && critical.length === 0 && warnings.length === 0) {
|
|
191
|
+
summary = `All issues resolved on ${baseUrl}. ${resolved.length} finding${resolved.length > 1 ? 's' : ''} cleared since last snapshot.`;
|
|
192
|
+
} else if (resolved.length > 0) {
|
|
193
|
+
summary = `${resolved.length} issue${resolved.length > 1 ? 's' : ''} resolved on ${baseUrl}. ${new_issues.length} new + ${persisting.length} persisting (${critical.length} critical). Pass the new snapshot_id to continue the fix loop.`;
|
|
194
|
+
} else if (critical.length === 0 && warnings.length === 0) {
|
|
195
|
+
summary = `No issues on ${baseUrl} — console and network are clean.`;
|
|
196
|
+
} else {
|
|
197
|
+
summary = `No change on ${baseUrl}: ${critical.length} critical + ${warnings.length} warning${warnings.length !== 1 ? 's' : ''} still present. Check the persisting array for what hasn't been fixed.`;
|
|
198
|
+
}
|
|
199
|
+
} else if (critical.length === 0 && warnings.length === 0) {
|
|
158
200
|
summary = `No issues detected on ${baseUrl} — console and network are clean.`;
|
|
159
201
|
} else if (critical.length > 0) {
|
|
160
|
-
summary = `${critical.length} critical issue${critical.length > 1 ? 's' : ''} + ${warnings.length} warning${warnings.length !== 1 ? 's' : ''} detected on ${baseUrl}. Focus on critical issues first.`;
|
|
202
|
+
summary = `${critical.length} critical issue${critical.length > 1 ? 's' : ''} + ${warnings.length} warning${warnings.length !== 1 ? 's' : ''} detected on ${baseUrl}. Focus on critical issues first. Pass snapshot_id to the next argus_get_context call after applying a fix to see what changed.`;
|
|
161
203
|
} else {
|
|
162
|
-
summary = `${warnings.length} warning${warnings.length !== 1 ? 's' : ''} detected on ${baseUrl}. No critical errors.`;
|
|
204
|
+
summary = `${warnings.length} warning${warnings.length !== 1 ? 's' : ''} detected on ${baseUrl}. No critical errors. Pass snapshot_id to the next call to verify fixes.`;
|
|
163
205
|
}
|
|
164
206
|
|
|
165
207
|
const context = {
|
|
208
|
+
snapshot_id: newId,
|
|
166
209
|
summary,
|
|
167
210
|
url: baseUrl,
|
|
168
211
|
timestamp: new Date().toISOString(),
|
|
@@ -172,6 +215,7 @@ async function handleGetContext({ url } = {}) {
|
|
|
172
215
|
network_failures: findings.filter(f => f.type === 'network-error' || f.type === 'cors-error' || f.type === 'auth-error'),
|
|
173
216
|
console_errors: newConsole.filter(m => m.level === 'error' || m.level === 'warning'),
|
|
174
217
|
recent_requests: newNetwork.slice(-20),
|
|
218
|
+
...(isDiff ? { resolved, new_issues, persisting } : {}),
|
|
175
219
|
};
|
|
176
220
|
|
|
177
221
|
return { content: [{ type: 'text', text: JSON.stringify(context, null, 2) }] };
|
|
@@ -196,7 +240,7 @@ async function handleLastReport() {
|
|
|
196
240
|
// ── Server bootstrap ──────────────────────────────────────────────────────────
|
|
197
241
|
|
|
198
242
|
const server = new Server(
|
|
199
|
-
{ name: 'argus', version: '9.
|
|
243
|
+
{ name: 'argus', version: '9.3.1' },
|
|
200
244
|
{ capabilities: { tools: {} } },
|
|
201
245
|
);
|
|
202
246
|
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
* individual poll() calls without running the interval loop.
|
|
14
14
|
*
|
|
15
15
|
* Environment variables:
|
|
16
|
-
* ARGUS_WATCH_INTERVAL_MS — poll interval in ms (default:
|
|
16
|
+
* ARGUS_WATCH_INTERVAL_MS — poll interval in ms (default: 1000)
|
|
17
17
|
* TARGET_DEV_URL — base URL to monitor (default: http://localhost:3000)
|
|
18
|
+
* ARGUS_WATCH_UI_PORT — port for the live web dashboard (default: 3002)
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
import fs from 'fs';
|
|
21
22
|
import path from 'path';
|
|
23
|
+
import http from 'http';
|
|
22
24
|
import { fileURLToPath } from 'url';
|
|
23
25
|
import 'dotenv/config';
|
|
24
26
|
import { createMcpClient } from '../utils/mcp-client.js';
|
|
@@ -41,6 +43,161 @@ import {
|
|
|
41
43
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
42
44
|
const REPORTS_DIR = path.resolve(__dirname, '../../reports');
|
|
43
45
|
|
|
46
|
+
// ── Live dashboard ─────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
49
|
+
<html lang="en">
|
|
50
|
+
<head>
|
|
51
|
+
<meta charset="UTF-8">
|
|
52
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
53
|
+
<title>Argus Watch Dashboard</title>
|
|
54
|
+
<style>
|
|
55
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
56
|
+
body { background: #0d0d0d; color: #e0e0e0; font-family: 'Segoe UI', system-ui, sans-serif; min-height: 100vh; }
|
|
57
|
+
header { background: #111; border-bottom: 1px solid #1e1e1e; padding: 14px 24px; display: flex; align-items: center; gap: 12px; }
|
|
58
|
+
.pulse-dot { width: 10px; height: 10px; border-radius: 50%; background: #5E0ED7; flex-shrink: 0;
|
|
59
|
+
animation: pulse 1.8s ease-in-out infinite; }
|
|
60
|
+
@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.4;transform:scale(1.3)} }
|
|
61
|
+
.header-text { flex: 1; }
|
|
62
|
+
.header-text h1 { font-size: 15px; font-weight: 600; color: #fff; letter-spacing: .02em; }
|
|
63
|
+
.header-text small { font-size: 11px; color: #666; }
|
|
64
|
+
.pills { display: flex; gap: 8px; flex-wrap: wrap; padding: 16px 24px; }
|
|
65
|
+
.pill { font-size: 12px; font-weight: 600; padding: 4px 12px; border-radius: 999px; letter-spacing: .04em; }
|
|
66
|
+
.pill-critical { background: #3a0e0e; color: #f87171; border: 1px solid #7f1d1d; }
|
|
67
|
+
.pill-warning { background: #2e2200; color: #fbbf24; border: 1px solid #78350f; }
|
|
68
|
+
.pill-info { background: #1a0b33; color: #a78bfa; border: 1px solid #4c1d95; }
|
|
69
|
+
.pill-clear { background: #0b2718; color: #34d399; border: 1px solid #064e3b; }
|
|
70
|
+
.status-bar { font-size: 11px; padding: 4px 24px 12px; color: #555; }
|
|
71
|
+
.status-bar.error { color: #f87171; }
|
|
72
|
+
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
73
|
+
thead th { background: #161616; color: #888; font-weight: 600; text-align: left;
|
|
74
|
+
padding: 10px 16px; border-bottom: 1px solid #222; font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }
|
|
75
|
+
tbody tr { border-bottom: 1px solid #181818; transition: background .15s; }
|
|
76
|
+
tbody tr:hover { background: #151515; }
|
|
77
|
+
td { padding: 10px 16px; vertical-align: top; }
|
|
78
|
+
.sev { font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 4px; white-space: nowrap; }
|
|
79
|
+
.sev-critical { background: #3a0e0e; color: #f87171; }
|
|
80
|
+
.sev-warning { background: #2e2200; color: #fbbf24; }
|
|
81
|
+
.sev-info { background: #1a0b33; color: #a78bfa; }
|
|
82
|
+
.type-cell { color: #9ca3af; font-size: 12px; font-family: monospace; white-space: nowrap; }
|
|
83
|
+
.msg-cell { color: #d1d5db; word-break: break-word; max-width: 480px; }
|
|
84
|
+
.empty-row td { text-align: center; color: #444; padding: 40px; font-size: 13px; }
|
|
85
|
+
.table-wrap { padding: 0 24px 24px; overflow-x: auto; }
|
|
86
|
+
</style>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<header>
|
|
90
|
+
<div class="pulse-dot" id="dot"></div>
|
|
91
|
+
<div class="header-text">
|
|
92
|
+
<h1 id="target">Argus Watch</h1>
|
|
93
|
+
<small id="lastPoll">Connecting…</small>
|
|
94
|
+
</div>
|
|
95
|
+
</header>
|
|
96
|
+
<div class="pills" id="pills"></div>
|
|
97
|
+
<div class="status-bar" id="status"></div>
|
|
98
|
+
<div class="table-wrap">
|
|
99
|
+
<table>
|
|
100
|
+
<thead><tr><th>Severity</th><th>Type</th><th>Message</th></tr></thead>
|
|
101
|
+
<tbody id="tbody"></tbody>
|
|
102
|
+
</table>
|
|
103
|
+
</div>
|
|
104
|
+
<script>
|
|
105
|
+
const SEV_ORDER = { critical: 0, warning: 1, info: 2 };
|
|
106
|
+
|
|
107
|
+
function renderPills(findings) {
|
|
108
|
+
const counts = { critical: 0, warning: 0, info: 0 };
|
|
109
|
+
for (const f of findings) counts[f.severity] = (counts[f.severity] || 0) + 1;
|
|
110
|
+
const pills = document.getElementById('pills');
|
|
111
|
+
if (findings.length === 0) {
|
|
112
|
+
pills.innerHTML = '<span class="pill pill-clear">All clear</span>';
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let html = '';
|
|
116
|
+
if (counts.critical) html += \`<span class="pill pill-critical">\${counts.critical} Critical</span>\`;
|
|
117
|
+
if (counts.warning) html += \`<span class="pill pill-warning">\${counts.warning} Warning</span>\`;
|
|
118
|
+
if (counts.info) html += \`<span class="pill pill-info">\${counts.info} Info</span>\`;
|
|
119
|
+
pills.innerHTML = html;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function renderTable(findings) {
|
|
123
|
+
const sorted = [...findings].sort((a, b) =>
|
|
124
|
+
(SEV_ORDER[a.severity] ?? 3) - (SEV_ORDER[b.severity] ?? 3));
|
|
125
|
+
const tbody = document.getElementById('tbody');
|
|
126
|
+
if (sorted.length === 0) {
|
|
127
|
+
tbody.innerHTML = '<tr class="empty-row"><td colspan="3">No findings yet</td></tr>';
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
tbody.innerHTML = sorted.map(f => {
|
|
131
|
+
const sc = 'sev-' + (f.severity || 'info');
|
|
132
|
+
const msg = (f.message || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
133
|
+
const typ = (f.type || '').replace(/&/g,'&');
|
|
134
|
+
return \`<tr>
|
|
135
|
+
<td><span class="sev \${sc}">\${f.severity ?? 'info'}</span></td>
|
|
136
|
+
<td class="type-cell">\${typ}</td>
|
|
137
|
+
<td class="msg-cell">\${msg}</td>
|
|
138
|
+
</tr>\`;
|
|
139
|
+
}).join('');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function poll() {
|
|
143
|
+
try {
|
|
144
|
+
const res = await fetch('/data');
|
|
145
|
+
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
146
|
+
const data = await res.json();
|
|
147
|
+
document.getElementById('target').textContent = 'Argus Watch — ' + (data.target || '');
|
|
148
|
+
document.getElementById('lastPoll').textContent = 'Last poll: ' + new Date(data.lastPoll).toLocaleTimeString();
|
|
149
|
+
document.getElementById('dot').style.background = '#5E0ED7';
|
|
150
|
+
document.getElementById('status').textContent = '';
|
|
151
|
+
document.getElementById('status').className = 'status-bar';
|
|
152
|
+
renderPills(data.findings || []);
|
|
153
|
+
renderTable(data.findings || []);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
document.getElementById('status').textContent = 'Connection lost — ' + e.message;
|
|
156
|
+
document.getElementById('status').className = 'status-bar error';
|
|
157
|
+
document.getElementById('dot').style.background = '#555';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
poll();
|
|
162
|
+
setInterval(poll, 2000);
|
|
163
|
+
</script>
|
|
164
|
+
</body>
|
|
165
|
+
</html>`;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Start the live web dashboard HTTP server.
|
|
169
|
+
*
|
|
170
|
+
* @param {() => object[]} getFindings — callback that returns current findings array
|
|
171
|
+
* @param {string} target — the URL being monitored (shown in header)
|
|
172
|
+
* @param {number} port — TCP port to listen on (default 3002)
|
|
173
|
+
* @returns {http.Server}
|
|
174
|
+
*/
|
|
175
|
+
function startDashboard(getFindings, target, port) {
|
|
176
|
+
const server = http.createServer((req, res) => {
|
|
177
|
+
if (req.url === '/data' || req.url?.startsWith('/data?')) {
|
|
178
|
+
const payload = JSON.stringify({
|
|
179
|
+
target,
|
|
180
|
+
lastPoll: new Date().toISOString(),
|
|
181
|
+
findings: getFindings(),
|
|
182
|
+
});
|
|
183
|
+
res.writeHead(200, {
|
|
184
|
+
'Content-Type': 'application/json',
|
|
185
|
+
'Access-Control-Allow-Origin': '*',
|
|
186
|
+
});
|
|
187
|
+
res.end(payload);
|
|
188
|
+
} else {
|
|
189
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
190
|
+
res.end(DASHBOARD_HTML);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
server.listen(port, '127.0.0.1', () => {
|
|
195
|
+
logger.info(`[ARGUS WATCH] Dashboard → http://localhost:${port}`);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return server;
|
|
199
|
+
}
|
|
200
|
+
|
|
44
201
|
// ── Deduplication key generators ───────────────────────────────────────────────
|
|
45
202
|
// Two messages/requests are considered "the same" if their keys match. This
|
|
46
203
|
// prevents re-reporting errors that were already captured in a previous poll.
|
|
@@ -217,7 +374,7 @@ export class WatchSession {
|
|
|
217
374
|
*/
|
|
218
375
|
export async function runWatchMode(baseUrl) {
|
|
219
376
|
const target = baseUrl ?? process.env.TARGET_DEV_URL ?? 'http://localhost:3000';
|
|
220
|
-
const pollIntervalMs = parseInt(process.env.ARGUS_WATCH_INTERVAL_MS ?? '
|
|
377
|
+
const pollIntervalMs = parseInt(process.env.ARGUS_WATCH_INTERVAL_MS ?? '1000', 10);
|
|
221
378
|
|
|
222
379
|
const mcp = await createMcpClient();
|
|
223
380
|
const browser = new CdpBrowserAdapter(mcp);
|
|
@@ -228,6 +385,9 @@ export async function runWatchMode(baseUrl) {
|
|
|
228
385
|
logger.info(`[ARGUS WATCH] Polling every ${pollIntervalMs}ms. Press Ctrl+C to stop.`);
|
|
229
386
|
logger.info('[ARGUS WATCH] ─────────────────────────────────────────────────\n');
|
|
230
387
|
|
|
388
|
+
const uiPort = parseInt(process.env.ARGUS_WATCH_UI_PORT ?? '3002', 10);
|
|
389
|
+
const dashServer = startDashboard(() => session.getAllFindings(), target, uiPort);
|
|
390
|
+
|
|
231
391
|
const badge = (severity) =>
|
|
232
392
|
severity === 'critical' ? '✗ CRIT' :
|
|
233
393
|
severity === 'warning' ? '! WARN' : 'i INFO';
|
|
@@ -270,6 +430,7 @@ export async function runWatchMode(baseUrl) {
|
|
|
270
430
|
|
|
271
431
|
process.on('SIGINT', async () => {
|
|
272
432
|
clearInterval(interval);
|
|
433
|
+
try { dashServer.close(); } catch { /* ignore */ }
|
|
273
434
|
const all = session.getAllFindings();
|
|
274
435
|
|
|
275
436
|
logger.info(`\n[ARGUS WATCH] Stopped. Total findings: ${all.length}`);
|