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

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/dist/index.js CHANGED
@@ -861,7 +861,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
861
861
  this._exitCallback = (err) => {
862
862
  if (err.code !== "commander.executeSubCommandAsync") {
863
863
  throw err;
864
- } else {}
864
+ }
865
865
  };
866
866
  }
867
867
  return this;
@@ -3596,7 +3596,7 @@ function getHumanReadableErrorMessage(code, context = {}) {
3596
3596
  function getErrorMessage(code, context = {}) {
3597
3597
  if (true) {
3598
3598
  return getHumanReadableErrorMessage(code, context);
3599
- } else {}
3599
+ }
3600
3600
  }
3601
3601
  function isSolanaError(e, code) {
3602
3602
  const isSolanaError2 = e instanceof Error && e.name === "SolanaError";
@@ -19992,7 +19992,7 @@ function finalize(ctx, schema) {
19992
19992
  result.$schema = "http://json-schema.org/draft-07/schema#";
19993
19993
  } else if (ctx.target === "draft-04") {
19994
19994
  result.$schema = "http://json-schema.org/draft-04/schema#";
19995
- } else if (ctx.target === "openapi-3.0") {} else {}
19995
+ } else if (ctx.target === "openapi-3.0") {}
19996
19996
  if (ctx.external?.uri) {
19997
19997
  const id = ctx.external.registry.get(schema)?.id;
19998
19998
  if (!id)
@@ -20257,7 +20257,7 @@ var formatMap, stringProcessor = (schema, ctx, _json, _params) => {
20257
20257
  if (val === undefined) {
20258
20258
  if (ctx.unrepresentable === "throw") {
20259
20259
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
20260
- } else {}
20260
+ }
20261
20261
  } else if (typeof val === "bigint") {
20262
20262
  if (ctx.unrepresentable === "throw") {
20263
20263
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -27061,6 +27061,53 @@ async function resolveGroup(auth, target) {
27061
27061
  return fetchConversationById(auth, matches[0].id);
27062
27062
  }
27063
27063
 
27064
+ // src/lib/reactions.ts
27065
+ var EMOJI_MAX = 32;
27066
+ function validateEmoji(raw) {
27067
+ const emoji3 = (raw ?? "").trim();
27068
+ if (!emoji3)
27069
+ throw new Error("No emoji given. Pass the emoji to react with, e.g. ❤️.");
27070
+ if (emoji3.length > EMOJI_MAX) {
27071
+ throw new Error(`Emoji is too long (max ${EMOJI_MAX} characters).`);
27072
+ }
27073
+ return emoji3;
27074
+ }
27075
+ async function fetchMessage(auth, messageId) {
27076
+ const data = await amikoWebFetch(auth, `/api/messages/${encodeURIComponent(messageId)}`, { timeoutMs: 20000 });
27077
+ if (!data.message?.id) {
27078
+ throw new Error(`Message ${messageId} not found.`);
27079
+ }
27080
+ return { ...data.message, reactions: data.message.reactions ?? [] };
27081
+ }
27082
+ function ownReaction(reactions, userId) {
27083
+ if (!userId)
27084
+ return null;
27085
+ const me = normalizeUserId(userId);
27086
+ const mine = reactions.find((r) => normalizeUserId(r.user_id) === me);
27087
+ return mine?.emoji ?? null;
27088
+ }
27089
+ function previewText(msg, max = 60) {
27090
+ const content = (msg.content ?? "").trim();
27091
+ if (content) {
27092
+ return content.length > max ? `${content.slice(0, max)}…` : content;
27093
+ }
27094
+ if (msg.message_type === "voice")
27095
+ return "[voice message]";
27096
+ if (msg.metadata?.images?.length)
27097
+ return "[image]";
27098
+ return `[${msg.message_type || "message"}]`;
27099
+ }
27100
+ function groupReactions(reactions) {
27101
+ const counts = new Map;
27102
+ for (const r of reactions) {
27103
+ counts.set(r.emoji, (counts.get(r.emoji) ?? 0) + 1);
27104
+ }
27105
+ return Array.from(counts.entries());
27106
+ }
27107
+ function summarizeReactions(reactions) {
27108
+ return groupReactions(reactions).map(([emoji3, count]) => `${emoji3}×${count}`).join(" ");
27109
+ }
27110
+
27064
27111
  // src/commands/chat-group.ts
27065
27112
  var collect = (v, prev) => [...prev, v];
27066
27113
  function statusOf2(e5) {
@@ -27650,8 +27697,11 @@ function registerChatCommand(chat) {
27650
27697
  const who = m.sender?.name ?? m.name ?? (m.sender_id ? m.sender_id.slice(0, 8) : "?");
27651
27698
  const when = m.created_at ? m.created_at.slice(0, 19).replace("T", " ") : "";
27652
27699
  console.log(label(who, m.content));
27653
- if (when)
27654
- console.log(dim(` ${when}`));
27700
+ const meta3 = [when, `id ${m.id}`].filter(Boolean).join(" ");
27701
+ console.log(dim(` ${meta3}`));
27702
+ if (m.reactions?.length) {
27703
+ console.log(dim(` reactions: ${summarizeReactions(m.reactions)}`));
27704
+ }
27655
27705
  }
27656
27706
  });
27657
27707
  chat.command("send <target> <message>").description("Send a message as yourself (the owner) to a conversation or person (conversation id, user id, @handle, or name — DMs are opened automatically)").option("--image <pathOrUrl>", "Attach an image: local file (jpg/png/webp/gif, ≤10MB) or an Amiko URL (e.g. from `amiko create`)").option("--audio <pathOrUrl>", "Attach audio as a voice note: local file (≤25MB) or an Amiko URL").option("--yes", "Skip the confirmation (required in non-interactive shells)").option("--raw", "Output raw JSON").action(async (target, message, opts) => {
@@ -27717,6 +27767,113 @@ function registerChatCommand(chat) {
27717
27767
  const what = opts.image ? "Message + image sent" : opts.audio ? "Message + voice note sent" : "Message sent";
27718
27768
  console.log(success(what) + dim(` (conversation ${convId.slice(0, 8)}…)`));
27719
27769
  });
27770
+ chat.command("react <messageId> <emoji>").description("React to a message as yourself (the owner). Get the message id from `amiko chat read`. One reaction per message — a new emoji replaces your previous one.").option("--yes", "Skip the confirmation (required in non-interactive shells)").option("--raw", "Output raw JSON").action(async (messageId, emojiArg, opts) => {
27771
+ const auth = authOrExit();
27772
+ let emoji3;
27773
+ try {
27774
+ emoji3 = validateEmoji(emojiArg);
27775
+ } catch (e5) {
27776
+ console.error(error(e5 instanceof Error ? e5.message : "Invalid emoji"));
27777
+ process.exit(1);
27778
+ }
27779
+ let msg;
27780
+ try {
27781
+ msg = await fetchMessage(auth, messageId);
27782
+ } catch (e5) {
27783
+ console.error(error(e5 instanceof Error ? e5.message : "Could not load that message"));
27784
+ process.exit(1);
27785
+ }
27786
+ if (ownReaction(msg.reactions, auth.userId) === emoji3) {
27787
+ if (opts.raw) {
27788
+ console.log(JSON.stringify({ success: true, action: "unchanged", reactions: msg.reactions }, null, 2));
27789
+ return;
27790
+ }
27791
+ console.log(success(`Already reacted with ${emoji3}`));
27792
+ return;
27793
+ }
27794
+ await requireApproval({
27795
+ free: "adds your reaction — people in the chat see it",
27796
+ summary: `React ${emoji3} to "${previewText(msg)}"`,
27797
+ yes: opts.yes,
27798
+ commandExample: `amiko chat react ${JSON.stringify(messageId)} ${JSON.stringify(emoji3)}`
27799
+ });
27800
+ let data;
27801
+ try {
27802
+ data = await amikoWebFetch(auth, `/api/messages/${encodeURIComponent(messageId)}/reactions`, { method: "POST", body: { emoji: emoji3 }, timeoutMs: 20000 });
27803
+ } catch (e5) {
27804
+ console.error(error(e5 instanceof Error ? e5.message : "Failed to react"));
27805
+ process.exit(1);
27806
+ }
27807
+ if (opts.raw) {
27808
+ console.log(JSON.stringify(data, null, 2));
27809
+ return;
27810
+ }
27811
+ const what = data.action === "removed" ? `Removed your ${emoji3} reaction` : data.action === "updated" ? `Reaction changed to ${emoji3}` : `Reacted ${emoji3}`;
27812
+ console.log(success(what));
27813
+ });
27814
+ chat.command("unreact <messageId>").description("Remove your (the owner's) reaction from a message. Get the message id from `amiko chat read`.").option("--yes", "Skip the confirmation (required in non-interactive shells)").option("--raw", "Output raw JSON").action(async (messageId, opts) => {
27815
+ const auth = authOrExit();
27816
+ let msg;
27817
+ try {
27818
+ msg = await fetchMessage(auth, messageId);
27819
+ } catch (e5) {
27820
+ console.error(error(e5 instanceof Error ? e5.message : "Could not load that message"));
27821
+ process.exit(1);
27822
+ }
27823
+ const mine = ownReaction(msg.reactions, auth.userId);
27824
+ if (!mine) {
27825
+ if (opts.raw) {
27826
+ console.log(JSON.stringify({ success: true, action: "none", reactions: msg.reactions }, null, 2));
27827
+ return;
27828
+ }
27829
+ console.log(dim("You have no reaction on that message."));
27830
+ return;
27831
+ }
27832
+ await requireApproval({
27833
+ free: "removes your reaction from the chat",
27834
+ summary: `Remove your ${mine} reaction from "${previewText(msg)}"`,
27835
+ yes: opts.yes,
27836
+ commandExample: `amiko chat unreact ${JSON.stringify(messageId)}`
27837
+ });
27838
+ let data;
27839
+ try {
27840
+ data = await amikoWebFetch(auth, `/api/messages/${encodeURIComponent(messageId)}/reactions`, { method: "POST", body: { emoji: mine }, timeoutMs: 20000 });
27841
+ } catch (e5) {
27842
+ console.error(error(e5 instanceof Error ? e5.message : "Failed to remove reaction"));
27843
+ process.exit(1);
27844
+ }
27845
+ if (opts.raw) {
27846
+ console.log(JSON.stringify(data, null, 2));
27847
+ return;
27848
+ }
27849
+ const what = data.action === "removed" ? `Removed your ${mine} reaction` : data.action === "updated" ? `Reaction changed to ${mine}` : `Reacted ${mine}`;
27850
+ console.log(success(what));
27851
+ });
27852
+ chat.command("reactions <messageId>").description("Show the reactions on a message. Get the message id from `amiko chat read`.").option("--raw", "Output raw JSON").action(async (messageId, opts) => {
27853
+ const auth = authOrExit();
27854
+ let msg;
27855
+ try {
27856
+ msg = await fetchMessage(auth, messageId);
27857
+ } catch (e5) {
27858
+ console.error(error(e5 instanceof Error ? e5.message : "Could not load that message"));
27859
+ process.exit(1);
27860
+ }
27861
+ if (opts.raw) {
27862
+ console.log(JSON.stringify({ reactions: msg.reactions }, null, 2));
27863
+ return;
27864
+ }
27865
+ if (msg.reactions.length === 0) {
27866
+ console.log(dim("No reactions yet."));
27867
+ return;
27868
+ }
27869
+ const mine = ownReaction(msg.reactions, auth.userId);
27870
+ console.log(heading(`Reactions (${msg.reactions.length})
27871
+ `));
27872
+ for (const [emoji3, count] of groupReactions(msg.reactions)) {
27873
+ const you = emoji3 === mine ? dim(" (includes you)") : "";
27874
+ console.log(label(emoji3, `×${count}`) + you);
27875
+ }
27876
+ });
27720
27877
  const group = chat.command("group").description("Group chats — create, list, info, rename, add/remove members, leave");
27721
27878
  registerChatGroupCommand(group);
27722
27879
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.14.0-beta.11",
3
+ "version": "0.14.0-beta.12",
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,6 +33,7 @@ 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
  | "search memory for X" | shell → `amiko memory search "X"` |
35
35
  | "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>"` |
36
+ | "react to the latest message from Ava about the team with a heart" | shell → `amiko chat read "Ava"` (find that message, copy its `id`) → `amiko chat react <messageId> ❤️ --yes` |
36
37
  | "what can amiko do?" | shell → `amiko --help` |
37
38
 
38
39
  The CLI is installed globally and is pre-authenticated when you're inside your workspace folder. Never suggest `amiko login` or `amiko connect` — they don't exist.
@@ -129,6 +130,7 @@ MiniMax Hailuo is silent — there is no CLI mux step to attach a separate music
129
130
  - `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). `<target>` = a conversation id, a **user id**, an `@handle`, or a name. A user id / @handle / unambiguous name opens (or reuses) the DM for you — but only **after** the owner approves the send (via the confirmation, or `--yes` in your shell); nothing is created if approval is refused. **The owner never needs to start the conversation first, and there is no platform authorization step for starting a DM.** For **group chats**, use the conversation id from `list`.
130
131
  - **Inline media**: `--image <pathOrUrl>` (jpg/png/webp/gif, local ≤10MB or an Amiko URL — e.g. a generated image) and `--audio <pathOrUrl>` (local ≤25MB or an Amiko URL → sent as a voice note). One media block per message (image XOR audio). **Video is not supported** on chat yet. To share a generated asset, pass its URL from `amiko create status` (no re-upload). A local audio file is sent as a voice note with the text as a separate message.
131
132
  - Plain names resolve against the owner's **friends first**, then people search; if ambiguous the CLI errors listing the candidates — ask the owner which one, don't blind-send. A plain name that exactly matches one of the owner's group titles targets that **group**. If a send fails, surface the CLI's error verbatim — don't invent permission explanations or ask the owner to open the chat from the app.
133
+ - **Reactions** — `amiko chat react <messageId> <emoji>`, `amiko chat unreact <messageId>`, `amiko chat reactions <messageId>`. The owner **won't give you a message id** — they'll say something like *"react to the latest Ava message about the team with a heart"*. Get ids from `amiko chat read <target>`: it prints each message's `id` and any existing reactions — match the message by its **content**, then act on that id. `react` adds (or replaces) your reaction — **one per message**, a new emoji replaces the previous one, and re-reacting the same emoji is a no-op (use `unreact` to remove). Pass the **actual emoji character** (`❤️`, `👍`, `😂`). `react`/`unreact` are outward (other people see them), so `--yes` is required in non-interactive shells and the confirmation shows a preview of the target message; `reactions` (read) is ungated. These act **as the owner** (人对人), just like `chat send` — not the twin's own `session_*`.
132
134
 
133
135
  ### Group chats — `amiko chat group`
134
136