bindler 1.3.0 → 1.6.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.
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/cli.ts
10
10
  import { Command } from "commander";
11
- import chalk30 from "chalk";
11
+ import chalk31 from "chalk";
12
12
 
13
13
  // src/commands/new.ts
14
14
  import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
@@ -284,6 +284,25 @@ function isPortAvailable(port) {
284
284
  const usedPorts = new Set(getUsedPorts());
285
285
  return !usedPorts.has(port);
286
286
  }
287
+ function checkPortInUse(port) {
288
+ const lsofResult = execCommandSafe(`lsof -i :${port} -P -n 2>/dev/null | grep LISTEN`);
289
+ if (lsofResult.success && lsofResult.output) {
290
+ const match = lsofResult.output.match(/^(\S+)\s+(\d+)/);
291
+ if (match) {
292
+ return { inUse: true, process: `${match[1]} (PID ${match[2]})` };
293
+ }
294
+ return { inUse: true };
295
+ }
296
+ const ssResult = execCommandSafe(`ss -tlnp 2>/dev/null | grep ":${port} "`);
297
+ if (ssResult.success && ssResult.output) {
298
+ const match = ssResult.output.match(/users:\(\("([^"]+)",pid=(\d+)/);
299
+ if (match) {
300
+ return { inUse: true, process: `${match[1]} (PID ${match[2]})` };
301
+ }
302
+ return { inUse: true };
303
+ }
304
+ return { inUse: false };
305
+ }
287
306
  function getPortsTable() {
288
307
  const config = readConfig();
289
308
  return config.projects.filter((p) => p.type === "npm" && p.port).map((p) => ({
@@ -570,18 +589,17 @@ function startProject(project) {
570
589
  }
571
590
  const pm2Name = getPm2ProcessName(project.name);
572
591
  const existingProcess = getProcessByName(project.name);
573
- const envVars = [];
574
- if (project.env) {
575
- for (const [key, value] of Object.entries(project.env)) {
576
- envVars.push(`${key}=${value}`);
577
- }
578
- }
579
592
  let command;
580
593
  if (existingProcess) {
581
594
  command = `pm2 restart "${pm2Name}"`;
582
595
  } else {
583
- const envFlags = envVars.length > 0 ? envVars.map((e) => `--env ${e}`).join(" ") : "";
584
- command = `pm2 start --name "${pm2Name}" --cwd "${project.path}" ${envFlags} -- bash -lc "${project.start}"`;
596
+ let startCmd = project.start;
597
+ if (project.env && Object.keys(project.env).length > 0) {
598
+ const envPrefix = Object.entries(project.env).map(([key, value]) => `${key}=${escapeShellArg(value)}`).join(" ");
599
+ startCmd = `${envPrefix} ${project.start}`;
600
+ }
601
+ const escapedCmd = startCmd.replace(/'/g, "'\\''");
602
+ command = `pm2 start 'bash -c "${escapedCmd}"' --name "${pm2Name}" --cwd "${project.path}"`;
585
603
  }
586
604
  const result = execCommandSafe(command);
587
605
  if (!result.success) {
@@ -590,6 +608,12 @@ function startProject(project) {
590
608
  execCommandSafe("pm2 save");
591
609
  return { success: true };
592
610
  }
611
+ function escapeShellArg(arg) {
612
+ if (/[^a-zA-Z0-9_\-=]/.test(arg)) {
613
+ return `'${arg.replace(/'/g, "'\\''")}'`;
614
+ }
615
+ return arg;
616
+ }
593
617
  function stopProject(name) {
594
618
  const pm2Name = getPm2ProcessName(name);
595
619
  const result = execCommandSafe(`pm2 stop "${pm2Name}"`);
@@ -981,7 +1005,17 @@ async function newCommand(options) {
981
1005
  if (yamlDefaults.environments) project.environments = yamlDefaults.environments;
982
1006
  if (answers.type === "npm") {
983
1007
  const scripts = existsSync6(answers.path) ? getPackageJsonScripts(answers.path) : [];
984
- const suggestedPort = yamlDefaults.port || findAvailablePort();
1008
+ let suggestedPort = yamlDefaults.port || findAvailablePort();
1009
+ const portCheck = checkPortInUse(suggestedPort);
1010
+ if (portCheck.inUse) {
1011
+ for (let p = suggestedPort + 1; p <= 9e3; p++) {
1012
+ const check = checkPortInUse(p);
1013
+ if (!check.inUse) {
1014
+ suggestedPort = p;
1015
+ break;
1016
+ }
1017
+ }
1018
+ }
985
1019
  const npmAnswers = await inquirer.prompt([
986
1020
  {
987
1021
  type: "input",
@@ -993,6 +1027,11 @@ async function newCommand(options) {
993
1027
  if (!validatePort(port)) {
994
1028
  return "Invalid port. Use a number between 1024 and 65535.";
995
1029
  }
1030
+ const inUseCheck = checkPortInUse(port);
1031
+ if (inUseCheck.inUse) {
1032
+ const processInfo = inUseCheck.process ? ` by ${inUseCheck.process}` : "";
1033
+ return `Port ${port} is already in use${processInfo}. Choose another port.`;
1034
+ }
996
1035
  return true;
997
1036
  },
998
1037
  filter: (input) => parseInt(input, 10)
@@ -1048,7 +1087,23 @@ async function newCommand(options) {
1048
1087
  project.local = true;
1049
1088
  }
1050
1089
  if (project.type === "npm") {
1051
- project.port = options.port || findAvailablePort();
1090
+ let port = options.port || findAvailablePort();
1091
+ const portCheck = checkPortInUse(port);
1092
+ if (portCheck.inUse) {
1093
+ const processInfo = portCheck.process ? ` by ${portCheck.process}` : "";
1094
+ console.log(chalk2.yellow(`Warning: Port ${port} is already in use${processInfo}`));
1095
+ if (!options.port) {
1096
+ for (let p = port + 1; p <= 9e3; p++) {
1097
+ const check = checkPortInUse(p);
1098
+ if (!check.inUse) {
1099
+ port = p;
1100
+ console.log(chalk2.dim(` Using port ${port} instead`));
1101
+ break;
1102
+ }
1103
+ }
1104
+ }
1105
+ }
1106
+ project.port = port;
1052
1107
  project.start = options.start || "npm start";
1053
1108
  project.env = { PORT: String(project.port) };
1054
1109
  }
@@ -1649,11 +1704,33 @@ async function removeCommand(name, options) {
1649
1704
  try {
1650
1705
  removeProject(name);
1651
1706
  console.log(chalk11.green(`\u2713 Project "${name}" removed from registry`));
1652
- console.log(chalk11.dim(`
1707
+ if (options.apply) {
1708
+ console.log(chalk11.dim("\nApplying nginx configuration..."));
1709
+ const config = readConfig();
1710
+ try {
1711
+ writeNginxConfig(config);
1712
+ const testResult = testNginxConfig();
1713
+ if (testResult.success) {
1714
+ reloadNginx();
1715
+ console.log(chalk11.green("\u2713 Nginx configuration updated"));
1716
+ } else {
1717
+ console.log(chalk11.yellow("! Nginx config test failed, reload skipped"));
1718
+ }
1719
+ } catch (err) {
1720
+ console.log(chalk11.yellow(`! Failed to update nginx: ${err}`));
1721
+ console.log(chalk11.dim(" Try running: sudo bindler apply"));
1722
+ }
1723
+ } else {
1724
+ console.log(chalk11.dim(`
1653
1725
  Run ${chalk11.cyan("sudo bindler apply")} to update nginx configuration.`));
1654
- console.log(chalk11.yellow("\nNote: The project files and Cloudflare DNS routes were not removed."));
1655
- console.log(chalk11.dim(` Project path: ${project.path}`));
1656
- console.log(chalk11.dim(` To remove DNS route manually: cloudflared tunnel route dns --remove ${project.hostname}`));
1726
+ }
1727
+ console.log(chalk11.yellow("\nNote: Project files were not deleted."));
1728
+ console.log(chalk11.dim(` Path: ${project.path}`));
1729
+ if (!project.local) {
1730
+ console.log(chalk11.yellow("\nCloudflare DNS route was not removed."));
1731
+ console.log(chalk11.dim(" Remove it manually from the Cloudflare dashboard:"));
1732
+ console.log(chalk11.dim(" https://dash.cloudflare.com \u2192 DNS \u2192 Records \u2192 Delete the CNAME for " + project.hostname));
1733
+ }
1657
1734
  } catch (error) {
1658
1735
  console.error(chalk11.red(`Error: ${error instanceof Error ? error.message : error}`));
1659
1736
  process.exit(1);
@@ -1784,12 +1861,9 @@ async function applyCommand(options) {
1784
1861
  const envProjects = listProjectsForEnv(options.env);
1785
1862
  config = { ...config, projects: envProjects };
1786
1863
  }
1787
- if (config.projects.length === 0) {
1788
- console.log(chalk12.yellow("No projects registered. Nothing to apply."));
1789
- return;
1790
- }
1864
+ const hasProjects = config.projects.length > 0;
1791
1865
  console.log(chalk12.blue("Applying configuration...\n"));
1792
- if (!options.skipChecks) {
1866
+ if (hasProjects && !options.skipChecks) {
1793
1867
  console.log(chalk12.dim("Running preflight checks..."));
1794
1868
  const checkResult = runPreflightChecks(config);
1795
1869
  if (!checkResult.valid) {
@@ -1857,11 +1931,32 @@ Try running with sudo: ${chalk12.cyan("sudo bindler apply")}`));
1857
1931
  process.exit(1);
1858
1932
  }
1859
1933
  console.log(chalk12.green(" \u2713 Nginx reloaded successfully"));
1934
+ const listenPort = parseInt(defaults.nginxListen.split(":").pop() || "80", 10);
1935
+ const portCheck = execCommandSafe(`lsof -i :${listenPort} -P -n 2>/dev/null | grep LISTEN | grep nginx`);
1936
+ if (!portCheck.success || !portCheck.output) {
1937
+ const isPrivilegedPort = listenPort < 1024;
1938
+ console.log(chalk12.yellow(`
1939
+ \u26A0 Nginx is not listening on port ${listenPort}`));
1940
+ if (isPrivilegedPort) {
1941
+ console.log(chalk12.dim(` Port ${listenPort} requires root privileges.
1942
+ `));
1943
+ console.log(chalk12.cyan(" Solutions:\n"));
1944
+ console.log(chalk12.white(" Option 1: Restart nginx with sudo (recommended for port 80)"));
1945
+ console.log(chalk12.dim(" sudo pkill nginx && sudo nginx\n"));
1946
+ console.log(chalk12.white(" Option 2: Use a non-privileged port (no sudo needed)"));
1947
+ console.log(chalk12.dim(" bindler config set nginxListen 8080"));
1948
+ console.log(chalk12.dim(" bindler apply"));
1949
+ console.log(chalk12.dim(" # Then access via http://hostname:8080\n"));
1950
+ } else {
1951
+ console.log(chalk12.dim(" Try restarting nginx: brew services restart nginx"));
1952
+ }
1953
+ }
1860
1954
  } else {
1861
1955
  console.log(chalk12.yellow(" - Skipped nginx reload (--no-reload)"));
1862
1956
  }
1863
1957
  const isDirectMode = defaults.mode === "direct";
1864
- if (isDirectMode) {
1958
+ if (!hasProjects) {
1959
+ } else if (isDirectMode) {
1865
1960
  console.log(chalk12.dim("\n - Direct mode: skipping Cloudflare DNS routes"));
1866
1961
  } else if (!options.noCloudflare && defaults.applyCloudflareDnsRoutes) {
1867
1962
  console.log(chalk12.dim("\nConfiguring Cloudflare DNS routes..."));
@@ -1887,7 +1982,7 @@ Try running with sudo: ${chalk12.cyan("sudo bindler apply")}`));
1887
1982
  } else if (options.noCloudflare) {
1888
1983
  console.log(chalk12.dim("\n - Skipped Cloudflare DNS routes (--no-cloudflare)"));
1889
1984
  }
1890
- if (isDirectMode && defaults.sslEnabled && options.ssl !== false) {
1985
+ if (hasProjects && isDirectMode && defaults.sslEnabled && options.ssl !== false) {
1891
1986
  console.log(chalk12.dim("\nSetting up SSL certificates..."));
1892
1987
  const hostnames = config.projects.filter((p) => p.enabled !== false && !p.local).map((p) => p.hostname);
1893
1988
  if (hostnames.length === 0) {
@@ -1917,11 +2012,15 @@ Try running with sudo: ${chalk12.cyan("sudo bindler apply")}`));
1917
2012
  }
1918
2013
  }
1919
2014
  console.log(chalk12.green("\n\u2713 Configuration applied successfully!"));
1920
- console.log(chalk12.dim(`
2015
+ if (hasProjects) {
2016
+ console.log(chalk12.dim(`
1921
2017
  ${config.projects.length} project(s) configured:`));
1922
- for (const project of config.projects) {
1923
- const status = project.enabled !== false ? chalk12.green("enabled") : chalk12.yellow("disabled");
1924
- console.log(chalk12.dim(` - ${project.name} \u2192 ${project.hostname} (${status})`));
2018
+ for (const project of config.projects) {
2019
+ const status = project.enabled !== false ? chalk12.green("enabled") : chalk12.yellow("disabled");
2020
+ console.log(chalk12.dim(` - ${project.name} \u2192 ${project.hostname} (${status})`));
2021
+ }
2022
+ } else {
2023
+ console.log(chalk12.dim("\nNo projects configured. Nginx config cleared."));
1925
2024
  }
1926
2025
  }
1927
2026
 
@@ -2234,7 +2333,7 @@ async function infoCommand() {
2234
2333
  `));
2235
2334
  console.log(chalk15.white(" Manage multiple projects behind Cloudflare Tunnel"));
2236
2335
  console.log(chalk15.white(" with Nginx and PM2\n"));
2237
- console.log(chalk15.dim(" Version: ") + chalk15.white("1.3.0"));
2336
+ console.log(chalk15.dim(" Version: ") + chalk15.white("1.6.0"));
2238
2337
  console.log(chalk15.dim(" Author: ") + chalk15.white("alfaoz"));
2239
2338
  console.log(chalk15.dim(" License: ") + chalk15.white("MIT"));
2240
2339
  console.log(chalk15.dim(" GitHub: ") + chalk15.cyan("https://github.com/alfaoz/bindler"));
@@ -2699,12 +2798,18 @@ async function initCommand() {
2699
2798
  }
2700
2799
  }
2701
2800
  console.log(chalk18.bold("\n1. Choose your setup:\n"));
2801
+ const hasCloudflared = isCloudflaredInstalled();
2802
+ const defaultMode = hasCloudflared ? "tunnel" : "direct";
2803
+ if (!hasCloudflared) {
2804
+ console.log(chalk18.dim(" cloudflared not detected - suggesting direct mode for VPS\n"));
2805
+ }
2702
2806
  const { mode } = await inquirer4.prompt([
2703
2807
  {
2704
2808
  type: "list",
2705
2809
  name: "mode",
2706
2810
  message: "How will you expose your projects?",
2707
- choices: [
2811
+ default: defaultMode,
2812
+ choices: hasCloudflared ? [
2708
2813
  {
2709
2814
  name: "Cloudflare Tunnel (recommended for home servers)",
2710
2815
  value: "tunnel"
@@ -2717,6 +2822,19 @@ async function initCommand() {
2717
2822
  name: "Local only (development, no internet access)",
2718
2823
  value: "local"
2719
2824
  }
2825
+ ] : [
2826
+ {
2827
+ name: "Direct (VPS with public IP, port 80/443) - recommended",
2828
+ value: "direct"
2829
+ },
2830
+ {
2831
+ name: "Cloudflare Tunnel (requires cloudflared)",
2832
+ value: "tunnel"
2833
+ },
2834
+ {
2835
+ name: "Local only (development, no internet access)",
2836
+ value: "local"
2837
+ }
2720
2838
  ]
2721
2839
  }
2722
2840
  ]);
@@ -3822,8 +3940,89 @@ Process exited with code ${code}`));
3822
3940
  });
3823
3941
  }
3824
3942
 
3825
- // src/lib/update-check.ts
3943
+ // src/commands/config.ts
3826
3944
  import chalk29 from "chalk";
3945
+ var VALID_KEYS = [
3946
+ "nginxListen",
3947
+ "projectsRoot",
3948
+ "tunnelName",
3949
+ "mode",
3950
+ "applyCloudflareDnsRoutes",
3951
+ "sslEnabled",
3952
+ "sslEmail"
3953
+ ];
3954
+ async function configCommand(action, key, value) {
3955
+ if (!action || action === "list") {
3956
+ const defaults = getDefaults();
3957
+ console.log(chalk29.blue("Current configuration:\n"));
3958
+ for (const [k, v] of Object.entries(defaults)) {
3959
+ console.log(` ${chalk29.cyan(k)}: ${chalk29.white(String(v))}`);
3960
+ }
3961
+ console.log(chalk29.dim("\nUse `bindler config set <key> <value>` to change a setting."));
3962
+ return;
3963
+ }
3964
+ if (action === "get") {
3965
+ if (!key) {
3966
+ console.error(chalk29.red("Usage: bindler config get <key>"));
3967
+ console.log(chalk29.dim(`
3968
+ Available keys: ${VALID_KEYS.join(", ")}`));
3969
+ process.exit(1);
3970
+ }
3971
+ const defaults = getDefaults();
3972
+ const configKey = key;
3973
+ if (!(configKey in defaults)) {
3974
+ console.error(chalk29.red(`Unknown config key: ${key}`));
3975
+ console.log(chalk29.dim(`
3976
+ Available keys: ${VALID_KEYS.join(", ")}`));
3977
+ process.exit(1);
3978
+ }
3979
+ console.log(defaults[configKey]);
3980
+ return;
3981
+ }
3982
+ if (action === "set") {
3983
+ if (!key || value === void 0) {
3984
+ console.error(chalk29.red("Usage: bindler config set <key> <value>"));
3985
+ console.log(chalk29.dim(`
3986
+ Available keys: ${VALID_KEYS.join(", ")}`));
3987
+ console.log(chalk29.dim("\nExamples:"));
3988
+ console.log(chalk29.dim(" bindler config set nginxListen 8080"));
3989
+ console.log(chalk29.dim(" bindler config set mode tunnel"));
3990
+ process.exit(1);
3991
+ }
3992
+ const configKey = key;
3993
+ if (!VALID_KEYS.includes(configKey)) {
3994
+ console.error(chalk29.red(`Unknown config key: ${key}`));
3995
+ console.log(chalk29.dim(`
3996
+ Available keys: ${VALID_KEYS.join(", ")}`));
3997
+ process.exit(1);
3998
+ }
3999
+ const config = readConfig();
4000
+ let parsedValue = value;
4001
+ if (configKey === "applyCloudflareDnsRoutes" || configKey === "sslEnabled") {
4002
+ parsedValue = value === "true" || value === "1";
4003
+ } else if (configKey === "mode") {
4004
+ if (value !== "tunnel" && value !== "direct") {
4005
+ console.error(chalk29.red('Mode must be "tunnel" or "direct"'));
4006
+ process.exit(1);
4007
+ }
4008
+ parsedValue = value;
4009
+ }
4010
+ config.defaults[configKey] = parsedValue;
4011
+ writeConfig(config);
4012
+ console.log(chalk29.green(`\u2713 Set ${key} = ${parsedValue}`));
4013
+ console.log(chalk29.dim("\nRun `bindler apply` to apply changes."));
4014
+ return;
4015
+ }
4016
+ console.error(chalk29.red(`Unknown action: ${action}`));
4017
+ console.log(chalk29.dim("\nUsage:"));
4018
+ console.log(chalk29.dim(" bindler config # list all settings"));
4019
+ console.log(chalk29.dim(" bindler config get <key> # get a setting"));
4020
+ console.log(chalk29.dim(" bindler config set <key> <value> # set a setting"));
4021
+ process.exit(1);
4022
+ }
4023
+
4024
+ // src/lib/update-check.ts
4025
+ import chalk30 from "chalk";
3827
4026
  import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
3828
4027
  import { join as join9 } from "path";
3829
4028
  import { homedir as homedir4 } from "os";
@@ -3876,28 +4075,28 @@ async function checkForUpdates() {
3876
4075
  const cache = readCache();
3877
4076
  const now = Date.now();
3878
4077
  if (now - cache.lastCheck < CHECK_INTERVAL) {
3879
- if (cache.latestVersion && compareVersions("1.3.0", cache.latestVersion) < 0) {
4078
+ if (cache.latestVersion && compareVersions("1.6.0", cache.latestVersion) < 0) {
3880
4079
  showUpdateMessage(cache.latestVersion);
3881
4080
  }
3882
4081
  return;
3883
4082
  }
3884
4083
  fetchLatestVersion().then((latestVersion) => {
3885
4084
  writeCache({ lastCheck: now, latestVersion });
3886
- if (latestVersion && compareVersions("1.3.0", latestVersion) < 0) {
4085
+ if (latestVersion && compareVersions("1.6.0", latestVersion) < 0) {
3887
4086
  showUpdateMessage(latestVersion);
3888
4087
  }
3889
4088
  });
3890
4089
  }
3891
4090
  function showUpdateMessage(latestVersion) {
3892
4091
  console.log("");
3893
- console.log(chalk29.yellow(` Update available: ${"1.3.0"} \u2192 ${latestVersion}`));
3894
- console.log(chalk29.dim(` Run: npm update -g bindler`));
4092
+ console.log(chalk30.yellow(` Update available: ${"1.6.0"} \u2192 ${latestVersion}`));
4093
+ console.log(chalk30.dim(` Run: npm update -g bindler`));
3895
4094
  console.log("");
3896
4095
  }
3897
4096
 
3898
4097
  // src/cli.ts
3899
4098
  var program = new Command();
3900
- program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.3.0");
4099
+ program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.6.0");
3901
4100
  program.hook("preAction", async () => {
3902
4101
  try {
3903
4102
  initConfig();
@@ -3916,73 +4115,73 @@ program.command("status").description("Show detailed status of all projects").ac
3916
4115
  });
3917
4116
  program.command("start [name]").description("Start an npm project with PM2").option("-a, --all", "Start all npm projects").action(async (name, options) => {
3918
4117
  if (!name && !options.all) {
3919
- console.log(chalk30.red("Usage: bindler start <name> or bindler start --all"));
3920
- console.log(chalk30.dim("\nExamples:"));
3921
- console.log(chalk30.dim(" bindler start myapp"));
3922
- console.log(chalk30.dim(" bindler start --all # start all npm projects"));
4118
+ console.log(chalk31.red("Usage: bindler start <name> or bindler start --all"));
4119
+ console.log(chalk31.dim("\nExamples:"));
4120
+ console.log(chalk31.dim(" bindler start myapp"));
4121
+ console.log(chalk31.dim(" bindler start --all # start all npm projects"));
3923
4122
  process.exit(1);
3924
4123
  }
3925
4124
  await startCommand(name, options);
3926
4125
  });
3927
4126
  program.command("stop [name]").description("Stop an npm project").option("-a, --all", "Stop all npm projects").action(async (name, options) => {
3928
4127
  if (!name && !options.all) {
3929
- console.log(chalk30.red("Usage: bindler stop <name> or bindler stop --all"));
3930
- console.log(chalk30.dim("\nExamples:"));
3931
- console.log(chalk30.dim(" bindler stop myapp"));
3932
- console.log(chalk30.dim(" bindler stop --all # stop all npm projects"));
4128
+ console.log(chalk31.red("Usage: bindler stop <name> or bindler stop --all"));
4129
+ console.log(chalk31.dim("\nExamples:"));
4130
+ console.log(chalk31.dim(" bindler stop myapp"));
4131
+ console.log(chalk31.dim(" bindler stop --all # stop all npm projects"));
3933
4132
  process.exit(1);
3934
4133
  }
3935
4134
  await stopCommand(name, options);
3936
4135
  });
3937
4136
  program.command("restart [name]").description("Restart an npm project").option("-a, --all", "Restart all npm projects").action(async (name, options) => {
3938
4137
  if (!name && !options.all) {
3939
- console.log(chalk30.red("Usage: bindler restart <name> or bindler restart --all"));
3940
- console.log(chalk30.dim("\nExamples:"));
3941
- console.log(chalk30.dim(" bindler restart myapp"));
3942
- console.log(chalk30.dim(" bindler restart --all # restart all npm projects"));
4138
+ console.log(chalk31.red("Usage: bindler restart <name> or bindler restart --all"));
4139
+ console.log(chalk31.dim("\nExamples:"));
4140
+ console.log(chalk31.dim(" bindler restart myapp"));
4141
+ console.log(chalk31.dim(" bindler restart --all # restart all npm projects"));
3943
4142
  process.exit(1);
3944
4143
  }
3945
4144
  await restartCommand(name, options);
3946
4145
  });
3947
4146
  program.command("logs [name]").description("Show logs for an npm project").option("-f, --follow", "Follow log output").option("-l, --lines <n>", "Number of lines to show", "200").action(async (name, options) => {
3948
4147
  if (!name) {
3949
- console.log(chalk30.red("Usage: bindler logs <name>"));
3950
- console.log(chalk30.dim("\nExamples:"));
3951
- console.log(chalk30.dim(" bindler logs myapp"));
3952
- console.log(chalk30.dim(" bindler logs myapp --follow"));
3953
- console.log(chalk30.dim(" bindler logs myapp --lines 500"));
4148
+ console.log(chalk31.red("Usage: bindler logs <name>"));
4149
+ console.log(chalk31.dim("\nExamples:"));
4150
+ console.log(chalk31.dim(" bindler logs myapp"));
4151
+ console.log(chalk31.dim(" bindler logs myapp --follow"));
4152
+ console.log(chalk31.dim(" bindler logs myapp --lines 500"));
3954
4153
  process.exit(1);
3955
4154
  }
3956
4155
  await logsCommand(name, { ...options, lines: parseInt(options.lines, 10) });
3957
4156
  });
3958
4157
  program.command("update [name]").description("Update project configuration").option("-h, --hostname <hostname>", "New hostname").option("--port <port>", "New port number").option("-s, --start <command>", "New start command").option("-p, --path <path>", "New project path").option("-e, --env <vars...>", "Environment variables (KEY=value)").option("--enable", "Enable the project").option("--disable", "Disable the project").action(async (name, options) => {
3959
4158
  if (!name) {
3960
- console.log(chalk30.red("Usage: bindler update <name> [options]"));
3961
- console.log(chalk30.dim("\nExamples:"));
3962
- console.log(chalk30.dim(" bindler update myapp --hostname newapp.example.com"));
3963
- console.log(chalk30.dim(" bindler update myapp --port 4000"));
3964
- console.log(chalk30.dim(" bindler update myapp --disable"));
4159
+ console.log(chalk31.red("Usage: bindler update <name> [options]"));
4160
+ console.log(chalk31.dim("\nExamples:"));
4161
+ console.log(chalk31.dim(" bindler update myapp --hostname newapp.example.com"));
4162
+ console.log(chalk31.dim(" bindler update myapp --port 4000"));
4163
+ console.log(chalk31.dim(" bindler update myapp --disable"));
3965
4164
  process.exit(1);
3966
4165
  }
3967
4166
  await updateCommand(name, options);
3968
4167
  });
3969
4168
  program.command("edit [name]").description("Edit project configuration in $EDITOR").action(async (name) => {
3970
4169
  if (!name) {
3971
- console.log(chalk30.red("Usage: bindler edit <name>"));
3972
- console.log(chalk30.dim("\nOpens the project config in your $EDITOR"));
3973
- console.log(chalk30.dim("\nExample:"));
3974
- console.log(chalk30.dim(" bindler edit myapp"));
4170
+ console.log(chalk31.red("Usage: bindler edit <name>"));
4171
+ console.log(chalk31.dim("\nOpens the project config in your $EDITOR"));
4172
+ console.log(chalk31.dim("\nExample:"));
4173
+ console.log(chalk31.dim(" bindler edit myapp"));
3975
4174
  process.exit(1);
3976
4175
  }
3977
4176
  await editCommand(name);
3978
4177
  });
3979
4178
  program.command("remove [name]").alias("rm").description("Remove a project from registry").option("-f, --force", "Skip confirmation").option("--apply", "Apply nginx config after removing").action(async (name, options) => {
3980
4179
  if (!name) {
3981
- console.log(chalk30.red("Usage: bindler remove <name>"));
3982
- console.log(chalk30.dim("\nExamples:"));
3983
- console.log(chalk30.dim(" bindler remove myapp"));
3984
- console.log(chalk30.dim(" bindler remove myapp --force # skip confirmation"));
3985
- console.log(chalk30.dim(" bindler rm myapp # alias"));
4180
+ console.log(chalk31.red("Usage: bindler remove <name>"));
4181
+ console.log(chalk31.dim("\nExamples:"));
4182
+ console.log(chalk31.dim(" bindler remove myapp"));
4183
+ console.log(chalk31.dim(" bindler remove myapp --force # skip confirmation"));
4184
+ console.log(chalk31.dim(" bindler rm myapp # alias"));
3986
4185
  process.exit(1);
3987
4186
  }
3988
4187
  await removeCommand(name, options);
@@ -4001,10 +4200,10 @@ program.command("info").description("Show bindler information and stats").action
4001
4200
  });
4002
4201
  program.command("check [hostname]").description("Check DNS propagation and HTTP accessibility for a hostname").option("-v, --verbose", "Show verbose output").action(async (hostname, options) => {
4003
4202
  if (!hostname) {
4004
- console.log(chalk30.red("Usage: bindler check <hostname>"));
4005
- console.log(chalk30.dim("\nExamples:"));
4006
- console.log(chalk30.dim(" bindler check myapp.example.com"));
4007
- console.log(chalk30.dim(" bindler check myapp # uses project name"));
4203
+ console.log(chalk31.red("Usage: bindler check <hostname>"));
4204
+ console.log(chalk31.dim("\nExamples:"));
4205
+ console.log(chalk31.dim(" bindler check myapp.example.com"));
4206
+ console.log(chalk31.dim(" bindler check myapp # uses project name"));
4008
4207
  process.exit(1);
4009
4208
  }
4010
4209
  await checkCommand(hostname, options);
@@ -4048,5 +4247,8 @@ program.command("clone [source] [new-name]").description("Clone a project config
4048
4247
  program.command("dev [name]").description("Start a project in development mode with hot reload").option("-p, --port <port>", "Override port number").option("-h, --hostname <hostname>", "Override hostname").action(async (name, options) => {
4049
4248
  await devCommand(name, options);
4050
4249
  });
4250
+ program.command("config [action] [key] [value]").description("View or modify bindler configuration").action(async (action, key, value) => {
4251
+ await configCommand(action, key, value);
4252
+ });
4051
4253
  program.parse();
4052
4254
  //# sourceMappingURL=cli.js.map