@studiometa/forge-cli 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { S as isOutputFormat, a as createContext, b as ValidationError, c as colors, d as deleteToken, g as setToken, h as setOrganizationSlug, i as runCommand, l as setColorEnabled, m as getToken, n as exitWithValidationError, p as getOrganizationSlug, s as OutputFormatter, u as createConfigStore } from "./error-handler-ByMitiOr.js";
2
+ import { S as isOutputFormat, _ as ApiError, a as createContext, b as ValidationError, c as colors, d as deleteToken, g as setToken, h as setOrganizationSlug, i as runCommand, l as setColorEnabled, m as getToken, n as exitWithValidationError, p as getOrganizationSlug, s as OutputFormatter, u as createConfigStore } from "./error-handler-peS_8nl4.js";
3
3
  import { activateCertificate, createAuditLogger, createBackupConfig, createCommand, createDatabaseUser, createMonitor, createNginxTemplate, createRedirectRule, createScheduledJob, createSecurityRule, deleteBackupConfig, deleteDatabaseUser, deleteMonitor, deleteNginxTemplate, deleteRedirectRule, deleteScheduledJob, deleteSecurityRule, deploySiteAndWait, getBackupConfig, getCertificate, getCommand, getDaemon, getDatabase, getDatabaseUser, getEnv, getFirewallRule, getMonitor, getNginxConfig, getNginxTemplate, getRecipe, getRedirectRule, getScheduledJob, getSecurityRule, getServer, getSite, getSshKey, getUser, listBackupConfigs, listCommands, listDaemons, listDatabaseUsers, listDatabases, listDeployments, listFirewallRules, listMonitors, listNginxTemplates, listRecipes, listRedirectRules, listScheduledJobs, listSecurityRules, listServers, listSites, listSshKeys, matchByName, rebootServer, restartDaemon, runRecipe, updateBackupConfig, updateDaemon, updateDatabaseUser, updateEnv, updateNginxConfig, updateNginxTemplate, updateRecipe, updateSecurityRule, updateSite } from "@studiometa/forge-core";
4
4
  import { mkdirSync, writeFileSync } from "node:fs";
5
5
  import { homedir } from "node:os";
6
6
  import { join } from "node:path";
7
+ import { spawnSync } from "node:child_process";
8
+ //#region src/audit.ts
7
9
  /**
8
10
  * Shared audit logger for CLI write operations.
9
11
  * Lazily initialized on first use.
@@ -13,6 +15,8 @@ function getCliAuditLogger() {
13
15
  if (!_auditLogger) _auditLogger = createAuditLogger("cli");
14
16
  return _auditLogger;
15
17
  }
18
+ //#endregion
19
+ //#region src/utils/command-router.ts
16
20
  /**
17
21
  * Creates a command router function that dispatches to the appropriate handler.
18
22
  */
@@ -55,6 +59,8 @@ function createCommandRouter(config) {
55
59
  else await handler(ctx);
56
60
  };
57
61
  }
62
+ //#endregion
63
+ //#region src/utils/resolve.ts
58
64
  /**
59
65
  * Smart resolution utilities for --server and --site options.
60
66
  *
@@ -106,6 +112,8 @@ async function resolveSiteId(value, serverId, execCtx) {
106
112
  }
107
113
  return String(partial[0].id);
108
114
  }
115
+ //#endregion
116
+ //#region src/commands/backups/handlers.ts
109
117
  async function backupsList(ctx) {
110
118
  const server = String(ctx.options.server ?? "");
111
119
  if (!server) exitWithValidationError("server_id", "forge backups list --server <server_id>", ctx.formatter);
@@ -216,7 +224,9 @@ async function backupsDelete(args, ctx) {
216
224
  ctx.formatter.success(`Backup configuration ${id} deleted.`);
217
225
  }, ctx.formatter);
218
226
  }
219
- const handleBackupsCommand = createCommandRouter({
227
+ //#endregion
228
+ //#region src/commands/backups/command.ts
229
+ var handleBackupsCommand = createCommandRouter({
220
230
  resource: "backups",
221
231
  handlers: {
222
232
  list: backupsList,
@@ -232,6 +242,8 @@ const handleBackupsCommand = createCommandRouter({
232
242
  "delete"
233
243
  ]
234
244
  });
245
+ //#endregion
246
+ //#region src/commands/backups/help.ts
235
247
  function showBackupsHelp(subcommand) {
236
248
  if (subcommand === "list" || subcommand === "ls") console.log(`
237
249
  ${colors.bold("forge backups list")} - List backup configurations on a server
@@ -309,6 +321,8 @@ ${colors.bold("OPTIONS:")}
309
321
  Run ${colors.cyan("forge backups <subcommand> --help")} for details.
310
322
  `);
311
323
  }
324
+ //#endregion
325
+ //#region src/commands/completion.ts
312
326
  /**
313
327
  * Shell completion installation command
314
328
  */
