@it-club/provisor 0.2.2 → 0.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
@@ -6,7 +6,7 @@ import { render } from "ink";
6
6
 
7
7
  // src/commands/init.tsx
8
8
  import { useState as useState2, useEffect } from "react";
9
- import { Box as Box4, Text as Text4, useApp } from "ink";
9
+ import { Box as Box5, Text as Text5, useApp } from "ink";
10
10
 
11
11
  // src/components/Task.tsx
12
12
  import { Box, Text } from "ink";
@@ -17,7 +17,8 @@ var statusIcons = {
17
17
  running: /* @__PURE__ */ jsx(Text, { color: "cyan", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
18
18
  success: /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
19
19
  error: /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717" }),
20
- skipped: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "-" })
20
+ skipped: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "-" }),
21
+ warning: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u26A0" })
21
22
  };
22
23
  function Task({ label, status, message }) {
23
24
  return /* @__PURE__ */ jsxs(Box, { children: [
@@ -68,6 +69,33 @@ function Confirm({ message, onConfirm }) {
68
69
  ] });
69
70
  }
70
71
 
72
+ // src/components/Layout.tsx
73
+ import { Box as Box4, Text as Text4 } from "ink";
74
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
75
+ function Layout({ title, subtitle, children, footerStatus, footerTips }) {
76
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height: "100%", children: [
77
+ /* @__PURE__ */ jsx4(Header, { title: title || "", subtitle: subtitle || "" }),
78
+ /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", flexGrow: 1, paddingX: 1, children }),
79
+ /* @__PURE__ */ jsxs4(
80
+ Box4,
81
+ {
82
+ borderStyle: "single",
83
+ borderColor: "gray",
84
+ flexDirection: "column",
85
+ paddingX: 1,
86
+ marginTop: 1,
87
+ children: [
88
+ /* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", children: [
89
+ /* @__PURE__ */ jsx4(Text4, { color: "blue", children: footerStatus || "Provisor CLI" }),
90
+ /* @__PURE__ */ jsx4(Text4, { color: "gray", children: (/* @__PURE__ */ new Date()).toLocaleTimeString() })
91
+ ] }),
92
+ footerTips && footerTips.length > 0 && /* @__PURE__ */ jsx4(Box4, { marginTop: 0, children: /* @__PURE__ */ jsx4(Text4, { color: "gray", children: footerTips.join(" | ") }) })
93
+ ]
94
+ }
95
+ )
96
+ ] });
97
+ }
98
+
71
99
  // src/utils/ssh.ts
72
100
  import { Client } from "ssh2";
73
101
  import { readFileSync, existsSync } from "fs";
@@ -98,7 +126,10 @@ function createSSHConfig(options) {
98
126
  host: options.host,
99
127
  port: parseInt(String(options.port || 22), 10),
100
128
  username: options.user || "root",
101
- privateKey: readFileSync(keyPath)
129
+ privateKey: readFileSync(keyPath),
130
+ readyTimeout: 3e4,
131
+ keepaliveInterval: 1e4,
132
+ tryKeyboard: true
102
133
  };
103
134
  }
104
135
  function connect(options) {
@@ -136,12 +167,31 @@ async function execScript(client, script, useSudo = false) {
136
167
  const command = useSudo ? `sudo bash -c '${escapedScript}'` : `bash -c '${escapedScript}'`;
137
168
  return exec(client, command);
138
169
  }
170
+ function execStream(client, command, onStdout, onStderr) {
171
+ return new Promise((resolve, reject) => {
172
+ client.exec(command, (err, stream) => {
173
+ if (err) {
174
+ reject(err);
175
+ return;
176
+ }
177
+ stream.on("data", (data) => {
178
+ onStdout(data.toString());
179
+ });
180
+ stream.stderr.on("data", (data) => {
181
+ onStderr(data.toString());
182
+ });
183
+ stream.on("close", (code) => {
184
+ resolve(code ?? 0);
185
+ });
186
+ });
187
+ });
188
+ }
139
189
  function disconnect(client) {
140
190
  client.end();
141
191
  }
142
192
 
143
193
  // src/commands/init.tsx
144
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
194
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
145
195
  var SCRIPTS = {
146
196
  checkUser: (user) => `id ${user} &>/dev/null && echo "exists" || echo "not_found"`,
147
197
  createUser: (user) => `
@@ -303,28 +353,28 @@ function InitCommand(props) {
303
353
  }
304
354
  }, [error]);
305
355
  const allDone = tasks.hardenSsh === "success" || tasks.hardenSsh === "skipped";
306
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
307
- /* @__PURE__ */ jsx4(Header, { title: "Server Initialization", subtitle: `Host: ${props.host}` }),
308
- /* @__PURE__ */ jsx4(Task, { label: "Connect to server", status: tasks.connect }),
309
- /* @__PURE__ */ jsx4(Task, { label: "Update system packages", status: tasks.update }),
310
- /* @__PURE__ */ jsx4(Task, { label: `Create user '${props.user}'`, status: tasks.createUser }),
311
- /* @__PURE__ */ jsx4(Task, { label: "Setup SSH keys", status: tasks.setupSsh }),
312
- /* @__PURE__ */ jsx4(Task, { label: "Configure firewall", status: tasks.firewall }),
313
- /* @__PURE__ */ jsx4(Task, { label: "Harden SSH", status: tasks.hardenSsh }),
314
- waitingConfirm && tasks.hardenSsh === "pending" && /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
315
- /* @__PURE__ */ jsxs4(Text4, { color: "yellow", bold: true, children: [
356
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
357
+ /* @__PURE__ */ jsx5(Header, { title: "Server Initialization", subtitle: `Host: ${props.host}` }),
358
+ /* @__PURE__ */ jsx5(Task, { label: "Connect to server", status: tasks.connect }),
359
+ /* @__PURE__ */ jsx5(Task, { label: "Update system packages", status: tasks.update }),
360
+ /* @__PURE__ */ jsx5(Task, { label: `Create user '${props.user}'`, status: tasks.createUser }),
361
+ /* @__PURE__ */ jsx5(Task, { label: "Setup SSH keys", status: tasks.setupSsh }),
362
+ /* @__PURE__ */ jsx5(Task, { label: "Configure firewall", status: tasks.firewall }),
363
+ /* @__PURE__ */ jsx5(Task, { label: "Harden SSH", status: tasks.hardenSsh }),
364
+ waitingConfirm && tasks.hardenSsh === "pending" && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
365
+ /* @__PURE__ */ jsxs5(Text5, { color: "yellow", bold: true, children: [
316
366
  "\u26A0 Before proceeding, verify SSH access as '",
317
367
  props.user,
318
368
  "':"
319
369
  ] }),
320
- /* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
370
+ /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
321
371
  " ssh ",
322
372
  props.port !== "22" ? `-p ${props.port} ` : "",
323
373
  props.user,
324
374
  "@",
325
375
  props.host
326
376
  ] }),
327
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(
377
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(
328
378
  Confirm,
329
379
  {
330
380
  message: "Have you verified SSH access works?",
@@ -332,19 +382,19 @@ function InitCommand(props) {
332
382
  }
333
383
  ) })
334
384
  ] }),
335
- error && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
385
+ error && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
336
386
  "Error: ",
337
387
  error
338
388
  ] }) }),
339
- allDone && /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
340
- /* @__PURE__ */ jsx4(Text4, { color: "green", bold: true, children: "\u2713 Initialization complete" }),
341
- summary.map((msg, i) => /* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
389
+ allDone && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
390
+ /* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "\u2713 Initialization complete" }),
391
+ summary.map((msg, i) => /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
342
392
  " \u2022 ",
343
393
  msg
344
394
  ] }, i)),
345
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text4, { children: [
395
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { children: [
346
396
  "Next: ",
347
- /* @__PURE__ */ jsxs4(Text4, { color: "cyan", children: [
397
+ /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
348
398
  "provisor app -h ",
349
399
  props.host
350
400
  ] })
@@ -355,12 +405,12 @@ function InitCommand(props) {
355
405
 
356
406
  // src/commands/app.tsx
357
407
  import { useState as useState3, useEffect as useEffect2 } from "react";
358
- import { Box as Box5, Text as Text5, useApp as useApp2, useInput as useInput2 } from "ink";
408
+ import { Box as Box6, Text as Text6, useApp as useApp2, useInput as useInput2 } from "ink";
359
409
  import SelectInput from "ink-select-input";
360
410
  import Spinner2 from "ink-spinner";
361
411
  import TextInput from "ink-text-input";
362
412
  import crypto from "crypto";
363
- import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
413
+ import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
364
414
  var SCRIPTS2 = {
365
415
  installCaddy: () => `
366
416
  if ! command -v caddy &>/dev/null; then
@@ -435,7 +485,7 @@ EOF
435
485
  ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes -T git@${host} 2>&1 || true
436
486
  `,
437
487
  // Push-to-deploy: setup bare repo with hook
438
- setupPushDeploy: (name, branch, user) => `
488
+ setupPushDeploy: (name, branch, user, buildCmd, startCmd) => `
439
489
  APP_DIR="/var/www/${name}"
440
490
  REPO_DIR="/var/repo/${name}.git"
441
491
 
@@ -490,14 +540,17 @@ if [ -f "package.json" ]; then
490
540
  npm ci --production 2>/dev/null || npm install --production
491
541
  fi
492
542
 
493
- if grep -q '"build"' "package.json"; then
543
+ if [ -n "${buildCmd}" ]; then
494
544
  echo "Building application..."
495
- npm run build
545
+ ${buildCmd}
496
546
  fi
497
547
 
498
548
  PM2_NAME="${name}"
499
549
  if pm2 list 2>/dev/null | grep -q "$PM2_NAME"; then
500
- pm2 restart "$PM2_NAME"
550
+ pm2 restart "$PM2_NAME" --update-env
551
+ else
552
+ echo "Starting application..."
553
+ pm2 start "${startCmd}" --name "$PM2_NAME"
501
554
  fi
502
555
  else
503
556
  echo "Static site detected."
@@ -512,7 +565,7 @@ HOOK_EOF
512
565
  echo "push-deploy-ready"
513
566
  `,
514
567
  // Clone from repository - fresh clone (replace)
515
- cloneRepoFresh: (name, repoUrl, branch, user) => `
568
+ cloneRepoFresh: (name, repoUrl, branch, user, buildCmd, startCmd) => `
516
569
  APP_DIR="/var/www/${name}"
517
570
 
518
571
  # Remove existing if present
@@ -530,10 +583,19 @@ HOOK_EOF
530
583
  echo "Node.js project detected."
531
584
  sudo -u ${user} npm ci --production 2>/dev/null || sudo -u ${user} npm install --production
532
585
 
533
- if grep -q '"build"' "package.json"; then
586
+ if [ -n "${buildCmd}" ]; then
534
587
  echo "Building application..."
535
- sudo -u ${user} npm run build
588
+ sudo -u ${user} ${buildCmd}
589
+ fi
590
+
591
+ PM2_NAME="${name}"
592
+ if pm2 list 2>/dev/null | grep -q "$PM2_NAME"; then
593
+ pm2 restart "$PM2_NAME" --update-env
594
+ else
595
+ echo "Starting application..."
596
+ pm2 start "${startCmd}" --name "$PM2_NAME"
536
597
  fi
598
+ pm2 save
537
599
  else
538
600
  echo "Static site detected."
539
601
  fi
@@ -542,7 +604,7 @@ HOOK_EOF
542
604
  echo "clone-complete"
543
605
  `,
544
606
  // Update existing cloned repo (git pull)
545
- updateRepo: (name, branch, user) => `
607
+ updateRepo: (name, branch, user, buildCmd, startCmd) => `
546
608
  APP_DIR="/var/www/${name}"
547
609
 
548
610
  cd "$APP_DIR"
@@ -556,14 +618,17 @@ HOOK_EOF
556
618
  echo "Node.js project detected."
557
619
  sudo -u ${user} npm ci --production 2>/dev/null || sudo -u ${user} npm install --production
558
620
 
559
- if grep -q '"build"' "package.json"; then
621
+ if [ -n "${buildCmd}" ]; then
560
622
  echo "Building application..."
561
- sudo -u ${user} npm run build
623
+ sudo -u ${user} ${buildCmd}
562
624
  fi
563
625
 
564
626
  PM2_NAME="${name}"
565
627
  if pm2 list 2>/dev/null | grep -q "$PM2_NAME"; then
566
- pm2 restart "$PM2_NAME"
628
+ pm2 restart "$PM2_NAME" --update-env
629
+ else
630
+ echo "Starting application..."
631
+ pm2 start "${startCmd}" --name "$PM2_NAME"
567
632
  fi
568
633
  else
569
634
  echo "Static site detected."
@@ -572,7 +637,7 @@ HOOK_EOF
572
637
  echo "update-complete"
573
638
  `,
574
639
  // Create update script for cloned repos
575
- createUpdateScript: (name, branch, user) => `
640
+ createUpdateScript: (name, branch, user, buildCmd, startCmd) => `
576
641
  cat << 'SCRIPT_EOF' > /usr/local/bin/update-${name}
577
642
  #!/bin/bash
578
643
  set -e
@@ -592,13 +657,16 @@ sudo -u $USER git reset --hard origin/$BRANCH
592
657
  if [ -f "package.json" ]; then
593
658
  sudo -u $USER npm ci --production 2>/dev/null || sudo -u $USER npm install --production
594
659
 
595
- if grep -q '"build"' "package.json"; then
596
- sudo -u $USER npm run build
660
+ if [ -n "${buildCmd}" ]; then
661
+ sudo -u $USER ${buildCmd}
597
662
  fi
598
663
 
599
664
  PM2_NAME="${name}"
600
665
  if pm2 list 2>/dev/null | grep -q "$PM2_NAME"; then
601
- pm2 restart "$PM2_NAME"
666
+ pm2 restart "$PM2_NAME" --update-env
667
+ else
668
+ echo "Starting application..."
669
+ pm2 start "${startCmd}" --name "$PM2_NAME"
602
670
  fi
603
671
  fi
604
672
 
@@ -612,12 +680,15 @@ SCRIPT_EOF
612
680
  cat << 'EOF' > /etc/caddy/Caddyfile
613
681
  {
614
682
  on_demand_tls {
615
- interval 2m
616
- burst 5
683
+ ask http://localhost:5555/check
617
684
  }
618
685
  }
619
686
 
620
- https:// {
687
+ :5555 {
688
+ respond /check 200
689
+ }
690
+
691
+ :443 {
621
692
  tls {
622
693
  on_demand
623
694
  }
@@ -628,7 +699,7 @@ https:// {
628
699
  file_server
629
700
  }
630
701
 
631
- http:// {
702
+ :80 {
632
703
  redir https://{host}{uri} permanent
633
704
  }
634
705
  EOF
@@ -985,6 +1056,10 @@ function AppCommand(props) {
985
1056
  caddyConfig: "pending"
986
1057
  });
987
1058
  const [error, setError] = useState3(null);
1059
+ const [buildCmd, setBuildCmd] = useState3("npm run build");
1060
+ const [startCmd, setStartCmd] = useState3("npm start");
1061
+ const [askingBuild, setAskingBuild] = useState3(false);
1062
+ const [askingStart, setAskingStart] = useState3(false);
988
1063
  const [selectingMethod, setSelectingMethod] = useState3(false);
989
1064
  const [deployMethod, setDeployMethod] = useState3(props.repo ? "clone-public" : null);
990
1065
  const [enteringRepo, setEnteringRepo] = useState3(false);
@@ -1077,9 +1152,21 @@ function AppCommand(props) {
1077
1152
  run();
1078
1153
  }, [client, tasks.caddy]);
1079
1154
  useEffect2(() => {
1080
- if (tasks.node !== "success" || selectingMethod || deployMethod !== null) return;
1081
- setSelectingMethod(true);
1155
+ if (tasks.node !== "success" || askingBuild || askingStart || selectingMethod || deployMethod !== null) return;
1156
+ setAskingBuild(true);
1082
1157
  }, [tasks.node]);
