@volc-emr/emr-cli 0.1.0-beta.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.
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getEmrService = getEmrService;
4
+ exports.callEmr = callEmr;
5
+ const openapi_1 = require("@volcengine/openapi");
6
+ const config_1 = require("../runtime/config");
7
+ const EMR_API_VERSION = "2023-08-15";
8
+ let cachedService = null;
9
+ let cachedKey = null;
10
+ function buildService(creds) {
11
+ const service = new openapi_1.Service({
12
+ host: `emr.${creds.region}.volcengineapi.com`,
13
+ serviceName: "emr",
14
+ region: creds.region,
15
+ accessKeyId: creds.accessKey,
16
+ secretKey: creds.secretKey,
17
+ defaultVersion: EMR_API_VERSION
18
+ });
19
+ return service;
20
+ }
21
+ function getEmrService(cli = {}) {
22
+ const creds = (0, config_1.resolveCredentials)(cli);
23
+ const key = `${creds.region}:${creds.accessKey}`;
24
+ if (cachedService && cachedKey === key)
25
+ return cachedService;
26
+ cachedService = buildService(creds);
27
+ cachedKey = key;
28
+ return cachedService;
29
+ }
30
+ async function callEmr(action, body = {}, options = {}) {
31
+ const service = getEmrService();
32
+ const fetchApi = service.createJSONAPI(action, {
33
+ method: "POST",
34
+ Version: options.version || EMR_API_VERSION,
35
+ contentType: "json"
36
+ });
37
+ const response = await fetchApi(body);
38
+ if (response?.ResponseMetadata?.Error) {
39
+ const err = response.ResponseMetadata.Error;
40
+ let message = err?.Message || err?.CodeN || err?.Code || "Volcengine EMR error";
41
+ if (typeof message === "string" && /\{\d+\}/.test(message)) {
42
+ try {
43
+ const bodyPreview = JSON.stringify(body);
44
+ message = `${message} (body=${bodyPreview})`;
45
+ }
46
+ catch {
47
+ /* ignore */
48
+ }
49
+ }
50
+ throw new Error(`[${action}] ${message}`);
51
+ }
52
+ return response?.Result;
53
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCluster = void 0;
4
+ const zod_1 = require("zod");
5
+ const confirm_1 = require("../../runtime/confirm");
6
+ const createClusterMemory_1 = require("../../runtime/createClusterMemory");
7
+ const CLUSTER_TYPES = [
8
+ "Hadoop",
9
+ "Presto",
10
+ "Trino",
11
+ "Stream-Kafka",
12
+ "Stream-Flink",
13
+ "HBase",
14
+ "OpenSearch",
15
+ "StarRocks",
16
+ "TensorFlow",
17
+ "Doris",
18
+ "Pulsar",
19
+ "ClickHouse",
20
+ "ZooKeeper"
21
+ ];
22
+ const CHARGE_TYPES = ["POST", "PRE"];
23
+ const DEPLOY_MODES = ["SIMPLE", "HIGH_AVAILABLE"];
24
+ const SECURITY_MODES = ["SIMPLE", "KERBEROS"];
25
+ const HISTORY_SERVER_MODES = ["LOCAL", "PHS"];
26
+ const NODE_GROUP_TYPES = ["MASTER", "CORE", "TASK", "GATEWAY"];
27
+ const SystemDiskSchema = zod_1.z.object({
28
+ VolumeType: zod_1.z.string().default("ESSD_FlexPL"),
29
+ Size: zod_1.z.number().int().positive().default(80)
30
+ });
31
+ const DataDiskSchema = zod_1.z.object({
32
+ VolumeType: zod_1.z.string(),
33
+ Size: zod_1.z.number().int().positive(),
34
+ Count: zod_1.z.number().int().positive()
35
+ });
36
+ const NodeGroupSchema = zod_1.z.object({
37
+ NodeGroupType: zod_1.z.enum(NODE_GROUP_TYPES),
38
+ NodeGroupName: zod_1.z.string().optional(),
39
+ NodeCount: zod_1.z.number().int().positive(),
40
+ ZoneId: zod_1.z.string().optional(),
41
+ SubnetIds: zod_1.z.array(zod_1.z.string()).optional(),
42
+ EcsInstanceTypes: zod_1.z.array(zod_1.z.string()).optional(),
43
+ SystemDisk: SystemDiskSchema.optional(),
44
+ DataDisks: zod_1.z.array(DataDiskSchema).optional(),
45
+ EcsKeyPairName: zod_1.z.string().optional(),
46
+ EcsPassword: zod_1.z.string().optional(),
47
+ Bandwidth: zod_1.z.number().int().nonnegative().optional(),
48
+ ChargeType: zod_1.z.enum(CHARGE_TYPES).optional(),
49
+ WithPublicIp: zod_1.z.boolean().optional()
50
+ });
51
+ const ChargePreConfigSchema = zod_1.z.object({
52
+ ChargePeriodUnit: zod_1.z.enum(["Month", "Year"]).default("Month"),
53
+ ChargePeriod: zod_1.z.number().int().positive().default(1),
54
+ AutoRenew: zod_1.z.boolean().default(false),
55
+ AutoRenewPeriodUnit: zod_1.z.enum(["Month", "Year"]).optional(),
56
+ AutoRenewPeriod: zod_1.z.number().int().positive().optional()
57
+ });
58
+ function randomSuffix() {
59
+ return Math.random().toString(36).slice(2, 7);
60
+ }
61
+ function isMissing(v) {
62
+ if (v === undefined || v === null)
63
+ return true;
64
+ if (typeof v === "string" && !v.trim())
65
+ return true;
66
+ if (Array.isArray(v) && v.length === 0)
67
+ return true;
68
+ return false;
69
+ }
70
+ function parseList(input) {
71
+ return input
72
+ .split(/[,,\s]+/)
73
+ .map((s) => s.trim())
74
+ .filter(Boolean);
75
+ }
76
+ function parseBool(input, defaultValue = false) {
77
+ const v = input.trim().toLowerCase();
78
+ if (!v)
79
+ return defaultValue;
80
+ return ["y", "yes", "true", "1", "是"].includes(v);
81
+ }
82
+ function parseInt10(input, defaultValue) {
83
+ const n = parseInt(input, 10);
84
+ if (Number.isFinite(n))
85
+ return n;
86
+ return defaultValue;
87
+ }
88
+ async function askEnum(prompt, question, enumValues, current, defaultValue) {
89
+ if (current && enumValues.includes(current))
90
+ return current;
91
+ const def = defaultValue && enumValues.includes(defaultValue) ? defaultValue : undefined;
92
+ const label = `${question} (可选: ${enumValues.join(" / ")})`;
93
+ while (true) {
94
+ const answer = (await prompt.ask(label, { defaultValue: def }));
95
+ if (enumValues.includes(answer))
96
+ return answer;
97
+ process.stdout.write(` 非法取值,请从 ${enumValues.join(" / ")} 中选择\n`);
98
+ }
99
+ }
100
+ async function askRequired(prompt, question, current, defaultValue) {
101
+ if (current && current.trim())
102
+ return current;
103
+ while (true) {
104
+ const v = await prompt.ask(question, { defaultValue });
105
+ if (v && v.trim())
106
+ return v.trim();
107
+ process.stdout.write(" 该字段必填,请输入\n");
108
+ }
109
+ }
110
+ async function completeNodeGroup(prompt, ng, defaults) {
111
+ const mem = defaults.memory || {};
112
+ const type = await askEnum(prompt, `节点组类型 (当前: ${ng.NodeGroupType || "未设置"})`, NODE_GROUP_TYPES, ng.NodeGroupType);
113
+ const name = ng.NodeGroupName ||
114
+ `OpenApi-${type[0] + type.slice(1).toLowerCase()}Group-${randomSuffix()}`;
115
+ const count = ng.NodeCount ??
116
+ parseInt10(await prompt.ask(` 节点数`, {
117
+ defaultValue: type === "MASTER" ? "3" : type === "CORE" ? "3" : "1"
118
+ }), type === "MASTER" ? 3 : type === "CORE" ? 3 : 1);
119
+ let subnets = ng.SubnetIds;
120
+ if (isMissing(subnets)) {
121
+ const memDefault = mem.SubnetIds && mem.SubnetIds.length ? mem.SubnetIds.join(",") : undefined;
122
+ const raw = memDefault
123
+ ? await prompt.ask(` SubnetIds (逗号分隔, 至少 1 个)`, {
124
+ defaultValue: memDefault
125
+ })
126
+ : await askRequired(prompt, ` SubnetIds (逗号分隔, 至少 1 个)`);
127
+ subnets = parseList(raw);
128
+ if (!subnets.length) {
129
+ subnets = await askRequired(prompt, ` SubnetIds 不能为空,请重新输入`).then(parseList);
130
+ }
131
+ }
132
+ let instanceTypes = ng.EcsInstanceTypes;
133
+ if (isMissing(instanceTypes)) {
134
+ const memDefault = mem.EcsInstanceTypes && mem.EcsInstanceTypes.length
135
+ ? mem.EcsInstanceTypes.join(",")
136
+ : "ecs.g3i.2xlarge";
137
+ const raw = await prompt.ask(` EcsInstanceTypes (逗号分隔)`, {
138
+ defaultValue: memDefault
139
+ });
140
+ instanceTypes = parseList(raw);
141
+ if (!instanceTypes.length)
142
+ instanceTypes = ["ecs.g3i.2xlarge"];
143
+ }
144
+ const system = ng.SystemDisk || { VolumeType: "ESSD_FlexPL", Size: 80 };
145
+ const keyPair = ng.EcsKeyPairName ||
146
+ (await prompt.ask(` EcsKeyPairName (可选, 回车跳过)`, {
147
+ defaultValue: mem.EcsKeyPairName
148
+ })) ||
149
+ undefined;
150
+ return {
151
+ NodeGroupType: type,
152
+ NodeGroupName: name,
153
+ NodeCount: count,
154
+ ZoneId: ng.ZoneId || defaults.ZoneId,
155
+ SubnetIds: subnets,
156
+ EcsInstanceTypes: instanceTypes,
157
+ SystemDisk: system,
158
+ DataDisks: ng.DataDisks,
159
+ EcsKeyPairName: keyPair,
160
+ EcsPassword: ng.EcsPassword,
161
+ Bandwidth: ng.Bandwidth,
162
+ ChargeType: ng.ChargeType || defaults.ChargeType,
163
+ WithPublicIp: ng.WithPublicIp ?? false
164
+ };
165
+ }
166
+ exports.createCluster = {
167
+ name: "createCluster",
168
+ description: "Create a Volcengine EMR cluster (CreateCluster). The tool will interactively ask the user for any missing required fields, " +
169
+ "then show a summary and ask for final confirmation before calling the OpenAPI. " +
170
+ "LLM should pre-fill as many fields as it can infer from the user's natural language description (e.g. cluster type, version, node count, charge type), " +
171
+ "and leave unknown fields as undefined — the tool itself will prompt the user.",
172
+ riskLevel: "high",
173
+ input: zod_1.z.object({
174
+ ProjectName: zod_1.z.string().optional(),
175
+ ClusterName: zod_1.z.string().optional(),
176
+ ClusterType: zod_1.z.enum(CLUSTER_TYPES).optional(),
177
+ ReleaseVersion: zod_1.z.string().optional(),
178
+ DeployMode: zod_1.z.enum(DEPLOY_MODES).optional(),
179
+ SecurityMode: zod_1.z.enum(SECURITY_MODES).optional(),
180
+ HistoryServerMode: zod_1.z.enum(HISTORY_SERVER_MODES).optional(),
181
+ ChargeType: zod_1.z.enum(CHARGE_TYPES).optional(),
182
+ ChargePreConfig: ChargePreConfigSchema.partial().optional(),
183
+ VpcId: zod_1.z.string().optional(),
184
+ SecurityGroupId: zod_1.z.string().optional(),
185
+ ZoneId: zod_1.z.string().optional(),
186
+ EcsIamRole: zod_1.z.string().optional(),
187
+ NodeGroupAttributes: zod_1.z.array(NodeGroupSchema.partial()).optional(),
188
+ ApplicationNames: zod_1.z.array(zod_1.z.string()).optional()
189
+ }),
190
+ async execute(input, ctx) {
191
+ const prompt = (0, confirm_1.createPromptSession)();
192
+ const memory = (0, createClusterMemory_1.readCreateClusterMemory)();
193
+ const pick = (inputVal, memVal, fallback) => {
194
+ if (inputVal !== undefined && inputVal !== null && inputVal !== "")
195
+ return inputVal;
196
+ if (memVal !== undefined && memVal !== null && memVal !== "")
197
+ return memVal;
198
+ return fallback;
199
+ };
200
+ try {
201
+ process.stdout.write("\n=== 创建 EMR 集群:补全配置 ===\n");
202
+ if (memory.updatedAt) {
203
+ process.stdout.write(`(已加载上次记忆,路径: ${(0, createClusterMemory_1.createClusterMemoryPath)()})\n`);
204
+ }
205
+ const ClusterType = await askEnum(prompt, "集群类型", CLUSTER_TYPES, input.ClusterType, memory.ClusterType || "Hadoop");
206
+ const ReleaseVersion = await askRequired(prompt, "集群版本 (ReleaseVersion, 如 3.7.0)", input.ReleaseVersion, memory.ReleaseVersion || "3.7.0");
207
+ const ClusterName = await askRequired(prompt, "集群名称", input.ClusterName, `OpenApi${ClusterType}${ReleaseVersion}-${randomSuffix()}`);
208
+ const ChargeType = await askEnum(prompt, "付费类型", CHARGE_TYPES, input.ChargeType, memory.ChargeType || "POST");
209
+ let ChargePreConfig;
210
+ if (ChargeType === "PRE") {
211
+ const pre = input.ChargePreConfig || {};
212
+ const unit = await askEnum(prompt, " 包年包月计费单位", ["Month", "Year"], pre.ChargePeriodUnit, "Month");
213
+ const period = parseInt10(await prompt.ask(" 包年包月周期数", {
214
+ defaultValue: String(pre.ChargePeriod ?? 1)
215
+ }), pre.ChargePeriod ?? 1);
216
+ const autoRenew = parseBool(await prompt.ask(" 是否自动续费 (y/n)", {
217
+ defaultValue: pre.AutoRenew ? "y" : "n"
218
+ }), !!pre.AutoRenew);
219
+ ChargePreConfig = {
220
+ ChargeType: "PRE",
221
+ ChargePeriodUnit: unit,
222
+ ChargePeriod: period,
223
+ AutoRenew: autoRenew,
224
+ AutoRenewPeriodUnit: autoRenew ? unit : undefined,
225
+ AutoRenewPeriod: autoRenew ? period : undefined
226
+ };
227
+ }
228
+ const DeployMode = await askEnum(prompt, "部署模式", DEPLOY_MODES, input.DeployMode, memory.DeployMode || "HIGH_AVAILABLE");
229
+ const SecurityMode = await askEnum(prompt, "安全模式", SECURITY_MODES, input.SecurityMode, memory.SecurityMode || "SIMPLE");
230
+ const VpcId = await askRequired(prompt, "VpcId (如 vpc-xxx)", input.VpcId, memory.VpcId);
231
+ const SecurityGroupId = await askRequired(prompt, "SecurityGroupId (如 sg-xxx)", input.SecurityGroupId, memory.SecurityGroupId);
232
+ const ZoneId = await askRequired(prompt, "可用区 ZoneId (如 cn-beijing-b)", input.ZoneId, memory.ZoneId || "cn-beijing-b");
233
+ const EcsIamRole = input.EcsIamRole ||
234
+ (await prompt.ask("EcsIamRole (可选, 回车跳过)", {
235
+ defaultValue: memory.EcsIamRole
236
+ })) ||
237
+ undefined;
238
+ let nodeGroupsInput = input.NodeGroupAttributes || [];
239
+ const hasMaster = nodeGroupsInput.some((g) => g?.NodeGroupType === "MASTER");
240
+ const hasCore = nodeGroupsInput.some((g) => g?.NodeGroupType === "CORE");
241
+ if (!hasMaster)
242
+ nodeGroupsInput = [...nodeGroupsInput, { NodeGroupType: "MASTER" }];
243
+ if (!hasCore)
244
+ nodeGroupsInput = [...nodeGroupsInput, { NodeGroupType: "CORE" }];
245
+ process.stdout.write(`\n-- 节点组配置 (共 ${nodeGroupsInput.length} 组) --\n`);
246
+ const NodeGroupAttributes = [];
247
+ for (const [i, ng] of nodeGroupsInput.entries()) {
248
+ process.stdout.write(`\n[节点组 ${i + 1}]\n`);
249
+ NodeGroupAttributes.push(await completeNodeGroup(prompt, ng, {
250
+ ZoneId,
251
+ ChargeType,
252
+ memory
253
+ }));
254
+ }
255
+ let ApplicationNames = input.ApplicationNames;
256
+ if (isMissing(ApplicationNames)) {
257
+ const raw = await prompt.ask("\nApplicationNames (逗号分隔, 回车跳过用默认组件)", {
258
+ defaultValue: memory.ApplicationNames && memory.ApplicationNames.length
259
+ ? memory.ApplicationNames.join(",")
260
+ : undefined
261
+ });
262
+ ApplicationNames = parseList(raw);
263
+ if (!ApplicationNames.length)
264
+ ApplicationNames = undefined;
265
+ }
266
+ const ProjectName = pick(input.ProjectName, memory.ProjectName, "default") || "default";
267
+ const params = {
268
+ ProjectName,
269
+ ClusterName,
270
+ ClusterType,
271
+ ReleaseVersion,
272
+ DeployMode,
273
+ SecurityMode,
274
+ HistoryServerMode: input.HistoryServerMode || "LOCAL",
275
+ ChargeType,
276
+ ChargePreConfig,
277
+ VpcId,
278
+ SecurityGroupId,
279
+ NodeAttribute: {
280
+ ZoneId,
281
+ EcsIamRole
282
+ },
283
+ NodeGroupAttributes,
284
+ ApplicationNames
285
+ };
286
+ process.stdout.write("\n=== 即将创建集群,配置如下 ===\n");
287
+ process.stdout.write(JSON.stringify(params, null, 2) + "\n\n");
288
+ const ok = await (0, confirm_1.confirm)("确认使用以上配置创建集群?");
289
+ if (!ok) {
290
+ return { Skipped: true, Reason: "USER_CANCELLED", Params: params };
291
+ }
292
+ const persistMemory = () => {
293
+ try {
294
+ const firstNg = NodeGroupAttributes[0];
295
+ (0, createClusterMemory_1.writeCreateClusterMemory)({
296
+ ProjectName,
297
+ ClusterType,
298
+ ReleaseVersion,
299
+ ChargeType,
300
+ DeployMode,
301
+ SecurityMode,
302
+ VpcId,
303
+ SecurityGroupId,
304
+ ZoneId,
305
+ EcsIamRole,
306
+ SubnetIds: firstNg?.SubnetIds,
307
+ EcsInstanceTypes: firstNg?.EcsInstanceTypes,
308
+ EcsKeyPairName: firstNg?.EcsKeyPairName,
309
+ ApplicationNames
310
+ });
311
+ process.stdout.write(`(已更新常用默认值记忆: ${(0, createClusterMemory_1.createClusterMemoryPath)()})\n`);
312
+ }
313
+ catch (e) {
314
+ process.stdout.write(`(警告: 写入记忆失败: ${e?.message || e})\n`);
315
+ }
316
+ };
317
+ persistMemory();
318
+ try {
319
+ const result = await ctx.api.emr.createCluster(params);
320
+ process.stdout.write(`\n✓ 集群创建成功: ClusterId=${result.ClusterId}` +
321
+ (result.OperationId ? `, OperationId=${result.OperationId}` : "") +
322
+ "\n");
323
+ return result;
324
+ }
325
+ catch (err) {
326
+ process.stdout.write(`\n✗ 集群创建失败: ${err?.message || err}\n` +
327
+ `(已保存本次填写的默认值到记忆,下次可直接复用)\n`);
328
+ throw err;
329
+ }
330
+ }
331
+ finally {
332
+ prompt.close();
333
+ }
334
+ }
335
+ };
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteCluster = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.deleteCluster = {
6
+ name: "deleteCluster",
7
+ description: "Release (delete) an EMR cluster via Volcengine OpenAPI (ReleaseCluster)",
8
+ riskLevel: "high",
9
+ input: zod_1.z.object({
10
+ ClusterId: zod_1.z.string().min(1)
11
+ }),
12
+ async execute(input, ctx) {
13
+ return ctx.api.emr.releaseCluster({ ClusterId: input.ClusterId });
14
+ }
15
+ };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findClustersToCleanup = void 0;
4
+ const zod_1 = require("zod");
5
+ const emrApi_1 = require("../../services/emrApi");
6
+ exports.findClustersToCleanup = {
7
+ name: "findClustersToCleanup",
8
+ description: "Find EMR clusters that are in shutdown-like states and older than N days (default 7). " +
9
+ "`states` MUST use the official uppercase enum: " +
10
+ emrApi_1.CLUSTER_STATES.join(", "),
11
+ input: zod_1.z.object({
12
+ olderThanDays: zod_1.z.number().int().positive().optional(),
13
+ states: zod_1.z.array(zod_1.z.nativeEnum(emrApi_1.EmrClusterState)).optional()
14
+ }),
15
+ async execute(input, ctx) {
16
+ return ctx.api.emr.findClustersToCleanup(input || {});
17
+ }
18
+ };
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.emrApi = exports.emrTools = void 0;
4
+ const listClusters_1 = require("./listClusters");
5
+ const deleteCluster_1 = require("./deleteCluster");
6
+ const findClustersToCleanup_1 = require("./findClustersToCleanup");
7
+ const createCluster_1 = require("./createCluster");
8
+ const emrApi_1 = require("../../services/emrApi");
9
+ Object.defineProperty(exports, "emrApi", { enumerable: true, get: function () { return emrApi_1.emrApi; } });
10
+ exports.emrTools = {
11
+ listClusters: listClusters_1.listClusters,
12
+ deleteCluster: deleteCluster_1.deleteCluster,
13
+ findClustersToCleanup: findClustersToCleanup_1.findClustersToCleanup,
14
+ createCluster: createCluster_1.createCluster
15
+ };
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listClusters = void 0;
4
+ const zod_1 = require("zod");
5
+ const emrApi_1 = require("../../services/emrApi");
6
+ const STATE_ALIASES = {
7
+ "已关停": emrApi_1.EmrClusterState.SHUTDOWN,
8
+ "关停": emrApi_1.EmrClusterState.SHUTDOWN,
9
+ "运行中": emrApi_1.EmrClusterState.RUNNING,
10
+ "已终止": emrApi_1.EmrClusterState.TERMINATED,
11
+ "已创建": emrApi_1.EmrClusterState.RUNNING,
12
+ "创建中": emrApi_1.EmrClusterState.CREATING,
13
+ "失败": emrApi_1.EmrClusterState.FAILED,
14
+ "异常": emrApi_1.EmrClusterState.EXCEPTION,
15
+ "已停止": emrApi_1.EmrClusterState.PAUSED,
16
+ "停止中": emrApi_1.EmrClusterState.PAUSING
17
+ };
18
+ function normalizeOneState(raw) {
19
+ if (!raw)
20
+ return undefined;
21
+ const upper = raw.toUpperCase();
22
+ if (emrApi_1.CLUSTER_STATES.includes(upper)) {
23
+ return upper;
24
+ }
25
+ return STATE_ALIASES[raw];
26
+ }
27
+ function normalizeStates(states) {
28
+ if (!states || !states.length)
29
+ return undefined;
30
+ const out = [];
31
+ for (const s of states) {
32
+ const v = normalizeOneState(String(s));
33
+ if (v)
34
+ out.push(v);
35
+ else
36
+ throw new Error(`Invalid ClusterState \`${s}\`. Allowed: ${emrApi_1.CLUSTER_STATES.join(", ")}`);
37
+ }
38
+ return out;
39
+ }
40
+ const TagSchema = zod_1.z.object({ Key: zod_1.z.string(), Value: zod_1.z.string() });
41
+ const ClusterStateEnum = zod_1.z.nativeEnum(emrApi_1.EmrClusterState);
42
+ exports.listClusters = {
43
+ name: "listClusters",
44
+ description: "List EMR clusters via Volcengine OpenAPI (ListClusters). ClusterStates MUST use the official uppercase enum: " +
45
+ emrApi_1.CLUSTER_STATES.join(", "),
46
+ input: zod_1.z.object({
47
+ ClusterName: zod_1.z.string().optional(),
48
+ ClusterId: zod_1.z.string().optional(),
49
+ ReleaseVersion: zod_1.z.string().optional(),
50
+ ProjectName: zod_1.z.string().optional(),
51
+ CreateTimeBefore: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).optional(),
52
+ CreateTimeAfter: zod_1.z.union([zod_1.z.number(), zod_1.z.string()]).optional(),
53
+ ClusterIds: zod_1.z.array(zod_1.z.string()).optional(),
54
+ ClusterTypes: zod_1.z.array(zod_1.z.string()).optional(),
55
+ ClusterStates: zod_1.z.array(ClusterStateEnum).optional(),
56
+ ChargeTypes: zod_1.z.array(zod_1.z.enum(["PRE", "POST"])).optional(),
57
+ Tags: zod_1.z.array(TagSchema).optional(),
58
+ MaxResults: zod_1.z.number().int().positive().max(100).optional(),
59
+ NextToken: zod_1.z.string().optional()
60
+ }),
61
+ async execute(input, ctx) {
62
+ const safeInput = { ...(input || {}) };
63
+ if (safeInput.ClusterStates) {
64
+ safeInput.ClusterStates = normalizeStates(safeInput.ClusterStates);
65
+ }
66
+ return ctx.api.emr.listClusters(safeInput);
67
+ }
68
+ };
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.apiRegistry = exports.toolList = exports.tools = void 0;
4
+ const emr_1 = require("./emr");
5
+ exports.tools = {
6
+ ...emr_1.emrTools
7
+ };
8
+ exports.toolList = Object.values(exports.tools);
9
+ exports.apiRegistry = {
10
+ emr: emr_1.emrApi
11
+ };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.systemPrompt = void 0;
4
+ exports.systemPrompt = `
5
+ You are a CLI Agent planner.
6
+ Given a natural language task and a list of tools,
7
+ output a JSON array of { tool, input } steps.
8
+ Never call tools yourself. Only produce plans.
9
+ `.trim();
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@volc-emr/emr-cli",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "Minimal controllable CLI Agent for Volcengine EMR OpenAPI: Tool-first + Plan/Execute with LLM-driven planner.",
5
+ "keywords": [
6
+ "volcengine",
7
+ "emr",
8
+ "cli",
9
+ "agent",
10
+ "openapi",
11
+ "llm",
12
+ "tool-first",
13
+ "plan-execute"
14
+ ],
15
+ "license": "MIT",
16
+ "bin": {
17
+ "volc-emr-agent": "dist/index.js"
18
+ },
19
+ "main": "dist/index.js",
20
+ "types": "dist/index.d.ts",
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "scripts": {
27
+ "dev": "ts-node src/index.ts",
28
+ "build": "tsc -p tsconfig.json && chmod +x dist/index.js",
29
+ "start": "node dist/index.js",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit",
31
+ "prepublishOnly": "npm run typecheck && npm run build"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "tag": "beta"
39
+ },
40
+ "dependencies": {
41
+ "@volcengine/openapi": "^1.26.0",
42
+ "commander": "^12.1.0",
43
+ "dotenv": "^16.4.5",
44
+ "zod": "^3.23.8",
45
+ "zod-to-json-schema": "^3.25.2"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^20.11.30",
49
+ "ts-node": "^10.9.2",
50
+ "typescript": "^5.4.5"
51
+ }
52
+ }