@rvanbaalen/roxyproxy 0.1.3 → 0.1.5
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 +169 -7
- package/dist/cli/banner.d.ts +1 -0
- package/dist/cli/banner.js +6 -0
- package/dist/cli/banner.js.map +1 -1
- package/dist/cli/commands/requests.js +150 -4
- package/dist/cli/commands/requests.js.map +1 -1
- package/dist/cli/format.d.ts +10 -0
- package/dist/cli/format.js +55 -12
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/tail-ui.d.ts +3 -0
- package/dist/cli/tail-ui.js +322 -0
- package/dist/cli/tail-ui.js.map +1 -0
- package/dist/server/api.d.ts +2 -1
- package/dist/server/api.js +19 -1
- package/dist/server/api.js.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/storage/db.js +7 -1
- package/dist/storage/db.js.map +1 -1
- package/dist/ui/assets/{index-C8qts74N.js → index-Bf9gaCxG.js} +2 -1
- package/dist/ui/assets/index-CEm91IVN.css +2 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-CrfXQK5U.css +0 -2
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
HTTP/HTTPS intercepting proxy with a CLI and web UI.<br>
|
|
7
|
-
Captures traffic, stores it in SQLite, and makes it queryable -- by humans and LLMs alike
|
|
7
|
+
Captures traffic, stores it in SQLite, and makes it queryable -- by humans and LLMs alike.<br>
|
|
8
|
+
<strong>Developed and tested on macOS.</strong>
|
|
8
9
|
</p>
|
|
9
10
|
|
|
10
11
|
<p align="center">
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
|
|
14
15
|
## Table of Contents
|
|
15
16
|
|
|
17
|
+
- [Platform](#platform)
|
|
16
18
|
- [Installation](#installation)
|
|
17
19
|
- [Claude Code Plugin](#claude-code-plugin)
|
|
18
20
|
- [Quick Start](#quick-start)
|
|
@@ -30,6 +32,7 @@
|
|
|
30
32
|
- [proxy-off](#proxy-off-macos)
|
|
31
33
|
- [Web UI](#web-ui)
|
|
32
34
|
- [HTTPS Interception](#https-interception)
|
|
35
|
+
- [iOS Device Inspection](#ios-device-inspection)
|
|
33
36
|
- [System Proxy](#system-proxy-macos)
|
|
34
37
|
- [Configuration](#configuration)
|
|
35
38
|
- [REST API](#rest-api)
|
|
@@ -38,6 +41,14 @@
|
|
|
38
41
|
|
|
39
42
|
---
|
|
40
43
|
|
|
44
|
+
## Platform
|
|
45
|
+
|
|
46
|
+
RoxyProxy is developed and tested on **macOS**. Core proxy and query functionality works on Linux, but the following features are macOS-only:
|
|
47
|
+
|
|
48
|
+
- **System proxy** (`proxy-on` / `proxy-off`) -- uses `networksetup`
|
|
49
|
+
- **Auto-enable system proxy** with `--tail` -- routes all macOS traffic through the proxy automatically
|
|
50
|
+
- **CA trust** (`trust-ca`) -- uses macOS Keychain; Linux support exists but is less tested
|
|
51
|
+
|
|
41
52
|
## Installation
|
|
42
53
|
|
|
43
54
|
Run directly without installing:
|
|
@@ -93,6 +104,20 @@ Just ask Claude anything about intercepting or inspecting HTTP traffic and it wi
|
|
|
93
104
|
|
|
94
105
|
## Quick Start
|
|
95
106
|
|
|
107
|
+
The fastest way to start capturing traffic:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# One command: starts proxy, enables system proxy, opens interactive TUI
|
|
111
|
+
roxyproxy requests --tail
|
|
112
|
+
|
|
113
|
+
# Filter for a specific host
|
|
114
|
+
roxyproxy requests --host api.example.com --tail
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This auto-starts the proxy, routes macOS system traffic through it, and opens a live terminal UI. Press Ctrl+C to quit -- the system proxy is automatically disabled.
|
|
118
|
+
|
|
119
|
+
### Manual setup
|
|
120
|
+
|
|
96
121
|
```bash
|
|
97
122
|
# Start the proxy (default: proxy on :8080, web UI on :8081)
|
|
98
123
|
roxyproxy start
|
|
@@ -101,7 +126,7 @@ roxyproxy start
|
|
|
101
126
|
curl -x http://127.0.0.1:8080 http://httpbin.org/get
|
|
102
127
|
|
|
103
128
|
# View captured traffic
|
|
104
|
-
roxyproxy requests
|
|
129
|
+
roxyproxy requests
|
|
105
130
|
|
|
106
131
|
# Open the web UI
|
|
107
132
|
open http://127.0.0.1:8081
|
|
@@ -235,10 +260,12 @@ roxyproxy requests [options]
|
|
|
235
260
|
| `--since <time>` | | After this time (Unix ms or ISO date) |
|
|
236
261
|
| `--until <time>` | | Before this time (Unix ms or ISO date) |
|
|
237
262
|
| `--limit <n>` | `100` | Maximum number of results |
|
|
238
|
-
| `--format <format>` | `
|
|
263
|
+
| `--format <format>` | `table` | Output format: `table` (default) or `json` |
|
|
264
|
+
| `--tail` | | Stream new requests in real-time (interactive TUI) |
|
|
265
|
+
| `--ui-port <number>` | `8081` | UI/API port (used with `--tail`) |
|
|
239
266
|
| `--db-path <path>` | `~/.roxyproxy/data.db` | Database location |
|
|
240
267
|
|
|
241
|
-
The default
|
|
268
|
+
The default output is a human-readable table. Use `--format json` for piping to `jq` or feeding to LLMs:
|
|
242
269
|
|
|
243
270
|
```bash
|
|
244
271
|
# All 500 errors
|
|
@@ -250,16 +277,51 @@ roxyproxy requests --host api.example.com --method POST
|
|
|
250
277
|
# Search URLs
|
|
251
278
|
roxyproxy requests --search "/api/v2"
|
|
252
279
|
|
|
253
|
-
#
|
|
280
|
+
# Limit results
|
|
254
281
|
roxyproxy requests --format table --limit 20
|
|
255
282
|
|
|
256
283
|
# Time-bounded query
|
|
257
284
|
roxyproxy requests --since "2024-01-15T00:00:00Z" --until "2024-01-16T00:00:00Z"
|
|
258
285
|
|
|
259
|
-
#
|
|
260
|
-
roxyproxy requests --host stripe.com | jq '.data[].url'
|
|
286
|
+
# JSON for piping to jq
|
|
287
|
+
roxyproxy requests --format json --host stripe.com | jq '.data[].url'
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### Real-time tailing
|
|
291
|
+
|
|
292
|
+
The `--tail` flag launches an interactive terminal UI that streams new requests as they arrive:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# Tail all traffic (auto-starts proxy + system proxy if needed)
|
|
296
|
+
roxyproxy requests --tail
|
|
297
|
+
|
|
298
|
+
# Tail with filters
|
|
299
|
+
roxyproxy requests --host todoist.com --tail
|
|
300
|
+
roxyproxy requests --status 500 --tail
|
|
301
|
+
roxyproxy requests --method POST --host api.example.com --tail
|
|
261
302
|
```
|
|
262
303
|
|
|
304
|
+
**What `--tail` does automatically:**
|
|
305
|
+
|
|
306
|
+
1. **Starts the proxy** if it isn't already running
|
|
307
|
+
2. **Enables the macOS system proxy** so all traffic routes through RoxyProxy
|
|
308
|
+
3. Opens an interactive TUI with arrow-key navigation
|
|
309
|
+
4. On quit (Ctrl+C), **disables the system proxy** and stops the proxy it started
|
|
310
|
+
|
|
311
|
+
**TUI keyboard shortcuts:**
|
|
312
|
+
|
|
313
|
+
| Key | Action |
|
|
314
|
+
|---|---|
|
|
315
|
+
| `↑` / `↓` | Navigate requests |
|
|
316
|
+
| `Enter` | View full request detail (headers, body) |
|
|
317
|
+
| `Esc` | Back to list from detail view |
|
|
318
|
+
| `g` / `G` | Jump to top (newest) / bottom (oldest) |
|
|
319
|
+
| `Ctrl+C` | Quit (cleans up proxy and system proxy) |
|
|
320
|
+
|
|
321
|
+
New requests auto-scroll to the top. Scrolling down disables auto-scroll; pressing `g` re-enables it.
|
|
322
|
+
|
|
323
|
+
To get raw JSON streaming instead of the TUI, use `--format json --tail`.
|
|
324
|
+
|
|
263
325
|
### request
|
|
264
326
|
|
|
265
327
|
Show full details of a single captured request, including headers and bodies.
|
|
@@ -455,6 +517,96 @@ roxyproxy proxy-on
|
|
|
455
517
|
|
|
456
518
|
---
|
|
457
519
|
|
|
520
|
+
## iOS Device Inspection
|
|
521
|
+
|
|
522
|
+
RoxyProxy can inspect HTTP/HTTPS traffic from an iOS device. Your computer and iOS device must be on the same Wi-Fi network.
|
|
523
|
+
|
|
524
|
+
### Setup
|
|
525
|
+
|
|
526
|
+
**Step 1: Start RoxyProxy on your computer**
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
roxyproxy start
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**Step 2: Note your computer's network address**
|
|
533
|
+
|
|
534
|
+
The CLI prints a `Network` line on startup with your hostname, e.g.:
|
|
535
|
+
|
|
536
|
+
```
|
|
537
|
+
● Network http://robins-macbook.local:8081
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
You can also find your IP manually:
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
ipconfig getifaddr en0 # macOS
|
|
544
|
+
hostname -I | awk '{print $1}' # Linux
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
**Step 3: Configure the iOS device to use the proxy**
|
|
548
|
+
|
|
549
|
+
1. Open **Settings > Wi-Fi**
|
|
550
|
+
2. Tap the **(i)** icon next to your connected network
|
|
551
|
+
3. Scroll down and tap **Configure Proxy**
|
|
552
|
+
4. Select **Manual**
|
|
553
|
+
5. Set **Server** to your computer's hostname or IP (e.g., `robins-macbook.local` or `192.168.1.42`)
|
|
554
|
+
6. Set **Port** to `8080`
|
|
555
|
+
7. Tap **Save**
|
|
556
|
+
|
|
557
|
+
HTTP traffic is now being captured. For HTTPS inspection, continue below.
|
|
558
|
+
|
|
559
|
+
**Step 4: Install the CA certificate on iOS**
|
|
560
|
+
|
|
561
|
+
Open Safari on your iOS device and navigate to the network address shown in the CLI or web UI:
|
|
562
|
+
|
|
563
|
+
```
|
|
564
|
+
http://robins-macbook.local:8081/api/ca.crt
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
Or use the IP directly: `http://192.168.1.42:8081/api/ca.crt`
|
|
568
|
+
|
|
569
|
+
You can also open the web UI (`http://robins-macbook.local:8081`) and tap the **CA Cert** link in the toolbar.
|
|
570
|
+
|
|
571
|
+
Safari will prompt you to download a configuration profile. Tap **Allow**.
|
|
572
|
+
|
|
573
|
+
**Step 5: Install the profile**
|
|
574
|
+
|
|
575
|
+
1. Open **Settings > General > VPN & Device Management** (or **Profiles & Device Management** on older iOS)
|
|
576
|
+
2. Tap the **RoxyProxy CA** profile
|
|
577
|
+
3. Tap **Install** and enter your passcode
|
|
578
|
+
|
|
579
|
+
**Step 6: Enable full trust for the certificate**
|
|
580
|
+
|
|
581
|
+
1. Open **Settings > General > About > Certificate Trust Settings**
|
|
582
|
+
2. Toggle **Enable Full Trust** for **RoxyProxy CA**
|
|
583
|
+
3. Tap **Continue** on the warning dialog
|
|
584
|
+
|
|
585
|
+
HTTPS traffic from the iOS device is now fully inspectable through RoxyProxy.
|
|
586
|
+
|
|
587
|
+
### Viewing traffic
|
|
588
|
+
|
|
589
|
+
Open the web UI from any browser:
|
|
590
|
+
|
|
591
|
+
```
|
|
592
|
+
http://<your-computer-ip>:8081
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Or use the CLI:
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
roxyproxy requests --tail
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Cleanup
|
|
602
|
+
|
|
603
|
+
When you're done inspecting, remove the proxy from iOS:
|
|
604
|
+
|
|
605
|
+
1. **Settings > Wi-Fi > (i) > Configure Proxy > Off**
|
|
606
|
+
2. Optionally remove the CA profile: **Settings > General > VPN & Device Management > RoxyProxy CA > Remove Profile**
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
458
610
|
## System Proxy (macOS)
|
|
459
611
|
|
|
460
612
|
On macOS, RoxyProxy can configure itself as the system-wide HTTP/HTTPS proxy. This routes all traffic from most applications through the proxy without needing per-app configuration.
|
|
@@ -589,6 +741,14 @@ Response:
|
|
|
589
741
|
}
|
|
590
742
|
```
|
|
591
743
|
|
|
744
|
+
#### `GET /api/ca.crt`
|
|
745
|
+
|
|
746
|
+
Download the RoxyProxy CA certificate. Useful for installing on mobile devices -- open this URL in the device's browser to trigger a certificate install prompt.
|
|
747
|
+
|
|
748
|
+
```bash
|
|
749
|
+
curl -O http://127.0.0.1:8081/api/ca.crt
|
|
750
|
+
```
|
|
751
|
+
|
|
592
752
|
#### `POST /api/proxy/start`
|
|
593
753
|
|
|
594
754
|
Start the proxy server.
|
|
@@ -710,6 +870,8 @@ src/
|
|
|
710
870
|
├── cli/ # CLI entry point, commands, interactive mode
|
|
711
871
|
│ ├── index.ts # Command registration (Commander.js)
|
|
712
872
|
│ ├── interactive.tsx # Interactive terminal menu (Ink/React)
|
|
873
|
+
│ ├── tail-ui.tsx # Real-time tail TUI (Ink/React)
|
|
874
|
+
│ ├── format.ts # Table/JSON output formatting
|
|
713
875
|
│ ├── commands/ # Individual CLI commands
|
|
714
876
|
│ └── system-proxy.ts # macOS system proxy & CA management
|
|
715
877
|
├── server/ # Proxy server, API, SSL, events
|
package/dist/cli/banner.d.ts
CHANGED
package/dist/cli/banner.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
export function getLocalHostname() {
|
|
4
|
+
return os.hostname();
|
|
5
|
+
}
|
|
2
6
|
export function printBanner() {
|
|
3
7
|
console.log('');
|
|
4
8
|
console.log(pc.cyan(' ___ ___ '));
|
|
@@ -9,8 +13,10 @@ export function printBanner() {
|
|
|
9
13
|
console.log('');
|
|
10
14
|
}
|
|
11
15
|
export function printStartInfo(proxyPort, uiPort) {
|
|
16
|
+
const hostname = getLocalHostname();
|
|
12
17
|
console.log(` ${pc.green('●')} Proxy ${pc.cyan(`http://127.0.0.1:${proxyPort}`)}`);
|
|
13
18
|
console.log(` ${pc.green('●')} Web UI ${pc.cyan(`http://127.0.0.1:${uiPort}`)}`);
|
|
19
|
+
console.log(` ${pc.green('●')} Network ${pc.cyan(`http://${hostname}:${uiPort}`)}`);
|
|
14
20
|
console.log('');
|
|
15
21
|
console.log(pc.dim(' Ctrl+C to stop'));
|
|
16
22
|
console.log('');
|
package/dist/cli/banner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"banner.js","sourceRoot":"","sources":["../../src/cli/banner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"banner.js","sourceRoot":"","sources":["../../src/cli/banner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,MAAM,UAAU,gBAAgB;IAC9B,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC,CAAC;IAC3E,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC,CAAC;IAC7E,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,MAAc;IAC9D,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,oBAAoB,SAAS,EAAE,CAAC,EAAE,CAAC,CAAC;IACvF,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,oBAAoB,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IACpF,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,UAAU,QAAQ,IAAI,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IACtF,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACxC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -1,6 +1,50 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
1
5
|
import { Database } from '../../storage/db.js';
|
|
2
6
|
import { loadConfig } from '../../server/config.js';
|
|
3
|
-
import {
|
|
7
|
+
import { RoxyProxyServer } from '../../server/index.js';
|
|
8
|
+
import { formatRequests, formatTailLine } from '../format.js';
|
|
9
|
+
import { enableSystemProxy, disableSystemProxy, checkSystemProxyStatus } from '../system-proxy.js';
|
|
10
|
+
/** Try to reach the API; resolves true if proxy is reachable. */
|
|
11
|
+
function isProxyRunning(port) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const req = http.request({ host: '127.0.0.1', port, path: '/api/status', method: 'GET', timeout: 1000 }, (res) => { res.resume(); resolve(res.statusCode === 200); });
|
|
14
|
+
req.on('error', () => resolve(false));
|
|
15
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
16
|
+
req.end();
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** Start a proxy server, enable system proxy, return instance and ports. */
|
|
20
|
+
async function autoStartProxy(requestedUiPort) {
|
|
21
|
+
const config = loadConfig({ uiPort: requestedUiPort });
|
|
22
|
+
const pidPath = path.join(os.homedir(), '.roxyproxy', 'pid');
|
|
23
|
+
fs.mkdirSync(path.dirname(pidPath), { recursive: true });
|
|
24
|
+
fs.writeFileSync(pidPath, process.pid.toString());
|
|
25
|
+
const server = new RoxyProxyServer(config);
|
|
26
|
+
const ports = await server.start();
|
|
27
|
+
// Enable system proxy so traffic flows through automatically
|
|
28
|
+
let systemProxyEnabled = false;
|
|
29
|
+
const alreadyEnabled = await checkSystemProxyStatus();
|
|
30
|
+
if (!alreadyEnabled) {
|
|
31
|
+
const proxyResult = await enableSystemProxy(String(ports.proxyPort));
|
|
32
|
+
systemProxyEnabled = proxyResult.ok;
|
|
33
|
+
}
|
|
34
|
+
const shutdown = async () => {
|
|
35
|
+
if (systemProxyEnabled)
|
|
36
|
+
await disableSystemProxy();
|
|
37
|
+
await server.stop();
|
|
38
|
+
try {
|
|
39
|
+
fs.unlinkSync(pidPath);
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
process.exit(0);
|
|
43
|
+
};
|
|
44
|
+
process.on('SIGINT', shutdown);
|
|
45
|
+
process.on('SIGTERM', shutdown);
|
|
46
|
+
return { server, uiPort: ports.uiPort, systemProxyEnabled };
|
|
47
|
+
}
|
|
4
48
|
export function registerRequests(program) {
|
|
5
49
|
program
|
|
6
50
|
.command('requests')
|
|
@@ -12,9 +56,51 @@ export function registerRequests(program) {
|
|
|
12
56
|
.option('--since <time>', 'Requests after this time')
|
|
13
57
|
.option('--until <time>', 'Requests before this time')
|
|
14
58
|
.option('--limit <n>', 'Max results', '100')
|
|
15
|
-
.option('--format <format>', 'Output format (json|table)'
|
|
59
|
+
.option('--format <format>', 'Output format (json|table)')
|
|
60
|
+
.option('--tail', 'Stream new requests in real-time')
|
|
61
|
+
.option('--ui-port <number>', 'UI/API port for --tail', '8081')
|
|
16
62
|
.option('--db-path <path>', 'Database path')
|
|
17
|
-
.action((opts) => {
|
|
63
|
+
.action(async (opts) => {
|
|
64
|
+
if (opts.tail) {
|
|
65
|
+
const filter = {};
|
|
66
|
+
if (opts.host)
|
|
67
|
+
filter.host = opts.host;
|
|
68
|
+
if (opts.status)
|
|
69
|
+
filter.status = parseInt(opts.status, 10);
|
|
70
|
+
if (opts.method)
|
|
71
|
+
filter.method = opts.method;
|
|
72
|
+
if (opts.search)
|
|
73
|
+
filter.search = opts.search;
|
|
74
|
+
// Default to table (interactive TUI) when tailing
|
|
75
|
+
const format = opts.format ?? 'table';
|
|
76
|
+
let uiPort = parseInt(opts.uiPort, 10);
|
|
77
|
+
let server = null;
|
|
78
|
+
// Auto-start proxy if not running
|
|
79
|
+
let didEnableSystemProxy = false;
|
|
80
|
+
const running = await isProxyRunning(uiPort);
|
|
81
|
+
if (!running) {
|
|
82
|
+
try {
|
|
83
|
+
const result = await autoStartProxy(uiPort);
|
|
84
|
+
server = result.server;
|
|
85
|
+
uiPort = result.uiPort;
|
|
86
|
+
didEnableSystemProxy = result.systemProxyEnabled;
|
|
87
|
+
console.error(`Proxy started (UI on :${uiPort})${didEnableSystemProxy ? ' — system proxy enabled' : ''}`);
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
console.error(`Failed to start proxy: ${e.message}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (format === 'table') {
|
|
95
|
+
// Launch interactive Ink TUI
|
|
96
|
+
const { launchTailUi } = await import('../tail-ui.js');
|
|
97
|
+
launchTailUi(uiPort, filter, server, didEnableSystemProxy);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// JSON streaming mode (plain stdout)
|
|
101
|
+
tailRequests(uiPort, filter, format);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
18
104
|
const config = loadConfig(opts.dbPath ? { dbPath: opts.dbPath } : {});
|
|
19
105
|
const db = new Database(config.dbPath);
|
|
20
106
|
const filter = {
|
|
@@ -33,10 +119,70 @@ export function registerRequests(program) {
|
|
|
33
119
|
if (opts.until)
|
|
34
120
|
filter.until = parseTime(opts.until);
|
|
35
121
|
const result = db.query(filter);
|
|
36
|
-
console.log(formatRequests(result, opts.format));
|
|
122
|
+
console.log(formatRequests(result, opts.format ?? 'table'));
|
|
37
123
|
db.close();
|
|
38
124
|
});
|
|
39
125
|
}
|
|
126
|
+
function matchesFilter(record, filter) {
|
|
127
|
+
if (filter.host && !record.host?.toLowerCase().includes(filter.host.toLowerCase()))
|
|
128
|
+
return false;
|
|
129
|
+
if (filter.status && record.status !== filter.status)
|
|
130
|
+
return false;
|
|
131
|
+
if (filter.method && record.method?.toUpperCase() !== filter.method.toUpperCase())
|
|
132
|
+
return false;
|
|
133
|
+
if (filter.search && !record.url?.toLowerCase().includes(filter.search.toLowerCase()))
|
|
134
|
+
return false;
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
function tailRequests(port, filter, format) {
|
|
138
|
+
const req = http.request({ host: '127.0.0.1', port, path: '/api/events', method: 'GET' }, (res) => {
|
|
139
|
+
if (res.statusCode !== 200) {
|
|
140
|
+
console.error(`Failed to connect to event stream (status ${res.statusCode}). Is the proxy running?`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
if (format === 'table') {
|
|
144
|
+
console.log(`\n Tailing requests${filter.host ? ` (host: ${filter.host})` : ''}... (Ctrl+C to stop)\n`);
|
|
145
|
+
}
|
|
146
|
+
let buffer = '';
|
|
147
|
+
res.on('data', (chunk) => {
|
|
148
|
+
buffer += chunk.toString();
|
|
149
|
+
// Parse SSE events from the buffer
|
|
150
|
+
const parts = buffer.split('\n\n');
|
|
151
|
+
buffer = parts.pop() || '';
|
|
152
|
+
for (const part of parts) {
|
|
153
|
+
const lines = part.split('\n');
|
|
154
|
+
let eventType = '';
|
|
155
|
+
let data = '';
|
|
156
|
+
for (const line of lines) {
|
|
157
|
+
if (line.startsWith('event: '))
|
|
158
|
+
eventType = line.slice(7);
|
|
159
|
+
else if (line.startsWith('data: '))
|
|
160
|
+
data = line.slice(6);
|
|
161
|
+
}
|
|
162
|
+
if (eventType !== 'request' || !data)
|
|
163
|
+
continue;
|
|
164
|
+
try {
|
|
165
|
+
const record = JSON.parse(data);
|
|
166
|
+
if (matchesFilter(record, filter)) {
|
|
167
|
+
console.log(formatTailLine(record, format));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Ignore malformed events
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
res.on('end', () => {
|
|
176
|
+
console.error('Event stream closed.');
|
|
177
|
+
process.exit(0);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
req.on('error', () => {
|
|
181
|
+
console.error('Could not connect to proxy. Is it running?');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
});
|
|
184
|
+
req.end();
|
|
185
|
+
}
|
|
40
186
|
function parseTime(value) {
|
|
41
187
|
const num = Number(value);
|
|
42
188
|
if (!isNaN(num))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"requests.js","sourceRoot":"","sources":["../../../src/cli/commands/requests.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"requests.js","sourceRoot":"","sources":["../../../src/cli/commands/requests.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/format.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
import type { RequestRecord, PaginatedResponse } from '../shared/types.js';
|
|
2
|
+
export declare const COL: {
|
|
3
|
+
readonly time: 12;
|
|
4
|
+
readonly method: 8;
|
|
5
|
+
readonly status: 8;
|
|
6
|
+
readonly host: 30;
|
|
7
|
+
readonly path: 30;
|
|
8
|
+
readonly duration: 10;
|
|
9
|
+
readonly size: 10;
|
|
10
|
+
};
|
|
2
11
|
export declare function formatRequests(result: PaginatedResponse<RequestRecord>, format: string): string;
|
|
3
12
|
export declare function formatRequest(record: RequestRecord, format: string): string;
|
|
13
|
+
export declare function formatTailLine(r: RequestRecord, format: string): string;
|
package/dist/cli/format.js
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
|
+
// ── Shared column widths ──
|
|
3
|
+
export const COL = {
|
|
4
|
+
time: 12,
|
|
5
|
+
method: 8,
|
|
6
|
+
status: 8,
|
|
7
|
+
host: 30,
|
|
8
|
+
path: 30,
|
|
9
|
+
duration: 10,
|
|
10
|
+
size: 10,
|
|
11
|
+
};
|
|
12
|
+
// ── ANSI-safe padding ──
|
|
13
|
+
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
14
|
+
function visibleLength(str) {
|
|
15
|
+
return str.replace(ANSI_RE, '').length;
|
|
16
|
+
}
|
|
17
|
+
function padAnsi(str, width) {
|
|
18
|
+
const pad = width - visibleLength(str);
|
|
19
|
+
return pad > 0 ? str + ' '.repeat(pad) : str;
|
|
20
|
+
}
|
|
21
|
+
// ── Colors ──
|
|
2
22
|
const methodColor = (method) => {
|
|
3
23
|
switch (method) {
|
|
4
24
|
case 'GET': return pc.blue(method);
|
|
@@ -21,6 +41,7 @@ const statusColor = (status) => {
|
|
|
21
41
|
return pc.magenta(s);
|
|
22
42
|
return pc.red(s);
|
|
23
43
|
};
|
|
44
|
+
// ── Table formatters ──
|
|
24
45
|
export function formatRequests(result, format) {
|
|
25
46
|
if (format === 'json') {
|
|
26
47
|
return JSON.stringify(result, null, 2);
|
|
@@ -28,21 +49,22 @@ export function formatRequests(result, format) {
|
|
|
28
49
|
if (result.data.length === 0) {
|
|
29
50
|
return `\n ${pc.dim('No requests found.')}\n`;
|
|
30
51
|
}
|
|
52
|
+
const totalWidth = COL.method + COL.status + COL.host + COL.path + COL.duration + COL.size;
|
|
31
53
|
const header = pc.dim(' ' +
|
|
32
|
-
'METHOD'.padEnd(
|
|
33
|
-
'STATUS'.padEnd(
|
|
34
|
-
'HOST'.padEnd(
|
|
35
|
-
'PATH'.padEnd(
|
|
36
|
-
'TIME'.padEnd(
|
|
37
|
-
'SIZE'.padEnd(
|
|
38
|
-
const divider = pc.dim(' ' + '─'.repeat(
|
|
54
|
+
'METHOD'.padEnd(COL.method) +
|
|
55
|
+
'STATUS'.padEnd(COL.status) +
|
|
56
|
+
'HOST'.padEnd(COL.host) +
|
|
57
|
+
'PATH'.padEnd(COL.path) +
|
|
58
|
+
'TIME'.padEnd(COL.duration) +
|
|
59
|
+
'SIZE'.padEnd(COL.size));
|
|
60
|
+
const divider = pc.dim(' ' + '─'.repeat(totalWidth));
|
|
39
61
|
const rows = result.data.map((r) => {
|
|
40
62
|
return ' ' +
|
|
41
|
-
methodColor(r.method || '').
|
|
42
|
-
statusColor(r.status).
|
|
43
|
-
(r.host || '').slice(0,
|
|
44
|
-
pc.dim((r.path || '').slice(0,
|
|
45
|
-
pc.dim(r.duration ? `${r.duration}ms` : '-').
|
|
63
|
+
padAnsi(methodColor(r.method || ''), COL.method) +
|
|
64
|
+
padAnsi(statusColor(r.status), COL.status) +
|
|
65
|
+
(r.host || '').slice(0, COL.host - 2).padEnd(COL.host) +
|
|
66
|
+
padAnsi(pc.dim((r.path || '').slice(0, COL.path - 2)), COL.path) +
|
|
67
|
+
padAnsi(pc.dim(r.duration ? `${r.duration}ms` : '-'), COL.duration) +
|
|
46
68
|
pc.dim(formatBytes(r.response_size || 0));
|
|
47
69
|
});
|
|
48
70
|
const footer = `\n ${pc.dim(`${result.total} total (showing ${result.data.length}, offset ${result.offset})`)}`;
|
|
@@ -102,6 +124,27 @@ function formatBody(body, contentType) {
|
|
|
102
124
|
}
|
|
103
125
|
return ` ${str}`;
|
|
104
126
|
}
|
|
127
|
+
export function formatTailLine(r, format) {
|
|
128
|
+
if (format === 'json') {
|
|
129
|
+
return JSON.stringify({
|
|
130
|
+
id: r.id,
|
|
131
|
+
timestamp: r.timestamp,
|
|
132
|
+
method: r.method,
|
|
133
|
+
status: r.status,
|
|
134
|
+
host: r.host,
|
|
135
|
+
path: r.path,
|
|
136
|
+
url: r.url,
|
|
137
|
+
duration: r.duration,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return ' ' +
|
|
141
|
+
padAnsi(pc.dim(new Date(r.timestamp).toLocaleTimeString()), COL.time) +
|
|
142
|
+
padAnsi(methodColor(r.method || ''), COL.method) +
|
|
143
|
+
padAnsi(statusColor(r.status), COL.status) +
|
|
144
|
+
(r.host || '').slice(0, COL.host - 2).padEnd(COL.host) +
|
|
145
|
+
padAnsi(pc.dim((r.path || '').slice(0, COL.path - 2)), COL.path) +
|
|
146
|
+
pc.dim(r.duration ? `${r.duration}ms` : '-');
|
|
147
|
+
}
|
|
105
148
|
function formatBytes(bytes) {
|
|
106
149
|
if (bytes < 1024)
|
|
107
150
|
return `${bytes}B`;
|
package/dist/cli/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/cli/format.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAG5B,MAAM,WAAW,GAAG,CAAC,MAAc,EAAU,EAAE;IAC7C,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,KAAK,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrC,KAAK,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACrC,KAAK,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACxC,KAAK,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,MAAqB,EAAU,EAAE;IACpD,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;IAChC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,MAAM,GAAG,GAAG;QAAE,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACrC,IAAI,MAAM,GAAG,GAAG;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACtC,IAAI,MAAM,GAAG,GAAG;QAAE,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACvC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,MAAwC,EAAE,MAAc;IACrF,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,OAAO,EAAE,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,CAAC;IACjD,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,CACnB,IAAI;QACJ,QAAQ,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/cli/format.ts"],"names":[],"mappings":"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"}
|