This CLI follows Node.js best practices with a modular, maintainable structure.
Each command module is self-contained with its own directory:
commands/
auth/ - login, logout, whoami
workspace/ - workspace management
devbox/ - devbox operations
s3/ - object storage
database/ - database management
template/ - template operations
quota/ - resource quotas
app/ - application management
config/ - CLI configuration
Each module exports factory functions that create Commander.js command instances.
- Reads/writes
~/.sealos/config.json - Manages contexts (host, token, workspace)
- Helper functions for context switching
- Axios-based client with interceptors
- Automatic authentication via Bearer token
- KUBECONFIG environment variable support
- Unified error handling (401 → AuthError)
outputJson()- JSON outputoutputYaml()- YAML output (TODO)outputTable()- Table formattingsuccess(),error(),warn(),info()- Colored messagesspinner()- Loading indicators
CliError- Base error classAuthError- Authentication errorsConfigError- Configuration errorsApiError- API call errorshandleError()- Global error handler
- TypeScript interfaces for configuration
- API request/response types
- Shared type definitions
- Creates Commander.js program
- Registers all command modules
- Sets up global error handling
- CLI entry point
- Calls
runCLI()from main.ts
User Command
↓
CLI Entry (bin/cli.ts)
↓
Main Program (main.ts)
↓
Command Module (commands/*/index.ts)
↓
┌─────────────┬──────────────┬────────────┐
│ │ │ │
Config API Client Output Error
(lib/config) (lib/api) (lib/output) (lib/errors)
↓ ↓ ↓ ↓
Configuration HTTP Req Terminal Error Handler
File to Sealos Display & Exit
Commands are created via factory functions:
export function createDevboxCommand(): Command {
const cmd = new Command('devbox')
// configure command
return cmd
}All config operations go through lib/config.ts:
const context = getCurrentContext()
upsertContext(newContext)API client uses axios interceptors for:
- Adding auth tokens to requests
- Converting 401 errors to AuthError
All command actions wrapped in try/catch:
.action(async (name) => {
try {
// command logic
} catch (error) {
handleError(error)
}
})KUBECONFIG- Automatically added to API requests viaX-KubeconfigheaderDEBUG- Shows full stack traces on errors
Location: ~/.sealos/config.json
{
"currentContext": "default",
"contexts": [
{
"name": "default",
"host": "https://hzh.sealos.run",
"token": "xxx",
"workspace": "default"
}
]
}- Create directory in
src/commands/ - Create
index.tswith factory function - Implement command logic with error handling
- Register in
src/main.ts - Use shared utilities from
lib/
Example:
// src/commands/example/index.ts
import { Command } from 'commander'
import { handleError } from '../../lib/errors.ts'
import { createApiClient } from '../../lib/api.ts'
export function createExampleCommand(): Command {
const cmd = new Command('example')
.description('Example command')
.action(async () => {
try {
const api = createApiClient()
// implementation
} catch (error) {
handleError(error)
}
})
return cmd
}- Unit tests for
lib/utilities - Integration tests for command execution
- Mock API responses for command tests
- E2E tests for full workflows
- Add YAML library for proper YAML output
- Implement interactive prompts (inquirer)
- Add progress bars for file uploads
- Support nested config key access
- Add command aliases
- Add shell completion scripts
This section describes the newly introduced architecture, independent from the above. The template command has been migrated to this approach. All new commands should use it.
OpenAPI Spec (src/docs/*.json)
│
│ npx openapi-typescript (generated at build time)
▼
Generated Types (src/generated/*.ts)
│
│ import type { paths }
▼
Typed Client Factory (src/lib/api-client.ts)
│
│ createTemplateClient()
▼
Command handler wrapped with withAuth / withErrorHandling
│
▼
src/commands/template/index.ts (all subcommands)
{
"generate:api": "openapi-typescript src/docs/template_openapi.json -o src/generated/template.ts",
"build": "npm run generate:api && tsc && tsup"
}sealos login <host> without -t triggers the device authorization flow. With -t the kubeconfig is saved directly.
sealos login <host>
│
▼
oauth.ts: requestDeviceAuthorization(region)
POST /api/auth/oauth2/device → { device_code, user_code, verification_uri }
│
▼
User opens browser to authorize
│
▼
oauth.ts: pollForToken(region, deviceCode, interval, expiresIn)
POST /api/auth/oauth2/token → poll until { access_token }
Handles: authorization_pending, slow_down (+5s), access_denied, expired_token
Hard cap: 10 minutes
│
▼
oauth.ts: exchangeForKubeconfig(region, accessToken)
POST /api/auth/getDefaultKubeconfig → { data: { kubeconfig } }
│
▼
config.ts: upsertContext({ name, host, token: kubeconfig, workspace })
→ ~/.sealos/config.json
After login, the kubeconfig is stored as context.token. API calls obtain it via auth.ts:
auth.ts: getToken() → getCurrentContext().token
│
▼
auth.ts: getAuthHeaders() → { Authorization: encodeURIComponent(token) }
│
▼
API request headers
api-client.ts validates that a host is configured when creating a client. Throws ConfigError if not.
// Authenticated commands (deploy-raw, create-instance)
.action(withAuth({ spinnerText: 'Creating...' }, async (ctx, template, options) => {
const client = createTemplateClient()
const { data, error, response } = await client.POST('/templates/instances', {
headers: ctx.auth,
body: { ... }
})
if (error) throw mapApiError(response.status, error)
}))
// Public commands (list, get — no auth required)
.action(withErrorHandling({ spinnerText: 'Loading...' }, async (ctx, options) => {
const client = createTemplateClient()
const { data, error, response } = await client.GET('/templates', { ... })
if (error) throw mapApiError(response.status, error)
}))Unified API error format: { error: { type, code, message, details? } }
mapApiError(status, body) maps to AuthError (401) or ApiError (other).
| File | Role |
|---|---|
src/lib/constants.ts |
OAuth2 CLIENT_ID |
src/lib/oauth.ts |
Device grant flow: request → poll → exchange → browser open |
src/commands/auth/login.ts |
Login command: -t direct token or device grant |
src/docs/template_openapi.json |
OpenAPI 3.1.0 spec |
src/generated/template.ts |
Auto-generated types (do not edit manually) |
src/lib/auth.ts |
getToken / getAuthHeaders / requireAuth |
src/lib/with-auth.ts |
withAuth / withErrorHandling HOF |
src/lib/api-client.ts |
Client factory + host validation |
src/commands/template/index.ts |
Template commands |
- Place spec at
src/docs/<name>_openapi.json - Extend the
generate:apiscript - Add
create<Name>Client()inapi-client.ts - Use typed client + withAuth in the command