@heyamiko/amiko-cli 0.12.0-beta.1 → 0.12.0-beta.4

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
@@ -1,4 +1,4 @@
1
- # @heyamiko/amiko-cli (v0.11.0-beta.3)
1
+ # @heyamiko/amiko-cli (v0.12.0-beta.4)
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
 
@@ -182,9 +182,19 @@ Modes: `image`, `video`, `tts`, `music`, `sfx` (mirrors the platform Create Stud
182
182
 
183
183
  ### How it works (and getting the result)
184
184
 
185
- The CLI **submits the job, then blocks and polls until it finishes** (video can take several minutes), printing the asset URL, whether you were charged, and the charge tx on success. In non-TTY environments it prints a `…still generating (Ns elapsed)` tick to stderr. `--raw` prints the full job result as JSON.
185
+ **By default the command returns immediately** with a **job id** and `status: PENDING` — it does **not** block for the whole generation. It prints how to check the result and a rough ETA. This keeps agents/scripts from being held for 60s–9min.
186
186
 
187
- If the poll window elapses (10 min for video, 5 min otherwise) the CLI does **not** treat it as failure — the generation may still complete server-side. It prints the **job id**; retrieve the finished asset with either:
187
+ ```
188
+ $ amiko create image "a whale in space" --yes
189
+ Submitted — job aX9k…
190
+ Status: PENDING — generating in the background
191
+ Check result: amiko create status aX9k…
192
+ Usually ready in ~15–60s — charged only on success.
193
+ ```
194
+
195
+ Rough ETAs: image ~15–60s, video ~2–9 min, music ~30–120s, tts/sfx ~5–20s.
196
+
197
+ Retrieve the finished asset with:
188
198
 
189
199
  ```bash
190
200
  amiko create status <jobId> # re-query that job (queryable ~24h)
@@ -192,12 +202,31 @@ amiko create media # list your recent generations (permanent)
192
202
  amiko create media --service video --limit 5
193
203
  ```
194
204
 
195
- - **`amiko create status <jobId>`** — re-polls the job once and prints its status / asset. Jobs stay queryable for ~24h.
205
+ - **`amiko create status <jobId>`** — re-polls the job once; prints `PROCESSING`, or the asset + charge when done. Jobs stay queryable for ~24h.
196
206
  - **`amiko create media`** — lists your recent Create Studio generations (URL, type, model, prompt, date). `--service <type>` filters, `--limit <n>` caps (max 50), `--raw` for JSON. Use this once a job id has expired, or just to browse.
197
207
  - **Web:** `https://platform.heyamiko.com/media`
198
208
 
209
+ **`--wait`** — if you'd rather block until it finishes and print the asset inline (e.g. an interactive terminal), pass `--wait`. It polls (10 min cap for video, 5 min otherwise) with a `…still generating (Ns)` tick; on timeout it falls back to the job-id + retrieval hint above. `--raw` prints the full job result as JSON either way.
210
+
199
211
  > `amiko markets image` still exists and is unchanged — it's the direct MPP pay-per-call path (pre-pay in AMIKO). Use `amiko create` for the Create Studio experience (async, charge-on-success); use `amiko markets` for raw MPP endpoints.
200
212
 
213
+ ## Chat
214
+
215
+ Your conversations — **DMs and group chats** — acting **as you (the owner)**. (This is distinct from the openhermit gateway's `session_*` tools, which act as the *agent* on the agent's own sessions.)
216
+
217
+ ```bash
218
+ amiko chat list # all conversations (DM + group): id, peer/title, last msg, unread
219
+ amiko chat list --limit 50 --archived # include archived
220
+ amiko chat read @sophie # recent messages with a user (by @handle)
221
+ amiko chat read <conversationId> --limit 40 # by conversation id (from `chat list`) — works for groups too
222
+ amiko chat send @sophie "on my way!" --yes # send as the owner
223
+ amiko chat send <groupConversationId> "hi all" --yes # send to a group chat
224
+ ```
225
+
226
+ - **`<target>`** for `read`/`send` is a **conversation id** (from `chat list` — use this for groups), an **`@handle`**, or a **name** (resolved via user search; if ambiguous, the CLI lists candidates instead of guessing).
227
+ - A DM target that doesn't exist yet is **created automatically** (find-or-create).
228
+ - `chat send` messages a real person, so it's **gated on `--yes`** in non-interactive shells. Delivery is real-time. `--raw` prints raw JSON on any subcommand.
229
+
201
230
  ## Amazon
202
231
 
203
232
  ```bash
package/dist/index.js CHANGED
@@ -28590,6 +28590,13 @@ function registerImageCommand(program2) {
28590
28590
 
28591
28591
  // src/commands/create.ts
28592
28592
  var POLL_INTERVAL_MS = 3000;
28593
+ var ETA = {
28594
+ IMAGE: "~15–60s",
28595
+ VIDEO: "~2–9 min",
28596
+ MUSIC: "~30–120s",
28597
+ TTS: "~5–20s",
28598
+ SFX: "~5–20s"
28599
+ };
28593
28600
  function statusOf(err) {
28594
28601
  return err?.status;
28595
28602
  }
@@ -28679,6 +28686,23 @@ async function submitAndPoll(opts) {
28679
28686
  console.error(error(e5 instanceof Error ? e5.message : "Submit failed"));
28680
28687
  process.exit(1);
28681
28688
  }
28689
+ if (!opts.wait) {
28690
+ stop();
28691
+ if (opts.raw) {
28692
+ console.log(JSON.stringify({
28693
+ jobId,
28694
+ status: "PENDING",
28695
+ check: `amiko create status ${jobId}`,
28696
+ eta: ETA[opts.mode]
28697
+ }, null, 2));
28698
+ return;
28699
+ }
28700
+ console.log(success(`Submitted — job ${jobId}`));
28701
+ console.log(label("Status", "PENDING — generating in the background"));
28702
+ console.log(label("Check result", `amiko create status ${jobId}`));
28703
+ console.log(dim(`Usually ready in ${ETA[opts.mode]} — charged only on success. (Also: amiko create media)`));
28704
+ return;
28705
+ }
28682
28706
  setStatus("Generating…");
28683
28707
  startTicker();
28684
28708
  const maxMs = opts.mode === "VIDEO" ? 600000 : 300000;
@@ -28717,12 +28741,13 @@ async function submitAndPoll(opts) {
28717
28741
  printCompleted(final, `${opts.mode[0]}${opts.mode.slice(1).toLowerCase()} created`);
28718
28742
  }
28719
28743
  function registerCreateCommand(create2) {
28720
- create2.command("image <prompt>").description("Generate an image (Create Studio)").option("--model <model>", "Image model", "gpt-image-2").option("--aspect <ratio>", "Aspect ratio: 1:1, 16:9, 9:16, 4:3, 3:4", "1:1").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (prompt, opts) => {
28744
+ create2.command("image <prompt>").description("Generate an image (Create Studio)").option("--model <model>", "Image model", "gpt-image-2").option("--aspect <ratio>", "Aspect ratio: 1:1, 16:9, 9:16, 4:3, 3:4", "1:1").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").option("--wait", "Block until generation finishes and print the asset (default: return a job id immediately)").action(async (prompt, opts) => {
28721
28745
  await submitAndPoll({
28722
28746
  mode: "IMAGE",
28723
28747
  summary: `Create image: ${prompt.slice(0, 80)}${prompt.length > 80 ? "…" : ""}`,
28724
28748
  raw: opts.raw,
28725
28749
  yes: opts.yes,
28750
+ wait: opts.wait,
28726
28751
  quoteBody: { mode: "IMAGE", model: opts.model, size: opts.aspect },
28727
28752
  jobBody: {
28728
28753
  mode: "IMAGE",
@@ -28733,7 +28758,7 @@ function registerCreateCommand(create2) {
28733
28758
  }
28734
28759
  });
28735
28760
  });
28736
- create2.command("video <prompt>").description("Generate a video (Create Studio)").option("--model <model>", "Video model", "MiniMax-Hailuo-2.3-Fast").option("--resolution <res>", "512P, 720P, 768P, 1080P", "768P").option("--seconds <n>", "Clip length: 6 or 10", "6").option("--aspect <ratio>", "Aspect ratio (provider-dependent)").option("--first-frame <url>", "Image-to-video: first frame URL/data URI").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (prompt, opts) => {
28761
+ create2.command("video <prompt>").description("Generate a video (Create Studio)").option("--model <model>", "Video model", "MiniMax-Hailuo-2.3-Fast").option("--resolution <res>", "512P, 720P, 768P, 1080P", "768P").option("--seconds <n>", "Clip length: 6 or 10", "6").option("--aspect <ratio>", "Aspect ratio (provider-dependent)").option("--first-frame <url>", "Image-to-video: first frame URL/data URI").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").option("--wait", "Block until generation finishes and print the asset (default: return a job id immediately)").action(async (prompt, opts) => {
28737
28762
  const seconds = Number(opts.seconds);
28738
28763
  if (seconds !== 6 && seconds !== 10) {
28739
28764
  console.error(error("--seconds must be 6 or 10."));
@@ -28744,6 +28769,7 @@ function registerCreateCommand(create2) {
28744
28769
  summary: `Create video: ${prompt.slice(0, 80)}${prompt.length > 80 ? "…" : ""}`,
28745
28770
  raw: opts.raw,
28746
28771
  yes: opts.yes,
28772
+ wait: opts.wait,
28747
28773
  quoteBody: {
28748
28774
  mode: "VIDEO",
28749
28775
  model: opts.model,
@@ -28762,12 +28788,13 @@ function registerCreateCommand(create2) {
28762
28788
  }
28763
28789
  });
28764
28790
  });
28765
- 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("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (text, opts) => {
28791
+ 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("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").option("--wait", "Block until generation finishes and print the asset (default: return a job id immediately)").action(async (text, opts) => {
28766
28792
  await submitAndPoll({
28767
28793
  mode: "TTS",
28768
28794
  summary: `Create speech: ${text.slice(0, 80)}${text.length > 80 ? "…" : ""}`,
28769
28795
  raw: opts.raw,
28770
28796
  yes: opts.yes,
28797
+ wait: opts.wait,
28771
28798
  quoteBody: { mode: "TTS", model: opts.model, prompt: text },
28772
28799
  jobBody: {
28773
28800
  mode: "TTS",
@@ -28778,7 +28805,7 @@ function registerCreateCommand(create2) {
28778
28805
  }
28779
28806
  });
28780
28807
  });
28781
- create2.command("music [prompt]").description("Generate music (Create Studio)").option("--model <model>", "music_v1, music-2.6, music-cover", "music-2.6").option("--lyrics <lyrics>", "Lyrics (optional)").option("--duration <ms>", "Length in ms").option("--instrumental", "Instrumental (no vocals)").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (prompt, opts) => {
28808
+ create2.command("music [prompt]").description("Generate music (Create Studio)").option("--model <model>", "music_v1, music-2.6, music-cover", "music-2.6").option("--lyrics <lyrics>", "Lyrics (optional)").option("--duration <ms>", "Length in ms").option("--instrumental", "Instrumental (no vocals)").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").option("--wait", "Block until generation finishes and print the asset (default: return a job id immediately)").action(async (prompt, opts) => {
28782
28809
  if (!prompt && !opts.lyrics) {
28783
28810
  console.error(error("Provide a prompt or --lyrics."));
28784
28811
  process.exit(1);
@@ -28788,6 +28815,7 @@ function registerCreateCommand(create2) {
28788
28815
  summary: `Create music: ${(prompt ?? opts.lyrics ?? "").slice(0, 80)}`,
28789
28816
  raw: opts.raw,
28790
28817
  yes: opts.yes,
28818
+ wait: opts.wait,
28791
28819
  quoteBody: { mode: "MUSIC", model: opts.model },
28792
28820
  jobBody: {
28793
28821
  mode: "MUSIC",
@@ -28800,12 +28828,13 @@ function registerCreateCommand(create2) {
28800
28828
  }
28801
28829
  });
28802
28830
  });
28803
- create2.command("sfx <text>").description("Generate a sound effect (Create Studio)").option("--duration <seconds>", "Clip length in seconds").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").action(async (text, opts) => {
28831
+ create2.command("sfx <text>").description("Generate a sound effect (Create Studio)").option("--duration <seconds>", "Clip length in seconds").option("--token <symbol>", "Preferred charge token: AMIKO, USDC, USDT, SOL").option("--raw", "Output raw JSON").option("--yes", "Skip the pre-spend confirmation (required in non-interactive shells)").option("--wait", "Block until generation finishes and print the asset (default: return a job id immediately)").action(async (text, opts) => {
28804
28832
  await submitAndPoll({
28805
28833
  mode: "SFX",
28806
28834
  summary: `Create SFX: ${text.slice(0, 80)}${text.length > 80 ? "…" : ""}`,
28807
28835
  raw: opts.raw,
28808
28836
  yes: opts.yes,
28837
+ wait: opts.wait,
28809
28838
  quoteBody: { mode: "SFX" },
28810
28839
  jobBody: {
28811
28840
  mode: "SFX",
@@ -28895,6 +28924,166 @@ More available — raise --limit to see more.`));
28895
28924
  });
