bindler 1.2.0 → 1.4.1

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
@@ -1,14 +1,20 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/cli.ts
4
10
  import { Command } from "commander";
5
- import chalk29 from "chalk";
11
+ import chalk30 from "chalk";
6
12
 
7
13
  // src/commands/new.ts
8
- import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
14
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
9
15
  import { basename as basename2 } from "path";
10
16
  import inquirer from "inquirer";
11
- import chalk from "chalk";
17
+ import chalk2 from "chalk";
12
18
 
13
19
  // src/lib/config.ts
14
20
  import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from "fs";
@@ -694,9 +700,175 @@ function mergeYamlWithProject(project, yamlConfig) {
694
700
  };
695
701
  }
696
702
 
703
+ // src/lib/validation.ts
704
+ import { existsSync as existsSync5, accessSync, constants } from "fs";
705
+ import { homedir as homedir2 } from "os";
706
+ import { join as join5 } from "path";
707
+ import chalk from "chalk";
708
+ var MACOS_PROTECTED_PATHS = [
709
+ join5(homedir2(), "Desktop"),
710
+ join5(homedir2(), "Documents"),
711
+ join5(homedir2(), "Downloads")
712
+ ];
713
+ function isProtectedPath(path) {
714
+ if (process.platform !== "darwin") return false;
715
+ const normalizedPath = path.replace(/\/+$/, "");
716
+ return MACOS_PROTECTED_PATHS.some(
717
+ (protected_) => normalizedPath === protected_ || normalizedPath.startsWith(protected_ + "/")
718
+ );
719
+ }
720
+ function checkPortAvailable(port) {
721
+ const result = execCommandSafe(`lsof -i :${port} -P -n 2>/dev/null | grep LISTEN | head -1`);
722
+ if (!result.success || !result.output) {
723
+ return { available: true };
724
+ }
725
+ const parts = result.output.trim().split(/\s+/);
726
+ const processName = parts[0] || "unknown";
727
+ return { available: false, usedBy: processName };
728
+ }
729
+ function validateProject(project) {
730
+ const errors = [];
731
+ const warnings = [];
732
+ if (!existsSync5(project.path)) {
733
+ errors.push(`Path does not exist: ${project.path}`);
734
+ }
735
+ if (isProtectedPath(project.path)) {
736
+ errors.push(
737
+ `Path is in a macOS protected folder (${project.path}). Nginx cannot access Desktop/Documents/Downloads without Full Disk Access. Move your project to ~/projects or grant nginx Full Disk Access in System Settings.`
738
+ );
739
+ }
740
+ if (project.type === "npm" && project.port) {
741
+ const portCheck = checkPortAvailable(project.port);
742
+ if (!portCheck.available) {
743
+ warnings.push(`Port ${project.port} is in use by ${portCheck.usedBy}`);
744
+ }
745
+ }
746
+ if (!project.hostname.match(/^[a-zA-Z0-9][a-zA-Z0-9.-]*[a-zA-Z0-9]$/)) {
747
+ warnings.push(`Hostname "${project.hostname}" may be invalid`);
748
+ }
749
+ if (project.type === "npm" && !project.start) {
750
+ const pkgPath = join5(project.path, "package.json");
751
+ if (existsSync5(pkgPath)) {
752
+ try {
753
+ const pkg = JSON.parse(__require("fs").readFileSync(pkgPath, "utf-8"));
754
+ if (!pkg.scripts?.start) {
755
+ warnings.push('No start command specified and package.json has no "start" script');
756
+ }
757
+ } catch {
758
+ }
759
+ }
760
+ }
761
+ return {
762
+ valid: errors.length === 0,
763
+ errors,
764
+ warnings
765
+ };
766
+ }
767
+ function validateConfig(config) {
768
+ const errors = [];
769
+ const warnings = [];
770
+ const listenPort = parseInt(config.defaults.nginxListen.split(":").pop() || "80", 10);
771
+ if (listenPort === 80 || listenPort === 443) {
772
+ if (process.getuid && process.getuid() !== 0) {
773
+ warnings.push(
774
+ `Nginx is configured to listen on port ${listenPort}, which requires root/sudo. Run 'sudo bindler apply' or use a higher port like 8080.`
775
+ );
776
+ }
777
+ }
778
+ const nginxPath = config.defaults.nginxManagedPath;
779
+ const nginxDir = nginxPath.substring(0, nginxPath.lastIndexOf("/"));
780
+ if (!existsSync5(nginxDir)) {
781
+ errors.push(`Nginx config directory does not exist: ${nginxDir}`);
782
+ }
783
+ for (const project of config.projects) {
784
+ if (project.enabled === false) continue;
785
+ const projectResult = validateProject(project);
786
+ for (const err of projectResult.errors) {
787
+ errors.push(`[${project.name}] ${err}`);
788
+ }
789
+ for (const warn of projectResult.warnings) {
790
+ warnings.push(`[${project.name}] ${warn}`);
791
+ }
792
+ }
793
+ const hostnames = config.projects.filter((p) => p.enabled !== false).map((p) => p.hostname);
794
+ const duplicates = hostnames.filter((h, i) => hostnames.indexOf(h) !== i);
795
+ if (duplicates.length > 0) {
796
+ const uniqueDupes = [...new Set(duplicates)];
797
+ for (const dupe of uniqueDupes) {
798
+ const projects = config.projects.filter((p) => p.hostname === dupe).map((p) => p.name);
799
+ if (!projects.every((p, i, arr) => {
800
+ const proj = config.projects.find((x) => x.name === p);
801
+ const firstProj = config.projects.find((x) => x.name === arr[0]);
802
+ return proj?.basePath !== firstProj?.basePath || i === 0;
803
+ })) {
804
+ warnings.push(`Hostname "${dupe}" is shared by: ${projects.join(", ")} (ensure they have different base paths)`);
805
+ }
806
+ }
807
+ }
808
+ const ports = config.projects.filter((p) => p.enabled !== false && p.type === "npm" && p.port).map((p) => ({ name: p.name, port: p.port }));
809
+ const portMap = /* @__PURE__ */ new Map();
810
+ for (const { name, port } of ports) {
811
+ const existing = portMap.get(port) || [];
812
+ existing.push(name);
813
+ portMap.set(port, existing);
814
+ }
815
+ for (const [port, names] of portMap) {
816
+ if (names.length > 1) {
817
+ errors.push(`Port ${port} is used by multiple projects: ${names.join(", ")}`);
818
+ }
819
+ }
820
+ return {
821
+ valid: errors.length === 0,
822
+ errors,
823
+ warnings
824
+ };
825
+ }
826
+ function printValidationResult(result) {
827
+ if (result.errors.length > 0) {
828
+ console.log(chalk.red("\n\u2717 Validation errors:"));
829
+ for (const err of result.errors) {
830
+ console.log(chalk.red(` \u2022 ${err}`));
831
+ }
832
+ }
833
+ if (result.warnings.length > 0) {
834
+ console.log(chalk.yellow("\n\u26A0 Warnings:"));
835
+ for (const warn of result.warnings) {
836
+ console.log(chalk.yellow(` \u2022 ${warn}`));
837
+ }
838
+ }
839
+ if (result.valid && result.warnings.length === 0) {
840
+ console.log(chalk.green("\u2713 All checks passed"));
841
+ }
842
+ }
843
+ function runPreflightChecks(config) {
844
+ const result = validateConfig(config);
845
+ const nginxCheck = execCommandSafe("which nginx");
846
+ if (!nginxCheck.success) {
847
+ result.errors.push("Nginx is not installed. Run: brew install nginx");
848
+ }
849
+ const nginxRunning = execCommandSafe("pgrep nginx");
850
+ if (!nginxRunning.success) {
851
+ result.warnings.push("Nginx is not running. After apply, start it with: brew services start nginx");
852
+ }
853
+ const localProjects = config.projects.filter((p) => p.local && p.enabled !== false);
854
+ if (localProjects.length > 0) {
855
+ const hostsResult = execCommandSafe("cat /etc/hosts");
856
+ if (hostsResult.success) {
857
+ for (const project of localProjects) {
858
+ if (!hostsResult.output.includes(project.hostname)) {
859
+ result.warnings.push(
860
+ `Hostname "${project.hostname}" not in /etc/hosts. Add: 127.0.0.1 ${project.hostname}`
861
+ );
862
+ }
863
+ }
864
+ }
865
+ }
866
+ return result;
867
+ }
868
+
697
869
  // src/commands/new.ts
