sealos-cli 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -104,6 +104,26 @@ export interface paths {
104
104
  patch?: never;
105
105
  trace?: never;
106
106
  };
107
+ "/templates/instances/{instanceName}": {
108
+ parameters: {
109
+ query?: never;
110
+ header?: never;
111
+ path?: never;
112
+ cookie?: never;
113
+ };
114
+ get?: never;
115
+ put?: never;
116
+ post?: never;
117
+ /**
118
+ * Delete template instance
119
+ * @description Deletes a deployed template instance from the user namespace. Instances created with ownerReferences enabled delete their explicit PVCs first and then delete the Instance so Kubernetes garbage collection removes owned dependents. Legacy instances without the ownerReferences-ready marker use the comprehensive label-selector cleanup path before deleting the Instance.
120
+ */
121
+ delete: operations["deleteInstance"];
122
+ options?: never;
123
+ head?: never;
124
+ patch?: never;
125
+ trace?: never;
126
+ };
107
127
  }
108
128
  export type webhooks = Record<string, never>;
109
129
  export interface components {
@@ -1004,4 +1024,212 @@ export interface operations {
1004
1024
  };
1005
1025
  };
1006
1026
  };
1027
+ deleteInstance: {
1028
+ parameters: {
1029
+ query?: never;
1030
+ header?: never;
1031
+ path: {
1032
+ /** @description Name of the deployed template instance to delete (must exist in user namespace) */
1033
+ instanceName: string;
1034
+ };
1035
+ cookie?: never;
1036
+ };
1037
+ requestBody?: never;
1038
+ responses: {
1039
+ /** @description Instance deleted successfully. No response body. */
1040
+ 204: {
1041
+ headers: {
1042
+ [name: string]: unknown;
1043
+ };
1044
+ content?: never;
1045
+ };
1046
+ /** @description Bad request - invalid instance name parameter */
1047
+ 400: {
1048
+ headers: {
1049
+ [name: string]: unknown;
1050
+ };
1051
+ content: {
1052
+ "application/json": {
1053
+ error: {
1054
+ /**
1055
+ * @description High-level error type for categorization
1056
+ * @enum {string}
1057
+ */
1058
+ type: "validation_error";
1059
+ /**
1060
+ * @description Specific error code for programmatic handling and i18n
1061
+ * @enum {string}
1062
+ */
1063
+ code: "INVALID_PARAMETER";
1064
+ /** @description Human-readable error message */
1065
+ message: string;
1066
+ /** @description For INVALID_PARAMETER: Array<{ field, message }>. For INVALID_VALUE: optional string. Omitted for other codes. */
1067
+ details?: {
1068
+ /** @description Field path using dot/bracket notation, e.g. "ports[0].number" */
1069
+ field: string;
1070
+ /** @description Validation error message for this field */
1071
+ message: string;
1072
+ }[] | string;
1073
+ };
1074
+ };
1075
+ };
1076
+ };
1077
+ /** @description Unauthorized - Missing or invalid kubeconfig */
1078
+ 401: {
1079
+ headers: {
1080
+ [name: string]: unknown;
1081
+ };
1082
+ content: {
1083
+ "application/json": {
1084
+ error: {
1085
+ /**
1086
+ * @description High-level error type for categorization
1087
+ * @constant
1088
+ */
1089
+ type: "authentication_error";
1090
+ /**
1091
+ * @description Specific error code for programmatic handling and i18n
1092
+ * @constant
1093
+ */
1094
+ code: "AUTHENTICATION_REQUIRED";
1095
+ /** @description Human-readable error message */
1096
+ message: string;
1097
+ /** @description Typically omitted. May contain additional context in edge cases. */
1098
+ details?: string;
1099
+ };
1100
+ };
1101
+ };
1102
+ };
1103
+ /** @description Forbidden - Insufficient permissions */
1104
+ 403: {
1105
+ headers: {
1106
+ [name: string]: unknown;
1107
+ };
1108
+ content: {
1109
+ "application/json": {
1110
+ error: {
1111
+ /**
1112
+ * @description High-level error type for categorization
1113
+ * @constant
1114
+ */
1115
+ type: "authorization_error";
1116
+ /**
1117
+ * @description Specific error code for programmatic handling and i18n
1118
+ * @enum {string}
1119
+ */
1120
+ code: "PERMISSION_DENIED";
1121
+ /** @description Human-readable error message */
1122
+ message: string;
1123
+ /** @description Typically omitted. May contain additional context in edge cases. */
1124
+ details?: string;
1125
+ };
1126
+ };
1127
+ };
1128
+ };
1129
+ /** @description Not Found - Instance not found */
1130
+ 404: {
1131
+ headers: {
1132
+ [name: string]: unknown;
1133
+ };
1134
+ content: {
1135
+ "application/json": {
1136
+ error: {
1137
+ /**
1138
+ * @description High-level error type for categorization
1139
+ * @constant
1140
+ */
1141
+ type: "resource_error";
1142
+ /**
1143
+ * @description Specific error code for programmatic handling and i18n
1144
+ * @constant
1145
+ */
1146
+ code: "NOT_FOUND";
1147
+ /** @description Human-readable error message */
1148
+ message: string;
1149
+ /** @description Typically omitted. May contain additional context in edge cases. */
1150
+ details?: string;
1151
+ };
1152
+ };
1153
+ };
1154
+ };
1155
+ /** @description Method Not Allowed */
1156
+ 405: {
1157
+ headers: {
1158
+ [name: string]: unknown;
1159
+ };
1160
+ content: {
1161
+ "application/json": {
1162
+ error: {
1163
+ /**
1164
+ * @description High-level error type for categorization
1165
+ * @constant
1166
+ */
1167
+ type: "client_error";
1168
+ /**
1169
+ * @description Specific error code for programmatic handling and i18n
1170
+ * @constant
1171
+ */
1172
+ code: "METHOD_NOT_ALLOWED";
1173
+ /** @description Human-readable error message */
1174
+ message: string;
1175
+ /** @description Typically omitted. */
1176
+ details?: string;
1177
+ };
1178
+ };
1179
+ };
1180
+ };
1181
+ /** @description Internal Server Error - Kubernetes API error or unexpected failure */
1182
+ 500: {
1183
+ headers: {
1184
+ [name: string]: unknown;
1185
+ };
1186
+ content: {
1187
+ "application/json": {
1188
+ error: {
1189
+ /**
1190
+ * @description High-level error type for categorization
1191
+ * @enum {string}
1192
+ */
1193
+ type: "operation_error" | "internal_error";
1194
+ /**
1195
+ * @description Specific error code for programmatic handling and i18n
1196
+ * @enum {string}
1197
+ */
1198
+ code: "KUBERNETES_ERROR" | "INTERNAL_ERROR";
1199
+ /** @description Human-readable error message */
1200
+ message: string;
1201
+ /** @description Raw error string from the underlying system, for troubleshooting. */
1202
+ details?: string;
1203
+ };
1204
+ };
1205
+ };
1206
+ };
1207
+ /** @description Service Unavailable - Kubernetes cluster temporarily unreachable */
1208
+ 503: {
1209
+ headers: {
1210
+ [name: string]: unknown;
1211
+ };
1212
+ content: {
1213
+ "application/json": {
1214
+ error: {
1215
+ /**
1216
+ * @description High-level error type for categorization
1217
+ * @constant
1218
+ */
1219
+ type: "internal_error";
1220
+ /**
1221
+ * @description Specific error code for programmatic handling and i18n
1222
+ * @constant
1223
+ */
1224
+ code: "SERVICE_UNAVAILABLE";
1225
+ /** @description Human-readable error message */
1226
+ message: string;
1227
+ /** @description Raw connection error from the underlying system (e.g. ECONNREFUSED). */
1228
+ details?: string;
1229
+ };
1230
+ };
1231
+ };
1232
+ };
1233
+ };
1234
+ };
1007
1235
  }
