sealos-cli 0.1.0 → 1.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.
@@ -3,16 +3,17 @@ import { handleError } from '../../lib/errors.ts'
3
3
 
4
4
  export function createQuotaCommand (): Command {
5
5
  const quotaCmd = new Command('quota')
6
- .description('View resource quotas')
6
+ .description('Future quota placeholder outside the v1 release surface')
7
7
 
8
- // TODO: 实现配额相关命令
8
+ // Future, non-v1 placeholder. Do not register this command until quota APIs
9
+ // are implemented and ready for release.
9
10
 
10
11
  quotaCmd
11
12
  .command('get')
12
13
  .description('Get quota information')
13
14
  .action(async () => {
14
15
  try {
15
- console.log('TODO: Implement quota get')
16
+ console.log('Quota commands are not part of the v1 release surface yet.')
16
17
  } catch (error) {
17
18
  handleError(error)
18
19
  }
@@ -3,9 +3,10 @@ import { handleError } from '../../lib/errors.ts'
3
3
 
4
4
  export function createS3Command (): Command {
5
5
  const s3Cmd = new Command('s3')
6
- .description('Manage S3 object storage')
6
+ .description('Future S3 placeholder outside the v1 release surface')
7
7
 
8
- // TODO: 实现 S3 相关命令
8
+ // Future, non-v1 placeholder. Do not register this command until the S3 API
9
+ // integration is implemented and ready for release.
9
10
  // - upload
10
11
  // - download
11
12
  // - list
@@ -22,14 +23,13 @@ export function createS3Command (): Command {
22
23
  .option('--acl <acl>', 'Access control: private, public-read')
23
24
  .action(async (source, destination, options) => {
24
25
  try {
25
- console.log('TODO: Implement s3 upload', { source, destination, options })
26
+ console.log('S3 commands are not part of the v1 release surface yet.', { source, destination, options })
26
27
  } catch (error) {
27
28
  handleError(error)
28
29
  }
29
30
  })
30
31
 
31
- // 其他命令...
32
- // 为了简洁,这里只实现 upload 作为示例
32
+ // This file is kept as a marker for future S3 work only.
33
33
 
34
34
  return s3Cmd
35
35
  }
@@ -16,6 +16,29 @@ interface TemplateDeployOptions {
16
16
 
17
17
  export type TemplateDeployMode = 'catalog' | 'raw'
18
18
 
19
+ interface TemplateInstanceResource {
20
+ name: string
21
+ uid?: string
22
+ resourceType: string
23
+ quota?: {
24
+ cpu?: number
25
+ memory?: number
26
+ storage?: number
27
+ replicas?: number
28
+ }
29
+ }
30
+
31
+ interface TemplateInstanceResult {
32
+ name: string
33
+ uid?: string
34
+ displayName?: string
35
+ createdAt?: string
36
+ resourceType?: string
37
+ dryRun?: boolean
38
+ args?: Record<string, string>
39
+ resources?: TemplateInstanceResource[]
40
+ }
41
+
19
42
  export function parseSetArgs (sets: string[]): Record<string, string> {
20
43
  const args: Record<string, string> = {}
21
44
  for (const s of sets) {
@@ -110,6 +133,56 @@ export function buildRawTemplateDeployBody (
110
133
  return body
111
134
  }
112
135
 
136
+ function formatValue (value: unknown): string {
137
+ if (value === undefined || value === null || value === '') return '-'
138
+ return String(value)
139
+ }
140
+
141
+ function printInstanceResult (instance: TemplateInstanceResult, options: { template?: string; raw?: boolean; dryRun?: boolean } = {}): void {
142
+ if (options.dryRun) {
143
+ console.log(chalk.dim(` Name: ${instance.name}`))
144
+ } else {
145
+ console.log(chalk.dim(` Name: ${instance.name}`))
146
+ if (instance.displayName) {
147
+ console.log(chalk.dim(` Display: ${instance.displayName}`))
148
+ }
149
+ if (instance.uid) {
150
+ console.log(chalk.dim(` UID: ${instance.uid}`))
151
+ }
152
+ if (instance.createdAt) {
153
+ console.log(chalk.dim(` Created: ${instance.createdAt}`))
154
+ }
155
+ if (options.template) {
156
+ console.log(chalk.dim(` Template:${options.template}`))
157
+ }
158
+ }
159
+
160
+ const argEntries = Object.entries(instance.args ?? {})
161
+ if (argEntries.length > 0) {
162
+ console.log(chalk.dim('\n Arguments:'))
163
+ outputTable([
164
+ [chalk.bold('Name'), chalk.bold('Value')],
165
+ ...argEntries.map(([key, value]) => [key, value])
166
+ ])
167
+ }
168
+
169
+ if (instance.resources && instance.resources.length > 0) {
170
+ console.log(chalk.dim(options.dryRun ? '\n Resources that would be created:' : '\n Resources:'))
171
+ outputTable([
172
+ [chalk.bold('Name'), chalk.bold('Type'), chalk.bold('UID'), chalk.bold('CPU'), chalk.bold('Memory'), chalk.bold('Storage'), chalk.bold('Replicas')],
173
+ ...instance.resources.map(resource => [
174
+ formatValue(resource.name),
175
+ formatValue(resource.resourceType),
176
+ formatValue(resource.uid),
177
+ resource.quota?.cpu != null ? `${resource.quota.cpu} vCPU` : '-',
178
+ resource.quota?.memory != null ? `${resource.quota.memory} GiB` : '-',
179
+ resource.quota?.storage != null ? `${resource.quota.storage} GiB` : '-',
180
+ formatValue(resource.quota?.replicas)
181
+ ])
182
+ ])
183
+ }
184
+ }
185
+
113
186
  export function createTemplateCommand (): Command {
114
187
  const tplCmd = new Command('template')
115
188
  .alias('tpl')
@@ -135,26 +208,8 @@ export function createTemplateCommand (): Command {
135
208
 
136
209
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
137
210
 
138
- ctx.spinner.succeed(`Instance "${data.name}" created successfully from catalog template "${catalogTemplate}"`)
139
- console.log(chalk.dim(` UID: ${data.uid}`))
140
- console.log(chalk.dim(` Created: ${data.createdAt}`))
141
-
142
- if (data.resources && data.resources.length > 0) {
143
- console.log(chalk.dim('\n Resources:'))
144
- const rows: string[][] = [
145
- [chalk.bold('Name'), chalk.bold('Type'), chalk.bold('CPU'), chalk.bold('Memory'), chalk.bold('Storage')]
146
- ]
147
- for (const r of data.resources) {
148
- rows.push([
149
- r.name,
150
- r.resourceType,
151
- r.quota?.cpu != null ? `${r.quota.cpu} vCPU` : '-',
152
- r.quota?.memory != null ? `${r.quota.memory} GiB` : '-',
153
- r.quota?.storage != null ? `${r.quota.storage} GiB` : '-'
154
- ])
155
- }
156
- outputTable(rows)
157
- }
211
+ ctx.spinner.succeed(`Instance "${data.name}" created successfully`)
212
+ printInstanceResult(data, { template: catalogTemplate })
158
213
  return
159
214
  }
160
215
 
@@ -171,23 +226,12 @@ export function createTemplateCommand (): Command {
171
226
 
172
227
  if (deployOptions.dryRun) {
173
228
  ctx.spinner.succeed('Raw template validation passed; no resources were created')
174
- console.log(chalk.dim(` Name: ${data.name}`))
175
- if (data.resources && data.resources.length > 0) {
176
- console.log(chalk.dim('\n Resources that would be created:'))
177
- for (const r of data.resources) {
178
- console.log(chalk.dim(` - ${r.resourceType}: ${r.name}`))
179
- }
180
- }
229
+ printInstanceResult(data, { raw: true, dryRun: true })
181
230
  return
182
231
  }
183
232
 
184
233
  ctx.spinner.succeed(`Raw template deployed as "${data.name}"`)
185
- if ('uid' in data) {
186
- console.log(chalk.dim(` UID: ${data.uid}`))
187
- }
188
- if ('createdAt' in data) {
189
- console.log(chalk.dim(` Created: ${data.createdAt}`))
190
- }
234
+ printInstanceResult(data, { raw: true })
191
235
  })
192
236
 
193
237
  // ── list ─────────────────────────────────────────────────────────
@@ -195,11 +239,12 @@ export function createTemplateCommand (): Command {
195
239
  .command('list')
196
240
  .description('List available templates')
197
241
  .option('-c, --category <category>', 'Filter by category')
242
+ .option('-l, --language <language>', 'Language code (for example: en, zh)')
198
243
  .option('-o, --output <format>', 'Output format (json|table)', 'table')
199
- .action(withErrorHandling({ spinnerText: 'Loading templates...' }, async (ctx, options: { category?: string; output: string }) => {
244
+ .action(withErrorHandling({ spinnerText: 'Loading templates...' }, async (ctx, options: { category?: string; language?: string; output: string }) => {
200
245
  const client = createTemplateClient()
201
246
  const { data, error, response } = await client.GET('/templates', {
202
- params: { query: {} }
247
+ params: { query: options.language ? { language: options.language } : {} }
203
248
  })
204
249
 
205
250
  if (error) throw mapApiError(response.status, error as ApiErrorBody)
@@ -235,12 +280,14 @@ export function createTemplateCommand (): Command {
235
280
  .command('get <name>')
236
281
  .alias('describe')
237
282
  .description('Get template details')
283
+ .option('-l, --language <language>', 'Language code (for example: en, zh)')
238
284
  .option('-o, --output <format>', 'Output format (json|table)', 'table')
239
- .action(withErrorHandling({ spinnerText: 'Loading template...' }, async (ctx, name: string, options: { output: string }) => {
285
+ .action(withErrorHandling({ spinnerText: 'Loading template...' }, async (ctx, name: string, options: { language?: string; output: string }) => {
240
286
  const client = createTemplateClient()
241
287
  const { data, error, response } = await client.GET('/templates/{name}', {
242
288
  params: {
243
- path: { name }
289
+ path: { name },
290
+ query: options.language ? { language: options.language } : {}
244
291
  }
245
292
  })
246
293
 
@@ -286,6 +333,25 @@ export function createTemplateCommand (): Command {
286
333
  }
287
334
  }))
288
335
 
336
+ // -- delete --------------------------------------------------------
337
+ tplCmd
338
+ .command('delete <instance>')
339
+ .alias('rm')
340
+ .description('Delete a deployed template instance')
341
+ .action(withAuth({ spinnerText: 'Deleting template instance...' }, async (ctx, instance: string) => {
342
+ const client = createTemplateClient()
343
+ const { error, response } = await client.DELETE('/templates/instances/{instanceName}', {
344
+ headers: ctx.auth,
345
+ params: {
346
+ path: { instanceName: instance }
347
+ }
348
+ })
349
+
350
+ if (error) throw mapApiError(response.status, error as ApiErrorBody)
351
+
352
+ ctx.spinner.succeed(`Instance "${instance}" deleted`)
353
+ }))
354
+
289
355
  // ── deploy ──────────────────────────────────────────────────────
290
356
  tplCmd
291
357
  .command('deploy [template]')
@@ -298,12 +364,12 @@ export function createTemplateCommand (): Command {
298
364
  .addHelpText('after', `
299
365
  Examples:
300
366
  Catalog:
301
- sealos template deploy perplexica --name my-app --set OPENAI_API_KEY=xxx
367
+ sealos-cli template deploy perplexica --name my-app --set OPENAI_API_KEY=xxx
302
368
 
303
369
  Raw:
304
- sealos template deploy --file ./template.yaml --dry-run
305
- sealos template deploy --yaml 'apiVersion: app.sealos.io/v1\nkind: Template\n...'
306
- cat template.yaml | sealos template deploy --dry-run
370
+ sealos-cli template deploy --file ./template.yaml --dry-run
371
+ sealos-cli template deploy --yaml 'apiVersion: app.sealos.io/v1\nkind: Template\n...'
372
+ cat template.yaml | sealos-cli template deploy --dry-run
307
373
  `)
308
374
  .action(async (template: string | undefined, options: TemplateDeployOptions) => {
309
375
  const mode = resolveTemplateDeployMode(template, options)
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander'
2
- import { getCurrentContext } from '../../lib/config.ts'
3
- import { success, outputTable, info } from '../../lib/output.ts'
2
+ import { getAuthInfo, listWorkspaces, switchWorkspace } from '../../lib/auth.ts'
3
+ import { success, outputJson, outputTable } from '../../lib/output.ts'
4
4
  import { handleError, AuthError } from '../../lib/errors.ts'
5
5
 
6
6
  export function createWorkspaceCommand (): Command {
@@ -8,73 +8,83 @@ export function createWorkspaceCommand (): Command {
8
8
  .alias('ws')
9
9
  .description('Manage workspaces')
10
10
 
11
- // workspace switch
12
11
  workspaceCmd
13
12
  .command('switch')
14
13
  .description('Switch to another workspace')
15
- .argument('<name>', 'Workspace name')
16
- .action(async (name) => {
14
+ .argument('<namespace>', 'Workspace id, uid, or team name')
15
+ .option('-o, --output <format>', 'Output format: json, table', 'table')
16
+ .action(async (namespace, options) => {
17
17
  try {
18
- // TODO: 调用 API 验证 workspace 是否存在
19
- // const api = createApiClient()
20
- // await api.get(`/api/v1/workspaces/${name}`)
21
-
22
- // 更新配置
23
- const context = getCurrentContext()
24
- if (context) {
25
- context.workspace = name
26
- // TODO: 更新到配置文件
18
+ const result = await switchWorkspace(namespace)
19
+ if (options.output === 'json') {
20
+ outputJson(result)
21
+ return
27
22
  }
28
23
 
29
- success(`Switched to workspace: ${name}`)
24
+ success(`Switched to workspace: ${result.workspace.id || result.workspace.uid || namespace}`)
30
25
  } catch (error) {
31
26
  handleError(error)
32
27
  }
33
28
  })
34
29
 
35
- // workspace list
36
30
  workspaceCmd
37
31
  .command('list')
38
32
  .description('List all workspaces')
39
- .action(async () => {
33
+ .option('-o, --output <format>', 'Output format: json, table', 'table')
34
+ .action(async (options) => {
40
35
  try {
41
- const context = getCurrentContext()
42
- if (!context) {
43
- throw new AuthError()
36
+ const result = await listWorkspaces()
37
+ if (options.output === 'json') {
38
+ outputJson(result)
39
+ return
44
40
  }
45
41
 
46
- // 示例数据
47
- const data = [
48
- ['NAME', 'STATUS', 'CURRENT'],
49
- ['default', 'Active', context.workspace === 'default' ? '*' : ''],
50
- ['production', 'Active', context.workspace === 'production' ? '*' : '']
51
- ]
52
-
53
- outputTable(data)
54
- info('API integration needed for real data')
42
+ outputTable([
43
+ ['UID', 'ID', 'TEAM', 'ROLE', 'TYPE', 'CURRENT'],
44
+ ...result.workspaces.map(workspace => [
45
+ workspace.uid || '',
46
+ workspace.id || '',
47
+ workspace.teamName || '',
48
+ workspace.role || '',
49
+ workspace.nstype || '',
50
+ workspace.id === result.current ? '*' : ''
51
+ ])
52
+ ])
55
53
  } catch (error) {
56
54
  handleError(error)
57
55
  }
58
56
  })
59
57
 
60
- // workspace current
61
58
  workspaceCmd
62
59
  .command('current')
63
60
  .description('Show current workspace')
64
- .action(async () => {
61
+ .option('-o, --output <format>', 'Output format: json, table', 'table')
62
+ .action(async (options) => {
65
63
  try {
66
- const context = getCurrentContext()
67
- if (!context) {
64
+ const authInfo = getAuthInfo()
65
+ if (!authInfo.authenticated) {
68
66
  throw new AuthError()
69
67
  }
70
68
 
71
- const data = [
72
- ['Field', 'Value'],
73
- ['Workspace', context.workspace],
74
- ['Context', context.name]
75
- ]
69
+ const workspace = authInfo.current_workspace || null
70
+ const result = {
71
+ workspace,
72
+ region: authInfo.region || 'unknown',
73
+ kubeconfig_path: authInfo.kubeconfig_path || 'unknown'
74
+ }
75
+
76
+ if (options.output === 'json') {
77
+ outputJson(result)
78
+ return
79
+ }
76
80
 
77
- outputTable(data)
81
+ outputTable([
82
+ ['Field', 'Value'],
83
+ ['Workspace', workspace?.id || authInfo.workspace || 'unknown'],
84
+ ['Team', workspace?.teamName || 'unknown'],
85
+ ['Region', authInfo.region || 'unknown'],
86
+ ['Kubeconfig', authInfo.kubeconfig_path || 'unknown']
87
+ ])
78
88
  } catch (error) {
79
89
  handleError(error)
80
90
  }
@@ -6889,46 +6889,29 @@
6889
6889
  "application/json": {
6890
6890
  "schema": {
6891
6891
  "type": "object",
6892
- "properties": {
6893
- "code": {
6894
- "type": "number"
6895
- },
6896
- "message": {
6892
+ "propertyNames": {
6893
+ "type": "string",
6894
+ "enum": [
6895
+ "postgresql",
6896
+ "mongodb",
6897
+ "apecloud-mysql",
6898
+ "mysql",
6899
+ "redis",
6900
+ "kafka",
6901
+ "qdrant",
6902
+ "nebula",
6903
+ "weaviate",
6904
+ "milvus",
6905
+ "pulsar",
6906
+ "clickhouse"
6907
+ ]
6908
+ },
6909
+ "additionalProperties": {
6910
+ "type": "array",
6911
+ "items": {
6897
6912
  "type": "string"
6898
- },
6899
- "data": {
6900
- "type": "object",
6901
- "propertyNames": {
6902
- "type": "string",
6903
- "enum": [
6904
- "postgresql",
6905
- "mongodb",
6906
- "apecloud-mysql",
6907
- "mysql",
6908
- "redis",
6909
- "kafka",
6910
- "qdrant",
6911
- "nebula",
6912
- "weaviate",
6913
- "milvus",
6914
- "pulsar",
6915
- "clickhouse"
6916
- ]
6917
- },
6918
- "additionalProperties": {
6919
- "type": "array",
6920
- "items": {
6921
- "type": "string"
6922
- }
6923
- }
6924
6913
  }
6925
- },
6926
- "required": [
6927
- "code",
6928
- "message",
6929
- "data"
6930
- ],
6931
- "additionalProperties": false
6914
+ }
6932
6915
  }
6933
6916
  }
6934
6917
  }