@heyamiko/amiko-cli 0.14.0-beta.17 β†’ 0.14.0-beta.18

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
@@ -259,6 +259,7 @@ amiko chat send "Trip planning" "standup in 5 @all" --yes # @all β€” notifie
259
259
  amiko chat send "Trip planning" "standup in 5" --all --yes # same, via the explicit flag
260
260
  amiko chat send @sophie "look πŸ‘€" --image ./cat.png --yes # attach a local image
261
261
  amiko chat send @sophie "made this 🎡" --audio <generated-url> --yes # attach audio (e.g. from `amiko create`)
262
+ amiko chat receipts <messageId> # who read your message + when (id from `chat read`)
262
263
  ```
263
264
 
264
265
  - **`<target>`** for `read`/`send` is a **conversation id** (from `chat list` β€” use this for groups), a **user id**, an **`@handle`**, or a **name**. Names resolve against your **friends first**, then people search; if ambiguous, the CLI lists candidates instead of guessing. A cuid that isn't one of your conversations is retried as a user id, so pasting a user id "just works".
@@ -266,6 +267,7 @@ amiko chat send @sophie "made this 🎡" --audio <generated-url> --yes # attach
266
267
  - **Inline media**: `--image <pathOrUrl>` (jpg/png/webp/gif, local ≀10MB or an Amiko URL) and `--audio <pathOrUrl>` (local ≀25MB or an Amiko URL β†’ sent as a voice note). One media block per message (image *or* audio). **Video isn't supported in chat yet.** A local file is uploaded; an Amiko URL (e.g. a generated asset from `amiko create`) attaches directly with no re-upload. (A local *audio* file is sent as a voice note, with your text as a separate message.)
267
268
  - **@all in groups**: `--all` prepends an @all mention, and a standalone `@all` word typed in the message converts too β€” either way **every member** is notified. Group **admins/owners** can always use it; everyone else only after an admin runs `amiko chat group mention-all <group> on`. Without permission, `--all` fails with the fix named, while a typed `@all` is delivered as plain text (with a note). Incoming mention markup renders as plain `@name`/`@all` in `chat read` and `chat list`.
268
269
  - `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.
270
+ - **`chat receipts <messageId>`** shows read receipts for **your own** messages (server-enforced; 403 otherwise): sent time, who read it with each person's first-read time, and who it was delivered to but hasn't read. Reads from before receipts tracking show as "exact time unknown". Read-only, ungated.
269
271
 
270
272
  ### Group chats
271
273
 
@@ -588,6 +590,10 @@ npm publish
588
590
 
589
591
  ## Changelog
590
592
 
593
+ ### 0.14.0-beta.18
594
+
595
+ - **Read receipts: `amiko chat receipts <messageId>`.** Shows when the message was sent, who has read it (per-person first-read time), and who it was delivered to but hasn't read yet. Sender-only, enforced server-side β€” works for messages sent as the owner (`chat send`); anything else 403s with a clear message. Reader names resolve from the conversation's participant list (short-id fallback). Read-only and ungated, like `chat reactions`; `--raw` prints the API payload. Rows read before receipts tracking existed render as "exact time unknown". Requires the companion amiko-web `feat/chat-read-receipts` deploy (`GET /api/messages/{id}/receipts`).
596
+
591
597
  ### 0.14.0-beta.17
592
598
 
593
599
  - **@all mentions in group chats + `amiko chat group mention-all <group> <on|off>`.** `chat send` gains `--all`, and a standalone literal `@all` word in the message converts too β€” both emit the canonical sentinel `@[all](all:all)` (kept in sync with amiko-web `src/utils/mentions.ts`), which amiko-chat expands into a mention notification for every group member. The CLI pre-checks permission client-side, mirroring the web gate plus the server-honored `owner` role (`admin`/`owner`, or the conversation's `allow_everyone_mention_all` flag; `left_at` participants excluded): a denied `--all` exits 1 naming the fix, before the approval gate and before any DM could be created, while a denied literal `@all` degrades to plain text with a note β€” the server treats an unauthorized token as inert either way. The new `chat group mention-all` toggle PATCHes `/api/conversations/{id}/mention-all-permission` (admin-gated server-side, 403 β†’ admin tip) behind `confirmDestructive`, and the current setting shows in `chat group info` (`@all mentions: everyone | admins only`). Incoming mention markup (`@[Name](id:user|agent)` / `@[all](all:all)`) now renders as plain `@Name`/`@all` in `chat read`, `chat list`, and `chat group list` previews (new `src/lib/mentions.ts`). Requires the companion amiko-web `feat/group-mention-all` deploy.
package/dist/index.js CHANGED
@@ -23665,8 +23665,8 @@ function readFromFile(path) {
23665
23665
  }
23666
23666
  function readRoutingIdsFromEnv() {
23667
23667
  return {
23668
- agentId: asString(process.env.AMIKO_AGENT_ID),
23669
- sessionId: asString(process.env.AMIKO_SESSION_ID)
23668
+ agentId: asString(process.env.OPENHERMIT_AGENT_ID),
23669
+ sessionId: asString(process.env.OPENHERMIT_SESSION_ID)
23670
23670
  };
23671
23671
  }
23672
23672
  function readFromEnv() {
@@ -28111,6 +28111,66 @@ function registerChatCommand(chat) {
28111
28111
  console.log(label(emoji3, `Γ—${count}`) + you);
28112
28112
  }
28113
28113
  });
