@heyamiko/amiko-cli 0.10.1-beta.4 → 0.11.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # @heyamiko/amiko-cli (v0.10.1-beta.4)
1
+ # @heyamiko/amiko-cli (v0.11.0-beta.1)
2
2
 
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.
3
+ Manage wallets, credits, swaps, MPP marketplace services, and your Amiko twin (identity, documents, voice, avatar, friends, feed) from the terminal. Works for both human users and AI agents running on OpenClaw.
4
4
 
5
5
  ## Install
6
6
 
@@ -16,15 +16,15 @@ amiko update
16
16
 
17
17
  ## Quick Start
18
18
 
19
- Auth is loaded from `.amiko.json` found in the current working directory (or any parent). The only required field is `amikoTwinToken`. The CLI walks upward from cwd; if none is found it falls back to `/data/.openclaw/workspace/.amiko.json`.
19
+ Auth is loaded from environment variables on most hosts:
20
20
 
21
- ```json
22
- {
23
- "amikoTwinToken": "clawd-...",
24
- "amikoTwinId": "twin_..."
25
- }
21
+ ```bash
22
+ export AMIKO_TWIN_TOKEN="clawd-..."
23
+ export AMIKO_TWIN_ID="twin_..."
26
24
  ```
27
25
 
26
+ On OpenClaw twin containers the platform writes `/data/.openclaw/workspace/.amiko.json` during init and the CLI reads it automatically — no env vars needed. See [Authentication](#authentication) for the full resolution order and all supported keys.
27
+
28
28
  All paid operations (credits topup, wallet transfer, swap) are signed by the Amiko platform on your behalf — the CLI never holds private keys.
29
29
 
30
30
  ```bash
@@ -250,25 +250,28 @@ This registers the CLI's skill file so the agent discovers all commands through
250
250
 
251
251
  ### Authentication
252
252
 
253
- Agents auto-authenticate via `.amiko.json` — no manual connect needed. The platform writes this file during twin init with wallets, billing config, and twin token.
253
+ Resolution order (first match wins):
254
+
255
+ 1. **OpenClaw only**: `/data/.openclaw/workspace/.amiko.json` (written by the platform during twin init; the CLI is detected as OpenClaw when `/data/.openclaw` exists *and* the process cwd is under `/data/.openclaw/workspace`).
256
+ 2. **Everywhere else**: environment variables.
257
+
258
+ Non-OpenClaw hosts do **not** read any `.amiko.json` — cwd doesn't matter, and the CLI never walks the filesystem looking for auth.
254
259
 
255
260
  ```bash
256
- # Agent environment — just works
261
+ # Local / CI / containers / etc. — env vars
262
+ export AMIKO_TWIN_TOKEN="clawd-..."
263
+ export AMIKO_TWIN_ID="twin_..."
257
264
  amiko credits balance
258
- amiko markets search "trending topics"
259
- amiko credits topup 10000 --token AMIKO --yes
260
265
  ```
261
266
 
262
- The CLI finds `.amiko.json` by:
263
- 1. Walking up from the current working directory.
264
- 2. Falling back to `/data/.openclaw/workspace/.amiko.json`.
267
+ Supported env vars:
268
+ - `AMIKO_TWIN_TOKEN` — **required** auth token
269
+ - `AMIKO_TWIN_ID` — twin identifier (required for paid ops)
270
+ - `AMIKO_USER_ID` — user identifier (optional, for MPP topup)
271
+ - `AMIKO_PLATFORM_URL` — platform base URL override
272
+ - `AMIKO_API_BASE` — MPP service URL override
265
273
 
266
- Supported keys (both camelCase and SCREAMING_SNAKE_CASE):
267
- - `amikoTwinToken` / `AMIKO_TWIN_TOKEN` — **required** auth token
268
- - `amikoTwinId` / `AMIKO_TWIN_ID` — twin identifier (needed for paid ops)
269
- - `amikoUserId` / `AMIKO_USER_ID` — user identifier (optional, for MPP topup)
270
- - `amikoPlatformUrl` / `AMIKO_PLATFORM_URL` — platform base URL override
271
- - `apiBase` — MPP service URL override
274
+ The `.amiko.json` file (OpenClaw only) accepts the same keys in either camelCase (`amikoTwinToken`, `amikoTwinId`, ...) or SCREAMING_SNAKE_CASE form.
272
275
 
273
276
  ## Configuration
274
277
 
@@ -303,8 +306,7 @@ src/
303
306
  │ ├── avatar.ts # avatar update
304
307
  │ ├── friends.ts # friends list, requests, add, accept, remove, matches, reports
305
308
  │ ├── users.ts # users search, profile
306
- │ ├── feed.ts # feed, post create, post comment
307
- │ └── composio.ts # composio connect, composio disconnect
309
+ │ └── feed.ts # feed, post create, post comment
308
310
  └── lib/
