sealos-cli 0.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.
package/dist/main.mjs ADDED
@@ -0,0 +1,2045 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
4
+
5
+ // src/main.ts
6
+ import { Command as Command13 } from "commander";
7
+
8
+ // src/commands/auth/index.ts
9
+ import { Command as Command4 } from "commander";
10
+
11
+ // src/commands/auth/login.ts
12
+ import { Command } from "commander";
13
+
14
+ // src/lib/auth.ts
15
+ import { execFileSync } from "child_process";
16
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
17
+ import { homedir, platform } from "os";
18
+ import { join } from "path";
19
+ var SEALOS_AUTH_CLIENT_ID = "af993c98-d19d-4bdc-b338-79b80dc4f8bf";
20
+ var DEFAULT_SEALOS_REGION = "https://usw-1.sealos.io";
21
+ var AUTH_METHOD_DEVICE_GRANT = "oauth2_device_grant";
22
+ var AUTH_METHOD_TOKEN = "token";
23
+ var DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
24
+ function getAuthPaths(sealosDir = join(homedir(), ".sealos")) {
25
+ return {
26
+ sealosDir,
27
+ authPath: join(sealosDir, "auth.json"),
28
+ kubeconfigPath: join(sealosDir, "kubeconfig")
29
+ };
30
+ }
31
+ __name(getAuthPaths, "getAuthPaths");
32
+ function normalizeRegion(region) {
33
+ return (region || process.env.SEALOS_REGION || DEFAULT_SEALOS_REGION).replace(/\/+$/, "");
34
+ }
35
+ __name(normalizeRegion, "normalizeRegion");
36
+ function createDefaultAuthDependencies() {
37
+ return {
38
+ fetch,
39
+ sleep: /* @__PURE__ */ __name(async (ms) => await new Promise((resolve) => setTimeout(resolve, ms)), "sleep"),
40
+ openBrowser,
41
+ now: /* @__PURE__ */ __name(() => /* @__PURE__ */ new Date(), "now"),
42
+ paths: getAuthPaths(),
43
+ stderr: process.stderr
44
+ };
45
+ }
46
+ __name(createDefaultAuthDependencies, "createDefaultAuthDependencies");
47
+ function withDeps(deps = {}) {
48
+ return {
49
+ ...createDefaultAuthDependencies(),
50
+ ...deps
51
+ };
52
+ }
53
+ __name(withDeps, "withDeps");
54
+ function ensureSealosDir(paths) {
55
+ mkdirSync(paths.sealosDir, { recursive: true });
56
+ }
57
+ __name(ensureSealosDir, "ensureSealosDir");
58
+ function saveAuth(auth, deps = {}) {
59
+ const { paths } = withDeps(deps);
60
+ ensureSealosDir(paths);
61
+ writeFileSync(paths.authPath, JSON.stringify(auth, null, 2), { mode: 384 });
62
+ }
63
+ __name(saveAuth, "saveAuth");
64
+ function loadAuth(deps = {}) {
65
+ const { paths } = withDeps(deps);
66
+ if (!existsSync(paths.authPath)) {
67
+ throw new Error("Not authenticated. Please run: sealos login");
68
+ }
69
+ return JSON.parse(readFileSync(paths.authPath, "utf-8"));
70
+ }
71
+ __name(loadAuth, "loadAuth");
72
+ function getToken(deps = {}) {
73
+ try {
74
+ return loadAuth(deps).regional_token || null;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ __name(getToken, "getToken");
80
+ function getAuthHeaders(deps = {}) {
81
+ const token = getToken(deps);
82
+ return token ? { Authorization: token } : null;
83
+ }
84
+ __name(getAuthHeaders, "getAuthHeaders");
85
+ function requireAuth(deps = {}) {
86
+ const headers = getAuthHeaders(deps);
87
+ if (!headers) {
88
+ throw new Error('Authentication required. Please run "sealos login" first.');
89
+ }
90
+ return headers;
91
+ }
92
+ __name(requireAuth, "requireAuth");
93
+ function saveKubeconfig(kubeconfig, deps = {}) {
94
+ const { paths } = withDeps(deps);
95
+ ensureSealosDir(paths);
96
+ writeFileSync(paths.kubeconfigPath, kubeconfig, { mode: 384 });
97
+ }
98
+ __name(saveKubeconfig, "saveKubeconfig");
99
+ function clearAuth(deps = {}) {
100
+ const { paths } = withDeps(deps);
101
+ rmSync(paths.authPath, { force: true });
102
+ rmSync(paths.kubeconfigPath, { force: true });
103
+ }
104
+ __name(clearAuth, "clearAuth");
105
+ function checkAuth(deps = {}) {
106
+ const { paths } = withDeps(deps);
107
+ if (!existsSync(paths.kubeconfigPath)) {
108
+ return { authenticated: false };
109
+ }
110
+ try {
111
+ const kubeconfig = readFileSync(paths.kubeconfigPath, "utf-8");
112
+ if (!kubeconfig.includes("server:") || !kubeconfig.includes("token:") && !kubeconfig.includes("client-certificate")) {
113
+ return { authenticated: false };
114
+ }
115
+ const auth = existsSync(paths.authPath) ? JSON.parse(readFileSync(paths.authPath, "utf-8")) : {};
116
+ return {
117
+ authenticated: true,
118
+ kubeconfig_path: paths.kubeconfigPath,
119
+ region: auth.region || "unknown",
120
+ workspace: auth.current_workspace?.id || "unknown"
121
+ };
122
+ } catch {
123
+ return { authenticated: false };
124
+ }
125
+ }
126
+ __name(checkAuth, "checkAuth");
127
+ function getAuthInfo(deps = {}) {
128
+ const { paths } = withDeps(deps);
129
+ const status = checkAuth(deps);
130
+ if (!status.authenticated) {
131
+ return { authenticated: false };
132
+ }
133
+ const auth = existsSync(paths.authPath) ? JSON.parse(readFileSync(paths.authPath, "utf-8")) : {};
134
+ return {
135
+ ...status,
136
+ auth_method: auth.auth_method || "unknown",
137
+ authenticated_at: auth.authenticated_at || "unknown",
138
+ current_workspace: auth.current_workspace || null
139
+ };
140
+ }
141
+ __name(getAuthInfo, "getAuthInfo");
142
+ async function parseResponse(res) {
143
+ return await res.json();
144
+ }
145
+ __name(parseResponse, "parseResponse");
146
+ async function readErrorBody(res) {
147
+ return await res.text().catch(() => "");
148
+ }
149
+ __name(readErrorBody, "readErrorBody");
150
+ async function requestDeviceAuthorization(region, deps = {}) {
151
+ const { fetch: fetchImpl } = withDeps(deps);
152
+ const res = await fetchImpl(`${region}/api/auth/oauth2/device`, {
153
+ method: "POST",
154
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
155
+ body: new URLSearchParams({
156
+ client_id: SEALOS_AUTH_CLIENT_ID,
157
+ grant_type: DEVICE_GRANT_TYPE
158
+ })
159
+ });
160
+ if (!res.ok) {
161
+ const body = await readErrorBody(res);
162
+ throw new Error(`Device authorization request failed (${res.status}): ${body || res.statusText}`);
163
+ }
164
+ return await parseResponse(res);
165
+ }
166
+ __name(requestDeviceAuthorization, "requestDeviceAuthorization");
167
+ async function pollForToken(region, deviceCode, interval, expiresIn, deps = {}) {
168
+ const { fetch: fetchImpl, sleep, now, stderr } = withDeps(deps);
169
+ const maxWait = Math.min(expiresIn, 600) * 1e3;
170
+ const deadline = now().getTime() + maxWait;
171
+ let pollInterval = interval * 1e3;
172
+ let lastLoggedMinute = -1;
173
+ while (now().getTime() < deadline) {
174
+ await sleep(pollInterval);
175
+ const remaining = Math.ceil((deadline - now().getTime()) / 6e4);
176
+ if (remaining !== lastLoggedMinute && remaining > 0) {
177
+ lastLoggedMinute = remaining;
178
+ stderr.write(` Waiting for authorization... (${remaining} min remaining)
179
+ `);
180
+ }
181
+ const res = await fetchImpl(`${region}/api/auth/oauth2/token`, {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
184
+ body: new URLSearchParams({
185
+ client_id: SEALOS_AUTH_CLIENT_ID,
186
+ grant_type: DEVICE_GRANT_TYPE,
187
+ device_code: deviceCode
188
+ })
189
+ });
190
+ if (res.ok) {
191
+ return await parseResponse(res);
192
+ }
193
+ const body = await res.json().catch(() => ({}));
194
+ switch (body.error) {
195
+ case "authorization_pending":
196
+ break;
197
+ case "slow_down":
198
+ pollInterval += 5e3;
199
+ break;
200
+ case "access_denied":
201
+ throw new Error("Authorization denied by user");
202
+ case "expired_token":
203
+ throw new Error("Device code expired. Please run login again.");
204
+ default:
205
+ throw new Error(`Token request failed: ${body.error || res.statusText}`);
206
+ }
207
+ }
208
+ throw new Error("Authorization timed out (10 minutes). Please run login again.");
209
+ }
210
+ __name(pollForToken, "pollForToken");
211
+ async function getRegionToken(region, globalToken, deps = {}) {
212
+ const { fetch: fetchImpl } = withDeps(deps);
213
+ const res = await fetchImpl(`${region}/api/auth/regionToken`, {
214
+ method: "POST",
215
+ headers: {
216
+ Authorization: globalToken,
217
+ "Content-Type": "application/json"
218
+ }
219
+ });
220
+ if (!res.ok) {
221
+ const body = await readErrorBody(res);
222
+ throw new Error(`Region token exchange failed (${res.status}): ${body || res.statusText}`);
223
+ }
224
+ return await parseResponse(res);
225
+ }
226
+ __name(getRegionToken, "getRegionToken");
227
+ function normalizeWorkspaces(data) {
228
+ const namespaces = Array.isArray(data.data) ? data.data : data.data?.namespaces;
229
+ return Array.isArray(namespaces) ? namespaces : [];
230
+ }
231
+ __name(normalizeWorkspaces, "normalizeWorkspaces");
232
+ async function listRemoteWorkspaces(region, regionalToken, deps = {}) {
233
+ const { fetch: fetchImpl } = withDeps(deps);
234
+ const res = await fetchImpl(`${region}/api/auth/namespace/list`, {
235
+ headers: { Authorization: regionalToken }
236
+ });
237
+ if (!res.ok) {
238
+ const body = await readErrorBody(res);
239
+ throw new Error(`List workspaces failed (${res.status}): ${body || res.statusText}`);
240
+ }
241
+ return normalizeWorkspaces(await parseResponse(res));
242
+ }
243
+ __name(listRemoteWorkspaces, "listRemoteWorkspaces");
244
+ async function switchRemoteWorkspace(region, regionalToken, nsUid, deps = {}) {
245
+ const { fetch: fetchImpl } = withDeps(deps);
246
+ const res = await fetchImpl(`${region}/api/auth/namespace/switch`, {
247
+ method: "POST",
248
+ headers: {
249
+ Authorization: regionalToken,
250
+ "Content-Type": "application/json"
251
+ },
252
+ body: JSON.stringify({ ns_uid: nsUid })
253
+ });
254
+ if (!res.ok) {
255
+ const body = await readErrorBody(res);
256
+ throw new Error(`Switch workspace failed (${res.status}): ${body || res.statusText}`);
257
+ }
258
+ return await parseResponse(res);
259
+ }
260
+ __name(switchRemoteWorkspace, "switchRemoteWorkspace");
261
+ async function getKubeconfig(region, regionalToken, deps = {}) {
262
+ const { fetch: fetchImpl } = withDeps(deps);
263
+ const res = await fetchImpl(`${region}/api/auth/getKubeconfig`, {
264
+ headers: { Authorization: regionalToken }
265
+ });
266
+ if (!res.ok) {
267
+ const body = await readErrorBody(res);
268
+ throw new Error(`Get kubeconfig failed (${res.status}): ${body || res.statusText}`);
269
+ }
270
+ return await parseResponse(res);
271
+ }
272
+ __name(getKubeconfig, "getKubeconfig");
273
+ function openBrowser(url) {
274
+ const command = platform() === "darwin" ? "open" : platform() === "win32" ? "cmd" : "xdg-open";
275
+ const args = platform() === "win32" ? ["/c", "start", "", url] : [url];
276
+ execFileSync(command, args, { stdio: "ignore" });
277
+ }
278
+ __name(openBrowser, "openBrowser");
279
+ async function loginWithToken(region, token, deps = {}) {
280
+ const { now } = withDeps(deps);
281
+ const normalizedRegion = normalizeRegion(region);
282
+ saveAuth({
283
+ region: normalizedRegion,
284
+ regional_token: token,
285
+ authenticated_at: now().toISOString(),
286
+ auth_method: AUTH_METHOD_TOKEN
287
+ }, deps);
288
+ return {
289
+ region: normalizedRegion,
290
+ workspace: "unknown",
291
+ limited: true
292
+ };
293
+ }
294
+ __name(loginWithToken, "loginWithToken");
295
+ async function loginWithDeviceFlow(region, deps = {}) {
296
+ const fullDeps = withDeps(deps);
297
+ const normalizedRegion = normalizeRegion(region);
298
+ const deviceAuth = await requestDeviceAuthorization(normalizedRegion, fullDeps);
299
+ const {
300
+ device_code: deviceCode,
301
+ user_code: userCode,
302
+ verification_uri: verificationUri,
303
+ verification_uri_complete: verificationUriComplete,
304
+ expires_in: expiresIn,
305
+ interval = 5
306
+ } = deviceAuth;
307
+ const url = verificationUriComplete || verificationUri;
308
+ fullDeps.stderr.write(`
309
+ Please open the following URL in your browser to authorize:
310
+
311
+ ${url}
312
+
313
+ Authorization code: ${userCode}
314
+ Expires in: ${Math.floor(expiresIn / 60)} minutes
315
+
316
+ Waiting for authorization...
317
+ `);
318
+ if (url) {
319
+ try {
320
+ fullDeps.openBrowser(url);
321
+ fullDeps.stderr.write("Browser opened automatically.\n");
322
+ } catch {
323
+ fullDeps.stderr.write("Could not open browser automatically. Please open the URL manually.\n");
324
+ }
325
+ }
326
+ const tokenResponse = await pollForToken(normalizedRegion, deviceCode, interval, expiresIn, fullDeps);
327
+ const accessToken = tokenResponse.access_token;
328
+ if (!accessToken) {
329
+ throw new Error("Token response missing access_token");
330
+ }
331
+ fullDeps.stderr.write("Authorization received. Exchanging for regional token...\n");
332
+ const regionData = await getRegionToken(normalizedRegion, accessToken, fullDeps);
333
+ const regionalToken = regionData.data?.token;
334
+ const kubeconfig = regionData.data?.kubeconfig;
335
+ if (!regionalToken) {
336
+ throw new Error("Region token response missing data.token field");
337
+ }
338
+ if (!kubeconfig) {
339
+ throw new Error("Region token response missing data.kubeconfig field");
340
+ }
341
+ let currentWorkspace;
342
+ try {
343
+ const workspaces = await listRemoteWorkspaces(normalizedRegion, regionalToken, fullDeps);
344
+ currentWorkspace = workspaces.find((workspace) => workspace.nstype === "private") || workspaces[0];
345
+ } catch {
346
+ currentWorkspace = void 0;
347
+ }
348
+ saveKubeconfig(kubeconfig, fullDeps);
349
+ saveAuth({
350
+ region: normalizedRegion,
351
+ access_token: accessToken,
352
+ regional_token: regionalToken,
353
+ authenticated_at: fullDeps.now().toISOString(),
354
+ auth_method: AUTH_METHOD_DEVICE_GRANT,
355
+ ...currentWorkspace ? {
356
+ current_workspace: {
357
+ uid: currentWorkspace.uid,
358
+ id: currentWorkspace.id,
359
+ teamName: currentWorkspace.teamName
360
+ }
361
+ } : {}
362
+ }, fullDeps);
363
+ fullDeps.stderr.write("Authentication successful!\n");
364
+ return {
365
+ kubeconfig_path: fullDeps.paths.kubeconfigPath,
366
+ region: normalizedRegion,
367
+ workspace: currentWorkspace?.id || "default"
368
+ };
369
+ }
370
+ __name(loginWithDeviceFlow, "loginWithDeviceFlow");
371
+ async function listWorkspaces(deps = {}) {
372
+ const auth = loadAuth(deps);
373
+ if (!auth.regional_token) {
374
+ throw new Error("No regional_token found. Please run: sealos login");
375
+ }
376
+ const workspaces = await listRemoteWorkspaces(auth.region, auth.regional_token, deps);
377
+ return {
378
+ current: auth.current_workspace?.id || null,
379
+ workspaces: workspaces.map((workspace) => ({
380
+ uid: workspace.uid,
381
+ id: workspace.id,
382
+ teamName: workspace.teamName,
383
+ role: workspace.role,
384
+ nstype: workspace.nstype
385
+ }))
386
+ };
387
+ }
388
+ __name(listWorkspaces, "listWorkspaces");
389
+ async function switchWorkspace(target, deps = {}) {
390
+ if (!target) {
391
+ throw new Error("Usage: sealos auth switch <namespace-id-or-uid>");
392
+ }
393
+ const fullDeps = withDeps(deps);
394
+ const auth = loadAuth(fullDeps);
395
+ if (!auth.regional_token) {
396
+ throw new Error("No regional_token found. Please run: sealos login");
397
+ }
398
+ const workspaces = await listRemoteWorkspaces(auth.region, auth.regional_token, fullDeps);
399
+ if (workspaces.length === 0) {
400
+ throw new Error("No workspaces found");
401
+ }
402
+ const targetLower = target.toLowerCase();
403
+ const match = workspaces.find(
404
+ (workspace) => workspace.id === target || workspace.uid === target || workspace.id?.toLowerCase().includes(targetLower) || workspace.teamName?.toLowerCase().includes(targetLower)
405
+ );
406
+ if (!match?.uid) {
407
+ const available = workspaces.map((workspace) => ` ${workspace.id || "unknown"} (${workspace.teamName || "unknown"})`).join("\n");
408
+ throw new Error(`No workspace matching "${target}". Available:
409
+ ${available}`);
410
+ }
411
+ fullDeps.stderr.write(`Switching to workspace: ${match.id || match.uid} (${match.teamName || "unknown"})...
412
+ `);
413
+ const switchData = await switchRemoteWorkspace(auth.region, auth.regional_token, match.uid, fullDeps);
414
+ const newToken = switchData.data?.token;
415
+ if (!newToken) {
416
+ throw new Error("Switch response missing data.token");
417
+ }
418
+ const kubeconfigData = await getKubeconfig(auth.region, newToken, fullDeps);
419
+ const kubeconfig = kubeconfigData.data?.kubeconfig;
420
+ if (!kubeconfig) {
421
+ throw new Error("Kubeconfig response missing data.kubeconfig");
422
+ }
423
+ const nextAuth = {
424
+ ...auth,
425
+ regional_token: newToken,
426
+ current_workspace: {
427
+ uid: match.uid,
428
+ id: match.id,
429
+ teamName: match.teamName
430
+ }
431
+ };
432
+ saveAuth(nextAuth, fullDeps);
433
+ saveKubeconfig(kubeconfig, fullDeps);
434
+ fullDeps.stderr.write(`Switched to workspace: ${match.id || match.uid}
435
+ `);
436
+ return {
437
+ workspace: {
438
+ uid: match.uid,
439
+ id: match.id,
440
+ teamName: match.teamName
441
+ },
442
+ kubeconfig_path: fullDeps.paths.kubeconfigPath
443
+ };
444
+ }
445
+ __name(switchWorkspace, "switchWorkspace");
446
+
447
+ // src/lib/output.ts
448
+ import chalk from "chalk";
449
+ import { table } from "table";
450
+ import ora from "ora";
451
+ function outputJson(data) {
452
+ console.log(JSON.stringify(data, null, 2));
453
+ }
454
+ __name(outputJson, "outputJson");
455
+ function outputYaml(data) {
456
+ console.log("YAML output not implemented yet");
457
+ console.log(data);
458
+ }
459
+ __name(outputYaml, "outputYaml");
460
+ function outputTable(data) {
461
+ console.log(table(data));
462
+ }
463
+ __name(outputTable, "outputTable");
464
+ function formatOutput(data, format = "table") {
465
+ switch (format) {
466
+ case "json":
467
+ outputJson(data);
468
+ break;
469
+ case "yaml":
470
+ outputYaml(data);
471
+ break;
472
+ case "table":
473
+ if (Array.isArray(data)) {
474
+ outputTable(data);
475
+ } else {
476
+ console.log(data);
477
+ }
478
+ break;
479
+ default:
480
+ console.log(data);
481
+ }
482
+ }
483
+ __name(formatOutput, "formatOutput");
484
+ function success(message) {
485
+ console.log(chalk.green("\u2713"), message);
486
+ }
487
+ __name(success, "success");
488
+ function warn(message) {
489
+ console.warn(chalk.yellow("\u26A0"), message);
490
+ }
491
+ __name(warn, "warn");
492
+ function info(message) {
493
+ console.log(chalk.blue("\u2139"), message);
494
+ }
495
+ __name(info, "info");
496
+ function spinner(text) {
497
+ return ora(text).start();
498
+ }
499
+ __name(spinner, "spinner");
500
+
501
+ // src/lib/errors.ts
502
+ import chalk2 from "chalk";
503
+ var CliError = class extends Error {
504
+ constructor(message, exitCode = 1) {
505
+ super(message);
506
+ this.exitCode = exitCode;
507
+ this.name = "CliError";
508
+ }
509
+ exitCode;
510
+ static {
511
+ __name(this, "CliError");
512
+ }
513
+ };
514
+ var AuthError = class extends CliError {
515
+ static {
516
+ __name(this, "AuthError");
517
+ }
518
+ constructor(message = 'Authentication required. Please run "sealos login" first.') {
519
+ super(message, 1);
520
+ this.name = "AuthError";
521
+ }
522
+ };
523
+ var ConfigError = class extends CliError {
524
+ static {
525
+ __name(this, "ConfigError");
526
+ }
527
+ constructor(message) {
528
+ super(message, 1);
529
+ this.name = "ConfigError";
530
+ }
531
+ };
532
+ var ApiError = class extends CliError {
533
+ constructor(message, statusCode, code, details) {
534
+ super(message, 1);
535
+ this.statusCode = statusCode;
536
+ this.code = code;
537
+ this.details = details;
538
+ this.name = "ApiError";
539
+ }
540
+ statusCode;
541
+ code;
542
+ details;
543
+ static {
544
+ __name(this, "ApiError");
545
+ }
546
+ };
547
+ function mapApiError(status, body) {
548
+ const message = body?.error?.message || `API request failed with status ${status}`;
549
+ if (status === 401) {
550
+ return new AuthError(message);
551
+ }
552
+ return new ApiError(message, status, body?.error?.code, body?.error?.details);
553
+ }
554
+ __name(mapApiError, "mapApiError");
555
+ function handleError(error) {
556
+ if (error instanceof ApiError) {
557
+ console.error(chalk2.red("Error:"), error.message);
558
+ if (error.details) {
559
+ if (Array.isArray(error.details)) {
560
+ for (const d of error.details) {
561
+ console.error(chalk2.yellow(` ${d.field}:`), d.message);
562
+ }
563
+ } else {
564
+ console.error(chalk2.yellow(" Details:"), error.details);
565
+ }
566
+ }
567
+ process.exit(error.exitCode);
568
+ }
569
+ if (error instanceof CliError) {
570
+ console.error(chalk2.red("Error:"), error.message);
571
+ process.exit(error.exitCode);
572
+ }
573
+ if (error instanceof Error) {
574
+ console.error(chalk2.red("Error:"), error.message);
575
+ if (process.env.DEBUG) {
576
+ console.error(error.stack);
577
+ }
578
+ process.exit(1);
579
+ }
580
+ console.error(chalk2.red("Error:"), "An unknown error occurred");
581
+ process.exit(1);
582
+ }
583
+ __name(handleError, "handleError");
584
+
585
+ // src/commands/auth/login.ts
586
+ function createLoginCommand() {
587
+ return new Command("login").description("Login to Sealos Cloud").argument("[region]", "Sealos region URL (e.g., https://usw-1.sealos.io)").option("-t, --token <token>", "Store a regional token without OAuth device login").option("-o, --output <format>", "Output format: json, table", "table").action(async (region, options) => {
588
+ try {
589
+ const result = options.token ? await loginWithToken(region, options.token) : await loginWithDeviceFlow(region);
590
+ if (options.token) {
591
+ warn("Token login is limited: kubeconfig is not generated. Use device login for full auth.");
592
+ }
593
+ if (options.output === "json") {
594
+ outputJson(result);
595
+ } else {
596
+ success(`Logged in to ${result.region}`);
597
+ info(`Workspace: ${result.workspace}`);
598
+ if (result.kubeconfig_path) {
599
+ info(`Kubeconfig: ${result.kubeconfig_path}`);
600
+ }
601
+ }
602
+ } catch (error) {
603
+ handleError(error);
604
+ }
605
+ });
606
+ }
607
+ __name(createLoginCommand, "createLoginCommand");
608
+
609
+ // src/commands/auth/logout.ts
610
+ import { Command as Command2 } from "commander";
611
+ function createLogoutCommand() {
612
+ return new Command2("logout").description("Logout from Sealos Cloud").action(async () => {
613
+ try {
614
+ const status = checkAuth();
615
+ if (!status.authenticated) {
616
+ warn("You are not logged in");
617
+ return;
618
+ }
619
+ clearAuth();
620
+ success("Logged out from Sealos Cloud");
621
+ } catch (error) {
622
+ handleError(error);
623
+ }
624
+ });
625
+ }
626
+ __name(createLogoutCommand, "createLogoutCommand");
627
+
628
+ // src/commands/auth/whoami.ts
629
+ import { Command as Command3 } from "commander";
630
+ function createWhoamiCommand() {
631
+ return new Command3("whoami").description("Display current user information").option("-o, --output <format>", "Output format: json, table", "table").action(async (options) => {
632
+ try {
633
+ const authInfo = getAuthInfo();
634
+ if (!authInfo.authenticated) {
635
+ throw new AuthError();
636
+ }
637
+ if (options.output === "json") {
638
+ outputJson(authInfo);
639
+ return;
640
+ }
641
+ const data = [
642
+ ["Field", "Value"],
643
+ ["Authenticated", "true"],
644
+ ["Region", authInfo.region || "unknown"],
645
+ ["Auth Method", authInfo.auth_method || "unknown"],
646
+ ["Workspace", authInfo.current_workspace?.id || authInfo.workspace || "unknown"],
647
+ ["Team", authInfo.current_workspace?.teamName || "unknown"],
648
+ ["Kubeconfig", authInfo.kubeconfig_path || "unknown"],
649
+ ["Authenticated At", authInfo.authenticated_at || "unknown"]
650
+ ];
651
+ outputTable(data);
652
+ } catch (error) {
653
+ handleError(error);
654
+ }
655
+ });
656
+ }
657
+ __name(createWhoamiCommand, "createWhoamiCommand");
658
+
659
+ // src/commands/auth/index.ts
660
+ function registerAuthCommands(program) {
661
+ program.addCommand(createLoginCommand());
662
+ program.addCommand(createLogoutCommand());
663
+ program.addCommand(createWhoamiCommand());
664
+ program.addCommand(createAuthCommand());
665
+ }
666
+ __name(registerAuthCommands, "registerAuthCommands");
667
+ function createAuthCommand() {
668
+ const authCmd = new Command4("auth").description("Manage Sealos authentication");
669
+ authCmd.command("check").description("Check authentication status").option("-o, --output <format>", "Output format: json, table", "json").action(async (options) => {
670
+ try {
671
+ const status = checkAuth();
672
+ if (options.output === "table") {
673
+ outputTable([
674
+ ["Field", "Value"],
675
+ ["Authenticated", String(status.authenticated)],
676
+ ["Region", status.region || "unknown"],
677
+ ["Workspace", status.workspace || "unknown"],
678
+ ["Kubeconfig", status.kubeconfig_path || "unknown"]
679
+ ]);
680
+ } else {
681
+ outputJson(status);
682
+ }
683
+ } catch (error) {
684
+ handleError(error);
685
+ }
686
+ });
687
+ authCmd.command("info").description("Show current auth details").option("-o, --output <format>", "Output format: json, table", "json").action(async (options) => {
688
+ try {
689
+ const info2 = getAuthInfo();
690
+ if (!info2.authenticated) {
691
+ throw new AuthError();
692
+ }
693
+ if (options.output === "table") {
694
+ outputTable([
695
+ ["Field", "Value"],
696
+ ["Authenticated", String(info2.authenticated)],
697
+ ["Region", info2.region || "unknown"],
698
+ ["Auth Method", info2.auth_method || "unknown"],
699
+ ["Workspace", info2.current_workspace?.id || info2.workspace || "unknown"],
700
+ ["Team", info2.current_workspace?.teamName || "unknown"],
701
+ ["Kubeconfig", info2.kubeconfig_path || "unknown"],
702
+ ["Authenticated At", info2.authenticated_at || "unknown"]
703
+ ]);
704
+ } else {
705
+ outputJson(info2);
706
+ }
707
+ } catch (error) {
708
+ handleError(error);
709
+ }
710
+ });
711
+ authCmd.command("list").description("List all workspaces").option("-o, --output <format>", "Output format: json, table", "table").action(async (options) => {
712
+ try {
713
+ const result = await listWorkspaces();
714
+ if (options.output === "json") {
715
+ outputJson(result);
716
+ return;
717
+ }
718
+ outputTable([
719
+ ["UID", "ID", "TEAM", "ROLE", "TYPE", "CURRENT"],
720
+ ...result.workspaces.map((workspace) => [
721
+ workspace.uid || "",
722
+ workspace.id || "",
723
+ workspace.teamName || "",
724
+ workspace.role || "",
725
+ workspace.nstype || "",
726
+ workspace.id === result.current ? "*" : ""
727
+ ])
728
+ ]);
729
+ } catch (error) {
730
+ handleError(error);
731
+ }
732
+ });
733
+ authCmd.command("switch").description("Switch workspace").argument("<namespace>", "Workspace id, uid, or team name").option("-o, --output <format>", "Output format: json, table", "table").action(async (namespace, options) => {
734
+ try {
735
+ const result = await switchWorkspace(namespace);
736
+ if (options.output === "json") {
737
+ outputJson(result);
738
+ return;
739
+ }
740
+ success(`Switched to workspace: ${result.workspace.id || result.workspace.uid || namespace}`);
741
+ } catch (error) {
742
+ handleError(error);
743
+ }
744
+ });
745
+ return authCmd;
746
+ }
747
+ __name(createAuthCommand, "createAuthCommand");
748
+
749
+ // src/commands/workspace/index.ts
750
+ import { Command as Command5 } from "commander";
751
+
752
+ // src/lib/config.ts
753
+ import { homedir as homedir2 } from "os";
754
+ import { join as join2 } from "path";
755
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
756
+ var CONFIG_DIR = join2(homedir2(), ".sealos");
757
+ var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
758
+ function ensureConfigDir() {
759
+ if (!existsSync2(CONFIG_DIR)) {
760
+ mkdirSync2(CONFIG_DIR, { recursive: true });
761
+ }
762
+ }
763
+ __name(ensureConfigDir, "ensureConfigDir");
764
+ function readConfig() {
765
+ ensureConfigDir();
766
+ if (!existsSync2(CONFIG_FILE)) {
767
+ const defaultConfig = {
768
+ currentContext: "",
769
+ contexts: []
770
+ };
771
+ return defaultConfig;
772
+ }
773
+ try {
774
+ const content = readFileSync2(CONFIG_FILE, "utf-8");
775
+ return JSON.parse(content);
776
+ } catch (error) {
777
+ throw new Error(`Failed to read config file: ${error}`);
778
+ }
779
+ }
780
+ __name(readConfig, "readConfig");
781
+ function writeConfig(config) {
782
+ ensureConfigDir();
783
+ try {
784
+ writeFileSync2(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
785
+ } catch (error) {
786
+ throw new Error(`Failed to write config file: ${error}`);
787
+ }
788
+ }
789
+ __name(writeConfig, "writeConfig");
790
+ function getCurrentContext() {
791
+ const config = readConfig();
792
+ if (!config.currentContext) {
793
+ return null;
794
+ }
795
+ const context = config.contexts.find((ctx) => ctx.name === config.currentContext);
796
+ return context || null;
797
+ }
798
+ __name(getCurrentContext, "getCurrentContext");
799
+ function getConfigValue(key) {
800
+ const config = readConfig();
801
+ return config[key];
802
+ }
803
+ __name(getConfigValue, "getConfigValue");
804
+ function setConfigValue(key, value) {
805
+ const config = readConfig();
806
+ config[key] = value;
807
+ writeConfig(config);
808
+ }
809
+ __name(setConfigValue, "setConfigValue");
810
+
811
+ // src/commands/workspace/index.ts
812
+ function createWorkspaceCommand() {
813
+ const workspaceCmd = new Command5("workspace").alias("ws").description("Manage workspaces");
814
+ workspaceCmd.command("switch").description("Switch to another workspace").argument("<name>", "Workspace name").action(async (name) => {
815
+ try {
816
+ const context = getCurrentContext();
817
+ if (context) {
818
+ context.workspace = name;
819
+ }
820
+ success(`Switched to workspace: ${name}`);
821
+ } catch (error) {
822
+ handleError(error);
823
+ }
824
+ });
825
+ workspaceCmd.command("list").description("List all workspaces").action(async () => {
826
+ try {
827
+ const context = getCurrentContext();
828
+ if (!context) {
829
+ throw new AuthError();
830
+ }
831
+ const data = [
832
+ ["NAME", "STATUS", "CURRENT"],
833
+ ["default", "Active", context.workspace === "default" ? "*" : ""],
834
+ ["production", "Active", context.workspace === "production" ? "*" : ""]
835
+ ];
836
+ outputTable(data);
837
+ info("API integration needed for real data");
838
+ } catch (error) {
839
+ handleError(error);
840
+ }
841
+ });
842
+ workspaceCmd.command("current").description("Show current workspace").action(async () => {
843
+ try {
844
+ const context = getCurrentContext();
845
+ if (!context) {
846
+ throw new AuthError();
847
+ }
848
+ const data = [
849
+ ["Field", "Value"],
850
+ ["Workspace", context.workspace],
851
+ ["Context", context.name]
852
+ ];
853
+ outputTable(data);
854
+ } catch (error) {
855
+ handleError(error);
856
+ }
857
+ });
858
+ return workspaceCmd;
859
+ }
860
+ __name(createWorkspaceCommand, "createWorkspaceCommand");
861
+
862
+ // src/commands/devbox/index.ts
863
+ import { Command as Command6 } from "commander";
864
+ function createDevboxCommand() {
865
+ const devboxCmd = new Command6("devbox").alias("dev").description("Manage devbox instances");
866
+ devboxCmd.command("create").description("Create a new devbox").argument("[path]", "Project path", ".").option("--name <name>", "Devbox name").option("--template <template>", "Template to use", "default").option("--cpu <cpu>", "CPU configuration", "1c").option("--memory <memory>", "Memory configuration", "2g").option("--port <port>", "Port number").option("--config <config>", "Config file path").action(async (path, options) => {
867
+ try {
868
+ const context = getCurrentContext();
869
+ if (!context) {
870
+ throw new AuthError();
871
+ }
872
+ const spin = spinner("Creating devbox...");
873
+ spin.succeed("Devbox created successfully");
874
+ success("Devbox URL: https://example.sealos.run");
875
+ info(`Run "sealos devbox connect ${options.name || "devbox"}" to connect`);
876
+ } catch (error) {
877
+ handleError(error);
878
+ }
879
+ });
880
+ devboxCmd.command("list").description("List all devboxes").option("-o, --output <format>", "Output format: json, yaml, table", "table").option("--selector <selector>", "Label selector").action(async (options) => {
881
+ try {
882
+ const context = getCurrentContext();
883
+ if (!context) {
884
+ throw new AuthError();
885
+ }
886
+ const data = [
887
+ ["NAME", "STATUS", "CPU", "MEMORY", "CREATED"],
888
+ ["my-devbox", "Running", "2c", "4g", "2h ago"],
889
+ ["test-box", "Stopped", "1c", "2g", "1d ago"]
890
+ ];
891
+ if (options.output === "json") {
892
+ formatOutput({ items: data.slice(1) }, "json");
893
+ } else if (options.output === "yaml") {
894
+ formatOutput({ items: data.slice(1) }, "yaml");
895
+ } else {
896
+ outputTable(data);
897
+ }
898
+ } catch (error) {
899
+ handleError(error);
900
+ }
901
+ });
902
+ devboxCmd.command("get").description("Get devbox details").argument("<name>", "Devbox name").action(async (name) => {
903
+ try {
904
+ const context = getCurrentContext();
905
+ if (!context) {
906
+ throw new AuthError();
907
+ }
908
+ const data = [
909
+ ["Field", "Value"],
910
+ ["Name", name],
911
+ ["Status", "Running"],
912
+ ["CPU", "2c"],
913
+ ["Memory", "4g"],
914
+ ["URL", "https://example.sealos.run"]
915
+ ];
916
+ outputTable(data);
917
+ } catch (error) {
918
+ handleError(error);
919
+ }
920
+ });
921
+ const actions = ["start", "stop", "restart", "delete"];
922
+ actions.forEach((action) => {
923
+ const cmd = devboxCmd.command(action).description(`${action.charAt(0).toUpperCase() + action.slice(1)} a devbox`).argument("<name>", "Devbox name");
924
+ if (action === "delete") {
925
+ cmd.option("-f, --force", "Force delete without confirmation");
926
+ }
927
+ cmd.action(async (name, options) => {
928
+ try {
929
+ const context = getCurrentContext();
930
+ if (!context) {
931
+ throw new AuthError();
932
+ }
933
+ const spin = spinner(`${action}ing devbox...`);
934
+ spin.succeed(`Devbox ${action}ed successfully`);
935
+ } catch (error) {
936
+ handleError(error);
937
+ }
938
+ });
939
+ });
940
+ devboxCmd.command("connect").description("Connect to a devbox").argument("<name>", "Devbox name").option("--ide <ide>", "IDE type: vscode, cursor", "vscode").action(async (name, options) => {
941
+ try {
942
+ const context = getCurrentContext();
943
+ if (!context) {
944
+ throw new AuthError();
945
+ }
946
+ info(`Opening ${name} in ${options.ide}...`);
947
+ success(`Connect URL: ${options.ide}://remote-ssh/devbox-${name}`);
948
+ } catch (error) {
949
+ handleError(error);
950
+ }
951
+ });
952
+ devboxCmd.command("publish").description("Publish a devbox").argument("<name>", "Devbox name").action(async (name) => {
953
+ try {
954
+ const context = getCurrentContext();
955
+ if (!context) {
956
+ throw new AuthError();
957
+ }
958
+ const spin = spinner("Publishing devbox...");
959
+ spin.succeed("Devbox published successfully");
960
+ } catch (error) {
961
+ handleError(error);
962
+ }
963
+ });
964
+ const templateCmd = devboxCmd.command("template").description("Manage devbox templates");
965
+ templateCmd.command("build").description("Build a devbox template").option("--name <name>", "Template name").action(async (options) => {
966
+ try {
967
+ info("Building template...");
968
+ } catch (error) {
969
+ handleError(error);
970
+ }
971
+ });
972
+ templateCmd.command("list").description("List devbox templates").action(async () => {
973
+ try {
974
+ const data = [
975
+ ["NAME", "DESCRIPTION", "VERSION"],
976
+ ["nextjs", "Next.js development environment", "1.0.0"],
977
+ ["python", "Python 3.11 environment", "1.0.0"]
978
+ ];
979
+ outputTable(data);
980
+ } catch (error) {
981
+ handleError(error);
982
+ }
983
+ });
984
+ return devboxCmd;
985
+ }
986
+ __name(createDevboxCommand, "createDevboxCommand");
987
+
988
+ // src/commands/s3/index.ts
989
+ import { Command as Command7 } from "commander";
990
+ function createS3Command() {
991
+ const s3Cmd = new Command7("s3").description("Manage S3 object storage");
992
+ s3Cmd.command("upload").description("Upload files to S3").argument("<source>", "Source file or directory").argument("[destination]", "Destination path").option("--bucket <bucket>", "Bucket name").option("--acl <acl>", "Access control: private, public-read").action(async (source, destination, options) => {
993
+ try {
994
+ console.log("TODO: Implement s3 upload", { source, destination, options });
995
+ } catch (error) {
996
+ handleError(error);
997
+ }
998
+ });
999
+ return s3Cmd;
1000
+ }
1001
+ __name(createS3Command, "createS3Command");
1002
+
1003
+ // src/commands/database/index.ts
1004
+ import { Command as Command8 } from "commander";
1005
+ import chalk3 from "chalk";
1006
+
1007
+ // src/lib/api-client.ts
1008
+ import createClient from "openapi-fetch";
1009
+ function resolveHost(options) {
1010
+ let authRegion;
1011
+ try {
1012
+ authRegion = loadAuth().region;
1013
+ } catch {
1014
+ authRegion = void 0;
1015
+ }
1016
+ const host = options?.baseUrl || process.env.SEALOS_REGION || authRegion || DEFAULT_SEALOS_REGION;
1017
+ if (!host) {
1018
+ throw new ConfigError('No Sealos Cloud host configured. Run "sealos login <host>" first.');
1019
+ }
1020
+ return host.replace(/\/+$/, "");
1021
+ }
1022
+ __name(resolveHost, "resolveHost");
1023
+ function resolveDbproviderHost(host) {
1024
+ return resolvePrefixedHost(host, "dbprovider");
1025
+ }
1026
+ __name(resolveDbproviderHost, "resolveDbproviderHost");
1027
+ function resolveTemplateProviderHost(host) {
1028
+ return resolvePrefixedHost(host, "template");
1029
+ }
1030
+ __name(resolveTemplateProviderHost, "resolveTemplateProviderHost");
1031
+ function resolvePrefixedHost(host, prefix) {
1032
+ const url = new URL(host);
1033
+ if (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname.startsWith(`${prefix}.`)) {
1034
+ return url.toString().replace(/\/+$/, "");
1035
+ }
1036
+ url.hostname = `${prefix}.${url.hostname}`;
1037
+ return url.toString().replace(/\/+$/, "");
1038
+ }
1039
+ __name(resolvePrefixedHost, "resolvePrefixedHost");
1040
+ function resolveDatabaseHost(options) {
1041
+ const override = process.env.SEALOS_DATABASE_HOST?.trim();
1042
+ if (override) {
1043
+ return override.replace(/\/+$/, "");
1044
+ }
1045
+ return resolveDbproviderHost(resolveHost(options));
1046
+ }
1047
+ __name(resolveDatabaseHost, "resolveDatabaseHost");
1048
+ function resolveTemplateHost(options) {
1049
+ return resolveTemplateProviderHost(resolveHost(options));
1050
+ }
1051
+ __name(resolveTemplateHost, "resolveTemplateHost");
1052
+ function createTemplateClient(options) {
1053
+ return createClient({ baseUrl: `${resolveTemplateHost(options)}/api/v2alpha` });
1054
+ }
1055
+ __name(createTemplateClient, "createTemplateClient");
1056
+ function createDatabaseClient(options) {
1057
+ return createClient({ baseUrl: `${resolveDatabaseHost(options)}/api/v2alpha` });
1058
+ }
1059
+ __name(createDatabaseClient, "createDatabaseClient");
1060
+
1061
+ // src/lib/with-auth.ts
1062
+ function withAuth(options, fn) {
1063
+ return async (...args) => {
1064
+ const spin = spinner(options.spinnerText);
1065
+ try {
1066
+ const auth = requireAuth();
1067
+ await fn({ auth, spinner: spin }, ...args);
1068
+ } catch (error) {
1069
+ spin.fail();
1070
+ handleError(error);
1071
+ }
1072
+ };
1073
+ }
1074
+ __name(withAuth, "withAuth");
1075
+ function withErrorHandling(options, fn) {
1076
+ return async (...args) => {
1077
+ const spin = spinner(options.spinnerText);
1078
+ try {
1079
+ await fn({ spinner: spin }, ...args);
1080
+ } catch (error) {
1081
+ spin.fail();
1082
+ handleError(error);
1083
+ }
1084
+ };
1085
+ }
1086
+ __name(withErrorHandling, "withErrorHandling");
1087
+
1088
+ // src/commands/database/index.ts
1089
+ var SUPPORTED_DATABASE_TYPES = [
1090
+ "postgresql",
1091
+ "mongodb",
1092
+ "apecloud-mysql",
1093
+ "mysql",
1094
+ "redis",
1095
+ "kafka",
1096
+ "qdrant",
1097
+ "nebula",
1098
+ "weaviate",
1099
+ "milvus",
1100
+ "pulsar",
1101
+ "clickhouse"
1102
+ ];
1103
+ var SUPPORTED_LOG_DB_TYPES = [
1104
+ "postgresql",
1105
+ "mongodb",
1106
+ "mysql",
1107
+ "redis"
1108
+ ];
1109
+ var SUPPORTED_LOG_TYPES = [
1110
+ "runtimeLog",
1111
+ "slowQuery",
1112
+ "errorLog"
1113
+ ];
1114
+ function collectOption(value, previous) {
1115
+ return [...previous, value];
1116
+ }
1117
+ __name(collectOption, "collectOption");
1118
+ function parseKeyValueArgs(pairs) {
1119
+ const values = {};
1120
+ for (const pair of pairs) {
1121
+ const index = pair.indexOf("=");
1122
+ if (index === -1) {
1123
+ throw new Error(`Invalid KEY=VALUE format: "${pair}"`);
1124
+ }
1125
+ values[pair.slice(0, index)] = pair.slice(index + 1);
1126
+ }
1127
+ return values;
1128
+ }
1129
+ __name(parseKeyValueArgs, "parseKeyValueArgs");
1130
+ function normalizeDatabaseType(type) {
1131
+ const normalized = type.trim().toLowerCase();
1132
+ const aliases = {
1133
+ postgres: "postgresql",
1134
+ postgresql: "postgresql",
1135
+ mongo: "mongodb",
1136
+ mongodb: "mongodb"
1137
+ };
1138
+ const resolved = aliases[normalized] || normalized;
1139
+ if (!SUPPORTED_DATABASE_TYPES.includes(resolved)) {
1140
+ throw new Error(`Unsupported database type "${type}"`);
1141
+ }
1142
+ return resolved;
1143
+ }
1144
+ __name(normalizeDatabaseType, "normalizeDatabaseType");
1145
+ function normalizeLogDbType(type) {
1146
+ const normalized = normalizeDatabaseType(type);
1147
+ if (!SUPPORTED_LOG_DB_TYPES.includes(normalized)) {
1148
+ throw new Error(`Logs API only supports db types: ${SUPPORTED_LOG_DB_TYPES.join(", ")}`);
1149
+ }
1150
+ return normalized;
1151
+ }
1152
+ __name(normalizeLogDbType, "normalizeLogDbType");
1153
+ function normalizeLogType(type) {
1154
+ const normalized = type.trim();
1155
+ if (!SUPPORTED_LOG_TYPES.includes(normalized)) {
1156
+ throw new Error(`Unsupported log type "${type}". Use one of: ${SUPPORTED_LOG_TYPES.join(", ")}`);
1157
+ }
1158
+ return normalized;
1159
+ }
1160
+ __name(normalizeLogType, "normalizeLogType");
1161
+ function parseNumericValue(value, field) {
1162
+ const normalized = value.trim().toLowerCase();
1163
+ let raw = normalized;
1164
+ if (field === "cpu" && normalized.endsWith("c")) {
1165
+ raw = normalized.slice(0, -1);
1166
+ }
1167
+ if ((field === "memory" || field === "storage") && /gi?$|gb$|g$/i.test(normalized)) {
1168
+ raw = normalized.replace(/gi?$|gb$|g$/i, "");
1169
+ }
1170
+ const parsed = Number(raw);
1171
+ if (!Number.isFinite(parsed)) {
1172
+ throw new Error(`Invalid ${field} value "${value}"`);
1173
+ }
1174
+ return parsed;
1175
+ }
1176
+ __name(parseNumericValue, "parseNumericValue");
1177
+ function parseIntegerValue(value, field) {
1178
+ const parsed = parseNumericValue(value, field);
1179
+ if (!Number.isInteger(parsed)) {
1180
+ throw new Error(`${field} must be an integer`);
1181
+ }
1182
+ return parsed;
1183
+ }
1184
+ __name(parseIntegerValue, "parseIntegerValue");
1185
+ function buildQuota(options) {
1186
+ const quota = {};
1187
+ if (options.cpu !== void 0) quota.cpu = parseNumericValue(options.cpu, "cpu");
1188
+ if (options.memory !== void 0) quota.memory = parseNumericValue(options.memory, "memory");
1189
+ if (options.storage !== void 0) quota.storage = parseNumericValue(options.storage, "storage");
1190
+ if (options.replicas !== void 0) quota.replicas = parseIntegerValue(options.replicas, "replicas");
1191
+ return quota;
1192
+ }
1193
+ __name(buildQuota, "buildQuota");
1194
+ function buildAutoBackup(options) {
1195
+ const autoBackup = {};
1196
+ if (options.backupStart) autoBackup.start = true;
1197
+ if (options.backupType) autoBackup.type = options.backupType;
1198
+ if (options.backupWeek.length > 0) autoBackup.week = options.backupWeek;
1199
+ if (options.backupHour) autoBackup.hour = options.backupHour;
1200
+ if (options.backupMinute) autoBackup.minute = options.backupMinute;
1201
+ if (options.backupSaveTime) autoBackup.saveTime = parseIntegerValue(options.backupSaveTime, "backup-save-time");
1202
+ if (options.backupSaveType) autoBackup.saveType = options.backupSaveType;
1203
+ return Object.keys(autoBackup).length > 0 ? autoBackup : void 0;
1204
+ }
1205
+ __name(buildAutoBackup, "buildAutoBackup");
1206
+ function formatValue(value) {
1207
+ if (value === void 0 || value === null || value === "") return "-";
1208
+ return String(value);
1209
+ }
1210
+ __name(formatValue, "formatValue");
1211
+ function summarizeVersions(versions) {
1212
+ if (versions.length <= 3) return versions.join(", ");
1213
+ return `${versions.slice(0, 3).join(", ")} ... (${versions.length} total)`;
1214
+ }
1215
+ __name(summarizeVersions, "summarizeVersions");
1216
+ function extractVersionsMap(payload) {
1217
+ if (payload && typeof payload === "object" && !Array.isArray(payload)) {
1218
+ if (payload.data && typeof payload.data === "object" && !Array.isArray(payload.data)) {
1219
+ return payload.data;
1220
+ }
1221
+ return payload;
1222
+ }
1223
+ throw new Error("Unexpected versions response shape");
1224
+ }
1225
+ __name(extractVersionsMap, "extractVersionsMap");
1226
+ function printDatabaseDetail(database) {
1227
+ console.log(chalk3.bold(`
1228
+ ${database.name}
1229
+ `));
1230
+ console.log(` ${chalk3.dim("Type:")} ${formatValue(database.type)}`);
1231
+ console.log(` ${chalk3.dim("Version:")} ${formatValue(database.version)}`);
1232
+ console.log(` ${chalk3.dim("Status:")} ${formatValue(database.status)}`);
1233
+ console.log(` ${chalk3.dim("Created:")} ${formatValue(database.createdAt)}`);
1234
+ console.log(` ${chalk3.dim("Termination policy:")} ${formatValue(database.terminationPolicy)}`);
1235
+ console.log(` ${chalk3.dim("UID:")} ${formatValue(database.uid)}`);
1236
+ console.log(` ${chalk3.dim("Resource type:")} ${formatValue(database.resourceType)}`);
1237
+ if (database.quota) {
1238
+ console.log(`
1239
+ ${chalk3.dim("Resources:")}`);
1240
+ console.log(` CPU: ${formatValue(database.quota.cpu)} core(s)`);
1241
+ console.log(` Memory: ${formatValue(database.quota.memory)} GB`);
1242
+ console.log(` Storage: ${formatValue(database.quota.storage)} GB`);
1243
+ console.log(` Replicas: ${formatValue(database.quota.replicas)}`);
1244
+ }
1245
+ if (database.connection) {
1246
+ const privateReady = database.connection.privateConnection != null;
1247
+ const publicReady = database.connection.publicConnection != null;
1248
+ console.log(`
1249
+ ${chalk3.dim("Connectivity:")}`);
1250
+ console.log(` Private: ${privateReady ? "available" : "not ready"}`);
1251
+ console.log(` Public: ${publicReady ? "enabled" : "disabled"}`);
1252
+ }
1253
+ if (database.autoBackup) {
1254
+ console.log(`
1255
+ ${chalk3.dim("Auto backup:")}`);
1256
+ console.log(` Enabled: ${database.autoBackup.start ? "yes" : "no"}`);
1257
+ if (database.autoBackup.type) {
1258
+ console.log(` Schedule: ${database.autoBackup.type}`);
1259
+ }
1260
+ if (database.autoBackup.week?.length) {
1261
+ console.log(` Weekdays: ${database.autoBackup.week.join(", ")}`);
1262
+ }
1263
+ if (database.autoBackup.hour || database.autoBackup.minute) {
1264
+ console.log(` Time: ${formatValue(database.autoBackup.hour)}:${formatValue(database.autoBackup.minute)}`);
1265
+ }
1266
+ if (database.autoBackup.saveTime || database.autoBackup.saveType) {
1267
+ console.log(` Retention: ${formatValue(database.autoBackup.saveTime)} ${formatValue(database.autoBackup.saveType)}`);
1268
+ }
1269
+ }
1270
+ const params = database.parameterConfig ? Object.entries(database.parameterConfig).filter(([, value]) => value !== void 0 && value !== null && value !== "") : [];
1271
+ if (params.length > 0) {
1272
+ console.log(`
1273
+ ${chalk3.dim("Parameters:")}`);
1274
+ const rows = [[chalk3.bold("Key"), chalk3.bold("Value")]];
1275
+ for (const [key, value] of params) {
1276
+ rows.push([key, formatValue(value)]);
1277
+ }
1278
+ outputTable(rows);
1279
+ }
1280
+ if (database.pods?.length > 0) {
1281
+ console.log(`
1282
+ ${chalk3.dim("Pods:")}`);
1283
+ const rows = [[chalk3.bold("Name"), chalk3.bold("Status")]];
1284
+ for (const pod of database.pods) {
1285
+ rows.push([formatValue(pod.name), formatValue(pod.status)]);
1286
+ }
1287
+ outputTable(rows);
1288
+ }
1289
+ }
1290
+ __name(printDatabaseDetail, "printDatabaseDetail");
1291
+ function printConnectionDetail(connection) {
1292
+ const rows = [[chalk3.bold("Field"), chalk3.bold("Value")]];
1293
+ const privateConnection = connection?.privateConnection;
1294
+ const publicConnection = connection?.publicConnection;
1295
+ if (privateConnection && typeof privateConnection === "object") {
1296
+ rows.push(["Private endpoint", formatValue(privateConnection.endpoint)]);
1297
+ rows.push(["Private host", formatValue(privateConnection.host)]);
1298
+ rows.push(["Private port", formatValue(privateConnection.port)]);
1299
+ rows.push(["Username", formatValue(privateConnection.username)]);
1300
+ rows.push(["Password", formatValue(privateConnection.password)]);
1301
+ rows.push(["Connection string", formatValue(privateConnection.connectionString)]);
1302
+ }
1303
+ if (publicConnection !== void 0 && publicConnection !== null) {
1304
+ rows.push(["Public connection", formatValue(publicConnection)]);
1305
+ }
1306
+ if (rows.length === 1) {
1307
+ console.log("Connection information is not available yet.");
1308
+ return;
1309
+ }
1310
+ outputTable(rows);
1311
+ }
1312
+ __name(printConnectionDetail, "printConnectionDetail");
1313
+ function createDatabaseCommand() {
1314
+ const dbCmd = new Command8("database").alias("db").description("Manage databases");
1315
+ dbCmd.command("list").description("List databases").option("-o, --output <format>", "Output format (json|table)", "table").action(withAuth({ spinnerText: "Loading databases..." }, async (ctx, options) => {
1316
+ const client = createDatabaseClient();
1317
+ const { data, error, response } = await client.GET("/databases", {
1318
+ headers: ctx.auth
1319
+ });
1320
+ if (error) throw mapApiError(response.status, error);
1321
+ ctx.spinner.stop();
1322
+ if (options.output === "json") {
1323
+ outputJson(data);
1324
+ return;
1325
+ }
1326
+ if (data.length === 0) {
1327
+ console.log("No databases found.");
1328
+ return;
1329
+ }
1330
+ const rows = [[
1331
+ chalk3.bold("Name"),
1332
+ chalk3.bold("Type"),
1333
+ chalk3.bold("Version"),
1334
+ chalk3.bold("Status"),
1335
+ chalk3.bold("CPU"),
1336
+ chalk3.bold("Memory"),
1337
+ chalk3.bold("Storage"),
1338
+ chalk3.bold("Replicas")
1339
+ ]];
1340
+ for (const database of data) {
1341
+ rows.push([
1342
+ formatValue(database.name),
1343
+ formatValue(database.type),
1344
+ formatValue(database.version),
1345
+ formatValue(database.status),
1346
+ formatValue(database.quota?.cpu),
1347
+ formatValue(database.quota?.memory),
1348
+ formatValue(database.quota?.storage),
1349
+ formatValue(database.quota?.replicas)
1350
+ ]);
1351
+ }
1352
+ outputTable(rows);
1353
+ }));
1354
+ dbCmd.command("versions").description("List supported database versions (public endpoint)").option("-o, --output <format>", "Output format (json|table)", "table").option("--host <host>", "Sealos region host for public version lookup, e.g. https://gzg.sealos.run").option("--type <type>", "Filter versions by database type").action(withErrorHandling({ spinnerText: "Loading versions..." }, async (ctx, options) => {
1355
+ const client = createDatabaseClient({ baseUrl: options.host });
1356
+ const { data, error, response } = await client.GET("/databases/versions");
1357
+ if (error) throw mapApiError(response.status, error);
1358
+ ctx.spinner.stop();
1359
+ if (options.output === "json") {
1360
+ if (!options.type) {
1361
+ outputJson(data);
1362
+ return;
1363
+ }
1364
+ const versionsMap2 = extractVersionsMap(data);
1365
+ const databaseType = normalizeDatabaseType(options.type);
1366
+ outputJson({ [databaseType]: versionsMap2[databaseType] ?? [] });
1367
+ return;
1368
+ }
1369
+ const versionsMap = extractVersionsMap(data);
1370
+ if (options.type) {
1371
+ const databaseType = normalizeDatabaseType(options.type);
1372
+ const versions = versionsMap[databaseType] ?? [];
1373
+ if (versions.length === 0) {
1374
+ console.log(`No versions found for database type "${databaseType}".`);
1375
+ return;
1376
+ }
1377
+ const rows2 = [[chalk3.bold("Type"), chalk3.bold("Version")]];
1378
+ for (const version of versions) {
1379
+ rows2.push([databaseType, version]);
1380
+ }
1381
+ outputTable(rows2);
1382
+ return;
1383
+ }
1384
+ const rows = [[chalk3.bold("Type"), chalk3.bold("Versions")]];
1385
+ for (const [type, versions] of Object.entries(versionsMap)) {
1386
+ rows.push([type, summarizeVersions(versions)]);
1387
+ }
1388
+ outputTable(rows);
1389
+ }));
1390
+ dbCmd.command("create <type>").description("Create a database").requiredOption("--name <name>", "Database name").option("--version <version>", "Database version").option("--cpu <cpu>", "CPU cores per replica", "1").option("--memory <memory>", "Memory in GB per replica", "1").option("--storage <storage>", "Storage in GB per replica", "3").option("--replicas <replicas>", "Replica count", "1").option("--termination-policy <policy>", "Termination policy (delete|wipeout)").option("--backup-start", "Enable automatic backups").option("--backup-type <type>", "Automatic backup frequency (day|hour|week)").option("--backup-week <day>", "Weekday for weekly backups", collectOption, []).option("--backup-hour <hour>", "Backup hour (00-23)").option("--backup-minute <minute>", "Backup minute (00-59)").option("--backup-save-time <count>", "Retention count").option("--backup-save-type <type>", "Retention unit (days|hours|weeks|months)").option("--param <KEY=VALUE>", "Database parameter override", collectOption, []).option("-o, --output <format>", "Output format (json|table)", "table").action(withAuth({
1391
+ spinnerText: "Creating database..."
1392
+ }, async (ctx, type, options) => {
1393
+ const client = createDatabaseClient();
1394
+ const quota = {
1395
+ cpu: parseNumericValue(options.cpu, "cpu"),
1396
+ memory: parseNumericValue(options.memory, "memory"),
1397
+ storage: parseNumericValue(options.storage, "storage"),
1398
+ replicas: parseIntegerValue(options.replicas, "replicas")
1399
+ };
1400
+ const autoBackup = buildAutoBackup(options);
1401
+ const parameterConfig = options.param.length > 0 ? parseKeyValueArgs(options.param) : void 0;
1402
+ const body = {
1403
+ name: options.name,
1404
+ type: normalizeDatabaseType(type),
1405
+ quota
1406
+ };
1407
+ if (options.version) Object.assign(body, { version: options.version });
1408
+ if (options.terminationPolicy) Object.assign(body, { terminationPolicy: options.terminationPolicy });
1409
+ if (autoBackup) Object.assign(body, { autoBackup });
1410
+ if (parameterConfig) Object.assign(body, { parameterConfig });
1411
+ const { data, error, response } = await client.POST("/databases", {
1412
+ headers: ctx.auth,
1413
+ body
1414
+ });
1415
+ if (error) throw mapApiError(response.status, error);
1416
+ if (options.output === "json") {
1417
+ ctx.spinner.stop();
1418
+ outputJson(data);
1419
+ return;
1420
+ }
1421
+ ctx.spinner.succeed(`Database "${data.name}" created successfully`);
1422
+ console.log(chalk3.dim(` Provisioning status: ${data.status}`));
1423
+ console.log(chalk3.dim(` Next: sealos database get ${data.name}`));
1424
+ }));
1425
+ dbCmd.command("get <name>").alias("describe").description("Get database details").option("-o, --output <format>", "Output format (json|table)", "table").action(withAuth({ spinnerText: "Loading database..." }, async (ctx, name, options) => {
1426
+ const client = createDatabaseClient();
1427
+ const { data, error, response } = await client.GET("/databases/{databaseName}", {
1428
+ headers: ctx.auth,
1429
+ params: {
1430
+ path: { databaseName: name }
1431
+ }
1432
+ });
1433
+ if (error) throw mapApiError(response.status, error);
1434
+ ctx.spinner.stop();
1435
+ if (options.output === "json") {
1436
+ outputJson(data);
1437
+ return;
1438
+ }
1439
+ printDatabaseDetail(data);
1440
+ }));
1441
+ dbCmd.command("connection <name>").description("Show database connection details").option("-o, --output <format>", "Output format (json|table)", "table").action(withAuth({ spinnerText: "Loading connection details..." }, async (ctx, name, options) => {
1442
+ const client = createDatabaseClient();
1443
+ const { data, error, response } = await client.GET("/databases/{databaseName}", {
1444
+ headers: ctx.auth,
1445
+ params: {
1446
+ path: { databaseName: name }
1447
+ }
1448
+ });
1449
+ if (error) throw mapApiError(response.status, error);
1450
+ ctx.spinner.stop();
1451
+ if (options.output === "json") {
1452
+ outputJson(data.connection ?? null);
1453
+ return;
1454
+ }
1455
+ printConnectionDetail(data.connection);
1456
+ }));
1457
+ dbCmd.command("update <name>").description("Update database resources").option("--cpu <cpu>", "CPU cores per replica").option("--memory <memory>", "Memory in GB per replica").option("--storage <storage>", "Storage in GB per replica").option("--replicas <replicas>", "Replica count").action(withAuth({
1458
+ spinnerText: "Updating database..."
1459
+ }, async (ctx, name, options) => {
1460
+ const quota = buildQuota(options);
1461
+ if (Object.keys(quota).length === 0) {
1462
+ throw new Error("Provide at least one of --cpu, --memory, --storage, or --replicas.");
1463
+ }
1464
+ const client = createDatabaseClient();
1465
+ const { error, response } = await client.PATCH("/databases/{databaseName}", {
1466
+ headers: ctx.auth,
1467
+ params: {
1468
+ path: { databaseName: name }
1469
+ },
1470
+ body: { quota }
1471
+ });
1472
+ if (error) throw mapApiError(response.status, error);
1473
+ ctx.spinner.succeed(`Database "${name}" update requested`);
1474
+ }));
1475
+ dbCmd.command("start <name>").description("Start a database").action(withAuth({ spinnerText: "Starting database..." }, async (ctx, name) => {
1476
+ const client = createDatabaseClient();
1477
+ const { error, response } = await client.POST("/databases/{databaseName}/start", {
1478
+ headers: ctx.auth,
1479
+ params: {
1480
+ path: { databaseName: name }
1481
+ }
1482
+ });
1483
+ if (error) throw mapApiError(response.status, error);
1484
+ ctx.spinner.succeed(`Database "${name}" start requested`);
1485
+ }));
1486
+ dbCmd.command("pause <name>").alias("stop").description("Pause a database").action(withAuth({ spinnerText: "Pausing database..." }, async (ctx, name) => {
1487
+ const client = createDatabaseClient();
1488
+ const { error, response } = await client.POST("/databases/{databaseName}/pause", {
1489
+ headers: ctx.auth,
1490
+ params: {
1491
+ path: { databaseName: name }
1492
+ }
1493
+ });
1494
+ if (error) throw mapApiError(response.status, error);
1495
+ ctx.spinner.succeed(`Database "${name}" pause requested`);
1496
+ }));
1497
+ dbCmd.command("restart <name>").description("Restart a database").action(withAuth({ spinnerText: "Restarting database..." }, async (ctx, name) => {
1498
+ const client = createDatabaseClient();
1499
+ const { error, response } = await client.POST("/databases/{databaseName}/restart", {
1500
+ headers: ctx.auth,
1501
+ params: {
1502
+ path: { databaseName: name }
1503
+ }
1504
+ });
1505
+ if (error) throw mapApiError(response.status, error);
1506
+ ctx.spinner.succeed(`Database "${name}" restart requested`);
1507
+ }));
1508
+ dbCmd.command("delete <name>").description("Delete a database").option("-f, --force", "Delete without confirmation").action(withAuth({ spinnerText: "Deleting database..." }, async (ctx, name) => {
1509
+ const client = createDatabaseClient();
1510
+ const { error, response } = await client.DELETE("/databases/{databaseName}", {
1511
+ headers: ctx.auth,
1512
+ params: {
1513
+ path: { databaseName: name }
1514
+ }
1515
+ });
1516
+ if (error) throw mapApiError(response.status, error);
1517
+ ctx.spinner.succeed(`Database "${name}" delete requested`);
1518
+ }));
1519
+ dbCmd.command("backups <name>").description("List backups for a database").option("-o, --output <format>", "Output format (json|table)", "table").action(withAuth({ spinnerText: "Loading backups..." }, async (ctx, name, options) => {
1520
+ const client = createDatabaseClient();
1521
+ const { data, error, response } = await client.GET("/databases/{databaseName}/backups", {
1522
+ headers: ctx.auth,
1523
+ params: {
1524
+ path: { databaseName: name }
1525
+ }
1526
+ });
1527
+ if (error) throw mapApiError(response.status, error);
1528
+ ctx.spinner.stop();
1529
+ if (options.output === "json") {
1530
+ outputJson(data);
1531
+ return;
1532
+ }
1533
+ if (data.length === 0) {
1534
+ console.log("No backups found.");
1535
+ return;
1536
+ }
1537
+ const rows = [[
1538
+ chalk3.bold("Name"),
1539
+ chalk3.bold("Status"),
1540
+ chalk3.bold("Created"),
1541
+ chalk3.bold("Description")
1542
+ ]];
1543
+ for (const backup of data) {
1544
+ rows.push([
1545
+ formatValue(backup.name),
1546
+ formatValue(backup.status),
1547
+ formatValue(backup.createdAt),
1548
+ formatValue(backup.description)
1549
+ ]);
1550
+ }
1551
+ outputTable(rows);
1552
+ }));
1553
+ dbCmd.command("backup <name>").description("Create a database backup").option("--name <backupName>", "Backup name").option("--description <description>", "Backup description").action(withAuth({
1554
+ spinnerText: "Creating backup..."
1555
+ }, async (ctx, name, options) => {
1556
+ const client = createDatabaseClient();
1557
+ const body = {};
1558
+ if (options.name) body.name = options.name;
1559
+ if (options.description) body.description = options.description;
1560
+ const { error, response } = await client.POST("/databases/{databaseName}/backups", {
1561
+ headers: ctx.auth,
1562
+ params: {
1563
+ path: { databaseName: name }
1564
+ },
1565
+ body
1566
+ });
1567
+ if (error) throw mapApiError(response.status, error);
1568
+ ctx.spinner.succeed(`Backup requested for database "${name}"`);
1569
+ }));
1570
+ dbCmd.command("backup-delete <databaseName> <backupName>").description("Delete a database backup").action(withAuth({
1571
+ spinnerText: "Deleting backup..."
1572
+ }, async (ctx, databaseName, backupName) => {
1573
+ const client = createDatabaseClient();
1574
+ const { error, response } = await client.DELETE("/databases/{databaseName}/backups/{backupName}", {
1575
+ headers: ctx.auth,
1576
+ params: {
1577
+ path: { databaseName, backupName }
1578
+ }
1579
+ });
1580
+ if (error) throw mapApiError(response.status, error);
1581
+ ctx.spinner.succeed(`Backup "${backupName}" deleted`);
1582
+ }));
1583
+ dbCmd.command("restore <databaseName>").description("Restore a database from a backup").requiredOption("--from <backupName>", "Backup name to restore from").option("--name <name>", "Name for the restored database").option("--replicas <replicas>", "Replica count for the restored database").action(withAuth({
1584
+ spinnerText: "Restoring database..."
1585
+ }, async (ctx, databaseName, options) => {
1586
+ const client = createDatabaseClient();
1587
+ const body = {};
1588
+ if (options.name) body.name = options.name;
1589
+ if (options.replicas) body.replicas = parseIntegerValue(options.replicas, "replicas");
1590
+ const { error, response } = await client.POST("/databases/{databaseName}/backups/{backupName}/restore", {
1591
+ headers: ctx.auth,
1592
+ params: {
1593
+ path: { databaseName, backupName: options.from }
1594
+ },
1595
+ body
1596
+ });
1597
+ if (error) throw mapApiError(response.status, error);
1598
+ ctx.spinner.succeed(`Restore requested from backup "${options.from}"`);
1599
+ }));
1600
+ dbCmd.command("enable-public <name>").description("Enable public access for a database").action(withAuth({ spinnerText: "Enabling public access..." }, async (ctx, name) => {
1601
+ const client = createDatabaseClient();
1602
+ const { error, response } = await client.POST("/databases/{databaseName}/enable-public", {
1603
+ headers: ctx.auth,
1604
+ params: {
1605
+ path: { databaseName: name }
1606
+ }
1607
+ });
1608
+ if (error) throw mapApiError(response.status, error);
1609
+ ctx.spinner.succeed(`Public access enabled for "${name}"`);
1610
+ }));
1611
+ dbCmd.command("disable-public <name>").description("Disable public access for a database").action(withAuth({ spinnerText: "Disabling public access..." }, async (ctx, name) => {
1612
+ const client = createDatabaseClient();
1613
+ const { error, response } = await client.POST("/databases/{databaseName}/disable-public", {
1614
+ headers: ctx.auth,
1615
+ params: {
1616
+ path: { databaseName: name }
1617
+ }
1618
+ });
1619
+ if (error) throw mapApiError(response.status, error);
1620
+ ctx.spinner.succeed(`Public access disabled for "${name}"`);
1621
+ }));
1622
+ dbCmd.command("logs <podName>").description("Get parsed database logs for a pod").requiredOption("--db-type <type>", "Database type used by the log service").requiredOption("--log-type <type>", "Log type used by the log service").requiredOption("--log-path <path>", 'Log path to read. Use "log-files" first to discover valid paths').option("--page <page>", "Page number", "1").option("--page-size <pageSize>", "Page size", "200").option("-o, --output <format>", "Output format (plain|json|table)", "plain").action(withAuth({
1623
+ spinnerText: "Loading logs..."
1624
+ }, async (ctx, podName, options) => {
1625
+ const client = createDatabaseClient();
1626
+ const { data, error, response } = await client.GET("/logs", {
1627
+ headers: ctx.auth,
1628
+ params: {
1629
+ query: {
1630
+ podName,
1631
+ dbType: normalizeLogDbType(options.dbType),
1632
+ logType: normalizeLogType(options.logType),
1633
+ logPath: options.logPath,
1634
+ page: parseIntegerValue(options.page, "page"),
1635
+ pageSize: parseIntegerValue(options.pageSize, "page-size")
1636
+ }
1637
+ }
1638
+ });
1639
+ if (error) throw mapApiError(response.status, error);
1640
+ ctx.spinner.stop();
1641
+ if (options.output === "json") {
1642
+ outputJson(data);
1643
+ return;
1644
+ }
1645
+ if (options.output === "table") {
1646
+ const rows = [[chalk3.bold("Timestamp"), chalk3.bold("Level"), chalk3.bold("Content")]];
1647
+ for (const log of data.data.logs) {
1648
+ rows.push([formatValue(log.timestamp), formatValue(log.level), formatValue(log.content)]);
1649
+ }
1650
+ outputTable(rows);
1651
+ return;
1652
+ }
1653
+ for (const log of data.data.logs) {
1654
+ console.log(`${log.timestamp} [${log.level}] ${log.content}`);
1655
+ }
1656
+ console.log(chalk3.dim(`
1657
+ page=${data.data.metadata.page} total=${data.data.metadata.total} hasMore=${String(data.data.metadata.hasMore)}`));
1658
+ }));
1659
+ dbCmd.command("log-files <podName>").description("List database log files for a pod").requiredOption("--db-type <type>", "Database type used by the log service").requiredOption("--log-type <type>", "Log type used by the log service").option("-o, --output <format>", "Output format (json|table)", "table").action(withAuth({
1660
+ spinnerText: "Loading log files..."
1661
+ }, async (ctx, podName, options) => {
1662
+ const client = createDatabaseClient();
1663
+ const { data, error, response } = await client.GET("/logs/files", {
1664
+ headers: ctx.auth,
1665
+ params: {
1666
+ query: {
1667
+ podName,
1668
+ dbType: normalizeLogDbType(options.dbType),
1669
+ logType: normalizeLogType(options.logType)
1670
+ }
1671
+ }
1672
+ });
1673
+ if (error) throw mapApiError(response.status, error);
1674
+ ctx.spinner.stop();
1675
+ if (options.output === "json") {
1676
+ outputJson(data);
1677
+ return;
1678
+ }
1679
+ if (data.data.length === 0) {
1680
+ console.log("No log files found.");
1681
+ return;
1682
+ }
1683
+ const rows = [[
1684
+ chalk3.bold("Name"),
1685
+ chalk3.bold("Path"),
1686
+ chalk3.bold("Kind"),
1687
+ chalk3.bold("Size"),
1688
+ chalk3.bold("Updated"),
1689
+ chalk3.bold("Processed")
1690
+ ]];
1691
+ for (const file of data.data) {
1692
+ rows.push([
1693
+ formatValue(file.name),
1694
+ formatValue(file.path),
1695
+ formatValue(file.kind),
1696
+ formatValue(file.size),
1697
+ formatValue(file.updateTime),
1698
+ formatValue(file.processed)
1699
+ ]);
1700
+ }
1701
+ outputTable(rows);
1702
+ }));
1703
+ return dbCmd;
1704
+ }
1705
+ __name(createDatabaseCommand, "createDatabaseCommand");
1706
+
1707
+ // src/commands/template/index.ts
1708
+ import { Command as Command9 } from "commander";
1709
+ import chalk4 from "chalk";
1710
+ import { readFileSync as readFileSync3 } from "fs";
1711
+ function parseSetArgs(sets) {
1712
+ const args = {};
1713
+ for (const s of sets) {
1714
+ const idx = s.indexOf("=");
1715
+ if (idx === -1) {
1716
+ throw new Error(`Invalid --set format: "${s}". Expected KEY=VALUE`);
1717
+ }
1718
+ args[s.slice(0, idx)] = s.slice(idx + 1);
1719
+ }
1720
+ return args;
1721
+ }
1722
+ __name(parseSetArgs, "parseSetArgs");
1723
+ function readStdin() {
1724
+ return new Promise((resolve, reject) => {
1725
+ if (process.stdin.isTTY) {
1726
+ reject(new Error("No input provided. Use --file, --yaml, or pipe YAML via stdin."));
1727
+ return;
1728
+ }
1729
+ const chunks = [];
1730
+ process.stdin.on("data", (chunk) => chunks.push(chunk));
1731
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8").trim()));
1732
+ process.stdin.on("error", reject);
1733
+ });
1734
+ }
1735
+ __name(readStdin, "readStdin");
1736
+ async function resolveYaml(options, spinner2) {
1737
+ if (options.file) {
1738
+ return readFileSync3(options.file, "utf-8");
1739
+ }
1740
+ if (options.yaml) {
1741
+ return options.yaml;
1742
+ }
1743
+ spinner2.stop();
1744
+ const content = await readStdin();
1745
+ spinner2.start("Deploying template...");
1746
+ return content;
1747
+ }
1748
+ __name(resolveYaml, "resolveYaml");
1749
+ function resolveTemplateDeployMode(template, options, stdinIsTTY = process.stdin.isTTY) {
1750
+ const isRaw = !!(options.file || options.yaml || !stdinIsTTY);
1751
+ if (template && isRaw) {
1752
+ throw new Error("Cannot specify both a template name and --file/--yaml/stdin. Use one or the other.");
1753
+ }
1754
+ if (!template && !isRaw) {
1755
+ throw new Error("Provide a template name or use --file/--yaml/stdin to supply raw YAML.");
1756
+ }
1757
+ if (template) {
1758
+ if (!options.name) {
1759
+ throw new Error("--name is required when deploying from the template catalog.");
1760
+ }
1761
+ if (options.dryRun) {
1762
+ throw new Error("--dry-run is only supported for raw template deploys (--file, --yaml, or stdin).");
1763
+ }
1764
+ return "catalog";
1765
+ }
1766
+ return "raw";
1767
+ }
1768
+ __name(resolveTemplateDeployMode, "resolveTemplateDeployMode");
1769
+ function buildCatalogTemplateDeployBody(template, options) {
1770
+ const body = {
1771
+ name: options.name,
1772
+ template
1773
+ };
1774
+ if (options.set.length > 0) {
1775
+ body.args = parseSetArgs(options.set);
1776
+ }
1777
+ return body;
1778
+ }
1779
+ __name(buildCatalogTemplateDeployBody, "buildCatalogTemplateDeployBody");
1780
+ function buildRawTemplateDeployBody(yaml, options) {
1781
+ const body = {
1782
+ yaml
1783
+ };
1784
+ if (options.set.length > 0) {
1785
+ body.args = parseSetArgs(options.set);
1786
+ }
1787
+ if (options.dryRun) {
1788
+ body.dryRun = true;
1789
+ }
1790
+ return body;
1791
+ }
1792
+ __name(buildRawTemplateDeployBody, "buildRawTemplateDeployBody");
1793
+ function createTemplateCommand() {
1794
+ const tplCmd = new Command9("template").alias("tpl").description("Manage templates");
1795
+ const deployTemplate = withAuth({
1796
+ spinnerText: "Deploying template..."
1797
+ }, async (ctx, catalogTemplate, deployOptions, deployMode) => {
1798
+ const client = createTemplateClient();
1799
+ if (deployMode === "catalog") {
1800
+ const body2 = buildCatalogTemplateDeployBody(catalogTemplate, deployOptions);
1801
+ const { data: data2, error: error2, response: response2 } = await client.POST("/templates/instances", {
1802
+ headers: ctx.auth,
1803
+ body: body2
1804
+ });
1805
+ if (error2) throw mapApiError(response2.status, error2);
1806
+ ctx.spinner.succeed(`Instance "${data2.name}" created successfully from catalog template "${catalogTemplate}"`);
1807
+ console.log(chalk4.dim(` UID: ${data2.uid}`));
1808
+ console.log(chalk4.dim(` Created: ${data2.createdAt}`));
1809
+ if (data2.resources && data2.resources.length > 0) {
1810
+ console.log(chalk4.dim("\n Resources:"));
1811
+ const rows = [
1812
+ [chalk4.bold("Name"), chalk4.bold("Type"), chalk4.bold("CPU"), chalk4.bold("Memory"), chalk4.bold("Storage")]
1813
+ ];
1814
+ for (const r of data2.resources) {
1815
+ rows.push([
1816
+ r.name,
1817
+ r.resourceType,
1818
+ r.quota?.cpu != null ? `${r.quota.cpu} vCPU` : "-",
1819
+ r.quota?.memory != null ? `${r.quota.memory} GiB` : "-",
1820
+ r.quota?.storage != null ? `${r.quota.storage} GiB` : "-"
1821
+ ]);
1822
+ }
1823
+ outputTable(rows);
1824
+ }
1825
+ return;
1826
+ }
1827
+ const yamlContent = await resolveYaml(deployOptions, ctx.spinner);
1828
+ const body = buildRawTemplateDeployBody(yamlContent, deployOptions);
1829
+ const { data, error, response } = await client.POST("/templates/raw", {
1830
+ headers: ctx.auth,
1831
+ body
1832
+ });
1833
+ if (error) throw mapApiError(response.status, error);
1834
+ if (deployOptions.dryRun) {
1835
+ ctx.spinner.succeed("Raw template validation passed; no resources were created");
1836
+ console.log(chalk4.dim(` Name: ${data.name}`));
1837
+ if (data.resources && data.resources.length > 0) {
1838
+ console.log(chalk4.dim("\n Resources that would be created:"));
1839
+ for (const r of data.resources) {
1840
+ console.log(chalk4.dim(` - ${r.resourceType}: ${r.name}`));
1841
+ }
1842
+ }
1843
+ return;
1844
+ }
1845
+ ctx.spinner.succeed(`Raw template deployed as "${data.name}"`);
1846
+ if ("uid" in data) {
1847
+ console.log(chalk4.dim(` UID: ${data.uid}`));
1848
+ }
1849
+ if ("createdAt" in data) {
1850
+ console.log(chalk4.dim(` Created: ${data.createdAt}`));
1851
+ }
1852
+ });
1853
+ tplCmd.command("list").description("List available templates").option("-c, --category <category>", "Filter by category").option("-o, --output <format>", "Output format (json|table)", "table").action(withErrorHandling({ spinnerText: "Loading templates..." }, async (ctx, options) => {
1854
+ const client = createTemplateClient();
1855
+ const { data, error, response } = await client.GET("/templates", {
1856
+ params: { query: {} }
1857
+ });
1858
+ if (error) throw mapApiError(response.status, error);
1859
+ ctx.spinner.stop();
1860
+ let templates = data;
1861
+ if (options.category) {
1862
+ templates = templates.filter((tpl) => tpl.category.includes(options.category));
1863
+ }
1864
+ if (options.output === "json") {
1865
+ outputJson(templates);
1866
+ return;
1867
+ }
1868
+ const rows = [
1869
+ [chalk4.bold("Name"), chalk4.bold("Description"), chalk4.bold("Category"), chalk4.bold("Deploys")]
1870
+ ];
1871
+ for (const tpl of templates) {
1872
+ rows.push([
1873
+ tpl.name,
1874
+ tpl.description.length > 50 ? tpl.description.slice(0, 47) + "..." : tpl.description,
1875
+ tpl.category.join(", "),
1876
+ String(tpl.deployCount)
1877
+ ]);
1878
+ }
1879
+ outputTable(rows);
1880
+ }));
1881
+ tplCmd.command("get <name>").alias("describe").description("Get template details").option("-o, --output <format>", "Output format (json|table)", "table").action(withErrorHandling({ spinnerText: "Loading template..." }, async (ctx, name, options) => {
1882
+ const client = createTemplateClient();
1883
+ const { data, error, response } = await client.GET("/templates/{name}", {
1884
+ params: {
1885
+ path: { name }
1886
+ }
1887
+ });
1888
+ if (error) throw mapApiError(response.status, error);
1889
+ ctx.spinner.stop();
1890
+ if (options.output === "json") {
1891
+ outputJson(data);
1892
+ return;
1893
+ }
1894
+ console.log(chalk4.bold(`
1895
+ ${data.name}
1896
+ `));
1897
+ console.log(` ${chalk4.dim("Description:")} ${data.description}`);
1898
+ console.log(` ${chalk4.dim("Category:")} ${data.category.join(", ")}`);
1899
+ console.log(` ${chalk4.dim("Git Repo:")} ${data.gitRepo}`);
1900
+ console.log(` ${chalk4.dim("Deploys:")} ${data.deployCount}`);
1901
+ if (data.quota) {
1902
+ console.log(`
1903
+ ${chalk4.dim("Resources:")}`);
1904
+ console.log(` CPU: ${data.quota.cpu} vCPU`);
1905
+ console.log(` Memory: ${data.quota.memory} GiB`);
1906
+ console.log(` Storage: ${data.quota.storage} GiB`);
1907
+ console.log(` NodePort: ${data.quota.nodeport}`);
1908
+ }
1909
+ const argEntries = Object.entries(data.args);
1910
+ if (argEntries.length > 0) {
1911
+ console.log(`
1912
+ ${chalk4.dim("Arguments:")}`);
1913
+ const argRows = [
1914
+ [chalk4.bold("Name"), chalk4.bold("Type"), chalk4.bold("Required"), chalk4.bold("Default"), chalk4.bold("Description")]
1915
+ ];
1916
+ for (const [key, arg] of argEntries) {
1917
+ argRows.push([
1918
+ key,
1919
+ arg.type,
1920
+ arg.required ? chalk4.red("yes") : "no",
1921
+ arg.default || chalk4.dim("-"),
1922
+ arg.description
1923
+ ]);
1924
+ }
1925
+ outputTable(argRows);
1926
+ }
1927
+ }));
1928
+ tplCmd.command("deploy [template]").description("Deploy a template (from catalog or raw YAML)").option("--name <name>", "Instance name (required when deploying from catalog)").option("--file <path>", "Path to template YAML file").option("--yaml <yaml>", "Template YAML string").option("--set <KEY=VALUE...>", "Set template arguments", (val, prev) => [...prev, val], []).option("--dry-run", "Validate raw template YAML without creating resources").addHelpText("after", `
1929
+ Examples:
1930
+ Catalog:
1931
+ sealos template deploy perplexica --name my-app --set OPENAI_API_KEY=xxx
1932
+
1933
+ Raw:
1934
+ sealos template deploy --file ./template.yaml --dry-run
1935
+ sealos template deploy --yaml 'apiVersion: app.sealos.io/v1
1936
+ kind: Template
1937
+ ...'
1938
+ cat template.yaml | sealos template deploy --dry-run
1939
+ `).action(async (template, options) => {
1940
+ const mode = resolveTemplateDeployMode(template, options);
1941
+ await deployTemplate(template, options, mode);
1942
+ });
1943
+ return tplCmd;
1944
+ }
1945
+ __name(createTemplateCommand, "createTemplateCommand");
1946
+
1947
+ // src/commands/quota/index.ts
1948
+ import { Command as Command10 } from "commander";
1949
+ function createQuotaCommand() {
1950
+ const quotaCmd = new Command10("quota").description("View resource quotas");
1951
+ quotaCmd.command("get").description("Get quota information").action(async () => {
1952
+ try {
1953
+ console.log("TODO: Implement quota get");
1954
+ } catch (error) {
1955
+ handleError(error);
1956
+ }
1957
+ });
1958
+ return quotaCmd;
1959
+ }
1960
+ __name(createQuotaCommand, "createQuotaCommand");
1961
+
1962
+ // src/commands/app/index.ts
1963
+ import { Command as Command11 } from "commander";
1964
+ function createAppCommand() {
1965
+ const appCmd = new Command11("app").description("Manage applications");
1966
+ appCmd.command("list").description("List all applications").action(async () => {
1967
+ try {
1968
+ console.log("TODO: Implement app list");
1969
+ } catch (error) {
1970
+ handleError(error);
1971
+ }
1972
+ });
1973
+ return appCmd;
1974
+ }
1975
+ __name(createAppCommand, "createAppCommand");
1976
+
1977
+ // src/commands/config/index.ts
1978
+ import { Command as Command12 } from "commander";
1979
+ function createConfigCommand() {
1980
+ const configCmd = new Command12("config").description("Manage CLI configuration");
1981
+ configCmd.command("set").description("Set a config value").argument("<key>", "Config key").argument("<value>", "Config value").action(async (key, value) => {
1982
+ try {
1983
+ setConfigValue(key, value);
1984
+ success(`Config ${key} set to ${value}`);
1985
+ } catch (error) {
1986
+ handleError(error);
1987
+ }
1988
+ });
1989
+ configCmd.command("get").description("Get a config value").argument("<key>", "Config key").action(async (key) => {
1990
+ try {
1991
+ const value = getConfigValue(key);
1992
+ if (value) {
1993
+ console.log(value);
1994
+ } else {
1995
+ console.log(`Config key "${key}" not found`);
1996
+ }
1997
+ } catch (error) {
1998
+ handleError(error);
1999
+ }
2000
+ });
2001
+ configCmd.command("list").description("List all config values").action(async () => {
2002
+ try {
2003
+ const config = readConfig();
2004
+ outputJson(config);
2005
+ } catch (error) {
2006
+ handleError(error);
2007
+ }
2008
+ });
2009
+ return configCmd;
2010
+ }
2011
+ __name(createConfigCommand, "createConfigCommand");
2012
+
2013
+ // src/main.ts
2014
+ function createProgram() {
2015
+ const program = new Command13();
2016
+ program.name("sealos").description("Official CLI tool for Sealos Cloud - Manage devbox, applications, databases, and object storage").version("0.0.1");
2017
+ registerAuthCommands(program);
2018
+ program.addCommand(createWorkspaceCommand());
2019
+ program.addCommand(createDevboxCommand());
2020
+ program.addCommand(createS3Command());
2021
+ program.addCommand(createDatabaseCommand());
2022
+ program.addCommand(createTemplateCommand());
2023
+ program.addCommand(createQuotaCommand());
2024
+ program.addCommand(createAppCommand());
2025
+ program.addCommand(createConfigCommand());
2026
+ return program;
2027
+ }
2028
+ __name(createProgram, "createProgram");
2029
+ function runCLI() {
2030
+ const program = createProgram();
2031
+ program.exitOverride();
2032
+ try {
2033
+ program.parse(process.argv);
2034
+ } catch (error) {
2035
+ if (error instanceof Error && "code" in error && typeof error.code === "string" && error.code.startsWith("commander.")) {
2036
+ process.exit(0);
2037
+ }
2038
+ handleError(error);
2039
+ }
2040
+ }
2041
+ __name(runCLI, "runCLI");
2042
+ export {
2043
+ createProgram,
2044
+ runCLI
2045
+ };