1158
+ const handleBuildSubmit = (value) => {
1159
+ setBuildCmd(value);
1160
+ setAskingBuild(false);
1161
+ setAskingStart(true);
1162
+ };
1163
+ const handleStartSubmit = (value) => {
1164
+ setStartCmd(value);
1165
+ setAskingStart(false);
1166
+ setSelectingMethod(true);
1167
+ };
1168
+ useEffect2(() => {
1169
+ }, []);
1083
1170
  const handleMethodSelect = (item) => {
1084
1171
  setSelectingMethod(false);
1085
1172
  const method = item.value;
@@ -1187,17 +1274,17 @@ function AppCommand(props) {
1187
1274
  updateTask("deploy", "running");
1188
1275
  try {
1189
1276
  if (deployMethod === "push") {
1190
- await execScript(client, SCRIPTS2.setupPushDeploy(props.name, props.branch, props.user || "deploy"), true);
1277
+ await execScript(client, SCRIPTS2.setupPushDeploy(props.name, props.branch, props.user || "deploy", buildCmd, startCmd), true);
1191
1278
  } else {
1192
1279
  if (deployMethod === "clone-private") {
1193
1280
  await execScript(client, SCRIPTS2.testGitConnection(gitHost), true);
1194
1281
  }
1195
1282
  if (existingAppAction === "replace") {
1196
- await execScript(client, SCRIPTS2.cloneRepoFresh(props.name, repoUrl, props.branch, props.user || "deploy"), true);
1283
+ await execScript(client, SCRIPTS2.cloneRepoFresh(props.name, repoUrl, props.branch, props.user || "deploy", buildCmd, startCmd), true);
1197
1284
  } else {
1198
- await execScript(client, SCRIPTS2.updateRepo(props.name, props.branch, props.user || "deploy"), true);
1285
+ await execScript(client, SCRIPTS2.updateRepo(props.name, props.branch, props.user || "deploy", buildCmd, startCmd), true);
1199
1286
  }
1200
- await execScript(client, SCRIPTS2.createUpdateScript(props.name, props.branch, props.user || "deploy"), true);
1287
+ await execScript(client, SCRIPTS2.createUpdateScript(props.name, props.branch, props.user || "deploy", buildCmd, startCmd), true);
1201
1288
  }
1202
1289
  updateTask("deploy", "success");
1203
1290
  } catch (err) {
@@ -1236,6 +1323,8 @@ function AppCommand(props) {
1236
1323
  const config = {
1237
1324
  repo: repoUrl,
1238
1325
  branch: props.branch,
1326
+ buildCmd,
1327
+ startCmd,
1239
1328
  autoDeployType: "polling",
1240
1329
  pollingInterval
1241
1330
  };
@@ -1250,6 +1339,8 @@ function AppCommand(props) {
1250
1339
  const config = {
1251
1340
  repo: repoUrl,
1252
1341
  branch: props.branch,
1342
+ buildCmd,
1343
+ startCmd,
1253
1344
  autoDeployType: "webhook",
1254
1345
  webhookPort: port
1255
1346
  };
@@ -1310,137 +1401,153 @@ function AppCommand(props) {
1310
1401
  const allDone = tasks.caddyConfig === "success";
1311
1402
  const isClone = deployMethod === "clone-public" || deployMethod === "clone-private";
1312
1403
  const deployLabel = isClone ? existingAppAction === "update" ? `Update ${props.name}` : `Clone from ${repoUrl || "repository"}` : "Setup push-to-deploy";
1313
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
1314
- /* @__PURE__ */ jsx5(Header, { title: "Application Provisioning", subtitle: `Host: ${props.host}` }),
1315
- /* @__PURE__ */ jsx5(Task, { label: "Connect to server", status: tasks.connect }),
1316
- /* @__PURE__ */ jsx5(Task, { label: "Install Caddy", status: tasks.caddy }),
1317
- /* @__PURE__ */ jsx5(Task, { label: "Install Node.js & PM2", status: tasks.node }),
1318
- deployMethod === "clone-private" && /* @__PURE__ */ jsx5(Task, { label: "Generate deploy key", status: tasks.deployKey }),
1319
- /* @__PURE__ */ jsx5(Task, { label: deployLabel, status: tasks.deploy }),
1320
- (deployMethod === "clone-public" || deployMethod === "clone-private") && autoDeployChoice && autoDeployChoice !== "none" && /* @__PURE__ */ jsx5(Task, { label: `Setup ${autoDeployChoice === "polling" ? "git polling" : "webhook"} for auto-deploy`, status: tasks.webhook }),
1321
- /* @__PURE__ */ jsx5(Task, { label: "Configure Caddy", status: tasks.caddyConfig }),
1322
- selectingMethod && deployMethod === null && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1323
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Select deployment method:" }),
1324
- /* @__PURE__ */ jsx5(SelectInput, { items: deployOptions, onSelect: handleMethodSelect })
1404
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1405
+ /* @__PURE__ */ jsx6(Header, { title: "Application Provisioning", subtitle: `Host: ${props.host}` }),
1406
+ /* @__PURE__ */ jsx6(Task, { label: "Connect to server", status: tasks.connect }),
1407
+ /* @__PURE__ */ jsx6(Task, { label: "Install Caddy", status: tasks.caddy }),
1408
+ /* @__PURE__ */ jsx6(Task, { label: "Install Node.js & PM2", status: tasks.node }),
1409
+ deployMethod === "clone-private" && /* @__PURE__ */ jsx6(Task, { label: "Generate deploy key", status: tasks.deployKey }),
1410
+ /* @__PURE__ */ jsx6(Task, { label: deployLabel, status: tasks.deploy }),
1411
+ (deployMethod === "clone-public" || deployMethod === "clone-private") && autoDeployChoice && autoDeployChoice !== "none" && /* @__PURE__ */ jsx6(Task, { label: `Setup ${autoDeployChoice === "polling" ? "git polling" : "webhook"} for auto-deploy`, status: tasks.webhook }),
1412
+ /* @__PURE__ */ jsx6(Task, { label: "Configure Caddy", status: tasks.caddyConfig }),
1413
+ askingBuild && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1414
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Build command (optional, press Enter to skip):" }),
1415
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Command to build the app (e.g. 'npm run build')" }),
1416
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1417
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "> " }),
1418
+ /* @__PURE__ */ jsx6(TextInput, { value: buildCmd, onChange: setBuildCmd, onSubmit: handleBuildSubmit })
1419
+ ] })
1420
+ ] }),
1421
+ askingStart && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1422
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Start command:" }),
1423
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "Command to start the app (e.g. 'npm start' or 'node server.js')" }),
1424
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1425
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "> " }),
1426
+ /* @__PURE__ */ jsx6(TextInput, { value: startCmd, onChange: setStartCmd, onSubmit: handleStartSubmit })
1427
+ ] })
1325
1428
  ] }),
1326
- enteringRepo && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1327
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Enter repository URL:" }),
1328
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: deployMethod === "clone-private" ? "(Use SSH URL: git@github.com:user/repo.git)" : "(e.g., https://github.com/user/repo.git)" }),
1329
- /* @__PURE__ */ jsxs5(Box5, { children: [
1330
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "> " }),
1331
- /* @__PURE__ */ jsx5(TextInput, { value: repoUrl, onChange: setRepoUrl, onSubmit: handleRepoSubmit })
1429
+ selectingMethod && deployMethod === null && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1430
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Select deployment method:" }),
1431
+ /* @__PURE__ */ jsx6(SelectInput, { items: deployOptions, onSelect: handleMethodSelect })
1432
+ ] }),
1433
+ enteringRepo && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1434
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Enter repository URL:" }),
1435
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: deployMethod === "clone-private" ? "(Use SSH URL: git@github.com:user/repo.git)" : "(e.g., https://github.com/user/repo.git)" }),
1436
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1437
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "> " }),
1438
+ /* @__PURE__ */ jsx6(TextInput, { value: repoUrl, onChange: setRepoUrl, onSubmit: handleRepoSubmit })
1332
1439
  ] })
1333
1440
  ] }),
1334
- selectingExistingAction && existingAppAction === null && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1335
- /* @__PURE__ */ jsxs5(Text5, { bold: true, color: "yellow", children: [
1441
+ selectingExistingAction && existingAppAction === null && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1442
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, color: "yellow", children: [
1336
1443
  "\u26A0 App directory ",
1337
1444
  appDir,
1338
1445
  " already exists"
1339
1446
  ] }),
1340
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "What would you like to do?" }),
1341
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(SelectInput, { items: existingAppOptions, onSelect: handleExistingAppSelect }) })
1447
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "What would you like to do?" }),
1448
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(SelectInput, { items: existingAppOptions, onSelect: handleExistingAppSelect }) })
1342
1449
  ] }),
1343
- waitingForKeySetup && deployKey && !keyConfirmed && !keyVerifying && !keyVerified && !keyVerifyError && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1344
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Deploy Key Generated \u2501\u2501\u2501" }),
1345
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1346
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Public key (copy this):" }),
1347
- /* @__PURE__ */ jsx5(Box5, { marginY: 1, paddingX: 1, borderStyle: "single", borderColor: "cyan", children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: deployKey }) })
1450
+ waitingForKeySetup && deployKey && !keyConfirmed && !keyVerifying && !keyVerified && !keyVerifyError && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1451
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Deploy Key Generated \u2501\u2501\u2501" }),
1452
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1453
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Public key (copy this):" }),
1454
+ /* @__PURE__ */ jsx6(Box6, { marginY: 1, paddingX: 1, borderStyle: "single", borderColor: "cyan", children: /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: deployKey }) })
1348
1455
  ] }),
1349
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1350
- /* @__PURE__ */ jsxs5(Text5, { bold: true, children: [
1456
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1457
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
1351
1458
  "Add this key to ",
1352
1459
  gitHost,
1353
1460
  ":"
1354
1461
  ] }),
1355
- keyInstructions.steps.map((step, i) => /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1462
+ keyInstructions.steps.map((step, i) => /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1356
1463
  " ",
1357
1464
  step
1358
1465
  ] }, i))
1359
1466
  ] }),
1360
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1361
- /* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
1467
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1468
+ /* @__PURE__ */ jsxs6(Text6, { color: "yellow", children: [
1362
1469
  "Have you added the deploy key to ",
1363
1470
  gitHost,
1364
1471
  "? "
1365
1472
  ] }),
1366
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "(y/n) " })
1473
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "(y/n) " })
1367
1474
  ] })
1368
1475
  ] }),
1369
- keyVerifying && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1370
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Verifying Deploy Key \u2501\u2501\u2501" }),
1371
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1372
- /* @__PURE__ */ jsx5(Spinner2, { type: "dots" }),
1373
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1476
+ keyVerifying && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1477
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Verifying Deploy Key \u2501\u2501\u2501" }),
1478
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1479
+ /* @__PURE__ */ jsx6(Spinner2, { type: "dots" }),
1480
+ /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1374
1481
  " Testing SSH connection to ",
1375
1482
  gitHost,
1376
1483
  "..."
1377
1484
  ] })
1378
1485
  ] })
1379
1486
  ] }),
1380
- keyVerified && !error && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "green", children: "\u2713 Deploy key verified successfully!" }) }),
1381
- keyVerifyError && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1382
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "red", children: "\u2501\u2501\u2501 Deploy Key Verification Failed \u2501\u2501\u2501" }),
1383
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
1487
+ keyVerified && !error && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "green", children: "\u2713 Deploy key verified successfully!" }) }),
1488
+ keyVerifyError && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1489
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "red", children: "\u2501\u2501\u2501 Deploy Key Verification Failed \u2501\u2501\u2501" }),
1490
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
1384
1491
  "\u2717 ",
1385
1492
  keyVerifyError
1386
1493
  ] }) }),
1387
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1388
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Please check:" }),
1389
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1494
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1495
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Please check:" }),
1496
+ /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1390
1497
  " 1. The deploy key is added to ",
1391
1498
  gitHost
1392
1499
  ] }),
1393
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: " 2. The key has read access to the repository" }),
1394
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: " 3. The repository URL is correct" })
1500
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 2. The key has read access to the repository" }),
1501
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 3. The repository URL is correct" })
1395
1502
  ] }),
1396
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1397
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Public key to add:" }),
1398
- /* @__PURE__ */ jsx5(Box5, { marginY: 1, paddingX: 1, borderStyle: "single", borderColor: "cyan", children: /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: deployKey }) })
1503
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1504
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Public key to add:" }),
1505
+ /* @__PURE__ */ jsx6(Box6, { marginY: 1, paddingX: 1, borderStyle: "single", borderColor: "cyan", children: /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: deployKey }) })
1399
1506
  ] }),
1400
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "Press (r) to retry or (q) to quit" }) })
1507
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "Press (r) to retry or (q) to quit" }) })
1401
1508
  ] }),
1402
- selectingAutoDeploy && autoDeployChoice === null && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1403
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Enable automatic deployment?" }),
1404
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1509
+ selectingAutoDeploy && autoDeployChoice === null && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1510
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Enable automatic deployment?" }),
1511
+ /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1405
1512
  "Auto-deploy when you push to ",
1406
1513
  props.branch,
1407
1514
  " branch."
1408
1515
  ] }),
1409
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(SelectInput, { items: autoDeployOptions, onSelect: handleAutoDeploySelect }) })
1516
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(SelectInput, { items: autoDeployOptions, onSelect: handleAutoDeploySelect }) })
1410
1517
  ] }),
1411
- selectingTls && tlsChoice === null && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1412
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Select TLS configuration:" }),
1413
- /* @__PURE__ */ jsx5(SelectInput, { items: tlsOptions, onSelect: handleTlsSelect })
1518
+ selectingTls && tlsChoice === null && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1519
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Select TLS configuration:" }),
1520
+ /* @__PURE__ */ jsx6(SelectInput, { items: tlsOptions, onSelect: handleTlsSelect })
1414
1521
  ] }),
1415
- error && /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
1522
+ error && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
1416
1523
  "Error: ",
1417
1524
  error
1418
1525
  ] }) }),
1419
- allDone && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1420
- /* @__PURE__ */ jsx5(Text5, { color: "green", bold: true, children: "\u2713 Application provisioning complete" }),
1421
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: deployMethod === "push" ? /* @__PURE__ */ jsxs5(Fragment, { children: [
1422
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Git remote (add to local project):" }),
1423
- /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
1526
+ allDone && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1527
+ /* @__PURE__ */ jsx6(Text6, { color: "green", bold: true, children: "\u2713 Application provisioning complete" }),
1528
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: deployMethod === "push" ? /* @__PURE__ */ jsxs6(Fragment, { children: [
1529
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Git remote (add to local project):" }),
1530
+ /* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
1424
1531
  " git remote add production ssh://",
1425
1532
  props.user,
1426
1533
  "@",
1427
1534
  serverIp,
1428
1535
  repoDir
1429
1536
  ] }),
1430
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Deploy with:" }) }),
1431
- /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
1537
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Deploy with:" }) }),
1538
+ /* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
1432
1539
  " git push production ",
1433
1540
  props.branch
1434
1541
  ] })
1435
- ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1436
- /* @__PURE__ */ jsxs5(Text5, { bold: true, children: [
1542
+ ] }) : /* @__PURE__ */ jsxs6(Fragment, { children: [
1543
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
1437
1544
  "App ",
1438
1545
  existingAppAction === "update" ? "updated" : "deployed",
1439
1546
  " from: ",
1440
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: repoUrl })
1547
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: repoUrl })
1441
1548
  ] }),
1442
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Manual update:" }) }),
1443
- /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
1549
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Manual update:" }) }),
1550
+ /* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
1444
1551
  " ssh ",
1445
1552
  props.user,
1446
1553
  "@",
@@ -1449,35 +1556,35 @@ function AppCommand(props) {
1449
1556
  props.name,
1450
1557
  '"'
1451
1558
  ] }),
1452
- autoDeployChoice === "polling" && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1453
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Auto-Deploy (Git Polling) \u2501\u2501\u2501" }),
1454
- /* @__PURE__ */ jsxs5(Text5, { children: [
1559
+ autoDeployChoice === "polling" && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1560
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Auto-Deploy (Git Polling) \u2501\u2501\u2501" }),
1561
+ /* @__PURE__ */ jsxs6(Text6, { children: [
1455
1562
  "Polling interval: ",
1456
- /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
1563
+ /* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
1457
1564
  pollingInterval,
1458
1565
  " seconds"
1459
1566
  ] })
1460
1567
  ] }),
1461
- /* @__PURE__ */ jsxs5(Text5, { children: [
1568
+ /* @__PURE__ */ jsxs6(Text6, { children: [
1462
1569
  "Branch: ",
1463
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: props.branch })
1570
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: props.branch })
1464
1571
  ] }),
1465
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: "The server checks for new commits and deploys automatically." }),
1466
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { children: [
1572
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: "The server checks for new commits and deploys automatically." }),
1573
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { children: [
1467
1574
  "View logs: ",
1468
- /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
1575
+ /* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
1469
1576
  "journalctl -u poll-",
1470
1577
  props.name,
1471
1578
  " -f"
1472
1579
  ] })
1473
1580
  ] }) })
1474
1581
  ] }),
1475
- autoDeployChoice === "webhook" && webhookPort > 0 && /* @__PURE__ */ jsxs5(Fragment, { children: [
1476
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1477
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Auto-Deploy (Webhook) \u2501\u2501\u2501" }),
1478
- /* @__PURE__ */ jsxs5(Text5, { children: [
1582
+ autoDeployChoice === "webhook" && webhookPort > 0 && /* @__PURE__ */ jsxs6(Fragment, { children: [
1583
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1584
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "\u2501\u2501\u2501 Auto-Deploy (Webhook) \u2501\u2501\u2501" }),
1585
+ /* @__PURE__ */ jsxs6(Text6, { children: [
1479
1586
  "Webhook URL: ",
1480
- /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
1587
+ /* @__PURE__ */ jsxs6(Text6, { color: "cyan", children: [
1481
1588
  "http://",
1482
1589
  serverIp,
1483
1590
  ":",
@@ -1485,58 +1592,58 @@ function AppCommand(props) {
1485
1592
  "/webhook"
1486
1593
  ] })
1487
1594
  ] }),
1488
- /* @__PURE__ */ jsxs5(Text5, { children: [
1595
+ /* @__PURE__ */ jsxs6(Text6, { children: [
1489
1596
  "Secret: ",
1490
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: webhookSecret })
1597
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: webhookSecret })
1491
1598
  ] }),
1492
- /* @__PURE__ */ jsxs5(Text5, { children: [
1599
+ /* @__PURE__ */ jsxs6(Text6, { children: [
1493
1600
  "Branch: ",
1494
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: props.branch })
1601
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: props.branch })
1495
1602
  ] })
1496
1603
  ] }),
1497
- /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1498
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Add webhook to your repository:" }),
1499
- gitHost === "github.com" && /* @__PURE__ */ jsxs5(Fragment, { children: [
1500
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: " 1. Go to your repo \u2192 Settings \u2192 Webhooks \u2192 Add webhook" }),
1501
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1604
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1605
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Add webhook to your repository:" }),
1606
+ gitHost === "github.com" && /* @__PURE__ */ jsxs6(Fragment, { children: [
1607
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 1. Go to your repo \u2192 Settings \u2192 Webhooks \u2192 Add webhook" }),
1608
+ /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1502
1609
  " 2. Payload URL: http://",
1503
1610
  serverIp,
1504
1611
  ":",
1505
1612
  webhookPort,
1506
1613
  "/webhook"
1507
1614
  ] }),
1508
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: " 3. Content type: application/json" }),
1509
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1615
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 3. Content type: application/json" }),
1616
+ /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1510
1617
  " 4. Secret: ",
1511
1618
  webhookSecret
1512
1619
  ] }),
1513
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: ' 5. Select "Just the push event"' })
1620
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: ' 5. Select "Just the push event"' })
1514
1621
  ] }),
