@heyamiko/amiko-cli 0.16.4 → 0.17.1

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
@@ -880,8 +880,8 @@ Generate a video (Create Studio)
880
880
  | Flag | Default | Description |
881
881
  |---|---|---|
882
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) |
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) |
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) |
@@ -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 |
@@ -2053,11 +2054,13 @@ Create a post (a.k.a. note) on your feed
2053
2054
  | Flag | Default | Description |
2054
2055
  |---|---|---|
2055
2056
  | `--content <text>` | | Post body. Optional when --media or --doc is given (an image-only note is a normal post) |
2057
+ | `--markdown` | | Render --content as markdown (bold/lists/links/tables). @mentions and #hashtags still link. Default is plain text. |
2056
2058
  | `--title <text>` | | Note title, max 150 chars — the heading shown on the feed card. Optional but strongly recommended for image notes |
2057
2059
  | `--visibility <public|private>` | `public` | Post visibility |
2058
2060
  | `--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
2061
  | `--doc <pathOrUrl...>` | | Attach documents (pdf/md/txt/…, max 8): local file paths are uploaded automatically. Rendered as file cards, not images |
2060
2062
  | `--draft` | | Save as a draft instead of publishing (list: amiko post drafts · publish: amiko post publish <id>) |
2063
+ | `--yes` | | Skip the publish confirmation (drafts never prompt). Only pass this after the owner has reviewed the exact copy and approved sending it. |
2061
2064
  | `--json` | | Output as JSON |
2062
2065
 
2063
2066
  #### `amiko post drafts`
@@ -2076,6 +2079,16 @@ Publish a draft post (stamps a fresh timestamp and notifies mentions)
2076
2079
 
2077
2080
  | Flag | Default | Description |
2078
2081
  |---|---|---|
2082
+ | `--yes` | | Skip the publish confirmation. Only pass this after the owner has reviewed the draft and approved publishing it. |
2083
+ | `--json` | | Output as JSON |
2084
+
2085
+ #### `amiko post delete <postId>`
2086
+
2087
+ Delete one of your posts. Use this to clean up a post that went out wrong (bad link, test post, wrong version)
2088
+
2089
+ | Flag | Default | Description |
2090
+ |---|---|---|
2091
+ | `--yes` | | Skip confirmation. Only pass this after the owner has confirmed which post to remove. |
2079
2092
  | `--json` | | Output as JSON |
2080
2093
 
2081
2094
  #### `amiko post comments`
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;
@@ -29980,7 +30023,7 @@ function registerCreateCommand(create2) {
29980
30023
  }
29981
30024
  });
29982
30025
  });
29983
- 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)", "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 --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) => {
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) => {
29984
30027
  const model = opts.model ?? (opts.subjectReference ? "S2V-01" : opts.firstFrame ? "MiniMax-Hailuo-2.3-Fast" : "MiniMax-Hailuo-02");
29985
30028
  const isH3 = isH3VideoModel(model);
29986
30029
  const seconds = Number(opts.seconds);
@@ -30003,6 +30046,9 @@ function registerCreateCommand(create2) {
30003
30046
  }
30004
30047
  resolution = upper;
30005
30048
  }
30049
+ const comboProblem = hailuoComboError(model, resolution, seconds);
30050
+ if (comboProblem)
30051
+ failInput(comboProblem);
30006
30052
  const maxPromptChars = isH3 ? H3_MAX_PROMPT_CHARS : NON_H3_MAX_PROMPT_CHARS;
30007
30053
  if (prompt.length > maxPromptChars) {
30008
30054
  failInput(`${isH3 ? "MiniMax-H3" : model} prompts are limited to ${maxPromptChars} characters (got ${prompt.length}).` + (isH3 ? "" : " MiniMax-H3 accepts up to 7000."));
@@ -34944,7 +34990,7 @@ function registerFeedCommand(program2) {
34944
34990
  });
34945
34991
  }
34946
34992
  function registerPostCommand(program2) {
34947
- 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("--markdown", "Render --content as markdown (bold/lists/links/tables). @mentions and #hashtags still link. Default is plain text.").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) => {
34948
34994
  const auth = resolveAuth();
34949
34995
  const visibility = opts.visibility === "private" ? "private" : "public";
34950
34996
  const content = opts.content?.trim() ?? "";
@@ -34958,6 +35004,19 @@ function registerPostCommand(program2) {
34958
35004
  console.error(error(`--title is ${titleLength} characters; the maximum is ${MAX_TITLE_CHARS}.`));
34959
35005
  process.exit(1);
34960
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.markdown ? " --markdown" : "") + (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
+ }
34961
35020
  const workingText = opts.draft ? "Saving draft..." : "Publishing post...";
34962
35021
  const spinner = opts.json ? null : ora(workingText).start();
34963
35022
  try {
@@ -34965,6 +35024,8 @@ function registerPostCommand(program2) {
34965
35024
  content,
34966
35025
  visibility
34967
35026
  };
35027
+ if (opts.markdown)
35028
+ body.content_format = "markdown";
34968
35029
  if (title)
34969
35030
  body.title = title;
34970
35031
  if (opts.draft)
@@ -35068,8 +35129,14 @@ function registerPostCommand(program2) {
35068
35129
  process.exit(1);
35069
35130
  }
35070
35131
  });
35071
- 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) => {
35132
+ 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) => {
35072
35133
  const auth = resolveAuth();
35134
+ await requireApproval({
35135
+ free: "publishes this draft to your public feed — everyone can see it",
35136
+ summary: `Publish draft ${postId} to your feed`,
35137
+ yes: opts.yes,
35138
+ commandExample: `amiko post publish ${JSON.stringify(postId)}`
35139
+ });
35073
35140
  const spinner = opts.json ? null : ora("Publishing draft...").start();
35074
35141
  try {
35075
35142
  const data = await amikoWebFetch(auth, `/api/posts/${encodeURIComponent(postId)}`, {
@@ -35096,6 +35163,36 @@ function registerPostCommand(program2) {
35096
35163
  process.exit(1);
35097
35164
  }
35098
35165
  });
35166
+ 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) => {
35167
+ await confirmDestructive({
35168
+ action: `Delete post ${postId} from your feed`,
35169
+ detail: "This removes it for everyone who could see it and cannot be undone from the CLI.",
35170
+ yes: opts.yes,
35171
+ commandExample: `amiko post delete ${JSON.stringify(postId)}`
35172
+ });
35173
+ const auth = resolveAuth();
35174
+ const spinner = opts.json ? null : ora("Deleting post...").start();
35175
+ try {
35176
+ await amikoWebFetch(auth, `/api/posts/${encodeURIComponent(postId)}`, {
35177
+ method: "DELETE"
35178
+ });
35179
+ spinner?.stop();
35180
+ if (opts.json) {
35181
+ console.log(JSON.stringify({ ok: true, id: postId }, null, 2));
35182
+ } else {
35183
+ console.log(success("Post deleted"));
35184
+ console.log(label("Post ID", postId));
35185
+ }
35186
+ } catch (e5) {
35187
+ spinner?.stop();
35188
+ if (e5.status === 404) {
35189
+ console.error(error(`Post ${postId} not found (or not yours). List your posts via: amiko post list`));
35190
+ } else {
35191
+ console.error(error(e5 instanceof Error ? e5.message : String(e5)));
35192
+ }
35193
+ process.exit(1);
35194
+ }
35195
+ });
35099
35196
  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) => {
35100
35197
  const auth = resolveAuth();
35101
35198
  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.4",
3
+ "version": "0.17.1",
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.
@@ -350,6 +352,8 @@ Two different relationships — pick the one the owner actually asked for:
350
352
 
351
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.
352
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
+
353
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.
354
358
 
355
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:
@@ -360,6 +364,8 @@ Two different relationships — pick the one the owner actually asked for:
360
364
 
361
365
  **Attaching documents: `--doc`, not `--media`.** `--doc <pathOrUrl...>` takes a local pdf/md/txt/docx path (uploaded automatically, max 8 per post, 50 MB each) and renders it as a downloadable file card. `--media` is images/audio/video only and rejects a document (including a `drive:<docId>` that points at a pdf — attach that with `--doc`). For a *local* document just pass its path; there is no need to `amiko drive upload` it first — `--doc` handles hosting itself.
362
366
 
367
+ **Markdown in the body is opt-in: pass `--markdown`.** `--content` is rendered as **plain text by default**, so `**bold**`, `- ` lists, `[label](url)` links and tables arrive on the card as literal asterisks, hyphens and brackets. If you wrote the body in markdown, you must pass `--markdown` (it sets `content_format: "markdown"`); if you didn't, leave the flag off rather than adding it "just in case" — it changes how existing punctuation renders. `@[Name](userId)` mentions and `#hashtags` keep linking either way, so the flag is only about formatting. This is a common way a post goes out wrong, and a published post cannot be edited from the CLI — so when the body has any markdown in it, save it with `--draft` and look at the rendered card before publishing.
368
+
363
369
  **@-mentions use `@[Name](userId)`, not `@handle`.** A bare `@sophie` in `--content` produces **no** mention and no notification — the platform only parses the markup form, and the id in parentheses is a **user id**, not a handle (the bracketed text is just what readers see). Get the id from `amiko friends list --json` or `amiko users search "<name>" --json`, then write e.g. `--content "thanks @[Sophie](cm1abc…) for the shots"`. The same rule applies to `amiko post comment`. (Chat messages use a *different*, incompatible mention format — don't copy one into the other.)
364
370
 
365
371
  **Quoting another post: put its URL in the body.** There is no repost/quote flag; paste the canonical `https://platform.heyamiko.com/post/<id>` link into `--content` and the feed renders it as a quote card. Only ever use a URL the CLI printed — never compose one from an id.
@@ -377,7 +383,9 @@ Do NOT paste a Drive **signed download URL** or a `/d/<slug>` **share link** int
377
383
 
378
384
  **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.
379
385
 
380
- **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.
386
+ **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.
387
+
388
+ **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`.
381
389
 
382
390
  **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.
383
391