@heyamiko/amiko-cli 0.10.1-beta.2 → 0.10.1-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -1
- package/dist/index.js +332 -232
- package/package.json +1 -1
- package/skills/SKILL.md +16 -13
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @heyamiko/amiko-cli (v0.10.1-beta.
|
|
1
|
+
# @heyamiko/amiko-cli (v0.10.1-beta.4)
|
|
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,17 @@ npm publish
|
|
|
393
393
|
|
|
394
394
|
## Changelog
|
|
395
395
|
|
|
396
|
+
### 0.10.1-beta.4
|
|
397
|
+
|
|
398
|
+
- **`amiko conversation` removed (breaking).** DMs / chat history are now served by the openhermit gateway's built-in `session_list` / `session_read` tools (unified view across the agent's local sessions and platform DMs), so the CLI no longer needs `conversation list / find / create / read / send`. `src/commands/conversation.ts` deleted; index registration + command group removed; SKILL.md frontmatter and Conversations section rewritten to route DM questions to the gateway tools and the remaining notifications-only block renamed `## Notifications`. Skill description and command-groups paragraph updated; the "did anyone DM me?" example row dropped from the cold-start examples table. Update any scripts that ran `amiko conversation` — there is no shim, the command returns "unknown command".
|
|
399
|
+
|
|
400
|
+
### 0.10.1-beta.3
|
|
401
|
+
|
|
402
|
+
- **`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:
|
|
403
|
+
- **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).
|
|
404
|
+
- **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).
|
|
405
|
+
- SKILL.md picks up a one-paragraph Drive section + three new rows in the Examples table (upload / download / search intents → exact `amiko drive` commands).
|
|
406
|
+
|
|
396
407
|
### 0.10.1-beta.2
|
|
397
408
|
|
|
398
409
|
- **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`.
|
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,53 @@ 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
|
-
dim("ID"),
|
|
29281
|
-
dim("NAME"),
|
|
29282
|
-
dim("TYPE"),
|
|
29283
|
-
dim("STATUS"),
|
|
29284
|
-
dim("CREATED")
|
|
29285
|
-
],
|
|
29286
|
-
...d.docs.map((doc2) => [
|
|
29287
|
-
doc2.id,
|
|
29288
|
-
doc2.title || doc2.filename,
|
|
29289
|
-
doc2.file_type || "-",
|
|
29290
|
-
doc2.is_processed ? "processed" : doc2.is_parsed ? "parsed" : "pending",
|
|
29291
|
-
doc2.created_at?.slice(0, 10) || "-"
|
|
29292
|
-
])
|
|
29293
|
-
];
|
|
29294
|
-
console.log(table(rows));
|
|
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);
|
|
29295
29326
|
}, { json: opts.json });
|
|
29296
29327
|
} catch (e5) {
|
|
29297
29328
|
spinner?.stop();
|
|
@@ -29299,7 +29330,35 @@ function registerDocsCommand(program2) {
|
|
|
29299
29330
|
process.exit(1);
|
|
29300
29331
|
}
|
|
29301
29332
|
});
|
|
29302
|
-
program2.command("
|
|
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).`));
|
|
29354
|
+
}, { json: opts.json });
|
|
29355
|
+
} catch (e5) {
|
|
29356
|
+
spinner?.stop();
|
|
29357
|
+
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
29358
|
+
process.exit(1);
|
|
29359
|
+
}
|
|
29360
|
+
});
|
|
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
|
|
@@ -30668,187 +30951,6 @@ function registerComposioCommand(program2) {
|
|
|
30668
30951
|
});
|
|
30669
30952
|
}
|
|
30670
30953
|
|
|
30671
|
-
// src/commands/conversation.ts
|
|
30672
|
-
function describeConversation(c, selfUserId) {
|
|
30673
|
-
if (c.title)
|
|
30674
|
-
return c.title;
|
|
30675
|
-
const others = c.participants?.filter((p) => !(p.participant_type === "user" && p.participant_id === selfUserId)) ?? [];
|
|
30676
|
-
return others.map((p) => p.name ?? p.participant_id).join(", ") || c.id;
|
|
30677
|
-
}
|
|
30678
|
-
function findDirectWithUser(list, selfUserId, otherUserId) {
|
|
30679
|
-
return list.find((c) => {
|
|
30680
|
-
if (c.conversation_type !== "direct")
|
|
30681
|
-
return false;
|
|
30682
|
-
const ps = c.participants ?? [];
|
|
30683
|
-
const userIds = ps.filter((p) => p.participant_type === "user" && !p.left_at).map((p) => p.participant_id);
|
|
30684
|
-
if (userIds.length !== 2)
|
|
30685
|
-
return false;
|
|
30686
|
-
if (selfUserId && !userIds.includes(selfUserId))
|
|
30687
|
-
return false;
|
|
30688
|
-
return userIds.includes(otherUserId);
|
|
30689
|
-
});
|
|
30690
|
-
}
|
|
30691
|
-
function registerConversationCommand(program2) {
|
|
30692
|
-
program2.command("list").description("List your conversations").option("--archived <state>", "active | archived | all", "active").option("--limit <n>", "Max results (1–100)", "50").option("--offset <n>", "Pagination offset", "0").option("--json", "Output as JSON").action(async (opts) => {
|
|
30693
|
-
const auth = resolveAuth();
|
|
30694
|
-
const config2 = loadConfig();
|
|
30695
|
-
const spinner = opts.json ? null : ora("Loading conversations...").start();
|
|
30696
|
-
try {
|
|
30697
|
-
const data = await amikoWebFetch(auth, "/api/conversations", {
|
|
30698
|
-
query: {
|
|
30699
|
-
archived: opts.archived,
|
|
30700
|
-
limit: opts.limit,
|
|
30701
|
-
offset: opts.offset
|
|
30702
|
-
}
|
|
30703
|
-
});
|
|
30704
|
-
spinner?.stop();
|
|
30705
|
-
renderOutput(data, (d) => {
|
|
30706
|
-
if (!d.conversations.length) {
|
|
30707
|
-
console.log(dim("No conversations."));
|
|
30708
|
-
return;
|
|
30709
|
-
}
|
|
30710
|
-
console.log(heading(`Conversations — ${d.conversations.length} of ${d.pagination.total}`));
|
|
30711
|
-
for (const c of d.conversations) {
|
|
30712
|
-
console.log("");
|
|
30713
|
-
console.log(label("ID", c.id));
|
|
30714
|
-
console.log(label("With", describeConversation(c, config2.userId)));
|
|
30715
|
-
console.log(label("Type", c.conversation_type));
|
|
30716
|
-
if (c.last_activity_at)
|
|
30717
|
-
console.log(label("Last activity", c.last_activity_at));
|
|
30718
|
-
if (c.unread_count)
|
|
30719
|
-
console.log(label("Unread", String(c.unread_count)));
|
|
30720
|
-
}
|
|
30721
|
-
if (d.pagination.has_more) {
|
|
30722
|
-
console.log("");
|
|
30723
|
-
console.log(dim(`More available — pass --offset ${Number(opts.offset ?? 0) + d.conversations.length}`));
|
|
30724
|
-
}
|
|
30725
|
-
}, { json: opts.json });
|
|
30726
|
-
} catch (e5) {
|
|
30727
|
-
spinner?.stop();
|
|
30728
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
30729
|
-
process.exit(1);
|
|
30730
|
-
}
|
|
30731
|
-
});
|
|
30732
|
-
program2.command("find").description("Find an existing direct conversation with a user").requiredOption("--user-id <id>", "Internal user id of the other party").option("--json", "Output as JSON").action(async (opts) => {
|
|
30733
|
-
const auth = resolveAuth();
|
|
30734
|
-
const config2 = loadConfig();
|
|
30735
|
-
const spinner = opts.json ? null : ora("Searching...").start();
|
|
30736
|
-
try {
|
|
30737
|
-
const data = await amikoWebFetch(auth, "/api/conversations", { query: { archived: "all", limit: 100 } });
|
|
30738
|
-
spinner?.stop();
|
|
30739
|
-
const match = findDirectWithUser(data.conversations, config2.userId, opts.userId);
|
|
30740
|
-
if (opts.json) {
|
|
30741
|
-
console.log(JSON.stringify({ conversation: match ?? null }, null, 2));
|
|
30742
|
-
return;
|
|
30743
|
-
}
|
|
30744
|
-
if (!match) {
|
|
30745
|
-
console.log(dim(`No direct conversation with user ${opts.userId}. Run \`amiko conversation create --user-id ${opts.userId}\` to start one.`));
|
|
30746
|
-
return;
|
|
30747
|
-
}
|
|
30748
|
-
console.log(success("Found conversation"));
|
|
30749
|
-
console.log(label("ID", match.id));
|
|
30750
|
-
console.log(label("With", describeConversation(match, config2.userId)));
|
|
30751
|
-
if (match.last_activity_at)
|
|
30752
|
-
console.log(label("Last activity", match.last_activity_at));
|
|
30753
|
-
} catch (e5) {
|
|
30754
|
-
spinner?.stop();
|
|
30755
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
30756
|
-
process.exit(1);
|
|
30757
|
-
}
|
|
30758
|
-
});
|
|
30759
|
-
program2.command("create").description("Create (or reuse) a direct conversation with another user").requiredOption("--user-id <id>", "Internal user id of the other party").option("--json", "Output as JSON").action(async (opts) => {
|
|
30760
|
-
const auth = resolveAuth();
|
|
30761
|
-
const spinner = opts.json ? null : ora("Creating conversation...").start();
|
|
30762
|
-
try {
|
|
30763
|
-
const data = await amikoWebFetch(auth, "/api/conversations", {
|
|
30764
|
-
method: "POST",
|
|
30765
|
-
body: {
|
|
30766
|
-
participant_ids: [opts.userId],
|
|
30767
|
-
participant_types: ["user"],
|
|
30768
|
-
conversation_type: "direct"
|
|
30769
|
-
}
|
|
30770
|
-
});
|
|
30771
|
-
spinner?.stop();
|
|
30772
|
-
if (opts.json) {
|
|
30773
|
-
console.log(JSON.stringify(data, null, 2));
|
|
30774
|
-
return;
|
|
30775
|
-
}
|
|
30776
|
-
console.log(success(data.reused_existing ? "Reused existing conversation" : "Conversation created"));
|
|
30777
|
-
console.log(label("ID", data.conversation.id));
|
|
30778
|
-
} catch (e5) {
|
|
30779
|
-
spinner?.stop();
|
|
30780
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
30781
|
-
process.exit(1);
|
|
30782
|
-
}
|
|
30783
|
-
});
|
|
30784
|
-
program2.command("read").description("Read messages from a conversation (oldest-first, paginated)").requiredOption("--id <conversationId>", "Target conversation id").option("--limit <n>", "Max messages (1–100)", "50").option("--before-cursor <cursor>", "Page back via next_cursor from a prior call").option("--json", "Output as JSON").action(async (opts) => {
|
|
30785
|
-
const auth = resolveAuth();
|
|
30786
|
-
const spinner = opts.json ? null : ora("Loading messages...").start();
|
|
30787
|
-
try {
|
|
30788
|
-
const data = await amikoWebFetch(auth, `/api/conversations/${encodeURIComponent(opts.id)}/messages`, {
|
|
30789
|
-
query: {
|
|
30790
|
-
limit: opts.limit,
|
|
30791
|
-
before_cursor: opts.beforeCursor
|
|
30792
|
-
}
|
|
30793
|
-
});
|
|
30794
|
-
spinner?.stop();
|
|
30795
|
-
if (opts.json) {
|
|
30796
|
-
console.log(JSON.stringify(data, null, 2));
|
|
30797
|
-
return;
|
|
30798
|
-
}
|
|
30799
|
-
if (!data.messages.length) {
|
|
30800
|
-
console.log(dim("No messages."));
|
|
30801
|
-
return;
|
|
30802
|
-
}
|
|
30803
|
-
console.log(heading(`Messages — ${data.messages.length}`));
|
|
30804
|
-
for (const m of data.messages) {
|
|
30805
|
-
console.log("");
|
|
30806
|
-
console.log(label(m.name ?? m.sender_id ?? "?", m.created_at?.slice(0, 19) ?? ""));
|
|
30807
|
-
if (m.content)
|
|
30808
|
-
console.log(m.content);
|
|
30809
|
-
if (m.message_type && m.message_type !== "text")
|
|
30810
|
-
console.log(dim(`[${m.message_type}]`));
|
|
30811
|
-
console.log(dim(`id: ${m.id}`));
|
|
30812
|
-
}
|
|
30813
|
-
if (data.has_more && data.next_cursor) {
|
|
30814
|
-
console.log("");
|
|
30815
|
-
console.log(dim(`Older messages available — pass --before-cursor ${data.next_cursor}`));
|
|
30816
|
-
}
|
|
30817
|
-
} catch (e5) {
|
|
30818
|
-
spinner?.stop();
|
|
30819
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
30820
|
-
process.exit(1);
|
|
30821
|
-
}
|
|
30822
|
-
});
|
|
30823
|
-
program2.command("send").description("Send a message into a conversation").requiredOption("--id <conversationId>", "Target conversation id").requiredOption("--message <text>", "Message body").option("--reply-to <messageId>", "Reply to a specific message").option("--as-agent", "Send as the twin agent (sender_type=agent, is_auto_reply=true). Twin must be a participant.").option("--json", "Output as JSON").action(async (opts) => {
|
|
30824
|
-
const auth = requireAuth();
|
|
30825
|
-
const spinner = opts.json ? null : ora("Sending message...").start();
|
|
30826
|
-
try {
|
|
30827
|
-
const data = await amikoWebFetch(auth, `/api/conversations/${encodeURIComponent(opts.id)}/messages`, {
|
|
30828
|
-
method: "POST",
|
|
30829
|
-
body: {
|
|
30830
|
-
content: opts.message,
|
|
30831
|
-
message_type: "text",
|
|
30832
|
-
...opts.replyTo ? { reply_to_id: opts.replyTo } : {},
|
|
30833
|
-
...opts.asAgent ? { as_agent: true } : {}
|
|
30834
|
-
}
|
|
30835
|
-
});
|
|
30836
|
-
spinner?.stop();
|
|
30837
|
-
if (opts.json) {
|
|
30838
|
-
console.log(JSON.stringify(data, null, 2));
|
|
30839
|
-
return;
|
|
30840
|
-
}
|
|
30841
|
-
console.log(success("Message sent"));
|
|
30842
|
-
if (data.message?.id)
|
|
30843
|
-
console.log(label("Message ID", data.message.id));
|
|
30844
|
-
} catch (e5) {
|
|
30845
|
-
spinner?.stop();
|
|
30846
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
30847
|
-
process.exit(1);
|
|
30848
|
-
}
|
|
30849
|
-
});
|
|
30850
|
-
}
|
|
30851
|
-
|
|
30852
30954
|
// src/commands/notifications.ts
|
|
30853
30955
|
function registerNotificationsCommand(program2) {
|
|
30854
30956
|
program2.command("list").description("List platform notifications (friend requests, system alerts, activity)").option("--limit <n>", "Max results (1–100)", "20").option("--cursor <iso>", "Page older items via next_cursor from a prior call").option("--unread", "Show only unread").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -31513,7 +31615,7 @@ program2.name("amiko").description("Amiko CLI — swap tokens, manage credits, b
|
|
|
31513
31615
|
var markets = program2.command("markets").description("MPP marketplace — discover and call paid AMIKO services");
|
|
31514
31616
|
var wallets = program2.command("wallets").description("Manage twin wallets — list, swap, bridge");
|
|
31515
31617
|
var twin = program2.command("twin").description("Update twin identity (name, description, visibility)");
|
|
31516
|
-
var
|
|
31618
|
+
var drive = program2.command("drive").alias("docs").description("Manage twin drive — upload/download/search files, organise into folders");
|
|
31517
31619
|
var voice = program2.command("voice").description("Manage twin voice — design, create, clone, reset");
|
|
31518
31620
|
var avatar = program2.command("avatar").description("Manage twin avatar image");
|
|
31519
31621
|
var friends = program2.command("friends").description("Manage friendships — list, requests, add, accept, remove, reports, matches");
|
|
@@ -31521,7 +31623,6 @@ var users = program2.command("users").description("Search users and view public
|
|
|
31521
31623
|
var post = program2.command("post").description("Create posts and comments on the feed");
|
|
31522
31624
|
var review = program2.command("review").description("Review queue — list, approve, or reject twin-drafted comments");
|
|
31523
31625
|
var composio = program2.command("composio").description("Manage Composio app connections");
|
|
31524
|
-
var conversation = program2.command("conversation").description("Conversations — list, find, create, send messages between users/agents");
|
|
31525
31626
|
var notifications = program2.command("notifications").description("Platform notifications — list, mark as read");
|
|
31526
31627
|
registerCreditsCommand(program2);
|
|
31527
31628
|
registerSearchCommand(markets);
|
|
@@ -31539,7 +31640,7 @@ registerDiscoveryCommand(markets);
|
|
|
31539
31640
|
registerUpdateCommand(program2);
|
|
31540
31641
|
registerAccountsCommand(program2);
|
|
31541
31642
|
registerTwinCommand(twin);
|
|
31542
|
-
|
|
31643
|
+
registerDriveCommand(drive);
|
|
31543
31644
|
registerVoiceCommand(voice);
|
|
31544
31645
|
registerAvatarCommand(avatar);
|
|
31545
31646
|
registerFriendsCommand(friends);
|
|
@@ -31548,7 +31649,6 @@ registerFeedCommand(program2);
|
|
|
31548
31649
|
registerPostCommand(post);
|
|
31549
31650
|
registerReviewCommand(review);
|
|
31550
31651
|
registerComposioCommand(composio);
|
|
31551
|
-
registerConversationCommand(conversation);
|
|
31552
31652
|
registerNotificationsCommand(notifications);
|
|
31553
31653
|
registerMemoryCommand(program2);
|
|
31554
31654
|
program2.parse();
|
package/package.json
CHANGED
package/skills/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: amiko-cli
|
|
3
|
-
description: The Amiko CLI lets an agent act on the Amiko platform end-to-end — read
|
|
3
|
+
description: The Amiko CLI lets an agent act on the Amiko platform end-to-end — read platform notifications (friend requests, mentions, system alerts), search and write **cross-agent memories** about the owner (what other agents already know — preferences, decisions, facts), manage Solana/Base wallets (create, swap via Jupiter, bridge USDC via Across, transfer tokens to external addresses), top up and spend credits, call paid MPP marketplace services (X/Twitter search, image generation, Amazon product search, TTS/STT, AI chat, SFX/music), manage the twin's identity and drive (files, folders, RAG), voice and avatar, social graph (friends, posts, comments, feed), and Composio OAuth connections. Auth is automatic when run from the agent's workspace folder; payments are platform-custodied (no keys on disk). DMs / chat sessions are NOT in this CLI — use the openhermit gateway's built-in session_list / session_read tools for both local sessions and platform DMs. Use this skill whenever the user asks about their Amiko notifications, drive, memory, social graph, wallets, credits, or marketplace services — anything that would show up in their Amiko account or cost AMIKO/credits.
|
|
4
4
|
homepage: https://platform.heyamiko.com
|
|
5
5
|
metadata: {"openclaw":{"emoji":"🤖","requires":{"bins":["node"]}}}
|
|
6
6
|
---
|
|
@@ -20,9 +20,11 @@ Call your shell tool (your runtime calls it `bash`, `shell`, `run`, or similar)
|
|
|
20
20
|
| "what's my balance?" | shell → `amiko credits balance` |
|
|
21
21
|
| "top up 10k credits with AMIKO" | shell → `amiko credits topup 10000 --token AMIKO --yes` |
|
|
22
22
|
| "swap 1 SOL to USDC" | shell → `amiko wallets swap quote 1 SOL USDC` (then send with `--yes` after approval) |
|
|
23
|
-
| "did anyone DM me?" | shell → `amiko conversation list` |
|
|
24
23
|
| "any notifications?" | shell → `amiko notifications list --unread` |
|
|
25
24
|
| "any new posts I haven't seen?" | shell → `amiko feed --unread` |
|
|
25
|
+
| "upload report.pdf to my drive" | shell → `amiko drive upload ./report.pdf` |
|
|
26
|
+
| "download the file with id X" | shell → `amiko drive download X` |
|
|
27
|
+
| "find files about Q1 revenue" | shell → `amiko drive search "Q1 revenue"` |
|
|
26
28
|
| "what comments are on my post?" | shell → `amiko post comments --id <postId>` |
|
|
27
29
|
| "search memory for X" | shell → `amiko memory search "X"` |
|
|
28
30
|
| "what can amiko do?" | shell → `amiko --help` |
|
|
@@ -36,7 +38,9 @@ The CLI is installed globally and is pre-authenticated when you're inside your w
|
|
|
36
38
|
|
|
37
39
|
## Command groups
|
|
38
40
|
|
|
39
|
-
Discover everything via `amiko --help`. Groups: `markets` (paid MPP services), `wallets`, `credits`, `twin`, `docs
|
|
41
|
+
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`, `notifications`, `memory`, plus the top-level `accounts`, `info`, `config`, `update`. All twin-scoped commands accept `--twin <id>`. Most commands support `--json`.
|
|
42
|
+
|
|
43
|
+
> **DMs / chat sessions are NOT in this CLI.** Use the openhermit gateway's built-in `session_list` / `session_read` tools for both the agent's local sessions and platform DMs with other users. The previous `amiko conversation` namespace was removed in 0.10.1-beta.4.
|
|
40
44
|
|
|
41
45
|
## Critical Rules
|
|
42
46
|
|
|
@@ -83,6 +87,10 @@ Supported: AMIKO, SOL, USDC, USDT. Balance can lag a few seconds after topup —
|
|
|
83
87
|
|
|
84
88
|
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.
|
|
85
89
|
|
|
90
|
+
## Drive (files & folders)
|
|
91
|
+
|
|
92
|
+
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.
|
|
93
|
+
|
|
86
94
|
## Friends & relationships
|
|
87
95
|
|
|
88
96
|
### "How is my relationship with X?" playbook
|
|
@@ -110,18 +118,13 @@ Workflow: start with `friends matches --limit 20 --json`; narrow with `--dimensi
|
|
|
110
118
|
|
|
111
119
|
**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.
|
|
112
120
|
|
|
113
|
-
##
|
|
121
|
+
## Notifications
|
|
114
122
|
|
|
115
|
-
> **Default platform = Amiko.** When the owner asks about
|
|
123
|
+
> **Default platform = Amiko.** When the owner asks about notifications **without naming a platform**, assume Amiko and answer with `amiko notifications`. Only ask if they explicitly mention a non-Amiko channel.
|
|
116
124
|
>
|
|
117
|
-
> **
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
- `conversation find` filters client-side for a 2-user direct conversation containing both you and `--user-id`.
|
|
121
|
-
- `conversation create` is idempotent — server reuses an existing direct DM (`reused_existing: true`).
|
|
122
|
-
- `--user-id` expects the **internal** user id (from `amiko users search`), not a Privy DID or handle.
|
|
123
|
-
- `conversation send --as-agent` sends as the twin (`sender_type=agent`).
|
|
124
|
-
- Platform notifications cover friend requests, mentions, system alerts, and post-related events. They don't replace `conversation read` for actual chat content.
|
|
125
|
+
> **DMs / chat history are handled by the openhermit gateway**, not the CLI. For "did X message me?" / "check my chat with Y", use the built-in `session_list` / `session_read` tools — they cover both the agent's local sessions and platform DMs in the unified gateway view.
|
|
126
|
+
|
|
127
|
+
Platform notifications cover friend requests, mentions, system alerts, and post-related events.
|
|
125
128
|
|
|
126
129
|
## Memory (cross-agent) — READ THIS BEFORE ANSWERING ANYTHING ABOUT THE OWNER
|
|
127
130
|
|