@volc-emr/emr-cli 0.1.0-beta.0 → 0.1.2

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 (47) hide show
  1. package/README.md +13 -325
  2. package/bin/emr-cli +3 -0
  3. package/dist/auth/index.d.ts +2 -0
  4. package/dist/auth/index.js +116 -0
  5. package/dist/client/index.d.ts +17 -0
  6. package/dist/client/index.js +129 -0
  7. package/dist/cluster/index.d.ts +13 -0
  8. package/dist/cluster/index.js +174 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +15 -176
  11. package/dist/utils/auth.d.ts +7 -0
  12. package/dist/utils/auth.js +18 -0
  13. package/dist/utils/client-state.d.ts +12 -0
  14. package/dist/utils/client-state.js +80 -0
  15. package/dist/utils/config.d.ts +17 -0
  16. package/dist/utils/config.js +103 -0
  17. package/dist/utils/http.d.ts +13 -0
  18. package/dist/utils/http.js +76 -0
  19. package/dist/utils/volc/emr.d.ts +30 -0
  20. package/dist/utils/volc/emr.js +50 -0
  21. package/dist/utils/volc/openapi.d.ts +19 -0
  22. package/dist/utils/volc/openapi.js +122 -0
  23. package/dist/version.d.ts +1 -0
  24. package/dist/version.js +6 -0
  25. package/package.json +13 -40
  26. package/LICENSE +0 -21
  27. package/dist/agent/agent.js +0 -19
  28. package/dist/agent/executor.js +0 -25
  29. package/dist/agent/llmPlanner.js +0 -131
  30. package/dist/agent/planner.js +0 -12
  31. package/dist/agent/types.js +0 -2
  32. package/dist/runtime/config.js +0 -73
  33. package/dist/runtime/confirm.js +0 -92
  34. package/dist/runtime/createClusterMemory.js +0 -56
  35. package/dist/runtime/llm.js +0 -64
  36. package/dist/runtime/logger.js +0 -8
  37. package/dist/runtime/memory.js +0 -4
  38. package/dist/services/emrApi.js +0 -181
  39. package/dist/services/volcApi.js +0 -53
  40. package/dist/tools/base.js +0 -2
  41. package/dist/tools/emr/createCluster.js +0 -335
  42. package/dist/tools/emr/deleteCluster.js +0 -15
  43. package/dist/tools/emr/findClustersToCleanup.js +0 -18
  44. package/dist/tools/emr/index.js +0 -15
  45. package/dist/tools/emr/listClusters.js +0 -68
  46. package/dist/tools/registry.js +0 -11
  47. package/dist/utils/prompt.js +0 -9
package/README.md CHANGED
@@ -1,336 +1,24 @@
1
- # volc-emr-agent
1
+ # @volc-emr/emr-cli
2
2
 
3
- > 一个 **可控、可审计、可 dry-run** Volcengine EMR CLI Agent。
4
- >
5
- > Tool-first · Plan → Execute · LLM Planner(可选)· 真实 OpenAPI 调用 · 本地凭证 / 偏好记忆。
3
+ EMR Command Line Interface,面向 ECS 提交机的认证初始化、客户端部署、集群信息查询与集群配置同步工具。
6
4
 
