@heyamiko/amiko-cli 0.16.3 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -879,16 +879,16 @@ Generate a video (Create Studio)
879
879
 
880
880
  | Flag | Default | Description |
881
881
  |---|---|---|
882
- | `--model <model>` | | Video model: MiniMax-Hailuo-02 (default for prompt-only; T2V+I2V), MiniMax-Hailuo-2.3-Fast (default with --first-frame; I2V only), MiniMax-Hailuo-2.3 (T2V+I2V), MiniMax-H3 (aliases h3, hailuo-03; T2V+I2V+reference-to-video, 768P\|2K, any 4–15s, billed per second), veo-3.1[-fast\|-lite]-generate-preview (Google; durations clamp to 4/6/8s), grok-imagine-video (xAI), dreamina-seedance-2-5-260628 (BytePlus Seedance 2.5; any whole 4–30s, billed per token so a long 1080p clip runs into dollars — quote it), other dreamina-seedance-* (BytePlus, ≤15s). ONLY these — kling and other ids are not supported |
883
- | `--resolution <res>` | `768P` | 512P, 720P, 768P, 1080P (MiniMax, Grok, Seedance); 720p, 1080p, 4k (Veo); 768P or 2K (MiniMax-H3) |
884
- | `--seconds <n>` | `6` | Clip length: 6 or 10 (Hailuo), 4/6/8 (Veo), 4/6/8/10 (Grok, Seedance 2.0), any whole number 4–15 (MiniMax-H3), any whole number 4–30 (Seedance 2.5) |
882
+ | `--model <model>` | | Video model: MiniMax-Hailuo-02 (default for prompt-only; T2V+I2V), MiniMax-Hailuo-2.3-Fast (default with --first-frame; I2V only), MiniMax-Hailuo-2.3 (T2V+I2V), S2V-01 (default with --subject-reference; the only MiniMax model that accepts subject_reference), MiniMax-H3 (aliases h3, hailuo-03; T2V+I2V+reference-to-video, 768P\|2K, any 4–15s, billed per second), veo-3.1[-fast\|-lite]-generate-preview (Google; durations clamp to 4/6/8s), grok-imagine-video (xAI), dreamina-seedance-2-5-260628 (BytePlus Seedance 2.5; any whole 4–30s, billed per token so a long 1080p clip runs into dollars — quote it), other dreamina-seedance-* (BytePlus, ≤15s). ONLY these — kling and other ids are not supported |
883
+ | `--resolution <res>` | `768P` | 512P, 720P, 768P, 1080P (MiniMax, Grok, Seedance); 720p, 1080p, 4k (Veo); 768P or 2K (MiniMax-H3). Hailuo pairs these with --seconds: 1080P is 6s only, and 512P exists on MiniMax-Hailuo-02 alone |
884
+ | `--seconds <n>` | `6` | Clip length: 6 or 10 (Hailuo — 10 needs 768P or lower, since 1080P is 6s only), 4/6/8 (Veo), 4/6/8/10 (Grok, Seedance 2.0), any whole number 4–15 (MiniMax-H3), any whole number 4–30 (Seedance 2.5) |
885
885
  | `--aspect <ratio>` | | Aspect ratio (provider-dependent). MiniMax-H3 text-to-video needs a concrete ratio (21:9, 16:9, 4:3, 1:1, 3:4, 9:16); defaults to 16:9 when omitted |
886
886
  | `--first-frame <file|url>` | | Image-to-video: first frame — local file path, https URL, or data URI |
887
887
  | `--last-frame <file|url>` | | First-and-last-frame video: ending frame — local file path, https URL, or data URI. MiniMax-H3, Seedance and Veo only (Seedance/Veo also need --first-frame) |
888
- | `--reference-image <file|url>` | | Reference-to-video: local file path or https URL (repeatable). MiniMax-H3 / Seedance / Grok: max 9; Veo: max 3; Hailuo v1: NOT supported — use --subject-reference |
888
+ | `--reference-image <file|url>` | | Reference-to-video: local file path or https URL (repeatable). MiniMax-H3 / Seedance / Grok: max 9; Veo: max 3; Hailuo v1: NOT supported — use --model MiniMax-H3 |
889
889
  | `--reference-video <file|url>` | | Reference-to-video: local file path or https URL (repeatable, max 3). MiniMax-H3 and Seedance only; combined reference video+audio must be ≤15s, and H3 bills 15 input-seconds whenever any is attached |
890
890
  | `--reference-audio <file|url>` | | Reference-to-video: local file path or https URL (repeatable, max 3). MiniMax-H3 and Seedance only; needs an image or video reference alongside, H3 wants 2–15s per clip |
891
- | `--subject-reference <file|url>` | | MiniMax S2V / H3: subject/character reference image — local file path or https URL. Takes the place of --reference-image rather than adding to it |
891
+ | `--subject-reference <file|url>` | | Subject/character reference image — local file path or https URL. Takes the place of --reference-image rather than adding to it. Selects S2V-01 unless --model says otherwise; Hailuo 02/2.3/2.3-Fast reject it, use MiniMax-H3 for reference-to-video |
892
892
  | `--generate-audio` | | Seedance: generate audio in the output video |
893
893
  | `--no-generate-audio` | | Seedance: output silent video |
894
894
  | `--watermark` | | Seedance: burn in a watermark |
@@ -2042,6 +2042,7 @@ Create posts (notes) and comments on the feed — text, images, and documents
2042
2042
  | `post create` | Create a post (a.k.a. note) on your feed |
2043
2043
  | `post drafts` | List your draft posts (newest first) |
2044
2044
  | `post publish` | Publish a draft post (stamps a fresh timestamp and notifies mentions) |
2045
+ | `post delete` | Delete one of your posts. Use this to clean up a post that went out wrong (bad link, test post, wrong version) |
2045
2046
  | `post comments` | List comments on a post |
2046
2047
  | `post comment` | Comment on a post |
2047
2048
  | `post likers` | List everyone who liked ONE OF THE OWNER'S OWN posts — pages through ALL likes (up to a cap), for 'who/which friends liked this'. Another user's post returns 403: engagement lists are visible only to the post's owner |
