@artillect/cli 0.1.1 → 0.1.3
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/README.md +30 -5
- package/dist/index.js +744 -123
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -10,17 +10,42 @@ npm install -g @artillect/cli
|
|
|
10
10
|
|
|
11
11
|
Нужен Node.js 20+. Без глобальной установки: `npx @artillect/cli@latest --help`.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Быстрый старт
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
artillect auth login
|
|
17
|
-
artillect
|
|
18
|
-
|
|
17
|
+
artillect generate image --prompt "кот в космосе" --wait --open
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Примеры
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
artillect generate image --model seedream-5-pro --prompt "кот в космосе" --wait --open
|
|
24
|
+
artillect generate video grok-imagine-video --prompt "кот идёт" --wait --open
|
|
19
25
|
artillect generate video kling-3 --prompt "кот идёт" --start-image ./a.png --wait
|
|
20
|
-
artillect
|
|
26
|
+
artillect estimate image -m seedream-5-pro -p "кот"
|
|
27
|
+
artillect models list
|
|
28
|
+
artillect generate get <task_id>
|
|
29
|
+
artillect generate wait <task_id> --open
|
|
30
|
+
artillect projects list
|
|
31
|
+
artillect assets list --project 12
|
|
21
32
|
```
|
|
22
33
|
|
|
23
|
-
Ключ также можно задать через `ARTILLECT_API_KEY`. `--wait`
|
|
34
|
+
Ключ также можно задать через `ARTILLECT_API_KEY`. `--wait` скачивает файл, `--open` открывает его.
|
|
35
|
+
|
|
36
|
+
## Команды
|
|
37
|
+
|
|
38
|
+
| Команда | Зачем |
|
|
39
|
+
| -------------------- | ------------------------------------------ |
|
|
40
|
+
| `artillect auth` | login / logout / status |
|
|
41
|
+
| `artillect balance` | баланс |
|
|
42
|
+
| `artillect models` | list / get |
|
|
43
|
+
| `artillect generate` | image / video / get / wait / list / cancel |
|
|
44
|
+
| `artillect estimate` | цена без списания |
|
|
45
|
+
| `artillect upload` | загрузить файл, получить ast_… |
|
|
46
|
+
| `artillect assets` | list / get / copy |
|
|
47
|
+
| `artillect projects` | list |
|
|
48
|
+
| `artillect elements` | list / create (`@Name`) |
|
|
24
49
|
|
|
25
50
|
Обновление: `npm i -g @artillect/cli@latest`.
|
|
26
51
|
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
|
+
// src/config.ts
|
|
7
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
|
|
6
11
|
// ../artillect-sdk/dist/uploadMime.js
|
|
7
12
|
import { basename, isAbsolute, resolve } from "node:path";
|
|
8
13
|
import { access, readFile } from "node:fs/promises";
|
|
@@ -176,6 +181,8 @@ async function publicApiFetch(config, method, path, body, extraHeaders) {
|
|
|
176
181
|
Authorization: `Bearer ${config.apiKey}`,
|
|
177
182
|
Accept: "application/json"
|
|
178
183
|
};
|
|
184
|
+
if (config.clientSource)
|
|
185
|
+
headers["X-Artillect-Client-Source"] = config.clientSource;
|
|
179
186
|
let payload;
|
|
180
187
|
if (body !== void 0) {
|
|
181
188
|
headers["Content-Type"] = "application/json";
|
|
@@ -195,11 +202,14 @@ async function publicApiUploadFile(config, file) {
|
|
|
195
202
|
const form = new FormData();
|
|
196
203
|
const blob = new Blob([Buffer.from(file.bytes)], { type: contentType });
|
|
197
204
|
form.append("file", blob, file.filename);
|
|
205
|
+
if (file.projectId != null)
|
|
206
|
+
form.append("project_id", String(file.projectId));
|
|
198
207
|
const res = await fetch(url, {
|
|
199
208
|
method: "POST",
|
|
200
209
|
headers: {
|
|
201
210
|
Authorization: `Bearer ${config.apiKey}`,
|
|
202
|
-
Accept: "application/json"
|
|
211
|
+
Accept: "application/json",
|
|
212
|
+
...config.clientSource ? { "X-Artillect-Client-Source": config.clientSource } : {}
|
|
203
213
|
},
|
|
204
214
|
body: form
|
|
205
215
|
});
|
|
@@ -231,6 +241,10 @@ function listQueryPath(basePath, params) {
|
|
|
231
241
|
search.set("before_created_at", params.before_created_at);
|
|
232
242
|
if (params?.q)
|
|
233
243
|
search.set("q", params.q);
|
|
244
|
+
if (params?.scope)
|
|
245
|
+
search.set("scope", params.scope);
|
|
246
|
+
if (params?.project_id != null)
|
|
247
|
+
search.set("project_id", String(params.project_id));
|
|
234
248
|
if (params?.mine_only)
|
|
235
249
|
search.set("mine_only", "1");
|
|
236
250
|
if (params?.actor_user_id != null)
|
|
@@ -282,7 +296,11 @@ function createClient(opts) {
|
|
|
282
296
|
if (!apiKey)
|
|
283
297
|
throw new Error("apiKey is required");
|
|
284
298
|
const baseUrl = String(opts.baseUrl || "https://app.artillect.pro").trim().replace(/\/+$/, "");
|
|
285
|
-
const config = {
|
|
299
|
+
const config = {
|
|
300
|
+
apiKey,
|
|
301
|
+
baseUrl,
|
|
302
|
+
...opts.clientSource ? { clientSource: opts.clientSource } : {}
|
|
303
|
+
};
|
|
286
304
|
return {
|
|
287
305
|
config,
|
|
288
306
|
getHealth: () => publicApiFetch(config, "GET", "/api/v1/health"),
|
|
@@ -318,14 +336,17 @@ function createClient(opts) {
|
|
|
318
336
|
deleteWebhook: () => publicApiFetch(config, "DELETE", "/api/v1/webhooks"),
|
|
319
337
|
rotateWebhookSecret: () => publicApiFetch(config, "POST", "/api/v1/webhooks/rotate-secret"),
|
|
320
338
|
listWebhookDeliveries: (params) => publicApiFetch(config, "GET", listQueryPath("/api/v1/webhooks/deliveries", params)),
|
|
321
|
-
getWebhookDelivery: (eventId) => publicApiFetch(config, "GET", `/api/v1/webhooks/deliveries/${encodeURIComponent(eventId)}`)
|
|
339
|
+
getWebhookDelivery: (eventId) => publicApiFetch(config, "GET", `/api/v1/webhooks/deliveries/${encodeURIComponent(eventId)}`),
|
|
340
|
+
listAssets: (params) => publicApiFetch(config, "GET", listQueryPath("/api/v1/assets", params)),
|
|
341
|
+
getAsset: (assetId) => publicApiFetch(config, "GET", `/api/v1/assets/${encodeURIComponent(assetId)}`),
|
|
342
|
+
copyAsset: (assetId, body) => publicApiFetch(config, "POST", `/api/v1/assets/${encodeURIComponent(assetId)}`, body),
|
|
343
|
+
listProjects: (params) => publicApiFetch(config, "GET", listQueryPath("/api/v1/projects", params)),
|
|
344
|
+
listProjectElements: (projectId, params) => publicApiFetch(config, "GET", listQueryPath(`/api/v1/projects/${encodeURIComponent(String(projectId))}/elements`, params)),
|
|
345
|
+
createProjectElement: (projectId, body) => publicApiFetch(config, "POST", `/api/v1/projects/${encodeURIComponent(String(projectId))}/elements`, body)
|
|
322
346
|
};
|
|
323
347
|
}
|
|
324
348
|
|
|
325
349
|
// src/config.ts
|
|
326
|
-
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
327
|
-
import { homedir } from "node:os";
|
|
328
|
-
import { dirname, join } from "node:path";
|
|
329
350
|
var DEFAULT_BASE = "https://app.artillect.pro";
|
|
330
351
|
function configPath() {
|
|
331
352
|
const xdg = String(process.env.XDG_CONFIG_HOME || "").trim();
|
|
@@ -359,6 +380,14 @@ function requireApiKey() {
|
|
|
359
380
|
}
|
|
360
381
|
return cfg;
|
|
361
382
|
}
|
|
383
|
+
function createCliClient(cfg) {
|
|
384
|
+
const resolved = cfg ?? requireApiKey();
|
|
385
|
+
return createClient({
|
|
386
|
+
apiKey: resolved.apiKey,
|
|
387
|
+
baseUrl: resolved.baseUrl,
|
|
388
|
+
clientSource: "cli"
|
|
389
|
+
});
|
|
390
|
+
}
|
|
362
391
|
function saveCliConfig(cfg) {
|
|
363
392
|
const path = configPath();
|
|
364
393
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -411,74 +440,9 @@ function sleep(ms) {
|
|
|
411
440
|
return new Promise((r) => setTimeout(r, ms));
|
|
412
441
|
}
|
|
413
442
|
|
|
414
|
-
// src/
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const started = await postJson(baseUrl, "/api/v1/device/code", {});
|
|
418
|
-
if (started.status >= 400) {
|
|
419
|
-
throw new Error(
|
|
420
|
-
String(started.json.error || `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u043A\u043E\u0434 \u0432\u0445\u043E\u0434\u0430 (${started.status})`)
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
const deviceCode = String(started.json.device_code || "");
|
|
424
|
-
const userCode = String(started.json.user_code || "");
|
|
425
|
-
const verificationUrl = String(started.json.verification_url || "");
|
|
426
|
-
const intervalSec = Math.max(1, Number(started.json.interval) || 5);
|
|
427
|
-
const expiresIn = Number(started.json.expires_in) || 600;
|
|
428
|
-
if (!deviceCode || !verificationUrl) {
|
|
429
|
-
throw new Error("\u0412 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 device_code \u0438\u043B\u0438 verification_url");
|
|
430
|
-
}
|
|
431
|
-
console.log("\u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u0441\u0441\u044B\u043B\u043A\u0443 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435, \u0433\u0434\u0435 \u0432\u044B \u0432\u043E\u0448\u043B\u0438 \u0432 \u0410\u0440\u0442\u0438\u043B\u043B\u0435\u043A\u0442:");
|
|
432
|
-
console.log(` ${verificationUrl}`);
|
|
433
|
-
if (userCode) console.log(`\u041A\u043E\u0434 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F: ${userCode}`);
|
|
434
|
-
console.log("\u0416\u0434\u0451\u043C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435\u2026");
|
|
435
|
-
const deadline = Date.now() + expiresIn * 1e3;
|
|
436
|
-
while (Date.now() < deadline) {
|
|
437
|
-
await sleep(intervalSec * 1e3);
|
|
438
|
-
const polled = await postJson(baseUrl, "/api/v1/device/token", { device_code: deviceCode });
|
|
439
|
-
const status = String(polled.json.status || "");
|
|
440
|
-
if (status === "pending") continue;
|
|
441
|
-
if (status === "expired" || polled.status === 410) {
|
|
442
|
-
throw new Error("\u041A\u043E\u0434 \u0438\u0441\u0442\u0435\u043A. \u0421\u043D\u043E\u0432\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u0435 `artillect auth login`.");
|
|
443
|
-
}
|
|
444
|
-
const apiKey = String(polled.json.api_key || "");
|
|
445
|
-
if (status === "completed" && apiKey) {
|
|
446
|
-
const path = saveCliConfig({ apiKey, baseUrl });
|
|
447
|
-
console.log(`\u0412\u0445\u043E\u0434 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D. \u041A\u043B\u044E\u0447 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u0432 ${path}`);
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
throw new Error(String(polled.json.error || `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u043E\u0439\u0442\u0438 (${polled.status})`));
|
|
451
|
-
}
|
|
452
|
-
throw new Error("\u0412\u0440\u0435\u043C\u044F \u043D\u0430 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0438\u0441\u0442\u0435\u043A\u043B\u043E.");
|
|
453
|
-
}
|
|
454
|
-
async function authLogout() {
|
|
455
|
-
clearCliConfig();
|
|
456
|
-
console.log("\u0412\u044B \u0432\u044B\u0448\u043B\u0438.");
|
|
457
|
-
}
|
|
458
|
-
async function authStatus() {
|
|
459
|
-
const envKey = Boolean(String(process.env.ARTILLECT_API_KEY || "").trim());
|
|
460
|
-
try {
|
|
461
|
-
const cfg = requireApiKey();
|
|
462
|
-
const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
|
|
463
|
-
const me = await client.getMe();
|
|
464
|
-
if (!me.ok) {
|
|
465
|
-
console.log(
|
|
466
|
-
`\u041A\u043B\u044E\u0447 \u0437\u0430\u0434\u0430\u043D (${envKey ? "ARTILLECT_API_KEY" : configPath()}), \u043D\u043E /me \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B: ${me.error}`
|
|
467
|
-
);
|
|
468
|
-
process.exitCode = 1;
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
const body = me.body;
|
|
472
|
-
console.log(`\u0412\u044B \u0432\u043E\u0448\u043B\u0438 \u043A\u0430\u043A ${body.email ?? body.user_id ?? "ok"}`);
|
|
473
|
-
console.log(`\u0421\u0435\u0440\u0432\u0435\u0440: ${cfg.baseUrl}`);
|
|
474
|
-
} catch (err) {
|
|
475
|
-
console.log(err instanceof Error ? err.message : String(err));
|
|
476
|
-
process.exitCode = 1;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// src/generate.ts
|
|
481
|
-
import { createWriteStream } from "node:fs";
|
|
443
|
+
// src/runtime.ts
|
|
444
|
+
import { spawn } from "node:child_process";
|
|
445
|
+
import { createWriteStream, mkdirSync as mkdirSync2 } from "node:fs";
|
|
482
446
|
import { basename as basename2 } from "node:path";
|
|
483
447
|
import { Readable } from "node:stream";
|
|
484
448
|
import { pipeline } from "node:stream/promises";
|
|
@@ -514,20 +478,126 @@ function formatModelsList(body, kind) {
|
|
|
514
478
|
}
|
|
515
479
|
return lines.join("\n");
|
|
516
480
|
}
|
|
481
|
+
function findModel(body, slug) {
|
|
482
|
+
const want = String(slug || "").trim().toLowerCase();
|
|
483
|
+
if (!want) return null;
|
|
484
|
+
const groups = ["images", "video", "chat", "audio", "upscale", "mesh", "switchx"];
|
|
485
|
+
for (const kind of groups) {
|
|
486
|
+
const models2 = asRecord(body[kind]).models;
|
|
487
|
+
if (!Array.isArray(models2)) continue;
|
|
488
|
+
for (const raw of models2) {
|
|
489
|
+
const m = asRecord(raw);
|
|
490
|
+
if (String(m.slug || "").toLowerCase() === want) return { kind, model: m };
|
|
491
|
+
const aliases = Array.isArray(m.aliases) ? m.aliases : [];
|
|
492
|
+
if (aliases.some((a) => String(a || "").toLowerCase() === want)) return { kind, model: m };
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
517
497
|
|
|
518
|
-
// src/
|
|
498
|
+
// src/runtime.ts
|
|
519
499
|
function fail(result) {
|
|
520
500
|
if (result.ok) throw new Error("expected error result");
|
|
501
|
+
const code = result.code ? ` [${result.code}]` : "";
|
|
521
502
|
const extra = result.requestId ? ` (request_id ${result.requestId})` : "";
|
|
522
|
-
throw new Error(`${result.error}${extra}`);
|
|
503
|
+
throw new Error(`${result.error}${code}${extra}`);
|
|
504
|
+
}
|
|
505
|
+
function printOrJson(jsonMode, payload, lines) {
|
|
506
|
+
if (jsonMode) {
|
|
507
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
for (const line of lines) console.log(line);
|
|
511
|
+
}
|
|
512
|
+
var ASSET_ID_RE = /^ast_[0-9A-HJKMNPQRSTVWXYZ]{26}$/;
|
|
513
|
+
function isAssetId(value) {
|
|
514
|
+
return ASSET_ID_RE.test(value.trim());
|
|
515
|
+
}
|
|
516
|
+
function parseProjectId(raw) {
|
|
517
|
+
const n = Number(raw);
|
|
518
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
519
|
+
throw new Error("project_id \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0446\u0435\u043B\u044B\u043C");
|
|
520
|
+
}
|
|
521
|
+
return n;
|
|
523
522
|
}
|
|
524
|
-
|
|
523
|
+
function parseLimit(raw, fallback) {
|
|
524
|
+
if (raw == null || raw === "") return fallback;
|
|
525
|
+
const n = Number(raw);
|
|
526
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
527
|
+
throw new Error(`--limit \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0446\u0435\u043B\u044B\u043C (\u043F\u043E\u043B\u0443\u0447\u0435\u043D\u043E ${raw})`);
|
|
528
|
+
}
|
|
529
|
+
return n;
|
|
530
|
+
}
|
|
531
|
+
function extensionFromDownload(contentType, url) {
|
|
532
|
+
const type = contentType.split(";")[0]?.trim().toLowerCase() || "";
|
|
533
|
+
switch (type) {
|
|
534
|
+
case "video/mp4":
|
|
535
|
+
return "mp4";
|
|
536
|
+
case "video/webm":
|
|
537
|
+
return "webm";
|
|
538
|
+
case "video/quicktime":
|
|
539
|
+
return "mov";
|
|
540
|
+
case "image/jpeg":
|
|
541
|
+
return "jpg";
|
|
542
|
+
case "image/webp":
|
|
543
|
+
return "webp";
|
|
544
|
+
case "image/gif":
|
|
545
|
+
return "gif";
|
|
546
|
+
case "image/png":
|
|
547
|
+
return "png";
|
|
548
|
+
case "model/gltf-binary":
|
|
549
|
+
return "glb";
|
|
550
|
+
case "model/gltf+json":
|
|
551
|
+
return "gltf";
|
|
552
|
+
case "audio/mpeg":
|
|
553
|
+
return "mp3";
|
|
554
|
+
case "audio/wav":
|
|
555
|
+
case "audio/x-wav":
|
|
556
|
+
return "wav";
|
|
557
|
+
default:
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
if (url.includes(".mp4")) return "mp4";
|
|
561
|
+
if (url.includes(".webm")) return "webm";
|
|
562
|
+
if (url.includes(".glb")) return "glb";
|
|
563
|
+
if (url.includes(".webp")) return "webp";
|
|
564
|
+
if (url.includes(".jpg") || url.includes(".jpeg")) return "jpg";
|
|
565
|
+
if (url.includes(".png")) return "png";
|
|
566
|
+
return "bin";
|
|
567
|
+
}
|
|
568
|
+
function nextPageLines(body) {
|
|
569
|
+
if (body.next_cursor == null || body.next_cursor === "") return [];
|
|
570
|
+
return [`\u0434\u0430\u043B\u044C\u0448\u0435 --cursor ${body.next_cursor}`];
|
|
571
|
+
}
|
|
572
|
+
async function uploadLocal(client, filePath, opts) {
|
|
525
573
|
const file = await readLocalUploadFile(filePath);
|
|
526
|
-
const up = await client.uploadFile({
|
|
574
|
+
const up = await client.uploadFile({
|
|
575
|
+
bytes: file.bytes,
|
|
576
|
+
filename: file.filename,
|
|
577
|
+
...opts?.projectId != null ? { projectId: opts.projectId } : {}
|
|
578
|
+
});
|
|
527
579
|
if (!up.ok) fail(up);
|
|
528
|
-
const
|
|
580
|
+
const body = asRecord(up.body);
|
|
581
|
+
const url = String(body.url || "").trim();
|
|
529
582
|
if (!url) throw new Error(`\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 ${basename2(filePath)} \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B\u0430 url`);
|
|
530
|
-
return
|
|
583
|
+
return {
|
|
584
|
+
url,
|
|
585
|
+
assetId: String(body.asset_id || "").trim(),
|
|
586
|
+
kind: String(body.kind || "").trim()
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
async function resolveInputRef(client, ref, opts) {
|
|
590
|
+
const value = ref.trim();
|
|
591
|
+
if (/^ast_/i.test(value)) {
|
|
592
|
+
if (!isAssetId(value)) throw new Error(`\u043D\u0435 \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 asset id: ${value} (\u043E\u0436\u0438\u0434\u0430\u044E ast_\u2026)`);
|
|
593
|
+
const res = await client.getAsset(value);
|
|
594
|
+
if (!res.ok) fail(res);
|
|
595
|
+
const url = String(asRecord(res.body).url || "").trim();
|
|
596
|
+
if (!url) throw new Error(`asset ${value} \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B url`);
|
|
597
|
+
return url;
|
|
598
|
+
}
|
|
599
|
+
if (/^https?:\/\//i.test(value)) return value;
|
|
600
|
+
return (await uploadLocal(client, value, opts)).url;
|
|
531
601
|
}
|
|
532
602
|
function mediaUrls(body) {
|
|
533
603
|
const images = Array.isArray(body.images) ? body.images : [];
|
|
@@ -545,15 +615,42 @@ function terminalStatus(body) {
|
|
|
545
615
|
if (body.done === true) return String(body.error || "") ? "failed" : "completed";
|
|
546
616
|
return null;
|
|
547
617
|
}
|
|
548
|
-
|
|
618
|
+
var STATUS_RU = {
|
|
619
|
+
queued: "\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438",
|
|
620
|
+
pending: "\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438",
|
|
621
|
+
running: "\u0438\u0434\u0451\u0442",
|
|
622
|
+
processing: "\u0438\u0434\u0451\u0442",
|
|
623
|
+
generating: "\u0438\u0434\u0451\u0442",
|
|
624
|
+
in_progress: "\u0438\u0434\u0451\u0442",
|
|
625
|
+
finalizing: "\u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0435\u043C"
|
|
626
|
+
};
|
|
627
|
+
function formatStatus(raw) {
|
|
628
|
+
const key = String(raw || "queued").toLowerCase();
|
|
629
|
+
return STATUS_RU[key] || key;
|
|
630
|
+
}
|
|
631
|
+
function parseDurationMs(raw, fallbackMs) {
|
|
632
|
+
const text = String(raw || "").trim();
|
|
633
|
+
if (!text) return fallbackMs;
|
|
634
|
+
const m = text.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/i);
|
|
635
|
+
if (!m) throw new Error(`\u043D\u0435 \u043F\u043E\u043D\u044F\u043B \u0434\u043B\u0438\u0442\u0435\u043B\u044C\u043D\u043E\u0441\u0442\u044C: ${text} (\u043F\u0440\u0438\u043C\u0435\u0440\u044B: 90s, 10m)`);
|
|
636
|
+
const n = Number(m[1]);
|
|
637
|
+
const unit = (m[2] || "s").toLowerCase();
|
|
638
|
+
if (unit === "ms") return Math.round(n);
|
|
639
|
+
if (unit === "s") return Math.round(n * 1e3);
|
|
640
|
+
if (unit === "m") return Math.round(n * 60 * 1e3);
|
|
641
|
+
return Math.round(n * 60 * 60 * 1e3);
|
|
642
|
+
}
|
|
643
|
+
async function waitForTask(client, taskId, opts) {
|
|
644
|
+
const timeoutMs = opts.timeoutMs ?? 20 * 60 * 1e3;
|
|
549
645
|
const started = Date.now();
|
|
550
|
-
const
|
|
646
|
+
const showProgress = !opts.jsonMode && Boolean(process.stderr.isTTY);
|
|
551
647
|
while (Date.now() - started < timeoutMs) {
|
|
552
648
|
const poll = await client.getTask(taskId);
|
|
553
649
|
if (!poll.ok) fail(poll);
|
|
554
650
|
const body = asRecord(poll.body);
|
|
555
651
|
const term = terminalStatus(body);
|
|
556
652
|
if (term) {
|
|
653
|
+
if (showProgress) process.stderr.write("\n");
|
|
557
654
|
if (term !== "completed") {
|
|
558
655
|
throw new Error(
|
|
559
656
|
String(
|
|
@@ -564,36 +661,130 @@ async function waitForTask(client, taskId, jsonMode) {
|
|
|
564
661
|
return body;
|
|
565
662
|
}
|
|
566
663
|
const interval = Math.max(2, Number(body.recommended_poll_interval_sec) || 3);
|
|
567
|
-
if (
|
|
664
|
+
if (showProgress) {
|
|
568
665
|
const progress = body.progress != null ? ` ${body.progress}%` : "";
|
|
569
|
-
process.stderr.write(`\r${
|
|
666
|
+
process.stderr.write(`\r${formatStatus(body.status)}${progress} `);
|
|
570
667
|
}
|
|
571
668
|
await sleep(interval * 1e3);
|
|
572
669
|
}
|
|
573
670
|
throw new Error("\u0412\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438 \u0438\u0441\u0442\u0435\u043A\u043B\u043E.");
|
|
574
671
|
}
|
|
575
672
|
async function downloadAll(urls, outDir) {
|
|
673
|
+
const destDir = outDir.replace(/\/+$/, "") || ".";
|
|
674
|
+
mkdirSync2(destDir, { recursive: true });
|
|
576
675
|
const saved = [];
|
|
577
676
|
for (const [i, url] of urls.entries()) {
|
|
578
677
|
const res = await fetch(url);
|
|
579
678
|
if (!res.ok || !res.body) throw new Error(`\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043A\u0430\u0447\u0430\u0442\u044C \u0444\u0430\u0439\u043B (HTTP ${res.status})`);
|
|
580
|
-
const
|
|
581
|
-
const dest = `${
|
|
679
|
+
const ext = extensionFromDownload(res.headers.get("content-type") || "", url);
|
|
680
|
+
const dest = `${destDir}/artillect-${Date.now()}-${i + 1}.${ext}`;
|
|
582
681
|
await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
|
|
583
682
|
saved.push(dest);
|
|
584
683
|
}
|
|
585
684
|
return saved;
|
|
586
685
|
}
|
|
587
|
-
function
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
686
|
+
function openExternal(target) {
|
|
687
|
+
const bin = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
688
|
+
const args = process.platform === "win32" ? ["/c", "start", "", target] : [target];
|
|
689
|
+
try {
|
|
690
|
+
spawn(bin, args, { detached: true, stdio: "ignore" }).unref();
|
|
691
|
+
} catch {
|
|
591
692
|
}
|
|
592
|
-
for (const line of lines) console.log(line);
|
|
593
693
|
}
|
|
694
|
+
function openSaved(paths) {
|
|
695
|
+
for (const filePath of paths) openExternal(filePath);
|
|
696
|
+
}
|
|
697
|
+
async function finishAndSave(client, taskId, opts) {
|
|
698
|
+
const done = await waitForTask(client, taskId, {
|
|
699
|
+
jsonMode: opts.json,
|
|
700
|
+
timeoutMs: opts.timeoutMs
|
|
701
|
+
});
|
|
702
|
+
const urls = mediaUrls(done);
|
|
703
|
+
const saved = urls.length ? await downloadAll(urls, opts.out) : [];
|
|
704
|
+
if (opts.open && saved.length) openSaved(saved);
|
|
705
|
+
printOrJson(opts.json, { ...done, saved }, [
|
|
706
|
+
`\u0413\u043E\u0442\u043E\u0432\u043E ${taskId}`,
|
|
707
|
+
...saved.map((p) => `\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E ${p}`),
|
|
708
|
+
...!saved.length ? urls.map((u) => u) : [],
|
|
709
|
+
...saved.length && !opts.open ? [`\u041E\u0442\u043A\u0440\u044B\u0442\u044C: ${process.platform === "darwin" ? "open" : "xdg-open"} ${saved[0]}`] : []
|
|
710
|
+
]);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/auth.ts
|
|
714
|
+
async function authLogin() {
|
|
715
|
+
const { baseUrl } = loadCliConfig();
|
|
716
|
+
const started = await postJson(baseUrl, "/api/v1/device/code", {});
|
|
717
|
+
if (started.status >= 400) {
|
|
718
|
+
throw new Error(
|
|
719
|
+
String(started.json.error || `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u043A\u043E\u0434 \u0432\u0445\u043E\u0434\u0430 (${started.status})`)
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
const deviceCode = String(started.json.device_code || "");
|
|
723
|
+
const userCode = String(started.json.user_code || "");
|
|
724
|
+
const verificationUrl = String(started.json.verification_url || "");
|
|
725
|
+
const intervalSec = Math.max(1, Number(started.json.interval) || 5);
|
|
726
|
+
const expiresIn = Number(started.json.expires_in) || 600;
|
|
727
|
+
if (!deviceCode || !verificationUrl) {
|
|
728
|
+
throw new Error("\u0412 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 device_code \u0438\u043B\u0438 verification_url");
|
|
729
|
+
}
|
|
730
|
+
console.log("\u041E\u0442\u043A\u0440\u043E\u0439\u0442\u0435 \u0441\u0441\u044B\u043B\u043A\u0443 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435, \u0433\u0434\u0435 \u0432\u044B \u0432\u043E\u0448\u043B\u0438 \u0432 \u0410\u0440\u0442\u0438\u043B\u043B\u0435\u043A\u0442:");
|
|
731
|
+
console.log(` ${verificationUrl}`);
|
|
732
|
+
if (userCode) console.log(`\u041A\u043E\u0434 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F: ${userCode}`);
|
|
733
|
+
if (process.env.ARTILLECT_NO_BROWSER !== "1") {
|
|
734
|
+
openExternal(verificationUrl);
|
|
735
|
+
console.log("\u0416\u0434\u0451\u043C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435\u2026");
|
|
736
|
+
} else {
|
|
737
|
+
console.log("\u0416\u0434\u0451\u043C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435\u2026");
|
|
738
|
+
}
|
|
739
|
+
const deadline = Date.now() + expiresIn * 1e3;
|
|
740
|
+
while (Date.now() < deadline) {
|
|
741
|
+
await sleep(intervalSec * 1e3);
|
|
742
|
+
const polled = await postJson(baseUrl, "/api/v1/device/token", { device_code: deviceCode });
|
|
743
|
+
const status = String(polled.json.status || "");
|
|
744
|
+
if (status === "pending") continue;
|
|
745
|
+
if (status === "expired" || polled.status === 410) {
|
|
746
|
+
throw new Error("\u041A\u043E\u0434 \u0438\u0441\u0442\u0435\u043A. \u0421\u043D\u043E\u0432\u0430 \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u0435 `artillect auth login`.");
|
|
747
|
+
}
|
|
748
|
+
const apiKey = String(polled.json.api_key || "");
|
|
749
|
+
if (status === "completed" && apiKey) {
|
|
750
|
+
const path = saveCliConfig({ apiKey, baseUrl });
|
|
751
|
+
console.log(`\u0412\u0445\u043E\u0434 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D. \u041A\u043B\u044E\u0447 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u0432 ${path}`);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
throw new Error(String(polled.json.error || `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u043E\u0439\u0442\u0438 (${polled.status})`));
|
|
755
|
+
}
|
|
756
|
+
throw new Error("\u0412\u0440\u0435\u043C\u044F \u043D\u0430 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0438\u0441\u0442\u0435\u043A\u043B\u043E.");
|
|
757
|
+
}
|
|
758
|
+
async function authLogout() {
|
|
759
|
+
clearCliConfig();
|
|
760
|
+
console.log("\u0412\u044B \u0432\u044B\u0448\u043B\u0438.");
|
|
761
|
+
}
|
|
762
|
+
async function authStatus() {
|
|
763
|
+
const envKey = Boolean(String(process.env.ARTILLECT_API_KEY || "").trim());
|
|
764
|
+
try {
|
|
765
|
+
const cfg = requireApiKey();
|
|
766
|
+
const client = createCliClient(cfg);
|
|
767
|
+
const me = await client.getMe();
|
|
768
|
+
if (!me.ok) {
|
|
769
|
+
console.log(
|
|
770
|
+
`\u041A\u043B\u044E\u0447 \u0437\u0430\u0434\u0430\u043D (${envKey ? "ARTILLECT_API_KEY" : configPath()}), \u043D\u043E /me \u043D\u0435 \u043E\u0442\u0432\u0435\u0442\u0438\u043B: ${me.error}`
|
|
771
|
+
);
|
|
772
|
+
process.exitCode = 1;
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
const body = me.body;
|
|
776
|
+
console.log(`\u0412\u044B \u0432\u043E\u0448\u043B\u0438 \u043A\u0430\u043A ${body.email ?? body.user_id ?? "ok"}`);
|
|
777
|
+
console.log(`\u0421\u0435\u0440\u0432\u0435\u0440: ${cfg.baseUrl}`);
|
|
778
|
+
} catch (err) {
|
|
779
|
+
console.log(err instanceof Error ? err.message : String(err));
|
|
780
|
+
process.exitCode = 1;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/generate.ts
|
|
594
785
|
async function cmdBalance(jsonMode) {
|
|
595
786
|
const cfg = requireApiKey();
|
|
596
|
-
const client =
|
|
787
|
+
const client = createCliClient(cfg);
|
|
597
788
|
const res = await client.getBalance();
|
|
598
789
|
if (!res.ok) fail(res);
|
|
599
790
|
const body = asRecord(res.body);
|
|
@@ -617,17 +808,77 @@ async function cmdModels(kind, jsonMode) {
|
|
|
617
808
|
if (text) process.stdout.write(text.endsWith("\n") ? text : `${text}
|
|
618
809
|
`);
|
|
619
810
|
}
|
|
811
|
+
async function cmdModelsGet(slug, jsonMode) {
|
|
812
|
+
const cfg = loadCliConfig();
|
|
813
|
+
const headers = {};
|
|
814
|
+
if (cfg.apiKey) headers.Authorization = `Bearer ${cfg.apiKey}`;
|
|
815
|
+
const res = await getJson(cfg.baseUrl, "/api/v1/models", headers);
|
|
816
|
+
if (res.status >= 400) {
|
|
817
|
+
throw new Error(
|
|
818
|
+
String(res.json.error || `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u043A\u0430\u0442\u0430\u043B\u043E\u0433 \u043C\u043E\u0434\u0435\u043B\u0435\u0439 (${res.status})`)
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
const found = findModel(res.json, slug);
|
|
822
|
+
if (!found) throw new Error(`\u043C\u043E\u0434\u0435\u043B\u044C \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430: ${slug}`);
|
|
823
|
+
if (jsonMode) {
|
|
824
|
+
console.log(JSON.stringify(found.model, null, 2));
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const m = found.model;
|
|
828
|
+
const lines = [
|
|
829
|
+
`${found.kind}: ${String(m.slug || slug)}`,
|
|
830
|
+
m.display_name ? String(m.display_name) : "",
|
|
831
|
+
Array.isArray(m.tasks) ? `\u0437\u0430\u0434\u0430\u0447\u0438: ${m.tasks.join(", ")}` : "",
|
|
832
|
+
m.max_prompt_chars != null ? `\u043B\u0438\u043C\u0438\u0442 \u043F\u0440\u043E\u043C\u043F\u0442\u0430: ${m.max_prompt_chars}` : ""
|
|
833
|
+
].filter(Boolean);
|
|
834
|
+
if (m.parameters && typeof m.parameters === "object") {
|
|
835
|
+
lines.push("\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B:");
|
|
836
|
+
lines.push(JSON.stringify(m.parameters, null, 2));
|
|
837
|
+
}
|
|
838
|
+
printOrJson(false, found.model, lines);
|
|
839
|
+
}
|
|
840
|
+
async function cmdUpload(opts) {
|
|
841
|
+
const cfg = requireApiKey();
|
|
842
|
+
const client = createCliClient(cfg);
|
|
843
|
+
const projectId = opts.projectId ? parseProjectId(opts.projectId) : void 0;
|
|
844
|
+
const uploaded = await uploadLocal(
|
|
845
|
+
client,
|
|
846
|
+
opts.filePath,
|
|
847
|
+
projectId != null ? { projectId } : void 0
|
|
848
|
+
);
|
|
849
|
+
printOrJson(opts.json, { asset_id: uploaded.assetId, url: uploaded.url, kind: uploaded.kind }, [
|
|
850
|
+
uploaded.assetId || "(\u043D\u0435\u0442 asset_id)",
|
|
851
|
+
uploaded.url
|
|
852
|
+
]);
|
|
853
|
+
}
|
|
854
|
+
async function cmdEstimate(opts) {
|
|
855
|
+
const cfg = requireApiKey();
|
|
856
|
+
const client = createCliClient(cfg);
|
|
857
|
+
const body = {
|
|
858
|
+
type: opts.type,
|
|
859
|
+
prompt: opts.prompt
|
|
860
|
+
};
|
|
861
|
+
if (opts.model) body.model = opts.model;
|
|
862
|
+
const res = await client.estimate(body);
|
|
863
|
+
if (!res.ok) fail(res);
|
|
864
|
+
const payload = asRecord(res.body);
|
|
865
|
+
printOrJson(opts.json, payload, [`\u041E\u0446\u0435\u043D\u043A\u0430: ${payload.cost_tokens ?? "?"} \u0442\u043E\u043A\u0435\u043D\u043E\u0432`]);
|
|
866
|
+
}
|
|
620
867
|
async function cmdGenerateImage(opts) {
|
|
621
868
|
const cfg = requireApiKey();
|
|
622
|
-
const client =
|
|
869
|
+
const client = createCliClient(cfg);
|
|
870
|
+
const projectId = opts.projectId ? parseProjectId(opts.projectId) : void 0;
|
|
623
871
|
const inputUrls = [];
|
|
624
|
-
for (const
|
|
625
|
-
inputUrls.push(
|
|
872
|
+
for (const ref of opts.input ?? []) {
|
|
873
|
+
inputUrls.push(
|
|
874
|
+
await resolveInputRef(client, ref, projectId != null ? { projectId } : void 0)
|
|
875
|
+
);
|
|
626
876
|
}
|
|
627
877
|
const res = await client.generateImage({
|
|
628
878
|
prompt: opts.prompt,
|
|
629
879
|
...opts.model ? { model: opts.model } : {},
|
|
630
|
-
...inputUrls.length ? { input_urls: inputUrls } : {}
|
|
880
|
+
...inputUrls.length ? { input_urls: inputUrls } : {},
|
|
881
|
+
...projectId != null ? { project_id: projectId } : {}
|
|
631
882
|
});
|
|
632
883
|
if (!res.ok) fail(res);
|
|
633
884
|
const body = asRecord(res.body);
|
|
@@ -637,21 +888,33 @@ async function cmdGenerateImage(opts) {
|
|
|
637
888
|
return;
|
|
638
889
|
}
|
|
639
890
|
if (!taskId) throw new Error("\u0441\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B task_id");
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
...!saved.length ? urls.map((u) => u) : []
|
|
647
|
-
]);
|
|
891
|
+
await finishAndSave(client, taskId, {
|
|
892
|
+
json: opts.json,
|
|
893
|
+
out: opts.out,
|
|
894
|
+
open: opts.open,
|
|
895
|
+
timeoutMs: parseDurationMs(opts.waitTimeout, 20 * 60 * 1e3)
|
|
896
|
+
});
|
|
648
897
|
}
|
|
649
898
|
async function cmdGenerateVideo(opts) {
|
|
650
899
|
const cfg = requireApiKey();
|
|
651
|
-
const client =
|
|
900
|
+
const client = createCliClient(cfg);
|
|
901
|
+
const projectId = opts.projectId ? parseProjectId(opts.projectId) : void 0;
|
|
652
902
|
const body = { model: opts.model, prompt: opts.prompt };
|
|
653
|
-
if (
|
|
654
|
-
if (opts.
|
|
903
|
+
if (projectId != null) body.project_id = projectId;
|
|
904
|
+
if (opts.startImage) {
|
|
905
|
+
body.start_image_url = await resolveInputRef(
|
|
906
|
+
client,
|
|
907
|
+
opts.startImage,
|
|
908
|
+
projectId != null ? { projectId } : void 0
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
if (opts.endImage) {
|
|
912
|
+
body.end_image_url = await resolveInputRef(
|
|
913
|
+
client,
|
|
914
|
+
opts.endImage,
|
|
915
|
+
projectId != null ? { projectId } : void 0
|
|
916
|
+
);
|
|
917
|
+
}
|
|
655
918
|
const res = await client.generateVideo(body);
|
|
656
919
|
if (!res.ok) fail(res);
|
|
657
920
|
const submitted = asRecord(res.body);
|
|
@@ -661,18 +924,247 @@ async function cmdGenerateVideo(opts) {
|
|
|
661
924
|
return;
|
|
662
925
|
}
|
|
663
926
|
if (!taskId) throw new Error("\u0441\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B task_id");
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
927
|
+
await finishAndSave(client, taskId, {
|
|
928
|
+
json: opts.json,
|
|
929
|
+
out: opts.out,
|
|
930
|
+
open: opts.open,
|
|
931
|
+
timeoutMs: parseDurationMs(opts.waitTimeout, 20 * 60 * 1e3)
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
async function cmdGenerateGet(taskId, jsonMode) {
|
|
935
|
+
const cfg = requireApiKey();
|
|
936
|
+
const client = createCliClient(cfg);
|
|
937
|
+
const res = await client.getTask(taskId);
|
|
938
|
+
if (!res.ok) fail(res);
|
|
939
|
+
const body = asRecord(res.body);
|
|
940
|
+
printOrJson(jsonMode, body, [`${taskId} ${formatListStatus(body)}`, ...mediaHint(body)]);
|
|
941
|
+
}
|
|
942
|
+
async function cmdGenerateWait(opts) {
|
|
943
|
+
const cfg = requireApiKey();
|
|
944
|
+
const client = createCliClient(cfg);
|
|
945
|
+
await finishAndSave(client, opts.taskId, {
|
|
946
|
+
json: opts.json,
|
|
947
|
+
out: opts.out,
|
|
948
|
+
open: opts.open,
|
|
949
|
+
timeoutMs: parseDurationMs(opts.waitTimeout, 20 * 60 * 1e3)
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
async function cmdGenerateCancel(taskId, jsonMode) {
|
|
953
|
+
const cfg = requireApiKey();
|
|
954
|
+
const client = createCliClient(cfg);
|
|
955
|
+
const kind = inferJobKind(taskId);
|
|
956
|
+
const res = kind === "image" ? await client.cancelImageGeneration(taskId) : kind === "video" ? await client.cancelVideoGeneration(taskId) : kind === "upscale" ? await client.cancelUpscaleGeneration(taskId) : kind === "mesh" ? await client.cancelMeshGeneration(taskId) : kind === "switchx" ? await client.cancelSwitchxGeneration(taskId) : null;
|
|
957
|
+
if (!res) {
|
|
958
|
+
throw new Error(`\u043D\u0435 \u0443\u043C\u0435\u044E \u043E\u0442\u043C\u0435\u043D\u044F\u0442\u044C \u044D\u0442\u043E\u0442 \u0442\u0438\u043F \u0437\u0430\u0434\u0430\u0447\u0438 (${kind || "unknown"})`);
|
|
959
|
+
}
|
|
960
|
+
if (!res.ok) fail(res);
|
|
961
|
+
printOrJson(jsonMode, res.body, [`\u041E\u0442\u043C\u0435\u043D\u0435\u043D\u043E ${taskId}`]);
|
|
962
|
+
}
|
|
963
|
+
async function cmdGenerateList(opts) {
|
|
964
|
+
const cfg = requireApiKey();
|
|
965
|
+
const client = createCliClient(cfg);
|
|
966
|
+
const params = {
|
|
967
|
+
limit: opts.limit,
|
|
968
|
+
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
969
|
+
};
|
|
970
|
+
const res = opts.kind === "video" ? await client.listVideoGenerations(params) : await client.listImageGenerations(params);
|
|
971
|
+
if (!res.ok) fail(res);
|
|
972
|
+
const body = asRecord(res.body);
|
|
973
|
+
const items = Array.isArray(body.items) ? body.items : [];
|
|
974
|
+
const lines = items.length ? items.map((raw) => {
|
|
975
|
+
const item = asRecord(raw);
|
|
976
|
+
return `${String(item.id || "")} ${formatStatus(item.status)} ${String(item.model || "")}`;
|
|
977
|
+
}) : ["\u043F\u0443\u0441\u0442\u043E"];
|
|
978
|
+
printOrJson(opts.json, body, [...lines, ...nextPageLines(body)]);
|
|
979
|
+
}
|
|
980
|
+
function formatListStatus(body) {
|
|
981
|
+
const status = formatStatus(body.status || (body.done ? "completed" : "queued"));
|
|
982
|
+
const progress = body.progress != null ? ` ${body.progress}%` : "";
|
|
983
|
+
return `${status}${progress}`;
|
|
984
|
+
}
|
|
985
|
+
function mediaHint(body) {
|
|
986
|
+
const images = Array.isArray(body.images) ? body.images : [];
|
|
987
|
+
const videos = Array.isArray(body.videos) ? body.videos : [];
|
|
988
|
+
const urls = [...images, ...videos].map((item) => item && typeof item === "object" ? String(asRecord(item).url || "") : "").filter(Boolean);
|
|
989
|
+
const media = Array.isArray(body.media_urls) ? body.media_urls.map((u) => String(u || "")) : [];
|
|
990
|
+
return [...urls, ...media].filter(Boolean);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// src/library.ts
|
|
994
|
+
function itemsOf(body) {
|
|
995
|
+
return Array.isArray(body.items) ? body.items.map((raw) => asRecord(raw)) : [];
|
|
996
|
+
}
|
|
997
|
+
async function cmdAssetsList(opts) {
|
|
998
|
+
const cfg = requireApiKey();
|
|
999
|
+
const client = createCliClient(cfg);
|
|
1000
|
+
const res = await client.listAssets({
|
|
1001
|
+
limit: opts.limit,
|
|
1002
|
+
...opts.kind ? { kind: opts.kind } : {},
|
|
1003
|
+
...opts.scope ? { scope: opts.scope } : {},
|
|
1004
|
+
...opts.projectId ? { project_id: parseProjectId(opts.projectId) } : {},
|
|
1005
|
+
...opts.query ? { q: opts.query } : {},
|
|
1006
|
+
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
1007
|
+
});
|
|
1008
|
+
if (!res.ok) fail(res);
|
|
1009
|
+
const body = asRecord(res.body);
|
|
1010
|
+
const items = itemsOf(body);
|
|
1011
|
+
const lines = items.length ? items.map((item) => {
|
|
1012
|
+
const id = String(item.asset_id || "");
|
|
1013
|
+
const kind = String(item.kind || "");
|
|
1014
|
+
const scope = String(item.scope || "");
|
|
1015
|
+
const project = item.project_id != null ? ` project:${item.project_id}` : "";
|
|
1016
|
+
const name = String(item.filename || item.title || "");
|
|
1017
|
+
return `${id} ${kind} ${scope}${project} ${name}`.trim();
|
|
1018
|
+
}) : ["\u043F\u0443\u0441\u0442\u043E"];
|
|
1019
|
+
printOrJson(opts.json, body, [...lines, ...nextPageLines(body)]);
|
|
1020
|
+
}
|
|
1021
|
+
async function cmdAssetsGet(assetId, json) {
|
|
1022
|
+
const id = assetId.trim();
|
|
1023
|
+
if (!isAssetId(id)) throw new Error(`\u043D\u0435 \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 asset id: ${assetId} (\u043E\u0436\u0438\u0434\u0430\u044E ast_\u2026)`);
|
|
1024
|
+
const cfg = requireApiKey();
|
|
1025
|
+
const client = createCliClient(cfg);
|
|
1026
|
+
const res = await client.getAsset(id);
|
|
1027
|
+
if (!res.ok) fail(res);
|
|
1028
|
+
const body = asRecord(res.body);
|
|
1029
|
+
printOrJson(
|
|
1030
|
+
json,
|
|
1031
|
+
body,
|
|
1032
|
+
[
|
|
1033
|
+
String(body.asset_id || id),
|
|
1034
|
+
[body.kind, body.scope, body.filename].filter(Boolean).join(" "),
|
|
1035
|
+
String(body.url || "")
|
|
1036
|
+
].filter(Boolean)
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
async function cmdAssetsCopy(opts) {
|
|
1040
|
+
const id = opts.assetId.trim();
|
|
1041
|
+
if (!isAssetId(id)) throw new Error(`\u043D\u0435 \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 asset id: ${opts.assetId} (\u043E\u0436\u0438\u0434\u0430\u044E ast_\u2026)`);
|
|
1042
|
+
if (opts.personal === Boolean(opts.projectId)) {
|
|
1043
|
+
throw new Error("\u0443\u043A\u0430\u0436\u0438\u0442\u0435 --project <id> \u0438\u043B\u0438 --personal");
|
|
1044
|
+
}
|
|
1045
|
+
const cfg = requireApiKey();
|
|
1046
|
+
const client = createCliClient(cfg);
|
|
1047
|
+
const res = await client.copyAsset(
|
|
1048
|
+
id,
|
|
1049
|
+
opts.personal ? { target: "personal" } : { target: "project", project_id: parseProjectId(String(opts.projectId)) }
|
|
1050
|
+
);
|
|
1051
|
+
if (!res.ok) fail(res);
|
|
1052
|
+
const body = asRecord(res.body);
|
|
1053
|
+
const copied = String(body.asset_id || "");
|
|
1054
|
+
printOrJson(
|
|
1055
|
+
opts.json,
|
|
1056
|
+
body,
|
|
1057
|
+
[
|
|
1058
|
+
copied ? `\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u043E ${copied}` : "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u043E",
|
|
1059
|
+
body.scope ? String(body.scope) : "",
|
|
1060
|
+
body.project_id != null ? `project:${body.project_id}` : ""
|
|
1061
|
+
].filter(Boolean)
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
async function cmdProjectsList(opts) {
|
|
1065
|
+
const cfg = requireApiKey();
|
|
1066
|
+
const client = createCliClient(cfg);
|
|
1067
|
+
const res = await client.listProjects({
|
|
1068
|
+
limit: opts.limit,
|
|
1069
|
+
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
1070
|
+
});
|
|
1071
|
+
if (!res.ok) fail(res);
|
|
1072
|
+
const body = asRecord(res.body);
|
|
1073
|
+
const items = itemsOf(body);
|
|
1074
|
+
const lines = items.length ? items.map((item) => {
|
|
1075
|
+
const hub = item.is_personal_generations_hub ? " \u043B\u0438\u0447\u043D\u044B\u0435" : "";
|
|
1076
|
+
return `${item.id} ${item.name || ""} ${item.role || ""}${hub}`.trim();
|
|
1077
|
+
}) : ["\u043F\u0443\u0441\u0442\u043E"];
|
|
1078
|
+
printOrJson(opts.json, body, [...lines, ...nextPageLines(body)]);
|
|
1079
|
+
}
|
|
1080
|
+
async function cmdElementsList(opts) {
|
|
1081
|
+
const projectId = parseProjectId(opts.projectId);
|
|
1082
|
+
const cfg = requireApiKey();
|
|
1083
|
+
const client = createCliClient(cfg);
|
|
1084
|
+
const res = await client.listProjectElements(projectId, {
|
|
1085
|
+
limit: opts.limit,
|
|
1086
|
+
...opts.kinds ? { kinds: opts.kinds } : {},
|
|
1087
|
+
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
1088
|
+
});
|
|
1089
|
+
if (!res.ok) fail(res);
|
|
1090
|
+
const body = asRecord(res.body);
|
|
1091
|
+
const items = itemsOf(body);
|
|
1092
|
+
const lines = items.length ? items.map((item) => {
|
|
1093
|
+
const mention = String(item.mention || (item.name ? `@${item.name}` : ""));
|
|
1094
|
+
return `${mention} ${item.kind || ""} ${item.id || ""}`.trim();
|
|
1095
|
+
}) : ["\u043F\u0443\u0441\u0442\u043E"];
|
|
1096
|
+
printOrJson(opts.json, body, [...lines, ...nextPageLines(body)]);
|
|
1097
|
+
}
|
|
1098
|
+
async function ensureProjectAsset(client, assetId, projectId) {
|
|
1099
|
+
const res = await client.getAsset(assetId);
|
|
1100
|
+
if (!res.ok) fail(res);
|
|
1101
|
+
const body = asRecord(res.body);
|
|
1102
|
+
if (String(body.scope) === "project" && Number(body.project_id) === projectId) {
|
|
1103
|
+
return assetId;
|
|
1104
|
+
}
|
|
1105
|
+
const copied = await client.copyAsset(assetId, { target: "project", project_id: projectId });
|
|
1106
|
+
if (!copied.ok) fail(copied);
|
|
1107
|
+
const newId = String(asRecord(copied.body).asset_id || "").trim();
|
|
1108
|
+
if (!newId) throw new Error("copy \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B asset_id");
|
|
1109
|
+
return newId;
|
|
1110
|
+
}
|
|
1111
|
+
async function cmdElementsCreate(opts) {
|
|
1112
|
+
const sources = [opts.from, opts.file, opts.url].filter(Boolean);
|
|
1113
|
+
if (sources.length !== 1) {
|
|
1114
|
+
throw new Error("\u0443\u043A\u0430\u0436\u0438\u0442\u0435 \u0440\u043E\u0432\u043D\u043E \u043E\u0434\u0438\u043D \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A: --from ast_\u2026, --file \u0438\u043B\u0438 --url");
|
|
1115
|
+
}
|
|
1116
|
+
const name = opts.name.replace(/^@/, "").trim();
|
|
1117
|
+
if (!name) throw new Error("\u0443\u043A\u0430\u0436\u0438\u0442\u0435 --name \u0431\u0435\u0437 \u043F\u0443\u0441\u0442\u043E\u0433\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F");
|
|
1118
|
+
const projectId = parseProjectId(opts.projectId);
|
|
1119
|
+
const cfg = requireApiKey();
|
|
1120
|
+
const client = createCliClient(cfg);
|
|
1121
|
+
let assetId = "";
|
|
1122
|
+
let imageUrl = "";
|
|
1123
|
+
let kind = String(opts.kind || "").toLowerCase();
|
|
1124
|
+
if (opts.file) {
|
|
1125
|
+
const uploaded = await uploadLocal(client, opts.file, { projectId });
|
|
1126
|
+
assetId = uploaded.assetId;
|
|
1127
|
+
if (!assetId) throw new Error("\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B\u0430 asset_id");
|
|
1128
|
+
if (!kind) kind = uploaded.kind || "image";
|
|
1129
|
+
} else if (opts.from) {
|
|
1130
|
+
const from = opts.from.trim();
|
|
1131
|
+
if (!isAssetId(from)) throw new Error(`\u043D\u0435 \u043F\u043E\u0445\u043E\u0436\u0435 \u043D\u0430 asset id: ${opts.from} (\u043E\u0436\u0438\u0434\u0430\u044E ast_\u2026)`);
|
|
1132
|
+
assetId = await ensureProjectAsset(client, from, projectId);
|
|
1133
|
+
if (!kind) kind = "image";
|
|
1134
|
+
} else {
|
|
1135
|
+
imageUrl = String(opts.url || "").trim();
|
|
1136
|
+
if (!/^https?:\/\//i.test(imageUrl)) throw new Error("--url \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C http(s)");
|
|
1137
|
+
if (!kind) kind = "image";
|
|
1138
|
+
}
|
|
1139
|
+
if (kind !== "image" && kind !== "video") {
|
|
1140
|
+
throw new Error("kind: image \u0438\u043B\u0438 video");
|
|
1141
|
+
}
|
|
1142
|
+
const body = { name, kind };
|
|
1143
|
+
if (assetId) body.asset_id = assetId;
|
|
1144
|
+
else if (kind === "video") {
|
|
1145
|
+
body.media_json = { video_url: imageUrl };
|
|
1146
|
+
body.preview_url = imageUrl;
|
|
1147
|
+
} else {
|
|
1148
|
+
body.media_json = { images: [imageUrl] };
|
|
1149
|
+
body.preview_url = imageUrl;
|
|
1150
|
+
}
|
|
1151
|
+
const res = await client.createProjectElement(projectId, body);
|
|
1152
|
+
if (!res.ok) fail(res);
|
|
1153
|
+
const created = asRecord(asRecord(res.body).item ?? res.body);
|
|
1154
|
+
const mention = String(created.mention || (created.name ? `@${created.name}` : `@${name}`));
|
|
1155
|
+
printOrJson(
|
|
1156
|
+
opts.json,
|
|
1157
|
+
res.body,
|
|
1158
|
+
[
|
|
1159
|
+
`\u042D\u043B\u0435\u043C\u0435\u043D\u0442 ${mention}`,
|
|
1160
|
+
created.id ? String(created.id) : "",
|
|
1161
|
+
assetId ? assetId : imageUrl
|
|
1162
|
+
].filter(Boolean)
|
|
1163
|
+
);
|
|
672
1164
|
}
|
|
673
1165
|
|
|
674
1166
|
// src/updateCheck.ts
|
|
675
|
-
import { mkdirSync as
|
|
1167
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
676
1168
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
677
1169
|
|
|
678
1170
|
// src/version.ts
|
|
@@ -692,7 +1184,7 @@ function parse(raw) {
|
|
|
692
1184
|
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
693
1185
|
}
|
|
694
1186
|
function cliVersion() {
|
|
695
|
-
return String("0.1.
|
|
1187
|
+
return String("0.1.3");
|
|
696
1188
|
}
|
|
697
1189
|
function updateAvailableMessage(local, latest) {
|
|
698
1190
|
return `\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u0430 \u043D\u043E\u0432\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F @artillect/cli (${latest}, \u0443 \u0432\u0430\u0441 ${local}).
|
|
@@ -719,7 +1211,7 @@ function readLastCheck() {
|
|
|
719
1211
|
}
|
|
720
1212
|
function writeLastCheck() {
|
|
721
1213
|
try {
|
|
722
|
-
|
|
1214
|
+
mkdirSync3(dirname2(cachePath()), { recursive: true });
|
|
723
1215
|
writeFileSync2(cachePath(), `${JSON.stringify({ checkedAt: Date.now() })}
|
|
724
1216
|
`);
|
|
725
1217
|
} catch {
|
|
@@ -748,6 +1240,9 @@ async function maybeNotifyUpdate() {
|
|
|
748
1240
|
// src/index.ts
|
|
749
1241
|
var program = new Command();
|
|
750
1242
|
program.name("artillect").description("Artillect CLI \u2014 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044F \u043A\u0430\u0440\u0442\u0438\u043D\u043E\u043A \u0438 \u0432\u0438\u0434\u0435\u043E \u0438\u0437 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430.").version(cliVersion());
|
|
1243
|
+
program.showHelpAfterError();
|
|
1244
|
+
program.showSuggestionAfterError();
|
|
1245
|
+
program.addHelpText("after", "\n\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F: https://app.artillect.pro/docs/cli\n");
|
|
751
1246
|
program.hook("preAction", async () => {
|
|
752
1247
|
await maybeNotifyUpdate();
|
|
753
1248
|
});
|
|
@@ -762,20 +1257,57 @@ var models = program.command("models").description("\u041A\u0430\u0442\u0430\u04
|
|
|
762
1257
|
models.command("list").description("\u0421\u043F\u0438\u0441\u043E\u043A \u043C\u043E\u0434\u0435\u043B\u0435\u0439").option("-k, --kind <kind>", "images | video | chat | audio | upscale | mesh").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(async (opts) => {
|
|
763
1258
|
await cmdModels(opts.kind, Boolean(opts.json));
|
|
764
1259
|
});
|
|
765
|
-
|
|
766
|
-
|
|
1260
|
+
models.command("get").description("\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u043E\u0434\u043D\u043E\u0439 \u043C\u043E\u0434\u0435\u043B\u0438").argument("<slug>", "\u0441\u043B\u0430\u0433, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 gpt-image-2").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(async (slug, opts) => {
|
|
1261
|
+
await cmdModelsGet(slug, Boolean(opts.json));
|
|
1262
|
+
});
|
|
1263
|
+
program.command("upload").description("\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u0432 storage \u0438 \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C ast_\u2026").argument("<file>", "\u043F\u0443\u0442\u044C \u043A \u0444\u0430\u0439\u043B\u0443").option("--project <id>", "\u043F\u043E\u043B\u043E\u0436\u0438\u0442\u044C \u0444\u0430\u0439\u043B \u0432 storage \u043F\u0440\u043E\u0435\u043A\u0442\u0430").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(async (file, opts) => {
|
|
1264
|
+
await cmdUpload({
|
|
1265
|
+
filePath: file,
|
|
1266
|
+
projectId: opts.project,
|
|
1267
|
+
json: Boolean(opts.json)
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1270
|
+
program.command("estimate").description("\u041E\u0446\u0435\u043D\u0438\u0442\u044C \u0446\u0435\u043D\u0443 \u0431\u0435\u0437 \u0441\u043F\u0438\u0441\u0430\u043D\u0438\u044F").argument("<type>", "image | video").requiredOption("-p, --prompt <text>", "\u043F\u0440\u043E\u043C\u043F\u0442").option("-m, --model <slug>", "\u0441\u043B\u0430\u0433 \u043C\u043E\u0434\u0435\u043B\u0438").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(async (type, opts) => {
|
|
1271
|
+
const kind = type === "video" ? "video" : type === "image" ? "image" : null;
|
|
1272
|
+
if (!kind) throw new Error("type: image \u0438\u043B\u0438 video");
|
|
1273
|
+
await cmdEstimate({
|
|
1274
|
+
type: kind,
|
|
1275
|
+
model: opts.model,
|
|
1276
|
+
prompt: opts.prompt,
|
|
1277
|
+
json: Boolean(opts.json)
|
|
1278
|
+
});
|
|
1279
|
+
});
|
|
1280
|
+
var generate = program.command("generate").description("\u0417\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0438\u043B\u0438 \u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044E");
|
|
1281
|
+
generate.command("image").description("\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043F\u043E \u0442\u0435\u043A\u0441\u0442\u0443 \u0438\u043B\u0438 \u043F\u0440\u0430\u0432\u043A\u0430 \u043F\u043E \u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441\u0430\u043C").requiredOption("-p, --prompt <text>", "\u043F\u0440\u043E\u043C\u043F\u0442").option("-m, --model <slug>", "\u0441\u043B\u0430\u0433 \u043C\u043E\u0434\u0435\u043B\u0438 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E gpt-image-2)").option("-i, --input <file...>", "\u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441\u044B: \u0444\u0430\u0439\u043B, url \u0438\u043B\u0438 ast_\u2026").option("--project <id>", "\u043F\u0440\u043E\u0435\u043A\u0442 (\u0434\u043B\u044F @\u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432 \u0438 \u043B\u0435\u043D\u0442\u044B)").option("-w, --wait", "\u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0438 \u0441\u043A\u0430\u0447\u0430\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442", false).option("--wait-timeout <duration>", "\u043B\u0438\u043C\u0438\u0442 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 10m", "20m").option("-o, --out <dir>", "\u043F\u0430\u043F\u043A\u0430 \u0434\u043B\u044F \u0444\u0430\u0439\u043B\u043E\u0432", ".").option("--open", "\u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0444\u0430\u0439\u043B \u043F\u043E\u0441\u043B\u0435 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F", false).option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).addHelpText(
|
|
1282
|
+
"after",
|
|
1283
|
+
`
|
|
1284
|
+
\u041F\u0440\u0438\u043C\u0435\u0440\u044B:
|
|
1285
|
+
$ artillect generate image -p "\u043A\u043E\u0442 \u0432 \u043A\u043E\u0441\u043C\u043E\u0441\u0435" --wait --open
|
|
1286
|
+
$ artillect generate image -m seedream-5-pro -p "\u043A\u043E\u0442" -i ./ref.png --wait
|
|
1287
|
+
`
|
|
1288
|
+
).action(
|
|
767
1289
|
async (opts) => {
|
|
768
1290
|
await cmdGenerateImage({
|
|
769
1291
|
prompt: opts.prompt,
|
|
770
1292
|
model: opts.model,
|
|
771
1293
|
input: opts.input,
|
|
1294
|
+
projectId: opts.project,
|
|
772
1295
|
wait: Boolean(opts.wait),
|
|
1296
|
+
waitTimeout: opts.waitTimeout,
|
|
773
1297
|
json: Boolean(opts.json),
|
|
774
|
-
out: opts.out
|
|
1298
|
+
out: opts.out,
|
|
1299
|
+
open: Boolean(opts.open)
|
|
775
1300
|
});
|
|
776
1301
|
}
|
|
777
1302
|
);
|
|
778
|
-
generate.command("video").description("\u0412\u0438\u0434\u0435\u043E \u043F\u043E \u0442\u0435\u043A\u0441\u0442\u0443 \u0438\u043B\u0438 \u043A\u0430\u0434\u0440\u0430\u043C").argument("[model]", "\u0441\u043B\u0430\u0433 \u0432\u0438\u0434\u0435\u043E\u043C\u043E\u0434\u0435\u043B\u0438 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E kling-3-turbo)").requiredOption("-p, --prompt <text>", "\u043F\u0440\u043E\u043C\u043F\u0442").option("-m, --model <slug>", "\u0441\u043B\u0430\u0433 \u043C\u043E\u0434\u0435\u043B\u0438 (\u0435\u0441\u043B\u0438 \u043D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u043C)").option("--start-image <
|
|
1303
|
+
generate.command("video").description("\u0412\u0438\u0434\u0435\u043E \u043F\u043E \u0442\u0435\u043A\u0441\u0442\u0443 \u0438\u043B\u0438 \u043A\u0430\u0434\u0440\u0430\u043C").argument("[model]", "\u0441\u043B\u0430\u0433 \u0432\u0438\u0434\u0435\u043E\u043C\u043E\u0434\u0435\u043B\u0438 (\u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E kling-3-turbo)").requiredOption("-p, --prompt <text>", "\u043F\u0440\u043E\u043C\u043F\u0442").option("-m, --model <slug>", "\u0441\u043B\u0430\u0433 \u043C\u043E\u0434\u0435\u043B\u0438 (\u0435\u0441\u043B\u0438 \u043D\u0435 \u0443\u043A\u0430\u0437\u0430\u043D \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u043C)").option("--start-image <ref>", "\u0441\u0442\u0430\u0440\u0442\u043E\u0432\u044B\u0439 \u043A\u0430\u0434\u0440: \u0444\u0430\u0439\u043B, url \u0438\u043B\u0438 ast_\u2026").option("--end-image <ref>", "\u0444\u0438\u043D\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u0430\u0434\u0440: \u0444\u0430\u0439\u043B, url \u0438\u043B\u0438 ast_\u2026").option("--project <id>", "\u043F\u0440\u043E\u0435\u043A\u0442 (\u043D\u0443\u0436\u0435\u043D \u0434\u043B\u044F @\u044D\u043B\u0435\u043C\u0435\u043D\u0442\u043E\u0432)").option("-w, --wait", "\u0434\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u0433\u043E\u0442\u043E\u0432\u043D\u043E\u0441\u0442\u0438 \u0438 \u0441\u043A\u0430\u0447\u0430\u0442\u044C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442", false).option("--wait-timeout <duration>", "\u043B\u0438\u043C\u0438\u0442 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 10m", "20m").option("-o, --out <dir>", "\u043F\u0430\u043F\u043A\u0430 \u0434\u043B\u044F \u0444\u0430\u0439\u043B\u043E\u0432", ".").option("--open", "\u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0444\u0430\u0439\u043B \u043F\u043E\u0441\u043B\u0435 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F", false).option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).addHelpText(
|
|
1304
|
+
"after",
|
|
1305
|
+
`
|
|
1306
|
+
\u041F\u0440\u0438\u043C\u0435\u0440\u044B:
|
|
1307
|
+
$ artillect generate video grok-imagine-video -p "\u043A\u043E\u0442 \u0438\u0434\u0451\u0442" --wait --open
|
|
1308
|
+
$ artillect generate video kling-3 -p "@Hero \u0438\u0434\u0451\u0442" --project 12 --start-image ast_\u2026 --wait
|
|
1309
|
+
`
|
|
1310
|
+
).action(
|
|
779
1311
|
async (positionalModel, opts) => {
|
|
780
1312
|
const model = String(opts.model || positionalModel || "kling-3-turbo").trim();
|
|
781
1313
|
await cmdGenerateVideo({
|
|
@@ -783,9 +1315,98 @@ generate.command("video").description("\u0412\u0438\u0434\u0435\u043E \u043F\u04
|
|
|
783
1315
|
prompt: opts.prompt,
|
|
784
1316
|
startImage: opts.startImage,
|
|
785
1317
|
endImage: opts.endImage,
|
|
1318
|
+
projectId: opts.project,
|
|
786
1319
|
wait: Boolean(opts.wait),
|
|
1320
|
+
waitTimeout: opts.waitTimeout,
|
|
787
1321
|
json: Boolean(opts.json),
|
|
788
|
-
out: opts.out
|
|
1322
|
+
out: opts.out,
|
|
1323
|
+
open: Boolean(opts.open)
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
);
|
|
1327
|
+
generate.command("get").description("\u0421\u0442\u0430\u0442\u0443\u0441 \u0437\u0430\u0434\u0430\u0447\u0438").argument("<task_id>", "id \u0438\u0437 generate").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(async (taskId, opts) => {
|
|
1328
|
+
await cmdGenerateGet(taskId, Boolean(opts.json));
|
|
1329
|
+
});
|
|
1330
|
+
generate.command("wait").description("\u0414\u043E\u0436\u0434\u0430\u0442\u044C\u0441\u044F \u0437\u0430\u0434\u0430\u0447\u0438 \u0438 \u0441\u043A\u0430\u0447\u0430\u0442\u044C \u0444\u0430\u0439\u043B").argument("<task_id>", "id \u0438\u0437 generate").option("--wait-timeout <duration>", "\u043B\u0438\u043C\u0438\u0442 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 10m", "20m").option("-o, --out <dir>", "\u043F\u0430\u043F\u043A\u0430 \u0434\u043B\u044F \u0444\u0430\u0439\u043B\u043E\u0432", ".").option("--open", "\u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0444\u0430\u0439\u043B \u043F\u043E\u0441\u043B\u0435 \u0441\u043A\u0430\u0447\u0438\u0432\u0430\u043D\u0438\u044F", false).option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(
|
|
1331
|
+
async (taskId, opts) => {
|
|
1332
|
+
await cmdGenerateWait({
|
|
1333
|
+
taskId,
|
|
1334
|
+
waitTimeout: opts.waitTimeout,
|
|
1335
|
+
out: opts.out,
|
|
1336
|
+
open: Boolean(opts.open),
|
|
1337
|
+
json: Boolean(opts.json)
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
);
|
|
1341
|
+
generate.command("cancel").description("\u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443").argument("<task_id>", "id \u0438\u0437 generate").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(async (taskId, opts) => {
|
|
1342
|
+
await cmdGenerateCancel(taskId, Boolean(opts.json));
|
|
1343
|
+
});
|
|
1344
|
+
generate.command("list").description("\u041F\u043E\u0441\u043B\u0435\u0434\u043D\u0438\u0435 \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u0438").option("-k, --kind <kind>", "image | video", "image").option("--limit <n>", "\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0442\u0440\u043E\u043A", "20").option("--cursor <id>", "\u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u043E\u0433\u043E task_id").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(async (opts) => {
|
|
1345
|
+
const kind = opts.kind === "video" ? "video" : "image";
|
|
1346
|
+
await cmdGenerateList({
|
|
1347
|
+
kind,
|
|
1348
|
+
json: Boolean(opts.json),
|
|
1349
|
+
limit: parseLimit(opts.limit, 20),
|
|
1350
|
+
cursor: opts.cursor
|
|
1351
|
+
});
|
|
1352
|
+
});
|
|
1353
|
+
var assets = program.command("assets").description("Storage: ast_\u2026 \u0444\u0430\u0439\u043B\u044B \u0430\u043A\u043A\u0430\u0443\u043D\u0442\u0430 \u0438 \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432");
|
|
1354
|
+
assets.command("list").description("\u0421\u043F\u0438\u0441\u043E\u043A \u0444\u0430\u0439\u043B\u043E\u0432 \u0432 storage").option("-k, --kind <kind>", "image | video | audio | mesh").option("--scope <scope>", "all | personal | project").option("--project <id>", "\u0442\u043E\u043B\u044C\u043A\u043E storage \u043F\u0440\u043E\u0435\u043A\u0442\u0430").option("-q, --query <text>", "\u043F\u043E\u0438\u0441\u043A \u043F\u043E \u0438\u043C\u0435\u043D\u0438").option("--limit <n>", "\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0442\u0440\u043E\u043A", "20").option("--cursor <id>", "\u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u043E\u0433\u043E ast_\u2026").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(
|
|
1355
|
+
async (opts) => {
|
|
1356
|
+
await cmdAssetsList({
|
|
1357
|
+
kind: opts.kind,
|
|
1358
|
+
scope: opts.scope,
|
|
1359
|
+
projectId: opts.project,
|
|
1360
|
+
query: opts.query,
|
|
1361
|
+
cursor: opts.cursor,
|
|
1362
|
+
limit: parseLimit(opts.limit, 20),
|
|
1363
|
+
json: Boolean(opts.json)
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
);
|
|
1367
|
+
assets.command("get").description("\u041C\u0435\u0442\u0430\u0434\u0430\u043D\u043D\u044B\u0435 \u0438 \u0441\u0432\u0435\u0436\u0438\u0439 url \u043F\u043E ast_\u2026").argument("<asset_id>", "ast_\u2026").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(async (assetId, opts) => {
|
|
1368
|
+
await cmdAssetsGet(assetId, Boolean(opts.json));
|
|
1369
|
+
});
|
|
1370
|
+
assets.command("copy").description("\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0444\u0430\u0439\u043B \u0432 \u043F\u0440\u043E\u0435\u043A\u0442 \u0438\u043B\u0438 \u0432 \u043B\u0438\u0447\u043D\u044B\u0439 storage").argument("<asset_id>", "ast_\u2026").option("--project <id>", "\u0446\u0435\u043B\u044C \u2014 storage \u043F\u0440\u043E\u0435\u043A\u0442\u0430").option("--personal", "\u0446\u0435\u043B\u044C \u2014 \u043B\u0438\u0447\u043D\u044B\u0439 storage", false).option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(
|
|
1371
|
+
async (assetId, opts) => {
|
|
1372
|
+
await cmdAssetsCopy({
|
|
1373
|
+
assetId,
|
|
1374
|
+
projectId: opts.project,
|
|
1375
|
+
personal: Boolean(opts.personal),
|
|
1376
|
+
json: Boolean(opts.json)
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
);
|
|
1380
|
+
var projects = program.command("projects").description("\u041F\u0440\u043E\u0435\u043A\u0442\u044B \u0430\u043A\u043A\u0430\u0443\u043D\u0442\u0430");
|
|
1381
|
+
projects.command("list").description("\u0421\u043F\u0438\u0441\u043E\u043A \u043F\u0440\u043E\u0435\u043A\u0442\u043E\u0432").option("--limit <n>", "\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0442\u0440\u043E\u043A", "50").option("--cursor <id>", "\u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u043E\u0433\u043E id").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(async (opts) => {
|
|
1382
|
+
await cmdProjectsList({
|
|
1383
|
+
limit: parseLimit(opts.limit, 50),
|
|
1384
|
+
cursor: opts.cursor,
|
|
1385
|
+
json: Boolean(opts.json)
|
|
1386
|
+
});
|
|
1387
|
+
});
|
|
1388
|
+
var elements = program.command("elements").description("Element library \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (@Name)");
|
|
1389
|
+
elements.command("list").description("\u042D\u043B\u0435\u043C\u0435\u043D\u0442\u044B \u043F\u0440\u043E\u0435\u043A\u0442\u0430").requiredOption("--project <id>", "id \u043F\u0440\u043E\u0435\u043A\u0442\u0430").option("--kinds <kinds>", "image,video").option("--limit <n>", "\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0442\u0440\u043E\u043A", "50").option("--cursor <id>", "\u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u043F\u043E\u0441\u043B\u0435 \u044D\u0442\u043E\u0433\u043E id").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(
|
|
1390
|
+
async (opts) => {
|
|
1391
|
+
await cmdElementsList({
|
|
1392
|
+
projectId: opts.project,
|
|
1393
|
+
kinds: opts.kinds,
|
|
1394
|
+
cursor: opts.cursor,
|
|
1395
|
+
limit: parseLimit(opts.limit, 50),
|
|
1396
|
+
json: Boolean(opts.json)
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
);
|
|
1400
|
+
elements.command("create").description("\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u044D\u043B\u0435\u043C\u0435\u043D\u0442 @Name \u0438\u0437 ast_\u2026, \u0444\u0430\u0439\u043B\u0430 \u0438\u043B\u0438 url").requiredOption("--project <id>", "id \u043F\u0440\u043E\u0435\u043A\u0442\u0430").requiredOption("--name <name>", "\u0438\u043C\u044F \u0431\u0435\u0437 @").option("--from <asset_id>", "ast_\u2026 (\u043B\u0438\u0447\u043D\u044B\u0439 \u0441\u043A\u043E\u043F\u0438\u0440\u0443\u0435\u0442\u0441\u044F \u0432 \u043F\u0440\u043E\u0435\u043A\u0442)").option("--file <path>", "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0444\u0430\u0439\u043B").option("--url <https>", "\u043F\u0443\u0431\u043B\u0438\u0447\u043D\u044B\u0439 HTTPS").option("--kind <kind>", "image | video").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(
|
|
1401
|
+
async (opts) => {
|
|
1402
|
+
await cmdElementsCreate({
|
|
1403
|
+
projectId: opts.project,
|
|
1404
|
+
name: opts.name,
|
|
1405
|
+
from: opts.from,
|
|
1406
|
+
file: opts.file,
|
|
1407
|
+
url: opts.url,
|
|
1408
|
+
kind: opts.kind,
|
|
1409
|
+
json: Boolean(opts.json)
|
|
789
1410
|
});
|
|
790
1411
|
}
|
|
791
1412
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artillect/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Artillect CLI — генерация картинок и видео из терминала.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -28,19 +28,20 @@
|
|
|
28
28
|
"engines": {
|
|
29
29
|
"node": ">=20"
|
|
30
30
|
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "node build.mjs",
|
|
33
|
+
"prepack": "node build.mjs",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
35
|
+
"pack:check": "node scripts/pack-check.mjs",
|
|
36
|
+
"smoke": "node scripts/smoke.mjs"
|
|
37
|
+
},
|
|
31
38
|
"dependencies": {
|
|
32
39
|
"commander": "^14.0.0"
|
|
33
40
|
},
|
|
34
41
|
"devDependencies": {
|
|
42
|
+
"@artillect/sdk": "workspace:*",
|
|
35
43
|
"@types/node": "^22.15.0",
|
|
36
44
|
"esbuild": "^0.25.0",
|
|
37
|
-
"typescript": "^5.8.0"
|
|
38
|
-
"@artillect/sdk": "0.1.0"
|
|
39
|
-
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"build": "node build.mjs",
|
|
42
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
43
|
-
"pack:check": "node scripts/pack-check.mjs",
|
|
44
|
-
"smoke": "node scripts/smoke.mjs"
|
|
45
|
+
"typescript": "^5.8.0"
|
|
45
46
|
}
|
|
46
|
-
}
|
|
47
|
+
}
|