@heyamiko/amiko-cli 0.14.0-beta.12 → 0.14.0-beta.13

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 +29 -4
  2. package/dist/index.js +159 -30
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @heyamiko/amiko-cli (v0.14.0-beta.0)
1
+ # @heyamiko/amiko-cli (v0.14.0-beta.13)
2
2
 
3
3
  Manage wallets, credits, swaps, MPP marketplace services, and your Amiko twin (identity, documents, voice, avatar, friends, feed) from the terminal. Works for both human users and AI agents running on OpenClaw.
4
4
 
@@ -131,6 +131,10 @@ amiko create video "animate cover" --first-frame https://.../cover.png --yes
131
131
  amiko create video "dance clip" --model dreamina-seedance-2-0-fast-260128 \
132
132
  --reference-image https://.../ref1.png --reference-audio https://.../beat.mp3 --generate-audio --yes
133
133
  amiko create tts "Hello world" --voice 21m00Tcm4TlvDq8ikWAM
134
+ amiko create tts "Hello world" --provider minimax --model speech-2.8-turbo
135
+ amiko create tts "Hello" --provider minimax --voice English_expressive_narrator --yes
136
+ amiko voice clone ./me.mp3 --provider minimax
137
+ amiko voice clone ./me.mp3 --provider elevenlabs
134
138
  amiko create music "lo-fi chill beat" --duration 30000
135
139
  amiko create music --lyrics "..." --instrumental # instrumental
136
140
  amiko create sfx "thunder and heavy rain" --duration 5