@@ -2058,6 +2059,7 @@ Create a post (a.k.a. note) on your feed
2058
2059
  | `--media <pathOrUrl...>` | | Attach media: local IMAGE file paths (uploaded automatically); Amiko-hosted URLs (image/music/video from `amiko create`); or `drive:<docId>` for a media file in your Drive (`amiko drive list`) |
2059
2060
  | `--doc <pathOrUrl...>` | | Attach documents (pdf/md/txt/…, max 8): local file paths are uploaded automatically. Rendered as file cards, not images |
2060
2061
  | `--draft` | | Save as a draft instead of publishing (list: amiko post drafts · publish: amiko post publish <id>) |
2062
+ | `--yes` | | Skip the publish confirmation (drafts never prompt). Only pass this after the owner has reviewed the exact copy and approved sending it. |
2061
2063
  | `--json` | | Output as JSON |
2062
2064
 
2063
2065
  #### `amiko post drafts`
@@ -2076,6 +2078,16 @@ Publish a draft post (stamps a fresh timestamp and notifies mentions)
2076
2078
 
2077
2079
  | Flag | Default | Description |
2078
2080
  |---|---|---|
2081
+ | `--yes` | | Skip the publish confirmation. Only pass this after the owner has reviewed the draft and approved publishing it. |
2082
+ | `--json` | | Output as JSON |
2083
+
2084
+ #### `amiko post delete <postId>`
2085
+
2086
+ Delete one of your posts. Use this to clean up a post that went out wrong (bad link, test post, wrong version)
2087
+
2088
+ | Flag | Default | Description |
2089
+ |---|---|---|
2090
+ | `--yes` | | Skip confirmation. Only pass this after the owner has confirmed which post to remove. |
2079
2091
  | `--json` | | Output as JSON |
2080
2092
 
2081
2093
  #### `amiko post comments`
@@ -2617,6 +2629,13 @@ npm publish
2617
2629
 
2618
2630
  ## Changelog
2619
2631
 
2632
+ ### 0.16.4
2633
+
2634
+ - **`--subject-reference` now works at all.** MiniMax accepts `subject_reference` on **S2V-01 and nothing else**, so every job that used the flag was rejected upstream with `param 'subject_reference' is incompatible with model MiniMax-Hailuo-02` — including the CLI's own documented example, because with no `--model` the flag resolved to exactly that id. The flag now selects `S2V-01` on its own (checked before `--first-frame`: the model follows the reference, not the frame), and naming a Hailuo model beside it is refused **before the quote**, with an error that points at `--model S2V-01` or `--model MiniMax-H3`. This corrects the 0.16.1 note below, which said Hailuo v1 accepts the flag — it reads the field's *shape*, but MiniMax rejects the *pairing*.
2635
+ - **A permanently-invalid model/flag pairing answers 400, not 502.** It had no branch in the video error classifier, so it took the 502 default — which reads "transient, retry", and callers were resubmitting a request that can never succeed.
2636
+ - **`--seconds` help no longer cites the one table Seedance 2.5 isn't subject to.** It still read "4/6/8/10 (Grok, Seedance)" after 2.5 gained its 4–30s range. The README command reference is generated from that string, so both were wrong together.
2637
+ - **Reference video and audio counts are enforced, not just images.** The validator capped images at 9 but let any number of clips through. The CLI and the studio both apply 3/3, so only a direct API caller reached it — where BytePlus either rejects after the reservation is taken, or accepts, silently drops the extras, and bills for what it ran.
2638
+
2620
2639
  ### 0.16.3
2621
2640
 
2622
2641
  - **Seedance 2.5 (`dreamina-seedance-2-5-260628`) is a supported `create video --model`**, and it is the only model that goes past 15s — any whole `--seconds` from **4 to 30**. The 4|6|8|10 set never applied to it, and neither does H3's 4–15 range, so `--seconds 22` is now accepted rather than rejected against a table it was never subject to.
package/dist/index.js CHANGED
@@ -29311,6 +29311,49 @@ var NON_H3_MAX_PROMPT_CHARS = 2000;
29311
29311
  var H3_MAX_REFERENCE_FILES = 12;
29312
29312
  var H3_DEFAULT_T2V_ASPECT = "16:9";
29313
29313
  var NON_H3_VIDEO_SECONDS = new Set([4, 6, 8, 10]);
29314
+ var HAILUO_COMBOS = {
29315
+ "MiniMax-Hailuo-02:512P": [6, 10],
29316
+ "MiniMax-Hailuo-02:768P": [6, 10],
29317
+ "MiniMax-Hailuo-02:1080P": [6],
29318
+ "MiniMax-Hailuo-2.3:768P": [6, 10],
29319
+ "MiniMax-Hailuo-2.3:1080P": [6],
29320
+ "MiniMax-Hailuo-2.3-Fast:768P": [6, 10],
29321
+ "MiniMax-Hailuo-2.3-Fast:1080P": [6]
29322
+ };
29323
+ var HAILUO_MATRIX_FAMILY = /^MiniMax-Hailuo-/i;
29324
+ function canonicalHailuoModel(model) {
29325
+ const aliases = {
29326
+ "hailuo-02": "MiniMax-Hailuo-02",
29327
+ "hailuo-2.3": "MiniMax-Hailuo-2.3",
29328
+ "hailuo-2.3-fast": "MiniMax-Hailuo-2.3-Fast"
29329
+ };
29330
+ const byAlias = aliases[model.toLowerCase()];
29331
+ if (byAlias)
29332
+ return byAlias;
29333
+ if (!HAILUO_MATRIX_FAMILY.test(model))
29334
+ return null;
29335
+ const known = ["MiniMax-Hailuo-02", "MiniMax-Hailuo-2.3", "MiniMax-Hailuo-2.3-Fast"];
29336
+ return known.find((m) => m.toLowerCase() === model.toLowerCase()) ?? null;
29337
+ }
29338
+ function hailuoComboError(model, resolution, seconds) {
29339
+ const id = canonicalHailuoModel(model);
29340
+ if (!id)
29341
+ return null;
29342
+ const upper = resolution.toUpperCase();
29343
+ const tier = upper === "720P" ? "768P" : upper;
29344
+ const allowed = HAILUO_COMBOS[`${id}:${tier}`];
29345
+ if (allowed?.includes(seconds))
29346
+ return null;
29347
+ const tiersForSeconds = Object.keys(HAILUO_COMBOS).filter((k) => k.startsWith(`${id}:`) && HAILUO_COMBOS[k].includes(seconds)).map((k) => k.split(":")[1]);
29348
+ const fixes = [];
29349
+ if (allowed?.length) {
29350
+ fixes.push(`${tier} renders at ${allowed.join("s or ")}s`);
29351
+ }
29352
+ if (tiersForSeconds.length) {
29353
+ fixes.push(`${seconds}s renders at ${tiersForSeconds.join(" or ")}`);
29354
+ }
29355
+ return `${id} does not support ${tier} at ${seconds}s` + (fixes.length ? ` — ${fixes.join("; ")}.` : ".") + " Use --model MiniMax-H3 for any 4–15s at 768P or 2K.";
29356
+ }
29314
29357
  var SEEDANCE_25_MODEL = /^(dreamina-)?seedance-2-5(-|$)/i;
