@studiometa/forge-cli 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,6 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as createContext, c as colors, d as deleteToken, i as runCommand, l as setColorEnabled, m as setToken, n as exitWithValidationError, p as getToken, s as OutputFormatter, u as createConfigStore, v as ValidationError } from "./error-handler-BahHMt6t.js";
3
- import { activateCertificate, createBackupConfig, createCommand, createDatabaseUser, createMonitor, createNginxTemplate, createRedirectRule, createScheduledJob, createSecurityRule, deleteBackupConfig, deleteDatabaseUser, deleteMonitor, deleteNginxTemplate, deleteRedirectRule, deleteScheduledJob, deleteSecurityRule, deploySite, getBackupConfig, getCertificate, getCommand, getDaemon, getDatabase, getDatabaseUser, getEnv, getFirewallRule, getMonitor, getNginxConfig, getNginxTemplate, getRecipe, getRedirectRule, getScheduledJob, getSecurityRule, getServer, getSite, getSshKey, getUser, listBackupConfigs, listCertificates, listCommands, listDaemons, listDatabaseUsers, listDatabases, listDeployments, listFirewallRules, listMonitors, listNginxTemplates, listRecipes, listRedirectRules, listScheduledJobs, listSecurityRules, listServers, listSites, listSshKeys, rebootServer, restartDaemon, runRecipe, updateEnv, updateNginxConfig, updateNginxTemplate } from "@studiometa/forge-core";
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, listCertificates, listCommands, listDaemons, listDatabaseUsers, listDatabases, listDeployments, listFirewallRules, listMonitors, listNginxTemplates, listRecipes, listRedirectRules, listScheduledJobs, listSecurityRules, listServers, listSites, listSshKeys, rebootServer, restartDaemon, runRecipe, updateEnv, updateNginxConfig, updateNginxTemplate } from "@studiometa/forge-core";
4
+ import { mkdirSync, writeFileSync } from "node:fs";
5
+ import { homedir } from "node:os";
6
+ import { join } from "node:path";
7
+ /**
8
+ * Shared audit logger for CLI write operations.
9
+ * Lazily initialized on first use.
10
+ */
11
+ var _auditLogger = null;
12
+ function getCliAuditLogger() {
13
+ if (!_auditLogger) _auditLogger = createAuditLogger("cli");
14
+ return _auditLogger;
15
+ }
4
16
  /**
5
17
  * Creates a command router function that dispatches to the appropriate handler.
6
18
  */
@@ -14,7 +26,31 @@ function createCommandRouter(config) {
14
26
  process.exit(1);
15
27
  return;
16
28
  }
17
- if (Array.isArray(handler)) await handler[0](args, ctx);
29
+ if (config.writeSubcommands?.includes(subcommand) ?? false) {
30
+ const { token: _token, ...safeOptions } = options;
31
+ try {
32
+ if (Array.isArray(handler)) await handler[0](args, ctx);
33
+ else await handler(ctx);
34
+ getCliAuditLogger().log({
35
+ source: "cli",
36
+ resource: config.resource,
37
+ action: subcommand,
38
+ args: safeOptions,
39
+ status: "success"
40
+ });
41
+ } catch (err) {
42
+ const errorMessage = err instanceof Error ? err.message : String(err);
43
+ getCliAuditLogger().log({
44
+ source: "cli",
45
+ resource: config.resource,
46
+ action: subcommand,
47
+ args: safeOptions,
48
+ status: "error",
49
+ error: errorMessage
50
+ });
51
+ throw err;
52
+ }
53
+ } else if (Array.isArray(handler)) await handler[0](args, ctx);
18
54
  else await handler(ctx);
19
55
  };
20
56
  }
@@ -163,7 +199,8 @@ const handleBackupsCommand = createCommandRouter({
163
199
  get: [backupsGet, "args"],
164
200
  create: backupsCreate,
165
201
  delete: [backupsDelete, "args"]
166
- }
202
+ },
203
+ writeSubcommands: ["create", "delete"]
167
204
  });