698
870
  async function newCommand(options) {
699
- console.log(chalk.dim("Checking prerequisites...\n"));
871
+ console.log(chalk2.dim("Checking prerequisites...\n"));
700
872
  const issues = [];
701
873
  if (!isNginxInstalled()) {
702
874
  issues.push("nginx is not installed. Install: brew install nginx (macOS) or apt install nginx (Linux)");
@@ -705,11 +877,11 @@ async function newCommand(options) {
705
877
  issues.push("PM2 is not installed. Install: npm install -g pm2");
706
878
  }
707
879
  if (issues.length > 0) {
708
- console.log(chalk.red("Missing prerequisites:\n"));
880
+ console.log(chalk2.red("Missing prerequisites:\n"));
709
881
  for (const issue of issues) {
710
- console.log(chalk.red(` \u2717 ${issue}`));
882
+ console.log(chalk2.red(` \u2717 ${issue}`));
711
883
  }
712
- console.log(chalk.dim("\nRun `bindler doctor` for full diagnostics."));
884
+ console.log(chalk2.dim("\nRun `bindler doctor` for full diagnostics."));
713
885
  const { proceed } = await inquirer.prompt([
714
886
  {
715
887
  type: "confirm",
@@ -723,17 +895,17 @@ async function newCommand(options) {
723
895
  }
724
896
  console.log("");
725
897
  } else {
726
- console.log(chalk.green("\u2713 Prerequisites OK\n"));
898
+ console.log(chalk2.green("\u2713 Prerequisites OK\n"));
727
899
  }
728
900
  const defaults = getDefaults();
729
901
  let project = {};
730
902
  const cwd = process.cwd();
731
903
  const cwdName = basename2(cwd);
732
904
  const initialPath = options.path || cwd;
733
- const yamlConfig = existsSync5(initialPath) ? readBindlerYaml(initialPath) : null;
905
+ const yamlConfig = existsSync6(initialPath) ? readBindlerYaml(initialPath) : null;
734
906
  let yamlDefaults = {};
735
907
  if (yamlConfig) {
736
- console.log(chalk.cyan("Found bindler.yaml - using as defaults\n"));
908
+ console.log(chalk2.cyan("Found bindler.yaml - using as defaults\n"));
737
909
  yamlDefaults = yamlToProject(yamlConfig, initialPath);
738
910
  }
739
911
  if (!options.name) {
@@ -767,7 +939,7 @@ async function newCommand(options) {
767
939
  name: "type",
768
940
  message: "Project type:",
769
941
  choices: (answers2) => {
770
- const detected = existsSync5(answers2.path) ? detectProjectType(answers2.path) : "static";
942
+ const detected = existsSync6(answers2.path) ? detectProjectType(answers2.path) : "static";
771
943
  return [
772
944
  { name: `npm (Node.js app)${detected === "npm" ? " - detected" : ""}`, value: "npm" },
773
945
  { name: `static (HTML/CSS/JS)${detected === "static" ? " - detected" : ""}`, value: "static" }
@@ -775,7 +947,7 @@ async function newCommand(options) {
775
947
  },
776
948
  default: (answers2) => {
777
949
  if (yamlDefaults.type) return yamlDefaults.type;
778
- return existsSync5(answers2.path) ? detectProjectType(answers2.path) : "static";
950
+ return existsSync6(answers2.path) ? detectProjectType(answers2.path) : "static";
779
951
  }
780
952
  },
781
953
  {
@@ -808,7 +980,7 @@ async function newCommand(options) {
808
980
  if (yamlDefaults.security) project.security = yamlDefaults.security;
809
981
  if (yamlDefaults.environments) project.environments = yamlDefaults.environments;
810
982
  if (answers.type === "npm") {
811
- const scripts = existsSync5(answers.path) ? getPackageJsonScripts(answers.path) : [];
983
+ const scripts = existsSync6(answers.path) ? getPackageJsonScripts(answers.path) : [];
812
984
  const suggestedPort = yamlDefaults.port || findAvailablePort();
813
985
  const npmAnswers = await inquirer.prompt([
814
986
  {
@@ -862,7 +1034,7 @@ async function newCommand(options) {
862
1034
  }
863
1035
  } else {
864
1036
  if (!options.hostname) {
865
- console.error(chalk.red("Error: --hostname is required"));
1037
+ console.error(chalk2.red("Error: --hostname is required"));
866
1038
  process.exit(1);
867
1039
  }
868
1040
  project.name = options.name;
@@ -882,14 +1054,14 @@ async function newCommand(options) {
882
1054
  }
883
1055
  }
884
1056
  if (!validateProjectName(project.name)) {
885
- console.error(chalk.red("Error: Invalid project name"));
1057
+ console.error(chalk2.red("Error: Invalid project name"));
886
1058
  process.exit(1);
887
1059
  }
888
1060
  if (!validateHostname(project.hostname)) {
889
- console.error(chalk.red("Error: Invalid hostname"));
1061
+ console.error(chalk2.red("Error: Invalid hostname"));
890
1062
  process.exit(1);
891
1063
  }
892
- if (!existsSync5(project.path)) {
1064
+ if (!existsSync6(project.path)) {
893
1065
  const createDir = options.name ? true : (await inquirer.prompt([
894
1066
  {
895
1067
  type: "confirm",
@@ -900,51 +1072,72 @@ async function newCommand(options) {
900
1072
  ])).create;
901
1073
  if (createDir) {
902
1074
  mkdirSync3(project.path, { recursive: true });
903
- console.log(chalk.green(`Created directory: ${project.path}`));
1075
+ console.log(chalk2.green(`Created directory: ${project.path}`));
1076
+ }
1077
+ }
1078
+ const validationResult = validateProject(project);
1079
+ if (!validationResult.valid || validationResult.warnings.length > 0) {
1080
+ console.log("");
1081
+ printValidationResult(validationResult);
1082
+ if (!validationResult.valid) {
1083
+ console.log(chalk2.red("\n\u2717 Cannot add project due to validation errors."));
1084
+ process.exit(1);
1085
+ }
1086
+ const { proceed } = await inquirer.prompt([
1087
+ {
1088
+ type: "confirm",
1089
+ name: "proceed",
1090
+ message: "Continue with these warnings?",
1091
+ default: true
1092
+ }
1093
+ ]);
1094
+ if (!proceed) {
1095
+ console.log(chalk2.yellow("Aborted."));
1096
+ process.exit(0);
904
1097
  }
905
1098
  }
906
1099
  try {
907
1100
  addProject(project);
908
- console.log(chalk.green(`
1101
+ console.log(chalk2.green(`
909
1102
  Project "${project.name}" added successfully!`));
910
1103
  if (project.local) {
911
- console.log(chalk.yellow(`
1104
+ console.log(chalk2.yellow(`
912
1105
  Local project - add to /etc/hosts:`));
913
- console.log(chalk.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
914
- console.log(chalk.dim(`
915
- Run ${chalk.cyan("sudo bindler apply")} to update nginx.`));
916
- console.log(chalk.dim(`Then access at: ${chalk.cyan(`http://${project.hostname}:8080`)}`));
1106
+ console.log(chalk2.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
1107
+ console.log(chalk2.dim(`
1108
+ Run ${chalk2.cyan("sudo bindler apply")} to update nginx.`));
1109
+ console.log(chalk2.dim(`Then access at: ${chalk2.cyan(`http://${project.hostname}:8080`)}`));
917
1110
  } else {
918
- console.log(chalk.dim(`
919
- Configuration saved. Run ${chalk.cyan("sudo bindler apply")} to update nginx and cloudflare.`));
1111
+ console.log(chalk2.dim(`
1112
+ Configuration saved. Run ${chalk2.cyan("sudo bindler apply")} to update nginx and cloudflare.`));
920
1113
  }
921
1114
  if (project.type === "npm") {
922
- console.log(chalk.dim(`Run ${chalk.cyan(`bindler start ${project.name}`)} to start the application.`));
1115
+ console.log(chalk2.dim(`Run ${chalk2.cyan(`bindler start ${project.name}`)} to start the application.`));
923
1116
  }
924
1117
  } catch (error) {
925
- console.error(chalk.red(`Error: ${error instanceof Error ? error.message : error}`));
1118
+ console.error(chalk2.red(`Error: ${error instanceof Error ? error.message : error}`));
926
1119
  process.exit(1);
927
1120
  }
928
1121
  }
929
1122
 
930
1123
  // src/commands/list.ts
931
- import chalk2 from "chalk";
1124
+ import chalk3 from "chalk";
932
1125
  import Table from "cli-table3";
933
1126
  async function listCommand() {
934
1127
  const projects = listProjects();
935
1128
  if (projects.length === 0) {
936
- console.log(chalk2.yellow("No projects registered."));
937
- console.log(chalk2.dim(`Run ${chalk2.cyan("bindler new")} to create one.`));
1129
+ console.log(chalk3.yellow("No projects registered."));
1130
+ console.log(chalk3.dim(`Run ${chalk3.cyan("bindler new")} to create one.`));
938
1131
  return;
939
1132
  }
940
1133
  const table = new Table({
941
1134
  head: [
942
- chalk2.cyan("Name"),
943
- chalk2.cyan("Type"),
944
- chalk2.cyan("Hostname"),
945
- chalk2.cyan("Port"),
946
- chalk2.cyan("Path"),
947
- chalk2.cyan("Status")
1135
+ chalk3.cyan("Name"),
1136
+ chalk3.cyan("Type"),
1137
+ chalk3.cyan("Hostname"),
1138
+ chalk3.cyan("Port"),
1139
+ chalk3.cyan("Path"),
1140
+ chalk3.cyan("Status")
948
1141
  ],
949
1142
  style: {
950
1143
  head: [],
@@ -956,15 +1149,15 @@ async function listCommand() {
956
1149
  if (project.type === "npm") {
957
1150
  const process2 = getProcessByName(project.name);
958
1151
  if (process2) {
959
- status = process2.status === "online" ? chalk2.green("online") : process2.status === "stopped" ? chalk2.yellow("stopped") : chalk2.red(process2.status);
1152
+ status = process2.status === "online" ? chalk3.green("online") : process2.status === "stopped" ? chalk3.yellow("stopped") : chalk3.red(process2.status);
960
1153
  } else {
961
- status = chalk2.dim("not started");
1154
+ status = chalk3.dim("not started");
962
1155
  }
963
1156
  } else {
964
- status = project.enabled !== false ? chalk2.green("serving") : chalk2.yellow("disabled");
1157
+ status = project.enabled !== false ? chalk3.green("serving") : chalk3.yellow("disabled");
965
1158
  }
966
1159
  if (project.enabled === false) {
967
- status = chalk2.yellow("disabled");
1160
+ status = chalk3.yellow("disabled");
968
1161
  }
969
1162
  table.push([
970
1163
  project.name,
@@ -976,17 +1169,17 @@ async function listCommand() {
976
1169
  ]);
977
1170
  }
978
1171
  console.log(table.toString());
979
- console.log(chalk2.dim(`
1172
+ console.log(chalk3.dim(`
980
1173
  ${projects.length} project(s) registered`));
981
1174
  }
982
1175
 
983
1176
  // src/commands/status.ts
984
- import chalk3 from "chalk";
1177
+ import chalk4 from "chalk";
985
1178
  import Table2 from "cli-table3";
986
1179
  async function statusCommand() {
987
1180
  const projects = listProjects();
988
1181
  if (projects.length === 0) {
989
- console.log(chalk3.yellow("No projects registered."));
1182
+ console.log(chalk4.yellow("No projects registered."));
990
1183
  return;
991
1184
  }
992
1185
  const statuses = [];
@@ -1010,12 +1203,12 @@ async function statusCommand() {
1010
1203
  }
1011
1204
  const table = new Table2({
1012
1205
  head: [
1013
- chalk3.cyan("Name"),
1014
- chalk3.cyan("Type"),
1015
- chalk3.cyan("Hostname"),
1016
- chalk3.cyan("Port"),
1017
- chalk3.cyan("PM2 Status"),
1018
- chalk3.cyan("Port Check")
1206
+ chalk4.cyan("Name"),
1207
+ chalk4.cyan("Type"),
1208
+ chalk4.cyan("Hostname"),
1209
+ chalk4.cyan("Port"),
1210
+ chalk4.cyan("PM2 Status"),
1211
+ chalk4.cyan("Port Check")
1019
1212
  ],
1020
1213
  style: {
1021
1214
  head: [],
@@ -1028,25 +1221,25 @@ async function statusCommand() {
1028
1221
  if (status.type === "npm") {
1029
1222
  switch (status.pm2Status) {
1030
1223
  case "online":
1031
- pm2StatusStr = chalk3.green("online");
1224
+ pm2StatusStr = chalk4.green("online");
1032
1225
  break;
1033
1226
  case "stopped":
1034
- pm2StatusStr = chalk3.yellow("stopped");
1227
+ pm2StatusStr = chalk4.yellow("stopped");
1035
1228
  break;
1036
1229
  case "errored":
1037
- pm2StatusStr = chalk3.red("errored");
1230
+ pm2StatusStr = chalk4.red("errored");
1038
1231
  break;
1039
1232
  case "not_managed":
1040
- pm2StatusStr = chalk3.dim("not started");
1233
+ pm2StatusStr = chalk4.dim("not started");
1041
1234
  break;
1042
1235
  default:
1043
- pm2StatusStr = chalk3.dim(status.pm2Status || "-");
1236
+ pm2StatusStr = chalk4.dim(status.pm2Status || "-");
1044
1237
  }
1045
- portCheckStr = status.portListening ? chalk3.green("listening") : chalk3.red("not listening");
1238
+ portCheckStr = status.portListening ? chalk4.green("listening") : chalk4.red("not listening");
1046
1239
  }
1047
1240
  if (status.enabled === false) {
1048
- pm2StatusStr = chalk3.yellow("disabled");
1049
- portCheckStr = chalk3.dim("-");
1241
+ pm2StatusStr = chalk4.yellow("disabled");
1242
+ portCheckStr = chalk4.dim("-");
1050
1243
  }
1051
1244
  table.push([
1052
1245
  status.name,
@@ -1060,14 +1253,14 @@ async function statusCommand() {
1060
1253
  console.log(table.toString());
1061
1254
  const runningProcesses = getPm2List().filter((p) => p.name.startsWith("bindler:"));
1062
1255
  if (runningProcesses.length > 0) {
1063
- console.log(chalk3.bold("\nPM2 Process Details:"));
1256
+ console.log(chalk4.bold("\nPM2 Process Details:"));
1064
1257
  const detailTable = new Table2({
1065
1258
  head: [
1066
- chalk3.cyan("Process"),
1067
- chalk3.cyan("CPU"),
1068
- chalk3.cyan("Memory"),
1069
- chalk3.cyan("Uptime"),
1070
- chalk3.cyan("Restarts")
1259
+ chalk4.cyan("Process"),
1260
+ chalk4.cyan("CPU"),
1261
+ chalk4.cyan("Memory"),
1262
+ chalk4.cyan("Uptime"),
1263
+ chalk4.cyan("Restarts")
1071
1264
  ],
1072
1265
  style: {
1073
1266
  head: [],
@@ -1088,197 +1281,198 @@ async function statusCommand() {
1088
1281
  }
1089
1282
 
1090
1283
  // src/commands/start.ts
1091
- import chalk4 from "chalk";
1284
+ import chalk5 from "chalk";
1092
1285
  async function startCommand(name, options) {
1093
1286
  if (options.all) {
1094
1287
  const projects = listProjects();
1095
1288
  const npmProjects = projects.filter((p) => p.type === "npm");
1096
1289
  if (npmProjects.length === 0) {
1097
- console.log(chalk4.yellow("No npm projects to start."));
1290
+ console.log(chalk5.yellow("No npm projects to start."));
1098
1291
  return;
1099
1292
  }
1100
- console.log(chalk4.blue(`Starting ${npmProjects.length} npm project(s)...`));
1293
+ console.log(chalk5.blue(`Starting ${npmProjects.length} npm project(s)...`));
1101
1294
  const results = startAllProjects(npmProjects);
1102
1295
  for (const result2 of results) {
1103
1296
  if (result2.success) {
1104
- console.log(chalk4.green(` \u2713 ${result2.name}`));
1297
+ console.log(chalk5.green(` \u2713 ${result2.name}`));
1105
1298
  } else {
1106
- console.log(chalk4.red(` \u2717 ${result2.name}: ${result2.error}`));
1299
+ console.log(chalk5.red(` \u2717 ${result2.name}: ${result2.error}`));
1107
1300
  }
1108
1301
  }
1109
1302
  const succeeded = results.filter((r) => r.success).length;
1110
- console.log(chalk4.dim(`
1303
+ console.log(chalk5.dim(`
1111
1304
  ${succeeded}/${results.length} started successfully`));
1112
1305
  return;
1113
1306
  }
1114
1307
  if (!name) {
1115
- console.error(chalk4.red("Error: Project name is required. Use --all to start all projects."));
1308
+ console.error(chalk5.red("Error: Project name is required. Use --all to start all projects."));
1116
1309
  process.exit(1);
1117
1310
  }
1118
1311
  const project = getProject(name);
1119
1312
  if (!project) {
1120
- console.error(chalk4.red(`Error: Project "${name}" not found`));
1313
+ console.error(chalk5.red(`Error: Project "${name}" not found`));
1121
1314
  process.exit(1);
1122
1315
  }
1123
1316
  if (project.type !== "npm") {
1124
- console.log(chalk4.blue(`Project "${name}" is a static site - no process to start.`));
1125
- console.log(chalk4.dim("Static sites are served directly by nginx."));
1317
+ console.log(chalk5.blue(`Project "${name}" is a static site - no process to start.`));
1318
+ console.log(chalk5.dim("Static sites are served directly by nginx."));
1126
1319
  return;
1127
1320
  }
1128
- console.log(chalk4.blue(`Starting ${name}...`));
1321
+ console.log(chalk5.blue(`Starting ${name}...`));
1129
1322
  const result = startProject(project);
1130
1323
  if (result.success) {
1131
- console.log(chalk4.green(`\u2713 ${name} started successfully`));
1132
- console.log(chalk4.dim(` Port: ${project.port}`));
1133
- console.log(chalk4.dim(` URL: https://${project.hostname}`));
1324
+ console.log(chalk5.green(`\u2713 ${name} started successfully`));
1325
+ console.log(chalk5.dim(` Port: ${project.port}`));
1326
+ console.log(chalk5.dim(` URL: https://${project.hostname}`));
1134
1327
  } else {
1135
- console.error(chalk4.red(`\u2717 Failed to start ${name}: ${result.error}`));
1328
+ console.error(chalk5.red(`\u2717 Failed to start ${name}: ${result.error}`));
1136
1329
  process.exit(1);
1137
1330
  }
1138
1331
  }
1139
1332
 
1140
1333
  // src/commands/stop.ts
1141
- import chalk5 from "chalk";
1334
+ import chalk6 from "chalk";
1142
1335
  async function stopCommand(name, options) {
1143
1336
  if (options.all) {
1144
1337
  const projects = listProjects();
1145
1338
  const npmProjects = projects.filter((p) => p.type === "npm");
1146
1339
  if (npmProjects.length === 0) {
1147
- console.log(chalk5.yellow("No npm projects to stop."));
1340
+ console.log(chalk6.yellow("No npm projects to stop."));
1148
1341
  return;
1149
1342
  }
1150
- console.log(chalk5.blue(`Stopping ${npmProjects.length} npm project(s)...`));
1343
+ console.log(chalk6.blue(`Stopping ${npmProjects.length} npm project(s)...`));
1151
1344
  const results = stopAllProjects(npmProjects);
1152
1345
  for (const result2 of results) {
1153
1346
  if (result2.success) {
1154
- console.log(chalk5.green(` \u2713 ${result2.name}`));
1347
+ console.log(chalk6.green(` \u2713 ${result2.name}`));
1155
1348
  } else {
1156
- console.log(chalk5.red(` \u2717 ${result2.name}: ${result2.error}`));
1349
+ console.log(chalk6.red(` \u2717 ${result2.name}: ${result2.error}`));
1157
1350
  }
1158
1351
  }
1159
1352
  const succeeded = results.filter((r) => r.success).length;
1160
- console.log(chalk5.dim(`
1353
+ console.log(chalk6.dim(`
1161
1354
  ${succeeded}/${results.length} stopped successfully`));
1162
1355
  return;
1163
1356
  }
1164
1357
  if (!name) {
1165
- console.error(chalk5.red("Error: Project name is required. Use --all to stop all projects."));
1358
+ console.error(chalk6.red("Error: Project name is required. Use --all to stop all projects."));
1166
1359
  process.exit(1);
1167
1360
  }
1168
1361
  const project = getProject(name);
1169
1362
  if (!project) {
1170
- console.error(chalk5.red(`Error: Project "${name}" not found`));
1363
+ console.error(chalk6.red(`Error: Project "${name}" not found`));
1171
1364
  process.exit(1);
1172
1365
  }
1173
1366
  if (project.type !== "npm") {
1174
- console.log(chalk5.yellow(`Project "${name}" is a static site - no process to stop.`));
1367
+ console.log(chalk6.yellow(`Project "${name}" is a static site - no process to stop.`));
1175
1368
  return;
1176
1369
  }
1177
- console.log(chalk5.blue(`Stopping ${name}...`));
1370
+ console.log(chalk6.blue(`Stopping ${name}...`));
1178
1371
  const result = stopProject(name);
1179
1372
  if (result.success) {
1180
- console.log(chalk5.green(`\u2713 ${name} stopped successfully`));
1373
+ console.log(chalk6.green(`\u2713 ${name} stopped successfully`));
1181
1374
  } else {
1182
- console.error(chalk5.red(`\u2717 Failed to stop ${name}: ${result.error}`));
1375
+ console.error(chalk6.red(`\u2717 Failed to stop ${name}: ${result.error}`));
1183
1376
  process.exit(1);
1184
1377
  }
1185
1378
  }
1186
1379
 
1187
1380
  // src/commands/restart.ts
1188
- import chalk6 from "chalk";
1381
+ import chalk7 from "chalk";
1189
1382
  async function restartCommand(name, options) {
1190
1383
  if (options.all) {
1191
1384
  const projects = listProjects();
1192
1385
  const npmProjects = projects.filter((p) => p.type === "npm");
1193
1386
  if (npmProjects.length === 0) {
1194
- console.log(chalk6.yellow("No npm projects to restart."));
1387
+ console.log(chalk7.yellow("No npm projects to restart."));
1195
1388
  return;
1196
1389
  }
1197
- console.log(chalk6.blue(`Restarting ${npmProjects.length} npm project(s)...`));
1390
+ console.log(chalk7.blue(`Restarting ${npmProjects.length} npm project(s)...`));
1198
1391
  const results = restartAllProjects(npmProjects);
1199
1392
  for (const result2 of results) {
1200
1393
  if (result2.success) {
1201
- console.log(chalk6.green(` \u2713 ${result2.name}`));
1394
+ console.log(chalk7.green(` \u2713 ${result2.name}`));
1202
1395
  } else {
1203
- console.log(chalk6.red(` \u2717 ${result2.name}: ${result2.error}`));
1396
+ console.log(chalk7.red(` \u2717 ${result2.name}: ${result2.error}`));
1204
1397
  }
1205
1398
  }
1206
1399
  const succeeded = results.filter((r) => r.success).length;
1207
- console.log(chalk6.dim(`
1400
+ console.log(chalk7.dim(`
1208
1401
  ${succeeded}/${results.length} restarted successfully`));
1209
1402
  return;
1210
1403
  }
1211
1404
  if (!name) {
1212
- console.error(chalk6.red("Error: Project name is required. Use --all to restart all projects."));
1405
+ console.error(chalk7.red("Error: Project name is required. Use --all to restart all projects."));
1213
1406
  process2.exit(1);
1214
1407
  }
1215
1408
  const project = getProject(name);
1216
1409
  if (!project) {
1217
- console.error(chalk6.red(`Error: Project "${name}" not found`));
1410
+ console.error(chalk7.red(`Error: Project "${name}" not found`));
1218
1411
  process2.exit(1);
1219
1412
  }
1220
1413
  if (project.type !== "npm") {
1221
- console.log(chalk6.yellow(`Project "${name}" is a static site - no process to restart.`));
1414
+ console.log(chalk7.yellow(`Project "${name}" is a static site - no process to restart.`));
1222
1415
  return;
1223
1416
  }
1224
- console.log(chalk6.blue(`Restarting ${name}...`));
1417
+ console.log(chalk7.blue(`Restarting ${name}...`));
1225
1418
  const process2 = getProcessByName(name);
1226
1419
  let result;
1227
1420
  if (process2) {
1228
1421
  result = restartProject(name);
1229
1422
  } else {
1230
- console.log(chalk6.dim("Process not running, starting..."));
1423
+ console.log(chalk7.dim("Process not running, starting..."));
1231
1424
  result = startProject(project);
1232
1425
  }
1233
1426
  if (result.success) {
1234
- console.log(chalk6.green(`\u2713 ${name} restarted successfully`));
1427
+ console.log(chalk7.green(`\u2713 ${name} restarted successfully`));
1235
1428
  } else {
1236
- console.error(chalk6.red(`\u2717 Failed to restart ${name}: ${result.error}`));
1429
+ console.error(chalk7.red(`\u2717 Failed to restart ${name}: ${result.error}`));
1237
1430
  process2.exit(1);
1238
1431
  }
1239
1432
  }
1240
1433
 
1241
1434
  // src/commands/logs.ts
1242
- import chalk7 from "chalk";
1435
+ import chalk8 from "chalk";
1243
1436
  async function logsCommand(name, options) {
1244
1437
  const project = getProject(name);
1245
1438
  if (!project) {
1246
- console.error(chalk7.red(`Error: Project "${name}" not found`));
1439
+ console.error(chalk8.red(`Error: Project "${name}" not found`));
1247
1440
  process2.exit(1);
1248
1441
  }
1249
1442
  if (project.type !== "npm") {
1250
- console.log(chalk7.yellow(`Project "${name}" is a static site - no logs available.`));
1251
- console.log(chalk7.dim("Check nginx access/error logs instead:"));
1252
- console.log(chalk7.dim(" /var/log/nginx/access.log"));
1253
- console.log(chalk7.dim(" /var/log/nginx/error.log"));
1443
+ console.log(chalk8.yellow(`Project "${name}" is a static site - no logs available.`));
1444
+ console.log(chalk8.dim("Check nginx access/error logs instead:"));
1445
+ console.log(chalk8.dim(" /var/log/nginx/access.log"));
1446
+ console.log(chalk8.dim(" /var/log/nginx/error.log"));
1254
1447
  return;
1255
1448
  }
1256
1449
  const process2 = getProcessByName(name);
1257
1450
  if (!process2) {
1258
- console.log(chalk7.yellow(`Project "${name}" has not been started yet.`));
1259
- console.log(chalk7.dim(`Run ${chalk7.cyan(`bindler start ${name}`)} first.`));
1451
+ console.log(chalk8.yellow(`Project "${name}" has not been started yet.`));
1452
+ console.log(chalk8.dim(`Run ${chalk8.cyan(`bindler start ${name}`)} first.`));
1260
1453
  return;
1261
1454
  }
1262
1455
  const lines = options.lines || 200;
1263
1456
  const follow = options.follow || false;
1264
1457
  if (follow) {
1265
- console.log(chalk7.dim(`Following logs for ${name}... (Ctrl+C to exit)`));
1458
+ console.log(chalk8.dim(`Following logs for ${name}... (Ctrl+C to exit)`));
1266
1459
  }
1267
1460
  await showLogs(name, follow, lines);
1268
1461
  }
1269
1462
 
1270
1463
  // src/commands/update.ts
1271
- import chalk8 from "chalk";
1464
+ import chalk9 from "chalk";
1465
+ import { existsSync as existsSync7 } from "fs";
1272
1466
  async function updateCommand(name, options) {
1273
1467
  const project = getProject(name);
1274
1468
  if (!project) {
1275
- console.error(chalk8.red(`Error: Project "${name}" not found`));
1469
+ console.error(chalk9.red(`Error: Project "${name}" not found`));
1276
1470
  process.exit(1);
1277
1471
  }
1278
1472
  const updates = {};
1279
1473
  if (options.hostname) {
1280
1474
  if (!validateHostname(options.hostname)) {
1281
- console.error(chalk8.red("Error: Invalid hostname format"));
1475
+ console.error(chalk9.red("Error: Invalid hostname format"));
1282
1476
  process.exit(1);
1283
1477
  }
1284
1478
  updates.hostname = options.hostname;
@@ -1286,11 +1480,11 @@ async function updateCommand(name, options) {
1286
1480
  if (options.port) {
1287
1481
  const port = parseInt(options.port, 10);
1288
1482
  if (!validatePort(port)) {
1289
- console.error(chalk8.red("Error: Invalid port. Use a number between 1024 and 65535."));
1483
+ console.error(chalk9.red("Error: Invalid port. Use a number between 1024 and 65535."));
1290
1484
  process.exit(1);
1291
1485
  }
1292
1486
  if (!isPortAvailable(port) && port !== project.port) {
1293
- console.error(chalk8.red(`Error: Port ${port} is already in use by another project.`));
1487
+ console.error(chalk9.red(`Error: Port ${port} is already in use by another project.`));
1294
1488
  process.exit(1);
1295
1489
  }
1296
1490
  updates.port = port;
@@ -1300,12 +1494,22 @@ async function updateCommand(name, options) {
1300
1494
  }
1301
1495
  if (options.start) {
1302
1496
  if (project.type !== "npm") {
1303
- console.error(chalk8.red("Error: Start command only applies to npm projects"));
1497
+ console.error(chalk9.red("Error: Start command only applies to npm projects"));
1304
1498
  process.exit(1);
1305
1499
  }
1306
1500
  updates.start = options.start;
1307
1501
  }
1308
1502
  if (options.path) {
1503
+ if (!existsSync7(options.path)) {
1504
+ console.error(chalk9.red(`Error: Path does not exist: ${options.path}`));
1505
+ process.exit(1);
1506
+ }
1507
+ if (isProtectedPath(options.path)) {
1508
+ console.error(chalk9.red(`Error: Path is in a macOS protected folder (Desktop/Documents/Downloads).`));
1509
+ console.error(chalk9.yellow(`Nginx cannot access these folders without Full Disk Access.`));
1510
+ console.error(chalk9.dim(`Move your project to ~/projects or another accessible location.`));
1511
+ process.exit(1);
1512
+ }
1309
1513
  updates.path = options.path;
1310
1514
  }
1311
1515
  if (options.env && options.env.length > 0) {
@@ -1313,7 +1517,7 @@ async function updateCommand(name, options) {
1313
1517
  for (const envStr of options.env) {
1314
1518
  const [key, ...valueParts] = envStr.split("=");
1315
1519
  if (!key) {
1316
- console.error(chalk8.red(`Error: Invalid env format: ${envStr}. Use KEY=value`));
1520
+ console.error(chalk9.red(`Error: Invalid env format: ${envStr}. Use KEY=value`));
1317
1521
  process.exit(1);
1318
1522
  }
1319
1523
  env[key] = valueParts.join("=");
@@ -1326,23 +1530,23 @@ async function updateCommand(name, options) {
1326
1530
  updates.enabled = false;
1327
1531
  }
1328
1532
  if (Object.keys(updates).length === 0) {
1329
- console.log(chalk8.yellow("No updates specified."));
1330
- console.log(chalk8.dim("Available options: --hostname, --port, --start, --path, --env, --enable, --disable"));
1533
+ console.log(chalk9.yellow("No updates specified."));
1534
+ console.log(chalk9.dim("Available options: --hostname, --port, --start, --path, --env, --enable, --disable"));
1331
1535
  return;
1332
1536
  }
1333
1537
  try {
1334
1538
  updateProject(name, updates);
1335
- console.log(chalk8.green(`\u2713 Project "${name}" updated successfully`));
1539
+ console.log(chalk9.green(`\u2713 Project "${name}" updated successfully`));
1336
1540
  for (const [key, value] of Object.entries(updates)) {
1337
- console.log(chalk8.dim(` ${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`));
1541
+ console.log(chalk9.dim(` ${key}: ${typeof value === "object" ? JSON.stringify(value) : value}`));
1338
1542
  }
1339
- console.log(chalk8.dim(`
1340
- Run ${chalk8.cyan("sudo bindler apply")} to apply changes to nginx.`));
1543
+ console.log(chalk9.dim(`
1544
+ Run ${chalk9.cyan("sudo bindler apply")} to apply changes to nginx.`));
1341
1545
  if (project.type === "npm" && (updates.port || updates.start || updates.env)) {
1342
- console.log(chalk8.dim(`Run ${chalk8.cyan(`bindler restart ${name}`)} to apply changes to the running process.`));
1546
+ console.log(chalk9.dim(`Run ${chalk9.cyan(`bindler restart ${name}`)} to apply changes to the running process.`));
1343
1547
  }
1344
1548
  } catch (error) {
1345
- console.error(chalk8.red(`Error: ${error instanceof Error ? error.message : error}`));
1549
+ console.error(chalk9.red(`Error: ${error instanceof Error ? error.message : error}`));
1346
1550
  process.exit(1);
1347
1551
  }
1348
1552
  }
@@ -1350,21 +1554,21 @@ Run ${chalk8.cyan("sudo bindler apply")} to apply changes to nginx.`));
1350
1554
  // src/commands/edit.ts
1351
1555
  import { writeFileSync as writeFileSync4, readFileSync as readFileSync4, unlinkSync } from "fs";
1352
1556
  import { tmpdir } from "os";
1353
- import { join as join5 } from "path";
1354
- import chalk9 from "chalk";
1557
+ import { join as join6 } from "path";
1558
+ import chalk10 from "chalk";
1355
1559
  async function editCommand(name) {
1356
1560
  const project = getProject(name);
1357
1561
  if (!project) {
1358
- console.error(chalk9.red(`Error: Project "${name}" not found`));
1562
+ console.error(chalk10.red(`Error: Project "${name}" not found`));
1359
1563
  process.exit(1);
1360
1564
  }
1361
1565
  const editor = process.env.EDITOR || process.env.VISUAL || "vi";
1362
- const tmpFile = join5(tmpdir(), `bindler-${name}-${Date.now()}.json`);
1566
+ const tmpFile = join6(tmpdir(), `bindler-${name}-${Date.now()}.json`);
1363
1567
  writeFileSync4(tmpFile, JSON.stringify(project, null, 2) + "\n");
1364
- console.log(chalk9.dim(`Opening ${name} config in ${editor}...`));
1568
+ console.log(chalk10.dim(`Opening ${name} config in ${editor}...`));
1365
1569
  const exitCode = await spawnInteractive(editor, [tmpFile]);
1366
1570
  if (exitCode !== 0) {
1367
- console.error(chalk9.red("Editor exited with error"));
1571
+ console.error(chalk10.red("Editor exited with error"));
1368
1572
  unlinkSync(tmpFile);
1369
1573
  process.exit(1);
1370
1574
  }
@@ -1372,7 +1576,7 @@ async function editCommand(name) {
1372
1576
  try {
1373
1577
  editedContent = readFileSync4(tmpFile, "utf-8");
1374
1578
  } catch (error) {
1375
- console.error(chalk9.red("Failed to read edited file"));
1579
+ console.error(chalk10.red("Failed to read edited file"));
1376
1580
  process.exit(1);
1377
1581
  } finally {
1378
1582
  unlinkSync(tmpFile);
@@ -1381,44 +1585,44 @@ async function editCommand(name) {
1381
1585
  try {
1382
1586
  editedProject = JSON.parse(editedContent);
1383
1587
  } catch (error) {
1384
- console.error(chalk9.red("Error: Invalid JSON in edited file"));
1588
+ console.error(chalk10.red("Error: Invalid JSON in edited file"));
1385
1589
  process.exit(1);
1386
1590
  }
1387
1591
  if (editedProject.name !== project.name) {
1388
- console.error(chalk9.red("Error: Cannot change project name via edit. Use a new project instead."));
1592
+ console.error(chalk10.red("Error: Cannot change project name via edit. Use a new project instead."));
1389
1593
  process.exit(1);
1390
1594
  }
1391
1595
  const originalStr = JSON.stringify(project);
1392
1596
  const editedStr = JSON.stringify(editedProject);
1393
1597
  if (originalStr === editedStr) {
1394
- console.log(chalk9.yellow("No changes made."));
1598
+ console.log(chalk10.yellow("No changes made."));
1395
1599
  return;
1396
1600
  }
1397
1601
  try {
1398
1602
  const config = readConfig();
1399
1603
  const index = config.projects.findIndex((p) => p.name === name);
1400
1604
  if (index === -1) {
1401
- console.error(chalk9.red("Error: Project not found"));
1605
+ console.error(chalk10.red("Error: Project not found"));
1402
1606
  process.exit(1);
1403
1607
  }
1404
1608
  config.projects[index] = editedProject;
1405
1609
  writeConfig(config);
1406
- console.log(chalk9.green(`\u2713 Project "${name}" updated successfully`));
1407
- console.log(chalk9.dim(`
1408
- Run ${chalk9.cyan("sudo bindler apply")} to apply changes to nginx.`));
1610
+ console.log(chalk10.green(`\u2713 Project "${name}" updated successfully`));
1611
+ console.log(chalk10.dim(`
1612
+ Run ${chalk10.cyan("sudo bindler apply")} to apply changes to nginx.`));
1409
1613
  } catch (error) {
1410
- console.error(chalk9.red(`Error: ${error instanceof Error ? error.message : error}`));
1614
+ console.error(chalk10.red(`Error: ${error instanceof Error ? error.message : error}`));
1411
1615
  process.exit(1);
1412
1616
  }
1413
1617
  }
1414
1618
 
1415
1619
  // src/commands/remove.ts
1416
1620
  import inquirer2 from "inquirer";
1417
- import chalk10 from "chalk";
1621
+ import chalk11 from "chalk";
1418
1622
  async function removeCommand(name, options) {
1419
1623
  const project = getProject(name);
1420
1624
  if (!project) {
1421
- console.error(chalk10.red(`Error: Project "${name}" not found`));
1625
+ console.error(chalk11.red(`Error: Project "${name}" not found`));
1422
1626
  process.exit(1);
1423
1627
  }
1424
1628
  if (!options.force) {
@@ -1431,34 +1635,56 @@ async function removeCommand(name, options) {
1431
1635
  }
1432
1636
  ]);
1433
1637
  if (!confirm) {
1434
- console.log(chalk10.yellow("Cancelled."));
1638
+ console.log(chalk11.yellow("Cancelled."));
1435
1639
  return;
1436
1640
  }
1437
1641
  }
1438
1642
  if (project.type === "npm") {
1439
1643
  const process2 = getProcessByName(name);
1440
1644
  if (process2) {
1441
- console.log(chalk10.dim("Stopping PM2 process..."));
1645
+ console.log(chalk11.dim("Stopping PM2 process..."));
1442
1646
  deleteProject(name);
1443
1647
  }
1444
1648
  }
1445
1649
  try {
1446
1650
  removeProject(name);
1447
- console.log(chalk10.green(`\u2713 Project "${name}" removed from registry`));
1448
- console.log(chalk10.dim(`
1449
- Run ${chalk10.cyan("sudo bindler apply")} to update nginx configuration.`));
1450
- console.log(chalk10.yellow("\nNote: The project files and Cloudflare DNS routes were not removed."));
1451
- console.log(chalk10.dim(` Project path: ${project.path}`));
1452
- console.log(chalk10.dim(` To remove DNS route manually: cloudflared tunnel route dns --remove ${project.hostname}`));
1651
+ console.log(chalk11.green(`\u2713 Project "${name}" removed from registry`));
1652
+ if (options.apply) {
1653
+ console.log(chalk11.dim("\nApplying nginx configuration..."));
1654
+ const config = readConfig();
1655
+ try {
1656
+ writeNginxConfig(config);
1657
+ const testResult = testNginxConfig();
1658
+ if (testResult.success) {
1659
+ reloadNginx();
1660
+ console.log(chalk11.green("\u2713 Nginx configuration updated"));
1661
+ } else {
1662
+ console.log(chalk11.yellow("! Nginx config test failed, reload skipped"));
1663
+ }
1664
+ } catch (err) {
1665
+ console.log(chalk11.yellow(`! Failed to update nginx: ${err}`));
1666
+ console.log(chalk11.dim(" Try running: sudo bindler apply"));
1667
+ }
1668
+ } else {
1669
+ console.log(chalk11.dim(`
1670
+ Run ${chalk11.cyan("sudo bindler apply")} to update nginx configuration.`));
1671
+ }
1672
+ console.log(chalk11.yellow("\nNote: Project files were not deleted."));
1673
+ console.log(chalk11.dim(` Path: ${project.path}`));
1674
+ if (!project.local) {
1675
+ console.log(chalk11.yellow("\nCloudflare DNS route was not removed."));
1676
+ console.log(chalk11.dim(" Remove it manually from the Cloudflare dashboard:"));
1677
+ console.log(chalk11.dim(" https://dash.cloudflare.com \u2192 DNS \u2192 Records \u2192 Delete the CNAME for " + project.hostname));
1678
+ }
1453
1679
  } catch (error) {
1454
- console.error(chalk10.red(`Error: ${error instanceof Error ? error.message : error}`));
1680
+ console.error(chalk11.red(`Error: ${error instanceof Error ? error.message : error}`));
1455
1681
  process.exit(1);
1456
1682
  }
1457
1683
  }
1458
1684
 
1459
1685
  // src/commands/apply.ts
1460
- import chalk11 from "chalk";
1461
- import { existsSync as existsSync6 } from "fs";
1686
+ import chalk12 from "chalk";
1687
+ import { existsSync as existsSync8 } from "fs";
1462
1688
 
1463
1689
  // src/lib/cloudflare.ts
1464
1690
  function isCloudflaredInstalled() {
@@ -1553,56 +1779,69 @@ async function applyCommand(options) {
1553
1779
  let config = readConfig();
1554
1780
  const defaults = getDefaults();
1555
1781
  if (options.sync) {
1556
- console.log(chalk11.dim("Syncing bindler.yaml from project directories...\n"));
1782
+ console.log(chalk12.dim("Syncing bindler.yaml from project directories...\n"));
1557
1783
  let synced = 0;
1558
1784
  for (const project of config.projects) {
1559
- if (!existsSync6(project.path)) continue;
1785
+ if (!existsSync8(project.path)) continue;
1560
1786
  const yamlConfig = readBindlerYaml(project.path);
1561
1787
  if (yamlConfig) {
1562
1788
  const merged = mergeYamlWithProject(project, yamlConfig);
1563
1789
  updateProject(project.name, merged);
1564
- console.log(chalk11.green(` \u2713 Synced ${project.name} from bindler.yaml`));
1790
+ console.log(chalk12.green(` \u2713 Synced ${project.name} from bindler.yaml`));
1565
1791
  synced++;
1566
1792
  }
1567
1793
  }
1568
1794
  if (synced === 0) {
1569
- console.log(chalk11.dim(" No bindler.yaml files found in project directories"));
1795
+ console.log(chalk12.dim(" No bindler.yaml files found in project directories"));
1570
1796
  } else {
1571
- console.log(chalk11.dim(`
1797
+ console.log(chalk12.dim(`
1572
1798
  Synced ${synced} project(s)
1573
1799
  `));
1574
1800
  }
1575
1801
  config = readConfig();
1576
1802
  }
1577
1803
  if (options.env) {
1578
- console.log(chalk11.dim(`Using ${options.env} environment configuration...
1804
+ console.log(chalk12.dim(`Using ${options.env} environment configuration...
1579
1805
  `));
1580
1806
  const envProjects = listProjectsForEnv(options.env);
1581
1807
  config = { ...config, projects: envProjects };
1582
1808
  }
1583
- if (config.projects.length === 0) {
1584
- console.log(chalk11.yellow("No projects registered. Nothing to apply."));
1585
- return;
1809
+ const hasProjects = config.projects.length > 0;
1810
+ console.log(chalk12.blue("Applying configuration...\n"));
1811
+ if (hasProjects && !options.skipChecks) {
1812
+ console.log(chalk12.dim("Running preflight checks..."));
1813
+ const checkResult = runPreflightChecks(config);
1814
+ if (!checkResult.valid) {
1815
+ printValidationResult(checkResult);
1816
+ console.log(chalk12.red("\n\u2717 Preflight checks failed. Fix the errors above before applying."));
1817
+ console.log(chalk12.dim(" Use --skip-checks to bypass (not recommended)"));
1818
+ process.exit(1);
1819
+ }
1820
+ if (checkResult.warnings.length > 0) {
1821
+ printValidationResult(checkResult);
1822
+ console.log("");
1823
+ } else {
1824
+ console.log(chalk12.green(" \u2713 Preflight checks passed"));
1825
+ }
1586
1826
  }
1587
- console.log(chalk11.blue("Applying configuration...\n"));
1588
- console.log(chalk11.dim("Generating nginx configuration..."));
1827
+ console.log(chalk12.dim("Generating nginx configuration..."));
1589
1828
  if (options.dryRun) {
1590
1829
  const nginxConfig = generateNginxConfig(config);
1591
- console.log(chalk11.cyan("\n--- Generated nginx config (dry-run) ---\n"));
1830
+ console.log(chalk12.cyan("\n--- Generated nginx config (dry-run) ---\n"));
1592
1831
  console.log(nginxConfig);
1593
- console.log(chalk11.cyan("--- End of config ---\n"));
1594
- console.log(chalk11.yellow("Dry run mode - no changes were made."));
1832
+ console.log(chalk12.cyan("--- End of config ---\n"));
1833
+ console.log(chalk12.yellow("Dry run mode - no changes were made."));
1595
1834
  return;
1596
1835
  }
1597
1836
  try {
1598
1837
  const { path, content } = writeNginxConfig(config);
1599
- console.log(chalk11.green(` \u2713 Wrote nginx config to ${path}`));
1838
+ console.log(chalk12.green(` \u2713 Wrote nginx config to ${path}`));
1600
1839
  } catch (error) {
1601
1840
  const errMsg = error instanceof Error ? error.message : String(error);
1602
- console.error(chalk11.red(` \u2717 Failed to write nginx config: ${errMsg}`));
1841
+ console.error(chalk12.red(` \u2717 Failed to write nginx config: ${errMsg}`));
1603
1842
  if (errMsg.includes("EACCES") || errMsg.includes("permission denied")) {
1604
- console.log(chalk11.yellow(`
1605
- Try running with sudo: ${chalk11.cyan("sudo bindler apply")}`));
1843
+ console.log(chalk12.yellow(`
1844
+ Try running with sudo: ${chalk12.cyan("sudo bindler apply")}`));
1606
1845
  }
1607
1846
  process.exit(1);
1608
1847
  }
@@ -1610,106 +1849,111 @@ Try running with sudo: ${chalk11.cyan("sudo bindler apply")}`));
1610
1849
  (p) => p.security?.basicAuth?.enabled && p.security.basicAuth.users?.length
1611
1850
  );
1612
1851
  if (authProjects.length > 0) {
1613
- console.log(chalk11.dim("Generating htpasswd files..."));
1852
+ console.log(chalk12.dim("Generating htpasswd files..."));
1614
1853
  try {
1615
1854
  generateHtpasswdFiles(config.projects);
1616
- console.log(chalk11.green(` \u2713 Generated htpasswd files for ${authProjects.length} project(s)`));
1855
+ console.log(chalk12.green(` \u2713 Generated htpasswd files for ${authProjects.length} project(s)`));
1617
1856
  } catch (error) {
1618
- console.log(chalk11.yellow(` ! Failed to generate htpasswd files: ${error}`));
1857
+ console.log(chalk12.yellow(` ! Failed to generate htpasswd files: ${error}`));
1619
1858
  }
1620
1859
  }
1621
- console.log(chalk11.dim("Testing nginx configuration..."));
1860
+ console.log(chalk12.dim("Testing nginx configuration..."));
1622
1861
  const testResult = testNginxConfig();
1623
1862
  if (!testResult.success) {
1624
- console.error(chalk11.red(" \u2717 Nginx configuration test failed:"));
1625
- console.error(chalk11.red(testResult.output));
1626
- console.log(chalk11.yellow("\nConfiguration was written but nginx was NOT reloaded."));
1627
- console.log(chalk11.dim("Fix the configuration and run `sudo bindler apply` again."));
1863
+ console.error(chalk12.red(" \u2717 Nginx configuration test failed:"));
1864
+ console.error(chalk12.red(testResult.output));
1865
+ console.log(chalk12.yellow("\nConfiguration was written but nginx was NOT reloaded."));
1866
+ console.log(chalk12.dim("Fix the configuration and run `sudo bindler apply` again."));
1628
1867
  process.exit(1);
1629
1868
  }
1630
- console.log(chalk11.green(" \u2713 Nginx configuration test passed"));
1869
+ console.log(chalk12.green(" \u2713 Nginx configuration test passed"));
1631
1870
  if (!options.noReload) {
1632
- console.log(chalk11.dim("Reloading nginx..."));
1871
+ console.log(chalk12.dim("Reloading nginx..."));
1633
1872
  const reloadResult = reloadNginx();
1634
1873
  if (!reloadResult.success) {
1635
- console.error(chalk11.red(` \u2717 Failed to reload nginx: ${reloadResult.error}`));
1636
- console.log(chalk11.dim("You may need to reload nginx manually: sudo systemctl reload nginx"));
1874
+ console.error(chalk12.red(` \u2717 Failed to reload nginx: ${reloadResult.error}`));
1875
+ console.log(chalk12.dim("You may need to reload nginx manually: sudo systemctl reload nginx"));
1637
1876
  process.exit(1);
1638
1877
  }
1639
- console.log(chalk11.green(" \u2713 Nginx reloaded successfully"));
1878
+ console.log(chalk12.green(" \u2713 Nginx reloaded successfully"));
1640
1879
  } else {
1641
- console.log(chalk11.yellow(" - Skipped nginx reload (--no-reload)"));
1880
+ console.log(chalk12.yellow(" - Skipped nginx reload (--no-reload)"));
1642
1881
  }
1643
1882
  const isDirectMode = defaults.mode === "direct";
1644
- if (isDirectMode) {
1645
- console.log(chalk11.dim("\n - Direct mode: skipping Cloudflare DNS routes"));
1883
+ if (!hasProjects) {
1884
+ } else if (isDirectMode) {
1885
+ console.log(chalk12.dim("\n - Direct mode: skipping Cloudflare DNS routes"));
1646
1886
  } else if (!options.noCloudflare && defaults.applyCloudflareDnsRoutes) {
1647
- console.log(chalk11.dim("\nConfiguring Cloudflare DNS routes..."));
1887
+ console.log(chalk12.dim("\nConfiguring Cloudflare DNS routes..."));
1648
1888
  if (!isCloudflaredInstalled()) {
1649
- console.log(chalk11.yellow(" - cloudflared not installed, skipping DNS routes"));
1889
+ console.log(chalk12.yellow(" - cloudflared not installed, skipping DNS routes"));
1650
1890
  } else {
1651
1891
  const dnsResults = routeDnsForAllProjects();
1652
1892
  if (dnsResults.length === 0) {
1653
- console.log(chalk11.dim(" No hostnames to route"));
1893
+ console.log(chalk12.dim(" No hostnames to route"));
1654
1894
  } else {
1655
1895
  for (const result of dnsResults) {
1656
1896
  if (result.skipped) {
1657
- console.log(chalk11.dim(` - ${result.hostname} (local - skipped)`));
1897
+ console.log(chalk12.dim(` - ${result.hostname} (local - skipped)`));
1658
1898
  } else if (result.success) {
1659
1899
  const msg = result.output?.includes("already exists") ? "exists" : "routed";
1660
- console.log(chalk11.green(` \u2713 ${result.hostname} (${msg})`));
1900
+ console.log(chalk12.green(` \u2713 ${result.hostname} (${msg})`));
1661
1901
  } else {
1662
- console.log(chalk11.red(` \u2717 ${result.hostname}: ${result.error}`));
1902
+ console.log(chalk12.red(` \u2717 ${result.hostname}: ${result.error}`));
1663
1903
  }
1664
1904
  }
1665
1905
  }
1666
1906
  }
1667
1907
  } else if (options.noCloudflare) {
1668
- console.log(chalk11.dim("\n - Skipped Cloudflare DNS routes (--no-cloudflare)"));
1908
+ console.log(chalk12.dim("\n - Skipped Cloudflare DNS routes (--no-cloudflare)"));
1669
1909
  }
1670
- if (isDirectMode && defaults.sslEnabled && options.ssl !== false) {
1671
- console.log(chalk11.dim("\nSetting up SSL certificates..."));
1910
+ if (hasProjects && isDirectMode && defaults.sslEnabled && options.ssl !== false) {
1911
+ console.log(chalk12.dim("\nSetting up SSL certificates..."));
1672
1912
  const hostnames = config.projects.filter((p) => p.enabled !== false && !p.local).map((p) => p.hostname);
1673
1913
  if (hostnames.length === 0) {
1674
- console.log(chalk11.dim(" No hostnames to secure"));
1914
+ console.log(chalk12.dim(" No hostnames to secure"));
1675
1915
  } else {
1676
1916
  const certbotResult = execCommandSafe("which certbot");
1677
1917
  if (!certbotResult.success) {
1678
- console.log(chalk11.yellow(" - certbot not installed, skipping SSL"));
1679
- console.log(chalk11.dim(" Run: bindler setup --direct"));
1918
+ console.log(chalk12.yellow(" - certbot not installed, skipping SSL"));
1919
+ console.log(chalk12.dim(" Run: bindler setup --direct"));
1680
1920
  } else {
1681
1921
  for (const hostname of hostnames) {
1682
- console.log(chalk11.dim(` Requesting certificate for ${hostname}...`));
1922
+ console.log(chalk12.dim(` Requesting certificate for ${hostname}...`));
1683
1923
  const email = defaults.sslEmail || "admin@" + hostname.split(".").slice(-2).join(".");
1684
1924
  const result = execCommandSafe(
1685
1925
  `sudo certbot --nginx -d ${hostname} --non-interactive --agree-tos --email ${email} 2>&1`
1686
1926
  );
1687
1927
  if (result.success || result.output?.includes("Certificate not yet due for renewal")) {
1688
- console.log(chalk11.green(` \u2713 ${hostname} (secured)`));
1928
+ console.log(chalk12.green(` \u2713 ${hostname} (secured)`));
1689
1929
  } else if (result.output?.includes("already exists")) {
1690
- console.log(chalk11.green(` \u2713 ${hostname} (exists)`));
1930
+ console.log(chalk12.green(` \u2713 ${hostname} (exists)`));
1691
1931
  } else {
1692
- console.log(chalk11.yellow(` ! ${hostname}: ${result.error || "failed"}`));
1693
- console.log(chalk11.dim(" Run manually: sudo certbot --nginx -d " + hostname));
1932
+ console.log(chalk12.yellow(` ! ${hostname}: ${result.error || "failed"}`));
1933
+ console.log(chalk12.dim(" Run manually: sudo certbot --nginx -d " + hostname));
1694
1934
  }
1695
1935
  }
1696
1936
  }
1697
1937
  }
1698
1938
  }
1699
- console.log(chalk11.green("\n\u2713 Configuration applied successfully!"));
1700
- console.log(chalk11.dim(`
1939
+ console.log(chalk12.green("\n\u2713 Configuration applied successfully!"));
1940
+ if (hasProjects) {
1941
+ console.log(chalk12.dim(`
1701
1942
  ${config.projects.length} project(s) configured:`));
1702
- for (const project of config.projects) {
1703
- const status = project.enabled !== false ? chalk11.green("enabled") : chalk11.yellow("disabled");
1704
- console.log(chalk11.dim(` - ${project.name} \u2192 ${project.hostname} (${status})`));
1943
+ for (const project of config.projects) {
1944
+ const status = project.enabled !== false ? chalk12.green("enabled") : chalk12.yellow("disabled");
1945
+ console.log(chalk12.dim(` - ${project.name} \u2192 ${project.hostname} (${status})`));
1946
+ }
1947
+ } else {
1948
+ console.log(chalk12.dim("\nNo projects configured. Nginx config cleared."));
1705
1949
  }
1706
1950
  }
1707
1951
 
1708
1952
  // src/commands/doctor.ts
1709
- import chalk12 from "chalk";
1710
- import { existsSync as existsSync7 } from "fs";
1953
+ import chalk13 from "chalk";
1954
+ import { existsSync as existsSync9 } from "fs";
1711
1955
  async function doctorCommand() {
1712
- console.log(chalk12.blue("Running diagnostics...\n"));
1956
+ console.log(chalk13.blue("Running diagnostics...\n"));
1713
1957
  const checks = [];
1714
1958
  const nodeVersion = process.version;
1715
1959
  const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
@@ -1845,7 +2089,7 @@ async function doctorCommand() {
1845
2089
  }
1846
2090
  }
1847
2091
  const defaults = getDefaults();
1848
- if (existsSync7(defaults.nginxManagedPath)) {
2092
+ if (existsSync9(defaults.nginxManagedPath)) {
1849
2093
  checks.push({
1850
2094
  name: "Nginx config file",
1851
2095
  status: "ok",
@@ -1859,7 +2103,7 @@ async function doctorCommand() {
1859
2103
  fix: "Run: sudo bindler apply"
1860
2104
  });
1861
2105
  }
1862
- if (existsSync7(defaults.projectsRoot)) {
2106
+ if (existsSync9(defaults.projectsRoot)) {
1863
2107
  checks.push({
1864
2108
  name: "Projects root",
1865
2109
  status: "ok",
@@ -1870,7 +2114,67 @@ async function doctorCommand() {
1870
2114
  name: "Projects root",
1871
2115
  status: "warning",
1872
2116
  message: `Not found at ${defaults.projectsRoot}`,
1873
- fix: `Create directory: sudo mkdir -p ${defaults.projectsRoot}`
2117
+ fix: `Create directory: mkdir -p ${defaults.projectsRoot}`
2118
+ });
2119
+ }
2120
+ const validation = validateConfig(config);
2121
+ if (validation.errors.length > 0) {
2122
+ for (const error of validation.errors) {
2123
+ checks.push({
2124
+ name: "Project validation",
2125
+ status: "error",
2126
+ message: error
2127
+ });
2128
+ }
2129
+ }
2130
+ if (validation.warnings.length > 0) {
2131
+ for (const warning of validation.warnings) {
2132
+ checks.push({
2133
+ name: "Project validation",
2134
+ status: "warning",
2135
+ message: warning
2136
+ });
2137
+ }
2138
+ }
2139
+ if (validation.valid && validation.warnings.length === 0) {
2140
+ checks.push({
2141
+ name: "Project validation",
2142
+ status: "ok",
2143
+ message: "All projects validated successfully"
2144
+ });
2145
+ }
2146
+ const localProjects = config.projects.filter((p) => p.local && p.enabled !== false);
2147
+ if (localProjects.length > 0) {
2148
+ const hostsResult = execCommandSafe("cat /etc/hosts");
2149
+ if (hostsResult.success) {
2150
+ const missingHosts = localProjects.filter(
2151
+ (p) => !hostsResult.output.includes(p.hostname)
2152
+ );
2153
+ if (missingHosts.length > 0) {
2154
+ for (const p of missingHosts) {
2155
+ checks.push({
2156
+ name: "Hosts file",
2157
+ status: "warning",
2158
+ message: `Missing entry for ${p.hostname}`,
2159
+ fix: `echo "127.0.0.1 ${p.hostname}" | sudo tee -a /etc/hosts`
2160
+ });
2161
+ }
2162
+ } else {
2163
+ checks.push({
2164
+ name: "Hosts file",
2165
+ status: "ok",
2166
+ message: `All ${localProjects.length} local hostname(s) configured`
2167
+ });
2168
+ }
2169
+ }
2170
+ }
2171
+ const listenPort = parseInt(defaults.nginxListen.split(":").pop() || "80", 10);
2172
+ if (listenPort <= 1024 && process.getuid && process.getuid() !== 0) {
2173
+ checks.push({
2174
+ name: "Nginx port",
2175
+ status: "warning",
2176
+ message: `Port ${listenPort} requires root privileges`,
2177
+ fix: "Run: sudo bindler apply (or change nginxListen to a port > 1024)"
1874
2178
  });
1875
2179
  }
1876
2180
  } else {
@@ -1889,47 +2193,47 @@ async function doctorCommand() {
1889
2193
  switch (check.status) {
1890
2194
  case "ok":
1891
2195
  icon = "\u2713";
1892
- color = chalk12.green;
2196
+ color = chalk13.green;
1893
2197
  break;
1894
2198
  case "warning":
1895
2199
  icon = "!";
1896
- color = chalk12.yellow;
2200
+ color = chalk13.yellow;
1897
2201
  hasWarnings = true;
1898
2202
  break;
1899
2203
  case "error":
1900
2204
  icon = "\u2717";
1901
- color = chalk12.red;
2205
+ color = chalk13.red;
1902
2206
  hasErrors = true;
1903
2207
  break;
1904
2208
  }
1905
- console.log(`${color(icon)} ${chalk12.bold(check.name)}: ${check.message}`);
2209
+ console.log(`${color(icon)} ${chalk13.bold(check.name)}: ${check.message}`);
1906
2210
  if (check.fix) {
1907
- console.log(chalk12.dim(` Fix: ${check.fix}`));
2211
+ console.log(chalk13.dim(` Fix: ${check.fix}`));
1908
2212
  }
1909
2213
  }
1910
2214
  console.log("");
1911
2215
  if (hasErrors) {
1912
- console.log(chalk12.red("Some checks failed. Please fix the issues above."));
2216
+ console.log(chalk13.red("Some checks failed. Please fix the issues above."));
1913
2217
  process.exit(1);
1914
2218
  } else if (hasWarnings) {
1915
- console.log(chalk12.yellow("Some warnings detected. Review the suggestions above."));
2219
+ console.log(chalk13.yellow("Some warnings detected. Review the suggestions above."));
1916
2220
  } else {
1917
- console.log(chalk12.green("All checks passed!"));
2221
+ console.log(chalk13.green("All checks passed!"));
1918
2222
  }
1919
2223
  }
1920
2224
 
1921
2225
  // src/commands/ports.ts
1922
- import chalk13 from "chalk";
2226
+ import chalk14 from "chalk";
1923
2227
  import Table3 from "cli-table3";
1924
2228
  async function portsCommand() {
1925
2229
  const ports = getPortsTable();
1926
2230
  if (ports.length === 0) {
1927
- console.log(chalk13.yellow("No ports allocated."));
1928
- console.log(chalk13.dim("npm projects will have ports allocated when created."));
2231
+ console.log(chalk14.yellow("No ports allocated."));
2232
+ console.log(chalk14.dim("npm projects will have ports allocated when created."));
1929
2233
  return;
1930
2234
  }
1931
2235
  const table = new Table3({
1932
- head: [chalk13.cyan("Port"), chalk13.cyan("Project"), chalk13.cyan("Hostname")],
2236
+ head: [chalk14.cyan("Port"), chalk14.cyan("Project"), chalk14.cyan("Hostname")],
1933
2237
  style: {
1934
2238
  head: [],
1935
2239
  border: []
@@ -1939,25 +2243,25 @@ async function portsCommand() {
1939
2243
  table.push([String(entry.port), entry.project, entry.hostname]);
1940
2244
  }
1941
2245
  console.log(table.toString());
1942
- console.log(chalk13.dim(`
2246
+ console.log(chalk14.dim(`
1943
2247
  ${ports.length} port(s) allocated`));
1944
2248
  }
1945
2249
 
1946
2250
  // src/commands/info.ts
1947
- import chalk14 from "chalk";
2251
+ import chalk15 from "chalk";
1948
2252
  async function infoCommand() {
1949
- console.log(chalk14.bold.cyan(String.raw`
2253
+ console.log(chalk15.bold.cyan(String.raw`
1950
2254
  _ _ _ _
1951
2255
  | |_|_|___ _| | |___ ___
1952
2256
  | . | | | . | | -_| _|
1953
2257
  |___|_|_|_|___|_|___|_|
1954
2258
  `));
1955
- console.log(chalk14.white(" Manage multiple projects behind Cloudflare Tunnel"));
1956
- console.log(chalk14.white(" with Nginx and PM2\n"));
1957
- console.log(chalk14.dim(" Version: ") + chalk14.white("1.2.0"));
1958
- console.log(chalk14.dim(" Author: ") + chalk14.white("alfaoz"));
1959
- console.log(chalk14.dim(" License: ") + chalk14.white("MIT"));
1960
- console.log(chalk14.dim(" GitHub: ") + chalk14.cyan("https://github.com/alfaoz/bindler"));
2259
+ console.log(chalk15.white(" Manage multiple projects behind Cloudflare Tunnel"));
2260
+ console.log(chalk15.white(" with Nginx and PM2\n"));
2261
+ console.log(chalk15.dim(" Version: ") + chalk15.white("1.4.1"));
2262
+ console.log(chalk15.dim(" Author: ") + chalk15.white("alfaoz"));
2263
+ console.log(chalk15.dim(" License: ") + chalk15.white("MIT"));
2264
+ console.log(chalk15.dim(" GitHub: ") + chalk15.cyan("https://github.com/alfaoz/bindler"));
1961
2265
  console.log("");
1962
2266
  if (configExists()) {
1963
2267
  const config = readConfig();
@@ -1965,23 +2269,23 @@ async function infoCommand() {
1965
2269
  const runningCount = pm2Processes.filter((p) => p.status === "online").length;
1966
2270
  const npmProjects = config.projects.filter((p) => p.type === "npm").length;
1967
2271
  const staticProjects = config.projects.filter((p) => p.type === "static").length;
1968
- console.log(chalk14.dim(" Config: ") + chalk14.white(getConfigPath()));
1969
- console.log(chalk14.dim(" Nginx: ") + chalk14.white(config.defaults.nginxManagedPath));
1970
- console.log(chalk14.dim(" Tunnel: ") + chalk14.white(config.defaults.tunnelName));
2272
+ console.log(chalk15.dim(" Config: ") + chalk15.white(getConfigPath()));
2273
+ console.log(chalk15.dim(" Nginx: ") + chalk15.white(config.defaults.nginxManagedPath));
2274
+ console.log(chalk15.dim(" Tunnel: ") + chalk15.white(config.defaults.tunnelName));
1971
2275
  console.log("");
1972
- console.log(chalk14.dim(" Projects: ") + chalk14.white(`${config.projects.length} total (${staticProjects} static, ${npmProjects} npm)`));
2276
+ console.log(chalk15.dim(" Projects: ") + chalk15.white(`${config.projects.length} total (${staticProjects} static, ${npmProjects} npm)`));
1973
2277
  if (npmProjects > 0) {
1974
- console.log(chalk14.dim(" Running: ") + chalk14.green(`${runningCount}/${npmProjects} npm apps online`));
2278
+ console.log(chalk15.dim(" Running: ") + chalk15.green(`${runningCount}/${npmProjects} npm apps online`));
1975
2279
  }
1976
2280
  } else {
1977
- console.log(chalk14.dim(" Config: ") + chalk14.yellow("Not initialized"));
1978
- console.log(chalk14.dim(" ") + chalk14.dim("Run `bindler new` to get started"));
2281
+ console.log(chalk15.dim(" Config: ") + chalk15.yellow("Not initialized"));
2282
+ console.log(chalk15.dim(" ") + chalk15.dim("Run `bindler new` to get started"));
1979
2283
  }
1980
2284
  console.log("");
1981
2285
  }
1982
2286
 
1983
2287
  // src/commands/check.ts
1984
- import chalk15 from "chalk";
2288
+ import chalk16 from "chalk";
1985
2289
  import { resolve4, resolve6, resolveCname } from "dns/promises";
1986
2290
  async function checkDns(hostname) {
1987
2291
  const result = {
@@ -2042,111 +2346,111 @@ async function checkCommand(hostnameOrName, options) {
2042
2346
  const hostname = project ? project.hostname : hostnameOrName;
2043
2347
  const basePath = project?.basePath || "/";
2044
2348
  const isLocal = project?.local || hostname.endsWith(".local") || hostname.endsWith(".localhost");
2045
- console.log(chalk15.blue(`
2349
+ console.log(chalk16.blue(`
2046
2350
  Checking ${hostname}...
2047
2351
  `));
2048
- console.log(chalk15.bold("DNS Resolution:"));
2352
+ console.log(chalk16.bold("DNS Resolution:"));
2049
2353
  const dns = await checkDns(hostname);
2050
2354
  if (!dns.resolved) {
2051
- console.log(chalk15.red(" \u2717 DNS not resolving"));
2355
+ console.log(chalk16.red(" \u2717 DNS not resolving"));
2052
2356
  if (isLocal) {
2053
- console.log(chalk15.dim(" Local hostname - add to /etc/hosts:"));
2054
- console.log(chalk15.cyan(` echo "127.0.0.1 ${hostname}" | sudo tee -a /etc/hosts`));
2357
+ console.log(chalk16.dim(" Local hostname - add to /etc/hosts:"));
2358
+ console.log(chalk16.cyan(` echo "127.0.0.1 ${hostname}" | sudo tee -a /etc/hosts`));
2055
2359
  } else {
2056
- console.log(chalk15.dim(" No DNS records found for this hostname."));
2057
- console.log(chalk15.dim(" If using Cloudflare Tunnel, run: bindler apply"));
2058
- console.log(chalk15.dim(" If using direct mode, add an A record pointing to your server IP."));
2360
+ console.log(chalk16.dim(" No DNS records found for this hostname."));
2361
+ console.log(chalk16.dim(" If using Cloudflare Tunnel, run: bindler apply"));
2362
+ console.log(chalk16.dim(" If using direct mode, add an A record pointing to your server IP."));
2059
2363
  }
2060
2364
  console.log("");
2061
2365
  return;
2062
2366
  }
2063
2367
  if (dns.cname.length > 0) {
2064
- console.log(chalk15.green(" \u2713 CNAME: ") + dns.cname.join(", "));
2368
+ console.log(chalk16.green(" \u2713 CNAME: ") + dns.cname.join(", "));
2065
2369
  if (dns.isCloudflare) {
2066
- console.log(chalk15.green(" \u2713 Points to Cloudflare Tunnel"));
2370
+ console.log(chalk16.green(" \u2713 Points to Cloudflare Tunnel"));
2067
2371
  }
2068
2372
  }
2069
2373
  if (dns.ipv4.length > 0) {
2070
- console.log(chalk15.green(" \u2713 A (IPv4): ") + dns.ipv4.join(", "));
2374
+ console.log(chalk16.green(" \u2713 A (IPv4): ") + dns.ipv4.join(", "));
2071
2375
  }
2072
2376
  if (dns.ipv6.length > 0) {
2073
- console.log(chalk15.green(" \u2713 AAAA (IPv6): ") + dns.ipv6.join(", "));
2377
+ console.log(chalk16.green(" \u2713 AAAA (IPv6): ") + dns.ipv6.join(", "));
2074
2378
  }
2075
2379
  console.log("");
2076
- console.log(chalk15.bold("HTTP Check:"));
2380
+ console.log(chalk16.bold("HTTP Check:"));
2077
2381
  const http = await checkHttp(hostname, basePath);
2078
2382
  if (!http.reachable) {
2079
- console.log(chalk15.red(" \u2717 Not reachable"));
2383
+ console.log(chalk16.red(" \u2717 Not reachable"));
2080
2384
  const err = http.error || "";
2081
2385
  if (err.includes("ECONNREFUSED")) {
2082
- console.log(chalk15.dim(" Connection refused - server not accepting connections"));
2083
- console.log(chalk15.yellow("\n Check:"));
2084
- console.log(chalk15.dim(" - Is nginx running? Run: bindler doctor"));
2386
+ console.log(chalk16.dim(" Connection refused - server not accepting connections"));
2387
+ console.log(chalk16.yellow("\n Check:"));
2388
+ console.log(chalk16.dim(" - Is nginx running? Run: bindler doctor"));
2085
2389
  if (project?.type === "npm") {
2086
- console.log(chalk15.dim(` - Is the app running? Run: bindler start ${project.name}`));
2390
+ console.log(chalk16.dim(` - Is the app running? Run: bindler start ${project.name}`));
2087
2391
  }
2088
2392
  } else if (err.includes("ENOTFOUND")) {
2089
- console.log(chalk15.dim(" Hostname not found"));
2090
- console.log(chalk15.yellow("\n Check:"));
2091
- console.log(chalk15.dim(" - DNS may not be propagated yet (wait a few minutes)"));
2092
- console.log(chalk15.dim(" - Verify DNS records are correct"));
2393
+ console.log(chalk16.dim(" Hostname not found"));
2394
+ console.log(chalk16.yellow("\n Check:"));
2395
+ console.log(chalk16.dim(" - DNS may not be propagated yet (wait a few minutes)"));
2396
+ console.log(chalk16.dim(" - Verify DNS records are correct"));
2093
2397
  } else if (err.includes("ETIMEDOUT") || err.includes("timeout")) {
2094
- console.log(chalk15.dim(" Connection timed out"));
2095
- console.log(chalk15.yellow("\n Check:"));
2398
+ console.log(chalk16.dim(" Connection timed out"));
2399
+ console.log(chalk16.yellow("\n Check:"));
2096
2400
  if (isLocal) {
2097
- console.log(chalk15.dim(" - Is nginx running on port 8080?"));
2401
+ console.log(chalk16.dim(" - Is nginx running on port 8080?"));
2098
2402
  } else {
2099
- console.log(chalk15.dim(" - Is your server/tunnel reachable from the internet?"));
2100
- console.log(chalk15.dim(" - Check firewall rules"));
2403
+ console.log(chalk16.dim(" - Is your server/tunnel reachable from the internet?"));
2404
+ console.log(chalk16.dim(" - Check firewall rules"));
2101
2405
  }
2102
2406
  } else if (err.includes("CERT") || err.includes("SSL") || err.includes("certificate")) {
2103
- console.log(chalk15.dim(" SSL/TLS certificate error"));
2104
- console.log(chalk15.yellow("\n Check:"));
2105
- console.log(chalk15.dim(" - Run: sudo bindler apply (to refresh SSL certs)"));
2106
- console.log(chalk15.dim(" - Or check certbot: sudo certbot certificates"));
2407
+ console.log(chalk16.dim(" SSL/TLS certificate error"));
2408
+ console.log(chalk16.yellow("\n Check:"));
2409
+ console.log(chalk16.dim(" - Run: sudo bindler apply (to refresh SSL certs)"));
2410
+ console.log(chalk16.dim(" - Or check certbot: sudo certbot certificates"));
2107
2411
  } else {
2108
- console.log(chalk15.dim(` Error: ${err}`));
2109
- console.log(chalk15.yellow("\n Possible issues:"));
2412
+ console.log(chalk16.dim(` Error: ${err}`));
2413
+ console.log(chalk16.yellow("\n Possible issues:"));
2110
2414
  if (!isLocal) {
2111
- console.log(chalk15.dim(" - Cloudflare tunnel not running"));
2415
+ console.log(chalk16.dim(" - Cloudflare tunnel not running"));
2112
2416
  }
2113
- console.log(chalk15.dim(" - Nginx not running or misconfigured"));
2417
+ console.log(chalk16.dim(" - Nginx not running or misconfigured"));
2114
2418
  if (project?.type === "npm") {
2115
- console.log(chalk15.dim(" - Project not started"));
2419
+ console.log(chalk16.dim(" - Project not started"));
2116
2420
  }
2117
2421
  }
2118
2422
  console.log("");
2119
2423
  return;
2120
2424
  }
2121
- const statusColor = http.statusCode < 400 ? chalk15.green : chalk15.red;
2425
+ const statusColor = http.statusCode < 400 ? chalk16.green : chalk16.red;
2122
2426
  console.log(statusColor(` \u2713 Status: ${http.statusCode}`));
2123
- console.log(chalk15.dim(` Response time: ${http.responseTime}ms`));
2427
+ console.log(chalk16.dim(` Response time: ${http.responseTime}ms`));
2124
2428
  if (http.redirectUrl) {
2125
- console.log(chalk15.dim(` Redirects to: ${http.redirectUrl}`));
2429
+ console.log(chalk16.dim(` Redirects to: ${http.redirectUrl}`));
2126
2430
  }
2127
2431
  console.log("");
2128
2432
  if (dns.resolved && http.reachable && http.statusCode < 400) {
2129
- console.log(chalk15.green("\u2713 All checks passed! Site is accessible."));
2433
+ console.log(chalk16.green("\u2713 All checks passed! Site is accessible."));
2130
2434
  } else if (dns.resolved && http.reachable) {
2131
- console.log(chalk15.yellow("! Site is reachable but returned an error status."));
2435
+ console.log(chalk16.yellow("! Site is reachable but returned an error status."));
2132
2436
  } else {
2133
- console.log(chalk15.red("\u2717 Some checks failed. See details above."));
2437
+ console.log(chalk16.red("\u2717 Some checks failed. See details above."));
2134
2438
  }
2135
2439
  if (project) {
2136
- console.log(chalk15.dim(`
2440
+ console.log(chalk16.dim(`
2137
2441
  Project: ${project.name} (${project.type})`));
2138
2442
  if (project.type === "npm") {
2139
- console.log(chalk15.dim(`Port: ${project.port}`));
2443
+ console.log(chalk16.dim(`Port: ${project.port}`));
2140
2444
  }
2141
2445
  }
2142
2446
  console.log("");
2143
2447
  }
2144
2448
 
2145
2449
  // src/commands/setup.ts
2146
- import chalk16 from "chalk";
2450
+ import chalk17 from "chalk";
2147
2451
  import inquirer3 from "inquirer";
2148
2452
  import { execSync as execSync2 } from "child_process";
2149
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
2453
+ import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
2150
2454
  function detectOs() {
2151
2455
  const platform = process.platform;
2152
2456
  if (platform === "darwin") {
@@ -2157,7 +2461,7 @@ function detectOs() {
2157
2461
  }
2158
2462
  if (platform === "linux") {
2159
2463
  try {
2160
- if (existsSync8("/etc/os-release")) {
2464
+ if (existsSync10("/etc/os-release")) {
2161
2465
  const osRelease = readFileSync5("/etc/os-release", "utf-8");
2162
2466
  const lines = osRelease.split("\n");
2163
2467
  const info = {};
@@ -2174,7 +2478,7 @@ function detectOs() {
2174
2478
  if (["ubuntu", "debian", "pop", "mint", "elementary"].includes(distro)) {
2175
2479
  packageManager = "apt";
2176
2480
  } else if (["fedora", "rhel", "centos", "rocky", "alma"].includes(distro)) {
2177
- packageManager = existsSync8("/usr/bin/dnf") ? "dnf" : "yum";
2481
+ packageManager = existsSync10("/usr/bin/dnf") ? "dnf" : "yum";
2178
2482
  } else if (["amzn"].includes(distro)) {
2179
2483
  packageManager = "yum";
2180
2484
  }
@@ -2187,7 +2491,7 @@ function detectOs() {
2187
2491
  return { platform: "unknown" };
2188
2492
  }
2189
2493
  function runCommand(command, description) {
2190
- console.log(chalk16.dim(` \u2192 ${description}...`));
2494
+ console.log(chalk17.dim(` \u2192 ${description}...`));
2191
2495
  try {
2192
2496
  execSync2(command, { stdio: "inherit" });
2193
2497
  return true;
@@ -2196,7 +2500,7 @@ function runCommand(command, description) {
2196
2500
  }
2197
2501
  }
2198
2502
  async function installNginx(os) {
2199
- console.log(chalk16.blue("\nInstalling nginx...\n"));
2503
+ console.log(chalk17.blue("\nInstalling nginx...\n"));
2200
2504
  if (os.platform === "darwin") {
2201
2505
  return runCommand("brew install nginx", "Installing via Homebrew");
2202
2506
  }
@@ -2207,16 +2511,16 @@ async function installNginx(os) {
2207
2511
  if (os.platform === "linux" && (os.packageManager === "yum" || os.packageManager === "dnf")) {
2208
2512
  return runCommand(`sudo ${os.packageManager} install -y nginx`, "Installing nginx");
2209
2513
  }
2210
- console.log(chalk16.yellow(" Automatic installation not supported for your OS."));
2211
- console.log(chalk16.dim(" Please install nginx manually."));
2514
+ console.log(chalk17.yellow(" Automatic installation not supported for your OS."));
2515
+ console.log(chalk17.dim(" Please install nginx manually."));
2212
2516
  return false;
2213
2517
  }
2214
2518
  async function installPm2() {
2215
- console.log(chalk16.blue("\nInstalling PM2...\n"));
2519
+ console.log(chalk17.blue("\nInstalling PM2...\n"));
2216
2520
  return runCommand("npm install -g pm2", "Installing via npm");
2217
2521
  }
2218
2522
  async function installCertbot(os) {
2219
- console.log(chalk16.blue("\nInstalling certbot (Let's Encrypt)...\n"));
2523
+ console.log(chalk17.blue("\nInstalling certbot (Let's Encrypt)...\n"));
2220
2524
  if (os.platform === "darwin") {
2221
2525
  return runCommand("brew install certbot", "Installing via Homebrew");
2222
2526
  }
@@ -2227,8 +2531,8 @@ async function installCertbot(os) {
2227
2531
  if (os.platform === "linux" && (os.packageManager === "yum" || os.packageManager === "dnf")) {
2228
2532
  return runCommand(`sudo ${os.packageManager} install -y certbot python3-certbot-nginx`, "Installing certbot");
2229
2533
  }
2230
- console.log(chalk16.yellow(" Automatic installation not supported for your OS."));
2231
- console.log(chalk16.dim(" Please install certbot manually."));
2534
+ console.log(chalk17.yellow(" Automatic installation not supported for your OS."));
2535
+ console.log(chalk17.dim(" Please install certbot manually."));
2232
2536
  return false;
2233
2537
  }
2234
2538
  function isCertbotInstalled() {
@@ -2236,7 +2540,7 @@ function isCertbotInstalled() {
2236
2540
  return result.success;
2237
2541
  }
2238
2542
  async function installCloudflared(os) {
2239
- console.log(chalk16.blue("\nInstalling cloudflared...\n"));
2543
+ console.log(chalk17.blue("\nInstalling cloudflared...\n"));
2240
2544
  if (os.platform === "darwin") {
2241
2545
  return runCommand("brew install cloudflared", "Installing via Homebrew");
2242
2546
  }
@@ -2270,21 +2574,21 @@ async function installCloudflared(os) {
2270
2574
  );
2271
2575
  return runCommand(`sudo ${os.packageManager} install -y cloudflared`, "Installing cloudflared");
2272
2576
  }
2273
- console.log(chalk16.yellow(" Automatic installation not supported for your OS."));
2274
- console.log(chalk16.dim(" Visit: https://pkg.cloudflare.com/index.html"));
2577
+ console.log(chalk17.yellow(" Automatic installation not supported for your OS."));
2578
+ console.log(chalk17.dim(" Visit: https://pkg.cloudflare.com/index.html"));
2275
2579
  return false;
2276
2580
  }
2277
2581
  async function setupCommand(options = {}) {
2278
- console.log(chalk16.bold.cyan("\nBindler Setup\n"));
2582
+ console.log(chalk17.bold.cyan("\nBindler Setup\n"));
2279
2583
  const os = detectOs();
2280
- console.log(chalk16.dim(`Detected: ${os.distro || os.platform}${os.version ? ` ${os.version}` : ""}${os.codename ? ` (${os.codename})` : ""}`));
2584
+ console.log(chalk17.dim(`Detected: ${os.distro || os.platform}${os.version ? ` ${os.version}` : ""}${os.codename ? ` (${os.codename})` : ""}`));
2281
2585
  const isDirect = options.direct;
2282
2586
  if (isDirect) {
2283
- console.log(chalk16.cyan("\nDirect mode (VPS without Cloudflare Tunnel)"));
2284
- console.log(chalk16.dim("nginx will listen on port 80/443 directly\n"));
2587
+ console.log(chalk17.cyan("\nDirect mode (VPS without Cloudflare Tunnel)"));
2588
+ console.log(chalk17.dim("nginx will listen on port 80/443 directly\n"));
2285
2589
  } else {
2286
- console.log(chalk16.cyan("\nTunnel mode (via Cloudflare Tunnel)"));
2287
- console.log(chalk16.dim("nginx will listen on 127.0.0.1:8080\n"));
2590
+ console.log(chalk17.cyan("\nTunnel mode (via Cloudflare Tunnel)"));
2591
+ console.log(chalk17.dim("nginx will listen on 127.0.0.1:8080\n"));
2288
2592
  }
2289
2593
  const missing = [];
2290
2594
  if (!isNginxInstalled()) {
@@ -2303,11 +2607,11 @@ async function setupCommand(options = {}) {
2303
2607
  }
2304
2608
  }
2305
2609
  if (missing.length === 0) {
2306
- console.log(chalk16.green("\u2713 All dependencies are already installed!\n"));
2610
+ console.log(chalk17.green("\u2713 All dependencies are already installed!\n"));
2307
2611
  } else {
2308
- console.log(chalk16.yellow("Missing dependencies:\n"));
2612
+ console.log(chalk17.yellow("Missing dependencies:\n"));
2309
2613
  for (const dep of missing) {
2310
- console.log(chalk16.red(` \u2717 ${dep.name}`));
2614
+ console.log(chalk17.red(` \u2717 ${dep.name}`));
2311
2615
  }
2312
2616
  console.log("");
2313
2617
  const { toInstall } = await inquirer3.prompt([
@@ -2331,12 +2635,12 @@ async function setupCommand(options = {}) {
2331
2635
  results.push({ name: depName, success });
2332
2636
  }
2333
2637
  }
2334
- console.log(chalk16.bold("\n\nInstallation Summary:\n"));
2638
+ console.log(chalk17.bold("\n\nInstallation Summary:\n"));
2335
2639
  for (const result of results) {
2336
2640
  if (result.success) {
2337
- console.log(chalk16.green(` \u2713 ${result.name} installed`));
2641
+ console.log(chalk17.green(` \u2713 ${result.name} installed`));
2338
2642
  } else {
2339
- console.log(chalk16.red(` \u2717 ${result.name} failed`));
2643
+ console.log(chalk17.red(` \u2717 ${result.name} failed`));
2340
2644
  }
2341
2645
  }
2342
2646
  console.log("");
@@ -2378,32 +2682,32 @@ async function setupCommand(options = {}) {
2378
2682
  config.defaults.applyCloudflareDnsRoutes = true;
2379
2683
  }
2380
2684
  writeConfig(config);
2381
- console.log(chalk16.green("\n\u2713 Setup complete!\n"));
2382
- console.log(chalk16.dim(`Mode: ${config.defaults.mode}`));
2383
- console.log(chalk16.dim(`nginx listen: ${config.defaults.nginxListen}`));
2685
+ console.log(chalk17.green("\n\u2713 Setup complete!\n"));
2686
+ console.log(chalk17.dim(`Mode: ${config.defaults.mode}`));
2687
+ console.log(chalk17.dim(`nginx listen: ${config.defaults.nginxListen}`));
2384
2688
  if (config.defaults.sslEnabled) {
2385
- console.log(chalk16.dim(`SSL: enabled (${config.defaults.sslEmail})`));
2689
+ console.log(chalk17.dim(`SSL: enabled (${config.defaults.sslEmail})`));
2386
2690
  }
2387
- console.log(chalk16.dim("\nRun `bindler new` to create your first project."));
2691
+ console.log(chalk17.dim("\nRun `bindler new` to create your first project."));
2388
2692
  }
2389
2693
 
2390
2694
  // src/commands/init.ts
2391
- import chalk17 from "chalk";
2695
+ import chalk18 from "chalk";
2392
2696
  import inquirer4 from "inquirer";
2393
2697
  async function initCommand() {
2394
- console.log(chalk17.bold.cyan(`
2698
+ console.log(chalk18.bold.cyan(`
2395
2699
  _ _ _ _
2396
2700
  | |_|_|___ _| | |___ ___
2397
2701
  | . | | | . | | -_| _|
2398
2702
  |___|_|_|_|___|_|___|_|
2399
2703
  `));
2400
- console.log(chalk17.white(" Welcome to bindler!\n"));
2704
+ console.log(chalk18.white(" Welcome to bindler!\n"));
2401
2705
  if (configExists()) {
2402
2706
  const config2 = readConfig();
2403
- console.log(chalk17.yellow("Bindler is already initialized."));
2404
- console.log(chalk17.dim(` Config: ~/.config/bindler/config.json`));
2405
- console.log(chalk17.dim(` Mode: ${config2.defaults.mode || "tunnel"}`));
2406
- console.log(chalk17.dim(` Projects: ${config2.projects.length}`));
2707
+ console.log(chalk18.yellow("Bindler is already initialized."));
2708
+ console.log(chalk18.dim(` Config: ~/.config/bindler/config.json`));
2709
+ console.log(chalk18.dim(` Mode: ${config2.defaults.mode || "tunnel"}`));
2710
+ console.log(chalk18.dim(` Projects: ${config2.projects.length}`));
2407
2711
  console.log("");
2408
2712
  const { reinit } = await inquirer4.prompt([
2409
2713
  {
@@ -2414,11 +2718,11 @@ async function initCommand() {
2414
2718
  }
2415
2719
  ]);
2416
2720
  if (!reinit) {
2417
- console.log(chalk17.dim("\nRun `bindler new` to add a project."));
2721
+ console.log(chalk18.dim("\nRun `bindler new` to add a project."));
2418
2722
  return;
2419
2723
  }
2420
2724
  }
2421
- console.log(chalk17.bold("\n1. Choose your setup:\n"));
2725
+ console.log(chalk18.bold("\n1. Choose your setup:\n"));
2422
2726
  const { mode } = await inquirer4.prompt([
2423
2727
  {
2424
2728
  type: "list",
@@ -2440,19 +2744,19 @@ async function initCommand() {
2440
2744
  ]
2441
2745
  }
2442
2746
  ]);
2443
- console.log(chalk17.bold("\n2. Checking dependencies...\n"));
2747
+ console.log(chalk18.bold("\n2. Checking dependencies...\n"));
2444
2748
  const deps = {
2445
2749
  nginx: isNginxInstalled(),
2446
2750
  pm2: isPm2Installed(),
2447
2751
  cloudflared: isCloudflaredInstalled()
2448
2752
  };
2449
2753
  const nginxRunning = deps.nginx && isNginxRunning();
2450
- console.log(deps.nginx ? chalk17.green(" \u2713 nginx") : chalk17.red(" \u2717 nginx"));
2451
- console.log(deps.pm2 ? chalk17.green(" \u2713 pm2") : chalk17.red(" \u2717 pm2"));
2754
+ console.log(deps.nginx ? chalk18.green(" \u2713 nginx") : chalk18.red(" \u2717 nginx"));
2755
+ console.log(deps.pm2 ? chalk18.green(" \u2713 pm2") : chalk18.red(" \u2717 pm2"));
2452
2756
  if (mode === "tunnel") {
2453
- console.log(deps.cloudflared ? chalk17.green(" \u2713 cloudflared") : chalk17.red(" \u2717 cloudflared"));
2757
+ console.log(deps.cloudflared ? chalk18.green(" \u2713 cloudflared") : chalk18.red(" \u2717 cloudflared"));
2454
2758
  } else if (mode === "direct") {
2455
- console.log(chalk17.dim(" - cloudflared (not needed for direct mode)"));
2759
+ console.log(chalk18.dim(" - cloudflared (not needed for direct mode)"));
2456
2760
  }
2457
2761
  const missingDeps = !deps.nginx || !deps.pm2 || mode === "tunnel" && !deps.cloudflared;
2458
2762
  if (missingDeps) {
@@ -2469,7 +2773,7 @@ async function initCommand() {
2469
2773
  await setupCommand({ direct: mode === "direct" });
2470
2774
  }
2471
2775
  }
2472
- console.log(chalk17.bold("\n3. Configuration:\n"));
2776
+ console.log(chalk18.bold("\n3. Configuration:\n"));
2473
2777
  let tunnelName = "homelab";
2474
2778
  let sslEmail = "";
2475
2779
  if (mode === "tunnel") {
@@ -2521,29 +2825,29 @@ async function initCommand() {
2521
2825
  config.defaults.nginxListen = "127.0.0.1:8080";
2522
2826
  }
2523
2827
  writeConfig(config);
2524
- console.log(chalk17.green("\n\u2713 Bindler initialized!\n"));
2525
- console.log(chalk17.dim(" Mode: ") + chalk17.white(mode));
2526
- console.log(chalk17.dim(" Listen: ") + chalk17.white(config.defaults.nginxListen));
2828
+ console.log(chalk18.green("\n\u2713 Bindler initialized!\n"));
2829
+ console.log(chalk18.dim(" Mode: ") + chalk18.white(mode));
2830
+ console.log(chalk18.dim(" Listen: ") + chalk18.white(config.defaults.nginxListen));
2527
2831
  if (mode === "tunnel") {
2528
- console.log(chalk17.dim(" Tunnel: ") + chalk17.white(tunnelName));
2832
+ console.log(chalk18.dim(" Tunnel: ") + chalk18.white(tunnelName));
2529
2833
  }
2530
2834
  if (sslEmail) {
2531
- console.log(chalk17.dim(" SSL: ") + chalk17.white(sslEmail));
2835
+ console.log(chalk18.dim(" SSL: ") + chalk18.white(sslEmail));
2532
2836
  }
2533
- console.log(chalk17.bold("\nNext steps:\n"));
2534
- console.log(chalk17.dim(" 1. ") + chalk17.white("bindler new") + chalk17.dim(" # add your first project"));
2535
- console.log(chalk17.dim(" 2. ") + chalk17.white("bindler apply") + chalk17.dim(" # apply nginx config"));
2837
+ console.log(chalk18.bold("\nNext steps:\n"));
2838
+ console.log(chalk18.dim(" 1. ") + chalk18.white("bindler new") + chalk18.dim(" # add your first project"));
2839
+ console.log(chalk18.dim(" 2. ") + chalk18.white("bindler apply") + chalk18.dim(" # apply nginx config"));
2536
2840
  if (mode === "tunnel") {
2537
- console.log(chalk17.dim(" 3. ") + chalk17.white(`cloudflared tunnel run ${tunnelName}`) + chalk17.dim(" # start tunnel"));
2841
+ console.log(chalk18.dim(" 3. ") + chalk18.white(`cloudflared tunnel run ${tunnelName}`) + chalk18.dim(" # start tunnel"));
2538
2842
  }
2539
2843
  console.log("");
2540
2844
  }
2541
2845
 
2542
2846
  // src/commands/deploy.ts
2543
- import chalk18 from "chalk";
2847
+ import chalk19 from "chalk";
2544
2848
  import { execSync as execSync3 } from "child_process";
2545
- import { existsSync as existsSync9 } from "fs";
2546
- import { join as join6 } from "path";
2849
+ import { existsSync as existsSync11 } from "fs";
2850
+ import { join as join7 } from "path";
2547
2851
  function runInDir(command, cwd) {
2548
2852
  try {
2549
2853
  const output = execSync3(command, { cwd, encoding: "utf-8", stdio: "pipe" });
@@ -2555,101 +2859,101 @@ function runInDir(command, cwd) {
2555
2859
  }
2556
2860
  async function deployCommand(name, options) {
2557
2861
  if (!name) {
2558
- console.log(chalk18.red("Usage: bindler deploy <name>"));
2559
- console.log(chalk18.dim("\nDeploys a project: git pull + npm install + restart"));
2560
- console.log(chalk18.dim("\nExamples:"));
2561
- console.log(chalk18.dim(" bindler deploy myapp"));
2562
- console.log(chalk18.dim(" bindler deploy myapp --skip-install"));
2563
- console.log(chalk18.dim(" bindler deploy myapp --skip-pull"));
2862
+ console.log(chalk19.red("Usage: bindler deploy <name>"));
2863
+ console.log(chalk19.dim("\nDeploys a project: git pull + npm install + restart"));
2864
+ console.log(chalk19.dim("\nExamples:"));
2865
+ console.log(chalk19.dim(" bindler deploy myapp"));
2866
+ console.log(chalk19.dim(" bindler deploy myapp --skip-install"));
2867
+ console.log(chalk19.dim(" bindler deploy myapp --skip-pull"));
2564
2868
  process.exit(1);
2565
2869
  }
2566
2870
  const project = getProject(name);
2567
2871
  if (!project) {
2568
- console.log(chalk18.red(`Project "${name}" not found.`));
2569
- console.log(chalk18.dim("\nAvailable projects:"));
2872
+ console.log(chalk19.red(`Project "${name}" not found.`));
2873
+ console.log(chalk19.dim("\nAvailable projects:"));
2570
2874
  const projects = listProjects();
2571
2875
  for (const p of projects) {
2572
- console.log(chalk18.dim(` - ${p.name}`));
2876
+ console.log(chalk19.dim(` - ${p.name}`));
2573
2877
  }
2574
2878
  process.exit(1);
2575
2879
  }
2576
- if (!existsSync9(project.path)) {
2577
- console.log(chalk18.red(`Project path does not exist: ${project.path}`));
2880
+ if (!existsSync11(project.path)) {
2881
+ console.log(chalk19.red(`Project path does not exist: ${project.path}`));
2578
2882
  process.exit(1);
2579
2883
  }
2580
- console.log(chalk18.blue(`
2884
+ console.log(chalk19.blue(`
2581
2885
  Deploying ${project.name}...
2582
2886
  `));
2583
2887
  if (!options.skipPull) {
2584
- const isGitRepo = existsSync9(join6(project.path, ".git"));
2888
+ const isGitRepo = existsSync11(join7(project.path, ".git"));
2585
2889
  if (isGitRepo) {
2586
- console.log(chalk18.dim("Pulling latest changes..."));
2890
+ console.log(chalk19.dim("Pulling latest changes..."));
2587
2891
  const result = runInDir("git pull", project.path);
2588
2892
  if (result.success) {
2589
2893
  if (result.output.includes("Already up to date")) {
2590
- console.log(chalk18.green(" \u2713 Already up to date"));
2894
+ console.log(chalk19.green(" \u2713 Already up to date"));
2591
2895
  } else {
2592
- console.log(chalk18.green(" \u2713 Pulled latest changes"));
2896
+ console.log(chalk19.green(" \u2713 Pulled latest changes"));
2593
2897
  if (result.output) {
2594
- console.log(chalk18.dim(` ${result.output.split("\n")[0]}`));
2898
+ console.log(chalk19.dim(` ${result.output.split("\n")[0]}`));
2595
2899
  }
2596
2900
  }
2597
2901
  } else {
2598
- console.log(chalk18.yellow(" ! Git pull failed"));
2599
- console.log(chalk18.dim(` ${result.output}`));
2902
+ console.log(chalk19.yellow(" ! Git pull failed"));
2903
+ console.log(chalk19.dim(` ${result.output}`));
2600
2904
  }
2601
2905
  } else {
2602
- console.log(chalk18.dim(" - Not a git repository, skipping pull"));
2906
+ console.log(chalk19.dim(" - Not a git repository, skipping pull"));
2603
2907
  }
2604
2908
  } else {
2605
- console.log(chalk18.dim(" - Skipped git pull (--skip-pull)"));
2909
+ console.log(chalk19.dim(" - Skipped git pull (--skip-pull)"));
2606
2910
  }
2607
2911
  if (project.type === "npm" && !options.skipInstall) {
2608
- const hasPackageJson = existsSync9(join6(project.path, "package.json"));
2912
+ const hasPackageJson = existsSync11(join7(project.path, "package.json"));
2609
2913
  if (hasPackageJson) {
2610
- console.log(chalk18.dim("Installing dependencies..."));
2914
+ console.log(chalk19.dim("Installing dependencies..."));
2611
2915
  const result = runInDir("npm install", project.path);
2612
2916
  if (result.success) {
2613
- console.log(chalk18.green(" \u2713 Dependencies installed"));
2917
+ console.log(chalk19.green(" \u2713 Dependencies installed"));
2614
2918
  } else {
2615
- console.log(chalk18.yellow(" ! npm install failed"));
2616
- console.log(chalk18.dim(` ${result.output.split("\n")[0]}`));
2919
+ console.log(chalk19.yellow(" ! npm install failed"));
2920
+ console.log(chalk19.dim(` ${result.output.split("\n")[0]}`));
2617
2921
  }
2618
2922
  }
2619
2923
  } else if (options.skipInstall) {
2620
- console.log(chalk18.dim(" - Skipped npm install (--skip-install)"));
2924
+ console.log(chalk19.dim(" - Skipped npm install (--skip-install)"));
2621
2925
  }
2622
2926
  if (project.type === "npm" && !options.skipRestart) {
2623
- console.log(chalk18.dim("Restarting application..."));
2927
+ console.log(chalk19.dim("Restarting application..."));
2624
2928
  const result = restartProject(name);
2625
2929
  if (result.success) {
2626
- console.log(chalk18.green(" \u2713 Application restarted"));
2930
+ console.log(chalk19.green(" \u2713 Application restarted"));
2627
2931
  } else {
2628
- console.log(chalk18.yellow(` ! Restart failed: ${result.error}`));
2629
- console.log(chalk18.dim(` Try: bindler start ${name}`));
2932
+ console.log(chalk19.yellow(` ! Restart failed: ${result.error}`));
2933
+ console.log(chalk19.dim(` Try: bindler start ${name}`));
2630
2934
  }
2631
2935
  } else if (project.type === "static") {
2632
- console.log(chalk18.dim(" - Static project, no restart needed"));
2936
+ console.log(chalk19.dim(" - Static project, no restart needed"));
2633
2937
  } else if (options.skipRestart) {
2634
- console.log(chalk18.dim(" - Skipped restart (--skip-restart)"));
2938
+ console.log(chalk19.dim(" - Skipped restart (--skip-restart)"));
2635
2939
  }
2636
- console.log(chalk18.green(`
2940
+ console.log(chalk19.green(`
2637
2941
  \u2713 Deploy complete for ${project.name}
2638
2942
  `));
2639
2943
  }
2640
2944
 
2641
2945
  // src/commands/backup.ts
2642
- import chalk19 from "chalk";
2643
- import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "fs";
2946
+ import chalk20 from "chalk";
2947
+ import { existsSync as existsSync12, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "fs";
2644
2948
  import { dirname as dirname3, resolve } from "path";
2645
- import { homedir as homedir2 } from "os";
2949
+ import { homedir as homedir3 } from "os";
2646
2950
  async function backupCommand(options) {
2647
2951
  const config = readConfig();
2648
2952
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2649
- const defaultPath = resolve(homedir2(), `bindler-backup-${timestamp}.json`);
2953
+ const defaultPath = resolve(homedir3(), `bindler-backup-${timestamp}.json`);
2650
2954
  const outputPath = options.output || defaultPath;
2651
2955
  const dir = dirname3(outputPath);
2652
- if (!existsSync10(dir)) {
2956
+ if (!existsSync12(dir)) {
2653
2957
  mkdirSync4(dir, { recursive: true });
2654
2958
  }
2655
2959
  const backup = {
@@ -2658,27 +2962,27 @@ async function backupCommand(options) {
2658
2962
  config
2659
2963
  };
2660
2964
  writeFileSync5(outputPath, JSON.stringify(backup, null, 2) + "\n");
2661
- console.log(chalk19.green(`
2965
+ console.log(chalk20.green(`
2662
2966
  \u2713 Backup saved to ${outputPath}
2663
2967
  `));
2664
- console.log(chalk19.dim(` Projects: ${config.projects.length}`));
2665
- console.log(chalk19.dim(` Mode: ${config.defaults.mode || "tunnel"}`));
2968
+ console.log(chalk20.dim(` Projects: ${config.projects.length}`));
2969
+ console.log(chalk20.dim(` Mode: ${config.defaults.mode || "tunnel"}`));
2666
2970
  console.log("");
2667
- console.log(chalk19.dim("Restore with:"));
2668
- console.log(chalk19.cyan(` bindler restore ${outputPath}`));
2971
+ console.log(chalk20.dim("Restore with:"));
2972
+ console.log(chalk20.cyan(` bindler restore ${outputPath}`));
2669
2973
  console.log("");
2670
2974
  }
2671
2975
  async function restoreCommand(file, options) {
2672
2976
  if (!file) {
2673
- console.log(chalk19.red("Usage: bindler restore <file>"));
2674
- console.log(chalk19.dim("\nExamples:"));
2675
- console.log(chalk19.dim(" bindler restore ~/bindler-backup.json"));
2676
- console.log(chalk19.dim(" bindler restore backup.json --force"));
2977
+ console.log(chalk20.red("Usage: bindler restore <file>"));
2978
+ console.log(chalk20.dim("\nExamples:"));
2979
+ console.log(chalk20.dim(" bindler restore ~/bindler-backup.json"));
2980
+ console.log(chalk20.dim(" bindler restore backup.json --force"));
2677
2981
  process.exit(1);
2678
2982
  }
2679
2983
  const filePath = resolve(file);
2680
- if (!existsSync10(filePath)) {
2681
- console.log(chalk19.red(`File not found: ${filePath}`));
2984
+ if (!existsSync12(filePath)) {
2985
+ console.log(chalk20.red(`File not found: ${filePath}`));
2682
2986
  process.exit(1);
2683
2987
  }
2684
2988
  let backup;
@@ -2686,140 +2990,140 @@ async function restoreCommand(file, options) {
2686
2990
  const content = readFileSync6(filePath, "utf-8");
2687
2991
  backup = JSON.parse(content);
2688
2992
  } catch (error) {
2689
- console.log(chalk19.red("Invalid backup file. Must be valid JSON."));
2993
+ console.log(chalk20.red("Invalid backup file. Must be valid JSON."));
2690
2994
  process.exit(1);
2691
2995
  }
2692
2996
  if (!backup.config || !backup.config.defaults || !Array.isArray(backup.config.projects)) {
2693
- console.log(chalk19.red("Invalid backup format. Missing config data."));
2997
+ console.log(chalk20.red("Invalid backup format. Missing config data."));
2694
2998
  process.exit(1);
2695
2999
  }
2696
- console.log(chalk19.blue("\nBackup info:\n"));
2697
- console.log(chalk19.dim(" Exported: ") + chalk19.white(backup.exportedAt || "unknown"));
2698
- console.log(chalk19.dim(" Projects: ") + chalk19.white(backup.config.projects.length));
2699
- console.log(chalk19.dim(" Mode: ") + chalk19.white(backup.config.defaults.mode || "tunnel"));
3000
+ console.log(chalk20.blue("\nBackup info:\n"));
3001
+ console.log(chalk20.dim(" Exported: ") + chalk20.white(backup.exportedAt || "unknown"));
3002
+ console.log(chalk20.dim(" Projects: ") + chalk20.white(backup.config.projects.length));
3003
+ console.log(chalk20.dim(" Mode: ") + chalk20.white(backup.config.defaults.mode || "tunnel"));
2700
3004
  console.log("");
2701
3005
  const currentConfig = readConfig();
2702
3006
  if (currentConfig.projects.length > 0 && !options.force) {
2703
- console.log(chalk19.yellow(`Warning: You have ${currentConfig.projects.length} existing project(s).`));
2704
- console.log(chalk19.dim("Use --force to overwrite.\n"));
3007
+ console.log(chalk20.yellow(`Warning: You have ${currentConfig.projects.length} existing project(s).`));
3008
+ console.log(chalk20.dim("Use --force to overwrite.\n"));
2705
3009
  process.exit(1);
2706
3010
  }
2707
3011
  writeConfig(backup.config);
2708
- console.log(chalk19.green("\u2713 Config restored!\n"));
2709
- console.log(chalk19.dim("Run `bindler apply` to apply nginx configuration."));
3012
+ console.log(chalk20.green("\u2713 Config restored!\n"));
3013
+ console.log(chalk20.dim("Run `bindler apply` to apply nginx configuration."));
2710
3014
  console.log("");
2711
3015
  }
2712
3016
 
2713
3017
  // src/commands/ssl.ts
2714
- import chalk20 from "chalk";
3018
+ import chalk21 from "chalk";
2715
3019
  async function sslCommand(hostname, options) {
2716
3020
  if (!hostname) {
2717
- console.log(chalk20.red("Usage: bindler ssl <hostname>"));
2718
- console.log(chalk20.dim("\nRequest SSL certificate for a hostname"));
2719
- console.log(chalk20.dim("\nExamples:"));
2720
- console.log(chalk20.dim(" bindler ssl myapp.example.com"));
2721
- console.log(chalk20.dim(" bindler ssl myapp # uses project hostname"));
2722
- console.log(chalk20.dim(" bindler ssl myapp --email me@example.com"));
3021
+ console.log(chalk21.red("Usage: bindler ssl <hostname>"));
3022
+ console.log(chalk21.dim("\nRequest SSL certificate for a hostname"));
3023
+ console.log(chalk21.dim("\nExamples:"));
3024
+ console.log(chalk21.dim(" bindler ssl myapp.example.com"));
3025
+ console.log(chalk21.dim(" bindler ssl myapp # uses project hostname"));
3026
+ console.log(chalk21.dim(" bindler ssl myapp --email me@example.com"));
2723
3027
  process.exit(1);
2724
3028
  }
2725
3029
  const project = getProject(hostname);
2726
3030
  const targetHostname = project ? project.hostname : hostname;
2727
3031
  const certbotCheck = execCommandSafe("which certbot");
2728
3032
  if (!certbotCheck.success) {
2729
- console.log(chalk20.red("certbot is not installed."));
2730
- console.log(chalk20.dim("\nInstall with:"));
2731
- console.log(chalk20.dim(" macOS: brew install certbot"));
2732
- console.log(chalk20.dim(" Linux: apt install certbot python3-certbot-nginx"));
3033
+ console.log(chalk21.red("certbot is not installed."));
3034
+ console.log(chalk21.dim("\nInstall with:"));
3035
+ console.log(chalk21.dim(" macOS: brew install certbot"));
3036
+ console.log(chalk21.dim(" Linux: apt install certbot python3-certbot-nginx"));
2733
3037
  process.exit(1);
2734
3038
  }
2735
3039
  const defaults = getDefaults();
2736
3040
  const email = options.email || defaults.sslEmail || `admin@${targetHostname.split(".").slice(-2).join(".")}`;
2737
- console.log(chalk20.blue(`
3041
+ console.log(chalk21.blue(`
2738
3042
  Requesting SSL certificate for ${targetHostname}...
2739
3043
  `));
2740
3044
  let cmd = `sudo certbot --nginx -d ${targetHostname} --non-interactive --agree-tos --email ${email}`;
2741
3045
  if (options.staging) {
2742
3046
  cmd += " --staging";
2743
- console.log(chalk20.yellow("Using staging server (test certificate)\n"));
3047
+ console.log(chalk21.yellow("Using staging server (test certificate)\n"));
2744
3048
  }
2745
- console.log(chalk20.dim(`Running: ${cmd}
3049
+ console.log(chalk21.dim(`Running: ${cmd}
2746
3050
  `));
2747
3051
  const result = execCommandSafe(cmd + " 2>&1");
2748
3052
  if (result.success) {
2749
- console.log(chalk20.green(`
3053
+ console.log(chalk21.green(`
2750
3054
  \u2713 SSL certificate installed for ${targetHostname}
2751
3055
  `));
2752
- console.log(chalk20.dim("Certificate will auto-renew via certbot timer."));
3056
+ console.log(chalk21.dim("Certificate will auto-renew via certbot timer."));
2753
3057
  } else if (result.output?.includes("Certificate not yet due for renewal")) {
2754
- console.log(chalk20.green(`
3058
+ console.log(chalk21.green(`
2755
3059
  \u2713 Certificate already exists and is valid
2756
3060
  `));
2757
- console.log(chalk20.dim("Use --force with certbot to renew early if needed."));
3061
+ console.log(chalk21.dim("Use --force with certbot to renew early if needed."));
2758
3062
  } else if (result.output?.includes("too many certificates")) {
2759
- console.log(chalk20.red("\n\u2717 Rate limit reached"));
2760
- console.log(chalk20.dim("Let's Encrypt limits certificates per domain."));
2761
- console.log(chalk20.dim("Try again later or use --staging for testing."));
3063
+ console.log(chalk21.red("\n\u2717 Rate limit reached"));
3064
+ console.log(chalk21.dim("Let's Encrypt limits certificates per domain."));
3065
+ console.log(chalk21.dim("Try again later or use --staging for testing."));
2762
3066
  } else if (result.output?.includes("Could not bind")) {
2763
- console.log(chalk20.red("\n\u2717 Port 80 is in use"));
2764
- console.log(chalk20.dim("Stop nginx temporarily or use webroot method."));
3067
+ console.log(chalk21.red("\n\u2717 Port 80 is in use"));
3068
+ console.log(chalk21.dim("Stop nginx temporarily or use webroot method."));
2765
3069
  } else {
2766
- console.log(chalk20.red("\n\u2717 Certificate request failed\n"));
3070
+ console.log(chalk21.red("\n\u2717 Certificate request failed\n"));
2767
3071
  if (result.output) {
2768
3072
  const lines = result.output.split("\n").filter(
2769
3073
  (l) => l.includes("Error") || l.includes("error") || l.includes("failed") || l.includes("Challenge")
2770
3074
  );
2771
3075
  for (const line of lines.slice(0, 5)) {
2772
- console.log(chalk20.dim(` ${line.trim()}`));
3076
+ console.log(chalk21.dim(` ${line.trim()}`));
2773
3077
  }
2774
3078
  }
2775
- console.log(chalk20.dim("\nCommon issues:"));
2776
- console.log(chalk20.dim(" - DNS not pointing to this server"));
2777
- console.log(chalk20.dim(" - Port 80 not accessible from internet"));
2778
- console.log(chalk20.dim(" - Firewall blocking HTTP validation"));
3079
+ console.log(chalk21.dim("\nCommon issues:"));
3080
+ console.log(chalk21.dim(" - DNS not pointing to this server"));
3081
+ console.log(chalk21.dim(" - Port 80 not accessible from internet"));
3082
+ console.log(chalk21.dim(" - Firewall blocking HTTP validation"));
2779
3083
  }
2780
3084
  console.log("");
2781
3085
  }
2782
3086
 
2783
3087
  // src/commands/tunnel.ts
2784
- import chalk21 from "chalk";
3088
+ import chalk22 from "chalk";
2785
3089
  import { execSync as execSync4, spawn as spawn2 } from "child_process";
2786
3090
  async function tunnelCommand(action, options) {
2787
3091
  if (!action) {
2788
- console.log(chalk21.red("Usage: bindler tunnel <action>"));
2789
- console.log(chalk21.dim("\nActions:"));
2790
- console.log(chalk21.dim(" status Show tunnel status"));
2791
- console.log(chalk21.dim(" start Start the tunnel"));
2792
- console.log(chalk21.dim(" stop Stop the tunnel"));
2793
- console.log(chalk21.dim(" login Authenticate with Cloudflare"));
2794
- console.log(chalk21.dim(" create Create a new tunnel"));
2795
- console.log(chalk21.dim(" list List all tunnels"));
2796
- console.log(chalk21.dim("\nExamples:"));
2797
- console.log(chalk21.dim(" bindler tunnel status"));
2798
- console.log(chalk21.dim(" bindler tunnel start"));
2799
- console.log(chalk21.dim(" bindler tunnel create --name mytunnel"));
3092
+ console.log(chalk22.red("Usage: bindler tunnel <action>"));
3093
+ console.log(chalk22.dim("\nActions:"));
3094
+ console.log(chalk22.dim(" status Show tunnel status"));
3095
+ console.log(chalk22.dim(" start Start the tunnel"));
3096
+ console.log(chalk22.dim(" stop Stop the tunnel"));
3097
+ console.log(chalk22.dim(" login Authenticate with Cloudflare"));
3098
+ console.log(chalk22.dim(" create Create a new tunnel"));
3099
+ console.log(chalk22.dim(" list List all tunnels"));
3100
+ console.log(chalk22.dim("\nExamples:"));
3101
+ console.log(chalk22.dim(" bindler tunnel status"));
3102
+ console.log(chalk22.dim(" bindler tunnel start"));
3103
+ console.log(chalk22.dim(" bindler tunnel create --name mytunnel"));
2800
3104
  process.exit(1);
2801
3105
  }
2802
3106
  if (!isCloudflaredInstalled()) {
2803
- console.log(chalk21.red("cloudflared is not installed."));
2804
- console.log(chalk21.dim("\nInstall with: bindler setup"));
3107
+ console.log(chalk22.red("cloudflared is not installed."));
3108
+ console.log(chalk22.dim("\nInstall with: bindler setup"));
2805
3109
  process.exit(1);
2806
3110
  }
2807
3111
  const defaults = getDefaults();
2808
3112
  const tunnelName = options.name || defaults.tunnelName;
2809
3113
  switch (action) {
2810
3114
  case "status": {
2811
- console.log(chalk21.blue("\nTunnel Status\n"));
3115
+ console.log(chalk22.blue("\nTunnel Status\n"));
2812
3116
  const info = getTunnelInfo(tunnelName);
2813
3117
  if (!info.exists) {
2814
- console.log(chalk21.yellow(`Tunnel "${tunnelName}" does not exist.`));
2815
- console.log(chalk21.dim(`
3118
+ console.log(chalk22.yellow(`Tunnel "${tunnelName}" does not exist.`));
3119
+ console.log(chalk22.dim(`
2816
3120
  Create with: bindler tunnel create --name ${tunnelName}`));
2817
3121
  } else {
2818
- console.log(chalk21.dim(" Name: ") + chalk21.white(tunnelName));
2819
- console.log(chalk21.dim(" ID: ") + chalk21.white(info.id || "unknown"));
2820
- console.log(chalk21.dim(" Running: ") + (info.running ? chalk21.green("yes") : chalk21.red("no")));
3122
+ console.log(chalk22.dim(" Name: ") + chalk22.white(tunnelName));
3123
+ console.log(chalk22.dim(" ID: ") + chalk22.white(info.id || "unknown"));
3124
+ console.log(chalk22.dim(" Running: ") + (info.running ? chalk22.green("yes") : chalk22.red("no")));
2821
3125
  if (!info.running) {
2822
- console.log(chalk21.dim(`
3126
+ console.log(chalk22.dim(`
2823
3127
  Start with: bindler tunnel start`));
2824
3128
  }
2825
3129
  }
@@ -2829,50 +3133,50 @@ Start with: bindler tunnel start`));
2829
3133
  case "start": {
2830
3134
  const info = getTunnelInfo(tunnelName);
2831
3135
  if (!info.exists) {
2832
- console.log(chalk21.red(`Tunnel "${tunnelName}" does not exist.`));
2833
- console.log(chalk21.dim(`Create with: bindler tunnel create`));
3136
+ console.log(chalk22.red(`Tunnel "${tunnelName}" does not exist.`));
3137
+ console.log(chalk22.dim(`Create with: bindler tunnel create`));
2834
3138
  process.exit(1);
2835
3139
  }
2836
3140
  if (info.running) {
2837
- console.log(chalk21.yellow(`Tunnel "${tunnelName}" is already running.`));
3141
+ console.log(chalk22.yellow(`Tunnel "${tunnelName}" is already running.`));
2838
3142
  process.exit(0);
2839
3143
  }
2840
- console.log(chalk21.blue(`Starting tunnel "${tunnelName}"...
3144
+ console.log(chalk22.blue(`Starting tunnel "${tunnelName}"...
2841
3145
  `));
2842
- console.log(chalk21.dim("Running in foreground. Press Ctrl+C to stop.\n"));
3146
+ console.log(chalk22.dim("Running in foreground. Press Ctrl+C to stop.\n"));
2843
3147
  const child = spawn2("cloudflared", ["tunnel", "run", tunnelName], {
2844
3148
  stdio: "inherit"
2845
3149
  });
2846
3150
  child.on("error", (err) => {
2847
- console.log(chalk21.red(`Failed to start tunnel: ${err.message}`));
3151
+ console.log(chalk22.red(`Failed to start tunnel: ${err.message}`));
2848
3152
  process.exit(1);
2849
3153
  });
2850
3154
  child.on("exit", (code) => {
2851
- console.log(chalk21.dim(`
3155
+ console.log(chalk22.dim(`
2852
3156
  Tunnel exited with code ${code}`));
2853
3157
  process.exit(code || 0);
2854
3158
  });
2855
3159
  break;
2856
3160
  }
2857
3161
  case "stop": {
2858
- console.log(chalk21.blue("Stopping tunnel...\n"));
3162
+ console.log(chalk22.blue("Stopping tunnel...\n"));
2859
3163
  const result = execCommandSafe(`pkill -f "cloudflared.*tunnel.*run.*${tunnelName}"`);
2860
3164
  if (result.success) {
2861
- console.log(chalk21.green(`\u2713 Tunnel "${tunnelName}" stopped`));
3165
+ console.log(chalk22.green(`\u2713 Tunnel "${tunnelName}" stopped`));
2862
3166
  } else {
2863
- console.log(chalk21.yellow(`Tunnel "${tunnelName}" was not running.`));
3167
+ console.log(chalk22.yellow(`Tunnel "${tunnelName}" was not running.`));
2864
3168
  }
2865
3169
  console.log("");
2866
3170
  break;
2867
3171
  }
2868
3172
  case "login": {
2869
- console.log(chalk21.blue("Authenticating with Cloudflare...\n"));
2870
- console.log(chalk21.dim("A browser window will open. Follow the instructions.\n"));
3173
+ console.log(chalk22.blue("Authenticating with Cloudflare...\n"));
3174
+ console.log(chalk22.dim("A browser window will open. Follow the instructions.\n"));
2871
3175
  try {
2872
3176
  execSync4("cloudflared tunnel login", { stdio: "inherit" });
2873
- console.log(chalk21.green("\n\u2713 Authentication successful!"));
3177
+ console.log(chalk22.green("\n\u2713 Authentication successful!"));
2874
3178
  } catch {
2875
- console.log(chalk21.red("\n\u2717 Authentication failed or cancelled."));
3179
+ console.log(chalk22.red("\n\u2717 Authentication failed or cancelled."));
2876
3180
  }
2877
3181
  console.log("");
2878
3182
  break;
@@ -2881,69 +3185,69 @@ Tunnel exited with code ${code}`));
2881
3185
  const existingTunnels = listTunnels();
2882
3186
  const exists = existingTunnels.some((t) => t.name === tunnelName);
2883
3187
  if (exists) {
2884
- console.log(chalk21.yellow(`Tunnel "${tunnelName}" already exists.`));
2885
- console.log(chalk21.dim("\nUse a different name with --name"));
3188
+ console.log(chalk22.yellow(`Tunnel "${tunnelName}" already exists.`));
3189
+ console.log(chalk22.dim("\nUse a different name with --name"));
2886
3190
  process.exit(1);
2887
3191
  }
2888
- console.log(chalk21.blue(`Creating tunnel "${tunnelName}"...
3192
+ console.log(chalk22.blue(`Creating tunnel "${tunnelName}"...
2889
3193
  `));
2890
3194
  try {
2891
3195
  execSync4(`cloudflared tunnel create ${tunnelName}`, { stdio: "inherit" });
2892
- console.log(chalk21.green(`
3196
+ console.log(chalk22.green(`
2893
3197
  \u2713 Tunnel "${tunnelName}" created!`));
2894
- console.log(chalk21.dim("\nNext steps:"));
2895
- console.log(chalk21.dim(" 1. Create ~/.cloudflared/config.yml"));
2896
- console.log(chalk21.dim(" 2. Run: bindler tunnel start"));
3198
+ console.log(chalk22.dim("\nNext steps:"));
3199
+ console.log(chalk22.dim(" 1. Create ~/.cloudflared/config.yml"));
3200
+ console.log(chalk22.dim(" 2. Run: bindler tunnel start"));
2897
3201
  } catch {
2898
- console.log(chalk21.red("\n\u2717 Failed to create tunnel."));
2899
- console.log(chalk21.dim("Make sure you're logged in: bindler tunnel login"));
3202
+ console.log(chalk22.red("\n\u2717 Failed to create tunnel."));
3203
+ console.log(chalk22.dim("Make sure you're logged in: bindler tunnel login"));
2900
3204
  }
2901
3205
  console.log("");
2902
3206
  break;
2903
3207
  }
2904
3208
  case "list": {
2905
- console.log(chalk21.blue("\nCloudflare Tunnels\n"));
3209
+ console.log(chalk22.blue("\nCloudflare Tunnels\n"));
2906
3210
  const tunnels = listTunnels();
2907
3211
  if (tunnels.length === 0) {
2908
- console.log(chalk21.dim("No tunnels found."));
2909
- console.log(chalk21.dim("\nCreate one with: bindler tunnel create"));
3212
+ console.log(chalk22.dim("No tunnels found."));
3213
+ console.log(chalk22.dim("\nCreate one with: bindler tunnel create"));
2910
3214
  } else {
2911
3215
  for (const tunnel of tunnels) {
2912
3216
  const isDefault = tunnel.name === tunnelName;
2913
- const prefix = isDefault ? chalk21.cyan("\u2192 ") : " ";
2914
- console.log(prefix + chalk21.white(tunnel.name) + chalk21.dim(` (${tunnel.id.slice(0, 8)}...)`));
3217
+ const prefix = isDefault ? chalk22.cyan("\u2192 ") : " ";
3218
+ console.log(prefix + chalk22.white(tunnel.name) + chalk22.dim(` (${tunnel.id.slice(0, 8)}...)`));
2915
3219
  }
2916
- console.log(chalk21.dim(`
3220
+ console.log(chalk22.dim(`
2917
3221
  ${tunnels.length} tunnel(s)`));
2918
3222
  }
2919
3223
  console.log("");
2920
3224
  break;
2921
3225
  }
2922
3226
  default:
2923
- console.log(chalk21.red(`Unknown action: ${action}`));
2924
- console.log(chalk21.dim("Run `bindler tunnel` for usage."));
3227
+ console.log(chalk22.red(`Unknown action: ${action}`));
3228
+ console.log(chalk22.dim("Run `bindler tunnel` for usage."));
2925
3229
  process.exit(1);
2926
3230
  }
2927
3231
  }
2928
3232
 
2929
3233
  // src/commands/open.ts
2930
- import chalk22 from "chalk";
3234
+ import chalk23 from "chalk";
2931
3235
  import { exec } from "child_process";
2932
3236
  async function openCommand(name) {
2933
3237
  if (!name) {
2934
- console.log(chalk22.red("Usage: bindler open <name>"));
2935
- console.log(chalk22.dim("\nOpen a project in your browser"));
2936
- console.log(chalk22.dim("\nExamples:"));
2937
- console.log(chalk22.dim(" bindler open myapp"));
3238
+ console.log(chalk23.red("Usage: bindler open <name>"));
3239
+ console.log(chalk23.dim("\nOpen a project in your browser"));
3240
+ console.log(chalk23.dim("\nExamples:"));
3241
+ console.log(chalk23.dim(" bindler open myapp"));
2938
3242
  process.exit(1);
2939
3243
  }
2940
3244
  const project = getProject(name);
2941
3245
  if (!project) {
2942
- console.log(chalk22.red(`Project "${name}" not found.`));
2943
- console.log(chalk22.dim("\nAvailable projects:"));
3246
+ console.log(chalk23.red(`Project "${name}" not found.`));
3247
+ console.log(chalk23.dim("\nAvailable projects:"));
2944
3248
  const projects = listProjects();
2945
3249
  for (const p of projects) {
2946
- console.log(chalk22.dim(` - ${p.name}`));
3250
+ console.log(chalk23.dim(` - ${p.name}`));
2947
3251
  }
2948
3252
  process.exit(1);
2949
3253
  }
@@ -2964,7 +3268,7 @@ async function openCommand(name) {
2964
3268
  if (project.basePath && project.basePath !== "/") {
2965
3269
  url += project.basePath;
2966
3270
  }
2967
- console.log(chalk22.dim(`Opening ${url}...`));
3271
+ console.log(chalk23.dim(`Opening ${url}...`));
2968
3272
  const platform = process.platform;
2969
3273
  let cmd;
2970
3274
  if (platform === "darwin") {
@@ -2976,15 +3280,15 @@ async function openCommand(name) {
2976
3280
  }
2977
3281
  exec(cmd, (error) => {
2978
3282
  if (error) {
2979
- console.log(chalk22.yellow(`Could not open browser automatically.`));
2980
- console.log(chalk22.dim(`
2981
- Open manually: ${chalk22.cyan(url)}`));
3283
+ console.log(chalk23.yellow(`Could not open browser automatically.`));
3284
+ console.log(chalk23.dim(`
3285
+ Open manually: ${chalk23.cyan(url)}`));
2982
3286
  }
2983
3287
  });
2984
3288
  }
2985
3289
 
2986
3290
  // src/commands/health.ts
2987
- import chalk23 from "chalk";
3291
+ import chalk24 from "chalk";
2988
3292
  async function pingUrl(url, timeout = 5e3) {
2989
3293
  const start = Date.now();
2990
3294
  try {
@@ -3014,15 +3318,15 @@ async function healthCommand() {
3014
3318
  const projects = listProjects();
3015
3319
  const defaults = getDefaults();
3016
3320
  if (projects.length === 0) {
3017
- console.log(chalk23.yellow("\nNo projects registered."));
3018
- console.log(chalk23.dim("Run `bindler new` to add a project.\n"));
3321
+ console.log(chalk24.yellow("\nNo projects registered."));
3322
+ console.log(chalk24.dim("Run `bindler new` to add a project.\n"));
3019
3323
  return;
3020
3324
  }
3021
- console.log(chalk23.blue("\nHealth Check\n"));
3325
+ console.log(chalk24.blue("\nHealth Check\n"));
3022
3326
  const results = [];
3023
3327
  for (const project of projects) {
3024
3328
  if (project.enabled === false) {
3025
- console.log(chalk23.dim(` - ${project.name} (disabled)`));
3329
+ console.log(chalk24.dim(` - ${project.name} (disabled)`));
3026
3330
  continue;
3027
3331
  }
3028
3332
  const isLocal = project.local || project.hostname.endsWith(".local");
@@ -3041,33 +3345,33 @@ async function healthCommand() {
3041
3345
  if (project.basePath && project.basePath !== "/") {
3042
3346
  url += project.basePath;
3043
3347
  }
3044
- process.stdout.write(chalk23.dim(` Checking ${project.name}...`));
3348
+ process.stdout.write(chalk24.dim(` Checking ${project.name}...`));
3045
3349
  const result = await pingUrl(url);
3046
3350
  results.push({ name: project.name, hostname: project.hostname, ...result });
3047
3351
  process.stdout.write("\r\x1B[K");
3048
3352
  if (result.ok) {
3049
- console.log(chalk23.green(" \u2713 ") + chalk23.white(project.name) + chalk23.dim(` (${result.time}ms)`));
3353
+ console.log(chalk24.green(" \u2713 ") + chalk24.white(project.name) + chalk24.dim(` (${result.time}ms)`));
3050
3354
  } else if (result.status) {
3051
- console.log(chalk23.yellow(" ! ") + chalk23.white(project.name) + chalk23.dim(` (${result.status})`));
3355
+ console.log(chalk24.yellow(" ! ") + chalk24.white(project.name) + chalk24.dim(` (${result.status})`));
3052
3356
  } else {
3053
- console.log(chalk23.red(" \u2717 ") + chalk23.white(project.name) + chalk23.dim(` (${result.error})`));
3357
+ console.log(chalk24.red(" \u2717 ") + chalk24.white(project.name) + chalk24.dim(` (${result.error})`));
3054
3358
  }
3055
3359
  }
3056
3360
  const healthy = results.filter((r) => r.ok).length;
3057
3361
  const unhealthy = results.filter((r) => !r.ok).length;
3058
3362
  console.log("");
3059
3363
  if (unhealthy === 0) {
3060
- console.log(chalk23.green(`\u2713 All ${healthy} project(s) healthy`));
3364
+ console.log(chalk24.green(`\u2713 All ${healthy} project(s) healthy`));
3061
3365
  } else if (healthy === 0) {
3062
- console.log(chalk23.red(`\u2717 All ${unhealthy} project(s) down`));
3366
+ console.log(chalk24.red(`\u2717 All ${unhealthy} project(s) down`));
3063
3367
  } else {
3064
- console.log(chalk23.yellow(`! ${healthy} healthy, ${unhealthy} down`));
3368
+ console.log(chalk24.yellow(`! ${healthy} healthy, ${unhealthy} down`));
3065
3369
  }
3066
3370
  console.log("");
3067
3371
  }
3068
3372
 
3069
3373
  // src/commands/stats.ts
3070
- import chalk24 from "chalk";
3374
+ import chalk25 from "chalk";
3071
3375
  function formatBytes2(bytes) {
3072
3376
  if (bytes === 0) return "0 B";
3073
3377
  const k = 1024;
@@ -3090,15 +3394,15 @@ async function statsCommand() {
3090
3394
  const pm2Processes = getPm2List();
3091
3395
  const npmProjects = projects.filter((p) => p.type === "npm");
3092
3396
  if (npmProjects.length === 0) {
3093
- console.log(chalk24.yellow("\nNo npm projects registered."));
3094
- console.log(chalk24.dim("Stats are only available for npm projects.\n"));
3397
+ console.log(chalk25.yellow("\nNo npm projects registered."));
3398
+ console.log(chalk25.dim("Stats are only available for npm projects.\n"));
3095
3399
  return;
3096
3400
  }
3097
- console.log(chalk24.blue("\nProject Stats\n"));
3401
+ console.log(chalk25.blue("\nProject Stats\n"));
3098
3402
  console.log(
3099
- chalk24.dim(" ") + chalk24.dim("NAME".padEnd(20)) + chalk24.dim("STATUS".padEnd(10)) + chalk24.dim("CPU".padEnd(8)) + chalk24.dim("MEM".padEnd(10)) + chalk24.dim("UPTIME".padEnd(10)) + chalk24.dim("RESTARTS")
3403
+ chalk25.dim(" ") + chalk25.dim("NAME".padEnd(20)) + chalk25.dim("STATUS".padEnd(10)) + chalk25.dim("CPU".padEnd(8)) + chalk25.dim("MEM".padEnd(10)) + chalk25.dim("UPTIME".padEnd(10)) + chalk25.dim("RESTARTS")
3100
3404
  );
3101
- console.log(chalk24.dim(" " + "-".repeat(70)));
3405
+ console.log(chalk25.dim(" " + "-".repeat(70)));
3102
3406
  let totalCpu = 0;
3103
3407
  let totalMem = 0;
3104
3408
  for (const project of npmProjects) {
@@ -3107,11 +3411,11 @@ async function statsCommand() {
3107
3411
  const name = project.name.slice(0, 18).padEnd(20);
3108
3412
  if (!pm2Process) {
3109
3413
  console.log(
3110
- " " + chalk24.white(name) + chalk24.dim("not managed".padEnd(10)) + chalk24.dim("-".padEnd(8)) + chalk24.dim("-".padEnd(10)) + chalk24.dim("-".padEnd(10)) + chalk24.dim("-")
3414
+ " " + chalk25.white(name) + chalk25.dim("not managed".padEnd(10)) + chalk25.dim("-".padEnd(8)) + chalk25.dim("-".padEnd(10)) + chalk25.dim("-".padEnd(10)) + chalk25.dim("-")
3111
3415
  );
3112
3416
  continue;
3113
3417
  }
3114
- const statusColor = pm2Process.status === "online" ? chalk24.green : chalk24.red;
3418
+ const statusColor = pm2Process.status === "online" ? chalk25.green : chalk25.red;
3115
3419
  const status = statusColor(pm2Process.status.padEnd(10));
3116
3420
  const cpu = `${pm2Process.cpu.toFixed(1)}%`.padEnd(8);
3117
3421
  const mem = formatBytes2(pm2Process.memory).padEnd(10);
@@ -3120,19 +3424,19 @@ async function statsCommand() {
3120
3424
  totalCpu += pm2Process.cpu;
3121
3425
  totalMem += pm2Process.memory;
3122
3426
  console.log(
3123
- " " + chalk24.white(name) + status + (pm2Process.cpu > 50 ? chalk24.yellow(cpu) : chalk24.dim(cpu)) + (pm2Process.memory > 500 * 1024 * 1024 ? chalk24.yellow(mem) : chalk24.dim(mem)) + chalk24.dim(uptime) + (pm2Process.restarts > 0 ? chalk24.yellow(restarts) : chalk24.dim(restarts))
3427
+ " " + chalk25.white(name) + status + (pm2Process.cpu > 50 ? chalk25.yellow(cpu) : chalk25.dim(cpu)) + (pm2Process.memory > 500 * 1024 * 1024 ? chalk25.yellow(mem) : chalk25.dim(mem)) + chalk25.dim(uptime) + (pm2Process.restarts > 0 ? chalk25.yellow(restarts) : chalk25.dim(restarts))
3124
3428
  );
3125
3429
  }
3126
3430
  const runningCount = pm2Processes.filter((p) => p.name.startsWith("bindler:") && p.status === "online").length;
3127
- console.log(chalk24.dim(" " + "-".repeat(70)));
3431
+ console.log(chalk25.dim(" " + "-".repeat(70)));
3128
3432
  console.log(
3129
- " " + chalk24.bold("TOTAL".padEnd(20)) + chalk24.dim(`${runningCount}/${npmProjects.length}`.padEnd(10)) + chalk24.dim(`${totalCpu.toFixed(1)}%`.padEnd(8)) + chalk24.dim(formatBytes2(totalMem).padEnd(10))
3433
+ " " + chalk25.bold("TOTAL".padEnd(20)) + chalk25.dim(`${runningCount}/${npmProjects.length}`.padEnd(10)) + chalk25.dim(`${totalCpu.toFixed(1)}%`.padEnd(8)) + chalk25.dim(formatBytes2(totalMem).padEnd(10))
3130
3434
  );
3131
3435
  console.log("");
3132
3436
  }
3133
3437
 
3134
3438
  // src/commands/completion.ts
3135
- import chalk25 from "chalk";
3439
+ import chalk26 from "chalk";
3136
3440
  var BASH_COMPLETION = `
3137
3441
  # bindler bash completion
3138
3442
  _bindler_completions() {
@@ -3265,12 +3569,12 @@ complete -c bindler -n '__fish_seen_subcommand_from tunnel' -a 'status start sto
3265
3569
  `;
3266
3570
  async function completionCommand(shell) {
3267
3571
  if (!shell) {
3268
- console.log(chalk25.red("Usage: bindler completion <shell>"));
3269
- console.log(chalk25.dim("\nSupported shells: bash, zsh, fish"));
3270
- console.log(chalk25.dim("\nSetup:"));
3271
- console.log(chalk25.dim(" bash: bindler completion bash >> ~/.bashrc"));
3272
- console.log(chalk25.dim(" zsh: bindler completion zsh >> ~/.zshrc"));
3273
- console.log(chalk25.dim(" fish: bindler completion fish > ~/.config/fish/completions/bindler.fish"));
3572
+ console.log(chalk26.red("Usage: bindler completion <shell>"));
3573
+ console.log(chalk26.dim("\nSupported shells: bash, zsh, fish"));
3574
+ console.log(chalk26.dim("\nSetup:"));
3575
+ console.log(chalk26.dim(" bash: bindler completion bash >> ~/.bashrc"));
3576
+ console.log(chalk26.dim(" zsh: bindler completion zsh >> ~/.zshrc"));
3577
+ console.log(chalk26.dim(" fish: bindler completion fish > ~/.config/fish/completions/bindler.fish"));
3274
3578
  process.exit(1);
3275
3579
  }
3276
3580
  switch (shell) {
@@ -3284,32 +3588,32 @@ async function completionCommand(shell) {
3284
3588
  console.log(FISH_COMPLETION.trim());
3285
3589
  break;
3286
3590
  default:
3287
- console.log(chalk25.red(`Unknown shell: ${shell}`));
3288
- console.log(chalk25.dim("Supported: bash, zsh, fish"));
3591
+ console.log(chalk26.red(`Unknown shell: ${shell}`));
3592
+ console.log(chalk26.dim("Supported: bash, zsh, fish"));
3289
3593
  process.exit(1);
3290
3594
  }
3291
3595
  }
3292
3596
 
3293
3597
  // src/commands/clone.ts
3294
- import chalk26 from "chalk";
3598
+ import chalk27 from "chalk";
3295
3599
  import inquirer5 from "inquirer";
3296
3600
  async function cloneCommand(source, newName, options) {
3297
3601
  if (!source) {
3298
- console.log(chalk26.red("Usage: bindler clone <source> <new-name>"));
3299
- console.log(chalk26.dim("\nClones a project configuration with a new name"));
3300
- console.log(chalk26.dim("\nExamples:"));
3301
- console.log(chalk26.dim(" bindler clone myapp myapp-staging"));
3302
- console.log(chalk26.dim(" bindler clone myapp myapp-v2 --hostname newapp.example.com"));
3303
- console.log(chalk26.dim(" bindler clone myapp myapp-copy --path /var/www/newapp"));
3602
+ console.log(chalk27.red("Usage: bindler clone <source> <new-name>"));
3603
+ console.log(chalk27.dim("\nClones a project configuration with a new name"));
3604
+ console.log(chalk27.dim("\nExamples:"));
3605
+ console.log(chalk27.dim(" bindler clone myapp myapp-staging"));
3606
+ console.log(chalk27.dim(" bindler clone myapp myapp-v2 --hostname newapp.example.com"));
3607
+ console.log(chalk27.dim(" bindler clone myapp myapp-copy --path /var/www/newapp"));
3304
3608
  process.exit(1);
3305
3609
  }
3306
3610
  const sourceProject = getProject(source);
3307
3611
  if (!sourceProject) {
3308
- console.log(chalk26.red(`Project "${source}" not found.`));
3309
- console.log(chalk26.dim("\nAvailable projects:"));
3612
+ console.log(chalk27.red(`Project "${source}" not found.`));
3613
+ console.log(chalk27.dim("\nAvailable projects:"));
3310
3614
  const projects = listProjects();
3311
3615
  for (const p of projects) {
3312
- console.log(chalk26.dim(` - ${p.name}`));
3616
+ console.log(chalk27.dim(` - ${p.name}`));
3313
3617
  }
3314
3618
  process.exit(1);
3315
3619
  }
@@ -3335,11 +3639,11 @@ async function cloneCommand(source, newName, options) {
3335
3639
  targetName = answer.name;
3336
3640
  } else {
3337
3641
  if (!validateProjectName(targetName)) {
3338
- console.log(chalk26.red("Invalid project name. Use alphanumeric characters, dashes, and underscores."));
3642
+ console.log(chalk27.red("Invalid project name. Use alphanumeric characters, dashes, and underscores."));
3339
3643
  process.exit(1);
3340
3644
  }
3341
3645
  if (getProject(targetName)) {
3342
- console.log(chalk26.red(`Project "${targetName}" already exists.`));
3646
+ console.log(chalk27.red(`Project "${targetName}" already exists.`));
3343
3647
  process.exit(1);
3344
3648
  }
3345
3649
  }
@@ -3362,7 +3666,7 @@ async function cloneCommand(source, newName, options) {
3362
3666
  targetHostname = answer.hostname;
3363
3667
  } else {
3364
3668
  if (!validateHostname(targetHostname)) {
3365
- console.log(chalk26.red("Invalid hostname format."));
3669
+ console.log(chalk27.red("Invalid hostname format."));
3366
3670
  process.exit(1);
3367
3671
  }
3368
3672
  }
@@ -3380,36 +3684,36 @@ async function cloneCommand(source, newName, options) {
3380
3684
  }
3381
3685
  try {
3382
3686
  addProject(newProject);
3383
- console.log(chalk26.green(`
3687
+ console.log(chalk27.green(`
3384
3688
  Project "${targetName}" cloned from "${source}"!
3385
3689
  `));
3386
- console.log(chalk26.dim("Configuration:"));
3387
- console.log(chalk26.dim(` Name: ${newProject.name}`));
3388
- console.log(chalk26.dim(` Type: ${newProject.type}`));
3389
- console.log(chalk26.dim(` Path: ${newProject.path}`));
3390
- console.log(chalk26.dim(` Hostname: ${newProject.hostname}`));
3690
+ console.log(chalk27.dim("Configuration:"));
3691
+ console.log(chalk27.dim(` Name: ${newProject.name}`));
3692
+ console.log(chalk27.dim(` Type: ${newProject.type}`));
3693
+ console.log(chalk27.dim(` Path: ${newProject.path}`));
3694
+ console.log(chalk27.dim(` Hostname: ${newProject.hostname}`));
3391
3695
  if (newProject.port) {
3392
- console.log(chalk26.dim(` Port: ${newProject.port}`));
3696
+ console.log(chalk27.dim(` Port: ${newProject.port}`));
3393
3697
  }
3394
- console.log(chalk26.dim(`
3395
- Run ${chalk26.cyan("sudo bindler apply")} to update nginx configuration.`));
3698
+ console.log(chalk27.dim(`
3699
+ Run ${chalk27.cyan("sudo bindler apply")} to update nginx configuration.`));
3396
3700
  if (newProject.type === "npm") {
3397
- console.log(chalk26.dim(`Run ${chalk26.cyan(`bindler start ${targetName}`)} to start the application.`));
3701
+ console.log(chalk27.dim(`Run ${chalk27.cyan(`bindler start ${targetName}`)} to start the application.`));
3398
3702
  }
3399
3703
  } catch (error) {
3400
- console.error(chalk26.red(`Error: ${error instanceof Error ? error.message : error}`));
3704
+ console.error(chalk27.red(`Error: ${error instanceof Error ? error.message : error}`));
3401
3705
  process.exit(1);
3402
3706
  }
3403
3707
  }
3404
3708
 
3405
3709
  // src/commands/dev.ts
3406
- import chalk27 from "chalk";
3710
+ import chalk28 from "chalk";
3407
3711
  import { spawn as spawn3 } from "child_process";
3408
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
3409
- import { basename as basename3, join as join7 } from "path";
3712
+ import { existsSync as existsSync13, readFileSync as readFileSync7 } from "fs";
3713
+ import { basename as basename3, join as join8 } from "path";
3410
3714
  function getPackageJson(dir) {
3411
- const pkgPath = join7(dir, "package.json");
3412
- if (!existsSync11(pkgPath)) return null;
3715
+ const pkgPath = join8(dir, "package.json");
3716
+ if (!existsSync13(pkgPath)) return null;
3413
3717
  try {
3414
3718
  return JSON.parse(readFileSync7(pkgPath, "utf-8"));
3415
3719
  } catch {
@@ -3433,11 +3737,11 @@ async function devCommand(name, options) {
3433
3737
  if (name) {
3434
3738
  project = getProject(name);
3435
3739
  if (!project) {
3436
- console.log(chalk27.red(`Project "${name}" not found.`));
3437
- console.log(chalk27.dim("\nAvailable projects:"));
3740
+ console.log(chalk28.red(`Project "${name}" not found.`));
3741
+ console.log(chalk28.dim("\nAvailable projects:"));
3438
3742
  const projects = listProjects();
3439
3743
  for (const p of projects) {
3440
- console.log(chalk27.dim(` - ${p.name}`));
3744
+ console.log(chalk28.dim(` - ${p.name}`));
3441
3745
  }
3442
3746
  process.exit(1);
3443
3747
  }
@@ -3448,7 +3752,7 @@ async function devCommand(name, options) {
3448
3752
  if (!project) {
3449
3753
  const yamlConfig = readBindlerYaml(cwd);
3450
3754
  if (yamlConfig) {
3451
- console.log(chalk27.cyan("Found bindler.yaml - creating temporary dev project\n"));
3755
+ console.log(chalk28.cyan("Found bindler.yaml - creating temporary dev project\n"));
3452
3756
  const yamlProject = yamlToProject(yamlConfig, cwd);
3453
3757
  project = {
3454
3758
  name: yamlProject.name || basename3(cwd),
@@ -3462,9 +3766,9 @@ async function devCommand(name, options) {
3462
3766
  } else {
3463
3767
  const pkg = getPackageJson(cwd);
3464
3768
  if (!pkg) {
3465
- console.log(chalk27.red("No package.json found in current directory."));
3466
- console.log(chalk27.dim("\nUsage: bindler dev [name]"));
3467
- console.log(chalk27.dim(" Run in a project directory or specify a project name"));
3769
+ console.log(chalk28.red("No package.json found in current directory."));
3770
+ console.log(chalk28.dim("\nUsage: bindler dev [name]"));
3771
+ console.log(chalk28.dim(" Run in a project directory or specify a project name"));
3468
3772
  process.exit(1);
3469
3773
  }
3470
3774
  project = {
@@ -3480,35 +3784,35 @@ async function devCommand(name, options) {
3480
3784
  projectDir = cwd;
3481
3785
  }
3482
3786
  if (project.type !== "npm") {
3483
- console.log(chalk27.red("Dev mode is only supported for npm projects."));
3484
- console.log(chalk27.dim("\nFor static projects, use a local web server:"));
3485
- console.log(chalk27.dim(" npx serve " + project.path));
3787
+ console.log(chalk28.red("Dev mode is only supported for npm projects."));
3788
+ console.log(chalk28.dim("\nFor static projects, use a local web server:"));
3789
+ console.log(chalk28.dim(" npx serve " + project.path));
3486
3790
  process.exit(1);
3487
3791
  }
3488
- if (!existsSync11(projectDir)) {
3489
- console.log(chalk27.red(`Project directory not found: ${projectDir}`));
3792
+ if (!existsSync13(projectDir)) {
3793
+ console.log(chalk28.red(`Project directory not found: ${projectDir}`));
3490
3794
  process.exit(1);
3491
3795
  }
3492
3796
  const devCmd = getDevCommand(projectDir) || project.start || "npm start";
3493
3797
  const port = options.port || project.port || findAvailablePort();
3494
- console.log(chalk27.blue(`
3798
+ console.log(chalk28.blue(`
3495
3799
  Starting ${project.name} in dev mode...
3496
3800
  `));
3497
- console.log(chalk27.dim(` Directory: ${projectDir}`));
3498
- console.log(chalk27.dim(` Command: ${devCmd}`));
3499
- console.log(chalk27.dim(` Port: ${port}`));
3500
- console.log(chalk27.dim(` Hostname: ${project.hostname}`));
3801
+ console.log(chalk28.dim(` Directory: ${projectDir}`));
3802
+ console.log(chalk28.dim(` Command: ${devCmd}`));
3803
+ console.log(chalk28.dim(` Port: ${port}`));
3804
+ console.log(chalk28.dim(` Hostname: ${project.hostname}`));
3501
3805
  if (project.hostname.endsWith(".local") || project.local) {
3502
- console.log(chalk27.yellow(`
3806
+ console.log(chalk28.yellow(`
3503
3807
  Note: Add to /etc/hosts if not already:`));
3504
- console.log(chalk27.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
3808
+ console.log(chalk28.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
3505
3809
  }
3506
3810
  const defaults = getDefaults();
3507
3811
  const listenPort = defaults.nginxListen.split(":")[1] || "8080";
3508
- console.log(chalk27.green(`
3812
+ console.log(chalk28.green(`
3509
3813
  Access at: http://${project.hostname}:${listenPort}`));
3510
- console.log(chalk27.dim("Press Ctrl+C to stop\n"));
3511
- console.log(chalk27.dim("---"));
3814
+ console.log(chalk28.dim("Press Ctrl+C to stop\n"));
3815
+ console.log(chalk28.dim("---"));
3512
3816
  const env = {
3513
3817
  ...process.env,
3514
3818
  PORT: String(port),
@@ -3522,19 +3826,19 @@ Access at: http://${project.hostname}:${listenPort}`));
3522
3826
  shell: true
3523
3827
  });
3524
3828
  child.on("error", (error) => {
3525
- console.error(chalk27.red(`
3829
+ console.error(chalk28.red(`
3526
3830
  Failed to start: ${error.message}`));
3527
3831
  process.exit(1);
3528
3832
  });
3529
3833
  child.on("exit", (code) => {
3530
3834
  if (code !== 0) {
3531
- console.log(chalk27.yellow(`
3835
+ console.log(chalk28.yellow(`
3532
3836
  Process exited with code ${code}`));
3533
3837
  }
3534
3838
  process.exit(code || 0);
3535
3839
  });
3536
3840
  process.on("SIGINT", () => {
3537
- console.log(chalk27.dim("\n\nStopping dev server..."));
3841
+ console.log(chalk28.dim("\n\nStopping dev server..."));
3538
3842
  child.kill("SIGINT");
3539
3843
  });
3540
3844
  process.on("SIGTERM", () => {
@@ -3543,16 +3847,16 @@ Process exited with code ${code}`));
3543
3847
  }
3544
3848
 
3545
3849
  // src/lib/update-check.ts
3546
- import chalk28 from "chalk";
3547
- import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
3548
- import { join as join8 } from "path";
3549
- import { homedir as homedir3 } from "os";
3850
+ import chalk29 from "chalk";
3851
+ import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
3852
+ import { join as join9 } from "path";
3853
+ import { homedir as homedir4 } from "os";
3550
3854
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
3551
- var CACHE_DIR = join8(homedir3(), ".config", "bindler");
3552
- var CACHE_FILE = join8(CACHE_DIR, ".update-check");
3855
+ var CACHE_DIR = join9(homedir4(), ".config", "bindler");
3856
+ var CACHE_FILE = join9(CACHE_DIR, ".update-check");
3553
3857
  function readCache() {
3554
3858
  try {
3555
- if (existsSync12(CACHE_FILE)) {
3859
+ if (existsSync14(CACHE_FILE)) {
3556
3860
  return JSON.parse(readFileSync8(CACHE_FILE, "utf-8"));
3557
3861
  }
3558
3862
  } catch {
@@ -3561,7 +3865,7 @@ function readCache() {
3561
3865
  }
3562
3866
  function writeCache(data) {
3563
3867
  try {
3564
- if (!existsSync12(CACHE_DIR)) {
3868
+ if (!existsSync14(CACHE_DIR)) {
3565
3869
  mkdirSync5(CACHE_DIR, { recursive: true });
3566
3870
  }
3567
3871
  writeFileSync6(CACHE_FILE, JSON.stringify(data));
@@ -3596,28 +3900,28 @@ async function checkForUpdates() {
3596
3900
  const cache = readCache();
3597
3901
  const now = Date.now();
3598
3902
  if (now - cache.lastCheck < CHECK_INTERVAL) {
3599
- if (cache.latestVersion && compareVersions("1.2.0", cache.latestVersion) < 0) {
3903
+ if (cache.latestVersion && compareVersions("1.4.1", cache.latestVersion) < 0) {
3600
3904
  showUpdateMessage(cache.latestVersion);
3601
3905
  }
3602
3906
  return;
3603
3907
  }
3604
3908
  fetchLatestVersion().then((latestVersion) => {
3605
3909
  writeCache({ lastCheck: now, latestVersion });
3606
- if (latestVersion && compareVersions("1.2.0", latestVersion) < 0) {
3910
+ if (latestVersion && compareVersions("1.4.1", latestVersion) < 0) {
3607
3911
  showUpdateMessage(latestVersion);
3608
3912
  }
3609
3913
  });
3610
3914
  }
3611
3915
  function showUpdateMessage(latestVersion) {
3612
3916
  console.log("");
3613
- console.log(chalk28.yellow(` Update available: ${"1.2.0"} \u2192 ${latestVersion}`));
3614
- console.log(chalk28.dim(` Run: npm update -g bindler`));
3917
+ console.log(chalk29.yellow(` Update available: ${"1.4.1"} \u2192 ${latestVersion}`));
3918
+ console.log(chalk29.dim(` Run: npm update -g bindler`));
3615
3919
  console.log("");
3616
3920
  }
3617
3921
 
3618
3922
  // src/cli.ts
3619
3923
  var program = new Command();
3620
- program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.2.0");
3924
+ program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.4.1");
3621
3925
  program.hook("preAction", async () => {
3622
3926
  try {
3623
3927
  initConfig();
@@ -3636,78 +3940,78 @@ program.command("status").description("Show detailed status of all projects").ac
3636
3940
  });
3637
3941
  program.command("start [name]").description("Start an npm project with PM2").option("-a, --all", "Start all npm projects").action(async (name, options) => {
3638
3942
  if (!name && !options.all) {
3639
- console.log(chalk29.red("Usage: bindler start <name> or bindler start --all"));
3640
- console.log(chalk29.dim("\nExamples:"));
3641
- console.log(chalk29.dim(" bindler start myapp"));
3642
- console.log(chalk29.dim(" bindler start --all # start all npm projects"));
3943
+ console.log(chalk30.red("Usage: bindler start <name> or bindler start --all"));
3944
+ console.log(chalk30.dim("\nExamples:"));
3945
+ console.log(chalk30.dim(" bindler start myapp"));
3946
+ console.log(chalk30.dim(" bindler start --all # start all npm projects"));
3643
3947
  process.exit(1);
3644
3948
  }
3645
3949
  await startCommand(name, options);
3646
3950
  });
3647
3951
  program.command("stop [name]").description("Stop an npm project").option("-a, --all", "Stop all npm projects").action(async (name, options) => {
3648
3952
  if (!name && !options.all) {
3649
- console.log(chalk29.red("Usage: bindler stop <name> or bindler stop --all"));
3650
- console.log(chalk29.dim("\nExamples:"));
3651
- console.log(chalk29.dim(" bindler stop myapp"));
3652
- console.log(chalk29.dim(" bindler stop --all # stop all npm projects"));
3953
+ console.log(chalk30.red("Usage: bindler stop <name> or bindler stop --all"));
3954
+ console.log(chalk30.dim("\nExamples:"));
3955
+ console.log(chalk30.dim(" bindler stop myapp"));
3956
+ console.log(chalk30.dim(" bindler stop --all # stop all npm projects"));
3653
3957
  process.exit(1);
3654
3958
  }
3655
3959
  await stopCommand(name, options);
3656
3960
  });
3657
3961
  program.command("restart [name]").description("Restart an npm project").option("-a, --all", "Restart all npm projects").action(async (name, options) => {
3658
3962
  if (!name && !options.all) {
3659
- console.log(chalk29.red("Usage: bindler restart <name> or bindler restart --all"));
3660
- console.log(chalk29.dim("\nExamples:"));
3661
- console.log(chalk29.dim(" bindler restart myapp"));
3662
- console.log(chalk29.dim(" bindler restart --all # restart all npm projects"));
3963
+ console.log(chalk30.red("Usage: bindler restart <name> or bindler restart --all"));
3964
+ console.log(chalk30.dim("\nExamples:"));
3965
+ console.log(chalk30.dim(" bindler restart myapp"));
3966
+ console.log(chalk30.dim(" bindler restart --all # restart all npm projects"));
3663
3967
  process.exit(1);
3664
3968
  }
3665
3969
  await restartCommand(name, options);
3666
3970
  });
3667
3971
  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) => {
3668
3972
  if (!name) {
3669
- console.log(chalk29.red("Usage: bindler logs <name>"));
3670
- console.log(chalk29.dim("\nExamples:"));
3671
- console.log(chalk29.dim(" bindler logs myapp"));
3672
- console.log(chalk29.dim(" bindler logs myapp --follow"));
3673
- console.log(chalk29.dim(" bindler logs myapp --lines 500"));
3973
+ console.log(chalk30.red("Usage: bindler logs <name>"));
3974
+ console.log(chalk30.dim("\nExamples:"));
3975
+ console.log(chalk30.dim(" bindler logs myapp"));
3976
+ console.log(chalk30.dim(" bindler logs myapp --follow"));
3977
+ console.log(chalk30.dim(" bindler logs myapp --lines 500"));
3674
3978
  process.exit(1);
3675
3979
  }
3676
3980
  await logsCommand(name, { ...options, lines: parseInt(options.lines, 10) });
3677
3981
  });
3678
3982
  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) => {
3679
3983
  if (!name) {
3680
- console.log(chalk29.red("Usage: bindler update <name> [options]"));
3681
- console.log(chalk29.dim("\nExamples:"));
3682
- console.log(chalk29.dim(" bindler update myapp --hostname newapp.example.com"));
3683
- console.log(chalk29.dim(" bindler update myapp --port 4000"));
3684
- console.log(chalk29.dim(" bindler update myapp --disable"));
3984
+ console.log(chalk30.red("Usage: bindler update <name> [options]"));
3985
+ console.log(chalk30.dim("\nExamples:"));
3986
+ console.log(chalk30.dim(" bindler update myapp --hostname newapp.example.com"));
3987
+ console.log(chalk30.dim(" bindler update myapp --port 4000"));
3988
+ console.log(chalk30.dim(" bindler update myapp --disable"));
3685
3989
  process.exit(1);
3686
3990
  }
3687
3991
  await updateCommand(name, options);
3688
3992
  });
3689
3993
  program.command("edit [name]").description("Edit project configuration in $EDITOR").action(async (name) => {
3690
3994
  if (!name) {
3691
- console.log(chalk29.red("Usage: bindler edit <name>"));
3692
- console.log(chalk29.dim("\nOpens the project config in your $EDITOR"));
3693
- console.log(chalk29.dim("\nExample:"));
3694
- console.log(chalk29.dim(" bindler edit myapp"));
3995
+ console.log(chalk30.red("Usage: bindler edit <name>"));
3996
+ console.log(chalk30.dim("\nOpens the project config in your $EDITOR"));
3997
+ console.log(chalk30.dim("\nExample:"));
3998
+ console.log(chalk30.dim(" bindler edit myapp"));
3695
3999
  process.exit(1);
3696
4000
  }
3697
4001
  await editCommand(name);
3698
4002
  });
3699
4003
  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) => {
3700
4004
  if (!name) {
3701
- console.log(chalk29.red("Usage: bindler remove <name>"));
3702
- console.log(chalk29.dim("\nExamples:"));
3703
- console.log(chalk29.dim(" bindler remove myapp"));
3704
- console.log(chalk29.dim(" bindler remove myapp --force # skip confirmation"));
3705
- console.log(chalk29.dim(" bindler rm myapp # alias"));
4005
+ console.log(chalk30.red("Usage: bindler remove <name>"));
4006
+ console.log(chalk30.dim("\nExamples:"));
4007
+ console.log(chalk30.dim(" bindler remove myapp"));
4008
+ console.log(chalk30.dim(" bindler remove myapp --force # skip confirmation"));
4009
+ console.log(chalk30.dim(" bindler rm myapp # alias"));
3706
4010
  process.exit(1);
3707
4011
  }
3708
4012
  await removeCommand(name, options);
3709
4013
  });
3710
- program.command("apply").description("Generate and apply nginx configuration + Cloudflare DNS routes").option("-d, --dry-run", "Print config without applying").option("--no-reload", "Write config but do not reload nginx").option("--no-cloudflare", "Skip Cloudflare DNS route configuration").option("--no-ssl", "Skip SSL certificate setup (direct mode)").option("--sync", "Sync bindler.yaml from project directories before applying").option("-e, --env <env>", "Use environment-specific config (staging, production)").action(async (options) => {
4014
+ program.command("apply").description("Generate and apply nginx configuration + Cloudflare DNS routes").option("-d, --dry-run", "Print config without applying").option("--no-reload", "Write config but do not reload nginx").option("--no-cloudflare", "Skip Cloudflare DNS route configuration").option("--no-ssl", "Skip SSL certificate setup (direct mode)").option("--sync", "Sync bindler.yaml from project directories before applying").option("-e, --env <env>", "Use environment-specific config (staging, production)").option("--skip-checks", "Skip preflight validation checks (not recommended)").action(async (options) => {
3711
4015
  await applyCommand(options);
3712
4016
  });
3713
4017
  program.command("doctor").description("Run system diagnostics and check dependencies").action(async () => {
@@ -3721,10 +4025,10 @@ program.command("info").description("Show bindler information and stats").action
3721
4025
  });
3722
4026
  program.command("check [hostname]").description("Check DNS propagation and HTTP accessibility for a hostname").option("-v, --verbose", "Show verbose output").action(async (hostname, options) => {
3723
4027
  if (!hostname) {
3724
- console.log(chalk29.red("Usage: bindler check <hostname>"));
3725
- console.log(chalk29.dim("\nExamples:"));
3726
- console.log(chalk29.dim(" bindler check myapp.example.com"));
3727
- console.log(chalk29.dim(" bindler check myapp # uses project name"));
4028
+ console.log(chalk30.red("Usage: bindler check <hostname>"));
4029
+ console.log(chalk30.dim("\nExamples:"));
4030
+ console.log(chalk30.dim(" bindler check myapp.example.com"));
4031
+ console.log(chalk30.dim(" bindler check myapp # uses project name"));
3728
4032
  process.exit(1);
3729
4033
  }
3730
4034
  await checkCommand(hostname, options);