@rollupdate/hctl 0.0.0-stage → 3.1.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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1071 -2
  3. package/dist/auction/cache.d.ts +6 -0
  4. package/dist/auction/cache.js +55 -0
  5. package/dist/auction/client.d.ts +24 -0
  6. package/dist/auction/client.js +236 -0
  7. package/dist/auction/commands.d.ts +2 -0
  8. package/dist/auction/commands.js +440 -0
  9. package/dist/auction/diff-formatter.d.ts +3 -0
  10. package/dist/auction/diff-formatter.js +61 -0
  11. package/dist/auction/diff.d.ts +11 -0
  12. package/dist/auction/diff.js +28 -0
  13. package/dist/auction/formatter.d.ts +21 -0
  14. package/dist/auction/formatter.js +182 -0
  15. package/dist/auction/store.d.ts +24 -0
  16. package/dist/auction/store.js +177 -0
  17. package/dist/auction/types.d.ts +74 -0
  18. package/dist/auction/types.js +5 -0
  19. package/dist/cli.d.ts +2 -0
  20. package/dist/cli.js +74 -0
  21. package/dist/client.d.ts +2 -0
  22. package/dist/client.js +4 -0
  23. package/dist/cloud/browser-bootstrap.d.ts +28 -0
  24. package/dist/cloud/browser-bootstrap.js +480 -0
  25. package/dist/cloud/client.d.ts +520 -0
  26. package/dist/cloud/client.js +755 -0
  27. package/dist/cloud/commands/certificate.d.ts +2 -0
  28. package/dist/cloud/commands/certificate.js +108 -0
  29. package/dist/cloud/commands/context.d.ts +2 -0
  30. package/dist/cloud/commands/context.js +133 -0
  31. package/dist/cloud/commands/datacenter.d.ts +2 -0
  32. package/dist/cloud/commands/datacenter.js +24 -0
  33. package/dist/cloud/commands/firewall.d.ts +2 -0
  34. package/dist/cloud/commands/firewall.js +142 -0
  35. package/dist/cloud/commands/floating-ip.d.ts +2 -0
  36. package/dist/cloud/commands/floating-ip.js +138 -0
  37. package/dist/cloud/commands/image.d.ts +2 -0
  38. package/dist/cloud/commands/image.js +98 -0
  39. package/dist/cloud/commands/index.d.ts +2 -0
  40. package/dist/cloud/commands/index.js +47 -0
  41. package/dist/cloud/commands/inventory.d.ts +2 -0
  42. package/dist/cloud/commands/inventory.js +13 -0
  43. package/dist/cloud/commands/iso.d.ts +2 -0
  44. package/dist/cloud/commands/iso.js +27 -0
  45. package/dist/cloud/commands/load-balancer-type.d.ts +2 -0
  46. package/dist/cloud/commands/load-balancer-type.js +24 -0
  47. package/dist/cloud/commands/load-balancer.d.ts +2 -0
  48. package/dist/cloud/commands/load-balancer.js +269 -0
  49. package/dist/cloud/commands/location.d.ts +2 -0
  50. package/dist/cloud/commands/location.js +24 -0
  51. package/dist/cloud/commands/network.d.ts +2 -0
  52. package/dist/cloud/commands/network.js +157 -0
  53. package/dist/cloud/commands/placement-group.d.ts +2 -0
  54. package/dist/cloud/commands/placement-group.js +79 -0
  55. package/dist/cloud/commands/pricing.d.ts +2 -0
  56. package/dist/cloud/commands/pricing.js +15 -0
  57. package/dist/cloud/commands/primary-ip.d.ts +2 -0
  58. package/dist/cloud/commands/primary-ip.js +139 -0
  59. package/dist/cloud/commands/server-type.d.ts +2 -0
  60. package/dist/cloud/commands/server-type.js +24 -0
  61. package/dist/cloud/commands/server.d.ts +2 -0
  62. package/dist/cloud/commands/server.js +346 -0
  63. package/dist/cloud/commands/ssh-key.d.ts +2 -0
  64. package/dist/cloud/commands/ssh-key.js +94 -0
  65. package/dist/cloud/commands/volume.d.ts +2 -0
  66. package/dist/cloud/commands/volume.js +138 -0
  67. package/dist/cloud/context.d.ts +34 -0
  68. package/dist/cloud/context.js +215 -0
  69. package/dist/cloud/formatter.d.ts +40 -0
  70. package/dist/cloud/formatter.js +824 -0
  71. package/dist/cloud/helpers.d.ts +27 -0
  72. package/dist/cloud/helpers.js +67 -0
  73. package/dist/cloud/inventory.d.ts +100 -0
  74. package/dist/cloud/inventory.js +229 -0
  75. package/dist/cloud/pricing.d.ts +27 -0
  76. package/dist/cloud/pricing.js +103 -0
  77. package/dist/cloud/token-instructions.d.ts +2 -0
  78. package/dist/cloud/token-instructions.js +21 -0
  79. package/dist/cloud/types.d.ts +437 -0
  80. package/dist/cloud/types.js +5 -0
  81. package/dist/config.d.ts +1 -0
  82. package/dist/config.js +2 -0
  83. package/dist/formatter.d.ts +3 -0
  84. package/dist/formatter.js +6 -0
  85. package/dist/index.d.ts +12 -0
  86. package/dist/index.js +21 -0
  87. package/dist/robot/client.d.ts +256 -0
  88. package/dist/robot/client.js +745 -0
  89. package/dist/robot/commands/auth.d.ts +2 -0
  90. package/dist/robot/commands/auth.js +54 -0
  91. package/dist/robot/commands/boot.d.ts +2 -0
  92. package/dist/robot/commands/boot.js +76 -0
  93. package/dist/robot/commands/cancel.d.ts +2 -0
  94. package/dist/robot/commands/cancel.js +37 -0
  95. package/dist/robot/commands/failover.d.ts +2 -0
  96. package/dist/robot/commands/failover.js +46 -0
  97. package/dist/robot/commands/firewall.d.ts +2 -0
  98. package/dist/robot/commands/firewall.js +72 -0
  99. package/dist/robot/commands/index.d.ts +2 -0
  100. package/dist/robot/commands/index.js +36 -0
  101. package/dist/robot/commands/interactive.d.ts +2 -0
  102. package/dist/robot/commands/interactive.js +152 -0
  103. package/dist/robot/commands/ip.d.ts +2 -0
  104. package/dist/robot/commands/ip.js +55 -0
  105. package/dist/robot/commands/key.d.ts +2 -0
  106. package/dist/robot/commands/key.js +68 -0
  107. package/dist/robot/commands/order.d.ts +2 -0
  108. package/dist/robot/commands/order.js +33 -0
  109. package/dist/robot/commands/rdns.d.ts +2 -0
  110. package/dist/robot/commands/rdns.js +41 -0
  111. package/dist/robot/commands/reset.d.ts +2 -0
  112. package/dist/robot/commands/reset.js +77 -0
  113. package/dist/robot/commands/server.d.ts +2 -0
  114. package/dist/robot/commands/server.js +32 -0
  115. package/dist/robot/commands/storagebox.d.ts +2 -0
  116. package/dist/robot/commands/storagebox.js +126 -0
  117. package/dist/robot/commands/subnet.d.ts +2 -0
  118. package/dist/robot/commands/subnet.js +24 -0
  119. package/dist/robot/commands/traffic.d.ts +2 -0
  120. package/dist/robot/commands/traffic.js +23 -0
  121. package/dist/robot/commands/vswitch.d.ts +2 -0
  122. package/dist/robot/commands/vswitch.js +81 -0
  123. package/dist/robot/commands/wol.d.ts +2 -0
  124. package/dist/robot/commands/wol.js +20 -0
  125. package/dist/robot/formatter.d.ts +57 -0
  126. package/dist/robot/formatter.js +582 -0
  127. package/dist/robot/types.d.ts +359 -0
  128. package/dist/robot/types.js +5 -0
  129. package/dist/shared/config.d.ts +86 -0
  130. package/dist/shared/config.js +289 -0
  131. package/dist/shared/formatter.d.ts +29 -0
  132. package/dist/shared/formatter.js +121 -0
  133. package/dist/shared/helpers.d.ts +39 -0
  134. package/dist/shared/helpers.js +86 -0
  135. package/dist/shared/init.d.ts +2 -0
  136. package/dist/shared/init.js +107 -0
  137. package/dist/shared/reference.d.ts +5 -0
  138. package/dist/shared/reference.js +689 -0
  139. package/dist/shared/schemas.d.ts +1 -0
  140. package/dist/shared/schemas.js +1031 -0
  141. package/dist/types.d.ts +2 -0
  142. package/dist/types.js +2 -0
  143. package/package.json +127 -4