@@ -178,8 +182,20 @@ Reference flags accept URLs or data URIs (not local paths). Typical I2V flow: `c
178
182
 
179
183
  | Flag | Default | Description |
180
184
  |------|---------|-------------|
181
- | `--model <model>` | `eleven_multilingual_v2` | TTS model id |
182
- | `--voice <voiceId>` | `21m00Tcm4TlvDq8ikWAM` | Voice id |
185
+ | `--provider <name>` | inferred / `elevenlabs` | `minimax` or `elevenlabs` — sets model/voice defaults when those flags are omitted |
186
+ | `--model <model>` | EL: `eleven_multilingual_v2`; MiniMax: `speech-2.8-turbo` | MiniMax: `speech-2.8-turbo\|hd`, `speech-2.6-turbo\|hd`. ElevenLabs: `eleven_*` |
187
+ | `--voice <voiceId>` | EL default hash; MiniMax: `English_expressive_narrator` | MiniMax system or cloned `voice_id`; ElevenLabs voice id |
188
+
189
+ Examples: `amiko create tts "Hi" --provider minimax --yes` · `amiko create tts "Hi" --provider minimax --model speech-2.8-turbo --voice <clonedId> --yes`
190
+
191
+ **`amiko voice clone <source>`** — audio path, https URL, or `-` (stdin)
192
+
193
+ | Flag | Default | Description |
194
+ |------|---------|-------------|
195
+ | `--provider <name>` | `minimax` | `minimax` (mpp rapid clone + ownership registry) or `elevenlabs` (legacy IVC) |
196
+ | `--voice-id <id>` | auto | **MiniMax only** — optional custom voice_id (ignored for elevenlabs) |
197
+ | `--model <model>` | `speech-2.8-turbo` | **MiniMax only** — preview speech model (ignored for elevenlabs) |
198
+ | `--preview-text <text>` | provider default | **MiniMax only** — preview line; empty string skips preview (ignored for elevenlabs) |
183
199
 
184
200
  **`amiko create music [prompt]`** — pass a prompt and/or `--lyrics`
185
201
 
@@ -345,8 +361,11 @@ amiko markets service call POST /v1/sfx '{"text":"Thunder with heavy rain","dura
345
361
  # Music generation ($0.10) — returns { url, format, service }
346
362
  amiko markets service call POST /v1/music '{"prompt":"Lo-fi chill beat","music_length_ms":30000}'
347
363
 
348
- # Text-to-speech (1 AMIKO) — returns { url, format, service }
364
+ # Text-to-speech — ElevenLabs voice id (default)
349
365
  amiko markets service tts 21m00Tcm4TlvDq8ikWAM "Hello world"
366
+ # MiniMax system or cloned voice_id
367
+ amiko markets service tts English_expressive_narrator "Hello" --provider minimax --yes
368
+ amiko markets service tts <clonedId> "Hello" --provider minimax --model speech-2.8-turbo --yes
350
369
 
351
370
  # Composition plan ($0.02)
352
371
  amiko markets service call POST /v1/music/plan '{"prompt":"Epic orchestral piece"}'
@@ -564,6 +583,12 @@ npm publish
564
583
 
565
584
  ## Changelog
566
585
 
586
+ ### 0.14.0-beta.13
587
+
588
+ - **Create Studio TTS + voice clone: MiniMax.** `amiko create tts` accepts `--provider minimax|elevenlabs` (infers from `speech-*` / `eleven_*` models when omitted), with provider-specific defaults (`speech-2.8-turbo` + `English_expressive_narrator` vs `eleven_multilingual_v2` + Rachel). Quote body includes `voiceId`. Cloned-voice examples require `--provider minimax` when using a MiniMax `voice_id`.
589
+ - **`amiko voice clone`** defaults to MiniMax (mpp rapid clone + ownership registry); `--provider elevenlabs` keeps legacy IVC. MiniMax-only flags: `--voice-id`, `--model`, `--preview-text`.
590
+ - **`amiko markets service tts`** gains `--provider minimax|elevenlabs` and `--model` for MiniMax speech routing on `/v1/tts/:voiceId`.
591
+
567
592
  ### 0.14.0-beta.7
568
593
 
569
594
  - **`amiko chat group invite <group>` — share a group's invite/join link + QR.** New read-only subcommand that fetches `GET /api/conversations/{id}/invite` (twin token) and prints the join link plus a scannable QR image URL. Admins-only: SKILL.md instructs the agent to confirm the asker holds `admin`/`owner` (via `amiko chat group info`) before sharing, and the endpoint is admin-gated server-side (403 → admin tip). Fails loudly if the response is missing its link/QR instead of printing a placeholder. Requires the companion amiko-web change (adds `qrUrl` to the invite response + a public `GET /api/conversations/join/[token]/qr` PNG route).
package/dist/index.js CHANGED
@@ -861,7 +861,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
861
861
  this._exitCallback = (err) => {
862
862
  if (err.code !== "commander.executeSubCommandAsync") {
863
863
  throw err;
864
- }
864
+ } else {}
865
865
  };
866
866
  }
867
867
  return this;
@@ -3596,7 +3596,7 @@ function getHumanReadableErrorMessage(code, context = {}) {
3596
3596
  function getErrorMessage(code, context = {}) {
3597
3597
  if (true) {
3598
3598
  return getHumanReadableErrorMessage(code, context);
3599
- }
3599
+ } else {}
3600
3600
  }
3601
3601
  function isSolanaError(e, code) {
3602
3602
  const isSolanaError2 = e instanceof Error && e.name === "SolanaError";
@@ -19992,7 +19992,7 @@ function finalize(ctx, schema) {
19992
19992
  result.$schema = "http://json-schema.org/draft-07/schema#";
19993
19993
  } else if (ctx.target === "draft-04") {
19994
19994
  result.$schema = "http://json-schema.org/draft-04/schema#";
19995
- } else if (ctx.target === "openapi-3.0") {}
19995
+ } else if (ctx.target === "openapi-3.0") {} else {}
19996
19996
  if (ctx.external?.uri) {
19997
19997
  const id = ctx.external.registry.get(schema)?.id;
19998
19998
  if (!id)
@@ -20257,7 +20257,7 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
20257
20257
  if (val === undefined) {
20258
20258
  if (ctx.unrepresentable === "throw") {
20259
20259
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
20260
- }
20260
+ } else {}
20261
20261
  } else if (typeof val === "bigint") {
20262
20262
  if (ctx.unrepresentable === "throw") {
20263
20263
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -23661,21 +23661,34 @@ function readFromFile(path) {
23661
23661
  return null;
23662
23662
  }
23663
23663
  }
23664
+ function readRoutingIdsFromEnv() {
23665
+ return {
23666
+ agentId: asString(process.env.AMIKO_AGENT_ID),
23667
+ sessionId: asString(process.env.AMIKO_SESSION_ID)
23668
+ };
23669
+ }
23664
23670
  function readFromEnv() {
23665
23671
  return {
23666
23672
  twinToken: asString(process.env.AMIKO_TWIN_TOKEN) ?? asString(process.env.AMIKO_USER_TOKEN),
23667
23673
  twinId: asString(process.env.AMIKO_TWIN_ID),
23668
23674
  userId: asString(process.env.AMIKO_USER_ID),
23669
23675
  platformUrl: asString(process.env.AMIKO_PLATFORM_URL),
23670
- apiBase: asString(process.env.AMIKO_API_BASE)
23676
+ apiBase: asString(process.env.AMIKO_API_BASE),
23677
+ ...readRoutingIdsFromEnv()
23671
23678
  };
23672
23679
  }
23680
+ function resolveFileAuth(path) {
23681
+ const fromFile = readFromFile(path);
23682
+ if (!fromFile)
23683
+ return null;
23684
+ return { ...fromFile, ...readRoutingIdsFromEnv() };
23685
+ }
23673
23686
  function resolveAuth() {
23674
23687
  const filePath = findAmikoJsonPath();
23675
23688
  if (filePath) {
23676
- const fromFile = readFromFile(filePath);
23677
- if (fromFile)
23678
- return fromFile;
23689
+ const merged = resolveFileAuth(filePath);
23690
+ if (merged)
23691
+ return merged;
23679
23692
  }
23680
23693
  return readFromEnv();
23681
23694
  }
@@ -26442,6 +26455,13 @@ var ETA = {
26442
26455
  function statusOf(err) {
26443
26456
  return err?.status;
26444
26457
  }
26458
+ function autoApproveLimit() {
26459
+ const raw = process.env.AMIKO_AUTO_APPROVE_LIMIT;
26460
+ if (typeof raw !== "string" || raw.trim() === "")
26461
+ return;
26462
+ const limit = Number(raw);
26463
+ return Number.isFinite(limit) ? limit : undefined;
26464
+ }
26445
26465
  function collectOption(value, previous) {
26446
26466
  return [...previous ?? [], value];
26447
26467
  }
@@ -26451,7 +26471,7 @@ function assertMaxUrls(resourceName, urls, max) {
26451
26471
  process.exit(1);
26452
26472
  }
26453
26473
  }
26454
- function printCompleted(final, title) {
26474
+ function printCompleted(final, title, deliveredToThisSession = false) {
26455
26475
  console.log(heading(`${title}
