@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 +163 -6
- package/package.json +1 -1
- package/skills/SKILL.md +2 -0
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
|
-
}
|
|
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
|
-
}
|
|
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") {}
|
|
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
|
-
}
|
|
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
|
-
|
|
27654
|
-
|
|
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
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
|
|