hetzner-cli 2.0.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.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +907 -0
  3. package/dist/auction/client.d.ts +4 -0
  4. package/dist/auction/client.js +103 -0
  5. package/dist/auction/commands.d.ts +2 -0
  6. package/dist/auction/commands.js +138 -0
  7. package/dist/auction/formatter.d.ts +3 -0
  8. package/dist/auction/formatter.js +87 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +39 -0
  11. package/dist/client.d.ts +2 -0
  12. package/dist/client.js +4 -0
  13. package/dist/cloud/client.d.ts +511 -0
  14. package/dist/cloud/client.js +706 -0
  15. package/dist/cloud/commands/certificate.d.ts +2 -0
  16. package/dist/cloud/commands/certificate.js +77 -0
  17. package/dist/cloud/commands/context.d.ts +2 -0
  18. package/dist/cloud/commands/context.js +78 -0
  19. package/dist/cloud/commands/datacenter.d.ts +2 -0
  20. package/dist/cloud/commands/datacenter.js +20 -0
  21. package/dist/cloud/commands/firewall.d.ts +2 -0
  22. package/dist/cloud/commands/firewall.js +77 -0
  23. package/dist/cloud/commands/floating-ip.d.ts +2 -0
  24. package/dist/cloud/commands/floating-ip.js +83 -0
  25. package/dist/cloud/commands/image.d.ts +2 -0
  26. package/dist/cloud/commands/image.js +60 -0
  27. package/dist/cloud/commands/index.d.ts +2 -0
  28. package/dist/cloud/commands/index.js +41 -0
  29. package/dist/cloud/commands/iso.d.ts +2 -0
  30. package/dist/cloud/commands/iso.js +22 -0
  31. package/dist/cloud/commands/load-balancer-type.d.ts +2 -0
  32. package/dist/cloud/commands/load-balancer-type.js +20 -0
  33. package/dist/cloud/commands/load-balancer.d.ts +2 -0
  34. package/dist/cloud/commands/load-balancer.js +177 -0
  35. package/dist/cloud/commands/location.d.ts +2 -0
  36. package/dist/cloud/commands/location.js +20 -0
  37. package/dist/cloud/commands/network.d.ts +2 -0
  38. package/dist/cloud/commands/network.js +96 -0
  39. package/dist/cloud/commands/placement-group.d.ts +2 -0
  40. package/dist/cloud/commands/placement-group.js +53 -0
  41. package/dist/cloud/commands/primary-ip.d.ts +2 -0
  42. package/dist/cloud/commands/primary-ip.js +83 -0
  43. package/dist/cloud/commands/server-type.d.ts +2 -0
  44. package/dist/cloud/commands/server-type.js +20 -0
  45. package/dist/cloud/commands/server.d.ts +2 -0
  46. package/dist/cloud/commands/server.js +260 -0
  47. package/dist/cloud/commands/ssh-key.d.ts +2 -0
  48. package/dist/cloud/commands/ssh-key.js +63 -0
  49. package/dist/cloud/commands/volume.d.ts +2 -0
  50. package/dist/cloud/commands/volume.js +92 -0
  51. package/dist/cloud/context.d.ts +28 -0
  52. package/dist/cloud/context.js +172 -0
  53. package/dist/cloud/formatter.d.ts +37 -0
  54. package/dist/cloud/formatter.js +413 -0
  55. package/dist/cloud/helpers.d.ts +18 -0
  56. package/dist/cloud/helpers.js +48 -0
  57. package/dist/cloud/types.d.ts +398 -0
  58. package/dist/cloud/types.js +5 -0
  59. package/dist/config.d.ts +1 -0
  60. package/dist/config.js +2 -0
  61. package/dist/formatter.d.ts +3 -0
  62. package/dist/formatter.js +6 -0
  63. package/dist/index.d.ts +10 -0
  64. package/dist/index.js +17 -0
  65. package/dist/robot/client.d.ts +256 -0
  66. package/dist/robot/client.js +656 -0
  67. package/dist/robot/commands/auth.d.ts +2 -0
  68. package/dist/robot/commands/auth.js +54 -0
  69. package/dist/robot/commands/boot.d.ts +2 -0
  70. package/dist/robot/commands/boot.js +72 -0
  71. package/dist/robot/commands/cancel.d.ts +2 -0
  72. package/dist/robot/commands/cancel.js +36 -0
  73. package/dist/robot/commands/failover.d.ts +2 -0
  74. package/dist/robot/commands/failover.js +42 -0
  75. package/dist/robot/commands/firewall.d.ts +2 -0
  76. package/dist/robot/commands/firewall.js +66 -0
  77. package/dist/robot/commands/index.d.ts +2 -0
  78. package/dist/robot/commands/index.js +36 -0
  79. package/dist/robot/commands/interactive.d.ts +2 -0
  80. package/dist/robot/commands/interactive.js +134 -0
  81. package/dist/robot/commands/ip.d.ts +2 -0
  82. package/dist/robot/commands/ip.js +52 -0
  83. package/dist/robot/commands/key.d.ts +2 -0
  84. package/dist/robot/commands/key.js +64 -0
  85. package/dist/robot/commands/order.d.ts +2 -0
  86. package/dist/robot/commands/order.js +33 -0
  87. package/dist/robot/commands/rdns.d.ts +2 -0
  88. package/dist/robot/commands/rdns.js +41 -0
  89. package/dist/robot/commands/reset.d.ts +2 -0
  90. package/dist/robot/commands/reset.js +77 -0
  91. package/dist/robot/commands/server.d.ts +2 -0
  92. package/dist/robot/commands/server.js +29 -0
  93. package/dist/robot/commands/storagebox.d.ts +2 -0
  94. package/dist/robot/commands/storagebox.js +116 -0
  95. package/dist/robot/commands/subnet.d.ts +2 -0
  96. package/dist/robot/commands/subnet.js +21 -0
  97. package/dist/robot/commands/traffic.d.ts +2 -0
  98. package/dist/robot/commands/traffic.js +20 -0
  99. package/dist/robot/commands/vswitch.d.ts +2 -0
  100. package/dist/robot/commands/vswitch.js +64 -0
  101. package/dist/robot/commands/wol.d.ts +2 -0
  102. package/dist/robot/commands/wol.js +20 -0
  103. package/dist/robot/formatter.d.ts +58 -0
  104. package/dist/robot/formatter.js +500 -0
  105. package/dist/robot/types.d.ts +352 -0
  106. package/dist/robot/types.js +5 -0
  107. package/dist/shared/config.d.ts +86 -0
  108. package/dist/shared/config.js +273 -0
  109. package/dist/shared/formatter.d.ts +29 -0
  110. package/dist/shared/formatter.js +118 -0
  111. package/dist/shared/helpers.d.ts +17 -0
  112. package/dist/shared/helpers.js +72 -0
  113. package/dist/shared/reference.d.ts +2 -0
  114. package/dist/shared/reference.js +626 -0
  115. package/dist/types.d.ts +75 -0
  116. package/dist/types.js +1 -0
  117. package/package.json +112 -0
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerCertificateCommands(parent: Command): void;
@@ -0,0 +1,77 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { cloudAction, cloudOutput, cloudConfirm } from '../helpers.js';
3
+ import * as fmt from '../../shared/formatter.js';
4
+ import * as cloudFmt from '../formatter.js';
5
+ export function registerCertificateCommands(parent) {
6
+ const cert = parent.command('certificate').description('Certificate management');
7
+ cert.command('list').alias('ls').description('List all certificates')
8
+ .option('-l, --label-selector <selector>', 'Label selector')
9
+ .option('-s, --sort <field>', 'Sort by field')
10
+ .option('--type <type>', 'Filter by type (uploaded, managed)')
11
+ .action(cloudAction(async (client, options) => {
12
+ const certs = await client.listCertificates({ label_selector: options.labelSelector, sort: options.sort, type: options.type });
13
+ cloudOutput(certs, cloudFmt.formatCertificateList, options);
14
+ }));
15
+ cert.command('describe <id>').description('Show certificate details')
16
+ .action(cloudAction(async (client, id, options) => {
17
+ const certificate = await client.getCertificate(parseInt(id));
18
+ cloudOutput(certificate, cloudFmt.formatCertificateDetails, options);
19
+ }));
20
+ cert.command('create').description('Create a certificate')
21
+ .requiredOption('--name <name>', 'Certificate name')
22
+ .option('--type <type>', 'Certificate type (uploaded, managed)', 'uploaded')
23
+ .option('--cert-file <path>', 'Path to certificate PEM file (for uploaded)')
24
+ .option('--key-file <path>', 'Path to private key PEM file (for uploaded)')
25
+ .option('--domain <domains...>', 'Domain names (for managed)')
26
+ .action(cloudAction(async (client, options) => {
27
+ const createOpts = {
28
+ name: options.name,
29
+ type: options.type,
30
+ };
31
+ if (options.type === 'managed') {
32
+ if (!options.domain || options.domain.length === 0) {
33
+ console.error(fmt.error('Managed certificates require --domain'));
34
+ process.exit(1);
35
+ }
36
+ createOpts.domain_names = options.domain;
37
+ }
38
+ else {
39
+ if (!options.certFile || !options.keyFile) {
40
+ console.error(fmt.error('Uploaded certificates require --cert-file and --key-file'));
41
+ process.exit(1);
42
+ }
43
+ createOpts.certificate = readFileSync(options.certFile, 'utf-8');
44
+ createOpts.private_key = readFileSync(options.keyFile, 'utf-8');
45
+ }
46
+ const { certificate: created } = await client.createCertificate(createOpts);
47
+ console.log(fmt.success(`Certificate '${created.name}' created (ID: ${created.id})`));
48
+ }));
49
+ cert.command('delete <id>').description('Delete a certificate')
50
+ .option('-y, --yes', 'Skip confirmation')
51
+ .action(cloudAction(async (client, id, options) => {
52
+ if (!await cloudConfirm(`Delete certificate ${id}?`, options))
53
+ return;
54
+ await client.deleteCertificate(parseInt(id));
55
+ console.log(fmt.success(`Certificate ${id} deleted.`));
56
+ }));
57
+ cert.command('update <id>').description('Update certificate')
58
+ .option('--name <name>', 'New name')
59
+ .action(cloudAction(async (client, id, options) => {
60
+ await client.updateCertificate(parseInt(id), { name: options.name });
61
+ console.log(fmt.success(`Certificate ${id} updated.`));
62
+ }));
63
+ cert.command('add-label <id> <label>').description('Add a label (key=value)')
64
+ .action(cloudAction(async (client, id, label) => {
65
+ const certificate = await client.getCertificate(parseInt(id));
66
+ const [key, value] = label.split('=');
67
+ await client.updateCertificate(parseInt(id), { labels: { ...certificate.labels, [key]: value || '' } });
68
+ console.log(fmt.success(`Label '${key}' added.`));
69
+ }));
70
+ cert.command('remove-label <id> <key>').description('Remove a label')
71
+ .action(cloudAction(async (client, id, key) => {
72
+ const certificate = await client.getCertificate(parseInt(id));
73
+ const labels = Object.fromEntries(Object.entries(certificate.labels).filter(([k]) => k !== key));
74
+ await client.updateCertificate(parseInt(id), { labels });
75
+ console.log(fmt.success(`Label '${key}' removed.`));
76
+ }));
77
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerContextCommands(parent: Command): void;
@@ -0,0 +1,78 @@
1
+ import { password as passwordPrompt } from '@inquirer/prompts';
2
+ import * as fmt from '../../shared/formatter.js';
3
+ import { createContext, useContext, deleteContext, listContexts, getActiveContext, } from '../context.js';
4
+ import { formatContextList } from '../formatter.js';
5
+ export function registerContextCommands(parent) {
6
+ const context = parent.command('context').description('Cloud context (token) management');
7
+ context
8
+ .command('create <name>')
9
+ .description('Create a new cloud context')
10
+ .option('-t, --token <token>', 'API token (will prompt if not provided)')
11
+ .action(async (name, options) => {
12
+ try {
13
+ let token = options.token;
14
+ if (!token) {
15
+ token = await passwordPrompt({
16
+ message: `Enter Hetzner Cloud API token for '${name}':`,
17
+ validate: (v) => v.length > 0 || 'Token is required',
18
+ });
19
+ }
20
+ await createContext(name, token);
21
+ console.log(fmt.success(`Context '${name}' created and activated.`));
22
+ }
23
+ catch (error) {
24
+ if (error instanceof Error && error.name === 'ExitPromptError') {
25
+ process.exit(0);
26
+ }
27
+ console.error(fmt.error(error instanceof Error ? error.message : 'Unknown error'));
28
+ process.exit(1);
29
+ }
30
+ });
31
+ context
32
+ .command('use <name>')
33
+ .description('Switch to a different cloud context')
34
+ .action((name) => {
35
+ try {
36
+ useContext(name);
37
+ console.log(fmt.success(`Switched to context '${name}'.`));
38
+ }
39
+ catch (error) {
40
+ console.error(fmt.error(error instanceof Error ? error.message : 'Unknown error'));
41
+ process.exit(1);
42
+ }
43
+ });
44
+ context
45
+ .command('delete <name>')
46
+ .alias('rm')
47
+ .description('Delete a cloud context')
48
+ .action(async (name) => {
49
+ try {
50
+ await deleteContext(name);
51
+ console.log(fmt.success(`Context '${name}' deleted.`));
52
+ }
53
+ catch (error) {
54
+ console.error(fmt.error(error instanceof Error ? error.message : 'Unknown error'));
55
+ process.exit(1);
56
+ }
57
+ });
58
+ context
59
+ .command('list')
60
+ .alias('ls')
61
+ .description('List all cloud contexts')
62
+ .action(() => {
63
+ const contexts = listContexts();
64
+ console.log(formatContextList(contexts));
65
+ });
66
+ context
67
+ .command('active')
68
+ .description('Show the active cloud context')
69
+ .action(() => {
70
+ const active = getActiveContext();
71
+ if (active) {
72
+ console.log(fmt.success(`Active context: ${active}`));
73
+ }
74
+ else {
75
+ console.log(fmt.warning('No active context. Run: hetzner cloud context create'));
76
+ }
77
+ });
78
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerDatacenterCommands(parent: Command): void;
@@ -0,0 +1,20 @@
1
+ import { cloudAction, cloudOutput } from '../helpers.js';
2
+ import * as cloudFmt from '../formatter.js';
3
+ export function registerDatacenterCommands(parent) {
4
+ const datacenter = parent.command('datacenter').description('Datacenter information');
5
+ datacenter
6
+ .command('list')
7
+ .alias('ls')
8
+ .description('List all datacenters')
9
+ .action(cloudAction(async (client, options) => {
10
+ const datacenters = await client.listDatacenters();
11
+ cloudOutput(datacenters, cloudFmt.formatDatacenterList, options);
12
+ }));
13
+ datacenter
14
+ .command('describe <id>')
15
+ .description('Show datacenter details')
16
+ .action(cloudAction(async (client, id, options) => {
17
+ const datacenter = await client.getDatacenter(parseInt(id));
18
+ cloudOutput(datacenter, cloudFmt.formatDatacenterDetails, options);
19
+ }));
20
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerCloudFirewallCommands(parent: Command): void;
@@ -0,0 +1,77 @@
1
+ import { cloudAction, cloudOutput, cloudConfirm } from '../helpers.js';
2
+ import * as fmt from '../../shared/formatter.js';
3
+ import * as cloudFmt from '../formatter.js';
4
+ export function registerCloudFirewallCommands(parent) {
5
+ const firewall = parent.command('firewall').description('Cloud firewall management');
6
+ firewall.command('list').alias('ls').description('List all firewalls')
7
+ .option('-l, --label-selector <selector>', 'Label selector')
8
+ .action(cloudAction(async (client, options) => {
9
+ const firewalls = await client.listFirewalls({ label_selector: options.labelSelector });
10
+ cloudOutput(firewalls, cloudFmt.formatCloudFirewallList, options);
11
+ }));
12
+ firewall.command('describe <id>').description('Show firewall details')
13
+ .action(cloudAction(async (client, id, options) => {
14
+ const fw = await client.getFirewall(parseInt(id));
15
+ cloudOutput(fw, cloudFmt.formatCloudFirewallDetails, options);
16
+ }));
17
+ firewall.command('create').description('Create a firewall')
18
+ .requiredOption('--name <name>', 'Firewall name')
19
+ .action(cloudAction(async (client, options) => {
20
+ const { firewall: fw } = await client.createFirewall({ name: options.name });
21
+ console.log(fmt.success(`Firewall '${fw.name}' created (ID: ${fw.id})`));
22
+ }));
23
+ firewall.command('delete <id>').description('Delete a firewall')
24
+ .option('-y, --yes', 'Skip confirmation')
25
+ .action(cloudAction(async (client, id, options) => {
26
+ if (!await cloudConfirm(`Delete firewall ${id}?`, options))
27
+ return;
28
+ await client.deleteFirewall(parseInt(id));
29
+ console.log(fmt.success(`Firewall ${id} deleted.`));
30
+ }));
31
+ firewall.command('update <id>').description('Update firewall')
32
+ .option('--name <name>', 'New name')
33
+ .action(cloudAction(async (client, id, options) => {
34
+ await client.updateFirewall(parseInt(id), { name: options.name });
35
+ console.log(fmt.success(`Firewall ${id} updated.`));
36
+ }));
37
+ firewall.command('apply-to-resource <id>').description('Apply firewall to resource')
38
+ .requiredOption('--type <type>', 'Resource type (server, label_selector)')
39
+ .option('--server <server>', 'Server ID')
40
+ .option('--label-selector <selector>', 'Label selector')
41
+ .action(cloudAction(async (client, id, options) => {
42
+ const applyTo = [{
43
+ type: options.type,
44
+ ...(options.server ? { server: { id: parseInt(options.server) } } : {}),
45
+ ...(options.labelSelector ? { label_selector: { selector: options.labelSelector } } : {}),
46
+ }];
47
+ await client.applyFirewall(parseInt(id), applyTo);
48
+ console.log(fmt.success(`Firewall ${id} applied.`));
49
+ }));
50
+ firewall.command('remove-from-resource <id>').description('Remove firewall from resource')
51
+ .requiredOption('--type <type>', 'Resource type (server, label_selector)')
52
+ .option('--server <server>', 'Server ID')
53
+ .option('--label-selector <selector>', 'Label selector')
54
+ .action(cloudAction(async (client, id, options) => {
55
+ const removeFrom = [{
56
+ type: options.type,
57
+ ...(options.server ? { server: { id: parseInt(options.server) } } : {}),
58
+ ...(options.labelSelector ? { label_selector: { selector: options.labelSelector } } : {}),
59
+ }];
60
+ await client.removeFirewallFromResources(parseInt(id), removeFrom);
61
+ console.log(fmt.success(`Firewall ${id} removed from resource.`));
62
+ }));
63
+ firewall.command('add-label <id> <label>').description('Add a label (key=value)')
64
+ .action(cloudAction(async (client, id, label) => {
65
+ const fw = await client.getFirewall(parseInt(id));
66
+ const [key, value] = label.split('=');
67
+ await client.updateFirewall(parseInt(id), { labels: { ...fw.labels, [key]: value || '' } });
68
+ console.log(fmt.success(`Label '${key}' added.`));
69
+ }));
70
+ firewall.command('remove-label <id> <key>').description('Remove a label')
71
+ .action(cloudAction(async (client, id, key) => {
72
+ const fw = await client.getFirewall(parseInt(id));
73
+ const labels = Object.fromEntries(Object.entries(fw.labels).filter(([k]) => k !== key));
74
+ await client.updateFirewall(parseInt(id), { labels });
75
+ console.log(fmt.success(`Label '${key}' removed.`));
76
+ }));
77
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerFloatingIpCommands(parent: Command): void;
@@ -0,0 +1,83 @@
1
+ import { cloudAction, cloudOutput, cloudConfirm } from '../helpers.js';
2
+ import * as fmt from '../../shared/formatter.js';
3
+ import * as cloudFmt from '../formatter.js';
4
+ export function registerFloatingIpCommands(parent) {
5
+ const fip = parent.command('floating-ip').description('Floating IP management');
6
+ fip.command('list').alias('ls').description('List all floating IPs')
7
+ .option('-l, --label-selector <selector>', 'Label selector')
8
+ .action(cloudAction(async (client, options) => {
9
+ const ips = await client.listFloatingIps({ label_selector: options.labelSelector });
10
+ cloudOutput(ips, cloudFmt.formatFloatingIpList, options);
11
+ }));
12
+ fip.command('describe <id>').description('Show floating IP details')
13
+ .action(cloudAction(async (client, id, options) => {
14
+ const ip = await client.getFloatingIp(parseInt(id));
15
+ cloudOutput(ip, cloudFmt.formatFloatingIpDetails, options);
16
+ }));
17
+ fip.command('create').description('Create a floating IP')
18
+ .requiredOption('--type <type>', 'IP type (ipv4, ipv6)')
19
+ .option('--name <name>', 'Name')
20
+ .option('--description <desc>', 'Description')
21
+ .option('--home-location <loc>', 'Home location')
22
+ .option('--server <id>', 'Server to assign to')
23
+ .action(cloudAction(async (client, options) => {
24
+ const { floating_ip: ip } = await client.createFloatingIp({ type: options.type, name: options.name, description: options.description, home_location: options.homeLocation, server: options.server ? parseInt(options.server) : undefined });
25
+ console.log(fmt.success(`Floating IP created (ID: ${ip.id}, IP: ${ip.ip})`));
26
+ }));
27
+ fip.command('delete <id>').description('Delete a floating IP')
28
+ .option('-y, --yes', 'Skip confirmation')
29
+ .action(cloudAction(async (client, id, options) => {
30
+ if (!await cloudConfirm(`Delete floating IP ${id}?`, options))
31
+ return;
32
+ await client.deleteFloatingIp(parseInt(id));
33
+ console.log(fmt.success(`Floating IP ${id} deleted.`));
34
+ }));
35
+ fip.command('update <id>').description('Update floating IP')
36
+ .option('--name <name>', 'New name')
37
+ .option('--description <desc>', 'New description')
38
+ .action(cloudAction(async (client, id, options) => {
39
+ await client.updateFloatingIp(parseInt(id), { name: options.name, description: options.description });
40
+ console.log(fmt.success(`Floating IP ${id} updated.`));
41
+ }));
42
+ fip.command('assign <id> <server>').description('Assign floating IP to server')
43
+ .action(cloudAction(async (client, id, server) => {
44
+ await client.assignFloatingIp(parseInt(id), parseInt(server));
45
+ console.log(fmt.success(`Floating IP ${id} assigned to server ${server}.`));
46
+ }));
47
+ fip.command('unassign <id>').description('Unassign floating IP')
48
+ .action(cloudAction(async (client, id) => {
49
+ await client.unassignFloatingIp(parseInt(id));
50
+ console.log(fmt.success(`Floating IP ${id} unassigned.`));
51
+ }));
52
+ fip.command('enable-protection <id>').description('Enable delete protection')
53
+ .action(cloudAction(async (client, id) => {
54
+ await client.changeFloatingIpProtection(parseInt(id), true);
55
+ console.log(fmt.success(`Protection enabled for floating IP ${id}.`));
56
+ }));
57
+ fip.command('disable-protection <id>').description('Disable delete protection')
58
+ .action(cloudAction(async (client, id) => {
59
+ await client.changeFloatingIpProtection(parseInt(id), false);
60
+ console.log(fmt.success(`Protection disabled for floating IP ${id}.`));
61
+ }));
62
+ fip.command('set-rdns <id>').description('Set reverse DNS')
63
+ .requiredOption('--ip <ip>', 'IP address')
64
+ .requiredOption('--dns-ptr <ptr>', 'DNS pointer (empty to reset)')
65
+ .action(cloudAction(async (client, id, options) => {
66
+ await client.changeFloatingIpDnsPtr(parseInt(id), options.ip, options.dnsPtr || null);
67
+ console.log(fmt.success('rDNS updated.'));
68
+ }));
69
+ fip.command('add-label <id> <label>').description('Add a label (key=value)')
70
+ .action(cloudAction(async (client, id, label) => {
71
+ const ip = await client.getFloatingIp(parseInt(id));
72
+ const [key, value] = label.split('=');
73
+ await client.updateFloatingIp(parseInt(id), { labels: { ...ip.labels, [key]: value || '' } });
74
+ console.log(fmt.success(`Label '${key}' added.`));
75
+ }));
76
+ fip.command('remove-label <id> <key>').description('Remove a label')
77
+ .action(cloudAction(async (client, id, key) => {
78
+ const ip = await client.getFloatingIp(parseInt(id));
79
+ const labels = Object.fromEntries(Object.entries(ip.labels).filter(([k]) => k !== key));
80
+ await client.updateFloatingIp(parseInt(id), { labels });
81
+ console.log(fmt.success(`Label '${key}' removed.`));
82
+ }));
83
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerImageCommands(parent: Command): void;
@@ -0,0 +1,60 @@
1
+ import { cloudAction, cloudOutput, cloudConfirm } from '../helpers.js';
2
+ import * as fmt from '../../shared/formatter.js';
3
+ import * as cloudFmt from '../formatter.js';
4
+ export function registerImageCommands(parent) {
5
+ const image = parent.command('image').description('Image management');
6
+ image.command('list').alias('ls').description('List all images')
7
+ .option('-l, --label-selector <selector>', 'Label selector')
8
+ .option('-s, --sort <field>', 'Sort by field')
9
+ .option('--type <type>', 'Filter by type (system, snapshot, backup, app)')
10
+ .option('--architecture <arch>', 'Filter by architecture (x86, arm)')
11
+ .option('--status <status>', 'Filter by status')
12
+ .action(cloudAction(async (client, options) => {
13
+ const images = await client.listImages({ label_selector: options.labelSelector, sort: options.sort, type: options.type, architecture: options.architecture, status: options.status });
14
+ cloudOutput(images, cloudFmt.formatImageList, options);
15
+ }));
16
+ image.command('describe <id>').description('Show image details')
17
+ .action(cloudAction(async (client, id, options) => {
18
+ const img = await client.getImage(parseInt(id));
19
+ cloudOutput(img, cloudFmt.formatImageDetails, options);
20
+ }));
21
+ image.command('update <id>').description('Update image')
22
+ .option('--description <desc>', 'New description')
23
+ .option('--type <type>', 'New type (snapshot)')
24
+ .action(cloudAction(async (client, id, options) => {
25
+ await client.updateImage(parseInt(id), { description: options.description, type: options.type });
26
+ console.log(fmt.success(`Image ${id} updated.`));
27
+ }));
28
+ image.command('delete <id>').description('Delete an image')
29
+ .option('-y, --yes', 'Skip confirmation')
30
+ .action(cloudAction(async (client, id, options) => {
31
+ if (!await cloudConfirm(`Delete image ${id}?`, options))
32
+ return;
33
+ await client.deleteImage(parseInt(id));
34
+ console.log(fmt.success(`Image ${id} deleted.`));
35
+ }));
36
+ image.command('enable-protection <id>').description('Enable delete protection')
37
+ .action(cloudAction(async (client, id) => {
38
+ await client.changeImageProtection(parseInt(id), true);
39
+ console.log(fmt.success(`Protection enabled for image ${id}.`));
40
+ }));
41
+ image.command('disable-protection <id>').description('Disable delete protection')
42
+ .action(cloudAction(async (client, id) => {
43
+ await client.changeImageProtection(parseInt(id), false);
44
+ console.log(fmt.success(`Protection disabled for image ${id}.`));
45
+ }));
46
+ image.command('add-label <id> <label>').description('Add a label (key=value)')
47
+ .action(cloudAction(async (client, id, label) => {
48
+ const img = await client.getImage(parseInt(id));
49
+ const [key, value] = label.split('=');
50
+ await client.updateImage(parseInt(id), { labels: { ...img.labels, [key]: value || '' } });
51
+ console.log(fmt.success(`Label '${key}' added.`));
52
+ }));
53
+ image.command('remove-label <id> <key>').description('Remove a label')
54
+ .action(cloudAction(async (client, id, key) => {
55
+ const img = await client.getImage(parseInt(id));
56
+ const labels = Object.fromEntries(Object.entries(img.labels).filter(([k]) => k !== key));
57
+ await client.updateImage(parseInt(id), { labels });
58
+ console.log(fmt.success(`Label '${key}' removed.`));
59
+ }));
60
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerCloudCommands(parent: Command): void;
@@ -0,0 +1,41 @@
1
+ import { registerContextCommands } from './context.js';
2
+ import { registerDatacenterCommands } from './datacenter.js';
3
+ import { registerLocationCommands } from './location.js';
4
+ import { registerServerTypeCommands } from './server-type.js';
5
+ import { registerLoadBalancerTypeCommands } from './load-balancer-type.js';
6
+ import { registerIsoCommands } from './iso.js';
7
+ import { registerCloudServerCommands } from './server.js';
8
+ import { registerNetworkCommands } from './network.js';
9
+ import { registerCloudFirewallCommands } from './firewall.js';
10
+ import { registerFloatingIpCommands } from './floating-ip.js';
11
+ import { registerPrimaryIpCommands } from './primary-ip.js';
12
+ import { registerVolumeCommands } from './volume.js';
13
+ import { registerLoadBalancerCommands } from './load-balancer.js';
14
+ import { registerImageCommands } from './image.js';
15
+ import { registerCloudSshKeyCommands } from './ssh-key.js';
16
+ import { registerCertificateCommands } from './certificate.js';
17
+ import { registerPlacementGroupCommands } from './placement-group.js';
18
+ export function registerCloudCommands(parent) {
19
+ const cloud = parent.command('cloud').description('Hetzner Cloud API management');
20
+ cloud.option('--token <token>', 'Cloud API token (overrides context)');
21
+ registerContextCommands(cloud);
22
+ // Read-only resources
23
+ registerDatacenterCommands(cloud);
24
+ registerLocationCommands(cloud);
25
+ registerServerTypeCommands(cloud);
26
+ registerLoadBalancerTypeCommands(cloud);
27
+ registerIsoCommands(cloud);
28
+ // Core resources
29
+ registerCloudServerCommands(cloud);
30
+ registerNetworkCommands(cloud);
31
+ registerCloudFirewallCommands(cloud);
32
+ registerFloatingIpCommands(cloud);
33
+ registerPrimaryIpCommands(cloud);
34
+ registerVolumeCommands(cloud);
35
+ registerLoadBalancerCommands(cloud);
36
+ registerImageCommands(cloud);
37
+ // Ancillary resources
38
+ registerCloudSshKeyCommands(cloud);
39
+ registerCertificateCommands(cloud);
40
+ registerPlacementGroupCommands(cloud);
41
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerIsoCommands(parent: Command): void;
@@ -0,0 +1,22 @@
1
+ import { cloudAction, cloudOutput } from '../helpers.js';
2
+ import * as cloudFmt from '../formatter.js';
3
+ export function registerIsoCommands(parent) {
4
+ const iso = parent.command('iso').description('ISO image management');
5
+ iso
6
+ .command('list')
7
+ .alias('ls')
8
+ .description('List all ISOs')
9
+ .option('-n, --name <name>', 'Filter by name')
10
+ .option('-a, --architecture <arch>', 'Filter by architecture')
11
+ .action(cloudAction(async (client, options) => {
12
+ const isos = await client.listIsos({ name: options.name, architecture: options.architecture });
13
+ cloudOutput(isos, cloudFmt.formatIsoList, options);
14
+ }));
15
+ iso
16
+ .command('describe <id>')
17
+ .description('Show ISO details')
18
+ .action(cloudAction(async (client, id, options) => {
19
+ const iso = await client.getIso(parseInt(id));
20
+ cloudOutput(iso, cloudFmt.formatIsoDetails, options);
21
+ }));
22
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerLoadBalancerTypeCommands(parent: Command): void;
@@ -0,0 +1,20 @@
1
+ import { cloudAction, cloudOutput } from '../helpers.js';
2
+ import * as cloudFmt from '../formatter.js';
3
+ export function registerLoadBalancerTypeCommands(parent) {
4
+ const lbType = parent.command('load-balancer-type').description('Load balancer type information');
5
+ lbType
6
+ .command('list')
7
+ .alias('ls')
8
+ .description('List all load balancer types')
9
+ .action(cloudAction(async (client, options) => {
10
+ const types = await client.listLoadBalancerTypes();
11
+ cloudOutput(types, cloudFmt.formatLoadBalancerTypeList, options);
12
+ }));
13
+ lbType
14
+ .command('describe <id>')
15
+ .description('Show load balancer type details')
16
+ .action(cloudAction(async (client, id, options) => {
17
+ const type = await client.getLoadBalancerType(parseInt(id));
18
+ cloudOutput(type, cloudFmt.formatLoadBalancerTypeDetails, options);
19
+ }));
20
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerLoadBalancerCommands(parent: Command): void;