28114
+ chat.command("receipts <messageId>").description("Show who read your message and when (sender-only). Get the message id from `amiko chat read`.").option("--raw", "Output raw JSON").action(async (messageId, opts) => {
28115
+ const auth = authOrExit();
28116
+ let msg;
28117
+ try {
28118
+ msg = await fetchMessage(auth, messageId);
28119
+ } catch (e5) {
28120
+ console.error(error(e5 instanceof Error ? e5.message : "Could not load that message"));
28121
+ process.exit(1);
28122
+ }
28123
+ let data;
28124
+ try {
28125
+ data = await amikoWebFetch(auth, `/api/messages/${encodeURIComponent(messageId)}/receipts`, { timeoutMs: 20000 });
28126
+ } catch (e5) {
28127
+ const status = e5.status;
28128
+ console.error(error(status === 403 ? "Read receipts are only available for messages you sent (as the owner)." : e5 instanceof Error ? e5.message : "Failed to load receipts"));
28129
+ process.exit(1);
28130
+ }
28131
+ const reads = data.reads ?? [];
28132
+ const delivered = data.delivered_to ?? [];
28133
+ if (opts.raw) {
28134
+ console.log(JSON.stringify(data, null, 2));
28135
+ return;
28136
+ }
28137
+ const names = new Map;
28138
+ if (msg.conversation_id) {
28139
+ try {
28140
+ const conv = await fetchConversationById(auth, msg.conversation_id);
28141
+ for (const p of conv.participants ?? []) {
28142
+ if (p.participant_type !== "user")
28143
+ continue;
28144
+ const id = normalizeUserId(p.participant_id);
28145
+ names.set(id, p.user?.name ?? p.user?.handle ?? id.slice(0, 8));
28146
+ }
28147
+ } catch {}
28148
+ }
28149
+ const nameFor = (userId) => {
28150
+ const id = normalizeUserId(userId);
28151
+ return names.get(id) ?? id.slice(0, 8);
28152
+ };
28153
+ const fmt = (iso) => iso.slice(0, 19).replace("T", " ");
28154
+ console.log(label("Message", previewText(msg)));
28155
+ console.log(label("Sent", fmt(data.sent_at)));
28156
+ console.log("");
28157
+ console.log(heading(`Read by (${reads.length})`));
28158
+ if (reads.length === 0) {
28159
+ console.log(dim(" No one has read this yet."));
28160
+ } else {
28161
+ for (const r of reads) {
28162
+ const when = r.approximate ? dim("read (exact time unknown)") : fmt(r.read_at);
28163
+ console.log(` ${label(nameFor(r.user_id), when)}`);
28164
+ }
28165
+ }
28166
+ if (delivered.length > 0) {
28167
+ console.log("");
28168
+ console.log(heading(`Delivered to (${delivered.length})`));
28169
+ for (const userId of delivered) {
28170
+ console.log(` ${nameFor(userId)}`);
28171
+ }
28172
+ }
28173
+ });
28114
28174
  const group = chat.command("group").description("Group chats β€” create, list, info, rename, add/remove members, @all permission, leave");
28115
28175
  registerChatGroupCommand(group);
28116
28176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.14.0-beta.17",
3
+ "version": "0.14.0-beta.18",
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
@@ -34,6 +34,7 @@ Call your shell tool (your runtime calls it `bash`, `shell`, `run`, or similar)
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
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` |
37
+ | "did Sophie read my message about the demo?" | shell β†’ `amiko chat read "Sophie"` (find the owner's message, copy its `id`) β†’ `amiko chat receipts <messageId>` |
37
38
  | "what can amiko do?" | shell β†’ `amiko --help` |
38
39
  | "what's my MiniMax / ElevenLabs voice id?" | shell β†’ `amiko info` |
39
40
  | "speak with my cloned MiniMax voice" | shell β†’ `amiko info`, quote the cost, obtain explicit approval, then run `amiko create tts "…" --provider minimax --voice <minimax_voice_id> --yes` |
@@ -148,6 +149,7 @@ MiniMax Hailuo is silent β€” there is no CLI mux step to attach a separate music
148
149
  - **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.
149
150
  - 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.
150
151
  - **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_*`.
152
+ - **Read receipts** β€” `amiko chat receipts <messageId>` shows when the message was sent, who has read it (with each person's first-read time), and who it was delivered to but hasn't read yet. **Only works for messages the owner sent** (server-enforced) β€” a 403 means it wasn't the owner's message; report that, don't retry. Get the id from `amiko chat read <target>`, matching the message by its **content**. Read-only and ungated, like `reactions`. Rows marked "exact time unknown" were read before receipts tracking existed β€” never invent a time for them.
151
153
 
152
154
  ### Group chats β€” `amiko chat group`
153
155