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/dist/index.cjs CHANGED
@@ -3085,9 +3085,13 @@ var init_options = __esm({
3085
3085
  vv: { type: "boolean", description: "Enable detailed logging for the Node.js SDK and Libpinggy, including both info and debug level logs." },
3086
3086
  vvv: { type: "boolean", description: "Enable all logs from Cli, SDK and internal components." },
3087
3087
  autoreconnect: { type: "string", short: "a", description: "Automatically reconnect tunnel on failure (enabled by default). Use -a false to disable." },
3088
- // Save and load config
3088
+ // Save and load config (legacy file-based)
3089
3089
  saveconf: { type: "string", description: "Create the configuration file based on the options provided here" },
3090
3090
  conf: { type: "string", description: "Use the configuration file as base. Other options will be used to override this file" },
3091
+ // Used by `pinggy config save` and `buildAndStartTunnel` save flow
3092
+ save: { type: "boolean", short: "s", description: "Save the tunnel config (use with config save or -l)", hidden: true },
3093
+ name: { type: "string", description: "Name for the tunnel config", hidden: true },
3094
+ auto: { type: "boolean", description: "Mark tunnel config for auto-start", hidden: true },
3091
3095
  // File server
3092
3096
  serve: { type: "string", description: "Start a webserver to serve files from the specified path. Eg --serve /path/to/files" },
3093
3097
  // Remote Control
@@ -3137,7 +3141,21 @@ function printHelpMessage() {
3137
3141
  console.log(" pinggy -R0:localhost:3000 # Basic HTTP tunnel");
3138
3142
  console.log(" pinggy --type tcp -R0:localhost:22 # TCP tunnel for SSH");
3139
3143
  console.log(" pinggy -R0:localhost:8080 -L4300:localhost:4300 # HTTP tunnel with debugger");
3140
- console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region\n");
3144
+ console.log(" pinggy tcp@ap.example.com -R0:localhost:22 # TCP tunnel to region");
3145
+ console.log("\nConfig Management:");
3146
+ console.log(" pinggy config list # List saved configs");
3147
+ console.log(" pinggy config show my-tunnel # Show config details");
3148
+ console.log(" pinggy config save my-tunnel -l 3000 token@pro.pinggy.io # Save config");
3149
+ console.log(" pinggy config save my-tunnel --auto -l 3000 # Save with auto-start");
3150
+ console.log(" pinggy config update my-tunnel -l 4000 # Update saved config");
3151
+ console.log(" pinggy config delete my-tunnel # Delete saved config");
3152
+ console.log(" pinggy config auto my-tunnel # Enable auto-start");
3153
+ console.log(" pinggy config noauto my-tunnel # Disable auto-start");
3154
+ console.log("\nStart Saved Tunnels:");
3155
+ console.log(" pinggy start my-tunnel # Start saved tunnel");
3156
+ console.log(" pinggy start my-tunnel -l 4000 # Start with runtime overrides");
3157
+ console.log(" pinggy start tunnela tunnelb # Start multiple tunnels");
3158
+ console.log(" pinggy start --all # Start all auto-start tunnels\n");
3141
3159
  }
3142
3160
  var init_help = __esm({
3143
3161
  "src/cli/help.ts"() {
@@ -3147,6 +3165,74 @@ var init_help = __esm({
3147
3165
  }
3148
3166
  });
3149
3167
 
3168
+ // src/utils/parseArgs.ts
3169
+ function isAttachedReverseOrLocalFlag(arg) {
3170
+ return /^-[RL].+/.test(arg);
3171
+ }
3172
+ function shouldMergeReverseOrLocalFragment(current, next) {
3173
+ if (next.startsWith("-")) {
3174
+ return false;
3175
+ }
3176
+ if (next.startsWith(".")) {
3177
+ return true;
3178
+ }
3179
+ const body = current.slice(2);
3180
+ if (body.endsWith(":")) {
3181
+ return true;
3182
+ }
3183
+ if (body.includes("//") && !body.includes(":")) {
3184
+ return true;
3185
+ }
3186
+ return false;
3187
+ }
3188
+ function preprocessWindowsArgs(args) {
3189
+ if (os2.platform() !== "win32") {
3190
+ return args;
3191
+ }
3192
+ ;
3193
+ const out = [];
3194
+ let i = 0;
3195
+ while (i < args.length) {
3196
+ const arg = args[i];
3197
+ if (isAttachedReverseOrLocalFlag(arg)) {
3198
+ let merged = arg;
3199
+ while (i + 1 < args.length && shouldMergeReverseOrLocalFragment(merged, args[i + 1])) {
3200
+ merged += args[i + 1];
3201
+ i++;
3202
+ }
3203
+ out.push(merged);
3204
+ i++;
3205
+ continue;
3206
+ }
3207
+ out.push(arg);
3208
+ i++;
3209
+ }
3210
+ return out;
3211
+ }
3212
+ function parseCliArgs(options, overrideArgs) {
3213
+ const rawArgs = overrideArgs ?? process.argv.slice(2);
3214
+ const processedArgs = preprocessWindowsArgs(rawArgs);
3215
+ const parsed = (0, import_util5.parseArgs)({
3216
+ args: processedArgs,
3217
+ options,
3218
+ allowPositionals: true
3219
+ });
3220
+ const hasAnyArgs = parsed.positionals.length > 0 || Object.values(parsed.values).some((v) => v !== void 0 && v !== false);
3221
+ return {
3222
+ ...parsed,
3223
+ hasAnyArgs
3224
+ };
3225
+ }
3226
+ var import_util5, os2;
3227
+ var init_parseArgs = __esm({
3228
+ "src/utils/parseArgs.ts"() {
3229
+ "use strict";
3230
+ init_cjs_shims();
3231
+ import_util5 = require("util");
3232
+ os2 = __toESM(require("os"), 1);
3233
+ }
3234
+ });
3235
+
3150
3236
  // src/cli/defaults.ts
3151
3237
  var defaultOptions;
3152
3238
  var init_defaults = __esm({
@@ -3154,6 +3240,7 @@ var init_defaults = __esm({
3154
3240
  "use strict";
3155
3241
  init_cjs_shims();
3156
3242
  defaultOptions = {
3243
+ version: "1.0",
3157
3244
  token: void 0,
3158
3245
  // No default token
3159
3246
  serverAddress: "a.pinggy.io",
@@ -3656,7 +3743,7 @@ function parseToken(finalConfig, explicitToken) {
3656
3743
  finalConfig.token = explicitToken;
3657
3744
  }
3658
3745
  }
3659
- function parseArgs(finalConfig, remainingPositionals) {
3746
+ function parseArgs2(finalConfig, remainingPositionals) {
3660
3747
  let localserverTls = "";
3661
3748
  localserverTls = parseExtendedOptions(remainingPositionals, finalConfig, localserverTls);
3662
3749
  if (localserverTls.length > 0 && finalConfig.forwarding) {
@@ -3667,10 +3754,10 @@ function parseArgs(finalConfig, remainingPositionals) {
3667
3754
  }
3668
3755
  function storeJson(config, saveconf) {
3669
3756
  if (saveconf) {
3670
- const path5 = saveconf;
3757
+ const path7 = saveconf;
3671
3758
  try {
3672
- import_fs4.default.writeFileSync(path5, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
3673
- logger.info(`Configuration saved to ${path5}`);
3759
+ import_fs4.default.writeFileSync(path7, JSON.stringify(config, null, 2), { encoding: "utf-8", flag: "w" });
3760
+ logger.info(`Configuration saved to ${path7}`);
3674
3761
  } catch (err) {
3675
3762
  const msg = err instanceof Error ? err.message : String(err);
3676
3763
  logger.error("Error loading configuration:", msg);
@@ -3720,7 +3807,7 @@ function parseAutoReconnect(finalConfig, values) {
3720
3807
  }
3721
3808
  return null;
3722
3809
  }
3723
- async function buildFinalConfig(values, positionals) {
3810
+ async function buildFinalConfig(values, positionals, baseConfig) {
3724
3811
  let token;
3725
3812
  let server;
3726
3813
  let type;
@@ -3728,7 +3815,7 @@ async function buildFinalConfig(values, positionals) {
3728
3815
  let qrCode = false;
3729
3816
  let finalConfig = new Object();
3730
3817
  let saveconf = isSaveConfOption(values);
3731
- const configFromFile = loadJsonConfig(values);
3818
+ const configFromFile = baseConfig || loadJsonConfig(values);
3732
3819
  const userParse = parseUsers(positionals, values.token);
3733
3820
  token = userParse.token;
3734
3821
  server = userParse.server;
@@ -3780,7 +3867,7 @@ async function buildFinalConfig(values, positionals) {
3780
3867
  if (forceFlag || values.force) {
3781
3868
  finalConfig.force = true;
3782
3869
  }
3783
- parseArgs(finalConfig, remainingPositionals);
3870
+ parseArgs2(finalConfig, remainingPositionals);
3784
3871
  storeJson(finalConfig, saveconf);
3785
3872
  return finalConfig;
3786
3873
  }
@@ -3811,74 +3898,6 @@ var init_buildConfig = __esm({
3811
3898
  }
3812
3899
  });
3813
3900
 
3814
- // src/utils/parseArgs.ts
3815
- function isAttachedReverseOrLocalFlag(arg) {
3816
- return /^-[RL].+/.test(arg);
3817
- }
3818
- function shouldMergeReverseOrLocalFragment(current, next) {
3819
- if (next.startsWith("-")) {
3820
- return false;
3821
- }
3822
- if (next.startsWith(".")) {
3823
- return true;
3824
- }
3825
- const body = current.slice(2);
3826
- if (body.endsWith(":")) {
3827
- return true;
3828
- }
3829
- if (body.includes("//") && !body.includes(":")) {
3830
- return true;
3831
- }
3832
- return false;
3833
- }
3834
- function preprocessWindowsArgs(args) {
3835
- if (os2.platform() !== "win32") {
3836
- return args;
3837
- }
3838
- ;
3839
- const out = [];
3840
- let i = 0;
3841
- while (i < args.length) {
3842
- const arg = args[i];
3843
- if (isAttachedReverseOrLocalFlag(arg)) {
3844
- let merged = arg;
3845
- while (i + 1 < args.length && shouldMergeReverseOrLocalFragment(merged, args[i + 1])) {
3846
- merged += args[i + 1];
3847
- i++;
3848
- }
3849
- out.push(merged);
3850
- i++;
3851
- continue;
3852
- }
3853
- out.push(arg);
3854
- i++;
3855
- }
3856
- return out;
3857
- }
3858
- function parseCliArgs(options) {
3859
- const rawArgs = process.argv.slice(2);
3860
- const processedArgs = preprocessWindowsArgs(rawArgs);
3861
- const parsed = (0, import_util6.parseArgs)({
3862
- args: processedArgs,
3863
- options,
3864
- allowPositionals: true
3865
- });
3866
- const hasAnyArgs = parsed.positionals.length > 0 || Object.values(parsed.values).some((v) => v !== void 0 && v !== false);
3867
- return {
3868
- ...parsed,
3869
- hasAnyArgs
3870
- };
3871
- }
3872
- var import_util6, os2;
3873
- var init_parseArgs = __esm({
3874
- "src/utils/parseArgs.ts"() {
3875
- "use strict";
3876
- init_cjs_shims();
3877
- import_util6 = require("util");
3878
- os2 = __toESM(require("os"), 1);
3879
- }
3880
- });
3881
-
3882
3901
  // src/utils/getFreePort.ts
3883
3902
  function getFreePort(webDebugger) {
3884
3903
  return new Promise((resolve, reject) => {
@@ -5492,6 +5511,600 @@ var init_starCli = __esm({
5492
5511
  }
5493
5512
  });
5494
5513
 
5514
+ // src/utils/configDir.ts
5515
+ function getPinggyConfigDir() {
5516
+ const platform2 = import_os2.default.platform();
5517
+ let baseDir;
5518
+ if (platform2 === "win32") {
5519
+ baseDir = process.env.APPDATA || import_path5.default.join(import_os2.default.homedir(), "AppData", "Roaming");
5520
+ } else {
5521
+ baseDir = process.env.XDG_CONFIG_HOME || import_path5.default.join(import_os2.default.homedir(), ".config");
5522
+ }
5523
+ return import_path5.default.join(baseDir, "pinggy");
5524
+ }
5525
+ function getTunnelConfigDir() {
5526
+ return import_path5.default.join(getPinggyConfigDir(), "tunnels");
5527
+ }
5528
+ function ensureTunnelConfigDir() {
5529
+ const dir = getTunnelConfigDir();
5530
+ import_fs5.default.mkdirSync(dir, { recursive: true });
5531
+ return dir;
5532
+ }
5533
+ var import_os2, import_path5, import_fs5;
5534
+ var init_configDir = __esm({
5535
+ "src/utils/configDir.ts"() {
5536
+ "use strict";
5537
+ init_cjs_shims();
5538
+ import_os2 = __toESM(require("os"), 1);
5539
+ import_path5 = __toESM(require("path"), 1);
5540
+ import_fs5 = __toESM(require("fs"), 1);
5541
+ }
5542
+ });
5543
+
5544
+ // src/cli/configStore.ts
5545
+ function buildFilename(name, configId) {
5546
+ return `${name}_${configId}.json`;
5547
+ }
5548
+ function sanitizeName(name) {
5549
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_");
5550
+ }
5551
+ function validateName(name) {
5552
+ if (!name || name.trim().length === 0) {
5553
+ return new Error("Tunnel name cannot be empty.");
5554
+ }
5555
+ if (name.length > 128) {
5556
+ return new Error("Tunnel name cannot exceed 128 characters.");
5557
+ }
5558
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
5559
+ return new Error("Tunnel name can only contain alphanumeric characters, hyphens, and underscores.");
5560
+ }
5561
+ return null;
5562
+ }
5563
+ function readConfigFile(filePath) {
5564
+ try {
5565
+ const data = import_fs6.default.readFileSync(filePath, { encoding: "utf-8" });
5566
+ return JSON.parse(data);
5567
+ } catch (err) {
5568
+ logger.warn(`Failed to read config file ${filePath}:`, err);
5569
+ return null;
5570
+ }
5571
+ }
5572
+ function writeConfigFile(filePath, config) {
5573
+ import_fs6.default.writeFileSync(filePath, JSON.stringify(config, null, 2), { encoding: "utf-8" });
5574
+ }
5575
+ function listSavedConfigs() {
5576
+ const dir = getTunnelConfigDir();
5577
+ if (!import_fs6.default.existsSync(dir)) {
5578
+ return [];
5579
+ }
5580
+ const files = import_fs6.default.readdirSync(dir).filter((f) => f.endsWith(".json"));
5581
+ const configs = [];
5582
+ for (const file of files) {
5583
+ const config = readConfigFile(import_path6.default.join(dir, file));
5584
+ if (config && config.name && config.configId) {
5585
+ configs.push(config);
5586
+ }
5587
+ }
5588
+ return configs;
5589
+ }
5590
+ function findConfigFile(nameOrId) {
5591
+ const dir = getTunnelConfigDir();
5592
+ if (!import_fs6.default.existsSync(dir)) return null;
5593
+ const files = import_fs6.default.readdirSync(dir).filter((f) => f.endsWith(".json"));
5594
+ const sanitized = sanitizeName(nameOrId);
5595
+ const nameMatch = files.find((f) => f.startsWith(sanitized + "_"));
5596
+ if (nameMatch) {
5597
+ const filePath = import_path6.default.join(dir, nameMatch);
5598
+ const config = readConfigFile(filePath);
5599
+ if (config && config.name === nameOrId) return { filePath, config };
5600
+ }
5601
+ const idCandidates = files.filter((f) => {
5602
+ const withoutExt = f.replace(/\.json$/, "");
5603
+ const lastUnderscore = withoutExt.indexOf("_");
5604
+ if (lastUnderscore === -1) return false;
5605
+ const idPart = withoutExt.slice(lastUnderscore + 1);
5606
+ return idPart.startsWith(nameOrId);
5607
+ });
5608
+ if (idCandidates.length === 1) {
5609
+ const filePath = import_path6.default.join(dir, idCandidates[0]);
5610
+ const config = readConfigFile(filePath);
5611
+ if (config) return { filePath, config };
5612
+ }
5613
+ return null;
5614
+ }
5615
+ function findConfigByName(name) {
5616
+ const resolved = findConfigFile(name);
5617
+ return resolved?.config.name === name ? resolved.config : null;
5618
+ }
5619
+ function findConfig(nameOrId) {
5620
+ return findConfigFile(nameOrId)?.config ?? null;
5621
+ }
5622
+ function saveConfig(name, configId, tunnelConfig, autoStart = false) {
5623
+ const nameErr = validateName(name);
5624
+ if (nameErr) {
5625
+ throw nameErr;
5626
+ }
5627
+ const existing = findConfigByName(name);
5628
+ if (existing) {
5629
+ throw new Error(
5630
+ `A tunnel config with the name "${name}" already exists (configId: ${existing.configId}). Please use a different name.`
5631
+ );
5632
+ }
5633
+ const dir = ensureTunnelConfigDir();
5634
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5635
+ const saved = {
5636
+ name,
5637
+ configId,
5638
+ autoStart,
5639
+ createdAt: now,
5640
+ updatedAt: now,
5641
+ tunnelConfig
5642
+ };
5643
+ const filename = buildFilename(sanitizeName(name), configId);
5644
+ const filePath = import_path6.default.join(dir, filename);
5645
+ import_fs6.default.writeFileSync(filePath, JSON.stringify(saved, null, 2), { encoding: "utf-8" });
5646
+ logger.info(`Config "${name}" saved to ${filePath}`);
5647
+ return saved;
5648
+ }
5649
+ function deleteConfig(nameOrId) {
5650
+ const resolved = findConfigFile(nameOrId);
5651
+ if (!resolved) return null;
5652
+ import_fs6.default.unlinkSync(resolved.filePath);
5653
+ logger.info(`Config "${resolved.config.name}" deleted.`);
5654
+ return resolved.config.name;
5655
+ }
5656
+ function updateConfigAutoStart(nameOrId, autoStart) {
5657
+ const resolved = findConfigFile(nameOrId);
5658
+ if (!resolved) return null;
5659
+ resolved.config.autoStart = autoStart;
5660
+ resolved.config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5661
+ writeConfigFile(resolved.filePath, resolved.config);
5662
+ logger.info(`Config "${resolved.config.name}" auto-start set to ${autoStart}`);
5663
+ return resolved.config;
5664
+ }
5665
+ function updateTunnelConfig(nameOrId, tunnelConfig) {
5666
+ const resolved = findConfigFile(nameOrId);
5667
+ if (!resolved) return null;
5668
+ resolved.config.tunnelConfig = tunnelConfig;
5669
+ resolved.config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5670
+ writeConfigFile(resolved.filePath, resolved.config);
5671
+ logger.info(`Config "${resolved.config.name}" tunnel configuration updated`);
5672
+ return resolved.config;
5673
+ }
5674
+ function getAutoStartConfigs() {
5675
+ return listSavedConfigs().filter((c) => c.autoStart);
5676
+ }
5677
+ function printConfigList() {
5678
+ const configs = listSavedConfigs();
5679
+ if (configs.length === 0) {
5680
+ console.log(import_picocolors5.default.yellow("No saved tunnel configs found."));
5681
+ console.log(import_picocolors5.default.gray(`Config directory: ${getTunnelConfigDir()}`));
5682
+ return;
5683
+ }
5684
+ const nameW = 20;
5685
+ const idW = 12;
5686
+ const typeW = 8;
5687
+ const fwdW = 25;
5688
+ const serverW = 22;
5689
+ const autoW = 10;
5690
+ const header = import_picocolors5.default.bold("Name".padEnd(nameW)) + import_picocolors5.default.bold("Config ID".padEnd(idW)) + import_picocolors5.default.bold("Type".padEnd(typeW)) + import_picocolors5.default.bold("Forwarding".padEnd(fwdW)) + import_picocolors5.default.bold("Server".padEnd(serverW)) + import_picocolors5.default.bold("Auto-start".padEnd(autoW));
5691
+ console.log("\n" + header);
5692
+ console.log(import_picocolors5.default.gray("\u2500".repeat(nameW + idW + typeW + fwdW + serverW + autoW)));
5693
+ for (const c of configs) {
5694
+ const tc = c.tunnelConfig;
5695
+ const forwarding = Array.isArray(tc.forwarding) ? tc.forwarding[0]?.address : String(tc.forwarding || "");
5696
+ const type = (Array.isArray(tc.forwarding) ? tc.forwarding[0]?.type : void 0) || "http";
5697
+ const server = tc.serverAddress || "a.pinggy.io";
5698
+ const line = import_picocolors5.default.cyanBright(c.name.padEnd(nameW)) + import_picocolors5.default.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 ? import_picocolors5.default.green("yes") : import_picocolors5.default.gray("no")).padEnd(autoW);
5699
+ console.log(line);
5700
+ }
5701
+ console.log();
5702
+ }
5703
+ function printConfigDetail(config) {
5704
+ console.log(import_picocolors5.default.bold(`
5705
+ Tunnel Config: ${import_picocolors5.default.cyanBright(config.name)}`));
5706
+ console.log(import_picocolors5.default.gray("\u2500".repeat(40)));
5707
+ console.log(` Config ID: ${config.configId}`);
5708
+ console.log(` Auto-start: ${config.autoStart ? import_picocolors5.default.green("yes") : import_picocolors5.default.gray("no")}`);
5709
+ console.log(` Created: ${config.createdAt}`);
5710
+ console.log(` Updated: ${config.updatedAt}`);
5711
+ console.log(import_picocolors5.default.gray("\u2500".repeat(40)));
5712
+ console.log(` Server: ${config.tunnelConfig.serverAddress || "a.pinggy.io"}`);
5713
+ console.log(` Token: ${config.tunnelConfig.token ? "***" + config.tunnelConfig.token.slice(-4) : "(none)"}`);
5714
+ const fwd = config.tunnelConfig.forwarding;
5715
+ if (Array.isArray(fwd)) {
5716
+ const defaultFwds = [];
5717
+ const customFwds = [];
5718
+ for (const f of fwd) {
5719
+ if (typeof f === "string") {
5720
+ defaultFwds.push(f);
5721
+ } else if (f.listenAddress) {
5722
+ customFwds.push(f);
5723
+ } else {
5724
+ defaultFwds.push(f);
5725
+ }
5726
+ }
5727
+ for (const f of defaultFwds) {
5728
+ const addr = typeof f === "string" ? f : `${f.address} (${f.type || "http"})`;
5729
+ console.log(` Forwarding: ${addr}`);
5730
+ if (config.tunnelConfig.webDebugger) {
5731
+ console.log(` Debugger: ${config.tunnelConfig.webDebugger}`);
5732
+ }
5733
+ }
5734
+ if (customFwds.length > 0) {
5735
+ console.log(import_picocolors5.default.gray("\u2500".repeat(40)));
5736
+ console.log(import_picocolors5.default.bold(" Domain Mappings:"));
5737
+ for (const f of customFwds) {
5738
+ if (typeof f === "string") continue;
5739
+ const domain = f.listenAddress;
5740
+ const target = f.address;
5741
+ const type = f.type || "http";
5742
+ console.log(` ${import_picocolors5.default.cyanBright(domain)} \u2192 ${target} (${type})`);
5743
+ }
5744
+ }
5745
+ } else if (fwd) {
5746
+ console.log(` Forwarding: ${fwd}`);
5747
+ }
5748
+ console.log();
5749
+ }
5750
+ var import_fs6, import_path6, import_picocolors5;
5751
+ var init_configStore = __esm({
5752
+ "src/cli/configStore.ts"() {
5753
+ "use strict";
5754
+ init_cjs_shims();
5755
+ import_fs6 = __toESM(require("fs"), 1);
5756
+ import_path6 = __toESM(require("path"), 1);
5757
+ init_configDir();
5758
+ init_logger();
5759
+ import_picocolors5 = __toESM(require("picocolors"), 1);
5760
+ }
5761
+ });
5762
+
5763
+ // src/cli/buildAndStartTunnel.ts
5764
+ async function buildAndStartTunnel(values, positionals, manager) {
5765
+ await initRemoteManagement(values);
5766
+ logger.debug("Building final config from CLI values and positionals", { values, positionals });
5767
+ const finalConfig = await buildFinalConfig(values, positionals);
5768
+ logger.debug("Final configuration built", finalConfig);
5769
+ if (values.save) {
5770
+ const name = values.name;
5771
+ if (!name) {
5772
+ printer_default.error("--save requires --name to specify a name for the tunnel config.");
5773
+ process.exit(1);
5774
+ }
5775
+ const nameErr = validateName(name);
5776
+ if (nameErr) {
5777
+ printer_default.error(nameErr.message);
5778
+ process.exit(1);
5779
+ }
5780
+ const autoStart = !!values.auto;
5781
+ saveConfig(name, finalConfig.configId, finalConfig, autoStart);
5782
+ printer_default.success(`Config "${name}" saved.`);
5783
+ }
5784
+ await startCli(finalConfig, manager);
5785
+ }
5786
+ async function initRemoteManagement(values) {
5787
+ const parseResult = await parseRemoteManagement(values);
5788
+ if (parseResult?.ok === false) {
5789
+ logger.error("Failed to initiate remote management:", parseResult.error);
5790
+ printer_default.fatal(parseResult.error);
5791
+ }
5792
+ }
5793
+ var init_buildAndStartTunnel = __esm({
5794
+ "src/cli/buildAndStartTunnel.ts"() {
5795
+ "use strict";
5796
+ init_cjs_shims();
5797
+ init_logger();
5798
+ init_remoteManagement();
5799
+ init_buildConfig();
5800
+ init_starCli();
5801
+ init_printer();
5802
+ init_configStore();
5803
+ }
5804
+ });
5805
+
5806
+ // src/cli/subcommands.ts
5807
+ function isSubcommand(rawArgs) {
5808
+ return rawArgs.length > 0 && SUBCOMMANDS.has(rawArgs[0]);
5809
+ }
5810
+ async function handleSubcommand(rawArgs, manager) {
5811
+ const sub = rawArgs[0];
5812
+ const rest = rawArgs.slice(1);
5813
+ switch (sub) {
5814
+ case "config":
5815
+ await handleConfig(rest);
5816
+ return;
5817
+ case "start":
5818
+ await handleStart(rest, manager);
5819
+ return;
5820
+ }
5821
+ }
5822
+ async function handleConfig(args) {
5823
+ if (args.length === 0) {
5824
+ printConfigHelp();
5825
+ return;
5826
+ }
5827
+ const verb = args[0];
5828
+ const rest = args.slice(1);
5829
+ switch (verb) {
5830
+ case "list":
5831
+ case "ls":
5832
+ printConfigList();
5833
+ return;
5834
+ case "show": {
5835
+ const names = requireNames(rest, "config show");
5836
+ for (const name of names) {
5837
+ const saved2 = resolveConfig(name);
5838
+ if (saved2) printConfigDetail(saved2);
5839
+ }
5840
+ return;
5841
+ }
5842
+ case "save": {
5843
+ const name = requireName(rest, "config save");
5844
+ await handleConfigSave(name, rest.slice(1));
5845
+ return;
5846
+ }
5847
+ case "delete": {
5848
+ const names = requireNames(rest, "config delete");
5849
+ for (const name of names) {
5850
+ const deletedName = deleteConfig(name);
5851
+ if (deletedName) {
5852
+ printer_default.success(`Config "${deletedName}" deleted.`);
5853
+ } else {
5854
+ printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
5855
+ }
5856
+ }
5857
+ return;
5858
+ }
5859
+ case "update": {
5860
+ const name = requireName(rest, "config update");
5861
+ await handleConfigUpdate(name, rest.slice(1));
5862
+ return;
5863
+ }
5864
+ case "auto": {
5865
+ const names = requireNames(rest, "config auto");
5866
+ for (const name of names) {
5867
+ const updated = updateConfigAutoStart(name, true);
5868
+ if (updated) {
5869
+ printer_default.success(`Config "${updated.name}" auto-start set to on.`);
5870
+ } else {
5871
+ printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
5872
+ }
5873
+ }
5874
+ return;
5875
+ }
5876
+ case "noauto": {
5877
+ const names = requireNames(rest, "config noauto");
5878
+ for (const name of names) {
5879
+ const updated = updateConfigAutoStart(name, false);
5880
+ if (updated) {
5881
+ printer_default.success(`Config "${updated.name}" auto-start set to off.`);
5882
+ } else {
5883
+ printer_default.error(`No config found matching "${name}". Use: pinggy config list`);
5884
+ }
5885
+ }
5886
+ return;
5887
+ }
5888
+ default:
5889
+ const saved = resolveConfig(verb);
5890
+ if (saved) printConfigDetail(saved);
5891
+ return;
5892
+ }
5893
+ }
5894
+ async function handleConfigSave(name, remainingArgs) {
5895
+ const nameErr = validateName(name);
5896
+ if (nameErr) {
5897
+ printer_default.error(nameErr.message);
5898
+ process.exit(1);
5899
+ }
5900
+ const { values, positionals } = parseCliArgs(cliOptions, remainingArgs);
5901
+ const autoStart = !!values.auto;
5902
+ logger.debug("Building config for save", { name, values, positionals });
5903
+ const finalConfig = await buildFinalConfig(values, positionals);
5904
+ saveConfig(name, finalConfig.configId, finalConfig, autoStart);
5905
+ printer_default.success(`Config "${name}" saved.`);
5906
+ }
5907
+ async function handleConfigUpdate(nameOrId, remainingArgs) {
5908
+ const saved = resolveConfig(nameOrId);
5909
+ if (!saved) return;
5910
+ const { values, positionals } = parseCliArgs(cliOptions, remainingArgs);
5911
+ logger.debug("Building updated config", { nameOrId, values, positionals });
5912
+ const updatedConfig = await buildFinalConfig(values, positionals, saved.tunnelConfig);
5913
+ const result = updateTunnelConfig(nameOrId, updatedConfig);
5914
+ if (result) {
5915
+ printer_default.success(`Config "${result.name}" updated.`);
5916
+ printConfigDetail(result);
5917
+ } else {
5918
+ printer_default.error(`Failed to update config "${nameOrId}".`);
5919
+ }
5920
+ }
5921
+ async function handleStart(args, manager) {
5922
+ const startAll = args.includes("--all");
5923
+ const argsWithoutAll = args.filter((a) => a !== "--all");
5924
+ const names = [];
5925
+ let i = 0;
5926
+ while (i < argsWithoutAll.length && !argsWithoutAll[i].startsWith("-")) {
5927
+ names.push(argsWithoutAll[i]);
5928
+ i++;
5929
+ }
5930
+ const flagArgs = argsWithoutAll.slice(i);
5931
+ const { values, positionals } = parseCliArgs(cliOptions, flagArgs);
5932
+ configureLogger(values);
5933
+ if (startAll) {
5934
+ await initRemoteManagementBackground(values);
5935
+ await startAutoStartTunnels(manager);
5936
+ return;
5937
+ }
5938
+ if (names.length === 0) {
5939
+ printStartHelp();
5940
+ return;
5941
+ }
5942
+ const resolved = [];
5943
+ for (const name of names) {
5944
+ const saved = resolveConfig(name);
5945
+ if (!saved) return;
5946
+ resolved.push(saved);
5947
+ }
5948
+ if (resolved.length > 1 && flagArgs.length > 0) {
5949
+ printer_default.error("Runtime overrides (-l, --type, etc.) can only be used when starting a single tunnel.");
5950
+ printer_default.print(" Start one tunnel: pinggy start my-tunnel -l 4000");
5951
+ printer_default.print(" Or update first: pinggy config update my-tunnel -l 4000");
5952
+ return;
5953
+ }
5954
+ await initRemoteManagementBackground(values);
5955
+ if (resolved.length === 1) {
5956
+ const saved = resolved[0];
5957
+ logger.debug("Building config with overrides", { name: saved.name });
5958
+ const finalConfig = await buildFinalConfig(values, positionals, saved.tunnelConfig);
5959
+ finalConfig.configId = saved.configId;
5960
+ await startCli(finalConfig, manager);
5961
+ } else {
5962
+ await startNamedTunnels(resolved, manager);
5963
+ }
5964
+ }
5965
+ async function startAutoStartTunnels(manager) {
5966
+ const configs = getAutoStartConfigs();
5967
+ if (configs.length === 0) {
5968
+ printer_default.warn("No configs marked for auto-start. Use: pinggy config auto <name>");
5969
+ return;
5970
+ }
5971
+ printer_default.print(import_picocolors6.default.cyanBright(`Starting ${configs.length} auto-start tunnel(s)...`));
5972
+ for (const saved of configs) {
5973
+ await startSavedTunnel(saved, manager);
5974
+ }
5975
+ printer_default.print(import_picocolors6.default.gray("\nAll auto-start tunnels launched. Press Ctrl+C to stop.\n"));
5976
+ await new Promise(() => {
5977
+ });
5978
+ }
5979
+ async function startNamedTunnels(configs, manager) {
5980
+ printer_default.print(import_picocolors6.default.cyanBright(`Starting ${configs.length} tunnel(s)...`));
5981
+ for (const saved of configs) {
5982
+ await startSavedTunnel(saved, manager);
5983
+ }
5984
+ printer_default.print(import_picocolors6.default.gray("\nAll tunnels launched. Press Ctrl+C to stop.\n"));
5985
+ await new Promise(() => {
5986
+ });
5987
+ }
5988
+ async function startSavedTunnel(saved, manager) {
5989
+ const config = {
5990
+ ...saved.tunnelConfig,
5991
+ configId: saved.configId,
5992
+ name: saved.name,
5993
+ optional: {
5994
+ ...saved.tunnelConfig.optional,
5995
+ noTui: true
5996
+ }
5997
+ };
5998
+ try {
5999
+ const tunnel = await manager.createTunnel(config);
6000
+ await manager.startTunnel(tunnel.tunnelid);
6001
+ const urls = await manager.getTunnelUrls(tunnel.tunnelid);
6002
+ printer_default.success(`"${saved.name}" started`);
6003
+ (urls ?? []).forEach(
6004
+ (url) => printer_default.print(" " + import_picocolors6.default.magentaBright(url))
6005
+ );
6006
+ manager.registerWorkerErrorListner(tunnel.tunnelid, (_id, error) => {
6007
+ printer_default.error(`[${saved.name}] Fatal: ${error.message}`);
6008
+ });
6009
+ manager.registerDisconnectListener(tunnel.tunnelid, async (_id, error, messages) => {
6010
+ if (error) printer_default.warn(`[${saved.name}] Disconnected: ${error}`);
6011
+ messages?.forEach((m) => printer_default.warn(`[${saved.name}] ${m}`));
6012
+ });
6013
+ manager.registerReconnectingListener(tunnel.tunnelid, (_id, retryCnt) => {
6014
+ printer_default.print(import_picocolors6.default.gray(`[${saved.name}] Reconnecting (attempt #${retryCnt})...`));
6015
+ });
6016
+ manager.registerReconnectionCompletedListener(tunnel.tunnelid, async (_id, urls2) => {
6017
+ printer_default.success(`[${saved.name}] Reconnected`);
6018
+ (urls2 ?? []).forEach(
6019
+ (url) => printer_default.print(" " + import_picocolors6.default.magentaBright(url))
6020
+ );
6021
+ });
6022
+ manager.registerReconnectionFailedListener(tunnel.tunnelid, (_id, retryCnt) => {
6023
+ printer_default.error(`[${saved.name}] Reconnection failed after ${retryCnt} attempts`);
6024
+ });
6025
+ } catch (err) {
6026
+ printer_default.error(`[${saved.name}] Failed to start: ${err.message || err}`);
6027
+ }
6028
+ }
6029
+ function resolveConfig(nameOrId) {
6030
+ const saved = findConfig(nameOrId);
6031
+ if (!saved) {
6032
+ printer_default.error(`No config found matching "${nameOrId}". Use: pinggy config list`);
6033
+ return null;
6034
+ }
6035
+ return saved;
6036
+ }
6037
+ function requireName(args, command) {
6038
+ if (args.length === 0 || args[0].startsWith("-")) {
6039
+ printer_default.error(`Tunnel name is required. Usage: pinggy ${command} <name>`);
6040
+ process.exit(1);
6041
+ }
6042
+ return args[0];
6043
+ }
6044
+ function requireNames(args, command) {
6045
+ const names = [];
6046
+ for (const arg of args) {
6047
+ if (arg.startsWith("-")) break;
6048
+ names.push(arg);
6049
+ }
6050
+ if (names.length === 0) {
6051
+ printer_default.error(`At least one tunnel name is required. Usage: pinggy ${command} <name> [name2 ...]`);
6052
+ process.exit(1);
6053
+ }
6054
+ return names;
6055
+ }
6056
+ async function initRemoteManagementBackground(values) {
6057
+ const rmToken = values["remote-management"];
6058
+ if (typeof rmToken === "string" && rmToken.trim().length > 0) {
6059
+ const manageHost = values["manage"];
6060
+ try {
6061
+ await startRemoteManagement({
6062
+ apiKey: rmToken,
6063
+ serverUrl: buildRemoteManagementWsUrl(manageHost)
6064
+ });
6065
+ } catch (e) {
6066
+ logger.error("Failed to initiate remote management:", e);
6067
+ printer_default.fatal(e);
6068
+ }
6069
+ }
6070
+ }
6071
+ function printConfigHelp() {
6072
+ console.log("\nUsage: pinggy config <command> [name] [options]\n");
6073
+ console.log("Commands:");
6074
+ console.log(" list List all saved configs");
6075
+ console.log(" show <name> Show config details");
6076
+ console.log(" save <name> [tunnel flags] Save a tunnel config");
6077
+ console.log(" update <name> [tunnel flags] Update a saved config");
6078
+ console.log(" delete <name> Delete a saved config");
6079
+ console.log(" auto <name> Enable auto-start");
6080
+ console.log(" noauto <name> Disable auto-start\n");
6081
+ }
6082
+ function printStartHelp() {
6083
+ console.log("\nUsage: pinggy start <name> [options]\n");
6084
+ console.log("Examples:");
6085
+ console.log(" pinggy start my-tunnel Start a saved tunnel");
6086
+ console.log(" pinggy start my-tunnel -l 4000 Start with override");
6087
+ console.log(" pinggy start tunnela tunnelb Start multiple tunnels");
6088
+ console.log(" pinggy start --all Start all auto-start tunnels\n");
6089
+ }
6090
+ var import_picocolors6, SUBCOMMANDS;
6091
+ var init_subcommands = __esm({
6092
+ "src/cli/subcommands.ts"() {
6093
+ "use strict";
6094
+ init_cjs_shims();
6095
+ init_options();
6096
+ init_parseArgs();
6097
+ init_buildConfig();
6098
+ init_starCli();
6099
+ init_printer();
6100
+ init_logger();
6101
+ import_picocolors6 = __toESM(require("picocolors"), 1);
6102
+ init_configStore();
6103
+ init_remoteManagement();
6104
+ SUBCOMMANDS = /* @__PURE__ */ new Set(["config", "start"]);
6105
+ }
6106
+ });
6107
+
5495
6108
  // src/main.ts
5496
6109
  var main_exports = {};
5497
6110
  __export(main_exports, {
@@ -5505,8 +6118,7 @@ __export(main_exports, {
5505
6118
  });
5506
6119
  async function main() {
5507
6120
  try {
5508
- const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
5509
- configureLogger(values);
6121
+ const rawArgs = process.argv.slice(2);
5510
6122
  const manager = TunnelManager.getInstance();
5511
6123
  process.on("SIGINT", () => {
5512
6124
  logger.info("SIGINT received: stopping tunnels and exiting");
@@ -5515,6 +6127,12 @@ async function main() {
5515
6127
  console.log("Tunnels stopped. Exiting.");
5516
6128
  process.exit(0);
5517
6129
  });
6130
+ if (isSubcommand(rawArgs)) {
6131
+ await handleSubcommand(rawArgs, manager);
6132
+ return;
6133
+ }
6134
+ const { values, positionals, hasAnyArgs } = parseCliArgs(cliOptions);
6135
+ configureLogger(values);
5518
6136
  if (!hasAnyArgs || values.help) {
5519
6137
  printHelpMessage();
5520
6138
  return;
@@ -5523,21 +6141,13 @@ async function main() {
5523
6141
  printer_default.print(`Pinggy CLI version: ${getVersion()}`);
5524
6142
  return;
5525
6143
  }
5526
- const parseResult = await parseRemoteManagement(values);
5527
- if (parseResult?.ok === false) {
5528
- logger.error("Failed to initiate remote management:", parseResult.error);
5529
- printer_default.fatal(parseResult.error);
5530
- }
5531
- logger.debug("Building final config from CLI values and positionals", { values, positionals });
5532
- const finalConfig = await buildFinalConfig(values, positionals);
5533
- logger.debug("Final configuration built", finalConfig);
5534
- await startCli(finalConfig, manager);
6144
+ await buildAndStartTunnel(values, positionals, manager);
5535
6145
  } catch (error) {
5536
6146
  logger.error("Unhandled error in CLI:", error);
5537
6147
  printer_default.fatal(error);
5538
6148
  }
5539
6149
  }
5540
- var import_url2, import_process, import_fs5, currentFile, entryFile;
6150
+ var import_url2, import_process, import_fs7, currentFile, entryFile;
5541
6151
  var init_main = __esm({
5542
6152
  "src/main.ts"() {
5543
6153
  "use strict";
@@ -5545,23 +6155,22 @@ var init_main = __esm({
5545
6155
  init_TunnelManager();
5546
6156
  init_help();
5547
6157
  init_options();
5548
- init_buildConfig();
5549
6158
  init_logger();
5550
- init_remoteManagement();
5551
6159
  init_parseArgs();
5552
6160
  init_printer();
5553
- init_starCli();
5554
6161
  init_util();
5555
6162
  init_handler();
5556
6163
  import_url2 = require("url");
5557
6164
  import_process = require("process");
5558
- import_fs5 = require("fs");
6165
+ import_fs7 = require("fs");
5559
6166
  init_logger();
5560
6167
  init_remoteManagement();
6168
+ init_buildAndStartTunnel();
6169
+ init_subcommands();
5561
6170
  currentFile = (0, import_url2.fileURLToPath)(importMetaUrl);
5562
6171
  entryFile = null;
5563
6172
  try {
5564
- entryFile = import_process.argv[1] ? (0, import_fs5.realpathSync)(import_process.argv[1]) : null;
6173
+ entryFile = import_process.argv[1] ? (0, import_fs7.realpathSync)(import_process.argv[1]) : null;
5565
6174
  } catch (e) {
5566
6175
  entryFile = null;
5567
6176
  }