peep-proxy 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +9 -0
- package/dist/app.js +162 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +98 -0
- package/dist/components/BorderedBox.d.ts +10 -0
- package/dist/components/BorderedBox.js +13 -0
- package/dist/components/DetailPanel.d.ts +13 -0
- package/dist/components/DetailPanel.js +8 -0
- package/dist/components/DetailView.d.ts +12 -0
- package/dist/components/DetailView.js +151 -0
- package/dist/components/DomainSidebar.d.ts +12 -0
- package/dist/components/DomainSidebar.js +36 -0
- package/dist/components/RequestList.d.ts +22 -0
- package/dist/components/RequestList.js +30 -0
- package/dist/components/RequestRow.d.ts +15 -0
- package/dist/components/RequestRow.js +84 -0
- package/dist/components/SortModal.d.ts +8 -0
- package/dist/components/SortModal.js +56 -0
- package/dist/components/SpinnerContext.d.ts +5 -0
- package/dist/components/SpinnerContext.js +20 -0
- package/dist/components/StatusBar.d.ts +11 -0
- package/dist/components/StatusBar.js +21 -0
- package/dist/hooks/useActivePanel.d.ts +11 -0
- package/dist/hooks/useActivePanel.js +36 -0
- package/dist/hooks/useDetailScroll.d.ts +11 -0
- package/dist/hooks/useDetailScroll.js +59 -0
- package/dist/hooks/useDetailTabs.d.ts +14 -0
- package/dist/hooks/useDetailTabs.js +50 -0
- package/dist/hooks/useDomainFilter.d.ts +30 -0
- package/dist/hooks/useDomainFilter.js +103 -0
- package/dist/hooks/useListNavigation.d.ts +12 -0
- package/dist/hooks/useListNavigation.js +105 -0
- package/dist/hooks/useSorting.d.ts +20 -0
- package/dist/hooks/useSorting.js +71 -0
- package/dist/hooks/useTerminalDimensions.d.ts +6 -0
- package/dist/hooks/useTerminalDimensions.js +25 -0
- package/dist/hooks/useTrafficEntries.d.ts +2 -0
- package/dist/hooks/useTrafficEntries.js +16 -0
- package/dist/proxy/ca.d.ts +4 -0
- package/dist/proxy/ca.js +72 -0
- package/dist/proxy/cert-trust.d.ts +20 -0
- package/dist/proxy/cert-trust.js +181 -0
- package/dist/proxy/index.d.ts +3 -0
- package/dist/proxy/index.js +2 -0
- package/dist/proxy/proxy-server.d.ts +9 -0
- package/dist/proxy/proxy-server.js +262 -0
- package/dist/proxy/system-proxy.d.ts +2 -0
- package/dist/proxy/system-proxy.js +64 -0
- package/dist/proxy/types.d.ts +45 -0
- package/dist/proxy/types.js +1 -0
- package/dist/store/index.d.ts +2 -0
- package/dist/store/index.js +1 -0
- package/dist/store/traffic-store.d.ts +14 -0
- package/dist/store/traffic-store.js +87 -0
- package/dist/store/types.d.ts +14 -0
- package/dist/store/types.js +1 -0
- package/dist/theme.d.ts +1 -0
- package/dist/theme.js +1 -0
- package/dist/utils/contentType.d.ts +7 -0
- package/dist/utils/contentType.js +46 -0
- package/dist/utils/copyToClipboard.d.ts +1 -0
- package/dist/utils/copyToClipboard.js +21 -0
- package/dist/utils/decompress.d.ts +2 -0
- package/dist/utils/decompress.js +25 -0
- package/dist/utils/formatBytes.d.ts +1 -0
- package/dist/utils/formatBytes.js +14 -0
- package/dist/utils/getTabText.d.ts +3 -0
- package/dist/utils/getTabText.js +96 -0
- package/dist/utils/highlightBody.d.ts +3 -0
- package/dist/utils/highlightBody.js +43 -0
- package/package.json +57 -0
- package/readme.md +73 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { hasBinaryBytes, isBinaryContentType, parseContentType, } from "./contentType.js";
|
|
2
|
+
import { decompressBody } from "./decompress.js";
|
|
3
|
+
import { formatBytes } from "./formatBytes.js";
|
|
4
|
+
function formatHeaders(headers) {
|
|
5
|
+
return Object.entries(headers)
|
|
6
|
+
.flatMap(([key, value]) => {
|
|
7
|
+
if (Array.isArray(value)) {
|
|
8
|
+
return value.map((v) => `${key}: ${v}`);
|
|
9
|
+
}
|
|
10
|
+
return [`${key}: ${value ?? ""}`];
|
|
11
|
+
})
|
|
12
|
+
.join("\n");
|
|
13
|
+
}
|
|
14
|
+
function getRequestBodyText(entry) {
|
|
15
|
+
if (entry.request.body.length === 0) {
|
|
16
|
+
return "Empty body";
|
|
17
|
+
}
|
|
18
|
+
const body = decompressBody(entry.request.body, entry.request.headers);
|
|
19
|
+
const mime = parseContentType(entry.request.headers);
|
|
20
|
+
if (isBinaryContentType(mime) || hasBinaryBytes(body)) {
|
|
21
|
+
const label = mime || "unknown";
|
|
22
|
+
const size = formatBytes(entry.request.body.length);
|
|
23
|
+
return `[Binary content: ${label}, ${size}]`;
|
|
24
|
+
}
|
|
25
|
+
return body.toString("utf-8");
|
|
26
|
+
}
|
|
27
|
+
function getResponseBodyText(entry) {
|
|
28
|
+
if (entry.state === "pending" || !entry.response) {
|
|
29
|
+
return "Waiting for response...";
|
|
30
|
+
}
|
|
31
|
+
if (entry.response.body.length === 0) {
|
|
32
|
+
return "Empty body";
|
|
33
|
+
}
|
|
34
|
+
const body = decompressBody(entry.response.body, entry.response.headers);
|
|
35
|
+
const mime = parseContentType(entry.response.headers);
|
|
36
|
+
if (isBinaryContentType(mime) || hasBinaryBytes(body)) {
|
|
37
|
+
const label = mime || "unknown";
|
|
38
|
+
const size = formatBytes(entry.response.body.length);
|
|
39
|
+
return `[Binary content: ${label}, ${size}]`;
|
|
40
|
+
}
|
|
41
|
+
return body.toString("utf-8");
|
|
42
|
+
}
|
|
43
|
+
function getRawRequest(entry) {
|
|
44
|
+
const { method, path, headers, body } = entry.request;
|
|
45
|
+
const lines = [`${method} ${path} HTTP/1.1`, formatHeaders(headers)];
|
|
46
|
+
if (body.length > 0) {
|
|
47
|
+
lines.push("");
|
|
48
|
+
const decompressed = decompressBody(body, headers);
|
|
49
|
+
const mime = parseContentType(headers);
|
|
50
|
+
if (isBinaryContentType(mime) || hasBinaryBytes(decompressed)) {
|
|
51
|
+
const label = mime || "unknown";
|
|
52
|
+
const size = formatBytes(body.length);
|
|
53
|
+
lines.push(`[Binary content: ${label}, ${size}]`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
lines.push(decompressed.toString("utf-8"));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
function getRawResponse(entry) {
|
|
62
|
+
if (entry.state === "pending" || !entry.response) {
|
|
63
|
+
return "Waiting for response...";
|
|
64
|
+
}
|
|
65
|
+
const { statusCode, headers, body } = entry.response;
|
|
66
|
+
const lines = [`HTTP/1.1 ${statusCode}`, formatHeaders(headers)];
|
|
67
|
+
if (body.length > 0) {
|
|
68
|
+
lines.push("");
|
|
69
|
+
const decompressed = decompressBody(body, headers);
|
|
70
|
+
const mime = parseContentType(headers);
|
|
71
|
+
if (isBinaryContentType(mime) || hasBinaryBytes(decompressed)) {
|
|
72
|
+
const label = mime || "unknown";
|
|
73
|
+
const size = formatBytes(body.length);
|
|
74
|
+
lines.push(`[Binary content: ${label}, ${size}]`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
lines.push(decompressed.toString("utf-8"));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
}
|
|
82
|
+
export function getTabText(entry, side, tab) {
|
|
83
|
+
if (tab === "headers") {
|
|
84
|
+
const headers = side === "request" ? entry.request.headers : entry.response?.headers;
|
|
85
|
+
if (!headers)
|
|
86
|
+
return "Waiting for response...";
|
|
87
|
+
return formatHeaders(headers);
|
|
88
|
+
}
|
|
89
|
+
if (tab === "body") {
|
|
90
|
+
if (side === "request")
|
|
91
|
+
return getRequestBodyText(entry);
|
|
92
|
+
return getResponseBodyText(entry);
|
|
93
|
+
}
|
|
94
|
+
// raw
|
|
95
|
+
return side === "request" ? getRawRequest(entry) : getRawResponse(entry);
|
|
96
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
export function highlightJson(text) {
|
|
3
|
+
let parsed;
|
|
4
|
+
try {
|
|
5
|
+
parsed = JSON.parse(text);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return text;
|
|
9
|
+
}
|
|
10
|
+
const pretty = JSON.stringify(parsed, null, 2);
|
|
11
|
+
return pretty.replace(/("(?:[^"\\]|\\.)*")(\s*:)?|(\b\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\b)|(\btrue\b|\bfalse\b)|(\bnull\b)/g, (_match, str, colon, num, bool, nil) => {
|
|
12
|
+
if (str && colon)
|
|
13
|
+
return chalk.cyan(str) + colon;
|
|
14
|
+
if (str)
|
|
15
|
+
return chalk.green(str);
|
|
16
|
+
if (num)
|
|
17
|
+
return chalk.yellow(num);
|
|
18
|
+
if (bool)
|
|
19
|
+
return chalk.magenta(bool);
|
|
20
|
+
if (nil)
|
|
21
|
+
return chalk.red(nil);
|
|
22
|
+
return _match;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function highlightHtml(text) {
|
|
26
|
+
return text.replace(/<\/?[\w-]+(?:\s+[\w-]+(?:=(?:"[^"]*"|'[^']*'|[^\s>]*))?)*\s*\/?>/g, (tag) => {
|
|
27
|
+
return tag
|
|
28
|
+
.replace(/^(<\/?)(\w[\w-]*)/, (_m, bracket, name) => chalk.cyan(bracket + name))
|
|
29
|
+
.replace(/([\w-]+)(=)("[^"]*"|'[^']*'|[^\s>]*)/g, (_m, attr, eq, val) => chalk.yellow(attr) + eq + chalk.green(val))
|
|
30
|
+
.replace(/(\/?)(>)$/, (_m, slash, bracket) => chalk.cyan(slash + bracket));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function highlightBody(text, language) {
|
|
34
|
+
switch (language) {
|
|
35
|
+
case "json":
|
|
36
|
+
return highlightJson(text);
|
|
37
|
+
case "html":
|
|
38
|
+
case "xml":
|
|
39
|
+
return highlightHtml(text);
|
|
40
|
+
case "plain":
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "peep-proxy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A TUI HTTP proxy for developers. Proxyman, but in your terminal.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/mvacoimbra/peep.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/mvacoimbra/peep#readme",
|
|
11
|
+
"author": "Matheus Coimbra",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"proxy",
|
|
14
|
+
"http",
|
|
15
|
+
"https",
|
|
16
|
+
"tui",
|
|
17
|
+
"terminal",
|
|
18
|
+
"debug",
|
|
19
|
+
"inspect",
|
|
20
|
+
"traffic",
|
|
21
|
+
"ink"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"peep": "dist/cli.js"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"packageManager": "pnpm@10.18.1",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"dev": "tsc --watch",
|
|
34
|
+
"format": "biome format .",
|
|
35
|
+
"lint": "biome lint .",
|
|
36
|
+
"format:fix": "biome format --write .",
|
|
37
|
+
"lint:fix": "biome check --write --unsafe ."
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"chalk": "^5.6.2",
|
|
44
|
+
"cli-truncate": "^5.1.1",
|
|
45
|
+
"ink": "^4.1.0",
|
|
46
|
+
"meow": "^11.0.0",
|
|
47
|
+
"node-forge": "^1.3.3",
|
|
48
|
+
"react": "^18.2.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@biomejs/biome": "^2.3.14",
|
|
52
|
+
"@sindresorhus/tsconfig": "^3.0.1",
|
|
53
|
+
"@types/node-forge": "^1.3.14",
|
|
54
|
+
"@types/react": "^18.0.32",
|
|
55
|
+
"typescript": "^5.0.3"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# peep
|
|
2
|
+
|
|
3
|
+
A TUI HTTP proxy for developers who want to see what's going on. Think [Proxyman](https://proxyman.io), but in your terminal.
|
|
4
|
+
|
|
5
|
+
Built with [Ink](https://github.com/vadimdemedes/ink) and React.
|
|
6
|
+
|
|
7
|
+
> **Status:** Under active development. Core proxy and TUI are functional.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Debugging HTTP traffic shouldn't require leaving the terminal. Peep sits between your app and the internet, letting you inspect requests and responses in a clean terminal UI.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- HTTP and HTTPS interception (automatic CA generation and trust)
|
|
16
|
+
- Three-panel TUI: domain sidebar, request list, request/response detail
|
|
17
|
+
- Request/response detail with headers, body, and raw views
|
|
18
|
+
- Syntax-highlighted body previews (JSON, HTML, CSS, XML)
|
|
19
|
+
- Domain grouping and filtering in the sidebar
|
|
20
|
+
- Sortable request list (by method, URL, status, duration, size)
|
|
21
|
+
- Vim-style keyboard navigation (`hjkl`, `gg`/`G`)
|
|
22
|
+
- Copy request/response to clipboard
|
|
23
|
+
- Automatic system proxy configuration (macOS)
|
|
24
|
+
- Firefox/Zen NSS certificate store support
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
peep # start on default port 8080
|
|
30
|
+
peep --port=3128 # custom port
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Peep will automatically configure itself as the system HTTP proxy and install its CA certificate on first run.
|
|
34
|
+
|
|
35
|
+
### Keyboard Shortcuts
|
|
36
|
+
|
|
37
|
+
| Key | Action |
|
|
38
|
+
|-----|--------|
|
|
39
|
+
| `j` / `k` | Scroll up/down |
|
|
40
|
+
| `h` / `l` | Switch panels / expand-collapse groups / switch tabs |
|
|
41
|
+
| `Enter` | Select / drill into detail |
|
|
42
|
+
| `Esc` | Go back |
|
|
43
|
+
| `s` | Sort request list |
|
|
44
|
+
| `y` | Copy to clipboard |
|
|
45
|
+
| `gg` / `G` | Jump to top/bottom |
|
|
46
|
+
| `q` | Quit |
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install --global peep-proxy
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm install
|
|
58
|
+
pnpm dev # watch mode
|
|
59
|
+
pnpm build # compile
|
|
60
|
+
pnpm lint # check linting
|
|
61
|
+
pnpm format # check formatting
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Tech Stack
|
|
65
|
+
|
|
66
|
+
- **Runtime:** Node.js
|
|
67
|
+
- **UI:** Ink v4 + React 18
|
|
68
|
+
- **Language:** TypeScript
|
|
69
|
+
- **Tooling:** Biome (lint + format), pnpm
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|