private-connect 0.3.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.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Private Connect CLI
4
+ *
5
+ * Zero-friction connectivity testing. No signup required.
6
+ *
7
+ * Usage:
8
+ * npx private-connect test vault.internal:8200
9
+ * npx private-connect test https://api.example.com
10
+ */
11
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Private Connect CLI
5
+ *
6
+ * Zero-friction connectivity testing. No signup required.
7
+ *
8
+ * Usage:
9
+ * npx private-connect test vault.internal:8200
10
+ * npx private-connect test https://api.example.com
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const net = require("net");
14
+ const tls = require("tls");
15
+ const https = require("https");
16
+ const http = require("http");
17
+ const url_1 = require("url");
18
+ // Colors (no dependencies)
19
+ const c = {
20
+ reset: '\x1b[0m',
21
+ bold: '\x1b[1m',
22
+ dim: '\x1b[2m',
23
+ green: '\x1b[32m',
24
+ red: '\x1b[31m',
25
+ yellow: '\x1b[33m',
26
+ cyan: '\x1b[36m',
27
+ gray: '\x1b[90m',
28
+ };
29
+ const ok = `${c.green}✓${c.reset}`;
30
+ const fail = `${c.red}✗${c.reset}`;
31
+ const warn = `${c.yellow}⚠${c.reset}`;
32
+ async function testTcp(host, port, timeout = 5000) {
33
+ return new Promise((resolve) => {
34
+ const start = Date.now();
35
+ const socket = new net.Socket();
36
+ socket.setTimeout(timeout);
37
+ socket.on('connect', () => {
38
+ const latency = Date.now() - start;
39
+ socket.destroy();
40
+ resolve({ ok: true, latency });
41
+ });
42
+ socket.on('timeout', () => {
43
+ socket.destroy();
44
+ resolve({ ok: false, error: 'Connection timeout' });
45
+ });
46
+ socket.on('error', (err) => {
47
+ resolve({ ok: false, error: err.message });
48
+ });
49
+ socket.connect(port, host);
50
+ });
51
+ }
52
+ async function testTls(host, port, timeout = 5000) {
53
+ return new Promise((resolve) => {
54
+ const socket = tls.connect({
55
+ host,
56
+ port,
57
+ timeout,
58
+ rejectUnauthorized: false, // We want to check even self-signed certs
59
+ });
60
+ socket.on('secureConnect', () => {
61
+ const cert = socket.getPeerCertificate();
62
+ socket.destroy();
63
+ if (cert && cert.issuer) {
64
+ resolve({
65
+ ok: true,
66
+ issuer: cert.issuer.O || cert.issuer.CN || 'Unknown',
67
+ expiry: cert.valid_to,
68
+ });
69
+ }
70
+ else {
71
+ resolve({ ok: true });
72
+ }
73
+ });
74
+ socket.on('timeout', () => {
75
+ socket.destroy();
76
+ resolve({ ok: false, error: 'TLS timeout' });
77
+ });
78
+ socket.on('error', (err) => {
79
+ // Not all services support TLS
80
+ if (err.message.includes('wrong version') || err.message.includes('ECONNRESET')) {
81
+ resolve({ ok: false, error: 'Not TLS' });
82
+ }
83
+ else {
84
+ resolve({ ok: false, error: err.message });
85
+ }
86
+ });
87
+ });
88
+ }
89
+ async function testHttp(url, timeout = 5000) {
90
+ return new Promise((resolve) => {
91
+ const start = Date.now();
92
+ const parsedUrl = new url_1.URL(url);
93
+ const client = parsedUrl.protocol === 'https:' ? https : http;
94
+ const req = client.request(url, {
95
+ method: 'GET',
96
+ timeout,
97
+ rejectUnauthorized: false,
98
+ }, (res) => {
99
+ const latency = Date.now() - start;
100
+ res.destroy();
101
+ resolve({
102
+ ok: res.statusCode !== undefined && res.statusCode < 500,
103
+ status: res.statusCode,
104
+ latency,
105
+ });
106
+ });
107
+ req.on('timeout', () => {
108
+ req.destroy();
109
+ resolve({ ok: false, error: 'HTTP timeout' });
110
+ });
111
+ req.on('error', (err) => {
112
+ resolve({ ok: false, error: err.message });
113
+ });
114
+ req.end();
115
+ });
116
+ }
117
+ function parseTarget(target) {
118
+ // Handle URLs
119
+ if (target.startsWith('http://') || target.startsWith('https://')) {
120
+ const url = new url_1.URL(target);
121
+ const port = url.port ? parseInt(url.port, 10) : (url.protocol === 'https:' ? 443 : 80);
122
+ return { host: url.hostname, port, isHttp: true, url: target };
123
+ }
124
+ // Handle host:port
125
+ const parts = target.split(':');
126
+ if (parts.length === 2) {
127
+ return { host: parts[0], port: parseInt(parts[1], 10), isHttp: false };
128
+ }
129
+ // Default to port 443
130
+ return { host: target, port: 443, isHttp: false };
131
+ }
132
+ async function runTest(target) {
133
+ const { host, port, isHttp, url } = parseTarget(target);
134
+ console.log();
135
+ console.log(`${c.bold}Testing ${c.cyan}${target}${c.reset}`);
136
+ console.log(`${c.gray}────────────────────────────────────${c.reset}`);
137
+ console.log();
138
+ // TCP test
139
+ process.stdout.write(` TCP `);
140
+ const tcpResult = await testTcp(host, port);
141
+ if (tcpResult.ok) {
142
+ console.log(`${ok} ${c.dim}${tcpResult.latency}ms${c.reset}`);
143
+ }
144
+ else {
145
+ console.log(`${fail} ${c.red}${tcpResult.error}${c.reset}`);
146
+ // Can't continue if TCP fails
147
+ printCta(false);
148
+ return;
149
+ }
150
+ // TLS test (only for likely TLS ports or explicit https)
151
+ const tlsPorts = [443, 8443, 5432, 6379, 27017, 9200];
152
+ const shouldTestTls = isHttp || tlsPorts.includes(port) || port > 1024;
153
+ let tlsResult = null;
154
+ if (shouldTestTls) {
155
+ process.stdout.write(` TLS `);
156
+ tlsResult = await testTls(host, port);
157
+ if (tlsResult.ok) {
158
+ const extra = tlsResult.issuer ? `${c.dim}(${tlsResult.issuer})${c.reset}` : '';
159
+ console.log(`${ok} ${extra}`);
160
+ }
161
+ else if (tlsResult.error === 'Not TLS') {
162
+ console.log(`${c.gray}– No TLS${c.reset}`);
163
+ }
164
+ else {
165
+ console.log(`${warn} ${c.yellow}${tlsResult.error}${c.reset}`);
166
+ }
167
+ }
168
+ // HTTP test (if it looks like HTTP)
169
+ let httpResult = null;
170
+ const httpUrl = url || (tlsResult?.ok ? `https://${host}:${port}` : `http://${host}:${port}`);
171
+ if (isHttp || [80, 443, 8080, 8443, 3000, 5000, 8000].includes(port)) {
172
+ process.stdout.write(` HTTP `);
173
+ httpResult = await testHttp(httpUrl);
174
+ if (httpResult.ok) {
175
+ console.log(`${ok} ${c.dim}${httpResult.status} (${httpResult.latency}ms)${c.reset}`);
176
+ }
177
+ else if (httpResult.error) {
178
+ console.log(`${c.gray}– ${httpResult.error}${c.reset}`);
179
+ }
180
+ else {
181
+ console.log(`${warn} ${c.yellow}${httpResult.status}${c.reset}`);
182
+ }
183
+ }
184
+ // Latency summary
185
+ const latency = tcpResult.latency || httpResult?.latency;
186
+ if (latency) {
187
+ console.log();
188
+ console.log(` Latency ${c.bold}${latency}ms${c.reset}`);
189
+ }
190
+ console.log();
191
+ console.log(`${c.gray}────────────────────────────────────${c.reset}`);
192
+ const allOk = tcpResult.ok && (tlsResult === null || tlsResult.ok || tlsResult.error === 'Not TLS');
193
+ if (allOk) {
194
+ console.log(` ${c.green}${c.bold}REACHABLE${c.reset}`);
195
+ }
196
+ else {
197
+ console.log(` ${c.yellow}${c.bold}ISSUES DETECTED${c.reset}`);
198
+ }
199
+ printCta(allOk);
200
+ }
201
+ function printCta(success) {
202
+ console.log();
203
+ console.log(`${c.gray}────────────────────────────────────${c.reset}`);
204
+ console.log();
205
+ if (success) {
206
+ console.log(` ${c.bold}Want to share this securely?${c.reset}`);
207
+ console.log();
208
+ console.log(` ${c.cyan}curl -fsSL https://privateconnect.co/install.sh | bash${c.reset}`);
209
+ console.log(` ${c.cyan}connect up${c.reset}`);
210
+ console.log();
211
+ console.log(` Then: ${c.bold}connect <target> --share${c.reset}`);
212
+ console.log(` → Get a shareable link anyone can use`);
213
+ }
214
+ else {
215
+ console.log(` ${c.bold}Need help debugging?${c.reset}`);
216
+ console.log();
217
+ console.log(` ${c.cyan}https://privateconnect.co${c.reset}`);
218
+ }
219
+ console.log();
220
+ }
221
+ function printHelp() {
222
+ console.log(`
223
+ ${c.bold}Private Connect${c.reset} - Test connectivity to any service
224
+
225
+ ${c.bold}Usage:${c.reset}
226
+ npx private-connect test <target>
227
+
228
+ ${c.bold}Examples:${c.reset}
229
+ npx private-connect test vault.internal:8200
230
+ npx private-connect test https://api.example.com
231
+ npx private-connect test postgres.prod:5432
232
+
233
+ ${c.bold}What it checks:${c.reset}
234
+ • TCP reachability
235
+ • TLS validation
236
+ • HTTP response (if applicable)
237
+ • Latency
238
+
239
+ No signup. No account. Just diagnostics.
240
+
241
+ ${c.dim}For full features: https://privateconnect.co${c.reset}
242
+ `);
243
+ }
244
+ // Main
245
+ const args = process.argv.slice(2);
246
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
247
+ printHelp();
248
+ process.exit(0);
249
+ }
250
+ if (args[0] === 'test') {
251
+ if (!args[1]) {
252
+ console.error(`${c.red}Error: Target required${c.reset}`);
253
+ console.error(`Usage: npx private-connect test <host:port>`);
254
+ process.exit(1);
255
+ }
256
+ runTest(args[1]).catch(console.error);
257
+ }
258
+ else {
259
+ // Default to test if just a target is provided
260
+ runTest(args[0]).catch(console.error);
261
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "private-connect",
3
+ "version": "0.3.0",
4
+ "description": "Test connectivity to any service. No signup required.",
5
+ "bin": {
6
+ "private-connect": "./dist/index.js"
7
+ },
8
+ "main": "dist/index.js",
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc -w"
15
+ },
16
+ "keywords": [
17
+ "connectivity",
18
+ "diagnostics",
19
+ "network",
20
+ "tunnel",
21
+ "private-connect"
22
+ ],
23
+ "license": "FSL-1.1-MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/treadiehq/private-connect.git",
27
+ "directory": "packages/cli"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^20.0.0",
31
+ "typescript": "^5.0.0"
32
+ }
33
+ }
34
+