@artillect/cli 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +30 -5
  2. package/dist/index.js +719 -115
  3. 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 models list
18
- artillect generate image --prompt "кот в космосе" --wait
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 --help
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` дожидается результата и сохраняет файлы в текущую папку (или `--out`).
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
@@ -195,6 +195,8 @@ async function publicApiUploadFile(config, file) {
195
195
  const form = new FormData();
196
196
  const blob = new Blob([Buffer.from(file.bytes)], { type: contentType });
197
197
  form.append("file", blob, file.filename);
198
+ if (file.projectId != null)
199
+ form.append("project_id", String(file.projectId));
198
200
  const res = await fetch(url, {
199
201
  method: "POST",
200
202
  headers: {
@@ -231,6 +233,10 @@ function listQueryPath(basePath, params) {
231
233
  search.set("before_created_at", params.before_created_at);
232
234
  if (params?.q)
233
235
  search.set("q", params.q);
236
+ if (params?.scope)
237
+ search.set("scope", params.scope);
238
+ if (params?.project_id != null)
239
+ search.set("project_id", String(params.project_id));
234
240
  if (params?.mine_only)
235
241
  search.set("mine_only", "1");
236
242
  if (params?.actor_user_id != null)
@@ -318,7 +324,13 @@ function createClient(opts) {
318
324
  deleteWebhook: () => publicApiFetch(config, "DELETE", "/api/v1/webhooks"),
319
325
  rotateWebhookSecret: () => publicApiFetch(config, "POST", "/api/v1/webhooks/rotate-secret"),
320
326
  listWebhookDeliveries: (params) => publicApiFetch(config, "GET", listQueryPath("/api/v1/webhooks/deliveries", params)),
321
- getWebhookDelivery: (eventId) => publicApiFetch(config, "GET", `/api/v1/webhooks/deliveries/${encodeURIComponent(eventId)}`)
327
+ getWebhookDelivery: (eventId) => publicApiFetch(config, "GET", `/api/v1/webhooks/deliveries/${encodeURIComponent(eventId)}`),
328
+ listAssets: (params) => publicApiFetch(config, "GET", listQueryPath("/api/v1/assets", params)),
329
+ getAsset: (assetId) => publicApiFetch(config, "GET", `/api/v1/assets/${encodeURIComponent(assetId)}`),
330
+ copyAsset: (assetId, body) => publicApiFetch(config, "POST", `/api/v1/assets/${encodeURIComponent(assetId)}`, body),
331
+ listProjects: (params) => publicApiFetch(config, "GET", listQueryPath("/api/v1/projects", params)),
332
+ listProjectElements: (projectId, params) => publicApiFetch(config, "GET", listQueryPath(`/api/v1/projects/${encodeURIComponent(String(projectId))}/elements`, params)),
333
+ createProjectElement: (projectId, body) => publicApiFetch(config, "POST", `/api/v1/projects/${encodeURIComponent(String(projectId))}/elements`, body)
322
334
  };
323
335
  }
324
336
 
@@ -411,74 +423,9 @@ function sleep(ms) {
411
423
  return new Promise((r) => setTimeout(r, ms));
412
424
  }
413
425
 
414
- // src/auth.ts
415
- async function authLogin() {
416
- const { baseUrl } = loadCliConfig();
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";
426
+ // src/runtime.ts
427
+ import { spawn } from "node:child_process";
428
+ import { createWriteStream, mkdirSync as mkdirSync2 } from "node:fs";
482
429
  import { basename as basename2 } from "node:path";
483
430
  import { Readable } from "node:stream";
484
431
  import { pipeline } from "node:stream/promises";
@@ -514,20 +461,126 @@ function formatModelsList(body, kind) {
514
461
  }
515
462
  return lines.join("\n");
516
463
  }
464
+ function findModel(body, slug) {
465
+ const want = String(slug || "").trim().toLowerCase();
466
+ if (!want) return null;
467
+ const groups = ["images", "video", "chat", "audio", "upscale", "mesh", "switchx"];
468
+ for (const kind of groups) {
469
+ const models2 = asRecord(body[kind]).models;
470
+ if (!Array.isArray(models2)) continue;
471
+ for (const raw of models2) {
472
+ const m = asRecord(raw);
473
+ if (String(m.slug || "").toLowerCase() === want) return { kind, model: m };
474
+ const aliases = Array.isArray(m.aliases) ? m.aliases : [];
475
+ if (aliases.some((a) => String(a || "").toLowerCase() === want)) return { kind, model: m };
476
+ }
477
+ }
478
+ return null;
479
+ }
517
480
 
518
- // src/generate.ts
481
+ // src/runtime.ts
519
482
  function fail(result) {
520
483
  if (result.ok) throw new Error("expected error result");
484
+ const code = result.code ? ` [${result.code}]` : "";
521
485
  const extra = result.requestId ? ` (request_id ${result.requestId})` : "";
522
- throw new Error(`${result.error}${extra}`);
486
+ throw new Error(`${result.error}${code}${extra}`);
487
+ }
488
+ function printOrJson(jsonMode, payload, lines) {
489
+ if (jsonMode) {
490
+ console.log(JSON.stringify(payload, null, 2));
491
+ return;
492
+ }
493
+ for (const line of lines) console.log(line);
494
+ }
495
+ var ASSET_ID_RE = /^ast_[0-9A-HJKMNPQRSTVWXYZ]{26}$/;
496
+ function isAssetId(value) {
497
+ return ASSET_ID_RE.test(value.trim());
498
+ }
499
+ function parseProjectId(raw) {
500
+ const n = Number(raw);
501
+ if (!Number.isInteger(n) || n <= 0) {
502
+ 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");
503
+ }
504
+ return n;
523
505
  }
524
- async function uploadLocal(client, filePath) {
506
+ function parseLimit(raw, fallback) {
507
+ if (raw == null || raw === "") return fallback;
508
+ const n = Number(raw);
509
+ if (!Number.isInteger(n) || n <= 0) {
510
+ 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})`);
511
+ }
512
+ return n;
513
+ }
514
+ function extensionFromDownload(contentType, url) {
515
+ const type = contentType.split(";")[0]?.trim().toLowerCase() || "";
516
+ switch (type) {
517
+ case "video/mp4":
518
+ return "mp4";
519
+ case "video/webm":
520
+ return "webm";
521
+ case "video/quicktime":
522
+ return "mov";
523
+ case "image/jpeg":
524
+ return "jpg";
525
+ case "image/webp":
526
+ return "webp";
527
+ case "image/gif":
528
+ return "gif";
529
+ case "image/png":
530
+ return "png";
531
+ case "model/gltf-binary":
532
+ return "glb";
533
+ case "model/gltf+json":
534
+ return "gltf";
535
+ case "audio/mpeg":
536
+ return "mp3";
537
+ case "audio/wav":
538
+ case "audio/x-wav":
539
+ return "wav";
540
+ default:
541
+ break;
542
+ }
543
+ if (url.includes(".mp4")) return "mp4";
544
+ if (url.includes(".webm")) return "webm";
545
+ if (url.includes(".glb")) return "glb";
546
+ if (url.includes(".webp")) return "webp";
547
+ if (url.includes(".jpg") || url.includes(".jpeg")) return "jpg";
548
+ if (url.includes(".png")) return "png";
549
+ return "bin";
550
+ }
551
+ function nextPageLines(body) {
552
+ if (body.next_cursor == null || body.next_cursor === "") return [];
553
+ return [`\u0434\u0430\u043B\u044C\u0448\u0435 --cursor ${body.next_cursor}`];
554
+ }
555
+ async function uploadLocal(client, filePath, opts) {
525
556
  const file = await readLocalUploadFile(filePath);
526
- const up = await client.uploadFile({ bytes: file.bytes, filename: file.filename });
557
+ const up = await client.uploadFile({
558
+ bytes: file.bytes,
559
+ filename: file.filename,
560
+ ...opts?.projectId != null ? { projectId: opts.projectId } : {}
561
+ });
527
562
  if (!up.ok) fail(up);
528
- const url = String(asRecord(up.body).url || "").trim();
563
+ const body = asRecord(up.body);
564
+ const url = String(body.url || "").trim();
529
565
  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 url;
566
+ return {
567
+ url,
568
+ assetId: String(body.asset_id || "").trim(),
569
+ kind: String(body.kind || "").trim()
570
+ };
571
+ }
572
+ async function resolveInputRef(client, ref, opts) {
573
+ const value = ref.trim();
574
+ if (/^ast_/i.test(value)) {
575
+ 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)`);
576
+ const res = await client.getAsset(value);
577
+ if (!res.ok) fail(res);
578
+ const url = String(asRecord(res.body).url || "").trim();
579
+ if (!url) throw new Error(`asset ${value} \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B url`);
580
+ return url;
581
+ }
582
+ if (/^https?:\/\//i.test(value)) return value;
583
+ return (await uploadLocal(client, value, opts)).url;
531
584
  }
532
585
  function mediaUrls(body) {
533
586
  const images = Array.isArray(body.images) ? body.images : [];
@@ -545,15 +598,42 @@ function terminalStatus(body) {
545
598
  if (body.done === true) return String(body.error || "") ? "failed" : "completed";
546
599
  return null;
547
600
  }
548
- async function waitForTask(client, taskId, jsonMode) {
601
+ var STATUS_RU = {
602
+ queued: "\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438",
603
+ pending: "\u0432 \u043E\u0447\u0435\u0440\u0435\u0434\u0438",
604
+ running: "\u0438\u0434\u0451\u0442",
605
+ processing: "\u0438\u0434\u0451\u0442",
606
+ generating: "\u0438\u0434\u0451\u0442",
607
+ in_progress: "\u0438\u0434\u0451\u0442",
608
+ finalizing: "\u0441\u043E\u0445\u0440\u0430\u043D\u044F\u0435\u043C"
609
+ };
610
+ function formatStatus(raw) {
611
+ const key = String(raw || "queued").toLowerCase();
612
+ return STATUS_RU[key] || key;
613
+ }
614
+ function parseDurationMs(raw, fallbackMs) {
615
+ const text = String(raw || "").trim();
616
+ if (!text) return fallbackMs;
617
+ const m = text.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/i);
618
+ 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)`);
619
+ const n = Number(m[1]);
620
+ const unit = (m[2] || "s").toLowerCase();
621
+ if (unit === "ms") return Math.round(n);
622
+ if (unit === "s") return Math.round(n * 1e3);
623
+ if (unit === "m") return Math.round(n * 60 * 1e3);
624
+ return Math.round(n * 60 * 60 * 1e3);
625
+ }
626
+ async function waitForTask(client, taskId, opts) {
627
+ const timeoutMs = opts.timeoutMs ?? 20 * 60 * 1e3;
549
628
  const started = Date.now();
550
- const timeoutMs = 20 * 60 * 1e3;
629
+ const showProgress = !opts.jsonMode && Boolean(process.stderr.isTTY);
551
630
  while (Date.now() - started < timeoutMs) {
552
631
  const poll = await client.getTask(taskId);
553
632
  if (!poll.ok) fail(poll);
554
633
  const body = asRecord(poll.body);
555
634
  const term = terminalStatus(body);
556
635
  if (term) {
636
+ if (showProgress) process.stderr.write("\n");
557
637
  if (term !== "completed") {
558
638
  throw new Error(
559
639
  String(
@@ -564,33 +644,127 @@ async function waitForTask(client, taskId, jsonMode) {
564
644
  return body;
565
645
  }
566
646
  const interval = Math.max(2, Number(body.recommended_poll_interval_sec) || 3);
567
- if (!jsonMode) {
647
+ if (showProgress) {
568
648
  const progress = body.progress != null ? ` ${body.progress}%` : "";
569
- process.stderr.write(`\r${String(body.status || "queued")}${progress} `);
649
+ process.stderr.write(`\r${formatStatus(body.status)}${progress} `);
570
650
  }
571
651
  await sleep(interval * 1e3);
572
652
  }
573
653
  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
654
  }
575
655
  async function downloadAll(urls, outDir) {
656
+ const destDir = outDir.replace(/\/+$/, "") || ".";
657
+ mkdirSync2(destDir, { recursive: true });
576
658
  const saved = [];
577
659
  for (const [i, url] of urls.entries()) {
578
660
  const res = await fetch(url);
579
661
  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 extGuess = url.includes(".mp4") ? "mp4" : url.includes(".webm") ? "webm" : "png";
581
- const dest = `${outDir.replace(/\/+$/, "") || "."}/artillect-${Date.now()}-${i + 1}.${extGuess}`;
662
+ const ext = extensionFromDownload(res.headers.get("content-type") || "", url);
663
+ const dest = `${destDir}/artillect-${Date.now()}-${i + 1}.${ext}`;
582
664
  await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
583
665
  saved.push(dest);
584
666
  }
585
667
  return saved;
586
668
  }
587
- function printOrJson(jsonMode, payload, lines) {
588
- if (jsonMode) {
589
- console.log(JSON.stringify(payload, null, 2));
590
- return;
669
+ function openExternal(target) {
670
+ const bin = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
671
+ const args = process.platform === "win32" ? ["/c", "start", "", target] : [target];
672
+ try {
673
+ spawn(bin, args, { detached: true, stdio: "ignore" }).unref();
674
+ } catch {
591
675
  }
592
- for (const line of lines) console.log(line);
593
676
  }
677
+ function openSaved(paths) {
678
+ for (const filePath of paths) openExternal(filePath);
679
+ }
680
+ async function finishAndSave(client, taskId, opts) {
681
+ const done = await waitForTask(client, taskId, {
682
+ jsonMode: opts.json,
683
+ timeoutMs: opts.timeoutMs
684
+ });
685
+ const urls = mediaUrls(done);
686
+ const saved = urls.length ? await downloadAll(urls, opts.out) : [];
687
+ if (opts.open && saved.length) openSaved(saved);
688
+ printOrJson(opts.json, { ...done, saved }, [
689
+ `\u0413\u043E\u0442\u043E\u0432\u043E ${taskId}`,
690
+ ...saved.map((p) => `\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E ${p}`),
691
+ ...!saved.length ? urls.map((u) => u) : [],
692
+ ...saved.length && !opts.open ? [`\u041E\u0442\u043A\u0440\u044B\u0442\u044C: ${process.platform === "darwin" ? "open" : "xdg-open"} ${saved[0]}`] : []
693
+ ]);
694
+ }
695
+
696
+ // src/auth.ts
697
+ async function authLogin() {
698
+ const { baseUrl } = loadCliConfig();
699
+ const started = await postJson(baseUrl, "/api/v1/device/code", {});
700
+ if (started.status >= 400) {
701
+ throw new Error(
702
+ 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})`)
703
+ );
704
+ }
705
+ const deviceCode = String(started.json.device_code || "");
706
+ const userCode = String(started.json.user_code || "");
707
+ const verificationUrl = String(started.json.verification_url || "");
708
+ const intervalSec = Math.max(1, Number(started.json.interval) || 5);
709
+ const expiresIn = Number(started.json.expires_in) || 600;
710
+ if (!deviceCode || !verificationUrl) {
711
+ throw new Error("\u0412 \u043E\u0442\u0432\u0435\u0442\u0435 \u043D\u0435\u0442 device_code \u0438\u043B\u0438 verification_url");
712
+ }
713
+ 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:");
714
+ console.log(` ${verificationUrl}`);
715
+ if (userCode) console.log(`\u041A\u043E\u0434 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F: ${userCode}`);
716
+ if (process.env.ARTILLECT_NO_BROWSER !== "1") {
717
+ openExternal(verificationUrl);
718
+ console.log("\u0416\u0434\u0451\u043C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435\u2026");
719
+ } else {
720
+ console.log("\u0416\u0434\u0451\u043C \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435\u2026");
721
+ }
722
+ const deadline = Date.now() + expiresIn * 1e3;
723
+ while (Date.now() < deadline) {
724
+ await sleep(intervalSec * 1e3);
725
+ const polled = await postJson(baseUrl, "/api/v1/device/token", { device_code: deviceCode });
726
+ const status = String(polled.json.status || "");
727
+ if (status === "pending") continue;
728
+ if (status === "expired" || polled.status === 410) {
729
+ 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`.");
730
+ }
731
+ const apiKey = String(polled.json.api_key || "");
732
+ if (status === "completed" && apiKey) {
733
+ const path = saveCliConfig({ apiKey, baseUrl });
734
+ 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}`);
735
+ return;
736
+ }
737
+ throw new Error(String(polled.json.error || `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u043E\u0439\u0442\u0438 (${polled.status})`));
738
+ }
739
+ 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.");
740
+ }
741
+ async function authLogout() {
742
+ clearCliConfig();
743
+ console.log("\u0412\u044B \u0432\u044B\u0448\u043B\u0438.");
744
+ }
745
+ async function authStatus() {
746
+ const envKey = Boolean(String(process.env.ARTILLECT_API_KEY || "").trim());
747
+ try {
748
+ const cfg = requireApiKey();
749
+ const client = createClient({ apiKey: cfg.apiKey, baseUrl: cfg.baseUrl });
750
+ const me = await client.getMe();
751
+ if (!me.ok) {
752
+ console.log(
753
+ `\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}`
754
+ );
755
+ process.exitCode = 1;
756
+ return;
757
+ }
758
+ const body = me.body;
759
+ console.log(`\u0412\u044B \u0432\u043E\u0448\u043B\u0438 \u043A\u0430\u043A ${body.email ?? body.user_id ?? "ok"}`);
760
+ console.log(`\u0421\u0435\u0440\u0432\u0435\u0440: ${cfg.baseUrl}`);
761
+ } catch (err) {
762
+ console.log(err instanceof Error ? err.message : String(err));
763
+ process.exitCode = 1;
764
+ }
765
+ }
766
+
767
+ // src/generate.ts
594
768
  async function cmdBalance(jsonMode) {
595
769
  const cfg = requireApiKey();
596
770
  const client = createClient(cfg);
@@ -617,17 +791,77 @@ async function cmdModels(kind, jsonMode) {
617
791
  if (text) process.stdout.write(text.endsWith("\n") ? text : `${text}
618
792
  `);
619
793
  }
794
+ async function cmdModelsGet(slug, jsonMode) {
795
+ const cfg = loadCliConfig();
796
+ const headers = {};
797
+ if (cfg.apiKey) headers.Authorization = `Bearer ${cfg.apiKey}`;
798
+ const res = await getJson(cfg.baseUrl, "/api/v1/models", headers);
799
+ if (res.status >= 400) {
800
+ throw new Error(
801
+ 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})`)
802
+ );
803
+ }
804
+ const found = findModel(res.json, slug);
805
+ if (!found) throw new Error(`\u043C\u043E\u0434\u0435\u043B\u044C \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430: ${slug}`);
806
+ if (jsonMode) {
807
+ console.log(JSON.stringify(found.model, null, 2));
808
+ return;
809
+ }
810
+ const m = found.model;
811
+ const lines = [
812
+ `${found.kind}: ${String(m.slug || slug)}`,
813
+ m.display_name ? String(m.display_name) : "",
814
+ Array.isArray(m.tasks) ? `\u0437\u0430\u0434\u0430\u0447\u0438: ${m.tasks.join(", ")}` : "",
815
+ m.max_prompt_chars != null ? `\u043B\u0438\u043C\u0438\u0442 \u043F\u0440\u043E\u043C\u043F\u0442\u0430: ${m.max_prompt_chars}` : ""
816
+ ].filter(Boolean);
817
+ if (m.parameters && typeof m.parameters === "object") {
818
+ lines.push("\u043F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B:");
819
+ lines.push(JSON.stringify(m.parameters, null, 2));
820
+ }
821
+ printOrJson(false, found.model, lines);
822
+ }
823
+ async function cmdUpload(opts) {
824
+ const cfg = requireApiKey();
825
+ const client = createClient(cfg);
826
+ const projectId = opts.projectId ? parseProjectId(opts.projectId) : void 0;
827
+ const uploaded = await uploadLocal(
828
+ client,
829
+ opts.filePath,
830
+ projectId != null ? { projectId } : void 0
831
+ );
832
+ printOrJson(opts.json, { asset_id: uploaded.assetId, url: uploaded.url, kind: uploaded.kind }, [
833
+ uploaded.assetId || "(\u043D\u0435\u0442 asset_id)",
834
+ uploaded.url
835
+ ]);
836
+ }
837
+ async function cmdEstimate(opts) {
838
+ const cfg = requireApiKey();
839
+ const client = createClient(cfg);
840
+ const body = {
841
+ type: opts.type,
842
+ prompt: opts.prompt
843
+ };
844
+ if (opts.model) body.model = opts.model;
845
+ const res = await client.estimate(body);
846
+ if (!res.ok) fail(res);
847
+ const payload = asRecord(res.body);
848
+ printOrJson(opts.json, payload, [`\u041E\u0446\u0435\u043D\u043A\u0430: ${payload.cost_tokens ?? "?"} \u0442\u043E\u043A\u0435\u043D\u043E\u0432`]);
849
+ }
620
850
  async function cmdGenerateImage(opts) {
621
851
  const cfg = requireApiKey();
622
852
  const client = createClient(cfg);
853
+ const projectId = opts.projectId ? parseProjectId(opts.projectId) : void 0;
623
854
  const inputUrls = [];
624
- for (const path of opts.input ?? []) {
625
- inputUrls.push(await uploadLocal(client, path));
855
+ for (const ref of opts.input ?? []) {
856
+ inputUrls.push(
857
+ await resolveInputRef(client, ref, projectId != null ? { projectId } : void 0)
858
+ );
626
859
  }
627
860
  const res = await client.generateImage({
628
861
  prompt: opts.prompt,
629
862
  ...opts.model ? { model: opts.model } : {},
630
- ...inputUrls.length ? { input_urls: inputUrls } : {}
863
+ ...inputUrls.length ? { input_urls: inputUrls } : {},
864
+ ...projectId != null ? { project_id: projectId } : {}
631
865
  });
632
866
  if (!res.ok) fail(res);
633
867
  const body = asRecord(res.body);
@@ -637,21 +871,33 @@ async function cmdGenerateImage(opts) {
637
871
  return;
638
872
  }
639
873
  if (!taskId) throw new Error("\u0441\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B task_id");
640
- const done = await waitForTask(client, taskId, opts.json);
641
- const urls = mediaUrls(done);
642
- const saved = urls.length ? await downloadAll(urls, opts.out) : [];
643
- printOrJson(opts.json, { ...done, saved }, [
644
- `\u0413\u043E\u0442\u043E\u0432\u043E ${taskId}`,
645
- ...saved.map((p) => `\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E ${p}`),
646
- ...!saved.length ? urls.map((u) => u) : []
647
- ]);
874
+ await finishAndSave(client, taskId, {
875
+ json: opts.json,
876
+ out: opts.out,
877
+ open: opts.open,
878
+ timeoutMs: parseDurationMs(opts.waitTimeout, 20 * 60 * 1e3)
879
+ });
648
880
  }
649
881
  async function cmdGenerateVideo(opts) {
650
882
  const cfg = requireApiKey();
651
883
  const client = createClient(cfg);
884
+ const projectId = opts.projectId ? parseProjectId(opts.projectId) : void 0;
652
885
  const body = { model: opts.model, prompt: opts.prompt };
653
- if (opts.startImage) body.start_image_url = await uploadLocal(client, opts.startImage);
654
- if (opts.endImage) body.end_image_url = await uploadLocal(client, opts.endImage);
886
+ if (projectId != null) body.project_id = projectId;
887
+ if (opts.startImage) {
888
+ body.start_image_url = await resolveInputRef(
889
+ client,
890
+ opts.startImage,
891
+ projectId != null ? { projectId } : void 0
892
+ );
893
+ }
894
+ if (opts.endImage) {
895
+ body.end_image_url = await resolveInputRef(
896
+ client,
897
+ opts.endImage,
898
+ projectId != null ? { projectId } : void 0
899
+ );
900
+ }
655
901
  const res = await client.generateVideo(body);
656
902
  if (!res.ok) fail(res);
657
903
  const submitted = asRecord(res.body);
@@ -661,18 +907,247 @@ async function cmdGenerateVideo(opts) {
661
907
  return;
662
908
  }
663
909
  if (!taskId) throw new Error("\u0441\u0435\u0440\u0432\u0435\u0440 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B task_id");
664
- const done = await waitForTask(client, taskId, opts.json);
665
- const urls = mediaUrls(done);
666
- const saved = urls.length ? await downloadAll(urls, opts.out) : [];
667
- printOrJson(opts.json, { ...done, saved }, [
668
- `\u0413\u043E\u0442\u043E\u0432\u043E ${taskId}`,
669
- ...saved.map((p) => `\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u043E ${p}`),
670
- ...!saved.length ? urls.map((u) => u) : []
671
- ]);
910
+ await finishAndSave(client, taskId, {
911
+ json: opts.json,
912
+ out: opts.out,
913
+ open: opts.open,
914
+ timeoutMs: parseDurationMs(opts.waitTimeout, 20 * 60 * 1e3)
915
+ });
916
+ }
917
+ async function cmdGenerateGet(taskId, jsonMode) {
918
+ const cfg = requireApiKey();
919
+ const client = createClient(cfg);
920
+ const res = await client.getTask(taskId);
921
+ if (!res.ok) fail(res);
922
+ const body = asRecord(res.body);
923
+ printOrJson(jsonMode, body, [`${taskId} ${formatListStatus(body)}`, ...mediaHint(body)]);
924
+ }
925
+ async function cmdGenerateWait(opts) {
926
+ const cfg = requireApiKey();
927
+ const client = createClient(cfg);
928
+ await finishAndSave(client, opts.taskId, {
929
+ json: opts.json,
930
+ out: opts.out,
931
+ open: opts.open,
932
+ timeoutMs: parseDurationMs(opts.waitTimeout, 20 * 60 * 1e3)
933
+ });
934
+ }
935
+ async function cmdGenerateCancel(taskId, jsonMode) {
936
+ const cfg = requireApiKey();
937
+ const client = createClient(cfg);
938
+ const kind = inferJobKind(taskId);
939
+ 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;
940
+ if (!res) {
941
+ 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"})`);
942
+ }
943
+ if (!res.ok) fail(res);
944
+ printOrJson(jsonMode, res.body, [`\u041E\u0442\u043C\u0435\u043D\u0435\u043D\u043E ${taskId}`]);
945
+ }
946
+ async function cmdGenerateList(opts) {
947
+ const cfg = requireApiKey();
948
+ const client = createClient(cfg);
949
+ const params = {
950
+ limit: opts.limit,
951
+ ...opts.cursor ? { cursor: opts.cursor } : {}
952
+ };
953
+ const res = opts.kind === "video" ? await client.listVideoGenerations(params) : await client.listImageGenerations(params);
954
+ if (!res.ok) fail(res);
955
+ const body = asRecord(res.body);
956
+ const items = Array.isArray(body.items) ? body.items : [];
957
+ const lines = items.length ? items.map((raw) => {
958
+ const item = asRecord(raw);
959
+ return `${String(item.id || "")} ${formatStatus(item.status)} ${String(item.model || "")}`;
960
+ }) : ["\u043F\u0443\u0441\u0442\u043E"];
961
+ printOrJson(opts.json, body, [...lines, ...nextPageLines(body)]);
962
+ }
963
+ function formatListStatus(body) {
964
+ const status = formatStatus(body.status || (body.done ? "completed" : "queued"));
965
+ const progress = body.progress != null ? ` ${body.progress}%` : "";
966
+ return `${status}${progress}`;
967
+ }
968
+ function mediaHint(body) {
969
+ const images = Array.isArray(body.images) ? body.images : [];
970
+ const videos = Array.isArray(body.videos) ? body.videos : [];
971
+ const urls = [...images, ...videos].map((item) => item && typeof item === "object" ? String(asRecord(item).url || "") : "").filter(Boolean);
972
+ const media = Array.isArray(body.media_urls) ? body.media_urls.map((u) => String(u || "")) : [];
973
+ return [...urls, ...media].filter(Boolean);
974
+ }
975
+
976
+ // src/library.ts
977
+ function itemsOf(body) {
978
+ return Array.isArray(body.items) ? body.items.map((raw) => asRecord(raw)) : [];
979
+ }
980
+ async function cmdAssetsList(opts) {
981
+ const cfg = requireApiKey();
982
+ const client = createClient(cfg);
983
+ const res = await client.listAssets({
984
+ limit: opts.limit,
985
+ ...opts.kind ? { kind: opts.kind } : {},
986
+ ...opts.scope ? { scope: opts.scope } : {},
987
+ ...opts.projectId ? { project_id: parseProjectId(opts.projectId) } : {},
988
+ ...opts.query ? { q: opts.query } : {},
989
+ ...opts.cursor ? { cursor: opts.cursor } : {}
990
+ });
991
+ if (!res.ok) fail(res);
992
+ const body = asRecord(res.body);
993
+ const items = itemsOf(body);
994
+ const lines = items.length ? items.map((item) => {
995
+ const id = String(item.asset_id || "");
996
+ const kind = String(item.kind || "");
997
+ const scope = String(item.scope || "");
998
+ const project = item.project_id != null ? ` project:${item.project_id}` : "";
999
+ const name = String(item.filename || item.title || "");
1000
+ return `${id} ${kind} ${scope}${project} ${name}`.trim();
1001
+ }) : ["\u043F\u0443\u0441\u0442\u043E"];
1002
+ printOrJson(opts.json, body, [...lines, ...nextPageLines(body)]);
1003
+ }
1004
+ async function cmdAssetsGet(assetId, json) {
1005
+ const id = assetId.trim();
1006
+ 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)`);
1007
+ const cfg = requireApiKey();
1008
+ const client = createClient(cfg);
1009
+ const res = await client.getAsset(id);
1010
+ if (!res.ok) fail(res);
1011
+ const body = asRecord(res.body);
1012
+ printOrJson(
1013
+ json,
1014
+ body,
1015
+ [
1016
+ String(body.asset_id || id),
1017
+ [body.kind, body.scope, body.filename].filter(Boolean).join(" "),
1018
+ String(body.url || "")
1019
+ ].filter(Boolean)
1020
+ );
1021
+ }
1022
+ async function cmdAssetsCopy(opts) {
1023
+ const id = opts.assetId.trim();
1024
+ 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)`);
1025
+ if (opts.personal === Boolean(opts.projectId)) {
1026
+ throw new Error("\u0443\u043A\u0430\u0436\u0438\u0442\u0435 --project <id> \u0438\u043B\u0438 --personal");
1027
+ }
1028
+ const cfg = requireApiKey();
1029
+ const client = createClient(cfg);
1030
+ const res = await client.copyAsset(
1031
+ id,
1032
+ opts.personal ? { target: "personal" } : { target: "project", project_id: parseProjectId(String(opts.projectId)) }
1033
+ );
1034
+ if (!res.ok) fail(res);
1035
+ const body = asRecord(res.body);
1036
+ const copied = String(body.asset_id || "");
1037
+ printOrJson(
1038
+ opts.json,
1039
+ body,
1040
+ [
1041
+ copied ? `\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u043E ${copied}` : "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u043E",
1042
+ body.scope ? String(body.scope) : "",
1043
+ body.project_id != null ? `project:${body.project_id}` : ""
1044
+ ].filter(Boolean)
1045
+ );
1046
+ }
1047
+ async function cmdProjectsList(opts) {
1048
+ const cfg = requireApiKey();
1049
+ const client = createClient(cfg);
1050
+ const res = await client.listProjects({
1051
+ limit: opts.limit,
1052
+ ...opts.cursor ? { cursor: opts.cursor } : {}
1053
+ });
1054
+ if (!res.ok) fail(res);
1055
+ const body = asRecord(res.body);
1056
+ const items = itemsOf(body);
1057
+ const lines = items.length ? items.map((item) => {
1058
+ const hub = item.is_personal_generations_hub ? " \u043B\u0438\u0447\u043D\u044B\u0435" : "";
1059
+ return `${item.id} ${item.name || ""} ${item.role || ""}${hub}`.trim();
1060
+ }) : ["\u043F\u0443\u0441\u0442\u043E"];
1061
+ printOrJson(opts.json, body, [...lines, ...nextPageLines(body)]);
1062
+ }
1063
+ async function cmdElementsList(opts) {
1064
+ const projectId = parseProjectId(opts.projectId);
1065
+ const cfg = requireApiKey();
1066
+ const client = createClient(cfg);
1067
+ const res = await client.listProjectElements(projectId, {
1068
+ limit: opts.limit,
1069
+ ...opts.kinds ? { kinds: opts.kinds } : {},
1070
+ ...opts.cursor ? { cursor: opts.cursor } : {}
1071
+ });
1072
+ if (!res.ok) fail(res);
1073
+ const body = asRecord(res.body);
1074
+ const items = itemsOf(body);
1075
+ const lines = items.length ? items.map((item) => {
1076
+ const mention = String(item.mention || (item.name ? `@${item.name}` : ""));
1077
+ return `${mention} ${item.kind || ""} ${item.id || ""}`.trim();
1078
+ }) : ["\u043F\u0443\u0441\u0442\u043E"];
1079
+ printOrJson(opts.json, body, [...lines, ...nextPageLines(body)]);
1080
+ }
1081
+ async function ensureProjectAsset(client, assetId, projectId) {
1082
+ const res = await client.getAsset(assetId);
1083
+ if (!res.ok) fail(res);
1084
+ const body = asRecord(res.body);
1085
+ if (String(body.scope) === "project" && Number(body.project_id) === projectId) {
1086
+ return assetId;
1087
+ }
1088
+ const copied = await client.copyAsset(assetId, { target: "project", project_id: projectId });
1089
+ if (!copied.ok) fail(copied);
1090
+ const newId = String(asRecord(copied.body).asset_id || "").trim();
1091
+ if (!newId) throw new Error("copy \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B asset_id");
1092
+ return newId;
1093
+ }
1094
+ async function cmdElementsCreate(opts) {
1095
+ const sources = [opts.from, opts.file, opts.url].filter(Boolean);
1096
+ if (sources.length !== 1) {
1097
+ 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");
1098
+ }
1099
+ const name = opts.name.replace(/^@/, "").trim();
1100
+ 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");
1101
+ const projectId = parseProjectId(opts.projectId);
1102
+ const cfg = requireApiKey();
1103
+ const client = createClient(cfg);
1104
+ let assetId = "";
1105
+ let imageUrl = "";
1106
+ let kind = String(opts.kind || "").toLowerCase();
1107
+ if (opts.file) {
1108
+ const uploaded = await uploadLocal(client, opts.file, { projectId });
1109
+ assetId = uploaded.assetId;
1110
+ if (!assetId) throw new Error("\u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043D\u0435 \u0432\u0435\u0440\u043D\u0443\u043B\u0430 asset_id");
1111
+ if (!kind) kind = uploaded.kind || "image";
1112
+ } else if (opts.from) {
1113
+ const from = opts.from.trim();
1114
+ 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)`);
1115
+ assetId = await ensureProjectAsset(client, from, projectId);
1116
+ if (!kind) kind = "image";
1117
+ } else {
1118
+ imageUrl = String(opts.url || "").trim();
1119
+ if (!/^https?:\/\//i.test(imageUrl)) throw new Error("--url \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C http(s)");
1120
+ if (!kind) kind = "image";
1121
+ }
1122
+ if (kind !== "image" && kind !== "video") {
1123
+ throw new Error("kind: image \u0438\u043B\u0438 video");
1124
+ }
1125
+ const body = { name, kind };
1126
+ if (assetId) body.asset_id = assetId;
1127
+ else if (kind === "video") {
1128
+ body.media_json = { video_url: imageUrl };
1129
+ body.preview_url = imageUrl;
1130
+ } else {
1131
+ body.media_json = { images: [imageUrl] };
1132
+ body.preview_url = imageUrl;
1133
+ }
1134
+ const res = await client.createProjectElement(projectId, body);
1135
+ if (!res.ok) fail(res);
1136
+ const created = asRecord(asRecord(res.body).item ?? res.body);
1137
+ const mention = String(created.mention || (created.name ? `@${created.name}` : `@${name}`));
1138
+ printOrJson(
1139
+ opts.json,
1140
+ res.body,
1141
+ [
1142
+ `\u042D\u043B\u0435\u043C\u0435\u043D\u0442 ${mention}`,
1143
+ created.id ? String(created.id) : "",
1144
+ assetId ? assetId : imageUrl
1145
+ ].filter(Boolean)
1146
+ );
672
1147
  }
673
1148
 
674
1149
  // src/updateCheck.ts
675
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
1150
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
676
1151
  import { dirname as dirname2, join as join2 } from "node:path";
677
1152
 
678
1153
  // src/version.ts
@@ -692,7 +1167,7 @@ function parse(raw) {
692
1167
  return [Number(m[1]), Number(m[2]), Number(m[3])];
693
1168
  }
694
1169
  function cliVersion() {
695
- return String("0.1.1");
1170
+ return String("0.1.2");
696
1171
  }
697
1172
  function updateAvailableMessage(local, latest) {
698
1173
  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 +1194,7 @@ function readLastCheck() {
719
1194
  }
720
1195
  function writeLastCheck() {
721
1196
  try {
722
- mkdirSync2(dirname2(cachePath()), { recursive: true });
1197
+ mkdirSync3(dirname2(cachePath()), { recursive: true });
723
1198
  writeFileSync2(cachePath(), `${JSON.stringify({ checkedAt: Date.now() })}
724
1199
  `);
725
1200
  } catch {
@@ -748,6 +1223,9 @@ async function maybeNotifyUpdate() {
748
1223
  // src/index.ts
749
1224
  var program = new Command();
750
1225
  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());
1226
+ program.showHelpAfterError();
1227
+ program.showSuggestionAfterError();
1228
+ program.addHelpText("after", "\n\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u044F: https://app.artillect.pro/docs/cli\n");
751
1229
  program.hook("preAction", async () => {
752
1230
  await maybeNotifyUpdate();
753
1231
  });
@@ -762,20 +1240,57 @@ var models = program.command("models").description("\u041A\u0430\u0442\u0430\u04
762
1240
  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
1241
  await cmdModels(opts.kind, Boolean(opts.json));
764
1242
  });
765
- var generate = program.command("generate").description("\u0417\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0433\u0435\u043D\u0435\u0440\u0430\u0446\u0438\u044E");
766
- 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...>", "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0435 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0438-\u0440\u0435\u0444\u0435\u0440\u0435\u043D\u0441\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("-o, --out <dir>", "\u043F\u0430\u043F\u043A\u0430 \u0434\u043B\u044F \u0444\u0430\u0439\u043B\u043E\u0432", ".").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(
1243
+ 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) => {
1244
+ await cmdModelsGet(slug, Boolean(opts.json));
1245
+ });
1246
+ 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) => {
1247
+ await cmdUpload({
1248
+ filePath: file,
1249
+ projectId: opts.project,
1250
+ json: Boolean(opts.json)
1251
+ });
1252
+ });
1253
+ 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) => {
1254
+ const kind = type === "video" ? "video" : type === "image" ? "image" : null;
1255
+ if (!kind) throw new Error("type: image \u0438\u043B\u0438 video");
1256
+ await cmdEstimate({
1257
+ type: kind,
1258
+ model: opts.model,
1259
+ prompt: opts.prompt,
1260
+ json: Boolean(opts.json)
1261
+ });
1262
+ });
1263
+ 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");
1264
+ 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(
1265
+ "after",
1266
+ `
1267
+ \u041F\u0440\u0438\u043C\u0435\u0440\u044B:
1268
+ $ artillect generate image -p "\u043A\u043E\u0442 \u0432 \u043A\u043E\u0441\u043C\u043E\u0441\u0435" --wait --open
1269
+ $ artillect generate image -m seedream-5-pro -p "\u043A\u043E\u0442" -i ./ref.png --wait
1270
+ `
1271
+ ).action(
767
1272
  async (opts) => {
768
1273
  await cmdGenerateImage({
769
1274
  prompt: opts.prompt,
770
1275
  model: opts.model,
771
1276
  input: opts.input,
1277
+ projectId: opts.project,
772
1278
  wait: Boolean(opts.wait),
1279
+ waitTimeout: opts.waitTimeout,
773
1280
  json: Boolean(opts.json),
774
- out: opts.out
1281
+ out: opts.out,
1282
+ open: Boolean(opts.open)
775
1283
  });
776
1284
  }
777
1285
  );
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 <file>", "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0441\u0442\u0430\u0440\u0442\u043E\u0432\u044B\u0439 \u043A\u0430\u0434\u0440").option("--end-image <file>", "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0444\u0438\u043D\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u0430\u0434\u0440").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("-o, --out <dir>", "\u043F\u0430\u043F\u043A\u0430 \u0434\u043B\u044F \u0444\u0430\u0439\u043B\u043E\u0432", ".").option("--json", "\u0432\u044B\u0432\u0435\u0441\u0442\u0438 \u0441\u044B\u0440\u043E\u0439 JSON", false).action(
1286
+ 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(
1287
+ "after",
1288
+ `
1289
+ \u041F\u0440\u0438\u043C\u0435\u0440\u044B:
1290
+ $ artillect generate video grok-imagine-video -p "\u043A\u043E\u0442 \u0438\u0434\u0451\u0442" --wait --open
1291
+ $ artillect generate video kling-3 -p "@Hero \u0438\u0434\u0451\u0442" --project 12 --start-image ast_\u2026 --wait
1292
+ `
1293
+ ).action(
779
1294
  async (positionalModel, opts) => {
780
1295
  const model = String(opts.model || positionalModel || "kling-3-turbo").trim();
781
1296
  await cmdGenerateVideo({
@@ -783,9 +1298,98 @@ generate.command("video").description("\u0412\u0438\u0434\u0435\u043E \u043F\u04
783
1298
  prompt: opts.prompt,
784
1299
  startImage: opts.startImage,
785
1300
  endImage: opts.endImage,
1301
+ projectId: opts.project,
786
1302
  wait: Boolean(opts.wait),
1303
+ waitTimeout: opts.waitTimeout,
787
1304
  json: Boolean(opts.json),
788
- out: opts.out
1305
+ out: opts.out,
1306
+ open: Boolean(opts.open)
1307
+ });
1308
+ }
1309
+ );
1310
+ 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) => {
1311
+ await cmdGenerateGet(taskId, Boolean(opts.json));
1312
+ });
1313
+ 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(
1314
+ async (taskId, opts) => {
1315
+ await cmdGenerateWait({
1316
+ taskId,
1317
+ waitTimeout: opts.waitTimeout,
1318
+ out: opts.out,
1319
+ open: Boolean(opts.open),
1320
+ json: Boolean(opts.json)
1321
+ });
1322
+ }
1323
+ );
1324
+ 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) => {
1325
+ await cmdGenerateCancel(taskId, Boolean(opts.json));
1326
+ });
1327
+ 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) => {
1328
+ const kind = opts.kind === "video" ? "video" : "image";
1329
+ await cmdGenerateList({
1330
+ kind,
1331
+ json: Boolean(opts.json),
1332
+ limit: parseLimit(opts.limit, 20),
1333
+ cursor: opts.cursor
1334
+ });
1335
+ });
1336
+ 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");
1337
+ 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(
1338
+ async (opts) => {
1339
+ await cmdAssetsList({
1340
+ kind: opts.kind,
1341
+ scope: opts.scope,
1342
+ projectId: opts.project,
1343
+ query: opts.query,
1344
+ cursor: opts.cursor,
1345
+ limit: parseLimit(opts.limit, 20),
1346
+ json: Boolean(opts.json)
1347
+ });
1348
+ }
1349
+ );
1350
+ 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) => {
1351
+ await cmdAssetsGet(assetId, Boolean(opts.json));
1352
+ });
1353
+ 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(
1354
+ async (assetId, opts) => {
1355
+ await cmdAssetsCopy({
1356
+ assetId,
1357
+ projectId: opts.project,
1358
+ personal: Boolean(opts.personal),
1359
+ json: Boolean(opts.json)
1360
+ });
1361
+ }
1362
+ );
1363
+ var projects = program.command("projects").description("\u041F\u0440\u043E\u0435\u043A\u0442\u044B \u0430\u043A\u043A\u0430\u0443\u043D\u0442\u0430");
1364
+ 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) => {
1365
+ await cmdProjectsList({
1366
+ limit: parseLimit(opts.limit, 50),
1367
+ cursor: opts.cursor,
1368
+ json: Boolean(opts.json)
1369
+ });
1370
+ });
1371
+ var elements = program.command("elements").description("Element library \u043F\u0440\u043E\u0435\u043A\u0442\u0430 (@Name)");
1372
+ 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(
1373
+ async (opts) => {
1374
+ await cmdElementsList({
1375
+ projectId: opts.project,
1376
+ kinds: opts.kinds,
1377
+ cursor: opts.cursor,
1378
+ limit: parseLimit(opts.limit, 50),
1379
+ json: Boolean(opts.json)
1380
+ });
1381
+ }
1382
+ );
1383
+ 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(
1384
+ async (opts) => {
1385
+ await cmdElementsCreate({
1386
+ projectId: opts.project,
1387
+ name: opts.name,
1388
+ from: opts.from,
1389
+ file: opts.file,
1390
+ url: opts.url,
1391
+ kind: opts.kind,
1392
+ json: Boolean(opts.json)
789
1393
  });
790
1394
  }
791
1395
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artillect/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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
+ }