@@ -322,7 +336,7 @@ _forge_completions() {
322
336
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
323
337
 
324
338
  # Main commands
325
- commands="config servers s sites deployments d databases db database-users daemons env nginx certificates certs firewall-rules fw ssh-keys backups commands scheduled-jobs user monitors nginx-templates security-rules redirect-rules recipes completion help"
339
+ commands="config servers s sites deployments d databases db database-users daemons env nginx certificates certs firewall-rules fw ssh ssh-keys backups commands scheduled-jobs user monitors nginx-templates security-rules redirect-rules recipes completion help"
326
340
 
327
341
  # Subcommands for each command
328
342
  local config_cmds="set get delete"
@@ -349,7 +363,7 @@ _forge_completions() {
349
363
  local completion_cmds="bash zsh fish"
350
364
 
351
365
  # Global options
352
- options="--token --server --site -f --format --no-color -h --help -v --version --name --command --content --from --to --provider --frequency --type --operator --threshold --minutes --key --port --ip-address --user --script --servers --domain --password --credential-id --region --size --project-type --directory"
366
+ options="--token --server --site -f --format --no-color -h --help -v --version --name --command --content --from --to --provider --frequency --type --operator --threshold --minutes --key --port --ip-address --user --private --dry-run --script --servers --domain --password --credential-id --region --size --project-type --directory"
353
367
 
354
368
  # Format options
355
369
  local formats="json human table"
@@ -486,6 +500,7 @@ _forge() {
486
500
  'certs:Manage SSL certificates (alias)'
487
501
  'firewall-rules:Manage firewall rules'
488
502
  'fw:Manage firewall rules (alias)'
503
+ 'ssh:Open an SSH session to a server'
489
504
  'ssh-keys:Manage SSH keys'
490
505
  'backups:Manage backup configurations'
491
506
  'commands:Manage site commands'
@@ -775,6 +790,7 @@ complete -c forge -f -n "__fish_use_subcommand" -a "certificates" -d "Manage SSL
775
790
  complete -c forge -f -n "__fish_use_subcommand" -a "certs" -d "Manage SSL certificates (alias)"
776
791
  complete -c forge -f -n "__fish_use_subcommand" -a "firewall-rules" -d "Manage firewall rules"
777
792
  complete -c forge -f -n "__fish_use_subcommand" -a "fw" -d "Manage firewall rules (alias)"
793
+ complete -c forge -f -n "__fish_use_subcommand" -a "ssh" -d "Open an SSH session to a server"
778
794
  complete -c forge -f -n "__fish_use_subcommand" -a "ssh-keys" -d "Manage SSH keys"
779
795
  complete -c forge -f -n "__fish_use_subcommand" -a "backups" -d "Manage backup configurations"
780
796
  complete -c forge -f -n "__fish_use_subcommand" -a "commands" -d "Manage site commands"
@@ -1053,6 +1069,8 @@ function handleCompletionCommand(shell, options = {}) {
1053
1069
  process.exit(1);
1054
1070
  }
1055
1071
  }
1072
+ //#endregion
1073
+ //#region src/commands/certificates/handlers.ts
1056
1074
  function requireServerSiteDomainRaw(ctx, usage) {
1057
1075
  const server = String(ctx.options.server ?? "");
1058
1076
  const site = String(ctx.options.site ?? "");
@@ -1104,7 +1122,9 @@ async function certificatesActivate(args, ctx) {
1104
1122
  ctx.formatter.success(`Certificate for domain ${domainId} activated.`);
1105
1123
  }, ctx.formatter);
1106
1124
  }
1107
- const handleCertificatesCommand = createCommandRouter({
1125
+ //#endregion
1126
+ //#region src/commands/certificates/command.ts
1127
+ var handleCertificatesCommand = createCommandRouter({
1108
1128
  resource: "certificates",
1109
1129
  handlers: {
1110
1130
  get: [certificatesGet, "args"],
@@ -1112,6 +1132,8 @@ const handleCertificatesCommand = createCommandRouter({
1112
1132
  },
1113
1133
  writeSubcommands: ["activate"]
1114
1134
  });
1135
+ //#endregion
1136
+ //#region src/commands/certificates/help.ts
1115
1137
  function showCertificatesHelp(subcommand) {
1116
1138
  if (subcommand === "get") console.log(`
1117
1139
  ${colors.bold("forge certificates get")} - Get certificate for a domain
@@ -1150,6 +1172,8 @@ ${colors.bold("NOTE:")}
1150
1172
  Run ${colors.cyan("forge certificates <subcommand> --help")} for details.
1151
1173
  `);
1152
1174
  }
1175
+ //#endregion
1176
+ //#region src/commands/commands/handlers.ts
1153
1177
  function requireServerAndSiteRaw$2(ctx, usage) {
1154
1178
  const server = String(ctx.options.server ?? "");
1155
1179
  const site = String(ctx.options.site ?? "");
@@ -1219,7 +1243,9 @@ async function commandsCreate(ctx) {
1219
1243
  ctx.formatter.success("Command queued for execution.");
1220
1244
  }, ctx.formatter);
1221
1245
  }
1222
- const handleCommandsCommand = createCommandRouter({
1246
+ //#endregion
1247
+ //#region src/commands/commands/command.ts
1248
+ var handleCommandsCommand = createCommandRouter({
1223
1249
  resource: "commands",
1224
1250
  handlers: {
1225
1251
  list: commandsList,
@@ -1229,6 +1255,8 @@ const handleCommandsCommand = createCommandRouter({
1229
1255
  },
1230
1256
  writeSubcommands: ["create"]
1231
1257
  });
1258
+ //#endregion
1259
+ //#region src/commands/commands/help.ts
1232
1260
  function showCommandsHelp(subcommand) {
1233
1261
  if (subcommand === "list" || subcommand === "ls") console.log(`
1234
1262
  ${colors.bold("forge commands list")} - List commands executed on a site
@@ -1278,6 +1306,8 @@ ${colors.bold("OPTIONS:")}
1278
1306
  Run ${colors.cyan("forge commands <subcommand> --help")} for details.
1279
1307
  `);
1280
1308
  }
1309
+ //#endregion
1310
+ //#region src/commands/config.ts
1281
1311
  /**
1282
1312
  * Config command — manage forge configuration
1283
1313
  */
@@ -1362,6 +1392,8 @@ function handleConfigCommand(subcommand, args, options) {
1362
1392
  function maskToken(token) {
1363
1393
  return token.length > 8 ? `${token.slice(0, 4)}${"*".repeat(token.length - 8)}${token.slice(-4)}` : "****";
1364
1394
  }
1395
+ //#endregion
1396
+ //#region src/commands/daemons/handlers.ts
1365
1397
  async function daemonsList(ctx) {
1366
1398
  const server = String(ctx.options.server ?? "");
1367
1399
  if (!server) exitWithValidationError("server_id", "forge daemons list --server <server_id>", ctx.formatter);
@@ -1443,7 +1475,9 @@ async function daemonsRestart(args, ctx) {
1443
1475
  ctx.formatter.success(`Daemon ${id} restarted.`);
1444
1476
  }, ctx.formatter);
1445
1477
  }
1446
- const handleDaemonsCommand = createCommandRouter({
1478
+ //#endregion
1479
+ //#region src/commands/daemons/command.ts
1480
+ var handleDaemonsCommand = createCommandRouter({
1447
1481
  resource: "daemons",
1448
1482
  handlers: {
1449
1483
  list: daemonsList,
@@ -1454,6 +1488,8 @@ const handleDaemonsCommand = createCommandRouter({
1454
1488
  },
1455
1489
  writeSubcommands: ["restart", "update"]
1456
1490
  });
1491
+ //#endregion
1492
+ //#region src/commands/daemons/help.ts
1457
1493
  function showDaemonsHelp(subcommand) {
1458
1494
  if (subcommand === "list" || subcommand === "ls") console.log(`
1459
1495
  ${colors.bold("forge daemons list")} - List daemons on a server
@@ -1498,6 +1534,8 @@ ${colors.bold("OPTIONS:")}
1498
1534
  Run ${colors.cyan("forge daemons <subcommand> --help")} for details.
1499
1535
  `);
1500
1536
  }
1537
+ //#endregion
1538
+ //#region src/commands/databases/handlers.ts
1501
1539
  async function databasesList(ctx) {
1502
1540
  const server = String(ctx.options.server ?? "");
1503
1541
  if (!server) exitWithValidationError("server_id", "forge databases list --server <server_id>", ctx.formatter);
@@ -1532,7 +1570,9 @@ async function databasesGet(args, ctx) {
1532
1570
  ]);
1533
1571
  }, ctx.formatter);
1534
1572
  }
1535
- const handleDatabasesCommand = createCommandRouter({
1573
+ //#endregion
1574
+ //#region src/commands/databases/command.ts
1575
+ var handleDatabasesCommand = createCommandRouter({
1536
1576
  resource: "databases",
1537
1577
  handlers: {
1538
1578
  list: databasesList,
@@ -1540,6 +1580,8 @@ const handleDatabasesCommand = createCommandRouter({
1540
1580
  get: [databasesGet, "args"]
1541
1581
  }
1542
1582
  });
1583
+ //#endregion
1584
+ //#region src/commands/databases/help.ts
1543
1585
  function showDatabasesHelp(subcommand) {
1544
1586
  if (subcommand === "list" || subcommand === "ls") console.log(`
1545
1587
  ${colors.bold("forge databases list")} - List databases on a server
@@ -1577,6 +1619,8 @@ ${colors.bold("OPTIONS:")}
1577
1619
  Run ${colors.cyan("forge databases <subcommand> --help")} for details.
1578
1620
  `);
1579
1621
  }
1622
+ //#endregion
1623
+ //#region src/commands/database-users/handlers.ts
1580
1624
  async function databaseUsersList(ctx) {
1581
1625
  const server = String(ctx.options.server ?? "");
1582
1626
  if (!server) exitWithValidationError("server_id", "forge database-users list --server <server_id>", ctx.formatter);
@@ -1677,7 +1721,9 @@ async function databaseUsersDelete(args, ctx) {
1677
1721
  ctx.formatter.success(`Database user ${id} deleted.`);
1678
1722
  }, ctx.formatter);
1679
1723
  }
1680
- const handleDatabaseUsersCommand = createCommandRouter({
1724
+ //#endregion
1725
+ //#region src/commands/database-users/command.ts
1726
+ var handleDatabaseUsersCommand = createCommandRouter({
1681
1727
  resource: "database-users",
1682
1728
  handlers: {
1683
1729
  list: databaseUsersList,
@@ -1693,6 +1739,8 @@ const handleDatabaseUsersCommand = createCommandRouter({
1693
1739
  "delete"
1694
1740
  ]
1695
1741
  });
1742
+ //#endregion
1743
+ //#region src/commands/database-users/help.ts
1696
1744
  function showDatabaseUsersHelp(subcommand) {
1697
1745
  if (subcommand === "list" || subcommand === "ls") console.log(`
1698
1746
  ${colors.bold("forge database-users list")} - List database users on a server
@@ -1761,6 +1809,8 @@ ${colors.bold("OPTIONS:")}
1761
1809
  Run ${colors.cyan("forge database-users <subcommand> --help")} for details.
1762
1810
  `);
1763
1811
  }
1812
+ //#endregion
1813
+ //#region src/commands/deployments/handlers.ts
1764
1814
  async function deploymentsList(ctx) {
1765
1815
  const server = String(ctx.options.server ?? "");
1766
1816
  const site = String(ctx.options.site ?? "");
@@ -1803,14 +1853,17 @@ async function deploymentsDeploy(ctx) {
1803
1853
  process.stdout.write(chunk);
1804
1854
  } : void 0
1805
1855
  }, execCtx);
1806
- if (!streamLogs) process.stderr.write("\n");
1856
+ if (streamLogs) process.stdout.write("\n");
1857
+ else process.stderr.write("\n");
1807
1858
  const elapsedSec = (result.data.elapsed_ms / 1e3).toFixed(1);
1808
- if (result.data.status === "success") ctx.formatter.success(`Deployment succeeded for site ${site_id} (${elapsedSec}s).`);
1809
- else ctx.formatter.error(`Deployment failed for site ${site_id} (${elapsedSec}s).`);
1810
1859
  if (!streamLogs && result.data.log) ctx.formatter.output(result.data.log);
1860
+ if (result.data.status === "success") ctx.formatter.success(`Deployment succeeded for site ${site_id} (${elapsedSec}s).`);
1861
+ else throw new ApiError(`Deployment failed for site ${site_id} (${elapsedSec}s).`);
1811
1862
  }, ctx.formatter);
1812
1863
  }
1813
- const handleDeploymentsCommand = createCommandRouter({
1864
+ //#endregion
1865
+ //#region src/commands/deployments/command.ts
1866
+ var handleDeploymentsCommand = createCommandRouter({
1814
1867
  resource: "deployments",
1815
1868
  handlers: {
1816
1869
  list: deploymentsList,
@@ -1819,6 +1872,8 @@ const handleDeploymentsCommand = createCommandRouter({
1819
1872
  },
1820
1873
  writeSubcommands: ["deploy"]
1821
1874
  });
1875
+ //#endregion
1876
+ //#region src/commands/deployments/help.ts
1822
1877
  function showDeploymentsHelp(subcommand) {
1823
1878
  if (subcommand === "list" || subcommand === "ls") console.log(`
1824
1879
  ${colors.bold("forge deployments list")} - List deployments for a site
@@ -1864,6 +1919,8 @@ ${colors.bold("OPTIONS:")}
1864
1919
  Run ${colors.cyan("forge deployments <subcommand> --help")} for details.
1865
1920
  `);
1866
1921
  }
1922
+ //#endregion
1923
+ //#region src/commands/env/handlers.ts
1867
1924
  async function envGet(ctx) {
1868
1925
  const server = String(ctx.options.server ?? "");
1869
1926
  const site = String(ctx.options.site ?? "");
@@ -1900,7 +1957,9 @@ async function envUpdate(ctx) {
1900
1957
  ctx.formatter.success("Environment variables updated.");
1901
1958
  }, ctx.formatter);
1902
1959
  }
1903
- const handleEnvCommand = createCommandRouter({
1960
+ //#endregion
1961
+ //#region src/commands/env/command.ts
1962
+ var handleEnvCommand = createCommandRouter({
1904
1963
  resource: "env",
1905
1964
  handlers: {
1906
1965
  get: envGet,
@@ -1908,6 +1967,8 @@ const handleEnvCommand = createCommandRouter({
1908
1967
  },
1909
1968
  writeSubcommands: ["update"]
1910
1969
  });
1970
+ //#endregion
1971
+ //#region src/commands/env/help.ts
1911
1972
  function showEnvHelp(subcommand) {
1912
1973
  if (subcommand === "get") console.log(`
1913
1974
  ${colors.bold("forge env get")} - Get environment variables
@@ -1940,6 +2001,8 @@ ${colors.bold("OPTIONS:")}
1940
2001
  Run ${colors.cyan("forge env <subcommand> --help")} for details.
1941
2002
  `);
1942
2003
  }
2004
+ //#endregion
2005
+ //#region src/commands/firewall-rules/handlers.ts
1943
2006
  async function firewallRulesList(ctx) {
1944
2007
  const server = String(ctx.options.server ?? "");
1945
2008
  if (!server) exitWithValidationError("server_id", "forge firewall-rules list --server <server_id>", ctx.formatter);
@@ -1980,7 +2043,9 @@ async function firewallRulesGet(args, ctx) {
1980
2043
  ]);
1981
2044
  }, ctx.formatter);
1982
2045
  }
1983
- const handleFirewallRulesCommand = createCommandRouter({
2046
+ //#endregion
2047
+ //#region src/commands/firewall-rules/command.ts
2048
+ var handleFirewallRulesCommand = createCommandRouter({
1984
2049
  resource: "firewall-rules",
1985
2050
  handlers: {
1986
2051
  list: firewallRulesList,
@@ -1988,6 +2053,8 @@ const handleFirewallRulesCommand = createCommandRouter({
1988
2053
  get: [firewallRulesGet, "args"]
1989
2054
  }
1990
2055
  });
2056
+ //#endregion
2057
+ //#region src/commands/firewall-rules/help.ts
1991
2058
  function showFirewallRulesHelp(subcommand) {
1992
2059
  if (subcommand === "list" || subcommand === "ls") console.log(`
1993
2060
  ${colors.bold("forge firewall-rules list")} - List firewall rules on a server
@@ -2021,6 +2088,8 @@ ${colors.bold("OPTIONS:")}
2021
2088
  Run ${colors.cyan("forge firewall-rules <subcommand> --help")} for details.
2022
2089
  `);
2023
2090
  }
2091
+ //#endregion
2092
+ //#region src/commands/monitors/handlers.ts
2024
2093
  async function monitorsList(ctx) {
2025
2094
  const server = String(ctx.options.server ?? "");
2026
2095
  if (!server) exitWithValidationError("server_id", "forge monitors list --server <server_id>", ctx.formatter);
@@ -2106,7 +2175,9 @@ async function monitorsDelete(args, ctx) {
2106
2175
  ctx.formatter.success(`Monitor ${id} deleted.`);
2107
2176
  }, ctx.formatter);
2108
2177
  }
2109
- const handleMonitorsCommand = createCommandRouter({
2178
+ //#endregion
2179
+ //#region src/commands/monitors/command.ts
2180
+ var handleMonitorsCommand = createCommandRouter({
2110
2181
  resource: "monitors",
2111
2182
  handlers: {
2112
2183
  list: monitorsList,
@@ -2117,6 +2188,8 @@ const handleMonitorsCommand = createCommandRouter({
2117
2188
  },
2118
2189
  writeSubcommands: ["create", "delete"]
2119
2190
  });
2191
+ //#endregion
2192
+ //#region src/commands/monitors/help.ts
2120
2193
  function showMonitorsHelp(subcommand) {
2121
2194
  if (subcommand === "list" || subcommand === "ls") console.log(`
2122
2195
  ${colors.bold("forge monitors list")} - List monitors on a server
@@ -2173,6 +2246,8 @@ ${colors.bold("OPTIONS:")}
2173
2246
  Run ${colors.cyan("forge monitors <subcommand> --help")} for details.
2174
2247
  `);
2175
2248
  }
2249
+ //#endregion
2250
+ //#region src/commands/nginx/handlers.ts
2176
2251
  async function nginxGet(ctx) {
2177
2252
  const server = String(ctx.options.server ?? "");
2178
2253
  const site = String(ctx.options.site ?? "");
@@ -2209,7 +2284,9 @@ async function nginxUpdate(ctx) {
2209
2284
  ctx.formatter.success("Nginx configuration updated.");
2210
2285
  }, ctx.formatter);
2211
2286
  }
2212
- const handleNginxCommand = createCommandRouter({
2287
+ //#endregion
2288
+ //#region src/commands/nginx/command.ts
2289
+ var handleNginxCommand = createCommandRouter({
2213
2290
  resource: "nginx",
2214
2291
  handlers: {
2215
2292
  get: nginxGet,
@@ -2217,6 +2294,8 @@ const handleNginxCommand = createCommandRouter({
2217
2294
  },
2218
2295
  writeSubcommands: ["update"]
2219
2296
  });
2297
+ //#endregion
2298
+ //#region src/commands/nginx/help.ts
2220
2299
  function showNginxHelp(subcommand) {
2221
2300
  if (subcommand === "get") console.log(`
2222
2301
  ${colors.bold("forge nginx get")} - Get nginx configuration
@@ -2248,6 +2327,8 @@ ${colors.bold("OPTIONS:")}
2248
2327
  Run ${colors.cyan("forge nginx <subcommand> --help")} for details.
2249
2328
  `);
2250
2329
  }
2330
+ //#endregion
2331
+ //#region src/commands/nginx-templates/handlers.ts
2251
2332
  async function nginxTemplatesList(ctx) {
2252
2333
  const server = String(ctx.options.server ?? "");
2253
2334
  if (!server) exitWithValidationError("server_id", "forge nginx-templates list --server <server_id>", ctx.formatter);
@@ -2312,11 +2393,14 @@ async function nginxTemplatesUpdate(args, ctx) {
2312
2393
  await runCommand(async () => {
2313
2394
  const token = ctx.getToken();
2314
2395
  const execCtx = ctx.createExecutorContext(token);
2396
+ const server_id = await resolveServerId(server, execCtx);
2397
+ const name = ctx.options.name ? String(ctx.options.name) : void 0;
2398
+ const content = ctx.options.content ? String(ctx.options.content) : void 0;
2315
2399
  const result = await updateNginxTemplate({
2316
- server_id: await resolveServerId(server, execCtx),
2400
+ server_id,
2317
2401
  id,
2318
- name: ctx.options.name ? String(ctx.options.name) : void 0,
2319
- content: ctx.options.content ? String(ctx.options.content) : void 0
2402
+ name,
2403
+ content
2320
2404
  }, execCtx);
2321
2405
  ctx.formatter.outputOne(result.data, [
2322
2406
  "id",
@@ -2340,7 +2424,9 @@ async function nginxTemplatesDelete(args, ctx) {
2340
2424
  ctx.formatter.success(`Nginx template ${id} deleted.`);
2341
2425
  }, ctx.formatter);
2342
2426
  }
2343
- const handleNginxTemplatesCommand = createCommandRouter({
2427
+ //#endregion
2428
+ //#region src/commands/nginx-templates/command.ts
2429
+ var handleNginxTemplatesCommand = createCommandRouter({
2344
2430
  resource: "nginx-templates",
2345
2431
  handlers: {
2346
2432
  list: nginxTemplatesList,
@@ -2356,6 +2442,8 @@ const handleNginxTemplatesCommand = createCommandRouter({
2356
2442
  "delete"
2357
2443
  ]
2358
2444
  });
2445
+ //#endregion
2446
+ //#region src/commands/nginx-templates/help.ts
2359
2447
  function showNginxTemplatesHelp(subcommand) {
2360
2448
  if (subcommand === "list" || subcommand === "ls") console.log(`
2361
2449
  ${colors.bold("forge nginx-templates list")} - List Nginx templates on a server
@@ -2423,6 +2511,8 @@ ${colors.bold("OPTIONS:")}
2423
2511
  Run ${colors.cyan("forge nginx-templates <subcommand> --help")} for details.
2424
2512
  `);
2425
2513
  }
2514
+ //#endregion
2515
+ //#region src/commands/recipes/handlers.ts
2426
2516
  async function recipesList(ctx) {
2427
2517
  await runCommand(async () => {
2428
2518
  const token = ctx.getToken();
@@ -2491,7 +2581,9 @@ async function recipesRun(args, ctx) {
2491
2581
  ctx.formatter.success(`Recipe ${id} dispatched to ${servers.length} server(s).`);
2492
2582
  }, ctx.formatter);
2493
2583
  }
2494
- const handleRecipesCommand = createCommandRouter({
2584
+ //#endregion
2585
+ //#region src/commands/recipes/command.ts
2586
+ var handleRecipesCommand = createCommandRouter({
2495
2587
  resource: "recipes",
2496
2588
  handlers: {
2497
2589
  list: recipesList,
@@ -2502,6 +2594,8 @@ const handleRecipesCommand = createCommandRouter({
2502
2594
  },
2503
2595
  writeSubcommands: ["update", "run"]
2504
2596
  });
2597
+ //#endregion
2598
+ //#region src/commands/recipes/help.ts
2505
2599
  function showRecipesHelp(subcommand) {
2506
2600
  if (subcommand === "list" || subcommand === "ls") console.log(`
2507
2601
  ${colors.bold("forge recipes list")} - List all recipes
@@ -2553,6 +2647,8 @@ ${colors.bold("OPTIONS:")}
2553
2647
  Run ${colors.cyan("forge recipes <subcommand> --help")} for details.
2554
2648
  `);
2555
2649
  }
2650
+ //#endregion
2651
+ //#region src/commands/redirect-rules/handlers.ts
2556
2652
  function requireServerAndSiteRaw$1(ctx, usage) {
2557
2653
  const server = String(ctx.options.server ?? "");
2558
2654
  const site = String(ctx.options.site ?? "");
@@ -2643,7 +2739,9 @@ async function redirectRulesDelete(args, ctx) {
2643
2739
  ctx.formatter.success(`Redirect rule ${id} deleted.`);
2644
2740
  }, ctx.formatter);
2645
2741
  }
2646
- const handleRedirectRulesCommand = createCommandRouter({
2742
+ //#endregion
2743
+ //#region src/commands/redirect-rules/command.ts
2744
+ var handleRedirectRulesCommand = createCommandRouter({
2647
2745
  resource: "redirect-rules",
2648
2746
  handlers: {
2649
2747
  list: redirectRulesList,
@@ -2654,6 +2752,8 @@ const handleRedirectRulesCommand = createCommandRouter({
2654
2752
  },
2655
2753
  writeSubcommands: ["create", "delete"]
2656
2754
  });
2755
+ //#endregion
2756
+ //#region src/commands/redirect-rules/help.ts
2657
2757
  function showRedirectRulesHelp(subcommand) {
2658
2758
  if (subcommand === "list" || subcommand === "ls") console.log(`
2659
2759
  ${colors.bold("forge redirect-rules list")} - List redirect rules for a site
@@ -2711,6 +2811,8 @@ ${colors.bold("OPTIONS:")}
2711
2811
  Run ${colors.cyan("forge redirect-rules <subcommand> --help")} for details.
2712
2812
  `);
2713
2813
  }
2814
+ //#endregion
2815
+ //#region src/commands/scheduled-jobs/handlers.ts
2714
2816
  async function scheduledJobsList(ctx) {
2715
2817
  const server = String(ctx.options.server ?? "");
2716
2818
  const site = ctx.options.site ? String(ctx.options.site) : void 0;
@@ -2741,10 +2843,11 @@ async function scheduledJobsGet(args, ctx) {
2741
2843
  const token = ctx.getToken();
2742
2844
  const execCtx = ctx.createExecutorContext(token);
2743
2845
  const server_id = await resolveServerId(server, execCtx);
2846
+ const site_id = site ? await resolveSiteId(site, server_id, execCtx) : void 0;
2744
2847
  const result = await getScheduledJob({
2745
2848
  server_id,
2746
2849
  id,
2747
- site_id: site ? await resolveSiteId(site, server_id, execCtx) : void 0
2850
+ site_id
2748
2851
  }, execCtx);
2749
2852
  ctx.formatter.outputOne(result.data, [
2750
2853
  "id",
@@ -2799,15 +2902,18 @@ async function scheduledJobsDelete(args, ctx) {
2799
2902
  const token = ctx.getToken();
2800
2903
  const execCtx = ctx.createExecutorContext(token);
2801
2904
  const server_id = await resolveServerId(server, execCtx);
2905
+ const site_id = site ? await resolveSiteId(site, server_id, execCtx) : void 0;
2802
2906
  await deleteScheduledJob({
2803
2907
  server_id,
2804
2908
  id,
2805
- site_id: site ? await resolveSiteId(site, server_id, execCtx) : void 0
2909
+ site_id
2806
2910
  }, execCtx);
2807
2911
  ctx.formatter.success(`Scheduled job ${id} deleted.`);
2808
2912
  }, ctx.formatter);
2809
2913
  }
2810
- const handleScheduledJobsCommand = createCommandRouter({
2914
+ //#endregion
2915
+ //#region src/commands/scheduled-jobs/command.ts
2916
+ var handleScheduledJobsCommand = createCommandRouter({
2811
2917
  resource: "scheduled-jobs",
2812
2918
  handlers: {
2813
2919
  list: scheduledJobsList,
@@ -2818,6 +2924,8 @@ const handleScheduledJobsCommand = createCommandRouter({
2818
2924
  },
2819
2925
  writeSubcommands: ["create", "delete"]
2820
2926
  });
2927
+ //#endregion
2928
+ //#region src/commands/scheduled-jobs/help.ts
2821
2929
  function showScheduledJobsHelp(subcommand) {
2822
2930
  if (subcommand === "list" || subcommand === "ls") console.log(`
2823
2931
  ${colors.bold("forge scheduled-jobs list")} - List scheduled jobs on a server
@@ -2871,6 +2979,8 @@ ${colors.bold("OPTIONS:")}
2871
2979
  Run ${colors.cyan("forge scheduled-jobs <subcommand> --help")} for details.
2872
2980
  `);
2873
2981
  }
2982
+ //#endregion
2983
+ //#region src/commands/security-rules/handlers.ts
2874
2984
  function requireServerAndSiteRaw(ctx, usage) {
2875
2985
  const server = String(ctx.options.server ?? "");
2876
2986
  const site = String(ctx.options.site ?? "");
@@ -2986,7 +3096,9 @@ async function securityRulesDelete(args, ctx) {
2986
3096
  ctx.formatter.success(`Security rule ${id} deleted.`);
2987
3097
  }, ctx.formatter);
2988
3098
  }
2989
- const handleSecurityRulesCommand = createCommandRouter({
3099
+ //#endregion
3100
+ //#region src/commands/security-rules/command.ts
3101
+ var handleSecurityRulesCommand = createCommandRouter({
2990
3102
  resource: "security-rules",
2991
3103
  handlers: {
2992
3104
  list: securityRulesList,
@@ -3002,6 +3114,8 @@ const handleSecurityRulesCommand = createCommandRouter({
3002
3114
  "delete"
3003
3115
  ]
3004
3116
  });
3117
+ //#endregion
3118
+ //#region src/commands/security-rules/help.ts
3005
3119
  function showSecurityRulesHelp(subcommand) {
3006
3120
  if (subcommand === "list" || subcommand === "ls") console.log(`
3007
3121
  ${colors.bold("forge security-rules list")} - List security rules for a site
@@ -3071,6 +3185,8 @@ ${colors.bold("OPTIONS:")}
3071
3185
  Run ${colors.cyan("forge security-rules <subcommand> --help")} for details.
3072
3186
  `);
3073
3187
  }
3188
+ //#endregion
3189
+ //#region src/commands/servers/handlers.ts
3074
3190
  /**
3075
3191
  * CLI handlers for servers commands.
3076
3192
  * Delegates all HTTP logic to forge-core executors.
@@ -3120,7 +3236,9 @@ async function serversReboot(args, ctx) {
3120
3236
  ctx.formatter.success(`Server ${server_id} reboot initiated.`);
3121
3237
  }, ctx.formatter);
3122
3238
  }
3123
- const handleServersCommand = createCommandRouter({
3239
+ //#endregion
3240
+ //#region src/commands/servers/command.ts
3241
+ var handleServersCommand = createCommandRouter({
3124
3242
  resource: "servers",
3125
3243
  handlers: {
3126
3244
  list: serversList,
@@ -3130,6 +3248,8 @@ const handleServersCommand = createCommandRouter({
3130
3248
  },
3131
3249
  writeSubcommands: ["reboot"]
3132
3250
  });
3251
+ //#endregion
3252
+ //#region src/commands/servers/help.ts
3133
3253
  function showServersHelp(subcommand) {
3134
3254
  if (subcommand === "list" || subcommand === "ls") console.log(`
3135
3255
  ${colors.bold("forge servers list")} - List all servers
@@ -3200,6 +3320,8 @@ ${colors.bold("EXAMPLES:")}
3200
3320
  Run ${colors.cyan("forge servers <subcommand> --help")} for subcommand details.
3201
3321
  `);
3202
3322
  }
3323
+ //#endregion
3324
+ //#region src/commands/sites/handlers.ts
3203
3325
  async function sitesList(ctx) {
3204
3326
  const server = String(ctx.options.server ?? "");
3205
3327
  if (!server) exitWithValidationError("server_id", "forge sites list --server <server_id>", ctx.formatter);
@@ -3275,7 +3397,9 @@ async function sitesUpdate(args, ctx) {
3275
3397
  ]);
3276
3398
  }, ctx.formatter);
3277
3399
  }
3278
- const handleSitesCommand = createCommandRouter({
3400
+ //#endregion
3401
+ //#region src/commands/sites/command.ts
3402
+ var handleSitesCommand = createCommandRouter({
3279
3403
  resource: "sites",
3280
3404
  handlers: {
3281
3405
  list: sitesList,
@@ -3284,6 +3408,8 @@ const handleSitesCommand = createCommandRouter({
3284
3408
  update: [sitesUpdate, "args"]
3285
3409
  }
3286
3410
  });
3411
+ //#endregion
3412
+ //#region src/commands/sites/help.ts
3287
3413
  function showSitesHelp(subcommand) {
3288
3414
  if (subcommand === "list" || subcommand === "ls") console.log(`
3289
3415
  ${colors.bold("forge sites list")} - List sites on a server
@@ -3334,6 +3460,172 @@ ${colors.bold("EXAMPLES:")}
3334
3460
  Run ${colors.cyan("forge sites <subcommand> --help")} for subcommand details.
3335
3461
  `);
3336
3462
  }
3463
+ /**
3464
+ * Resolve the concrete user/host/port to connect to.
3465
+ *
3466
+ * @throws {ValidationError} when the selected IP is not available yet, or the port override is invalid.
3467
+ */
3468
+ function resolveSshTarget(server, options = {}) {
3469
+ const user = options.user?.trim() || "forge";
3470
+ const host = options.private ? server.private_ip_address : server.ip_address;
3471
+ if (!host) {
3472
+ const which = options.private ? "private IP address" : "IP address";
3473
+ const hints = [server.is_ready ? "The server is ready but no address is available — check the Forge dashboard." : "The server is still provisioning. Wait until it is ready, then try again.", options.private ? void 0 : "Use --private to connect over the private network."].filter((hint) => Boolean(hint));
3474
+ throw new ValidationError(`Server "${server.name}" has no ${which} available`, "server", hints);
3475
+ }
3476
+ let port = server.ssh_port;
3477
+ if (options.port !== void 0) {
3478
+ const parsed = Number(options.port);
3479
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) throw new ValidationError(`Invalid SSH port "${options.port}"`, "port", ["Port must be an integer between 1 and 65535."]);
3480
+ port = parsed;
3481
+ }
3482
+ return {
3483
+ user,
3484
+ host,
3485
+ port
3486
+ };
3487
+ }
3488
+ /**
3489
+ * Build the argument list passed to the `ssh` binary.
3490
+ *
3491
+ * Any {@link remoteCommand} is appended after the connection flags, so
3492
+ * `forge ssh web uptime` runs `uptime` remotely and exits.
3493
+ */
3494
+ function buildSshArgs(target, remoteCommand = []) {
3495
+ const args = [
3496
+ `${target.user}@${target.host}`,
3497
+ "-p",
3498
+ String(target.port)
3499
+ ];
3500
+ if (remoteCommand.length > 0) args.push(...remoteCommand);
3501
+ return args;
3502
+ }
3503
+ //#endregion
3504
+ //#region src/commands/ssh/handlers.ts
3505
+ /**
3506
+ * CLI handler for `forge ssh` — open an interactive SSH session to a server.
3507
+ *
3508
+ * Resolves the server, reads its connection details from the API, then hands
3509
+ * off to the local `ssh` binary with an inherited stdio (interactive) session.
3510
+ */
3511
+ /**
3512
+ * Connect to a server over SSH.
3513
+ *
3514
+ * @param server - Server ID or name (resolved via {@link resolveServerId}).
3515
+ * @param remote - Optional remote command to run instead of an interactive shell.
3516
+ * @param ctx - CLI command context.
3517
+ */
3518
+ async function sshConnect(server, remote, ctx) {
3519
+ if (!server) exitWithValidationError("server", "forge ssh <server> [command...]", ctx.formatter);
3520
+ await runCommand(async () => {
3521
+ const token = ctx.getToken();
3522
+ const execCtx = ctx.createExecutorContext(token);
3523
+ const server_id = await resolveServerId(server, execCtx);
3524
+ const { data } = await getServer({ server_id }, execCtx);
3525
+ const target = resolveSshTarget(data, {
3526
+ user: typeof ctx.options.user === "string" ? ctx.options.user : void 0,
3527
+ private: ctx.options.private === true,
3528
+ port: typeof ctx.options.port === "string" ? ctx.options.port : void 0
3529
+ });
3530
+ const sshArgs = buildSshArgs(target, remote);
3531
+ const printable = `ssh ${sshArgs.join(" ")}`;
3532
+ if (ctx.formatter.isJson()) {
3533
+ ctx.formatter.output({
3534
+ command: printable,
3535
+ ...target,
3536
+ args: sshArgs
3537
+ });
3538
+ return;
3539
+ }
3540
+ if (ctx.options["dry-run"] === true) {
3541
+ console.log(printable);
3542
+ return;
3543
+ }
3544
+ const auditArgs = {
3545
+ server: server_id,
3546
+ user: target.user,
3547
+ host: target.host,
3548
+ port: target.port
3549
+ };
3550
+ try {
3551
+ const child = spawnSync("ssh", sshArgs, { stdio: "inherit" });
3552
+ if (child.error) throw child.error;
3553
+ getCliAuditLogger().log({
3554
+ source: "cli",
3555
+ resource: "ssh",
3556
+ action: "connect",
3557
+ args: auditArgs,
3558
+ status: "success"
3559
+ });
3560
+ if (typeof child.status === "number" && child.status !== 0) process.exit(child.status);
3561
+ } catch (err) {
3562
+ getCliAuditLogger().log({
3563
+ source: "cli",
3564
+ resource: "ssh",
3565
+ action: "connect",
3566
+ args: auditArgs,
3567
+ status: "error",
3568
+ error: err instanceof Error ? err.message : String(err)
3569
+ });
3570
+ throw err;
3571
+ }
3572
+ }, ctx.formatter);
3573
+ }
3574
+ //#endregion
3575
+ //#region src/commands/ssh/command.ts
3576
+ /**
3577
+ * Route the top-level `forge ssh` command.
3578
+ *
3579
+ * Unlike resource commands there is no sub-action: the first positional is the
3580
+ * server, and any remaining positionals form an optional remote command.
3581
+ */
3582
+ async function handleSshCommand(server, remote, options) {
3583
+ await sshConnect(server, remote, createContext(options));
3584
+ }
3585
+ //#endregion
3586
+ //#region src/commands/ssh/help.ts
3587
+ function showSshHelp() {
3588
+ console.log(`
3589
+ ${colors.bold("forge ssh")} - Open an SSH session to a server
3590
+
3591
+ ${colors.bold("USAGE:")}
3592
+ forge ssh <server> [command...] [options]
3593
+
3594
+ ${colors.bold("ARGUMENTS:")}
3595
+ <server> Server ID or name (required)
3596
+ [command...] Optional remote command to run, then exit
3597
+
3598
+ ${colors.bold("OPTIONS:")}
3599
+ --user <name> SSH user (default: forge)
3600
+ --private Connect over the private IP address
3601
+ --port <n> Override the server's SSH port
3602
+ --dry-run Print the ssh command instead of connecting
3603
+ -f, --format json Print the resolved connection details as JSON
3604
+ -h, --help Show this help
3605
+
3606
+ ${colors.bold("EXAMPLES:")}
3607
+ # Interactive session (connects as forge@<ip>)
3608
+ forge ssh my-server
3609
+
3610
+ # Connect as a custom user
3611
+ forge ssh my-server --user deploy
3612
+
3613
+ # Use the private network
3614
+ forge ssh my-server --private
3615
+
3616
+ # Run a one-off remote command
3617
+ forge ssh my-server uptime
3618
+
3619
+ # Just show the command that would run
3620
+ forge ssh my-server --dry-run
3621
+
3622
+ ${colors.bold("NOTES:")}
3623
+ Requires your local SSH key to be authorized on the server; this command
3624
+ launches ssh, it does not upload keys.
3625
+ `);
3626
+ }
3627
+ //#endregion
3628
+ //#region src/commands/ssh-keys/handlers.ts
3337
3629
  async function sshKeysList(ctx) {
3338
3630
  const server = String(ctx.options.server ?? "");
3339
3631
  if (!server) exitWithValidationError("server_id", "forge ssh-keys list --server <server_id>", ctx.formatter);
@@ -3368,7 +3660,9 @@ async function sshKeysGet(args, ctx) {
3368
3660
  ]);
3369
3661
  }, ctx.formatter);
3370
3662
  }
3371
- const handleSshKeysCommand = createCommandRouter({
3663
+ //#endregion
3664
+ //#region src/commands/ssh-keys/command.ts
3665
+ var handleSshKeysCommand = createCommandRouter({
3372
3666
  resource: "ssh-keys",
3373
3667
  handlers: {
3374
3668
  list: sshKeysList,
@@ -3376,6 +3670,8 @@ const handleSshKeysCommand = createCommandRouter({
3376
3670
  get: [sshKeysGet, "args"]
3377
3671
  }
3378
3672
  });
3673
+ //#endregion
3674
+ //#region src/commands/ssh-keys/help.ts
3379
3675
  function showSshKeysHelp(subcommand) {
3380
3676
  if (subcommand === "list" || subcommand === "ls") console.log(`
3381
3677
  ${colors.bold("forge ssh-keys list")} - List SSH keys on a server
@@ -3406,6 +3702,8 @@ ${colors.bold("OPTIONS:")}
3406
3702
  Run ${colors.cyan("forge ssh-keys <subcommand> --help")} for details.
3407
3703
  `);
3408
3704
  }
3705
+ //#endregion
3706
+ //#region src/commands/user/handlers.ts
3409
3707
  async function userGet(ctx) {
3410
3708
  await runCommand(async () => {
3411
3709
  const token = ctx.getToken();
@@ -3413,10 +3711,14 @@ async function userGet(ctx) {
3413
3711
  ctx.formatter.output(result.data);
3414
3712
  }, ctx.formatter);
3415
3713
  }
3416
- const handleUserCommand = createCommandRouter({
3714
+ //#endregion
3715
+ //#region src/commands/user/command.ts
3716
+ var handleUserCommand = createCommandRouter({
3417
3717
  resource: "user",
3418
3718
  handlers: { get: userGet }
3419
3719
  });
3720
+ //#endregion
3721
+ //#region src/commands/user/help.ts
3420
3722
  function showUserHelp(subcommand) {
3421
3723
  if (subcommand === "get") console.log(`
3422
3724
  ${colors.bold("forge user get")} - Get the authenticated user profile
@@ -3442,6 +3744,8 @@ ${colors.bold("OPTIONS:")}
3442
3744
  Run ${colors.cyan("forge user <subcommand> --help")} for details.
3443
3745
  `);
3444
3746
  }
3747
+ //#endregion
3748
+ //#region src/utils/args.ts
3445
3749
  function parseArgs(argv = process.argv.slice(2)) {
3446
3750
  const options = {};
3447
3751
  const positional = [];
@@ -3478,7 +3782,9 @@ function parseArgs(argv = process.argv.slice(2)) {
3478
3782
  positional
3479
3783
  };
3480
3784
  }
3481
- var VERSION = "0.4.3";
3785
+ //#endregion
3786
+ //#region src/cli.ts
3787
+ var VERSION = "0.4.5";
3482
3788
  function showHelp() {
3483
3789
  console.log(`
3484
3790
  ${colors.bold("forge")} v${VERSION}
@@ -3537,6 +3843,9 @@ ${colors.bold("COMMANDS:")}
3537
3843
  list, ls List firewall rules (requires --server)
3538
3844
  get <id> Get firewall rule details (requires --server)
3539
3845
 
3846
+ ssh <server> Open an SSH session to a server
3847
+ --user <name> (default: forge), --private, --port <n>
3848
+
3540
3849
  ssh-keys Manage SSH keys
3541
3850
  list, ls List SSH keys (requires --server)
3542
3851
  get <id> Get SSH key details (requires --server)
@@ -3739,6 +4048,13 @@ async function main() {
3739
4048
  }
3740
4049
  await handleFirewallRulesCommand(subcommand ?? "list", positional, options);
3741
4050
  break;
4051
+ case "ssh":
4052
+ if (wantsHelp) {
4053
+ showSshHelp();
4054
+ process.exit(0);
4055
+ }
4056
+ await handleSshCommand(subcommand, positional, options);
4057
+ break;
3742
4058
  case "ssh-keys":
3743
4059
  if (wantsHelp) {
3744
4060
  showSshKeysHelp(subcommand);
@@ -3830,5 +4146,6 @@ main().catch((error) => {
3830
4146
  console.error(colors.red("Unhandled error:"), error);
3831
4147
  process.exit(1);
3832
4148
  });
4149
+ //#endregion
3833
4150
 
3834
4151
  //# sourceMappingURL=cli.js.map