@@ -0,0 +1,215 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { HetznerCloudClient } from "./client.js";
5
+ const CONFIG_DIR = join(homedir(), ".hctl");
6
+ const CONTEXTS_FILE = join(CONFIG_DIR, "cloud-contexts.json");
7
+ const LEGACY_CONTEXTS_FILE = join(homedir(), ".hetzner-cli", "cloud-contexts.json");
8
+ const KEYCHAIN_SERVICE = "hctl";
9
+ const LEGACY_KEYCHAIN_SERVICE = "hetzner-cli";
10
+ // Lazy-loaded keytar module
11
+ let keytarModule = null;
12
+ let keytarLoadAttempted = false;
13
+ async function getKeytar() {
14
+ if (keytarLoadAttempted) {
15
+ return keytarModule;
16
+ }
17
+ keytarLoadAttempted = true;
18
+ try {
19
+ keytarModule = await import("keytar");
20
+ return keytarModule;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ function ensureConfigDir() {
27
+ if (!existsSync(CONFIG_DIR)) {
28
+ mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
29
+ }
30
+ }
31
+ function loadContexts() {
32
+ const file = existsSync(CONTEXTS_FILE) ? CONTEXTS_FILE : LEGACY_CONTEXTS_FILE;
33
+ if (!existsSync(file)) {
34
+ return { active: null, contexts: {} };
35
+ }
36
+ try {
37
+ const data = readFileSync(file, "utf-8");
38
+ return JSON.parse(data);
39
+ }
40
+ catch {
41
+ return { active: null, contexts: {} };
42
+ }
43
+ }
44
+ function saveContexts(config) {
45
+ ensureConfigDir();
46
+ writeFileSync(CONTEXTS_FILE, JSON.stringify(config, null, 2), {
47
+ mode: 0o600,
48
+ });
49
+ }
50
+ function keychainAccount(contextName) {
51
+ return `cloud-token:${contextName}`;
52
+ }
53
+ async function storeToken(contextName, token) {
54
+ const keytar = await getKeytar();
55
+ if (keytar) {
56
+ try {
57
+ await keytar.setPassword(KEYCHAIN_SERVICE, keychainAccount(contextName), token);
58
+ return true;
59
+ }
60
+ catch {
61
+ // Fall through to file storage
62
+ }
63
+ }
64
+ console.warn("Warning: System keychain unavailable. Cloud token will be stored in plaintext at ~/.hctl/cloud-contexts.json");
65
+ return false;
66
+ }
67
+ async function retrieveToken(contextName) {
68
+ // Try keytar first
69
+ const keytar = await getKeytar();
70
+ if (keytar) {
71
+ try {
72
+ const token = (await keytar.getPassword(KEYCHAIN_SERVICE, keychainAccount(contextName))) ??
73
+ (await keytar.getPassword(LEGACY_KEYCHAIN_SERVICE, keychainAccount(contextName)));
74
+ if (token) {
75
+ return token;
76
+ }
77
+ }
78
+ catch {
79
+ // Fall through
80
+ }
81
+ }
82
+ // Fall back to file
83
+ const config = loadContexts();
84
+ return config.contexts[contextName]?.token ?? null;
85
+ }
86
+ async function deleteToken(contextName) {
87
+ const keytar = await getKeytar();
88
+ if (keytar) {
89
+ try {
90
+ await keytar.deletePassword(KEYCHAIN_SERVICE, keychainAccount(contextName));
91
+ await keytar.deletePassword(LEGACY_KEYCHAIN_SERVICE, keychainAccount(contextName));
92
+ }
93
+ catch {
94
+ // Ignore
95
+ }
96
+ }
97
+ }
98
+ /**
99
+ * Create a new cloud context.
100
+ */
101
+ export async function createContext(name, token, options = {}) {
102
+ if (options.verify !== false) {
103
+ await validateCloudToken(token);
104
+ }
105
+ const config = loadContexts();
106
+ const storedInKeychain = await storeToken(name, token);
107
+ config.contexts[name] = {
108
+ name,
109
+ ...(storedInKeychain ? {} : { token }),
110
+ };
111
+ // Auto-activate if first context
112
+ if (!config.active) {
113
+ config.active = name;
114
+ }
115
+ saveContexts(config);
116
+ }
117
+ /**
118
+ * Set the active context.
119
+ */
120
+ export function useContext(name) {
121
+ const config = loadContexts();
122
+ if (!config.contexts[name]) {
123
+ throw new Error(`Context '${name}' not found. Use 'hctl cloud context list' to see available contexts.`);
124
+ }
125
+ config.active = name;
126
+ saveContexts(config);
127
+ }
128
+ /**
129
+ * Delete a context.
130
+ */
131
+ export async function deleteContext(name) {
132
+ const config = loadContexts();
133
+ if (!config.contexts[name]) {
134
+ throw new Error(`Context '${name}' not found.`);
135
+ }
136
+ await deleteToken(name);
137
+ const { [name]: _, ...rest } = config.contexts;
138
+ config.contexts = rest;
139
+ if (config.active === name) {
140
+ const remaining = Object.keys(config.contexts);
141
+ config.active = remaining.length > 0 ? remaining[0] : null;
142
+ }
143
+ saveContexts(config);
144
+ }
145
+ /**
146
+ * List all contexts.
147
+ */
148
+ export function listContexts() {
149
+ const config = loadContexts();
150
+ return Object.values(config.contexts).map((ctx) => ({
151
+ name: ctx.name,
152
+ active: ctx.name === config.active,
153
+ }));
154
+ }
155
+ /**
156
+ * Get the active context name.
157
+ */
158
+ export function getActiveContext() {
159
+ const config = loadContexts();
160
+ return config.active;
161
+ }
162
+ /**
163
+ * Resolve the cloud API token.
164
+ * Priority: --token flag > HETZNER_CLOUD_TOKEN env > active context
165
+ */
166
+ export async function resolveToken(flagToken) {
167
+ // 1. CLI flag
168
+ if (flagToken) {
169
+ return flagToken;
170
+ }
171
+ // 2. Environment variable
172
+ const envToken = process.env.HETZNER_CLOUD_TOKEN;
173
+ if (envToken) {
174
+ return envToken;
175
+ }
176
+ // 3. Active context
177
+ const config = loadContexts();
178
+ if (config.active) {
179
+ const token = await retrieveToken(config.active);
180
+ if (token) {
181
+ return token;
182
+ }
183
+ }
184
+ throw new Error("No cloud token found. Use one of:\n" +
185
+ " --token <token> Pass token directly\n" +
186
+ " HETZNER_CLOUD_TOKEN=<token> Set environment variable\n" +
187
+ " hctl cloud context create Configure a named context");
188
+ }
189
+ /**
190
+ * Validate that a token authenticates against the Hetzner Cloud API.
191
+ */
192
+ export async function validateCloudToken(token) {
193
+ if (!token.trim()) {
194
+ throw new Error("Cloud token is empty.");
195
+ }
196
+ const client = new HetznerCloudClient(token);
197
+ try {
198
+ await client.listLocations();
199
+ }
200
+ catch (error) {
201
+ const message = error instanceof Error ? error.message : "Unknown error";
202
+ const normalizedMessage = message.toLowerCase();
203
+ if (normalizedMessage.includes("401") ||
204
+ normalizedMessage.includes("unauthorized")) {
205
+ throw new Error("Hetzner Cloud token validation failed: api.hetzner.cloud rejected this token. Create a project API token in the Hetzner Cloud Console; Robot credentials, console logins, Cloudflare tokens, DNS tokens, and R2 tokens will not work.", { cause: error });
206
+ }
207
+ if (normalizedMessage.includes("403") ||
208
+ normalizedMessage.includes("forbidden")) {
209
+ throw new Error("Hetzner Cloud token validation failed: this token authenticated but cannot read Cloud API resources.", { cause: error });
210
+ }
211
+ throw new Error(`Hetzner Cloud token validation failed: ${message}`, {
212
+ cause: error,
213
+ });
214
+ }
215
+ }
@@ -0,0 +1,40 @@
1
+ import type { InventoryReport } from "./inventory.js";
2
+ import type { Certificate, CloudFirewall, CloudPricing, CloudServer, CloudSshKey, Datacenter, FloatingIp, Image, ISO, LoadBalancer, LoadBalancerType, Location, Network, PlacementGroup, PrimaryIp, ServerType, Volume } from "./types.js";
3
+ export declare function formatContextList(contexts: {
4
+ name: string;
5
+ active: boolean;
6
+ }[]): string;
7
+ export declare function formatDatacenterList(datacenters: Datacenter[]): string;
8
+ export declare function formatDatacenterDetails(dc: Datacenter): string;
9
+ export declare function formatLocationList(locations: Location[]): string;
10
+ export declare function formatLocationDetails(loc: Location): string;
11
+ export declare function formatServerTypeList(types: ServerType[]): string;
12
+ export declare function formatServerTypeDetails(st: ServerType): string;
13
+ export declare function formatLoadBalancerTypeList(types: LoadBalancerType[]): string;
14
+ export declare function formatLoadBalancerTypeDetails(lbt: LoadBalancerType): string;
15
+ export declare function formatIsoList(isos: ISO[]): string;
16
+ export declare function formatIsoDetails(iso: ISO): string;
17
+ export declare function formatCloudServerList(servers: CloudServer[]): string;
18
+ export declare function formatCloudServerDetails(srv: CloudServer): string;
19
+ export declare function formatNetworkList(networks: Network[]): string;
20
+ export declare function formatNetworkDetails(net: Network): string;
21
+ export declare function formatCloudFirewallList(firewalls: CloudFirewall[]): string;
22
+ export declare function formatCloudFirewallDetails(fw: CloudFirewall): string;
23
+ export declare function formatFloatingIpList(ips: FloatingIp[]): string;
24
+ export declare function formatFloatingIpDetails(ip: FloatingIp): string;
25
+ export declare function formatPrimaryIpList(ips: PrimaryIp[]): string;
26
+ export declare function formatPrimaryIpDetails(ip: PrimaryIp): string;
27
+ export declare function formatVolumeList(volumes: Volume[]): string;
28
+ export declare function formatVolumeDetails(vol: Volume): string;
29
+ export declare function formatLoadBalancerList(lbs: LoadBalancer[]): string;
30
+ export declare function formatLoadBalancerDetails(lb: LoadBalancer): string;
31
+ export declare function formatImageList(images: Image[]): string;
32
+ export declare function formatImageDetails(img: Image): string;
33
+ export declare function formatCloudSshKeyList(keys: CloudSshKey[]): string;
34
+ export declare function formatCloudSshKeyDetails(key: CloudSshKey): string;
35
+ export declare function formatCertificateList(certs: Certificate[]): string;
36
+ export declare function formatCertificateDetails(cert: Certificate): string;
37
+ export declare function formatPlacementGroupList(groups: PlacementGroup[]): string;
38
+ export declare function formatPlacementGroupDetails(pg: PlacementGroup): string;
39
+ export declare function formatPricingSummary(pricing: CloudPricing): string;
40
+ export declare function formatInventoryReport(report: InventoryReport): string;