28896
28925
  }
28897
28926
 
28927
+ // src/commands/chat.ts
28928
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
28929
+ function authOrExit() {
28930
+ try {
28931
+ return requireAuth();
28932
+ } catch (e5) {
28933
+ console.error(error(e5 instanceof Error ? e5.message : "auth failed"));
28934
+ process.exit(1);
28935
+ }
28936
+ }
28937
+ function convLabel(conv, selfUserId) {
28938
+ if (conv.title)
28939
+ return conv.title;
28940
+ const others = (conv.participants ?? []).filter((p) => !(p.participant_type === "user" && p.participant_id === selfUserId)).map((p) => p.user?.name ?? p.twin?.name ?? p.participant_id.slice(0, 8)).filter((n) => !!n);
28941
+ return others.length ? others.join(", ") : "(conversation)";
28942
+ }
28943
+ async function findOrCreateDm(auth, userId) {
28944
+ const data = await amikoWebFetch(auth, "/api/conversations", {
28945
+ method: "POST",
28946
+ body: {
28947
+ participant_ids: [userId],
28948
+ participant_types: ["user"],
28949
+ conversation_type: "direct"
28950
+ },
28951
+ timeoutMs: 20000
28952
+ });
28953
+ const id = data.conversation?.id;
28954
+ if (!id)
28955
+ throw new Error("Could not open a conversation with that user.");
28956
+ return id;
28957
+ }
28958
+ async function resolveConversation(auth, target) {
28959
+ const t = target.trim();
28960
+ if (UUID_RE.test(t))
28961
+ return t;
28962
+ if (t.startsWith("@")) {
28963
+ const handle = t.slice(1);
28964
+ const prof = await amikoWebFetch(auth, `/api/users/${encodeURIComponent(handle)}`, { timeoutMs: 15000 });
28965
+ if (!prof.user?.id)
28966
+ throw new Error(`No user @${handle}.`);
28967
+ return findOrCreateDm(auth, prof.user.id);
28968
+ }
28969
+ const res = await amikoWebFetch(auth, "/api/search", {
28970
+ query: { q: t, type: "people", limit: 8 },
28971
+ timeoutMs: 15000
28972
+ });
28973
+ const users = res.users ?? [];
28974
+ if (users.length === 0) {
28975
+ throw new Error(`No user matching "${t}". Use an exact @handle, or a conversation id from \`amiko chat list\`.`);
28976
+ }
28977
+ if (users.length > 1) {
28978
+ const names = users.map((u) => `${u.name ?? "?"} (@${u.handle ?? u.id.slice(0, 8)})`).join(", ");
28979
+ throw new Error(`"${t}" is ambiguous — matches: ${names}. Use an exact @handle or a conversation id.`);
28980
+ }
28981
+ return findOrCreateDm(auth, users[0].id);
28982
+ }
28983
+ function registerChatCommand(chat) {
28984
+ chat.command("list").description("List your conversations (DMs and group chats)").option("--limit <n>", "Max conversations", "30").option("--archived", "Include archived conversations").option("--raw", "Output raw JSON").action(async (opts) => {
28985
+ const auth = authOrExit();
28986
+ let data;
28987
+ try {
28988
+ data = await amikoWebFetch(auth, "/api/conversations", {
28989
+ query: {
28990
+ limit: opts.limit,
28991
+ ...opts.archived ? { archived: "all" } : {}
28992
+ },
28993
+ timeoutMs: 20000
28994
+ });
28995
+ } catch (e5) {
28996
+ console.error(error(e5 instanceof Error ? e5.message : "Failed to list conversations"));
28997
+ process.exit(1);
28998
+ }
28999
+ if (opts.raw) {
29000
+ console.log(JSON.stringify(data, null, 2));
29001
+ return;
29002
+ }
29003
+ const convs = data.conversations ?? [];
29004
+ if (convs.length === 0) {
29005
+ console.log(dim("No conversations."));
29006
+ return;
29007
+ }
29008
+ console.log(heading(`Conversations (${convs.length})
29009
+ `));
29010
+ for (const c of convs) {
29011
+ const kind = c.conversation_type === "direct" ? "DM" : c.conversation_type;
29012
+ const unread = c.unread_count ? ` (${c.unread_count} unread)` : "";
29013
+ console.log(label(`[${kind}] ${convLabel(c, auth.userId)}`, dim(c.id)));
29014
+ if (c.last_message?.content) {
29015
+ const who = c.last_message.name ? `${c.last_message.name}: ` : "";
29016
+ console.log(dim(` ${who}${c.last_message.content.slice(0, 80)}`));
29017
+ }
29018
+ if (unread)
29019
+ console.log(dim(` ${unread.trim()}`));
29020
+ }
29021
+ });
29022
+ chat.command("read <target>").description("Read recent messages in a conversation (conversation id, @handle, or name)").option("--limit <n>", "Max messages", "20").option("--raw", "Output raw JSON").action(async (target, opts) => {
29023
+ const auth = authOrExit();
29024
+ let convId;
29025
+ try {
29026
+ convId = await resolveConversation(auth, target);
29027
+ } catch (e5) {
29028
+ console.error(error(e5 instanceof Error ? e5.message : "Could not resolve target"));
29029
+ process.exit(1);
29030
+ }
29031
+ let data;
29032
+ try {
29033
+ data = await amikoWebFetch(auth, `/api/conversations/${encodeURIComponent(convId)}/messages`, { query: { limit: opts.limit }, timeoutMs: 20000 });
29034
+ } catch (e5) {
29035
+ console.error(error(e5 instanceof Error ? e5.message : "Failed to read messages"));
29036
+ process.exit(1);
29037
+ }
29038
+ if (opts.raw) {
29039
+ console.log(JSON.stringify(data, null, 2));
29040
+ return;
29041
+ }
29042
+ const msgs = (data.messages ?? []).slice().reverse();
29043
+ if (msgs.length === 0) {
29044
+ console.log(dim("No messages."));
29045
+ return;
29046
+ }
29047
+ console.log(heading(`Messages (${msgs.length})
29048
+ `));
29049
+ for (const m of msgs) {
29050
+ const who = m.sender?.name ?? m.name ?? (m.sender_id ? m.sender_id.slice(0, 8) : "?");
29051
+ const when = m.created_at ? m.created_at.slice(0, 19).replace("T", " ") : "";
29052
+ console.log(label(who, m.content));
29053
+ if (when)
29054
+ console.log(dim(` ${when}`));
29055
+ }
29056
+ });
29057
+ chat.command("send <target> <message>").description("Send a message as yourself (the owner) to a conversation (id, @handle, or name)").option("--yes", "Skip the confirmation (required in non-interactive shells)").option("--raw", "Output raw JSON").action(async (target, message, opts) => {
29058
+ const auth = authOrExit();
29059
+ let convId;
29060
+ try {
29061
+ convId = await resolveConversation(auth, target);
29062
+ } catch (e5) {
29063
+ console.error(error(e5 instanceof Error ? e5.message : "Could not resolve target"));
29064
+ process.exit(1);
29065
+ }
29066
+ await requireApproval({
29067
+ cost: "no charge — sends a message as you (the owner)",
29068
+ summary: `Send to ${target}: ${message.slice(0, 80)}${message.length > 80 ? "…" : ""}`,
29069
+ yes: opts.yes,
29070
+ commandExample: `amiko chat send ${JSON.stringify(target)} ${JSON.stringify(message)}`
29071
+ });
29072
+ let data;
29073
+ try {
29074
+ data = await amikoWebFetch(auth, `/api/conversations/${encodeURIComponent(convId)}/messages`, { method: "POST", body: { content: message }, timeoutMs: 20000 });
29075
+ } catch (e5) {
29076
+ console.error(error(e5 instanceof Error ? e5.message : "Send failed"));
29077
+ process.exit(1);
29078
+ }
29079
+ if (opts.raw) {
29080
+ console.log(JSON.stringify(data, null, 2));
29081
+ return;
29082
+ }
29083
+ console.log(success("Message sent") + dim(` (conversation ${convId.slice(0, 8)}…)`));
29084
+ });
29085
+ }
29086
+
28898
29087
  // src/commands/amazon.ts
