xuanwu-cli 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.
Files changed (45) hide show
  1. package/README.md +171 -0
  2. package/bin/xuanwu +20 -0
  3. package/dist/api/client.d.ts +30 -0
  4. package/dist/api/client.js +332 -0
  5. package/dist/commands/build.d.ts +5 -0
  6. package/dist/commands/build.js +57 -0
  7. package/dist/commands/connect.d.ts +5 -0
  8. package/dist/commands/connect.js +67 -0
  9. package/dist/commands/deploy.d.ts +5 -0
  10. package/dist/commands/deploy.js +85 -0
  11. package/dist/commands/env.d.ts +5 -0
  12. package/dist/commands/env.js +119 -0
  13. package/dist/commands/logs.d.ts +5 -0
  14. package/dist/commands/logs.js +39 -0
  15. package/dist/commands/pods.d.ts +5 -0
  16. package/dist/commands/pods.js +56 -0
  17. package/dist/commands/scale.d.ts +5 -0
  18. package/dist/commands/scale.js +32 -0
  19. package/dist/commands/svc.d.ts +5 -0
  20. package/dist/commands/svc.js +100 -0
  21. package/dist/config/store.d.ts +18 -0
  22. package/dist/config/store.js +108 -0
  23. package/dist/config/types.d.ts +86 -0
  24. package/dist/config/types.js +5 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.js +142 -0
  27. package/dist/output/formatter.d.ts +15 -0
  28. package/dist/output/formatter.js +95 -0
  29. package/docs/DESIGN.md +363 -0
  30. package/docs//345/276/205/344/274/230/345/214/226.md +89 -0
  31. package/package.json +31 -0
  32. package/src/api/client.ts +380 -0
  33. package/src/commands/build.ts +67 -0
  34. package/src/commands/connect.ts +75 -0
  35. package/src/commands/deploy.ts +90 -0
  36. package/src/commands/env.ts +144 -0
  37. package/src/commands/logs.ts +47 -0
  38. package/src/commands/pods.ts +60 -0
  39. package/src/commands/scale.ts +35 -0
  40. package/src/commands/svc.ts +114 -0
  41. package/src/config/store.ts +86 -0
  42. package/src/config/types.ts +99 -0
  43. package/src/index.ts +127 -0
  44. package/src/output/formatter.ts +112 -0
  45. package/tsconfig.json +17 -0
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # xuanwu-cli
2
+
3
+ 玄武工厂平台 CLI 工具
4
+
5
+ ## 快速开始
6
+
7
+ ### 1. 安装依赖
8
+
9
+ ```bash
10
+ npm install
11
+ ```
12
+
13
+ ### 2. 编译
14
+
15
+ ```bash
16
+ npm run build
17
+ ```
18
+
19
+ ### 3. 配置连接
20
+
21
+ ```bash
22
+ # 添加连接
23
+ node bin/xuanwu connect add prod \
24
+ --endpoint http://localhost:3000 \
25
+ --token <your-api-token>
26
+ ```
27
+
28
+ ### 4. 运行演示
29
+
30
+ ```bash
31
+ ./demo.sh
32
+ ```
33
+
34
+ ### 5. 运行验证测试
35
+
36
+ ```bash
37
+ ./verify.sh
38
+ ```
39
+
40
+ ## 功能验证
41
+
42
+ 运行验证脚本以测试所有已实现的功能:
43
+
44
+ ```bash
45
+ ./verify.sh
46
+ ```
47
+
48
+ 验证内容包括:
49
+ - ✅ 依赖检查(Node.js, jq, curl)
50
+ - ✅ 后端服务连接
51
+ - ✅ CLI 编译和执行
52
+ - ✅ 用户认证(注册、登录)
53
+ - ✅ 环境管理(列出、创建)
54
+ - ✅ 服务管理(列出、查询)
55
+ - ✅ 所有 CLI 命令
56
+
57
+ ## 详细文档
58
+
59
+ - [API 任务清单](../xuanwu-factory-v2/docs/tasks.md)
60
+ - [功能验证报告](../xuanwu-factory-v2/docs/verification-report.md)
61
+
62
+ ## 当前状态
63
+
64
+ **阶段 1 (基础功能)**: ✅ 完成
65
+ - 环境管理 API
66
+ - 服务管理 API
67
+ - K8s 查询 API
68
+ - 扩缩容 API
69
+ - 用户认证 API
70
+
71
+ **验证结果**: 19/19 测试通过 ✅
72
+
73
+ ## 获取 API Token
74
+
75
+ ### 方法1: Web 界面
76
+
77
+ 登录平台后,进入 `用户菜单 → API Token` 创建新 Token
78
+
79
+ ### 方法2: 命令行
80
+
81
+ ```bash
82
+ # 登录获取 JWT Token
83
+ curl -X POST http://localhost:3000/api/auth/login \
84
+ -H "Content-Type: application/json" \
85
+ -d '{"email": "your@email.com", "password": "password"}'
86
+
87
+ # 使用 JWT Token 创建 API Token
88
+ curl -X POST http://localhost:3000/api/users/api-tokens \
89
+ -H "Authorization: Bearer <jwt-token>" \
90
+ -H "Content-Type: application/json" \
91
+ -d '{"name": "cli-token"}'
92
+ ```
93
+
94
+ ## 快速开始
95
+
96
+ ### 1. 配置连接
97
+
98
+ ```bash
99
+ xuanwu connect add prod --endpoint https://xuanwu.company.com --token your-token
100
+ ```
101
+
102
+ ### 2. 环境管理
103
+
104
+ ```bash
105
+ # 列出环境
106
+ xuanwu env ls
107
+
108
+ # 创建环境
109
+ xuanwu env create shop-dev
110
+ ```
111
+
112
+ ### 3. 部署服务
113
+
114
+ ```bash
115
+ # 部署镜像
116
+ xuanwu deploy shop-dev nginx --type image --image nginx:latest
117
+
118
+ # 部署数据库
119
+ xuanwu deploy shop-dev mysql --type database --db-type mysql --db-version 8.0
120
+
121
+ # 部署应用(源码构建)
122
+ xuanwu deploy shop --type application \
123
+ --git https-dev api://gitlab.com/user/repo.git \
124
+ --git-branch main \
125
+ --build-type template \
126
+ --language java-springboot
127
+ ```
128
+
129
+ ### 4. 查看服务
130
+
131
+ ```bash
132
+ # 列出服务
133
+ xuanwu svc ls shop-dev
134
+
135
+ # 服务状态
136
+ xuanwu svc status shop-dev nginx
137
+ ```
138
+
139
+ ### 5. 调试
140
+
141
+ ```bash
142
+ # 查看日志
143
+ xuanwu logs shop-dev nginx -n 100
144
+
145
+ # 实时日志
146
+ xuanwu logs shop-dev nginx -f
147
+
148
+ # Pod 列表
149
+ xuanwu pods shop-dev nginx
150
+ ```
151
+
152
+ ## 命令参考
153
+
154
+ | 命令 | 说明 |
155
+ |------|------|
156
+ | `connect` | 管理连接 |
157
+ | `env` | 环境管理 |
158
+ | `deploy` | 部署服务 |
159
+ | `svc` | 服务管理 |
160
+ | `build` | 构建应用 |
161
+ | `scale` | 扩缩容 |
162
+ | `logs` | 查看日志 |
163
+ | `pods` | Pod 列表 |
164
+
165
+ ## 输出格式
166
+
167
+ 默认输出人类可读格式,使用 `-o json` 切换到 JSON 格式:
168
+
169
+ ```bash
170
+ xuanwu env ls -o json
171
+ ```
package/bin/xuanwu ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * xuanwu-cli 入口脚本
5
+ */
6
+
7
+ const { spawn } = require('child_process');
8
+ const path = require('path');
9
+
10
+ const args = process.argv.slice(2);
11
+ const distPath = path.join(__dirname, '..', 'dist', 'index.js');
12
+
13
+ const child = spawn('node', [distPath, ...args], {
14
+ stdio: 'inherit',
15
+ shell: process.platform === 'win32'
16
+ });
17
+
18
+ child.on('exit', (code) => {
19
+ process.exit(code || 0);
20
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * API 客户端
3
+ */
4
+ import { Connection, CLIResult, ServiceInfo, NamespaceInfo, DeployOptions } from '../config/types';
5
+ export declare class APIClient {
6
+ private client;
7
+ constructor(connection: Connection);
8
+ private request;
9
+ listNamespaces(): Promise<CLIResult<NamespaceInfo[]>>;
10
+ createNamespace(name: string): Promise<CLIResult<any>>;
11
+ deleteNamespace(id: string): Promise<CLIResult<void>>;
12
+ getNamespaceInfo(identifier: string): Promise<CLIResult<any>>;
13
+ getProjects(): Promise<CLIResult<any[]>>;
14
+ createNamespaceWithProject(name: string, projectId: string, environment?: string): Promise<CLIResult<any>>;
15
+ listServices(namespace: string): Promise<CLIResult<ServiceInfo[]>>;
16
+ getServiceStatus(namespace: string, name: string): Promise<CLIResult<any>>;
17
+ deleteService(namespace: string, name: string): Promise<CLIResult<void>>;
18
+ deploy(options: DeployOptions): Promise<CLIResult<any>>;
19
+ private getDefaultPort;
20
+ build(namespace: string, serviceName: string): Promise<CLIResult<any>>;
21
+ getBuildStatus(namespace: string, serviceName: string): Promise<CLIResult<any>>;
22
+ private getServiceByName;
23
+ scale(namespace: string, serviceName: string, replicas: number): Promise<CLIResult<any>>;
24
+ getLogs(namespace: string, serviceName: string, lines?: number, follow?: boolean): Promise<CLIResult<any>>;
25
+ streamLogs(namespace: string, serviceName: string): Promise<void>;
26
+ listPods(namespace: string, serviceName: string): Promise<CLIResult<any>>;
27
+ getMetrics(namespace: string, serviceName: string, podName?: string): Promise<CLIResult<any>>;
28
+ exec(namespace: string, serviceName: string, command: string, podName?: string): Promise<CLIResult<any>>;
29
+ }
30
+ export declare function createClient(connection: Connection): APIClient;
@@ -0,0 +1,332 @@
1
+ "use strict";
2
+ /**
3
+ * API 客户端
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.APIClient = void 0;
10
+ exports.createClient = createClient;
11
+ const axios_1 = __importDefault(require("axios"));
12
+ const formatter_1 = require("../output/formatter");
13
+ class APIClient {
14
+ constructor(connection) {
15
+ this.client = axios_1.default.create({
16
+ baseURL: connection.endpoint,
17
+ headers: {
18
+ 'Authorization': `Bearer ${connection.token}`,
19
+ 'Content-Type': 'application/json'
20
+ },
21
+ timeout: 30000
22
+ });
23
+ }
24
+ async request(method, url, data, config) {
25
+ const startTime = Date.now();
26
+ try {
27
+ const response = await this.client.request({
28
+ method,
29
+ url,
30
+ data,
31
+ ...config
32
+ });
33
+ return {
34
+ success: true,
35
+ data: response.data,
36
+ meta: {
37
+ timestamp: new Date().toISOString(),
38
+ duration: Date.now() - startTime
39
+ }
40
+ };
41
+ }
42
+ catch (error) {
43
+ const duration = Date.now() - startTime;
44
+ const message = error.response?.data?.message || error.message;
45
+ return {
46
+ success: false,
47
+ error: {
48
+ code: error.response?.status?.toString() || 'ERROR',
49
+ message,
50
+ details: error.response?.data
51
+ },
52
+ meta: {
53
+ timestamp: new Date().toISOString(),
54
+ duration
55
+ }
56
+ };
57
+ }
58
+ }
59
+ // ===================
60
+ // Namespace (环境)
61
+ // ===================
62
+ async listNamespaces() {
63
+ return this.request('GET', '/api/deploy-spaces');
64
+ }
65
+ async createNamespace(name) {
66
+ return this.request('POST', '/api/deploy-spaces', {
67
+ name,
68
+ identifier: name,
69
+ namespace: name,
70
+ environment: 'development'
71
+ });
72
+ }
73
+ async deleteNamespace(id) {
74
+ return this.request('DELETE', `/api/deploy-spaces/${id}`);
75
+ }
76
+ async getNamespaceInfo(identifier) {
77
+ return this.request('GET', `/api/deploy-spaces?identifier=${identifier}`);
78
+ }
79
+ async getProjects() {
80
+ return this.request('GET', '/api/projects');
81
+ }
82
+ async createNamespaceWithProject(name, projectId, environment = 'development') {
83
+ return this.request('POST', '/api/deploy-spaces', {
84
+ name,
85
+ identifier: name,
86
+ namespace: name,
87
+ environment,
88
+ project_id: projectId
89
+ });
90
+ }
91
+ // ===================
92
+ // Service
93
+ // ===================
94
+ async listServices(namespace) {
95
+ return this.request('GET', `/api/k8s?kind=deployments&namespace=${namespace}`);
96
+ }
97
+ async getServiceStatus(namespace, name) {
98
+ return this.request('GET', `/api/k8s/deployments/${name}?namespace=${namespace}`);
99
+ }
100
+ async deleteService(namespace, name) {
101
+ return this.request('DELETE', `/api/services/by-ns/${namespace}/${name}`);
102
+ }
103
+ // ===================
104
+ // Deploy
105
+ // ===================
106
+ async deploy(options) {
107
+ const { namespace, serviceName, type, ...rest } = options;
108
+ // 获取 namespace 对应的 project_id
109
+ let projectId;
110
+ // 尝试通过 identifier 查询
111
+ const envResult = await this.getNamespaceInfo(namespace);
112
+ if (envResult.success && envResult.data) {
113
+ const envData = Array.isArray(envResult.data) ? envResult.data[0] : envResult.data;
114
+ projectId = envData.project?.id || envData.projectId;
115
+ }
116
+ // 如果没找到,尝试从列表中查找
117
+ if (!projectId) {
118
+ const listResult = await this.listNamespaces();
119
+ if (listResult.success && listResult.data) {
120
+ const env = listResult.data.find((e) => e.namespace === namespace || e.identifier === namespace);
121
+ if (env) {
122
+ projectId = env.project?.id || env.projectId;
123
+ }
124
+ }
125
+ }
126
+ // 如果还是没有,使用第一个项目的 ID
127
+ if (!projectId) {
128
+ const projectsResult = await this.getProjects();
129
+ if (projectsResult.success && projectsResult.data && projectsResult.data.length > 0) {
130
+ projectId = projectsResult.data[0].id;
131
+ }
132
+ }
133
+ if (!projectId) {
134
+ return {
135
+ success: false,
136
+ error: { code: 'NOT_FOUND', message: `Cannot find project for namespace "${namespace}"` }
137
+ };
138
+ }
139
+ const payload = {
140
+ project_id: projectId,
141
+ name: serviceName,
142
+ type
143
+ };
144
+ // 根据类型添加配置
145
+ if (type === 'application') {
146
+ payload.git_provider = 'gitlab';
147
+ payload.git_repository = rest.git;
148
+ payload.git_branch = rest.gitBranch || 'main';
149
+ payload.build_type = rest.buildType || 'template';
150
+ payload.port = rest.port || 8080;
151
+ }
152
+ else if (type === 'database') {
153
+ payload.database_type = rest.dbType;
154
+ payload.version = rest.dbVersion || '8.0';
155
+ payload.root_password = rest.rootPassword || 'root';
156
+ payload.port = rest.port || this.getDefaultPort(rest.dbType);
157
+ }
158
+ else if (type === 'image') {
159
+ payload.image = rest.image;
160
+ payload.port = rest.port || 80;
161
+ }
162
+ // 添加通用配置
163
+ payload.replicas = rest.replicas || 1;
164
+ payload.port = rest.port || 8080;
165
+ // 添加资源限制
166
+ if (rest.cpu || rest.memory) {
167
+ payload.resource_limits = {
168
+ cpu: rest.cpu || '500m',
169
+ memory: rest.memory || '512Mi'
170
+ };
171
+ }
172
+ // 构建网络配置
173
+ payload.network_config = {
174
+ service_type: 'ClusterIP',
175
+ ports: [{
176
+ container_port: payload.port,
177
+ protocol: 'TCP'
178
+ }]
179
+ };
180
+ // 添加域名
181
+ if (rest.domain) {
182
+ payload.network_config.ports[0].domain = {
183
+ enabled: true,
184
+ prefix: rest.domain,
185
+ host: `${rest.domain}.${namespace}.dev.aimstek.cn`
186
+ };
187
+ }
188
+ // 环境变量
189
+ if (rest.envVars) {
190
+ payload.env_vars = rest.envVars;
191
+ }
192
+ return this.request('POST', '/api/services', payload);
193
+ }
194
+ getDefaultPort(dbType) {
195
+ const ports = {
196
+ mysql: 3306,
197
+ redis: 6379,
198
+ postgres: 5432,
199
+ elasticsearch: 9200
200
+ };
201
+ return ports[dbType] || 3306;
202
+ }
203
+ // ===================
204
+ // Build
205
+ // ===================
206
+ async build(namespace, serviceName) {
207
+ // 先获取服务 ID
208
+ const serviceResult = await this.getServiceByName(namespace, serviceName);
209
+ if (!serviceResult.success || !serviceResult.data) {
210
+ return {
211
+ success: false,
212
+ error: { code: 'NOT_FOUND', message: `Service ${serviceName} not found` }
213
+ };
214
+ }
215
+ const service = serviceResult.data;
216
+ if (!service.application_id) {
217
+ return {
218
+ success: false,
219
+ error: { code: 'NO_APPLICATION', message: 'Service is not an application type' }
220
+ };
221
+ }
222
+ return this.request('POST', `/api/applications/${service.application_id}/build`);
223
+ }
224
+ async getBuildStatus(namespace, serviceName) {
225
+ const serviceResult = await this.getServiceByName(namespace, serviceName);
226
+ if (!serviceResult.success || !serviceResult.data) {
227
+ return { success: false, error: { code: 'NOT_FOUND', message: 'Service not found' } };
228
+ }
229
+ const service = serviceResult.data;
230
+ if (!service.application_id) {
231
+ return { success: false, error: { code: 'NO_APPLICATION', message: 'Not an application' } };
232
+ }
233
+ return this.request('GET', `/api/applications/${service.application_id}/builds`);
234
+ }
235
+ async getServiceByName(namespace, name) {
236
+ return this.request('GET', `/api/services?project_id=${namespace}&name=${name}`);
237
+ }
238
+ // ===================
239
+ // Scale
240
+ // ===================
241
+ async scale(namespace, serviceName, replicas) {
242
+ return this.request('POST', `/api/k8s?namespace=${namespace}&action=scale`, {
243
+ name: serviceName,
244
+ replicas
245
+ });
246
+ }
247
+ // ===================
248
+ // Logs
249
+ // ===================
250
+ async getLogs(namespace, serviceName, lines = 100, follow = false) {
251
+ return this.request('GET', `/api/k8s/logs?namespace=${namespace}&serviceName=${serviceName}&lines=${lines}&follow=${follow}`);
252
+ }
253
+ async streamLogs(namespace, serviceName) {
254
+ const url = `${this.client.defaults.baseURL}/api/k8s/logs/stream?namespace=${namespace}&serviceName=${serviceName}`;
255
+ try {
256
+ const response = await fetch(url, {
257
+ headers: {
258
+ 'Authorization': `Bearer ${this.client.defaults.headers['Authorization']}`
259
+ }
260
+ });
261
+ if (!response.ok) {
262
+ const error = await response.text();
263
+ formatter_1.OutputFormatter.error(`Failed to stream logs: ${error}`);
264
+ return;
265
+ }
266
+ const reader = response.body?.getReader();
267
+ if (!reader) {
268
+ formatter_1.OutputFormatter.error('Failed to read stream');
269
+ return;
270
+ }
271
+ const decoder = new TextDecoder();
272
+ while (true) {
273
+ const { done, value } = await reader.read();
274
+ if (done)
275
+ break;
276
+ const text = decoder.decode(value);
277
+ // 解析 SSE 格式
278
+ const lines = text.split('\n');
279
+ for (const line of lines) {
280
+ if (line.startsWith('data: ')) {
281
+ console.log(line.slice(6));
282
+ }
283
+ }
284
+ }
285
+ }
286
+ catch (error) {
287
+ formatter_1.OutputFormatter.error(`Stream error: ${error.message}`);
288
+ }
289
+ }
290
+ // ===================
291
+ // Pods
292
+ // ===================
293
+ async listPods(namespace, serviceName) {
294
+ return this.request('GET', `/api/k8s?kind=pods&namespace=${namespace}&labelSelector=app=${serviceName}`);
295
+ }
296
+ // ===================
297
+ // Metrics
298
+ // ===================
299
+ async getMetrics(namespace, serviceName, podName) {
300
+ const url = podName
301
+ ? `/api/k8s/metrics?namespace=${namespace}&podName=${podName}`
302
+ : `/api/k8s/metrics?namespace=${namespace}&serviceName=${serviceName}`;
303
+ return this.request('GET', url);
304
+ }
305
+ // ===================
306
+ // Exec
307
+ // ===================
308
+ async exec(namespace, serviceName, command, podName) {
309
+ let targetPod = podName;
310
+ // 如果没有指定 podName,自动获取第一个 pod
311
+ if (!targetPod) {
312
+ const podsResult = await this.listPods(namespace, serviceName);
313
+ if (!podsResult.success || !podsResult.data || podsResult.data.length === 0) {
314
+ return { success: false, error: { code: 'NOT_FOUND', message: 'No pods found' } };
315
+ }
316
+ const pods = Array.isArray(podsResult.data) ? podsResult.data : podsResult.data.items || [];
317
+ if (pods.length === 0) {
318
+ return { success: false, error: { code: 'NOT_FOUND', message: 'No pods found' } };
319
+ }
320
+ targetPod = pods[0].metadata?.name;
321
+ }
322
+ return this.request('POST', '/api/debug/pod-exec', {
323
+ namespace,
324
+ podName: targetPod,
325
+ command
326
+ });
327
+ }
328
+ }
329
+ exports.APIClient = APIClient;
330
+ function createClient(connection) {
331
+ return new APIClient(connection);
332
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 构建命令
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare function makeBuildCommand(): Command;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * 构建命令
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.makeBuildCommand = makeBuildCommand;
7
+ const commander_1 = require("commander");
8
+ const store_1 = require("../config/store");
9
+ const client_1 = require("../api/client");
10
+ const formatter_1 = require("../output/formatter");
11
+ function makeBuildCommand() {
12
+ const cmd = new commander_1.Command('build')
13
+ .description('Build applications');
14
+ cmd
15
+ .command('<namespace> <service-name>')
16
+ .description('Build a service')
17
+ .action(async (namespace, serviceName) => {
18
+ const conn = store_1.configStore.getDefaultConnection();
19
+ if (!conn) {
20
+ formatter_1.OutputFormatter.error('No connection configured');
21
+ return;
22
+ }
23
+ const client = (0, client_1.createClient)(conn);
24
+ formatter_1.OutputFormatter.info('Starting build...');
25
+ const result = await client.build(namespace, serviceName);
26
+ if (!result.success) {
27
+ formatter_1.OutputFormatter.error(result.error.message);
28
+ return;
29
+ }
30
+ formatter_1.OutputFormatter.success('Build started');
31
+ });
32
+ cmd
33
+ .command('status <namespace> <service-name>')
34
+ .description('Get build status')
35
+ .action(async (namespace, serviceName) => {
36
+ const conn = store_1.configStore.getDefaultConnection();
37
+ if (!conn) {
38
+ formatter_1.OutputFormatter.error('No connection configured');
39
+ return;
40
+ }
41
+ const client = (0, client_1.createClient)(conn);
42
+ const result = await client.getBuildStatus(namespace, serviceName);
43
+ if (!result.success) {
44
+ formatter_1.OutputFormatter.error(result.error.message);
45
+ return;
46
+ }
47
+ const builds = result.data || [];
48
+ if (builds.length === 0) {
49
+ formatter_1.OutputFormatter.info('No builds found');
50
+ return;
51
+ }
52
+ const latest = builds[0];
53
+ formatter_1.OutputFormatter.info(`Status: ${latest.status}`);
54
+ formatter_1.OutputFormatter.info(`Created: ${latest.created_at}`);
55
+ });
56
+ return cmd;
57
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 连接管理命令
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare function makeConnectCommand(): Command;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ /**
3
+ * 连接管理命令
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.makeConnectCommand = makeConnectCommand;
7
+ const commander_1 = require("commander");
8
+ const store_1 = require("../config/store");
9
+ const formatter_1 = require("../output/formatter");
10
+ function makeConnectCommand() {
11
+ const cmd = new commander_1.Command('connect')
12
+ .description('Manage connections to xuanwu platform');
13
+ cmd
14
+ .command('add <name>')
15
+ .description('Add a new connection')
16
+ .requiredOption('-e, --endpoint <url>', 'Platform API endpoint')
17
+ .requiredOption('-t, --token <token>', 'API token')
18
+ .action(async (name, options) => {
19
+ try {
20
+ store_1.configStore.addConnection({
21
+ name,
22
+ endpoint: options.endpoint,
23
+ token: options.token,
24
+ isDefault: store_1.configStore.getConnections().length === 0
25
+ });
26
+ formatter_1.OutputFormatter.success(`Connection "${name}" added`);
27
+ }
28
+ catch (error) {
29
+ formatter_1.OutputFormatter.error(error.message);
30
+ }
31
+ });
32
+ cmd
33
+ .command('ls')
34
+ .description('List all connections')
35
+ .action(() => {
36
+ const connections = store_1.configStore.getConnections();
37
+ if (connections.length === 0) {
38
+ formatter_1.OutputFormatter.info('No connections configured');
39
+ return;
40
+ }
41
+ formatter_1.OutputFormatter.table(['Name', 'Endpoint', 'Default'], connections.map(c => [
42
+ c.name,
43
+ c.endpoint,
44
+ c.isDefault ? '✓' : ''
45
+ ]));
46
+ });
47
+ cmd
48
+ .command('use <name>')
49
+ .description('Set default connection')
50
+ .action((name) => {
51
+ const conn = store_1.configStore.getConnection(name);
52
+ if (!conn) {
53
+ formatter_1.OutputFormatter.error(`Connection "${name}" not found`);
54
+ return;
55
+ }
56
+ store_1.configStore.setDefaultConnection(name);
57
+ formatter_1.OutputFormatter.success(`Using connection "${name}"`);
58
+ });
59
+ cmd
60
+ .command('rm <name>')
61
+ .description('Remove a connection')
62
+ .action((name) => {
63
+ store_1.configStore.removeConnection(name);
64
+ formatter_1.OutputFormatter.success(`Connection "${name}" removed`);
65
+ });
66
+ return cmd;
67
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 部署命令
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare function makeDeployCommand(): Command;