pinggy 0.3.5 → 0.3.7

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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-65R2GMKQ.js +2101 -0
  3. package/dist/index.cjs +1798 -1349
  4. package/dist/index.d.cts +616 -0
  5. package/dist/index.d.ts +616 -0
  6. package/dist/index.js +24 -2
  7. package/dist/{main-K44C44NW.js → main-2QDG7PWL.js} +166 -1705
  8. package/package.json +2 -3
  9. package/.github/workflows/npm-publish-github-packages.yml +0 -34
  10. package/.github/workflows/publish-binaries.yml +0 -223
  11. package/Makefile +0 -4
  12. package/caxa_build.js +0 -24
  13. package/dist/chunk-T5ESYDJY.js +0 -121
  14. package/ent.plist +0 -14
  15. package/jest.config.js +0 -19
  16. package/src/_tests_/build_config.test.ts +0 -91
  17. package/src/cli/buildConfig.ts +0 -535
  18. package/src/cli/defaults.ts +0 -20
  19. package/src/cli/extendedOptions.ts +0 -153
  20. package/src/cli/help.ts +0 -43
  21. package/src/cli/options.ts +0 -50
  22. package/src/cli/starCli.ts +0 -229
  23. package/src/index.ts +0 -31
  24. package/src/logger.ts +0 -138
  25. package/src/main.ts +0 -87
  26. package/src/remote_management/handler.ts +0 -244
  27. package/src/remote_management/remoteManagement.ts +0 -226
  28. package/src/remote_management/remote_schema.ts +0 -176
  29. package/src/remote_management/websocket_handlers.ts +0 -180
  30. package/src/tui/blessed/TunnelTui.ts +0 -340
  31. package/src/tui/blessed/components/DisplayUpdaters.ts +0 -189
  32. package/src/tui/blessed/components/KeyBindings.ts +0 -236
  33. package/src/tui/blessed/components/Modals.ts +0 -302
  34. package/src/tui/blessed/components/UIComponents.ts +0 -306
  35. package/src/tui/blessed/components/index.ts +0 -4
  36. package/src/tui/blessed/config.ts +0 -53
  37. package/src/tui/blessed/headerFetcher.ts +0 -42
  38. package/src/tui/blessed/index.ts +0 -2
  39. package/src/tui/blessed/qrCodeGenerator.ts +0 -20
  40. package/src/tui/blessed/webDebuggerConnection.ts +0 -128
  41. package/src/tui/ink/asciArt.ts +0 -7
  42. package/src/tui/ink/hooks/useQrCodes.ts +0 -27
  43. package/src/tui/ink/hooks/useReqResHeaders.ts +0 -27
  44. package/src/tui/ink/hooks/useTerminalSize.ts +0 -26
  45. package/src/tui/ink/hooks/useTerminalStats.ts +0 -24
  46. package/src/tui/ink/hooks/useWebDebugger.ts +0 -98
  47. package/src/tui/ink/index.tsx +0 -243
  48. package/src/tui/ink/layout/Borders.tsx +0 -15
  49. package/src/tui/ink/layout/Container.tsx +0 -15
  50. package/src/tui/ink/sections/DebuggerDetailModal.tsx +0 -53
  51. package/src/tui/ink/sections/KeyBindings.tsx +0 -58
  52. package/src/tui/ink/sections/QrCodeSection.tsx +0 -28
  53. package/src/tui/ink/sections/StatsSection.tsx +0 -20
  54. package/src/tui/ink/sections/URLsSection.tsx +0 -53
  55. package/src/tui/ink/utils/utils.ts +0 -35
  56. package/src/tui/spinner/spinner.ts +0 -64
  57. package/src/tunnel_manager/TunnelManager.ts +0 -1212
  58. package/src/types.ts +0 -255
  59. package/src/utils/FileServer.ts +0 -112
  60. package/src/utils/detect_vc_redist_on_windows.ts +0 -111
  61. package/src/utils/getFreePort.ts +0 -41
  62. package/src/utils/htmlTemplates.ts +0 -146
  63. package/src/utils/parseArgs.ts +0 -79
  64. package/src/utils/printer.ts +0 -81
  65. package/src/utils/util.ts +0 -18
  66. package/src/workers/file_serve_worker.ts +0 -33
  67. package/tsconfig.json +0 -17
  68. package/tsup.config.ts +0 -12
