@minato-aqukin/autodl-cli 0.1.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.
package/dist/cli.js ADDED
@@ -0,0 +1,1058 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __nodeCreateRequire } from "node:module";
3
+ const require = __nodeCreateRequire(import.meta.url);
4
+ import {
5
+ assertBudget,
6
+ connectInteractive,
7
+ deployWorkflow,
8
+ listPrivateImages,
9
+ pull,
10
+ push,
11
+ resolveGitToken,
12
+ runWorkflow,
13
+ saveImage,
14
+ startMcpServer,
15
+ watchIdle
16
+ } from "./chunk-5RZ4Q7T4.js";
17
+ import {
18
+ BASE_IMAGES,
19
+ DEFAULT_BASE_IMAGE,
20
+ GPU_SPECS,
21
+ PRO_CREATE_REGIONS,
22
+ REGIONS,
23
+ VERSION,
24
+ assertProCreateRegion,
25
+ chooseRegions,
26
+ findBaseImage,
27
+ getBalance,
28
+ getStockByRegion,
29
+ parseCudaVersion,
30
+ resolveGpuSpec,
31
+ resolveRegion
32
+ } from "./chunk-CWCG3MOC.js";
33
+ import {
34
+ action,
35
+ bareAction,
36
+ confirmDestructive,
37
+ globalsOf,
38
+ lazyAction,
39
+ registerTuiCommand
40
+ } from "./chunk-2TWWLI3Y.js";
41
+ import "./chunk-NBWWTYSL.js";
42
+ import {
43
+ AutoDLClient,
44
+ ExitCode,
45
+ UsageError,
46
+ armTTLOverSSH,
47
+ clearToken,
48
+ colorStatus,
49
+ composeStartCommand,
50
+ configPath,
51
+ configureOutput,
52
+ createInstance,
53
+ disarmTTLOverSSH,
54
+ emit,
55
+ emitError,
56
+ execCommand,
57
+ findInstance,
58
+ formatBytes,
59
+ formatDuration,
60
+ formatRate,
61
+ formatTime,
62
+ formatYuan,
63
+ getCredentials,
64
+ getInstanceSnapshot,
65
+ getInstanceStatus,
66
+ isJson,
67
+ listAllInstances,
68
+ listTracked,
69
+ note,
70
+ parseDuration,
71
+ powerOffInstance,
72
+ powerOnInstance,
73
+ printKeyValues,
74
+ printTable,
75
+ readConfig,
76
+ recordTTL,
77
+ redactToken,
78
+ releaseInstance,
79
+ resolveLang,
80
+ setLang,
81
+ success,
82
+ sweepExpired,
83
+ t,
84
+ toAutoDLError,
85
+ untrackInstance,
86
+ updateConfig,
87
+ waitForRunning,
88
+ waitForShutdown,
89
+ warn
90
+ } from "./chunk-6QR4ZYQR.js";
91
+
92
+ // src/cli.ts
93
+ import { Command } from "commander";
94
+
95
+ // src/commands/account.ts
96
+ function registerAccountCommands(program) {
97
+ program.command("account").alias("balance").description("\u67E5\u770B\u8D26\u53F7\u4F59\u989D\u3001\u7D2F\u8BA1\u6D88\u8D39\u4E0E\u4EE3\u91D1\u5238").action(
98
+ action(async (context) => {
99
+ const balance = await getBalance(context.client);
100
+ emit({ ...balance, spendableYuan: balance.balanceYuan + balance.voucherYuan }, () => {
101
+ printKeyValues([
102
+ [t("account.balance"), formatYuan(balance.balanceYuan)],
103
+ [t("account.voucher"), formatYuan(balance.voucherYuan)],
104
+ [t("account.accumulated"), formatYuan(balance.accumulatedYuan)]
105
+ ]);
106
+ });
107
+ return 0;
108
+ })
109
+ );
110
+ }
111
+
112
+ // src/commands/auth.ts
113
+ import { intro, isCancel, outro, password, spinner } from "@clack/prompts";
114
+ import pc from "picocolors";
115
+ function registerAuthCommands(program) {
116
+ program.command("login").description("\u914D\u7F6E AutoDL \u5F00\u53D1\u8005 Token").option("--token <token>", "\u76F4\u63A5\u63D0\u4F9B Token\uFF0C\u8DF3\u8FC7\u4EA4\u4E92\u5F0F\u8F93\u5165").action(
117
+ bareAction(async (globals, options) => {
118
+ setLang(resolveLang(globals.lang));
119
+ configureOutput({ json: globals.json ?? false, color: globals.color ?? true });
120
+ let token = options.token ?? globals.token;
121
+ if (!token) {
122
+ if (isJson() || !process.stdin.isTTY) {
123
+ throw new UsageError("\u975E\u4EA4\u4E92\u73AF\u5883\u4E0B\u5FC5\u987B\u901A\u8FC7 --token \u63D0\u4F9B Token", {
124
+ hint: "\u4F8B\u5982\uFF1Aautodl login --token <\u4F60\u7684Token>\uFF0C\u6216\u76F4\u63A5\u8BBE\u7F6E AUTODL_TOKEN \u73AF\u5883\u53D8\u91CF\u3002"
125
+ });
126
+ }
127
+ intro(pc.bold("AutoDL \u767B\u5F55"));
128
+ const answer = await password({
129
+ message: `${t("login.prompt")}
130
+ ${pc.dim(t("login.hint"))}`
131
+ });
132
+ if (isCancel(answer)) {
133
+ outro(t("login.cancelled"));
134
+ return 0;
135
+ }
136
+ token = String(answer);
137
+ }
138
+ const spin = isJson() ? null : spinner();
139
+ spin?.start(t("login.verifying"));
140
+ const client = new AutoDLClient({ token });
141
+ let balance;
142
+ try {
143
+ balance = await getBalance(client);
144
+ } catch (err) {
145
+ spin?.stop("\u9A8C\u8BC1\u5931\u8D25", 1);
146
+ throw err;
147
+ }
148
+ spin?.stop("Token \u6709\u6548");
149
+ updateConfig({ token });
150
+ emit(
151
+ {
152
+ saved: true,
153
+ configPath: configPath(),
154
+ token: redactToken(token),
155
+ balanceYuan: balance.balanceYuan
156
+ },
157
+ () => {
158
+ success(`${t("login.success")} ${configPath()}`);
159
+ printKeyValues([
160
+ [t("account.balance"), formatYuan(balance.balanceYuan)],
161
+ ["Token", redactToken(token)]
162
+ ]);
163
+ }
164
+ );
165
+ return 0;
166
+ })
167
+ );
168
+ program.command("logout").description("\u6E05\u9664\u672C\u5730\u4FDD\u5B58\u7684 Token").action(
169
+ bareAction(async (globals) => {
170
+ setLang(resolveLang(globals.lang));
171
+ configureOutput({ json: globals.json ?? false, color: globals.color ?? true });
172
+ clearToken();
173
+ emit({ cleared: true, configPath: configPath() }, () => success(t("logout.done")));
174
+ return 0;
175
+ })
176
+ );
177
+ program.command("whoami").description("\u663E\u793A\u5F53\u524D Token \u6765\u6E90\u4E0E\u8D26\u53F7\u53EF\u7528\u4F59\u989D").action(
178
+ action(
179
+ async (context) => {
180
+ const balance = await getBalance(context.client);
181
+ const stored = readConfig().token;
182
+ const source = process.env.AUTODL_TOKEN ? "env:AUTODL_TOKEN" : stored ? `config:${configPath()}` : "flag";
183
+ emit({ tokenSource: source, token: context.client.maskedToken, ...balance }, () => {
184
+ printKeyValues([
185
+ ["Token", context.client.maskedToken],
186
+ ["\u6765\u6E90", source],
187
+ [t("account.balance"), formatYuan(balance.balanceYuan)]
188
+ ]);
189
+ });
190
+ return 0;
191
+ },
192
+ { sweep: false }
193
+ )
194
+ );
195
+ }
196
+
197
+ // src/commands/catalog.ts
198
+ import pc2 from "picocolors";
199
+ function registerCatalogCommands(program) {
200
+ program.command("gpus").description("\u5217\u51FA\u5B98\u65B9\u5F00\u653E API \u652F\u6301\u7684 GPU \u89C4\u683C").action(
201
+ lazyAction(async () => {
202
+ emit([...GPU_SPECS], () => {
203
+ printTable(
204
+ ["\u89C4\u683C ID", "\u578B\u53F7", "\u7C7B\u578B", "\u663E\u5B58", "\u522B\u540D"],
205
+ GPU_SPECS.map((spec) => [
206
+ spec.id,
207
+ spec.displayName,
208
+ spec.tier === "general" ? "\u901A\u7528\u578B" : "\u6027\u80FD\u578B",
209
+ `${spec.vramGb}G`,
210
+ spec.aliases.join(", ")
211
+ ])
212
+ );
213
+ note(
214
+ pc2.dim(
215
+ "\u5B98\u65B9 API \u6CA1\u6709\u5E93\u5B58\u67E5\u8BE2\u63A5\u53E3\uFF0C\u521B\u5EFA\u5B9E\u4F8B\u53EA\u80FD\u76F4\u63A5\u5C1D\u8BD5\uFF1B\u65E0\u8D27\u65F6\u4F1A\u8FD4\u56DE NO_STOCK\uFF08\u9000\u51FA\u7801 6\uFF09\u3002"
216
+ )
217
+ );
218
+ });
219
+ return 0;
220
+ })
221
+ );
222
+ program.command("regions").description("\u5217\u51FA\u53EF\u7528\u5730\u533A\u4EE3\u7801").action(
223
+ lazyAction(async () => {
224
+ emit([...REGIONS], () => {
225
+ printTable(
226
+ ["\u5730\u533A\u4EE3\u7801", "\u540D\u79F0", "\u522B\u540D"],
227
+ REGIONS.map((region) => [region.id, region.displayName, region.aliases.join(", ")])
228
+ );
229
+ });
230
+ return 0;
231
+ })
232
+ );
233
+ program.command("images").description("\u5217\u51FA\u955C\u50CF\uFF08\u9ED8\u8BA4\u663E\u793A\u79C1\u6709\u955C\u50CF\uFF0C--base \u663E\u793A\u5B98\u65B9\u516C\u5171\u57FA\u7840\u955C\u50CF\uFF09").option("--base", "\u663E\u793A\u5B98\u65B9\u516C\u5171\u57FA\u7840\u955C\u50CF").option("--page <n>", "\u9875\u7801", "1").option("--page-size <n>", "\u6BCF\u9875\u6761\u6570", "50").action(
234
+ lazyAction(
235
+ async (getContext, options) => {
236
+ if (options.base) {
237
+ emit([...BASE_IMAGES], () => {
238
+ printTable(
239
+ ["\u955C\u50CF UUID", "\u6846\u67B6", "CUDA", "Python", "\u6807\u7B7E"],
240
+ BASE_IMAGES.map((image) => [
241
+ image.uuid,
242
+ image.framework,
243
+ image.cuda,
244
+ image.python,
245
+ image.tag
246
+ ])
247
+ );
248
+ note(pc2.dim("\u8FD9\u4E9B\u662F\u5185\u7F6E\u7684\u9759\u6001\u8868\uFF0C\u82E5\u5E73\u53F0\u5DF2\u66F4\u65B0\u8BF7\u5230\u4ED3\u5E93\u63D0 issue\u3002"));
249
+ });
250
+ return 0;
251
+ }
252
+ const { images, pagination } = await listPrivateImages(getContext().client, {
253
+ pageIndex: Number(options.page),
254
+ pageSize: Number(options.pageSize)
255
+ });
256
+ emit({ images, pagination }, () => {
257
+ if (images.length === 0) {
258
+ note("\u5F53\u524D\u8D26\u53F7\u6CA1\u6709\u79C1\u6709\u955C\u50CF");
259
+ return;
260
+ }
261
+ printTable(
262
+ ["\u955C\u50CF UUID", "\u540D\u79F0", "\u72B6\u6001", "\u5927\u5C0F", "\u521B\u5EFA\u65F6\u95F4"],
263
+ images.map((image) => [
264
+ image.imageUuid,
265
+ image.name,
266
+ image.status ?? "-",
267
+ formatBytes(image.sizeBytes),
268
+ formatTime(image.createdAt)
269
+ ])
270
+ );
271
+ });
272
+ return 0;
273
+ }
274
+ )
275
+ );
276
+ }
277
+
278
+ // src/commands/deploy.ts
279
+ import pc3 from "picocolors";
280
+ function registerDeployCommand(program) {
281
+ program.command("deploy <repo>").description(
282
+ "\u5F00\u5361\u90E8\u7F72\u4EE3\u7801\u6258\u7BA1\u5E73\u53F0\u7684\u9879\u76EE\uFF1A\u5EFA\u5B9E\u4F8B \u2192 \u62C9\u4EE3\u7801 \u2192 \u88C5\u4F9D\u8D56 \u2192 \u542F\u52A8 \u2192\uFF08\u9ED8\u8BA4\uFF09\u5173\u673A\u4FDD\u7559\u6570\u636E"
283
+ ).option("--gpu <spec>", "GPU \u89C4\u683C\uFF1B\u4E0D\u6307\u5B9A --instance \u65F6\u5FC5\u586B").option("--instance <id>", "\u590D\u7528\u5DF2\u6709\u5B9E\u4F8B\uFF1A\u5F00\u673A \u2192 git pull \u2192 \u91CD\u65B0\u90E8\u7F72").option("--branch <name>", "\u5206\u652F\u6216\u6807\u7B7E").option("--dir <path>", "\u8FDC\u7A0B\u4EE3\u7801\u76EE\u5F55\uFF0C\u9ED8\u8BA4 /root/autodl-tmp/<\u4ED3\u5E93\u540D>").option("--setup <cmd>", "\u81EA\u5B9A\u4E49\u4F9D\u8D56\u5B89\u88C5\u547D\u4EE4\uFF0C\u8986\u76D6\u81EA\u52A8\u63A2\u6D4B").option("--no-setup", "\u8DF3\u8FC7\u4F9D\u8D56\u5B89\u88C5").option("--start <cmd>", "\u4F9D\u8D56\u88C5\u597D\u540E\u6267\u884C\u7684\u542F\u52A8\u547D\u4EE4").option("--detach", "\u540E\u53F0\u542F\u52A8\u5E76\u7ACB\u5373\u8FD4\u56DE\uFF08\u5B9E\u4F8B\u4FDD\u6301\u8FD0\u884C\uFF09", false).option("--git-token <token>", "\u79C1\u6709\u4ED3\u5E93\u51ED\u8BC1\uFF0C\u4E5F\u53EF\u7528 GIT_TOKEN / GITHUB_TOKEN \u73AF\u5883\u53D8\u91CF").option("--no-accel", "\u5173\u95ED\u5B66\u672F\u8D44\u6E90\u52A0\u901F").option("--num <n>", "GPU \u6570\u91CF\uFF081-4\uFF09", "1").option("--image <uuid>", "\u955C\u50CF UUID \u6216\u516C\u5171\u955C\u50CF\u6807\u7B7E").option("--region <code...>", "\u9650\u5B9A\u5730\u533A\uFF0C\u9ED8\u8BA4\u6309\u5E93\u5B58\u81EA\u52A8\u62E9\u4F18").option("--disk <gb>", "\u7CFB\u7EDF\u76D8\u6269\u5BB9 GB", "0").option("--name <name>", "\u5B9E\u4F8B\u540D\u79F0\uFF0C\u9ED8\u8BA4\u53D6\u4ED3\u5E93\u540D").option("--ttl <duration>", "\u5230\u671F\u81EA\u52A8\u5173\u673A", "4h").option("--on-finish <action>", "\u7ED3\u675F\u540E\u52A8\u4F5C\uFF1Apoweroff / release / keep", "poweroff").option("--timeout <duration>", "\u5355\u6761\u8FDC\u7A0B\u547D\u4EE4\u8D85\u65F6\u65F6\u95F4").option("--min-balance <yuan>", "\u4F59\u989D\u4F4E\u4E8E\u8BE5\u503C\u65F6\u62D2\u7EDD\u521B\u5EFA").option("--no-stock-check", "\u8DF3\u8FC7\u521B\u5EFA\u524D\u7684 GPU \u5E93\u5B58\u67E5\u8BE2").option("--env <key=value...>", "\u6CE8\u5165\u73AF\u5883\u53D8\u91CF").action(
284
+ action(async (context, repo, options) => {
285
+ const env = {};
286
+ for (const pair of options.env ?? []) {
287
+ const index = pair.indexOf("=");
288
+ if (index > 0) env[pair.slice(0, index)] = pair.slice(index + 1);
289
+ }
290
+ if (!["poweroff", "release", "keep"].includes(options.onFinish)) {
291
+ warn(`\u672A\u77E5\u7684 --on-finish "${options.onFinish}"\uFF0C\u6309 poweroff \u5904\u7406`);
292
+ }
293
+ const controller = new AbortController();
294
+ let interrupted = false;
295
+ const onSigint = () => {
296
+ if (interrupted) return;
297
+ interrupted = true;
298
+ warn("\u6536\u5230\u4E2D\u65AD\u4FE1\u53F7\uFF0C\u6B63\u5728\u5B89\u5168\u6536\u5C3E\u2026");
299
+ controller.abort();
300
+ };
301
+ process.on("SIGINT", onSigint);
302
+ process.on("SIGTERM", onSigint);
303
+ try {
304
+ const deployOptions = {
305
+ repo,
306
+ ...options.gpu ? { gpu: options.gpu } : {},
307
+ ...options.instance ? { instanceUuid: options.instance } : {},
308
+ ...options.branch ? { branch: options.branch } : {},
309
+ gpuNum: Number(options.num),
310
+ ...options.image ? { image: options.image } : {},
311
+ ...options.region ? { regions: options.region } : {},
312
+ diskGb: Number(options.disk),
313
+ ...options.name ? { name: options.name } : {},
314
+ ...options.dir ? { dir: options.dir } : {},
315
+ ...options.setup ? { setup: options.setup } : {},
316
+ noSetup: options.noSetup === true,
317
+ ...options.start ? { start: options.start } : {},
318
+ detach: options.detach,
319
+ ...resolveGitToken(options.gitToken) ? { gitToken: resolveGitToken(options.gitToken) } : {},
320
+ noAcceleration: options.noAccel === true,
321
+ ttlSeconds: parseDuration(options.ttl),
322
+ onFinish: ["poweroff", "release", "keep"].includes(options.onFinish) ? options.onFinish : "poweroff",
323
+ ...options.timeout ? { commandTimeoutMs: parseDuration(options.timeout) * 1e3 } : {},
324
+ ...options.minBalance !== void 0 ? { minBalanceYuan: Number(options.minBalance) } : {},
325
+ ...options.stockCheck === false ? { stockCheck: false } : {},
326
+ ...Object.keys(env).length ? { env } : {},
327
+ signal: controller.signal
328
+ };
329
+ const result = await deployWorkflow(context.client, deployOptions);
330
+ emit(result, () => {
331
+ note(`\u7528\u65F6 ${formatDuration(Math.round(result.durationMs / 1e3))}`);
332
+ if (result.detached) {
333
+ success("\u9879\u76EE\u5DF2\u5728\u540E\u53F0\u8FD0\u884C");
334
+ for (const url of result.access.publicUrls) note(` \u516C\u7F51\u8BBF\u95EE\uFF1A${url}`);
335
+ if (result.access.tunnelHint) {
336
+ note(` ${pc3.dim("\u516C\u7F51\u6620\u5C04\u9700\u4F01\u4E1A\u8BA4\u8BC1\uFF0C\u4E2A\u4EBA\u8D26\u53F7\u8BF7\u7528 SSH \u96A7\u9053\uFF1A")}`);
337
+ note(` ${result.access.tunnelHint}`);
338
+ }
339
+ if (result.access.logHint) note(` \u67E5\u770B\u65E5\u5FD7\uFF1A${result.access.logHint}`);
340
+ } else if (result.startCommand) {
341
+ if (result.exitCode === 0) success("\u9879\u76EE\u6267\u884C\u6210\u529F");
342
+ else warn(`\u9879\u76EE\u9000\u51FA\u7801 ${result.exitCode}`);
343
+ } else {
344
+ success(`\u90E8\u7F72\u5B8C\u6210\uFF1A${result.dir}`);
345
+ }
346
+ if (result.finalAction === "poweroff" && !result.detached) {
347
+ note(`\u4E0B\u6B21\u76F4\u63A5\u590D\u7528\uFF1Aautodl deploy ${repo} --instance ${result.instanceUuid}`);
348
+ }
349
+ });
350
+ return result.startCommand && !result.detached ? result.exitCode ?? 1 : 0;
351
+ } finally {
352
+ process.off("SIGINT", onSigint);
353
+ process.off("SIGTERM", onSigint);
354
+ }
355
+ })
356
+ );
357
+ }
358
+
359
+ // src/commands/guard.ts
360
+ import pc4 from "picocolors";
361
+ function registerGuardCommands(program) {
362
+ const guard = program.command("guard").description("\u6210\u672C\u62A4\u680F\uFF1A\u5B9A\u65F6\u5173\u673A\u3001\u95F2\u7F6E\u68C0\u6D4B\u3001\u8D85\u65F6\u6E05\u7406");
363
+ guard.command("ttl <id> <duration>").description("\u4E3A\u8FD0\u884C\u4E2D\u7684\u5B9E\u4F8B\u8BBE\u7F6E\u5230\u671F\u81EA\u52A8\u5173\u673A\uFF0C\u5982 `autodl guard ttl pro-xxx 2h`").action(
364
+ action(async (context, id, duration) => {
365
+ const seconds = parseDuration(duration);
366
+ const armed = await armTTLOverSSH(context.client, id, seconds);
367
+ recordTTL({ uuid: id, ttlSeconds: seconds, inInstanceTimer: armed });
368
+ if (!armed) warn(t("guard.armFailed"));
369
+ emit(
370
+ { uuid: id, ttlSeconds: seconds, inInstanceTimer: armed },
371
+ () => success(`${t("guard.armed")}\uFF1A${formatDuration(seconds)} \u540E\u81EA\u52A8\u5173\u673A`)
372
+ );
373
+ return 0;
374
+ })
375
+ );
376
+ guard.command("cancel <id>").description("\u53D6\u6D88\u5B9E\u4F8B\u5185\u7684\u5B9A\u65F6\u5173\u673A").action(
377
+ action(async (context, id) => {
378
+ const ok = await disarmTTLOverSSH(context.client, id);
379
+ emit(
380
+ { uuid: id, cancelled: ok },
381
+ () => ok ? success("\u5DF2\u53D6\u6D88\u5B9A\u65F6\u5173\u673A") : warn("\u672A\u80FD\u786E\u8BA4\u53D6\u6D88\u7ED3\u679C\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5\u5B9E\u4F8B\u5185\u5B9A\u65F6\u4EFB\u52A1")
382
+ );
383
+ return 0;
384
+ })
385
+ );
386
+ guard.command("idle <id>").description("\u6301\u7EED\u91C7\u6837 GPU \u5229\u7528\u7387\uFF0C\u95F2\u7F6E\u8DB3\u591F\u4E45\u540E\u81EA\u52A8\u5173\u673A").option("--threshold <percent>", "\u5224\u5B9A\u4E3A\u95F2\u7F6E\u7684\u5229\u7528\u7387\u4E0A\u9650", "5").option("--samples <n>", "\u8FDE\u7EED\u95F2\u7F6E\u591A\u5C11\u6B21\u540E\u5173\u673A", "6").option("--interval <duration>", "\u91C7\u6837\u95F4\u9694", "1m").option("--dry-run", "\u53EA\u62A5\u544A\u4E0D\u5173\u673A", false).action(
387
+ action(
388
+ async (context, id, options) => {
389
+ const controller = new AbortController();
390
+ const onSigint = () => controller.abort();
391
+ process.once("SIGINT", onSigint);
392
+ try {
393
+ const result = await watchIdle(context.client, id, {
394
+ thresholdPercent: Number(options.threshold),
395
+ samples: Number(options.samples),
396
+ intervalSeconds: parseDuration(options.interval),
397
+ dryRun: options.dryRun,
398
+ signal: controller.signal,
399
+ onSample: (utilisation, consecutive) => note(
400
+ pc4.dim(
401
+ `GPU ${utilisation.toFixed(1)}%\uFF08\u8FDE\u7EED\u95F2\u7F6E ${consecutive}/${options.samples}\uFF09`
402
+ )
403
+ )
404
+ });
405
+ emit({ uuid: id, ...result }, () => {
406
+ if (result.stopped) success(`\u5B9E\u4F8B ${id} \u56E0\u6301\u7EED\u95F2\u7F6E\u5DF2\u81EA\u52A8\u5173\u673A`);
407
+ else note(`\u95F2\u7F6E\u68C0\u6D4B\u7ED3\u675F\uFF1A${result.reason}`);
408
+ });
409
+ return 0;
410
+ } finally {
411
+ process.off("SIGINT", onSigint);
412
+ }
413
+ }
414
+ )
415
+ );
416
+ guard.command("list").description("\u5217\u51FA\u672C\u673A\u8BB0\u5F55\u7684 TTL \u53F0\u8D26").action(
417
+ lazyAction(async () => {
418
+ const tracked = listTracked();
419
+ emit(tracked, () => {
420
+ if (tracked.length === 0) {
421
+ note("\u672C\u673A\u6CA1\u6709\u8BB0\u5F55\u4EFB\u4F55\u5E26 TTL \u7684\u5B9E\u4F8B");
422
+ return;
423
+ }
424
+ const now = Date.now();
425
+ printTable(
426
+ ["\u5B9E\u4F8B ID", "\u540D\u79F0", "TTL", "\u5269\u4F59", "\u5B9E\u4F8B\u5185\u5B9A\u65F6\u5668"],
427
+ tracked.map((entry) => [
428
+ entry.uuid,
429
+ entry.name ?? "-",
430
+ formatDuration(entry.ttlSeconds),
431
+ entry.expiresAt <= now ? pc4.red("\u5DF2\u8D85\u65F6") : formatDuration(Math.round((entry.expiresAt - now) / 1e3)),
432
+ entry.inInstanceTimer ? "\u5DF2\u8BBE\u7F6E" : pc4.yellow("\u672A\u8BBE\u7F6E")
433
+ ])
434
+ );
435
+ });
436
+ return 0;
437
+ })
438
+ );
439
+ guard.command("sweep").description("\u7ACB\u5373\u6E05\u7406\u6240\u6709\u8D85\u65F6\u5B9E\u4F8B").action(
440
+ action(
441
+ async (context) => {
442
+ const result = await sweepExpired(context.client);
443
+ emit(result, () => {
444
+ if (result.stopped.length === 0 && result.failed.length === 0) {
445
+ note("\u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684\u8D85\u65F6\u5B9E\u4F8B");
446
+ return;
447
+ }
448
+ if (result.stopped.length) success(`\u5DF2\u5173\u95ED ${result.stopped.length} \u4E2A\u8D85\u65F6\u5B9E\u4F8B`);
449
+ for (const failure of result.failed) warn(`${failure.uuid}\uFF1A${failure.reason}`);
450
+ });
451
+ return 0;
452
+ },
453
+ { sweep: false }
454
+ )
455
+ );
456
+ const image = program.command("image").description("\u955C\u50CF\u7BA1\u7406");
457
+ image.command("save <id> <name>").description("\u628A\u5B9E\u4F8B\u4FDD\u5B58\u4E3A\u79C1\u6709\u955C\u50CF").action(
458
+ action(async (context, id, name) => {
459
+ const imageUuid = await saveImage(context.client, id, name);
460
+ emit(
461
+ { instanceUuid: id, imageUuid, name },
462
+ () => success(
463
+ `\u955C\u50CF\u4FDD\u5B58\u5DF2\u63D0\u4EA4\uFF1A${imageUuid}\uFF08\u4FDD\u5B58\u9700\u8981\u4E00\u6BB5\u65F6\u95F4\uFF0C\u7528 \`autodl images\` \u67E5\u770B\u72B6\u6001\uFF09`
464
+ )
465
+ );
466
+ return 0;
467
+ })
468
+ );
469
+ }
470
+
471
+ // src/commands/instances.ts
472
+ import { spinner as spinner2 } from "@clack/prompts";
473
+ import pc5 from "picocolors";
474
+ function registerInstanceCommands(program) {
475
+ program.command("ls").alias("list").description("\u5217\u51FA\u8D26\u53F7\u4E0B\u7684\u6240\u6709\u5B9E\u4F8B").option("--status <status>", "\u6309\u72B6\u6001\u8FC7\u6EE4\uFF0C\u5982 running / shutdown").action(
476
+ action(async (context, options) => {
477
+ let instances = await listAllInstances(context.client);
478
+ if (options.status) {
479
+ instances = instances.filter((instance) => instance.status === options.status);
480
+ }
481
+ emit(instances, () => {
482
+ if (instances.length === 0) {
483
+ note(t("instance.none"));
484
+ return;
485
+ }
486
+ printTable(
487
+ ["\u5B9E\u4F8B ID", "\u540D\u79F0", "\u72B6\u6001", "GPU", "\u5730\u533A", "\u521B\u5EFA\u65F6\u95F4"],
488
+ instances.map((instance) => [
489
+ instance.uuid,
490
+ instance.name ?? "-",
491
+ colorStatus(instance.status),
492
+ `${instance.gpuSpec ?? "-"} \xD7${instance.gpuNum}`,
493
+ instance.regionName ?? instance.regionSign ?? "-",
494
+ formatTime(instance.createdAt)
495
+ ])
496
+ );
497
+ });
498
+ return 0;
499
+ })
500
+ );
501
+ program.command("info <id>").description("\u67E5\u770B\u5B9E\u4F8B\u8BE6\u60C5\uFF0C\u542B SSH \u8FDE\u63A5\u4FE1\u606F").option("--show-password", "\u5728\u8F93\u51FA\u4E2D\u663E\u793A root \u5BC6\u7801\u660E\u6587", false).action(
502
+ action(async (context, id, options) => {
503
+ const [instance, snapshot] = await Promise.all([
504
+ findInstance(context.client, id),
505
+ getInstanceSnapshot(context.client, id).catch(() => null)
506
+ ]);
507
+ const payload = {
508
+ instance,
509
+ snapshot: snapshot && !options.showPassword ? {
510
+ ...snapshot,
511
+ ssh: { ...snapshot.ssh, password: snapshot.ssh.password ? "***" : null }
512
+ } : snapshot
513
+ };
514
+ emit(payload, () => {
515
+ printKeyValues([
516
+ ["\u5B9E\u4F8B ID", instance.uuid],
517
+ ["\u540D\u79F0", instance.name ?? "-"],
518
+ ["\u72B6\u6001", colorStatus(instance.status)],
519
+ ["GPU", `${instance.gpuSpec ?? "-"} \xD7${instance.gpuNum}`],
520
+ ["\u5730\u533A", instance.regionName ?? instance.regionSign ?? "-"],
521
+ ["\u8BA1\u8D39\u65B9\u5F0F", instance.chargeType ?? "-"],
522
+ ["\u521B\u5EFA\u65F6\u95F4", formatTime(instance.createdAt)],
523
+ ["\u5F00\u673A\u65F6\u95F4", formatTime(instance.startedAt)]
524
+ ]);
525
+ if (!snapshot) {
526
+ note(pc5.dim("\n\u5B9E\u4F8B\u672A\u8FD0\u884C\uFF0C\u6682\u65E0 SSH \u4FE1\u606F\u3002"));
527
+ return;
528
+ }
529
+ process.stdout.write("\n");
530
+ printKeyValues([
531
+ ["\u5355\u4EF7", snapshot.priceYuanPerHour ? formatRate(snapshot.priceYuanPerHour) : "-"],
532
+ ["SSH", snapshot.ssh.command ?? "-"],
533
+ [
534
+ "\u5BC6\u7801",
535
+ options.showPassword ? snapshot.ssh.password ?? "-" : pc5.dim("*** (\u52A0 --show-password \u663E\u793A)")
536
+ ],
537
+ [
538
+ "CPU",
539
+ snapshot.usage.cpuPercent !== null ? `${snapshot.usage.cpuPercent.toFixed(1)}%` : "-"
540
+ ],
541
+ [
542
+ "\u5185\u5B58",
543
+ snapshot.usage.memUsedBytes !== null ? `${formatBytes(snapshot.usage.memUsedBytes)} / ${formatBytes(snapshot.usage.memLimitBytes)}` : "-"
544
+ ],
545
+ [
546
+ "\u7CFB\u7EDF\u76D8",
547
+ snapshot.usage.rootFsUsedBytes !== null ? `${formatBytes(snapshot.usage.rootFsUsedBytes)} / ${formatBytes(snapshot.usage.rootFsTotalBytes)}` : "-"
548
+ ]
549
+ ]);
550
+ if (snapshot.services.length) {
551
+ process.stdout.write("\n");
552
+ printTable(
553
+ ["\u7AEF\u53E3", "\u534F\u8BAE", "\u5916\u90E8\u5730\u5740"],
554
+ snapshot.services.map((service) => [
555
+ service.port,
556
+ service.protocol ?? "-",
557
+ service.domain
558
+ ])
559
+ );
560
+ }
561
+ });
562
+ return 0;
563
+ })
564
+ );
565
+ program.command("create").description("\u521B\u5EFA\u4E00\u4E2A\u6309\u91CF\u8BA1\u8D39\u7684 Pro \u5B9E\u4F8B").requiredOption("--gpu <spec>", "GPU \u89C4\u683C\uFF0C\u53EF\u7528 `autodl gpus` \u67E5\u770B").option("--num <n>", "GPU \u6570\u91CF\uFF081-4\uFF09", "1").option("--image <uuid>", "\u955C\u50CF UUID \u6216\u516C\u5171\u955C\u50CF\u6807\u7B7E", DEFAULT_BASE_IMAGE).option("--cuda <version>", "\u6700\u4F4E CUDA \u7248\u672C\uFF0C\u5982 11.8").option("--region <code...>", "\u4F18\u5148\u5730\u533A\uFF0C\u53EF\u591A\u6B21\u6307\u5B9A").option("--disk <gb>", "\u7CFB\u7EDF\u76D8\u6269\u5BB9 GB\uFF080-500\uFF09", "0").option("--name <name>", "\u5B9E\u4F8B\u540D\u79F0").option("--ttl <duration>", "\u5230\u671F\u81EA\u52A8\u5173\u673A\uFF0C\u5982 2h / 90m\uFF08\u5F3A\u70C8\u5EFA\u8BAE\u8BBE\u7F6E\uFF09").option("--start-command <cmd>", "\u5F00\u673A\u540E\u6267\u884C\u7684\u547D\u4EE4").option("--min-balance <yuan>", "\u4F59\u989D\u4F4E\u4E8E\u8BE5\u503C\u65F6\u62D2\u7EDD\u521B\u5EFA").option("--wait", "\u7B49\u5F85\u5B9E\u4F8B\u8FDB\u5165 running \u72B6\u6001", false).option("--no-stock-check", "\u8DF3\u8FC7\u521B\u5EFA\u524D\u7684 GPU \u5E93\u5B58\u67E5\u8BE2").action(
566
+ action(async (context, options) => {
567
+ const spec = resolveGpuSpec(options.gpu);
568
+ if (!spec) {
569
+ throw new UsageError(`\u672A\u77E5\u7684 GPU \u89C4\u683C "${options.gpu}"`, {
570
+ hint: "\u8FD0\u884C `autodl gpus` \u67E5\u770B\u5B98\u65B9 API \u652F\u6301\u7684\u5168\u90E8\u89C4\u683C\u3002"
571
+ });
572
+ }
573
+ const image = findBaseImage(options.image ?? DEFAULT_BASE_IMAGE);
574
+ const imageUuid = image?.uuid ?? options.image ?? DEFAULT_BASE_IMAGE;
575
+ const cudaFrom = options.cuda ? parseCudaVersion(options.cuda) : parseCudaVersion(image?.cuda ?? "11.8");
576
+ const requestedRegions = (options.region ?? []).map(
577
+ (input) => assertProCreateRegion(input).id
578
+ );
579
+ const regions = options.stockCheck === false ? requestedRegions : (await chooseRegions(context.client, spec, requestedRegions)).regions;
580
+ const ttlSeconds = options.ttl ? parseDuration(options.ttl) : void 0;
581
+ if (!ttlSeconds) {
582
+ warn(
583
+ "\u672A\u8BBE\u7F6E --ttl\uFF1A\u5B9E\u4F8B\u4F1A\u4E00\u76F4\u8BA1\u8D39\u76F4\u5230\u624B\u52A8\u5173\u673A\u3002AutoDL \u6309\u5F00\u673A\u65F6\u957F\u8BA1\u8D39\uFF0C\u4E0E\u662F\u5426\u4F7F\u7528 GPU \u65E0\u5173\u3002"
584
+ );
585
+ }
586
+ await assertBudget(
587
+ context.client,
588
+ options.minBalance !== void 0 ? Number(options.minBalance) : void 0
589
+ );
590
+ const startCommand = composeStartCommand(ttlSeconds, options.startCommand);
591
+ const spin = isJson() ? null : spinner2();
592
+ spin?.start(t("instance.creating"));
593
+ let uuid;
594
+ try {
595
+ uuid = await createInstance(context.client, {
596
+ gpuSpec: spec.id,
597
+ gpuNum: Number(options.num),
598
+ imageUuid,
599
+ cudaFrom,
600
+ expandSystemDiskGb: Number(options.disk),
601
+ ...regions.length ? { regions } : {},
602
+ ...options.name ? { name: options.name } : {},
603
+ ...startCommand ? { startCommand } : {}
604
+ });
605
+ } catch (err) {
606
+ spin?.stop("\u521B\u5EFA\u5931\u8D25", 1);
607
+ throw err;
608
+ }
609
+ spin?.stop(`${t("instance.created")}\uFF1A${uuid}`);
610
+ if (ttlSeconds) {
611
+ recordTTL({
612
+ uuid,
613
+ ...options.name ? { name: options.name } : {},
614
+ ttlSeconds,
615
+ inInstanceTimer: true
616
+ });
617
+ }
618
+ let status = "creating";
619
+ if (options.wait) {
620
+ const waitSpin = isJson() ? null : spinner2();
621
+ waitSpin?.start(t("instance.waiting"));
622
+ status = await waitForRunning(context.client, uuid);
623
+ waitSpin?.stop(t("instance.ready"));
624
+ }
625
+ emit(
626
+ {
627
+ uuid,
628
+ gpuSpec: spec.id,
629
+ gpuNum: Number(options.num),
630
+ imageUuid,
631
+ cudaFrom,
632
+ regions,
633
+ ttlSeconds: ttlSeconds ?? null,
634
+ status
635
+ },
636
+ () => {
637
+ success(`\u5B9E\u4F8B ${pc5.bold(uuid)} \u5DF2\u521B\u5EFA`);
638
+ if (ttlSeconds) note(`${t("guard.armed")}\uFF1A${formatDuration(ttlSeconds)} \u540E\u81EA\u52A8\u5173\u673A`);
639
+ note(`\u8FDE\u63A5\uFF1Aautodl ssh ${uuid}`);
640
+ }
641
+ );
642
+ return 0;
643
+ })
644
+ );
645
+ program.command("start <id>").description("\u5F00\u673A").option("--ttl <duration>", "\u5F00\u673A\u540E\u8BBE\u7F6E\u5230\u671F\u81EA\u52A8\u5173\u673A").option("--start-command <cmd>", "\u5F00\u673A\u540E\u6267\u884C\u7684\u547D\u4EE4").option("--wait", "\u7B49\u5F85\u5B9E\u4F8B\u8FDB\u5165 running \u72B6\u6001", false).action(
646
+ action(
647
+ async (context, id, options) => {
648
+ const ttlSeconds = options.ttl ? parseDuration(options.ttl) : void 0;
649
+ const startCommand = composeStartCommand(ttlSeconds, options.startCommand);
650
+ note(t("instance.poweringOn"));
651
+ await powerOnInstance(context.client, id, {
652
+ ...startCommand ? { startCommand } : {}
653
+ });
654
+ let armed = Boolean(ttlSeconds);
655
+ if (options.wait || ttlSeconds) {
656
+ const spin = isJson() ? null : spinner2();
657
+ spin?.start(t("instance.waiting"));
658
+ await waitForRunning(context.client, id);
659
+ spin?.stop(t("instance.ready"));
660
+ if (ttlSeconds) {
661
+ armed = await armTTLOverSSH(context.client, id, ttlSeconds);
662
+ if (!armed) warn(t("guard.armFailed"));
663
+ recordTTL({ uuid: id, ttlSeconds, inInstanceTimer: armed });
664
+ }
665
+ }
666
+ emit(
667
+ { uuid: id, started: true, ttlSeconds: ttlSeconds ?? null, inInstanceTimer: armed },
668
+ () => {
669
+ success(`\u5B9E\u4F8B ${id} \u5DF2\u5F00\u673A`);
670
+ if (ttlSeconds) note(`${t("guard.armed")}\uFF1A${formatDuration(ttlSeconds)} \u540E\u81EA\u52A8\u5173\u673A`);
671
+ }
672
+ );
673
+ return 0;
674
+ }
675
+ )
676
+ );
677
+ program.command("stop <id>").description("\u5173\u673A\uFF08\u505C\u6B62\u8BA1\u8D39\uFF0C\u6570\u636E\u4FDD\u7559\uFF09").action(
678
+ action(async (context, id) => {
679
+ note(t("instance.poweringOff"));
680
+ await powerOffInstance(context.client, id);
681
+ const after = await getInstanceStatus(context.client, id).catch(() => "unknown");
682
+ const stopped = after !== "running" && after !== "starting";
683
+ if (stopped) untrackInstance(id);
684
+ emit({ uuid: id, stopped, status: after }, () => {
685
+ if (stopped) success(`\u5B9E\u4F8B ${id} \u5DF2\u5173\u673A\uFF0C\u8BA1\u8D39\u5DF2\u505C\u6B62`);
686
+ else
687
+ warn(
688
+ `\u5173\u673A\u6307\u4EE4\u5DF2\u53D1\u9001\uFF0C\u4F46\u5B9E\u4F8B\u72B6\u6001\u4ECD\u4E3A ${after}\u3002\u5B9E\u4F8B\u53EF\u80FD\u4ECD\u5728\u542F\u52A8\u4E2D\uFF0C\u7A0D\u540E\u8BF7\u7528 \`autodl ls\` \u786E\u8BA4\uFF0C\u5FC5\u8981\u65F6\u91CD\u8BD5 \`autodl stop ${id}\`\u3002`
689
+ );
690
+ });
691
+ return stopped ? 0 : 1;
692
+ })
693
+ );
694
+ program.command("rm <id>").alias("release").description("\u91CA\u653E\u5B9E\u4F8B\uFF08\u4E0D\u53EF\u9006\uFF0C\u6570\u636E\u5C06\u88AB\u6C38\u4E45\u6E05\u7A7A\uFF09").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4", false).option("--force", "\u82E5\u5B9E\u4F8B\u4ECD\u5728\u8FD0\u884C\u5219\u5148\u81EA\u52A8\u5173\u673A", false).action(
695
+ action(async (context, id, options) => {
696
+ const status = await getInstanceStatus(context.client, id);
697
+ if (status !== "shutdown") {
698
+ if (!options.force) {
699
+ throw new UsageError(`\u5B9E\u4F8B\u5F53\u524D\u72B6\u6001\u4E3A "${status}"\uFF0CAutoDL \u8981\u6C42\u5148\u5173\u673A\u624D\u80FD\u91CA\u653E`, {
700
+ hint: "\u5148\u8FD0\u884C `autodl stop <id>`\uFF0C\u6216\u52A0 --force \u8BA9\u672C\u547D\u4EE4\u81EA\u52A8\u5173\u673A\u3002"
701
+ });
702
+ }
703
+ if (status !== "shutting_down") {
704
+ note(t("instance.poweringOff"));
705
+ await powerOffInstance(context.client, id);
706
+ }
707
+ await waitForShutdown(context.client, id, { timeoutMs: 10 * 6e4 });
708
+ }
709
+ const confirmed = await confirmDestructive(
710
+ `${t("instance.confirmRelease")} ${id}`,
711
+ options.yes
712
+ );
713
+ if (!confirmed) {
714
+ emit({ uuid: id, released: false, cancelled: true }, () => note(t("common.cancelled")));
715
+ return 0;
716
+ }
717
+ await releaseInstance(context.client, id);
718
+ untrackInstance(id);
719
+ emit({ uuid: id, released: true }, () => success(`${t("instance.released")}\uFF1A${id}`));
720
+ return 0;
721
+ })
722
+ );
723
+ }
724
+
725
+ // src/commands/mcp.ts
726
+ function registerMcpCommand(program) {
727
+ program.command("mcp").description("\u4EE5 stdio MCP server \u65B9\u5F0F\u8FD0\u884C\uFF0C\u4F9B Claude Code / Cursor / Cline \u7B49 agent \u8C03\u7528").action(async (_options, command) => {
728
+ const globals = globalsOf(command);
729
+ try {
730
+ await startMcpServer({
731
+ ...globals.token ? { token: globals.token } : {},
732
+ ...globals.baseUrl ? { baseUrl: globals.baseUrl } : {}
733
+ });
734
+ } catch (err) {
735
+ const error = toAutoDLError(err);
736
+ process.stderr.write(`${error.message}
737
+ `);
738
+ if (error.hint) process.stderr.write(`${error.hint}
739
+ `);
740
+ process.exitCode = error.exitCode;
741
+ }
742
+ });
743
+ }
744
+
745
+ // src/commands/run.ts
746
+ function registerRunCommand(program) {
747
+ program.command("run <command...>").description("\u4E00\u952E\u5DE5\u4F5C\u6D41\uFF1A\u5EFA\u5B9E\u4F8B \u2192 \u540C\u6B65\u4EE3\u7801 \u2192 \u8FDC\u7A0B\u6267\u884C \u2192 \u56DE\u4F20\u4EA7\u7269 \u2192 \u81EA\u52A8\u5173\u673A").requiredOption("--gpu <spec>", "GPU \u89C4\u683C\uFF0C\u53EF\u7528 `autodl gpus` \u67E5\u770B").option("--num <n>", "GPU \u6570\u91CF\uFF081-4\uFF09", "1").option("--image <uuid>", "\u955C\u50CF UUID \u6216\u516C\u5171\u955C\u50CF\u6807\u7B7E").option("--cuda <version>", "\u6700\u4F4E CUDA \u7248\u672C\uFF0C\u5982 11.8").option("--region <code...>", "\u4F18\u5148\u5730\u533A").option("--disk <gb>", "\u7CFB\u7EDF\u76D8\u6269\u5BB9 GB", "0").option("--name <name>", "\u5B9E\u4F8B\u540D\u79F0").option("--ttl <duration>", "\u5230\u671F\u81EA\u52A8\u5173\u673A\uFF08\u515C\u5E95\u4FDD\u62A4\uFF0C\u9ED8\u8BA4 4h\uFF09", "4h").option("--sync <dir>", "\u6267\u884C\u524D\u4E0A\u4F20\u7684\u672C\u5730\u76EE\u5F55").option("--workdir <dir>", "\u8FDC\u7A0B\u5DE5\u4F5C\u76EE\u5F55\uFF08\u6570\u636E\u76D8\uFF09", "/root/autodl-tmp/autodl-cli").option("--pull <remote>", "\u6267\u884C\u540E\u56DE\u4F20\u7684\u8FDC\u7A0B\u8DEF\u5F84").option("--pull-to <local>", "\u56DE\u4F20\u4EA7\u7269\u7684\u672C\u5730\u76EE\u5F55", "./autodl-output").option("--on-finish <action>", "\u7ED3\u675F\u540E\u52A8\u4F5C\uFF1Apoweroff / release / keep", "poweroff").option("--timeout <duration>", "\u8FDC\u7A0B\u547D\u4EE4\u8D85\u65F6\u65F6\u95F4").option("--min-balance <yuan>", "\u4F59\u989D\u4F4E\u4E8E\u8BE5\u503C\u65F6\u62D2\u7EDD\u521B\u5EFA").option("--env <key=value...>", "\u6CE8\u5165\u73AF\u5883\u53D8\u91CF").action(
748
+ action(async (context, commandParts, options) => {
749
+ const command = commandParts.join(" ");
750
+ const env = {};
751
+ for (const pair of options.env ?? []) {
752
+ const index = pair.indexOf("=");
753
+ if (index > 0) env[pair.slice(0, index)] = pair.slice(index + 1);
754
+ }
755
+ if (!["poweroff", "release", "keep"].includes(options.onFinish)) {
756
+ warn(`\u672A\u77E5\u7684 --on-finish "${options.onFinish}"\uFF0C\u6309 poweroff \u5904\u7406`);
757
+ }
758
+ const controller = new AbortController();
759
+ let interrupted = false;
760
+ const onSigint = () => {
761
+ if (interrupted) return;
762
+ interrupted = true;
763
+ warn("\u6536\u5230\u4E2D\u65AD\u4FE1\u53F7\uFF0C\u6B63\u5728\u5B89\u5168\u6536\u5C3E\uFF08\u5B9E\u4F8B\u5C06\u88AB\u5173\u673A\uFF09\u2026");
764
+ controller.abort();
765
+ };
766
+ process.on("SIGINT", onSigint);
767
+ process.on("SIGTERM", onSigint);
768
+ try {
769
+ const result = await runWorkflow(context.client, {
770
+ command,
771
+ gpu: options.gpu,
772
+ gpuNum: Number(options.num),
773
+ ...options.image ? { image: options.image } : {},
774
+ ...options.cuda ? { cudaFrom: options.cuda } : {},
775
+ ...options.region ? { regions: options.region } : {},
776
+ diskGb: Number(options.disk),
777
+ ...options.name ? { name: options.name } : {},
778
+ ttlSeconds: parseDuration(options.ttl),
779
+ ...options.sync ? { sync: options.sync } : {},
780
+ ...options.workdir ? { workdir: options.workdir } : {},
781
+ ...options.pull ? { pullFrom: options.pull } : {},
782
+ ...options.pullTo ? { pullTo: options.pullTo } : {},
783
+ onFinish: ["poweroff", "release", "keep"].includes(options.onFinish) ? options.onFinish : "poweroff",
784
+ ...options.timeout ? { commandTimeoutMs: parseDuration(options.timeout) * 1e3 } : {},
785
+ ...options.minBalance !== void 0 ? { minBalanceYuan: Number(options.minBalance) } : {},
786
+ ...Object.keys(env).length ? { env } : {},
787
+ signal: controller.signal
788
+ });
789
+ emit(result, () => {
790
+ note(`\u7528\u65F6 ${formatDuration(Math.round(result.durationMs / 1e3))}`);
791
+ if (result.exitCode === 0) success("\u8FDC\u7A0B\u547D\u4EE4\u6267\u884C\u6210\u529F");
792
+ else warn(`\u8FDC\u7A0B\u547D\u4EE4\u9000\u51FA\u7801 ${result.exitCode}`);
793
+ });
794
+ return result.exitCode ?? 1;
795
+ } finally {
796
+ process.off("SIGINT", onSigint);
797
+ process.off("SIGTERM", onSigint);
798
+ }
799
+ })
800
+ );
801
+ }
802
+
803
+ // src/commands/ssh.ts
804
+ import pc6 from "picocolors";
805
+ function registerSSHCommands(program) {
806
+ program.command("ssh <id>").description("\u4EA4\u4E92\u5F0F SSH \u767B\u5F55\u5B9E\u4F8B").option("--start", "\u5B9E\u4F8B\u672A\u8FD0\u884C\u65F6\u81EA\u52A8\u5F00\u673A", false).option("--print", "\u53EA\u6253\u5370\u8FDE\u63A5\u4FE1\u606F\uFF0C\u4E0D\u5EFA\u7ACB\u8FDE\u63A5", false).allowUnknownOption().action(
807
+ action(async (context, id, options, command) => {
808
+ if (options.print) {
809
+ const creds = await getCredentials(context.client, id, { autoStart: options.start });
810
+ emit(creds, () => {
811
+ printKeyValues([
812
+ ["\u547D\u4EE4", `ssh -p ${creds.port} ${creds.user}@${creds.host}`],
813
+ ["\u5BC6\u7801", creds.password]
814
+ ]);
815
+ });
816
+ return 0;
817
+ }
818
+ const extraArgs = command.args.slice(1);
819
+ return connectInteractive(context.client, id, {
820
+ autoStart: options.start,
821
+ extraArgs
822
+ });
823
+ })
824
+ );
825
+ program.command("exec <id> <command...>").description("\u5728\u5B9E\u4F8B\u4E0A\u6267\u884C\u547D\u4EE4\uFF0C\u6D41\u5F0F\u56DE\u4F20\u8F93\u51FA\uFF0C\u900F\u4F20\u8FDC\u7A0B\u9000\u51FA\u7801").option("--start", "\u5B9E\u4F8B\u672A\u8FD0\u884C\u65F6\u81EA\u52A8\u5F00\u673A", false).option("--cwd <dir>", "\u8FDC\u7A0B\u5DE5\u4F5C\u76EE\u5F55").option("--timeout <duration>", "\u547D\u4EE4\u8D85\u65F6\u65F6\u95F4\uFF0C\u5982 30m").option("--env <key=value...>", "\u6CE8\u5165\u73AF\u5883\u53D8\u91CF").option("--pty", "\u5206\u914D\u4F2A\u7EC8\u7AEF\uFF08\u9700\u8981 isatty \u7684\u7A0B\u5E8F\u7528\u5F97\u4E0A\uFF09", false).action(
826
+ action(
827
+ async (context, id, commandParts, options) => {
828
+ const remoteCommand = commandParts.join(" ");
829
+ const env = {};
830
+ for (const pair of options.env ?? []) {
831
+ const index = pair.indexOf("=");
832
+ if (index > 0) env[pair.slice(0, index)] = pair.slice(index + 1);
833
+ }
834
+ const result = await execCommand(context.client, id, remoteCommand, {
835
+ autoStart: options.start,
836
+ capture: true,
837
+ // Humans expect `autodl exec box "cat f" > out.txt` to work, so remote
838
+ // stdout goes to stdout. In --json mode (and under MCP, which sets it)
839
+ // stdout is reserved for the payload, so it is diverted to stderr.
840
+ stdout: isJson() ? process.stderr : process.stdout,
841
+ stderr: process.stderr,
842
+ pty: options.pty,
843
+ ...options.cwd ? { cwd: options.cwd } : {},
844
+ ...Object.keys(env).length ? { env } : {},
845
+ ...options.timeout ? { timeoutMs: parseDuration(options.timeout) * 1e3 } : {}
846
+ });
847
+ emit(
848
+ {
849
+ uuid: id,
850
+ command: remoteCommand,
851
+ exitCode: result.exitCode,
852
+ signal: result.signal,
853
+ stdout: result.stdout,
854
+ stderr: result.stderr
855
+ },
856
+ () => {
857
+ }
858
+ );
859
+ return result.exitCode ?? 1;
860
+ }
861
+ )
862
+ );
863
+ program.command("push <id> <local> [remote]").description("\u4E0A\u4F20\u6587\u4EF6\u6216\u76EE\u5F55\u5230\u5B9E\u4F8B\uFF08SFTP\uFF09").option("--start", "\u5B9E\u4F8B\u672A\u8FD0\u884C\u65F6\u81EA\u52A8\u5F00\u673A", false).option("--ignore <pattern...>", "\u989D\u5916\u5FFD\u7565\u7684\u8DEF\u5F84\u6A21\u5F0F").action(
864
+ action(
865
+ async (context, id, local, remote, options) => {
866
+ const target = remote ?? "/root/autodl-tmp/autodl-cli";
867
+ const summary = await push(context.client, id, local, target, {
868
+ autoStart: options.start,
869
+ ...options.ignore ? { ignore: options.ignore } : {},
870
+ onProgress: ({ file, index, total }) => note(pc6.dim(`\u2191 [${index}/${total}] ${file}`))
871
+ });
872
+ emit(
873
+ { uuid: id, local, remote: target, ...summary },
874
+ () => success(`\u5DF2\u4E0A\u4F20 ${summary.files} \u4E2A\u6587\u4EF6\uFF08${formatBytes(summary.bytes)}\uFF09\u5230 ${target}`)
875
+ );
876
+ return 0;
877
+ }
878
+ )
879
+ );
880
+ program.command("pull <id> <remote> [local]").description("\u4ECE\u5B9E\u4F8B\u4E0B\u8F7D\u6587\u4EF6\u6216\u76EE\u5F55\uFF08SFTP\uFF09").option("--start", "\u5B9E\u4F8B\u672A\u8FD0\u884C\u65F6\u81EA\u52A8\u5F00\u673A", false).action(
881
+ action(
882
+ async (context, id, remote, local, options) => {
883
+ const target = local ?? "./autodl-output";
884
+ const summary = await pull(context.client, id, remote, target, {
885
+ autoStart: options.start,
886
+ onProgress: ({ file, index, total }) => note(pc6.dim(`\u2193 [${index}/${total}] ${file}`))
887
+ });
888
+ emit(
889
+ { uuid: id, remote, local: target, ...summary },
890
+ () => success(`\u5DF2\u4E0B\u8F7D ${summary.files} \u4E2A\u6587\u4EF6\uFF08${formatBytes(summary.bytes)}\uFF09\u5230 ${target}`)
891
+ );
892
+ return 0;
893
+ }
894
+ )
895
+ );
896
+ }
897
+
898
+ // src/commands/stock.ts
899
+ import pc7 from "picocolors";
900
+ function registerStockCommand(program) {
901
+ program.command("stock").description("\u67E5\u8BE2\u5404\u5730\u533A GPU \u5B9E\u65F6\u5E93\u5B58\uFF08\u521B\u5EFA\u5B9E\u4F8B\u524D\u770B\u54EA\u91CC\u6709\u8D27\uFF09").option("--gpu <spec>", "\u53EA\u770B\u67D0\u4E2A GPU \u89C4\u683C\uFF0C\u53EF\u7528 `autodl gpus` \u67E5\u770B").option("--region <code...>", "\u53EA\u770B\u6307\u5B9A\u5730\u533A\uFF0C\u9ED8\u8BA4\u67E5\u5168\u90E8").option("--all", "\u663E\u793A\u65E0\u8D27\u7684\u5730\u533A\u4E0E\u578B\u53F7", false).action(
902
+ action(
903
+ async (context, options) => {
904
+ const spec = options.gpu ? resolveGpuSpec(options.gpu) : void 0;
905
+ if (options.gpu && !spec) {
906
+ throw new UsageError(`\u672A\u77E5\u7684 GPU \u89C4\u683C "${options.gpu}"`, {
907
+ hint: "\u8FD0\u884C `autodl gpus` \u67E5\u770B\u5B98\u65B9 API \u652F\u6301\u7684\u5168\u90E8\u89C4\u683C\u3002"
908
+ });
909
+ }
910
+ const { snapshots, failures } = await getStockByRegion(context.client, {
911
+ ...options.region?.length ? { regions: options.region } : {},
912
+ ...spec ? { gpuNames: [spec.stockName] } : {}
913
+ });
914
+ const rentable = new Map(GPU_SPECS.map((s) => [s.stockName, s]));
915
+ const rows = snapshots.flatMap(
916
+ (snapshot) => snapshot.entries.filter((entry) => options.all || entry.idle > 0).map((entry) => ({
917
+ regionId: snapshot.regionId,
918
+ regionName: snapshot.regionName,
919
+ gpuName: entry.gpuName,
920
+ gpuSpec: rentable.get(entry.gpuName)?.id ?? null,
921
+ // Only two regions accept a Pro instance; the rest are ESD-only.
922
+ proCreate: resolveRegion(snapshot.regionId)?.proCreate ?? false,
923
+ idle: entry.idle,
924
+ total: entry.total
925
+ }))
926
+ );
927
+ rows.sort((a, b) => b.idle - a.idle);
928
+ emit({ rows, failures }, () => {
929
+ if (rows.length === 0) {
930
+ note(
931
+ spec ? `${spec.displayName} \u5F53\u524D\u5728\u67E5\u8BE2\u7684\u5730\u533A\u5747\u65E0\u7A7A\u95F2` : "\u67E5\u8BE2\u8303\u56F4\u5185\u6CA1\u6709\u7A7A\u95F2 GPU"
932
+ );
933
+ } else {
934
+ printTable(
935
+ ["\u5730\u533A", "\u53EF\u5EFAPro", "GPU", "\u53EF\u79DF\u89C4\u683C", "\u7A7A\u95F2", "\u603B\u6570"],
936
+ rows.map((row) => [
937
+ row.regionName,
938
+ row.proCreate ? pc7.green("\u662F") : pc7.dim("\u5426"),
939
+ row.gpuName,
940
+ row.gpuSpec ? pc7.green(row.gpuSpec) : pc7.dim("\u2014"),
941
+ row.idle === 0 ? pc7.dim("0") : pc7.bold(String(row.idle)),
942
+ row.total
943
+ ])
944
+ );
945
+ note(
946
+ pc7.dim(
947
+ `\u300C\u53EF\u5EFAPro\u300D\u53EA\u6709 ${PRO_CREATE_REGIONS.map((r) => r.displayName).join(" / ")} \u4E3A\u300C\u662F\u300D\uFF0C\u5176\u4F59\u5730\u533A\u4EC5\u7528\u4E8E\u5F39\u6027\u90E8\u7F72\uFF0C\u5199\u8FDB --region \u4F1A\u88AB\u62D2\u7EDD\u3002`
948
+ )
949
+ );
950
+ note(
951
+ pc7.dim(
952
+ "\u300C\u53EF\u79DF\u89C4\u683C\u300D\u4E3A\u7A7A\u8868\u793A\u8BE5\u578B\u53F7\u65E0\u6CD5\u901A\u8FC7\u5B98\u65B9\u5F00\u653E API \u521B\u5EFA\uFF08\u53EA\u80FD\u5F00 Pro \u5B9E\u4F8B\u7684 7 \u4E2A\u89C4\u683C\uFF09\u3002"
953
+ )
954
+ );
955
+ note(
956
+ pc7.yellow(
957
+ "\u6CE8\u610F\uFF1A\u8FD9\u4E9B\u6570\u5B57\u6765\u81EA\u300C\u5F39\u6027\u90E8\u7F72 GPU \u5E93\u5B58\u300D\uFF0C\u4E0D\u4EE3\u8868 Pro \u5B9E\u4F8B\u7684\u53EF\u7528\u91CF\u2014\u2014\u5B9E\u6D4B\u51FA\u73B0\u8FC7\u5E93\u5B58\u663E\u793A 140 \u5F20\u7A7A\u95F2\u3001Pro \u521B\u5EFA\u5374\u63D0\u793A\u6682\u65E0\u5E93\u5B58\u7684\u60C5\u51B5\u3002\u4EC5\u4F9B\u53C2\u8003\uFF0C\u4E0D\u8981\u636E\u6B64\u5224\u65AD\u521B\u5EFA\u4E00\u5B9A\u5931\u8D25\u3002"
958
+ )
959
+ );
960
+ note(pc7.dim("\u521B\u5EFA\u5B9E\u4F8B\u65F6\u4E0D\u6307\u5B9A --region\uFF0C\u7531 AutoDL \u81EA\u884C\u8C03\u5EA6\uFF0C\u6210\u529F\u7387\u66F4\u9AD8\u3002"));
961
+ }
962
+ for (const failure of failures) warn(`${failure.regionId} \u67E5\u8BE2\u5931\u8D25\uFF1A${failure.reason}`);
963
+ });
964
+ return 0;
965
+ },
966
+ { sweep: false }
967
+ )
968
+ );
969
+ }
970
+
971
+ // src/cli.ts
972
+ function buildProgram() {
973
+ const program = new Command();
974
+ program.name("autodl").description(
975
+ [
976
+ "AutoDL \u5B9E\u4F8B\u7BA1\u7406\u547D\u4EE4\u884C\u5DE5\u5177\uFF08\u975E\u5B98\u65B9\uFF09\u3002",
977
+ "",
978
+ "\u9762\u5411\u4EBA\u548C agent \u4E24\u79CD\u4F7F\u7528\u8005\uFF1A\u6240\u6709\u547D\u4EE4\u90FD\u652F\u6301 --json \u8F93\u51FA\u4E0E\u7A33\u5B9A\u9000\u51FA\u7801\uFF0C",
979
+ "`autodl mcp` \u53EF\u76F4\u63A5\u4F5C\u4E3A MCP server \u63A5\u5165 Claude Code / Cursor / Cline\u3002"
980
+ ].join("\n")
981
+ ).version(VERSION, "-v, --version").option("--token <token>", "AutoDL \u5F00\u53D1\u8005 Token\uFF08\u4F18\u5148\u7EA7\u9AD8\u4E8E\u73AF\u5883\u53D8\u91CF\u4E0E\u914D\u7F6E\u6587\u4EF6\uFF09").option("--base-url <url>", "API \u57FA\u5730\u5740\uFF0C\u9ED8\u8BA4 https://api.autodl.com").option("--json", "\u4EE5 JSON \u8F93\u51FA\u5230 stdout\uFF08\u4EBA\u7C7B\u53EF\u8BFB\u4FE1\u606F\u8D70 stderr\uFF09", false).option("--no-color", "\u7981\u7528\u5F69\u8272\u8F93\u51FA").option("--verbose", "\u8F93\u51FA\u8C03\u8BD5\u4FE1\u606F", false).option("--lang <lang>", "\u8F93\u51FA\u8BED\u8A00\uFF1Azh / en").option("--no-sweep", "\u8DF3\u8FC7\u542F\u52A8\u65F6\u7684 TTL \u8D85\u65F6\u5B9E\u4F8B\u81EA\u52A8\u6E05\u7406").showHelpAfterError("\uFF08\u8FD0\u884C `autodl --help` \u67E5\u770B\u7528\u6CD5\uFF09");
982
+ registerAuthCommands(program);
983
+ registerAccountCommands(program);
984
+ registerInstanceCommands(program);
985
+ registerSSHCommands(program);
986
+ registerRunCommand(program);
987
+ registerDeployCommand(program);
988
+ registerGuardCommands(program);
989
+ registerCatalogCommands(program);
990
+ registerStockCommand(program);
991
+ registerTuiCommand(program);
992
+ registerMcpCommand(program);
993
+ program.addHelpText(
994
+ "after",
995
+ [
996
+ "",
997
+ "\u9000\u51FA\u7801\uFF1A",
998
+ " 0 \u6210\u529F 1 \u901A\u7528\u9519\u8BEF 2 \u53C2\u6570\u9519\u8BEF 3 Token \u65E0\u6548 4 \u8D44\u6E90\u4E0D\u5B58\u5728",
999
+ " 5 \u4F59\u989D\u4E0D\u8DB3/\u88AB\u62A4\u680F\u62E6\u622A 6 GPU \u65E0\u5E93\u5B58 7 \u8D85\u65F6 8 SSH \u5931\u8D25",
1000
+ "",
1001
+ "\u793A\u4F8B\uFF1A",
1002
+ " autodl login \u914D\u7F6E\u5F00\u53D1\u8005 Token",
1003
+ " autodl create --gpu 4090 --ttl 2h --wait \u521B\u5EFA\u5B9E\u4F8B\u5E76\u7B49\u5F85\u5C31\u7EEA",
1004
+ " autodl exec pro-xxx 'nvidia-smi' \u8FDC\u7A0B\u6267\u884C\u547D\u4EE4",
1005
+ " autodl run 'python train.py' --gpu 4090 \\",
1006
+ " --sync ./ --pull /root/autodl-tmp/autodl-cli/out \u8DD1\u5B8C\u81EA\u52A8\u5173\u673A",
1007
+ " autodl ls --json | jq '.data[].uuid' \u4F9B\u811A\u672C\u4E0E agent \u6D88\u8D39",
1008
+ "",
1009
+ "\u63A5\u5165 agent\uFF08Claude Code\uFF09\uFF1A",
1010
+ " claude mcp add autodl -- npx -y @minato-aqukin/autodl-cli mcp"
1011
+ ].join("\n")
1012
+ );
1013
+ return program;
1014
+ }
1015
+ function shouldLaunchBareTui(argv) {
1016
+ const args = argv.slice(2);
1017
+ if (args.length > 0) return false;
1018
+ if (!process.stdout.isTTY || !process.stdin.isTTY) return false;
1019
+ return true;
1020
+ }
1021
+ async function main(argv = process.argv) {
1022
+ if (shouldLaunchBareTui(argv)) {
1023
+ try {
1024
+ const { launchTui } = await import("./tui-AARJCHRA.js");
1025
+ await launchTui({});
1026
+ return;
1027
+ } catch (err) {
1028
+ const error = toAutoDLError(err);
1029
+ emitError(error);
1030
+ process.exitCode = error.exitCode;
1031
+ return;
1032
+ }
1033
+ }
1034
+ const program = buildProgram();
1035
+ try {
1036
+ await program.parseAsync(argv);
1037
+ } catch (err) {
1038
+ const asCommanderError = err;
1039
+ if (typeof asCommanderError?.code === "string" && asCommanderError.code.startsWith("commander.")) {
1040
+ process.exitCode = asCommanderError.exitCode ?? ExitCode.OK;
1041
+ return;
1042
+ }
1043
+ const error = toAutoDLError(err);
1044
+ emitError(error);
1045
+ process.exitCode = error.exitCode;
1046
+ }
1047
+ }
1048
+ main().catch((err) => {
1049
+ const error = toAutoDLError(err);
1050
+ emitError(error);
1051
+ process.exitCode = error.exitCode;
1052
+ });
1053
+ export {
1054
+ buildProgram,
1055
+ main,
1056
+ shouldLaunchBareTui
1057
+ };
1058
+ //# sourceMappingURL=cli.js.map