@aisa-one/cli 0.2.1 → 0.2.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +144 -1
  2. package/README.md +249 -102
  3. package/dist/api.d.ts +46 -1
  4. package/dist/api.js +77 -8
  5. package/dist/cache.d.ts +18 -0
  6. package/dist/cache.js +65 -0
  7. package/dist/catalog.d.ts +90 -0
  8. package/dist/catalog.js +101 -0
  9. package/dist/commands/account.d.ts +4 -1
  10. package/dist/commands/account.js +36 -6
  11. package/dist/commands/api.d.ts +18 -4
  12. package/dist/commands/api.js +357 -24
  13. package/dist/commands/cacheCmd.d.ts +2 -0
  14. package/dist/commands/cacheCmd.js +10 -0
  15. package/dist/commands/completionCmd.d.ts +7 -0
  16. package/dist/commands/completionCmd.js +37 -0
  17. package/dist/commands/configCmd.js +19 -1
  18. package/dist/commands/finance.d.ts +4 -0
  19. package/dist/commands/finance.js +212 -29
  20. package/dist/commands/models.js +6 -0
  21. package/dist/commands/run.d.ts +21 -0
  22. package/dist/commands/run.js +113 -6
  23. package/dist/commands/search.js +8 -3
  24. package/dist/commands/skills.d.ts +6 -2
  25. package/dist/commands/skills.js +189 -134
  26. package/dist/commands/twitter.js +2 -3
  27. package/dist/commands/video.d.ts +40 -0
  28. package/dist/commands/video.js +272 -55
  29. package/dist/completion.d.ts +26 -0
  30. package/dist/completion.js +293 -0
  31. package/dist/constants.d.ts +55 -5
  32. package/dist/constants.js +136 -11
  33. package/dist/index.js +86 -23
  34. package/dist/skills-registry.d.ts +64 -0
  35. package/dist/skills-registry.js +219 -0
  36. package/dist/types.d.ts +32 -1
  37. package/dist/utils/file.d.ts +5 -1
  38. package/dist/utils/file.js +5 -1
  39. package/dist/utils/pool.d.ts +8 -0
  40. package/dist/utils/pool.js +27 -0
  41. package/package.json +9 -3
  42. package/dist/agent/env-setup.d.ts +0 -5
  43. package/dist/agent/env-setup.js +0 -49
  44. package/dist/agent/index-client.d.ts +0 -7
  45. package/dist/agent/index-client.js +0 -22
  46. package/dist/agent/installer.d.ts +0 -19
  47. package/dist/agent/installer.js +0 -70
  48. package/dist/agent/setup-python.d.ts +0 -15
  49. package/dist/agent/setup-python.js +0 -51
  50. package/dist/commands/agent.d.ts +0 -37
  51. package/dist/commands/agent.js +0 -129
  52. package/tests/streaming.test.ts +0 -73
@@ -1,80 +1,11 @@
1
1
  import ora from "ora";
2
2
  import chalk from "chalk";
3
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
5
5
  import { error, success, hint, truncate } from "../utils/display.js";
6
6
  import { expandHome, ensureDir, writeSkillFiles, removeDir, detectAgents } from "../utils/file.js";
7
7
  import { AGENT_DIRS } from "../constants.js";
