@heyamiko/amiko-cli 0.12.0-beta.5 → 0.12.0-beta.7
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 +22 -2
- package/dist/index.js +237 -88
- package/package.json +1 -1
- package/skills/SKILL.md +12 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @heyamiko/amiko-cli (v0.12.0-beta.
|
|
1
|
+
# @heyamiko/amiko-cli (v0.12.0-beta.7)
|
|
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
|
|
|
@@ -206,7 +206,7 @@ amiko create media --service video --limit 5
|
|
|
206
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.
|
|
207
207
|
- **Web:** `https://platform.heyamiko.com/media`
|
|
208
208
|
|
|
209
|
-
|
|
209
|
+
`create` is always non-blocking — there is no `--wait`. `--raw` prints the submit result (`{ jobId, status, check, eta }`) as JSON.
|
|
210
210
|
|
|
211
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.
|
|
212
212
|
|
|
@@ -230,6 +230,26 @@ amiko chat send @sophie "made this 🎵" --audio <generated-url> --yes # attach
|
|
|
230
230
|
- **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.)
|
|
231
231
|
- `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.
|
|
232
232
|
|
|
233
|
+
## Twin Cards
|
|
234
|
+
|
|
235
|
+
Generate, view, download, and mint your **Twin Card** (template `twins_take`, flavors **work / play / love**).
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
amiko card templates # available flavors (DB-managed)
|
|
239
|
+
amiko card create --flavor work # generate → prints slug + share link
|
|
240
|
+
amiko card create --flavor love --reroll 1 # a fresh take
|
|
241
|
+
amiko card list # your latest card
|
|
242
|
+
amiko card show <slug> # stored card data (JSON)
|
|
243
|
+
amiko card image <slug> --flavor work --out card.png # download the PNG
|
|
244
|
+
amiko card share <slug> # print https://platform.heyamiko.com/s/<slug>
|
|
245
|
+
amiko card mint <slug> --flavor work --yes # mint as an NFT (gated on --yes)
|
|
246
|
+
amiko card mint <slug> --status # check an existing mint
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- Flavor defaults to `love`; flavors are DB-managed — `card templates` lists the valid ones.
|
|
250
|
+
- `create` errors are user-fixable and exit with **code 2** (not a crash): `profile_not_ready`, `guardrails_failed`, `rate_limited` (reroll cooldown).
|
|
251
|
+
- `card mint` creates an on-chain NFT → **gated on `--yes`**. All subcommands support `--json`.
|
|
252
|
+
|
|
233
253
|
## Amazon
|
|
234
254
|
|
|
235
255
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -28589,7 +28589,6 @@ function registerImageCommand(program2) {
|
|
|
28589
28589
|
}
|
|
28590
28590
|
|
|
28591
28591
|
// src/commands/create.ts
|
|
28592
|
-
var POLL_INTERVAL_MS = 3000;
|
|
28593
28592
|
var ETA = {
|
|
28594
28593
|
IMAGE: "~15–60s",
|
|
28595
28594
|
VIDEO: "~2–9 min",
|
|
@@ -28617,7 +28616,7 @@ function printCompleted(final, title) {
|
|
|
28617
28616
|
}
|
|
28618
28617
|
console.log(dim("View in your media space: https://platform.heyamiko.com/media"));
|
|
28619
28618
|
}
|
|
28620
|
-
async function
|
|
28619
|
+
async function submitCreateJob(opts) {
|
|
28621
28620
|
let auth;
|
|
28622
28621
|
try {
|
|
28623
28622
|
auth = requireAuth();
|
|
@@ -28644,27 +28643,7 @@ async function submitAndPoll(opts) {
|
|
|
28644
28643
|
});
|
|
28645
28644
|
const isTTY = process.stderr.isTTY;
|
|
28646
28645
|
const spinner = isTTY ? ora("Submitting…").start() : null;
|
|
28647
|
-
let progressTimer = null;
|
|
28648
|
-
const started = Date.now();
|
|
28649
|
-
const setStatus = (text) => {
|
|
28650
|
-
if (spinner)
|
|
28651
|
-
spinner.text = text;
|
|
28652
|
-
else
|
|
28653
|
-
console.error(text);
|
|
28654
|
-
};
|
|
28655
|
-
const startTicker = () => {
|
|
28656
|
-
if (!spinner && !progressTimer) {
|
|
28657
|
-
progressTimer = setInterval(() => {
|
|
28658
|
-
const elapsed = Math.round((Date.now() - started) / 1000);
|
|
28659
|
-
console.error(` …still generating (${elapsed}s elapsed)`);
|
|
28660
|
-
}, 3000);
|
|
28661
|
-
}
|
|
28662
|
-
};
|
|
28663
28646
|
const stop = () => {
|
|
28664
|
-
if (progressTimer) {
|
|
28665
|
-
clearInterval(progressTimer);
|
|
28666
|
-
progressTimer = null;
|
|
28667
|
-
}
|
|
28668
28647
|
if (spinner)
|
|
28669
28648
|
spinner.stop();
|
|
28670
28649
|
};
|
|
@@ -28686,68 +28665,28 @@ async function submitAndPoll(opts) {
|
|
|
28686
28665
|
console.error(error(e5 instanceof Error ? e5.message : "Submit failed"));
|
|
28687
28666
|
process.exit(1);
|
|
28688
28667
|
}
|
|
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
|
-
}
|
|
28706
|
-
setStatus("Generating…");
|
|
28707
|
-
startTicker();
|
|
28708
|
-
const maxMs = opts.mode === "VIDEO" ? 600000 : 300000;
|
|
28709
|
-
let final = null;
|
|
28710
|
-
while (Date.now() - started < maxMs) {
|
|
28711
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
28712
|
-
try {
|
|
28713
|
-
const p = await amikoWebFetch(auth, `/api/create/jobs/${encodeURIComponent(jobId)}`, { timeoutMs: 15000 });
|
|
28714
|
-
if (p.status === "COMPLETED" || p.status === "FAILED") {
|
|
28715
|
-
final = p;
|
|
28716
|
-
break;
|
|
28717
|
-
}
|
|
28718
|
-
} catch (e5) {
|
|
28719
|
-
const s = statusOf(e5);
|
|
28720
|
-
if (s === 401 || s === 404) {
|
|
28721
|
-
stop();
|
|
28722
|
-
console.error(error(e5 instanceof Error ? e5.message : "Poll failed"));
|
|
28723
|
-
process.exit(1);
|
|
28724
|
-
}
|
|
28725
|
-
}
|
|
28726
|
-
}
|
|
28727
28668
|
stop();
|
|
28728
|
-
if (!final) {
|
|
28729
|
-
console.error(error("Timed out waiting for the job. It may still be generating — check your media space at https://platform.heyamiko.com/media before retrying."));
|
|
28730
|
-
console.log(dim(`Job id: ${jobId}`));
|
|
28731
|
-
process.exit(1);
|
|
28732
|
-
}
|
|
28733
28669
|
if (opts.raw) {
|
|
28734
|
-
console.log(JSON.stringify(
|
|
28670
|
+
console.log(JSON.stringify({
|
|
28671
|
+
jobId,
|
|
28672
|
+
status: "PENDING",
|
|
28673
|
+
check: `amiko create status ${jobId}`,
|
|
28674
|
+
eta: ETA[opts.mode]
|
|
28675
|
+
}, null, 2));
|
|
28735
28676
|
return;
|
|
28736
28677
|
}
|
|
28737
|
-
|
|
28738
|
-
|
|
28739
|
-
|
|
28740
|
-
}
|
|
28741
|
-
printCompleted(final, `${opts.mode[0]}${opts.mode.slice(1).toLowerCase()} created`);
|
|
28678
|
+
console.log(success(`Submitted — job ${jobId}`));
|
|
28679
|
+
console.log(label("Status", "PENDING — generating in the background"));
|
|
28680
|
+
console.log(label("Check result", `amiko create status ${jobId}`));
|
|
28681
|
+
console.log(dim(`Usually ready in ${ETA[opts.mode]} — charged only on success. (Also: amiko create media)`));
|
|
28742
28682
|
}
|
|
28743
28683
|
function registerCreateCommand(create2) {
|
|
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)").
|
|
28745
|
-
await
|
|
28684
|
+
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) => {
|
|
28685
|
+
await submitCreateJob({
|
|
28746
28686
|
mode: "IMAGE",
|
|
28747
28687
|
summary: `Create image: ${prompt.slice(0, 80)}${prompt.length > 80 ? "…" : ""}`,
|
|
28748
28688
|
raw: opts.raw,
|
|
28749
28689
|
yes: opts.yes,
|
|
28750
|
-
wait: opts.wait,
|
|
28751
28690
|
quoteBody: { mode: "IMAGE", model: opts.model, size: opts.aspect },
|
|
28752
28691
|
jobBody: {
|
|
28753
28692
|
mode: "IMAGE",
|
|
@@ -28758,18 +28697,17 @@ function registerCreateCommand(create2) {
|
|
|
28758
28697
|
}
|
|
28759
28698
|
});
|
|
28760
28699
|
});
|
|
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)").
|
|
28700
|
+
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) => {
|
|
28762
28701
|
const seconds = Number(opts.seconds);
|
|
28763
28702
|
if (seconds !== 6 && seconds !== 10) {
|
|
28764
28703
|
console.error(error("--seconds must be 6 or 10."));
|
|
28765
28704
|
process.exit(1);
|
|
28766
28705
|
}
|
|
28767
|
-
await
|
|
28706
|
+
await submitCreateJob({
|
|
28768
28707
|
mode: "VIDEO",
|
|
28769
28708
|
summary: `Create video: ${prompt.slice(0, 80)}${prompt.length > 80 ? "…" : ""}`,
|
|
28770
28709
|
raw: opts.raw,
|
|
28771
28710
|
yes: opts.yes,
|
|
28772
|
-
wait: opts.wait,
|
|
28773
28711
|
quoteBody: {
|
|
28774
28712
|
mode: "VIDEO",
|
|
28775
28713
|
model: opts.model,
|
|
@@ -28788,13 +28726,12 @@ function registerCreateCommand(create2) {
|
|
|
28788
28726
|
}
|
|
28789
28727
|
});
|
|
28790
28728
|
});
|
|
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)").
|
|
28792
|
-
await
|
|
28729
|
+
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) => {
|
|
28730
|
+
await submitCreateJob({
|
|
28793
28731
|
mode: "TTS",
|
|
28794
28732
|
summary: `Create speech: ${text.slice(0, 80)}${text.length > 80 ? "…" : ""}`,
|
|
28795
28733
|
raw: opts.raw,
|
|
28796
28734
|
yes: opts.yes,
|
|
28797
|
-
wait: opts.wait,
|
|
28798
28735
|
quoteBody: { mode: "TTS", model: opts.model, prompt: text },
|
|
28799
28736
|
jobBody: {
|
|
28800
28737
|
mode: "TTS",
|
|
@@ -28805,17 +28742,16 @@ function registerCreateCommand(create2) {
|
|
|
28805
28742
|
}
|
|
28806
28743
|
});
|
|
28807
28744
|
});
|
|
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)").
|
|
28745
|
+
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) => {
|
|
28809
28746
|
if (!prompt && !opts.lyrics) {
|
|
28810
28747
|
console.error(error("Provide a prompt or --lyrics."));
|
|
28811
28748
|
process.exit(1);
|
|
28812
28749
|
}
|
|
28813
|
-
await
|
|
28750
|
+
await submitCreateJob({
|
|
28814
28751
|
mode: "MUSIC",
|
|
28815
28752
|
summary: `Create music: ${(prompt ?? opts.lyrics ?? "").slice(0, 80)}`,
|
|
28816
28753
|
raw: opts.raw,
|
|
28817
28754
|
yes: opts.yes,
|
|
28818
|
-
wait: opts.wait,
|
|
28819
28755
|
quoteBody: { mode: "MUSIC", model: opts.model },
|
|
28820
28756
|
jobBody: {
|
|
28821
28757
|
mode: "MUSIC",
|
|
@@ -28828,13 +28764,12 @@ function registerCreateCommand(create2) {
|
|
|
28828
28764
|
}
|
|
28829
28765
|
});
|
|
28830
28766
|
});
|
|
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)").
|
|
28832
|
-
await
|
|
28767
|
+
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) => {
|
|
28768
|
+
await submitCreateJob({
|
|
28833
28769
|
mode: "SFX",
|
|
28834
28770
|
summary: `Create SFX: ${text.slice(0, 80)}${text.length > 80 ? "…" : ""}`,
|
|
28835
28771
|
raw: opts.raw,
|
|
28836
28772
|
yes: opts.yes,
|
|
28837
|
-
wait: opts.wait,
|
|
28838
28773
|
quoteBody: { mode: "SFX" },
|
|
28839
28774
|
jobBody: {
|
|
28840
28775
|
mode: "SFX",
|
|
@@ -29255,6 +29190,218 @@ function registerChatCommand(chat) {
|
|
|
29255
29190
|
});
|
|
29256
29191
|
}
|
|
29257
29192
|
|
|
29193
|
+
// src/commands/card.ts
|
|
29194
|
+
import { writeFile } from "node:fs/promises";
|
|
29195
|
+
var TEMPLATE_ID = "twins_take";
|
|
29196
|
+
var DEFAULT_FLAVOR = "love";
|
|
29197
|
+
function authOrExit2() {
|
|
29198
|
+
try {
|
|
29199
|
+
return requireAuth();
|
|
29200
|
+
} catch (e5) {
|
|
29201
|
+
console.error(error(e5 instanceof Error ? e5.message : "auth failed"));
|
|
29202
|
+
process.exit(1);
|
|
29203
|
+
}
|
|
29204
|
+
}
|
|
29205
|
+
function platformBase(auth) {
|
|
29206
|
+
return (auth.platformUrl ?? "https://platform.heyamiko.com").replace(/\/$/, "");
|
|
29207
|
+
}
|
|
29208
|
+
function cardErrorExit(e5) {
|
|
29209
|
+
const err = e5;
|
|
29210
|
+
let body = {};
|
|
29211
|
+
try {
|
|
29212
|
+
body = JSON.parse(err?.body ?? "{}");
|
|
29213
|
+
} catch {}
|
|
29214
|
+
const code = body.error ?? "";
|
|
29215
|
+
const map2 = {
|
|
29216
|
+
profile_not_ready: {
|
|
29217
|
+
msg: "Your Amiko profile isn't complete enough to generate a card yet — flesh out your profile first.",
|
|
29218
|
+
exit: 2
|
|
29219
|
+
},
|
|
29220
|
+
guardrails_failed: {
|
|
29221
|
+
msg: "Content guardrails didn't pass — try again, or a different --flavor.",
|
|
29222
|
+
exit: 2
|
|
29223
|
+
},
|
|
29224
|
+
rate_limited: {
|
|
29225
|
+
msg: `Card generation is rate-limited right now — try again later${body.retry_after_seconds ? ` (~${body.retry_after_seconds}s)` : ""}.`,
|
|
29226
|
+
exit: 2
|
|
29227
|
+
},
|
|
29228
|
+
unknown_flavor: {
|
|
29229
|
+
msg: "Unknown flavor. Run `amiko card templates` to see the valid flavors.",
|
|
29230
|
+
exit: 1
|
|
29231
|
+
},
|
|
29232
|
+
unknown_template: { msg: "Only the twins_take card can be generated.", exit: 1 },
|
|
29233
|
+
template_not_generatable: {
|
|
29234
|
+
msg: "Only the twins_take card can be generated.",
|
|
29235
|
+
exit: 1
|
|
29236
|
+
}
|
|
29237
|
+
};
|
|
29238
|
+
const hit = map2[code];
|
|
29239
|
+
if (hit) {
|
|
29240
|
+
console.error(error(hit.msg));
|
|
29241
|
+
process.exit(hit.exit);
|
|
29242
|
+
}
|
|
29243
|
+
console.error(error(e5 instanceof Error ? e5.message : "Card request failed"));
|
|
29244
|
+
process.exit(1);
|
|
29245
|
+
}
|
|
29246
|
+
function registerCardCommand(card) {
|
|
29247
|
+
card.command("templates").description("List the available card flavors (work / play / love)").option("--json", "Output raw JSON").action(async (opts) => {
|
|
29248
|
+
const auth = authOrExit2();
|
|
29249
|
+
let data;
|
|
29250
|
+
try {
|
|
29251
|
+
data = await amikoWebFetch(auth, "/api/twin-cards/templates", {
|
|
29252
|
+
timeoutMs: 15000
|
|
29253
|
+
});
|
|
29254
|
+
} catch (e5) {
|
|
29255
|
+
cardErrorExit(e5);
|
|
29256
|
+
}
|
|
29257
|
+
if (opts.json) {
|
|
29258
|
+
console.log(JSON.stringify(data, null, 2));
|
|
29259
|
+
return;
|
|
29260
|
+
}
|
|
29261
|
+
const flavors = data.templates ?? [];
|
|
29262
|
+
if (flavors.length === 0) {
|
|
29263
|
+
console.log(dim("No flavors available."));
|
|
29264
|
+
return;
|
|
29265
|
+
}
|
|
29266
|
+
console.log(heading(`Card flavors (${flavors.length})
|
|
29267
|
+
`));
|
|
29268
|
+
for (const f of flavors)
|
|
29269
|
+
console.log(label(f.key, f.name ?? ""));
|
|
29270
|
+
});
|
|
29271
|
+
card.command("list").description("Show your most recently generated card").option("--json", "Output raw JSON").action(async (opts) => {
|
|
29272
|
+
const auth = authOrExit2();
|
|
29273
|
+
let data;
|
|
29274
|
+
try {
|
|
29275
|
+
data = await amikoWebFetch(auth, "/api/twin-cards/me", {
|
|
29276
|
+
timeoutMs: 15000
|
|
29277
|
+
});
|
|
29278
|
+
} catch (e5) {
|
|
29279
|
+
cardErrorExit(e5);
|
|
29280
|
+
}
|
|
29281
|
+
if (opts.json) {
|
|
29282
|
+
console.log(JSON.stringify(data, null, 2));
|
|
29283
|
+
return;
|
|
29284
|
+
}
|
|
29285
|
+
if (!data.slug) {
|
|
29286
|
+
console.log(dim("No card yet — generate one with `amiko card create`."));
|
|
29287
|
+
return;
|
|
29288
|
+
}
|
|
29289
|
+
console.log(heading(`Your latest card
|
|
29290
|
+
`));
|
|
29291
|
+
console.log(label("Slug", data.slug));
|
|
29292
|
+
console.log(label("Flavors", Object.keys(data.cards ?? {}).join(", ") || "-"));
|
|
29293
|
+
if (data.generated_at)
|
|
29294
|
+
console.log(label("Generated", data.generated_at));
|
|
29295
|
+
console.log(label("Share", `${platformBase(auth)}/s/${data.slug}`));
|
|
29296
|
+
});
|
|
29297
|
+
card.command("create").description("Generate (or reroll) your Twin Card").option("--flavor <flavor>", "Flavor: work, play, love (DB-managed; see `card templates`)", DEFAULT_FLAVOR).option("--reroll <n>", "Reroll number (≥0) for a fresh take", "0").option("--json", "Output raw JSON").action(async (opts) => {
|
|
29298
|
+
const auth = authOrExit2();
|
|
29299
|
+
let data;
|
|
29300
|
+
try {
|
|
29301
|
+
data = await amikoWebFetch(auth, "/api/share/generate-card", {
|
|
29302
|
+
method: "POST",
|
|
29303
|
+
body: {
|
|
29304
|
+
template_id: TEMPLATE_ID,
|
|
29305
|
+
flavor: opts.flavor,
|
|
29306
|
+
reroll_number: Math.max(0, Math.floor(Number(opts.reroll) || 0))
|
|
29307
|
+
},
|
|
29308
|
+
timeoutMs: 120000
|
|
29309
|
+
});
|
|
29310
|
+
} catch (e5) {
|
|
29311
|
+
cardErrorExit(e5);
|
|
29312
|
+
}
|
|
29313
|
+
if (opts.json) {
|
|
29314
|
+
console.log(JSON.stringify(data, null, 2));
|
|
29315
|
+
return;
|
|
29316
|
+
}
|
|
29317
|
+
console.log(heading(`${opts.flavor} card ready
|
|
29318
|
+
`));
|
|
29319
|
+
if (data.slug) {
|
|
29320
|
+
console.log(label("Slug", data.slug));
|
|
29321
|
+
console.log(label("Share", `${platformBase(auth)}/s/${data.slug}`));
|
|
29322
|
+
console.log(dim(`Image: amiko card image ${data.slug} --flavor ${opts.flavor}`));
|
|
29323
|
+
}
|
|
29324
|
+
});
|
|
29325
|
+
card.command("show <slug>").description("Read a card's stored data").option("--json", "Output raw JSON").action(async (slug, opts) => {
|
|
29326
|
+
const auth = authOrExit2();
|
|
29327
|
+
let data;
|
|
29328
|
+
try {
|
|
29329
|
+
data = await amikoWebFetch(auth, `/api/share/${encodeURIComponent(slug)}`, { timeoutMs: 15000 });
|
|
29330
|
+
} catch (e5) {
|
|
29331
|
+
cardErrorExit(e5);
|
|
29332
|
+
}
|
|
29333
|
+
console.log(JSON.stringify(data, null, 2));
|
|
29334
|
+
});
|
|
29335
|
+
card.command("image <slug>").description("Download the card image (PNG)").option("--flavor <flavor>", "Flavor: work, play, love", DEFAULT_FLAVOR).option("--out <path>", "Output file (default: card-<slug>-<flavor>.png)").action(async (slug, opts) => {
|
|
29336
|
+
const auth = authOrExit2();
|
|
29337
|
+
const url2 = `${platformBase(auth)}/api/share/${encodeURIComponent(slug)}/image?flavor=${encodeURIComponent(opts.flavor)}`;
|
|
29338
|
+
let buf;
|
|
29339
|
+
try {
|
|
29340
|
+
const res = await fetch(url2, {
|
|
29341
|
+
headers: { Authorization: `Bearer ${auth.twinToken ?? ""}` },
|
|
29342
|
+
signal: AbortSignal.timeout(60000)
|
|
29343
|
+
});
|
|
29344
|
+
if (!res.ok) {
|
|
29345
|
+
console.error(error(`Failed to fetch card image: ${res.status} ${res.statusText}`));
|
|
29346
|
+
process.exit(1);
|
|
29347
|
+
}
|
|
29348
|
+
buf = Buffer.from(await res.arrayBuffer());
|
|
29349
|
+
} catch (e5) {
|
|
29350
|
+
console.error(error(e5 instanceof Error ? e5.message : "Image download failed"));
|
|
29351
|
+
process.exit(1);
|
|
29352
|
+
}
|
|
29353
|
+
const out = opts.out ?? `card-${slug}-${opts.flavor}.png`;
|
|
29354
|
+
await writeFile(out, buf);
|
|
29355
|
+
console.log(success(`Saved ${buf.length.toLocaleString()} bytes → ${out}`));
|
|
29356
|
+
});
|
|
29357
|
+
card.command("mint <slug>").description("Mint a card as an NFT (or --status to check an existing mint)").option("--flavor <flavor>", "Flavor: work, play, love", DEFAULT_FLAVOR).option("--name <name>", "NFT name (optional)").option("--description <desc>", "NFT description (optional)").option("--status", "Check mint status instead of minting").option("--yes", "Skip the confirmation (required in non-interactive shells)").option("--json", "Output raw JSON").action(async (slug, opts) => {
|
|
29358
|
+
const auth = authOrExit2();
|
|
29359
|
+
if (opts.status) {
|
|
29360
|
+
let data2;
|
|
29361
|
+
try {
|
|
29362
|
+
data2 = await amikoWebFetch(auth, `/api/share/twin-cards/mint?slug=${encodeURIComponent(slug)}`, { timeoutMs: 15000 });
|
|
29363
|
+
} catch (e5) {
|
|
29364
|
+
cardErrorExit(e5);
|
|
29365
|
+
}
|
|
29366
|
+
console.log(JSON.stringify(data2, null, 2));
|
|
29367
|
+
return;
|
|
29368
|
+
}
|
|
29369
|
+
await requireApproval({
|
|
29370
|
+
cost: "mints an on-chain NFT (cNFT) from your twin card",
|
|
29371
|
+
summary: `Mint ${opts.flavor} card ${slug} as an NFT`,
|
|
29372
|
+
yes: opts.yes,
|
|
29373
|
+
commandExample: `amiko card mint ${slug} --flavor ${opts.flavor}`
|
|
29374
|
+
});
|
|
29375
|
+
let data;
|
|
29376
|
+
try {
|
|
29377
|
+
data = await amikoWebFetch(auth, "/api/share/twin-cards/mint", {
|
|
29378
|
+
method: "POST",
|
|
29379
|
+
body: {
|
|
29380
|
+
slug,
|
|
29381
|
+
flavor: opts.flavor,
|
|
29382
|
+
...opts.name ? { name: opts.name } : {},
|
|
29383
|
+
...opts.description ? { description: opts.description } : {}
|
|
29384
|
+
},
|
|
29385
|
+
timeoutMs: 120000
|
|
29386
|
+
});
|
|
29387
|
+
} catch (e5) {
|
|
29388
|
+
cardErrorExit(e5);
|
|
29389
|
+
}
|
|
29390
|
+
if (opts.json) {
|
|
29391
|
+
console.log(JSON.stringify(data, null, 2));
|
|
29392
|
+
return;
|
|
29393
|
+
}
|
|
29394
|
+
console.log(success(`Mint ${data.status ?? "submitted"}`));
|
|
29395
|
+
if (data.mint_address)
|
|
29396
|
+
console.log(label("Mint", data.mint_address));
|
|
29397
|
+
console.log(dim(`Check status: amiko card mint ${slug} --status`));
|
|
29398
|
+
});
|
|
29399
|
+
card.command("share <slug>").description("Print the public share link for a card").action(async (slug) => {
|
|
29400
|
+
const auth = authOrExit2();
|
|
29401
|
+
console.log(`${platformBase(auth)}/s/${slug}`);
|
|
29402
|
+
});
|
|
29403
|
+
}
|
|
29404
|
+
|
|
29258
29405
|
// src/commands/amazon.ts
|
|
29259
29406
|
var TREASURY3 = "FPiZAf3jEnNfCwfvbv46jdPfHbmSepD4Q6iHiLJDQXvb";
|
|
29260
29407
|
async function payAmiko(auth, wallet, amount2) {
|
|
@@ -29803,7 +29950,7 @@ function registerTwinCommand(program2) {
|
|
|
29803
29950
|
}
|
|
29804
29951
|
|
|
29805
29952
|
// src/commands/drive.ts
|
|
29806
|
-
import { writeFile } from "node:fs/promises";
|
|
29953
|
+
import { writeFile as writeFile2 } from "node:fs/promises";
|
|
29807
29954
|
import { basename as basename2, resolve as resolvePath } from "node:path";
|
|
29808
29955
|
function fmtBytes(n) {
|
|
29809
29956
|
if (!n || n <= 0)
|
|
@@ -30016,7 +30163,7 @@ Search hits filename, title, description, and parsed RAG content (once processed
|
|
|
30016
30163
|
return;
|
|
30017
30164
|
}
|
|
30018
30165
|
const outPath = resolvePath(opts.out ?? basename2(meta3.doc.filename));
|
|
30019
|
-
await
|
|
30166
|
+
await writeFile2(outPath, buf);
|
|
30020
30167
|
spinner?.stop();
|
|
30021
30168
|
console.log(success(`Saved ${meta3.doc.filename} → ${outPath}`));
|
|
30022
30169
|
console.log(label("Size", fmtBytes(buf.length)));
|
|
@@ -32039,6 +32186,7 @@ program2.name("amiko").description("Amiko CLI — swap tokens, manage credits, b
|
|
|
32039
32186
|
var markets = program2.command("markets").description("MPP marketplace — discover and call paid AMIKO services");
|
|
32040
32187
|
var create2 = program2.command("create").description("Create Studio — generate images, video, speech, music, and SFX (charged on success)");
|
|
32041
32188
|
var chat = program2.command("chat").description("Your conversations (DMs + group chats) as the owner — list, read, send");
|
|
32189
|
+
var card = program2.command("card").description("Twin Cards (work/play/love) — generate, view, download, mint");
|
|
32042
32190
|
var wallets = program2.command("wallets").description("Manage twin wallets — list, swap, bridge");
|
|
32043
32191
|
var twin = program2.command("twin").description("Update twin identity (name, description, visibility)");
|
|
32044
32192
|
var drive = program2.command("drive").alias("docs").description("Manage twin drive — upload/download/search files, organise into folders");
|
|
@@ -32054,6 +32202,7 @@ registerSearchCommand(markets);
|
|
|
32054
32202
|
registerImageCommand(markets);
|
|
32055
32203
|
registerCreateCommand(create2);
|
|
32056
32204
|
registerChatCommand(chat);
|
|
32205
|
+
registerCardCommand(card);
|
|
32057
32206
|
registerAmazonCommand(markets);
|
|
32058
32207
|
registerWalletsCreateCommand(wallets);
|
|
32059
32208
|
registerWalletsListCommand(wallets);
|
package/package.json
CHANGED
package/skills/SKILL.md
CHANGED
|
@@ -41,13 +41,13 @@ The CLI is installed globally and is pre-authenticated when you're inside your w
|
|
|
41
41
|
|
|
42
42
|
## Command groups
|
|
43
43
|
|
|
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`.
|
|
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), `card` (Twin Cards — work/play/love), `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`.
|
|
45
45
|
|
|
46
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.)
|
|
47
47
|
|
|
48
48
|
## Critical Rules
|
|
49
49
|
|
|
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`.**
|
|
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`, `card mint`, `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`.**
|
|
51
51
|
2. **After every paid command, report the remaining balance.** The CLI prints a `Balance: N credits` line — include that figure in your reply.
|
|
52
52
|
3. **Never retry a failed command.** Report and stop. Every paid call costs tokens even on failure.
|
|
53
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`.
|
|
@@ -94,7 +94,7 @@ All paid markets commands auto-select the twin's active Solana wallet (see `--wa
|
|
|
94
94
|
|
|
95
95
|
## Create Studio — behavior notes
|
|
96
96
|
|
|
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. **
|
|
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. **Do NOT sit in a tight polling loop** (it burns turns/tokens). After submitting, **tell the user roughly how long to wait and stop** — image ~15–60s, video ~2–9min, music ~30–120s, tts/sfx ~5–20s (e.g. "your video's generating — check back in a few minutes"). Then retrieve it **once** later — when the user next asks, or after the ETA — 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. **Do NOT use `--wait`** — there's no such option; `create` is always non-blocking, so submit then check later with `amiko create status <jobId>`. `--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
98
|
|
|
99
99
|
## Chat — behavior notes
|
|
100
100
|
|
|
@@ -106,6 +106,15 @@ All paid markets commands auto-select the twin's active Solana wallet (see `--wa
|
|
|
106
106
|
- **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.
|
|
107
107
|
- 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.
|
|
108
108
|
|
|
109
|
+
## Card — behavior notes
|
|
110
|
+
|
|
111
|
+
`amiko card` = the owner's **Twin Card** (shareable card, template `twins_take`, flavors **work / play / love** — DB-managed, discover with `card templates`).
|
|
112
|
+
|
|
113
|
+
- `card create --flavor <work|play|love> [--reroll <n>]` — generate/reroll; returns a slug + share link (`/s/<slug>`). Flavor defaults to `love`. Errors are user-fixable and exit **non-zero code 2** (don't treat as a crash): `profile_not_ready` (complete the profile first), `guardrails_failed` (retry / other flavor), `rate_limited` (reroll cooldown — try later).
|
|
114
|
+
- `card list` — your latest card; `card show <slug>` — its data; `card image <slug> --flavor <f> [--out <path>]` — download the PNG; `card share <slug>` — print the public link.
|
|
115
|
+
- `card mint <slug> --flavor <f>` — mint as an on-chain NFT → **`--yes` gated**. `card mint <slug> --status` checks an existing mint.
|
|
116
|
+
- All `--json`. Auth is the injected twin token (same as `post`/`create`); no login.
|
|
117
|
+
|
|
109
118
|
## Drive (files & folders)
|
|
110
119
|
|
|
111
120
|
The drive is **shared between the user and the agent** — anything either side uploads is visible to both. Uploads kick off an async RAG-parsing job (~seconds to minutes); list/search/rename/move/delete all work regardless of parsing status. `drive search` and `drive list --search` hit filename, title, description, and the parsed content body (once parsing completes). Prefer reading parsed content via the agent workspace sync over polling `drive status` from long-running tasks. `amiko docs` is kept as an alias for backward compat.
|