@heyamiko/amiko-cli 0.12.0-beta.5 → 0.12.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @heyamiko/amiko-cli (v0.12.0-beta.5)
1
+ # @heyamiko/amiko-cli (v0.12.0-beta.6)
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
 
@@ -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
@@ -29255,6 +29255,218 @@ function registerChatCommand(chat) {
29255
29255
  });
29256
29256
  }
29257
29257
 
29258
+ // src/commands/card.ts
29259
+ import { writeFile } from "node:fs/promises";
29260
+ var TEMPLATE_ID = "twins_take";
29261
+ var DEFAULT_FLAVOR = "love";
29262
+ function authOrExit2() {
29263
+ try {
29264
+ return requireAuth();
29265
+ } catch (e5) {
29266
+ console.error(error(e5 instanceof Error ? e5.message : "auth failed"));
29267
+ process.exit(1);
29268
+ }
29269
+ }
29270
+ function platformBase(auth) {
29271
+ return (auth.platformUrl ?? "https://platform.heyamiko.com").replace(/\/$/, "");
29272
+ }
29273
+ function cardErrorExit(e5) {
29274
+ const err = e5;
29275
+ let body = {};
29276
+ try {
29277
+ body = JSON.parse(err?.body ?? "{}");
29278
+ } catch {}
29279
+ const code = body.error ?? "";
29280
+ const map2 = {
29281
+ profile_not_ready: {
29282
+ msg: "Your Amiko profile isn't complete enough to generate a card yet — flesh out your profile first.",
29283
+ exit: 2
29284
+ },
29285
+ guardrails_failed: {
29286
+ msg: "Content guardrails didn't pass — try again, or a different --flavor.",
29287
+ exit: 2
29288
+ },
29289
+ rate_limited: {
29290
+ msg: `Card generation is rate-limited right now — try again later${body.retry_after_seconds ? ` (~${body.retry_after_seconds}s)` : ""}.`,
29291
+ exit: 2
29292
+ },
29293
+ unknown_flavor: {
29294
+ msg: "Unknown flavor. Run `amiko card templates` to see the valid flavors.",
29295
+ exit: 1
29296
+ },
29297
+ unknown_template: { msg: "Only the twins_take card can be generated.", exit: 1 },
29298
+ template_not_generatable: {
29299
+ msg: "Only the twins_take card can be generated.",
29300
+ exit: 1
29301
+ }
29302
+ };
29303
+ const hit = map2[code];
29304
+ if (hit) {
29305
+ console.error(error(hit.msg));
29306
+ process.exit(hit.exit);
29307
+ }
29308
+ console.error(error(e5 instanceof Error ? e5.message : "Card request failed"));
29309
+ process.exit(1);
29310
+ }
29311
+ function registerCardCommand(card) {
29312
+ card.command("templates").description("List the available card flavors (work / play / love)").option("--json", "Output raw JSON").action(async (opts) => {
29313
+ const auth = authOrExit2();
29314
+ let data;
29315
+ try {
29316
+ data = await amikoWebFetch(auth, "/api/twin-cards/templates", {
29317
+ timeoutMs: 15000
29318
+ });
29319
+ } catch (e5) {
29320
+ cardErrorExit(e5);
29321
+ }
29322
+ if (opts.json) {
29323
+ console.log(JSON.stringify(data, null, 2));
29324
+ return;
29325
+ }
29326
+ const flavors = data.templates ?? [];
29327
+ if (flavors.length === 0) {
29328
+ console.log(dim("No flavors available."));
29329
+ return;
29330
+ }
29331
+ console.log(heading(`Card flavors (${flavors.length})
29332
+ `));
29333
+ for (const f of flavors)
29334
+ console.log(label(f.key, f.name ?? ""));
29335
+ });
29336
+ card.command("list").description("Show your most recently generated card").option("--json", "Output raw JSON").action(async (opts) => {
29337
+ const auth = authOrExit2();
29338
+ let data;
29339
+ try {
29340
+ data = await amikoWebFetch(auth, "/api/twin-cards/me", {
29341
+ timeoutMs: 15000
29342
+ });
29343
+ } catch (e5) {
29344
+ cardErrorExit(e5);
29345
+ }
29346
+ if (opts.json) {
29347
+ console.log(JSON.stringify(data, null, 2));
29348
+ return;
29349
+ }
29350
+ if (!data.slug) {
29351
+ console.log(dim("No card yet — generate one with `amiko card create`."));
29352
+ return;
29353
+ }
29354
+ console.log(heading(`Your latest card
29355
+ `));
29356
+ console.log(label("Slug", data.slug));
29357
+ console.log(label("Flavors", Object.keys(data.cards ?? {}).join(", ") || "-"));
29358
+ if (data.generated_at)
29359
+ console.log(label("Generated", data.generated_at));
29360
+ console.log(label("Share", `${platformBase(auth)}/s/${data.slug}`));
29361
+ });
29362
+ 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) => {
29363
+ const auth = authOrExit2();
29364
+ let data;
29365
+ try {
29366
+ data = await amikoWebFetch(auth, "/api/share/generate-card", {
29367
+ method: "POST",
29368
+ body: {
29369
+ template_id: TEMPLATE_ID,
29370
+ flavor: opts.flavor,
29371
+ reroll_number: Math.max(0, Math.floor(Number(opts.reroll) || 0))
29372
+ },
29373
+ timeoutMs: 120000
29374
+ });
29375
+ } catch (e5) {
29376
+ cardErrorExit(e5);
29377
+ }
29378
+ if (opts.json) {
29379
+ console.log(JSON.stringify(data, null, 2));
29380
+ return;
29381
+ }
29382
+ console.log(heading(`${opts.flavor} card ready
29383
+ `));
29384
+ if (data.slug) {
29385
+ console.log(label("Slug", data.slug));
29386
+ console.log(label("Share", `${platformBase(auth)}/s/${data.slug}`));
29387
+ console.log(dim(`Image: amiko card image ${data.slug} --flavor ${opts.flavor}`));
29388
+ }
29389
+ });
29390
+ card.command("show <slug>").description("Read a card's stored data").option("--json", "Output raw JSON").action(async (slug, opts) => {
29391
+ const auth = authOrExit2();
29392
+ let data;
29393
+ try {
29394
+ data = await amikoWebFetch(auth, `/api/share/${encodeURIComponent(slug)}`, { timeoutMs: 15000 });
29395
+ } catch (e5) {
29396
+ cardErrorExit(e5);
29397
+ }
29398
+ console.log(JSON.stringify(data, null, 2));
29399
+ });
29400
+ 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) => {
29401
+ const auth = authOrExit2();
29402
+ const url2 = `${platformBase(auth)}/api/share/${encodeURIComponent(slug)}/image?flavor=${encodeURIComponent(opts.flavor)}`;
29403
+ let buf;
29404
+ try {
29405
+ const res = await fetch(url2, {
29406
+ headers: { Authorization: `Bearer ${auth.twinToken ?? ""}` },
29407
+ signal: AbortSignal.timeout(60000)
29408
+ });
29409
+ if (!res.ok) {
29410
+ console.error(error(`Failed to fetch card image: ${res.status} ${res.statusText}`));
29411
+ process.exit(1);
29412
+ }
29413
+ buf = Buffer.from(await res.arrayBuffer());
29414
+ } catch (e5) {
29415
+ console.error(error(e5 instanceof Error ? e5.message : "Image download failed"));
29416
+ process.exit(1);
29417
+ }
29418
+ const out = opts.out ?? `card-${slug}-${opts.flavor}.png`;
29419
+ await writeFile(out, buf);
29420
+ console.log(success(`Saved ${buf.length.toLocaleString()} bytes → ${out}`));
29421
+ });
29422
+ 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) => {
29423
+ const auth = authOrExit2();
29424
+ if (opts.status) {
29425
+ let data2;
29426
+ try {
29427
+ data2 = await amikoWebFetch(auth, `/api/share/twin-cards/mint?slug=${encodeURIComponent(slug)}`, { timeoutMs: 15000 });
29428
+ } catch (e5) {
29429
+ cardErrorExit(e5);
29430
+ }
29431
+ console.log(JSON.stringify(data2, null, 2));
29432
+ return;
29433
+ }
29434
+ await requireApproval({
29435
+ cost: "mints an on-chain NFT (cNFT) from your twin card",
29436
+ summary: `Mint ${opts.flavor} card ${slug} as an NFT`,
29437
+ yes: opts.yes,
29438
+ commandExample: `amiko card mint ${slug} --flavor ${opts.flavor}`
29439
+ });
29440
+ let data;
29441
+ try {
29442
+ data = await amikoWebFetch(auth, "/api/share/twin-cards/mint", {
29443
+ method: "POST",
29444
+ body: {
29445
+ slug,
29446
+ flavor: opts.flavor,
29447
+ ...opts.name ? { name: opts.name } : {},
29448
+ ...opts.description ? { description: opts.description } : {}
29449
+ },
29450
+ timeoutMs: 120000
29451
+ });
29452
+ } catch (e5) {
29453
+ cardErrorExit(e5);
29454
+ }
29455
+ if (opts.json) {
29456
+ console.log(JSON.stringify(data, null, 2));
29457
+ return;
29458
+ }
29459
+ console.log(success(`Mint ${data.status ?? "submitted"}`));
29460
+ if (data.mint_address)
29461
+ console.log(label("Mint", data.mint_address));
29462
+ console.log(dim(`Check status: amiko card mint ${slug} --status`));
29463
+ });
29464
+ card.command("share <slug>").description("Print the public share link for a card").action(async (slug) => {
29465
+ const auth = authOrExit2();
29466
+ console.log(`${platformBase(auth)}/s/${slug}`);
29467
+ });
29468
+ }
29469
+
29258
29470
  // src/commands/amazon.ts