8
- // --- GitHub-backed skill registry ---
9
- const SKILLS_REPO = "AIsa-team/agent-skills";
10
- const GH_API = `https://api.github.com/repos/${SKILLS_REPO}`;
11
- const GH_RAW = `https://raw.githubusercontent.com/${SKILLS_REPO}/main`;
12
- /** Fetch the repo tree and extract top-level skill folder names. */
13
- async function fetchSkillSlugs() {
14
- const res = await fetch(`${GH_API}/git/trees/main`);
15
- if (!res.ok)
16
- throw new Error(`GitHub API error: ${res.status}`);
17
- const data = (await res.json());
18
- // Top-level directories (excluding dotfiles, LICENSE, README) are skills
19
- return data.tree
20
- .filter((e) => e.type === "tree" &&
21
- !e.path.startsWith(".") &&
22
- e.path !== "node_modules")
23
- .map((e) => e.path);
24
- }
25
- /** Fetch and parse SKILL.md frontmatter for a single skill. */
26
- async function fetchSkillMeta(slug) {
27
- const res = await fetch(`${GH_RAW}/${slug}/SKILL.md`);
28
- if (!res.ok)
29
- return null;
30
- const text = await res.text();
31
- return parseSkillFrontmatter(slug, text);
32
- }
33
- /** Parse YAML frontmatter from SKILL.md content. */
34
- function parseSkillFrontmatter(slug, content) {
35
- const info = { slug, name: slug, description: "", emoji: "" };
36
- const match = content.match(/^---\n([\s\S]*?)\n---/);
37
- if (!match)
38
- return info;
39
- const frontmatter = match[1];
40
- const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
41
- if (nameMatch)
42
- info.name = nameMatch[1].trim().replace(/^["']|["']$/g, "");
43
- const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
44
- if (descMatch)
45
- info.description = descMatch[1].trim().replace(/^["']|["']$/g, "");
46
- const metaMatch = frontmatter.match(/^metadata:\s*(.+)$/m);
47
- if (metaMatch) {
48
- try {
49
- const meta = JSON.parse(metaMatch[1]);
50
- // agent-skills repo uses metadata.aisa; legacy skills used metadata.openclaw.
51
- info.emoji = meta?.aisa?.emoji || meta?.openclaw?.emoji || "";
52
- }
53
- catch {
54
- // ignore malformed metadata
55
- }
56
- }
57
- return info;
58
- }
59
- /** Fetch all files in a skill folder from GitHub. */
60
- async function fetchSkillFiles(slug) {
61
- const treeRes = await fetch(`${GH_API}/git/trees/main?recursive=1`);
62
- if (!treeRes.ok)
63
- throw new Error(`GitHub API error: ${treeRes.status}`);
64
- const treeData = (await treeRes.json());
65
- const prefix = `${slug}/`;
66
- const blobs = treeData.tree.filter((e) => e.type === "blob" && e.path.startsWith(prefix));
67
- const files = [];
68
- await Promise.all(blobs.map(async (entry) => {
69
- const res = await fetch(`${GH_RAW}/${entry.path}`);
70
- if (!res.ok)
71
- return;
72
- const content = await res.text();
73
- const relativePath = entry.path.slice(prefix.length);
74
- files.push({ path: relativePath, content });
75
- }));
76
- return files;
77
- }
8
+ import { SKILLS_REPO, getSkillIndex, listSkills, resolveSlug, leafName, fetchSkillFiles, fetchSkillMarkdown, parseSkillFrontmatter, } from "../skills-registry.js";
78
9
  // --- Skill Templates (for init) ---
79
10
  const TEMPLATES = {
80
11
  default: `---
@@ -156,7 +87,7 @@ export AISA_API_KEY=sk-your-key
156
87
  ## Smart Search
157
88
 
158
89
  \`\`\`bash
159
- aisa web-search "latest AI research" --type smart
90
+ aisa web-search "latest AI research" --type tavily
160
91
  \`\`\`
161
92
 
162
93
  ## YouTube Search
@@ -275,27 +206,40 @@ aisa video status <task-id>
275
206
  `,
276
207
  };
277
208
  // --- Commands ---
278
- export async function skillsListAction(_options) {
209
+ function printSkill(s) {
210
+ const emoji = s.emoji ? `${s.emoji} ` : "";
211
+ console.log(` ${emoji}${chalk.cyan.bold(s.name)} ${chalk.gray(s.slug)}`);
212
+ if (s.description) {
213
+ console.log(` ${chalk.gray(truncate(s.description, 80))}`);
214
+ }
215
+ console.log();
216
+ }
217
+ export async function skillsListAction(options) {
279
218
  const spinner = ora("Fetching skills from GitHub...").start();
280
219
  try {
281
- const slugs = await fetchSkillSlugs();
282
- // Fetch all SKILL.md frontmatters in parallel
283
- const metas = await Promise.all(slugs.map((s) => fetchSkillMeta(s)));
284
- const skills = metas.filter((m) => m !== null);
220
+ const index = await getSkillIndex({ refresh: options.refresh });
221
+ let skills = await listSkills(index);
222
+ if (options.category) {
223
+ const cat = options.category.toLowerCase();
224
+ skills = skills.filter((s) => s.slug.split("/")[0].toLowerCase() === cat);
225
+ }
226
+ const total = skills.length;
227
+ if (options.limit)
228
+ skills = skills.slice(0, parseInt(options.limit));
285
229
  spinner.stop();
286
230
  if (skills.length === 0) {
287
- console.log(" No skills found.");
288
- return;
289
- }
290
- console.log(chalk.bold(`\n ${skills.length} skills available\n`));
291
- for (const s of skills) {
292
- const emoji = s.emoji ? `${s.emoji} ` : "";
293
- console.log(` ${emoji}${chalk.cyan.bold(s.name)} ${chalk.gray(s.slug)}`);
294
- if (s.description) {
295
- console.log(` ${chalk.gray(truncate(s.description, 80))}`);
231
+ console.log(options.category
232
+ ? ` No skills in category "${options.category}".`
233
+ : " No skills found.");
234
+ if (options.category) {
235
+ const categories = [...new Set(index.slugs.map((s) => s.split("/")[0]))];
236
+ hint(`Categories: ${categories.join(", ")}`);
296
237
  }
297
- console.log();
238
+ return;
298
239
  }
240
+ console.log(chalk.bold(`\n ${skills.length}${skills.length < total ? ` of ${total}` : ""} skills available\n`));
241
+ for (const s of skills)
242
+ printSkill(s);
299
243
  hint("Install: aisa skills install <slug>");
300
244
  hint("Details: aisa skills show <slug>");
301
245
  }
@@ -304,30 +248,26 @@ export async function skillsListAction(_options) {
304
248
  error(err.message);
305
249
  }
306
250
  }
307
- export async function skillsSearchAction(query, _options) {
251
+ export async function skillsSearchAction(query, options) {
308
252
  const spinner = ora(`Searching skills: "${query}"...`).start();
309
253
  try {
310
- const slugs = await fetchSkillSlugs();
311
- const metas = await Promise.all(slugs.map((s) => fetchSkillMeta(s)));
312
- const skills = metas.filter((m) => m !== null);
254
+ const index = await getSkillIndex({ refresh: options.refresh });
255
+ const skills = await listSkills(index);
313
256
  const q = query.toLowerCase();
314
- const matches = skills.filter((s) => s.name.toLowerCase().includes(q) ||
257
+ let matches = skills.filter((s) => s.name.toLowerCase().includes(q) ||
315
258
  s.slug.toLowerCase().includes(q) ||
316
259
  s.description.toLowerCase().includes(q));
260
+ const total = matches.length;
261
+ if (options.limit)
262
+ matches = matches.slice(0, parseInt(options.limit));
317
263
  spinner.stop();
318
264
  if (matches.length === 0) {
319
265
  console.log(` No skills found for "${query}".`);
320
266
  return;
321
267
  }
322
- console.log(chalk.bold(`\n ${matches.length} result(s)\n`));
323
- for (const s of matches) {
324
- const emoji = s.emoji ? `${s.emoji} ` : "";
325
- console.log(` ${emoji}${chalk.cyan.bold(s.name)} ${chalk.gray(s.slug)}`);
326
- if (s.description) {
327
- console.log(` ${chalk.gray(truncate(s.description, 80))}`);
328
- }
329
- console.log();
330
- }
268
+ console.log(chalk.bold(`\n ${matches.length}${matches.length < total ? ` of ${total}` : ""} result(s)\n`));
269
+ for (const s of matches)
270
+ printSkill(s);
331
271
  hint("Install: aisa skills install <slug>");
332
272
  }
333
273
  catch (err) {
@@ -338,29 +278,19 @@ export async function skillsSearchAction(query, _options) {
338
278
  export async function skillsShowAction(slug) {
339
279
  const spinner = ora(`Loading ${slug}...`).start();
340
280
  try {
341
- // Fetch SKILL.md
342
- const res = await fetch(`${GH_RAW}/${slug}/SKILL.md`);
343
- if (!res.ok) {
344
- spinner.fail("Skill not found");
345
- error(`No skill "${slug}" in ${SKILLS_REPO}`);
346
- return;
347
- }
348
- const content = await res.text();
349
- const meta = parseSkillFrontmatter(slug, content);
350
- // Fetch file list
351
- const treeRes = await fetch(`${GH_API}/git/trees/main?recursive=1`);
352
- const treeData = (await treeRes.json());
353
- const prefix = `${slug}/`;
354
- const files = treeData.tree
355
- .filter((e) => e.type === "blob" && e.path.startsWith(prefix))
356
- .map((e) => e.path.slice(prefix.length));
281
+ const index = await getSkillIndex();
282
+ const canonical = resolveSlug(slug, index);
283
+ const content = await fetchSkillMarkdown(canonical);
284
+ const meta = parseSkillFrontmatter(canonical, content);
285
+ const prefix = `${canonical}/`;
286
+ const files = (index.blobs[canonical] || []).map((b) => b.path.slice(prefix.length));
357
287
  spinner.stop();
358
288
  const emoji = meta.emoji ? `${meta.emoji} ` : "";
359
289
  console.log(`\n ${emoji}${chalk.cyan.bold(meta.name)}`);
360
290
  console.log(` ${meta.description}`);
361
- console.log(` Slug: ${chalk.gray(slug)}`);
291
+ console.log(` Slug: ${chalk.gray(canonical)}`);
362
292
  console.log(` Files: ${files.join(", ")}`);
363
- console.log(` Source: ${chalk.gray(`https://github.com/${SKILLS_REPO}/tree/main/${slug}`)}`);
293
+ console.log(` Source: ${chalk.gray(`https://github.com/${SKILLS_REPO}/tree/main/${canonical}`)}`);
364
294
  // Print SKILL.md body (after frontmatter)
365
295
  const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "").trim();
366
296
  if (body) {
@@ -368,20 +298,83 @@ export async function skillsShowAction(slug) {
368
298
  console.log(body);
369
299
  }
370
300
  console.log();
371
- hint(`Install: aisa skills install ${slug}`);
301
+ hint(`Install: aisa skills install ${leafName(canonical)}`);
372
302
  }
373
303
  catch (err) {
374
304
  spinner.fail("Failed to load skill");
375
305
  error(err.message);
376
306
  }
377
307
  }
308
+ /** Agent skill loaders expect one directory level, named to match SKILL.md's `name:`. */
309
+ function installedName(canonical) {
310
+ return leafName(canonical);
311
+ }
312
+ /**
313
+ * Marker recording which skill owns an installed directory.
314
+ *
315
+ * Frontmatter `name:` is not an identity: two skills in different categories can
316
+ * share both a leaf name and a `name:`, and comparing those would let the second
317
+ * install silently overwrite the first. The canonical slug is the only unique
318
+ * key, and nothing in SKILL.md carries it.
319
+ */
320
+ const MARKER_FILE = ".aisa-skill.json";
321
+ function readMarker(dir) {
322
+ try {
323
+ const parsed = JSON.parse(readFileSync(join(dir, MARKER_FILE), "utf-8"));
324
+ return typeof parsed?.slug === "string" ? parsed : undefined;
325
+ }
326
+ catch {
327
+ return undefined;
328
+ }
329
+ }
330
+ /**
331
+ * Identify the skill currently installed at `dir`. Falls back to frontmatter for
332
+ * directories written before markers existed, where the name is all we have.
333
+ */
334
+ function occupant(dir) {
335
+ const marker = readMarker(dir);
336
+ if (marker)
337
+ return { slug: marker.slug };
338
+ const skillMd = join(dir, "SKILL.md");
339
+ if (!existsSync(skillMd))
340
+ return undefined;
341
+ try {
342
+ return { name: parseSkillFrontmatter("", readFileSync(skillMd, "utf-8")).name };
343
+ }
344
+ catch {
345
+ return undefined;
346
+ }
347
+ }
348
+ /**
349
+ * Why replacing what is already installed would be unsafe, or undefined when it
350
+ * is the same skill.
351
+ *
352
+ * Frontmatter `name:` cannot settle ownership: two skills in different
353
+ * categories may share both a leaf name and a `name:`. That ambiguity is only
354
+ * reachable when the caller named a category — a bare name would have been
355
+ * rejected as ambiguous before we got here — so an unmarked directory is
356
+ * treated as unverifiable exactly in that case.
357
+ */
358
+ function describeConflict(present, canonical, expectedName, requestedCanonical) {
359
+ if (!present)
360
+ return undefined;
361
+ if (present.slug) {
362
+ return present.slug === canonical ? undefined : `holds a different skill (${present.slug})`;
363
+ }
364
+ if (requestedCanonical) {
365
+ return `holds an unmarked install that cannot be verified as ${canonical}`;
366
+ }
367
+ return present.name === expectedName ? undefined : `holds a different skill ("${present.name}")`;
368
+ }
378
369
  export async function skillsInstallAction(slug, options) {
379
370
  const spinner = ora(`Fetching skill '${slug}' from GitHub...`).start();
380
371
  try {
381
- const files = await fetchSkillFiles(slug);
372
+ const index = await getSkillIndex();
373
+ const canonical = resolveSlug(slug, index);
374
+ const files = await fetchSkillFiles(canonical, index);
382
375
  if (files.length === 0) {
383
376
  spinner.fail("Skill not found");
384
- error(`No skill "${slug}" in ${SKILLS_REPO}`);
377
+ error(`No files found for "${canonical}" in ${SKILLS_REPO}`);
385
378
  return;
386
379
  }
387
380
  spinner.stop();
@@ -405,15 +398,49 @@ export async function skillsInstallAction(slug, options) {
405
398
  targets = Object.keys(AGENT_DIRS);
406
399
  }
407
400
  }
401
+ const dirName = installedName(canonical);
402
+ const expectedName = parseSkillFrontmatter(canonical, files.find((f) => f.path === "SKILL.md")?.content.toString("utf-8") || "").name;
403
+ const marker = { slug: canonical, installedAt: new Date().toISOString() };
404
+ // A bare name is only resolvable when the leaf is unique across the repo —
405
+ // resolveSlug rejects it otherwise — so whatever occupies the directory must
406
+ // be this skill. A canonical slug bypasses that check, which is exactly when
407
+ // an unmarked directory becomes unverifiable.
408
+ const requestedCanonical = slug.includes("/");
408
409
  let installed = 0;
409
410
  for (const agent of targets) {
410
- const dir = expandHome(join(AGENT_DIRS[agent], slug));
411
+ const dir = expandHome(join(AGENT_DIRS[agent], dirName));
412
+ // A different skill already occupying this directory name means the
413
+ // install would replace it. Compare canonical slugs where we have them —
414
+ // names collide across categories.
415
+ const exists = existsSync(dir);
416
+ const present = exists ? occupant(dir) : undefined;
417
+ const conflict = exists && !present
418
+ ? // No marker and no readable SKILL.md: an interrupted install, or a
419
+ // directory the user put there. Neither is safe to delete silently.
420
+ "exists but is not a recognizable skill install"
421
+ : describeConflict(present, canonical, expectedName, requestedCanonical);
422
+ if (conflict && !options.force) {
423
+ error(`${AGENT_DIRS[agent]}${dirName}/ ${conflict} — pass --force to replace`);
424
+ continue;
425
+ }
426
+ // Replace rather than merge: writing over a directory only overwrites
427
+ // files that happen to share a name, leaving the previous skill's scripts
428
+ // and assets behind for an agent to keep loading. Keyed on the directory
429
+ // existing, not on identifying its occupant — an unidentifiable directory
430
+ // still must not be merged into.
431
+ if (exists)
432
+ removeDir(dir);
411
433
  ensureDir(dir);
412
434
  writeSkillFiles(dir, files);
413
- console.log(` ${chalk.green("✓")} ${AGENT_DIRS[agent]} (${agent})`);
435
+ writeFileSync(join(dir, MARKER_FILE), JSON.stringify(marker, null, 2) + "\n", "utf-8");
436
+ console.log(` ${chalk.green("✓")} ${AGENT_DIRS[agent]}${dirName} (${agent})`);
414
437
  installed++;
415
438
  }
416
- success(`Skill '${slug}' installed to ${installed} agent(s)`);
439
+ if (installed === 0) {
440
+ error("Nothing installed.");
441
+ return;
442
+ }
443
+ success(`Skill '${canonical}' installed to ${installed} agent(s)`);
417
444
  }
418
445
  catch (err) {
419
446
  spinner.fail("Failed to install skill");
@@ -421,6 +448,12 @@ export async function skillsInstallAction(slug, options) {
421
448
  }
422
449
  }
423
450
  export function skillsRemoveAction(slug, options) {
451
+ // Installed directories use the leaf name, so a bare name is what's on disk.
452
+ const requested = slug.replace(/^\/+|\/+$/g, "");
453
+ const dirName = leafName(requested);
454
+ // A canonical slug names one specific skill; a bare name names whatever holds
455
+ // that directory.
456
+ const wantsCanonical = requested.includes("/");
424
457
  let targets;
425
458
  if (options.agent && options.agent !== "all") {
426
459
  targets = [options.agent];
@@ -429,20 +462,42 @@ export function skillsRemoveAction(slug, options) {
429
462
  targets = Object.keys(AGENT_DIRS);
430
463
  }
431
464
  let removed = 0;
465
+ let refused = 0;
432
466
  for (const agent of targets) {
433
- const dir = expandHome(join(AGENT_DIRS[agent], slug));
434
- if (existsSync(dir)) {
435
- removeDir(dir);
436
- console.log(` ${chalk.green("✓")} Removed from ${AGENT_DIRS[agent]}`);
437
- removed++;
467
+ const dir = expandHome(join(AGENT_DIRS[agent], dirName));
468
+ if (!existsSync(dir))
469
+ continue;
470
+ const marker = readMarker(dir);
471
+ // Leaf names collide across categories, so deleting by name alone can take
472
+ // out a different skill than the one asked for.
473
+ if (wantsCanonical && !options.force) {
474
+ if (marker && marker.slug !== requested) {
475
+ error(`${AGENT_DIRS[agent]}${dirName}/ holds ${marker.slug}, not ${requested} — skipped`);
476
+ refused++;
477
+ continue;
478
+ }
479
+ // Pre-marker installs carry no slug, and a same-named skill from another
480
+ // category is indistinguishable from the requested one.
481
+ if (!marker) {
482
+ error(`${AGENT_DIRS[agent]}${dirName}/ has no install marker — cannot verify it is ${requested} — skipped`);
483
+ refused++;
484
+ continue;
485
+ }
486
+ }
487
+ if (!marker) {
488
+ hint(`${AGENT_DIRS[agent]}${dirName}/ has no install marker — removing by directory name`);
438
489
  }
490
+ removeDir(dir);
491
+ console.log(` ${chalk.green("✓")} Removed from ${AGENT_DIRS[agent]}`);
492
+ removed++;
439
493
  }
440
494
  if (removed === 0) {
441
- console.log(" Skill not found in any agent directory.");
442
- }
443
- else {
444
- success(`Removed '${slug}' from ${removed} agent(s)`);
495
+ console.log(refused > 0
496
+ ? " Nothing removed — pass --force to remove regardless of what is installed."
497
+ : " Skill not found in any agent directory.");
498
+ return;
445
499
  }
500
+ success(`Removed '${dirName}' from ${removed} agent(s)`);
446
501
  }
447
502
  export function skillsInitAction(name, options) {
448
503
  const dir = resolve(name);
@@ -1,8 +1,7 @@
1
1
  import ora from "ora";
2
2
  import chalk from "chalk";
3
3
  import { requireApiKey, getConfig, setConfig } from "../config.js";
4
- import { apiRequest } from "../api.js";
5
- import { APIS_BASE_URL } from "../constants.js";
4
+ import { apiRequest, resolveBases } from "../api.js";
6
5
  import { error, success, formatJson } from "../utils/display.js";
7
6
  function requireCookies() {
8
7
  const cookies = getConfig("twitterCookies");
@@ -344,7 +343,7 @@ export async function twitterUploadMediaAction(filePath, options) {
344
343
  }
345
344
  formData.append("login_cookies", auth.login_cookies);
346
345
  formData.append("proxy", auth.proxy);
347
- const url = `${APIS_BASE_URL}/twitter/upload_media_v2`;
346
+ const url = `${resolveBases().domain}/twitter/upload_media_v2`;
348
347
  const res = await fetch(url, {
349
348
  method: "POST",
350
349
  headers: {
@@ -1,9 +1,49 @@
1
+ import { type MediaType } from "../constants.js";
2
+ /**
3
+ * Each upstream nests the finished video somewhere different (DashScope puts it
4
+ * at result.output.video_url, Seedance under result.data[].content[].url), so
5
+ * search rather than hardcode a path.
6
+ *
7
+ * Every pass covers the whole tree before the next one starts, and each is
8
+ * narrower than a plain "ends in url" match would be. Responses ship thumbnails
9
+ * and cover images beside the clip, and `thumbnail_url` both ends in "url" and
10
+ * tends to sit closer to the root than the video does — matching it would make
11
+ * `--output` download a still image.
12
+ */
13
+ export declare function findVideoUrl(value: unknown): string | undefined;
14
+ export interface MediaInput {
15
+ type: MediaType;
16
+ url: string;
17
+ }
18
+ /** Parse `--media first_frame=https://…`, and bare URLs from `--image`. */
19
+ export declare function parseMedia(specs?: string[], images?: string[]): MediaInput[];
20
+ /**
21
+ * Build the request body for a model's vendor. The gateway rewrites only the
22
+ * top-level `model` and forwards the rest verbatim, so a Bailian body sent to a
23
+ * Seedance model is rejected with a 400.
24
+ */
25
+ export declare function buildVideoBody(model: string, prompt: string, media: MediaInput[], options: {
26
+ resolution?: string;
27
+ duration?: string;
28
+ }): Record<string, unknown>;
29
+ /**
30
+ * Parse a `--body` payload. The option means "send this verbatim", so the model
31
+ * it names wins over the CLI's default — only an explicit `--model` overrides
32
+ * it. Anything else would silently redirect a valid request to another model.
33
+ */
34
+ export declare function parseBodyOverride(raw: string, explicitModel?: string): Record<string, unknown>;
1
35
  export declare function videoCreateAction(prompt: string, options: {
2
36
  model?: string;
3
37
  wait?: boolean;
4
38
  output?: string;
5
39
  raw?: boolean;
40
+ resolution?: string;
41
+ duration?: string;
42
+ image?: string[];
43
+ media?: string[];
44
+ body?: string;
6
45
  }): Promise<void>;
7
46
  export declare function videoStatusAction(taskId: string, options: {
8
47
  raw?: boolean;
48
+ output?: string;
9
49
  }): Promise<void>;