1515
- gitHost === "gitlab.com" && /* @__PURE__ */ jsxs5(Fragment, { children: [
1516
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: " 1. Go to your repo \u2192 Settings \u2192 Webhooks" }),
1517
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1622
+ gitHost === "gitlab.com" && /* @__PURE__ */ jsxs6(Fragment, { children: [
1623
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 1. Go to your repo \u2192 Settings \u2192 Webhooks" }),
1624
+ /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1518
1625
  " 2. URL: http://",
1519
1626
  serverIp,
1520
1627
  ":",
1521
1628
  webhookPort,
1522
1629
  "/webhook"
1523
1630
  ] }),
1524
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1631
+ /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1525
1632
  " 3. Secret token: ",
1526
1633
  webhookSecret
1527
1634
  ] }),
1528
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: " 4. Trigger: Push events" })
1635
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 4. Trigger: Push events" })
1529
1636
  ] }),
1530
- gitHost === "bitbucket.org" && /* @__PURE__ */ jsxs5(Fragment, { children: [
1531
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: " 1. Go to your repo \u2192 Repository settings \u2192 Webhooks" }),
1532
- /* @__PURE__ */ jsxs5(Text5, { color: "gray", children: [
1637
+ gitHost === "bitbucket.org" && /* @__PURE__ */ jsxs6(Fragment, { children: [
1638
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 1. Go to your repo \u2192 Repository settings \u2192 Webhooks" }),
1639
+ /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1533
1640
  " 2. URL: http://",
1534
1641
  serverIp,
1535
1642
  ":",
1536
1643
  webhookPort,
1537
1644
  "/webhook"
1538
1645
  ] }),
1539
- /* @__PURE__ */ jsx5(Text5, { color: "gray", children: " 3. Triggers: Repository push" })
1646
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: " 3. Triggers: Repository push" })
1540
1647
  ] })
1541
1648
  ] })
1542
1649
  ] })
@@ -1547,8 +1654,8 @@ function AppCommand(props) {
1547
1654
 
1548
1655
  // src/commands/ssh-key.tsx
1549
1656
  import { useState as useState4, useEffect as useEffect3 } from "react";
1550
- import { Box as Box6, Text as Text6, useApp as useApp3 } from "ink";
1551
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1657
+ import { Box as Box7, Text as Text7, useApp as useApp3, useInput as useInput3 } from "ink";
1658
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1552
1659
  function SshKeyCommand(props) {
1553
1660
  const { exit } = useApp3();
1554
1661
  const [client, setClient] = useState4(null);
@@ -1556,6 +1663,19 @@ function SshKeyCommand(props) {
1556
1663
  const [error, setError] = useState4(null);
1557
1664
  const [keys, setKeys] = useState4([]);
1558
1665
  const [message, setMessage] = useState4("");
1666
+ const [done, setDone] = useState4(false);
1667
+ const goBack = () => {
1668
+ if (props.onBack) {
1669
+ props.onBack();
1670
+ } else {
1671
+ exit();
1672
+ }
1673
+ };
1674
+ useInput3((input, key) => {
1675
+ if (done && (key.escape || input === "q")) {
1676
+ goBack();
1677
+ }
1678
+ });
1559
1679
  useEffect3(() => {
1560
1680
  const run = async () => {
1561
1681
  setStatus("running");
@@ -1594,46 +1714,60 @@ function SshKeyCommand(props) {
1594
1714
  setError("Specify --add <key> or --list");
1595
1715
  }
1596
1716
  disconnect(sshClient);
1597
- setTimeout(() => exit(), 100);
1717
+ setDone(true);
1598
1718
  } catch (err) {
1599
1719
  setStatus("error");
1600
1720
  setError(err instanceof Error ? err.message : String(err));
1601
1721
  if (client) disconnect(client);
1602
- setTimeout(() => exit(), 100);
1722
+ setDone(true);
1603
1723
  }
1604
1724
  };
1605
1725
  run();
1606
1726
  }, []);
1607
1727
  const operation = props.list ? "List SSH keys" : "Add SSH key";
1608
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1609
- /* @__PURE__ */ jsx6(Header, { title: "SSH Key Management", subtitle: `Host: ${props.host}` }),
1610
- /* @__PURE__ */ jsx6(Task, { label: operation, status, message }),
1611
- props.list && keys.length > 0 && /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1612
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Authorized keys:" }),
1613
- keys.map((key, i) => /* @__PURE__ */ jsxs6(Text6, { color: "gray", children: [
1728
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1729
+ /* @__PURE__ */ jsx7(Header, { title: "SSH Key Management", subtitle: `Host: ${props.host}` }),
1730
+ /* @__PURE__ */ jsx7(Task, { label: operation, status, message }),
1731
+ props.list && keys.length > 0 && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
1732
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: "Authorized keys:" }),
1733
+ keys.map((key, i) => /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
1614
1734
  " ",
1615
1735
  i + 1,
1616
1736
  ". ",
1617
1737
  key
1618
1738
  ] }, i))
1619
1739
  ] }),
1620
- error && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
1740
+ error && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
1621
1741
  "Error: ",
1622
1742
  error
1623
- ] }) })
1743
+ ] }) }),
1744
+ done && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "Press 'q' or Esc to go back" }) })
1624
1745
  ] });
1625
1746
  }
1626
1747
 
1627
1748
  // src/commands/status.tsx
1628
1749
  import { useState as useState5, useEffect as useEffect4 } from "react";
1629
- import { Box as Box7, Text as Text7, useApp as useApp4 } from "ink";
1630
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1750
+ import { Box as Box8, Text as Text8, useApp as useApp4, useInput as useInput4 } from "ink";
1751
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1631
1752
  function StatusCommand(props) {
1632
1753
  const { exit } = useApp4();
1633
1754
  const [taskStatus, setTaskStatus] = useState5("pending");
1634
1755
  const [error, setError] = useState5(null);
1635
1756
  const [services, setServices] = useState5([]);
1636
1757
  const [system, setSystem] = useState5(null);
1758
+ const [done, setDone] = useState5(false);
1759
+ const goBack = () => {
1760
+ if (props.onBack) {
1761
+ props.onBack();
1762
+ } else {
1763
+ exit();
1764
+ }
1765
+ };
1766
+ useInput4((input, key) => {
1767
+ if (done && (key.escape || input === "q")) {
1768
+ goBack();
1769
+ }
1770
+ });
1637
1771
  useEffect4(() => {
1638
1772
  const run = async () => {
1639
1773
  setTaskStatus("running");
@@ -1673,66 +1807,67 @@ function StatusCommand(props) {
1673
1807
  });
1674
1808
  setServices(serviceResults);
1675
1809
  setTaskStatus("success");
1810
+ setDone(true);
1676
1811
  disconnect(client);
1677
- setTimeout(() => exit(), 100);
1678
1812
  } catch (err) {
1679
1813
  setTaskStatus("error");
1680
1814
  setError(err instanceof Error ? err.message : String(err));
1681
- setTimeout(() => exit(), 100);
1815
+ setDone(true);
1682
1816
  }
1683
1817
  };
1684
1818
  run();
1685
1819
  }, []);
1686
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1687
- /* @__PURE__ */ jsx7(Header, { title: "Server Status", subtitle: `Host: ${props.host}` }),
1688
- /* @__PURE__ */ jsx7(Task, { label: "Checking server status", status: taskStatus }),
1689
- system && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
1690
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: "System:" }),
1691
- /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
1820
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1821
+ /* @__PURE__ */ jsx8(Header, { title: "Server Status", subtitle: `Host: ${props.host}` }),
1822
+ /* @__PURE__ */ jsx8(Task, { label: "Checking server status", status: taskStatus }),
1823
+ system && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
1824
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "System:" }),
1825
+ /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
1692
1826
  " Hostname: ",
1693
- /* @__PURE__ */ jsx7(Text7, { color: "white", children: system.hostname })
1827
+ /* @__PURE__ */ jsx8(Text8, { color: "white", children: system.hostname })
1694
1828
  ] }),
1695
- /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
1829
+ /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
1696
1830
  " Uptime: ",
1697
- /* @__PURE__ */ jsx7(Text7, { color: "white", children: system.uptime })
1831
+ /* @__PURE__ */ jsx8(Text8, { color: "white", children: system.uptime })
1698
1832
  ] }),
1699
- /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
1833
+ /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
1700
1834
  " Load: ",
1701
- /* @__PURE__ */ jsx7(Text7, { color: "white", children: system.load })
1835
+ /* @__PURE__ */ jsx8(Text8, { color: "white", children: system.load })
1702
1836
  ] }),
1703
- /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
1837
+ /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
1704
1838
  " Memory: ",
1705
- /* @__PURE__ */ jsx7(Text7, { color: "white", children: system.memory })
1839
+ /* @__PURE__ */ jsx8(Text8, { color: "white", children: system.memory })
1706
1840
  ] }),
1707
- /* @__PURE__ */ jsxs7(Text7, { color: "gray", children: [
1841
+ /* @__PURE__ */ jsxs8(Text8, { color: "gray", children: [
1708
1842
  " Disk: ",
1709
- /* @__PURE__ */ jsx7(Text7, { color: "white", children: system.disk })
1843
+ /* @__PURE__ */ jsx8(Text8, { color: "white", children: system.disk })
1710
1844
  ] })
1711
1845
  ] }),
1712
- services.length > 0 && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
1713
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: "Services:" }),
1714
- services.map((svc) => /* @__PURE__ */ jsxs7(Box7, { children: [
1715
- /* @__PURE__ */ jsx7(Text7, { color: "gray", children: " " }),
1716
- /* @__PURE__ */ jsx7(Text7, { color: svc.running ? "green" : "red", children: svc.running ? "\u25CF" : "\u25CB" }),
1717
- /* @__PURE__ */ jsxs7(Text7, { children: [
1846
+ services.length > 0 && /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
1847
+ /* @__PURE__ */ jsx8(Text8, { bold: true, children: "Services:" }),
1848
+ services.map((svc) => /* @__PURE__ */ jsxs8(Box8, { children: [
1849
+ /* @__PURE__ */ jsx8(Text8, { color: "gray", children: " " }),
1850
+ /* @__PURE__ */ jsx8(Text8, { color: svc.running ? "green" : "red", children: svc.running ? "\u25CF" : "\u25CB" }),
1851
+ /* @__PURE__ */ jsxs8(Text8, { children: [
1718
1852
  " ",
1719
1853
  svc.name,
1720
1854
  ": "
1721
1855
  ] }),
1722
- /* @__PURE__ */ jsx7(Text7, { color: svc.running ? "green" : "yellow", children: svc.status })
1856
+ /* @__PURE__ */ jsx8(Text8, { color: svc.running ? "green" : "yellow", children: svc.status })
1723
1857
  ] }, svc.name))
1724
1858
  ] }),
1725
- error && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
1859
+ error && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: "red", children: [
1726
1860
  "Error: ",
1727
1861
  error
1728
- ] }) })
1862
+ ] }) }),
1863
+ done && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "gray", children: "Press 'q' or Esc to go back" }) })
1729
1864
  ] });
1730
1865
  }
1731
1866
 
1732
1867
  // src/commands/deploy.tsx
1733
1868
  import { useState as useState6, useEffect as useEffect5 } from "react";
1734
- import { Box as Box8, Text as Text8, useApp as useApp5 } from "ink";
1735
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1869
+ import { Box as Box9, Text as Text9, useApp as useApp5, useInput as useInput5 } from "ink";
1870
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1736
1871
  function DeployCommand(props) {
1737
1872
  const { exit } = useApp5();
1738
1873
  const [status, setStatus] = useState6("pending");
@@ -1751,33 +1886,42 @@ function DeployCommand(props) {
1751
1886
  setOutput(result.stdout.trim().split("\n"));
1752
1887
  setStatus("success");
1753
1888
  disconnect(client);
1754
- setTimeout(() => exit(), 100);
1889
+ if (!props.onBack) {
1890
+ setTimeout(() => exit(), 100);
1891
+ }
1755
1892
  } catch (err) {
1756
1893
  setStatus("error");
1757
1894
  setError(err instanceof Error ? err.message : String(err));
1758
- setTimeout(() => exit(), 100);
1895
+ if (!props.onBack) {
1896
+ setTimeout(() => exit(), 100);
1897
+ }
1759
1898
  }
1760
1899
  };
1761
1900
  run();
1762
1901
  }, []);
1763
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1764
- /* @__PURE__ */ jsx8(Header, { title: "Deploy Application", subtitle: `App: ${props.name} | Host: ${props.host}` }),
1765
- /* @__PURE__ */ jsx8(Task, { label: `Deploying ${props.name}`, status }),
1766
- output.length > 0 && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: output.map((line, i) => /* @__PURE__ */ jsx8(Text8, { color: "gray", children: line }, i)) }),
1767
- error && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { color: "red", children: [
1902
+ useInput5((input, key) => {
1903
+ if ((status === "success" || status === "error") && props.onBack) {
1904
+ props.onBack();
1905
+ }
1906
+ });
1907
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
1908
+ /* @__PURE__ */ jsx9(Header, { title: "Deploy Application", subtitle: `App: ${props.name} | Host: ${props.host}` }),
1909
+ /* @__PURE__ */ jsx9(Task, { label: `Deploying ${props.name}`, status }),
1910
+ output.length > 0 && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: output.map((line, i) => /* @__PURE__ */ jsx9(Text9, { color: "gray", children: line }, i)) }),
1911
+ error && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
1768
1912
  "Error: ",
1769
1913
  error
1770
1914
  ] }) }),
1771
- status === "success" && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "green", bold: true, children: "\u2713 Deployment complete" }) })
1915
+ status === "success" && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "green", bold: true, children: "\u2713 Deployment complete. Press any key to return." }) })
1772
1916
  ] });
1773
1917
  }
1774
1918
 
1775
1919
  // src/commands/config.tsx
1776
- import { useState as useState7, useEffect as useEffect6 } from "react";
1777
- import { Box as Box9, Text as Text9, useApp as useApp6 } from "ink";
1920
+ import React7, { useState as useState7, useEffect as useEffect6 } from "react";
1921
+ import { Box as Box10, Text as Text10, useApp as useApp6, useInput as useInput6 } from "ink";
1778
1922
  import SelectInput2 from "ink-select-input";
1779
1923
  import TextInput2 from "ink-text-input";
1780
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1924
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1781
1925
  var SCRIPTS3 = {
1782
1926
  // Read current config
1783
1927
  getConfig: (name) => `
@@ -1791,12 +1935,65 @@ var SCRIPTS3 = {
1791
1935
  cd "$APP_DIR"
1792
1936
  REPO=$(git remote get-url origin 2>/dev/null || echo "")
1793
1937
  BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
1794
- echo '{"repo":"'"$REPO"'","branch":"'"$BRANCH"'","webhookEnabled":false,"webhookPort":0}'
1938
+ echo '{"repo":"'"$REPO"'","branch":"'"$BRANCH"'","webhookEnabled":false,"webhookPort":0,"buildCmd":"npm run build","startCmd":"npm start"}'
1795
1939
  else
1796
1940
  echo '{"error":"App not found or not a git repository"}'
1797
1941
  fi
1798
1942
  fi
1799
1943
  `,
1944
+ // Get full app footprint
1945
+ getAppFootprint: (name, user) => `
1946
+ echo '{'
1947
+
1948
+ # Paths
1949
+ APP_DIR="/var/www/${name}"
1950
+ echo '"paths": {'
1951
+ echo '"appDir": "'$APP_DIR'",'
1952
+ echo '"config": "'$APP_DIR/.provisor.json'",'
1953
+ echo '"updateScript": "/usr/local/bin/update-'$name'",'
1954
+ echo '"webhookService": "/etc/systemd/system/webhook-'$name'.service",'
1955
+ echo '"pollScript": "/usr/local/bin/poll-'$name'.sh",'
1956
+ echo '"pollService": "/etc/systemd/system/poll-'$name'.service",'
1957
+ echo '"pollTimer": "/etc/systemd/system/poll-'$name'.timer"'
1958
+ echo '},'
1959
+
1960
+ # Statuses
1961
+ echo '"status": {'
1962
+
1963
+ # PM2 Status
1964
+ # We try to use pm2 jlist. If running as deploy user, we might need sudo -u deploy?
1965
+ # Usually pm2 is run by the user.
1966
+ PM2_STATUS="offline"
1967
+ if command -v pm2 >/dev/null 2>&1; then
1968
+ # Try current user first
1969
+ if pm2 describe $name > /dev/null 2>&1; then
1970
+ PM2_STATUS=$(pm2 jlist | grep -oP '"name":"'$name'".*?"status":"\\K[^"]+')
1971
+ else
1972
+ # Try sudo -u user
1973
+ PM2_STATUS=$(sudo -u ${user} pm2 jlist 2>/dev/null | grep -oP '"name":"'$name'".*?"status":"\\K[^"]+' || echo "offline")
1974
+ fi
1975
+ elif sudo -u ${user} command -v pm2 >/dev/null 2>&1; then
1976
+ PM2_STATUS=$(sudo -u ${user} pm2 jlist 2>/dev/null | grep -oP '"name":"'$name'".*?"status":"\\K[^"]+' || echo "offline")
1977
+ fi
1978
+ # Cleanup grep result
1979
+ PM2_STATUS=$(echo $PM2_STATUS | tr -d '
1980
+ ')
1981
+ if [ -z "$PM2_STATUS" ]; then PM2_STATUS="offline"; fi
1982
+ echo '"pm2": "'$PM2_STATUS'",'
1983
+
1984
+ # Webhook Status
1985
+ WEBHOOK="inactive"
1986
+ if systemctl is-active --quiet webhook-$name; then WEBHOOK="active"; fi
1987
+ echo '"webhook": "'$WEBHOOK'",'
1988
+
1989
+ # Polling Status
1990
+ POLL="inactive"
1991
+ if systemctl is-active --quiet poll-$name.timer; then POLL="active"; fi
1992
+ echo '"polling": "'$POLL'"'
1993
+
1994
+ echo '}'
1995
+ echo '}'
1996
+ `,
1800
1997
  // Save config
1801
1998
  saveConfig: (name, config) => `
1802
1999
  CONFIG_FILE="/var/www/${name}/.provisor.json"
@@ -2087,6 +2284,30 @@ DAEMON_EOF
2087
2284
  rm -f /usr/local/bin/poll-${name}-daemon.sh
2088
2285
 
2089
2286
  echo "polling-disabled"
2287
+ `,
2288
+ // Update Log Settings
2289
+ updateLogSettings: (name, source, path3) => `
2290
+ CONFIG_FILE="/var/www/${name}/.provisor.json"
2291
+ if [ -f "$CONFIG_FILE" ]; then
2292
+ TMP_FILE="/tmp/.provisor.json.tmp"
2293
+ # Use jq if available for safe editing, otherwise fallback to simple replacement or append
2294
+ # Since we don't assume jq, let's read the file in nodejs on the server to be safe
2295
+
2296
+ cat << 'NODE_EOF' > /tmp/update-config-${name}.js
2297
+ const fs = require('fs');
2298
+ const config = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
2299
+ if ('${source}') config.logSource = '${source}';
2300
+ if ('${path3}') config.logPath = '${path3}';
2301
+ fs.writeFileSync('$CONFIG_FILE', JSON.stringify(config));
2302
+ NODE_EOF
2303
+
2304
+ node /tmp/update-config-${name}.js
2305
+ rm /tmp/update-config-${name}.js
2306
+ chmod 600 "$CONFIG_FILE"
2307
+ echo "log-settings-updated"
2308
+ else
2309
+ echo "config-not-found"
2310
+ fi
2090
2311
  `
2091
2312
  };
