bindler 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,34 @@ 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
+ console.log(chalk11.dim(`
1653
+ Run ${chalk11.cyan("sudo bindler apply")} to update nginx configuration.`));
1654
+ console.log(chalk11.yellow("\nNote: The project files and Cloudflare DNS routes were not removed."));
1655
+ console.log(chalk11.dim(` Project path: ${project.path}`));
1656
+ console.log(chalk11.dim(` To remove DNS route manually: cloudflared tunnel route dns --remove ${project.hostname}`));
1453
1657
  } catch (error) {
1454
- console.error(chalk10.red(`Error: ${error instanceof Error ? error.message : error}`));
1658
+ console.error(chalk11.red(`Error: ${error instanceof Error ? error.message : error}`));
1455
1659
  process.exit(1);
1456
1660
  }
1457
1661
  }
1458
1662
 
1459
1663
  // src/commands/apply.ts
1460
- import chalk11 from "chalk";
1461
- import { existsSync as existsSync6 } from "fs";
1664
+ import chalk12 from "chalk";
1665
+ import { existsSync as existsSync8 } from "fs";
1462
1666
 
1463
1667
  // src/lib/cloudflare.ts
1464
1668
  function isCloudflaredInstalled() {
@@ -1553,56 +1757,72 @@ async function applyCommand(options) {
1553
1757
  let config = readConfig();
1554
1758
  const defaults = getDefaults();
1555
1759
  if (options.sync) {
1556
- console.log(chalk11.dim("Syncing bindler.yaml from project directories...\n"));
1760
+ console.log(chalk12.dim("Syncing bindler.yaml from project directories...\n"));
1557
1761
  let synced = 0;
1558
1762
  for (const project of config.projects) {
1559
- if (!existsSync6(project.path)) continue;
1763
+ if (!existsSync8(project.path)) continue;
1560
1764
  const yamlConfig = readBindlerYaml(project.path);
1561
1765
  if (yamlConfig) {
1562
1766
  const merged = mergeYamlWithProject(project, yamlConfig);
1563
1767
  updateProject(project.name, merged);
1564
- console.log(chalk11.green(` \u2713 Synced ${project.name} from bindler.yaml`));
1768
+ console.log(chalk12.green(` \u2713 Synced ${project.name} from bindler.yaml`));
1565
1769
  synced++;
1566
1770
  }
1567
1771
  }
1568
1772
  if (synced === 0) {
1569
- console.log(chalk11.dim(" No bindler.yaml files found in project directories"));
1773
+ console.log(chalk12.dim(" No bindler.yaml files found in project directories"));
1570
1774
  } else {
1571
- console.log(chalk11.dim(`
1775
+ console.log(chalk12.dim(`
1572
1776
  Synced ${synced} project(s)
1573
1777
  `));
1574
1778
  }
1575
1779
  config = readConfig();
1576
1780
  }
1577
1781
  if (options.env) {
1578
- console.log(chalk11.dim(`Using ${options.env} environment configuration...
1782
+ console.log(chalk12.dim(`Using ${options.env} environment configuration...
1579
1783
  `));
1580
1784
  const envProjects = listProjectsForEnv(options.env);
1581
1785
  config = { ...config, projects: envProjects };
1582
1786
  }
1583
1787
  if (config.projects.length === 0) {
1584
- console.log(chalk11.yellow("No projects registered. Nothing to apply."));
1788
+ console.log(chalk12.yellow("No projects registered. Nothing to apply."));
1585
1789
  return;
1586
1790
  }
1587
- console.log(chalk11.blue("Applying configuration...\n"));
1588
- console.log(chalk11.dim("Generating nginx configuration..."));
1791
+ console.log(chalk12.blue("Applying configuration...\n"));
1792
+ if (!options.skipChecks) {
1793
+ console.log(chalk12.dim("Running preflight checks..."));
1794
+ const checkResult = runPreflightChecks(config);
1795
+ if (!checkResult.valid) {
1796
+ printValidationResult(checkResult);
1797
+ console.log(chalk12.red("\n\u2717 Preflight checks failed. Fix the errors above before applying."));
1798
+ console.log(chalk12.dim(" Use --skip-checks to bypass (not recommended)"));
1799
+ process.exit(1);
1800
+ }
1801
+ if (checkResult.warnings.length > 0) {
1802
+ printValidationResult(checkResult);
1803
+ console.log("");
1804
+ } else {
1805
+ console.log(chalk12.green(" \u2713 Preflight checks passed"));
1806
+ }
1807
+ }
1808
+ console.log(chalk12.dim("Generating nginx configuration..."));
1589
1809
  if (options.dryRun) {
1590
1810
  const nginxConfig = generateNginxConfig(config);
1591
- console.log(chalk11.cyan("\n--- Generated nginx config (dry-run) ---\n"));
1811
+ console.log(chalk12.cyan("\n--- Generated nginx config (dry-run) ---\n"));
1592
1812
  console.log(nginxConfig);
1593
- console.log(chalk11.cyan("--- End of config ---\n"));
1594
- console.log(chalk11.yellow("Dry run mode - no changes were made."));
1813
+ console.log(chalk12.cyan("--- End of config ---\n"));
1814
+ console.log(chalk12.yellow("Dry run mode - no changes were made."));
1595
1815
  return;
1596
1816
  }
1597
1817
  try {
1598
1818
  const { path, content } = writeNginxConfig(config);
1599
- console.log(chalk11.green(` \u2713 Wrote nginx config to ${path}`));
1819
+ console.log(chalk12.green(` \u2713 Wrote nginx config to ${path}`));
1600
1820
  } catch (error) {
1601
1821
  const errMsg = error instanceof Error ? error.message : String(error);
1602
- console.error(chalk11.red(` \u2717 Failed to write nginx config: ${errMsg}`));
1822
+ console.error(chalk12.red(` \u2717 Failed to write nginx config: ${errMsg}`));
1603
1823
  if (errMsg.includes("EACCES") || errMsg.includes("permission denied")) {
1604
- console.log(chalk11.yellow(`
1605
- Try running with sudo: ${chalk11.cyan("sudo bindler apply")}`));
1824
+ console.log(chalk12.yellow(`
1825
+ Try running with sudo: ${chalk12.cyan("sudo bindler apply")}`));
1606
1826
  }
1607
1827
  process.exit(1);
1608
1828
  }
@@ -1610,106 +1830,106 @@ Try running with sudo: ${chalk11.cyan("sudo bindler apply")}`));
1610
1830
  (p) => p.security?.basicAuth?.enabled && p.security.basicAuth.users?.length
1611
1831
  );
1612
1832
  if (authProjects.length > 0) {
1613
- console.log(chalk11.dim("Generating htpasswd files..."));
1833
+ console.log(chalk12.dim("Generating htpasswd files..."));
1614
1834
  try {
1615
1835
  generateHtpasswdFiles(config.projects);
1616
- console.log(chalk11.green(` \u2713 Generated htpasswd files for ${authProjects.length} project(s)`));
1836
+ console.log(chalk12.green(` \u2713 Generated htpasswd files for ${authProjects.length} project(s)`));
1617
1837
  } catch (error) {
1618
- console.log(chalk11.yellow(` ! Failed to generate htpasswd files: ${error}`));
1838
+ console.log(chalk12.yellow(` ! Failed to generate htpasswd files: ${error}`));
1619
1839
  }
1620
1840
  }
1621
- console.log(chalk11.dim("Testing nginx configuration..."));
1841
+ console.log(chalk12.dim("Testing nginx configuration..."));
1622
1842
  const testResult = testNginxConfig();
1623
1843
  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."));
1844
+ console.error(chalk12.red(" \u2717 Nginx configuration test failed:"));
1845
+ console.error(chalk12.red(testResult.output));
1846
+ console.log(chalk12.yellow("\nConfiguration was written but nginx was NOT reloaded."));
1847
+ console.log(chalk12.dim("Fix the configuration and run `sudo bindler apply` again."));
1628
1848
  process.exit(1);
1629
1849
  }
1630
- console.log(chalk11.green(" \u2713 Nginx configuration test passed"));
1850
+ console.log(chalk12.green(" \u2713 Nginx configuration test passed"));
1631
1851
  if (!options.noReload) {
1632
- console.log(chalk11.dim("Reloading nginx..."));
1852
+ console.log(chalk12.dim("Reloading nginx..."));
1633
1853
  const reloadResult = reloadNginx();
1634
1854
  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"));
1855
+ console.error(chalk12.red(` \u2717 Failed to reload nginx: ${reloadResult.error}`));
1856
+ console.log(chalk12.dim("You may need to reload nginx manually: sudo systemctl reload nginx"));
1637
1857
  process.exit(1);
1638
1858
  }
1639
- console.log(chalk11.green(" \u2713 Nginx reloaded successfully"));
1859
+ console.log(chalk12.green(" \u2713 Nginx reloaded successfully"));
1640
1860
  } else {
1641
- console.log(chalk11.yellow(" - Skipped nginx reload (--no-reload)"));
1861
+ console.log(chalk12.yellow(" - Skipped nginx reload (--no-reload)"));
1642
1862
  }
1643
1863
  const isDirectMode = defaults.mode === "direct";
1644
1864
  if (isDirectMode) {
1645
- console.log(chalk11.dim("\n - Direct mode: skipping Cloudflare DNS routes"));
1865
+ console.log(chalk12.dim("\n - Direct mode: skipping Cloudflare DNS routes"));
1646
1866
  } else if (!options.noCloudflare && defaults.applyCloudflareDnsRoutes) {
1647
- console.log(chalk11.dim("\nConfiguring Cloudflare DNS routes..."));
1867
+ console.log(chalk12.dim("\nConfiguring Cloudflare DNS routes..."));
1648
1868
  if (!isCloudflaredInstalled()) {
1649
- console.log(chalk11.yellow(" - cloudflared not installed, skipping DNS routes"));
1869
+ console.log(chalk12.yellow(" - cloudflared not installed, skipping DNS routes"));
1650
1870
  } else {
1651
1871
  const dnsResults = routeDnsForAllProjects();
1652
1872
  if (dnsResults.length === 0) {
1653
- console.log(chalk11.dim(" No hostnames to route"));
1873
+ console.log(chalk12.dim(" No hostnames to route"));
1654
1874
  } else {
1655
1875
  for (const result of dnsResults) {
1656
1876
  if (result.skipped) {
1657
- console.log(chalk11.dim(` - ${result.hostname} (local - skipped)`));
1877
+ console.log(chalk12.dim(` - ${result.hostname} (local - skipped)`));
1658
1878
  } else if (result.success) {
1659
1879
  const msg = result.output?.includes("already exists") ? "exists" : "routed";
1660
- console.log(chalk11.green(` \u2713 ${result.hostname} (${msg})`));
1880
+ console.log(chalk12.green(` \u2713 ${result.hostname} (${msg})`));
1661
1881
  } else {
1662
- console.log(chalk11.red(` \u2717 ${result.hostname}: ${result.error}`));
1882
+ console.log(chalk12.red(` \u2717 ${result.hostname}: ${result.error}`));
1663
1883
  }
1664
1884
  }
1665
1885
  }
1666
1886
  }
1667
1887
  } else if (options.noCloudflare) {
1668
- console.log(chalk11.dim("\n - Skipped Cloudflare DNS routes (--no-cloudflare)"));
1888
+ console.log(chalk12.dim("\n - Skipped Cloudflare DNS routes (--no-cloudflare)"));
1669
1889
  }
1670
1890
  if (isDirectMode && defaults.sslEnabled && options.ssl !== false) {
1671
- console.log(chalk11.dim("\nSetting up SSL certificates..."));
1891
+ console.log(chalk12.dim("\nSetting up SSL certificates..."));
1672
1892
  const hostnames = config.projects.filter((p) => p.enabled !== false && !p.local).map((p) => p.hostname);
1673
1893
  if (hostnames.length === 0) {
1674
- console.log(chalk11.dim(" No hostnames to secure"));
1894
+ console.log(chalk12.dim(" No hostnames to secure"));
1675
1895
  } else {
1676
1896
  const certbotResult = execCommandSafe("which certbot");
1677
1897
  if (!certbotResult.success) {
1678
- console.log(chalk11.yellow(" - certbot not installed, skipping SSL"));
1679
- console.log(chalk11.dim(" Run: bindler setup --direct"));
1898
+ console.log(chalk12.yellow(" - certbot not installed, skipping SSL"));
1899
+ console.log(chalk12.dim(" Run: bindler setup --direct"));
1680
1900
  } else {
1681
1901
  for (const hostname of hostnames) {
1682
- console.log(chalk11.dim(` Requesting certificate for ${hostname}...`));
1902
+ console.log(chalk12.dim(` Requesting certificate for ${hostname}...`));
1683
1903
  const email = defaults.sslEmail || "admin@" + hostname.split(".").slice(-2).join(".");
1684
1904
  const result = execCommandSafe(
1685
1905
  `sudo certbot --nginx -d ${hostname} --non-interactive --agree-tos --email ${email} 2>&1`
1686
1906
  );
1687
1907
  if (result.success || result.output?.includes("Certificate not yet due for renewal")) {
1688
- console.log(chalk11.green(` \u2713 ${hostname} (secured)`));
1908
+ console.log(chalk12.green(` \u2713 ${hostname} (secured)`));
1689
1909
  } else if (result.output?.includes("already exists")) {
1690
- console.log(chalk11.green(` \u2713 ${hostname} (exists)`));
1910
+ console.log(chalk12.green(` \u2713 ${hostname} (exists)`));
1691
1911
  } else {
1692
- console.log(chalk11.yellow(` ! ${hostname}: ${result.error || "failed"}`));
1693
- console.log(chalk11.dim(" Run manually: sudo certbot --nginx -d " + hostname));
1912
+ console.log(chalk12.yellow(` ! ${hostname}: ${result.error || "failed"}`));
1913
+ console.log(chalk12.dim(" Run manually: sudo certbot --nginx -d " + hostname));
1694
1914
  }
1695
1915
  }
1696
1916
  }
1697
1917
  }
1698
1918
  }
1699
- console.log(chalk11.green("\n\u2713 Configuration applied successfully!"));
1700
- console.log(chalk11.dim(`
1919
+ console.log(chalk12.green("\n\u2713 Configuration applied successfully!"));
1920
+ console.log(chalk12.dim(`
1701
1921
  ${config.projects.length} project(s) configured:`));
1702
1922
  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})`));
1923
+ const status = project.enabled !== false ? chalk12.green("enabled") : chalk12.yellow("disabled");
1924
+ console.log(chalk12.dim(` - ${project.name} \u2192 ${project.hostname} (${status})`));
1705
1925
  }
1706
1926
  }
1707
1927
 
1708
1928
  // src/commands/doctor.ts
1709
- import chalk12 from "chalk";
1710
- import { existsSync as existsSync7 } from "fs";
1929
+ import chalk13 from "chalk";
1930
+ import { existsSync as existsSync9 } from "fs";
1711
1931
  async function doctorCommand() {
1712
- console.log(chalk12.blue("Running diagnostics...\n"));
1932
+ console.log(chalk13.blue("Running diagnostics...\n"));
1713
1933
  const checks = [];
1714
1934
  const nodeVersion = process.version;
1715
1935
  const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0], 10);
@@ -1845,7 +2065,7 @@ async function doctorCommand() {
1845
2065
  }
1846
2066
  }
1847
2067
  const defaults = getDefaults();
1848
- if (existsSync7(defaults.nginxManagedPath)) {
2068
+ if (existsSync9(defaults.nginxManagedPath)) {
1849
2069
  checks.push({
1850
2070
  name: "Nginx config file",
1851
2071
  status: "ok",
@@ -1859,7 +2079,7 @@ async function doctorCommand() {
1859
2079
  fix: "Run: sudo bindler apply"
1860
2080
  });
1861
2081
  }
1862
- if (existsSync7(defaults.projectsRoot)) {
2082
+ if (existsSync9(defaults.projectsRoot)) {
1863
2083
  checks.push({
1864
2084
  name: "Projects root",
1865
2085
  status: "ok",
@@ -1870,7 +2090,67 @@ async function doctorCommand() {
1870
2090
  name: "Projects root",
1871
2091
  status: "warning",
1872
2092
  message: `Not found at ${defaults.projectsRoot}`,
1873
- fix: `Create directory: sudo mkdir -p ${defaults.projectsRoot}`
2093
+ fix: `Create directory: mkdir -p ${defaults.projectsRoot}`
2094
+ });
2095
+ }
2096
+ const validation = validateConfig(config);
2097
+ if (validation.errors.length > 0) {
2098
+ for (const error of validation.errors) {
2099
+ checks.push({
2100
+ name: "Project validation",
2101
+ status: "error",
2102
+ message: error
2103
+ });
2104
+ }
2105
+ }
2106
+ if (validation.warnings.length > 0) {
2107
+ for (const warning of validation.warnings) {
2108
+ checks.push({
2109
+ name: "Project validation",
2110
+ status: "warning",
2111
+ message: warning
2112
+ });
2113
+ }
2114
+ }
2115
+ if (validation.valid && validation.warnings.length === 0) {
2116
+ checks.push({
2117
+ name: "Project validation",
2118
+ status: "ok",
2119
+ message: "All projects validated successfully"
2120
+ });
2121
+ }
2122
+ const localProjects = config.projects.filter((p) => p.local && p.enabled !== false);
2123
+ if (localProjects.length > 0) {
2124
+ const hostsResult = execCommandSafe("cat /etc/hosts");
2125
+ if (hostsResult.success) {
2126
+ const missingHosts = localProjects.filter(
2127
+ (p) => !hostsResult.output.includes(p.hostname)
2128
+ );
2129
+ if (missingHosts.length > 0) {
2130
+ for (const p of missingHosts) {
2131
+ checks.push({
2132
+ name: "Hosts file",
2133
+ status: "warning",
2134
+ message: `Missing entry for ${p.hostname}`,
2135
+ fix: `echo "127.0.0.1 ${p.hostname}" | sudo tee -a /etc/hosts`
2136
+ });
2137
+ }
2138
+ } else {
2139
+ checks.push({
2140
+ name: "Hosts file",
2141
+ status: "ok",
2142
+ message: `All ${localProjects.length} local hostname(s) configured`
2143
+ });
2144
+ }
2145
+ }
2146
+ }
2147
+ const listenPort = parseInt(defaults.nginxListen.split(":").pop() || "80", 10);
2148
+ if (listenPort <= 1024 && process.getuid && process.getuid() !== 0) {
2149
+ checks.push({
2150
+ name: "Nginx port",
2151
+ status: "warning",
2152
+ message: `Port ${listenPort} requires root privileges`,
2153
+ fix: "Run: sudo bindler apply (or change nginxListen to a port > 1024)"
1874
2154
  });
1875
2155
  }
1876
2156
  } else {
@@ -1889,47 +2169,47 @@ async function doctorCommand() {
1889
2169
  switch (check.status) {
1890
2170
  case "ok":
1891
2171
  icon = "\u2713";
1892
- color = chalk12.green;
2172
+ color = chalk13.green;
1893
2173
  break;
1894
2174
  case "warning":
1895
2175
  icon = "!";
1896
- color = chalk12.yellow;
2176
+ color = chalk13.yellow;
1897
2177
  hasWarnings = true;
1898
2178
  break;
1899
2179
  case "error":
1900
2180
  icon = "\u2717";
1901
- color = chalk12.red;
2181
+ color = chalk13.red;
1902
2182
  hasErrors = true;
1903
2183
  break;
1904
2184
  }
1905
- console.log(`${color(icon)} ${chalk12.bold(check.name)}: ${check.message}`);
2185
+ console.log(`${color(icon)} ${chalk13.bold(check.name)}: ${check.message}`);
1906
2186
  if (check.fix) {
1907
- console.log(chalk12.dim(` Fix: ${check.fix}`));
2187
+ console.log(chalk13.dim(` Fix: ${check.fix}`));
1908
2188
  }
1909
2189
  }
1910
2190
  console.log("");
1911
2191
  if (hasErrors) {
1912
- console.log(chalk12.red("Some checks failed. Please fix the issues above."));
2192
+ console.log(chalk13.red("Some checks failed. Please fix the issues above."));
1913
2193
  process.exit(1);
1914
2194
  } else if (hasWarnings) {
1915
- console.log(chalk12.yellow("Some warnings detected. Review the suggestions above."));
2195
+ console.log(chalk13.yellow("Some warnings detected. Review the suggestions above."));
1916
2196
  } else {
1917
- console.log(chalk12.green("All checks passed!"));
2197
+ console.log(chalk13.green("All checks passed!"));
1918
2198
  }
1919
2199
  }
1920
2200
 
1921
2201
  // src/commands/ports.ts
1922
- import chalk13 from "chalk";
2202
+ import chalk14 from "chalk";
1923
2203
  import Table3 from "cli-table3";
1924
2204
  async function portsCommand() {
1925
2205
  const ports = getPortsTable();
1926
2206
  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."));
2207
+ console.log(chalk14.yellow("No ports allocated."));
2208
+ console.log(chalk14.dim("npm projects will have ports allocated when created."));
1929
2209
  return;
1930
2210
  }
1931
2211
  const table = new Table3({
1932
- head: [chalk13.cyan("Port"), chalk13.cyan("Project"), chalk13.cyan("Hostname")],
2212
+ head: [chalk14.cyan("Port"), chalk14.cyan("Project"), chalk14.cyan("Hostname")],
1933
2213
  style: {
1934
2214
  head: [],
1935
2215
  border: []
@@ -1939,25 +2219,25 @@ async function portsCommand() {
1939
2219
  table.push([String(entry.port), entry.project, entry.hostname]);
1940
2220
  }
1941
2221
  console.log(table.toString());
1942
- console.log(chalk13.dim(`
2222
+ console.log(chalk14.dim(`
1943
2223
  ${ports.length} port(s) allocated`));
1944
2224
  }
1945
2225
 
1946
2226
  // src/commands/info.ts
1947
- import chalk14 from "chalk";
2227
+ import chalk15 from "chalk";
1948
2228
  async function infoCommand() {
1949
- console.log(chalk14.bold.cyan(String.raw`
2229
+ console.log(chalk15.bold.cyan(String.raw`
1950
2230
  _ _ _ _
1951
2231
  | |_|_|___ _| | |___ ___
1952
2232
  | . | | | . | | -_| _|
1953
2233
  |___|_|_|_|___|_|___|_|
1954
2234
  `));
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"));
2235
+ console.log(chalk15.white(" Manage multiple projects behind Cloudflare Tunnel"));
2236
+ console.log(chalk15.white(" with Nginx and PM2\n"));
2237
+ console.log(chalk15.dim(" Version: ") + chalk15.white("1.3.0"));
2238
+ console.log(chalk15.dim(" Author: ") + chalk15.white("alfaoz"));
2239
+ console.log(chalk15.dim(" License: ") + chalk15.white("MIT"));
2240
+ console.log(chalk15.dim(" GitHub: ") + chalk15.cyan("https://github.com/alfaoz/bindler"));
1961
2241
  console.log("");
1962
2242
  if (configExists()) {
1963
2243
  const config = readConfig();
@@ -1965,23 +2245,23 @@ async function infoCommand() {
1965
2245
  const runningCount = pm2Processes.filter((p) => p.status === "online").length;
1966
2246
  const npmProjects = config.projects.filter((p) => p.type === "npm").length;
1967
2247
  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));
2248
+ console.log(chalk15.dim(" Config: ") + chalk15.white(getConfigPath()));
2249
+ console.log(chalk15.dim(" Nginx: ") + chalk15.white(config.defaults.nginxManagedPath));
2250
+ console.log(chalk15.dim(" Tunnel: ") + chalk15.white(config.defaults.tunnelName));
1971
2251
  console.log("");
1972
- console.log(chalk14.dim(" Projects: ") + chalk14.white(`${config.projects.length} total (${staticProjects} static, ${npmProjects} npm)`));
2252
+ console.log(chalk15.dim(" Projects: ") + chalk15.white(`${config.projects.length} total (${staticProjects} static, ${npmProjects} npm)`));
1973
2253
  if (npmProjects > 0) {
1974
- console.log(chalk14.dim(" Running: ") + chalk14.green(`${runningCount}/${npmProjects} npm apps online`));
2254
+ console.log(chalk15.dim(" Running: ") + chalk15.green(`${runningCount}/${npmProjects} npm apps online`));
1975
2255
  }
1976
2256
  } else {
1977
- console.log(chalk14.dim(" Config: ") + chalk14.yellow("Not initialized"));
1978
- console.log(chalk14.dim(" ") + chalk14.dim("Run `bindler new` to get started"));
2257
+ console.log(chalk15.dim(" Config: ") + chalk15.yellow("Not initialized"));
2258
+ console.log(chalk15.dim(" ") + chalk15.dim("Run `bindler new` to get started"));
1979
2259
  }
1980
2260
  console.log("");
1981
2261
  }
1982
2262
 
1983
2263
  // src/commands/check.ts
1984
- import chalk15 from "chalk";
2264
+ import chalk16 from "chalk";
1985
2265
  import { resolve4, resolve6, resolveCname } from "dns/promises";
1986
2266
  async function checkDns(hostname) {
1987
2267
  const result = {
@@ -2042,111 +2322,111 @@ async function checkCommand(hostnameOrName, options) {
2042
2322
  const hostname = project ? project.hostname : hostnameOrName;
2043
2323
  const basePath = project?.basePath || "/";
2044
2324
  const isLocal = project?.local || hostname.endsWith(".local") || hostname.endsWith(".localhost");
2045
- console.log(chalk15.blue(`
2325
+ console.log(chalk16.blue(`
2046
2326
  Checking ${hostname}...
2047
2327
  `));
2048
- console.log(chalk15.bold("DNS Resolution:"));
2328
+ console.log(chalk16.bold("DNS Resolution:"));
2049
2329
  const dns = await checkDns(hostname);
2050
2330
  if (!dns.resolved) {
2051
- console.log(chalk15.red(" \u2717 DNS not resolving"));
2331
+ console.log(chalk16.red(" \u2717 DNS not resolving"));
2052
2332
  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`));
2333
+ console.log(chalk16.dim(" Local hostname - add to /etc/hosts:"));
2334
+ console.log(chalk16.cyan(` echo "127.0.0.1 ${hostname}" | sudo tee -a /etc/hosts`));
2055
2335
  } 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."));
2336
+ console.log(chalk16.dim(" No DNS records found for this hostname."));
2337
+ console.log(chalk16.dim(" If using Cloudflare Tunnel, run: bindler apply"));
2338
+ console.log(chalk16.dim(" If using direct mode, add an A record pointing to your server IP."));
2059
2339
  }
2060
2340
  console.log("");
2061
2341
  return;
2062
2342
  }
2063
2343
  if (dns.cname.length > 0) {
2064
- console.log(chalk15.green(" \u2713 CNAME: ") + dns.cname.join(", "));
2344
+ console.log(chalk16.green(" \u2713 CNAME: ") + dns.cname.join(", "));
2065
2345
  if (dns.isCloudflare) {
2066
- console.log(chalk15.green(" \u2713 Points to Cloudflare Tunnel"));
2346
+ console.log(chalk16.green(" \u2713 Points to Cloudflare Tunnel"));
2067
2347
  }
2068
2348
  }
2069
2349
  if (dns.ipv4.length > 0) {
2070
- console.log(chalk15.green(" \u2713 A (IPv4): ") + dns.ipv4.join(", "));
2350
+ console.log(chalk16.green(" \u2713 A (IPv4): ") + dns.ipv4.join(", "));
2071
2351
  }
2072
2352
  if (dns.ipv6.length > 0) {
2073
- console.log(chalk15.green(" \u2713 AAAA (IPv6): ") + dns.ipv6.join(", "));
2353
+ console.log(chalk16.green(" \u2713 AAAA (IPv6): ") + dns.ipv6.join(", "));
2074
2354
  }
2075
2355
  console.log("");
2076
- console.log(chalk15.bold("HTTP Check:"));
2356
+ console.log(chalk16.bold("HTTP Check:"));
2077
2357
  const http = await checkHttp(hostname, basePath);
2078
2358
  if (!http.reachable) {
2079
- console.log(chalk15.red(" \u2717 Not reachable"));
2359
+ console.log(chalk16.red(" \u2717 Not reachable"));
2080
2360
  const err = http.error || "";
2081
2361
  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"));
2362
+ console.log(chalk16.dim(" Connection refused - server not accepting connections"));
2363
+ console.log(chalk16.yellow("\n Check:"));
2364
+ console.log(chalk16.dim(" - Is nginx running? Run: bindler doctor"));
2085
2365
  if (project?.type === "npm") {
2086
- console.log(chalk15.dim(` - Is the app running? Run: bindler start ${project.name}`));
2366
+ console.log(chalk16.dim(` - Is the app running? Run: bindler start ${project.name}`));
2087
2367
  }
2088
2368
  } 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"));
2369
+ console.log(chalk16.dim(" Hostname not found"));
2370
+ console.log(chalk16.yellow("\n Check:"));
2371
+ console.log(chalk16.dim(" - DNS may not be propagated yet (wait a few minutes)"));
2372
+ console.log(chalk16.dim(" - Verify DNS records are correct"));
2093
2373
  } else if (err.includes("ETIMEDOUT") || err.includes("timeout")) {
2094
- console.log(chalk15.dim(" Connection timed out"));
2095
- console.log(chalk15.yellow("\n Check:"));
2374
+ console.log(chalk16.dim(" Connection timed out"));
2375
+ console.log(chalk16.yellow("\n Check:"));
2096
2376
  if (isLocal) {
2097
- console.log(chalk15.dim(" - Is nginx running on port 8080?"));
2377
+ console.log(chalk16.dim(" - Is nginx running on port 8080?"));
2098
2378
  } else {
2099
- console.log(chalk15.dim(" - Is your server/tunnel reachable from the internet?"));
2100
- console.log(chalk15.dim(" - Check firewall rules"));
2379
+ console.log(chalk16.dim(" - Is your server/tunnel reachable from the internet?"));
2380
+ console.log(chalk16.dim(" - Check firewall rules"));
2101
2381
  }
2102
2382
  } 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"));
2383
+ console.log(chalk16.dim(" SSL/TLS certificate error"));
2384
+ console.log(chalk16.yellow("\n Check:"));
2385
+ console.log(chalk16.dim(" - Run: sudo bindler apply (to refresh SSL certs)"));
2386
+ console.log(chalk16.dim(" - Or check certbot: sudo certbot certificates"));
2107
2387
  } else {
2108
- console.log(chalk15.dim(` Error: ${err}`));
2109
- console.log(chalk15.yellow("\n Possible issues:"));
2388
+ console.log(chalk16.dim(` Error: ${err}`));
2389
+ console.log(chalk16.yellow("\n Possible issues:"));
2110
2390
  if (!isLocal) {
2111
- console.log(chalk15.dim(" - Cloudflare tunnel not running"));
2391
+ console.log(chalk16.dim(" - Cloudflare tunnel not running"));
2112
2392
  }
2113
- console.log(chalk15.dim(" - Nginx not running or misconfigured"));
2393
+ console.log(chalk16.dim(" - Nginx not running or misconfigured"));
2114
2394
  if (project?.type === "npm") {
2115
- console.log(chalk15.dim(" - Project not started"));
2395
+ console.log(chalk16.dim(" - Project not started"));
2116
2396
  }
2117
2397
  }
2118
2398
  console.log("");
2119
2399
  return;
2120
2400
  }
2121
- const statusColor = http.statusCode < 400 ? chalk15.green : chalk15.red;
2401
+ const statusColor = http.statusCode < 400 ? chalk16.green : chalk16.red;
2122
2402
  console.log(statusColor(` \u2713 Status: ${http.statusCode}`));
2123
- console.log(chalk15.dim(` Response time: ${http.responseTime}ms`));
2403
+ console.log(chalk16.dim(` Response time: ${http.responseTime}ms`));
2124
2404
  if (http.redirectUrl) {
2125
- console.log(chalk15.dim(` Redirects to: ${http.redirectUrl}`));
2405
+ console.log(chalk16.dim(` Redirects to: ${http.redirectUrl}`));
2126
2406
  }
2127
2407
  console.log("");
2128
2408
  if (dns.resolved && http.reachable && http.statusCode < 400) {
2129
- console.log(chalk15.green("\u2713 All checks passed! Site is accessible."));
2409
+ console.log(chalk16.green("\u2713 All checks passed! Site is accessible."));
2130
2410
  } else if (dns.resolved && http.reachable) {
2131
- console.log(chalk15.yellow("! Site is reachable but returned an error status."));
2411
+ console.log(chalk16.yellow("! Site is reachable but returned an error status."));
2132
2412
  } else {
2133
- console.log(chalk15.red("\u2717 Some checks failed. See details above."));
2413
+ console.log(chalk16.red("\u2717 Some checks failed. See details above."));
2134
2414
  }
2135
2415
  if (project) {
2136
- console.log(chalk15.dim(`
2416
+ console.log(chalk16.dim(`
2137
2417
  Project: ${project.name} (${project.type})`));
2138
2418
  if (project.type === "npm") {
2139
- console.log(chalk15.dim(`Port: ${project.port}`));
2419
+ console.log(chalk16.dim(`Port: ${project.port}`));
2140
2420
  }
2141
2421
  }
2142
2422
  console.log("");
2143
2423
  }
2144
2424
 
2145
2425
  // src/commands/setup.ts
2146
- import chalk16 from "chalk";
2426
+ import chalk17 from "chalk";
2147
2427
  import inquirer3 from "inquirer";
2148
2428
  import { execSync as execSync2 } from "child_process";
2149
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
2429
+ import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
2150
2430
  function detectOs() {
2151
2431
  const platform = process.platform;
2152
2432
  if (platform === "darwin") {
@@ -2157,7 +2437,7 @@ function detectOs() {
2157
2437
  }
2158
2438
  if (platform === "linux") {
2159
2439
  try {
2160
- if (existsSync8("/etc/os-release")) {
2440
+ if (existsSync10("/etc/os-release")) {
2161
2441
  const osRelease = readFileSync5("/etc/os-release", "utf-8");
2162
2442
  const lines = osRelease.split("\n");
2163
2443
  const info = {};
@@ -2174,7 +2454,7 @@ function detectOs() {
2174
2454
  if (["ubuntu", "debian", "pop", "mint", "elementary"].includes(distro)) {
2175
2455
  packageManager = "apt";
2176
2456
  } else if (["fedora", "rhel", "centos", "rocky", "alma"].includes(distro)) {
2177
- packageManager = existsSync8("/usr/bin/dnf") ? "dnf" : "yum";
2457
+ packageManager = existsSync10("/usr/bin/dnf") ? "dnf" : "yum";
2178
2458
  } else if (["amzn"].includes(distro)) {
2179
2459
  packageManager = "yum";
2180
2460
  }
@@ -2187,7 +2467,7 @@ function detectOs() {
2187
2467
  return { platform: "unknown" };
2188
2468
  }
2189
2469
  function runCommand(command, description) {
2190
- console.log(chalk16.dim(` \u2192 ${description}...`));
2470
+ console.log(chalk17.dim(` \u2192 ${description}...`));
2191
2471
  try {
2192
2472
  execSync2(command, { stdio: "inherit" });
2193
2473
  return true;
@@ -2196,7 +2476,7 @@ function runCommand(command, description) {
2196
2476
  }
2197
2477
  }
2198
2478
  async function installNginx(os) {
2199
- console.log(chalk16.blue("\nInstalling nginx...\n"));
2479
+ console.log(chalk17.blue("\nInstalling nginx...\n"));
2200
2480
  if (os.platform === "darwin") {
2201
2481
  return runCommand("brew install nginx", "Installing via Homebrew");
2202
2482
  }
@@ -2207,16 +2487,16 @@ async function installNginx(os) {
2207
2487
  if (os.platform === "linux" && (os.packageManager === "yum" || os.packageManager === "dnf")) {
2208
2488
  return runCommand(`sudo ${os.packageManager} install -y nginx`, "Installing nginx");
2209
2489
  }
2210
- console.log(chalk16.yellow(" Automatic installation not supported for your OS."));
2211
- console.log(chalk16.dim(" Please install nginx manually."));
2490
+ console.log(chalk17.yellow(" Automatic installation not supported for your OS."));
2491
+ console.log(chalk17.dim(" Please install nginx manually."));
2212
2492
  return false;
2213
2493
  }
2214
2494
  async function installPm2() {
2215
- console.log(chalk16.blue("\nInstalling PM2...\n"));
2495
+ console.log(chalk17.blue("\nInstalling PM2...\n"));
2216
2496
  return runCommand("npm install -g pm2", "Installing via npm");
2217
2497
  }
2218
2498
  async function installCertbot(os) {
2219
- console.log(chalk16.blue("\nInstalling certbot (Let's Encrypt)...\n"));
2499
+ console.log(chalk17.blue("\nInstalling certbot (Let's Encrypt)...\n"));
2220
2500
  if (os.platform === "darwin") {
2221
2501
  return runCommand("brew install certbot", "Installing via Homebrew");
2222
2502
  }
@@ -2227,8 +2507,8 @@ async function installCertbot(os) {
2227
2507
  if (os.platform === "linux" && (os.packageManager === "yum" || os.packageManager === "dnf")) {
2228
2508
  return runCommand(`sudo ${os.packageManager} install -y certbot python3-certbot-nginx`, "Installing certbot");
2229
2509
  }
2230
- console.log(chalk16.yellow(" Automatic installation not supported for your OS."));
2231
- console.log(chalk16.dim(" Please install certbot manually."));
2510
+ console.log(chalk17.yellow(" Automatic installation not supported for your OS."));
2511
+ console.log(chalk17.dim(" Please install certbot manually."));
2232
2512
  return false;
2233
2513
  }
2234
2514
  function isCertbotInstalled() {
@@ -2236,7 +2516,7 @@ function isCertbotInstalled() {
2236
2516
  return result.success;
2237
2517
  }
2238
2518
  async function installCloudflared(os) {
2239
- console.log(chalk16.blue("\nInstalling cloudflared...\n"));
2519
+ console.log(chalk17.blue("\nInstalling cloudflared...\n"));
2240
2520
  if (os.platform === "darwin") {
2241
2521
  return runCommand("brew install cloudflared", "Installing via Homebrew");
2242
2522
  }
@@ -2270,21 +2550,21 @@ async function installCloudflared(os) {
2270
2550
  );
2271
2551
  return runCommand(`sudo ${os.packageManager} install -y cloudflared`, "Installing cloudflared");
2272
2552
  }
2273
- console.log(chalk16.yellow(" Automatic installation not supported for your OS."));
2274
- console.log(chalk16.dim(" Visit: https://pkg.cloudflare.com/index.html"));
2553
+ console.log(chalk17.yellow(" Automatic installation not supported for your OS."));
2554
+ console.log(chalk17.dim(" Visit: https://pkg.cloudflare.com/index.html"));
2275
2555
  return false;
2276
2556
  }
2277
2557
  async function setupCommand(options = {}) {
2278
- console.log(chalk16.bold.cyan("\nBindler Setup\n"));
2558
+ console.log(chalk17.bold.cyan("\nBindler Setup\n"));
2279
2559
  const os = detectOs();
2280
- console.log(chalk16.dim(`Detected: ${os.distro || os.platform}${os.version ? ` ${os.version}` : ""}${os.codename ? ` (${os.codename})` : ""}`));
2560
+ console.log(chalk17.dim(`Detected: ${os.distro || os.platform}${os.version ? ` ${os.version}` : ""}${os.codename ? ` (${os.codename})` : ""}`));
2281
2561
  const isDirect = options.direct;
2282
2562
  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"));
2563
+ console.log(chalk17.cyan("\nDirect mode (VPS without Cloudflare Tunnel)"));
2564
+ console.log(chalk17.dim("nginx will listen on port 80/443 directly\n"));
2285
2565
  } 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"));
2566
+ console.log(chalk17.cyan("\nTunnel mode (via Cloudflare Tunnel)"));
2567
+ console.log(chalk17.dim("nginx will listen on 127.0.0.1:8080\n"));
2288
2568
  }
2289
2569
  const missing = [];
2290
2570
  if (!isNginxInstalled()) {
@@ -2303,11 +2583,11 @@ async function setupCommand(options = {}) {
2303
2583
  }
2304
2584
  }
2305
2585
  if (missing.length === 0) {
2306
- console.log(chalk16.green("\u2713 All dependencies are already installed!\n"));
2586
+ console.log(chalk17.green("\u2713 All dependencies are already installed!\n"));
2307
2587
  } else {
2308
- console.log(chalk16.yellow("Missing dependencies:\n"));
2588
+ console.log(chalk17.yellow("Missing dependencies:\n"));
2309
2589
  for (const dep of missing) {
2310
- console.log(chalk16.red(` \u2717 ${dep.name}`));
2590
+ console.log(chalk17.red(` \u2717 ${dep.name}`));
2311
2591
  }
2312
2592
  console.log("");
2313
2593
  const { toInstall } = await inquirer3.prompt([
@@ -2331,12 +2611,12 @@ async function setupCommand(options = {}) {
2331
2611
  results.push({ name: depName, success });
2332
2612
  }
2333
2613
  }
2334
- console.log(chalk16.bold("\n\nInstallation Summary:\n"));
2614
+ console.log(chalk17.bold("\n\nInstallation Summary:\n"));
2335
2615
  for (const result of results) {
2336
2616
  if (result.success) {
2337
- console.log(chalk16.green(` \u2713 ${result.name} installed`));
2617
+ console.log(chalk17.green(` \u2713 ${result.name} installed`));
2338
2618
  } else {
2339
- console.log(chalk16.red(` \u2717 ${result.name} failed`));
2619
+ console.log(chalk17.red(` \u2717 ${result.name} failed`));
2340
2620
  }
2341
2621
  }
2342
2622
  console.log("");
@@ -2378,32 +2658,32 @@ async function setupCommand(options = {}) {
2378
2658
  config.defaults.applyCloudflareDnsRoutes = true;
2379
2659
  }
2380
2660
  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}`));
2661
+ console.log(chalk17.green("\n\u2713 Setup complete!\n"));
2662
+ console.log(chalk17.dim(`Mode: ${config.defaults.mode}`));
2663
+ console.log(chalk17.dim(`nginx listen: ${config.defaults.nginxListen}`));
2384
2664
  if (config.defaults.sslEnabled) {
2385
- console.log(chalk16.dim(`SSL: enabled (${config.defaults.sslEmail})`));
2665
+ console.log(chalk17.dim(`SSL: enabled (${config.defaults.sslEmail})`));
2386
2666
  }
2387
- console.log(chalk16.dim("\nRun `bindler new` to create your first project."));
2667
+ console.log(chalk17.dim("\nRun `bindler new` to create your first project."));
2388
2668
  }
2389
2669
 
2390
2670
  // src/commands/init.ts
2391
- import chalk17 from "chalk";
2671
+ import chalk18 from "chalk";
2392
2672
  import inquirer4 from "inquirer";
2393
2673
  async function initCommand() {
2394
- console.log(chalk17.bold.cyan(`
2674
+ console.log(chalk18.bold.cyan(`
2395
2675
  _ _ _ _
2396
2676
  | |_|_|___ _| | |___ ___
2397
2677
  | . | | | . | | -_| _|
2398
2678
  |___|_|_|_|___|_|___|_|
2399
2679
  `));
2400
- console.log(chalk17.white(" Welcome to bindler!\n"));
2680
+ console.log(chalk18.white(" Welcome to bindler!\n"));
2401
2681
  if (configExists()) {
2402
2682
  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}`));
2683
+ console.log(chalk18.yellow("Bindler is already initialized."));
2684
+ console.log(chalk18.dim(` Config: ~/.config/bindler/config.json`));
2685
+ console.log(chalk18.dim(` Mode: ${config2.defaults.mode || "tunnel"}`));
2686
+ console.log(chalk18.dim(` Projects: ${config2.projects.length}`));
2407
2687
  console.log("");
2408
2688
  const { reinit } = await inquirer4.prompt([
2409
2689
  {
@@ -2414,11 +2694,11 @@ async function initCommand() {
2414
2694
  }
2415
2695
  ]);
2416
2696
  if (!reinit) {
2417
- console.log(chalk17.dim("\nRun `bindler new` to add a project."));
2697
+ console.log(chalk18.dim("\nRun `bindler new` to add a project."));
2418
2698
  return;
2419
2699
  }
2420
2700
  }
2421
- console.log(chalk17.bold("\n1. Choose your setup:\n"));
2701
+ console.log(chalk18.bold("\n1. Choose your setup:\n"));
2422
2702
  const { mode } = await inquirer4.prompt([
2423
2703
  {
2424
2704
  type: "list",
@@ -2440,19 +2720,19 @@ async function initCommand() {
2440
2720
  ]
2441
2721
  }
2442
2722
  ]);
2443
- console.log(chalk17.bold("\n2. Checking dependencies...\n"));
2723
+ console.log(chalk18.bold("\n2. Checking dependencies...\n"));
2444
2724
  const deps = {
2445
2725
  nginx: isNginxInstalled(),
2446
2726
  pm2: isPm2Installed(),
2447
2727
  cloudflared: isCloudflaredInstalled()
2448
2728
  };
2449
2729
  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"));
2730
+ console.log(deps.nginx ? chalk18.green(" \u2713 nginx") : chalk18.red(" \u2717 nginx"));
2731
+ console.log(deps.pm2 ? chalk18.green(" \u2713 pm2") : chalk18.red(" \u2717 pm2"));
2452
2732
  if (mode === "tunnel") {
2453
- console.log(deps.cloudflared ? chalk17.green(" \u2713 cloudflared") : chalk17.red(" \u2717 cloudflared"));
2733
+ console.log(deps.cloudflared ? chalk18.green(" \u2713 cloudflared") : chalk18.red(" \u2717 cloudflared"));
2454
2734
  } else if (mode === "direct") {
2455
- console.log(chalk17.dim(" - cloudflared (not needed for direct mode)"));
2735
+ console.log(chalk18.dim(" - cloudflared (not needed for direct mode)"));
2456
2736
  }
2457
2737
  const missingDeps = !deps.nginx || !deps.pm2 || mode === "tunnel" && !deps.cloudflared;
2458
2738
  if (missingDeps) {
@@ -2469,7 +2749,7 @@ async function initCommand() {
2469
2749
  await setupCommand({ direct: mode === "direct" });
2470
2750
  }
2471
2751
  }
2472
- console.log(chalk17.bold("\n3. Configuration:\n"));
2752
+ console.log(chalk18.bold("\n3. Configuration:\n"));
2473
2753
  let tunnelName = "homelab";
2474
2754
  let sslEmail = "";
2475
2755
  if (mode === "tunnel") {
@@ -2521,29 +2801,29 @@ async function initCommand() {
2521
2801
  config.defaults.nginxListen = "127.0.0.1:8080";
2522
2802
  }
2523
2803
  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));
2804
+ console.log(chalk18.green("\n\u2713 Bindler initialized!\n"));
2805
+ console.log(chalk18.dim(" Mode: ") + chalk18.white(mode));
2806
+ console.log(chalk18.dim(" Listen: ") + chalk18.white(config.defaults.nginxListen));
2527
2807
  if (mode === "tunnel") {
2528
- console.log(chalk17.dim(" Tunnel: ") + chalk17.white(tunnelName));
2808
+ console.log(chalk18.dim(" Tunnel: ") + chalk18.white(tunnelName));
2529
2809
  }
2530
2810
  if (sslEmail) {
2531
- console.log(chalk17.dim(" SSL: ") + chalk17.white(sslEmail));
2811
+ console.log(chalk18.dim(" SSL: ") + chalk18.white(sslEmail));
2532
2812
  }
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"));
2813
+ console.log(chalk18.bold("\nNext steps:\n"));
2814
+ console.log(chalk18.dim(" 1. ") + chalk18.white("bindler new") + chalk18.dim(" # add your first project"));
2815
+ console.log(chalk18.dim(" 2. ") + chalk18.white("bindler apply") + chalk18.dim(" # apply nginx config"));
2536
2816
  if (mode === "tunnel") {
2537
- console.log(chalk17.dim(" 3. ") + chalk17.white(`cloudflared tunnel run ${tunnelName}`) + chalk17.dim(" # start tunnel"));
2817
+ console.log(chalk18.dim(" 3. ") + chalk18.white(`cloudflared tunnel run ${tunnelName}`) + chalk18.dim(" # start tunnel"));
2538
2818
  }
2539
2819
  console.log("");
2540
2820
  }
2541
2821
 
2542
2822
  // src/commands/deploy.ts
2543
- import chalk18 from "chalk";
2823
+ import chalk19 from "chalk";
2544
2824
  import { execSync as execSync3 } from "child_process";
2545
- import { existsSync as existsSync9 } from "fs";
2546
- import { join as join6 } from "path";
2825
+ import { existsSync as existsSync11 } from "fs";
2826
+ import { join as join7 } from "path";
2547
2827
  function runInDir(command, cwd) {
2548
2828
  try {
2549
2829
  const output = execSync3(command, { cwd, encoding: "utf-8", stdio: "pipe" });
@@ -2555,101 +2835,101 @@ function runInDir(command, cwd) {
2555
2835
  }
2556
2836
  async function deployCommand(name, options) {
2557
2837
  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"));
2838
+ console.log(chalk19.red("Usage: bindler deploy <name>"));
2839
+ console.log(chalk19.dim("\nDeploys a project: git pull + npm install + restart"));
2840
+ console.log(chalk19.dim("\nExamples:"));
2841
+ console.log(chalk19.dim(" bindler deploy myapp"));
2842
+ console.log(chalk19.dim(" bindler deploy myapp --skip-install"));
2843
+ console.log(chalk19.dim(" bindler deploy myapp --skip-pull"));
2564
2844
  process.exit(1);
2565
2845
  }
2566
2846
  const project = getProject(name);
2567
2847
  if (!project) {
2568
- console.log(chalk18.red(`Project "${name}" not found.`));
2569
- console.log(chalk18.dim("\nAvailable projects:"));
2848
+ console.log(chalk19.red(`Project "${name}" not found.`));
2849
+ console.log(chalk19.dim("\nAvailable projects:"));
2570
2850
  const projects = listProjects();
2571
2851
  for (const p of projects) {
2572
- console.log(chalk18.dim(` - ${p.name}`));
2852
+ console.log(chalk19.dim(` - ${p.name}`));
2573
2853
  }
2574
2854
  process.exit(1);
2575
2855
  }
2576
- if (!existsSync9(project.path)) {
2577
- console.log(chalk18.red(`Project path does not exist: ${project.path}`));
2856
+ if (!existsSync11(project.path)) {
2857
+ console.log(chalk19.red(`Project path does not exist: ${project.path}`));
2578
2858
  process.exit(1);
2579
2859
  }
2580
- console.log(chalk18.blue(`
2860
+ console.log(chalk19.blue(`
2581
2861
  Deploying ${project.name}...
2582
2862
  `));
2583
2863
  if (!options.skipPull) {
2584
- const isGitRepo = existsSync9(join6(project.path, ".git"));
2864
+ const isGitRepo = existsSync11(join7(project.path, ".git"));
2585
2865
  if (isGitRepo) {
2586
- console.log(chalk18.dim("Pulling latest changes..."));
2866
+ console.log(chalk19.dim("Pulling latest changes..."));
2587
2867
  const result = runInDir("git pull", project.path);
2588
2868
  if (result.success) {
2589
2869
  if (result.output.includes("Already up to date")) {
2590
- console.log(chalk18.green(" \u2713 Already up to date"));
2870
+ console.log(chalk19.green(" \u2713 Already up to date"));
2591
2871
  } else {
2592
- console.log(chalk18.green(" \u2713 Pulled latest changes"));
2872
+ console.log(chalk19.green(" \u2713 Pulled latest changes"));
2593
2873
  if (result.output) {
2594
- console.log(chalk18.dim(` ${result.output.split("\n")[0]}`));
2874
+ console.log(chalk19.dim(` ${result.output.split("\n")[0]}`));
2595
2875
  }
2596
2876
  }
2597
2877
  } else {
2598
- console.log(chalk18.yellow(" ! Git pull failed"));
2599
- console.log(chalk18.dim(` ${result.output}`));
2878
+ console.log(chalk19.yellow(" ! Git pull failed"));
2879
+ console.log(chalk19.dim(` ${result.output}`));
2600
2880
  }
2601
2881
  } else {
2602
- console.log(chalk18.dim(" - Not a git repository, skipping pull"));
2882
+ console.log(chalk19.dim(" - Not a git repository, skipping pull"));
2603
2883
  }
2604
2884
  } else {
2605
- console.log(chalk18.dim(" - Skipped git pull (--skip-pull)"));
2885
+ console.log(chalk19.dim(" - Skipped git pull (--skip-pull)"));
2606
2886
  }
2607
2887
  if (project.type === "npm" && !options.skipInstall) {
2608
- const hasPackageJson = existsSync9(join6(project.path, "package.json"));
2888
+ const hasPackageJson = existsSync11(join7(project.path, "package.json"));
2609
2889
  if (hasPackageJson) {
2610
- console.log(chalk18.dim("Installing dependencies..."));
2890
+ console.log(chalk19.dim("Installing dependencies..."));
2611
2891
  const result = runInDir("npm install", project.path);
2612
2892
  if (result.success) {
2613
- console.log(chalk18.green(" \u2713 Dependencies installed"));
2893
+ console.log(chalk19.green(" \u2713 Dependencies installed"));
2614
2894
  } else {
2615
- console.log(chalk18.yellow(" ! npm install failed"));
2616
- console.log(chalk18.dim(` ${result.output.split("\n")[0]}`));
2895
+ console.log(chalk19.yellow(" ! npm install failed"));
2896
+ console.log(chalk19.dim(` ${result.output.split("\n")[0]}`));
2617
2897
  }
2618
2898
  }
2619
2899
  } else if (options.skipInstall) {
2620
- console.log(chalk18.dim(" - Skipped npm install (--skip-install)"));
2900
+ console.log(chalk19.dim(" - Skipped npm install (--skip-install)"));
2621
2901
  }
2622
2902
  if (project.type === "npm" && !options.skipRestart) {
2623
- console.log(chalk18.dim("Restarting application..."));
2903
+ console.log(chalk19.dim("Restarting application..."));
2624
2904
  const result = restartProject(name);
2625
2905
  if (result.success) {
2626
- console.log(chalk18.green(" \u2713 Application restarted"));
2906
+ console.log(chalk19.green(" \u2713 Application restarted"));
2627
2907
  } else {
2628
- console.log(chalk18.yellow(` ! Restart failed: ${result.error}`));
2629
- console.log(chalk18.dim(` Try: bindler start ${name}`));
2908
+ console.log(chalk19.yellow(` ! Restart failed: ${result.error}`));
2909
+ console.log(chalk19.dim(` Try: bindler start ${name}`));
2630
2910
  }
2631
2911
  } else if (project.type === "static") {
2632
- console.log(chalk18.dim(" - Static project, no restart needed"));
2912
+ console.log(chalk19.dim(" - Static project, no restart needed"));
2633
2913
  } else if (options.skipRestart) {
2634
- console.log(chalk18.dim(" - Skipped restart (--skip-restart)"));
2914
+ console.log(chalk19.dim(" - Skipped restart (--skip-restart)"));
2635
2915
  }
2636
- console.log(chalk18.green(`
2916
+ console.log(chalk19.green(`
2637
2917
  \u2713 Deploy complete for ${project.name}
2638
2918
  `));
2639
2919
  }
2640
2920
 
2641
2921
  // 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";
2922
+ import chalk20 from "chalk";
2923
+ import { existsSync as existsSync12, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4 } from "fs";
2644
2924
  import { dirname as dirname3, resolve } from "path";
2645
- import { homedir as homedir2 } from "os";
2925
+ import { homedir as homedir3 } from "os";
2646
2926
  async function backupCommand(options) {
2647
2927
  const config = readConfig();
2648
2928
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2649
- const defaultPath = resolve(homedir2(), `bindler-backup-${timestamp}.json`);
2929
+ const defaultPath = resolve(homedir3(), `bindler-backup-${timestamp}.json`);
2650
2930
  const outputPath = options.output || defaultPath;
2651
2931
  const dir = dirname3(outputPath);
2652
- if (!existsSync10(dir)) {
2932
+ if (!existsSync12(dir)) {
2653
2933
  mkdirSync4(dir, { recursive: true });
2654
2934
  }
2655
2935
  const backup = {
@@ -2658,27 +2938,27 @@ async function backupCommand(options) {
2658
2938
  config
2659
2939
  };
2660
2940
  writeFileSync5(outputPath, JSON.stringify(backup, null, 2) + "\n");
2661
- console.log(chalk19.green(`
2941
+ console.log(chalk20.green(`
2662
2942
  \u2713 Backup saved to ${outputPath}
2663
2943
  `));
2664
- console.log(chalk19.dim(` Projects: ${config.projects.length}`));
2665
- console.log(chalk19.dim(` Mode: ${config.defaults.mode || "tunnel"}`));
2944
+ console.log(chalk20.dim(` Projects: ${config.projects.length}`));
2945
+ console.log(chalk20.dim(` Mode: ${config.defaults.mode || "tunnel"}`));
2666
2946
  console.log("");
2667
- console.log(chalk19.dim("Restore with:"));
2668
- console.log(chalk19.cyan(` bindler restore ${outputPath}`));
2947
+ console.log(chalk20.dim("Restore with:"));
2948
+ console.log(chalk20.cyan(` bindler restore ${outputPath}`));
2669
2949
  console.log("");
2670
2950
  }
2671
2951
  async function restoreCommand(file, options) {
2672
2952
  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"));
2953
+ console.log(chalk20.red("Usage: bindler restore <file>"));
2954
+ console.log(chalk20.dim("\nExamples:"));
2955
+ console.log(chalk20.dim(" bindler restore ~/bindler-backup.json"));
2956
+ console.log(chalk20.dim(" bindler restore backup.json --force"));
2677
2957
  process.exit(1);
2678
2958
  }
2679
2959
  const filePath = resolve(file);
2680
- if (!existsSync10(filePath)) {
2681
- console.log(chalk19.red(`File not found: ${filePath}`));
2960
+ if (!existsSync12(filePath)) {
2961
+ console.log(chalk20.red(`File not found: ${filePath}`));
2682
2962
  process.exit(1);
2683
2963
  }
2684
2964
  let backup;
@@ -2686,140 +2966,140 @@ async function restoreCommand(file, options) {
2686
2966
  const content = readFileSync6(filePath, "utf-8");
2687
2967
  backup = JSON.parse(content);
2688
2968
  } catch (error) {
2689
- console.log(chalk19.red("Invalid backup file. Must be valid JSON."));
2969
+ console.log(chalk20.red("Invalid backup file. Must be valid JSON."));
2690
2970
  process.exit(1);
2691
2971
  }
2692
2972
  if (!backup.config || !backup.config.defaults || !Array.isArray(backup.config.projects)) {
2693
- console.log(chalk19.red("Invalid backup format. Missing config data."));
2973
+ console.log(chalk20.red("Invalid backup format. Missing config data."));
2694
2974
  process.exit(1);
2695
2975
  }
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"));
2976
+ console.log(chalk20.blue("\nBackup info:\n"));
2977
+ console.log(chalk20.dim(" Exported: ") + chalk20.white(backup.exportedAt || "unknown"));
2978
+ console.log(chalk20.dim(" Projects: ") + chalk20.white(backup.config.projects.length));
2979
+ console.log(chalk20.dim(" Mode: ") + chalk20.white(backup.config.defaults.mode || "tunnel"));
2700
2980
  console.log("");
2701
2981
  const currentConfig = readConfig();
2702
2982
  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"));
2983
+ console.log(chalk20.yellow(`Warning: You have ${currentConfig.projects.length} existing project(s).`));
2984
+ console.log(chalk20.dim("Use --force to overwrite.\n"));
2705
2985
  process.exit(1);
2706
2986
  }
2707
2987
  writeConfig(backup.config);
2708
- console.log(chalk19.green("\u2713 Config restored!\n"));
2709
- console.log(chalk19.dim("Run `bindler apply` to apply nginx configuration."));
2988
+ console.log(chalk20.green("\u2713 Config restored!\n"));
2989
+ console.log(chalk20.dim("Run `bindler apply` to apply nginx configuration."));
2710
2990
  console.log("");
2711
2991
  }
2712
2992
 
2713
2993
  // src/commands/ssl.ts
2714
- import chalk20 from "chalk";
2994
+ import chalk21 from "chalk";
2715
2995
  async function sslCommand(hostname, options) {
2716
2996
  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"));
2997
+ console.log(chalk21.red("Usage: bindler ssl <hostname>"));
2998
+ console.log(chalk21.dim("\nRequest SSL certificate for a hostname"));
2999
+ console.log(chalk21.dim("\nExamples:"));
3000
+ console.log(chalk21.dim(" bindler ssl myapp.example.com"));
3001
+ console.log(chalk21.dim(" bindler ssl myapp # uses project hostname"));
3002
+ console.log(chalk21.dim(" bindler ssl myapp --email me@example.com"));
2723
3003
  process.exit(1);
2724
3004
  }
2725
3005
  const project = getProject(hostname);
2726
3006
  const targetHostname = project ? project.hostname : hostname;
2727
3007
  const certbotCheck = execCommandSafe("which certbot");
2728
3008
  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"));
3009
+ console.log(chalk21.red("certbot is not installed."));
3010
+ console.log(chalk21.dim("\nInstall with:"));
3011
+ console.log(chalk21.dim(" macOS: brew install certbot"));
3012
+ console.log(chalk21.dim(" Linux: apt install certbot python3-certbot-nginx"));
2733
3013
  process.exit(1);
2734
3014
  }
2735
3015
  const defaults = getDefaults();
2736
3016
  const email = options.email || defaults.sslEmail || `admin@${targetHostname.split(".").slice(-2).join(".")}`;
2737
- console.log(chalk20.blue(`
3017
+ console.log(chalk21.blue(`
2738
3018
  Requesting SSL certificate for ${targetHostname}...
2739
3019
  `));
2740
3020
  let cmd = `sudo certbot --nginx -d ${targetHostname} --non-interactive --agree-tos --email ${email}`;
2741
3021
  if (options.staging) {
2742
3022
  cmd += " --staging";
2743
- console.log(chalk20.yellow("Using staging server (test certificate)\n"));
3023
+ console.log(chalk21.yellow("Using staging server (test certificate)\n"));
2744
3024
  }
2745
- console.log(chalk20.dim(`Running: ${cmd}
3025
+ console.log(chalk21.dim(`Running: ${cmd}
2746
3026
  `));
2747
3027
  const result = execCommandSafe(cmd + " 2>&1");
2748
3028
  if (result.success) {
2749
- console.log(chalk20.green(`
3029
+ console.log(chalk21.green(`
2750
3030
  \u2713 SSL certificate installed for ${targetHostname}
2751
3031
  `));
2752
- console.log(chalk20.dim("Certificate will auto-renew via certbot timer."));
3032
+ console.log(chalk21.dim("Certificate will auto-renew via certbot timer."));
2753
3033
  } else if (result.output?.includes("Certificate not yet due for renewal")) {
2754
- console.log(chalk20.green(`
3034
+ console.log(chalk21.green(`
2755
3035
  \u2713 Certificate already exists and is valid
2756
3036
  `));
2757
- console.log(chalk20.dim("Use --force with certbot to renew early if needed."));
3037
+ console.log(chalk21.dim("Use --force with certbot to renew early if needed."));
2758
3038
  } 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."));
3039
+ console.log(chalk21.red("\n\u2717 Rate limit reached"));
3040
+ console.log(chalk21.dim("Let's Encrypt limits certificates per domain."));
3041
+ console.log(chalk21.dim("Try again later or use --staging for testing."));
2762
3042
  } 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."));
3043
+ console.log(chalk21.red("\n\u2717 Port 80 is in use"));
3044
+ console.log(chalk21.dim("Stop nginx temporarily or use webroot method."));
2765
3045
  } else {
2766
- console.log(chalk20.red("\n\u2717 Certificate request failed\n"));
3046
+ console.log(chalk21.red("\n\u2717 Certificate request failed\n"));
2767
3047
  if (result.output) {
2768
3048
  const lines = result.output.split("\n").filter(
2769
3049
  (l) => l.includes("Error") || l.includes("error") || l.includes("failed") || l.includes("Challenge")
2770
3050
  );
2771
3051
  for (const line of lines.slice(0, 5)) {
2772
- console.log(chalk20.dim(` ${line.trim()}`));
3052
+ console.log(chalk21.dim(` ${line.trim()}`));
2773
3053
  }
2774
3054
  }
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"));
3055
+ console.log(chalk21.dim("\nCommon issues:"));
3056
+ console.log(chalk21.dim(" - DNS not pointing to this server"));
3057
+ console.log(chalk21.dim(" - Port 80 not accessible from internet"));
3058
+ console.log(chalk21.dim(" - Firewall blocking HTTP validation"));
2779
3059
  }
2780
3060
  console.log("");
2781
3061
  }
2782
3062
 
2783
3063
  // src/commands/tunnel.ts
2784
- import chalk21 from "chalk";
3064
+ import chalk22 from "chalk";
2785
3065
  import { execSync as execSync4, spawn as spawn2 } from "child_process";
2786
3066
  async function tunnelCommand(action, options) {
2787
3067
  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"));
3068
+ console.log(chalk22.red("Usage: bindler tunnel <action>"));
3069
+ console.log(chalk22.dim("\nActions:"));
3070
+ console.log(chalk22.dim(" status Show tunnel status"));
3071
+ console.log(chalk22.dim(" start Start the tunnel"));
3072
+ console.log(chalk22.dim(" stop Stop the tunnel"));
3073
+ console.log(chalk22.dim(" login Authenticate with Cloudflare"));
3074
+ console.log(chalk22.dim(" create Create a new tunnel"));
3075
+ console.log(chalk22.dim(" list List all tunnels"));
3076
+ console.log(chalk22.dim("\nExamples:"));
3077
+ console.log(chalk22.dim(" bindler tunnel status"));
3078
+ console.log(chalk22.dim(" bindler tunnel start"));
3079
+ console.log(chalk22.dim(" bindler tunnel create --name mytunnel"));
2800
3080
  process.exit(1);
2801
3081
  }
2802
3082
  if (!isCloudflaredInstalled()) {
2803
- console.log(chalk21.red("cloudflared is not installed."));
2804
- console.log(chalk21.dim("\nInstall with: bindler setup"));
3083
+ console.log(chalk22.red("cloudflared is not installed."));
3084
+ console.log(chalk22.dim("\nInstall with: bindler setup"));
2805
3085
  process.exit(1);
2806
3086
  }
2807
3087
  const defaults = getDefaults();
2808
3088
  const tunnelName = options.name || defaults.tunnelName;
2809
3089
  switch (action) {
2810
3090
  case "status": {
2811
- console.log(chalk21.blue("\nTunnel Status\n"));
3091
+ console.log(chalk22.blue("\nTunnel Status\n"));
2812
3092
  const info = getTunnelInfo(tunnelName);
2813
3093
  if (!info.exists) {
2814
- console.log(chalk21.yellow(`Tunnel "${tunnelName}" does not exist.`));
2815
- console.log(chalk21.dim(`
3094
+ console.log(chalk22.yellow(`Tunnel "${tunnelName}" does not exist.`));
3095
+ console.log(chalk22.dim(`
2816
3096
  Create with: bindler tunnel create --name ${tunnelName}`));
2817
3097
  } 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")));
3098
+ console.log(chalk22.dim(" Name: ") + chalk22.white(tunnelName));
3099
+ console.log(chalk22.dim(" ID: ") + chalk22.white(info.id || "unknown"));
3100
+ console.log(chalk22.dim(" Running: ") + (info.running ? chalk22.green("yes") : chalk22.red("no")));
2821
3101
  if (!info.running) {
2822
- console.log(chalk21.dim(`
3102
+ console.log(chalk22.dim(`
2823
3103
  Start with: bindler tunnel start`));
2824
3104
  }
2825
3105
  }
@@ -2829,50 +3109,50 @@ Start with: bindler tunnel start`));
2829
3109
  case "start": {
2830
3110
  const info = getTunnelInfo(tunnelName);
2831
3111
  if (!info.exists) {
2832
- console.log(chalk21.red(`Tunnel "${tunnelName}" does not exist.`));
2833
- console.log(chalk21.dim(`Create with: bindler tunnel create`));
3112
+ console.log(chalk22.red(`Tunnel "${tunnelName}" does not exist.`));
3113
+ console.log(chalk22.dim(`Create with: bindler tunnel create`));
2834
3114
  process.exit(1);
2835
3115
  }
2836
3116
  if (info.running) {
2837
- console.log(chalk21.yellow(`Tunnel "${tunnelName}" is already running.`));
3117
+ console.log(chalk22.yellow(`Tunnel "${tunnelName}" is already running.`));
2838
3118
  process.exit(0);
2839
3119
  }
2840
- console.log(chalk21.blue(`Starting tunnel "${tunnelName}"...
3120
+ console.log(chalk22.blue(`Starting tunnel "${tunnelName}"...
2841
3121
  `));
2842
- console.log(chalk21.dim("Running in foreground. Press Ctrl+C to stop.\n"));
3122
+ console.log(chalk22.dim("Running in foreground. Press Ctrl+C to stop.\n"));
2843
3123
  const child = spawn2("cloudflared", ["tunnel", "run", tunnelName], {
2844
3124
  stdio: "inherit"
2845
3125
  });
2846
3126
  child.on("error", (err) => {
2847
- console.log(chalk21.red(`Failed to start tunnel: ${err.message}`));
3127
+ console.log(chalk22.red(`Failed to start tunnel: ${err.message}`));
2848
3128
  process.exit(1);
2849
3129
  });
2850
3130
  child.on("exit", (code) => {
2851
- console.log(chalk21.dim(`
3131
+ console.log(chalk22.dim(`
2852
3132
  Tunnel exited with code ${code}`));
2853
3133
  process.exit(code || 0);
2854
3134
  });
2855
3135
  break;
2856
3136
  }
2857
3137
  case "stop": {
2858
- console.log(chalk21.blue("Stopping tunnel...\n"));
3138
+ console.log(chalk22.blue("Stopping tunnel...\n"));
2859
3139
  const result = execCommandSafe(`pkill -f "cloudflared.*tunnel.*run.*${tunnelName}"`);
2860
3140
  if (result.success) {
2861
- console.log(chalk21.green(`\u2713 Tunnel "${tunnelName}" stopped`));
3141
+ console.log(chalk22.green(`\u2713 Tunnel "${tunnelName}" stopped`));
2862
3142
  } else {
2863
- console.log(chalk21.yellow(`Tunnel "${tunnelName}" was not running.`));
3143
+ console.log(chalk22.yellow(`Tunnel "${tunnelName}" was not running.`));
2864
3144
  }
2865
3145
  console.log("");
2866
3146
  break;
2867
3147
  }
2868
3148
  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"));
3149
+ console.log(chalk22.blue("Authenticating with Cloudflare...\n"));
3150
+ console.log(chalk22.dim("A browser window will open. Follow the instructions.\n"));
2871
3151
  try {
2872
3152
  execSync4("cloudflared tunnel login", { stdio: "inherit" });
2873
- console.log(chalk21.green("\n\u2713 Authentication successful!"));
3153
+ console.log(chalk22.green("\n\u2713 Authentication successful!"));
2874
3154
  } catch {
2875
- console.log(chalk21.red("\n\u2717 Authentication failed or cancelled."));
3155
+ console.log(chalk22.red("\n\u2717 Authentication failed or cancelled."));
2876
3156
  }
2877
3157
  console.log("");
2878
3158
  break;
@@ -2881,69 +3161,69 @@ Tunnel exited with code ${code}`));
2881
3161
  const existingTunnels = listTunnels();
2882
3162
  const exists = existingTunnels.some((t) => t.name === tunnelName);
2883
3163
  if (exists) {
2884
- console.log(chalk21.yellow(`Tunnel "${tunnelName}" already exists.`));
2885
- console.log(chalk21.dim("\nUse a different name with --name"));
3164
+ console.log(chalk22.yellow(`Tunnel "${tunnelName}" already exists.`));
3165
+ console.log(chalk22.dim("\nUse a different name with --name"));
2886
3166
  process.exit(1);
2887
3167
  }
2888
- console.log(chalk21.blue(`Creating tunnel "${tunnelName}"...
3168
+ console.log(chalk22.blue(`Creating tunnel "${tunnelName}"...
2889
3169
  `));
2890
3170
  try {
2891
3171
  execSync4(`cloudflared tunnel create ${tunnelName}`, { stdio: "inherit" });
2892
- console.log(chalk21.green(`
3172
+ console.log(chalk22.green(`
2893
3173
  \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"));
3174
+ console.log(chalk22.dim("\nNext steps:"));
3175
+ console.log(chalk22.dim(" 1. Create ~/.cloudflared/config.yml"));
3176
+ console.log(chalk22.dim(" 2. Run: bindler tunnel start"));
2897
3177
  } 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"));
3178
+ console.log(chalk22.red("\n\u2717 Failed to create tunnel."));
3179
+ console.log(chalk22.dim("Make sure you're logged in: bindler tunnel login"));
2900
3180
  }
2901
3181
  console.log("");
2902
3182
  break;
2903
3183
  }
2904
3184
  case "list": {
2905
- console.log(chalk21.blue("\nCloudflare Tunnels\n"));
3185
+ console.log(chalk22.blue("\nCloudflare Tunnels\n"));
2906
3186
  const tunnels = listTunnels();
2907
3187
  if (tunnels.length === 0) {
2908
- console.log(chalk21.dim("No tunnels found."));
2909
- console.log(chalk21.dim("\nCreate one with: bindler tunnel create"));
3188
+ console.log(chalk22.dim("No tunnels found."));
3189
+ console.log(chalk22.dim("\nCreate one with: bindler tunnel create"));
2910
3190
  } else {
2911
3191
  for (const tunnel of tunnels) {
2912
3192
  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)}...)`));
3193
+ const prefix = isDefault ? chalk22.cyan("\u2192 ") : " ";
3194
+ console.log(prefix + chalk22.white(tunnel.name) + chalk22.dim(` (${tunnel.id.slice(0, 8)}...)`));
2915
3195
  }
2916
- console.log(chalk21.dim(`
3196
+ console.log(chalk22.dim(`
2917
3197
  ${tunnels.length} tunnel(s)`));
2918
3198
  }
2919
3199
  console.log("");
2920
3200
  break;
2921
3201
  }
2922
3202
  default:
2923
- console.log(chalk21.red(`Unknown action: ${action}`));
2924
- console.log(chalk21.dim("Run `bindler tunnel` for usage."));
3203
+ console.log(chalk22.red(`Unknown action: ${action}`));
3204
+ console.log(chalk22.dim("Run `bindler tunnel` for usage."));
2925
3205
  process.exit(1);
2926
3206
  }
2927
3207
  }
2928
3208
 
2929
3209
  // src/commands/open.ts
2930
- import chalk22 from "chalk";
3210
+ import chalk23 from "chalk";
2931
3211
  import { exec } from "child_process";
2932
3212
  async function openCommand(name) {
2933
3213
  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"));
3214
+ console.log(chalk23.red("Usage: bindler open <name>"));
3215
+ console.log(chalk23.dim("\nOpen a project in your browser"));
3216
+ console.log(chalk23.dim("\nExamples:"));
3217
+ console.log(chalk23.dim(" bindler open myapp"));
2938
3218
  process.exit(1);
2939
3219
  }
2940
3220
  const project = getProject(name);
2941
3221
  if (!project) {
2942
- console.log(chalk22.red(`Project "${name}" not found.`));
2943
- console.log(chalk22.dim("\nAvailable projects:"));
3222
+ console.log(chalk23.red(`Project "${name}" not found.`));
3223
+ console.log(chalk23.dim("\nAvailable projects:"));
2944
3224
  const projects = listProjects();
2945
3225
  for (const p of projects) {
2946
- console.log(chalk22.dim(` - ${p.name}`));
3226
+ console.log(chalk23.dim(` - ${p.name}`));
2947
3227
  }
2948
3228
  process.exit(1);
2949
3229
  }
@@ -2964,7 +3244,7 @@ async function openCommand(name) {
2964
3244
  if (project.basePath && project.basePath !== "/") {
2965
3245
  url += project.basePath;
2966
3246
  }
2967
- console.log(chalk22.dim(`Opening ${url}...`));
3247
+ console.log(chalk23.dim(`Opening ${url}...`));
2968
3248
  const platform = process.platform;
2969
3249
  let cmd;
2970
3250
  if (platform === "darwin") {
@@ -2976,15 +3256,15 @@ async function openCommand(name) {
2976
3256
  }
2977
3257
  exec(cmd, (error) => {
2978
3258
  if (error) {
2979
- console.log(chalk22.yellow(`Could not open browser automatically.`));
2980
- console.log(chalk22.dim(`
2981
- Open manually: ${chalk22.cyan(url)}`));
3259
+ console.log(chalk23.yellow(`Could not open browser automatically.`));
3260
+ console.log(chalk23.dim(`
3261
+ Open manually: ${chalk23.cyan(url)}`));
2982
3262
  }
2983
3263
  });
2984
3264
  }
2985
3265
 
2986
3266
  // src/commands/health.ts
2987
- import chalk23 from "chalk";
3267
+ import chalk24 from "chalk";
2988
3268
  async function pingUrl(url, timeout = 5e3) {
2989
3269
  const start = Date.now();
2990
3270
  try {
@@ -3014,15 +3294,15 @@ async function healthCommand() {
3014
3294
  const projects = listProjects();
3015
3295
  const defaults = getDefaults();
3016
3296
  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"));
3297
+ console.log(chalk24.yellow("\nNo projects registered."));
3298
+ console.log(chalk24.dim("Run `bindler new` to add a project.\n"));
3019
3299
  return;
3020
3300
  }
3021
- console.log(chalk23.blue("\nHealth Check\n"));
3301
+ console.log(chalk24.blue("\nHealth Check\n"));
3022
3302
  const results = [];
3023
3303
  for (const project of projects) {
3024
3304
  if (project.enabled === false) {
3025
- console.log(chalk23.dim(` - ${project.name} (disabled)`));
3305
+ console.log(chalk24.dim(` - ${project.name} (disabled)`));
3026
3306
  continue;
3027
3307
  }
3028
3308
  const isLocal = project.local || project.hostname.endsWith(".local");
@@ -3041,33 +3321,33 @@ async function healthCommand() {
3041
3321
  if (project.basePath && project.basePath !== "/") {
3042
3322
  url += project.basePath;
3043
3323
  }
3044
- process.stdout.write(chalk23.dim(` Checking ${project.name}...`));
3324
+ process.stdout.write(chalk24.dim(` Checking ${project.name}...`));
3045
3325
  const result = await pingUrl(url);
3046
3326
  results.push({ name: project.name, hostname: project.hostname, ...result });
3047
3327
  process.stdout.write("\r\x1B[K");
3048
3328
  if (result.ok) {
3049
- console.log(chalk23.green(" \u2713 ") + chalk23.white(project.name) + chalk23.dim(` (${result.time}ms)`));
3329
+ console.log(chalk24.green(" \u2713 ") + chalk24.white(project.name) + chalk24.dim(` (${result.time}ms)`));
3050
3330
  } else if (result.status) {
3051
- console.log(chalk23.yellow(" ! ") + chalk23.white(project.name) + chalk23.dim(` (${result.status})`));
3331
+ console.log(chalk24.yellow(" ! ") + chalk24.white(project.name) + chalk24.dim(` (${result.status})`));
3052
3332
  } else {
3053
- console.log(chalk23.red(" \u2717 ") + chalk23.white(project.name) + chalk23.dim(` (${result.error})`));
3333
+ console.log(chalk24.red(" \u2717 ") + chalk24.white(project.name) + chalk24.dim(` (${result.error})`));
3054
3334
  }
3055
3335
  }
3056
3336
  const healthy = results.filter((r) => r.ok).length;
3057
3337
  const unhealthy = results.filter((r) => !r.ok).length;
3058
3338
  console.log("");
3059
3339
  if (unhealthy === 0) {
3060
- console.log(chalk23.green(`\u2713 All ${healthy} project(s) healthy`));
3340
+ console.log(chalk24.green(`\u2713 All ${healthy} project(s) healthy`));
3061
3341
  } else if (healthy === 0) {
3062
- console.log(chalk23.red(`\u2717 All ${unhealthy} project(s) down`));
3342
+ console.log(chalk24.red(`\u2717 All ${unhealthy} project(s) down`));
3063
3343
  } else {
3064
- console.log(chalk23.yellow(`! ${healthy} healthy, ${unhealthy} down`));
3344
+ console.log(chalk24.yellow(`! ${healthy} healthy, ${unhealthy} down`));
3065
3345
  }
3066
3346
  console.log("");
3067
3347
  }
3068
3348
 
3069
3349
  // src/commands/stats.ts
3070
- import chalk24 from "chalk";
3350
+ import chalk25 from "chalk";
3071
3351
  function formatBytes2(bytes) {
3072
3352
  if (bytes === 0) return "0 B";
3073
3353
  const k = 1024;
@@ -3090,15 +3370,15 @@ async function statsCommand() {
3090
3370
  const pm2Processes = getPm2List();
3091
3371
  const npmProjects = projects.filter((p) => p.type === "npm");
3092
3372
  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"));
3373
+ console.log(chalk25.yellow("\nNo npm projects registered."));
3374
+ console.log(chalk25.dim("Stats are only available for npm projects.\n"));
3095
3375
  return;
3096
3376
  }
3097
- console.log(chalk24.blue("\nProject Stats\n"));
3377
+ console.log(chalk25.blue("\nProject Stats\n"));
3098
3378
  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")
3379
+ 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
3380
  );
3101
- console.log(chalk24.dim(" " + "-".repeat(70)));
3381
+ console.log(chalk25.dim(" " + "-".repeat(70)));
3102
3382
  let totalCpu = 0;
3103
3383
  let totalMem = 0;
3104
3384
  for (const project of npmProjects) {
@@ -3107,11 +3387,11 @@ async function statsCommand() {
3107
3387
  const name = project.name.slice(0, 18).padEnd(20);
3108
3388
  if (!pm2Process) {
3109
3389
  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("-")
3390
+ " " + chalk25.white(name) + chalk25.dim("not managed".padEnd(10)) + chalk25.dim("-".padEnd(8)) + chalk25.dim("-".padEnd(10)) + chalk25.dim("-".padEnd(10)) + chalk25.dim("-")
3111
3391
  );
3112
3392
  continue;
3113
3393
  }
3114
- const statusColor = pm2Process.status === "online" ? chalk24.green : chalk24.red;
3394
+ const statusColor = pm2Process.status === "online" ? chalk25.green : chalk25.red;
3115
3395
  const status = statusColor(pm2Process.status.padEnd(10));
3116
3396
  const cpu = `${pm2Process.cpu.toFixed(1)}%`.padEnd(8);
3117
3397
  const mem = formatBytes2(pm2Process.memory).padEnd(10);
@@ -3120,19 +3400,19 @@ async function statsCommand() {
3120
3400
  totalCpu += pm2Process.cpu;
3121
3401
  totalMem += pm2Process.memory;
3122
3402
  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))
3403
+ " " + 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
3404
  );
3125
3405
  }
3126
3406
  const runningCount = pm2Processes.filter((p) => p.name.startsWith("bindler:") && p.status === "online").length;
3127
- console.log(chalk24.dim(" " + "-".repeat(70)));
3407
+ console.log(chalk25.dim(" " + "-".repeat(70)));
3128
3408
  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))
3409
+ " " + 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
3410
  );
3131
3411
  console.log("");
3132
3412
  }
3133
3413
 
3134
3414
  // src/commands/completion.ts
3135
- import chalk25 from "chalk";
3415
+ import chalk26 from "chalk";
3136
3416
  var BASH_COMPLETION = `
3137
3417
  # bindler bash completion
3138
3418
  _bindler_completions() {
@@ -3265,12 +3545,12 @@ complete -c bindler -n '__fish_seen_subcommand_from tunnel' -a 'status start sto
3265
3545
  `;
3266
3546
  async function completionCommand(shell) {
3267
3547
  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"));
3548
+ console.log(chalk26.red("Usage: bindler completion <shell>"));
3549
+ console.log(chalk26.dim("\nSupported shells: bash, zsh, fish"));
3550
+ console.log(chalk26.dim("\nSetup:"));
3551
+ console.log(chalk26.dim(" bash: bindler completion bash >> ~/.bashrc"));
3552
+ console.log(chalk26.dim(" zsh: bindler completion zsh >> ~/.zshrc"));
3553
+ console.log(chalk26.dim(" fish: bindler completion fish > ~/.config/fish/completions/bindler.fish"));
3274
3554
  process.exit(1);
3275
3555
  }
3276
3556
  switch (shell) {
@@ -3284,32 +3564,32 @@ async function completionCommand(shell) {
3284
3564
  console.log(FISH_COMPLETION.trim());
3285
3565
  break;
3286
3566
  default:
3287
- console.log(chalk25.red(`Unknown shell: ${shell}`));
3288
- console.log(chalk25.dim("Supported: bash, zsh, fish"));
3567
+ console.log(chalk26.red(`Unknown shell: ${shell}`));
3568
+ console.log(chalk26.dim("Supported: bash, zsh, fish"));
3289
3569
  process.exit(1);
3290
3570
  }
3291
3571
  }
3292
3572
 
3293
3573
  // src/commands/clone.ts
3294
- import chalk26 from "chalk";
3574
+ import chalk27 from "chalk";
3295
3575
  import inquirer5 from "inquirer";
3296
3576
  async function cloneCommand(source, newName, options) {
3297
3577
  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"));
3578
+ console.log(chalk27.red("Usage: bindler clone <source> <new-name>"));
3579
+ console.log(chalk27.dim("\nClones a project configuration with a new name"));
3580
+ console.log(chalk27.dim("\nExamples:"));
3581
+ console.log(chalk27.dim(" bindler clone myapp myapp-staging"));
3582
+ console.log(chalk27.dim(" bindler clone myapp myapp-v2 --hostname newapp.example.com"));
3583
+ console.log(chalk27.dim(" bindler clone myapp myapp-copy --path /var/www/newapp"));
3304
3584
  process.exit(1);
3305
3585
  }
3306
3586
  const sourceProject = getProject(source);
3307
3587
  if (!sourceProject) {
3308
- console.log(chalk26.red(`Project "${source}" not found.`));
3309
- console.log(chalk26.dim("\nAvailable projects:"));
3588
+ console.log(chalk27.red(`Project "${source}" not found.`));
3589
+ console.log(chalk27.dim("\nAvailable projects:"));
3310
3590
  const projects = listProjects();
3311
3591
  for (const p of projects) {
3312
- console.log(chalk26.dim(` - ${p.name}`));
3592
+ console.log(chalk27.dim(` - ${p.name}`));
3313
3593
  }
3314
3594
  process.exit(1);
3315
3595
  }
@@ -3335,11 +3615,11 @@ async function cloneCommand(source, newName, options) {
3335
3615
  targetName = answer.name;
3336
3616
  } else {
3337
3617
  if (!validateProjectName(targetName)) {
3338
- console.log(chalk26.red("Invalid project name. Use alphanumeric characters, dashes, and underscores."));
3618
+ console.log(chalk27.red("Invalid project name. Use alphanumeric characters, dashes, and underscores."));
3339
3619
  process.exit(1);
3340
3620
  }
3341
3621
  if (getProject(targetName)) {
3342
- console.log(chalk26.red(`Project "${targetName}" already exists.`));
3622
+ console.log(chalk27.red(`Project "${targetName}" already exists.`));
3343
3623
  process.exit(1);
3344
3624
  }
3345
3625
  }
@@ -3362,7 +3642,7 @@ async function cloneCommand(source, newName, options) {
3362
3642
  targetHostname = answer.hostname;
3363
3643
  } else {
3364
3644
  if (!validateHostname(targetHostname)) {
3365
- console.log(chalk26.red("Invalid hostname format."));
3645
+ console.log(chalk27.red("Invalid hostname format."));
3366
3646
  process.exit(1);
3367
3647
  }
3368
3648
  }
@@ -3380,36 +3660,36 @@ async function cloneCommand(source, newName, options) {
3380
3660
  }
3381
3661
  try {
3382
3662
  addProject(newProject);
3383
- console.log(chalk26.green(`
3663
+ console.log(chalk27.green(`
3384
3664
  Project "${targetName}" cloned from "${source}"!
3385
3665
  `));
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}`));
3666
+ console.log(chalk27.dim("Configuration:"));
3667
+ console.log(chalk27.dim(` Name: ${newProject.name}`));
3668
+ console.log(chalk27.dim(` Type: ${newProject.type}`));
3669
+ console.log(chalk27.dim(` Path: ${newProject.path}`));
3670
+ console.log(chalk27.dim(` Hostname: ${newProject.hostname}`));
3391
3671
  if (newProject.port) {
3392
- console.log(chalk26.dim(` Port: ${newProject.port}`));
3672
+ console.log(chalk27.dim(` Port: ${newProject.port}`));
3393
3673
  }
3394
- console.log(chalk26.dim(`
3395
- Run ${chalk26.cyan("sudo bindler apply")} to update nginx configuration.`));
3674
+ console.log(chalk27.dim(`
3675
+ Run ${chalk27.cyan("sudo bindler apply")} to update nginx configuration.`));
3396
3676
  if (newProject.type === "npm") {
3397
- console.log(chalk26.dim(`Run ${chalk26.cyan(`bindler start ${targetName}`)} to start the application.`));
3677
+ console.log(chalk27.dim(`Run ${chalk27.cyan(`bindler start ${targetName}`)} to start the application.`));
3398
3678
  }
3399
3679
  } catch (error) {
3400
- console.error(chalk26.red(`Error: ${error instanceof Error ? error.message : error}`));
3680
+ console.error(chalk27.red(`Error: ${error instanceof Error ? error.message : error}`));
3401
3681
  process.exit(1);
3402
3682
  }
3403
3683
  }
3404
3684
 
3405
3685
  // src/commands/dev.ts
3406
- import chalk27 from "chalk";
3686
+ import chalk28 from "chalk";
3407
3687
  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";
3688
+ import { existsSync as existsSync13, readFileSync as readFileSync7 } from "fs";
3689
+ import { basename as basename3, join as join8 } from "path";
3410
3690
  function getPackageJson(dir) {
3411
- const pkgPath = join7(dir, "package.json");
3412
- if (!existsSync11(pkgPath)) return null;
3691
+ const pkgPath = join8(dir, "package.json");
3692
+ if (!existsSync13(pkgPath)) return null;
3413
3693
  try {
3414
3694
  return JSON.parse(readFileSync7(pkgPath, "utf-8"));
3415
3695
  } catch {
@@ -3433,11 +3713,11 @@ async function devCommand(name, options) {
3433
3713
  if (name) {
3434
3714
  project = getProject(name);
3435
3715
  if (!project) {
3436
- console.log(chalk27.red(`Project "${name}" not found.`));
3437
- console.log(chalk27.dim("\nAvailable projects:"));
3716
+ console.log(chalk28.red(`Project "${name}" not found.`));
3717
+ console.log(chalk28.dim("\nAvailable projects:"));
3438
3718
  const projects = listProjects();
3439
3719
  for (const p of projects) {
3440
- console.log(chalk27.dim(` - ${p.name}`));
3720
+ console.log(chalk28.dim(` - ${p.name}`));
3441
3721
  }
3442
3722
  process.exit(1);
3443
3723
  }
@@ -3448,7 +3728,7 @@ async function devCommand(name, options) {
3448
3728
  if (!project) {
3449
3729
  const yamlConfig = readBindlerYaml(cwd);
3450
3730
  if (yamlConfig) {
3451
- console.log(chalk27.cyan("Found bindler.yaml - creating temporary dev project\n"));
3731
+ console.log(chalk28.cyan("Found bindler.yaml - creating temporary dev project\n"));
3452
3732
  const yamlProject = yamlToProject(yamlConfig, cwd);
3453
3733
  project = {
3454
3734
  name: yamlProject.name || basename3(cwd),
@@ -3462,9 +3742,9 @@ async function devCommand(name, options) {
3462
3742
  } else {
3463
3743
  const pkg = getPackageJson(cwd);
3464
3744
  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"));
3745
+ console.log(chalk28.red("No package.json found in current directory."));
3746
+ console.log(chalk28.dim("\nUsage: bindler dev [name]"));
3747
+ console.log(chalk28.dim(" Run in a project directory or specify a project name"));
3468
3748
  process.exit(1);
3469
3749
  }
3470
3750
  project = {
@@ -3480,35 +3760,35 @@ async function devCommand(name, options) {
3480
3760
  projectDir = cwd;
3481
3761
  }
3482
3762
  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));
3763
+ console.log(chalk28.red("Dev mode is only supported for npm projects."));
3764
+ console.log(chalk28.dim("\nFor static projects, use a local web server:"));
3765
+ console.log(chalk28.dim(" npx serve " + project.path));
3486
3766
  process.exit(1);
3487
3767
  }
3488
- if (!existsSync11(projectDir)) {
3489
- console.log(chalk27.red(`Project directory not found: ${projectDir}`));
3768
+ if (!existsSync13(projectDir)) {
3769
+ console.log(chalk28.red(`Project directory not found: ${projectDir}`));
3490
3770
  process.exit(1);
3491
3771
  }
3492
3772
  const devCmd = getDevCommand(projectDir) || project.start || "npm start";
3493
3773
  const port = options.port || project.port || findAvailablePort();
3494
- console.log(chalk27.blue(`
3774
+ console.log(chalk28.blue(`
3495
3775
  Starting ${project.name} in dev mode...
3496
3776
  `));
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}`));
3777
+ console.log(chalk28.dim(` Directory: ${projectDir}`));
3778
+ console.log(chalk28.dim(` Command: ${devCmd}`));
3779
+ console.log(chalk28.dim(` Port: ${port}`));
3780
+ console.log(chalk28.dim(` Hostname: ${project.hostname}`));
3501
3781
  if (project.hostname.endsWith(".local") || project.local) {
3502
- console.log(chalk27.yellow(`
3782
+ console.log(chalk28.yellow(`
3503
3783
  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`));
3784
+ console.log(chalk28.cyan(` echo "127.0.0.1 ${project.hostname}" | sudo tee -a /etc/hosts`));
3505
3785
  }
3506
3786
  const defaults = getDefaults();
3507
3787
  const listenPort = defaults.nginxListen.split(":")[1] || "8080";
3508
- console.log(chalk27.green(`
3788
+ console.log(chalk28.green(`
3509
3789
  Access at: http://${project.hostname}:${listenPort}`));
3510
- console.log(chalk27.dim("Press Ctrl+C to stop\n"));
3511
- console.log(chalk27.dim("---"));
3790
+ console.log(chalk28.dim("Press Ctrl+C to stop\n"));
3791
+ console.log(chalk28.dim("---"));
3512
3792
  const env = {
3513
3793
  ...process.env,
3514
3794
  PORT: String(port),
@@ -3522,19 +3802,19 @@ Access at: http://${project.hostname}:${listenPort}`));
3522
3802
  shell: true
3523
3803
  });
3524
3804
  child.on("error", (error) => {
3525
- console.error(chalk27.red(`
3805
+ console.error(chalk28.red(`
3526
3806
  Failed to start: ${error.message}`));
3527
3807
  process.exit(1);
3528
3808
  });
3529
3809
  child.on("exit", (code) => {
3530
3810
  if (code !== 0) {
3531
- console.log(chalk27.yellow(`
3811
+ console.log(chalk28.yellow(`
3532
3812
  Process exited with code ${code}`));
3533
3813
  }
3534
3814
  process.exit(code || 0);
3535
3815
  });
3536
3816
  process.on("SIGINT", () => {
3537
- console.log(chalk27.dim("\n\nStopping dev server..."));
3817
+ console.log(chalk28.dim("\n\nStopping dev server..."));
3538
3818
  child.kill("SIGINT");
3539
3819
  });
3540
3820
  process.on("SIGTERM", () => {
@@ -3543,16 +3823,16 @@ Process exited with code ${code}`));
3543
3823
  }
3544
3824
 
3545
3825
  // 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";
3826
+ import chalk29 from "chalk";
3827
+ import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
3828
+ import { join as join9 } from "path";
3829
+ import { homedir as homedir4 } from "os";
3550
3830
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
3551
- var CACHE_DIR = join8(homedir3(), ".config", "bindler");
3552
- var CACHE_FILE = join8(CACHE_DIR, ".update-check");
3831
+ var CACHE_DIR = join9(homedir4(), ".config", "bindler");
3832
+ var CACHE_FILE = join9(CACHE_DIR, ".update-check");
3553
3833
  function readCache() {
3554
3834
  try {
3555
- if (existsSync12(CACHE_FILE)) {
3835
+ if (existsSync14(CACHE_FILE)) {
3556
3836
  return JSON.parse(readFileSync8(CACHE_FILE, "utf-8"));
3557
3837
  }
3558
3838
  } catch {
@@ -3561,7 +3841,7 @@ function readCache() {
3561
3841
  }
3562
3842
  function writeCache(data) {
3563
3843
  try {
3564
- if (!existsSync12(CACHE_DIR)) {
3844
+ if (!existsSync14(CACHE_DIR)) {
3565
3845
  mkdirSync5(CACHE_DIR, { recursive: true });
3566
3846
  }
3567
3847
  writeFileSync6(CACHE_FILE, JSON.stringify(data));
@@ -3596,28 +3876,28 @@ async function checkForUpdates() {
3596
3876
  const cache = readCache();
3597
3877
  const now = Date.now();
3598
3878
  if (now - cache.lastCheck < CHECK_INTERVAL) {
3599
- if (cache.latestVersion && compareVersions("1.2.0", cache.latestVersion) < 0) {
3879
+ if (cache.latestVersion && compareVersions("1.3.0", cache.latestVersion) < 0) {
3600
3880
  showUpdateMessage(cache.latestVersion);
3601
3881
  }
3602
3882
  return;
3603
3883
  }
3604
3884
  fetchLatestVersion().then((latestVersion) => {
3605
3885
  writeCache({ lastCheck: now, latestVersion });
3606
- if (latestVersion && compareVersions("1.2.0", latestVersion) < 0) {
3886
+ if (latestVersion && compareVersions("1.3.0", latestVersion) < 0) {
3607
3887
  showUpdateMessage(latestVersion);
3608
3888
  }
3609
3889
  });
3610
3890
  }
3611
3891
  function showUpdateMessage(latestVersion) {
3612
3892
  console.log("");
3613
- console.log(chalk28.yellow(` Update available: ${"1.2.0"} \u2192 ${latestVersion}`));
3614
- console.log(chalk28.dim(` Run: npm update -g bindler`));
3893
+ console.log(chalk29.yellow(` Update available: ${"1.3.0"} \u2192 ${latestVersion}`));
3894
+ console.log(chalk29.dim(` Run: npm update -g bindler`));
3615
3895
  console.log("");
3616
3896
  }
3617
3897
 
3618
3898
  // src/cli.ts
3619
3899
  var program = new Command();
3620
- program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.2.0");
3900
+ program.name("bindler").description("Manage multiple projects behind Cloudflare Tunnel with Nginx and PM2").version("1.3.0");
3621
3901
  program.hook("preAction", async () => {
3622
3902
  try {
3623
3903
  initConfig();
@@ -3636,78 +3916,78 @@ program.command("status").description("Show detailed status of all projects").ac
3636
3916
  });
3637
3917
  program.command("start [name]").description("Start an npm project with PM2").option("-a, --all", "Start all npm projects").action(async (name, options) => {
3638
3918
  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"));
3919
+ console.log(chalk30.red("Usage: bindler start <name> or bindler start --all"));
3920
+ console.log(chalk30.dim("\nExamples:"));
3921
+ console.log(chalk30.dim(" bindler start myapp"));
3922
+ console.log(chalk30.dim(" bindler start --all # start all npm projects"));
3643
3923
  process.exit(1);
3644
3924
  }
3645
3925
  await startCommand(name, options);
3646
3926
  });
3647
3927
  program.command("stop [name]").description("Stop an npm project").option("-a, --all", "Stop all npm projects").action(async (name, options) => {
3648
3928
  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"));
3929
+ console.log(chalk30.red("Usage: bindler stop <name> or bindler stop --all"));
3930
+ console.log(chalk30.dim("\nExamples:"));
3931
+ console.log(chalk30.dim(" bindler stop myapp"));
3932
+ console.log(chalk30.dim(" bindler stop --all # stop all npm projects"));
3653
3933
  process.exit(1);
3654
3934
  }
3655
3935
  await stopCommand(name, options);
3656
3936
  });
3657
3937
  program.command("restart [name]").description("Restart an npm project").option("-a, --all", "Restart all npm projects").action(async (name, options) => {
3658
3938
  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"));
3939
+ console.log(chalk30.red("Usage: bindler restart <name> or bindler restart --all"));
3940
+ console.log(chalk30.dim("\nExamples:"));
3941
+ console.log(chalk30.dim(" bindler restart myapp"));
3942
+ console.log(chalk30.dim(" bindler restart --all # restart all npm projects"));
3663
3943
  process.exit(1);
3664
3944
  }
3665
3945
  await restartCommand(name, options);
3666
3946
  });
3667
3947
  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
3948
  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"));
3949
+ console.log(chalk30.red("Usage: bindler logs <name>"));
3950
+ console.log(chalk30.dim("\nExamples:"));
3951
+ console.log(chalk30.dim(" bindler logs myapp"));
3952
+ console.log(chalk30.dim(" bindler logs myapp --follow"));
3953
+ console.log(chalk30.dim(" bindler logs myapp --lines 500"));
3674
3954
  process.exit(1);
3675
3955
  }
3676
3956
  await logsCommand(name, { ...options, lines: parseInt(options.lines, 10) });
3677
3957
  });
3678
3958
  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
3959
  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"));
3960
+ console.log(chalk30.red("Usage: bindler update <name> [options]"));
3961
+ console.log(chalk30.dim("\nExamples:"));
3962
+ console.log(chalk30.dim(" bindler update myapp --hostname newapp.example.com"));
3963
+ console.log(chalk30.dim(" bindler update myapp --port 4000"));
3964
+ console.log(chalk30.dim(" bindler update myapp --disable"));
3685
3965
  process.exit(1);
3686
3966
  }
3687
3967
  await updateCommand(name, options);
3688
3968
  });
3689
3969
  program.command("edit [name]").description("Edit project configuration in $EDITOR").action(async (name) => {
3690
3970
  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"));
3971
+ console.log(chalk30.red("Usage: bindler edit <name>"));
3972
+ console.log(chalk30.dim("\nOpens the project config in your $EDITOR"));
3973
+ console.log(chalk30.dim("\nExample:"));
3974
+ console.log(chalk30.dim(" bindler edit myapp"));
3695
3975
  process.exit(1);
3696
3976
  }
3697
3977
  await editCommand(name);
3698
3978
  });
3699
3979
  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
3980
  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"));
3981
+ console.log(chalk30.red("Usage: bindler remove <name>"));
3982
+ console.log(chalk30.dim("\nExamples:"));
3983
+ console.log(chalk30.dim(" bindler remove myapp"));
3984
+ console.log(chalk30.dim(" bindler remove myapp --force # skip confirmation"));
3985
+ console.log(chalk30.dim(" bindler rm myapp # alias"));
3706
3986
  process.exit(1);
3707
3987
  }
3708
3988
  await removeCommand(name, options);
3709
3989
  });
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) => {
3990
+ 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
3991
  await applyCommand(options);
3712
3992
  });
3713
3993
  program.command("doctor").description("Run system diagnostics and check dependencies").action(async () => {
@@ -3721,10 +4001,10 @@ program.command("info").description("Show bindler information and stats").action
3721
4001
  });
3722
4002
  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
4003
  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"));
4004
+ console.log(chalk30.red("Usage: bindler check <hostname>"));
4005
+ console.log(chalk30.dim("\nExamples:"));
4006
+ console.log(chalk30.dim(" bindler check myapp.example.com"));
4007
+ console.log(chalk30.dim(" bindler check myapp # uses project name"));
3728
4008
  process.exit(1);
3729
4009
  }
3730
4010
  await checkCommand(hostname, options);