168
205
  function showBackupsHelp(subcommand) {
169
206
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -220,6 +257,753 @@ ${colors.bold("OPTIONS:")}
220
257
  Run ${colors.cyan("forge-cli backups <subcommand> --help")} for details.
221
258
  `);
222
259
  }
260
+ /**
261
+ * Shell completion installation command
262
+ */
263
+ var BASH_COMPLETION = `#!/usr/bin/env bash
264
+
265
+ _forge_cli_completions() {
266
+ local cur prev commands subcommands options
267
+
268
+ COMPREPLY=()
269
+ cur="\${COMP_WORDS[COMP_CWORD]}"
270
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
271
+
272
+ # Main commands
273
+ 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"
274
+
275
+ # Subcommands for each command
276
+ local config_cmds="set get delete"
277
+ local servers_cmds="list ls get reboot"
278
+ local sites_cmds="list ls get"
279
+ local deployments_cmds="list ls deploy"
280
+ local databases_cmds="list ls get"
281
+ local database_users_cmds="list ls get create delete"
282
+ local daemons_cmds="list ls get restart"
283
+ local env_cmds="get update"
284
+ local nginx_cmds="get update"
285
+ local certificates_cmds="list ls get activate"
286
+ local firewall_rules_cmds="list ls get"
287
+ local ssh_keys_cmds="list ls get"
288
+ local backups_cmds="list ls get create delete"
289
+ local commands_cmds="list ls get create"
290
+ local scheduled_jobs_cmds="list ls get create delete"
291
+ local user_cmds="get"
292
+ local monitors_cmds="list ls get create delete"
293
+ local nginx_templates_cmds="list ls get create update delete"
294
+ local security_rules_cmds="list ls get create delete"
295
+ local redirect_rules_cmds="list ls get create delete"
296
+ local recipes_cmds="list ls get run"
297
+ local completion_cmds="bash zsh fish"
298
+
299
+ # Global options
300
+ 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"
301
+
302
+ # Format options
303
+ local formats="json human table"
304
+
305
+ # Completion logic
306
+ case "\${COMP_CWORD}" in
307
+ 1)
308
+ # Complete main commands
309
+ COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
310
+ ;;
311
+ 2)
312
+ # Complete subcommands based on main command
313
+ case "\${prev}" in
314
+ config)
315
+ COMPREPLY=( $(compgen -W "\${config_cmds}" -- "\${cur}") )
316
+ ;;
317
+ servers|s)
318
+ COMPREPLY=( $(compgen -W "\${servers_cmds}" -- "\${cur}") )
319
+ ;;
320
+ sites)
321
+ COMPREPLY=( $(compgen -W "\${sites_cmds}" -- "\${cur}") )
322
+ ;;
323
+ deployments|d)
324
+ COMPREPLY=( $(compgen -W "\${deployments_cmds}" -- "\${cur}") )
325
+ ;;
326
+ databases|db)
327
+ COMPREPLY=( $(compgen -W "\${databases_cmds}" -- "\${cur}") )
328
+ ;;
329
+ database-users)
330
+ COMPREPLY=( $(compgen -W "\${database_users_cmds}" -- "\${cur}") )
331
+ ;;
332
+ daemons)
333
+ COMPREPLY=( $(compgen -W "\${daemons_cmds}" -- "\${cur}") )
334
+ ;;
335
+ env)
336
+ COMPREPLY=( $(compgen -W "\${env_cmds}" -- "\${cur}") )
337
+ ;;
338
+ nginx)
339
+ COMPREPLY=( $(compgen -W "\${nginx_cmds}" -- "\${cur}") )
340
+ ;;
341
+ certificates|certs)
342
+ COMPREPLY=( $(compgen -W "\${certificates_cmds}" -- "\${cur}") )
343
+ ;;
344
+ firewall-rules|fw)
345
+ COMPREPLY=( $(compgen -W "\${firewall_rules_cmds}" -- "\${cur}") )
346
+ ;;
347
+ ssh-keys)
348
+ COMPREPLY=( $(compgen -W "\${ssh_keys_cmds}" -- "\${cur}") )
349
+ ;;
350
+ backups)
351
+ COMPREPLY=( $(compgen -W "\${backups_cmds}" -- "\${cur}") )
352
+ ;;
353
+ commands)
354
+ COMPREPLY=( $(compgen -W "\${commands_cmds}" -- "\${cur}") )
355
+ ;;
356
+ scheduled-jobs)
357
+ COMPREPLY=( $(compgen -W "\${scheduled_jobs_cmds}" -- "\${cur}") )
358
+ ;;
359
+ user)
360
+ COMPREPLY=( $(compgen -W "\${user_cmds}" -- "\${cur}") )
361
+ ;;
362
+ monitors)
363
+ COMPREPLY=( $(compgen -W "\${monitors_cmds}" -- "\${cur}") )
364
+ ;;
365
+ nginx-templates)
366
+ COMPREPLY=( $(compgen -W "\${nginx_templates_cmds}" -- "\${cur}") )
367
+ ;;
368
+ security-rules)
369
+ COMPREPLY=( $(compgen -W "\${security_rules_cmds}" -- "\${cur}") )
370
+ ;;
371
+ redirect-rules)
372
+ COMPREPLY=( $(compgen -W "\${redirect_rules_cmds}" -- "\${cur}") )
373
+ ;;
374
+ recipes)
375
+ COMPREPLY=( $(compgen -W "\${recipes_cmds}" -- "\${cur}") )
376
+ ;;
377
+ completion)
378
+ COMPREPLY=( $(compgen -W "\${completion_cmds}" -- "\${cur}") )
379
+ ;;
380
+ *)
381
+ COMPREPLY=( $(compgen -W "\${options}" -- "\${cur}") )
382
+ ;;
383
+ esac
384
+ ;;
385
+ *)
386
+ # Complete options and their values
387
+ case "\${prev}" in
388
+ --format|-f)
389
+ COMPREPLY=( $(compgen -W "\${formats}" -- "\${cur}") )
390
+ ;;
391
+ --token|--server|--site|--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)
392
+ # No completion for values requiring arguments
393
+ COMPREPLY=()
394
+ ;;
395
+ *)
396
+ COMPREPLY=( $(compgen -W "\${options}" -- "\${cur}") )
397
+ ;;
398
+ esac
399
+ ;;
400
+ esac
401
+
402
+ return 0
403
+ }
404
+
405
+ complete -F _forge_cli_completions forge-cli
406
+ `;
407
+ var ZSH_COMPLETION = `#compdef forge-cli
408
+
409
+ _forge_cli() {
410
+ local line state
411
+
412
+ _arguments -C \\
413
+ '1: :->command' \\
414
+ '2: :->subcommand' \\
415
+ '*: :->args'
416
+
417
+ case $state in
418
+ command)
419
+ local -a commands
420
+ commands=(
421
+ 'config:Manage CLI configuration'
422
+ 'servers:Manage servers'
423
+ 's:Manage servers (alias)'
424
+ 'sites:Manage sites'
425
+ 'deployments:Manage deployments'
426
+ 'd:Manage deployments (alias)'
427
+ 'databases:Manage databases'
428
+ 'db:Manage databases (alias)'
429
+ 'database-users:Manage database users'
430
+ 'daemons:Manage daemons'
431
+ 'env:Manage environment variables'
432
+ 'nginx:Manage nginx configuration'
433
+ 'certificates:Manage SSL certificates'
434
+ 'certs:Manage SSL certificates (alias)'
435
+ 'firewall-rules:Manage firewall rules'
436
+ 'fw:Manage firewall rules (alias)'
437
+ 'ssh-keys:Manage SSH keys'
438
+ 'backups:Manage backup configurations'
439
+ 'commands:Manage site commands'
440
+ 'scheduled-jobs:Manage scheduled jobs'
441
+ 'user:Display authenticated user profile'
442
+ 'monitors:Manage server monitors'
443
+ 'nginx-templates:Manage Nginx templates'
444
+ 'security-rules:Manage site security rules'
445
+ 'redirect-rules:Manage site redirect rules'
446
+ 'recipes:Manage recipes'
447
+ 'completion:Generate shell completion script'
448
+ 'help:Show help'
449
+ )
450
+ _describe 'command' commands
451
+ ;;
452
+ subcommand)
453
+ case $line[1] in
454
+ config)
455
+ local -a config_cmds
456
+ config_cmds=(
457
+ 'set:Save API token'
458
+ 'get:Show current token'
459
+ 'delete:Delete stored token'
460
+ )
461
+ _describe 'config command' config_cmds
462
+ ;;
463
+ servers|s)
464
+ local -a servers_cmds
465
+ servers_cmds=(
466
+ 'list:List all servers'
467
+ 'ls:List all servers (alias)'
468
+ 'get:Get server details'
469
+ 'reboot:Reboot a server'
470
+ )
471
+ _describe 'servers command' servers_cmds
472
+ ;;
473
+ sites)
474
+ local -a sites_cmds
475
+ sites_cmds=(
476
+ 'list:List sites'
477
+ 'ls:List sites (alias)'
478
+ 'get:Get site details'
479
+ )
480
+ _describe 'sites command' sites_cmds
481
+ ;;
482
+ deployments|d)
483
+ local -a deployments_cmds
484
+ deployments_cmds=(
485
+ 'list:List deployments'
486
+ 'ls:List deployments (alias)'
487
+ 'deploy:Trigger a deployment'
488
+ )
489
+ _describe 'deployments command' deployments_cmds
490
+ ;;
491
+ databases|db)
492
+ local -a databases_cmds
493
+ databases_cmds=(
494
+ 'list:List databases'
495
+ 'ls:List databases (alias)'
496
+ 'get:Get database details'
497
+ )
498
+ _describe 'databases command' databases_cmds
499
+ ;;
500
+ database-users)
501
+ local -a database_users_cmds
502
+ database_users_cmds=(
503
+ 'list:List database users'
504
+ 'ls:List database users (alias)'
505
+ 'get:Get database user details'
506
+ 'create:Create a database user'
507
+ 'delete:Delete a database user'
508
+ )
509
+ _describe 'database-users command' database_users_cmds
510
+ ;;
511
+ daemons)
512
+ local -a daemons_cmds
513
+ daemons_cmds=(
514
+ 'list:List daemons'
515
+ 'ls:List daemons (alias)'
516
+ 'get:Get daemon details'
517
+ 'restart:Restart a daemon'
518
+ )
519
+ _describe 'daemons command' daemons_cmds
520
+ ;;
521
+ env)
522
+ local -a env_cmds
523
+ env_cmds=(
524
+ 'get:Get env variables'
525
+ 'update:Update env variables'
526
+ )
527
+ _describe 'env command' env_cmds
528
+ ;;
529
+ nginx)
530
+ local -a nginx_cmds
531
+ nginx_cmds=(
532
+ 'get:Get nginx config'
533
+ 'update:Update nginx config'
534
+ )
535
+ _describe 'nginx command' nginx_cmds
536
+ ;;
537
+ certificates|certs)
538
+ local -a certificates_cmds
539
+ certificates_cmds=(
540
+ 'list:List certificates'
541
+ 'ls:List certificates (alias)'
542
+ 'get:Get certificate details'
543
+ 'activate:Activate a certificate'
544
+ )
545
+ _describe 'certificates command' certificates_cmds
546
+ ;;
547
+ firewall-rules|fw)
548
+ local -a firewall_rules_cmds
549
+ firewall_rules_cmds=(
550
+ 'list:List firewall rules'
551
+ 'ls:List firewall rules (alias)'
552
+ 'get:Get firewall rule details'
553
+ )
554
+ _describe 'firewall-rules command' firewall_rules_cmds
555
+ ;;
556
+ ssh-keys)
557
+ local -a ssh_keys_cmds
558
+ ssh_keys_cmds=(
559
+ 'list:List SSH keys'
560
+ 'ls:List SSH keys (alias)'
561
+ 'get:Get SSH key details'
562
+ )
563
+ _describe 'ssh-keys command' ssh_keys_cmds
564
+ ;;
565
+ backups)
566
+ local -a backups_cmds
567
+ backups_cmds=(
568
+ 'list:List backup configurations'
569
+ 'ls:List backup configurations (alias)'
570
+ 'get:Get backup configuration details'
571
+ 'create:Create a backup configuration'
572
+ 'delete:Delete a backup configuration'
573
+ )
574
+ _describe 'backups command' backups_cmds
575
+ ;;
576
+ commands)
577
+ local -a commands_cmds
578
+ commands_cmds=(
579
+ 'list:List commands'
580
+ 'ls:List commands (alias)'
581
+ 'get:Get command details'
582
+ 'create:Execute a command'
583
+ )
584
+ _describe 'commands command' commands_cmds
585
+ ;;
586
+ scheduled-jobs)
587
+ local -a scheduled_jobs_cmds
588
+ scheduled_jobs_cmds=(
589
+ 'list:List scheduled jobs'
590
+ 'ls:List scheduled jobs (alias)'
591
+ 'get:Get scheduled job details'
592
+ 'create:Create a scheduled job'
593
+ 'delete:Delete a scheduled job'
594
+ )
595
+ _describe 'scheduled-jobs command' scheduled_jobs_cmds
596
+ ;;
597
+ user)
598
+ local -a user_cmds
599
+ user_cmds=(
600
+ 'get:Get the authenticated user profile'
601
+ )
602
+ _describe 'user command' user_cmds
603
+ ;;
604
+ monitors)
605
+ local -a monitors_cmds
606
+ monitors_cmds=(
607
+ 'list:List monitors'
608
+ 'ls:List monitors (alias)'
609
+ 'get:Get monitor details'
610
+ 'create:Create a monitor'
611
+ 'delete:Delete a monitor'
612
+ )
613
+ _describe 'monitors command' monitors_cmds
614
+ ;;
615
+ nginx-templates)
616
+ local -a nginx_templates_cmds
617
+ nginx_templates_cmds=(
618
+ 'list:List Nginx templates'
619
+ 'ls:List Nginx templates (alias)'
620
+ 'get:Get Nginx template details'
621
+ 'create:Create a Nginx template'
622
+ 'update:Update a Nginx template'
623
+ 'delete:Delete a Nginx template'
624
+ )
625
+ _describe 'nginx-templates command' nginx_templates_cmds
626
+ ;;
627
+ security-rules)
628
+ local -a security_rules_cmds
629
+ security_rules_cmds=(
630
+ 'list:List security rules'
631
+ 'ls:List security rules (alias)'
632
+ 'get:Get security rule details'
633
+ 'create:Create a security rule'
634
+ 'delete:Delete a security rule'
635
+ )
636
+ _describe 'security-rules command' security_rules_cmds
637
+ ;;
638
+ redirect-rules)
639
+ local -a redirect_rules_cmds
640
+ redirect_rules_cmds=(
641
+ 'list:List redirect rules'
642
+ 'ls:List redirect rules (alias)'
643
+ 'get:Get redirect rule details'
644
+ 'create:Create a redirect rule'
645
+ 'delete:Delete a redirect rule'
646
+ )
647
+ _describe 'redirect-rules command' redirect_rules_cmds
648
+ ;;
649
+ recipes)
650
+ local -a recipes_cmds
651
+ recipes_cmds=(
652
+ 'list:List all recipes'
653
+ 'ls:List all recipes (alias)'
654
+ 'get:Get recipe details'
655
+ 'run:Run a recipe'
656
+ )
657
+ _describe 'recipes command' recipes_cmds
658
+ ;;
659
+ completion)
660
+ local -a completion_cmds
661
+ completion_cmds=(
662
+ 'bash:Generate Bash completion script'
663
+ 'zsh:Generate Zsh completion script'
664
+ 'fish:Generate Fish completion script'
665
+ )
666
+ _describe 'completion shell' completion_cmds
667
+ ;;
668
+ esac
669
+ ;;
670
+ args)
671
+ _arguments \\
672
+ '--token[API token]:token:' \\
673
+ '--server[Server ID]:server id:' \\
674
+ '--site[Site ID]:site id:' \\
675
+ '(-f --format)'{-f,--format}'[Output format]:format:(json human table)' \\
676
+ '--no-color[Disable colored output]' \\
677
+ '(-h --help)'{-h,--help}'[Show help]' \\
678
+ '(-v --version)'{-v,--version}'[Show version]' \\
679
+ '--name[Name]:name:' \\
680
+ '--command[Command]:command:' \\
681
+ '--content[Content]:content:' \\
682
+ '--from[From]:from:' \\
683
+ '--to[To]:to:' \\
684
+ '--provider[Provider]:provider:' \\
685
+ '--frequency[Frequency]:frequency:' \\
686
+ '--type[Type]:type:' \\
687
+ '--operator[Operator]:operator:' \\
688
+ '--threshold[Threshold]:threshold:' \\
689
+ '--minutes[Minutes]:minutes:' \\
690
+ '--key[Key]:key:' \\
691
+ '--port[Port]:port:' \\
692
+ '--ip-address[IP address]:ip address:' \\
693
+ '--user[User]:user:' \\
694
+ '--script[Script]:script:' \\
695
+ '--servers[Servers]:servers:' \\
696
+ '--domain[Domain]:domain:' \\
697
+ '--password[Password]:password:' \\
698
+ '--credential-id[Credential ID]:credential id:' \\
699
+ '--region[Region]:region:' \\
700
+ '--size[Size]:size:' \\
701
+ '--project-type[Project type]:project type:' \\
702
+ '--directory[Directory]:directory:'
703
+ ;;
704
+ esac
705
+ }
706
+
707
+ _forge_cli "$@"
708
+ `;
709
+ var FISH_COMPLETION = `# Completions for forge-cli
710
+
711
+ # Main commands
712
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "config" -d "Manage CLI configuration"
713
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "servers" -d "Manage servers"
714
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "s" -d "Manage servers (alias)"
715
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "sites" -d "Manage sites"
716
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "deployments" -d "Manage deployments"
717
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "d" -d "Manage deployments (alias)"
718
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "databases" -d "Manage databases"
719
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "db" -d "Manage databases (alias)"
720
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "database-users" -d "Manage database users"
721
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "daemons" -d "Manage daemons"
722
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "env" -d "Manage environment variables"
723
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "nginx" -d "Manage nginx configuration"
724
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "certificates" -d "Manage SSL certificates"
725
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "certs" -d "Manage SSL certificates (alias)"
726
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "firewall-rules" -d "Manage firewall rules"
727
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "fw" -d "Manage firewall rules (alias)"
728
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "ssh-keys" -d "Manage SSH keys"
729
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "backups" -d "Manage backup configurations"
730
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "commands" -d "Manage site commands"
731
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "scheduled-jobs" -d "Manage scheduled jobs"
732
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "user" -d "Display authenticated user profile"
733
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "monitors" -d "Manage server monitors"
734
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "nginx-templates" -d "Manage Nginx templates"
735
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "security-rules" -d "Manage site security rules"
736
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "redirect-rules" -d "Manage site redirect rules"
737
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "recipes" -d "Manage recipes"
738
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "completion" -d "Generate shell completion script"
739
+ complete -c forge-cli -f -n "__fish_use_subcommand" -a "help" -d "Show help"
740
+
741
+ # config subcommands
742
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from config" -a "set" -d "Save API token"
743
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from config" -a "get" -d "Show current token"
744
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from config" -a "delete" -d "Delete stored token"
745
+
746
+ # servers subcommands
747
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from servers s" -a "list" -d "List all servers"
748
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from servers s" -a "ls" -d "List all servers (alias)"
749
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from servers s" -a "get" -d "Get server details"
750
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from servers s" -a "reboot" -d "Reboot a server"
751
+
752
+ # sites subcommands
753
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from sites" -a "list" -d "List sites"
754
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from sites" -a "ls" -d "List sites (alias)"
755
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from sites" -a "get" -d "Get site details"
756
+
757
+ # deployments subcommands
758
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from deployments d" -a "list" -d "List deployments"
759
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from deployments d" -a "ls" -d "List deployments (alias)"
760
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from deployments d" -a "deploy" -d "Trigger a deployment"
761
+
762
+ # databases subcommands
763
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from databases db" -a "list" -d "List databases"
764
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from databases db" -a "ls" -d "List databases (alias)"
765
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from databases db" -a "get" -d "Get database details"
766
+
767
+ # database-users subcommands
768
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from database-users" -a "list" -d "List database users"
769
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from database-users" -a "ls" -d "List database users (alias)"
770
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from database-users" -a "get" -d "Get database user details"
771
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from database-users" -a "create" -d "Create a database user"
772
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from database-users" -a "delete" -d "Delete a database user"
773
+
774
+ # daemons subcommands
775
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from daemons" -a "list" -d "List daemons"
776
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from daemons" -a "ls" -d "List daemons (alias)"
777
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from daemons" -a "get" -d "Get daemon details"
778
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from daemons" -a "restart" -d "Restart a daemon"
779
+
780
+ # env subcommands
781
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from env" -a "get" -d "Get env variables"
782
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from env" -a "update" -d "Update env variables"
783
+
784
+ # nginx subcommands
785
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from nginx" -a "get" -d "Get nginx config"
786
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from nginx" -a "update" -d "Update nginx config"
787
+
788
+ # certificates subcommands
789
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from certificates certs" -a "list" -d "List certificates"
790
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from certificates certs" -a "ls" -d "List certificates (alias)"
791
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from certificates certs" -a "get" -d "Get certificate details"
792
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from certificates certs" -a "activate" -d "Activate a certificate"
793
+
794
+ # firewall-rules subcommands
795
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from firewall-rules fw" -a "list" -d "List firewall rules"
796
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from firewall-rules fw" -a "ls" -d "List firewall rules (alias)"
797
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from firewall-rules fw" -a "get" -d "Get firewall rule details"
798
+
799
+ # ssh-keys subcommands
800
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from ssh-keys" -a "list" -d "List SSH keys"
801
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from ssh-keys" -a "ls" -d "List SSH keys (alias)"
802
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from ssh-keys" -a "get" -d "Get SSH key details"
803
+
804
+ # backups subcommands
805
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from backups" -a "list" -d "List backup configurations"
806
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from backups" -a "ls" -d "List backup configurations (alias)"
807
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from backups" -a "get" -d "Get backup configuration details"
808
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from backups" -a "create" -d "Create a backup configuration"
809
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from backups" -a "delete" -d "Delete a backup configuration"
810
+
811
+ # commands subcommands
812
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from commands" -a "list" -d "List commands"
813
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from commands" -a "ls" -d "List commands (alias)"
814
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from commands" -a "get" -d "Get command details"
815
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from commands" -a "create" -d "Execute a command"
816
+
817
+ # scheduled-jobs subcommands
818
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from scheduled-jobs" -a "list" -d "List scheduled jobs"
819
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from scheduled-jobs" -a "ls" -d "List scheduled jobs (alias)"
820
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from scheduled-jobs" -a "get" -d "Get scheduled job details"
821
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from scheduled-jobs" -a "create" -d "Create a scheduled job"
822
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from scheduled-jobs" -a "delete" -d "Delete a scheduled job"
823
+
824
+ # user subcommands
825
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from user" -a "get" -d "Get the authenticated user profile"
826
+
827
+ # monitors subcommands
828
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from monitors" -a "list" -d "List monitors"
829
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from monitors" -a "ls" -d "List monitors (alias)"
830
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from monitors" -a "get" -d "Get monitor details"
831
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from monitors" -a "create" -d "Create a monitor"
832
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from monitors" -a "delete" -d "Delete a monitor"
833
+
834
+ # nginx-templates subcommands
835
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from nginx-templates" -a "list" -d "List Nginx templates"
836
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from nginx-templates" -a "ls" -d "List Nginx templates (alias)"
837
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from nginx-templates" -a "get" -d "Get Nginx template details"
838
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from nginx-templates" -a "create" -d "Create a Nginx template"
839
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from nginx-templates" -a "update" -d "Update a Nginx template"
840
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from nginx-templates" -a "delete" -d "Delete a Nginx template"
841
+
842
+ # security-rules subcommands
843
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from security-rules" -a "list" -d "List security rules"
844
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from security-rules" -a "ls" -d "List security rules (alias)"
845
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from security-rules" -a "get" -d "Get security rule details"
846
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from security-rules" -a "create" -d "Create a security rule"
847
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from security-rules" -a "delete" -d "Delete a security rule"
848
+
849
+ # redirect-rules subcommands
850
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from redirect-rules" -a "list" -d "List redirect rules"
851
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from redirect-rules" -a "ls" -d "List redirect rules (alias)"
852
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from redirect-rules" -a "get" -d "Get redirect rule details"
853
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from redirect-rules" -a "create" -d "Create a redirect rule"
854
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from redirect-rules" -a "delete" -d "Delete a redirect rule"
855
+
856
+ # recipes subcommands
857
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from recipes" -a "list" -d "List all recipes"
858
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from recipes" -a "ls" -d "List all recipes (alias)"
859
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from recipes" -a "get" -d "Get recipe details"
860
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from recipes" -a "run" -d "Run a recipe"
861
+
862
+ # completion subcommands
863
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from completion" -a "bash" -d "Install Bash completion"
864
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from completion" -a "zsh" -d "Install Zsh completion"
865
+ complete -c forge-cli -f -n "__fish_seen_subcommand_from completion" -a "fish" -d "Install Fish completion"
866
+
867
+ # Global options
868
+ complete -c forge-cli -l token -d "API token" -r
869
+ complete -c forge-cli -l server -d "Server ID" -r
870
+ complete -c forge-cli -l site -d "Site ID" -r
871
+ complete -c forge-cli -s f -l format -d "Output format" -xa "json human table"
872
+ complete -c forge-cli -l no-color -d "Disable colored output"
873
+ complete -c forge-cli -s h -l help -d "Show help"
874
+ complete -c forge-cli -s v -l version -d "Show version"
875
+ complete -c forge-cli -l name -d "Name" -r
876
+ complete -c forge-cli -l command -d "Command" -r
877
+ complete -c forge-cli -l content -d "Content" -r
878
+ complete -c forge-cli -l from -d "From" -r
879
+ complete -c forge-cli -l to -d "To" -r
880
+ complete -c forge-cli -l provider -d "Provider" -r
881
+ complete -c forge-cli -l frequency -d "Frequency" -r
882
+ complete -c forge-cli -l type -d "Type" -r
883
+ complete -c forge-cli -l operator -d "Operator" -r
884
+ complete -c forge-cli -l threshold -d "Threshold" -r
885
+ complete -c forge-cli -l minutes -d "Minutes" -r
886
+ complete -c forge-cli -l key -d "Key" -r
887
+ complete -c forge-cli -l port -d "Port" -r
888
+ complete -c forge-cli -l ip-address -d "IP address" -r
889
+ complete -c forge-cli -l user -d "User" -r
890
+ complete -c forge-cli -l script -d "Script" -r
891
+ complete -c forge-cli -l servers -d "Servers" -r
892
+ complete -c forge-cli -l domain -d "Domain" -r
893
+ complete -c forge-cli -l password -d "Password" -r
894
+ complete -c forge-cli -l credential-id -d "Credential ID" -r
895
+ complete -c forge-cli -l region -d "Region" -r
896
+ complete -c forge-cli -l size -d "Size" -r
897
+ complete -c forge-cli -l project-type -d "Project type" -r
898
+ complete -c forge-cli -l directory -d "Directory" -r
899
+ `;
900
+ /**
901
+ * Get the appropriate completion file path for the shell
902
+ */
903
+ function getCompletionPath(shell) {
904
+ const home = homedir();
905
+ switch (shell) {
906
+ case "bash": return join(home, ".local/share/bash-completion/completions/forge-cli");
907
+ case "zsh": return join(home, ".zfunc/_forge-cli");
908
+ case "fish": return join(process.env["XDG_CONFIG_HOME"] ?? join(home, ".config"), "fish/completions/forge-cli.fish");
909
+ default: throw new Error(`Unknown shell: ${shell}`);
910
+ }
911
+ }
912
+ function showCompletionHelp() {
913
+ console.log(`
914
+ ${colors.bold("forge-cli completion")} - Install shell completion
915
+
916
+ ${colors.bold("USAGE:")}
917
+ forge-cli completion <shell> [--print]
918
+
919
+ ${colors.bold("SHELLS:")}
920
+ bash Install Bash completion
921
+ zsh Install Zsh completion
922
+ fish Install Fish completion
923
+
924
+ ${colors.bold("OPTIONS:")}
925
+ --print Print completion script instead of installing
926
+
927
+ ${colors.bold("INSTALLATION:")}
928
+ The completion script is automatically installed to the appropriate
929
+ standard directory for your shell. After installation, restart your
930
+ shell to activate completions.
931
+
932
+ ${colors.bold("Bash:")}
933
+ forge-cli completion bash
934
+ # Installs to: ~/.local/share/bash-completion/completions/forge-cli
935
+ # Then run: exec bash
936
+
937
+ ${colors.bold("Zsh:")}
938
+ forge-cli completion zsh
939
+ # Installs to: ~/.zfunc/_forge-cli
940
+ # Ensure fpath includes ~/.zfunc (add to ~/.zshrc before compinit):
941
+ # fpath=(~/.zfunc $fpath)
942
+ # Then run: exec zsh
943
+
944
+ ${colors.bold("Fish:")}
945
+ forge-cli completion fish
946
+ # Installs to: ~/.config/fish/completions/forge-cli.fish
947
+ # Completions are loaded automatically
948
+
949
+ ${colors.bold("PRINT ONLY:")}
950
+ Use --print to output the script without installing:
951
+
952
+ forge-cli completion bash --print > my-completion.sh
953
+ forge-cli completion zsh --print | less
954
+ `);
955
+ }
956
+ function handleCompletionCommand(shell, options = {}) {
957
+ const shouldPrint = options["print"] !== void 0;
958
+ const shellLower = shell.toLowerCase();
959
+ let script;
960
+ switch (shellLower) {
961
+ case "bash":
962
+ script = BASH_COMPLETION;
963
+ break;
964
+ case "zsh":
965
+ script = ZSH_COMPLETION;
966
+ break;
967
+ case "fish":
968
+ script = FISH_COMPLETION;
969
+ break;
970
+ default:
971
+ console.error(`${colors.red("✗")} Unknown shell: ${shell}. Supported shells: bash, zsh, fish`);
972
+ console.error(`Run ${colors.cyan("forge-cli completion --help")} for usage information.`);
973
+ process.exit(1);
974
+ return;
975
+ }
976
+ if (shouldPrint) {
977
+ process.stdout.write(script);
978
+ return;
979
+ }
980
+ const installPath = getCompletionPath(shellLower);
981
+ const dir = installPath.substring(0, installPath.lastIndexOf("/"));
982
+ try {
983
+ mkdirSync(dir, { recursive: true });
984
+ writeFileSync(installPath, script, "utf8");
985
+ console.log(`${colors.green("✓")} Installed ${shell} completion to ${colors.cyan(installPath)}`);
986
+ console.log();
987
+ console.log(`${colors.bold("Next steps:")}`);
988
+ switch (shellLower) {
989
+ case "bash":
990
+ console.log(` 1. Restart your shell: ${colors.cyan("exec bash")}`);
991
+ console.log(` 2. Or source the file: ${colors.cyan(`source ${installPath}`)}`);
992
+ break;
993
+ case "zsh":
994
+ console.log(` 1. Ensure ${colors.cyan("~/.zfunc")} is in your $fpath (add to ~/.zshrc before compinit):`);
995
+ console.log(` ${colors.cyan("fpath=(~/.zfunc $fpath)")}`);
996
+ console.log(` 2. Restart your shell: ${colors.cyan("exec zsh")}`);
997
+ break;
998
+ case "fish":
999
+ console.log(` Completions are loaded automatically. Restart fish if needed.`);
1000
+ break;
1001
+ }
1002
+ } catch (error) {
1003
+ console.error(`${colors.red("✗")} Failed to install completion: ${error instanceof Error ? error.message : String(error)}`);
1004
+ process.exit(1);
1005
+ }
1006
+ }
223
1007
  function requireServerAndSiteRaw$3(ctx, usage) {
224
1008
  const server = String(ctx.options.server ?? "");
225
1009
  const site = String(ctx.options.site ?? "");
@@ -299,7 +1083,8 @@ const handleCertificatesCommand = createCommandRouter({
299
1083
  ls: certificatesList,
300
1084
  get: [certificatesGet, "args"],
301
1085
  activate: [certificatesActivate, "args"]
302
- }
1086
+ },
1087
+ writeSubcommands: ["activate"]
303
1088
  });
304
1089
  function showCertificatesHelp(subcommand) {
305
1090
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -422,7 +1207,8 @@ const handleCommandsCommand = createCommandRouter({
422
1207
  ls: commandsList,
423
1208
  get: [commandsGet, "args"],
424
1209
  create: commandsCreate
425
- }
1210
+ },
1211
+ writeSubcommands: ["create"]
426
1212
  });
427
1213
  function showCommandsHelp(subcommand) {
428
1214
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -591,7 +1377,8 @@ const handleDaemonsCommand = createCommandRouter({
591
1377
  ls: daemonsList,
592
1378
  get: [daemonsGet, "args"],
593
1379
  restart: [daemonsRestart, "args"]
594
- }
1380
+ },
1381
+ writeSubcommands: ["restart"]
595
1382
  });
596
1383
  function showDaemonsHelp(subcommand) {
597
1384
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -794,7 +1581,8 @@ const handleDatabaseUsersCommand = createCommandRouter({
794
1581
  get: [databaseUsersGet, "args"],
795
1582
  create: databaseUsersCreate,
796
1583
  delete: [databaseUsersDelete, "args"]
797
- }
1584
+ },
1585
+ writeSubcommands: ["create", "delete"]
798
1586
  });
799
1587
  function showDatabaseUsersHelp(subcommand) {
800
1588
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -882,11 +1670,18 @@ async function deploymentsDeploy(ctx) {
882
1670
  const execCtx = ctx.createExecutorContext(token);
883
1671
  const server_id = await resolveServerId(server, execCtx);
884
1672
  const site_id = await resolveSiteId(site, server_id, execCtx);
885
- await deploySite({
1673
+ const result = await deploySiteAndWait({
886
1674
  server_id,
887
- site_id
1675
+ site_id,
1676
+ onProgress: ({ status, elapsed_ms }) => {
1677
+ process.stderr.write(`\rDeploying… ${status} (${(elapsed_ms / 1e3).toFixed(1)}s)`);
1678
+ }
888
1679
  }, execCtx);
889
- ctx.formatter.success(`Deployment triggered for site ${site_id}.`);
1680
+ process.stderr.write("\n");
1681
+ const elapsedSec = (result.data.elapsed_ms / 1e3).toFixed(1);
1682
+ if (result.data.status === "success") ctx.formatter.success(`Deployment succeeded for site ${site_id} (${elapsedSec}s).`);
1683
+ else ctx.formatter.success(`Deployment failed for site ${site_id} (${elapsedSec}s).`);
1684
+ if (result.data.log) ctx.formatter.output(result.data.log);
890
1685
  }, ctx.formatter);
891
1686
  }
892
1687
  const handleDeploymentsCommand = createCommandRouter({
@@ -895,7 +1690,8 @@ const handleDeploymentsCommand = createCommandRouter({
895
1690
  list: deploymentsList,
896
1691
  ls: deploymentsList,
897
1692
  deploy: deploymentsDeploy
898
- }
1693
+ },
1694
+ writeSubcommands: ["deploy"]
899
1695
  });
900
1696
  function showDeploymentsHelp(subcommand) {
901
1697
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -977,7 +1773,8 @@ const handleEnvCommand = createCommandRouter({
977
1773
  handlers: {
978
1774
  get: envGet,
979
1775
  update: envUpdate
980
- }
1776
+ },
1777
+ writeSubcommands: ["update"]
981
1778
  });
982
1779
  function showEnvHelp(subcommand) {
983
1780
  if (subcommand === "get") console.log(`