2092
2313
  var configActions = [
@@ -2099,7 +2320,10 @@ var configActions = [
2099
2320
  { label: "Disable webhook", value: "disable-webhook" },
2100
2321
  { label: "Change polling interval", value: "polling-interval" },
2101
2322
  { label: "Enable git polling", value: "enable-polling" },
2102
- { label: "Disable git polling", value: "disable-polling" }
2323
+ { label: "Disable git polling", value: "disable-polling" },
2324
+ { label: "Change log source (pm2, file, systemd)", value: "log-source" },
2325
+ { label: "Set custom log path", value: "log-path" },
2326
+ { label: "< Back", value: "__back__" }
2103
2327
  ];
2104
2328
  function ConfigCommand(props) {
2105
2329
  const { exit } = useApp6();
@@ -2115,6 +2339,8 @@ function ConfigCommand(props) {
2115
2339
  const [config, setConfig] = useState7(null);
2116
2340
  const [deployKey, setDeployKey] = useState7(null);
2117
2341
  const [webhookStatus, setWebhookStatus] = useState7("");
2342
+ const [pollingStatus, setPollingStatus] = useState7("");
2343
+ const [configLoaded, setConfigLoaded] = useState7(false);
2118
2344
  useEffect6(() => {
2119
2345
  if (props.show) setAction("show");
2120
2346
  else if (props.repo) {
@@ -2133,7 +2359,15 @@ function ConfigCommand(props) {
2133
2359
  setAction("polling-interval");
2134
2360
  setInputValue(String(props.pollingInterval));
2135
2361
  } else if (props.enablePolling) setAction("enable-polling");
2362
+ else if (props.enablePolling) setAction("enable-polling");
2136
2363
  else if (props.disablePolling) setAction("disable-polling");
2364
+ else if (props.logSource) {
2365
+ setAction("log-source");
2366
+ setInputValue(props.logSource);
2367
+ } else if (props.logPath) {
2368
+ setAction("log-path");
2369
+ setInputValue(props.logPath);
2370
+ }
2137
2371
  }, []);
2138
2372
  useEffect6(() => {
2139
2373
  const run = async () => {
@@ -2149,6 +2383,30 @@ function ConfigCommand(props) {
2149
2383
  };
2150
2384
  run();
2151
2385
  }, []);
2386
+ const clientRef = React7.useRef(null);
2387
+ useEffect6(() => {
2388
+ clientRef.current = client;
2389
+ return () => {
2390
+ if (clientRef.current) disconnect(clientRef.current);
2391
+ };
2392
+ }, [client]);
2393
+ useInput6((input, key) => {
2394
+ if (key.escape) {
2395
+ if (enteringValue) {
2396
+ setEnteringValue(false);
2397
+ } else if (action && (output.length > 0 || error)) {
2398
+ setAction(null);
2399
+ setOutput([]);
2400
+ setError(null);
2401
+ setInputValue("");
2402
+ setSelectingAction(true);
2403
+ } else {
2404
+ if (client) disconnect(client);
2405
+ if (props.onBack) props.onBack();
2406
+ else exit();
2407
+ }
2408
+ }
2409
+ });
2152
2410
  useEffect6(() => {
2153
2411
  if (status === "success" && action === null && !selectingAction) {
2154
2412
  setSelectingAction(true);
@@ -2170,6 +2428,15 @@ function ConfigCommand(props) {
2170
2428
  } else if (selectedAction === "polling-interval") {
2171
2429
  setInputLabel("Enter polling interval in seconds (e.g., 10, 30, 60):");
2172
2430
  setEnteringValue(true);
2431
+ } else if (selectedAction === "log-source") {
2432
+ setInputLabel("Enter log source (pm2, file, or systemd):");
2433
+ setEnteringValue(true);
2434
+ } else if (selectedAction === "log-path") {
2435
+ setEnteringValue(true);
2436
+ } else if (selectedAction === "__back__") {
2437
+ if (client) disconnect(client);
2438
+ if (props.onBack) props.onBack();
2439
+ else exit();
2173
2440
  }
2174
2441
  };
2175
2442
  const handleInputSubmit = (value) => {
@@ -2178,7 +2445,7 @@ function ConfigCommand(props) {
2178
2445
  };
2179
2446
  useEffect6(() => {
2180
2447
  if (!client || action === null || enteringValue) return;
2181
- if ((action === "repo" || action === "branch" || action === "webhook-secret" || action === "polling-interval") && !inputValue) return;
2448
+ if ((action === "repo" || action === "branch" || action === "webhook-secret" || action === "polling-interval" || action === "log-source" || action === "log-path") && !inputValue) return;
2182
2449
  const run = async () => {
2183
2450
  try {
2184
2451
  const user = props.user || "deploy";
@@ -2191,31 +2458,62 @@ function ConfigCommand(props) {
2191
2458
  setError(configData.error);
2192
2459
  return;
2193
2460
  }
2461
+ const footprintResult = await exec(client, SCRIPTS3.getAppFootprint(props.name, user));
2462
+ let footprint = {};
2463
+ try {
2464
+ footprint = JSON.parse(footprintResult.stdout.trim());
2465
+ } catch (e) {
2466
+ console.error("Failed to parse footprint JSON", footprintResult.stdout);
2467
+ }
2194
2468
  const keyResult = await exec(client, SCRIPTS3.getDeployKey(props.name, user));
2195
2469
  const webhookResult = await exec(client, SCRIPTS3.getWebhookStatus(props.name));
2196
2470
  const pollingResult = await exec(client, SCRIPTS3.getPollingStatus(props.name));
2471
+ lines.push("--- STATUS ---");
2472
+ const pm2Status = footprint.status?.pm2 === "online" ? "\u{1F7E2} Online" : "\u{1F534} Offline";
2473
+ lines.push(`App (PM2): ${pm2Status}`);
2474
+ const whStatus = footprint.status?.webhook === "active" ? "\u{1F7E2} Active" : "\u26AB Inactive";
2475
+ lines.push(`Webhook: ${whStatus}`);
2476
+ const plStatus = footprint.status?.polling === "active" ? "\u{1F7E2} Active" : "\u26AB Inactive";
2477
+ lines.push(`Polling: ${plStatus}`);
2478
+ lines.push("");
2479
+ lines.push("--- CONFIGURATION ---");
2197
2480
  lines.push(`Repository: ${configData.repo || "Not configured"}`);
2198
- lines.push(`Branch: ${configData.branch || "main"}`);
2481
+ lines.push(`Branch: ${configData.branch || "main"}`);
2482
+ lines.push(`Build Cmd: ${configData.buildCmd || "npm run build"}`);
2483
+ lines.push(`Start Cmd: ${configData.startCmd || "npm start"}`);
2199
2484
  lines.push(`Deploy Key: ${keyResult.stdout.trim() ? "Configured" : "Not configured"}`);
2485
+ lines.push(`Log Source: ${configData.logSource || "pm2"}`);
2486
+ if (configData.logPath) {
2487
+ lines.push(`Log Path: ${configData.logPath}`);
2488
+ }
2200
2489
  const webhookStatusStr = webhookResult.stdout.trim();
2201
2490
  if (webhookStatusStr.startsWith("running:")) {
2202
2491
  const port = webhookStatusStr.split(":")[1];
2203
- lines.push(`Webhook: Running on port ${port}`);
2204
- } else {
2205
- lines.push("Webhook: Not configured");
2492
+ lines.push(`Webhook Port: ${port}`);
2206
2493
  }
2207
2494
  const pollingStatusStr = pollingResult.stdout.trim();
2208
2495
  if (pollingStatusStr.startsWith("running:")) {
2209
2496
  const parts = pollingStatusStr.split(":");
2210
- const interval = parts[1];
2211
- const mode = parts[2] || "systemd";
2212
- lines.push(`Git Polling: Running (every ${interval}s, ${mode} mode)`);
2213
- } else {
2214
- lines.push("Git Polling: Not configured");
2497
+ lines.push(`Polling Interval: ${parts[1]}s (${parts[2] || "systemd"})`);
2498
+ }
2499
+ lines.push("");
2500
+ lines.push("--- FOOTPRINT (Server Paths) ---");
2501
+ if (footprint.paths) {
2502
+ lines.push(`App Directory: ${footprint.paths.appDir}`);
2503
+ lines.push(`Config File: ${footprint.paths.config}`);
2504
+ lines.push(`Update Script: ${footprint.paths.updateScript}`);
2505
+ if (footprint.status?.webhook === "active") {
2506
+ lines.push(`Webhook Service: ${footprint.paths.webhookService}`);
2507
+ }
2508
+ if (footprint.status?.polling === "active") {
2509
+ lines.push(`Poll Script: ${footprint.paths.pollScript}`);
2510
+ lines.push(`Poll Service: ${footprint.paths.pollService}`);
2511
+ lines.push(`Poll Timer: ${footprint.paths.pollTimer}`);
2512
+ }
2215
2513
  }
2216
2514
  if (keyResult.stdout.trim()) {
2217
2515
  lines.push("");
2218
- lines.push("Public Key:");
2516
+ lines.push("--- PUBLIC KEY ---");
2219
2517
  lines.push(keyResult.stdout.trim());
2220
2518
  }
2221
2519
  break;
@@ -2303,34 +2601,95 @@ function ConfigCommand(props) {
2303
2601
  lines.push("Git polling disabled.");
2304
2602
  break;
2305
2603
  }
2604
+ case "log-source": {
2605
+ if (!["pm2", "file", "systemd"].includes(inputValue)) {
2606
+ setError("Invalid source. Must be pm2, file, or systemd.");
2607
+ return;
2608
+ }
2609
+ await execScript(client, SCRIPTS3.updateLogSettings(props.name, inputValue, ""), true);
2610
+ lines.push(`Log source updated to: ${inputValue}`);
2611
+ break;
2612
+ }
2613
+ case "log-path": {
2614
+ await execScript(client, SCRIPTS3.updateLogSettings(props.name, "", inputValue), true);
2615
+ lines.push(`Log path updated to: ${inputValue}`);
2616
+ break;
2617
+ }
2306
2618
  }
2307
2619
  setOutput(lines);
2308
- disconnect(client);
2309
- setTimeout(() => exit(), 100);
2310
2620
  } catch (err) {
2311
2621
  setError(`Operation failed: ${err instanceof Error ? err.message : err}`);
2312
- if (client) disconnect(client);
2313
- setTimeout(() => exit(), 100);
2314
2622
  }
2315
2623
  };
2316
2624
  run();
2317
2625
  }, [client, action, inputValue, enteringValue]);
2318
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2319
- /* @__PURE__ */ jsx9(Header, { title: "App Configuration", subtitle: `App: ${props.name} | Host: ${props.host}` }),
2320
- /* @__PURE__ */ jsx9(Task, { label: "Connect to server", status }),
2321
- selectingAction && action === null && /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2322
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: "Select action:" }),
2323
- /* @__PURE__ */ jsx9(SelectInput2, { items: configActions, onSelect: handleActionSelect })
2626
+ useEffect6(() => {
2627
+ if (!client || configLoaded) return;
2628
+ const fetchConfig = async () => {
2629
+ try {
2630
+ const user = props.user || "deploy";
2631
+ const [cfgRes, keyRes, whRes, pollRes] = await Promise.all([
2632
+ exec(client, SCRIPTS3.getConfig(props.name)),
2633
+ exec(client, SCRIPTS3.getDeployKey(props.name, user)),
2634
+ exec(client, SCRIPTS3.getWebhookStatus(props.name)),
2635
+ exec(client, SCRIPTS3.getPollingStatus(props.name))
2636
+ ]);
2637
+ try {
2638
+ const cfgData = JSON.parse(cfgRes.stdout.trim());
2639
+ setConfig(cfgData);
2640
+ } catch (e) {
2641
+ setConfig({});
2642
+ }
2643
+ setDeployKey(keyRes.stdout.trim());
2644
+ setWebhookStatus(whRes.stdout.trim());
2645
+ setPollingStatus(pollRes.stdout.trim());
2646
+ setConfigLoaded(true);
2647
+ } catch (e) {
2648
+ setConfigLoaded(true);
2649
+ }
2650
+ };
2651
+ fetchConfig();
2652
+ }, [client]);
2653
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2654
+ /* @__PURE__ */ jsx10(Header, { title: "App Configuration", subtitle: `App: ${props.name} | Host: ${props.host}` }),
2655
+ /* @__PURE__ */ jsx10(Task, { label: "Connect to server", status }),
2656
+ status === "success" && !configLoaded && /* @__PURE__ */ jsx10(Task, { label: "Fetching current configuration", status: "running" }),
2657
+ selectingAction && action === null && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
2658
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: "Select action:" }),
2659
+ /* @__PURE__ */ jsx10(
2660
+ SelectInput2,
2661
+ {
2662
+ items: configActions.map((item) => {
2663
+ if (!configLoaded) return item;
2664
+ let label = item.label;
2665
+ const c = config || {};
2666
+ if (item.value === "repo" && c.repo) label += ` (current: ${c.repo})`;
2667
+ else if (item.value === "branch" && c.branch) label += ` (current: ${c.branch})`;
2668
+ else if (item.value === "polling-interval" && pollingStatus.startsWith("running:")) {
2669
+ const interval = pollingStatus.split(":")[1];
2670
+ label += ` (current: ${interval}s)`;
2671
+ } else if (item.value === "log-source" && c.logSource) label += ` (current: ${c.logSource})`;
2672
+ else if (item.value === "delete-key" && !deployKey) return null;
2673
+ else if (item.value === "new-key" && deployKey) label = "Regenerate deploy key";
2674
+ return { ...item, label };
2675
+ }).filter(Boolean),
2676
+ onSelect: handleActionSelect
2677
+ },
2678
+ configLoaded ? "loaded" : "loading"
2679
+ )
2324
2680
  ] }),
2325
- enteringValue && /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2326
- /* @__PURE__ */ jsx9(Text9, { bold: true, children: inputLabel }),
2327
- /* @__PURE__ */ jsxs9(Box9, { children: [
2328
- /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "> " }),
2329
- /* @__PURE__ */ jsx9(TextInput2, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit })
2681
+ enteringValue && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
2682
+ /* @__PURE__ */ jsx10(Text10, { bold: true, children: inputLabel }),
2683
+ /* @__PURE__ */ jsxs10(Box10, { children: [
2684
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: "> " }),
2685
+ /* @__PURE__ */ jsx10(TextInput2, { value: inputValue, onChange: setInputValue, onSubmit: handleInputSubmit })
2330
2686
  ] })
2331
2687
  ] }),
2332
- output.length > 0 && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: output.map((line, i) => /* @__PURE__ */ jsx9(Text9, { color: line.startsWith("ssh-") ? "cyan" : "white", children: line }, i)) }),
2333
- error && /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
2688
+ output.length > 0 && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
2689
+ output.map((line, i) => /* @__PURE__ */ jsx10(Text10, { color: line.startsWith("ssh-") ? "cyan" : "white", children: line }, i)),
2690
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Press Esc to go back" }) })
2691
+ ] }),
2692
+ error && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
2334
2693
  "Error: ",
2335
2694
  error
2336
2695
  ] }) })
