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,413 @@
1
+ import { colorize, info, heading, formatStatus, formatBytes, formatDate, createTable, } from '../shared/formatter.js';
2
+ // Context
3
+ export function formatContextList(contexts) {
4
+ if (contexts.length === 0) {
5
+ return info('No contexts configured. Run: hetzner cloud context create');
6
+ }
7
+ const table = createTable(['Name', 'Active']);
8
+ for (const ctx of contexts) {
9
+ table.push([
10
+ ctx.name,
11
+ ctx.active ? colorize('*', 'green') : '',
12
+ ]);
13
+ }
14
+ return table.toString();
15
+ }
16
+ // Datacenters
17
+ export function formatDatacenterList(datacenters) {
18
+ if (datacenters.length === 0)
19
+ return info('No datacenters found.');
20
+ const table = createTable(['ID', 'Name', 'Description', 'Location']);
21
+ for (const dc of datacenters) {
22
+ table.push([dc.id.toString(), dc.name, dc.description, dc.location.name]);
23
+ }
24
+ return table.toString();
25
+ }
26
+ export function formatDatacenterDetails(dc) {
27
+ const lines = [];
28
+ lines.push(heading(`Datacenter: ${dc.name}`));
29
+ lines.push('');
30
+ const table = createTable(['Property', 'Value']);
31
+ table.push(['ID', dc.id.toString()], ['Name', dc.name], ['Description', dc.description], ['Location', dc.location.description], ['City', dc.location.city], ['Country', dc.location.country], ['Network Zone', dc.location.network_zone]);
32
+ lines.push(table.toString());
33
+ return lines.join('\n');
34
+ }
35
+ // Locations
36
+ export function formatLocationList(locations) {
37
+ if (locations.length === 0)
38
+ return info('No locations found.');
39
+ const table = createTable(['ID', 'Name', 'Description', 'City', 'Country', 'Network Zone']);
40
+ for (const loc of locations) {
41
+ table.push([loc.id.toString(), loc.name, loc.description, loc.city, loc.country, loc.network_zone]);
42
+ }
43
+ return table.toString();
44
+ }
45
+ export function formatLocationDetails(loc) {
46
+ const lines = [];
47
+ lines.push(heading(`Location: ${loc.name}`));
48
+ lines.push('');
49
+ const table = createTable(['Property', 'Value']);
50
+ table.push(['ID', loc.id.toString()], ['Name', loc.name], ['Description', loc.description], ['City', loc.city], ['Country', loc.country], ['Latitude', loc.latitude.toString()], ['Longitude', loc.longitude.toString()], ['Network Zone', loc.network_zone]);
51
+ lines.push(table.toString());
52
+ return lines.join('\n');
53
+ }
54
+ // Server Types
55
+ export function formatServerTypeList(types) {
56
+ if (types.length === 0)
57
+ return info('No server types found.');
58
+ const table = createTable(['ID', 'Name', 'Cores', 'Memory', 'Disk', 'Storage', 'CPU', 'Arch']);
59
+ for (const st of types) {
60
+ table.push([
61
+ st.id.toString(),
62
+ st.name,
63
+ st.cores.toString(),
64
+ `${st.memory} GB`,
65
+ `${st.disk} GB`,
66
+ st.storage_type,
67
+ st.cpu_type,
68
+ st.architecture,
69
+ ]);
70
+ }
71
+ return table.toString();
72
+ }
73
+ export function formatServerTypeDetails(st) {
74
+ const lines = [];
75
+ lines.push(heading(`Server Type: ${st.name}`));
76
+ lines.push('');
77
+ const table = createTable(['Property', 'Value']);
78
+ table.push(['ID', st.id.toString()], ['Name', st.name], ['Description', st.description], ['Cores', st.cores.toString()], ['Memory', `${st.memory} GB`], ['Disk', `${st.disk} GB`], ['Storage Type', st.storage_type], ['CPU Type', st.cpu_type], ['Architecture', st.architecture], ['Deprecated', st.deprecated ? colorize('Yes', 'red') : 'No']);
79
+ lines.push(table.toString());
80
+ if (st.prices.length > 0) {
81
+ lines.push('');
82
+ lines.push(colorize('Pricing:', 'bold'));
83
+ const priceTable = createTable(['Location', 'Hourly', 'Monthly', 'Traffic']);
84
+ for (const p of st.prices) {
85
+ priceTable.push([p.location, `€${p.price_hourly.gross}`, `€${p.price_monthly.gross}`, formatBytes(p.included_traffic)]);
86
+ }
87
+ lines.push(priceTable.toString());
88
+ }
89
+ return lines.join('\n');
90
+ }
91
+ // Load Balancer Types
92
+ export function formatLoadBalancerTypeList(types) {
93
+ if (types.length === 0)
94
+ return info('No load balancer types found.');
95
+ const table = createTable(['ID', 'Name', 'Description', 'Max Conn', 'Max Services', 'Max Targets']);
96
+ for (const lbt of types) {
97
+ table.push([
98
+ lbt.id.toString(),
99
+ lbt.name,
100
+ lbt.description,
101
+ lbt.max_connections.toString(),
102
+ lbt.max_services.toString(),
103
+ lbt.max_targets.toString(),
104
+ ]);
105
+ }
106
+ return table.toString();
107
+ }
108
+ export function formatLoadBalancerTypeDetails(lbt) {
109
+ const lines = [];
110
+ lines.push(heading(`Load Balancer Type: ${lbt.name}`));
111
+ lines.push('');
112
+ const table = createTable(['Property', 'Value']);
113
+ table.push(['ID', lbt.id.toString()], ['Name', lbt.name], ['Description', lbt.description], ['Max Connections', lbt.max_connections.toString()], ['Max Services', lbt.max_services.toString()], ['Max Targets', lbt.max_targets.toString()], ['Max Certificates', lbt.max_assigned_certificates.toString()], ['Deprecated', lbt.deprecated ? colorize('Yes', 'red') : 'No']);
114
+ lines.push(table.toString());
115
+ return lines.join('\n');
116
+ }
117
+ // ISOs
118
+ export function formatIsoList(isos) {
119
+ if (isos.length === 0)
120
+ return info('No ISOs found.');
121
+ const table = createTable(['ID', 'Name', 'Type', 'Architecture']);
122
+ for (const iso of isos) {
123
+ table.push([iso.id.toString(), iso.name, iso.type, iso.architecture || '-']);
124
+ }
125
+ return table.toString();
126
+ }
127
+ export function formatIsoDetails(iso) {
128
+ const lines = [];
129
+ lines.push(heading(`ISO: ${iso.name}`));
130
+ lines.push('');
131
+ const table = createTable(['Property', 'Value']);
132
+ table.push(['ID', iso.id.toString()], ['Name', iso.name], ['Description', iso.description], ['Type', iso.type], ['Architecture', iso.architecture || '-']);
133
+ lines.push(table.toString());
134
+ return lines.join('\n');
135
+ }
136
+ // Cloud Servers
137
+ export function formatCloudServerList(servers) {
138
+ if (servers.length === 0)
139
+ return info('No servers found.');
140
+ const table = createTable(['ID', 'Name', 'Status', 'Type', 'DC', 'IPv4', 'Labels']);
141
+ for (const srv of servers) {
142
+ const labels = Object.entries(srv.labels).map(([k, v]) => `${k}=${v}`).join(', ') || '-';
143
+ table.push([
144
+ srv.id.toString(),
145
+ srv.name,
146
+ formatStatus(srv.status),
147
+ srv.server_type.name,
148
+ srv.datacenter.name,
149
+ srv.public_net.ipv4?.ip || '-',
150
+ labels.length > 30 ? labels.substring(0, 27) + '...' : labels,
151
+ ]);
152
+ }
153
+ return table.toString();
154
+ }
155
+ export function formatCloudServerDetails(srv) {
156
+ const lines = [];
157
+ lines.push(heading(`Server: ${srv.name}`));
158
+ lines.push('');
159
+ const table = createTable(['Property', 'Value']);
160
+ table.push(['ID', srv.id.toString()], ['Name', srv.name], ['Status', formatStatus(srv.status)], ['Server Type', `${srv.server_type.name} (${srv.server_type.cores} cores, ${srv.server_type.memory} GB RAM)`], ['Datacenter', `${srv.datacenter.name} (${srv.datacenter.location.city})`], ['Image', srv.image ? `${srv.image.description} (${srv.image.name || srv.image.id})` : '-'], ['IPv4', srv.public_net.ipv4?.ip || '-'], ['IPv6', srv.public_net.ipv6?.ip || '-'], ['Primary Disk', `${srv.primary_disk_size} GB`], ['Rescue', srv.rescue_enabled ? colorize('Enabled', 'yellow') : 'Disabled'], ['Locked', srv.locked ? colorize('Yes', 'red') : 'No'], ['Backup Window', srv.backup_window || 'Disabled'], ['Protection', srv.protection.delete ? colorize('Delete protected', 'yellow') : 'None'], ['Created', formatDate(srv.created)]);
161
+ lines.push(table.toString());
162
+ if (Object.keys(srv.labels).length > 0) {
163
+ lines.push('');
164
+ lines.push(colorize('Labels:', 'bold'));
165
+ for (const [k, v] of Object.entries(srv.labels)) {
166
+ lines.push(` ${k} = ${v}`);
167
+ }
168
+ }
169
+ if (srv.private_net.length > 0) {
170
+ lines.push('');
171
+ lines.push(colorize('Private Networks:', 'bold'));
172
+ const netTable = createTable(['Network', 'IP', 'Alias IPs']);
173
+ for (const pn of srv.private_net) {
174
+ netTable.push([pn.network.toString(), pn.ip, pn.alias_ips.join(', ') || '-']);
175
+ }
176
+ lines.push(netTable.toString());
177
+ }
178
+ if (srv.volumes.length > 0) {
179
+ lines.push('');
180
+ lines.push(colorize('Volumes:', 'bold'));
181
+ lines.push(` ${srv.volumes.join(', ')}`);
182
+ }
183
+ return lines.join('\n');
184
+ }
185
+ // Networks
186
+ export function formatNetworkList(networks) {
187
+ if (networks.length === 0)
188
+ return info('No networks found.');
189
+ const table = createTable(['ID', 'Name', 'IP Range', 'Subnets', 'Servers']);
190
+ for (const net of networks) {
191
+ table.push([net.id.toString(), net.name, net.ip_range, net.subnets.length.toString(), net.servers.length.toString()]);
192
+ }
193
+ return table.toString();
194
+ }
195
+ export function formatNetworkDetails(net) {
196
+ const lines = [];
197
+ lines.push(heading(`Network: ${net.name}`));
198
+ lines.push('');
199
+ const table = createTable(['Property', 'Value']);
200
+ table.push(['ID', net.id.toString()], ['Name', net.name], ['IP Range', net.ip_range], ['Servers', net.servers.length.toString()], ['Protection', net.protection.delete ? colorize('Delete protected', 'yellow') : 'None'], ['Created', formatDate(net.created)]);
201
+ lines.push(table.toString());
202
+ if (net.subnets.length > 0) {
203
+ lines.push('');
204
+ lines.push(colorize('Subnets:', 'bold'));
205
+ const subTable = createTable(['IP Range', 'Type', 'Network Zone', 'Gateway']);
206
+ for (const s of net.subnets) {
207
+ subTable.push([s.ip_range, s.type, s.network_zone, s.gateway]);
208
+ }
209
+ lines.push(subTable.toString());
210
+ }
211
+ if (net.routes.length > 0) {
212
+ lines.push('');
213
+ lines.push(colorize('Routes:', 'bold'));
214
+ const routeTable = createTable(['Destination', 'Gateway']);
215
+ for (const r of net.routes) {
216
+ routeTable.push([r.destination, r.gateway]);
217
+ }
218
+ lines.push(routeTable.toString());
219
+ }
220
+ return lines.join('\n');
221
+ }
222
+ // Cloud Firewalls
223
+ export function formatCloudFirewallList(firewalls) {
224
+ if (firewalls.length === 0)
225
+ return info('No firewalls found.');
226
+ const table = createTable(['ID', 'Name', 'Rules', 'Applied To', 'Created']);
227
+ for (const fw of firewalls) {
228
+ table.push([fw.id.toString(), fw.name, fw.rules.length.toString(), fw.applied_to.length.toString(), formatDate(fw.created)]);
229
+ }
230
+ return table.toString();
231
+ }
232
+ export function formatCloudFirewallDetails(fw) {
233
+ const lines = [];
234
+ lines.push(heading(`Firewall: ${fw.name}`));
235
+ lines.push('');
236
+ const table = createTable(['Property', 'Value']);
237
+ table.push(['ID', fw.id.toString()], ['Name', fw.name], ['Created', formatDate(fw.created)]);
238
+ lines.push(table.toString());
239
+ if (fw.rules.length > 0) {
240
+ lines.push('');
241
+ lines.push(colorize('Rules:', 'bold'));
242
+ const ruleTable = createTable(['Direction', 'Protocol', 'Port', 'Source IPs', 'Description']);
243
+ for (const r of fw.rules) {
244
+ ruleTable.push([r.direction, r.protocol, r.port || 'any', r.source_ips.join(', ').substring(0, 30) || '-', r.description || '-']);
245
+ }
246
+ lines.push(ruleTable.toString());
247
+ }
248
+ return lines.join('\n');
249
+ }
250
+ // Floating IPs
251
+ export function formatFloatingIpList(ips) {
252
+ if (ips.length === 0)
253
+ return info('No floating IPs found.');
254
+ const table = createTable(['ID', 'Name', 'IP', 'Type', 'Server', 'Location', 'Blocked']);
255
+ for (const ip of ips) {
256
+ table.push([ip.id.toString(), ip.name, ip.ip, ip.type, ip.server?.toString() || '-', ip.home_location.name, ip.blocked ? colorize('Yes', 'red') : 'No']);
257
+ }
258
+ return table.toString();
259
+ }
260
+ export function formatFloatingIpDetails(ip) {
261
+ const lines = [];
262
+ lines.push(heading(`Floating IP: ${ip.name}`));
263
+ lines.push('');
264
+ const table = createTable(['Property', 'Value']);
265
+ table.push(['ID', ip.id.toString()], ['Name', ip.name], ['Description', ip.description || '-'], ['IP', ip.ip], ['Type', ip.type], ['Server', ip.server?.toString() || 'Unassigned'], ['Home Location', ip.home_location.name], ['Blocked', ip.blocked ? colorize('Yes', 'red') : 'No'], ['Protection', ip.protection.delete ? colorize('Delete protected', 'yellow') : 'None'], ['Created', formatDate(ip.created)]);
266
+ lines.push(table.toString());
267
+ return lines.join('\n');
268
+ }
269
+ // Primary IPs
270
+ export function formatPrimaryIpList(ips) {
271
+ if (ips.length === 0)
272
+ return info('No primary IPs found.');
273
+ const table = createTable(['ID', 'Name', 'IP', 'Type', 'Assignee', 'DC', 'Auto Delete']);
274
+ for (const ip of ips) {
275
+ table.push([ip.id.toString(), ip.name, ip.ip, ip.type, ip.assignee_id?.toString() || '-', ip.datacenter.name, ip.auto_delete ? 'Yes' : 'No']);
276
+ }
277
+ return table.toString();
278
+ }
279
+ export function formatPrimaryIpDetails(ip) {
280
+ const lines = [];
281
+ lines.push(heading(`Primary IP: ${ip.name}`));
282
+ lines.push('');
283
+ const table = createTable(['Property', 'Value']);
284
+ table.push(['ID', ip.id.toString()], ['Name', ip.name], ['IP', ip.ip], ['Type', ip.type], ['Assignee', ip.assignee_id?.toString() || 'Unassigned'], ['Datacenter', ip.datacenter.name], ['Auto Delete', ip.auto_delete ? 'Yes' : 'No'], ['Blocked', ip.blocked ? colorize('Yes', 'red') : 'No'], ['Protection', ip.protection.delete ? colorize('Delete protected', 'yellow') : 'None'], ['Created', formatDate(ip.created)]);
285
+ lines.push(table.toString());
286
+ return lines.join('\n');
287
+ }
288
+ // Volumes
289
+ export function formatVolumeList(volumes) {
290
+ if (volumes.length === 0)
291
+ return info('No volumes found.');
292
+ const table = createTable(['ID', 'Name', 'Size', 'Server', 'Status', 'Location', 'Format']);
293
+ for (const vol of volumes) {
294
+ table.push([vol.id.toString(), vol.name, `${vol.size} GB`, vol.server?.toString() || '-', formatStatus(vol.status), vol.location.name, vol.format || '-']);
295
+ }
296
+ return table.toString();
297
+ }
298
+ export function formatVolumeDetails(vol) {
299
+ const lines = [];
300
+ lines.push(heading(`Volume: ${vol.name}`));
301
+ lines.push('');
302
+ const table = createTable(['Property', 'Value']);
303
+ table.push(['ID', vol.id.toString()], ['Name', vol.name], ['Size', `${vol.size} GB`], ['Server', vol.server?.toString() || 'Unattached'], ['Status', formatStatus(vol.status)], ['Location', vol.location.name], ['Linux Device', vol.linux_device || '-'], ['Format', vol.format || '-'], ['Protection', vol.protection.delete ? colorize('Delete protected', 'yellow') : 'None'], ['Created', formatDate(vol.created)]);
304
+ lines.push(table.toString());
305
+ return lines.join('\n');
306
+ }
307
+ // Load Balancers
308
+ export function formatLoadBalancerList(lbs) {
309
+ if (lbs.length === 0)
310
+ return info('No load balancers found.');
311
+ const table = createTable(['ID', 'Name', 'IPv4', 'Type', 'Location', 'Targets', 'Services']);
312
+ for (const lb of lbs) {
313
+ table.push([lb.id.toString(), lb.name, lb.public_net.ipv4.ip, lb.load_balancer_type.name, lb.location.name, lb.targets.length.toString(), lb.services.length.toString()]);
314
+ }
315
+ return table.toString();
316
+ }
317
+ export function formatLoadBalancerDetails(lb) {
318
+ const lines = [];
319
+ lines.push(heading(`Load Balancer: ${lb.name}`));
320
+ lines.push('');
321
+ const table = createTable(['Property', 'Value']);
322
+ table.push(['ID', lb.id.toString()], ['Name', lb.name], ['Type', lb.load_balancer_type.name], ['Location', lb.location.name], ['IPv4', lb.public_net.ipv4.ip], ['IPv6', lb.public_net.ipv6.ip], ['Public Net', lb.public_net.enabled ? 'Enabled' : 'Disabled'], ['Algorithm', lb.algorithm.type], ['Protection', lb.protection.delete ? colorize('Delete protected', 'yellow') : 'None'], ['Created', formatDate(lb.created)]);
323
+ lines.push(table.toString());
324
+ if (lb.services.length > 0) {
325
+ lines.push('');
326
+ lines.push(colorize('Services:', 'bold'));
327
+ const svcTable = createTable(['Protocol', 'Listen Port', 'Destination Port', 'Proxyprotocol']);
328
+ for (const svc of lb.services) {
329
+ svcTable.push([svc.protocol, svc.listen_port.toString(), svc.destination_port.toString(), svc.proxyprotocol ? 'Yes' : 'No']);
330
+ }
331
+ lines.push(svcTable.toString());
332
+ }
333
+ return lines.join('\n');
334
+ }
335
+ // Images
336
+ export function formatImageList(images) {
337
+ if (images.length === 0)
338
+ return info('No images found.');
339
+ const table = createTable(['ID', 'Type', 'Name', 'Description', 'OS', 'Arch', 'Disk', 'Status']);
340
+ for (const img of images) {
341
+ table.push([img.id.toString(), img.type, img.name || '-', img.description.substring(0, 30), img.os_flavor, img.architecture, `${img.disk_size} GB`, formatStatus(img.status)]);
342
+ }
343
+ return table.toString();
344
+ }
345
+ export function formatImageDetails(img) {
346
+ const lines = [];
347
+ lines.push(heading(`Image: ${img.description}`));
348
+ lines.push('');
349
+ const table = createTable(['Property', 'Value']);
350
+ table.push(['ID', img.id.toString()], ['Type', img.type], ['Name', img.name || '-'], ['Description', img.description], ['OS Flavor', img.os_flavor], ['OS Version', img.os_version || '-'], ['Architecture', img.architecture], ['Disk Size', `${img.disk_size} GB`], ['Image Size', img.image_size ? formatBytes(img.image_size * 1024 * 1024) : '-'], ['Status', formatStatus(img.status)], ['Rapid Deploy', img.rapid_deploy ? 'Yes' : 'No'], ['Protection', img.protection.delete ? colorize('Delete protected', 'yellow') : 'None'], ['Created', formatDate(img.created)]);
351
+ lines.push(table.toString());
352
+ return lines.join('\n');
353
+ }
354
+ // Cloud SSH Keys
355
+ export function formatCloudSshKeyList(keys) {
356
+ if (keys.length === 0)
357
+ return info('No SSH keys found.');
358
+ const table = createTable(['ID', 'Name', 'Fingerprint', 'Created']);
359
+ for (const key of keys) {
360
+ table.push([key.id.toString(), key.name, key.fingerprint, formatDate(key.created)]);
361
+ }
362
+ return table.toString();
363
+ }
364
+ export function formatCloudSshKeyDetails(key) {
365
+ const lines = [];
366
+ lines.push(heading(`SSH Key: ${key.name}`));
367
+ lines.push('');
368
+ const table = createTable(['Property', 'Value']);
369
+ table.push(['ID', key.id.toString()], ['Name', key.name], ['Fingerprint', key.fingerprint], ['Created', formatDate(key.created)]);
370
+ lines.push(table.toString());
371
+ lines.push('');
372
+ lines.push(colorize('Public Key:', 'bold'));
373
+ lines.push(key.public_key);
374
+ return lines.join('\n');
375
+ }
376
+ // Certificates
377
+ export function formatCertificateList(certs) {
378
+ if (certs.length === 0)
379
+ return info('No certificates found.');
380
+ const table = createTable(['ID', 'Name', 'Type', 'Domains', 'Valid Until', 'Created']);
381
+ for (const cert of certs) {
382
+ table.push([cert.id.toString(), cert.name, cert.type, cert.domain_names.join(', ').substring(0, 30), formatDate(cert.not_valid_after), formatDate(cert.created)]);
383
+ }
384
+ return table.toString();
385
+ }
386
+ export function formatCertificateDetails(cert) {
387
+ const lines = [];
388
+ lines.push(heading(`Certificate: ${cert.name}`));
389
+ lines.push('');
390
+ const table = createTable(['Property', 'Value']);
391
+ table.push(['ID', cert.id.toString()], ['Name', cert.name], ['Type', cert.type], ['Domains', cert.domain_names.join(', ')], ['Fingerprint', cert.fingerprint || '-'], ['Valid From', formatDate(cert.not_valid_before)], ['Valid Until', formatDate(cert.not_valid_after)], ['Created', formatDate(cert.created)]);
392
+ lines.push(table.toString());
393
+ return lines.join('\n');
394
+ }
395
+ // Placement Groups
396
+ export function formatPlacementGroupList(groups) {
397
+ if (groups.length === 0)
398
+ return info('No placement groups found.');
399
+ const table = createTable(['ID', 'Name', 'Type', 'Servers', 'Created']);
400
+ for (const pg of groups) {
401
+ table.push([pg.id.toString(), pg.name, pg.type, pg.servers.length.toString(), formatDate(pg.created)]);
402
+ }
403
+ return table.toString();
404
+ }
405
+ export function formatPlacementGroupDetails(pg) {
406
+ const lines = [];
407
+ lines.push(heading(`Placement Group: ${pg.name}`));
408
+ lines.push('');
409
+ const table = createTable(['Property', 'Value']);
410
+ table.push(['ID', pg.id.toString()], ['Name', pg.name], ['Type', pg.type], ['Servers', pg.servers.length > 0 ? pg.servers.join(', ') : '-'], ['Created', formatDate(pg.created)]);
411
+ lines.push(table.toString());
412
+ return lines.join('\n');
413
+ }
@@ -0,0 +1,18 @@
1
+ import { HetznerCloudClient } from './client.js';
2
+ export interface CloudActionOptions {
3
+ token?: string;
4
+ json?: boolean;
5
+ yes?: boolean;
6
+ }
7
+ /**
8
+ * Wrap a cloud API action with token resolution and error handling.
9
+ */
10
+ export declare function cloudAction<T extends unknown[]>(fn: (client: HetznerCloudClient, ...args: T) => Promise<void>): (...args: [...T, CloudActionOptions]) => Promise<void>;
11
+ /**
12
+ * Output data as JSON or formatted table based on options.
13
+ */
14
+ export declare function cloudOutput<T>(data: T, formatter: (data: T) => string, options: CloudActionOptions): void;
15
+ /**
16
+ * Confirm destructive action unless --yes flag is set.
17
+ */
18
+ export declare function cloudConfirm(message: string, options: CloudActionOptions, defaultValue?: boolean): Promise<boolean>;
@@ -0,0 +1,48 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import * as fmt from '../shared/formatter.js';
3
+ import { HetznerCloudClient } from './client.js';
4
+ import { resolveToken } from './context.js';
5
+ /**
6
+ * Wrap a cloud API action with token resolution and error handling.
7
+ */
8
+ export function cloudAction(fn) {
9
+ return async (...args) => {
10
+ const options = args[args.length - 1];
11
+ try {
12
+ const token = await resolveToken(options.token);
13
+ const client = new HetznerCloudClient(token);
14
+ await fn(client, ...args.slice(0, -1));
15
+ }
16
+ catch (error) {
17
+ if (error instanceof Error) {
18
+ if (error.message.includes('ExitPromptError') || error.name === 'ExitPromptError') {
19
+ process.exit(0);
20
+ }
21
+ console.error(fmt.error(error.message));
22
+ }
23
+ else {
24
+ console.error(fmt.error('An unknown error occurred'));
25
+ }
26
+ process.exit(1);
27
+ }
28
+ };
29
+ }
30
+ /**
31
+ * Output data as JSON or formatted table based on options.
32
+ */
33
+ export function cloudOutput(data, formatter, options) {
34
+ console.log(options.json ? fmt.formatJson(data) : formatter(data));
35
+ }
36
+ /**
37
+ * Confirm destructive action unless --yes flag is set.
38
+ */
39
+ export async function cloudConfirm(message, options, defaultValue = false) {
40
+ if (options.yes)
41
+ return true;
42
+ const confirmed = await confirm({ message, default: defaultValue });
43
+ if (!confirmed) {
44
+ console.log('Aborted.');
45
+ return false;
46
+ }
47
+ return true;
48
+ }