7
- 你用一句自然语言描述任务(例如"列出所有运行中的集群"、"清理一周前已关停的集群"、"帮我建一个 3.7.0 的 Hadoop 集群"),Agent 会:
8
-
9
- 1. 由 LLM 生成一份结构化 `Plan`
10
- 2. 高风险步骤前弹确认(或 `-y` 跳过)
11
- 3. 交由 Executor 依次调用 Tool
12
- 4. Tool 用 Zod 校验 LLM 输入 → 调用真实 EMR OpenAPI → 把结果写回 stdout
13
- 5. 创建集群这类复合 Tool,会**再次**交互式向你追问缺失字段并回填常用默认值
14
-
15
- ---
16
-
17
- ## 已对接的 EMR OpenAPI(Version=2023-08-15)
18
-
19
- | Tool | OpenAPI Action | 风险级别 | 说明 |
20
- |---|---|---|---|
21
- | `listClusters` | [ListClusters](https://www.volcengine.com/docs/6491/1208305) | low | 支持全字段过滤 / 分页 / 中文状态别名归一化 |
22
- | `findClustersToCleanup` | ListClusters | low | 组合查询:超过 N 天 + 处于 SHUTDOWN 类状态 |
23
- | `deleteCluster` | [ReleaseCluster](https://www.volcengine.com/docs/6491/1208304) | **high** | 自动识别归档 / 已释放 / 不存在 → 幂等跳过而非中断 |
24
- | `createCluster` | [CreateCluster](https://www.volcengine.com/docs/6491/1208307) | **high** | 交互式补全 13 类必填字段、PRE/POST 计费分支、节点组向导 |
25
-
26
- ---
27
-
28
- ## 安装
29
-
30
- ```bash
31
- # 通过 npm 全局安装(推荐)
32
- npm install -g @volc-emr/emr-cli@beta
33
-
34
- # 验证(CLI 命令名仍是 volc-emr-agent)
35
- volc-emr-agent --help
36
- ```
37
-
38
- 或者本地开发模式:
39
-
40
- ```bash
41
- git clone <repo>
42
- cd console-cli
43
- npm install
44
- npm run build
45
- npm link # 注册 `volc-emr-agent` 到 PATH
46
- ```
47
-
48
- **环境要求**:Node.js ≥ 18
49
-
50
- ---
51
-
52
- ## 快速上手(三步)
53
-
54
- ```bash
55
- # 1) 初始化:填 AK/SK、地域,可选一起填 LLM
56
- volc-emr-agent init
57
-
58
- # 2) 跑第一条命令(只打印 Plan,不执行)
59
- volc-emr-agent run "列出所有运行中的集群" --dry-run
60
-
61
- # 3) 真正执行
62
- volc-emr-agent run "列出所有运行中的集群"
63
- ```
64
-
65
- ---
66
-
67
- ## 凭证与配置
68
-
69
- 优先级统一为:**CLI 参数 > 环境变量 > 本地配置文件**。
70
-
71
- ### 本地配置文件位置
72
-
73
- | 文件 | 内容 | 权限 |
74
- |---|---|---|
75
- | `~/.volc-emr/config.json` | Volcengine AK/SK/Region + LLM endpoint/apiKey/model | `0600` |
76
- | `~/.volc-emr/create-cluster-memory.json` | 创建集群向导的常用偏好(VPC/子网/可用区/机型等) | `0600` |
77
-
78
- 可通过 `VOLC_EMR_CONFIG_DIR` 环境变量整体改位置(测试 / 多环境友好)。
79
-
80
- ### 写配置
81
-
82
- ```bash
83
- # 交互式向导(支持同时配 LLM)
84
- volc-emr-agent init
85
-
86
- # 已有配置?加 --force 跳过确认
87
- volc-emr-agent init --force
88
-
89
- # 非交互(CI 友好)
90
- volc-emr-agent init \
91
- --access-key "$VOLC_ACCESSKEY" \
92
- --secret-key "$VOLC_SECRETKEY" \
93
- --region cn-beijing
94
-
95
- # 单独写凭证
96
- volc-emr-agent config set-credentials \
97
- --access-key xxx --secret-key yyy --region cn-beijing
98
-
99
- # 单独写 LLM
100
- volc-emr-agent config set-llm \
101
- --endpoint https://ark.cn-beijing.volces.com/api/v3/chat/completions \
102
- --api-key ark-xxx \
103
- --model doubao-seed-character-251128
104
- ```
105
-
106
- ### 环境变量
107
-
108
- ```bash
109
- export VOLC_ACCESSKEY=xxx
110
- export VOLC_SECRETKEY=yyy
111
- export VOLC_REGION=cn-beijing
112
-
113
- # LLM(可选;未配置时 Planner 会直接报错,不做 rule-based 兜底)
114
- export VOLC_LLM_ENDPOINT=https://ark.cn-beijing.volces.com/api/v3/chat/completions
115
- export VOLC_LLM_API_KEY=ark-xxx
116
- export VOLC_LLM_MODEL=doubao-seed-character-251128
117
- ```
118
-
119
- ### 查看 / 重定向
120
-
121
- ```bash
122
- # 查看已保存的本地配置(secrets 自动脱敏)
123
- volc-emr-agent config show
124
-
125
- # 改用自定义目录
126
- VOLC_EMR_CONFIG_DIR=/tmp/volc-test volc-emr-agent init
127
- ```
128
-
129
- ---
130
-
131
- ## 运行示例
132
-
133
- ### 列出集群
134
-
135
- ```bash
136
- volc-emr-agent run "列出所有运行中的集群"
137
- volc-emr-agent run "列出 cn-beijing 最近创建的 10 个 Hadoop 集群"
138
- ```
139
-
140
- ### 清理老集群
5
+ ## 安装(npmjs)
141
6
 
142
7
  ```bash
143
- # Plan、不执行
144
- volc-emr-agent run "清理一周前已关停的集群" --dry-run
145
-
146
- # 批量执行(自动确认所有 high-risk 步骤)
147
- volc-emr-agent run "清理一周前已关停的集群" -y
148
-
149
- # 自定义天数
150
- volc-emr-agent run "清理 14 天前已关停的集群"
8
+ npm install -g @volc-emr/emr-cli --registry https://registry.npmjs.org/
151
9
  ```
152
10
 
153
- > 已被 EMR 归档(`marked archived`)的集群会被自动识别并**跳过**,不会让整个清理流程中断。
154
-
155
- ### 创建集群(交互式向导)
11
+ ## 使用
156
12
 
157
13
  ```bash
158
- volc-emr-agent run "帮我创建一个 3.7.0 的 Hadoop 集群"
159
- ```
160
-
161
- LLM 会先从你的描述里预填能猜到的字段(`ClusterType=Hadoop, ReleaseVersion=3.7.0`),Tool 接着启动向导:
162
-
163
- ```
164
- === 创建 EMR 集群:补全配置 ===
165
- (已加载上次记忆,路径: /Users/you/.volc-emr/create-cluster-memory.json)
166
-
167
- 集群类型 (可选: Hadoop / Presto / ...) [Hadoop]: ⏎
168
- 集群版本 (ReleaseVersion) [3.7.0]: ⏎
169
- 集群名称 [OpenApiHadoop3.7.0-a7x2m]: ⏎
170
- 付费类型 (可选: POST / PRE) [POST]: ⏎
171
- VpcId (如 vpc-xxx) [vpc-abc123]: ⏎ ← 来自上次记忆
172
- SecurityGroupId (如 sg-xxx) [sg-def456]: ⏎ ← 来自上次记忆
173
- 可用区 ZoneId [cn-beijing-b]: ⏎
174
-
175
- -- 节点组配置 (共 2 组) --
176
- [节点组 1]
177
- 节点组类型 (可选: MASTER / CORE / TASK / GATEWAY) [MASTER]: ⏎
178
- 节点数 [3]: ⏎
179
- SubnetIds (逗号分隔) [subnet-xyz]: ⏎
180
- EcsInstanceTypes [ecs.g3i.2xlarge]: ⏎
181
- ...
182
-
183
- === 即将创建集群,配置如下 ===
184
- { ...完整 JSON... }
185
- 确认使用以上配置创建集群? (y/n): y
186
-
187
- (已更新常用默认值记忆: /Users/you/.volc-emr/create-cluster-memory.json)
188
- ✓ 集群创建成功: ClusterId=emr-xxx, OperationId=op-xxx
189
- ```
190
-
191
- **特性**:
192
-
193
- - **记忆**:用户确认后立刻写 `create-cluster-memory.json`。即使 OpenAPI 随后返回错误,下次依然能复用你填过的网络/节点偏好,不用再填一遍
194
- - **中文状态 / 大小写自动归一化**:LLM 写成 "已关停" / "Running" 都能被翻译成官方枚举
195
- - **MASTER + CORE 自动补齐**:Hadoop 类集群强制至少两个节点组
196
- - **ChargeType=PRE 自动展开包年包月子向导**(周期 / 自动续费)
197
-
198
- ---
199
-
200
- ## 命令总览
201
-
202
- | 命令 | 作用 |
203
- |---|---|
204
- | `volc-emr-agent init [--force]` | 交互式写入 AK/SK、可选一起配 LLM |
205
- | `volc-emr-agent config set-credentials --access-key ... --secret-key ...` | 单独更新凭证 |
206
- | `volc-emr-agent config set-llm --endpoint ... [--api-key ...] [--model ...]` | 单独更新 LLM |
207
- | `volc-emr-agent config show` | 查看本地配置(脱敏) |
208
- | `volc-emr-agent run <task> [--dry-run] [-y] [--region ...]` | 执行一条自然语言任务 |
209
-
210
- 运行选项:
211
-
212
- - `--dry-run`:只打印 Plan,不调用任何 OpenAPI
213
- - `-y, --yes`:对 `riskLevel: "high"` 的 Tool 自动确认
214
- - `--region <region>`:临时覆盖当前会话的区域(不写配置)
215
-
216
- ---
217
-
218
- ## 目录结构
219
-
220
- ```
221
- src/
222
- ├── index.ts # CLI 入口(commander)
223
- ├── agent/
224
- │ ├── agent.ts # 对外 Agent.run(task, options)
225
- │ ├── planner.ts # LLM-only Planner,无 LLM 时直接报错
226
- │ ├── llmPlanner.ts # 把 Zod 输入翻成 JSON Schema 喂给 LLM
227
- │ ├── executor.ts # 依次执行 Plan,触发高风险确认
228
- │ └── types.ts
229
- ├── services/
230
- │ ├── volcApi.ts # @volcengine/openapi 签名 + 错误解析
231
- │ └── emrApi.ts # listClusters / releaseCluster / createCluster ...
232
- ├── tools/
233
- │ ├── base.ts # Tool 定义 (name/description/input/riskLevel/execute)
234
- │ ├── registry.ts # 聚合所有业务 Tool + API
235
- │ └── emr/
236
- │ ├── index.ts
237
- │ ├── listClusters.ts
238
- │ ├── findClustersToCleanup.ts
239
- │ ├── deleteCluster.ts # → ReleaseCluster
240
- │ └── createCluster.ts # 交互式向导 + 记忆
241
- ├── runtime/
242
- │ ├── config.ts # AK/SK + LLM 持久化
243
- │ ├── createClusterMemory.ts # 创建集群专用的偏好记忆(独立文件)
244
- │ ├── confirm.ts # TTY / pipe 双模安全的 prompt
245
- │ ├── llm.ts # OpenAI-compatible Chat Completions 客户端
246
- │ ├── memory.ts # 进程内简易 KV
247
- │ └── logger.ts
248
- └── utils/prompt.ts
249
- ```
250
-
251
- ---
252
-
253
- ## 关键设计
254
-
255
- ### 1. Tool-first
256
-
257
- 所有业务能力都是 Tool(`name / description / input (Zod) / riskLevel / execute`)。LLM 看到的只有 Tool 目录(由 `zod-to-json-schema` 自动生成),**不能直接执行副作用**。
258
-
259
- ### 2. Plan → Execute 分离
260
-
261
- ```
262
- 用户自然语言
263
-
264
- Planner(LLM, Zod 校验)→ PlanStep[]
265
-
266
- Executor(读 registry, 拦截 high-risk, 调用 Tool)
267
-
268
- 真实 Volcengine OpenAPI
14
+ emr-cli --help
15
+ emr-cli auth init
16
+ emr-cli cluster get --pretty
17
+ emr-cli cluster get --cluster-id emrxxx --pretty
269
18
  ```
270
19
 
271
- ### 3. 枚举三层防御(以 `ClusterStates` 为例)
272
-
273
- - **Schema 层**:Tool input 用 `z.nativeEnum(EmrClusterState)`,LLM 收到带 `enum` 的 JSON Schema
274
- - **Prompt 层**:Tool description 里显式印合法枚举清单
275
- - **执行层**:Tool `execute()` 内部再做一次大小写 + 中文别名兜底
276
-
277
- ### 4. OpenAPI 错误可读化
278
-
279
- [volcApi.ts](src/services/volcApi.ts) 对火山返回做结构化解析,错误消息会带 `Code / RequestId / body` 等排障必需的上下文,而不是一句干巴巴的 `Internal error`。
280
-
281
- ### 5. 记忆分文件,互不污染
282
-
283
- - `config.json`:**身份**(AK/SK、LLM token)。敏感,少变。
284
- - `create-cluster-memory.json`:**偏好**(VPC/子网/ZoneId/机型)。常变,可分享模板。
285
-
286
- 写入时机在 **确认后立刻写**(而不是 API 成功后),哪怕 OpenAPI 挂了,你下次也不用重填网络信息。
287
-
288
- ---
289
-
290
- ## LLM 配置提示
291
-
292
- Planner 要求必须配置 LLM(OpenAI-compatible `/chat/completions`)。已验证可用的:
293
-
294
- - **火山方舟 Ark**:
295
- ```
296
- endpoint: https://ark.cn-beijing.volces.com/api/v3/chat/completions
297
- model: doubao-seed-character-251128(或其他 doubao 模型)
298
- ```
299
- - **OpenAI 官方**:
300
- ```
301
- endpoint: https://api.openai.com/v1/chat/completions
302
- model: gpt-4o-mini
303
- ```
304
- - **任何兼容 OpenAI 协议的网关**(Together / Groq / Fireworks / 本地 vLLM 等)
305
-
306
- 如果模型不支持 `response_format=json_object`,Planner 会退化到 "字符串 JSON + markdown fence 清洗" 的 robust 模式,无需额外改配置。
307
-
308
- ---
309
-
310
- ## 故障排查
311
-
312
- | 现象 | 原因 / 处理 |
313
- |---|---|
314
- | `[ListClusters] The request is missing Version parameter` | `@volcengine/openapi` 需要 `Version` 而非 `version` — 现已修好 |
315
- | `[ListClusters] Invalid request {0}` | LLM 传了非法字段或类型,Tool 层错误消息会附上 body 预览 |
316
- | `[CreateCluster] Internal error` | 5xx;抓取 `RequestId` 后去[火山工单](https://console.volcengine.com/workorder/create/),或用相同参数在 [API Explorer](https://api.volcengine.com/api-explorer/?action=CreateCluster&serviceCode=emr&version=2023-08-15) 复现 |
317
- | `LLM is not configured` | Planner 没有 rule-based 兜底,请先 `volc-emr-agent config set-llm` |
318
- | 清理时个别集群报 `marked archived` | 会被 `deleteCluster` 内部捕获,返回 `{ Skipped: true, Reason: "ARCHIVED" }` 并作为该步 `Done` 结果打印,不影响其它集群清理 |
319
-
320
- 想看更详细的请求/响应?可以临时编辑 [volcApi.ts](src/services/volcApi.ts) 在 `fetchApi(body)` 前后加 `console.error` 打印 body 和 response。
321
-
322
- ---
323
-
324
- ## 后续规划
325
-
326
- - [ ] 支持 `CreateCluster` 的 `--config file.json` 无交互模式(CI 友好)
327
- - [ ] 更多 EMR Action:`GetCluster` / `ScaleOutNodeGroup` / `UpdateClusterAttribute`
328
- - [ ] 多 profile 记忆(`--profile prod/test`)
329
- - [ ] Tool catalog 热插拔、跨业务目录(非 EMR)
330
- - [ ] Executor 级别的 retry / rollback / 流式输出
331
-
332
- ---
333
-
334
- ## License
20
+ ## 说明
335
21
 
336
- [MIT](./LICENSE)
22
+ - `cluster get` 调用 Volc EMR OpenAPI `GetCluster`
23
+ - `--pretty` 会格式化输出集群信息,便于阅读
24
+ - `--debug` 会输出 `RequestId` 与签名用的 `CanonicalRequest`,便于排查鉴权/权限问题
package/bin/emr-cli ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../dist/index');
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerAuthCommands(program: Command): void;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerAuthCommands = registerAuthCommands;
4
+ const config_1 = require("../utils/config");
5
+ // Local typings for `inquirer` can differ across package manager layouts; use
6
+ // a narrow runtime import here to keep the CLI compile-safe in this workspace.
7
+ const inquirer = require('inquirer');
8
+ async function promptAuthConfig(force = false) {
9
+ const current = (0, config_1.tryLoadConfig)();
10
+ if (!force &&
11
+ (current.ak || current.sk || current.region || current.clusterId)) {
12
+ const { confirmed } = await inquirer.prompt([
13
+ {
14
+ type: 'confirm',
15
+ name: 'confirmed',
16
+ message: '检测到已有认证配置,是否覆盖?',
17
+ default: false,
18
+ },
19
+ ]);
20
+ if (!confirmed) {
21
+ console.info('已取消覆盖,保留现有配置。');
22
+ return undefined;
23
+ }
24
+ }
25
+ return inquirer.prompt([
26
+ {
27
+ type: 'input',
28
+ name: 'ak',
29
+ message: '请输入 AccessKey (AK):',
30
+ default: current.ak,
31
+ validate: (input) => input?.trim() ? true : 'AccessKey 不能为空',
32
+ },
33
+ {
34
+ type: 'password',
35
+ name: 'sk',
36
+ message: '请输入 SecretKey (SK):',
37
+ default: current.sk,
38
+ mask: '*',
39
+ validate: (input) => input?.trim() ? true : 'SecretKey 不能为空',
40
+ },
41
+ {
42
+ type: 'input',
43
+ name: 'region',
44
+ message: '请输入默认 Region:',
45
+ default: current.region || 'cn-beijing',
46
+ validate: (input) => (input?.trim() ? true : 'Region 不能为空'),
47
+ },
48
+ {
49
+ type: 'input',
50
+ name: 'clusterId',
51
+ message: '请输入默认 ClusterId [选填]:',
52
+ default: current.clusterId,
53
+ },
54
+ ]);
55
+ }
56
+ function registerAuthCommands(program) {
57
+ const authCmd = program.command('auth').description('认证与配置管理');
58
+ authCmd
59
+ .command('init')
60
+ .description('初始化认证配置')
61
+ .option('--ak <ak>', 'AccessKey')
62
+ .option('--sk <sk>', 'SecretKey')
63
+ .option('--region <region>', '默认 Region')
64
+ .option('--cluster-id <clusterId>', '默认 ClusterId')
65
+ .option('--force', '强制覆盖已有配置')
66
+ .action(async (options) => {
67
+ let payload = {
68
+ ak: options.ak,
69
+ sk: options.sk,
70
+ region: options.region,
71
+ clusterId: options.clusterId,
72
+ };
73
+ if (!payload.ak || !payload.sk || !payload.region) {
74
+ const answers = await promptAuthConfig(Boolean(options.force));
75
+ if (!answers)
76
+ return;
77
+ payload = {
78
+ ak: answers.ak?.trim(),
79
+ sk: answers.sk?.trim(),
80
+ region: answers.region?.trim(),
81
+ clusterId: answers.clusterId?.trim() || undefined,
82
+ };
83
+ }
84
+ (0, config_1.updateConfig)(payload);
85
+ console.info('✓ 认证配置已保存');
86
+ console.info(`配置文件位置: ${(0, config_1.getConfigPath)()}`);
87
+ });
88
+ authCmd
89
+ .command('show')
90
+ .description('查看当前认证配置')
91
+ .action(() => {
92
+ const config = (0, config_1.tryLoadConfig)();
93
+ console.info(`配置文件位置: ${(0, config_1.getConfigPath)()}`);
94
+ console.info(`AK: ${(0, config_1.maskSecret)(config.ak)}`);
95
+ console.info(`SK: ${(0, config_1.maskSecret)(config.sk)}`);
96
+ console.info(`Region: ${config.region || '未设置'}`);
97
+ console.info(`ClusterId: ${config.clusterId || '未设置'}`);
98
+ });
99
+ authCmd
100
+ .command('clear')
101
+ .description('清空本地认证配置')
102
+ .action(() => {
103
+ (0, config_1.clearConfig)();
104
+ console.info('✓ 本地认证配置已清空');
105
+ });
106
+ // Shortcut for first-time users
107
+ program
108
+ .command('init')
109
+ .description('初始化 EMR-CLI 认证配置')
110
+ .action(async () => {
111
+ await program.parseAsync(['node', 'emr-cli', 'auth', 'init'], {
112
+ from: 'user',
113
+ });
114
+ });
115
+ }
116
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ import type { Command } from 'commander';
2
+ export interface InstallClientOptions {
3
+ dir?: string;
4
+ force?: boolean;
5
+ version?: string;
6
+ packageUrl?: string;
7
+ }
8
+ export declare function installClient(options: InstallClientOptions): {
9
+ dir: string;
10
+ version: string;
11
+ };
12
+ export declare function updateClient(options: InstallClientOptions): {
13
+ dir: string;
14
+ version: string;
15
+ updated: boolean;
16
+ };
17
+ export declare function registerClientCommands(program: Command): void;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.installClient = installClient;
40
+ exports.updateClient = updateClient;
41
+ exports.registerClientCommands = registerClientCommands;
42
+ const fs = __importStar(require("node:fs"));
43
+ const path = __importStar(require("node:path"));
44
+ const ora_1 = __importDefault(require("ora"));
45
+ const client_state_1 = require("../utils/client-state");
46
+ function installClient(options) {
47
+ const clientDir = (0, client_state_1.ensureClientDir)(options.dir);
48
+ const current = (0, client_state_1.readClientState)(clientDir);
49
+ if (current && !options.force) {
50
+ throw new Error('客户端已安装。如需覆盖安装,请使用 --force 参数。');
51
+ }
52
+ const version = options.version || 'latest';
53
+ const binDir = path.join(clientDir, 'bin');
54
+ const confDir = path.join(clientDir, 'conf');
55
+ fs.mkdirSync(binDir, { recursive: true });
56
+ fs.mkdirSync(confDir, { recursive: true });
57
+ fs.writeFileSync(path.join(binDir, 'emr-client.sh'), '#!/usr/bin/env bash\necho "EMR Client Ready"\n');
58
+ fs.writeFileSync(path.join(confDir, 'README.txt'), 'EMR 客户端配置目录\n');
59
+ (0, client_state_1.writeClientState)({
60
+ version,
61
+ installedAt: new Date().toISOString(),
62
+ packageUrl: options.packageUrl,
63
+ }, clientDir);
64
+ return { dir: clientDir, version };
65
+ }
66
+ function updateClient(options) {
67
+ const clientDir = (0, client_state_1.resolveClientDir)(options.dir);
68
+ const current = (0, client_state_1.readClientState)(clientDir);
69
+ if (!current) {
70
+ throw new Error('尚未安装客户端,请先执行 emr-cli client install');
71
+ }
72
+ const targetVersion = options.version || current.version;
73
+ if (current.version === targetVersion && !options.force) {
74
+ return { dir: clientDir, version: current.version, updated: false };
75
+ }
76
+ const result = installClient({
77
+ ...options,
78
+ dir: clientDir,
79
+ force: true,
80
+ version: targetVersion,
81
+ });
82
+ return { ...result, updated: true };
83
+ }
84
+ function registerClientCommands(program) {
85
+ const clientCmd = program.command('client').description('客户端安装与更新');
86
+ clientCmd
87
+ .command('install')
88
+ .description('安装客户端软件包并初始化目录')
89
+ .option('--dir <dir>', '客户端安装目录')
90
+ .option('--version <version>', '安装版本', 'latest')
91
+ .option('--package-url <url>', '安装包地址(记录到元数据中)')
92
+ .option('--force', '覆盖已有安装')
93
+ .action(async (options) => {
94
+ const spinner = (0, ora_1.default)('开始安装 EMR 客户端...').start();
95
+ try {
96
+ const result = installClient(options);
97
+ spinner.succeed(`客户端安装完成: ${result.dir}`);
98
+ console.info(`当前版本: ${result.version}`);
99
+ }
100
+ catch (error) {
101
+ spinner.fail('客户端安装失败');
102
+ throw error;
103
+ }
104
+ });
105
+ clientCmd
106
+ .command('update')
107
+ .description('更新客户端软件包')
108
+ .option('--dir <dir>', '客户端安装目录')
109
+ .option('--version <version>', '目标版本')
110
+ .option('--package-url <url>', '安装包地址(记录到元数据中)')
111
+ .option('--force', '即使版本一致也强制覆盖')
112
+ .action(async (options) => {
113
+ const spinner = (0, ora_1.default)('开始更新 EMR 客户端...').start();
114
+ try {
115
+ const result = updateClient(options);
116
+ if (!result.updated) {
117
+ spinner.succeed('当前客户端已是目标版本,无需更新');
118
+ return;
119
+ }
120
+ spinner.succeed(`客户端更新完成: ${result.dir}`);
121
+ console.info(`当前版本: ${result.version}`);
122
+ }
123
+ catch (error) {
124
+ spinner.fail('客户端更新失败');
125
+ throw error;
126
+ }
127
+ });
128
+ }
129
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ import type { Command } from 'commander';
2
+ export interface SyncClusterOptions {
3
+ clusterId?: string;
4
+ dir?: string;
5
+ debug?: boolean;
6
+ }
7
+ export declare function syncClusterConfig(options: SyncClusterOptions): Promise<{
8
+ clusterId: string;
9
+ outputDir: string;
10
+ requestId?: string;
11
+ canonicalRequest?: string;
12
+ }>;
13
+ export declare function registerClusterCommands(program: Command): void;