@@ -2339,7 +2698,7 @@ function ConfigCommand(props) {
2339
2698
 
2340
2699
  // src/commands/update.tsx
2341
2700
  import { useState as useState8, useEffect as useEffect7 } from "react";
2342
- import { Box as Box10, Text as Text10 } from "ink";
2701
+ import { Box as Box11, Text as Text11 } from "ink";
2343
2702
  import Spinner3 from "ink-spinner";
2344
2703
  import SelectInput3 from "ink-select-input";
2345
2704
 
@@ -2349,7 +2708,7 @@ import { execSync } from "child_process";
2349
2708
  // package.json
2350
2709
  var package_default = {
2351
2710
  name: "@it-club/provisor",
2352
- version: "0.2.2",
2711
+ version: "0.3.0",
2353
2712
  description: "Server provisioning and deployment CLI tool",
2354
2713
  type: "module",
2355
2714
  bin: {
@@ -2456,7 +2815,7 @@ function performUpdate() {
2456
2815
  }
2457
2816
 
2458
2817
  // src/commands/update.tsx
2459
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2818
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2460
2819
  function UpdateCommand({ check = false }) {
2461
2820
  const [stage, setStage] = useState8("checking");
2462
2821
  const [updateInfo, setUpdateInfo] = useState8(null);
@@ -2493,51 +2852,51 @@ function UpdateCommand({ check = false }) {
2493
2852
  }
2494
2853
  };
2495
2854
  if (stage === "checking") {
2496
- return /* @__PURE__ */ jsxs10(Box10, { children: [
2497
- /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: /* @__PURE__ */ jsx10(Spinner3, { type: "dots" }) }),
2498
- /* @__PURE__ */ jsx10(Text10, { children: " Checking for updates..." })
2855
+ return /* @__PURE__ */ jsxs11(Box11, { children: [
2856
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: /* @__PURE__ */ jsx11(Spinner3, { type: "dots" }) }),
2857
+ /* @__PURE__ */ jsx11(Text11, { children: " Checking for updates..." })
2499
2858
  ] });
2500
2859
  }
2501
2860
  if (stage === "error") {
2502
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
2861
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs11(Text11, { color: "red", children: [
2503
2862
  "\u2717 Error: ",
2504
2863
  error || resultMessage
2505
2864
  ] }) });
2506
2865
  }
2507
2866
  if (stage === "show-result" && updateInfo) {
2508
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 1, children: [
2509
- /* @__PURE__ */ jsxs10(Box10, { children: [
2510
- /* @__PURE__ */ jsx10(Text10, { children: "Current version: " }),
2511
- /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: updateInfo.currentVersion })
2867
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
2868
+ /* @__PURE__ */ jsxs11(Box11, { children: [
2869
+ /* @__PURE__ */ jsx11(Text11, { children: "Current version: " }),
2870
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: updateInfo.currentVersion })
2512
2871
  ] }),
2513
- /* @__PURE__ */ jsxs10(Box10, { children: [
2514
- /* @__PURE__ */ jsx10(Text10, { children: "Latest version: " }),
2515
- /* @__PURE__ */ jsx10(Text10, { color: updateInfo.updateAvailable ? "green" : "cyan", children: updateInfo.latestVersion })
2872
+ /* @__PURE__ */ jsxs11(Box11, { children: [
2873
+ /* @__PURE__ */ jsx11(Text11, { children: "Latest version: " }),
2874
+ /* @__PURE__ */ jsx11(Text11, { color: updateInfo.updateAvailable ? "green" : "cyan", children: updateInfo.latestVersion })
2516
2875
  ] }),
2517
- updateInfo.updateAvailable ? /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginTop: 1, children: [
2518
- /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "\u26A1 A new version is available!" }),
2519
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Run `provisor update` to install." }),
2520
- updateInfo.releaseUrl && /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
2876
+ updateInfo.updateAvailable ? /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginTop: 1, children: [
2877
+ /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "\u26A1 A new version is available!" }),
2878
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "Run `provisor update` to install." }),
2879
+ updateInfo.releaseUrl && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
2521
2880
  "Release notes: ",
2522
2881
  updateInfo.releaseUrl
2523
2882
  ] })
2524
- ] }) : /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "green", children: "\u2713 You are using the latest version." }) })
2883
+ ] }) : /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713 You are using the latest version." }) })
2525
2884
  ] });
2526
2885
  }
2527
2886
  if (stage === "confirm" && updateInfo) {
2528
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", gap: 1, children: [
2529
- /* @__PURE__ */ jsxs10(Box10, { children: [
2530
- /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "\u26A1 Update available: " }),
2531
- /* @__PURE__ */ jsx10(Text10, { children: updateInfo.currentVersion }),
2532
- /* @__PURE__ */ jsx10(Text10, { color: "gray", children: " \u2192 " }),
2533
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: updateInfo.latestVersion })
2887
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", gap: 1, children: [
2888
+ /* @__PURE__ */ jsxs11(Box11, { children: [
2889
+ /* @__PURE__ */ jsx11(Text11, { color: "yellow", children: "\u26A1 Update available: " }),
2890
+ /* @__PURE__ */ jsx11(Text11, { children: updateInfo.currentVersion }),
2891
+ /* @__PURE__ */ jsx11(Text11, { color: "gray", children: " \u2192 " }),
2892
+ /* @__PURE__ */ jsx11(Text11, { color: "green", children: updateInfo.latestVersion })
2534
2893
  ] }),
2535
- updateInfo.releaseUrl && /* @__PURE__ */ jsxs10(Text10, { color: "gray", children: [
2894
+ updateInfo.releaseUrl && /* @__PURE__ */ jsxs11(Text11, { color: "gray", children: [
2536
2895
  "Release notes: ",
2537
2896
  updateInfo.releaseUrl
2538
2897
  ] }),
2539
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { children: "Install update? " }) }),
2540
- /* @__PURE__ */ jsx10(
2898
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { children: "Install update? " }) }),
2899
+ /* @__PURE__ */ jsx11(
2541
2900
  SelectInput3,
2542
2901
  {
2543
2902
  items: [
@@ -2550,46 +2909,1478 @@ function UpdateCommand({ check = false }) {
2550
2909
  ] });
2551
2910
  }