28899
29088
  var TREASURY3 = "FPiZAf3jEnNfCwfvbv46jdPfHbmSepD4Q6iHiLJDQXvb";
28900
29089
  async function payAmiko(auth, wallet, amount2) {
@@ -30963,7 +31152,7 @@ var MAX_IMAGE_BYTES = 5 * 1024 * 1024;
30963
31152
  async function uploadPostMedia(auth, source) {
30964
31153
  const file2 = await resolveFileInput(source);
30965
31154
  if (!ALLOWED_IMAGE_TYPES.includes(file2.mime)) {
30966
- throw new Error(`"${source}" is not a supported image (detected ${file2.mime}). Allowed: JPEG, PNG, WebP, GIF.`);
31155
+ throw new Error(`"${source}" is not a supported image (detected ${file2.mime}). Local file upload allows only JPEG, PNG, WebP, GIF. For audio/video, pass an Amiko-hosted URL (e.g. from \`amiko create\`) instead of a local file — local audio/video upload isn't supported yet.`);
30967
31156
  }
30968
31157
  if (file2.size > MAX_IMAGE_BYTES) {
30969
31158
  throw new Error(`"${source}" is ${file2.size} bytes; exceeds the 5 MB post-image limit.`);
@@ -30976,12 +31165,12 @@ async function uploadPostMedia(auth, source) {
30976
31165
  }
30977
31166
  return res.url;
30978
31167
  }
30979
- var POST_MEDIA_PATH = "/storage/v1/object/public/post-media/";
30980
- function isPostMediaUrl(value) {
31168
+ var AMIKO_PUBLIC_STORAGE_PATH = "/storage/v1/object/public/";
31169
+ function isAmikoMediaUrl(value) {
30981
31170
  if (!URL.canParse(value))
30982
31171
  return false;
30983
31172
  const parsed = new URL(value);
30984
- return parsed.protocol === "https:" && parsed.pathname.startsWith(POST_MEDIA_PATH);
31173
+ return parsed.protocol === "https:" && parsed.pathname.startsWith(AMIKO_PUBLIC_STORAGE_PATH);
30985
31174
  }
30986
31175
  async function resolveMediaUrls(auth, inputs) {
30987
31176
  const resolved = [];
@@ -30990,10 +31179,10 @@ async function resolveMediaUrls(auth, inputs) {
30990
31179
  if (trimmed.length === 0)
30991
31180
  continue;
30992
31181
  if (/^https?:\/\//i.test(trimmed)) {
30993
- if (isPostMediaUrl(trimmed)) {
31182
+ if (isAmikoMediaUrl(trimmed)) {
30994
31183
  resolved.push(trimmed);
30995
31184
  } else {
30996
- throw new Error(`"${trimmed}" is not an Amiko post-image URL. To attach an image, pass its LOCAL FILE PATH to --media (it is uploaded to post storage automatically) — do not pass a drive/docs URL (private bucket → broken image) or any other link.`);
31185
+ throw new Error(`"${trimmed}" is not an Amiko-hosted media URL. Attach either a local IMAGE file path, or an Amiko public storage URL — e.g. an image/music/video URL from \`amiko create\` (run \`amiko create status <jobId>\` to get it). Drive/docs URLs (private bucket → broken media) and other hosts are not accepted.`);
30997
31186
  }
30998
31187
  } else {
30999
31188
  resolved.push(await uploadPostMedia(auth, trimmed));
@@ -31049,7 +31238,7 @@ function registerFeedCommand(program2) {
31049
31238
  });
31050
31239
  }
31051
31240
  function registerPostCommand(program2) {
31052
- program2.command("create").description("Create a post on your feed").requiredOption("--content <text>", "Post body (required, non-empty)").option("--visibility <public|private>", "Post visibility", "public").option("--media <pathOrUrl...>", "One or more images: local file paths (uploaded to Amiko storage automatically) or https URLs").option("--json", "Output as JSON").action(async (opts) => {
31241
+ program2.command("create").description("Create a post on your feed").requiredOption("--content <text>", "Post body (required, non-empty)").option("--visibility <public|private>", "Post visibility", "public").option("--media <pathOrUrl...>", "Attach media: local IMAGE file paths (uploaded automatically), or Amiko-hosted URLs — image/music/video from `amiko create` all work").option("--json", "Output as JSON").action(async (opts) => {
31053
31242
  const auth = resolveAuth();
31054
31243
  const visibility = opts.visibility === "private" ? "private" : "public";
31055
31244
  const spinner = opts.json ? null : ora("Publishing post...").start();
@@ -31121,7 +31310,7 @@ function registerPostCommand(program2) {
31121
31310
  process.exit(1);
31122
31311
  }
31123
31312
  });
31124
- program2.command("comment").description("Comment on a post").requiredOption("--id <postId>", "Target post id").requiredOption("--comment <text>", "Comment body").option("--media <pathOrUrl...>", "One or more images to attach: local file paths (uploaded to Amiko storage automatically) or https URLs").option("--twin <idOrName>", "Post as this twin (agent mode)").option("--json", "Output as JSON").action(async (opts) => {
31313
+ program2.command("comment").description("Comment on a post").requiredOption("--id <postId>", "Target post id").requiredOption("--comment <text>", "Comment body").option("--media <pathOrUrl...>", "Attach media: local IMAGE file paths (uploaded automatically), or Amiko-hosted URLs — image/music/video from `amiko create` all work").option("--twin <idOrName>", "Post as this twin (agent mode)").option("--json", "Output as JSON").action(async (opts) => {
31125
31314
  const config2 = loadConfig();
31126
31315
  const auth = resolveAuth();
31127
31316
  let twinId;
@@ -31777,6 +31966,7 @@ var program2 = new Command;
31777
31966
  program2.name("amiko").description("Amiko CLI — swap tokens, manage credits, bridge cross-chain, and call marketplace services").version(PKG_VERSION);
31778
31967
  var markets = program2.command("markets").description("MPP marketplace — discover and call paid AMIKO services");
31779
31968
  var create2 = program2.command("create").description("Create Studio — generate images, video, speech, music, and SFX (charged on success)");
31969
+ var chat = program2.command("chat").description("Your conversations (DMs + group chats) as the owner — list, read, send");
31780
31970
  var wallets = program2.command("wallets").description("Manage twin wallets — list, swap, bridge");
31781
31971
  var twin = program2.command("twin").description("Update twin identity (name, description, visibility)");
31782
31972
  var drive = program2.command("drive").alias("docs").description("Manage twin drive — upload/download/search files, organise into folders");
@@ -31791,6 +31981,7 @@ registerCreditsCommand(program2);
31791
31981
  registerSearchCommand(markets);
31792
31982
  registerImageCommand(markets);
31793
31983
  registerCreateCommand(create2);
31984
+ registerChatCommand(chat);
31794
31985
  registerAmazonCommand(markets);
31795
31986
  registerWalletsCreateCommand(wallets);
31796
31987
  registerWalletsListCommand(wallets);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.12.0-beta.1",
3
+ "version": "0.12.0-beta.4",
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
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: amiko-cli
3
- description: The Amiko CLI lets an agent act on the Amiko platform end-to-end — read platform notifications (friend requests, mentions, system alerts), search and write **cross-agent memories** about the owner (what other agents already know — preferences, decisions, facts), manage Solana/Base wallets (create, swap via Jupiter, bridge USDC via Across, transfer tokens to external addresses), top up and spend credits, call paid MPP marketplace services (X/Twitter search, image generation, Amazon product search, TTS/STT, AI chat, SFX/music), manage the twin's identity and drive (files, folders, RAG), voice and avatar, and social graph (friends, posts, comments, feed). Auth is automatic when run from the agent's workspace folder; payments are platform-custodied (no keys on disk). DMs / chat sessions are NOT in this CLI — use the openhermit gateway's built-in session_list / session_read tools for both local sessions and platform DMs. Use this skill whenever the user asks about their Amiko notifications, drive, memory, social graph, wallets, credits, or marketplace services — anything that would show up in their Amiko account or cost AMIKO/credits.
3
+ description: The Amiko CLI lets an agent act on the Amiko platform end-to-end — read platform notifications (friend requests, mentions, system alerts), search and write **cross-agent memories** about the owner (what other agents already know — preferences, decisions, facts), manage Solana/Base wallets (create, swap via Jupiter, bridge USDC via Across, transfer tokens to external addresses), top up and spend credits, generate media — images, video, speech, music, and SFX — via Create Studio (`amiko create`, async and charged on success), call paid MPP marketplace services (X/Twitter search, Amazon product search, TTS/STT, AI chat), manage the twin's identity and drive (files, folders, RAG), voice and avatar, and social graph (friends, posts, comments, feed). Auth is automatic when run from the agent's workspace folder; payments are platform-custodied (no keys on disk). The owner's DMs and group chats are `amiko chat` (list / read / send AS the owner, 人对人); the agent's OWN sessions are the openhermit gateway's session_list / session_send (AS the agent) — different identities, different surfaces. Use this skill whenever the user asks about their Amiko notifications, chats/DMs, drive, memory, social graph, wallets, credits, or marketplace services — anything that would show up in their Amiko account or cost AMIKO/credits.
4
4
  homepage: https://platform.heyamiko.com
5
5
  metadata: {"openclaw":{"emoji":"🤖","requires":{"bins":["node"]}}}
6
6
  ---
@@ -22,6 +22,9 @@ Call your shell tool (your runtime calls it `bash`, `shell`, `run`, or similar)
22
22
  | "swap 1 SOL to USDC" | shell → `amiko wallets swap quote 1 SOL USDC` (then send with `--yes` after approval) |
23
23
  | "any notifications?" | shell → `amiko notifications list --unread` |
24
24
  | "any new posts I haven't seen?" | shell → `amiko feed --unread` |
25
+ | "make an image of a whale in space" | shell → `amiko create image "a whale in space" --yes` (quote cost first) |
26
+ | "generate a lo-fi track" | shell → `amiko create music "lo-fi chill beat" --yes` (quote cost first) |
27
+ | "show my recent creations" | shell → `amiko create media` |
25
28
  | "upload report.pdf to my drive" | shell → `amiko drive upload ./report.pdf` |
26
29
  | "download the file with id X" | shell → `amiko drive download X` |
27
30
  | "find files about Q1 revenue" | shell → `amiko drive search "Q1 revenue"` |
@@ -38,13 +41,13 @@ The CLI is installed globally and is pre-authenticated when you're inside your w
38
41
 
39
42
  ## Command groups
40
43
 
41
- Discover everything via `amiko --help`. Groups: `markets` (paid MPP services), `create` (Create Studio — async media generation, charged on success), `wallets`, `credits`, `twin`, `drive` (files / folders / RAG; `docs` is an alias), `voice`, `avatar`, `friends`, `users`, `post`, `review`, `feed`, `notifications`, `memory`, plus the top-level `accounts`, `info`, `config`, `update`. All twin-scoped commands accept `--twin <id>`. Most commands support `--json`.
44
+ Discover everything via `amiko --help`. Groups: `markets` (paid MPP services), `create` (Create Studio — async media generation, charged on success), `chat` (owner's DMs + group chats), `wallets`, `credits`, `twin`, `drive` (files / folders / RAG; `docs` is an alias), `voice`, `avatar`, `friends`, `users`, `post`, `review`, `feed`, `notifications`, `memory`, plus the top-level `accounts`, `info`, `config`, `update`. All twin-scoped commands accept `--twin <id>`. Most commands support `--json`.
42
45
 
43
- > **DMs / chat sessions are NOT in this CLI.** Use the openhermit gateway's built-in `session_list` / `session_read` tools for both the agent's local sessions and platform DMs with other users. The previous `amiko conversation` namespace was removed in 0.10.1-beta.4.
46
+ > **Two chat surfaces — pick by identity.** `amiko chat` is the **owner's** DMs and group chats, acting **as the owner** (list / read / send). The openhermit gateway's `session_list` / `session_send` are the **agent's own** sessions, acting **as the agent**. "Send a message to Sophie for me" → `amiko chat send`; "have the twin reply as itself" → gateway. (The old `amiko conversation` namespace was removed in 0.10.1-beta.4; `amiko chat` is its owner-identity replacement.)
44
47
 
45
48
  ## Critical Rules
46
49
 
47
- 1. **Every paid / value-moving command is hard-gated on `--yes` in a non-interactive shell.** The CLI refuses to run `markets *`, `create *`, `credits topup`, `wallets swap send`, `wallets bridge send`, `wallets transfer` (and destructive ops like `twin update --public`, `drive delete`, `friends remove`, `friends reports request`, `avatar update`, `voice reset`, `review reject`) unless `--yes` is passed. The refusal prints the cost and the re-run command. **Quote the cost, get explicit approval, THEN append `--yes`.**
50
+ 1. **Every paid / value-moving command is hard-gated on `--yes` in a non-interactive shell.** The CLI refuses to run `markets *`, `create *`, `chat send`, `credits topup`, `wallets swap send`, `wallets bridge send`, `wallets transfer` (and destructive ops like `twin update --public`, `drive delete`, `friends remove`, `friends reports request`, `avatar update`, `voice reset`, `review reject`) unless `--yes` is passed. The refusal prints the cost and the re-run command. **Quote the cost, get explicit approval, THEN append `--yes`.**
48
51
  2. **After every paid command, report the remaining balance.** The CLI prints a `Balance: N credits` line — include that figure in your reply.
49
52
  3. **Never retry a failed command.** Report and stop. Every paid call costs tokens even on failure.
50
53
  4. **Auth is automatic.** Never suggest `amiko login` / `amiko connect`. Run from the agent's workspace folder; if anything looks off, `amiko accounts` shows the resolved `userId` / `twinId`.
@@ -87,9 +90,20 @@ Supported: AMIKO, SOL, USDC, USDT. Balance can lag a few seconds after topup —
87
90
 
88
91
  All paid markets commands auto-select the twin's active Solana wallet (see `--wallet` default above). Audio and image endpoints return permanent Supabase Storage URLs. Use `markets service call <METHOD> <path> [body]` for any MPP endpoint not covered by a dedicated subcommand.
89
92
 
93
+ **Prefer `amiko create` for anything it supports.** For media generation — image, video, speech (TTS), music, SFX — use `amiko create` (async, charge-on-success, never loses money on a timeout), NOT `markets`. Reach for `markets` **only for what `create` does not cover**: X/Twitter search, Amazon, AI chat, speech-to-text/transcription, and arbitrary MPP endpoints via `markets service call`.
94
+
90
95
  ## Create Studio — behavior notes
91
96
 
92
- `amiko create <image|video|tts|music|sfx>` is the CLI half of the platform Create Studio. It generates through Amiko's own authenticated endpoints (not raw MPP), runs **async**, and is **charged on success** from the twin's custodial wallet — a failed or timed-out generation is **never billed**, and there is no pre-pay. The CLI submits then polls until done (video can take minutes); on a poll timeout it prints the **job id** and does NOT fail — the asset may still finish server-side. Retrieve it with `amiko create status <jobId>` (re-query that job, ~24h) or `amiko create media` (list recent generations; `--service`/`--limit`/`--raw`). Result is a permanent Supabase Storage URL. `--token <AMIKO|USDC|USDT|SOL>` selects the charge token (default auto, AMIKO-first). Prefer `create` over `markets image` for media generation — it doesn't lose money on timeouts. `markets image` remains the raw MPP pre-pay path.
97
+ `amiko create <image|video|tts|music|sfx>` is the CLI half of the platform Create Studio. It generates through Amiko's own authenticated endpoints (not raw MPP), runs **async**, and is **charged on success** from the twin's custodial wallet — a failed or timed-out generation is **never billed**, and there is no pre-pay. **The command returns immediately** with a `jobId` and `status: PENDING` — it does NOT block for the whole generation (so you're not held for 60s–9min). It prints how to check + a rough ETA. **Poll for the result** with `amiko create status <jobId>` (re-query that job, ~24h) — wait roughly: image ~15–60s, video ~2–9min, music ~30–120s, tts/sfx ~5–20s. Or `amiko create media` (list recent generations; `--service`/`--limit`/`--raw`). Result is a permanent Supabase Storage URL. (Pass `--wait` only if you deliberately want the command to block until done.) `--token <AMIKO|USDC|USDT|SOL>` selects the charge token (default auto, AMIKO-first). Prefer `create` over `markets image` for media generation — it doesn't lose money on timeouts. `markets image` remains the raw MPP pre-pay path.
98
+
99
+ ## Chat — behavior notes
100
+
101
+ `amiko chat` is the **owner's** conversations, acting **as the owner** (人对人) — NOT the agent's own sessions (those are the gateway's `session_*`, as the agent).
102
+
103
+ - `amiko chat list` — all conversations, **DMs and group chats** (id, type, peer/title, last message, unread).
104
+ - `amiko chat read <target>` — recent messages. `<target>` = a conversation id (from `list`), an `@handle`, or a name.
105
+ - `amiko chat send <target> "message"` — send **as the owner**. Delivered in real time. `--yes` required in non-interactive shells (it's an outward message to a real person). For **group chats**, use the conversation id from `list`; for a **DM**, an `@handle`/name resolves + finds-or-creates the DM.
106
+ - Name resolution goes through user search; if ambiguous it lists candidates — don't blind-send. Server enforces who you're allowed to message (friend/participant rules); surface its error, don't retry.
93
107
 
94
108
  ## Drive (files & folders)
95
109
 
@@ -128,47 +142,10 @@ Workflow: start with `friends matches --limit 20 --json`; narrow with `--dimensi
128
142
 
129
143
  > **Default platform = Amiko.** When the owner asks about notifications **without naming a platform**, assume Amiko and answer with `amiko notifications`. Only ask if they explicitly mention a non-Amiko channel.
130
144
  >
131
- > **DMs / chat history are handled by the openhermit gateway**, not the CLI. For "did X message me?" / "check my chat with Y", use the built-in `session_list` / `session_read` tools — they cover both the agent's local sessions and platform DMs in the unified gateway view.
145
+ > **Owner DMs / group chats: `amiko chat`.** For "did X message me?" / "what did Sophie and I say?" / "message Y for me", use `amiko chat list` / `amiko chat read <who>` / `amiko chat send <who> "…"` — these act **as the owner** (人对人). The gateway's `session_*` tools are the **agent's own** sessions (as the agent), a separate surface (see Chat behavior notes).
132
146
 
133
147
  Platform notifications cover friend requests, mentions, system alerts, and post-related events.
134
148
 
135
- ## Memory (cross-agent) — READ THIS BEFORE ANSWERING ANYTHING ABOUT THE OWNER
136
-
137
- Memories are scoped to the **owner's `user_id`**, not your agent — every agent the owner runs reads and writes the same pool. Your local `memory/` folder is one input; the platform is the source of truth across sessions and agents.
138
-
139
- **Two non-negotiable habits:**
140
-
141
- 1. **Search before you answer.** Run `amiko memory search "<query>"` **every time** the owner asks anything about themselves, their work, or their history — *before* composing a reply. Another agent may have written a memory you've never seen. A miss costs ~100ms; an amnesic answer costs trust.
142
- 2. **Upload changed files.** If you keep memory in local files (e.g. `MEMORY.md`, `memory/*.md`), upload them with `amiko memory upload <file...>` after meaningful edits. The server extracts memories from each file; default path key is the basename. No bulk-walking — pass the exact files you want to push.
143
-
144
- ### When to search — bias toward calling
145
-
146
- The single most common failure mode is NOT calling `memory search` on abstract self-referential questions. If the owner's message has any of these shapes, you MUST search — no judgment, no exceptions:
147
-
148
- 1. **Preference / habit questions**, even without a specific entity. "what do I usually use for X", "what's my coding style".
149
- 2. **Callbacks to prior context.** "as I mentioned", "like last time", "what was that X we set up".
150
- 3. **Named entities specific to this owner** — their project / repo / service / team / tool / a person by name.
151
- 4. **Past bugs, decisions, investigations, design choices.**
152
- 5. **Start of a new session** where they reference anything about themselves or their work.
153
-
154
- Do NOT search for purely textbook programming questions, or things the current code / `git log` answers directly.
155
-
156
- **When unsure, search.** Empty results cost nothing. Missing context costs trust.
157
-
158
- ### When to save (`amiko memory add`)
159
-
160
- - Fixing a non-obvious bug → `pattern` or `fact`
161
- - Architecture decision → `decision`
162
- - Useful pattern or workaround → `pattern`
163
- - "Remember this" / "save this" / "from now on..." → match category to content
164
- - Preference you'd otherwise re-ask ("I prefer rg", "I always use pnpm") → `preference`
165
-
166
- Write memories as **standalone sentences with full context** — include names, not pronouns. Bad: "He prefers it that way." Good: "William prefers terse PR descriptions in the Amiko-Layer repo."
167
-
168
- Categories: `fact` | `preference` | `pattern` | `decision` | `context`.
169
-
170
- Do NOT save trivial code-derivable facts, ephemeral `git log` state, or duplicates (search first; `rm` near-matches before adding).
171
-
172
149
  ## Where to run
173
150
 
174
151
  **Each agent must run `amiko` from inside its own workspace directory.** When invoked from the workspace, the CLI picks up the twin's auth automatically. Run from the wrong folder and you'll act on the wrong twin or fail auth. Before the first call in a session, `cd` into the workspace. `amiko accounts` confirms the resolved scope.