sealos-cli 1.1.1 → 1.1.3

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.
@@ -33,6 +33,7 @@ interface DevboxUpdateOptions {
33
33
  cpu?: string
34
34
  memory?: string
35
35
  port: string[]
36
+ output: string
36
37
  }
37
38
 
38
39
  interface DevboxReleaseOptions {
@@ -40,6 +41,7 @@ interface DevboxReleaseOptions {
40
41
  description?: string
41
42
  execCommand?: string
42
43
  noStart?: boolean
44
+ output: string
43
45
  }
44
46
 
45
47
  function formatValue (value: unknown): string {
@@ -398,7 +400,7 @@ export function createDevboxCommand (): Command {
398
400
  devboxCmd
399
401
  .command('list')
400
402
  .description('List all devboxes')
401
- .option('-o, --output <format>', 'Output format (json|table)', 'table')
403
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
402
404
  .action(withAuth({ spinnerText: 'Loading devboxes...' }, async (ctx, options: { output: string }) => {
403
405
  const client = createDevboxClient()
404
406
  const { data, error, response } = await client.GET('/devbox', {
@@ -427,7 +429,7 @@ export function createDevboxCommand (): Command {
427
429
  .option('--env <NAME=VALUE>', 'Environment variable', collectOption, [] as string[])
428
430
  .option('--secret-env <NAME=SECRET:KEY>', 'Environment variable from secret', collectOption, [] as string[])
429
431
  .option('--autostart', 'Auto start devbox after creation')
430
- .option('-o, --output <format>', 'Output format (json|table)', 'table')
432
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
431
433
  .action(withAuth({ spinnerText: 'Creating devbox...' }, async (ctx, options: DevboxCreateOptions & { output: string }) => {
432
434
  const client = createDevboxClient()
433
435
  const { data, error, response } = await client.POST('/devbox', {
@@ -449,7 +451,7 @@ export function createDevboxCommand (): Command {
449
451
  devboxCmd
450
452
  .command('get <name>')
451
453
  .description('Get devbox details')
452
- .option('-o, --output <format>', 'Output format (json|table)', 'table')
454
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
453
455
  .action(withAuth({ spinnerText: 'Loading devbox...' }, async (ctx, name: string, options: { output: string }) => {
454
456
  const client = createDevboxClient()
455
457
  const { data, error, response } = await client.GET('/devbox/{name}', {
@@ -475,6 +477,7 @@ export function createDevboxCommand (): Command {
475
477
  .option('--cpu <cpu>', 'CPU cores')
476
478
  .option('--memory <memory>', 'Memory in GB')
477
479
  .option('--port <spec>', 'Port spec. Existing ports can include portName=...', collectOption, [] as string[])
480
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
478
481
  .action(withAuth({ spinnerText: 'Updating devbox...' }, async (ctx, name: string, options: DevboxUpdateOptions) => {
479
482
  const client = createDevboxClient()
480
483
  const { error, response } = await client.PATCH('/devbox/{name}', {
@@ -486,6 +489,17 @@ export function createDevboxCommand (): Command {
486
489
  })
487
490
 
488
491
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
492
+ if (options.output === 'json') {
493
+ ctx.spinner.stop()
494
+ outputJson({
495
+ success: true,
496
+ action: 'update',
497
+ resource: 'devbox',
498
+ name,
499
+ status: 'requested'
500
+ })
501
+ return
502
+ }
489
503
  ctx.spinner.succeed(`Devbox "${name}" update requested`)
490
504
  }))
491
505
 
@@ -493,7 +507,8 @@ export function createDevboxCommand (): Command {
493
507
  .command('delete <name>')
494
508
  .description('Delete a devbox')
495
509
  .alias('rm')
496
- .action(withAuth({ spinnerText: 'Deleting devbox...' }, async (ctx, name: string) => {
510
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
511
+ .action(withAuth({ spinnerText: 'Deleting devbox...' }, async (ctx, name: string, options: { output: string }) => {
497
512
  const client = createDevboxClient()
498
513
  const { error, response } = await client.DELETE('/devbox/{name}', {
499
514
  headers: ctx.auth,
@@ -503,6 +518,17 @@ export function createDevboxCommand (): Command {
503
518
  })
504
519
 
505
520
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
521
+ if (options.output === 'json') {
522
+ ctx.spinner.stop()
523
+ outputJson({
524
+ success: true,
525
+ action: 'delete',
526
+ resource: 'devbox',
527
+ name,
528
+ status: 'deleted'
529
+ })
530
+ return
531
+ }
506
532
  ctx.spinner.succeed(`Devbox "${name}" deleted`)
507
533
  }))
508
534
 
@@ -520,26 +546,40 @@ export function createDevboxCommand (): Command {
520
546
 
521
547
  if (action.alias) command.alias(action.alias)
522
548
 
523
- command.action(withAuth({ spinnerText: action.spinnerText }, async (ctx, name: string) => {
524
- const client = createDevboxClient()
525
- const { error, response } = await client.POST(action.endpoint, {
526
- headers: ctx.auth,
527
- params: {
528
- path: { name }
529
- },
530
- body: {}
531
- } as any)
532
-
533
- if (error) throw mapApiError(response.status, error as ApiErrorBody)
534
- ctx.spinner.succeed(`Devbox "${name}" ${action.done}`)
535
- }))
549
+ command
550
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
551
+ .action(withAuth({ spinnerText: action.spinnerText }, async (ctx, name: string, options: { output: string }) => {
552
+ const client = createDevboxClient()
553
+ const { error, response } = await client.POST(action.endpoint, {
554
+ headers: ctx.auth,
555
+ params: {
556
+ path: { name }
557
+ },
558
+ body: {}
559
+ } as any)
560
+
561
+ if (error) throw mapApiError(response.status, error as ApiErrorBody)
562
+ if (options.output === 'json') {
563
+ ctx.spinner.stop()
564
+ outputJson({
565
+ success: true,
566
+ action: action.name,
567
+ resource: 'devbox',
568
+ name,
569
+ status: 'requested'
570
+ })
571
+ return
572
+ }
573
+ ctx.spinner.succeed(`Devbox "${name}" ${action.done}`)
574
+ }))
536
575
  }
537
576
 
538
577
  devboxCmd
539
578
  .command('autostart <name>')
540
579
  .description('Configure devbox autostart')
541
580
  .option('--exec-command <command>', 'Command to execute when the devbox starts')
542
- .action(withAuth({ spinnerText: 'Configuring autostart...' }, async (ctx, name: string, options: { execCommand?: string }) => {
581
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
582
+ .action(withAuth({ spinnerText: 'Configuring autostart...' }, async (ctx, name: string, options: { execCommand?: string; output: string }) => {
543
583
  const client = createDevboxClient()
544
584
  const body = options.execCommand ? { execCommand: options.execCommand } : {}
545
585
  const { error, response } = await client.POST('/devbox/{name}/autostart', {
@@ -551,6 +591,18 @@ export function createDevboxCommand (): Command {
551
591
  })
552
592
 
553
593
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
594
+ if (options.output === 'json') {
595
+ ctx.spinner.stop()
596
+ outputJson({
597
+ success: true,
598
+ action: 'autostart',
599
+ resource: 'devbox',
600
+ name,
601
+ execCommand: options.execCommand ?? null,
602
+ status: 'configured'
603
+ })
604
+ return
605
+ }
554
606
  ctx.spinner.succeed(`Autostart configured for "${name}"`)
555
607
  }))
556
608
 
@@ -560,7 +612,7 @@ export function createDevboxCommand (): Command {
560
612
  .option('--start <timestamp>', 'Start timestamp in seconds or milliseconds')
561
613
  .option('--end <timestamp>', 'End timestamp in seconds or milliseconds')
562
614
  .option('--step <step>', 'Sampling interval, e.g. 2m')
563
- .option('-o, --output <format>', 'Output format (json|table)', 'table')
615
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
564
616
  .action(withAuth({ spinnerText: 'Loading monitor data...' }, async (
565
617
  ctx,
566
618
  name: string,
@@ -592,7 +644,7 @@ export function createDevboxCommand (): Command {
592
644
  devboxCmd
593
645
  .command('templates')
594
646
  .description('List available devbox templates')
595
- .option('-o, --output <format>', 'Output format (json|table)', 'table')
647
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
596
648
  .action(withAuth({ spinnerText: 'Loading devbox templates...' }, async (ctx, options: { output: string }) => {
597
649
  const client = createDevboxClient()
598
650
  const { data, error, response } = await client.GET('/devbox/templates', {
@@ -616,7 +668,7 @@ export function createDevboxCommand (): Command {
616
668
  releasesCommand
617
669
  .command('list <name>')
618
670
  .description('List devbox releases')
619
- .option('-o, --output <format>', 'Output format (json|table)', 'table')
671
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
620
672
  .action(withAuth({ spinnerText: 'Loading releases...' }, async (ctx, name: string, options: { output: string }) => {
621
673
  const client = createDevboxClient()
622
674
  const { data, error, response } = await client.GET('/devbox/{name}/releases', {
@@ -643,6 +695,7 @@ export function createDevboxCommand (): Command {
643
695
  .option('--description <description>', 'Release description')
644
696
  .option('--exec-command <command>', 'Autostart command after release restart')
645
697
  .option('--no-start', 'Keep devbox stopped after the release build completes')
698
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
646
699
  .action(withAuth({ spinnerText: 'Creating release...' }, async (ctx, name: string, options: DevboxReleaseOptions) => {
647
700
  const client = createDevboxClient()
648
701
  const { data, error, response } = await client.POST('/devbox/{name}/releases', {
@@ -654,6 +707,11 @@ export function createDevboxCommand (): Command {
654
707
  })
655
708
 
656
709
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
710
+ if (options.output === 'json') {
711
+ ctx.spinner.stop()
712
+ outputJson(data)
713
+ return
714
+ }
657
715
  ctx.spinner.succeed(`Release "${options.tag}" accepted for "${data.name}" (${data.status})`)
658
716
  }))
659
717
 
@@ -661,7 +719,8 @@ export function createDevboxCommand (): Command {
661
719
  .command('delete <name> <tag>')
662
720
  .alias('rm')
663
721
  .description('Delete a devbox release')
664
- .action(withAuth({ spinnerText: 'Deleting release...' }, async (ctx, name: string, tag: string) => {
722
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
723
+ .action(withAuth({ spinnerText: 'Deleting release...' }, async (ctx, name: string, tag: string, options: { output: string }) => {
665
724
  const client = createDevboxClient()
666
725
  const { error, response } = await client.DELETE('/devbox/{name}/releases/{tag}', {
667
726
  headers: ctx.auth,
@@ -671,13 +730,26 @@ export function createDevboxCommand (): Command {
671
730
  })
672
731
 
673
732
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
733
+ if (options.output === 'json') {
734
+ ctx.spinner.stop()
735
+ outputJson({
736
+ success: true,
737
+ action: 'delete',
738
+ resource: 'devbox-release',
739
+ name,
740
+ tag,
741
+ status: 'deleted'
742
+ })
743
+ return
744
+ }
674
745
  ctx.spinner.succeed(`Release "${tag}" deleted for "${name}"`)
675
746
  }))
676
747
 
677
748
  releasesCommand
678
749
  .command('deploy <name> <tag>')
679
750
  .description('Deploy a release to AppLaunchpad')
680
- .action(withAuth({ spinnerText: 'Deploying release...' }, async (ctx, name: string, tag: string) => {
751
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
752
+ .action(withAuth({ spinnerText: 'Deploying release...' }, async (ctx, name: string, tag: string, options: { output: string }) => {
681
753
  const client = createDevboxClient()
682
754
  const { error, response } = await client.POST('/devbox/{name}/releases/{tag}/deploy', {
683
755
  headers: ctx.auth,
@@ -687,13 +759,25 @@ export function createDevboxCommand (): Command {
687
759
  })
688
760
 
689
761
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
762
+ if (options.output === 'json') {
763
+ ctx.spinner.stop()
764
+ outputJson({
765
+ success: true,
766
+ action: 'deploy',
767
+ resource: 'devbox-release',
768
+ name,
769
+ tag,
770
+ status: 'deployed'
771
+ })
772
+ return
773
+ }
690
774
  ctx.spinner.succeed(`Release "${tag}" deployed for "${name}"`)
691
775
  }))
692
776
 
693
777
  devboxCmd
694
778
  .command('deployments <name>')
695
779
  .description('List deployed applications from a devbox')
696
- .option('-o, --output <format>', 'Output format (json|table)', 'table')
780
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
697
781
  .action(withAuth({ spinnerText: 'Loading deployments...' }, async (ctx, name: string, options: { output: string }) => {
698
782
  const client = createDevboxClient()
699
783
  const { data, error, response } = await client.GET('/devbox/{name}/deployments', {
@@ -2,7 +2,7 @@ import { Command } from 'commander'
2
2
  import chalk from 'chalk'
3
3
  import { readFileSync } from 'node:fs'
4
4
  import { createTemplateClient } from '../../lib/api-client.ts'
5
- import { type ApiErrorBody, mapApiError } from '../../lib/errors.ts'
5
+ import { type ApiErrorBody, handleError, mapApiError } from '../../lib/errors.ts'
6
6
  import { outputJson, outputTable } from '../../lib/output.ts'
7
7
  import { withAuth, withErrorHandling } from '../../lib/with-auth.ts'
8
8
 
@@ -12,6 +12,7 @@ interface TemplateDeployOptions {
12
12
  yaml?: string
13
13
  set: string[]
14
14
  dryRun?: boolean
15
+ output: string
15
16
  }
16
17
 
17
18
  export type TemplateDeployMode = 'catalog' | 'raw'
@@ -82,18 +83,16 @@ export function resolveTemplateDeployMode (
82
83
  options: TemplateDeployOptions,
83
84
  stdinIsTTY: boolean = process.stdin.isTTY
84
85
  ): TemplateDeployMode {
85
- const isRaw = !!(options.file || options.yaml || !stdinIsTTY)
86
+ const hasExplicitRawInput = !!(options.file || options.yaml)
87
+ const isRaw = hasExplicitRawInput || (!template && !stdinIsTTY)
86
88
 
87
- if (template && isRaw) {
88
- throw new Error('Cannot specify both a template name and --file/--yaml/stdin. Use one or the other.')
89
+ if (template && hasExplicitRawInput) {
90
+ throw new Error('Cannot specify both a template name and --file/--yaml. Use one or the other.')
89
91
  }
90
92
  if (!template && !isRaw) {
91
93
  throw new Error('Provide a template name or use --file/--yaml/stdin to supply raw YAML.')
92
94
  }
93
95
  if (template) {
94
- if (!options.name) {
95
- throw new Error('--name is required when deploying from the template catalog.')
96
- }
97
96
  if (options.dryRun) {
98
97
  throw new Error('--dry-run is only supported for raw template deploys (--file, --yaml, or stdin).')
99
98
  }
@@ -108,7 +107,7 @@ export function buildCatalogTemplateDeployBody (
108
107
  options: Pick<TemplateDeployOptions, 'name' | 'set'>
109
108
  ): { name: string; template: string; args?: Record<string, string> } {
110
109
  const body: { name: string; template: string; args?: Record<string, string> } = {
111
- name: options.name!,
110
+ name: options.name ?? template,
112
111
  template
113
112
  }
114
113
  if (options.set.length > 0) {
@@ -208,6 +207,11 @@ export function createTemplateCommand (): Command {
208
207
 
209
208
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
210
209
 
210
+ if (deployOptions.output === 'json') {
211
+ ctx.spinner.stop()
212
+ outputJson(data)
213
+ return
214
+ }
211
215
  ctx.spinner.succeed(`Instance "${data.name}" created successfully`)
212
216
  printInstanceResult(data, { template: catalogTemplate })
213
217
  return
@@ -224,6 +228,12 @@ export function createTemplateCommand (): Command {
224
228
 
225
229
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
226
230
 
231
+ if (deployOptions.output === 'json') {
232
+ ctx.spinner.stop()
233
+ outputJson(data)
234
+ return
235
+ }
236
+
227
237
  if (deployOptions.dryRun) {
228
238
  ctx.spinner.succeed('Raw template validation passed; no resources were created')
229
239
  printInstanceResult(data, { raw: true, dryRun: true })
@@ -240,7 +250,7 @@ export function createTemplateCommand (): Command {
240
250
  .description('List available templates')
241
251
  .option('-c, --category <category>', 'Filter by category')
242
252
  .option('-l, --language <language>', 'Language code (for example: en, zh)')
243
- .option('-o, --output <format>', 'Output format (json|table)', 'table')
253
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
244
254
  .action(withErrorHandling({ spinnerText: 'Loading templates...' }, async (ctx, options: { category?: string; language?: string; output: string }) => {
245
255
  const client = createTemplateClient()
246
256
  const { data, error, response } = await client.GET('/templates', {
@@ -281,7 +291,7 @@ export function createTemplateCommand (): Command {
281
291
  .alias('describe')
282
292
  .description('Get template details')
283
293
  .option('-l, --language <language>', 'Language code (for example: en, zh)')
284
- .option('-o, --output <format>', 'Output format (json|table)', 'table')
294
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
285
295
  .action(withErrorHandling({ spinnerText: 'Loading template...' }, async (ctx, name: string, options: { language?: string; output: string }) => {
286
296
  const client = createTemplateClient()
287
297
  const { data, error, response } = await client.GET('/templates/{name}', {
@@ -338,7 +348,8 @@ export function createTemplateCommand (): Command {
338
348
  .command('delete <instance>')
339
349
  .alias('rm')
340
350
  .description('Delete a deployed template instance')
341
- .action(withAuth({ spinnerText: 'Deleting template instance...' }, async (ctx, instance: string) => {
351
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
352
+ .action(withAuth({ spinnerText: 'Deleting template instance...' }, async (ctx, instance: string, options: { output: string }) => {
342
353
  const client = createTemplateClient()
343
354
  const { error, response } = await client.DELETE('/templates/instances/{instanceName}', {
344
355
  headers: ctx.auth,
@@ -349,6 +360,17 @@ export function createTemplateCommand (): Command {
349
360
 
350
361
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
351
362
 
363
+ if (options.output === 'json') {
364
+ ctx.spinner.stop()
365
+ outputJson({
366
+ success: true,
367
+ action: 'delete',
368
+ resource: 'template-instance',
369
+ instance,
370
+ status: 'deleted'
371
+ })
372
+ return
373
+ }
352
374
  ctx.spinner.succeed(`Instance "${instance}" deleted`)
353
375
  }))
354
376
 
@@ -356,14 +378,16 @@ export function createTemplateCommand (): Command {
356
378
  tplCmd
357
379
  .command('deploy [template]')
358
380
  .description('Deploy a template (from catalog or raw YAML)')
359
- .option('--name <name>', 'Instance name (required when deploying from catalog)')
381
+ .option('--name <name>', 'Instance name (defaults to the catalog template name)')
360
382
  .option('--file <path>', 'Path to template YAML file')
361
383
  .option('--yaml <yaml>', 'Template YAML string')
362
384
  .option('--set <KEY=VALUE...>', 'Set template arguments', (val: string, prev: string[]) => [...prev, val], [] as string[])
363
385
  .option('--dry-run', 'Validate raw template YAML without creating resources')
386
+ .option('-o, --output <format>', 'Output format (json|table)', 'json')
364
387
  .addHelpText('after', `
365
388
  Examples:
366
389
  Catalog:
390
+ sealos-cli template deploy rybbit
367
391
  sealos-cli template deploy perplexica --name my-app --set OPENAI_API_KEY=xxx
368
392
 
369
393
  Raw:
@@ -372,8 +396,12 @@ Examples:
372
396
  cat template.yaml | sealos-cli template deploy --dry-run
373
397
  `)
374
398
  .action(async (template: string | undefined, options: TemplateDeployOptions) => {
375
- const mode = resolveTemplateDeployMode(template, options)
376
- await deployTemplate(template, options, mode)
399
+ try {
400
+ const mode = resolveTemplateDeployMode(template, options)
401
+ await deployTemplate(template, options, mode)
402
+ } catch (error) {
403
+ handleError(error)
404
+ }
377
405
  })
378
406
 
379
407
  return tplCmd
@@ -12,7 +12,7 @@ export function createWorkspaceCommand (): Command {
12
12
  .command('switch')
13
13
  .description('Switch to another workspace')
14
14
  .argument('<namespace>', 'Workspace id, uid, or team name')
15
- .option('-o, --output <format>', 'Output format: json, table', 'table')
15
+ .option('-o, --output <format>', 'Output format: json, table', 'json')
16
16
  .action(async (namespace, options) => {
17
17
  try {
18
18
  const result = await switchWorkspace(namespace)
@@ -30,7 +30,7 @@ export function createWorkspaceCommand (): Command {
30
30
  workspaceCmd
31
31
  .command('list')
32
32
  .description('List all workspaces')
33
- .option('-o, --output <format>', 'Output format: json, table', 'table')
33
+ .option('-o, --output <format>', 'Output format: json, table', 'json')
34
34
  .action(async (options) => {
35
35
  try {
36
36
  const result = await listWorkspaces()
@@ -58,7 +58,7 @@ export function createWorkspaceCommand (): Command {
58
58
  workspaceCmd
59
59
  .command('current')
60
60
  .description('Show current workspace')
61
- .option('-o, --output <format>', 'Output format: json, table', 'table')
61
+ .option('-o, --output <format>', 'Output format: json, table', 'json')
62
62
  .action(async (options) => {
63
63
  try {
64
64
  const authInfo = getAuthInfo()
package/src/lib/auth.ts CHANGED
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node
3
3
  import { homedir, platform } from 'node:os'
4
4
  import { join } from 'node:path'
5
5
  import type { SealosAuthData, SealosWorkspace } from '../types/index.ts'
6
+ import { AuthError } from './errors.ts'
6
7
 
7
8
  export const SEALOS_AUTH_CLIENT_ID = 'af993c98-d19d-4bdc-b338-79b80dc4f8bf'
8
9
  export const DEFAULT_SEALOS_REGION = 'https://usw-1.sealos.io'
@@ -64,6 +65,11 @@ interface KubeconfigResponse {
64
65
  }
65
66
  }
66
67
 
68
+ interface SealosApiEnvelope {
69
+ code?: number
70
+ message?: string
71
+ }
72
+
67
73
  export interface LoginResult {
68
74
  kubeconfig_path?: string
69
75
  region: string
@@ -236,7 +242,17 @@ export function getAuthInfo (deps: AuthDependencies = {}): AuthInfo {
236
242
  }
237
243
 
238
244
  async function parseResponse<T> (res: Response): Promise<T> {
239
- return await res.json() as T
245
+ const body = await res.json() as T & SealosApiEnvelope
246
+ if (body && typeof body === 'object' && typeof body.code === 'number' && ![0, 200].includes(body.code)) {
247
+ const message = body.message || `Sealos API request failed with code ${body.code}`
248
+ if (body.code === 401) {
249
+ throw new AuthError(`Authentication expired. Please run "sealos-cli login" again. (${message})`)
250
+ }
251
+
252
+ throw new Error(message)
253
+ }
254
+
255
+ return body as T
240
256
  }
241
257
 
242
258
  async function readErrorBody (res: Response): Promise<string> {