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,4 @@
1
+ import type { AuctionServer, AuctionFilterOptions, AuctionResponse } from '../types.js';
2
+ export declare function fetchAuctionServers(currency?: 'EUR' | 'USD'): Promise<AuctionResponse>;
3
+ export declare function filterAuctionServers(servers: AuctionServer[], filters: AuctionFilterOptions): AuctionServer[];
4
+ export declare function sortAuctionServers(servers: AuctionServer[], field: string, desc: boolean): AuctionServer[];
@@ -0,0 +1,103 @@
1
+ // ============================================================================
2
+ // Hetzner Auction API Client (public, no auth required)
3
+ // Endpoint: https://www.hetzner.com/_resources/app/data/app/live_data_sb_EUR.json
4
+ // ============================================================================
5
+ const AUCTION_BASE_URL = 'https://www.hetzner.com/_resources/app/data/app';
6
+ export async function fetchAuctionServers(currency = 'EUR') {
7
+ const url = `${AUCTION_BASE_URL}/live_data_sb_${currency}.json`;
8
+ const response = await fetch(url);
9
+ if (!response.ok) {
10
+ throw new Error(`Failed to fetch auction data: HTTP ${response.status}`);
11
+ }
12
+ return response.json();
13
+ }
14
+ export function filterAuctionServers(servers, filters) {
15
+ return servers.filter(s => {
16
+ if (filters.minPrice !== undefined && s.price < filters.minPrice)
17
+ return false;
18
+ if (filters.maxPrice !== undefined && s.price > filters.maxPrice)
19
+ return false;
20
+ if (filters.maxHourlyPrice !== undefined && s.hourly_price > filters.maxHourlyPrice)
21
+ return false;
22
+ if (filters.minRam !== undefined && s.ram_size < filters.minRam)
23
+ return false;
24
+ if (filters.maxRam !== undefined && s.ram_size > filters.maxRam)
25
+ return false;
26
+ if (filters.cpu && !s.cpu.toLowerCase().includes(filters.cpu.toLowerCase()))
27
+ return false;
28
+ if (filters.datacenter && !s.datacenter.toLowerCase().includes(filters.datacenter.toLowerCase()))
29
+ return false;
30
+ if (filters.minDiskSize !== undefined || filters.maxDiskSize !== undefined) {
31
+ const total = [...s.serverDiskData.nvme, ...s.serverDiskData.sata, ...s.serverDiskData.hdd]
32
+ .reduce((sum, d) => sum + d, 0);
33
+ if (filters.minDiskSize !== undefined && total < filters.minDiskSize)
34
+ return false;
35
+ if (filters.maxDiskSize !== undefined && total > filters.maxDiskSize)
36
+ return false;
37
+ }
38
+ if (filters.minDiskCount !== undefined && s.hdd_count < filters.minDiskCount)
39
+ return false;
40
+ if (filters.maxDiskCount !== undefined && s.hdd_count > filters.maxDiskCount)
41
+ return false;
42
+ if (filters.diskType) {
43
+ if (s.serverDiskData[filters.diskType].length === 0)
44
+ return false;
45
+ }
46
+ if (filters.ecc !== undefined && s.is_ecc !== filters.ecc)
47
+ return false;
48
+ if (filters.gpu !== undefined) {
49
+ const hasGpu = s.specials.includes('GPU');
50
+ if (filters.gpu !== hasGpu)
51
+ return false;
52
+ }
53
+ if (filters.inic !== undefined) {
54
+ const hasInic = s.specials.includes('iNIC');
55
+ if (filters.inic !== hasInic)
56
+ return false;
57
+ }
58
+ if (filters.highio !== undefined && s.is_highio !== filters.highio)
59
+ return false;
60
+ if (filters.specials) {
61
+ const search = filters.specials.toLowerCase();
62
+ if (!s.specials.some(sp => sp.toLowerCase().includes(search)))
63
+ return false;
64
+ }
65
+ if (filters.fixedPrice !== undefined && s.fixed_price !== filters.fixedPrice)
66
+ return false;
67
+ if (filters.maxSetupPrice !== undefined && s.setup_price > filters.maxSetupPrice)
68
+ return false;
69
+ if (filters.minCpuCount !== undefined && s.cpu_count < filters.minCpuCount)
70
+ return false;
71
+ if (filters.maxCpuCount !== undefined && s.cpu_count > filters.maxCpuCount)
72
+ return false;
73
+ if (filters.minBandwidth !== undefined && s.bandwidth < filters.minBandwidth)
74
+ return false;
75
+ if (filters.text) {
76
+ const searchText = filters.text.toLowerCase();
77
+ const haystack = s.description.join(' ').toLowerCase();
78
+ if (!haystack.includes(searchText))
79
+ return false;
80
+ }
81
+ return true;
82
+ });
83
+ }
84
+ export function sortAuctionServers(servers, field, desc) {
85
+ const totalDisk = (s) => [...s.serverDiskData.nvme, ...s.serverDiskData.sata, ...s.serverDiskData.hdd].reduce((sum, d) => sum + d, 0);
86
+ const sorted = [...servers].sort((a, b) => {
87
+ switch (field) {
88
+ case 'price': return a.price - b.price;
89
+ case 'hourly': return a.hourly_price - b.hourly_price;
90
+ case 'setup': return a.setup_price - b.setup_price;
91
+ case 'ram': return a.ram_size - b.ram_size;
92
+ case 'disk': return totalDisk(a) - totalDisk(b);
93
+ case 'disk_count': return a.hdd_count - b.hdd_count;
94
+ case 'cpu': return a.cpu.localeCompare(b.cpu);
95
+ case 'cpu_count': return a.cpu_count - b.cpu_count;
96
+ case 'datacenter': return a.datacenter.localeCompare(b.datacenter);
97
+ case 'bandwidth': return a.bandwidth - b.bandwidth;
98
+ case 'next_reduce': return a.next_reduce - b.next_reduce;
99
+ default: return a.price - b.price;
100
+ }
101
+ });
102
+ return desc ? sorted.reverse() : sorted;
103
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerAuctionCommands(parent: Command): void;
@@ -0,0 +1,138 @@
1
+ import { Option } from 'commander';
2
+ import { fetchAuctionServers, filterAuctionServers, sortAuctionServers } from './client.js';
3
+ import { formatAuctionList, formatAuctionDetails } from './formatter.js';
4
+ import { formatJson, error } from '../shared/formatter.js';
5
+ function parseNum(val) {
6
+ if (val === undefined)
7
+ return undefined;
8
+ const n = Number(val);
9
+ return isNaN(n) ? undefined : n;
10
+ }
11
+ export function registerAuctionCommands(parent) {
12
+ const auction = parent.command('auction').description('Server auction monitoring (public, no auth required).\n' +
13
+ 'Fetches ~1000+ servers from Hetzner\'s public auction endpoint and filters/sorts client-side.\n' +
14
+ 'Typical ranges: 30-450 EUR/mo, 32-1024 GB RAM, NVMe/SATA/HDD, datacenters in FSN/HEL/NBG.');
15
+ auction
16
+ .command('list')
17
+ .alias('ls')
18
+ .description('List and filter auction servers.\n' +
19
+ 'All filters are optional and combinable. String filters (cpu, datacenter, specials, search) are case-insensitive substrings.\n' +
20
+ 'Examples:\n' +
21
+ ' hetzner auction list --cpu epyc --ecc --disk-type nvme --datacenter HEL --sort price\n' +
22
+ ' hetzner auction list --gpu --max-price 150 --json\n' +
23
+ ' hetzner auction list --auction-only --sort next_reduce --limit 20')
24
+ .option('--min-price <n>', 'Minimum monthly price')
25
+ .option('--max-price <n>', 'Maximum monthly price')
26
+ .option('--max-hourly-price <n>', 'Maximum hourly price')
27
+ .option('--min-ram <gb>', 'Minimum RAM in GB')
28
+ .option('--max-ram <gb>', 'Maximum RAM in GB')
29
+ .option('--cpu <text>', 'CPU model substring (e.g. "Ryzen", "i7-6700")')
30
+ .option('--min-cpu-count <n>', 'Minimum CPU/socket count')
31
+ .option('--max-cpu-count <n>', 'Maximum CPU/socket count')
32
+ .option('--datacenter <text>', 'Datacenter substring (e.g. "FSN", "HEL1-DC2")')
33
+ .option('--min-disk-size <gb>', 'Minimum total disk capacity in GB')
34
+ .option('--max-disk-size <gb>', 'Maximum total disk capacity in GB')
35
+ .option('--min-disk-count <n>', 'Minimum number of drives')
36
+ .option('--max-disk-count <n>', 'Maximum number of drives')
37
+ .addOption(new Option('--disk-type <type>', 'Filter by disk type present')
38
+ .choices(['nvme', 'sata', 'hdd']))
39
+ .option('--min-bandwidth <mbit>', 'Minimum bandwidth in Mbit/s')
40
+ .option('--ecc', 'Only ECC RAM servers')
41
+ .option('--gpu', 'Only GPU servers')
42
+ .option('--inic', 'Only servers with Intel NIC')
43
+ .option('--highio', 'Only high I/O servers')
44
+ .option('--specials <text>', 'Filter by special feature (substring match)')
45
+ .option('--fixed-price', 'Only fixed-price servers')
46
+ .option('--auction-only', 'Only auction (non-fixed) servers')
47
+ .option('--no-setup-fee', 'Only servers with no setup fee')
48
+ .option('--max-setup-price <n>', 'Maximum setup price')
49
+ .option('--search <text>', 'Free-text search across description')
50
+ .addOption(new Option('--currency <currency>', 'Price currency')
51
+ .choices(['EUR', 'USD'])
52
+ .default('EUR'))
53
+ .addOption(new Option('--sort <field>', 'Sort by field')
54
+ .choices(['price', 'hourly', 'setup', 'ram', 'disk', 'disk_count', 'cpu', 'cpu_count', 'datacenter', 'bandwidth', 'next_reduce'])
55
+ .default('price'))
56
+ .option('--desc', 'Sort in descending order')
57
+ .option('--limit <n>', 'Limit output rows')
58
+ .action(async (options) => {
59
+ try {
60
+ const { server: servers } = await fetchAuctionServers(options.currency);
61
+ const filters = {
62
+ minPrice: parseNum(options.minPrice),
63
+ maxPrice: parseNum(options.maxPrice),
64
+ maxHourlyPrice: parseNum(options.maxHourlyPrice),
65
+ minRam: parseNum(options.minRam),
66
+ maxRam: parseNum(options.maxRam),
67
+ cpu: options.cpu,
68
+ datacenter: options.datacenter,
69
+ minDiskSize: parseNum(options.minDiskSize),
70
+ maxDiskSize: parseNum(options.maxDiskSize),
71
+ minDiskCount: parseNum(options.minDiskCount),
72
+ maxDiskCount: parseNum(options.maxDiskCount),
73
+ diskType: options.diskType,
74
+ minBandwidth: parseNum(options.minBandwidth),
75
+ ecc: options.ecc ? true : undefined,
76
+ gpu: options.gpu ? true : undefined,
77
+ inic: options.inic ? true : undefined,
78
+ highio: options.highio ? true : undefined,
79
+ specials: options.specials,
80
+ fixedPrice: options.fixedPrice ? true : options.auctionOnly ? false : undefined,
81
+ maxSetupPrice: options.noSetupFee ? 0 : parseNum(options.maxSetupPrice),
82
+ minCpuCount: parseNum(options.minCpuCount),
83
+ maxCpuCount: parseNum(options.maxCpuCount),
84
+ text: options.search,
85
+ };
86
+ let filtered = filterAuctionServers(servers, filters);
87
+ filtered = sortAuctionServers(filtered, options.sort || 'price', !!options.desc);
88
+ const limit = parseNum(options.limit);
89
+ if (limit !== undefined && limit > 0) {
90
+ filtered = filtered.slice(0, limit);
91
+ }
92
+ if (options.json) {
93
+ console.log(formatJson(filtered));
94
+ }
95
+ else {
96
+ console.log(formatAuctionList(filtered));
97
+ }
98
+ }
99
+ catch (err) {
100
+ console.error(error(err instanceof Error ? err.message : 'Failed to fetch auction data'));
101
+ process.exit(1);
102
+ }
103
+ });
104
+ auction
105
+ .command('show <id>')
106
+ .description('Show detailed info for an auction server.\n' +
107
+ 'Displays CPU, RAM, disk breakdown by type, datacenter, pricing (monthly + hourly),\n' +
108
+ 'specials (GPU, iNIC, ECC), IP pricing, and full description.\n' +
109
+ 'Example: hetzner auction show 2919866')
110
+ .addOption(new Option('--currency <currency>', 'Price currency')
111
+ .choices(['EUR', 'USD'])
112
+ .default('EUR'))
113
+ .action(async (id, options) => {
114
+ try {
115
+ const serverId = parseInt(id);
116
+ if (isNaN(serverId)) {
117
+ console.error(error('Invalid server ID'));
118
+ process.exit(1);
119
+ }
120
+ const { server: servers } = await fetchAuctionServers(options.currency);
121
+ const server = servers.find(s => s.id === serverId);
122
+ if (!server) {
123
+ console.error(error(`Auction server ${id} not found`));
124
+ process.exit(1);
125
+ }
126
+ if (options.json) {
127
+ console.log(formatJson(server));
128
+ }
129
+ else {
130
+ console.log(formatAuctionDetails(server));
131
+ }
132
+ }
133
+ catch (err) {
134
+ console.error(error(err instanceof Error ? err.message : 'Failed to fetch auction data'));
135
+ process.exit(1);
136
+ }
137
+ });
138
+ }
@@ -0,0 +1,3 @@
1
+ import type { AuctionServer } from '../types.js';
2
+ export declare function formatAuctionList(servers: AuctionServer[]): string;
3
+ export declare function formatAuctionDetails(server: AuctionServer): string;
@@ -0,0 +1,87 @@
1
+ import { colorize, info, heading, createTable, } from '../shared/formatter.js';
2
+ function summarizeDisks(server) {
3
+ const parts = [];
4
+ const { nvme, sata, hdd } = server.serverDiskData;
5
+ if (nvme.length > 0) {
6
+ parts.push(`${nvme.length}x ${formatDiskSize(nvme[0])} NVMe`);
7
+ }
8
+ if (sata.length > 0) {
9
+ parts.push(`${sata.length}x ${formatDiskSize(sata[0])} SATA`);
10
+ }
11
+ if (hdd.length > 0) {
12
+ parts.push(`${hdd.length}x ${formatDiskSize(hdd[0])} HDD`);
13
+ }
14
+ return parts.length > 0 ? parts.join(', ') : '-';
15
+ }
16
+ function formatDiskSize(gb) {
17
+ if (gb >= 1000)
18
+ return `${(gb / 1000).toFixed(gb % 1000 === 0 ? 0 : 1)} TB`;
19
+ return `${gb} GB`;
20
+ }
21
+ function formatNextReduce(server) {
22
+ if (server.fixed_price)
23
+ return 'Fixed';
24
+ if (server.next_reduce <= 0)
25
+ return '-';
26
+ const hours = Math.floor(server.next_reduce / 60);
27
+ const mins = server.next_reduce % 60;
28
+ if (hours > 0)
29
+ return `${hours}h ${mins}m`;
30
+ return `${mins}m`;
31
+ }
32
+ export function formatAuctionList(servers) {
33
+ if (servers.length === 0) {
34
+ return info('No auction servers found matching your filters.');
35
+ }
36
+ const table = createTable(['ID', 'CPU', 'RAM', 'Disks', 'DC', 'Price', 'Setup', 'Type', 'Reduce', 'Specials']);
37
+ for (const s of servers) {
38
+ const specials = s.specials.length > 0 ? s.specials.join(', ') : '-';
39
+ table.push([
40
+ s.id.toString(),
41
+ s.cpu,
42
+ `${s.ram_size} GB${s.is_ecc ? ' ECC' : ''}`,
43
+ summarizeDisks(s),
44
+ s.datacenter,
45
+ `€${s.price.toFixed(2)}`,
46
+ s.setup_price > 0 ? `€${s.setup_price.toFixed(2)}` : 'Free',
47
+ s.fixed_price ? 'Fixed' : 'Auction',
48
+ formatNextReduce(s),
49
+ specials,
50
+ ]);
51
+ }
52
+ return `${table.toString()}\n${colorize(`${servers.length} server(s) found`, 'gray')}`;
53
+ }
54
+ export function formatAuctionDetails(server) {
55
+ const lines = [];
56
+ lines.push(heading(`Auction Server ${server.id}`));
57
+ lines.push('');
58
+ const table = createTable(['Property', 'Value']);
59
+ table.push(['ID', server.id.toString()], ['Name', server.name], ['CPU', server.cpu], ['CPU Count', server.cpu_count.toString()], ['RAM', `${server.ram_size} GB${server.is_ecc ? ' (ECC)' : ''}`], ['RAM Details', server.ram.join(', ')], ['Disks', summarizeDisks(server)], ['Disk Details', server.hdd_hr.join(', ') || '-'], ['Total Disk Size', `${server.hdd_size} GB`], ['Disk Count', server.hdd_count.toString()], ['Datacenter', `${server.datacenter_hr} (${server.datacenter})`], ['Traffic', server.traffic], ['Bandwidth', `${server.bandwidth} Mbit/s`], ['Price', `€${server.price.toFixed(2)}/mo`], ['Hourly Price', `€${server.hourly_price.toFixed(4)}/h`], ['Setup Price', server.setup_price > 0 ? `€${server.setup_price.toFixed(2)}` : 'Free'], ['Price Type', server.fixed_price ? 'Fixed' : 'Auction'], ['Next Reduce', formatNextReduce(server)], ['Specials', server.specials.length > 0 ? server.specials.join(', ') : 'None'], ['High I/O', server.is_highio ? 'Yes' : 'No'], ['ECC', server.is_ecc ? 'Yes' : 'No']);
60
+ lines.push(table.toString());
61
+ // Disk breakdown
62
+ const { nvme, sata, hdd } = server.serverDiskData;
63
+ if (nvme.length > 0 || sata.length > 0 || hdd.length > 0) {
64
+ lines.push('');
65
+ lines.push(colorize('Disk Breakdown:', 'bold'));
66
+ if (nvme.length > 0)
67
+ lines.push(` NVMe: ${nvme.map(d => formatDiskSize(d)).join(', ')}`);
68
+ if (sata.length > 0)
69
+ lines.push(` SATA: ${sata.map(d => formatDiskSize(d)).join(', ')}`);
70
+ if (hdd.length > 0)
71
+ lines.push(` HDD: ${hdd.map(d => formatDiskSize(d)).join(', ')}`);
72
+ }
73
+ // IP Pricing
74
+ lines.push('');
75
+ lines.push(colorize('Additional IP Pricing:', 'bold'));
76
+ lines.push(` Monthly: €${server.ip_price.Monthly.toFixed(2)}`);
77
+ lines.push(` Hourly: €${server.ip_price.Hourly.toFixed(4)}`);
78
+ // Description
79
+ if (server.description.length > 0) {
80
+ lines.push('');
81
+ lines.push(colorize('Description:', 'bold'));
82
+ for (const line of server.description) {
83
+ lines.push(` ${line}`);
84
+ }
85
+ }
86
+ return lines.join('\n');
87
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { Command, Option } from 'commander';
3
+ import { config } from 'dotenv';
4
+ import { registerRobotCommands } from './robot/commands/index.js';
5
+ import { registerCloudCommands } from './cloud/commands/index.js';
6
+ import { registerAuctionCommands } from './auction/commands.js';
7
+ import { generateReference } from './shared/reference.js';
8
+ config();
9
+ const program = new Command();
10
+ program
11
+ .name('hetzner')
12
+ .description('Unified CLI for Hetzner Robot API (dedicated servers), Cloud API, and public server auction.\n\n' +
13
+ ' Three APIs, one tool:\n' +
14
+ ' Robot commands — manage dedicated servers (requires Robot web service credentials)\n' +
15
+ ' Cloud commands — manage cloud infrastructure under "hetzner cloud ..." (requires Cloud API token)\n' +
16
+ ' Auction commands — browse server auction under "hetzner auction ..." (no auth required)\n\n' +
17
+ ' Run "hetzner reference" for a complete structured reference of all commands and options.\n' +
18
+ ' Run "hetzner <command> --help" for help on a specific command.')
19
+ .version('2.0.0')
20
+ .option('-u, --user <username>', 'Hetzner Robot web service username (or set HETZNER_ROBOT_USER)')
21
+ .option('-p, --password <password>', 'Hetzner Robot web service password (or set HETZNER_ROBOT_PASSWORD; use "-" to read from stdin)')
22
+ .option('--json', 'Output raw JSON instead of formatted tables');
23
+ // Register all robot commands directly on program (backward compat)
24
+ registerRobotCommands(program);
25
+ // Register cloud commands under 'hetzner cloud ...'
26
+ registerCloudCommands(program);
27
+ // Register auction commands (public API, no auth required)
28
+ registerAuctionCommands(program);
29
+ // Built-in reference command (structured docs for LLMs and humans)
30
+ program
31
+ .command('reference')
32
+ .alias('ref')
33
+ .description('Print complete CLI reference (optimized for LLM context)')
34
+ .addOption(new Option('--section <section>', 'Show only a specific section')
35
+ .choices(['robot', 'cloud', 'auction']))
36
+ .action((options) => {
37
+ console.log(generateReference(options.section));
38
+ });
39
+ program.parse();
@@ -0,0 +1,2 @@
1
+ export { HetznerRobotClient } from './robot/client.js';
2
+ export { fetchAuctionServers, filterAuctionServers, sortAuctionServers } from './auction/client.js';
package/dist/client.js ADDED
@@ -0,0 +1,4 @@
1
+ // Re-export from new location for backward compatibility
2
+ export { HetznerRobotClient } from './robot/client.js';
3
+ // Auction client (public API, no auth)
4
+ export { fetchAuctionServers, filterAuctionServers, sortAuctionServers } from './auction/client.js';