29315
29358
  var SEEDANCE_25_MIN_SECONDS = 4;
29316
29359
  var SEEDANCE_25_MAX_SECONDS = 30;
@@ -29399,6 +29442,11 @@ var VIDEO_CAPS = {
29399
29442
  label: "MiniMax Hailuo"
29400
29443
  }
29401
29444
  };
29445
+ function acceptsSubjectReference(model) {
29446
+ if (videoFamily(model) !== "hailuo")
29447
+ return true;
29448
+ return /^S2V-/i.test(model);
29449
+ }
29402
29450
  function effectiveReferenceImageCount(input) {
29403
29451
  if (VIDEO_CAPS[videoFamily(input.model)].subjectReferenceNative) {
29404
29452
  return input.referenceImageUrls.length;
@@ -29411,8 +29459,11 @@ function validateVideoReferences(input) {
29411
29459
  const images = effectiveReferenceImageCount(input);
29412
29460
  const videos = input.referenceVideoUrls.length;
29413
29461
  const audio = input.referenceAudioUrls.length;
29462
+ if (input.subjectReference && !acceptsSubjectReference(input.model)) {
29463
+ return `${caps.label} rejects --subject-reference: MiniMax accepts subject_reference on S2V-01 only, and ${input.model} fails the submit with "param 'subject_reference' is incompatible with model ${input.model}". Use --model S2V-01, or --model MiniMax-H3 for reference-to-video, or --first-frame to start from an image.`;
29464
+ }
29414
29465
  if (input.referenceImageUrls.length > 0 && caps.referenceImages === 0) {
29415
- return `${caps.label} ignores --reference-image — mpp's ${family} branch never reads it, so the video would be generated and billed without it. Use --subject-reference for a single character image, or --model MiniMax-H3 for true reference-to-video.`;
29466
+ return `${caps.label} ignores --reference-image — mpp's ${family} branch never reads it, so the video would be generated and billed without it. Use --model S2V-01 with --subject-reference for a single character image, or --model MiniMax-H3 for true reference-to-video.`;
29416
29467
  }
29417
29468
  if (videos > 0 && caps.referenceVideos === 0) {
29418
29469
  return `${caps.label} ignores --reference-video — it would be dropped and the job billed anyway. Only MiniMax-H3 and Seedance read reference clips.`;
@@ -29972,8 +30023,8 @@ function registerCreateCommand(create2) {
29972
30023
  }
29973
30024
  });
29974
30025
  });