29259
29471
  var TREASURY3 = "FPiZAf3jEnNfCwfvbv46jdPfHbmSepD4Q6iHiLJDQXvb";
29260
29472
  async function payAmiko(auth, wallet, amount2) {
@@ -29803,7 +30015,7 @@ function registerTwinCommand(program2) {
29803
30015
  }
29804
30016
 
29805
30017
  // src/commands/drive.ts
29806
- import { writeFile } from "node:fs/promises";
30018
+ import { writeFile as writeFile2 } from "node:fs/promises";
29807
30019
  import { basename as basename2, resolve as resolvePath } from "node:path";
29808
30020
  function fmtBytes(n) {
29809
30021
  if (!n || n <= 0)
@@ -30016,7 +30228,7 @@ Search hits filename, title, description, and parsed RAG content (once processed
30016
30228
  return;
30017
30229
  }
30018
30230
  const outPath = resolvePath(opts.out ?? basename2(meta3.doc.filename));
30019
- await writeFile(outPath, buf);
30231
+ await writeFile2(outPath, buf);
30020
30232
  spinner?.stop();
30021
30233
  console.log(success(`Saved ${meta3.doc.filename} → ${outPath}`));
30022
30234
  console.log(label("Size", fmtBytes(buf.length)));
@@ -32039,6 +32251,7 @@ program2.name("amiko").description("Amiko CLI — swap tokens, manage credits, b
32039
32251
  var markets = program2.command("markets").description("MPP marketplace — discover and call paid AMIKO services");
32040
32252
  var create2 = program2.command("create").description("Create Studio — generate images, video, speech, music, and SFX (charged on success)");
32041
32253
  var chat = program2.command("chat").description("Your conversations (DMs + group chats) as the owner — list, read, send");
32254
+ var card = program2.command("card").description("Twin Cards (work/play/love) — generate, view, download, mint");
32042
32255
  var wallets = program2.command("wallets").description("Manage twin wallets — list, swap, bridge");
32043
32256
  var twin = program2.command("twin").description("Update twin identity (name, description, visibility)");
32044
32257
  var drive = program2.command("drive").alias("docs").description("Manage twin drive — upload/download/search files, organise into folders");
@@ -32054,6 +32267,7 @@ registerSearchCommand(markets);
32054
32267
  registerImageCommand(markets);
32055
32268
  registerCreateCommand(create2);
32056
32269
  registerChatCommand(chat);
32270
+ registerCardCommand(card);
32057
32271
  registerAmazonCommand(markets);
32058
32272
  registerWalletsCreateCommand(wallets);
32059
32273
  registerWalletsListCommand(wallets);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.12.0-beta.5",
3
+ "version": "0.12.0-beta.6",
4
4
  "description": "Amiko CLI — swap tokens, manage credits, bridge cross-chain, and call marketplace agents",
5
5
  "type": "module",
6
6
  "bin": {
package/skills/SKILL.md CHANGED
@@ -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`.
@@ -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.