@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 +33 -4
- package/dist/index.js +204 -13
- package/package.json +1 -1
- package/skills/SKILL.md +20 -43
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @heyamiko/amiko-cli (v0.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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}).
|
|
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
|
|
30980
|
-
function
|
|
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(
|
|
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 (
|
|
31182
|
+
if (isAmikoMediaUrl(trimmed)) {
|
|
30994
31183
|
resolved.push(trimmed);
|
|
30995
31184
|
} else {
|
|
30996
|
-
throw new Error(`"${trimmed}" is not an Amiko
|
|
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...>", "
|
|
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...>", "
|
|
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
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,
|
|
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
|
-
> **
|
|
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
|
|
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 /
|
|
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.
|