2552
2911
  if (stage === "updating") {
2553
- return /* @__PURE__ */ jsxs10(Box10, { children: [
2554
- /* @__PURE__ */ jsx10(Text10, { color: "cyan", children: /* @__PURE__ */ jsx10(Spinner3, { type: "dots" }) }),
2555
- /* @__PURE__ */ jsx10(Text10, { children: " Installing update..." })
2912
+ return /* @__PURE__ */ jsxs11(Box11, { children: [
2913
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", children: /* @__PURE__ */ jsx11(Spinner3, { type: "dots" }) }),
2914
+ /* @__PURE__ */ jsx11(Text11, { children: " Installing update..." })
2556
2915
  ] });
2557
2916
  }
2558
2917
  if (stage === "done") {
2559
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2560
- /* @__PURE__ */ jsxs10(Text10, { color: "green", children: [
2918
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
2919
+ /* @__PURE__ */ jsxs11(Text11, { color: "green", children: [
2561
2920
  "\u2713 ",
2562
2921
  resultMessage
2563
2922
  ] }),
2564
- updateInfo?.updateAvailable && resultMessage.includes("successfully") && /* @__PURE__ */ jsx10(Text10, { color: "gray", children: "Run `provisor --version` to verify." })
2923
+ updateInfo?.updateAvailable && resultMessage.includes("successfully") && /* @__PURE__ */ jsx11(Text11, { color: "gray", children: "Run `provisor --version` to verify." })
2565
2924
  ] });
2566
2925
  }
2567
2926
  return null;
2568
2927
  }
2569
2928
 
2929
+ // src/commands/logs.tsx
2930
+ import { useState as useState9, useEffect as useEffect8 } from "react";
2931
+ import { Box as Box12, Text as Text12, useApp as useApp7, useInput as useInput7, useStdin } from "ink";
2932
+ import SelectInput4 from "ink-select-input";
2933
+ import Spinner4 from "ink-spinner";
2934
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
2935
+ var LOG_OPTIONS = [
2936
+ { label: "Application Logs (PM2)", value: "app" },
2937
+ { label: "Build/Deploy Logs", value: "build" },
2938
+ { label: "Web Server Logs (Caddy)", value: "caddy" },
2939
+ { label: "Git Polling Audit Logs", value: "audit" }
2940
+ ];
2941
+ function LogsCommand(props) {
2942
+ const { exit } = useApp7();
2943
+ const { isRawModeSupported } = useStdin();
2944
+ const [client, setClient] = useState9(null);
2945
+ const [connecting, setConnecting] = useState9(true);
2946
+ const [error, setError] = useState9(null);
2947
+ const [logType, setLogType] = useState9(null);
2948
+ const [logs, setLogs] = useState9([]);
2949
+ const [appName, setAppName] = useState9(props.name || "");
2950
+ const [config, setConfig] = useState9(null);
2951
+ useInput7((input, key) => {
2952
+ if (key.escape || key.ctrl && input === "c") {
2953
+ if (logType) {
2954
+ if (client) disconnect(client);
2955
+ if (props.onBack) {
2956
+ props.onBack();
2957
+ } else {
2958
+ exit();
2959
+ }
2960
+ } else {
2961
+ if (client) disconnect(client);
2962
+ if (props.onBack) {
2963
+ props.onBack();
2964
+ } else {
2965
+ exit();
2966
+ }
2967
+ }
2968
+ }
2969
+ });
2970
+ useEffect8(() => {
2971
+ const connectSSH = async () => {
2972
+ try {
2973
+ const sshClient = await connect(props);
2974
+ setClient(sshClient);
2975
+ if (props.name) {
2976
+ const configCmd = `
2977
+ if [ -f "/var/www/${props.name}/.provisor.json" ]; then
2978
+ cat "/var/www/${props.name}/.provisor.json"
2979
+ else
2980
+ echo "{}"
2981
+ fi
2982
+ `;
2983
+ const result = await exec(sshClient, configCmd);
2984
+ try {
2985
+ setConfig(JSON.parse(result.stdout.trim()));
2986
+ } catch (e) {
2987
+ setConfig({});
2988
+ }
2989
+ }
2990
+ setConnecting(false);
2991
+ } catch (err) {
2992
+ setError(`Connection failed: ${err instanceof Error ? err.message : err}`);
2993
+ setConnecting(false);
2994
+ }
2995
+ };
2996
+ connectSSH();
2997
+ return () => {
2998
+ if (client) disconnect(client);
2999
+ };
3000
+ }, []);
3001
+ const handleSelect = (item) => {
3002
+ setLogType(item.value);
3003
+ startStreaming(item.value);
3004
+ };
3005
+ const startStreaming = async (type) => {
3006
+ if (!client) return;
3007
+ setLogs(["Connecting to log stream..."]);
3008
+ let command = "";
3009
+ const user = props.user || "deploy";
3010
+ const home = user === "root" ? "/root" : `/home/${user}`;
3011
+ switch (type) {
3012
+ case "app":
3013
+ const source = config?.logSource || "pm2";
3014
+ const path3 = config?.logPath;
3015
+ if (source === "file" && path3) {
3016
+ command = `tail -f ${path3}`;
3017
+ } else if (source === "systemd" && path3) {
3018
+ command = `sudo journalctl -u ${path3} -f -n 50`;
3019
+ } else {
3020
+ command = `tail -f ${home}/.pm2/logs/${appName}-out.log ${home}/.pm2/logs/${appName}-error.log`;
3021
+ }
3022
+ break;
3023
+ case "caddy":
3024
+ command = "sudo journalctl -u caddy -f -n 50";
3025
+ break;
3026
+ case "audit":
3027
+ command = `
3028
+ if [ -f "/var/log/poll-${appName}.log" ]; then
3029
+ tail -f "/var/log/poll-${appName}.log"
3030
+ else
3031
+ sudo journalctl -u poll-${appName} -f -n 50
3032
+ fi
3033
+ `;
3034
+ break;
3035
+ case "build":
3036
+ setLogs(["Build logs are only available during deployment currently."]);
3037
+ return;
3038
+ }
3039
+ try {
3040
+ await execStream(
3041
+ client,
3042
+ command,
3043
+ (data) => setLogs((prev) => [...prev, ...data.split("\n")].slice(-50)),
3044
+ // Keep last 50 lines
3045
+ (data) => setLogs((prev) => [...prev, ...data.split("\n")].slice(-50))
3046
+ );
3047
+ } catch (err) {
3048
+ }
3049
+ };
3050
+ if (error) {
3051
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
3052
+ /* @__PURE__ */ jsx12(Header, { title: "Server Logs" }),
3053
+ /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
3054
+ "Error: ",
3055
+ error
3056
+ ] })
3057
+ ] });
3058
+ }
3059
+ if (connecting) {
3060
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
3061
+ /* @__PURE__ */ jsx12(Header, { title: "Server Logs" }),
3062
+ /* @__PURE__ */ jsxs12(Text12, { children: [
3063
+ /* @__PURE__ */ jsx12(Spinner4, { type: "dots" }),
3064
+ " Connecting to server..."
3065
+ ] })
3066
+ ] });
3067
+ }
3068
+ if (!appName) {
3069
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
3070
+ /* @__PURE__ */ jsx12(Header, { title: "Server Logs" }),
3071
+ /* @__PURE__ */ jsx12(Text12, { color: "red", children: "App name is required. Use -n <app>" })
3072
+ ] });
3073
+ }
3074
+ if (!logType) {
3075
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
3076
+ /* @__PURE__ */ jsx12(Header, { title: `Logs: ${appName}` }),
3077
+ /* @__PURE__ */ jsx12(Text12, { children: "Select log to view:" }),
3078
+ /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(SelectInput4, { items: LOG_OPTIONS, onSelect: handleSelect }) })
3079
+ ] });
3080
+ }
3081
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", padding: 1, children: [
3082
+ /* @__PURE__ */ jsx12(Header, { title: `Logs: ${appName} (${logType})` }),
3083
+ /* @__PURE__ */ jsx12(
3084
+ Box12,
3085
+ {
3086
+ flexDirection: "column",
3087
+ borderStyle: "single",
3088
+ padding: 1,
3089
+ height: 20,
3090
+ children: logs.map((line, i) => /* @__PURE__ */ jsx12(Text12, { wrap: "wrap", children: line }, i))
3091
+ }
3092
+ ),
3093
+ /* @__PURE__ */ jsx12(Text12, { color: "gray", children: "Press Ctrl+C to exit" })
3094
+ ] });
3095
+ }
3096
+
3097
+ // src/utils/server.ts
3098
+ import fs from "fs";
3099
+ import path from "path";
3100
+ import os from "os";
3101
+ var CONFIG_DIR = path.join(os.homedir(), ".provisor");
3102
+ var CONFIG_FILE = path.join(CONFIG_DIR, "servers.json");
3103
+ function ensureConfigDir() {
3104
+ if (!fs.existsSync(CONFIG_DIR)) {
3105
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
3106
+ }
3107
+ }
3108
+ function loadServers() {
3109
+ if (!fs.existsSync(CONFIG_FILE)) {
3110
+ return {};
3111
+ }
3112
+ try {
3113
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
3114
+ } catch (err) {
3115
+ return {};
3116
+ }
3117
+ }
3118
+ function saveServers(servers) {
3119
+ ensureConfigDir();
3120
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(servers, null, 2));
3121
+ }
3122
+ var ServerManager = {
3123
+ addServer: (alias, config) => {
3124
+ const servers = loadServers();
3125
+ servers[alias] = config;
3126
+ saveServers(servers);
3127
+ },
3128
+ removeServer: (alias) => {
3129
+ const servers = loadServers();
3130
+ delete servers[alias];
3131
+ saveServers(servers);
3132
+ },
3133
+ getServer: (alias) => {
3134
+ const servers = loadServers();
3135
+ return servers[alias] || null;
3136
+ },
3137
+ getApps: (alias) => {
3138
+ const servers = loadServers();
3139
+ return servers[alias]?.apps || null;
3140
+ },
3141
+ updateApps: (alias, apps) => {
3142
+ const servers = loadServers();
3143
+ if (servers[alias]) {
3144
+ servers[alias].apps = apps;
3145
+ saveServers(servers);
3146
+ }
3147
+ },
3148
+ listServers: () => {
3149
+ return loadServers();
3150
+ }
3151
+ };
3152
+
3153
+ // src/commands/servers.tsx
3154
+ import { useState as useState11, useEffect as useEffect10 } from "react";
3155
+ import { Box as Box14, Text as Text14, useApp as useApp9 } from "ink";
3156
+ import SelectInput6 from "ink-select-input";
3157
+ import TextInput3 from "ink-text-input";
3158
+
3159
+ // src/commands/caddy.tsx
3160
+ import { useState as useState10, useEffect as useEffect9 } from "react";
3161
+ import { Box as Box13, Text as Text13, useApp as useApp8, useInput as useInput8 } from "ink";
3162
+ import SelectInput5 from "ink-select-input";
3163
+ import Spinner5 from "ink-spinner";
3164
+ import fs2 from "fs";
3165
+ import os2 from "os";
3166
+ import path2 from "path";
3167
+ import { spawn } from "child_process";
3168
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
3169
+ function CaddyCommand(props) {
3170
+ const { exit } = useApp8();
3171
+ const [client, setClient] = useState10(null);
3172
+ const [connectStatus, setConnectStatus] = useState10("pending");
3173
+ const [error, setError] = useState10(null);
3174
+ const [mode, setMode] = useState10("menu");
3175
+ const [caddyContent, setCaddyContent] = useState10("");
3176
+ const [logsContent, setLogsContent] = useState10("");
3177
+ const [saveStatus, setSaveStatus] = useState10("");
3178
+ const [restartStatus, setRestartStatus] = useState10("pending");
3179
+ const [restartMessage, setRestartMessage] = useState10("");
3180
+ const goBack = () => {
3181
+ if (client) disconnect(client);
3182
+ if (props.onBack) {
3183
+ props.onBack();
3184
+ } else {
3185
+ exit();
3186
+ }
3187
+ };
3188
+ const items = [
3189
+ { label: "View Configuration", value: "view" },
3190
+ { label: "Edit Configuration", value: "edit" },
3191
+ { label: "View Logs", value: "logs" },
3192
+ { label: "Restart Caddy", value: "restart" },
3193
+ { label: "< Back", value: "cancel" }
3194
+ ];
3195
+ useEffect9(() => {
3196
+ const run = async () => {
3197
+ setConnectStatus("running");
3198
+ try {
3199
+ const sshClient = await connect(props);
3200
+ setClient(sshClient);
3201
+ setConnectStatus("success");
3202
+ } catch (err) {
3203
+ setConnectStatus("error");
3204
+ setError(`Connection failed: ${err instanceof Error ? err.message : err}`);
3205
+ }
3206
+ };
3207
+ run();
3208
+ return () => {
3209
+ if (client) disconnect(client);
3210
+ };
3211
+ }, []);
3212
+ const handleSelect = async (item) => {
3213
+ if (item.value === "cancel") {
3214
+ goBack();
3215
+ return;
3216
+ }
3217
+ if (!client) return;
3218
+ if (item.value === "view") {
3219
+ setMode("view");
3220
+ try {
3221
+ const result = await exec(client, "cat /etc/caddy/Caddyfile");
3222
+ setCaddyContent(result.stdout);
3223
+ } catch (err) {
3224
+ setError(`Failed to read Caddyfile: ${err}`);
3225
+ }
3226
+ }
3227
+ if (item.value === "logs") {
3228
+ setMode("logs");
3229
+ try {
3230
+ const result = await exec(client, 'journalctl -u caddy --no-pager -n 50 2>/dev/null || echo "No logs available"');
3231
+ setLogsContent(result.stdout);
3232
+ } catch (err) {
3233
+ setError(`Failed to read logs: ${err}`);
3234
+ }
3235
+ }
3236
+ if (item.value === "restart") {
3237
+ setMode("restarting");
3238
+ setRestartStatus("running");
3239
+ setRestartMessage("Restarting Caddy service...");
3240
+ try {
3241
+ await exec(client, "sudo systemctl restart caddy");
3242
+ setRestartStatus("success");
3243
+ setRestartMessage("Caddy restarted successfully!");
3244
+ const ports = await exec(client, 'ss -tlnp 2>/dev/null | grep -E ":(80|443)" || echo "none"');
3245
+ const output = ports.stdout.trim();
3246
+ const has443 = output.includes(":443");
3247
+ const has80 = output.includes(":80");
3248
+ if (has80 && has443) {
3249
+ setRestartMessage("Caddy restarted! Ports 80 & 443 now listening.");
3250
+ } else if (has80) {
3251
+ setRestartMessage("Caddy restarted! Port 80 listening. Port 443 may need a request to activate.");
3252
+ } else {
3253
+ setRestartMessage("Caddy restarted but no ports detected. Check logs.");
3254
+ }
3255
+ } catch (err) {
3256
+ setRestartStatus("error");
3257
+ setRestartMessage(`Failed to restart: ${err instanceof Error ? err.message : err}`);
3258
+ }
3259
+ }
3260
+ if (item.value === "edit") {
3261
+ setMode("edit");
3262
+ try {
3263
+ const result = await exec(client, "cat /etc/caddy/Caddyfile");
3264
+ const currentContent = result.stdout;
3265
+ const tempFile = path2.join(os2.tmpdir(), `Caddyfile-${Date.now()}`);
3266
+ fs2.writeFileSync(tempFile, currentContent);
3267
+ const editor = process.env.EDITOR || "nano";
3268
+ const child = spawn(editor, [tempFile], {
3269
+ stdio: "inherit"
3270
+ });
3271
+ child.on("exit", async (code) => {
3272
+ if (code === 0) {
3273
+ setMode("saving");
3274
+ setSaveStatus("Reading changes...");
3275
+ const newContent = fs2.readFileSync(tempFile, "utf-8");
3276
+ if (newContent !== currentContent) {
3277
+ setSaveStatus("Uploading changes...");
3278
+ const b64 = Buffer.from(newContent).toString("base64");
3279
+ const writeCmd = `echo "${b64}" | base64 -d > /etc/caddy/Caddyfile`;
3280
+ await exec(client, writeCmd);
3281
+ setSaveStatus("Reloading Caddy...");
3282
+ await exec(client, "caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy");
3283
+ setSaveStatus("Success! Caddy reloaded.");
3284
+ setTimeout(() => {
3285
+ setMode("view");
3286
+ setCaddyContent(newContent);
3287
+ setSaveStatus("");
3288
+ }, 1500);
3289
+ } else {
3290
+ setSaveStatus("No changes made.");
3291
+ setTimeout(() => {
3292
+ setMode("view");
3293
+ setCaddyContent(currentContent);
3294
+ setSaveStatus("");
3295
+ }, 1500);
3296
+ }
3297
+ fs2.unlinkSync(tempFile);
3298
+ } else {
3299
+ setError("Editor exited with error code " + code);
3300
+ setMode("menu");
3301
+ }
3302
+ });
3303
+ } catch (err) {
3304
+ setError(`Failed to edit: ${err}`);
3305
+ }
3306
+ }
3307
+ };
3308
+ useInput8((input, key) => {
3309
+ if (mode === "view" || mode === "logs" || mode === "restarting" && (restartStatus === "success" || restartStatus === "error")) {
3310
+ if (key.escape || input === "q") {
3311
+ setMode("menu");
3312
+ setCaddyContent("");
3313
+ setLogsContent("");
3314
+ setRestartStatus("pending");
3315
+ setRestartMessage("");
3316
+ }
3317
+ }
3318
+ });
3319
+ if (error) {
3320
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
3321
+ /* @__PURE__ */ jsx13(Header, { title: "Caddy Configuration" }),
3322
+ /* @__PURE__ */ jsxs13(Text13, { color: "red", children: [
3323
+ "Error: ",
3324
+ error
3325
+ ] }),
3326
+ /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Press 'q' or Esc to go back" }) })
3327
+ ] });
3328
+ }
3329
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
3330
+ /* @__PURE__ */ jsx13(Header, { title: "Caddy Configuration" }),
3331
+ /* @__PURE__ */ jsx13(Task, { label: "Connect to server", status: connectStatus }),
3332
+ connectStatus === "success" && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginTop: 1, children: [
3333
+ mode === "menu" && /* @__PURE__ */ jsxs13(Fragment2, { children: [
3334
+ /* @__PURE__ */ jsx13(Text13, { children: "Select an action:" }),
3335
+ /* @__PURE__ */ jsx13(SelectInput5, { items, onSelect: handleSelect })
3336
+ ] }),
3337
+ mode === "view" && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
3338
+ /* @__PURE__ */ jsx13(Text13, { color: "green", children: "Current Configuration (/etc/caddy/Caddyfile):" }),
3339
+ /* @__PURE__ */ jsx13(Box13, { borderStyle: "single", padding: 1, borderColor: "gray", children: caddyContent ? /* @__PURE__ */ jsx13(Text13, { children: caddyContent }) : /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs13(Text13, { color: "green", children: [
3340
+ /* @__PURE__ */ jsx13(Spinner5, { type: "dots" }),
3341
+ " Loading..."
3342
+ ] }) }) }),
3343
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Press 'q' or Esc to go back" })
3344
+ ] }),
3345
+ mode === "logs" && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
3346
+ /* @__PURE__ */ jsx13(Text13, { color: "green", children: "Caddy Logs (last 50 lines):" }),
3347
+ /* @__PURE__ */ jsx13(Box13, { borderStyle: "single", padding: 1, borderColor: "gray", flexDirection: "column", children: logsContent ? /* @__PURE__ */ jsx13(Text13, { children: logsContent }) : /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs13(Text13, { color: "green", children: [
3348
+ /* @__PURE__ */ jsx13(Spinner5, { type: "dots" }),
3349
+ " Loading..."
3350
+ ] }) }) }),
3351
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Press 'q' or Esc to go back" })
3352
+ ] }),
3353
+ mode === "restarting" && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
3354
+ /* @__PURE__ */ jsx13(Task, { label: "Restart Caddy", status: restartStatus, message: restartMessage }),
3355
+ (restartStatus === "success" || restartStatus === "error") && /* @__PURE__ */ jsx13(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx13(Text13, { color: "gray", children: "Press 'q' or Esc to go back" }) })
3356
+ ] }),
3357
+ mode === "edit" && /* @__PURE__ */ jsxs13(Box13, { children: [
3358
+ /* @__PURE__ */ jsx13(Text13, { color: "yellow", children: "Opening external editor..." }),
3359
+ /* @__PURE__ */ jsx13(Text13, { color: "gray", children: " Check your terminal focus." })
3360
+ ] }),
3361
+ mode === "saving" && /* @__PURE__ */ jsx13(Box13, { children: /* @__PURE__ */ jsxs13(Text13, { color: "green", children: [
3362
+ /* @__PURE__ */ jsx13(Spinner5, { type: "dots" }),
3363
+ " ",
3364
+ saveStatus
3365
+ ] }) })
3366
+ ] })
3367
+ ] });
3368
+ }
3369
+
3370
+ // src/commands/servers.tsx
3371
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
3372
+ function ServerCommand(props) {
3373
+ const { exit } = useApp9();
3374
+ const [mode, setMode] = useState11(
3375
+ props.action === "add" ? "add-alias" : props.action === "remove" ? "remove" : "list"
3376
+ );
3377
+ const [selectedServer, setSelectedServer] = useState11(null);
3378
+ const [newAlias, setNewAlias] = useState11(props.alias || "");
3379
+ const [newHost, setNewHost] = useState11(props.host || "");
3380
+ const [newUser, setNewUser] = useState11(props.user || "root");
3381
+ const [servers, setServers] = useState11({});
3382
+ useEffect10(() => {
3383
+ setServers(ServerManager.listServers());
3384
+ }, []);
3385
+ useEffect10(() => {
3386
+ if (props.action === "add" && props.alias && props.host) {
3387
+ ServerManager.addServer(props.alias, {
3388
+ host: props.host,
3389
+ user: props.user || "root"
3390
+ });
3391
+ console.log(`Server '${props.alias}' added.`);
3392
+ exit();
3393
+ }
3394
+ }, []);
3395
+ const handleAliasSubmit = (value) => {
3396
+ if (!value) return;
3397
+ setNewAlias(value);
3398
+ setMode("add-host");
3399
+ };
3400
+ const handleHostSubmit = (value) => {
3401
+ if (!value) return;
3402
+ setNewHost(value);
3403
+ setMode("add-user");
3404
+ };
3405
+ const handleUserSubmit = (value) => {
3406
+ const user = value || "root";
3407
+ ServerManager.addServer(newAlias, { host: newHost, user });
3408
+ setServers(ServerManager.listServers());
3409
+ setMode("list");
3410
+ };
3411
+ const handleRemoveSelect = (item) => {
3412
+ ServerManager.removeServer(item.value);
3413
+ setServers(ServerManager.listServers());
3414
+ setMode("list");
3415
+ };
3416
+ if (mode === "list") {
3417
+ const listItems = Object.entries(servers).map(([alias, config]) => ({
3418
+ label: `${alias} (${config.user}@${config.host})`,
3419
+ value: alias
3420
+ }));
3421
+ const options = [
3422
+ { label: "+ Add New Server", value: "__add__" },
3423
+ ...listItems.length > 0 ? [{ label: "- Remove Server", value: "__remove__" }] : [],
3424
+ ...listItems
3425
+ ];
3426
+ return /* @__PURE__ */ jsx14(
3427
+ Layout,
3428
+ {
3429
+ title: "Saved Servers",
3430
+ footerStatus: "Use Enter to Select, Arrows to Move",
3431
+ footerTips: ["Select a server to view options"],
3432
+ children: /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(
3433
+ SelectInput6,
3434
+ {
3435
+ items: options,
3436
+ onSelect: (item) => {
3437
+ if (item.value === "__add__") setMode("add-alias");
3438
+ else if (item.value === "__remove__") setMode("remove");
3439
+ else {
3440
+ setSelectedServer(item.value);
3441
+ setMode("actions");
3442
+ }
3443
+ }
3444
+ }
3445
+ ) })
3446
+ }
3447
+ );
3448
+ }
3449
+ if (mode === "actions" && selectedServer) {
3450
+ const config = servers[selectedServer];
3451
+ const actionItems = [
3452
+ { label: "Manage Caddy Configuration", value: "caddy" },
3453
+ { label: "< Back", value: "__back__" }
3454
+ ];
3455
+ return /* @__PURE__ */ jsx14(Layout, { title: `Server: ${selectedServer}`, children: /* @__PURE__ */ jsx14(
3456
+ SelectInput6,
3457
+ {
3458
+ items: actionItems,
3459
+ onSelect: (item) => {
3460
+ if (item.value === "__back__") {
3461
+ setMode("list");
3462
+ setSelectedServer(null);
3463
+ }
3464
+ if (item.value === "caddy") {
3465
+ setMode("caddy");
3466
+ }
3467
+ }
3468
+ }
3469
+ ) });
3470
+ }
3471
+ if (mode === "caddy" && selectedServer) {
3472
+ const config = servers[selectedServer];
3473
+ return /* @__PURE__ */ jsx14(CaddyCommand, { host: config.host, user: config.user }, config.keyPath);
3474
+ }
3475
+ if (mode === "remove") {
3476
+ const listItems = Object.entries(servers).map(([alias, config]) => ({
3477
+ label: `Remove ${alias} (${config.host})`,
3478
+ value: alias
3479
+ }));
3480
+ return /* @__PURE__ */ jsxs14(Layout, { title: "Remove Server", footerStatus: "Removing Server", children: [
3481
+ /* @__PURE__ */ jsx14(Text14, { children: "Select server to remove:" }),
3482
+ /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(
3483
+ SelectInput6,
3484
+ {
3485
+ items: [...listItems, { label: "< Back", value: "__back__" }],
3486
+ onSelect: (item) => {
3487
+ if (item.value === "__back__") setMode("list");
3488
+ else handleRemoveSelect(item);
3489
+ }
3490
+ }
3491
+ ) })
3492
+ ] });
3493
+ }
3494
+ return /* @__PURE__ */ jsxs14(Layout, { title: "Add Server", footerStatus: mode === "add-alias" ? "Enter Alias" : mode === "add-host" ? "Enter Host IP" : "Enter SSH User", children: [
3495
+ mode === "add-alias" && /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
3496
+ /* @__PURE__ */ jsx14(Text14, { children: "Enter server alias (e.g. production):" }),
3497
+ /* @__PURE__ */ jsxs14(Box14, { children: [
3498
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "> " }),
3499
+ /* @__PURE__ */ jsx14(TextInput3, { value: newAlias, onChange: setNewAlias, onSubmit: handleAliasSubmit })
3500
+ ] })
3501
+ ] }),
3502
+ mode === "add-host" && /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
3503
+ /* @__PURE__ */ jsx14(Text14, { children: "Enter IP address or hostname:" }),
3504
+ /* @__PURE__ */ jsxs14(Box14, { children: [
3505
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "> " }),
3506
+ /* @__PURE__ */ jsx14(TextInput3, { value: newHost, onChange: setNewHost, onSubmit: handleHostSubmit })
3507
+ ] })
3508
+ ] }),
3509
+ mode === "add-user" && /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
3510
+ /* @__PURE__ */ jsx14(Text14, { children: "Enter SSH user (default: root):" }),
3511
+ /* @__PURE__ */ jsxs14(Box14, { children: [
3512
+ /* @__PURE__ */ jsx14(Text14, { color: "cyan", children: "> " }),
3513
+ /* @__PURE__ */ jsx14(TextInput3, { value: newUser, onChange: setNewUser, onSubmit: handleUserSubmit })
3514
+ ] })
3515
+ ] })
3516
+ ] });
3517
+ }
3518
+
3519
+ // src/commands/main.tsx
3520
+ import { useState as useState14 } from "react";
3521
+ import { Box as Box17, Text as Text17, useApp as useApp11, useInput as useInput10 } from "ink";
3522
+ import SelectInput8 from "ink-select-input";
3523
+ import TextInput4 from "ink-text-input";
3524
+
3525
+ // src/commands/appList.tsx
3526
+ import { useState as useState12, useEffect as useEffect11 } from "react";
3527
+ import { Box as Box15, Text as Text15 } from "ink";
3528
+ import SelectInput7 from "ink-select-input";
3529
+
3530
+ // src/utils/scan.ts
3531
+ var SCAN_SCRIPT = `
3532
+ # JSON output array
3533
+ echo "["
3534
+
3535
+ FIRST=1
3536
+
3537
+ # Iterate directories in /var/www
3538
+ for DIR in /var/www/*; do
3539
+ if [ -d "$DIR" ]; then
3540
+ NAME=$(basename "$DIR")
3541
+ if [ "$NAME" != "html" ]; then
3542
+ if [ "$FIRST" != "1" ]; then echo ","; fi
3543
+ FIRST=0
3544
+
3545
+ echo "{"
3546
+ echo ""name": "$NAME","
3547
+ echo ""path": "$DIR","
3548
+
3549
+ # Check .provisor.json
3550
+ if [ -f "$DIR/.provisor.json" ]; then
3551
+ CONTENT=$(cat "$DIR/.provisor.json" | tr -d '
3552
+ ')
3553
+ echo ""config": $CONTENT,"
3554
+ fi
3555
+
3556
+ # Check Git info
3557
+ if [ -d "$DIR/.git" ]; then
3558
+ cd "$DIR"
3559
+ REMOTE=$(git remote get-url origin 2>/dev/null || echo "")
3560
+ BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
3561
+ echo ""gitRemote": "$REMOTE","
3562
+ echo ""gitBranch": "$BRANCH","
3563
+ fi
3564
+
3565
+ # Check Services
3566
+ WEBHOOK="false"
3567
+ if systemctl is-active --quiet webhook-$NAME 2>/dev/null; then WEBHOOK="true"; fi
3568
+
3569
+ POLL="false"
3570
+ if systemctl is-active --quiet poll-$NAME.timer 2>/dev/null; then POLL="true"; fi
3571
+
3572
+ # Basic check for PM2 (might need sudo or user switch, assuming running as deploy user generally)
3573
+ # This part is tricky if we don't have pm2 in path of the non-interactive shell or different user
3574
+ # We'll skip deep pm2 check for now to avoid complexity, or try simple process check
3575
+
3576
+ echo ""services": {"
3577
+ echo ""webhook": $WEBHOOK,"
3578
+ echo ""poll": $POLL"
3579
+ echo "}"
3580
+
3581
+ echo "}"
3582
+ fi
3583
+ fi
3584
+ done
3585
+
3586
+ echo "]"
3587
+ `;
3588
+ async function scanServer(options) {
3589
+ let client = null;
3590
+ try {
3591
+ client = await connect(options);
3592
+ const result = await exec(client, SCAN_SCRIPT);
3593
+ const nodeScript = `
3594
+ const fs = require('fs');
3595
+ const path = require('path');
3596
+ const { execSync } = require('child_process');
3597
+
3598
+ const apps = [];
3599
+ const wwwDir = '/var/www';
3600
+
3601
+ try {
3602
+ if (fs.existsSync(wwwDir)) {
3603
+ const dirs = fs.readdirSync(wwwDir);
3604
+ for (const name of dirs) {
3605
+ if (name === 'html') continue;
3606
+ const dirPath = path.join(wwwDir, name);
3607
+ if (fs.statSync(dirPath).isDirectory()) {
3608
+ const app = { name, path: dirPath, services: {} };
3609
+
3610
+ // Read config
3611
+ const configPath = path.join(dirPath, '.provisor.json');
3612
+ if (fs.existsSync(configPath)) {
3613
+ try {
3614
+ app.config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
3615
+ } catch (e) {}
3616
+ }
3617
+
3618
+ // Git info
3619
+ try {
3620
+ const remote = execSync('git -C ' + dirPath + ' remote get-url origin', { stdio: 'pipe' }).toString().trim();
3621
+ app.gitRemote = remote;
3622
+ const branch = execSync('git -C ' + dirPath + ' rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
3623
+ app.gitBranch = branch;
3624
+ } catch (e) {}
3625
+
3626
+ // Systemd checks
3627
+ try {
3628
+ execSync('systemctl is-active webhook-' + name, { stdio: 'ignore' });
3629
+ app.services.webhook = true;
3630
+ } catch (e) { app.services.webhook = false; }
3631
+
3632
+ try {
3633
+ execSync('systemctl is-active poll-' + name + '.timer', { stdio: 'ignore' });
3634
+ app.services.poll = true;
3635
+ } catch (e) { app.services.poll = false; }
3636
+
3637
+ apps.push(app);
3638
+ }
3639
+ }
3640
+ }
3641
+ } catch (e) {
3642
+ // ignore dir errors
3643
+ }
3644
+
3645
+ console.log(JSON.stringify(apps));
3646
+ `;
3647
+ await exec(client, `cat << 'EOF' > /tmp/scan-apps.js
3648
+ ${nodeScript}
3649
+ EOF`);
3650
+ const nodeResult = await exec(client, "node /tmp/scan-apps.js");
3651
+ await exec(client, "rm /tmp/scan-apps.js");
3652
+ try {
3653
+ const apps = JSON.parse(nodeResult.stdout.trim());
3654
+ return apps;
3655
+ } catch (e) {
3656
+ console.error("Failed to parse scan result:", nodeResult.stdout);
3657
+ return [];
3658
+ }
3659
+ } catch (err) {
3660
+ console.error("Scan failed:", err);
3661
+ throw err;
3662
+ } finally {
3663
+ if (client) disconnect(client);
3664
+ }
3665
+ }
3666
+
3667
+ // src/commands/appList.tsx
3668
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
3669
+ function AppListCommand({ alias, config, onSelectApp, onProvision, onBack }) {
3670
+ const [apps, setApps] = useState12(null);
3671
+ const [scanning, setScanning] = useState12(false);
3672
+ const [error, setError] = useState12(null);
3673
+ useEffect11(() => {
3674
+ let mounted = true;
3675
+ const load = async () => {
3676
+ const cached = ServerManager.getApps(alias);
3677
+ if (cached && cached.length > 0) {
3678
+ if (mounted) setApps(cached);
3679
+ return;
3680
+ }
3681
+ if (mounted) setScanning(true);
3682
+ try {
3683
+ const found = await scanServer(config);
3684
+ if (mounted) {
3685
+ setApps(found);
3686
+ ServerManager.updateApps(alias, found);
3687
+ }
3688
+ } catch (err) {
3689
+ if (mounted) {
3690
+ setError(err instanceof Error ? err.message : String(err));
3691
+ setApps([]);
3692
+ }
3693
+ } finally {
3694
+ if (mounted) setScanning(false);
3695
+ }
3696
+ };
3697
+ if (apps === null && !scanning && !error) {
3698
+ load();
3699
+ }
3700
+ return () => {
3701
+ mounted = false;
3702
+ };
3703
+ }, [alias, config]);
3704
+ const handleRescan = async () => {
3705
+ setScanning(true);
3706
+ setError(null);
3707
+ setApps(null);
3708
+ try {
3709
+ const found = await scanServer(config);
3710
+ setApps(found);
3711
+ ServerManager.updateApps(alias, found);
3712
+ } catch (err) {
3713
+ setError(err instanceof Error ? err.message : String(err));
3714
+ setApps([]);
3715
+ } finally {
3716
+ setScanning(false);
3717
+ }
3718
+ };
3719
+ if (scanning) {
3720
+ return /* @__PURE__ */ jsx15(Layout, { title: `Scanning ${alias}...`, footerStatus: "Please wait", children: /* @__PURE__ */ jsx15(Text15, { children: "Connecting to server and scanning for apps..." }) });
3721
+ }
3722
+ if (error) {
3723
+ return /* @__PURE__ */ jsxs15(Layout, { title: `Error Scanning ${alias}`, footerStatus: "Scan Failed", children: [
3724
+ /* @__PURE__ */ jsxs15(Text15, { color: "red", children: [
3725
+ "Failed to scan server: ",
3726
+ error
3727
+ ] }),
3728
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(
3729
+ SelectInput7,
3730
+ {
3731
+ items: [{ label: "Retry", value: "retry" }, { label: "< Back", value: "back" }],
3732
+ onSelect: (item) => {
3733
+ if (item.value === "retry") {
3734
+ setApps(null);
3735
+ } else onBack();
3736
+ }
3737
+ }
3738
+ ) })
3739
+ ] });
3740
+ }
3741
+ const listItems = (apps || []).map((app) => ({
3742
+ label: app.name,
3743
+ value: app.name
3744
+ }));
3745
+ const menuItems = [
3746
+ ...listItems,
3747
+ { label: "+ Provision New App", value: "__provision__" },
3748
+ { label: "\u21BB Re-scan", value: "__rescan__" },
3749
+ { label: "< Back", value: "__back__" }
3750
+ ];
3751
+ return /* @__PURE__ */ jsx15(
3752
+ Layout,
3753
+ {
3754
+ title: `Apps on ${alias}`,
3755
+ subtitle: `${(apps || []).length} found`,
3756
+ footerStatus: "Select an App",
3757
+ children: /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx15(
3758
+ SelectInput7,
3759
+ {
3760
+ items: menuItems,
3761
+ onSelect: (item) => {
3762
+ if (item.value === "__back__") onBack();
3763
+ else if (item.value === "__rescan__") {
3764
+ handleRescan();
3765
+ } else if (item.value === "__provision__") {
3766
+ onProvision();
3767
+ } else {
3768
+ onSelectApp(item.value);
3769
+ }
3770
+ }
3771
+ }
3772
+ ) })
3773
+ }
3774
+ );
3775
+ }
3776
+
3777
+ // src/commands/diagnostics.tsx
3778
+ import { useState as useState13, useEffect as useEffect12 } from "react";
3779
+ import { Box as Box16, Text as Text16, useApp as useApp10, useInput as useInput9 } from "ink";
3780
+ import Spinner6 from "ink-spinner";
3781
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
3782
+ function DiagnosticsCommand(props) {
3783
+ const { exit } = useApp10();
3784
+ const [checks, setChecks] = useState13([
3785
+ { name: "Caddy Service", status: "pending" },
3786
+ { name: "Firewall (UFW)", status: "pending" },
3787
+ { name: "Port Listeners", status: "pending" },
3788
+ { name: "Caddy Logs", status: "pending" },
3789
+ { name: "HTTP Connectivity", status: "pending" }
3790
+ ]);
3791
+ const [done, setDone] = useState13(false);
3792
+ const [overallStatus, setOverallStatus] = useState13("running");
3793
+ const goBack = () => {
3794
+ if (props.onBack) {
3795
+ props.onBack();
3796
+ } else {
3797
+ exit();
3798
+ }
3799
+ };
3800
+ useInput9((input, key) => {
3801
+ if (done && (key.escape || input === "q")) {
3802
+ goBack();
3803
+ }
3804
+ });
3805
+ const updateCheck = (index, update) => {
3806
+ setChecks((prev) => {
3807
+ const newChecks = [...prev];
3808
+ newChecks[index] = { ...newChecks[index], ...update };
3809
+ return newChecks;
3810
+ });
3811
+ };
3812
+ useEffect12(() => {
3813
+ const run = async () => {
3814
+ let hasErrors = false;
3815
+ let hasWarnings = false;
3816
+ try {
3817
+ const client = await connect(props);
3818
+ updateCheck(0, { status: "running" });
3819
+ try {
3820
+ const caddyStatus = await exec(client, 'systemctl is-active caddy 2>/dev/null || echo "not-found"');
3821
+ const status = caddyStatus.stdout.trim();
3822
+ if (status === "active") {
3823
+ updateCheck(0, { status: "success", result: "Running" });
3824
+ } else if (status === "inactive") {
3825
+ updateCheck(0, { status: "error", result: "Stopped - run: sudo systemctl start caddy" });
3826
+ hasErrors = true;
3827
+ } else {
3828
+ updateCheck(0, { status: "error", result: `Status: ${status}` });
3829
+ hasErrors = true;
3830
+ }
3831
+ } catch (err) {
3832
+ updateCheck(0, { status: "error", result: "Failed to check" });
3833
+ hasErrors = true;
3834
+ }
3835
+ updateCheck(1, { status: "running" });
3836
+ try {
3837
+ const ufwStatus = await exec(client, 'ufw status 2>/dev/null || echo "ufw not installed"');
3838
+ const output = ufwStatus.stdout.trim();
3839
+ if (output.includes("inactive")) {
3840
+ updateCheck(1, { status: "success", result: "Disabled (all ports open)" });
3841
+ } else if (output.includes("not installed")) {
3842
+ updateCheck(1, { status: "success", result: "Not installed" });
3843
+ } else {
3844
+ const has80 = output.includes("80") || output.includes("HTTP");
3845
+ const has443 = output.includes("443") || output.includes("HTTPS");
3846
+ if (has80 && has443) {
3847
+ updateCheck(1, { status: "success", result: "Ports 80/443 allowed" });
3848
+ } else {
3849
+ const missing = [];
3850
+ if (!has80) missing.push("80");
3851
+ if (!has443) missing.push("443");
3852
+ updateCheck(1, {
3853
+ status: "error",
3854
+ result: `Missing ports: ${missing.join(", ")}`,
3855
+ details: ["Run: sudo ufw allow 80 && sudo ufw allow 443"]
3856
+ });
3857
+ hasErrors = true;
3858
+ }
3859
+ }
3860
+ } catch (err) {
3861
+ updateCheck(1, { status: "warning", result: "Could not check firewall" });
3862
+ hasWarnings = true;
3863
+ }
3864
+ updateCheck(2, { status: "running" });
3865
+ try {
3866
+ const ports = await exec(client, 'ss -tlnp 2>/dev/null | grep -E ":(80|443)" || echo "none"');
3867
+ const output = ports.stdout.trim();
3868
+ if (output === "none" || output === "") {
3869
+ updateCheck(2, {
3870
+ status: "error",
3871
+ result: "Nothing listening on 80/443",
3872
+ details: ["Caddy may not be running or configured correctly"]
3873
+ });
3874
+ hasErrors = true;
3875
+ } else {
3876
+ const lines = output.split("\n").filter((l) => l.trim());
3877
+ const has80 = lines.some((l) => l.includes(":80 ") || l.includes(":80 "));
3878
+ const has443 = lines.some((l) => l.includes(":443 ") || l.includes(":443 "));
3879
+ if (has80 && has443) {
3880
+ updateCheck(2, { status: "success", result: "Ports 80 & 443 listening" });
3881
+ } else {
3882
+ const listening = [];
3883
+ if (has80) listening.push("80");
3884
+ if (has443) listening.push("443");
3885
+ updateCheck(2, {
3886
+ status: "warning",
3887
+ result: `Only port ${listening.join(", ")} listening`
3888
+ });
3889
+ hasWarnings = true;
3890
+ }
3891
+ }
3892
+ } catch (err) {
3893
+ updateCheck(2, { status: "warning", result: "Could not check ports" });
3894
+ hasWarnings = true;
3895
+ }
3896
+ updateCheck(3, { status: "running" });
3897
+ try {
3898
+ const logs = await exec(client, 'journalctl -u caddy --no-pager -n 10 2>/dev/null | tail -5 || echo "no logs"');
3899
+ const output = logs.stdout.trim();
3900
+ if (output.toLowerCase().includes("error") || output.toLowerCase().includes("failed")) {
3901
+ const errorLines = output.split("\n").filter(
3902
+ (l) => l.toLowerCase().includes("error") || l.toLowerCase().includes("failed")
3903
+ ).slice(0, 3);
3904
+ updateCheck(3, {
3905
+ status: "warning",
3906
+ result: "Recent errors found",
3907
+ details: errorLines.map((l) => l.slice(0, 80))
3908
+ });
3909
+ hasWarnings = true;
3910
+ } else if (output === "no logs") {
3911
+ updateCheck(3, { status: "success", result: "No logs available" });
3912
+ } else {
3913
+ updateCheck(3, { status: "success", result: "No recent errors" });
3914
+ }
3915
+ } catch (err) {
3916
+ updateCheck(3, { status: "warning", result: "Could not read logs" });
3917
+ hasWarnings = true;
3918
+ }
3919
+ updateCheck(4, { status: "running" });
3920
+ try {
3921
+ const httpTest = await exec(client, 'curl -s -o /dev/null -w "%{http_code}" http://localhost:80 2>/dev/null || echo "failed"');
3922
+ const code = httpTest.stdout.trim();
3923
+ if (code === "failed" || code === "000") {
3924
+ updateCheck(4, {
3925
+ status: "error",
3926
+ result: "Cannot connect to localhost:80",
3927
+ details: ["Web server may not be running"]
3928
+ });
3929
+ hasErrors = true;
3930
+ } else if (code.startsWith("2") || code.startsWith("3")) {
3931
+ updateCheck(4, { status: "success", result: `HTTP ${code} OK` });
3932
+ } else if (code === "404") {
3933
+ updateCheck(4, { status: "warning", result: "HTTP 404 - Check /var/www/app exists" });
3934
+ hasWarnings = true;
3935
+ } else {
3936
+ updateCheck(4, { status: "warning", result: `HTTP ${code}` });
3937
+ hasWarnings = true;
3938
+ }
3939
+ } catch (err) {
3940
+ updateCheck(4, { status: "warning", result: "Could not test HTTP" });
3941
+ hasWarnings = true;
3942
+ }
3943
+ disconnect(client);
3944
+ if (hasErrors) {
3945
+ setOverallStatus("error");
3946
+ } else if (hasWarnings) {
3947
+ setOverallStatus("warning");
3948
+ } else {
3949
+ setOverallStatus("success");
3950
+ }
3951
+ setDone(true);
3952
+ } catch (err) {
3953
+ setChecks((prev) => prev.map((c) => ({ ...c, status: "error", result: "Connection failed" })));
3954
+ setOverallStatus("error");
3955
+ setDone(true);
3956
+ }
3957
+ };
3958
+ run();
3959
+ }, []);
3960
+ const getStatusIcon = (status) => {
3961
+ switch (status) {
3962
+ case "pending":
3963
+ return "\u25CB";
3964
+ case "running":
3965
+ return "\u25D0";
3966
+ case "success":
3967
+ return "\u25CF";
3968
+ case "error":
3969
+ return "\u2717";
3970
+ default:
3971
+ return "\u25CB";
3972
+ }
3973
+ };
3974
+ const getStatusColor = (status) => {
3975
+ switch (status) {
3976
+ case "pending":
3977
+ return "gray";
3978
+ case "running":
3979
+ return "yellow";
3980
+ case "success":
3981
+ return "green";
3982
+ case "error":
3983
+ return "red";
3984
+ default:
3985
+ return "gray";
3986
+ }
3987
+ };
3988
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
3989
+ /* @__PURE__ */ jsx16(Header, { title: "Server Diagnostics", subtitle: `Host: ${props.host}` }),
3990
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, flexDirection: "column", children: checks.map((check, i) => /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
3991
+ /* @__PURE__ */ jsxs16(Box16, { children: [
3992
+ check.status === "running" ? /* @__PURE__ */ jsxs16(Text16, { color: "yellow", children: [
3993
+ /* @__PURE__ */ jsx16(Spinner6, { type: "dots" }),
3994
+ " "
3995
+ ] }) : /* @__PURE__ */ jsxs16(Text16, { color: getStatusColor(check.status), children: [
3996
+ getStatusIcon(check.status),
3997
+ " "
3998
+ ] }),
3999
+ /* @__PURE__ */ jsxs16(Text16, { children: [
4000
+ check.name,
4001
+ ": "
4002
+ ] }),
4003
+ check.result && /* @__PURE__ */ jsx16(Text16, { color: getStatusColor(check.status), children: check.result })
4004
+ ] }),
4005
+ check.details && check.details.map((detail, j) => /* @__PURE__ */ jsxs16(Text16, { color: "gray", children: [
4006
+ " \u2192 ",
4007
+ detail
4008
+ ] }, j))
4009
+ ] }, i)) }),
4010
+ done && /* @__PURE__ */ jsxs16(Box16, { marginTop: 1, flexDirection: "column", children: [
4011
+ /* @__PURE__ */ jsxs16(Box16, { children: [
4012
+ /* @__PURE__ */ jsxs16(Text16, { bold: true, children: [
4013
+ "Overall: ",
4014
+ " "
4015
+ ] }),
4016
+ overallStatus === "success" && /* @__PURE__ */ jsx16(Text16, { color: "green", children: "All checks passed!" }),
4017
+ overallStatus === "warning" && /* @__PURE__ */ jsx16(Text16, { color: "yellow", children: "Some warnings - review above" }),
4018
+ overallStatus === "error" && /* @__PURE__ */ jsx16(Text16, { color: "red", children: "Issues found - see above for fixes" })
4019
+ ] }),
4020
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text16, { color: "gray", children: "Press 'q' or Esc to go back" }) })
4021
+ ] })
4022
+ ] });
4023
+ }
4024
+
4025
+ // src/commands/main.tsx
4026
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
4027
+ function MainCommand() {
4028
+ const { exit } = useApp11();
4029
+ const [stack, setStack] = useState14([{ name: "root" }]);
4030
+ const [inputValue, setInputValue] = useState14("");
4031
+ const push = (name, props) => {
4032
+ setInputValue("");
4033
+ setStack((prev) => [...prev, { name, props }]);
4034
+ };
4035
+ const pop = () => {
4036
+ if (stack.length <= 1) {
4037
+ exit();
4038
+ } else {
4039
+ setStack((prev) => prev.slice(0, -1));
4040
+ }
4041
+ };
4042
+ const current = stack[stack.length - 1];
4043
+ useInput10((input, key) => {
4044
+ if (key.escape) {
4045
+ const currentView = stack[stack.length - 1].name;
4046
+ if (["app-logs", "app-config", "app-deploy", "server-status", "server-ssh-keys", "server-caddy", "server-diagnostics"].includes(currentView)) {
4047
+ return;
4048
+ }
4049
+ pop();
4050
+ }
4051
+ });
4052
+ if (current.name === "root") {
4053
+ const items = [
4054
+ { label: "Servers", value: "server-list" },
4055
+ { label: "Exit", value: "exit" }
4056
+ ];
4057
+ return /* @__PURE__ */ jsx17(
4058
+ Layout,
4059
+ {
4060
+ title: "Provisor",
4061
+ subtitle: "Main Menu",
4062
+ footerStatus: "Main Menu",
4063
+ footerTips: ["Select an Option", "Esc to Exit"],
4064
+ children: /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", marginTop: 1, children: [
4065
+ /* @__PURE__ */ jsx17(Text17, { bold: true, children: "Select category:" }),
4066
+ /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
4067
+ SelectInput8,
4068
+ {
4069
+ items,
4070
+ onSelect: (item) => {
4071
+ if (item.value === "exit") exit();
4072
+ else push(item.value);
4073
+ }
4074
+ }
4075
+ ) })
4076
+ ] })
4077
+ }
4078
+ );
4079
+ }
4080
+ if (current.name === "server-list") {
4081
+ const servers = ServerManager.listServers();
4082
+ const items = Object.entries(servers).map(([alias, conf]) => ({
4083
+ label: `${alias} (${conf.user}@${conf.host})`,
4084
+ value: alias
4085
+ }));
4086
+ const menuItems = [
4087
+ ...items,
4088
+ { label: "< Back", value: "__back__" }
4089
+ ];
4090
+ return /* @__PURE__ */ jsx17(Layout, { title: "Servers", footerStatus: "Select a Server", children: /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
4091
+ SelectInput8,
4092
+ {
4093
+ items: menuItems,
4094
+ onSelect: (item) => {
4095
+ if (item.value === "__back__") pop();
4096
+ else {
4097
+ push("server-dashboard", { alias: item.value, config: servers[item.value] });
4098
+ }
4099
+ }
4100
+ }
4101
+ ) }) });
4102
+ }
4103
+ if (current.name === "server-dashboard") {
4104
+ const { alias, config } = current.props;
4105
+ const items = [
4106
+ { label: "Apps", value: "app-list" },
4107
+ { label: "Run Diagnostics", value: "server-diagnostics" },
4108
+ { label: "Server Status", value: "server-status" },
4109
+ { label: "Manage SSH Keys", value: "server-ssh-keys" },
4110
+ { label: "Manage Caddy Configuration", value: "server-caddy" },
4111
+ { label: "Delete Server", value: "delete" },
4112
+ { label: "< Back", value: "__back__" }
4113
+ ];
4114
+ return /* @__PURE__ */ jsx17(
4115
+ Layout,
4116
+ {
4117
+ title: `Server: ${alias}`,
4118
+ subtitle: config.host,
4119
+ footerStatus: "Server Dashboard",
4120
+ children: /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
4121
+ SelectInput8,
4122
+ {
4123
+ items,
4124
+ onSelect: (item) => {
4125
+ if (item.value === "__back__") pop();
4126
+ else if (item.value === "app-list") {
4127
+ push("app-list", { alias, config });
4128
+ } else if (item.value === "delete") {
4129
+ push("delete-server-confirm", { alias });
4130
+ } else if (item.value === "server-status") {
4131
+ push("server-status", { alias, config });
4132
+ } else if (item.value === "server-ssh-keys") {
4133
+ push("server-ssh-keys", { alias, config });
4134
+ } else if (item.value === "server-caddy") {
4135
+ push("server-caddy", { alias, config });
4136
+ } else if (item.value === "server-diagnostics") {
4137
+ push("server-diagnostics", { alias, config });
4138
+ }
4139
+ }
4140
+ }
4141
+ ) })
4142
+ }
4143
+ );
4144
+ }
4145
+ if (current.name === "app-list") {
4146
+ const { alias, config } = current.props;
4147
+ return /* @__PURE__ */ jsx17(
4148
+ AppListCommand,
4149
+ {
4150
+ alias,
4151
+ config,
4152
+ onBack: pop,
4153
+ onSelectApp: (appName) => push("app-dashboard", { alias, config, appName }),
4154
+ onProvision: () => push("app-dashboard", { alias, config, askingName: true })
4155
+ }
4156
+ );
4157
+ }
4158
+ if (current.name === "delete-server-confirm") {
4159
+ const { alias } = current.props;
4160
+ return /* @__PURE__ */ jsx17(Layout, { title: "Delete Server", footerStatus: "Confirm Deletion", children: /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
4161
+ /* @__PURE__ */ jsxs17(Text17, { color: "red", bold: true, children: [
4162
+ "Are you sure you want to delete server '",
4163
+ alias,
4164
+ "'?"
4165
+ ] }),
4166
+ /* @__PURE__ */ jsx17(Text17, { children: "This will only remove it from your local configuration." }),
4167
+ /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
4168
+ SelectInput8,
4169
+ {
4170
+ items: [
4171
+ { label: "No, Cancel", value: "no" },
4172
+ { label: "Yes, Delete", value: "yes" }
4173
+ ],
4174
+ onSelect: (item) => {
4175
+ if (item.value === "yes") {
4176
+ ServerManager.removeServer(alias);
4177
+ setStack([{ name: "root" }, { name: "server-list" }]);
4178
+ } else {
4179
+ pop();
4180
+ }
4181
+ }
4182
+ }
4183
+ ) })
4184
+ ] }) });
4185
+ }
4186
+ if (current.name === "app-dashboard") {
4187
+ const { alias, config, askingName, appName } = current.props;
4188
+ if (askingName) {
4189
+ return /* @__PURE__ */ jsx17(Layout, { title: "Select App", footerStatus: "Enter App Name", children: /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", marginTop: 1, children: [
4190
+ /* @__PURE__ */ jsx17(Text17, { children: "Enter Application Name:" }),
4191
+ /* @__PURE__ */ jsxs17(Box17, { children: [
4192
+ /* @__PURE__ */ jsx17(Text17, { color: "cyan", children: "> " }),
4193
+ /* @__PURE__ */ jsx17(
4194
+ TextInput4,
4195
+ {
4196
+ value: inputValue,
4197
+ onChange: setInputValue,
4198
+ onSubmit: (val) => {
4199
+ setStack((prev) => {
4200
+ const newStack = [...prev];
4201
+ newStack[newStack.length - 1] = {
4202
+ name: "app-dashboard",
4203
+ props: { alias, config, appName: val, askingName: false }
4204
+ };
4205
+ return newStack;
4206
+ });
4207
+ setInputValue("");
4208
+ }
4209
+ }
4210
+ )
4211
+ ] })
4212
+ ] }) });
4213
+ }
4214
+ const items = [
4215
+ { label: "View Logs", value: "logs" },
4216
+ { label: "Configuration", value: "config" },
4217
+ { label: "Deploy", value: "deploy" },
4218
+ { label: "< Back", value: "__back__" }
4219
+ ];
4220
+ return /* @__PURE__ */ jsx17(Layout, { title: `App: ${appName}`, subtitle: `Server: ${alias}`, footerStatus: "App Dashboard", children: /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx17(
4221
+ SelectInput8,
4222
+ {
4223
+ items,
4224
+ onSelect: (item) => {
4225
+ if (item.value === "__back__") pop();
4226
+ else if (item.value === "logs") push("app-logs", { config, appName });
4227
+ else if (item.value === "config") push("app-config", { config, appName });
4228
+ else if (item.value === "deploy") push("app-deploy", { config, appName });
4229
+ }
4230
+ }
4231
+ ) }) });
4232
+ }
4233
+ if (current.name === "app-logs") {
4234
+ const { config, appName } = current.props;
4235
+ return /* @__PURE__ */ jsx17(
4236
+ LogsCommand,
4237
+ {
4238
+ host: config.host,
4239
+ user: config.user,
4240
+ name: appName,
4241
+ onBack: pop
4242
+ }
4243
+ );
4244
+ }
4245
+ if (current.name === "app-config") {
4246
+ const { config, appName } = current.props;
4247
+ return /* @__PURE__ */ jsx17(
4248
+ ConfigCommand,
4249
+ {
4250
+ host: config.host,
4251
+ user: config.user,
4252
+ name: appName,
4253
+ onBack: pop
4254
+ }
4255
+ );
4256
+ }
4257
+ if (current.name === "app-deploy") {
4258
+ const { config, appName } = current.props;
4259
+ return /* @__PURE__ */ jsx17(
4260
+ DeployCommand,
4261
+ {
4262
+ host: config.host,
4263
+ user: config.user,
4264
+ name: appName,
4265
+ onBack: pop
4266
+ }
4267
+ );
4268
+ }
4269
+ if (current.name === "server-status") {
4270
+ const { config } = current.props;
4271
+ return /* @__PURE__ */ jsx17(
4272
+ StatusCommand,
4273
+ {
4274
+ host: config.host,
4275
+ user: config.user,
4276
+ onBack: pop
4277
+ }
4278
+ );
4279
+ }
4280
+ if (current.name === "server-ssh-keys") {
4281
+ const { config } = current.props;
4282
+ return /* @__PURE__ */ jsx17(
4283
+ SshKeyCommand,
4284
+ {
4285
+ host: config.host,
4286
+ user: config.user,
4287
+ list: true,
4288
+ onBack: pop
4289
+ }
4290
+ );
4291
+ }
4292
+ if (current.name === "server-caddy") {
4293
+ const { config } = current.props;
4294
+ return /* @__PURE__ */ jsx17(
4295
+ CaddyCommand,
4296
+ {
4297
+ host: config.host,
4298
+ user: config.user,
4299
+ onBack: pop
4300
+ }
4301
+ );
4302
+ }
4303
+ if (current.name === "server-diagnostics") {
4304
+ const { config } = current.props;
4305
+ return /* @__PURE__ */ jsx17(
4306
+ DiagnosticsCommand,
4307
+ {
4308
+ host: config.host,
4309
+ user: config.user,
4310
+ onBack: pop
4311
+ }
4312
+ );
4313
+ }
4314
+ return /* @__PURE__ */ jsxs17(Text17, { children: [
4315
+ "State Error: Unknown View ",
4316
+ current.name
4317
+ ] });
4318
+ }
4319
+
2570
4320
  // src/cli.tsx
