@heyamiko/amiko-cli 0.10.1-beta.0 → 0.10.1-beta.3
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 +16 -1
- package/dist/index.js +332 -49
- package/package.json +1 -1
- package/skills/SKILL.md +108 -354
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @heyamiko/amiko-cli (v0.10.1-beta.
|
|
1
|
+
# @heyamiko/amiko-cli (v0.10.1-beta.3)
|
|
2
2
|
|
|
3
3
|
Manage wallets, credits, swaps, MPP marketplace services, and your Amiko twin (identity, documents, voice, avatar, friends, feed, Composio apps) from the terminal. Works for both human users and AI agents running on OpenClaw.
|
|
4
4
|
|
|
@@ -393,6 +393,21 @@ npm publish
|
|
|
393
393
|
|
|
394
394
|
## Changelog
|
|
395
395
|
|
|
396
|
+
### 0.10.1-beta.3
|
|
397
|
+
|
|
398
|
+
- **`amiko drive` namespace** replaces `amiko docs` (kept as a `.alias("docs")` so existing scripts and agents keep working). Tracks the [Amiko Drive API](https://github.com/HCF-STUDIOS/amiko-platform/blob/main/amiko-web/docs/api/drive.md): files live under twin-scoped folders, every upload kicks off async RAG parsing, and search hits parsed content once processing completes. Commands:
|
|
399
|
+
- **Files** — `drive list [--folder <id|root>] [--search <q>] [--limit] [--offset]`, `drive search <query> [--folder]`, `drive upload <source> [--folder] [--title] [--description] [--doc-type]`, `drive download <docId> [--out <path>|--stdout]` (NEW — fetches a 1h signed URL from `GET /docs/[docId]` and writes the bytes), `drive get <docId> [--content]` (NEW — metadata + signed URL, optional parsed content body), `drive rename <docId> --title <new>` (NEW — `PATCH /docs/[docId]`), `drive move <docId> --folder <id|root>` (NEW), `drive delete <docId> --yes`, `drive presign <name>`, `drive status <docId...>` (NEW — `POST /docs/check-processing` for RAG job state).
|
|
400
|
+
- **Folders** — `drive folder list`, `drive folder create <name> [--parent <id>]`, `drive folder rename <id> --name <new>`, `drive folder move <id> --parent <id|root>`, `drive folder delete <id> [--force] --yes` (force=1 walks the subtree).
|
|
401
|
+
- SKILL.md picks up a one-paragraph Drive section + three new rows in the Examples table (upload / download / search intents → exact `amiko drive` commands).
|
|
402
|
+
|
|
403
|
+
### 0.10.1-beta.2
|
|
404
|
+
|
|
405
|
+
- **SKILL.md slimmed ~55%** (31.6 KB / 429 lines → 14.3 KB / 169 lines; ~8.5k → ~3.5k tokens). The skill now leads with "this is the mental model, not a syntax reference — run `amiko <group> --help` for exact flags" and drops per-namespace bash blocks that duplicated `--help` output (credits/wallets/markets/account/docs/voice/avatar/friends/users/feed/posts/conversations/notifications/memory/composio/JSON). Kept everything the CLI's own help *can't* tell an agent: shell-tool invocation rule + "no `amiko_cli` tool exists" failure modes, the `--yes` / report-balance / no-retry critical rules, `--wallet` default behavior, the credits intent-mapping cheatsheet (1 SOL → `--spend`, $5 → `--usd`, 10000 → positional), wallets/markets/conversations behavior notes that aren't in help, the "relationship report" and "who should I meet" playbooks (`friends find` vs `users search` vs `friends matches`), feed/post draft-review workflow + "reading via CLI counts as reading", DM-vs-built-in-sessions routing, and the full memory `search-before-you-answer` doctrine. Pricing table replaced with a one-paragraph "run `markets service list` for live prices" pointer plus rough order-of-magnitude. Result: skill stays well within recommended SKILL.md size budget while preserving every judgment / mental-model line that agents can't learn from `--help`.
|
|
406
|
+
|
|
407
|
+
### 0.10.1-beta.1
|
|
408
|
+
|
|
409
|
+
- **SKILL.md** documents the two new feed/post surfaces so agents actually find them: top-of-file Examples table gets rows for "any new posts I haven't seen?" → `amiko feed --unread` and "what comments are on my post?" → `amiko post comments --id <postId>`; top-level command index updated; the Feed & Posts section gains a callout that *reading via the CLI counts as reading the post* (every `amiko feed` / `amiko post comments` call auto-marks for this twin, so successive `feed --unread` calls drain without bookkeeping; user-side reads are written by the web client only). No CLI behavior change.
|
|
410
|
+
|
|
396
411
|
### 0.10.1-beta.0
|
|
397
412
|
|
|
398
413
|
- **`amiko post comments --id <postId>`** (new): lists comments on a post via `GET /api/posts/[id]/comments`. Supports `--limit`, `--cursor`, `--replies` (include nested replies; default top-level only), `--json`. Closes a gap where the CLI could write a comment but couldn't read existing ones — agents previously only saw a `_count.comments` integer from `amiko feed`.
|
package/dist/index.js
CHANGED
|
@@ -29139,6 +29139,10 @@ function registerTwinCommand(program2) {
|
|
|
29139
29139
|
});
|
|
29140
29140
|
}
|
|
29141
29141
|
|
|
29142
|
+
// src/commands/drive.ts
|
|
29143
|
+
import { writeFile } from "node:fs/promises";
|
|
29144
|
+
import { basename as basename2, resolve as resolvePath } from "node:path";
|
|
29145
|
+
|
|
29142
29146
|
// src/lib/file-input.ts
|
|
29143
29147
|
import { readFile } from "node:fs/promises";
|
|
29144
29148
|
import { basename, extname } from "node:path";
|
|
@@ -29236,7 +29240,7 @@ function detectMime(buffer, name) {
|
|
|
29236
29240
|
return EXT_MIME[ext] ?? "application/octet-stream";
|
|
29237
29241
|
}
|
|
29238
29242
|
|
|
29239
|
-
// src/commands/
|
|
29243
|
+
// src/commands/drive.ts
|
|
29240
29244
|
function fmtBytes(n) {
|
|
29241
29245
|
if (!n || n <= 0)
|
|
29242
29246
|
return "-";
|
|
@@ -29249,6 +29253,16 @@ function fmtBytes(n) {
|
|
|
29249
29253
|
}
|
|
29250
29254
|
return `${v.toFixed(v >= 10 ? 0 : 1)} ${units[i]}`;
|
|
29251
29255
|
}
|
|
29256
|
+
function statusOf(doc2) {
|
|
29257
|
+
const meta3 = doc2.metadata?.status;
|
|
29258
|
+
if (meta3)
|
|
29259
|
+
return meta3;
|
|
29260
|
+
if (doc2.is_processed)
|
|
29261
|
+
return "processed";
|
|
29262
|
+
if (doc2.is_parsed)
|
|
29263
|
+
return "parsed";
|
|
29264
|
+
return "pending";
|
|
29265
|
+
}
|
|
29252
29266
|
function twinFromFlag(opts) {
|
|
29253
29267
|
const config2 = loadConfig();
|
|
29254
29268
|
const auth = resolveAuth();
|
|
@@ -29262,36 +29276,81 @@ function twinFromFlag(opts) {
|
|
|
29262
29276
|
throw e5;
|
|
29263
29277
|
}
|
|
29264
29278
|
}
|
|
29265
|
-
function
|
|
29266
|
-
|
|
29279
|
+
function renderDocTable(docs) {
|
|
29280
|
+
const rows = [
|
|
29281
|
+
[
|
|
29282
|
+
dim("ID"),
|
|
29283
|
+
dim("NAME"),
|
|
29284
|
+
dim("TYPE"),
|
|
29285
|
+
dim("FOLDER"),
|
|
29286
|
+
dim("STATUS"),
|
|
29287
|
+
dim("CREATED")
|
|
29288
|
+
],
|
|
29289
|
+
...docs.map((doc2) => [
|
|
29290
|
+
doc2.id,
|
|
29291
|
+
doc2.title || doc2.filename,
|
|
29292
|
+
doc2.file_type || "-",
|
|
29293
|
+
doc2.folder_id || dim("root"),
|
|
29294
|
+
statusOf(doc2),
|
|
29295
|
+
doc2.created_at?.slice(0, 10) || "-"
|
|
29296
|
+
])
|
|
29297
|
+
];
|
|
29298
|
+
console.log(table(rows));
|
|
29299
|
+
}
|
|
29300
|
+
async function fetchSignedUrl(auth, twinId, docId) {
|
|
29301
|
+
return amikoWebFetch(auth, `/api/agents/${twinId}/docs/${docId}`);
|
|
29302
|
+
}
|
|
29303
|
+
function registerDriveCommand(program2) {
|
|
29304
|
+
program2.command("list").description("List drive files (optionally filter by folder or search)").option("--twin <idOrName>", "Target twin id or name").option("--folder <id|root>", "Filter by folder id, or 'root' for drive root").option("--search <q>", "Case-insensitive search over filename/title/description/content").option("--limit <n>", "Max results per page", "50").option("--offset <n>", "Skip N rows", "0").option("--json", "Output as JSON").action(async (opts) => {
|
|
29267
29305
|
const { twinId, auth } = twinFromFlag(opts);
|
|
29268
|
-
const spinner = opts.json ? null : ora("Loading
|
|
29306
|
+
const spinner = opts.json ? null : ora("Loading files...").start();
|
|
29269
29307
|
try {
|
|
29270
|
-
const
|
|
29308
|
+
const query = {
|
|
29309
|
+
limit: opts.limit ?? "50",
|
|
29310
|
+
offset: opts.offset ?? "0"
|
|
29311
|
+
};
|
|
29312
|
+
if (opts.search)
|
|
29313
|
+
query.search = opts.search;
|
|
29314
|
+
if (opts.folder !== undefined)
|
|
29315
|
+
query.folderId = opts.folder;
|
|
29316
|
+
const data = await amikoWebFetch(auth, `/api/agents/${twinId}/docs`, { query });
|
|
29271
29317
|
spinner?.stop();
|
|
29272
29318
|
renderOutput(data, (d) => {
|
|
29273
29319
|
if (!d.docs || d.docs.length === 0) {
|
|
29274
|
-
console.log(dim("No
|
|
29320
|
+
console.log(dim("No files."));
|
|
29275
29321
|
return;
|
|
29276
29322
|
}
|
|
29277
|
-
|
|
29278
|
-
|
|
29279
|
-
|
|
29280
|
-
|
|
29281
|
-
|
|
29282
|
-
|
|
29283
|
-
|
|
29284
|
-
|
|
29285
|
-
|
|
29286
|
-
|
|
29287
|
-
|
|
29288
|
-
|
|
29289
|
-
|
|
29290
|
-
|
|
29291
|
-
|
|
29292
|
-
|
|
29293
|
-
|
|
29294
|
-
|
|
29323
|
+
const header = opts.search ? `Files matching "${opts.search}" (${d.total ?? d.docs.length})` : opts.folder ? `Files in ${opts.folder} (${d.total ?? d.docs.length})` : `Files (${d.total ?? d.docs.length})`;
|
|
29324
|
+
console.log(heading(header));
|
|
29325
|
+
renderDocTable(d.docs);
|
|
29326
|
+
}, { json: opts.json });
|
|
29327
|
+
} catch (e5) {
|
|
29328
|
+
spinner?.stop();
|
|
29329
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29330
|
+
process.exit(1);
|
|
29331
|
+
}
|
|
29332
|
+
});
|
|
29333
|
+
program2.command("search <query>").description("Search files by name, description, or parsed content").option("--twin <idOrName>", "Target twin id or name").option("--folder <id|root>", "Restrict search to a folder").option("--limit <n>", "Max results", "50").option("--json", "Output as JSON").action(async (query, opts) => {
|
|
29334
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29335
|
+
const spinner = opts.json ? null : ora(`Searching for "${query}"...`).start();
|
|
29336
|
+
try {
|
|
29337
|
+
const q = {
|
|
29338
|
+
search: query,
|
|
29339
|
+
limit: opts.limit ?? "50"
|
|
29340
|
+
};
|
|
29341
|
+
if (opts.folder !== undefined)
|
|
29342
|
+
q.folderId = opts.folder;
|
|
29343
|
+
const data = await amikoWebFetch(auth, `/api/agents/${twinId}/docs`, { query: q });
|
|
29344
|
+
spinner?.stop();
|
|
29345
|
+
renderOutput(data, (d) => {
|
|
29346
|
+
if (!d.docs || d.docs.length === 0) {
|
|
29347
|
+
console.log(dim(`No files match "${query}".`));
|
|
29348
|
+
return;
|
|
29349
|
+
}
|
|
29350
|
+
console.log(heading(`Matches for "${query}" (${d.total ?? d.docs.length})`));
|
|
29351
|
+
renderDocTable(d.docs);
|
|
29352
|
+
console.log(dim(`
|
|
29353
|
+
Search hits filename, title, description, and parsed RAG content (once processed).`));
|
|
29295
29354
|
}, { json: opts.json });
|
|
29296
29355
|
} catch (e5) {
|
|
29297
29356
|
spinner?.stop();
|
|
@@ -29299,7 +29358,7 @@ function registerDocsCommand(program2) {
|
|
|
29299
29358
|
process.exit(1);
|
|
29300
29359
|
}
|
|
29301
29360
|
});
|
|
29302
|
-
program2.command("upload <source>").description("Upload a
|
|
29361
|
+
program2.command("upload <source>").description("Upload a file (path | https URL | - for stdin)").option("--twin <idOrName>", "Target twin id or name").option("--name <filename>", "Override filename").option("--title <title>", "File title (defaults to filename without extension)").option("--description <text>", "File description").option("--doc-type <type>", "Document type: personal, work, academic, financial, communication, health, other", "other").option("--folder <id>", "Target folder id (omit for drive root)").option("--stdin", "Read from stdin (source must be -)").option("--json", "Output as JSON").action(async (source, opts) => {
|
|
29303
29362
|
const { twinId, auth } = twinFromFlag(opts);
|
|
29304
29363
|
const spinner = opts.json ? null : ora("Reading input...").start();
|
|
29305
29364
|
try {
|
|
@@ -29321,28 +29380,118 @@ function registerDocsCommand(program2) {
|
|
|
29321
29380
|
if (spinner)
|
|
29322
29381
|
spinner.text = `Registering ${upload.filename}...`;
|
|
29323
29382
|
const title = opts.title || upload.filename.replace(/\.[^.]+$/, "");
|
|
29324
|
-
const
|
|
29325
|
-
|
|
29326
|
-
|
|
29327
|
-
|
|
29328
|
-
|
|
29329
|
-
|
|
29330
|
-
|
|
29331
|
-
|
|
29332
|
-
|
|
29333
|
-
|
|
29334
|
-
|
|
29383
|
+
const body = {
|
|
29384
|
+
filename: upload.filename,
|
|
29385
|
+
fileUrl: upload.url,
|
|
29386
|
+
fileType: upload.fileType,
|
|
29387
|
+
title,
|
|
29388
|
+
doc_type: opts.docType ?? "other"
|
|
29389
|
+
};
|
|
29390
|
+
if (opts.description !== undefined)
|
|
29391
|
+
body.description = opts.description;
|
|
29392
|
+
if (opts.folder !== undefined)
|
|
29393
|
+
body.folderId = opts.folder;
|
|
29394
|
+
const doc2 = await amikoWebFetch(auth, `/api/agents/${twinId}/docs`, { method: "POST", body });
|
|
29335
29395
|
spinner?.stop();
|
|
29336
29396
|
renderOutput(doc2, (d) => {
|
|
29337
29397
|
console.log(success(`Uploaded ${d.filename}`));
|
|
29338
29398
|
console.log(label("Doc ID", d.id));
|
|
29339
29399
|
console.log(label("Title", d.title || "-"));
|
|
29400
|
+
if (d.folder_id)
|
|
29401
|
+
console.log(label("Folder", d.folder_id));
|
|
29340
29402
|
if (upload.fileSize)
|
|
29341
29403
|
console.log(label("Size", fmtBytes(upload.fileSize)));
|
|
29342
29404
|
if (upload.fileType)
|
|
29343
29405
|
console.log(label("Type", upload.fileType));
|
|
29344
|
-
if (
|
|
29345
|
-
console.log(label("
|
|
29406
|
+
if (d.job_id) {
|
|
29407
|
+
console.log(label("RAG job", d.job_id));
|
|
29408
|
+
console.log(dim("Processing runs in the background. Use `amiko drive status <id>` to check."));
|
|
29409
|
+
}
|
|
29410
|
+
}, { json: opts.json });
|
|
29411
|
+
} catch (e5) {
|
|
29412
|
+
spinner?.stop();
|
|
29413
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29414
|
+
process.exit(1);
|
|
29415
|
+
}
|
|
29416
|
+
});
|
|
29417
|
+
program2.command("download <docId>").description("Download a file from the drive").option("--twin <idOrName>", "Target twin id or name").option("--out <path>", "Output path (defaults to original filename in cwd)").option("--stdout", "Write file bytes to stdout instead of a file").option("--json", "Print metadata JSON instead of downloading").action(async (docId, opts) => {
|
|
29418
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29419
|
+
const modes = [opts.json, opts.stdout, opts.out].filter(Boolean).length;
|
|
29420
|
+
if (modes > 1) {
|
|
29421
|
+
console.error(error("--json, --stdout, and --out are mutually exclusive — pick one."));
|
|
29422
|
+
process.exit(1);
|
|
29423
|
+
}
|
|
29424
|
+
const spinner = opts.json || opts.stdout ? null : ora("Fetching file...").start();
|
|
29425
|
+
try {
|
|
29426
|
+
const meta3 = await fetchSignedUrl(auth, twinId, docId);
|
|
29427
|
+
if (opts.json) {
|
|
29428
|
+
console.log(JSON.stringify(meta3, null, 2));
|
|
29429
|
+
return;
|
|
29430
|
+
}
|
|
29431
|
+
if (spinner)
|
|
29432
|
+
spinner.text = `Downloading ${meta3.doc.filename}...`;
|
|
29433
|
+
const controller = new AbortController;
|
|
29434
|
+
const timeout = setTimeout(() => controller.abort(), 60000);
|
|
29435
|
+
let res;
|
|
29436
|
+
try {
|
|
29437
|
+
res = await fetch(meta3.signed_url, { signal: controller.signal });
|
|
29438
|
+
} catch (fetchErr) {
|
|
29439
|
+
if (fetchErr.name === "AbortError") {
|
|
29440
|
+
throw new Error("Signed-URL download timed out after 60s. Retry; the URL is valid for 1h.");
|
|
29441
|
+
}
|
|
29442
|
+
throw fetchErr;
|
|
29443
|
+
} finally {
|
|
29444
|
+
clearTimeout(timeout);
|
|
29445
|
+
}
|
|
29446
|
+
if (!res.ok) {
|
|
29447
|
+
throw new Error(`Signed-URL fetch failed: ${res.status} ${res.statusText}`);
|
|
29448
|
+
}
|
|
29449
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
29450
|
+
if (opts.stdout) {
|
|
29451
|
+
process.stdout.write(buf);
|
|
29452
|
+
return;
|
|
29453
|
+
}
|
|
29454
|
+
const outPath = resolvePath(opts.out ?? basename2(meta3.doc.filename));
|
|
29455
|
+
await writeFile(outPath, buf);
|
|
29456
|
+
spinner?.stop();
|
|
29457
|
+
console.log(success(`Saved ${meta3.doc.filename} → ${outPath}`));
|
|
29458
|
+
console.log(label("Size", fmtBytes(buf.length)));
|
|
29459
|
+
if (meta3.doc.file_type)
|
|
29460
|
+
console.log(label("Type", meta3.doc.file_type));
|
|
29461
|
+
} catch (e5) {
|
|
29462
|
+
spinner?.stop();
|
|
29463
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29464
|
+
process.exit(1);
|
|
29465
|
+
}
|
|
29466
|
+
});
|
|
29467
|
+
program2.command("get <docId>").description("Show one file's metadata, parsed content, and signed URL").option("--twin <idOrName>", "Target twin id or name").option("--content", "Include parsed RAG content body in the output").option("--json", "Output as JSON").action(async (docId, opts) => {
|
|
29468
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29469
|
+
const spinner = opts.json ? null : ora("Loading file...").start();
|
|
29470
|
+
try {
|
|
29471
|
+
const data = await fetchSignedUrl(auth, twinId, docId);
|
|
29472
|
+
spinner?.stop();
|
|
29473
|
+
renderOutput(data, (d) => {
|
|
29474
|
+
console.log(heading(d.doc.title || d.doc.filename));
|
|
29475
|
+
console.log(label("ID", d.doc.id));
|
|
29476
|
+
console.log(label("Filename", d.doc.filename));
|
|
29477
|
+
if (d.doc.file_type)
|
|
29478
|
+
console.log(label("Type", d.doc.file_type));
|
|
29479
|
+
if (d.doc.folder_id)
|
|
29480
|
+
console.log(label("Folder", d.doc.folder_id));
|
|
29481
|
+
if (d.doc.description)
|
|
29482
|
+
console.log(label("Description", d.doc.description));
|
|
29483
|
+
console.log(label("Status", statusOf(d.doc)));
|
|
29484
|
+
if (d.doc.metadata?.error)
|
|
29485
|
+
console.log(label("Error", d.doc.metadata.error));
|
|
29486
|
+
console.log(label("Signed URL", d.signed_url));
|
|
29487
|
+
if (opts.content && d.doc.content) {
|
|
29488
|
+
console.log(heading(`
|
|
29489
|
+
Parsed content`));
|
|
29490
|
+
console.log(d.doc.content);
|
|
29491
|
+
} else if (d.doc.content) {
|
|
29492
|
+
console.log(dim(`
|
|
29493
|
+
Parsed content available (${d.doc.content.length} chars). Pass --content to print.`));
|
|
29494
|
+
}
|
|
29346
29495
|
}, { json: opts.json });
|
|
29347
29496
|
} catch (e5) {
|
|
29348
29497
|
spinner?.stop();
|
|
@@ -29350,36 +29499,170 @@ function registerDocsCommand(program2) {
|
|
|
29350
29499
|
process.exit(1);
|
|
29351
29500
|
}
|
|
29352
29501
|
});
|
|
29353
|
-
program2.command("
|
|
29502
|
+
program2.command("rename <docId>").description("Rename a file (updates title)").option("--twin <idOrName>", "Target twin id or name").requiredOption("--title <newTitle>", "New title").option("--description <text>", "Also update description (pass empty string to clear)").option("--json", "Output as JSON").action(async (docId, opts) => {
|
|
29503
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29504
|
+
const body = { title: opts.title };
|
|
29505
|
+
if (opts.description !== undefined)
|
|
29506
|
+
body.description = opts.description;
|
|
29507
|
+
try {
|
|
29508
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/${docId}`, { method: "PATCH", body });
|
|
29509
|
+
renderOutput(result, () => console.log(success(`Renamed ${docId} → "${opts.title}"`)), { json: opts.json });
|
|
29510
|
+
} catch (e5) {
|
|
29511
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29512
|
+
process.exit(1);
|
|
29513
|
+
}
|
|
29514
|
+
});
|
|
29515
|
+
program2.command("move <docId>").description("Move a file to a different folder").option("--twin <idOrName>", "Target twin id or name").requiredOption("--folder <id|root>", "Destination folder id, or 'root' for drive root").option("--json", "Output as JSON").action(async (docId, opts) => {
|
|
29516
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29517
|
+
const folderId = opts.folder === "root" ? null : opts.folder;
|
|
29518
|
+
try {
|
|
29519
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/${docId}`, { method: "PATCH", body: { folderId } });
|
|
29520
|
+
renderOutput(result, () => console.log(success(`Moved ${docId} → ${folderId ?? "root"}`)), { json: opts.json });
|
|
29521
|
+
} catch (e5) {
|
|
29522
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29523
|
+
process.exit(1);
|
|
29524
|
+
}
|
|
29525
|
+
});
|
|
29526
|
+
program2.command("delete <docId>").description("Delete a file (removes storage object + RAG embeddings)").option("--twin <idOrName>", "Target twin id or name").option("--yes", "Skip confirmation").option("--json", "Output as JSON").action(async (docId, opts) => {
|
|
29354
29527
|
const { twinId, auth } = twinFromFlag(opts);
|
|
29355
29528
|
await confirmDestructive({
|
|
29356
|
-
action: `Delete
|
|
29357
|
-
detail: "This
|
|
29529
|
+
action: `Delete file ${docId}`,
|
|
29530
|
+
detail: "This removes the file from storage and clears its embeddings from the twin's RAG.",
|
|
29358
29531
|
yes: opts.yes,
|
|
29359
|
-
commandExample: `amiko
|
|
29532
|
+
commandExample: `amiko drive delete ${docId}`
|
|
29360
29533
|
});
|
|
29361
|
-
const spinner = opts.json ? null : ora("Deleting
|
|
29534
|
+
const spinner = opts.json ? null : ora("Deleting...").start();
|
|
29362
29535
|
try {
|
|
29363
29536
|
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/${docId}`, { method: "DELETE" });
|
|
29364
29537
|
spinner?.stop();
|
|
29365
|
-
renderOutput(result, () => console.log(success(`Deleted ${docId}`)), {
|
|
29538
|
+
renderOutput(result, () => console.log(success(`Deleted ${docId}`)), {
|
|
29539
|
+
json: opts.json
|
|
29540
|
+
});
|
|
29366
29541
|
} catch (e5) {
|
|
29367
29542
|
spinner?.stop();
|
|
29368
29543
|
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29369
29544
|
process.exit(1);
|
|
29370
29545
|
}
|
|
29371
29546
|
});
|
|
29372
|
-
program2.command("presign <
|
|
29547
|
+
program2.command("presign <name>").description("Get a presigned upload URL (browser-direct uploads; no upload performed)").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (name, opts) => {
|
|
29373
29548
|
const { twinId, auth } = twinFromFlag(opts);
|
|
29374
|
-
const filename = opts.name ?? (source === "-" ? "stdin" : source.split("/").pop() ?? source);
|
|
29375
29549
|
try {
|
|
29376
|
-
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/presigned-url`, { method: "POST", body: { filename } });
|
|
29550
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/presigned-url`, { method: "POST", body: { filename: name } });
|
|
29377
29551
|
renderOutput(result, (r) => console.log(JSON.stringify(r, null, 2)), { json: opts.json });
|
|
29378
29552
|
} catch (e5) {
|
|
29379
29553
|
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29380
29554
|
process.exit(1);
|
|
29381
29555
|
}
|
|
29382
29556
|
});
|
|
29557
|
+
program2.command("status <docId...>").description("Check RAG processing status for one or more files").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (docIds, opts) => {
|
|
29558
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29559
|
+
try {
|
|
29560
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/check-processing`, {
|
|
29561
|
+
method: "POST",
|
|
29562
|
+
body: { docIds }
|
|
29563
|
+
});
|
|
29564
|
+
renderOutput(result, (r) => {
|
|
29565
|
+
const rows = [[dim("DOC ID"), dim("STATUS"), dim("ERROR")]];
|
|
29566
|
+
for (const [id, v] of Object.entries(r.results)) {
|
|
29567
|
+
rows.push([id, v.status, v.error ?? ""]);
|
|
29568
|
+
}
|
|
29569
|
+
console.log(table(rows));
|
|
29570
|
+
}, { json: opts.json });
|
|
29571
|
+
} catch (e5) {
|
|
29572
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29573
|
+
process.exit(1);
|
|
29574
|
+
}
|
|
29575
|
+
});
|
|
29576
|
+
const folder = program2.command("folder").description("Manage drive folders — list, create, rename, move, delete");
|
|
29577
|
+
folder.command("list").description("List all folders for the twin (flat — clients build the tree)").option("--twin <idOrName>", "Target twin id or name").option("--json", "Output as JSON").action(async (opts) => {
|
|
29578
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29579
|
+
try {
|
|
29580
|
+
const data = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/folders`);
|
|
29581
|
+
renderOutput(data, (d) => {
|
|
29582
|
+
if (!d.folders || d.folders.length === 0) {
|
|
29583
|
+
console.log(dim("No folders."));
|
|
29584
|
+
return;
|
|
29585
|
+
}
|
|
29586
|
+
console.log(heading(`Folders (${d.folders.length})`));
|
|
29587
|
+
const rows = [
|
|
29588
|
+
[dim("ID"), dim("NAME"), dim("PARENT"), dim("CREATED")],
|
|
29589
|
+
...d.folders.map((f) => [
|
|
29590
|
+
f.id,
|
|
29591
|
+
f.name,
|
|
29592
|
+
f.parent_id || dim("root"),
|
|
29593
|
+
f.created_at?.slice(0, 10) || "-"
|
|
29594
|
+
])
|
|
29595
|
+
];
|
|
29596
|
+
console.log(table(rows));
|
|
29597
|
+
}, { json: opts.json });
|
|
29598
|
+
} catch (e5) {
|
|
29599
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29600
|
+
process.exit(1);
|
|
29601
|
+
}
|
|
29602
|
+
});
|
|
29603
|
+
folder.command("create <name>").description("Create a folder (defaults to drive root)").option("--twin <idOrName>", "Target twin id or name").option("--parent <id>", "Parent folder id (omit for root)").option("--json", "Output as JSON").action(async (name, opts) => {
|
|
29604
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29605
|
+
const body = { name };
|
|
29606
|
+
if (opts.parent !== undefined)
|
|
29607
|
+
body.parentId = opts.parent;
|
|
29608
|
+
try {
|
|
29609
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/folders`, { method: "POST", body });
|
|
29610
|
+
renderOutput(result, (r) => {
|
|
29611
|
+
console.log(success(`Created folder "${r.folder.name}"`));
|
|
29612
|
+
console.log(label("Folder ID", r.folder.id));
|
|
29613
|
+
console.log(label("Parent", r.folder.parent_id || "root"));
|
|
29614
|
+
}, { json: opts.json });
|
|
29615
|
+
} catch (e5) {
|
|
29616
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29617
|
+
process.exit(1);
|
|
29618
|
+
}
|
|
29619
|
+
});
|
|
29620
|
+
folder.command("rename <folderId>").description("Rename a folder").option("--twin <idOrName>", "Target twin id or name").requiredOption("--name <newName>", "New folder name (1–120 chars)").option("--json", "Output as JSON").action(async (folderId, opts) => {
|
|
29621
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29622
|
+
try {
|
|
29623
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/folders/${folderId}`, { method: "PATCH", body: { name: opts.name } });
|
|
29624
|
+
renderOutput(result, () => console.log(success(`Renamed folder ${folderId} → "${opts.name}"`)), { json: opts.json });
|
|
29625
|
+
} catch (e5) {
|
|
29626
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29627
|
+
process.exit(1);
|
|
29628
|
+
}
|
|
29629
|
+
});
|
|
29630
|
+
folder.command("move <folderId>").description("Move a folder under a new parent").option("--twin <idOrName>", "Target twin id or name").requiredOption("--parent <id|root>", "New parent folder id, or 'root'").option("--json", "Output as JSON").action(async (folderId, opts) => {
|
|
29631
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29632
|
+
const parentId = opts.parent === "root" ? null : opts.parent;
|
|
29633
|
+
try {
|
|
29634
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/folders/${folderId}`, { method: "PATCH", body: { parentId } });
|
|
29635
|
+
renderOutput(result, () => console.log(success(`Moved folder ${folderId} → ${parentId ?? "root"}`)), { json: opts.json });
|
|
29636
|
+
} catch (e5) {
|
|
29637
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29638
|
+
process.exit(1);
|
|
29639
|
+
}
|
|
29640
|
+
});
|
|
29641
|
+
folder.command("delete <folderId>").description("Delete a folder (use --force to delete non-empty folders + their contents)").option("--twin <idOrName>", "Target twin id or name").option("--force", "Recursively delete subfolders and files in the subtree").option("--yes", "Skip confirmation").option("--json", "Output as JSON").action(async (folderId, opts) => {
|
|
29642
|
+
const { twinId, auth } = twinFromFlag(opts);
|
|
29643
|
+
await confirmDestructive({
|
|
29644
|
+
action: opts.force ? `Force-delete folder ${folderId} and all its contents` : `Delete folder ${folderId}`,
|
|
29645
|
+
detail: opts.force ? "This walks the subtree and removes every doc + subfolder under this folder." : "Empty folder only. Non-empty deletes return 409; pass --force to remove the whole subtree.",
|
|
29646
|
+
yes: opts.yes,
|
|
29647
|
+
commandExample: `amiko drive folder delete ${folderId}${opts.force ? " --force" : ""}`
|
|
29648
|
+
});
|
|
29649
|
+
try {
|
|
29650
|
+
const result = await amikoWebFetch(auth, `/api/agents/${twinId}/docs/folders/${folderId}`, {
|
|
29651
|
+
method: "DELETE",
|
|
29652
|
+
query: opts.force ? { force: "1" } : undefined
|
|
29653
|
+
});
|
|
29654
|
+
renderOutput(result, (r) => {
|
|
29655
|
+
console.log(success(`Deleted folder ${folderId}`));
|
|
29656
|
+
if (typeof r.deletedFiles === "number")
|
|
29657
|
+
console.log(label("Files removed", String(r.deletedFiles)));
|
|
29658
|
+
if (typeof r.deletedSubfolders === "number")
|
|
29659
|
+
console.log(label("Subfolders removed", String(r.deletedSubfolders)));
|
|
29660
|
+
}, { json: opts.json });
|
|
29661
|
+
} catch (e5) {
|
|
29662
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29663
|
+
process.exit(1);
|
|
29664
|
+
}
|
|
29665
|
+
});
|
|
29383
29666
|
}
|
|
29384
29667
|
|
|
29385
29668
|
// src/commands/voice.ts
|
|
@@ -31513,7 +31796,7 @@ program2.name("amiko").description("Amiko CLI — swap tokens, manage credits, b
|
|
|
31513
31796
|
var markets = program2.command("markets").description("MPP marketplace — discover and call paid AMIKO services");
|
|
31514
31797
|
var wallets = program2.command("wallets").description("Manage twin wallets — list, swap, bridge");
|
|
31515
31798
|
var twin = program2.command("twin").description("Update twin identity (name, description, visibility)");
|
|
31516
|
-
var
|
|
31799
|
+
var drive = program2.command("drive").alias("docs").description("Manage twin drive — upload/download/search files, organise into folders");
|
|
31517
31800
|
var voice = program2.command("voice").description("Manage twin voice — design, create, clone, reset");
|
|
31518
31801
|
var avatar = program2.command("avatar").description("Manage twin avatar image");
|
|
31519
31802
|
var friends = program2.command("friends").description("Manage friendships — list, requests, add, accept, remove, reports, matches");
|
|
@@ -31539,7 +31822,7 @@ registerDiscoveryCommand(markets);
|
|
|
31539
31822
|
registerUpdateCommand(program2);
|
|
31540
31823
|
registerAccountsCommand(program2);
|
|
31541
31824
|
registerTwinCommand(twin);
|
|
31542
|
-
|
|
31825
|
+
registerDriveCommand(drive);
|
|
31543
31826
|
registerVoiceCommand(voice);
|
|
31544
31827
|
registerAvatarCommand(avatar);
|
|
31545
31828
|
registerFriendsCommand(friends);
|
package/package.json
CHANGED
package/skills/SKILL.md
CHANGED
|
@@ -7,9 +7,11 @@ metadata: {"openclaw":{"emoji":"🤖","requires":{"bins":["node"]}}}
|
|
|
7
7
|
|
|
8
8
|
# Amiko CLI
|
|
9
9
|
|
|
10
|
+
This file is the **mental model**, not a syntax reference. For exact flags and subcommands, run `amiko <group> --help` — the CLI's help is the authoritative source and stays in sync with each release.
|
|
11
|
+
|
|
10
12
|
## How to invoke — ALWAYS use your shell-execution tool
|
|
11
13
|
|
|
12
|
-
Call your shell tool (your runtime calls it `bash`, `shell`, `run`, or similar) and run `amiko <subcommand>`. Amiko is a shell program, not a callable tool.
|
|
14
|
+
Call your shell tool (your runtime calls it `bash`, `shell`, `run`, or similar) and run `amiko <subcommand>`. Amiko is a shell program, not a callable tool.
|
|
13
15
|
|
|
14
16
|
### Examples — copy this pattern
|
|
15
17
|
|
|
@@ -20,403 +22,155 @@ Call your shell tool (your runtime calls it `bash`, `shell`, `run`, or similar)
|
|
|
20
22
|
| "swap 1 SOL to USDC" | shell → `amiko wallets swap quote 1 SOL USDC` (then send with `--yes` after approval) |
|
|
21
23
|
| "did anyone DM me?" | shell → `amiko conversation list` |
|
|
22
24
|
| "any notifications?" | shell → `amiko notifications list --unread` |
|
|
25
|
+
| "any new posts I haven't seen?" | shell → `amiko feed --unread` |
|
|
26
|
+
| "upload report.pdf to my drive" | shell → `amiko drive upload ./report.pdf` |
|
|
27
|
+
| "download the file with id X" | shell → `amiko drive download X` |
|
|
28
|
+
| "find files about Q1 revenue" | shell → `amiko drive search "Q1 revenue"` |
|
|
29
|
+
| "what comments are on my post?" | shell → `amiko post comments --id <postId>` |
|
|
23
30
|
| "search memory for X" | shell → `amiko memory search "X"` |
|
|
24
31
|
| "what can amiko do?" | shell → `amiko --help` |
|
|
25
32
|
|
|
26
|
-
The CLI is installed globally and is pre-authenticated when you're inside your workspace folder. Never suggest `amiko login` or `amiko connect
|
|
33
|
+
The CLI is installed globally and is pre-authenticated when you're inside your workspace folder. Never suggest `amiko login` or `amiko connect` — they don't exist.
|
|
27
34
|
|
|
28
35
|
### Failure modes to avoid
|
|
29
36
|
|
|
30
|
-
- A tool named `amiko_cli` / `amiko-cli` / `amiko` (or this skill's name) does **not** exist. The only path is the shell tool above. If you try to call any of those
|
|
31
|
-
- Do not invent intermediate "amiko" tools (`amiko.balance`,
|
|
32
|
-
|
|
33
|
-
##
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
| `amiko review <cmd>` | Review queue — list, approve, or reject twin-drafted comments |
|
|
50
|
-
| `amiko feed` | Read the friends feed, for-you feed, or filter by hashtag |
|
|
51
|
-
| `amiko composio <cmd>` | Connect / disconnect third-party OAuth apps (Gmail, GitHub, …) |
|
|
52
|
-
| `amiko conversation <cmd>` | DM/chat — list, find, create, send messages |
|
|
53
|
-
| `amiko notifications <cmd>` | Platform notifications — list, mark as read |
|
|
54
|
-
| `amiko memory <cmd>` | Cross-agent memory — `search` before answering personal questions, `add` what's worth remembering, `list` / `rm` / `status` / `sync` local memory files |
|
|
55
|
-
| `amiko accounts` | Show the resolved identity (authenticated, userId, twinId, platform) |
|
|
56
|
-
| `amiko info` | Show the active twin (name, description, public, voice, avatar) |
|
|
57
|
-
| `amiko config <cmd>` | Show resolved config |
|
|
58
|
-
| `amiko update` | Self-update the CLI |
|
|
59
|
-
|
|
60
|
-
All twin-scoped commands accept `--twin <id>` to target a non-default twin.
|
|
37
|
+
- A tool named `amiko_cli` / `amiko-cli` / `amiko` (or this skill's name) does **not** exist as a callable tool. The only path is the shell tool above. If you try to call any of those, the gateway returns `Tool ... not found` — and retries burn credits. Don't retry; re-route through the shell.
|
|
38
|
+
- Do not invent intermediate "amiko" tools (`amiko.balance`, etc.). Every action goes through one path: shell tool → `amiko <subcommand>`.
|
|
39
|
+
|
|
40
|
+
## Command groups
|
|
41
|
+
|
|
42
|
+
Discover everything via `amiko --help`. Groups: `markets` (paid MPP services), `wallets`, `credits`, `twin`, `drive` (files / folders / RAG; `docs` is an alias), `voice`, `avatar`, `friends`, `users`, `post`, `review`, `feed`, `composio`, `conversation`, `notifications`, `memory`, plus the top-level `accounts`, `info`, `config`, `update`. All twin-scoped commands accept `--twin <id>`. Most commands support `--json`.
|
|
43
|
+
|
|
44
|
+
## Critical Rules
|
|
45
|
+
|
|
46
|
+
1. **Every paid / value-moving command is hard-gated on `--yes` in a non-interactive shell.** The CLI refuses to run `markets *`, `credits topup`, `wallets swap send`, `wallets bridge send`, `wallets transfer` (and destructive ops like `twin update --public`, `docs delete`, `friends remove`, `friends reports request`, `avatar update`, `voice reset`, `composio disconnect`, `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`.**
|
|
47
|
+
2. **After every paid command, report the remaining balance.** The CLI prints a `Balance: N credits` line — include that figure in your reply.
|
|
48
|
+
3. **Never retry a failed command.** Report and stop. Every paid call costs tokens even on failure.
|
|
49
|
+
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`.
|
|
50
|
+
5. **Check balance before expensive ops.** Run `amiko credits balance` if unsure.
|
|
51
|
+
6. **Payments are custodied.** The platform signs and moves tokens from the twin's wallet — the CLI never holds keys.
|
|
52
|
+
|
|
53
|
+
### Quoting cost before running
|
|
54
|
+
|
|
55
|
+
Prices change. Before any paid call, run `amiko markets service list` or `amiko markets discover` to fetch the live price, then quote it to the user. Rough order of magnitude: text/search/TTS ≈ 1 AMIKO, SFX ≈ $0.05, music ≈ $0.10, image gen varies by model/quality (OpenAI pass-through × 1.30 markup).
|
|
61
56
|
|
|
62
57
|
## The `--wallet` default (read this before any paid command)
|
|
63
58
|
|
|
64
|
-
Every paid / value-moving command
|
|
59
|
+
Every paid / value-moving command spends from an on-chain wallet, but you almost never need `--wallet` explicitly:
|
|
65
60
|
|
|
66
|
-
- **Default**:
|
|
67
|
-
- **Override**: pass `--wallet <address>` only if you want a different wallet
|
|
68
|
-
- **No wallet yet?** The CLI
|
|
69
|
-
- **Bridges**: `wallets bridge quote/send`
|
|
61
|
+
- **Default**: CLI auto-selects the twin's first active **Solana** wallet.
|
|
62
|
+
- **Override**: pass `--wallet <address>` only if you want a different wallet.
|
|
63
|
+
- **No wallet yet?** The CLI tells you to run `amiko wallets create --chain solana`. One-shot per `chain+custodian`; reuse forever.
|
|
64
|
+
- **Bridges**: `wallets bridge quote/send` defaults `--depositor` to the wallet on the origin chain (Solana for `--from solana`, Base for `--from base`).
|
|
70
65
|
|
|
71
|
-
> Never prompt the user for
|
|
66
|
+
> Never prompt the user for a wallet address when the default works. Only surface `--wallet` if the command errors out.
|
|
72
67
|
|
|
73
|
-
##
|
|
68
|
+
## Credits — intent mapping
|
|
74
69
|
|
|
75
|
-
|
|
76
|
-
2. **After every paid command, report the remaining balance.** The CLI prints a `Balance: N credits` line at the end of every paid command — include that figure in your reply.
|
|
77
|
-
3. **Never retry a failed command.** Report the error and stop. Every paid call costs tokens even on failure.
|
|
78
|
-
4. **Never suggest `amiko login` or `amiko connect`.** These don't exist. Auth is automatic when you run from your workspace folder.
|
|
79
|
-
5. **Check balance before expensive operations.** Run `amiko credits balance` first if unsure.
|
|
80
|
-
6. **Payments are automatic.** The platform signs and moves tokens from the twin's wallet for each paid call — the CLI never holds keys.
|
|
81
|
-
|
|
82
|
-
### Pricing cheat sheet (quote this to the user before running)
|
|
83
|
-
|
|
84
|
-
| Command | Cost |
|
|
85
|
-
|---------|------|
|
|
86
|
-
| `amiko markets search "<query>"` | 1 AMIKO |
|
|
87
|
-
| `amiko markets image "<prompt>"` | varies by model/quality/size (≈$0.005–$1.30 × 1.30 markup) |
|
|
88
|
-
| `amiko markets amazon search "<query>"` | 1 AMIKO |
|
|
89
|
-
| `amiko markets amazon quote <ASIN>` | Free |
|
|
90
|
-
| `amiko markets service tts <voiceId> "<text>"` | 1 AMIKO |
|
|
91
|
-
| `amiko markets service call POST /v1/sfx ...` | $0.05 |
|
|
92
|
-
| `amiko markets service call POST /v1/music ...` | $0.10 |
|
|
93
|
-
| `amiko markets service call POST /v1/music/plan ...` | $0.02 |
|
|
94
|
-
| `amiko wallets swap quote ...` | Free |
|
|
95
|
-
| `amiko wallets swap send ...` | Solana gas + 10bps |
|
|
96
|
-
| `amiko wallets transfer ...` | Chain gas only |
|
|
97
|
-
| `amiko credits topup ...` | Whatever amount you top up |
|
|
98
|
-
|
|
99
|
-
## Credits
|
|
100
|
-
|
|
101
|
-
**Display credits: 10,000 credits = $1.00 USD.**
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
amiko credits balance # show balance as "N credits"
|
|
105
|
-
amiko credits topup --spend 1 --token SOL --yes # "1 SOL worth of credits"
|
|
106
|
-
amiko credits topup --usd 5 --token USDC --yes # $5 paying in USDC
|
|
107
|
-
amiko credits topup 10000 --token AMIKO --yes # 10,000 credits = $1
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
`topup` accepts **one** of three amount forms:
|
|
111
|
-
|
|
112
|
-
| Form | Meaning | Example |
|
|
113
|
-
|------|---------|---------|
|
|
114
|
-
| `<amount>` (positional) | credits | `amiko credits topup 10000 --token AMIKO` |
|
|
115
|
-
| `--usd <n>` | US dollars | `amiko credits topup --usd 5 --token USDC` |
|
|
116
|
-
| `--spend <n>` | spend N of `--token` | `amiko credits topup --spend 1 --token SOL` |
|
|
117
|
-
|
|
118
|
-
Map user intent directly — no manual price math:
|
|
70
|
+
Display unit: **10,000 credits = $1.00 USD.** `topup` accepts one of three amount forms — map user intent directly, no manual price math:
|
|
119
71
|
|
|
120
72
|
- "top up 1 SOL of credits" → `--spend 1 --token SOL`
|
|
121
73
|
- "top up $5 in USDC" → `--usd 5 --token USDC`
|
|
122
|
-
- "top up 10000 credits with AMIKO" → `10000 --token AMIKO`
|
|
123
|
-
|
|
124
|
-
Supported tokens: AMIKO, SOL, USDC, USDT. The CLI fetches the live price, the platform transfers from the twin's wallet, and on-chain verification credits the account. Balance can lag by a few seconds after topup — re-run `amiko credits balance` if it looks stale.
|
|
125
|
-
|
|
126
|
-
## Wallets
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
amiko wallets list # all twin wallets + cached balances
|
|
130
|
-
amiko wallets create --chain solana # create a wallet (solana | base)
|
|
131
|
-
amiko wallets balance <address> # force-sync one wallet; returns fresh balances
|
|
132
|
-
amiko wallets transfer --to <addr> --amount 100 --token amiko --yes # send tokens out of a twin wallet
|
|
133
|
-
amiko wallets swap quote 1.0 SOL USDC # Jupiter quote (free)
|
|
134
|
-
amiko wallets swap send 1.0 SOL USDC --yes # --wallet defaults to your Solana wallet
|
|
135
|
-
amiko wallets swap tokens # supported tokens list
|
|
136
|
-
amiko wallets bridge quote 10 --from solana --to base --recipient <addr>
|
|
137
|
-
amiko wallets bridge send 10 --from solana --to base --recipient <addr> --yes
|
|
138
|
-
amiko wallets bridge status <txHash>
|
|
139
|
-
amiko wallets bridge routes
|
|
140
|
-
amiko wallets bridge limits
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
- `wallets list` returns **cached** balances (platform doesn't auto-sync). If they look stale, run `wallets balance <address>` to force a sync for that wallet.
|
|
144
|
-
- `wallets create` is one-shot per `chain+custodian` pair (409 if one already exists) — run once for the twin and reuse.
|
|
145
|
-
- `wallets transfer` sends tokens from a twin wallet to an external address. Only Crossmint-custodied wallets are supported (agent-signed on the server). `--wallet` defaults to your Solana wallet; pass `--chain base` + `--wallet` to send from a Base wallet. `--token` accepts symbols (`sol`, `usdc`, `usdt`, `amiko` on Solana; `eth` on Base) or a raw mint/contract address. Returns a tx hash once the transaction lands on-chain. Paid/destructive — requires `--yes` in non-interactive shells.
|
|
146
|
-
- Swaps run via Jupiter on Solana. Supported: SOL, USDC, USDT, AMIKO, PYUSD, BONK, JUP, RAY, JitoSOL — or any mint address.
|
|
147
|
-
- Bridging goes through Across Protocol. Solana ↔ EVM requires `--recipient` (different address formats).
|
|
148
|
-
|
|
149
|
-
## Market (paid MPP services)
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
amiko markets search "AI agents" # 1 AMIKO — X/Twitter search
|
|
153
|
-
amiko markets image "a sunset over mountains" --yes # gpt-image-2 high 1024² default — AMIKO @ OpenAI pass-through × 1.30
|
|
154
|
-
amiko markets image "logo" --background transparent # transparent bg
|
|
155
|
-
amiko markets image "portrait" --size 1024x1536 # portrait orientation
|
|
156
|
-
amiko markets image "icon" --model gpt-image-1-mini --quality low # cheapest tier
|
|
157
|
-
amiko markets amazon search "usb c cable" # 1 AMIKO — product search
|
|
158
|
-
amiko markets amazon quote B01GGKYKQM # free — price quote
|
|
159
|
-
amiko markets service list # all services + prices
|
|
160
|
-
amiko markets service call POST /v1/sfx '{"text":"thunder","duration_seconds":5}'
|
|
161
|
-
amiko markets service call POST /v1/music '{"prompt":"lo-fi beat","music_length_ms":30000}'
|
|
162
|
-
amiko markets service tts 21m00Tcm4TlvDq8ikWAM "Hello world"
|
|
163
|
-
amiko markets service call POST /v1/music/plan '{"prompt":"epic orchestral"}'
|
|
164
|
-
amiko markets service call <METHOD> <path> [body] # any MPP endpoint
|
|
165
|
-
amiko markets discover # service info + pricing
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
All paid markets commands auto-select the twin's active Solana wallet. Pass `--wallet <address>` only to override. Audio and image endpoints return permanent Supabase Storage URLs.
|
|
169
|
-
|
|
170
|
-
## Account & Twin
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
amiko accounts # resolved identity (authenticated, userId, twinId, platform)
|
|
174
|
-
amiko info # twin info (name, description, public, voice, avatar)
|
|
175
|
-
amiko twin update --name "New Name"
|
|
176
|
-
amiko twin update --description "..."
|
|
177
|
-
amiko twin update --public true --yes # destructive — requires --yes in non-TTY
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## Documents (RAG)
|
|
181
|
-
|
|
182
|
-
```bash
|
|
183
|
-
amiko docs list # list
|
|
184
|
-
amiko docs upload ./notes.pdf # path
|
|
185
|
-
amiko docs upload https://example.com/x.pdf # URL
|
|
186
|
-
cat file.pdf | amiko docs upload - --stdin --name file.pdf
|
|
187
|
-
amiko docs delete <docId> --yes # destructive
|
|
188
|
-
amiko docs presign ./file.pdf # presigned S3 URL only
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Voice
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
amiko voice design --description "warm, low, male, confident, measured" # min 20 chars
|
|
195
|
-
amiko voice create --sample <generated_voice_id>
|
|
196
|
-
amiko voice clone ./me.mp3 # path | URL | -
|
|
197
|
-
amiko voice reset --yes # destructive
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## Avatar
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
amiko avatar update --file ./portrait.png --yes # max 5 MB, destructive
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## Friends
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
amiko friends list # list all
|
|
210
|
-
amiko friends list --type user # filter
|
|
211
|
-
amiko friends requests # pending requests (incoming + outgoing)
|
|
212
|
-
amiko friends requests --direction incoming
|
|
213
|
-
amiko friends add --id <userId>
|
|
214
|
-
amiko friends accept <friendshipId>
|
|
215
|
-
amiko friends remove <friendshipId> --yes # destructive
|
|
216
|
-
amiko friends matches # pre-generated match candidates (from cron)
|
|
217
|
-
amiko friends matches --dimension personality
|
|
218
|
-
amiko friends find --relationship "cofounder with design taste" # on-demand LLM match for a custom relationship
|
|
219
|
-
amiko friends reports list # all reports (any status)
|
|
220
|
-
amiko friends reports pending # pending-consent: awaiting mine + awaiting theirs
|
|
221
|
-
amiko friends reports view <reportId>
|
|
222
|
-
amiko friends reports request --id <userId> --type friend|romantic|career --yes
|
|
223
|
-
amiko friends reports consent <reportId> # respondent approves; report then generates
|
|
224
|
-
amiko friends reports cancel <reportId>
|
|
225
|
-
amiko friends reports retry <reportId> # re-run a failed generation
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Answering relationship questions ("how is my relationship with X?")
|
|
229
|
-
|
|
230
|
-
When the user asks about their relationship / compatibility with a specific person (by name or handle), follow this playbook:
|
|
231
|
-
|
|
232
|
-
1. **Resolve the person to a user id.** Check `amiko friends list --json` first. If not a friend, try `amiko users search "<name>" --json`. If still ambiguous, ask the user which one they mean.
|
|
233
|
-
2. **Look for an existing report.** Run `amiko friends reports list --json` and filter by `initiator.id` or `respondent.id` matching the resolved user id. If one exists with `status=completed`, use `amiko friends reports view <id>` and summarize. If `status=pending_consent` or `generating`, tell the user it is in progress.
|
|
234
|
-
3. **If none exists**, don't silently create one. Ask the user which of the three report types they want (`friend` / `romantic` / `career`), and confirm that the other user will be notified to consent. Only then run `amiko friends reports request --id <userId> --type <type> --yes`.
|
|
235
|
-
4. **For "who is waiting on whom"**, use `amiko friends reports pending` — it splits `pending_consent` reports into *awaiting my consent* (I can `consent <id>`) vs *awaiting theirs* (I can `cancel <id>`). Use this before deciding to `request` — you cannot have two active reports of the same type between the same pair (the API returns 409).
|
|
236
|
-
|
|
237
|
-
Both users must have a personality profile, otherwise the request returns 422.
|
|
238
|
-
|
|
239
|
-
### Recommending interesting people ("who should I meet / connect with?")
|
|
240
|
-
|
|
241
|
-
The discovery primitive is **`amiko friends matches`** — it returns personality-match candidates with a score (0–1), a match dimension, a pairing label, and compatibility highlights.
|
|
242
|
-
|
|
243
|
-
**`friends find` vs `users search` — do not confuse them:**
|
|
244
|
-
|
|
245
|
-
| Command | Purpose | Input | Backend |
|
|
246
|
-
|---|---|---|---|
|
|
247
|
-
| `users search <query>` | **Look up a specific known person** by name or handle ("find wendao", "search for someone called Sophie") | Exact/substring text against `name` / `handle` | Simple SQL lookup in `/api/search?type=people` |
|
|
248
|
-
| `friends find --relationship <text>` | **Discover unknown people** whose personality matches a free-form relationship description ("cofounder with design taste", "someone to hike with") | Free-form relationship description | LLM-generated MatchingSpec + semantic matching across ~250 candidate profiles |
|
|
249
|
-
|
|
250
|
-
Rule of thumb: if the user already has a name in mind → `users search`. If the user is describing what *kind of person* they want to meet → `friends find`. For already-curated ambient recommendations with no input required → `friends matches`.
|
|
251
|
-
|
|
252
|
-
1. Start broad: `amiko friends matches --limit 20 --json`. Summarize the top few by `display_name`, `score`, `match_dimension`, `pairing_label`, and `recommendation_reason`. These are pre-generated by a cron job.
|
|
253
|
-
2. If the user wants to narrow, filter by `--dimension personality` or `--dimension interest` (the only two dimensions the matching cron actually produces). `--relationship-type` also exists but takes free-form LLM-generated labels (often localized, e.g. `深度思维_对打手`) — usually not worth filtering on unless you first inspect the `--json` output to pick a known value.
|
|
254
|
-
3. If the user describes a **specific kind of person** not covered by the cron matches ("a cofounder who is good at design", "a hiking buddy", "someone to debate philosophy with"), use `amiko friends find --relationship "<description>"`. This is an on-demand, LLM-backed matcher against ~250 candidates; may take ~10s and may return zero matches if nothing clears the internal score threshold.
|
|
255
|
-
4. Skip anyone whose `friendship_status` is already `accepted` unless the user explicitly wants to revisit existing friends.
|
|
256
|
-
5. Follow-ups: `amiko users profile <handle>` for a fuller view, `amiko friends add --id <matched_user_id>` (after explicit user approval) to send a friend request, and — once the friendship is accepted — `amiko friends reports request --type friend|romantic|career` for a deeper compatibility report. Note that report `--type` is a separate enum and has nothing to do with `match.relationship_type`.
|
|
257
|
-
|
|
258
|
-
## Users
|
|
259
|
-
|
|
260
|
-
```bash
|
|
261
|
-
amiko users search <query> # search by name/handle
|
|
262
|
-
amiko users profile <handle> # public profile
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
## Feed & Posts
|
|
266
|
-
|
|
267
|
-
```bash
|
|
268
|
-
amiko feed # friends feed (default)
|
|
269
|
-
amiko feed --type for_you --limit 20
|
|
270
|
-
amiko feed --hashtag ai
|
|
271
|
-
amiko post create --content "hello from the CLI"
|
|
272
|
-
amiko post create --content "private note" --visibility private
|
|
273
|
-
amiko post create --content "look" --media https://...jpg
|
|
274
|
-
amiko post comment --id <postId> --comment "great post"
|
|
275
|
-
amiko post comment --id <postId> --comment "look" --media https://...jpg
|
|
276
|
-
amiko post comment --id <postId> --comment "..." --twin <id>
|
|
277
|
-
amiko review list # list twin-drafted comments awaiting your approval
|
|
278
|
-
amiko review approve <commentId> # publish a draft comment from the review queue
|
|
279
|
-
amiko review reject <commentId> --yes # destructive — delete a draft comment
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
**Comments authored by a twin always go through review.** Any comment created with `amiko post comment ... --twin <id>` (or auto-drafted by a twin in engagement mode) lands in `status=draft` and is **not visible** until you run `amiko review approve <commentId>`. Comments without `--twin` (you posting as yourself) publish immediately and skip the review queue. Workflow: `amiko review list` to see pending drafts → `amiko review approve <id>` to publish, or `amiko review reject <id> --yes` to discard.
|
|
283
|
-
|
|
284
|
-
## Conversations
|
|
285
|
-
|
|
286
|
-
> **Default platform = Amiko.** When the owner asks about messages, DMs, chats, notifications, or "anything new" **without naming a platform** (no "on WeChat", "on Telegram", "on Slack", etc.), assume they mean the Amiko platform and answer with `amiko conversation` / `amiko notifications`. Do **not** ask "which platform?" — just use the Amiko CLI. Only ask for clarification if the owner explicitly mentions a non-Amiko channel or the question is genuinely ambiguous across multiple connected channels.
|
|
287
|
-
>
|
|
288
|
-
> **When the owner asks about chat history with another person** ("did X message me?", "check my chat with Y", "anyone DM me this week?"), use `amiko conversation` — these are platform DMs between the owner and other Amiko users, stored in amiko-web's database. Do **not** use the built-in `sessions_list` / `sessions_history` tools for this: those read the agent's *own* past chat sessions with the owner (local openclaw memory), not platform DMs with third parties, and will return nothing useful. Typical flow: `amiko users search "<name>"` → `amiko conversation find --user-id <id>` → `amiko conversation read --id <conversationId>`. To scan everyone who messaged recently, start with `amiko conversation list` (sorted by last activity) or `amiko notifications list --unread`.
|
|
289
|
-
|
|
290
|
-
```bash
|
|
291
|
-
amiko conversation list # all your conversations (default: active)
|
|
292
|
-
amiko conversation list --archived all --limit 100 # include archived
|
|
293
|
-
amiko conversation find --user-id <userId> # is there an existing direct DM with this user?
|
|
294
|
-
amiko conversation create --user-id <userId> # create or reuse a direct DM (idempotent)
|
|
295
|
-
amiko conversation read --id <conversationId> # read recent messages (oldest-first)
|
|
296
|
-
amiko conversation read --id <id> --before-cursor <next_cursor> # page older messages
|
|
297
|
-
amiko conversation send --id <conversationId> --message "hi" # send a text message
|
|
298
|
-
amiko conversation send --id <id> --message "ok" --reply-to <messageId>
|
|
299
|
-
amiko conversation send --id <id> --message "agent reply" --as-agent # send as twin (sender_type=agent)
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
- `list` and `find` query amiko-web's `/api/conversations` (Prisma-backed). `find` filters client-side for a 2-user direct conversation containing both you and `--user-id`.
|
|
303
|
-
- `create` POSTs to `/api/conversations` with `conversation_type=direct`. The server reuses an existing conversation if one already exists between the two users (`reused_existing: true` in the JSON response), so calling it repeatedly is safe.
|
|
304
|
-
- `read` queries amiko-web's `GET /api/conversations/<id>/messages` (paginated by `next_cursor`, oldest-first per page).
|
|
305
|
-
- `send` posts to amiko-web's `POST /api/conversations/<id>/messages` with the Clawd twin token. The server persists the message and fans it out to other participants via the WebSocket broadcast.
|
|
306
|
-
- `--user-id` expects the **internal** user id (the one returned by `amiko users search`), not a Privy DID or handle.
|
|
307
|
-
|
|
308
|
-
## Notifications
|
|
309
|
-
|
|
310
|
-
```bash
|
|
311
|
-
amiko notifications list # recent platform notifications (default 20)
|
|
312
|
-
amiko notifications list --unread # only unread
|
|
313
|
-
amiko notifications list --cursor <iso> # page older items
|
|
314
|
-
amiko notifications read --id <notificationId> # mark one read
|
|
315
|
-
amiko notifications read --all # mark all read
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
Platform notifications cover friend requests, mentions, system alerts, and other activity that doesn't belong to any conversation. Use this when the user asks "any notifications?" or "what's new on the platform?". Notifications belonging to a conversation (new DM, comment on your post) still show up here when the platform writes one — they don't replace `conversation read` for actual chat content.
|
|
74
|
+
- "top up 10000 credits with AMIKO" → positional `10000 --token AMIKO`
|
|
319
75
|
|
|
320
|
-
|
|
76
|
+
Supported: AMIKO, SOL, USDC, USDT. Balance can lag a few seconds after topup — re-run `credits balance` if stale.
|
|
321
77
|
|
|
322
|
-
|
|
78
|
+
## Wallets — behavior notes
|
|
323
79
|
|
|
324
|
-
**
|
|
80
|
+
- `wallets list` returns **cached** balances. If stale, run `wallets balance <address>` to force a sync.
|
|
81
|
+
- `wallets create` is one-shot per `chain+custodian` (409 if one exists).
|
|
82
|
+
- `wallets transfer` only works for Crossmint-custodied wallets. `--token` accepts symbols (`sol`, `usdc`, `usdt`, `amiko` on Solana; `eth` on Base) or a raw mint/contract address.
|
|
83
|
+
- Swaps run via Jupiter on Solana. Bridges run via Across; Solana ↔ EVM requires `--recipient` (different address formats).
|
|
325
84
|
|
|
326
|
-
|
|
327
|
-
2. **Sync periodically.** Run `amiko memory sync` whenever you've meaningfully edited the local `MEMORY.md` / `memory/**/*.md` files, and at least once at the end of any session in which they changed. The platform only sees what you push.
|
|
85
|
+
## Markets — behavior notes
|
|
328
86
|
|
|
329
|
-
|
|
330
|
-
amiko memory search "git workflow" --limit 5 # hybrid (vector + FTS); query in any language
|
|
331
|
-
amiko memory list --limit 25 # newest-first, paginate with --offset
|
|
332
|
-
amiko memory list --category preference # filter: fact | preference | pattern | decision | context
|
|
333
|
-
amiko memory add "Owner prefers terse PR descriptions" --category preference --tags pr,style
|
|
334
|
-
amiko memory rm <memoryId> # soft-delete
|
|
335
|
-
amiko memory status # totals
|
|
336
|
-
amiko memory sync # one-way upload of local memory files to the platform
|
|
337
|
-
```
|
|
87
|
+
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.
|
|
338
88
|
|
|
339
|
-
|
|
89
|
+
## Drive (files & folders)
|
|
340
90
|
|
|
341
|
-
|
|
91
|
+
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.
|
|
342
92
|
|
|
343
|
-
|
|
93
|
+
## Friends & relationships
|
|
344
94
|
|
|
345
|
-
|
|
95
|
+
### "How is my relationship with X?" playbook
|
|
346
96
|
|
|
347
|
-
1. **
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
97
|
+
1. **Resolve to a user id**: `amiko friends list --json` first; if not a friend, `amiko users search "<name>" --json`. If ambiguous, ask.
|
|
98
|
+
2. **Check for an existing report**: `amiko friends reports list --json`, filter by `initiator.id` / `respondent.id`. If `completed` → `reports view <id>`. If `pending_consent` / `generating` → say so.
|
|
99
|
+
3. **None exists?** Don't silently create. Ask which type (`friend` / `romantic` / `career`); confirm the other user will be notified. Then `amiko friends reports request --id <userId> --type <type> --yes`.
|
|
100
|
+
4. **"Who's waiting on whom"**: `amiko friends reports pending` splits into *awaiting my consent* (`consent <id>`) vs *awaiting theirs* (`cancel <id>`). Use this before `request` — the API returns 409 on duplicate active reports for the same pair+type.
|
|
101
|
+
|
|
102
|
+
Both users must have a personality profile (else 422).
|
|
353
103
|
|
|
354
|
-
|
|
355
|
-
- Purely textbook programming questions with no owner-specific signal ("how does `useEffect` work", "what is the time complexity of quicksort").
|
|
356
|
-
- Questions the current code or `git log` already answers directly.
|
|
104
|
+
### "Who should I meet?" — `friends find` vs `users search` vs `friends matches`
|
|
357
105
|
|
|
358
|
-
|
|
106
|
+
| Command | Purpose | Input |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `users search <query>` | **Look up a known person** by name/handle | Exact/substring text |
|
|
109
|
+
| `friends find --relationship <text>` | **Discover unknown people** matching a free-form relationship description | "cofounder with design taste" |
|
|
110
|
+
| `friends matches` | **Pre-curated** personality-match candidates from cron | (no input) |
|
|
359
111
|
|
|
360
|
-
|
|
112
|
+
Workflow: start with `friends matches --limit 20 --json`; narrow with `--dimension personality` or `--dimension interest` (the only two dimensions the cron produces). For specific kinds not covered → `friends find` (LLM-backed, ~10s, may return 0). Skip anyone with `friendship_status=accepted` unless explicitly asked. Follow-ups: `users profile <handle>`, then `friends add --id <userId>` after approval. Report `--type` is unrelated to `match.relationship_type`.
|
|
361
113
|
|
|
362
|
-
|
|
114
|
+
## Feed, Posts & Review
|
|
363
115
|
|
|
364
|
-
|
|
365
|
-
- Making an architecture decision → save reasoning as `decision`
|
|
366
|
-
- Discovering a useful pattern or workaround → `pattern`
|
|
367
|
-
- Owner explicitly says "remember this" / "save this" / "from now on..." → match the category to the content
|
|
368
|
-
- Learning a preference you'd otherwise have to re-ask ("I prefer rg", "I always use pnpm") → `preference`
|
|
116
|
+
**Reading a post via the CLI counts as reading it.** Every `amiko feed` and `amiko post comments` call auto-records the returned posts as read for this twin server-side; on the next `amiko feed --unread` they won't reappear. No manual "mark read" command exists. (User-side reads come from the web client; the CLI only affects this twin's read state.)
|
|
369
117
|
|
|
370
|
-
|
|
118
|
+
**Comments authored by a twin always go through review.** Any comment created with `amiko post comment ... --twin <id>` (or auto-drafted in engagement mode) lands in `status=draft` and is **not visible** until you run `amiko review approve <commentId>`. Comments without `--twin` (the owner posting) publish immediately. Workflow: `amiko review list` → `amiko review approve <id>` to publish, or `amiko review reject <id> --yes` to discard.
|
|
371
119
|
|
|
372
|
-
|
|
120
|
+
## Conversations & Notifications
|
|
373
121
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
| `preference` | How they like things done (tone, formats, tools, coding style) |
|
|
378
|
-
| `pattern` | Recurring patterns, pitfalls, team conventions, workarounds |
|
|
379
|
-
| `decision` | Architecture decisions and their reasoning |
|
|
380
|
-
| `context` | Project context, deadlines, ongoing work, transient state |
|
|
122
|
+
> **Default platform = Amiko.** When the owner asks about messages/DMs/chats/notifications **without naming a platform**, assume Amiko and answer with `amiko conversation` / `amiko notifications`. Do **not** ask "which platform?". Only ask if they explicitly mention a non-Amiko channel.
|
|
123
|
+
>
|
|
124
|
+
> **For chat history with another person** ("did X message me?", "check my chat with Y"), use `amiko conversation`. Do **NOT** use built-in `sessions_list` / `sessions_history` — those read the agent's *own* local sessions with the owner, not platform DMs with third parties, and will return nothing useful. Typical flow: `amiko users search "<name>"` → `amiko conversation find --user-id <id>` → `amiko conversation read --id <conversationId>`.
|
|
381
125
|
|
|
382
|
-
|
|
383
|
-
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
126
|
+
Behavior notes:
|
|
127
|
+
- `conversation find` filters client-side for a 2-user direct conversation containing both you and `--user-id`.
|
|
128
|
+
- `conversation create` is idempotent — server reuses an existing direct DM (`reused_existing: true`).
|
|
129
|
+
- `--user-id` expects the **internal** user id (from `amiko users search`), not a Privy DID or handle.
|
|
130
|
+
- `conversation send --as-agent` sends as the twin (`sender_type=agent`).
|
|
131
|
+
- Platform notifications cover friend requests, mentions, system alerts, and post-related events. They don't replace `conversation read` for actual chat content.
|
|
386
132
|
|
|
133
|
+
## Memory (cross-agent) — READ THIS BEFORE ANSWERING ANYTHING ABOUT THE OWNER
|
|
387
134
|
|
|
388
|
-
|
|
135
|
+
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.
|
|
389
136
|
|
|
390
|
-
|
|
391
|
-
amiko composio connect --app gmail # idempotent — prints OAuth URL if not yet connected, or returns alreadyConnected:true
|
|
392
|
-
amiko composio connect --app gmail --force # destructive — disconnects any existing link and starts a fresh OAuth
|
|
393
|
-
amiko composio disconnect --app gmail --yes # destructive
|
|
394
|
-
```
|
|
137
|
+
**Two non-negotiable habits:**
|
|
395
138
|
|
|
396
|
-
`
|
|
139
|
+
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.
|
|
140
|
+
2. **Sync periodically.** Run `amiko memory sync` whenever you've meaningfully edited local `MEMORY.md` / `memory/**/*.md`, and at least once at the end of any session in which they changed.
|
|
397
141
|
|
|
398
|
-
|
|
142
|
+
### When to search — bias toward calling
|
|
399
143
|
|
|
400
|
-
|
|
401
|
-
amiko config show # resolved config
|
|
402
|
-
amiko update # self-update
|
|
403
|
-
amiko --help # top-level help
|
|
404
|
-
amiko <group> --help # e.g. `amiko wallets --help`
|
|
405
|
-
```
|
|
144
|
+
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:
|
|
406
145
|
|
|
407
|
-
|
|
146
|
+
1. **Preference / habit questions**, even without a specific entity. "what do I usually use for X", "what's my coding style".
|
|
147
|
+
2. **Callbacks to prior context.** "as I mentioned", "like last time", "what was that X we set up".
|
|
148
|
+
3. **Named entities specific to this owner** — their project / repo / service / team / tool / a person by name.
|
|
149
|
+
4. **Past bugs, decisions, investigations, design choices.**
|
|
150
|
+
5. **Start of a new session** where they reference anything about themselves or their work.
|
|
408
151
|
|
|
409
|
-
|
|
152
|
+
Do NOT search for purely textbook programming questions, or things the current code / `git log` answers directly.
|
|
410
153
|
|
|
411
|
-
|
|
412
|
-
amiko wallets list --json | jq '.wallets[].wallet_address'
|
|
413
|
-
amiko friends list --json | jq '.friends[] | .friend.name'
|
|
414
|
-
amiko info --json | jq -r '.name'
|
|
415
|
-
```
|
|
154
|
+
**When unsure, search.** Empty results cost nothing. Missing context costs trust.
|
|
416
155
|
|
|
417
|
-
|
|
156
|
+
### When to save (`amiko memory add`)
|
|
418
157
|
|
|
419
|
-
|
|
158
|
+
- Fixing a non-obvious bug → `pattern` or `fact`
|
|
159
|
+
- Architecture decision → `decision`
|
|
160
|
+
- Useful pattern or workaround → `pattern`
|
|
161
|
+
- "Remember this" / "save this" / "from now on..." → match category to content
|
|
162
|
+
- Preference you'd otherwise re-ask ("I prefer rg", "I always use pnpm") → `preference`
|
|
163
|
+
|
|
164
|
+
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."
|
|
165
|
+
|
|
166
|
+
Categories: `fact` | `preference` | `pattern` | `decision` | `context`.
|
|
167
|
+
|
|
168
|
+
Do NOT save trivial code-derivable facts, ephemeral `git log` state, or duplicates (search first; `rm` near-matches before adding).
|
|
169
|
+
|
|
170
|
+
## Composio
|
|
171
|
+
|
|
172
|
+
`composio connect --app <app>` is safe to call as a status check — idempotent, never destroys an existing connection. Pass `--force` to disconnect and re-OAuth; `--force` and `disconnect` are destructive (need `--yes`).
|
|
173
|
+
|
|
174
|
+
## Where to run
|
|
420
175
|
|
|
421
|
-
|
|
422
|
-
- If anything looks off, run `amiko accounts` — it prints the resolved `userId` and `twinId` so you can confirm scope.
|
|
176
|
+
**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.
|