pinggy 0.4.6 → 0.4.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.
- package/README.md +92 -4
- package/dist/{chunk-STEISST3.js → chunk-443UO6IY.js} +1 -0
- package/dist/index.cjs +703 -94
- package/dist/index.js +2 -2
- package/dist/{main-XKFFUSKJ.js → main-PUM4SD6B.js} +659 -86
- package/package.json +2 -2
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
RemoteManagementUnauthorizedError,
|
|
4
4
|
TunnelManager,
|
|
5
5
|
TunnelOperations,
|
|
6
|
+
buildRemoteManagementWsUrl,
|
|
6
7
|
closeRemoteManagement,
|
|
7
8
|
getRandomId,
|
|
8
9
|
getRemoteManagementState,
|
|
@@ -10,8 +11,9 @@ import {
|
|
|
10
11
|
initiateRemoteManagement,
|
|
11
12
|
isValidPort,
|
|
12
13
|
parseRemoteManagement,
|
|
13
|
-
printer_default
|
|
14
|
-
|
|
14
|
+
printer_default,
|
|
15
|
+
startRemoteManagement
|
|
16
|
+
} from "./chunk-443UO6IY.js";
|
|
15
17
|
import {
|
|
16
18
|
configureLogger,
|
|
17
19
|
enablePackageLogging,
|
|
@@ -45,9 +47,13 @@ var cliOptions = {
|
|
|
45
47
|
vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
|
|
46
48
|
vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
|
|
47
49
|
autoreconnect: { type: "string", short: "a", description: "Automatically reconnect tunnel on failure (enabled by default). Use -a false to disable." },
|
|
48
|
-
// Save and load config
|
|
50
|
+
// Save and load config (legacy file-based)
|
|
49
51
|
saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
|
|
50
52
|
conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
|
|
53
|
+
// Used by `pinggy config save` and `buildAndStartTunnel` save flow
|
|
54
|
+
save: { type: "boolean", short: "s", description: "Save the tunnel config (use with config save or -l)", hidden: true },
|
|
55
|
+
name: { type: "string", description: "Name for the tunnel config", hidden: true },
|
|
56
|
+
auto: { type: "boolean", description: "Mark tunnel config for auto-start", hidden: true },
|
|
51
57
|
// File server
|
|
52
58
|
serve: { type: "string", description: "Start a webserver to serve files from the specified path. Eg --serve /path/to/files" },
|
|
53
59
|
// Remote Control
|
|
@@ -95,11 +101,92 @@ function printHelpMessage() {
|
|
|
95
101
|
console.log(" pinggy -R0:localhost:3000 # Basic HTTP tunnel");
|
|
96
102
|
console.log(" pinggy --type tcp -R0:localhost:22 # TCP tunnel for SSH");
|
|
97
103
|
console.log(" pinggy -R0:localhost:8080 -L4300:localhost:4300 # HTTP tunnel with debugger");
|
|
98
|
-
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region
|
|
104
|
+
console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region");
|
|
105
|
+
console.log("\nConfig Management:");
|
|
106
|
+
console.log(" pinggy config list # List saved configs");
|
|
107
|
+
console.log(" pinggy config show my-tunnel # Show config details");
|
|
108
|
+
console.log(" pinggy config save my-tunnel -l 3000 token@pro.pinggy.io # Save config");
|
|
109
|
+
console.log(" pinggy config save my-tunnel --auto -l 3000 # Save with auto-start");
|
|
110
|
+
console.log(" pinggy config update my-tunnel -l 4000 # Update saved config");
|
|
111
|
+
console.log(" pinggy config delete my-tunnel # Delete saved config");
|
|
112
|
+
console.log(" pinggy config auto my-tunnel # Enable auto-start");
|
|
113
|
+
console.log(" pinggy config noauto my-tunnel # Disable auto-start");
|
|
114
|
+
console.log("\nStart Saved Tunnels:");
|
|
115
|
+
console.log(" pinggy start my-tunnel # Start saved tunnel");
|
|
116
|
+
console.log(" pinggy start my-tunnel -l 4000 # Start with runtime overrides");
|
|
117
|
+
console.log(" pinggy start tunnela tunnelb # Start multiple tunnels");
|
|
118
|
+
console.log(" pinggy start --all # Start all auto-start tunnels\n");
|
|
99
119
|
}
|
|
100
120
|
|
|
121
|
+
// src/utils/parseArgs.ts
|
|
122
|
+
import { parseArgs } from "util";
|
|
123
|
+
import * as os from "os";
|
|
124
|
+
function isAttachedReverseOrLocalFlag(arg) {
|
|
125
|
+
return /^-[RL].+/.test(arg);
|
|
126
|
+
}
|
|
127
|
+
function shouldMergeReverseOrLocalFragment(current, next) {
|
|
128
|
+
if (next.startsWith("-")) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if (next.startsWith(".")) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
const body = current.slice(2);
|
|
135
|
+
if (body.endsWith(":")) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (body.includes("//") && !body.includes(":")) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
function preprocessWindowsArgs(args) {
|
|
144
|
+
if (os.platform() !== "win32") {
|
|
145
|
+
return args;
|
|
146
|
+
}
|
|
147
|
+
;
|
|
148
|
+
const out = [];
|
|
149
|
+
let i = 0;
|
|
150
|
+
while (i < args.length) {
|
|
151
|
+
const arg = args[i];
|
|
152
|
+
if (isAttachedReverseOrLocalFlag(arg)) {
|
|
153
|
+
let merged = arg;
|
|
154
|
+
while (i + 1 < args.length && shouldMergeReverseOrLocalFragment(merged, args[i + 1])) {
|
|
155
|
+
merged += args[i + 1];
|
|
156
|
+
i++;
|
|
157
|
+
}
|
|
158
|
+
out.push(merged);
|
|
159
|
+
i++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
out.push(arg);
|
|
163
|
+
i++;
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
function parseCliArgs(options, overrideArgs) {
|
|
168
|
+
const rawArgs = overrideArgs ?? process.argv.slice(2);
|
|
169
|
+
const processedArgs = preprocessWindowsArgs(rawArgs);
|
|
170
|
+
const parsed = parseArgs({
|
|
171
|
+
args: processedArgs,
|
|
172
|
+
options,
|
|
173
|
+
allowPositionals: true
|
|
174
|
+
});
|
|
175
|
+
const hasAnyArgs = parsed.positionals.length > 0 || Object.values(parsed.values).some((v) => v !== void 0 && v !== false);
|
|
176
|
+
return {
|
|
177
|
+
...parsed,
|
|
178
|
+
hasAnyArgs
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/main.ts
|
|
183
|
+
import { fileURLToPath } from "url";
|
|
184
|
+
import { argv } from "process";
|
|
185
|
+
import { realpathSync } from "fs";
|
|
186
|
+
|
|
101
187
|
// src/cli/defaults.ts
|
|
102
188
|
var defaultOptions = {
|
|
189
|
+
version: "1.0",
|
|
103
190
|
token: void 0,
|
|
104
191
|
// No default token
|
|
105
192
|
serverAddress: "a.pinggy.io",
|
|
@@ -606,7 +693,7 @@ function parseToken(finalConfig, explicitToken) {
|
|
|
606
693
|
finalConfig.token = explicitToken;
|
|
607
694
|
}
|
|
608
695
|
}
|
|
609
|
-
function
|
|
696
|
+
function parseArgs2(finalConfig, remainingPositionals) {
|
|
610
697
|
let localserverTls = "";
|
|
611
698
|
localserverTls = parseExtendedOptions(remainingPositionals, finalConfig, localserverTls);
|
|
612
699
|
if (localserverTls.length > 0 && finalConfig.forwarding) {
|
|
@@ -617,10 +704,10 @@ function parseArgs(finalConfig, remainingPositionals) {
|
|
|
617
704
|
}
|
|
618
705
|
function storeJson(config, saveconf) {
|
|
619
706
|
if (saveconf) {
|
|
620
|
-
const
|
|
707
|
+
const path4 = saveconf;
|
|
621
708
|
try {
|
|
622
|
-
fs.writeFileSync(
|
|
623
|
-
logger.info(`Configuration saved to ${
|
|
709
|
+
fs.writeFileSync(path4, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
|
|
710
|
+
logger.info(`Configuration saved to ${path4}`);
|
|
624
711
|
} catch (err) {
|
|
625
712
|
const msg = err instanceof Error ? err.message : String(err);
|
|
626
713
|
logger.error("Error loading configuration:", msg);
|
|
@@ -670,7 +757,7 @@ function parseAutoReconnect(finalConfig, values) {
|
|
|
670
757
|
}
|
|
671
758
|
return null;
|
|
672
759
|
}
|
|
673
|
-
async function buildFinalConfig(values, positionals) {
|
|
760
|
+
async function buildFinalConfig(values, positionals, baseConfig) {
|
|
674
761
|
let token;
|
|
675
762
|
let server;
|
|
676
763
|
let type;
|
|
@@ -678,7 +765,7 @@ async function buildFinalConfig(values, positionals) {
|
|
|
678
765
|
let qrCode = false;
|
|
679
766
|
let finalConfig = new Object();
|
|
680
767
|
let saveconf = isSaveConfOption(values);
|
|
681
|
-
const configFromFile = loadJsonConfig(values);
|
|
768
|
+
const configFromFile = baseConfig || loadJsonConfig(values);
|
|
682
769
|
const userParse = parseUsers(positionals, values.token);
|
|
683
770
|
token = userParse.token;
|
|
684
771
|
server = userParse.server;
|
|
@@ -730,72 +817,11 @@ async function buildFinalConfig(values, positionals) {
|
|
|
730
817
|
if (forceFlag || values.force) {
|
|
731
818
|
finalConfig.force = true;
|
|
732
819
|
}
|
|
733
|
-
|
|
820
|
+
parseArgs2(finalConfig, remainingPositionals);
|
|
734
821
|
storeJson(finalConfig, saveconf);
|
|
735
822
|
return finalConfig;
|
|
736
823
|
}
|
|
737
824
|
|
|
738
|
-
// src/utils/parseArgs.ts
|
|
739
|
-
import { parseArgs as parseArgs2 } from "util";
|
|
740
|
-
import * as os from "os";
|
|
741
|
-
function isAttachedReverseOrLocalFlag(arg) {
|
|
742
|
-
return /^-[RL].+/.test(arg);
|
|
743
|
-
}
|
|
744
|
-
function shouldMergeReverseOrLocalFragment(current, next) {
|
|
745
|
-
if (next.startsWith("-")) {
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
if (next.startsWith(".")) {
|
|
749
|
-
return true;
|
|
750
|
-
}
|
|
751
|
-
const body = current.slice(2);
|
|
752
|
-
if (body.endsWith(":")) {
|
|
753
|
-
return true;
|
|
754
|
-
}
|
|
755
|
-
if (body.includes("//") && !body.includes(":")) {
|
|
756
|
-
return true;
|
|
757
|
-
}
|
|
758
|
-
return false;
|
|
759
|
-
}
|
|
760
|
-
function preprocessWindowsArgs(args) {
|
|
761
|
-
if (os.platform() !== "win32") {
|
|
762
|
-
return args;
|
|
763
|
-
}
|
|
764
|
-
;
|
|
765
|
-
const out = [];
|
|
766
|
-
let i = 0;
|
|
767
|
-
while (i < args.length) {
|
|
768
|
-
const arg = args[i];
|
|
769
|
-
if (isAttachedReverseOrLocalFlag(arg)) {
|
|
770
|
-
let merged = arg;
|
|
771
|
-
while (i + 1 < args.length && shouldMergeReverseOrLocalFragment(merged, args[i + 1])) {
|
|
772
|
-
merged += args[i + 1];
|
|
773
|
-
i++;
|
|
774
|
-
}
|
|
775
|
-
out.push(merged);
|
|
776
|
-
i++;
|
|
777
|
-
continue;
|
|
778
|
-
}
|
|
779
|
-
out.push(arg);
|
|
780
|
-
i++;
|
|
781
|
-
}
|
|
782
|
-
return out;
|
|
783
|
-
}
|
|
784
|
-
function parseCliArgs(options) {
|
|
785
|
-
const rawArgs = process.argv.slice(2);
|
|
786
|
-
const processedArgs = preprocessWindowsArgs(rawArgs);
|
|
787
|
-
const parsed = parseArgs2({
|
|
788
|
-
args: processedArgs,
|
|
789
|
-
options,
|
|
790
|
-
allowPositionals: true
|
|
791
|
-
});
|
|
792
|
-
const hasAnyArgs = parsed.positionals.length > 0 || Object.values(parsed.values).some((v) => v !== void 0 && v !== false);
|
|
793
|
-
return {
|
|
794
|
-
...parsed,
|
|
795
|
-
hasAnyArgs
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
|
|
799
825
|
// src/utils/getFreePort.ts
|
|
800
826
|
import net from "net";
|
|
801
827
|
function getFreePort(webDebugger) {
|
|
@@ -2299,14 +2325,563 @@ async function startCli(finalConfig, manager) {
|
|
|
2299
2325
|
}
|
|
2300
2326
|
}
|
|
2301
2327
|
|
|
2328
|
+
// src/cli/configStore.ts
|
|
2329
|
+
import fs3 from "fs";
|
|
2330
|
+
import path3 from "path";
|
|
2331
|
+
|
|
2332
|
+
// src/utils/configDir.ts
|
|
2333
|
+
import os2 from "os";
|
|
2334
|
+
import path2 from "path";
|
|
2335
|
+
import fs2 from "fs";
|
|
2336
|
+
function getPinggyConfigDir() {
|
|
2337
|
+
const platform2 = os2.platform();
|
|
2338
|
+
let baseDir;
|
|
2339
|
+
if (platform2 === "win32") {
|
|
2340
|
+
baseDir = process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming");
|
|
2341
|
+
} else {
|
|
2342
|
+
baseDir = process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
|
|
2343
|
+
}
|
|
2344
|
+
return path2.join(baseDir, "pinggy");
|
|
2345
|
+
}
|
|
2346
|
+
function getTunnelConfigDir() {
|
|
2347
|
+
return path2.join(getPinggyConfigDir(), "tunnels");
|
|
2348
|
+
}
|
|
2349
|
+
function ensureTunnelConfigDir() {
|
|
2350
|
+
const dir = getTunnelConfigDir();
|
|
2351
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
2352
|
+
return dir;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
// src/cli/configStore.ts
|
|
2356
|
+
import pico2 from "picocolors";
|
|
2357
|
+
function buildFilename(name, configId) {
|
|
2358
|
+
return `${name}_${configId}.json`;
|
|
2359
|
+
}
|
|
2360
|
+
function sanitizeName(name) {
|
|
2361
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2362
|
+
}
|
|
2363
|
+
function validateName(name) {
|
|
2364
|
+
if (!name || name.trim().length === 0) {
|
|
2365
|
+
return new Error("Tunnel name cannot be empty.");
|
|
2366
|
+
}
|
|
2367
|
+
if (name.length > 128) {
|
|
2368
|
+
return new Error("Tunnel name cannot exceed 128 characters.");
|
|
2369
|
+
}
|
|
2370
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
2371
|
+
return new Error("Tunnel name can only contain alphanumeric characters, hyphens, and underscores.");
|
|
2372
|
+
}
|
|
2373
|
+
return null;
|
|
2374
|
+
}
|
|
2375
|
+
function readConfigFile(filePath) {
|
|
2376
|
+
try {
|
|
2377
|
+
const data = fs3.readFileSync(filePath, { encoding: "utf-8" });
|
|
2378
|
+
return JSON.parse(data);
|
|
2379
|
+
} catch (err) {
|
|
2380
|
+
logger.warn(`Failed to read config file ${filePath}:`, err);
|
|
2381
|
+
return null;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
function writeConfigFile(filePath, config) {
|
|
2385
|
+
fs3.writeFileSync(filePath, JSON.stringify(config, null, 2), { encoding: "utf-8" });
|
|
2386
|
+
}
|
|
2387
|
+
function listSavedConfigs() {
|
|
2388
|
+
const dir = getTunnelConfigDir();
|
|
2389
|
+
if (!fs3.existsSync(dir)) {
|
|
2390
|
+
return [];
|
|
2391
|
+
}
|
|
2392
|
+
const files = fs3.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
2393
|
+
const configs = [];
|
|
2394
|
+
for (const file of files) {
|
|
2395
|
+
const config = readConfigFile(path3.join(dir, file));
|
|
2396
|
+
if (config && config.name && config.configId) {
|
|
2397
|
+
configs.push(config);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
return configs;
|
|
2401
|
+
}
|
|
2402
|
+
function findConfigFile(nameOrId) {
|
|
2403
|
+
const dir = getTunnelConfigDir();
|
|
2404
|
+
if (!fs3.existsSync(dir)) return null;
|
|
2405
|
+
const files = fs3.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
2406
|
+
const sanitized = sanitizeName(nameOrId);
|
|
2407
|
+
const nameMatch = files.find((f) => f.startsWith(sanitized + "_"));
|
|
2408
|
+
if (nameMatch) {
|
|
2409
|
+
const filePath = path3.join(dir, nameMatch);
|
|
2410
|
+
const config = readConfigFile(filePath);
|
|
2411
|
+
if (config && config.name === nameOrId) return { filePath, config };
|
|
2412
|
+
}
|
|
2413
|
+
const idCandidates = files.filter((f) => {
|
|
2414
|
+
const withoutExt = f.replace(/\.json$/, "");
|
|
2415
|
+
const lastUnderscore = withoutExt.indexOf("_");
|
|
2416
|
+
if (lastUnderscore === -1) return false;
|
|
2417
|
+
const idPart = withoutExt.slice(lastUnderscore + 1);
|
|
2418
|
+
return idPart.startsWith(nameOrId);
|
|
2419
|
+
});
|
|
2420
|
+
if (idCandidates.length === 1) {
|
|
2421
|
+
const filePath = path3.join(dir, idCandidates[0]);
|
|
2422
|
+
const config = readConfigFile(filePath);
|
|
2423
|
+
if (config) return { filePath, config };
|
|
2424
|
+
}
|
|
2425
|
+
return null;
|
|
2426
|
+
}
|
|
2427
|
+
function findConfigByName(name) {
|
|
2428
|
+
const resolved = findConfigFile(name);
|
|
2429
|
+
return resolved?.config.name === name ? resolved.config : null;
|
|
2430
|
+
}
|
|
2431
|
+
function findConfig(nameOrId) {
|
|
2432
|
+
return findConfigFile(nameOrId)?.config ?? null;
|
|
2433
|
+
}
|
|
2434
|
+
function saveConfig(name, configId, tunnelConfig, autoStart = false) {
|
|
2435
|
+
const nameErr = validateName(name);
|
|
2436
|
+
if (nameErr) {
|
|
2437
|
+
throw nameErr;
|
|
2438
|
+
}
|
|
2439
|
+
const existing = findConfigByName(name);
|
|
2440
|
+
if (existing) {
|
|
2441
|
+
throw new Error(
|
|
2442
|
+
`A tunnel config with the name "${name}" already exists (configId: ${existing.configId}). Please use a different name.`
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
const dir = ensureTunnelConfigDir();
|
|
2446
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2447
|
+
const saved = {
|
|
2448
|
+
name,
|
|
2449
|
+
configId,
|
|
2450
|
+
autoStart,
|
|
2451
|
+
createdAt: now,
|
|
2452
|
+
updatedAt: now,
|
|
2453
|
+
tunnelConfig
|
|
2454
|
+
};
|
|
2455
|
+
const filename = buildFilename(sanitizeName(name), configId);
|
|
2456
|
+
const filePath = path3.join(dir, filename);
|
|
2457
|
+
fs3.writeFileSync(filePath, JSON.stringify(saved, null, 2), { encoding: "utf-8" });
|
|
2458
|
+
logger.info(`Config "${name}" saved to ${filePath}`);
|
|
2459
|
+
return saved;
|
|
2460
|
+
}
|
|
2461
|
+
function deleteConfig(nameOrId) {
|
|
2462
|
+
const resolved = findConfigFile(nameOrId);
|
|
2463
|
+
if (!resolved) return null;
|
|
2464
|
+
fs3.unlinkSync(resolved.filePath);
|
|
2465
|
+
logger.info(`Config "${resolved.config.name}" deleted.`);
|
|
2466
|
+
return resolved.config.name;
|
|
2467
|
+
}
|
|
2468
|
+
function updateConfigAutoStart(nameOrId, autoStart) {
|
|
2469
|
+
const resolved = findConfigFile(nameOrId);
|
|
2470
|
+
if (!resolved) return null;
|
|
2471
|
+
resolved.config.autoStart = autoStart;
|
|
2472
|
+
resolved.config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2473
|
+
writeConfigFile(resolved.filePath, resolved.config);
|
|
2474
|
+
logger.info(`Config "${resolved.config.name}" auto-start set to ${autoStart}`);
|
|
2475
|
+
return resolved.config;
|
|
2476
|
+
}
|
|
2477
|
+
function updateTunnelConfig(nameOrId, tunnelConfig) {
|
|
2478
|
+
const resolved = findConfigFile(nameOrId);
|
|
2479
|
+
if (!resolved) return null;
|
|
2480
|
+
resolved.config.tunnelConfig = tunnelConfig;
|
|
2481
|
+
resolved.config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2482
|
+
writeConfigFile(resolved.filePath, resolved.config);
|
|
2483
|
+
logger.info(`Config "${resolved.config.name}" tunnel configuration updated`);
|
|
2484
|
+
return resolved.config;
|
|
2485
|
+
}
|
|
2486
|
+
function getAutoStartConfigs() {
|
|
2487
|
+
return listSavedConfigs().filter((c) => c.autoStart);
|
|
2488
|
+
}
|
|
2489
|
+
function printConfigList() {
|
|
2490
|
+
const configs = listSavedConfigs();
|
|
2491
|
+
if (configs.length === 0) {
|
|
2492
|
+
console.log(pico2.yellow("No saved tunnel configs found."));
|
|
2493
|
+
console.log(pico2.gray(`Config directory: ${getTunnelConfigDir()}`));
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
const nameW = 20;
|
|
2497
|
+
const idW = 12;
|
|
2498
|
+
const typeW = 8;
|
|
2499
|
+
const fwdW = 25;
|
|
2500
|
+
const serverW = 22;
|
|
2501
|
+
const autoW = 10;
|
|
2502
|
+
const header = pico2.bold("Name".padEnd(nameW)) + pico2.bold("Config ID".padEnd(idW)) + pico2.bold("Type".padEnd(typeW)) + pico2.bold("Forwarding".padEnd(fwdW)) + pico2.bold("Server".padEnd(serverW)) + pico2.bold("Auto-start".padEnd(autoW));
|
|
2503
|
+
console.log("\n" + header);
|
|
2504
|
+
console.log(pico2.gray("\u2500".repeat(nameW + idW + typeW + fwdW + serverW + autoW)));
|
|
2505
|
+
for (const c of configs) {
|
|
2506
|
+
const tc = c.tunnelConfig;
|
|
2507
|
+
const forwarding = Array.isArray(tc.forwarding) ? tc.forwarding[0]?.address : String(tc.forwarding || "");
|
|
2508
|
+
const type = (Array.isArray(tc.forwarding) ? tc.forwarding[0]?.type : void 0) || "http";
|
|
2509
|
+
const server = tc.serverAddress || "a.pinggy.io";
|
|
2510
|
+
const line = pico2.cyanBright(c.name.padEnd(nameW)) + pico2.gray(c.configId.slice(0, 8).padEnd(idW)) + type.padEnd(typeW) + forwarding.slice(0, fwdW - 2).padEnd(fwdW) + server.slice(0, serverW - 2).padEnd(serverW) + (c.autoStart ? pico2.green("yes") : pico2.gray("no")).padEnd(autoW);
|
|
2511
|
+
console.log(line);
|
|
2512
|
+
}
|
|
2513
|
+
console.log();
|
|
2514
|
+
}
|
|
2515
|
+
function printConfigDetail(config) {
|
|
2516
|
+
console.log(pico2.bold(`
|
|
2517
|
+
Tunnel Config: ${pico2.cyanBright(config.name)}`));
|
|
2518
|
+
console.log(pico2.gray("\u2500".repeat(40)));
|
|
2519
|
+
console.log(` Config ID: ${config.configId}`);
|
|
2520
|
+
console.log(` Auto-start: ${config.autoStart ? pico2.green("yes") : pico2.gray("no")}`);
|
|
2521
|
+
console.log(` Created: ${config.createdAt}`);
|
|
2522
|
+
console.log(` Updated: ${config.updatedAt}`);
|
|
2523
|
+
console.log(pico2.gray("\u2500".repeat(40)));
|
|
2524
|
+
console.log(` Server: ${config.tunnelConfig.serverAddress || "a.pinggy.io"}`);
|
|
2525
|
+
console.log(` Token: ${config.tunnelConfig.token ? "***" + config.tunnelConfig.token.slice(-4) : "(none)"}`);
|
|
2526
|
+
const fwd = config.tunnelConfig.forwarding;
|
|
2527
|
+
if (Array.isArray(fwd)) {
|
|
2528
|
+
const defaultFwds = [];
|
|
2529
|
+
const customFwds = [];
|
|
2530
|
+
for (const f of fwd) {
|
|
2531
|
+
if (typeof f === "string") {
|
|
2532
|
+
defaultFwds.push(f);
|
|
2533
|
+
} else if (f.listenAddress) {
|
|
2534
|
+
customFwds.push(f);
|
|
2535
|
+
} else {
|
|
2536
|
+
defaultFwds.push(f);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
for (const f of defaultFwds) {
|
|
2540
|
+
const addr = typeof f === "string" ? f : `${f.address} (${f.type || "http"})`;
|
|
2541
|
+
console.log(` Forwarding: ${addr}`);
|
|
2542
|
+
if (config.tunnelConfig.webDebugger) {
|
|
2543
|
+
console.log(` Debugger: ${config.tunnelConfig.webDebugger}`);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
if (customFwds.length > 0) {
|
|
2547
|
+
console.log(pico2.gray("\u2500".repeat(40)));
|
|
2548
|
+
console.log(pico2.bold(" Domain Mappings:"));
|
|
2549
|
+
for (const f of customFwds) {
|
|
2550
|
+
if (typeof f === "string") continue;
|
|
2551
|
+
const domain = f.listenAddress;
|
|
2552
|
+
const target = f.address;
|
|
2553
|
+
const type = f.type || "http";
|
|
2554
|
+
console.log(` ${pico2.cyanBright(domain)} \u2192 ${target} (${type})`);
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
} else if (fwd) {
|
|
2558
|
+
console.log(` Forwarding: ${fwd}`);
|
|
2559
|
+
}
|
|
2560
|
+
console.log();
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
// src/cli/buildAndStartTunnel.ts
|
|
2564
|
+
async function buildAndStartTunnel(values, positionals, manager) {
|
|
2565
|
+
await initRemoteManagement(values);
|
|
2566
|
+
logger.debug("Building final config from CLI values and positionals", { values, positionals });
|
|
2567
|
+
const finalConfig = await buildFinalConfig(values, positionals);
|
|
2568
|
+
logger.debug("Final configuration built", finalConfig);
|
|
2569
|
+
if (values.save) {
|
|
2570
|
+
const name = values.name;
|
|
2571
|
+
if (!name) {
|
|
2572
|
+
printer_default.error("--save requires --name to specify a name for the tunnel config.");
|
|
2573
|
+
process.exit(1);
|
|
2574
|
+
}
|
|
2575
|
+
const nameErr = validateName(name);
|
|
2576
|
+
if (nameErr) {
|
|
2577
|
+
printer_default.error(nameErr.message);
|
|
2578
|
+
process.exit(1);
|
|
2579
|
+
}
|
|
2580
|
+
const autoStart = !!values.auto;
|
|
2581
|
+
saveConfig(name, finalConfig.configId, finalConfig, autoStart);
|
|
2582
|
+
printer_default.success(`Config "${name}" saved.`);
|
|
2583
|
+
}
|
|
2584
|
+
await startCli(finalConfig, manager);
|
|
2585
|
+
}
|
|
2586
|
+
async function initRemoteManagement(values) {
|
|
2587
|
+
const parseResult = await parseRemoteManagement(values);
|
|
2588
|
+
if (parseResult?.ok === false) {
|
|
2589
|
+
logger.error("Failed to initiate remote management:", parseResult.error);
|
|
2590
|
+
printer_default.fatal(parseResult.error);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
// src/cli/subcommands.ts
|
|
2595
|
+
import pico3 from "picocolors";
|
|
2596
|
+
var SUBCOMMANDS = /* @__PURE__ */ new Set(["config", "start"]);
|
|
2597
|
+
function isSubcommand(rawArgs) {
|
|
2598
|
+
return rawArgs.length > 0 && SUBCOMMANDS.has(rawArgs[0]);
|
|
2599
|
+
}
|
|
2600
|
+
async function handleSubcommand(rawArgs, manager) {
|
|
2601
|
+
const sub = rawArgs[0];
|
|
2602
|
+
const rest = rawArgs.slice(1);
|
|
2603
|
+
switch (sub) {
|
|
2604
|
+
case "config":
|
|
2605
|
+
await handleConfig(rest);
|
|
2606
|
+
return;
|
|
2607
|
+
case "start":
|
|
2608
|
+
await handleStart(rest, manager);
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
async function handleConfig(args) {
|
|
2613
|
+
if (args.length === 0) {
|
|
2614
|
+
printConfigHelp();
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
const verb = args[0];
|
|
2618
|
+
const rest = args.slice(1);
|
|
2619
|
+
switch (verb) {
|
|
2620
|
+
case "list":
|
|
2621
|
+
case "ls":
|
|
2622
|
+
printConfigList();
|
|
2623
|
+
return;
|
|
2624
|
+
case "show": {
|
|
2625
|
+
const names = requireNames(rest, "config show");
|
|
2626
|
+
for (const name of names) {
|
|
2627
|
+
const saved2 = resolveConfig(name);
|
|
2628
|
+
if (saved2) printConfigDetail(saved2);
|
|
2629
|
+
}
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
case "save": {
|
|
2633
|
+
const name = requireName(rest, "config save");
|
|
2634
|
+
await handleConfigSave(name, rest.slice(1));
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
case "delete": {
|
|
2638
|
+
const names = requireNames(rest, "config delete");
|
|
2639
|
+
for (const name of names) {
|
|
2640
|
+
const deletedName = deleteConfig(name);
|
|
2641
|
+
if (deletedName) {
|
|
2642
|
+
printer_default.success(`Config "${deletedName}" deleted.`);
|
|
2643
|
+
} else {
|
|
2644
|
+
printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
return;
|
|
2648
|
+
}
|
|
2649
|
+
case "update": {
|
|
2650
|
+
const name = requireName(rest, "config update");
|
|
2651
|
+
await handleConfigUpdate(name, rest.slice(1));
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
case "auto": {
|
|
2655
|
+
const names = requireNames(rest, "config auto");
|
|
2656
|
+
for (const name of names) {
|
|
2657
|
+
const updated = updateConfigAutoStart(name, true);
|
|
2658
|
+
if (updated) {
|
|
2659
|
+
printer_default.success(`Config "${updated.name}" auto-start set to on.`);
|
|
2660
|
+
} else {
|
|
2661
|
+
printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
case "noauto": {
|
|
2667
|
+
const names = requireNames(rest, "config noauto");
|
|
2668
|
+
for (const name of names) {
|
|
2669
|
+
const updated = updateConfigAutoStart(name, false);
|
|
2670
|
+
if (updated) {
|
|
2671
|
+
printer_default.success(`Config "${updated.name}" auto-start set to off.`);
|
|
2672
|
+
} else {
|
|
2673
|
+
printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
default:
|
|
2679
|
+
const saved = resolveConfig(verb);
|
|
2680
|
+
if (saved) printConfigDetail(saved);
|
|
2681
|
+
return;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
async function handleConfigSave(name, remainingArgs) {
|
|
2685
|
+
const nameErr = validateName(name);
|
|
2686
|
+
if (nameErr) {
|
|
2687
|
+
printer_default.error(nameErr.message);
|
|
2688
|
+
process.exit(1);
|
|
2689
|
+
}
|
|
2690
|
+
const { values, positionals } = parseCliArgs(cliOptions, remainingArgs);
|
|
2691
|
+
const autoStart = !!values.auto;
|
|
2692
|
+
logger.debug("Building config for save", { name, values, positionals });
|
|
2693
|
+
const finalConfig = await buildFinalConfig(values, positionals);
|
|
2694
|
+
saveConfig(name, finalConfig.configId, finalConfig, autoStart);
|
|
2695
|
+
printer_default.success(`Config "${name}" saved.`);
|
|
2696
|
+
}
|
|
2697
|
+
async function handleConfigUpdate(nameOrId, remainingArgs) {
|
|
2698
|
+
const saved = resolveConfig(nameOrId);
|
|
2699
|
+
if (!saved) return;
|
|
2700
|
+
const { values, positionals } = parseCliArgs(cliOptions, remainingArgs);
|
|
2701
|
+
logger.debug("Building updated config", { nameOrId, values, positionals });
|
|
2702
|
+
const updatedConfig = await buildFinalConfig(values, positionals, saved.tunnelConfig);
|
|
2703
|
+
const result = updateTunnelConfig(nameOrId, updatedConfig);
|
|
2704
|
+
if (result) {
|
|
2705
|
+
printer_default.success(`Config "${result.name}" updated.`);
|
|
2706
|
+
printConfigDetail(result);
|
|
2707
|
+
} else {
|
|
2708
|
+
printer_default.error(`Failed to update config "${nameOrId}".`);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
async function handleStart(args, manager) {
|
|
2712
|
+
const startAll = args.includes("--all");
|
|
2713
|
+
const argsWithoutAll = args.filter((a) => a !== "--all");
|
|
2714
|
+
const names = [];
|
|
2715
|
+
let i = 0;
|
|
2716
|
+
while (i < argsWithoutAll.length && !argsWithoutAll[i].startsWith("-")) {
|
|
2717
|
+
names.push(argsWithoutAll[i]);
|
|
2718
|
+
i++;
|
|
2719
|
+
}
|
|
2720
|
+
const flagArgs = argsWithoutAll.slice(i);
|
|
2721
|
+
const { values, positionals } = parseCliArgs(cliOptions, flagArgs);
|
|
2722
|
+
configureLogger(values);
|
|
2723
|
+
if (startAll) {
|
|
2724
|
+
await initRemoteManagementBackground(values);
|
|
2725
|
+
await startAutoStartTunnels(manager);
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
if (names.length === 0) {
|
|
2729
|
+
printStartHelp();
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
const resolved = [];
|
|
2733
|
+
for (const name of names) {
|
|
2734
|
+
const saved = resolveConfig(name);
|
|
2735
|
+
if (!saved) return;
|
|
2736
|
+
resolved.push(saved);
|
|
2737
|
+
}
|
|
2738
|
+
if (resolved.length > 1 && flagArgs.length > 0) {
|
|
2739
|
+
printer_default.error("Runtime overrides (-l, --type, etc.) can only be used when starting a single tunnel.");
|
|
2740
|
+
printer_default.print(" Start one tunnel: pinggy start my-tunnel -l 4000");
|
|
2741
|
+
printer_default.print(" Or update first: pinggy config update my-tunnel -l 4000");
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
await initRemoteManagementBackground(values);
|
|
2745
|
+
if (resolved.length === 1) {
|
|
2746
|
+
const saved = resolved[0];
|
|
2747
|
+
logger.debug("Building config with overrides", { name: saved.name });
|
|
2748
|
+
const finalConfig = await buildFinalConfig(values, positionals, saved.tunnelConfig);
|
|
2749
|
+
finalConfig.configId = saved.configId;
|
|
2750
|
+
await startCli(finalConfig, manager);
|
|
2751
|
+
} else {
|
|
2752
|
+
await startNamedTunnels(resolved, manager);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
async function startAutoStartTunnels(manager) {
|
|
2756
|
+
const configs = getAutoStartConfigs();
|
|
2757
|
+
if (configs.length === 0) {
|
|
2758
|
+
printer_default.warn("No configs marked for auto-start. Use: pinggy config auto <name>");
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
printer_default.print(pico3.cyanBright(`Starting ${configs.length} auto-start tunnel(s)...`));
|
|
2762
|
+
for (const saved of configs) {
|
|
2763
|
+
await startSavedTunnel(saved, manager);
|
|
2764
|
+
}
|
|
2765
|
+
printer_default.print(pico3.gray("\nAll auto-start tunnels launched. Press Ctrl+C to stop.\n"));
|
|
2766
|
+
await new Promise(() => {
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
async function startNamedTunnels(configs, manager) {
|
|
2770
|
+
printer_default.print(pico3.cyanBright(`Starting ${configs.length} tunnel(s)...`));
|
|
2771
|
+
for (const saved of configs) {
|
|
2772
|
+
await startSavedTunnel(saved, manager);
|
|
2773
|
+
}
|
|
2774
|
+
printer_default.print(pico3.gray("\nAll tunnels launched. Press Ctrl+C to stop.\n"));
|
|
2775
|
+
await new Promise(() => {
|
|
2776
|
+
});
|
|
2777
|
+
}
|
|
2778
|
+
async function startSavedTunnel(saved, manager) {
|
|
2779
|
+
const config = {
|
|
2780
|
+
...saved.tunnelConfig,
|
|
2781
|
+
configId: saved.configId,
|
|
2782
|
+
name: saved.name,
|
|
2783
|
+
optional: {
|
|
2784
|
+
...saved.tunnelConfig.optional,
|
|
2785
|
+
noTui: true
|
|
2786
|
+
}
|
|
2787
|
+
};
|
|
2788
|
+
try {
|
|
2789
|
+
const tunnel = await manager.createTunnel(config);
|
|
2790
|
+
await manager.startTunnel(tunnel.tunnelid);
|
|
2791
|
+
const urls = await manager.getTunnelUrls(tunnel.tunnelid);
|
|
2792
|
+
printer_default.success(`"${saved.name}" started`);
|
|
2793
|
+
(urls ?? []).forEach(
|
|
2794
|
+
(url) => printer_default.print(" " + pico3.magentaBright(url))
|
|
2795
|
+
);
|
|
2796
|
+
manager.registerWorkerErrorListner(tunnel.tunnelid, (_id, error) => {
|
|
2797
|
+
printer_default.error(`[${saved.name}] Fatal: ${error.message}`);
|
|
2798
|
+
});
|
|
2799
|
+
manager.registerDisconnectListener(tunnel.tunnelid, async (_id, error, messages) => {
|
|
2800
|
+
if (error) printer_default.warn(`[${saved.name}] Disconnected: ${error}`);
|
|
2801
|
+
messages?.forEach((m) => printer_default.warn(`[${saved.name}] ${m}`));
|
|
2802
|
+
});
|
|
2803
|
+
manager.registerReconnectingListener(tunnel.tunnelid, (_id, retryCnt) => {
|
|
2804
|
+
printer_default.print(pico3.gray(`[${saved.name}] Reconnecting (attempt #${retryCnt})...`));
|
|
2805
|
+
});
|
|
2806
|
+
manager.registerReconnectionCompletedListener(tunnel.tunnelid, async (_id, urls2) => {
|
|
2807
|
+
printer_default.success(`[${saved.name}] Reconnected`);
|
|
2808
|
+
(urls2 ?? []).forEach(
|
|
2809
|
+
(url) => printer_default.print(" " + pico3.magentaBright(url))
|
|
2810
|
+
);
|
|
2811
|
+
});
|
|
2812
|
+
manager.registerReconnectionFailedListener(tunnel.tunnelid, (_id, retryCnt) => {
|
|
2813
|
+
printer_default.error(`[${saved.name}] Reconnection failed after ${retryCnt} attempts`);
|
|
2814
|
+
});
|
|
2815
|
+
} catch (err) {
|
|
2816
|
+
printer_default.error(`[${saved.name}] Failed to start: ${err.message || err}`);
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
function resolveConfig(nameOrId) {
|
|
2820
|
+
const saved = findConfig(nameOrId);
|
|
2821
|
+
if (!saved) {
|
|
2822
|
+
printer_default.error(`No config found matching "${nameOrId}". Use: pinggy config list`);
|
|
2823
|
+
return null;
|
|
2824
|
+
}
|
|
2825
|
+
return saved;
|
|
2826
|
+
}
|
|
2827
|
+
function requireName(args, command) {
|
|
2828
|
+
if (args.length === 0 || args[0].startsWith("-")) {
|
|
2829
|
+
printer_default.error(`Tunnel name is required. Usage: pinggy ${command} <name>`);
|
|
2830
|
+
process.exit(1);
|
|
2831
|
+
}
|
|
2832
|
+
return args[0];
|
|
2833
|
+
}
|
|
2834
|
+
function requireNames(args, command) {
|
|
2835
|
+
const names = [];
|
|
2836
|
+
for (const arg of args) {
|
|
2837
|
+
if (arg.startsWith("-")) break;
|
|
2838
|
+
names.push(arg);
|
|
2839
|
+
}
|
|
2840
|
+
if (names.length === 0) {
|
|
2841
|
+
printer_default.error(`At least one tunnel name is required. Usage: pinggy ${command} <name> [name2 ...]`);
|
|
2842
|
+
process.exit(1);
|
|
2843
|
+
}
|
|
2844
|
+
return names;
|
|
2845
|
+
}
|
|
2846
|
+
async function initRemoteManagementBackground(values) {
|
|
2847
|
+
const rmToken = values["remote-management"];
|
|
2848
|
+
if (typeof rmToken === "string" && rmToken.trim().length > 0) {
|
|
2849
|
+
const manageHost = values["manage"];
|
|
2850
|
+
try {
|
|
2851
|
+
await startRemoteManagement({
|
|
2852
|
+
apiKey: rmToken,
|
|
2853
|
+
serverUrl: buildRemoteManagementWsUrl(manageHost)
|
|
2854
|
+
});
|
|
2855
|
+
} catch (e) {
|
|
2856
|
+
logger.error("Failed to initiate remote management:", e);
|
|
2857
|
+
printer_default.fatal(e);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
function printConfigHelp() {
|
|
2862
|
+
console.log("\nUsage: pinggy config <command> [name] [options]\n");
|
|
2863
|
+
console.log("Commands:");
|
|
2864
|
+
console.log(" list List all saved configs");
|
|
2865
|
+
console.log(" show <name> Show config details");
|
|
2866
|
+
console.log(" save <name> [tunnel flags] Save a tunnel config");
|
|
2867
|
+
console.log(" update <name> [tunnel flags] Update a saved config");
|
|
2868
|
+
console.log(" delete <name> Delete a saved config");
|
|
2869
|
+
console.log(" auto <name> Enable auto-start");
|
|
2870
|
+
console.log(" noauto <name> Disable auto-start\n");
|
|
2871
|
+
}
|
|
2872
|
+
function printStartHelp() {
|
|
2873
|
+
console.log("\nUsage: pinggy start <name> [options]\n");
|
|
2874
|
+
console.log("Examples:");
|
|
2875
|
+
console.log(" pinggy start my-tunnel Start a saved tunnel");
|
|
2876
|
+
console.log(" pinggy start my-tunnel -l 4000 Start with override");
|
|
2877
|
+
console.log(" pinggy start tunnela tunnelb Start multiple tunnels");
|
|
2878
|
+
console.log(" pinggy start --all Start all auto-start tunnels\n");
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2302
2881
|
// src/main.ts
|
|
2303
|
-
import { fileURLToPath } from "url";
|
|
2304
|
-
import { argv } from "process";
|
|
2305
|
-
import { realpathSync } from "fs";
|
|
2306
2882
|
async function main() {
|
|
2307
2883
|
try {
|
|
2308
|
-
const
|
|
2309
|
-
configureLogger(values);
|
|
2884
|
+
const rawArgs = process.argv.slice(2);
|
|
2310
2885
|
const manager = TunnelManager.getInstance();
|
|
2311
2886
|
process.on("SIGINT", () => {
|
|
2312
2887
|
logger.info("SIGINT received: stopping tunnels and exiting");
|
|
@@ -2315,6 +2890,12 @@ async function main() {
|
|
|
2315
2890
|
console.log("Tunnels stopped. Exiting.");
|
|
2316
2891
|
process.exit(0);
|
|
2317
2892
|
});
|
|
2893
|
+
if (isSubcommand(rawArgs)) {
|
|
2894
|
+
await handleSubcommand(rawArgs, manager);
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
|
|
2898
|
+
configureLogger(values);
|
|
2318
2899
|
if (!hasAnyArgs || values.help) {
|
|
2319
2900
|
printHelpMessage();
|
|
2320
2901
|
return;
|
|
@@ -2323,15 +2904,7 @@ async function main() {
|
|
|
2323
2904
|
printer_default.print(`Pinggy CLI version: ${getVersion()}`);
|
|
2324
2905
|
return;
|
|
2325
2906
|
}
|
|
2326
|
-
|
|
2327
|
-
if (parseResult?.ok === false) {
|
|
2328
|
-
logger.error("Failed to initiate remote management:", parseResult.error);
|
|
2329
|
-
printer_default.fatal(parseResult.error);
|
|
2330
|
-
}
|
|
2331
|
-
logger.debug("Building final config from CLI values and positionals", { values, positionals });
|
|
2332
|
-
const finalConfig = await buildFinalConfig(values, positionals);
|
|
2333
|
-
logger.debug("Final configuration built", finalConfig);
|
|
2334
|
-
await startCli(finalConfig, manager);
|
|
2907
|
+
await buildAndStartTunnel(values, positionals, manager);
|
|
2335
2908
|
} catch (error) {
|
|
2336
2909
|
logger.error("Unhandled error in CLI:", error);
|
|
2337
2910
|
printer_default.fatal(error);
|