29975
- create2.command("video <prompt>").description("Generate a video (Create Studio)").option("--model <model>", "Video model: MiniMax-Hailuo-02 (default for prompt-only; T2V+I2V), MiniMax-Hailuo-2.3-Fast (default with --first-frame; I2V only), MiniMax-Hailuo-2.3 (T2V+I2V), MiniMax-H3 (aliases h3, hailuo-03; T2V+I2V+reference-to-video, 768P|2K, any 4–15s, billed per second), veo-3.1[-fast|-lite]-generate-preview (Google; durations clamp to 4/6/8s), grok-imagine-video (xAI), dreamina-seedance-2-5-260628 (BytePlus Seedance 2.5; any whole 4–30s, billed per token so a long 1080p clip runs into dollars — quote it), other dreamina-seedance-* (BytePlus, ≤15s). ONLY these — kling and other ids are not supported").option("--resolution <res>", "512P, 720P, 768P, 1080P (MiniMax, Grok, Seedance); 720p, 1080p, 4k (Veo); 768P or 2K (MiniMax-H3)", "768P").option("--seconds <n>", "Clip length: 6 or 10 (Hailuo), 4/6/8 (Veo), 4/6/8/10 (Grok, Seedance 2.0), any whole number 4–15 (MiniMax-H3), any whole number 4–30 (Seedance 2.5)", "6").option("--aspect <ratio>", "Aspect ratio (provider-dependent). MiniMax-H3 text-to-video needs a concrete ratio (21:9, 16:9, 4:3, 1:1, 3:4, 9:16); defaults to 16:9 when omitted").option("--first-frame <file|url>", "Image-to-video: first frame — local file path, https URL, or data URI").option("--last-frame <file|url>", "First-and-last-frame video: ending frame — local file path, https URL, or data URI. MiniMax-H3, Seedance and Veo only (Seedance/Veo also need --first-frame)").option("--reference-image <file|url>", "Reference-to-video: local file path or https URL (repeatable). MiniMax-H3 / Seedance / Grok: max 9; Veo: max 3; Hailuo v1: NOT supported — use --subject-reference", collectOption, []).option("--reference-video <file|url>", "Reference-to-video: local file path or https URL (repeatable, max 3). MiniMax-H3 and Seedance only; combined reference video+audio must be ≤15s, and H3 bills 15 input-seconds whenever any is attached", collectOption, []).option("--reference-audio <file|url>", "Reference-to-video: local file path or https URL (repeatable, max 3). MiniMax-H3 and Seedance only; needs an image or video reference alongside, H3 wants 2–15s per clip", collectOption, []).option("--subject-reference <file|url>", "MiniMax S2V / H3: subject/character reference image — local file path or https URL. Takes the place of --reference-image rather than adding to it").option("--generate-audio", "Seedance: generate audio in the output video").option("--no-generate-audio", "Seedance: output silent video").option("--watermark", "Seedance: burn in a watermark").option("--no-watermark", "Seedance: no watermark").option("--camera-fixed", "Seedance: hold the camera still").option("--no-camera-fixed", "Seedance: allow camera movement").option("--prompt-optimizer", "MiniMax (H3 and Hailuo v1): let the model rewrite the prompt before generating").option("--no-prompt-optimizer", "MiniMax: use the prompt verbatim").option("--fast-pretreatment", "MiniMax Hailuo v1: faster input pre-processing (ignored by H3, Veo, Grok and Seedance)").option("--no-fast-pretreatment", "MiniMax Hailuo v1: standard input pre-processing").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--pay <method>", "Payment method: credits (Amiko account credits, default) or wallet (twin wallet)", "credits").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (prompt, opts) => {
29976
- const model = opts.model ?? (opts.firstFrame ? "MiniMax-Hailuo-2.3-Fast" : "MiniMax-Hailuo-02");
30026
+ create2.command("video <prompt>").description("Generate a video (Create Studio)").option("--model <model>", "Video model: MiniMax-Hailuo-02 (default for prompt-only; T2V+I2V), MiniMax-Hailuo-2.3-Fast (default with --first-frame; I2V only), MiniMax-Hailuo-2.3 (T2V+I2V), S2V-01 (default with --subject-reference; the only MiniMax model that accepts subject_reference), MiniMax-H3 (aliases h3, hailuo-03; T2V+I2V+reference-to-video, 768P|2K, any 4–15s, billed per second), veo-3.1[-fast|-lite]-generate-preview (Google; durations clamp to 4/6/8s), grok-imagine-video (xAI), dreamina-seedance-2-5-260628 (BytePlus Seedance 2.5; any whole 4–30s, billed per token so a long 1080p clip runs into dollars — quote it), other dreamina-seedance-* (BytePlus, ≤15s). ONLY these — kling and other ids are not supported").option("--resolution <res>", "512P, 720P, 768P, 1080P (MiniMax, Grok, Seedance); 720p, 1080p, 4k (Veo); 768P or 2K (MiniMax-H3). Hailuo pairs these with --seconds: 1080P is 6s only, and 512P exists on MiniMax-Hailuo-02 alone", "768P").option("--seconds <n>", "Clip length: 6 or 10 (Hailuo — 10 needs 768P or lower, since 1080P is 6s only), 4/6/8 (Veo), 4/6/8/10 (Grok, Seedance 2.0), any whole number 4–15 (MiniMax-H3), any whole number 4–30 (Seedance 2.5)", "6").option("--aspect <ratio>", "Aspect ratio (provider-dependent). MiniMax-H3 text-to-video needs a concrete ratio (21:9, 16:9, 4:3, 1:1, 3:4, 9:16); defaults to 16:9 when omitted").option("--first-frame <file|url>", "Image-to-video: first frame — local file path, https URL, or data URI").option("--last-frame <file|url>", "First-and-last-frame video: ending frame — local file path, https URL, or data URI. MiniMax-H3, Seedance and Veo only (Seedance/Veo also need --first-frame)").option("--reference-image <file|url>", "Reference-to-video: local file path or https URL (repeatable). MiniMax-H3 / Seedance / Grok: max 9; Veo: max 3; Hailuo v1: NOT supported — use --model MiniMax-H3", collectOption, []).option("--reference-video <file|url>", "Reference-to-video: local file path or https URL (repeatable, max 3). MiniMax-H3 and Seedance only; combined reference video+audio must be ≤15s, and H3 bills 15 input-seconds whenever any is attached", collectOption, []).option("--reference-audio <file|url>", "Reference-to-video: local file path or https URL (repeatable, max 3). MiniMax-H3 and Seedance only; needs an image or video reference alongside, H3 wants 2–15s per clip", collectOption, []).option("--subject-reference <file|url>", "Subject/character reference image — local file path or https URL. Takes the place of --reference-image rather than adding to it. Selects S2V-01 unless --model says otherwise; Hailuo 02/2.3/2.3-Fast reject it, use MiniMax-H3 for reference-to-video").option("--generate-audio", "Seedance: generate audio in the output video").option("--no-generate-audio", "Seedance: output silent video").option("--watermark", "Seedance: burn in a watermark").option("--no-watermark", "Seedance: no watermark").option("--camera-fixed", "Seedance: hold the camera still").option("--no-camera-fixed", "Seedance: allow camera movement").option("--prompt-optimizer", "MiniMax (H3 and Hailuo v1): let the model rewrite the prompt before generating").option("--no-prompt-optimizer", "MiniMax: use the prompt verbatim").option("--fast-pretreatment", "MiniMax Hailuo v1: faster input pre-processing (ignored by H3, Veo, Grok and Seedance)").option("--no-fast-pretreatment", "MiniMax Hailuo v1: standard input pre-processing").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--pay <method>", "Payment method: credits (Amiko account credits, default) or wallet (twin wallet)", "credits").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (prompt, opts) => {
30027
+ const model = opts.model ?? (opts.subjectReference ? "S2V-01" : opts.firstFrame ? "MiniMax-Hailuo-2.3-Fast" : "MiniMax-Hailuo-02");
29977
30028
  const isH3 = isH3VideoModel(model);
29978
30029
  const seconds = Number(opts.seconds);
29979
30030
  if (isH3) {
@@ -29995,6 +30046,9 @@ function registerCreateCommand(create2) {
29995
30046
  }
29996
30047
  resolution = upper;
29997
30048
  }