26456
26476
  `));
26457
26477
  if (final.prompt)
@@ -26460,6 +26480,9 @@ function printCompleted(final, title) {
26460
26480
  console.log(label("URL", final.assetUrl));
26461
26481
  if (final.assetMime)
26462
26482
  console.log(label("Type", final.assetMime));
26483
+ if (deliveredToThisSession) {
26484
+ console.log(success("Already delivered to the user in this chat automatically. Do NOT send this file again and do NOT paste the URL — you would duplicate it."));
26485
+ }
26463
26486
  console.log("");
26464
26487
  if (final.charged) {
26465
26488
  const amt = final.chargeAmount ?? final.costAmiko ?? "?";
@@ -26488,6 +26511,7 @@ async function submitCreateJob(opts) {
26488
26511
  console.error(dim("--token is ignored with --pay credits (credits come from your Amiko account, not a wallet token)."));
26489
26512
  }
26490
26513
  let costLine = payCredits ? "reserved from your Amiko account credits, captured on success (released on failure)" : "charged from your twin wallet on success (nothing on failure)";
26514
+ let quotedCostAmiko;
26491
26515
  try {
26492
26516
  const q = await amikoWebFetch(auth, "/api/create/quote", {
26493
26517
  method: "POST",
@@ -26497,11 +26521,29 @@ async function submitCreateJob(opts) {
26497
26521
  if (q.cost_usd != null) {
26498
26522
  costLine = payCredits ? `≈ ${Math.ceil(q.cost_usd * 1e4)} Amiko credits (≈ $${q.cost_usd.toFixed(4)}) — reserved on submit, captured only on success` : `${q.cost_amiko ?? "?"} AMIKO (≈ $${q.cost_usd.toFixed(4)}) — charged only on success`;
26499
26523
  }
26524
+ if (typeof q.cost_amiko === "string" && q.cost_amiko.trim() !== "") {
26525
+ const n = Number(q.cost_amiko);
26526
+ if (Number.isFinite(n))
26527
+ quotedCostAmiko = n;
26528
+ }
26500
26529
  } catch {}
26530
+ const preferredToken = typeof opts.jobBody.preferredToken === "string" ? opts.jobBody.preferredToken.toUpperCase() : undefined;
26531
+ const chargeIsAmiko = !payCredits && (preferredToken === undefined || preferredToken === "AMIKO");
26532
+ const effectiveChargeToken = preferredToken ?? "AMIKO";
26533
+ let yes = opts.yes;
26534
+ let autoApproved = false;
26535
+ if (!yes) {
26536
+ const limit = autoApproveLimit();
26537
+ if (limit !== undefined && quotedCostAmiko !== undefined && quotedCostAmiko > 0 && chargeIsAmiko && quotedCostAmiko <= limit) {
26538
+ yes = true;
26539
+ autoApproved = true;
26540
+ console.log(dim(`Auto-approved (cost ${quotedCostAmiko} ≤ limit ${limit} AMIKO)`));
26541
+ }
26542
+ }
26501
26543
  await requireApproval({
26502
26544
  cost: costLine,
26503
26545
  summary: opts.summary,
26504
- yes: opts.yes,
26546
+ yes,
26505
26547
  commandExample: `amiko create ${opts.mode.toLowerCase()} ...`
26506
26548
  });
26507
26549
  const isTTY = process.stderr.isTTY;
@@ -26511,16 +26553,21 @@ async function submitCreateJob(opts) {
26511
26553
  spinner.stop();
26512
26554
  };
26513
26555
  let jobId;
26556
+ let routedAgentId;
26557
+ let routedSessionId;
26514
26558
  try {
26515
26559
  const submit = await amikoWebFetch(auth, "/api/create/jobs", {
26516
26560
  method: "POST",
26517
26561
  body: {
26518
26562
  ...opts.jobBody,
26519
26563
  twinId: auth.twinId,
26564
+ ...autoApproved ? { preferredToken: effectiveChargeToken, strictToken: true } : {},
26520
26565
  ...payCredits ? {
26521
26566
  paymentMethod: { kind: "account_credits" },
26522
26567
  requestId: randomUUID()
26523
- } : {}
26568
+ } : {},
26569
+ ...auth.agentId ? { agentId: auth.agentId } : {},
26570
+ ...auth.sessionId ? { sessionId: auth.sessionId } : {}
26524
26571
  },
26525
26572
  timeoutMs: 30000
26526
26573
  });
@@ -26530,6 +26577,8 @@ async function submitCreateJob(opts) {
26530
26577
  process.exit(1);
26531
26578
  }
26532
26579
  jobId = submit.jobId;
26580
+ routedAgentId = submit.agentId;
26581
+ routedSessionId = submit.sessionId;
26533
26582
  } catch (e5) {
26534
26583
  stop();
26535
26584
  const webErr = e5;
@@ -26550,19 +26599,24 @@ async function submitCreateJob(opts) {
26550
26599
  }
26551
26600
  process.exit(1);
26552
26601
  }
26602
+ const sessionRouted = !!(routedAgentId && routedSessionId);
26553
26603
  stop();
26554
26604
  if (opts.raw) {
26555
26605
  console.log(JSON.stringify({
26556
26606
  jobId,
26557
26607
  status: "PENDING",
26558
26608
  check: `amiko create status ${jobId}`,
26559
- eta: ETA[opts.mode]
26609
+ eta: ETA[opts.mode],
26610
+ ...sessionRouted ? { sessionRouted: true, deliveredToSession: routedSessionId } : {}
26560
26611
  }, null, 2));
26561
26612
  return;
26562
26613
  }
26563
26614
  console.log(success(`Submitted — job ${jobId}`));
26564
26615
  console.log(label("Status", "PENDING — generating in the background"));
26565
26616
  console.log(label("Check result", `amiko create status ${jobId}`));
26617
+ if (sessionRouted) {
26618
+ console.log(dim("A generating-placeholder is already showing in the chat and the finished media will be delivered there automatically — do not send the file or URL yourself."));
26619
+ }
26566
26620
  console.log(dim(`Usually ready in ${ETA[opts.mode]} — charged only on success. (Also: amiko create media)`));
26567
26621
  if (opts.mode === "VIDEO") {
26568
26622
  console.log(dim("Async video: HTTP 202 / PENDING means the job was accepted — not a failure. Poll status after ~2–9 min."));
@@ -26610,7 +26664,8 @@ function registerCreateCommand(create2) {
26610
26664
  mode: "VIDEO",
26611
26665
  model,
26612
26666
  size: opts.resolution,
26613
- seconds
26667
+ seconds,
26668
+ ...referenceVideoUrls.length ? { referenceVideoUrls } : {}
26614
26669
  },
26615
26670
  jobBody: {
26616
26671
  mode: "VIDEO",
@@ -26637,19 +26692,54 @@ function registerCreateCommand(create2) {
26637
26692
  }
26638
26693
  });
26639
26694
  });
26640
- create2.command("tts <text>").description("Generate speech from text (Create Studio)").option("--model <model>", "TTS model id", "eleven_multilingual_v2").option("--voice <voiceId>", "Voice id", "21m00Tcm4TlvDq8ikWAM").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--pay <method>", "Payment method: wallet (twin wallet, default) or credits (Amiko account credits — unified accounts only)", "wallet").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (text, opts) => {
26695
+ const DEFAULT_EL_TTS_MODEL = "eleven_multilingual_v2";
26696
+ const DEFAULT_EL_TTS_VOICE = "21m00Tcm4TlvDq8ikWAM";
26697
+ const DEFAULT_MM_TTS_MODEL = "speech-2.8-turbo";
26698
+ const DEFAULT_MM_TTS_VOICE = "English_expressive_narrator";
26699
+ const MINIMAX_TTS_MODELS = new Set([
26700
+ "speech-2.8-turbo",
26701
+ "speech-2.8-hd",
26702
+ "speech-2.6-turbo",
26703
+ "speech-2.6-hd"
26704
+ ]);
26705
+ create2.command("tts <text>").description("Generate speech from text (Create Studio)").option("--provider <name>", "TTS provider: minimax | elevenlabs (sets defaults when --model/--voice omitted)").option("--model <model>", `TTS model id (MiniMax: speech-2.8-turbo|hd, speech-2.6-turbo|hd; ElevenLabs: eleven_*)`).option("--voice <voiceId>", "Voice id (MiniMax system e.g. English_expressive_narrator, or a cloned voice_id; ElevenLabs voice hash)").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--pay <method>", "Payment method: wallet (twin wallet, default) or credits (Amiko account credits — unified accounts only)", "wallet").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (text, opts) => {
26706
+ const providerRaw = typeof opts.provider === "string" ? opts.provider.trim().toLowerCase() : "";
26707
+ if (providerRaw && providerRaw !== "minimax" && providerRaw !== "elevenlabs") {
26708
+ console.error(error("--provider must be minimax or elevenlabs"));
26709
+ process.exit(1);
26710
+ }
26711
+ let model = typeof opts.model === "string" && opts.model.trim().length > 0 ? opts.model.trim() : undefined;
26712
+ let voice = typeof opts.voice === "string" && opts.voice.trim().length > 0 ? opts.voice.trim() : undefined;
26713
+ let provider = providerRaw === "minimax" || providerRaw === "elevenlabs" ? providerRaw : model && MINIMAX_TTS_MODELS.has(model) ? "minimax" : model && model.startsWith("eleven") ? "elevenlabs" : "elevenlabs";
26714
+ if (!model) {
26715
+ model = provider === "minimax" ? DEFAULT_MM_TTS_MODEL : DEFAULT_EL_TTS_MODEL;
26716
+ }
26717
+ if (!voice) {
26718
+ voice = provider === "minimax" ? DEFAULT_MM_TTS_VOICE : DEFAULT_EL_TTS_VOICE;
26719
+ }
26720
+ if (MINIMAX_TTS_MODELS.has(model) && providerRaw !== "elevenlabs") {
26721
+ provider = "minimax";
26722
+ }
26723
+ if (provider === "minimax" && !MINIMAX_TTS_MODELS.has(model)) {
26724
+ console.error(error(`--provider minimax requires a MiniMax speech model (${[...MINIMAX_TTS_MODELS].join(", ")})`));
26725
+ process.exit(1);
26726
+ }
26727
+ if (provider === "elevenlabs" && !model.startsWith("eleven")) {
26728
+ console.error(error("--provider elevenlabs requires an eleven_* model"));
26729
+ process.exit(1);
26730
+ }
26641
26731
  await submitCreateJob({
26642
26732
  mode: "TTS",
26643
- summary: `Create speech: ${text.slice(0, 80)}${text.length > 80 ? "…" : ""}`,
26733
+ summary: `Create speech (${provider}/${model}): ${text.slice(0, 80)}${text.length > 80 ? "…" : ""}`,
26644
26734
  raw: opts.raw,
26645
26735
  pay: opts.pay,
26646
26736
  yes: opts.yes,
26647
- quoteBody: { mode: "TTS", model: opts.model, prompt: text },
26737
+ quoteBody: { mode: "TTS", model, prompt: text, voiceId: voice },
26648
26738
  jobBody: {
26649
26739
  mode: "TTS",
26650
26740
  prompt: text,
26651
- model: opts.model,
26652
- voiceId: opts.voice,
26741
+ model,
26742
+ voiceId: voice,
26653
26743
  ...opts.token ? { preferredToken: opts.token } : {}
26654
26744
  }
26655
26745
  });
@@ -26713,8 +26803,9 @@ function registerCreateCommand(create2) {
26713
26803
  }
26714
26804
  process.exit(1);
26715
26805
  }
26806
+ const deliveredToThisSession = !!(p.agentId && p.sessionId && auth.sessionId && p.sessionId === auth.sessionId);
26716
26807
  if (opts.raw) {
26717
- console.log(JSON.stringify(p, null, 2));
26808
+ console.log(JSON.stringify(deliveredToThisSession ? { ...p, sessionRouted: true, deliveredToSession: p.sessionId } : p, null, 2));
26718
26809
  return;
26719
26810
  }
26720
26811
  if (p.status === "FAILED") {
@@ -26726,7 +26817,7 @@ function registerCreateCommand(create2) {
26726
26817
  console.log(dim(`Job id: ${jobId}`));
26727
26818
  return;
26728
26819
  }
26729
- printCompleted(p, "Job complete");
26820
+ printCompleted(p, "Job complete", deliveredToThisSession);
26730
26821
  });
26731
26822
  create2.command("media").description("List your recent Create Studio generations").option("--limit <n>", "How many to show (max 50)", "20").option("--service <type>", "Filter by type: image, video, tts, music, sfx").option("--raw", "Output raw JSON").action(async (opts) => {
26732
26823
  let auth;
@@ -28311,18 +28402,32 @@ async function fetchWithTimeout2(url2, init = {}, timeoutMs) {
28311
28402
  }
28312
28403
  function registerServicesCommand(program2) {
28313
28404
  const svc = program2.command("service").alias("svc").description("Call any MPP service — pay in AMIKO, USDC, or USDT");
28314
- svc.command("tts <voiceId> <text>").description("Text-to-speech — 1 AMIKO").option("--raw", "Output raw response").option("--wallet <address>", "Wallet to pay from (defaults to your Solana wallet — run `amiko wallets create --chain solana` if none)").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (voiceId, text, opts) => {
28405
+ svc.command("tts <voiceId> <text>").description("Text-to-speech via mpp /v1/tts (pay-per-call)").option("--provider <name>", "elevenlabs (default) | minimax").option("--model <model>", "MiniMax speech-* model when provider=minimax").option("--raw", "Output raw response").option("--wallet <address>", "Wallet to pay from (defaults to your Solana wallet — run `amiko wallets create --chain solana` if none)").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (voiceId, text, opts) => {
28406
+ const provider = typeof opts.provider === "string" ? opts.provider.trim().toLowerCase() : "";
28407
+ const body = { text };
28408
+ if (provider === "minimax") {
28409
+ body.provider = "minimax";
28410
+ if (opts.model)
28411
+ body.model_id = opts.model;
28412
+ else
28413
+ body.model_id = "speech-2.8-turbo";
28414
+ } else if (provider && provider !== "elevenlabs") {
28415
+ console.error(error("--provider must be minimax or elevenlabs"));
28416
+ process.exit(1);
28417
+ }
28418
+ const providerArgs = provider ? ` --provider ${provider}` : "";
28419
+ const modelArgs = provider === "minimax" ? ` --model ${JSON.stringify(typeof opts.model === "string" && opts.model ? opts.model : "speech-2.8-turbo")}` : "";
28315
28420
  await requireApproval({
28316
- cost: "1 AMIKO",
28317
- summary: `Text-to-speech (voice ${voiceId}): "${text.slice(0, 60)}${text.length > 60 ? "…" : ""}"`,
28421
+ cost: "variable (see mpp pricing)",
28422
+ summary: `Text-to-speech (voice ${voiceId}${provider ? `, ${provider}` : ""}): "${text.slice(0, 60)}${text.length > 60 ? "…" : ""}"`,
28318
28423
  yes: opts.yes,
28319
- commandExample: `amiko markets service tts ${voiceId} ${JSON.stringify(text)}`
28424
+ commandExample: `amiko markets service tts ${voiceId} ${JSON.stringify(text)}${providerArgs}${modelArgs}`
28320
28425
  });
28321
28426
  await callService({
28322
28427
  name: "Text-to-Speech",
28323
28428
  method: "POST",
28324
28429
  path: `/v1/tts/${encodeURIComponent(voiceId)}`,
28325
- body: { text },
28430
+ body,
28326
28431
  amiko: "1",
28327
28432
  opts
28328
28433
  });
@@ -29201,8 +29306,18 @@ function twinFromFlag2(opts) {
29201
29306
  throw e5;
29202
29307
  }
29203
29308
  }
29309
+ function parseProvider(raw, fallback2) {
29310
+ if (raw == null || raw.trim() === "")
29311
+ return fallback2;
29312
+ const p = raw.trim().toLowerCase();
29313
+ if (p !== "minimax" && p !== "elevenlabs") {
29314
+ console.error(error("--provider must be minimax or elevenlabs"));
29315
+ process.exit(1);
29316
+ }
29317
+ return p;
29318
+ }
29204
29319
  function registerVoiceCommand(program2) {
29205
- program2.command("design").description("Design a voice from a text description").requiredOption("--description <text>", "Voice description (min 20 chars)").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (opts) => {
29320
+ program2.command("design").description("Design a voice from a text description (ElevenLabs)").requiredOption("--description <text>", "Voice description (min 20 chars)").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (opts) => {
29206
29321
  if (opts.description.trim().length < 20) {
29207
29322
  console.error(error("--description must be at least 20 characters"));
29208
29323
  process.exit(1);
@@ -29230,7 +29345,7 @@ function registerVoiceCommand(program2) {
29230
29345
  process.exit(1);
29231
29346
  }
29232
29347
  });
29233
- program2.command("create").description("Create a voice from a previously designed preview").requiredOption("--sample <id>", "generated_voice_id from voice design").option("--description <text>", "Optional voice description").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (opts) => {
29348
+ program2.command("create").description("Create a voice from a previously designed preview (ElevenLabs)").requiredOption("--sample <id>", "generated_voice_id from voice design").option("--description <text>", "Optional voice description").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (opts) => {
29234
29349
  const { twinId, auth } = twinFromFlag2(opts);
29235
29350
  const spinner = opts.json ? null : ora("Creating voice...").start();
29236
29351
  try {
@@ -29256,7 +29371,8 @@ function registerVoiceCommand(program2) {
29256
29371
  process.exit(1);
29257
29372
  }
29258
29373
  });
29259
- program2.command("clone <source>").description("Clone a voice from an audio file (path | https URL | -)").option("--twin <idOrName>", "Target twin id or name").option("--name <filename>", "Override filename").option("--stdin", "Read from stdin (source must be -)").option("--json", "Output as JSON").action(async (source, opts) => {
29374
+ program2.command("clone <source>").description("Clone a voice from an audio file (path | https URL | -). Default provider: minimax").option("--provider <name>", "minimax (default, mpp rapid clone) | elevenlabs (platform IVC)", "minimax").option("--voice-id <id>", "MiniMax: optional custom voice_id (8–256 chars)").option("--model <model>", "MiniMax preview TTS model", "speech-2.8-turbo").option("--preview-text <text>", "MiniMax: preview line (empty string skips preview)").option("--twin <idOrName>", "Target twin id or name").option("--name <filename>", "Override filename").option("--stdin", "Read from stdin (source must be -)").option("--json", "Output as JSON").action(async (source, opts) => {
29375
+ const provider = parseProvider(opts.provider, "minimax");
29260
29376
  const { twinId, auth } = twinFromFlag2(opts);
29261
29377
  const spinner = opts.json ? null : ora("Reading audio...").start();
29262
29378
  try {
@@ -29266,17 +29382,30 @@ function registerVoiceCommand(program2) {
29266
29382
  hintMime: "audio/mpeg"
29267
29383
  });
29268
29384
  if (spinner)
29269
- spinner.text = `Cloning voice from ${file2.name}...`;
29385
+ spinner.text = `Cloning voice (${provider}) from ${file2.name}...`;
29270
29386
  const form = new FormData;
29271
29387
  form.set("audio", new Blob([new Uint8Array(file2.buffer)], { type: file2.mime }), file2.name);
29388
+ form.set("provider", provider);
29389
+ if (provider === "minimax") {
29390
+ if (opts.voiceId)
29391
+ form.set("voice_id", opts.voiceId);
29392
+ if (opts.model)
29393
+ form.set("model_id", opts.model);
29394
+ if (opts.previewText !== undefined)
29395
+ form.set("text", opts.previewText);
29396
+ }
29272
29397
  const result = await amikoWebFetch(auth, `/api/agents/${twinId}/voice/clone`, { method: "POST", multipart: form, timeoutMs: 180000 });
29273
29398
  spinner?.stop();
29274
29399
  renderOutput(result, (r) => {
29275
- console.log(success("Voice cloned"));
29400
+ console.log(success(`Voice cloned (${String(r.provider ?? provider)})`));
29276
29401
  if (r.voice_id)
29277
29402
  console.log(label("Voice ID", String(r.voice_id)));
29278
29403
  if (r.voice_status)
29279
29404
  console.log(label("Status", String(r.voice_status)));
29405
+ if (r.demo_audio)
29406
+ console.log(label("Demo", String(r.demo_audio)));
29407
+ console.log("");
29408
+ console.log(dim(`Synthesize: amiko create tts "Hello" --provider ${provider} --voice ${String(r.voice_id ?? "<id>")} --yes`));
29280
29409
  }, { json: opts.json });
29281
29410
  } catch (e5) {
29282
29411
  spinner?.stop();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.14.0-beta.12",
3
+ "version": "0.14.0-beta.13",
4
4
  "description": "Amiko CLI — swap tokens, manage credits, bridge cross-chain, and call marketplace agents",
5
5
  "type": "module",
6
6
  "bin": {