2571
- import { jsx as jsx11 } from "react/jsx-runtime";
4321
+ import { jsx as jsx18 } from "react/jsx-runtime";
4322
+ var resolveHostAlias = (options) => {
4323
+ if (options.host) {
4324
+ const savedServer = ServerManager.getServer(options.host);
4325
+ if (savedServer) {
4326
+ options.host = savedServer.host;
4327
+ if (!options.user && savedServer.user) options.user = savedServer.user;
4328
+ if (!options.key && savedServer.keyPath) options.key = savedServer.keyPath;
4329
+ }
4330
+ }
4331
+ };
2572
4332
  program.name("provisor").description("Server provisioning and deployment CLI").version(package_default.version);
2573
- program.command("init").description("Initialize server with user, SSH, and firewall setup").requiredOption("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to create", "deploy").option("-k, --key <path>", "Path to SSH private key for root access").option("-p, --port <port>", "SSH port", "22").action((options) => {
2574
- render(/* @__PURE__ */ jsx11(InitCommand, { ...options }));
4333
+ program.option("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to create", "deploy").option("-k, --key <path>", "Path to SSH private key for root access").option("-p, --port <port>", "SSH port", "22").action((options) => {
4334
+ resolveHostAlias(options);
4335
+ render(/* @__PURE__ */ jsx18(MainCommand, {}));
4336
+ });
4337
+ program.command("init").description("Initialize a new server").option("-h, --host <host>", "Server IP address").option("-u, --user <user>", "SSH user (default: root)").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
4338
+ resolveHostAlias(options);
4339
+ render(/* @__PURE__ */ jsx18(InitCommand, { ...options }));
2575
4340
  });