@@ -1,98 +0,0 @@
1
-
2
- import { useEffect, useRef, useState } from "react";
3
- import WebSocket from "ws";
4
- import { ReqResPair, WebDebuggerSocketRequest } from "../../../types.js";
5
- import { logger } from "../../../logger.js";
6
-
7
- export function useWebDebugger(webDebuggerUrl?: string) {
8
- const [pairs, setPairs] = useState<Map<number, ReqResPair>>(new Map());
9
- const socketRef = useRef<WebSocket | null>(null);
10
- const reconnectTimeout = useRef<NodeJS.Timeout | null>(null);
11
-
12
- useEffect(() => {
13
- if (!webDebuggerUrl) return;
14
-
15
- let isStopped = false;
16
-
17
- const connect = () => {
18
- const ws = new WebSocket(`ws://${webDebuggerUrl}/introspec/websocket`);
19
- socketRef.current = ws;
20
-
21
- ws.on("open", () => {
22
- logger.info("Web debugger connected.");
23
- });
24
-
25
- ws.on("message", (data) => {
26
- try {
27
- const raw = data.toString();
28
- const parsed = JSON.parse(raw);
29
- const msg = {
30
- Req: parsed.Req || parsed.req,
31
- Res: parsed.Res || parsed.res,
32
- } as Partial<WebDebuggerSocketRequest>;
33
-
34
- setPairs((prev) => {
35
- const newMap = new Map(prev);
36
-
37
- if (msg.Req) {
38
- const { key } = msg.Req;
39
- const existing = newMap.get(key) as ReqResPair | undefined;
40
- const merged = {
41
-
42
- request: msg.Req,
43
- response: existing?.response,
44
- reqHeaders: existing?.reqHeaders ?? {},
45
- resHeaders: existing?.resHeaders ?? {},
46
- headersLoaded: existing?.headersLoaded ?? false,
47
- } as ReqResPair;
48
- newMap.set(key, merged);
49
- }
50
-
51
- if (msg.Res) {
52
- const { key } = msg.Res;
53
- const existing = newMap.get(key) as ReqResPair | undefined;
54
- const merged = {
55
-
56
- request: existing?.request ?? ({} as any),
57
- response: msg.Res,
58
- reqHeaders: existing?.reqHeaders ?? {},
59
- resHeaders: existing?.resHeaders ?? {},
60
- headersLoaded: existing?.headersLoaded ?? false,
61
- } as ReqResPair;
62
- newMap.set(key, merged);
63
- }
64
-
65
- return newMap;
66
- });
67
- } catch (err: any) {
68
- logger.error("Error parsing WebSocket message:", err.message || err);
69
- }
70
- });
71
-
72
- ws.on("close", () => {
73
- logger.warn("Web debugger disconnected. Reconnecting in 5s...");
74
- if (!isStopped) {
75
- reconnectTimeout.current = setTimeout(connect, 5000);
76
- }
77
- });
78
-
79
- ws.on("error", (err) => {
80
- logger.error(`WebSocket error: ${err.message}`);
81
- });
82
- };
83
-
84
- connect();
85
-
86
- return () => {
87
- isStopped = true;
88
- if (socketRef.current) {
89
- socketRef.current.close();
90
- }
91
- if (reconnectTimeout.current) {
92
- clearTimeout(reconnectTimeout.current);
93
- }
94
- };
95
- }, [webDebuggerUrl]);
96
-
97
- return { pairs };
98
- }
@@ -1,243 +0,0 @@
1
- /**
2
- * Pinggy TUI Main Component - Ink Version
3
- * As of now not in use, kept for future reference.
4
- * Now We are using the blessed based TUI located in src/tui/blessed
5
- * Ink has some packaging issues with pkg it hard to bundle properly.
6
- * If you want to use ink install ink and react as dependencies.
7
- * Check this issue for more details:
8
- * https://github.com/vadimdemedes/ink/issues/844
9
- */
10
-
11
- import React, { useEffect, useState } from "react";
12
- import { Box, Text, useApp, useInput } from "ink";
13
- import { useTerminalSize } from "./hooks/useTerminalSize.js";
14
- import { FinalConfig } from "../../types.js";
15
- import { Container } from "./layout/Container.js";
16
- import { Borders } from "./layout/Borders.js";
17
- import { useQrCodes } from "./hooks/useQrCodes.js";
18
- import { useTunnelStats } from "./hooks/useTerminalStats.js";
19
- import { URLsSection } from "./sections/URLsSection.js";
20
- import { QrCodeSection } from "./sections/QrCodeSection.js";
21
- import { StatsSection } from "./sections/StatsSection.js";
22
- import { useWebDebugger } from "./hooks/useWebDebugger.js";
23
- import { DebuggerDetailModal } from "./sections/DebuggerDetailModal.js";
24
- import { useReqResHeaders } from "./hooks/useReqResHeaders.js";
25
- import { logger } from "../../logger.js";
26
- import { getStatusColor } from "./utils/utils.js";
27
- import { KeyBindings } from "./sections/KeyBindings.js";
28
-
29
-
30
- interface TunnelAppProps {
31
- urls: string[];
32
- greet?: string;
33
- tunnelConfig?: FinalConfig;
34
- disconnectInfo?: {
35
- disconnected: boolean;
36
- error?: string;
37
- messages?: string[];
38
- } | null;
39
- }
40
-
41
- const MIN_WIDTH_WARNING = 60;
42
- const SIMPLE_LAYOUT_THRESHOLD = 80;
43
-
44
- const TunnelTui = ({ urls, greet, tunnelConfig, disconnectInfo }: TunnelAppProps) => {
45
- const { exit } = useApp();
46
- const { columns: terminalWidth } = useTerminalSize();
47
- const isQrCodeRequested = tunnelConfig?.qrCode || false;
48
-
49
- // States
50
- const [currentQrIndex, setCurrentQrIndex] = useState(0);
51
- const [selectedIndex, setSelectedIndex] = useState(0);
52
- const [inDetailView, setInDetailView] = useState(false);
53
- const [keyBindingView, setKeyBindingView] = useState(false);
54
-
55
- // Hooks
56
- const qrCodes = useQrCodes(urls, isQrCodeRequested);
57
- const stats = useTunnelStats();
58
- const { pairs } = useWebDebugger(tunnelConfig?.webDebugger);
59
- const { headers, fetchHeaders, clear } = useReqResHeaders(tunnelConfig?.webDebugger);
60
-
61
- const allPairs = [...pairs.values()];
62
-
63
- useEffect(() => {
64
- if (disconnectInfo?.disconnected) {
65
- exit();
66
- }
67
- }, [disconnectInfo, exit]);
68
-
69
- // Key handling
70
- useInput((input, key) => {
71
- if (inDetailView && key.escape) {
72
- setInDetailView(false);
73
- clear();
74
- return;
75
- }
76
- if (keyBindingView && key.escape) {
77
- setKeyBindingView(false);
78
- return;
79
- }
80
-
81
- if (key.upArrow && selectedIndex > 0) setSelectedIndex((i) => i - 1);
82
- if (key.downArrow && selectedIndex < allPairs.length - 1) setSelectedIndex((i) => i + 1);
83
-
84
- if (key.return) {
85
- const pair = allPairs[selectedIndex];
86
- if (pair && pair.request && pair.request.key !== undefined && pair.request.key !== null) {
87
- (async () => {
88
- try {
89
- await fetchHeaders(pair.request.key);
90
- setInDetailView(true);
91
- } catch (err) {
92
- logger.error("Fetch error:", err);
93
- }
94
- })();
95
- }
96
- }
97
- if (input === 'h') {
98
- setKeyBindingView(i => !i);
99
- return;
100
- }
101
- if (qrCodes.length > 1 || urls.length > 1) {
102
- if (key.rightArrow && currentQrIndex < urls.length - 1) setCurrentQrIndex(i => i + 1);
103
- if (key.leftArrow && currentQrIndex > 0) setCurrentQrIndex(i => i - 1);
104
- }
105
- });
106
-
107
- const visiblePairs = allPairs.slice(-10);
108
- const startIndex = allPairs.length - visiblePairs.length;
109
-
110
- if (terminalWidth < MIN_WIDTH_WARNING) {
111
- return (
112
- <Box flexDirection="column" alignItems="center" justifyContent="center" height="100%">
113
- <Text color="redBright" bold>
114
- Terminal is too narrow to show TUI ({terminalWidth} cols).
115
- </Text>
116
- <Text color="yellow">
117
- Please resize your terminal to at least {MIN_WIDTH_WARNING} columns for proper display.
118
- </Text>
119
- </Box>
120
- );
121
- }
122
-
123
- // === Simplified layout for narrow terminals ===
124
- if (terminalWidth < SIMPLE_LAYOUT_THRESHOLD) {
125
- return (
126
- <Box flexDirection="column" paddingX={1} height={"100%"}>
127
- {greet && (
128
- <Box justifyContent="center" marginBottom={1}>
129
- <Text color="cyanBright" bold>{greet}</Text>
130
- </Box>
131
- )}
132
-
133
- <URLsSection urls={urls} currentQrIndex={currentQrIndex} width="100%" />
134
-
135
- <Box marginTop={1}>
136
- <StatsSection stats={stats} />
137
- </Box>
138
-
139
- <Box flexDirection="column" marginTop={1}>
140
- <Text color="yellowBright">HTTP Requests:</Text>
141
- {visiblePairs.map((pair, i) => (
142
- <Text key={i} color={selectedIndex === startIndex + i ? "greenBright" : "white"}>
143
- {selectedIndex === startIndex + i ? "> " : " "}
144
- {pair.request?.method || ""}
145
- {" "}
146
- {pair.response ? (
147
- <Text color="cyan"> {pair.response.status}</Text>
148
- ) : (
149
- <Text dimColor>...</Text>
150
- )}
151
- {" "}{pair.request?.uri || ""}
152
- </Text>
153
- ))}
154
- </Box>
155
-
156
- {/* Qr codes are removed for small terminals */}
157
-
158
- <Box marginTop={1} justifyContent="center">
159
- <Text dimColor>Press Ctrl+C to stop the tunnel.Or h for key bindings</Text>
160
- </Box>
161
-
162
- {inDetailView && (
163
- <DebuggerDetailModal
164
- requestText={headers?.req}
165
- responseText={headers?.res}
166
- onClose={() => setInDetailView(false)}
167
- />
168
- )}
169
- </Box>
170
- );
171
- }
172
-
173
-
174
- return (
175
- <>
176
- <Container>
177
- <Borders>
178
- <Box flexDirection="column" height="100%" justifyContent="space-between">
179
- {/* ===== Top content ===== */}
180
- <Box flexDirection="column">
181
- {greet && (
182
- <Box justifyContent="center" width="94%" marginBottom={1}>
183
- <Text color="cyanBright" bold>
184
- {greet}
185
- </Text>
186
- </Box>
187
- )}
188
-
189
- {/* Upper Box (URL + stats) */}
190
- <Box flexDirection="row" justifyContent="space-evenly" width="100%" paddingY={1}>
191
- <URLsSection urls={urls} currentQrIndex={currentQrIndex} />
192
- <StatsSection stats={stats} />
193
- </Box>
194
-
195
- {/* Lower Box (Requests + QR code) */}
196
- <Box flexDirection="row" justifyContent="space-evenly" width="100%" paddingY={1}>
197
- <Box flexDirection="column" marginBottom={1} width={isQrCodeRequested ? "60%" : "80%"}>
198
- <Text color="yellowBright">HTTP Requests:</Text>
199
- {visiblePairs.map((pair, i) => (
200
- <Text key={i}>
201
- <Text color={selectedIndex === startIndex + i ? "cyanBright" : getStatusColor(pair.response?.status || "")}>
202
-
203
- {selectedIndex === startIndex + i ? "> " : " "}
204
- {pair.request?.method || ""}
205
- {pair.response ? (
206
- <Text color={selectedIndex === startIndex + i ? "cyanBright" : getStatusColor(pair.response?.status || "")}>{" "} {pair.response.status}</Text>
207
- ) : (
208
- <Text dimColor>...</Text>
209
- )}
210
- {" "}{pair.request?.uri || ""}
211
- </Text>
212
- </Text>
213
- ))}
214
- </Box>
215
-
216
- {isQrCodeRequested && (
217
- <QrCodeSection qrCodes={qrCodes} urls={urls} currentQrIndex={currentQrIndex} />
218
- )}
219
- </Box>
220
- </Box>
221
-
222
- {/* ===== Bottom sticky message ===== */}
223
- <Box justifyContent="center" marginTop={1}>
224
- <Text dimColor>Press Ctrl+C twice to stop the tunnel. Or press h for key bindings.</Text>
225
- </Box>
226
- </Box>
227
- </Borders>
228
- </Container>
229
- {inDetailView && (
230
- <DebuggerDetailModal
231
- requestText={headers?.req}
232
- responseText={headers?.res}
233
- onClose={() => setInDetailView(false)}
234
- />
235
- )}
236
- {
237
- keyBindingView && <KeyBindings />
238
- }
239
- </>
240
- );
241
- };
242
-
243
- export default TunnelTui;
@@ -1,15 +0,0 @@
1
- import React from "react";
2
- import { Box } from "ink";
3
-
4
- export const Borders = ({ children }: { children: React.ReactNode }) => (
5
- <Box
6
- borderStyle="round"
7
- borderColor="green"
8
- padding={1}
9
- flexDirection="column"
10
- width="100%"
11
- alignItems="center"
12
- >
13
- {children}
14
- </Box>
15
- );
@@ -1,15 +0,0 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import Gradient from "ink-gradient";
4
- import { asciiArtPinggyLogo } from "../asciArt.js";
5
-
6
- export const Container = ({ children }: { children: React.ReactNode }) => (
7
- <Box flexDirection="column" height="100%" width="100%" padding={1}>
8
- <Gradient name="fruit">
9
- <Text>{asciiArtPinggyLogo}</Text>
10
- </Gradient>
11
- <Box marginTop={1} flexGrow={1} width="100%">
12
- {children}
13
- </Box>
14
- </Box>
15
- );
@@ -1,53 +0,0 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
-
4
- interface DebuggerDetailModalProps {
5
- requestText?: string;
6
- responseText?: string;
7
- onClose: () => void;
8
- }
9
-
10
- export const DebuggerDetailModal = ({
11
- requestText,
12
- responseText,
13
- onClose,
14
- }: DebuggerDetailModalProps) => {
15
- return (
16
- <Box
17
- flexDirection="column"
18
- width="100%"
19
- height="100%"
20
- position="absolute"
21
- alignItems="center"
22
- justifyContent="center"
23
- >
24
- <Box
25
- flexDirection="column"
26
- paddingX={2}
27
- paddingY={1}
28
- width="90%"
29
- height="90%"
30
- overflow="hidden"
31
- backgroundColor="default"
32
- borderStyle="round"
33
- borderColor="green"
34
- >
35
- <Text color="cyanBright" bold>
36
- Request
37
- </Text>
38
- <Text wrap="truncate-end">{requestText || "(no request data)"}</Text>
39
-
40
- <Box marginTop={1} />
41
-
42
- <Text color="magentaBright" bold>
43
- Response
44
- </Text>
45
- <Text wrap="truncate-end">{responseText || "(no response data)"}</Text>
46
-
47
- <Box marginTop={1} justifyContent="center">
48
- <Text dimColor>Press ESC to close</Text>
49
- </Box>
50
- </Box>
51
- </Box>
52
- );
53
- };
@@ -1,58 +0,0 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
-
4
-
5
- export const KeyBindings = () => {
6
- return (
7
- <Box
8
- flexDirection="column"
9
- width="100%"
10
- height="100%"
11
- position="absolute"
12
- alignItems="center"
13
- justifyContent="center"
14
-
15
- >
16
- <Box
17
- flexDirection="column"
18
- paddingX={2}
19
- paddingY={1}
20
- width="60%"
21
- height="80%"
22
- overflow="hidden"
23
- backgroundColor="default"
24
- borderStyle="round"
25
- borderColor="green"
26
-
27
- >
28
- <Box justifyContent="center" marginBottom={1} >
29
- <Text color="cyanBright" bold> Key Bindings </Text>
30
- </Box>
31
- <Box flexDirection="column" padding={1} justifyContent="space-evenly" gap={2} >
32
- {/* Header */}
33
- <Box flexDirection="column">
34
- <Text>
35
- <Text bold>h</Text> This page
36
- </Text>
37
- <Text>
38
- <Text bold>c</Text> Copy the selected URL to clipboard
39
- </Text>
40
- <Text>
41
- <Text bold>Ctrl+c</Text> Exit
42
- </Text>
43
- </Box>
44
-
45
- <Box marginTop={1} flexDirection="column">
46
- <Text>Enter/Return Open selected request</Text>
47
- <Text>Esc Return to main page</Text>
48
- <Text>UP (↑) Scroll up the requests</Text>
49
- <Text>Down (↓) Scroll down the requests</Text>
50
- <Text>Left (←) Show qr code for previous url</Text>
51
- <Text>Right (→) Show qr code for next url</Text>
52
- <Text>Ctrl+c Force Exit</Text>
53
- </Box>
54
- </Box>
55
- </Box>
56
- </Box>
57
- );
58
- };
@@ -1,28 +0,0 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
-
4
- interface Props {
5
- qrCodes: string[];
6
- urls: string[];
7
- currentQrIndex: number;
8
- }
9
-
10
- export const QrCodeSection = ({ qrCodes, urls, currentQrIndex }: Props) => {
11
- if (qrCodes.length === 0) return null;
12
-
13
- return (
14
- <Box flexDirection="column" alignItems="center" flexGrow={1} paddingX={1} width="40%" >
15
- <Text color="greenBright" bold>
16
- QR Code {currentQrIndex + 1}/{urls.length}
17
- </Text>
18
-
19
- <Box marginY={1} flexDirection="column" alignItems="center">
20
- <Text>{qrCodes[currentQrIndex]}</Text>
21
- </Box>
22
-
23
- {urls.length > 1 && (
24
- <Text color="yellow">← → to switch QR codes</Text>
25
- )}
26
- </Box>
27
- );
28
- };
@@ -1,20 +0,0 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
- import { TunnelUsageType } from "@pinggy/pinggy";
4
- import { getBytesInt } from "../utils/utils.js";
5
-
6
- export const StatsSection = ({ stats }: { stats: TunnelUsageType }) => (
7
- <Box flexDirection="column" paddingX={1} alignItems="center" width="40%">
8
- <Box flexDirection="column" alignItems="flex-start">
9
- <Text color="greenBright" bold>
10
- Live Stats
11
- </Text>
12
- <Text>Elapsed: {stats.elapsedTime}s</Text>
13
- <Text>Live Connections: {stats.numLiveConnections}</Text>
14
- <Text>Total Connections: {stats.numTotalConnections}</Text>
15
- <Text>Request : {getBytesInt(stats.numTotalReqBytes)}</Text>
16
- <Text>Response : {getBytesInt(stats.numTotalResBytes)}</Text>
17
- <Text>Total Transfer: {getBytesInt(stats.numTotalTxBytes)}</Text>
18
- </Box>
19
- </Box>
20
- );
@@ -1,53 +0,0 @@
1
- import React from "react";
2
- import { Box, Text, useInput } from "ink";
3
- import clipboardy from "clipboardy";
4
-
5
- interface Props {
6
- urls: string[];
7
- currentQrIndex: number;
8
- width?: string;
9
- }
10
-
11
- export const URLsSection = ({ urls, currentQrIndex, width = "60%" }: Props) => {
12
- const [copiedIndex, setCopiedIndex] = React.useState<number | null>(null);
13
-
14
- useInput((input, key) => {
15
- // Press 'c' to copy current URL
16
- if (input === "c" && urls.length > 0) {
17
- const urlToCopy = urls[currentQrIndex];
18
- clipboardy.writeSync(urlToCopy);
19
- setCopiedIndex(currentQrIndex);
20
-
21
- // reset feedback after a second
22
- setTimeout(() => setCopiedIndex(null), 1000);
23
- }
24
- });
25
-
26
- return (<Box flexDirection="column" paddingX={1} alignItems="flex-start" width={width} >
27
- <Text color="greenBright" bold>
28
- Public URLs
29
- </Text>
30
- {urls.map((url, index) => {
31
- const isSelected = index === currentQrIndex;
32
- const isCopied = copiedIndex === index;
33
-
34
- return (
35
- <Text
36
- key={`url-${url}-${index}`}
37
- color={
38
- isCopied
39
- ? "cyanBright"
40
- : isSelected
41
- ? "yellowBright"
42
- : "magentaBright"
43
- }
44
- bold={isSelected || isCopied}
45
- >
46
- {isSelected ? "→ " : "• "}
47
- {url}
48
- {isCopied && <Text color="greenBright"> [Copied!]</Text>}
49
- </Text>
50
- );
51
- })}
52
- </Box>);
53
- };
@@ -1,35 +0,0 @@
1
- export function getStatusColor(status: string): string {
2
-
3
- const match = status.match(/\b(\d{3})\b/);
4
- const statusCode = match ? parseInt(match[1], 10) : 0;
5
-
6
-
7
- switch (true) {
8
- case statusCode >= 100 && statusCode < 200:
9
- return "yellow";
10
- case statusCode >= 200 && statusCode < 300:
11
- return "green";
12
- case statusCode >= 300 && statusCode < 400:
13
- return "yellow";
14
- case statusCode >= 400 && statusCode < 500:
15
- return "red";
16
- case statusCode >= 500:
17
- return "pink";
18
- default:
19
- return "yellow";
20
- }
21
- }
22
-
23
- export function getBytesInt(b: number): string {
24
- if (b >= 1024 * 1024 * 1024) {
25
- return `${(b / (1024 * 1024 * 1024)).toFixed(2)} G`;
26
- }
27
- if (b >= 1024 * 1024) {
28
- return `${(b / (1024 * 1024)).toFixed(2)} M`;
29
- }
30
- if (b >= 1024) {
31
- return `${(b / 1024).toFixed(2)} K`;
32
- }
33
- return `${b.toFixed(2)} `;
34
- }
35
-
@@ -1,64 +0,0 @@
1
- import pico from 'picocolors';
2
-
3
- interface Spinner {
4
- interval: number;
5
- frames: string[];
6
- }
7
-
8
- interface Spinners {
9
- [key: string]: Spinner;
10
- }
11
-
12
- export const spinners: Spinners = {
13
- dots: {
14
- interval: 80,
15
- frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
16
- },
17
- };
18
-
19
- let currentTimer: NodeJS.Timeout | null = null;
20
- let currentText = '';
21
-
22
- export function startSpinner(name = 'dots', text = 'Loading') {
23
- const spinner = spinners[name];
24
- let i = 0;
25
- currentText = text;
26
-
27
- // Clear any existing spinner
28
- if (currentTimer) {
29
- clearInterval(currentTimer);
30
- }
31
-
32
- currentTimer = setInterval(() => {
33
- const frame = spinner.frames[i = ++i % spinner.frames.length];
34
- process.stdout.write(`\r${pico.cyan(frame)} ${text}`);
35
- }, spinner.interval);
36
-
37
- return () => stopSpinner();
38
- }
39
-
40
- export function stopSpinner() {
41
- if (currentTimer) {
42
- clearInterval(currentTimer);
43
- currentTimer = null;
44
- process.stdout.write('\r\x1b[K'); // Clear the line
45
- }
46
- }
47
-
48
- export function stopSpinnerSuccess(message?: string) {
49
- if (currentTimer) {
50
- clearInterval(currentTimer);
51
- currentTimer = null;
52
- const finalMessage = message || currentText;
53
- process.stdout.write(`\r${pico.green('✔')} ${finalMessage}\n`);
54
- }
55
- }
56
-
57
- export function stopSpinnerFail(message?: string) {
58
- if (currentTimer) {
59
- clearInterval(currentTimer);
60
- currentTimer = null;
61
- const finalMessage = message || currentText;
62
- process.stdout.write(`\r${pico.red('✖')} ${finalMessage}\n`);
63
- }
64
- }