@heyamiko/amiko-cli 0.9.9-beta.1 → 0.9.9-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/dist/index.js CHANGED
@@ -22013,7 +22013,7 @@ function registerNotificationsCommand(program2) {
22013
22013
  }
22014
22014
 
22015
22015
  // src/commands/memory.ts
22016
- import { existsSync as existsSync3, readFileSync as readFileSync2, statSync } from "node:fs";
22016
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
22017
22017
  import { dirname, isAbsolute, normalize, resolve as resolve2 } from "node:path";
22018
22018
  var VALID_CATEGORIES = ["fact", "preference", "pattern", "decision", "context"];
22019
22019
  var VALID_SOURCES = ["chat", "file", "manual"];
@@ -22184,13 +22184,18 @@ function registerMemoryCommand(program2) {
22184
22184
  fail(e instanceof Error ? e.message : String(e));
22185
22185
  }
22186
22186
  });
22187
- memory.command("sync").description("Sync MEMORY.md and referenced files to the platform (one-way: local → server)").option("--path <dir>", `Workspace dir containing MEMORY.md (default ${DEFAULT_WORKSPACE})`).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) => {
22187
+ 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) => {
22188
22188
  const root = resolve2(opts.path || DEFAULT_WORKSPACE);
22189
- const indexPath = resolve2(root, "MEMORY.md");
22190
- if (!existsSync3(indexPath)) {
22191
- fail(`MEMORY.md not found at ${indexPath}`);
22192
- }
22193
- const collected = collectMemoryFiles(root, indexPath);
22189
+ const candidateIndexes = [
22190
+ resolve2(root, "MEMORY.md"),
22191
+ resolve2(root, "memory", "MEMORY.md")
22192
+ ].filter((p) => existsSync3(p));
22193
+ const memoryDir = resolve2(root, "memory");
22194
+ const memoryDirExists = existsSync3(memoryDir) && statSync(memoryDir).isDirectory();
22195
+ if (!candidateIndexes.length && !memoryDirExists) {
22196
+ fail(`No MEMORY.md or memory/ folder found under ${root}. Expected ${root}/MEMORY.md or ${root}/memory/.`);
22197
+ }
22198
+ const collected = collectMemoryFiles(root, candidateIndexes, memoryDirExists ? memoryDir : null);
22194
22199
  if (collected.errors.length && !opts.json) {
22195
22200
  for (const e of collected.errors)
22196
22201
  console.error(error(e));
@@ -22273,37 +22278,72 @@ function registerMemoryCommand(program2) {
22273
22278
  }
22274
22279
  });
22275
22280
  }