@@ -1183,7 +1980,8 @@ const handleMonitorsCommand = createCommandRouter({
1183
1980
  get: [monitorsGet, "args"],
1184
1981
  create: monitorsCreate,
1185
1982
  delete: [monitorsDelete, "args"]
1186
- }
1983
+ },
1984
+ writeSubcommands: ["create", "delete"]
1187
1985
  });
1188
1986
  function showMonitorsHelp(subcommand) {
1189
1987
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -1282,7 +2080,8 @@ const handleNginxCommand = createCommandRouter({
1282
2080
  handlers: {
1283
2081
  get: nginxGet,
1284
2082
  update: nginxUpdate
1285
- }
2083
+ },
2084
+ writeSubcommands: ["update"]
1286
2085
  });
1287
2086
  function showNginxHelp(subcommand) {
1288
2087
  if (subcommand === "get") console.log(`
@@ -1416,7 +2215,12 @@ const handleNginxTemplatesCommand = createCommandRouter({
1416
2215
  create: nginxTemplatesCreate,
1417
2216
  update: [nginxTemplatesUpdate, "args"],
1418
2217
  delete: [nginxTemplatesDelete, "args"]
1419
- }
2218
+ },
2219
+ writeSubcommands: [
2220
+ "create",
2221
+ "update",
2222
+ "delete"
2223
+ ]
1420
2224
  });
1421
2225
  function showNginxTemplatesHelp(subcommand) {
1422
2226
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -1536,7 +2340,8 @@ const handleRecipesCommand = createCommandRouter({
1536
2340
  ls: recipesList,
1537
2341
  get: [recipesGet, "args"],
1538
2342
  run: [recipesRun, "args"]
1539
- }
2343
+ },
2344
+ writeSubcommands: ["run"]
1540
2345
  });
1541
2346
  function showRecipesHelp(subcommand) {
1542
2347
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -1679,7 +2484,8 @@ const handleRedirectRulesCommand = createCommandRouter({
1679
2484
  get: [redirectRulesGet, "args"],
1680
2485
  create: redirectRulesCreate,
1681
2486
  delete: [redirectRulesDelete, "args"]
1682
- }
2487
+ },
2488
+ writeSubcommands: ["create", "delete"]
1683
2489
  });
1684
2490
  function showRedirectRulesHelp(subcommand) {
1685
2491
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -1822,7 +2628,8 @@ const handleScheduledJobsCommand = createCommandRouter({
1822
2628
  get: [scheduledJobsGet, "args"],
1823
2629
  create: scheduledJobsCreate,
1824
2630
  delete: [scheduledJobsDelete, "args"]
1825
- }
2631
+ },
2632
+ writeSubcommands: ["create", "delete"]
1826
2633
  });
1827
2634
  function showScheduledJobsHelp(subcommand) {
1828
2635
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -1975,7 +2782,8 @@ const handleSecurityRulesCommand = createCommandRouter({
1975
2782
  get: [securityRulesGet, "args"],
1976
2783
  create: securityRulesCreate,
1977
2784
  delete: [securityRulesDelete, "args"]
1978
- }
2785
+ },
2786
+ writeSubcommands: ["create", "delete"]
1979
2787
  });
1980
2788
  function showSecurityRulesHelp(subcommand) {
1981
2789
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -2089,7 +2897,8 @@ const handleServersCommand = createCommandRouter({
2089
2897
  ls: serversList,
2090
2898
  get: [serversGet, "args"],
2091
2899
  reboot: [serversReboot, "args"]
2092
- }
2900
+ },
2901
+ writeSubcommands: ["reboot"]
2093
2902
  });
2094
2903
  function showServersHelp(subcommand) {
2095
2904
  if (subcommand === "list" || subcommand === "ls") console.log(`
@@ -2405,7 +3214,7 @@ function parseArgs(argv = process.argv.slice(2)) {
2405
3214
  positional
2406
3215
  };
2407
3216
  }
2408
- var VERSION = "0.1.0";
3217
+ var VERSION = "0.2.1";
2409
3218
  function showHelp() {
2410
3219
  console.log(`
2411
3220
  ${colors.bold("forge-cli")} v${VERSION}
@@ -2518,6 +3327,11 @@ ${colors.bold("COMMANDS:")}
2518
3327
  get <id> Get recipe details
2519
3328
  run <id> Run a recipe (requires --servers)
2520
3329
 
3330
+ completion Generate shell completion script
3331
+ bash Install Bash completion
3332
+ zsh Install Zsh completion
3333
+ fish Install Fish completion
3334
+
2521
3335
  ${colors.bold("OPTIONS:")}
2522
3336
  --token <token> Forge API token (overrides config and env)
2523
3337
  --server <id> Server ID (required for server-scoped commands)
@@ -2728,6 +3542,13 @@ async function main() {
2728
3542
  }
2729
3543
  await handleRecipesCommand(subcommand ?? "list", positional, options);
2730
3544
  break;
3545
+ case "completion":
3546
+ if (wantsHelp || !subcommand) {
3547
+ showCompletionHelp();
3548
+ process.exit(0);
3549
+ }
3550
+ handleCompletionCommand(subcommand, options);
3551
+ break;
2731
3552
  default:
2732
3553
  console.error(colors.red(`Unknown command: ${mainCommand}`));
2733
3554
  console.log(`Run ${colors.cyan("forge-cli --help")} for usage information`);