309
311
  ├── config.ts # defaults + resolved auth wrapper
310
312
  ├── agent-config.ts # .amiko.json reader + auth resolution
@@ -320,7 +322,7 @@ src/
320
322
 
321
323
  ## Twin & Social Commands (v0.8.0)
322
324
 
323
- The CLI also manages the twin's identity, documents, voice, avatar, friends, feed, and Composio connections via the amiko-web platform API.
325
+ The CLI also manages the twin's identity, documents, voice, avatar, friends, and feed via the amiko-web platform API.
324
326
 
325
327
  ```bash
326
328
  # Identity
@@ -367,21 +369,17 @@ amiko post create --content "private note" --visibility private
367
369
  amiko post create --content "look" --media https://...jpg
368
370
  amiko post comment --id <postId> --comment "great post"
369
371
  amiko post comment --id <postId> --comment "nice!" --media https://...jpg
370
-
371
- # Composio
372
- amiko composio connect --app gmail # prints OAuth URL
373
- amiko composio disconnect --app gmail --yes
374
372
  ```
375
373
 
376
374
  Every new command accepts `--json` for structured output and `--twin <id>` where a target twin is needed.
377
375
 
378
- **Destructive or identity-changing commands** (`docs delete`, `friends remove`, `composio disconnect`, `voice reset`, `avatar update`, `twin update --public`) prompt for confirmation in a TTY and require `--yes` in non-interactive shells.
376
+ **Destructive or identity-changing commands** (`docs delete`, `friends remove`, `voice reset`, `avatar update`, `twin update --public`) prompt for confirmation in a TTY and require `--yes` in non-interactive shells.
379
377
 
380
378
  ## API Endpoints
381
379
 
382
380
  | Client | Base URL | Purpose |
383
381
  |--------|----------|---------|
384
- | Platform | `platform.heyamiko.com` | Twin identity, wallets, credits, transfers/swaps, docs, voice, avatar, friends, feed, Composio |
382
+ | Platform | `platform.heyamiko.com` | Twin identity, wallets, credits, transfers/swaps, docs, voice, avatar, friends, feed |
385
383
  | MPP API | `mpp.amikomarkets.com` | Marketplace services: search, image, amazon, audio |
386
384
 
387
385
  ## Build
@@ -393,6 +391,16 @@ npm publish
393
391
 
394
392
  ## Changelog
395
393
 
394
+ ### 0.11.0-beta.1
395
+
396
+ - **`amiko composio` removed (breaking).** The Composio OAuth-connection commands (`composio connect`, `composio disconnect`) are gone — `src/commands/composio.ts` deleted, `composio` command group dropped from `src/index.ts`. SKILL.md frontmatter, command-groups list, `--yes`-gated list, and the dedicated `## Composio` block are all removed; README's lead paragraph, architecture tree, destructive-ops list, Platform-table row, and Composio code-block example are updated to match. Scripts that called `amiko composio ...` will now error with "unknown command".
397
+
398
+ ### 0.11.0-beta.0
399
+
400
+ - **Non-OpenClaw auth is env-var-only (breaking).** `resolveAuth()` no longer reads `.amiko.json` from cwd or anywhere except `/data/.openclaw/workspace/.amiko.json` (gated on `isOpenclawEnvironment()`). The previous "check `cwd/.amiko.json`, then fall back to env" path is gone — on local/CI/non-OpenClaw containers the CLI looks at `AMIKO_TWIN_TOKEN` / `AMIKO_TWIN_ID` directly and ignores any `.amiko.json` you might have lying around. cwd no longer affects auth anywhere off OpenClaw. README's Quick Start + Agent Integration → Authentication sections rewritten; `NO_TOKEN_HINT` points at the env vars (no more "place .amiko.json in cwd" tip).
401
+ - **`amiko memory sync` removed, replaced by `amiko memory upload <file...>` (breaking).** The old `sync` command walked a `MEMORY.md` index plus `memory/**/*.md` under the OpenClaw workspace — useless for any agent that doesn't store memory on disk in that layout. `upload` takes one or more explicit paths instead: single file → `POST /api/memory-files`, multiple → batched `POST /api/memory-files/bulk` (100/batch). Default server-side path key is `basename(file)`; `--key <key>` overrides it but only for a single-file upload. Duplicate basenames within one call error out before any request fires. `--dry-run` and `--prune` are gone — for prune behavior use `memory rm` against listed memories. `memory status` unchanged (still shows memory_files count, which is still meaningful for uploaded files). All the local-workspace scanning helpers (`collectMemoryFiles`, `walkMarkdown`, `extractMarkdownLinks`, `isInsideRoot`, `fetchAllMemoryFiles`, `DEFAULT_WORKSPACE = "/data/.openclaw/workspace"`) are deleted; `src/commands/memory.ts` drops from ~630 to ~360 lines. Cap per upload: 1 MB (`MAX_UPLOAD_BYTES`).
402
+ - SKILL.md updated: the "Sync periodically — Run `amiko memory sync` ..." instruction is now "Upload changed files with `amiko memory upload`"; no other behavior change.
403
+
396
404
  ### 0.10.1-beta.4