@@ -1,6 +1,7 @@
1
1
  import createClient from 'openapi-fetch'
2
2
  import type { paths as TemplatePaths } from '../generated/template.ts'
3
3
  import type { paths as DatabasePaths } from '../generated/database.ts'
4
+ import type { paths as DevboxPaths } from '../generated/devbox.ts'
4
5
  import { DEFAULT_SEALOS_REGION, loadAuth } from './auth.ts'
5
6
  import { ConfigError } from './errors.ts'
6
7
 
@@ -27,6 +28,10 @@ export function resolveTemplateProviderHost (host: string): string {
27
28
  return resolvePrefixedHost(host, 'template')
28
29
  }
29
30
 
31
+ export function resolveDevboxProviderHost (host: string): string {
32
+ return resolvePrefixedHost(host, 'devbox')
33
+ }
34
+
30
35
  function resolvePrefixedHost (host: string, prefix: string): string {
31
36
  const url = new URL(host)
32
37
 
@@ -55,6 +60,15 @@ function resolveTemplateHost (options?: { baseUrl?: string }): string {
55
60
  return resolveTemplateProviderHost(resolveHost(options))
56
61
  }
57
62
 
63
+ function resolveDevboxHost (options?: { baseUrl?: string }): string {
64
+ const override = process.env.SEALOS_DEVBOX_HOST?.trim()
65
+ if (override) {
66
+ return override.replace(/\/+$/, '')
67
+ }
68
+
69
+ return resolveDevboxProviderHost(resolveHost(options))
70
+ }
71
+
58
72
  export function createTemplateClient (options?: { baseUrl?: string }) {
59
73
  return createClient<TemplatePaths>({ baseUrl: `${resolveTemplateHost(options)}/api/v2alpha` })
60
74
  }
@@ -62,3 +76,7 @@ export function createTemplateClient (options?: { baseUrl?: string }) {
62
76
  export function createDatabaseClient (options?: { baseUrl?: string }) {
63
77
  return createClient<DatabasePaths>({ baseUrl: `${resolveDatabaseHost(options)}/api/v2alpha` })
64
78
  }
79
+
80
+ export function createDevboxClient (options?: { baseUrl?: string }) {
81
+ return createClient<DevboxPaths>({ baseUrl: `${resolveDevboxHost(options)}/api/v2alpha` })
82
+ }
package/src/lib/auth.ts CHANGED
@@ -147,7 +147,7 @@ export function loadAuth (deps: AuthDependencies = {}): SealosAuthData {
147
147
  return JSON.parse(readFileSync(paths.authPath, 'utf-8')) as SealosAuthData
148
148
  }
149
149
 
150
- export function getToken (deps: AuthDependencies = {}): string | null {
150
+ export function getRegionalToken (deps: AuthDependencies = {}): string | null {
151
151
  try {
152
152
  return loadAuth(deps).regional_token || null
153
153
  } catch {
@@ -155,9 +155,18 @@ export function getToken (deps: AuthDependencies = {}): string | null {
155
155
  }
156
156
  }
157
157
 
158
+ export function getKubeconfigContent (deps: AuthDependencies = {}): string | null {
159
+ const { paths } = withDeps(deps)
160
+ try {
161
+ return readFileSync(paths.kubeconfigPath, 'utf-8')
162
+ } catch {
163
+ return null
164
+ }
165
+ }
166
+
158
167
  export function getAuthHeaders (deps: AuthDependencies = {}): { Authorization: string } | null {
159
- const token = getToken(deps)
160
- return token ? { Authorization: token } : null
168
+ const kubeconfig = getKubeconfigContent(deps)
169
+ return kubeconfig ? { Authorization: encodeURIComponent(kubeconfig) } : null
161
170
  }
162
171
 
163
172
  export function requireAuth (deps: AuthDependencies = {}): { Authorization: string } {
package/src/main.ts CHANGED
@@ -3,12 +3,8 @@ import { Command } from 'commander'
3
3
  import { registerAuthCommands } from './commands/auth/index.ts'
4
4
  import { createWorkspaceCommand } from './commands/workspace/index.ts'
5
5
  import { createDevboxCommand } from './commands/devbox/index.ts'
6
- import { createS3Command } from './commands/s3/index.ts'
7
6
  import { createDatabaseCommand } from './commands/database/index.ts'
8
7
  import { createTemplateCommand } from './commands/template/index.ts'
9
- import { createQuotaCommand } from './commands/quota/index.ts'
10
- import { createAppCommand } from './commands/app/index.ts'
11
- import { createConfigCommand } from './commands/config/index.ts'
12
8
  import { handleError } from './lib/errors.ts'
13
9
 
14
10
  export function createProgram (): Command {
@@ -16,19 +12,15 @@ export function createProgram (): Command {
16
12
 
17
13
  program
18
14
  .name('sealos')
19
- .description('Official CLI tool for Sealos Cloud - Manage devbox, applications, databases, and object storage')
15
+ .description('Official CLI tool for Sealos Cloud - Manage devbox, databases, templates, auth, and workspaces')
20
16
  .version('0.0.1')
21
17
 
22
18
  // Register all command modules
23
19
  registerAuthCommands(program)
24
20
  program.addCommand(createWorkspaceCommand())
25
21
  program.addCommand(createDevboxCommand())
26
- program.addCommand(createS3Command())
27
22
  program.addCommand(createDatabaseCommand())
28
23
  program.addCommand(createTemplateCommand())
29
- program.addCommand(createQuotaCommand())
30
- program.addCommand(createAppCommand())
31
- program.addCommand(createConfigCommand())
32
24
 
33
25
  return program
34
26
  }
@@ -1,17 +1,5 @@
1
1
  // Core type definitions
2
2
 
3
- export interface SealosConfig {
4
- currentContext: string
5
- contexts: Context[]
6
- }
7
-
8
- export interface Context {
9
- name: string
10
- host: string
11
- token: string
12
- workspace: string
13
- }
14
-
15
3
  export interface DevboxConfig {
16
4
  name?: string
17
5
  template: string
@@ -1,54 +0,0 @@
1
- import { Command } from 'commander'
2
- import { readConfig, setConfigValue, getConfigValue } from '../../lib/config.ts'
3
- import { success, outputJson } from '../../lib/output.ts'
4
- import { handleError } from '../../lib/errors.ts'
5
-
6
- export function createConfigCommand (): Command {
7
- const configCmd = new Command('config')
8
- .description('Manage CLI configuration')
9
-
10
- configCmd
11
- .command('set')
12
- .description('Set a config value')
13
- .argument('<key>', 'Config key')
14
- .argument('<value>', 'Config value')
15
- .action(async (key, value) => {
16
- try {
17
- setConfigValue(key, value)
18
- success(`Config ${key} set to ${value}`)
19
- } catch (error) {
20
- handleError(error)
21
- }
22
- })
23
-
24
- configCmd
25
- .command('get')
26
- .description('Get a config value')
27
- .argument('<key>', 'Config key')
28
- .action(async (key) => {
29
- try {
30
- const value = getConfigValue(key)
31
- if (value) {
32
- console.log(value)
33
- } else {
34
- console.log(`Config key "${key}" not found`)
35
- }
36
- } catch (error) {
37
- handleError(error)
38
- }
39
- })
40
-
41
- configCmd
42
- .command('list')
43
- .description('List all config values')
44
- .action(async () => {
45
- try {
46
- const config = readConfig()
47
- outputJson(config)
48
- } catch (error) {
49
- handleError(error)
50
- }
51
- })
52
-
53
- return configCmd
54
- }
package/src/lib/api.ts DELETED
@@ -1,83 +0,0 @@
1
- import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
2
- import { getCurrentContext } from './config.ts'
3
-
4
- /**
5
- * API client base class
6
- */
7
- export class ApiClient {
8
- private client: AxiosInstance
9
-
10
- constructor (baseURL?: string) {
11
- const context = getCurrentContext()
12
-
13
- this.client = axios.create({
14
- baseURL: baseURL || context?.host || '',
15
- timeout: 30000,
16
- headers: {
17
- 'Content-Type': 'application/json'
18
- }
19
- })
20
-
21
- // Request interceptor - add auth token
22
- this.client.interceptors.request.use(
23
- (config) => {
24
- const context = getCurrentContext()
25
- if (context?.token) {
26
- config.headers.Authorization = `Bearer ${context.token}`
27
- }
28
-
29
- // Support KUBECONFIG environment variable
30
- const kubeconfig = process.env.KUBECONFIG
31
- if (kubeconfig) {
32
- config.headers['X-Kubeconfig'] = kubeconfig
33
- }
34
-
35
- return config
36
- },
37
- async (error) => await Promise.reject(error)
38
- )
39
-
40
- // Response interceptor - unified error handling
41
- this.client.interceptors.response.use(
42
- (response) => response,
43
- async (error) => {
44
- if (error.response?.status === 401) {
45
- throw new Error('Authentication failed. Please run "sealos login" first.')
46
- }
47
- return await Promise.reject(error)
48
- }
49
- )
50
- }
51
-
52
- async get<T = any> (url: string, config?: AxiosRequestConfig): Promise<T> {
53
- const response = await this.client.get<T>(url, config)
54
- return response.data
55
- }
56
-
57
- async post<T = any> (url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
58
- const response = await this.client.post<T>(url, data, config)
59
- return response.data
60
- }
61
-
62
- async put<T = any> (url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
63
- const response = await this.client.put<T>(url, data, config)
64
- return response.data
65
- }
66
-
67
- async delete<T = any> (url: string, config?: AxiosRequestConfig): Promise<T> {
68
- const response = await this.client.delete<T>(url, config)
69
- return response.data
70
- }
71
-
72
- async patch<T = any> (url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
73
- const response = await this.client.patch<T>(url, data, config)
74
- return response.data
75
- }
76
- }
77
-
78
- /**
79
- * Create API client instance
80
- */
81
- export function createApiClient (baseURL?: string): ApiClient {
82
- return new ApiClient(baseURL)
83
- }
package/src/lib/config.ts DELETED
@@ -1,134 +0,0 @@
1
- import { homedir } from 'node:os'
2
- import { join } from 'node:path'
3
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
4
- import type { SealosConfig, Context } from '../types/index.ts'
5
-
6
- const CONFIG_DIR = join(homedir(), '.sealos')
7
- const CONFIG_FILE = join(CONFIG_DIR, 'config.json')
8
-
9
- /**
10
- * Ensure config directory exists
11
- */
12
- export function ensureConfigDir (): void {
13
- if (!existsSync(CONFIG_DIR)) {
14
- mkdirSync(CONFIG_DIR, { recursive: true })
15
- }
16
- }
17
-
18
- /**
19
- * Read config file
20
- */
21
- export function readConfig (): SealosConfig {
22
- ensureConfigDir()
23
-
24
- if (!existsSync(CONFIG_FILE)) {
25
- const defaultConfig: SealosConfig = {
26
- currentContext: '',
27
- contexts: []
28
- }
29
- return defaultConfig
30
- }
31
-
32
- try {
33
- const content = readFileSync(CONFIG_FILE, 'utf-8')
34
- return JSON.parse(content) as SealosConfig
35
- } catch (error) {
36
- throw new Error(`Failed to read config file: ${error}`)
37
- }
38
- }
39
-
40
- /**
41
- * Write config file
42
- */
43
- export function writeConfig (config: SealosConfig): void {
44
- ensureConfigDir()
45
-
46
- try {
47
- writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8')
48
- } catch (error) {
49
- throw new Error(`Failed to write config file: ${error}`)
50
- }
51
- }
52
-
53
- /**
54
- * Get current context
55
- */
56
- export function getCurrentContext (): Context | null {
57
- const config = readConfig()
58
- if (!config.currentContext) {
59
- return null
60
- }
61
-
62
- const context = config.contexts.find(ctx => ctx.name === config.currentContext)
63
- return context || null
64
- }
65
-
66
- /**
67
- * Set current context
68
- */
69
- export function setCurrentContext (name: string): void {
70
- const config = readConfig()
71
- const context = config.contexts.find(ctx => ctx.name === name)
72
-
73
- if (!context) {
74
- throw new Error(`Context "${name}" not found`)
75
- }
76
-
77
- config.currentContext = name
78
- writeConfig(config)
79
- }
80
-
81
- /**
82
- * Add or update context
83
- */
84
- export function upsertContext (context: Context): void {
85
- const config = readConfig()
86
- const existingIndex = config.contexts.findIndex(ctx => ctx.name === context.name)
87
-
88
- if (existingIndex >= 0) {
89
- config.contexts[existingIndex] = context
90
- } else {
91
- config.contexts.push(context)
92
- }
93
-
94
- // If this is the first context, set it as current automatically
95
- if (!config.currentContext) {
96
- config.currentContext = context.name
97
- }
98
-
99
- writeConfig(config)
100
- }
101
-
102
- /**
103
- * Remove context
104
- */
105
- export function removeContext (name: string): void {
106
- const config = readConfig()
107
- config.contexts = config.contexts.filter(ctx => ctx.name !== name)
108
-
109
- // If removing current context, clear currentContext
110
- if (config.currentContext === name) {
111
- config.currentContext = config.contexts[0]?.name || ''
112
- }
113
-
114
- writeConfig(config)
115
- }
116
-
117
- /**
118
- * Get config value
119
- */
120
- export function getConfigValue (key: string): string | undefined {
121
- const config = readConfig()
122
- // TODO: Implement nested key access, e.g. "contexts.0.name"
123
- return (config as unknown as Record<string, unknown>)[key] as string | undefined
124
- }
125
-
126
- /**
127
- * Set config value
128
- */
129
- export function setConfigValue (key: string, value: string): void {
130
- const config = readConfig()
131
- // TODO: Implement nested key setting
132
- ;(config as unknown as Record<string, unknown>)[key] = value
133
- writeConfig(config)
134
- }