22276
- function collectMemoryFiles(root, indexPath) {
22281
+ function collectMemoryFiles(root, indexPaths, memoryDir) {
22277
22282
  const errors2 = [];
22278
22283
  const seen = new Set;
22279
22284
  const files = [];
22280
- const indexContent = safeRead(indexPath, errors2);
22281
- if (indexContent === null)
22282
- return { files, errors: errors2 };
22283
- pushFile(files, seen, indexPath, indexContent, root);
22284
- const indexDir = dirname(indexPath);
22285
- for (const ref of extractMarkdownLinks(indexContent)) {
22286
- if (isExternal(ref))
22287
- continue;
22288
- const abs = normalize(isAbsolute(ref) ? ref : resolve2(indexDir, ref));
22289
- if (!abs.startsWith(root)) {
22290
- errors2.push(`Skipping ${ref}: outside workspace ${root}`);
22285
+ for (const indexPath of indexPaths) {
22286
+ const indexContent = safeRead(indexPath, errors2);
22287
+ if (indexContent === null)
22291
22288
  continue;
22289
+ pushFile(files, seen, indexPath, indexContent, root);
22290
+ const indexDir = dirname(indexPath);
22291
+ for (const ref of extractMarkdownLinks(indexContent)) {
22292
+ if (isExternal(ref))
22293
+ continue;
22294
+ const abs = normalize(isAbsolute(ref) ? ref : resolve2(indexDir, ref));
22295
+ if (!abs.startsWith(root)) {
22296
+ errors2.push(`Skipping ${ref}: outside workspace ${root}`);
22297
+ continue;
22298
+ }
22299
+ if (!existsSync3(abs)) {
22300
+ errors2.push(`Referenced file missing: ${ref}`);
22301
+ continue;
22302
+ }
22303
+ const st = statSync(abs);
22304
+ if (!st.isFile())
22305
+ continue;
22306
+ const content = safeRead(abs, errors2);
22307
+ if (content === null)
22308
+ continue;
22309
+ pushFile(files, seen, abs, content, root);
22292
22310
  }
22293
- if (!existsSync3(abs)) {
22294
- errors2.push(`Referenced file missing: ${ref}`);
22295
- continue;
22311
+ }
22312
+ if (memoryDir) {
22313
+ for (const abs of walkMarkdown(memoryDir, errors2)) {
22314
+ const content = safeRead(abs, errors2);
22315
+ if (content === null)
22316
+ continue;
22317
+ pushFile(files, seen, abs, content, root);
22296
22318
  }
22297
- const st = statSync(abs);
22298
- if (!st.isFile())
22299
- continue;
22300
- const content = safeRead(abs, errors2);
22301
- if (content === null)
22302
- continue;
22303
- pushFile(files, seen, abs, content, root);
22304
22319
  }
22305
22320
  return { files, errors: errors2 };
22306
22321
  }
22322
+ function walkMarkdown(dir, errors2) {
22323
+ const out = [];
22324
+ let names;
22325
+ try {
22326
+ names = readdirSync(dir);
22327
+ } catch (e) {
22328
+ errors2.push(`Failed to read ${dir}: ${e instanceof Error ? e.message : String(e)}`);
22329
+ return out;
22330
+ }
22331
+ for (const name of names) {
22332
+ const abs = resolve2(dir, name);
22333
+ let st;
22334
+ try {
22335
+ st = statSync(abs);
22336
+ } catch {
22337
+ continue;
22338
+ }
22339
+ if (st.isDirectory()) {
22340
+ out.push(...walkMarkdown(abs, errors2));
22341
+ } else if (st.isFile() && name.toLowerCase().endsWith(".md")) {
22342
+ out.push(abs);
22343
+ }
22344
+ }
22345
+ return out;
22346
+ }
22307
22347
  function pushFile(files, seen, abs, content, root) {
22308
22348
  const key = relativeKey(abs, root);
22309
22349
  if (seen.has(key))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.9.9-beta.1",
3
+ "version": "0.9.9-beta.4",
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 and send platform DMs with other users (conversations list/find/read/send, the right tool when the owner asks "did X message me?" or "check my chat history with Y" — NOT the built-in sessions_list/sessions_history, which only see the agent's own local sessions), read platform notifications (friend requests, mentions, system alerts), 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 RAG documents, 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). Use this skill whenever the user asks about their Amiko inbox/DMs/notifications or to do 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 and send platform DMs with other users (conversations list/find/read/send, the right tool when the owner asks "did X message me?" or "check my chat history with Y" — NOT the built-in sessions_list/sessions_history, which only see the agent's own local sessions), 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 RAG documents, 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). Use this skill whenever the user asks about their Amiko inbox/DMs/notifications or to do 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
  ---
@@ -34,6 +34,7 @@ Each row is a first-level command group or command. Start here when the user ask
34
34
  | `amiko composio <cmd>` | Connect / disconnect third-party OAuth apps (Gmail, GitHub, …) |
35
35
  | `amiko conversation <cmd>` | DM/chat — list, find, create, send messages |
36
36
  | `amiko notifications <cmd>` | Platform notifications — list, mark as read |
37
+ | `amiko memory <cmd>` | Cross-agent memory — `search` before answering personal questions, `add` what's worth remembering, `list` / `rm` / `status` / `sync` local memory files |
37
38
  | `amiko accounts` | Show the resolved identity (authenticated, userId, twinId, platform) |
38
39
  | `amiko info` | Show the active twin (name, description, public, voice, avatar) |
39
40
  | `amiko config <cmd>` | Show resolved config |
@@ -298,6 +299,81 @@ amiko notifications read --all # mark all read
298
299
 
299
300
  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.
300
301
 
302
+ ## Memory (cross-agent)
303
+
304
+ Memories are scoped to the **owner's `user_id`**, not to your individual agent — every agent the owner runs reads and writes the same pool. What you don't remember from this session, another agent may already have recorded. Three commands cover almost everything:
305
+
306
+ - `amiko memory search "<query>"` — natural-language hybrid search (vector + FTS). Query in any language.
307
+ - `amiko memory add "<content>" --category <cat>` — save a durable memory for cross-agent recall.
308
+ - `amiko memory sync` — upload local `MEMORY.md` + `memory/**/*.md` so the platform extracts granular memories from them.
309
+
310
+ ```bash
311
+ amiko memory search "git workflow" --limit 5 # ranked results with score
312
+ amiko memory list --limit 25 # newest-first, paginate with --offset
313
+ amiko memory list --category preference # filter: fact | preference | pattern | decision | context
314
+ amiko memory list --source chat|file|manual # filter by origin
315
+ amiko memory add "Owner prefers terse PR descriptions" --category preference --tags pr,style
316
+ amiko memory rm <memoryId> # soft-delete
317
+ amiko memory status # totals: memories + memory_files
318
+ amiko memory sync # one-way upload of local MEMORY.md + memory/**/*.md
319
+ amiko memory sync --dry-run # show what would be uploaded
320
+ amiko memory sync --prune # also delete server files that no longer exist locally
321
+ ```
322
+
323
+ All `memory` commands support `--json`.
324
+
325
+ ### When to search — bias toward calling
326
+
327
+ **Default assumption: the owner has stored context you don't have. Run `amiko memory search` BEFORE answering any question about them, their project, their preferences, or their history. A call that returns empty costs ~100ms; a missed hit makes you look amnesic and forces them to re-teach you every session.**
328
+
329
+ 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:
330
+
331
+ 1. **Preference / habit questions**, even without a specific entity named.
332
+ Examples: "what do I usually use for X", "how do I normally do Y", "what's my preferred tool for Z", "what's my coding style". Pass a short paraphrase as the query.
333
+ 2. **Callbacks to prior context.** "as I mentioned", "like last time", "you know the one", "we discussed before", "what was that X we set up".
334
+ 3. **Named entities specific to this owner.** Their project / repo / service / team / tool name. A person by name.
335
+ 4. **Past bugs, decisions, investigations, design choices.**
336
+ 5. **Start of a new session** where they reference anything about themselves or their work.
337
+
338
+ Do NOT search for:
339
+ - Purely textbook programming questions with no owner-specific signal ("how does `useEffect` work", "what is the time complexity of quicksort").
340
+ - Questions the current code or `git log` already answers directly.
341
+
342
+ **When unsure, search.** Empty results cost you nothing. Missing the owner's context costs you their trust.
343
+
344
+ ### When to save
345
+
346
+ Use `amiko memory add` after:
347
+
348
+ - Fixing a non-obvious bug → save root cause + fix as `pattern` or `fact`
349
+ - Making an architecture decision → save reasoning as `decision`
350
+ - Discovering a useful pattern or workaround → `pattern`
351
+ - Owner explicitly says "remember this" / "save this" / "from now on..." → match the category to the content
352
+ - Learning a preference you'd otherwise have to re-ask ("I prefer rg", "I always use pnpm") → `preference`
353
+
354
+ Write memories as **standalone sentences with full context** — include names, not pronouns. A future session will read this without knowing today's conversation. Bad: "He prefers it that way." Good: "William prefers terse PR descriptions in the Amiko-Layer repo."
355
+
356
+ Categories:
357
+
358
+ | Category | Use for |
359
+ |----------|---------|
360
+ | `fact` | Technical facts, API details, config values, stable facts about the owner |
361
+ | `preference` | How they like things done (tone, formats, tools, coding style) |
362
+ | `pattern` | Recurring patterns, pitfalls, team conventions, workarounds |
363
+ | `decision` | Architecture decisions and their reasoning |
364
+ | `context` | Project context, deadlines, ongoing work, transient state |
365
+
366
+ Do NOT save:
367
+ - Trivial facts obvious from the code itself or generic programming knowledge.
368
+ - Ephemeral state already captured by `git log` / the current diff.
369
+ - Duplicates — `memory search` first; if a near-match exists, skip or `rm` the old one before adding.
370
+
371
+ ### `source` and `sync` semantics
372
+
373
+ `source` is set automatically: `manual` for `memory add`, `chat` for cron-extracted conversation memories, `file` for memories the platform extracted from a synced `MEMORY.md`/file.
374
+
375
+ `sync` walks `<workspace>/MEMORY.md`, every markdown link inside it, **and** every `.md` under `<workspace>/memory/` (recursively). Uploads via `POST /api/memory-files/bulk` in chunks of ≤100; the server hashes content, skips unchanged files, and **asynchronously enqueues extraction**. Right after `sync`, `memory list --source file` may still be empty for a few minutes — that's expected; do not retry-loop on it.
376
+
301
377
  ## Composio
302
378
 
303
379
  ```bash