2576
4341
  program.command("app").description("Provision application (Caddy, Node.js, Git deploy)").requiredOption("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").option("-b, --branch <branch>", "Deploy branch", "main").option("-n, --name <name>", "Application name", "app").option("-r, --repo <url>", "Clone from repository URL (GitHub, GitLab, etc.)").action((options) => {
2577
- render(/* @__PURE__ */ jsx11(AppCommand, { ...options }));
4342
+ resolveHostAlias(options);
4343
+ render(/* @__PURE__ */ jsx18(AppCommand, { ...options }));
2578
4344
  });
2579
4345
  program.command("ssh-key").description("Manage SSH keys on the server").requiredOption("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").option("--add <pubkey>", "Add a new public key").option("--list", "List authorized keys").action((options) => {
2580
- render(/* @__PURE__ */ jsx11(SshKeyCommand, { ...options }));
4346
+ resolveHostAlias(options);
4347
+ render(/* @__PURE__ */ jsx18(SshKeyCommand, { ...options }));
2581
4348
  });
2582
4349
  program.command("status").description("Check server status and services").requiredOption("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
2583
- render(/* @__PURE__ */ jsx11(StatusCommand, { ...options }));
4350
+ resolveHostAlias(options);
4351
+ render(/* @__PURE__ */ jsx18(StatusCommand, { ...options }));
2584
4352
  });
2585
4353
  program.command("deploy").description("Trigger deployment for an application").requiredOption("-h, --host <host>", "Server hostname or IP").requiredOption("-n, --name <name>", "Application name").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
2586
- render(/* @__PURE__ */ jsx11(DeployCommand, { ...options }));
4354
+ resolveHostAlias(options);
4355
+ render(/* @__PURE__ */ jsx18(DeployCommand, { ...options }));
2587
4356
  });
2588
4357
  program.command("config").description("Manage application configuration").requiredOption("-h, --host <host>", "Server hostname or IP").requiredOption("-n, --name <name>", "Application name").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").option("--show", "Show current configuration").option("--repo <url>", "Change repository URL").option("--branch <branch>", "Change deploy branch").option("--new-key", "Generate new deploy key").option("--delete-key", "Delete deploy key").option("--webhook-secret <secret>", "Update webhook secret").option("--disable-webhook", "Disable webhook").option("--polling-interval <seconds>", "Set git polling interval in seconds", parseInt).option("--enable-polling", "Enable git polling for auto-deploy").option("--disable-polling", "Disable git polling").action((options) => {
2589
- render(/* @__PURE__ */ jsx11(ConfigCommand, { ...options }));
4358
+ resolveHostAlias(options);
4359
+ render(/* @__PURE__ */ jsx18(ConfigCommand, { ...options }));
4360
+ });
4361
+ program.command("caddy").description("Manage Caddy configuration").requiredOption("-h, --host <host>", "Server hostname or IP").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
4362
+ resolveHostAlias(options);
4363
+ render(/* @__PURE__ */ jsx18(CaddyCommand, { ...options }));
2590
4364
  });
2591
4365
  program.command("update").description("Check for updates and update the CLI").option("--check", "Only check for updates, do not install").action((options) => {
2592
- render(/* @__PURE__ */ jsx11(UpdateCommand, { ...options }));
4366
+ render(/* @__PURE__ */ jsx18(UpdateCommand, { ...options }));
4367
+ });
4368
+ program.command("logs").description("View application and server logs").requiredOption("-h, --host <host>", "Server hostname or IP").requiredOption("-n, --name <name>", "Application name").option("-u, --user <user>", "Username to connect as", "deploy").option("-k, --key <path>", "Path to SSH private key").option("-p, --port <port>", "SSH port", "22").action((options) => {
4369
+ resolveHostAlias(options);
4370
+ render(/* @__PURE__ */ jsx18(LogsCommand, { ...options }));
4371
+ });
4372
+ program.command("server").description("Manage saved servers").argument("[action]", "Action to perform: add, list, remove").argument("[alias]", "Server alias").argument("[host]", "Server host (IP)").option("-u, --user <user>", "SSH user").action((action, alias, host, options) => {
4373
+ render(
4374
+ /* @__PURE__ */ jsx18(
4375
+ ServerCommand,
4376
+ {
4377
+ action,
4378
+ alias,
4379
+ host,
4380
+ user: options.user
4381
+ }
4382
+ )
4383
+ );
2593
4384
  });
2594
4385
  program.parse();
2595
4386
  //# sourceMappingURL=cli.js.map