397
405
 
398
406
  - **`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".
package/dist/index.js CHANGED
@@ -25832,7 +25832,6 @@ var source_default = chalk;
25832
25832
 
25833
25833
  // src/lib/agent-config.ts
25834
25834
  import { existsSync, readFileSync, statSync } from "node:fs";
25835
- import { join } from "node:path";
25836
25835
  var OPENCLAW_ROOT = "/data/.openclaw";
25837
25836
  var OPENCLAW_WORKSPACE_PREFIX = "/data/.openclaw/workspace";
25838
25837
  var OPENCLAW_AMIKO_JSON = "/data/.openclaw/workspace/.amiko.json";
@@ -25846,9 +25845,6 @@ function isOpenclawEnvironment() {
25846
25845
  return process.cwd().startsWith(OPENCLAW_WORKSPACE_PREFIX);
25847
25846
  }
25848
25847
  function findAmikoJsonPath() {
25849
- const cwdPath = join(process.cwd(), ".amiko.json");
25850
- if (existsSync(cwdPath))
25851
- return cwdPath;
25852
25848
  if (isOpenclawEnvironment() && existsSync(OPENCLAW_AMIKO_JSON)) {
25853
25849
  return OPENCLAW_AMIKO_JSON;
25854
25850
  }
@@ -25889,13 +25885,13 @@ function resolveAuth() {
25889
25885
  }
25890
25886
  return readFromEnv();
25891
25887
  }
25892
- var NO_TOKEN_HINT = "No twin token found. Set AMIKO_TWIN_TOKEN env var, or place .amiko.json with amikoTwinToken in the current directory.";
25888
+ var NO_TOKEN_HINT = "No twin token found. Set AMIKO_TWIN_TOKEN and AMIKO_TWIN_ID environment variables.";
25893
25889
  function requireAuth() {
25894
25890
  const auth = resolveAuth();
25895
25891
  if (!auth.twinToken)
25896
25892
  throw new Error(NO_TOKEN_HINT);
25897
25893
  if (!auth.twinId) {
25898
- throw new Error("No twin id found. Set AMIKO_TWIN_ID env var, or add amikoTwinId to .amiko.json.");
25894
+ throw new Error("No twin id found. Set AMIKO_TWIN_ID env var.");
25899
25895
  }
25900
25896
  return auth;
25901
25897
  }
@@ -27067,7 +27063,7 @@ function baseUrl(auth) {
27067
27063
  function authHeader(auth) {
27068
27064
  if (auth.twinToken)
27069
27065
  return `Bearer ${auth.twinToken}`;
27070
- throw new Error("No twin token found. Set AMIKO_TWIN_TOKEN env var, or place .amiko.json with amikoTwinToken in the current directory.");
27066
+ throw new Error("No twin token found. Set AMIKO_TWIN_TOKEN and AMIKO_TWIN_ID environment variables.");
27071
27067
  }
27072
27068
  async function amikoWebFetch(auth, path, opts = {}) {
27073
27069
  const url = new URL(`${baseUrl(auth)}${path}`);
@@ -30870,87 +30866,6 @@ function registerReviewCommand(program2) {
30870
30866
  });
30871
30867
  }
30872
30868
 
30873
- // src/commands/composio.ts
30874
- function twinFromFlag3(opts) {
30875
- const config2 = loadConfig();
30876
- const auth = resolveAuth();
30877
- try {
30878
- return { twinId: resolveTwinId({ flag: opts.twin, config: config2, auth }), auth };
30879
- } catch (e5) {
30880
- if (e5 instanceof TwinResolutionError) {
30881
- console.error(error(e5.message));
30882
- process.exit(1);
30883
- }
30884
- throw e5;
30885
- }
30886
- }
30887
- function registerComposioCommand(program2) {
30888
- program2.command("connect").description("Start Composio OAuth for an app (gmail, github, ...)").requiredOption("--app <slug>", "App name (e.g. gmail)").option("--twin <idOrName>", "Target twin id or name").option("--redirect <url>", "Override OAuth redirect URL").option("--force", "Force reconnect if already connected").option("--json", "Output as JSON").action(async (opts) => {
30889
- const { twinId, auth } = twinFromFlag3(opts);
30890
- const spinner = opts.json ? null : ora(`Connecting ${opts.app}...`).start();
30891
- try {
30892
- const data = await amikoWebFetch(auth, `/api/agents/${twinId}/composio/connect`, {
30893
- method: "POST",
30894
- body: {
30895
- appName: opts.app,
30896
- redirectUrl: opts.redirect,
30897
- forceReconnect: opts.force ?? false
30898
- }
30899
- });
30900
- spinner?.stop();
30901
- renderOutput(data, (d) => {
30902
- if (d.alreadyConnected) {
30903
- console.log(success(`${opts.app} is already connected`));
30904
- if (d.connectionId)
30905
- console.log(label("Connection ID", d.connectionId));
30906
- return;
30907
- }
30908
- console.log(success(`OAuth URL ready for ${opts.app}`));
30909
- if (d.redirectUrl) {
30910
- console.log("");
30911
- console.log(d.redirectUrl);
30912
- console.log("");
30913
- console.log(dim("Open this URL in a browser to complete the connection."));
30914
- }
30915
- if (d.connectionId)
30916
- console.log(label("Connection ID", d.connectionId));
30917
- }, { json: opts.json });
30918
- } catch (e5) {
30919
- spinner?.stop();
30920
- console.error(error(e5 instanceof Error ? e5.message : String(e5)));
30921
- process.exit(1);
30922
- }
30923
- });
30924
- program2.command("disconnect").description("Disconnect a Composio app for the twin").requiredOption("--app <slug>", "App name (e.g. gmail)").option("--twin <idOrName>", "Target twin id or name").option("--yes", "Skip confirmation").option("--json", "Output as JSON").action(async (opts) => {
30925
- const { twinId, auth } = twinFromFlag3(opts);
30926
- await confirmDestructive({
30927
- action: `Disconnect ${opts.app}`,
30928
- detail: "The twin will lose access to the app's tools until reconnected.",
30929
- yes: opts.yes,
30930
- commandExample: `amiko composio disconnect --app ${opts.app}`
30931
- });
30932
- const spinner = opts.json ? null : ora("Finding connection...").start();
30933
- try {
30934
- const connections = await amikoWebFetch(auth, `/api/agents/${twinId}/composio/connections`);
30935
- const match = connections.details?.find((d) => d.appName?.toLowerCase() === opts.app.toLowerCase());
30936
- if (!match?.id) {
30937
- spinner?.stop();
30938
- console.error(error(`No active connection for "${opts.app}". Connected: ${connections.connections?.join(", ") || "none"}`));
30939
- process.exit(1);
30940
- }
30941
- if (spinner)
30942
- spinner.text = `Disconnecting ${opts.app}...`;
30943
- const result = await amikoWebFetch(auth, `/api/agents/${twinId}/composio/connections`, { method: "DELETE", body: { connectionId: match.id } });
30944
- spinner?.stop();
30945
- renderOutput(result, () => console.log(success(`Disconnected ${opts.app}`)), { json: opts.json });
30946
- } catch (e5) {
30947
- spinner?.stop();
30948
- console.error(error(e5 instanceof Error ? e5.message : String(e5)));
30949
- process.exit(1);
30950
- }
30951
- });
30952
- }
30953
-
30954
30869
  // src/commands/notifications.ts
30955
30870
  function registerNotificationsCommand(program2) {
30956
30871
  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) => {
@@ -31019,16 +30934,12 @@ function registerNotificationsCommand(program2) {
31019
30934
  }
31020
30935
 
31021
30936
  // src/commands/memory.ts
31022
- import { existsSync as existsSync2, lstatSync, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
31023
- import { dirname, isAbsolute, normalize, relative, resolve } from "node:path";
31024
- function isInsideRoot(abs, root) {
31025
- const rel = relative(root, abs);
31026
- return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
31027
- }
30937
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
30938
+ import { basename as basename3, resolve } from "node:path";
31028
30939
  var VALID_CATEGORIES = ["fact", "preference", "pattern", "decision", "context"];
31029
30940
  var VALID_SOURCES = ["chat", "file", "manual"];
31030
- var DEFAULT_WORKSPACE = "/data/.openclaw/workspace";
31031
- var SYNC_BATCH_SIZE = 100;
30941
+ var UPLOAD_BATCH_SIZE = 100;
30942
+ var MAX_UPLOAD_BYTES = 1e6;
31032
30943
  function fail(msg) {
31033
30944
  console.error(error(msg));
31034
30945
  process.exit(1);
@@ -31053,7 +30964,7 @@ function fmtMemory(m) {
31053
30964
  console.log(m.content);
31054
30965
  }
31055
30966
  function registerMemoryCommand(program2) {
31056
- const memory = program2.command("memory").description("Manage cross-agent memories — add, list, search, remove, sync local files");
30967
+ const memory = program2.command("memory").description("Manage cross-agent memories — add, list, search, remove, upload files");
31057
30968
  memory.command("add <content>").description("Add a single memory directly").option("--category <cat>", `Category (${VALID_CATEGORIES.join("|")})`, "fact").option("--tags <list>", "Comma-separated tags (max 10)").option("--source <src>", `Source (${VALID_SOURCES.join("|")})`, "manual").option("--twin <twinId>", "Override twin_id (defaults to scopedTwinId)").option("--json", "Output as JSON").action(async (content, opts) => {
31058
30969
  if (!content || !content.trim())
31059
30970
  fail("Content is required and cannot be empty.");
@@ -31172,7 +31083,7 @@ function registerMemoryCommand(program2) {
31172
31083
  fail(e5 instanceof Error ? e5.message : String(e5));
31173
31084
  }
31174
31085
  });
31175
- memory.command("status").description("Show counts of memories and synced memory files").option("--json", "Output as JSON").action(async (opts) => {
31086
+ memory.command("status").description("Show counts of memories and uploaded memory files").option("--json", "Output as JSON").action(async (opts) => {
31176
31087
  const auth = resolveAuth();
31177
31088
  const spinner = opts.json ? null : ora("Loading status...").start();
31178
31089
  try {
@@ -31194,79 +31105,89 @@ function registerMemoryCommand(program2) {
31194
31105
  fail(e5 instanceof Error ? e5.message : String(e5));
31195
31106
  }
31196
31107
  });
31197
- memory.command("sync").description("Sync MEMORY.md, its referenced files, and every .md under <workspace>/memory/ (one-way: local → server)").option("--path <dir>", `Workspace dir (default ${DEFAULT_WORKSPACE}). Looks for MEMORY.md, memory/MEMORY.md, and memory/**/*.md`).option("--dry-run", "Print what would be synced without uploading").option("--prune", "Delete server-side memory files that no longer exist locally").option("--json", "Output as JSON").action(async (opts) => {
31198
- const root = resolve(opts.path || DEFAULT_WORKSPACE);
31199
- const candidateIndexes = [
31200
- resolve(root, "MEMORY.md"),
31201
- resolve(root, "memory", "MEMORY.md")
31202
- ].filter((p) => existsSync2(p));
31203
- const memoryDir = resolve(root, "memory");
31204
- const memoryDirExists = existsSync2(memoryDir) && statSync2(memoryDir).isDirectory();
31205
- if (!candidateIndexes.length && !memoryDirExists) {
31206
- fail(`No MEMORY.md or memory/ folder found under ${root}. Expected ${root}/MEMORY.md or ${root}/memory/.`);
31207
- }
31208
- const collected = collectMemoryFiles(root, candidateIndexes, memoryDirExists ? memoryDir : null);
31209
- if (collected.errors.length && !opts.json) {
31210
- for (const e5 of collected.errors)
31211
- console.error(error(e5));
31212
- }
31213
- const files = collected.files;
31214
- if (opts.dryRun) {
31215
- renderOutput({ root, files: files.map((f) => f.path), prune: !!opts.prune }, (d) => {
31216
- console.log(heading(`Dry run — ${d.files.length} file(s)`));
31217
- for (const p of d.files)
31218
- console.log(` ${p}`);
31219
- if (d.prune)
31220
- console.log(dim("--prune would remove server files not listed above."));
31221
- }, { json: opts.json });
31222
- return;
31108
+ memory.command("upload <file...>").description("Upload one or more text files as memory documents (server extracts memories from each). Path key defaults to basename.").option("--key <key>", "Override the server-side path key (single file only)").option("--twin <twinId>", "Override twin_id (defaults to scopedTwinId)").option("--json", "Output as JSON").action(async (files, opts) => {
31109
+ if (!files || files.length === 0)
31110
+ fail("At least one file path is required.");
31111
+ const keyOverride = opts.key !== undefined ? opts.key.trim() : undefined;
31112
+ if (opts.key !== undefined && !keyOverride) {
31113
+ fail("--key cannot be empty or whitespace-only.");
31223
31114
  }
31224
- if (!files.length) {
31225
- renderOutput({ synced: 0, changed: 0, errors: 0, extracted: 0 }, () => console.log(dim("Nothing to sync.")), { json: opts.json });
31226
- return;
31115
+ if (keyOverride && files.length > 1) {
31116
+ fail("--key can only be used when uploading a single file.");
31117
+ }
31118
+ const prepared = [];
31119
+ const seenKeys = new Set;
31120
+ for (const f of files) {
31121
+ const abs = resolve(f);
31122
+ if (!existsSync2(abs))
31123
+ fail(`File not found: ${f}`);
31124
+ const st = statSync2(abs);
31125
+ if (!st.isFile())
31126
+ fail(`Not a regular file: ${f}`);
31127
+ if (st.size > MAX_UPLOAD_BYTES) {
31128
+ fail(`${f} is ${st.size} bytes; max ${MAX_UPLOAD_BYTES}.`);
31129
+ }
31130
+ let content;
31131
+ try {
31132
+ content = readFileSync2(abs, "utf-8");
31133
+ } catch (e5) {
31134
+ fail(`Failed to read ${f}: ${e5 instanceof Error ? e5.message : String(e5)}`);
31135
+ }
31136
+ const key = keyOverride && files.length === 1 ? keyOverride : basename3(abs);
31137
+ if (seenKeys.has(key)) {
31138
+ fail(`Duplicate path key "${key}" within this upload. Run separately or pass --key per file.`);
31139
+ }
31140
+ seenKeys.add(key);
31141
+ prepared.push({ path: key, content, source: f });
31227
31142
  }
31228
31143
  const auth = resolveAuth();
31229
- const spinner = opts.json ? null : ora(`Syncing ${files.length} file(s)...`).start();
31144
+ const spinner = opts.json ? null : ora(`Uploading ${prepared.length} file(s)...`).start();
31230
31145
  try {
31231
- const allResults = [];
31232
- for (let i = 0;i < files.length; i += SYNC_BATCH_SIZE) {
31233
- const chunk = files.slice(i, i + SYNC_BATCH_SIZE);
31234
- const data = await amikoWebFetch(auth, "/api/memory-files/bulk", {
31146
+ let results;
31147
+ if (prepared.length === 1) {
31148
+ const f = prepared[0];
31149
+ const data = await amikoWebFetch(auth, "/api/memory-files", {
31235
31150
  method: "POST",
31236
- body: { files: chunk }
31151
+ body: {
31152
+ path: f.path,
31153
+ content: f.content,
31154
+ twin_id: opts.twin
31155
+ },
31156
+ timeoutMs: 120000
31237
31157
  });
31238
- allResults.push(...data.results);
31239
- }
31240
- let pruned = 0;
31241
- const pruneErrors = [];
31242
- if (opts.prune) {
31243
- const localPaths = new Set(files.map((f) => f.path));
31244
- const remote = await fetchAllMemoryFiles(auth);
31245
- const stale = remote.filter((f) => !localPaths.has(f.path));
31246
- for (const f of stale) {
31247
- try {
31248
- await amikoWebFetch(auth, `/api/memory-files/${encodeURIComponent(f.id)}`, { method: "DELETE" });
31249
- pruned++;
31250
- } catch (e5) {
31251
- pruneErrors.push(`${f.path}: ${e5 instanceof Error ? e5.message : String(e5)}`);
31158
+ results = [
31159
+ {
31160
+ path: f.path,
31161
+ file_id: data.file_id ?? data.id,
31162
+ changed: data.changed,
31163
+ extracted: data.extracted
31252
31164
  }
31165
+ ];
31166
+ } else {
31167
+ results = [];
31168
+ for (let i = 0;i < prepared.length; i += UPLOAD_BATCH_SIZE) {
31169
+ const chunk = prepared.slice(i, i + UPLOAD_BATCH_SIZE);
31170
+ const data = await amikoWebFetch(auth, "/api/memory-files/bulk", {
31171
+ method: "POST",
31172
+ body: {
31173
+ files: chunk.map((c) => ({
31174
+ path: c.path,
31175
+ content: c.content,
31176
+ twin_id: opts.twin
31177
+ }))
31178
+ },
31179
+ timeoutMs: 120000
31180
+ });
31181
+ results.push(...data.results);
31253
31182
  }
31254
31183
  }
31255
31184
  spinner?.stop();
31256
- const changed = allResults.filter((r) => r.changed).length;
31257
- const errors3 = allResults.filter((r) => r.error).length;
31258
- const extracted = allResults.reduce((acc, r) => acc + (r.extracted ?? 0), 0);
31259
- renderOutput({
31260
- synced: allResults.length,
31261
- changed,
31262
- errors: errors3,
31263
- extracted,
31264
- pruned,
31265
- prune_errors: pruneErrors,
31266
- results: allResults
31267
- }, (d) => {
31268
- console.log(success(`${d.synced} file(s) synced (${d.changed} changed, ${d.errors} errors), ${d.extracted} memories extracted`));
31269
- for (const r of allResults) {
31185
+ const changed = results.filter((r) => r.changed).length;
31186
+ const errors3 = results.filter((r) => r.error).length;
31187
+ const extracted = results.reduce((acc, r) => acc + (r.extracted ?? 0), 0);
31188
+ renderOutput({ uploaded: results.length, changed, errors: errors3, extracted, results }, (d) => {
31189
+ console.log(success(`${d.uploaded} file(s) uploaded (${d.changed} changed, ${d.errors} errors), ${d.extracted} memories extracted`));
31190
+ for (const r of results) {
31270
31191
  if (r.error) {
31271
31192
  console.log(error(`${r.path}: ${r.error}`));
31272
31193
  } else if (r.changed) {
@@ -31275,12 +31196,6 @@ function registerMemoryCommand(program2) {
31275
31196
  console.log(` ${r.path} ${dim("(unchanged)")}`);
31276
31197
  }
31277
31198
  }
31278
- if (opts.prune) {
31279
- console.log("");
31280
- console.log(label("Pruned", String(d.pruned)));
31281
- for (const e5 of pruneErrors)
31282
- console.log(error(e5));
31283
- }
31284
31199
  }, { json: opts.json });
31285
31200
  } catch (e5) {
31286
31201
  spinner?.stop();
@@ -31288,126 +31203,6 @@ function registerMemoryCommand(program2) {
31288
31203
  }
31289
31204
  });
31290
31205
  }
31291
- function collectMemoryFiles(root, indexPaths, memoryDir) {
31292
- const errors3 = [];
31293
- const seen = new Set;
31294
- const files = [];
31295
- for (const indexPath of indexPaths) {
31296
- const indexContent = safeRead(indexPath, errors3);
31297
- if (indexContent === null)
31298
- continue;
31299
- pushFile(files, seen, indexPath, indexContent, root);
31300
- const indexDir = dirname(indexPath);
31301
- for (const ref of extractMarkdownLinks(indexContent)) {
31302
- if (isExternal(ref))
31303
- continue;
31304
- const abs = normalize(isAbsolute(ref) ? ref : resolve(indexDir, ref));
31305
- if (!isInsideRoot(abs, root)) {
31306
- errors3.push(`Skipping ${ref}: outside workspace ${root}`);
31307
- continue;
31308
- }
31309
- if (!existsSync2(abs)) {
31310
- errors3.push(`Referenced file missing: ${ref}`);
31311
- continue;
31312
- }
31313
- const st = lstatSync(abs);
31314
- if (st.isSymbolicLink()) {
31315
- errors3.push(`Skipping ${ref}: symlinks not followed`);
31316
- continue;
31317
- }
31318
- if (!st.isFile())
31319
- continue;
31320
- const content = safeRead(abs, errors3);
31321
- if (content === null)
31322
- continue;
31323
- pushFile(files, seen, abs, content, root);
31324
- }
31325
- }
31326
- if (memoryDir) {
31327
- for (const abs of walkMarkdown(memoryDir, errors3)) {
31328
- const content = safeRead(abs, errors3);
31329
- if (content === null)
31330
- continue;
31331
- pushFile(files, seen, abs, content, root);
31332
- }
31333
- }
31334
- return { files, errors: errors3 };
31335
- }
31336
- function walkMarkdown(dir, errors3) {
31337
- const out = [];
31338
- let names;
31339
- try {
31340
- names = readdirSync(dir);
31341
- } catch (e5) {
31342
- errors3.push(`Failed to read ${dir}: ${e5 instanceof Error ? e5.message : String(e5)}`);
31343
- return out;
31344
- }
31345
- for (const name of names) {
31346
- const abs = resolve(dir, name);
31347
- let st;
31348
- try {
31349
- st = lstatSync(abs);
31350
- } catch {
31351
- continue;
31352
- }
31353
- if (st.isSymbolicLink())
31354
- continue;
31355
- if (st.isDirectory()) {
31356
- out.push(...walkMarkdown(abs, errors3));
31357
- } else if (st.isFile() && name.toLowerCase().endsWith(".md")) {
31358
- out.push(abs);
31359
- }
31360
- }
31361
- return out;
31362
- }
31363
- function pushFile(files, seen, abs, content, root) {
31364
- const key = relativeKey(abs, root);
31365
- if (seen.has(key))
31366
- return;
31367
- seen.add(key);
31368
- files.push({ path: key, content });
31369
- }
31370
- function relativeKey(abs, root) {
31371
- const rel = abs.startsWith(root) ? abs.slice(root.length) : abs;
31372
- return rel.replace(/^\/+/, "");
31373
- }
31374
- function safeRead(path2, errors3) {
31375
- try {
31376
- return readFileSync2(path2, "utf-8");
31377
- } catch (e5) {
31378
- errors3.push(`Failed to read ${path2}: ${e5 instanceof Error ? e5.message : String(e5)}`);
31379
- return null;
31380
- }
31381
- }
31382
- function extractMarkdownLinks(md) {
31383
- const out = [];
31384
- const re = /\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
31385
- let m;
31386
- while ((m = re.exec(md)) !== null) {
31387
- const target = m[1].split("#")[0];
31388
- if (target)
31389
- out.push(target);
31390
- }
31391
- return out;
31392
- }
31393
- function isExternal(ref) {
31394
- return /^[a-z][a-z0-9+.-]*:/i.test(ref) || ref.startsWith("//");
31395
- }
31396
- async function fetchAllMemoryFiles(auth) {
31397
- const all = [];
31398
- let offset = 0;
31399
- const limit = 100;
31400
- for (;; ) {
31401
- const data = await amikoWebFetch(auth, "/api/memory-files", {
31402
- query: { limit: String(limit), offset: String(offset) }
31403
- });
31404
- all.push(...data.items ?? []);
31405
- if (!data.has_more || !data.items?.length)
31406
- break;
31407
- offset += data.items.length;
31408
- }
31409
- return all;
31410
- }
31411
31206
 
31412
31207
  // src/commands/wallets.ts
31413
31208
  function formatBalance(b) {
@@ -31622,7 +31417,6 @@ var friends = program2.command("friends").description("Manage friendships — li
31622
31417
  var users = program2.command("users").description("Search users and view public profiles");
31623
31418
  var post = program2.command("post").description("Create posts and comments on the feed");
31624
31419
  var review = program2.command("review").description("Review queue — list, approve, or reject twin-drafted comments");
31625
- var composio = program2.command("composio").description("Manage Composio app connections");
31626
31420
  var notifications = program2.command("notifications").description("Platform notifications — list, mark as read");
31627
31421
  registerCreditsCommand(program2);
31628
31422
  registerSearchCommand(markets);
@@ -31648,7 +31442,6 @@ registerUsersCommand(users);
31648
31442
  registerFeedCommand(program2);
31649
31443
  registerPostCommand(post);
31650
31444
  registerReviewCommand(review);
31651
- registerComposioCommand(composio);
31652
31445
  registerNotificationsCommand(notifications);
31653
31446
  registerMemoryCommand(program2);
31654
31447
  program2.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.10.1-beta.4",
3
+ "version": "0.11.0-beta.1",
4
4
  "description": "Amiko CLI — swap tokens, manage credits, bridge cross-chain, and call marketplace agents",
5
5
  "type": "module",
6
6
  "bin": {
package/skills/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: amiko-cli
3
- description: The Amiko CLI lets an agent act on the Amiko platform end-to-end — read platform notifications (friend requests, mentions, system alerts), search and write **cross-agent memories** about the owner (what other agents already know — preferences, decisions, facts), manage Solana/Base wallets (create, swap via Jupiter, bridge USDC via Across, transfer tokens to external addresses), top up and spend credits, call paid MPP marketplace services (X/Twitter search, 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.
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, and social graph (friends, posts, comments, feed). Auth is automatic when run from the agent's workspace folder; payments are platform-custodied (no keys on disk). 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
  ---
@@ -38,13 +38,13 @@ The CLI is installed globally and is pre-authenticated when you're inside your w
38
38
 
39
39
  ## Command groups
40
40
 
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`.
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`, `notifications`, `memory`, plus the top-level `accounts`, `info`, `config`, `update`. All twin-scoped commands accept `--twin <id>`. Most commands support `--json`.
42
42
 
43
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.
44
44
 
45
45
  ## Critical Rules
46
46
 
47
- 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
+ 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`, `drive delete`, `friends remove`, `friends reports request`, `avatar update`, `voice reset`, `review reject`) unless `--yes` is passed. The refusal prints the cost and the re-run command. **Quote the cost, get explicit approval, THEN append `--yes`.**
48
48
  2. **After every paid command, report the remaining balance.** The CLI prints a `Balance: N credits` line — include that figure in your reply.
49
49
  3. **Never retry a failed command.** Report and stop. Every paid call costs tokens even on failure.
50
50
  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`.
@@ -133,7 +133,7 @@ Memories are scoped to the **owner's `user_id`**, not your agent — every agent
133
133
  **Two non-negotiable habits:**
134
134
 
135
135
  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.
136
- 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.
136
+ 2. **Upload changed files.** If you keep memory in local files (e.g. `MEMORY.md`, `memory/*.md`), upload them with `amiko memory upload <file...>` after meaningful edits. The server extracts memories from each file; default path key is the basename. No bulk-walking — pass the exact files you want to push.
137
137
 
138
138
  ### When to search — bias toward calling
139
139
 
@@ -163,10 +163,6 @@ Categories: `fact` | `preference` | `pattern` | `decision` | `context`.
163
163
 
164
164
  Do NOT save trivial code-derivable facts, ephemeral `git log` state, or duplicates (search first; `rm` near-matches before adding).
165
165
 
166
- ## Composio
167
-
168
- `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`).
169
-
170
166
  ## Where to run
171
167
 
172
168
  **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.