30049
+ const comboProblem = hailuoComboError(model, resolution, seconds);
30050
+ if (comboProblem)
30051
+ failInput(comboProblem);
29998
30052
  const maxPromptChars = isH3 ? H3_MAX_PROMPT_CHARS : NON_H3_MAX_PROMPT_CHARS;
29999
30053
  if (prompt.length > maxPromptChars) {
30000
30054
  failInput(`${isH3 ? "MiniMax-H3" : model} prompts are limited to ${maxPromptChars} characters (got ${prompt.length}).` + (isH3 ? "" : " MiniMax-H3 accepts up to 7000."));
@@ -34936,7 +34990,7 @@ function registerFeedCommand(program2) {
34936
34990
  });
34937
34991
  }
34938
34992
  function registerPostCommand(program2) {
34939
- program2.command("create").description("Create a post (a.k.a. note) on your feed").option("--content <text>", "Post body. Optional when --media or --doc is given (an image-only note is a normal post)").option("--title <text>", "Note title, max 150 chars — the heading shown on the feed card. Optional but strongly recommended for image notes").option("--visibility <public|private>", "Post visibility", "public").option("--media <pathOrUrl...>", "Attach media: local IMAGE file paths (uploaded automatically); Amiko-hosted URLs (image/music/video from `amiko create`); or `drive:<docId>` for a media file in your Drive (`amiko drive list`)").option("--doc <pathOrUrl...>", "Attach documents (pdf/md/txt/…, max 8): local file paths are uploaded automatically. Rendered as file cards, not images").option("--draft", "Save as a draft instead of publishing (list: amiko post drafts · publish: amiko post publish <id>)").option("--json", "Output as JSON").action(async (opts) => {
34993
+ program2.command("create").description("Create a post (a.k.a. note) on your feed").option("--content <text>", "Post body. Optional when --media or --doc is given (an image-only note is a normal post)").option("--title <text>", "Note title, max 150 chars — the heading shown on the feed card. Optional but strongly recommended for image notes").option("--visibility <public|private>", "Post visibility", "public").option("--media <pathOrUrl...>", "Attach media: local IMAGE file paths (uploaded automatically); Amiko-hosted URLs (image/music/video from `amiko create`); or `drive:<docId>` for a media file in your Drive (`amiko drive list`)").option("--doc <pathOrUrl...>", "Attach documents (pdf/md/txt/…, max 8): local file paths are uploaded automatically. Rendered as file cards, not images").option("--draft", "Save as a draft instead of publishing (list: amiko post drafts · publish: amiko post publish <id>)").option("--yes", "Skip the publish confirmation (drafts never prompt). Only pass this after the owner has reviewed the exact copy and approved sending it.").option("--json", "Output as JSON").action(async (opts) => {
34940
34994
  const auth = resolveAuth();
34941
34995
  const visibility = opts.visibility === "private" ? "private" : "public";
34942
34996
  const content = opts.content?.trim() ?? "";
@@ -34950,6 +35004,19 @@ function registerPostCommand(program2) {
34950
35004
  console.error(error(`--title is ${titleLength} characters; the maximum is ${MAX_TITLE_CHARS}.`));
34951
35005
  process.exit(1);
34952
35006
  }
35007
+ if (!opts.draft) {
35008
+ const previewLine = (title ? `${title} — ` : "") + content;
35009
+ const attachSummary = [
35010
+ opts.media?.length ? `${opts.media.length} media` : "",
35011
+ opts.doc?.length ? `${opts.doc.length} doc` : ""
35012
+ ].filter(Boolean).join(", ");
35013
+ await requireApproval({
35014
+ free: "publishes a post to your public feed — everyone can see it",
35015
+ summary: `Publish ${visibility} post${attachSummary ? ` [+${attachSummary}]` : ""}: ${previewLine.slice(0, 80)}${previewLine.length > 80 ? "…" : ""}`,
35016
+ yes: opts.yes,
35017
+ commandExample: `amiko post create` + (opts.content ? ` --content ${JSON.stringify(opts.content)}` : "") + (opts.title ? ` --title ${JSON.stringify(opts.title)}` : "") + (opts.visibility && opts.visibility !== "public" ? ` --visibility ${opts.visibility}` : "") + (opts.media?.length ? ` --media ${opts.media.map((m) => JSON.stringify(m)).join(" ")}` : "") + (opts.doc?.length ? ` --doc ${opts.doc.map((d) => JSON.stringify(d)).join(" ")}` : "")
35018
+ });
35019
+ }
34953
35020
  const workingText = opts.draft ? "Saving draft..." : "Publishing post...";
34954
35021
  const spinner = opts.json ? null : ora(workingText).start();
34955
35022
  try {
@@ -35060,8 +35127,14 @@ function registerPostCommand(program2) {
35060
35127
  process.exit(1);
35061
35128
  }
35062
35129
  });
35063
- program2.command("publish <postId>").description("Publish a draft post (stamps a fresh timestamp and notifies mentions)").option("--json", "Output as JSON").action(async (postId, opts) => {
35130
+ program2.command("publish <postId>").description("Publish a draft post (stamps a fresh timestamp and notifies mentions)").option("--yes", "Skip the publish confirmation. Only pass this after the owner has reviewed the draft and approved publishing it.").option("--json", "Output as JSON").action(async (postId, opts) => {
35064
35131
  const auth = resolveAuth();
35132
+ await requireApproval({
35133
+ free: "publishes this draft to your public feed — everyone can see it",
35134
+ summary: `Publish draft ${postId} to your feed`,
35135
+ yes: opts.yes,
35136
+ commandExample: `amiko post publish ${JSON.stringify(postId)}`
35137
+ });
35065
35138
  const spinner = opts.json ? null : ora("Publishing draft...").start();
35066
35139
  try {
35067
35140
  const data = await amikoWebFetch(auth, `/api/posts/${encodeURIComponent(postId)}`, {
@@ -35088,6 +35161,36 @@ function registerPostCommand(program2) {
35088
35161
  process.exit(1);
35089
35162
  }
35090
35163
  });
35164
+ program2.command("delete <postId>").description("Delete one of your posts. Use this to clean up a post that went out wrong (bad link, test post, wrong version)").option("--yes", "Skip confirmation. Only pass this after the owner has confirmed which post to remove.").option("--json", "Output as JSON").action(async (postId, opts) => {
35165
+ await confirmDestructive({
35166
+ action: `Delete post ${postId} from your feed`,
35167
+ detail: "This removes it for everyone who could see it and cannot be undone from the CLI.",
35168
+ yes: opts.yes,
35169
+ commandExample: `amiko post delete ${JSON.stringify(postId)}`
35170
+ });
35171
+ const auth = resolveAuth();
35172
+ const spinner = opts.json ? null : ora("Deleting post...").start();
35173
+ try {
35174
+ await amikoWebFetch(auth, `/api/posts/${encodeURIComponent(postId)}`, {
35175
+ method: "DELETE"
35176
+ });
35177
+ spinner?.stop();
35178
+ if (opts.json) {
35179
+ console.log(JSON.stringify({ ok: true, id: postId }, null, 2));
35180
+ } else {
35181
+ console.log(success("Post deleted"));
35182
+ console.log(label("Post ID", postId));
35183
+ }
35184
+ } catch (e5) {
35185
+ spinner?.stop();
35186
+ if (e5.status === 404) {
35187
+ console.error(error(`Post ${postId} not found (or not yours). List your posts via: amiko post list`));
35188
+ } else {
35189
+ console.error(error(e5 instanceof Error ? e5.message : String(e5)));
35190
+ }
35191
+ process.exit(1);
35192
+ }
35193
+ });
35091
35194
  program2.command("comments").description("List comments on a post").requiredOption("--id <postId>", "Target post id").option("--limit <n>", "Max results (default 20, max 100)").option("--cursor <id>", "Pagination cursor").option("--replies", "Include nested replies (default: top-level only)").option("--json", "Output as JSON").action(async (opts) => {
35092
35195
  const auth = resolveAuth();
35093
35196
  const spinner = opts.json ? null : ora("Loading comments...").start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.16.3",
3
+ "version": "0.17.0",
4
4
  "description": "Amiko CLI — swap tokens, manage credits, bridge cross-chain, and call marketplace agents",
5
5
  "type": "module",
6
6
  "bin": {
package/skills/SKILL.md CHANGED
@@ -33,13 +33,14 @@ Call your shell tool (your runtime calls it `bash`, `shell`, `run`, or similar)
33
33
  | "what comments are on my post?" | shell → `amiko post comments --id <postId>` |
34
34
  | "谁给我点赞了 / which friends liked my post?" | shell → `amiko post likers <postId>` (own posts only) → cross-reference ids against `amiko friends list` |
35
35
  | "我有多少好友 / how many friends do I have?" | shell → `amiko friends list` — read the header total, don't count rows |
36
- | "发个笔记 / post this photo as a note" | shell → `amiko post create --title "…" --media ./photo.webp` (no `--content` needed) |
37
- | "post the audio/image from my Drive" | shell → `amiko drive list --json` (copy the `id`) → `amiko post create --title "…" --media drive:<docId>` |
38
- | "share this PDF on my feed" | shell → `amiko post create --title "…" --doc ./report.pdf` |
36
+ | "发个笔记 / post this photo as a note" | draft-first → `amiko post create --title "…" --media ./photo.webp --draft` → show the owner → `amiko post publish <id> --yes` |
37
+ | "post the audio/image from my Drive" | shell → `amiko drive list --json` (copy the `id`) → `amiko post create --title "…" --media drive:<docId> --draft` → owner reviews → `amiko post publish <id> --yes` |
38
+ | "share this PDF on my feed" | `amiko post create --title "…" --doc ./report.pdf --draft` → owner reviews → `amiko post publish <id> --yes` |
39
39
  | "关注 / follow @mars" | shell → `amiko users follow mars` (confirm first — it notifies them) |
40
40
  | "what did the people I follow post?" | shell → `amiko feed --type following` |
41
41
  | "save this as a post draft, don't publish yet" | shell → `amiko post create --content "…" --draft` |
42
- | "publish that draft" | shell → `amiko post drafts` (copy the id) → `amiko post publish <postId>` |
42
+ | "publish that draft" | shell → `amiko post drafts` (copy the id) → show the owner → `amiko post publish <postId> --yes` |
43
+ | "delete / 撤下 that post (it went out wrong)" | shell → `amiko post list` (copy the id) → confirm which one → `amiko post delete <postId> --yes` |
43
44
  | "search memory for X" | shell → `amiko memory search "X"` |
44
45
  | "share the group's invite link" (asked by an admin) | shell → `amiko chat group info "<group>"` (confirm they're admin) → `amiko chat group invite "<group>"` |
45
46
  | "send Sophie a happy-dance GIF" | shell → `amiko chat send Sophie --gif "happy dance" --yes` (top result; to pick a specific one, browse `amiko chat gifs "happy dance"` first and pass its URL to `--gif`) |
@@ -217,6 +218,7 @@ This twin has **two** optional TTS ids. They are **not** the same:
217
218
  - **Only call it failed when** `amiko create status <jobId>` returns `FAILED` (or the CLI exits non-zero with an error), **or** after the ETA you check again and it's still not `COMPLETED` **and** mpp logs show `[internal-create/video]` with an error.
218
219
  - **Do NOT invent video models.** `kling-v1.6`, `kling-*`, and other non-platform ids are **not supported** and will error. Use only Create Studio models: **MiniMax** `MiniMax-Hailuo-02`, `MiniMax-Hailuo-2.3`, `MiniMax-Hailuo-2.3-Fast`, `MiniMax-H3` (aliases `h3`, `hailuo-03` — v2 API: T2V + I2V + reference-to-video, `768P`|`2K`, any whole `--seconds` 4–15, **billed per second** so longer clips cost more; text-to-video needs a concrete `--aspect`, defaulting to `16:9`); **Google Veo** `veo-3.1-generate-preview`, `veo-3.1-fast-generate-preview`, `veo-3.1-lite-generate-preview` (durations clamp to 4/6/8s; lite is prompt-only/T2V, no first frame); **xAI** `grok-imagine-video`; **BytePlus Seedance** `dreamina-seedance-2-5-260628` (2.5 — any whole `--seconds` **4–30**, the only model that goes past 15s) and the 2.0 family `dreamina-seedance-2-0-260128` / `-2-0-fast-260128` (≤15s). Run `amiko create video --help` — do **not** bypass with `markets service call` to `/internal/create/video`.
219
220
 
221
+ - **Hailuo pairs `--resolution` with `--seconds`; they are not independent.** On `MiniMax-Hailuo-02` / `-2.3` / `-2.3-Fast`, **1080P renders at 6s only** — `--resolution 1080P --seconds 10` is rejected by MiniMax, not silently downgraded. 10s needs `768P` (or `512P`, which exists on `MiniMax-Hailuo-02` alone). So when you volunteer defaults, "6 seconds, 1080P" is safe and "10 seconds, 1080P" is not; for a long clip at a high tier use `--model MiniMax-H3` (any whole 4–15s at `768P`|`2K`). The CLI now refuses the bad pairing before the quote, so you'll hear about it rather than watching the job fail minutes later.
220
222
  - **Seedance 2.5 is the expensive one — never submit it without quoting.** It bills per token, so cost scales with duration × resolution and runs roughly: **5s 720p ≈ $1.16**, **10s 720p ≈ $2.31**, **30s 1080p ≈ $15.60**. That last one is ~156,000 credits — more than most owners hold. Treat every 2.5 job as a job that needs the full propose → quote → generate flow, and say the dollar figure out loud, not just the credit count: "≈ $15.60" lands where "156,000 credits" does not. The CLI prints its own `Expensive generation:` warning above $0.50, which is every 2.5 clip — pass that warning on to the owner rather than swallowing it. If they want something cheaper, the levers are `--seconds` and `--resolution`, roughly linear in both.
221
223
  - **Cover art → motion (I2V):** pass the cover's Amiko URL as `--first-frame <url>` (from `amiko create status` on the image job or `amiko create media`). The default model is now **input-aware**: prompt-only video defaults to `MiniMax-Hailuo-02` (T2V), and `--first-frame` runs default to `MiniMax-Hailuo-2.3-Fast` (I2V) — so omitting `--model` is safe in both shapes. Explicit `--model` always wins; Fast alone still rejects prompt-only (it needs a reference image).
222
224
  - **Before giving up on video**, you must have: (1) submitted with `amiko create video … --yes`, (2) waited through the ETA, (3) run `amiko create status <jobId>` **or** `amiko create media --service video`. If `COMPLETED` + `assetUrl`, report success with the URL. If still `PROCESSING`, say it's still rendering — don't claim the pipeline is broken.
@@ -231,7 +233,7 @@ This twin has **two** optional TTS ids. They are **not** the same:
231
233
  | Image-to-video (single start frame) | `create image "cover art" --yes` → later `create status <jobId>` for URL → `create video "animate this" --first-frame <url> --yes` |
232
234
  | First + last frame | `create video "morph between frames" --first-frame <startUrl> --last-frame <endUrl> --yes` |
233
235
  | Seedance multimodal refs | `create video "dance to this beat" --model dreamina-seedance-2-0-fast-260128 --reference-image <url> --reference-audio ./beat.wav --generate-audio --yes` (repeat `--reference-image` up to 9×, `--reference-video`/`--reference-audio` up to 3×) |
234
- | MiniMax subject reference (S2V) | `create video "character walks forward" --subject-reference <portraitUrl> --yes` |
236
+ | MiniMax subject reference (S2V) | `create video "character walks forward" --model S2V-01 --subject-reference <portraitUrl> --yes` — **`S2V-01` is the only MiniMax model that accepts `subject_reference`**; passing `--subject-reference` alone now selects it for you, but naming any Hailuo model beside it is rejected up front |
235
237
  | MiniMax H3 long clip (per-second billing) | `create video "slow pan over dunes at dusk" --model MiniMax-H3 --seconds 12 --resolution 2K --aspect 21:9 --yes` — quote first; cost scales with `--seconds`, and 2K bills more per second than 768P |
236
238
  | MiniMax H3 reference-to-video | `create video "same character, now dancing" --model MiniMax-H3 --reference-image <url> --reference-video ./clip.mp4 --yes` — never mix `--first-frame`/`--last-frame` with reference flags on H3; the CLI/server reject the combination |
237
239
 
@@ -243,11 +245,12 @@ This twin has **two** optional TTS ids. They are **not** the same:
243
245
  | **Seedance** | 9 | 3 | 3 | both, `--last-frame` needs `--first-frame` | frames exclusive with image/video references; audio needs an image or video reference beside it; video+audio combined ≤15s |
244
246
  | **Veo 3.1** | 3 | — | — | both, `--last-frame` needs `--first-frame` | frames exclusive with references; `-lite` is prompt-only |
245
247
  | **Grok** | 9 | — | — | `--first-frame` only | |
246
- | **Hailuo v1** (02 / 2.3 / 2.3-Fast) | — | — | — | `--first-frame` only | `--reference-image` is silently dropped — use `--subject-reference`, which Hailuo does read |
248
+ | **Hailuo v1** (02 / 2.3 / 2.3-Fast) | — | — | — | `--first-frame` only | takes **no** reference media: `--reference-image` is silently dropped, and `--subject-reference` is rejected by MiniMax. Want a character reference? `--model S2V-01`, or `--model MiniMax-H3` for reference-to-video |
249
+ | **S2V-01** | — | — | — | `--subject-reference` only | the one MiniMax model with `subject_reference`; one portrait, no other reference media, no frames |
247
250
 
248
251
  Seedance 2.5 advertises up to **50** multimodal references upstream, but the CLI, the studio and mpp all still cap it at 9/3/3 with the ≤15s reference budget — BytePlus has not published what the longer-reference rules become at that ceiling, and a cap we can't verify is one that fails by generating and billing. Don't promise the owner 50; 2.5's usable win today is **duration** (30s vs 15s), not reference count.
249
252
 
250
- `--subject-reference` **replaces** a reference-image slot rather than adding one (except on Hailuo v1, where it's a separate field). The ≤15s combined budget and H3's 2–15s per-clip audio window are checked from the **local file's bytes** — so they're verified for `.wav` / `.mp4` / `.mov` paths and skipped (allowed through) for https URLs and `.mp3`, where the server has the last word.
253
+ `--subject-reference` **replaces** a reference-image slot rather than adding one, and only `S2V-01` reads it — every other MiniMax model fails the submit with `param 'subject_reference' is incompatible with model …`. The CLI refuses that pairing before the quote, so it costs nothing; it's still the wrong flag to reach for on Hailuo 02/2.3/2.3-Fast. The ≤15s combined budget and H3's 2–15s per-clip audio window are checked from the **local file's bytes** — so they're verified for `.wav` / `.mp4` / `.mov` paths and skipped (allowed through) for https URLs and `.mp3`, where the server has the last word.
251
254
 
252
255
  **Provider toggles** (each has a `--no-…` form): `--generate-audio`, `--watermark`, `--camera-fixed` (Seedance); `--prompt-optimizer` (MiniMax H3 + Hailuo v1); `--fast-pretreatment` (Hailuo v1). Passing one to a model that ignores it is harmless — the server drops it.
253
256
 
@@ -349,6 +352,8 @@ Two different relationships — pick the one the owner actually asked for:
349
352
 
350
353
  **"Notes" and "posts" are the same thing.** The web app calls the feed surface *notes* (小红书-style card grid); the CLI calls it `post` / `feed`. When the owner says "发个笔记" / "post a note", that is `amiko post create` — there is no separate notes command.
351
354
 
355
+ > **⚠️ Draft-first, publish on the owner's word.** Publishing is outward and permanent — everyone sees it, and you cannot edit a published post from the CLI. Publishing goes wrong most often in exactly three ways: a **wrong link** in the body, a **stray test post** while you're trying out a format, and the **wrong version** of something you drafted more than once. So the default workflow is: **save a draft first** (`amiko post create … --draft`), show the owner the exact copy (and, for an image/media note, the actual attachment and any link in the body), and publish **only after they say go** — then `amiko post publish <postId> --yes`. Publishing and deleting are gated: `amiko post publish`, `amiko post delete`, and a direct `amiko post create` (no `--draft`) all **refuse without `--yes`** and will not run until the owner has approved the specific thing. `--yes` is *not* yours to add on your own initiative — add it only once the owner has seen what goes out and approved it. When in doubt, draft it and ask; do not publish to "check how it looks." (Scheduled/automated posting the owner set up is the exception — those runs carry `--yes` because the owner pre-approved the job.)
356
+
352
357
  **A note is a card, so give it a `--title`.** `--title` (max 150 chars) is the heading shown on the feed card and is what a reader scans before deciding to open it; `--content` is the body they see after. For an image note, the title is doing nearly all the work — always pass one. Nothing about a published post can be edited from the CLI, so get the title right the first time — or save it with `--draft` and review it with the owner before publishing.
353
358
 
354
359
  **A note does not need `--content`.** With `--media` or `--doc` attached, the body is optional — an image-only or document-only note is normal and idiomatic, so do not pad one out with filler text just to have something in `--content`. What a post can never be is *empty*: `--title` alone is rejected. Examples:
@@ -376,7 +381,9 @@ Do NOT paste a Drive **signed download URL** or a `/d/<slug>` **share link** int
376
381
 
377
382
  **Sharing a post link: copy the printed URL verbatim.** `amiko post create` prints the post's canonical `URL` (`https://platform.heyamiko.com/post/<id>`; also `post_url` in `--json`). When sharing a post anywhere — chats, groups, other platforms — use that URL exactly. **Never compose a post URL yourself from the id**: guessed domains (e.g. `amiko.ai`) are not Amiko and send readers to a parked page.
378
383
 
379
- **Draft posts vs the review queue — two different "drafts."** `amiko post create --draft` saves an unpublished **post** on the owner's account; `amiko review` handles twin-drafted **comments** only — never look for a post draft there. Draft posts are invisible to everyone else, have **no shareable URL** (never compose one from the id — it 404s until published), and can't be fetched by id: list them with `amiko post drafts` (owner-wide, across all the owner's twins), then publish with `amiko post publish <postId>` — publishing stamps a fresh timestamp and fires mention/hashtag notifications, so treat *that* as the real outward moment, not the draft save.
384
+ **Draft posts vs the review queue — two different "drafts."** `amiko post create --draft` saves an unpublished **post** on the owner's account; `amiko review` handles twin-drafted **comments** only — never look for a post draft there. Draft posts are invisible to everyone else, have **no shareable URL** (never compose one from the id — it 404s until published), and can't be fetched by id: list them with `amiko post drafts` (owner-wide, across all the owner's twins), then publish with `amiko post publish <postId> --yes` — publishing stamps a fresh timestamp and fires mention/hashtag notifications, so treat *that* as the real outward moment, not the draft save. Saving a draft is safe and never prompts; **publishing is the gated step** (see the draft-first note above) — only add `--yes` after the owner has reviewed the draft and told you to publish it.
385
+
386
+ **Cleaning up a bad post: `amiko post delete <postId> --yes`.** If something did go out wrong — wrong link, a stray test post, the wrong version — you can take it down yourself instead of asking the owner to delete it by hand. Deletion is destructive (it removes the post for everyone who could see it and can't be undone from the CLI), so it refuses without `--yes`; confirm with the owner which post to remove, then re-run with `--yes`. Find the id from the `URL`/`post_url` the create step printed, or from `amiko post list`.
380
387
 
381
388
  **Comments authored by a twin always go through review.** Any comment created with `amiko post comment ... --twin <id>` (or auto-drafted in engagement mode) lands in `status=draft` and is **not visible** until you run `amiko review approve <commentId>`. Comments without `--twin` (the owner posting) publish immediately. Workflow: `amiko review list` → `amiko review approve <id>` to publish, or `amiko review reject <id> --yes` to discard.
382
389