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.
Files changed (72) hide show
  1. package/dist/app.d.ts +9 -0
  2. package/dist/app.js +162 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +98 -0
  5. package/dist/components/BorderedBox.d.ts +10 -0
  6. package/dist/components/BorderedBox.js +13 -0
  7. package/dist/components/DetailPanel.d.ts +13 -0
  8. package/dist/components/DetailPanel.js +8 -0
  9. package/dist/components/DetailView.d.ts +12 -0
  10. package/dist/components/DetailView.js +151 -0
  11. package/dist/components/DomainSidebar.d.ts +12 -0
  12. package/dist/components/DomainSidebar.js +36 -0
  13. package/dist/components/RequestList.d.ts +22 -0
  14. package/dist/components/RequestList.js +30 -0
  15. package/dist/components/RequestRow.d.ts +15 -0
  16. package/dist/components/RequestRow.js +84 -0
  17. package/dist/components/SortModal.d.ts +8 -0
  18. package/dist/components/SortModal.js +56 -0
  19. package/dist/components/SpinnerContext.d.ts +5 -0
  20. package/dist/components/SpinnerContext.js +20 -0
  21. package/dist/components/StatusBar.d.ts +11 -0
  22. package/dist/components/StatusBar.js +21 -0
  23. package/dist/hooks/useActivePanel.d.ts +11 -0
  24. package/dist/hooks/useActivePanel.js +36 -0
  25. package/dist/hooks/useDetailScroll.d.ts +11 -0
  26. package/dist/hooks/useDetailScroll.js +59 -0
  27. package/dist/hooks/useDetailTabs.d.ts +14 -0
  28. package/dist/hooks/useDetailTabs.js +50 -0
  29. package/dist/hooks/useDomainFilter.d.ts +30 -0
  30. package/dist/hooks/useDomainFilter.js +103 -0
  31. package/dist/hooks/useListNavigation.d.ts +12 -0
  32. package/dist/hooks/useListNavigation.js +105 -0
  33. package/dist/hooks/useSorting.d.ts +20 -0
  34. package/dist/hooks/useSorting.js +71 -0
  35. package/dist/hooks/useTerminalDimensions.d.ts +6 -0
  36. package/dist/hooks/useTerminalDimensions.js +25 -0
  37. package/dist/hooks/useTrafficEntries.d.ts +2 -0
  38. package/dist/hooks/useTrafficEntries.js +16 -0
  39. package/dist/proxy/ca.d.ts +4 -0
  40. package/dist/proxy/ca.js +72 -0
  41. package/dist/proxy/cert-trust.d.ts +20 -0
  42. package/dist/proxy/cert-trust.js +181 -0
  43. package/dist/proxy/index.d.ts +3 -0
  44. package/dist/proxy/index.js +2 -0
  45. package/dist/proxy/proxy-server.d.ts +9 -0
  46. package/dist/proxy/proxy-server.js +262 -0
  47. package/dist/proxy/system-proxy.d.ts +2 -0
  48. package/dist/proxy/system-proxy.js +64 -0
  49. package/dist/proxy/types.d.ts +45 -0
  50. package/dist/proxy/types.js +1 -0
  51. package/dist/store/index.d.ts +2 -0
  52. package/dist/store/index.js +1 -0
  53. package/dist/store/traffic-store.d.ts +14 -0
  54. package/dist/store/traffic-store.js +87 -0
  55. package/dist/store/types.d.ts +14 -0
  56. package/dist/store/types.js +1 -0
  57. package/dist/theme.d.ts +1 -0
  58. package/dist/theme.js +1 -0
  59. package/dist/utils/contentType.d.ts +7 -0
  60. package/dist/utils/contentType.js +46 -0
  61. package/dist/utils/copyToClipboard.d.ts +1 -0
  62. package/dist/utils/copyToClipboard.js +21 -0
  63. package/dist/utils/decompress.d.ts +2 -0
  64. package/dist/utils/decompress.js +25 -0
  65. package/dist/utils/formatBytes.d.ts +1 -0
  66. package/dist/utils/formatBytes.js +14 -0
  67. package/dist/utils/getTabText.d.ts +3 -0
  68. package/dist/utils/getTabText.js +96 -0
  69. package/dist/utils/highlightBody.d.ts +3 -0
  70. package/dist/utils/highlightBody.js +43 -0
  71. package/package.json +57 -0
  72. 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,3 @@
1
+ export declare function highlightJson(text: string): string;
2
+ export declare function highlightHtml(text: string): string;
3
+ export declare function highlightBody(text: string, language: "json" | "html" | "xml" | "plain"): string;
@@ -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