@heyamiko/amiko-cli 0.10.1-beta.3 → 0.11.0-beta.0
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 +33 -20
- package/dist/index.js +82 -389
- package/package.json +1 -1
- package/skills/SKILL.md +10 -14
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @heyamiko/amiko-cli (v0.
|
|
1
|
+
# @heyamiko/amiko-cli (v0.11.0-beta.0)
|
|
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
|
|
|
@@ -16,15 +16,15 @@ amiko update
|
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
|
-
Auth is loaded from
|
|
19
|
+
Auth is loaded from environment variables on most hosts:
|
|
20
20
|
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -393,6 +396,16 @@ npm publish
|
|
|
393
396
|
|
|
394
397
|
## Changelog
|
|
395
398
|
|
|
399
|
+
### 0.11.0-beta.0
|
|
400
|
+
|
|
401
|
+
- **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).
|
|
402
|
+
- **`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`).
|
|
403
|
+
- SKILL.md updated: the "Sync periodically — Run `amiko memory sync` ..." instruction is now "Upload changed files with `amiko memory upload`"; no other behavior change.
|
|
404
|
+
|
|
405
|
+
### 0.10.1-beta.4
|
|
406
|
+
|
|
407
|
+
- **`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".
|
|
408
|
+
|
|
396
409
|
### 0.10.1-beta.3
|
|
397
410
|
|
|
398
411
|
- **`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:
|
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
|
|
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
|
|
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
|
|
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}`);
|
|
@@ -30951,187 +30947,6 @@ function registerComposioCommand(program2) {
|
|
|
30951
30947
|
});
|
|
30952
30948
|
}
|
|
30953
30949
|
|
|
30954
|
-
// src/commands/conversation.ts
|
|
30955
|
-
function describeConversation(c, selfUserId) {
|
|
30956
|
-
if (c.title)
|
|
30957
|
-
return c.title;
|
|
30958
|
-
const others = c.participants?.filter((p) => !(p.participant_type === "user" && p.participant_id === selfUserId)) ?? [];
|
|
30959
|
-
return others.map((p) => p.name ?? p.participant_id).join(", ") || c.id;
|
|
30960
|
-
}
|
|
30961
|
-
function findDirectWithUser(list, selfUserId, otherUserId) {
|
|
30962
|
-
return list.find((c) => {
|
|
30963
|
-
if (c.conversation_type !== "direct")
|
|
30964
|
-
return false;
|
|
30965
|
-
const ps = c.participants ?? [];
|
|
30966
|
-
const userIds = ps.filter((p) => p.participant_type === "user" && !p.left_at).map((p) => p.participant_id);
|
|
30967
|
-
if (userIds.length !== 2)
|
|
30968
|
-
return false;
|
|
30969
|
-
if (selfUserId && !userIds.includes(selfUserId))
|
|
30970
|
-
return false;
|
|
30971
|
-
return userIds.includes(otherUserId);
|
|
30972
|
-
});
|
|
30973
|
-
}
|
|
30974
|
-
function registerConversationCommand(program2) {
|
|
30975
|
-
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) => {
|
|
30976
|
-
const auth = resolveAuth();
|
|
30977
|
-
const config2 = loadConfig();
|
|
30978
|
-
const spinner = opts.json ? null : ora("Loading conversations...").start();
|
|
30979
|
-
try {
|
|
30980
|
-
const data = await amikoWebFetch(auth, "/api/conversations", {
|
|
30981
|
-
query: {
|
|
30982
|
-
archived: opts.archived,
|
|
30983
|
-
limit: opts.limit,
|
|
30984
|
-
offset: opts.offset
|
|
30985
|
-
}
|
|
30986
|
-
});
|
|
30987
|
-
spinner?.stop();
|
|
30988
|
-
renderOutput(data, (d) => {
|
|
30989
|
-
if (!d.conversations.length) {
|
|
30990
|
-
console.log(dim("No conversations."));
|
|
30991
|
-
return;
|
|
30992
|
-
}
|
|
30993
|
-
console.log(heading(`Conversations — ${d.conversations.length} of ${d.pagination.total}`));
|
|
30994
|
-
for (const c of d.conversations) {
|
|
30995
|
-
console.log("");
|
|
30996
|
-
console.log(label("ID", c.id));
|
|
30997
|
-
console.log(label("With", describeConversation(c, config2.userId)));
|
|
30998
|
-
console.log(label("Type", c.conversation_type));
|
|
30999
|
-
if (c.last_activity_at)
|
|
31000
|
-
console.log(label("Last activity", c.last_activity_at));
|
|
31001
|
-
if (c.unread_count)
|
|
31002
|
-
console.log(label("Unread", String(c.unread_count)));
|
|
31003
|
-
}
|
|
31004
|
-
if (d.pagination.has_more) {
|
|
31005
|
-
console.log("");
|
|
31006
|
-
console.log(dim(`More available — pass --offset ${Number(opts.offset ?? 0) + d.conversations.length}`));
|
|
31007
|
-
}
|
|
31008
|
-
}, { json: opts.json });
|
|
31009
|
-
} catch (e5) {
|
|
31010
|
-
spinner?.stop();
|
|
31011
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
31012
|
-
process.exit(1);
|
|
31013
|
-
}
|
|
31014
|
-
});
|
|
31015
|
-
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) => {
|
|
31016
|
-
const auth = resolveAuth();
|
|
31017
|
-
const config2 = loadConfig();
|
|
31018
|
-
const spinner = opts.json ? null : ora("Searching...").start();
|
|
31019
|
-
try {
|
|
31020
|
-
const data = await amikoWebFetch(auth, "/api/conversations", { query: { archived: "all", limit: 100 } });
|
|
31021
|
-
spinner?.stop();
|
|
31022
|
-
const match = findDirectWithUser(data.conversations, config2.userId, opts.userId);
|
|
31023
|
-
if (opts.json) {
|
|
31024
|
-
console.log(JSON.stringify({ conversation: match ?? null }, null, 2));
|
|
31025
|
-
return;
|
|
31026
|
-
}
|
|
31027
|
-
if (!match) {
|
|
31028
|
-
console.log(dim(`No direct conversation with user ${opts.userId}. Run \`amiko conversation create --user-id ${opts.userId}\` to start one.`));
|
|
31029
|
-
return;
|
|
31030
|
-
}
|
|
31031
|
-
console.log(success("Found conversation"));
|
|
31032
|
-
console.log(label("ID", match.id));
|
|
31033
|
-
console.log(label("With", describeConversation(match, config2.userId)));
|
|
31034
|
-
if (match.last_activity_at)
|
|
31035
|
-
console.log(label("Last activity", match.last_activity_at));
|
|
31036
|
-
} catch (e5) {
|
|
31037
|
-
spinner?.stop();
|
|
31038
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
31039
|
-
process.exit(1);
|
|
31040
|
-
}
|
|
31041
|
-
});
|
|
31042
|
-
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) => {
|
|
31043
|
-
const auth = resolveAuth();
|
|
31044
|
-
const spinner = opts.json ? null : ora("Creating conversation...").start();
|
|
31045
|
-
try {
|
|
31046
|
-
const data = await amikoWebFetch(auth, "/api/conversations", {
|
|
31047
|
-
method: "POST",
|
|
31048
|
-
body: {
|
|
31049
|
-
participant_ids: [opts.userId],
|
|
31050
|
-
participant_types: ["user"],
|
|
31051
|
-
conversation_type: "direct"
|
|
31052
|
-
}
|
|
31053
|
-
});
|
|
31054
|
-
spinner?.stop();
|
|
31055
|
-
if (opts.json) {
|
|
31056
|
-
console.log(JSON.stringify(data, null, 2));
|
|
31057
|
-
return;
|
|
31058
|
-
}
|
|
31059
|
-
console.log(success(data.reused_existing ? "Reused existing conversation" : "Conversation created"));
|
|
31060
|
-
console.log(label("ID", data.conversation.id));
|
|
31061
|
-
} catch (e5) {
|
|
31062
|
-
spinner?.stop();
|
|
31063
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
31064
|
-
process.exit(1);
|
|
31065
|
-
}
|
|
31066
|
-
});
|
|
31067
|
-
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) => {
|
|
31068
|
-
const auth = resolveAuth();
|
|
31069
|
-
const spinner = opts.json ? null : ora("Loading messages...").start();
|
|
31070
|
-
try {
|
|
31071
|
-
const data = await amikoWebFetch(auth, `/api/conversations/${encodeURIComponent(opts.id)}/messages`, {
|
|
31072
|
-
query: {
|
|
31073
|
-
limit: opts.limit,
|
|
31074
|
-
before_cursor: opts.beforeCursor
|
|
31075
|
-
}
|
|
31076
|
-
});
|
|
31077
|
-
spinner?.stop();
|
|
31078
|
-
if (opts.json) {
|
|
31079
|
-
console.log(JSON.stringify(data, null, 2));
|
|
31080
|
-
return;
|
|
31081
|
-
}
|
|
31082
|
-
if (!data.messages.length) {
|
|
31083
|
-
console.log(dim("No messages."));
|
|
31084
|
-
return;
|
|
31085
|
-
}
|
|
31086
|
-
console.log(heading(`Messages — ${data.messages.length}`));
|
|
31087
|
-
for (const m of data.messages) {
|
|
31088
|
-
console.log("");
|
|
31089
|
-
console.log(label(m.name ?? m.sender_id ?? "?", m.created_at?.slice(0, 19) ?? ""));
|
|
31090
|
-
if (m.content)
|
|
31091
|
-
console.log(m.content);
|
|
31092
|
-
if (m.message_type && m.message_type !== "text")
|
|
31093
|
-
console.log(dim(`[${m.message_type}]`));
|
|
31094
|
-
console.log(dim(`id: ${m.id}`));
|
|
31095
|
-
}
|
|
31096
|
-
if (data.has_more && data.next_cursor) {
|
|
31097
|
-
console.log("");
|
|
31098
|
-
console.log(dim(`Older messages available — pass --before-cursor ${data.next_cursor}`));
|
|
31099
|
-
}
|
|
31100
|
-
} catch (e5) {
|
|
31101
|
-
spinner?.stop();
|
|
31102
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
31103
|
-
process.exit(1);
|
|
31104
|
-
}
|
|
31105
|
-
});
|
|
31106
|
-
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) => {
|
|
31107
|
-
const auth = requireAuth();
|
|
31108
|
-
const spinner = opts.json ? null : ora("Sending message...").start();
|
|
31109
|
-
try {
|
|
31110
|
-
const data = await amikoWebFetch(auth, `/api/conversations/${encodeURIComponent(opts.id)}/messages`, {
|
|
31111
|
-
method: "POST",
|
|
31112
|
-
body: {
|
|
31113
|
-
content: opts.message,
|
|
31114
|
-
message_type: "text",
|
|
31115
|
-
...opts.replyTo ? { reply_to_id: opts.replyTo } : {},
|
|
31116
|
-
...opts.asAgent ? { as_agent: true } : {}
|
|
31117
|
-
}
|
|
31118
|
-
});
|
|
31119
|
-
spinner?.stop();
|
|
31120
|
-
if (opts.json) {
|
|
31121
|
-
console.log(JSON.stringify(data, null, 2));
|
|
31122
|
-
return;
|
|
31123
|
-
}
|
|
31124
|
-
console.log(success("Message sent"));
|
|
31125
|
-
if (data.message?.id)
|
|
31126
|
-
console.log(label("Message ID", data.message.id));
|
|
31127
|
-
} catch (e5) {
|
|
31128
|
-
spinner?.stop();
|
|
31129
|
-
console.error(error(e5 instanceof Error ? e5.message : String(e5)));
|
|
31130
|
-
process.exit(1);
|
|
31131
|
-
}
|
|
31132
|
-
});
|
|
31133
|
-
}
|
|
31134
|
-
|
|
31135
30950
|
// src/commands/notifications.ts
|
|
31136
30951
|
function registerNotificationsCommand(program2) {
|
|
31137
30952
|
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) => {
|
|
@@ -31200,16 +31015,12 @@ function registerNotificationsCommand(program2) {
|
|
|
31200
31015
|
}
|
|
31201
31016
|
|
|
31202
31017
|
// src/commands/memory.ts
|
|
31203
|
-
import { existsSync as existsSync2,
|
|
31204
|
-
import {
|
|
31205
|
-
function isInsideRoot(abs, root) {
|
|
31206
|
-
const rel = relative(root, abs);
|
|
31207
|
-
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
31208
|
-
}
|
|
31018
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
31019
|
+
import { basename as basename3, resolve } from "node:path";
|
|
31209
31020
|
var VALID_CATEGORIES = ["fact", "preference", "pattern", "decision", "context"];
|
|
31210
31021
|
var VALID_SOURCES = ["chat", "file", "manual"];
|
|
31211
|
-
var
|
|
31212
|
-
var
|
|
31022
|
+
var UPLOAD_BATCH_SIZE = 100;
|
|
31023
|
+
var MAX_UPLOAD_BYTES = 1e6;
|
|
31213
31024
|
function fail(msg) {
|
|
31214
31025
|
console.error(error(msg));
|
|
31215
31026
|
process.exit(1);
|
|
@@ -31234,7 +31045,7 @@ function fmtMemory(m) {
|
|
|
31234
31045
|
console.log(m.content);
|
|
31235
31046
|
}
|
|
31236
31047
|
function registerMemoryCommand(program2) {
|
|
31237
|
-
const memory = program2.command("memory").description("Manage cross-agent memories — add, list, search, remove,
|
|
31048
|
+
const memory = program2.command("memory").description("Manage cross-agent memories — add, list, search, remove, upload files");
|
|
31238
31049
|
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) => {
|
|
31239
31050
|
if (!content || !content.trim())
|
|
31240
31051
|
fail("Content is required and cannot be empty.");
|
|
@@ -31353,7 +31164,7 @@ function registerMemoryCommand(program2) {
|
|
|
31353
31164
|
fail(e5 instanceof Error ? e5.message : String(e5));
|
|
31354
31165
|
}
|
|
31355
31166
|
});
|
|
31356
|
-
memory.command("status").description("Show counts of memories and
|
|
31167
|
+
memory.command("status").description("Show counts of memories and uploaded memory files").option("--json", "Output as JSON").action(async (opts) => {
|
|
31357
31168
|
const auth = resolveAuth();
|
|
31358
31169
|
const spinner = opts.json ? null : ora("Loading status...").start();
|
|
31359
31170
|
try {
|
|
@@ -31375,79 +31186,89 @@ function registerMemoryCommand(program2) {
|
|
|
31375
31186
|
fail(e5 instanceof Error ? e5.message : String(e5));
|
|
31376
31187
|
}
|
|
31377
31188
|
});
|
|
31378
|
-
memory.command("
|
|
31379
|
-
|
|
31380
|
-
|
|
31381
|
-
|
|
31382
|
-
|
|
31383
|
-
|
|
31384
|
-
const memoryDir = resolve(root, "memory");
|
|
31385
|
-
const memoryDirExists = existsSync2(memoryDir) && statSync2(memoryDir).isDirectory();
|
|
31386
|
-
if (!candidateIndexes.length && !memoryDirExists) {
|
|
31387
|
-
fail(`No MEMORY.md or memory/ folder found under ${root}. Expected ${root}/MEMORY.md or ${root}/memory/.`);
|
|
31388
|
-
}
|
|
31389
|
-
const collected = collectMemoryFiles(root, candidateIndexes, memoryDirExists ? memoryDir : null);
|
|
31390
|
-
if (collected.errors.length && !opts.json) {
|
|
31391
|
-
for (const e5 of collected.errors)
|
|
31392
|
-
console.error(error(e5));
|
|
31393
|
-
}
|
|
31394
|
-
const files = collected.files;
|
|
31395
|
-
if (opts.dryRun) {
|
|
31396
|
-
renderOutput({ root, files: files.map((f) => f.path), prune: !!opts.prune }, (d) => {
|
|
31397
|
-
console.log(heading(`Dry run — ${d.files.length} file(s)`));
|
|
31398
|
-
for (const p of d.files)
|
|
31399
|
-
console.log(` ${p}`);
|
|
31400
|
-
if (d.prune)
|
|
31401
|
-
console.log(dim("--prune would remove server files not listed above."));
|
|
31402
|
-
}, { json: opts.json });
|
|
31403
|
-
return;
|
|
31189
|
+
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) => {
|
|
31190
|
+
if (!files || files.length === 0)
|
|
31191
|
+
fail("At least one file path is required.");
|
|
31192
|
+
const keyOverride = opts.key !== undefined ? opts.key.trim() : undefined;
|
|
31193
|
+
if (opts.key !== undefined && !keyOverride) {
|
|
31194
|
+
fail("--key cannot be empty or whitespace-only.");
|
|
31404
31195
|
}
|
|
31405
|
-
if (
|
|
31406
|
-
|
|
31407
|
-
|
|
31196
|
+
if (keyOverride && files.length > 1) {
|
|
31197
|
+
fail("--key can only be used when uploading a single file.");
|
|
31198
|
+
}
|
|
31199
|
+
const prepared = [];
|
|
31200
|
+
const seenKeys = new Set;
|
|
31201
|
+
for (const f of files) {
|
|
31202
|
+
const abs = resolve(f);
|
|
31203
|
+
if (!existsSync2(abs))
|
|
31204
|
+
fail(`File not found: ${f}`);
|
|
31205
|
+
const st = statSync2(abs);
|
|
31206
|
+
if (!st.isFile())
|
|
31207
|
+
fail(`Not a regular file: ${f}`);
|
|
31208
|
+
if (st.size > MAX_UPLOAD_BYTES) {
|
|
31209
|
+
fail(`${f} is ${st.size} bytes; max ${MAX_UPLOAD_BYTES}.`);
|
|
31210
|
+
}
|
|
31211
|
+
let content;
|
|
31212
|
+
try {
|
|
31213
|
+
content = readFileSync2(abs, "utf-8");
|
|
31214
|
+
} catch (e5) {
|
|
31215
|
+
fail(`Failed to read ${f}: ${e5 instanceof Error ? e5.message : String(e5)}`);
|
|
31216
|
+
}
|
|
31217
|
+
const key = keyOverride && files.length === 1 ? keyOverride : basename3(abs);
|
|
31218
|
+
if (seenKeys.has(key)) {
|
|
31219
|
+
fail(`Duplicate path key "${key}" within this upload. Run separately or pass --key per file.`);
|
|
31220
|
+
}
|
|
31221
|
+
seenKeys.add(key);
|
|
31222
|
+
prepared.push({ path: key, content, source: f });
|
|
31408
31223
|
}
|
|
31409
31224
|
const auth = resolveAuth();
|
|
31410
|
-
const spinner = opts.json ? null : ora(`
|
|
31225
|
+
const spinner = opts.json ? null : ora(`Uploading ${prepared.length} file(s)...`).start();
|
|
31411
31226
|
try {
|
|
31412
|
-
|
|
31413
|
-
|
|
31414
|
-
const
|
|
31415
|
-
const data = await amikoWebFetch(auth, "/api/memory-files
|
|
31227
|
+
let results;
|
|
31228
|
+
if (prepared.length === 1) {
|
|
31229
|
+
const f = prepared[0];
|
|
31230
|
+
const data = await amikoWebFetch(auth, "/api/memory-files", {
|
|
31416
31231
|
method: "POST",
|
|
31417
|
-
body: {
|
|
31232
|
+
body: {
|
|
31233
|
+
path: f.path,
|
|
31234
|
+
content: f.content,
|
|
31235
|
+
twin_id: opts.twin
|
|
31236
|
+
},
|
|
31237
|
+
timeoutMs: 120000
|
|
31418
31238
|
});
|
|
31419
|
-
|
|
31420
|
-
|
|
31421
|
-
|
|
31422
|
-
|
|
31423
|
-
|
|
31424
|
-
|
|
31425
|
-
const remote = await fetchAllMemoryFiles(auth);
|
|
31426
|
-
const stale = remote.filter((f) => !localPaths.has(f.path));
|
|
31427
|
-
for (const f of stale) {
|
|
31428
|
-
try {
|
|
31429
|
-
await amikoWebFetch(auth, `/api/memory-files/${encodeURIComponent(f.id)}`, { method: "DELETE" });
|
|
31430
|
-
pruned++;
|
|
31431
|
-
} catch (e5) {
|
|
31432
|
-
pruneErrors.push(`${f.path}: ${e5 instanceof Error ? e5.message : String(e5)}`);
|
|
31239
|
+
results = [
|
|
31240
|
+
{
|
|
31241
|
+
path: f.path,
|
|
31242
|
+
file_id: data.file_id ?? data.id,
|
|
31243
|
+
changed: data.changed,
|
|
31244
|
+
extracted: data.extracted
|
|
31433
31245
|
}
|
|
31246
|
+
];
|
|
31247
|
+
} else {
|
|
31248
|
+
results = [];
|
|
31249
|
+
for (let i = 0;i < prepared.length; i += UPLOAD_BATCH_SIZE) {
|
|
31250
|
+
const chunk = prepared.slice(i, i + UPLOAD_BATCH_SIZE);
|
|
31251
|
+
const data = await amikoWebFetch(auth, "/api/memory-files/bulk", {
|
|
31252
|
+
method: "POST",
|
|
31253
|
+
body: {
|
|
31254
|
+
files: chunk.map((c) => ({
|
|
31255
|
+
path: c.path,
|
|
31256
|
+
content: c.content,
|
|
31257
|
+
twin_id: opts.twin
|
|
31258
|
+
}))
|
|
31259
|
+
},
|
|
31260
|
+
timeoutMs: 120000
|
|
31261
|
+
});
|
|
31262
|
+
results.push(...data.results);
|
|
31434
31263
|
}
|
|
31435
31264
|
}
|
|
31436
31265
|
spinner?.stop();
|
|
31437
|
-
const changed =
|
|
31438
|
-
const errors3 =
|
|
31439
|
-
const extracted =
|
|
31440
|
-
renderOutput({
|
|
31441
|
-
|
|
31442
|
-
|
|
31443
|
-
errors: errors3,
|
|
31444
|
-
extracted,
|
|
31445
|
-
pruned,
|
|
31446
|
-
prune_errors: pruneErrors,
|
|
31447
|
-
results: allResults
|
|
31448
|
-
}, (d) => {
|
|
31449
|
-
console.log(success(`${d.synced} file(s) synced (${d.changed} changed, ${d.errors} errors), ${d.extracted} memories extracted`));
|
|
31450
|
-
for (const r of allResults) {
|
|
31266
|
+
const changed = results.filter((r) => r.changed).length;
|
|
31267
|
+
const errors3 = results.filter((r) => r.error).length;
|
|
31268
|
+
const extracted = results.reduce((acc, r) => acc + (r.extracted ?? 0), 0);
|
|
31269
|
+
renderOutput({ uploaded: results.length, changed, errors: errors3, extracted, results }, (d) => {
|
|
31270
|
+
console.log(success(`${d.uploaded} file(s) uploaded (${d.changed} changed, ${d.errors} errors), ${d.extracted} memories extracted`));
|
|
31271
|
+
for (const r of results) {
|
|
31451
31272
|
if (r.error) {
|
|
31452
31273
|
console.log(error(`${r.path}: ${r.error}`));
|
|
31453
31274
|
} else if (r.changed) {
|
|
@@ -31456,12 +31277,6 @@ function registerMemoryCommand(program2) {
|
|
|
31456
31277
|
console.log(` ${r.path} ${dim("(unchanged)")}`);
|
|
31457
31278
|
}
|
|
31458
31279
|
}
|
|
31459
|
-
if (opts.prune) {
|
|
31460
|
-
console.log("");
|
|
31461
|
-
console.log(label("Pruned", String(d.pruned)));
|
|
31462
|
-
for (const e5 of pruneErrors)
|
|
31463
|
-
console.log(error(e5));
|
|
31464
|
-
}
|
|
31465
31280
|
}, { json: opts.json });
|
|
31466
31281
|
} catch (e5) {
|
|
31467
31282
|
spinner?.stop();
|
|
@@ -31469,126 +31284,6 @@ function registerMemoryCommand(program2) {
|
|
|
31469
31284
|
}
|
|
31470
31285
|
});
|
|
31471
31286
|
}
|
|
31472
|
-
function collectMemoryFiles(root, indexPaths, memoryDir) {
|
|
31473
|
-
const errors3 = [];
|
|
31474
|
-
const seen = new Set;
|
|
31475
|
-
const files = [];
|
|
31476
|
-
for (const indexPath of indexPaths) {
|
|
31477
|
-
const indexContent = safeRead(indexPath, errors3);
|
|
31478
|
-
if (indexContent === null)
|
|
31479
|
-
continue;
|
|
31480
|
-
pushFile(files, seen, indexPath, indexContent, root);
|
|
31481
|
-
const indexDir = dirname(indexPath);
|
|
31482
|
-
for (const ref of extractMarkdownLinks(indexContent)) {
|
|
31483
|
-
if (isExternal(ref))
|
|
31484
|
-
continue;
|
|
31485
|
-
const abs = normalize(isAbsolute(ref) ? ref : resolve(indexDir, ref));
|
|
31486
|
-
if (!isInsideRoot(abs, root)) {
|
|
31487
|
-
errors3.push(`Skipping ${ref}: outside workspace ${root}`);
|
|
31488
|
-
continue;
|
|
31489
|
-
}
|
|
31490
|
-
if (!existsSync2(abs)) {
|
|
31491
|
-
errors3.push(`Referenced file missing: ${ref}`);
|
|
31492
|
-
continue;
|
|
31493
|
-
}
|
|
31494
|
-
const st = lstatSync(abs);
|
|
31495
|
-
if (st.isSymbolicLink()) {
|
|
31496
|
-
errors3.push(`Skipping ${ref}: symlinks not followed`);
|
|
31497
|
-
continue;
|
|
31498
|
-
}
|
|
31499
|
-
if (!st.isFile())
|
|
31500
|
-
continue;
|
|
31501
|
-
const content = safeRead(abs, errors3);
|
|
31502
|
-
if (content === null)
|
|
31503
|
-
continue;
|
|
31504
|
-
pushFile(files, seen, abs, content, root);
|
|
31505
|
-
}
|
|
31506
|
-
}
|
|
31507
|
-
if (memoryDir) {
|
|
31508
|
-
for (const abs of walkMarkdown(memoryDir, errors3)) {
|
|
31509
|
-
const content = safeRead(abs, errors3);
|
|
31510
|
-
if (content === null)
|
|
31511
|
-
continue;
|
|
31512
|
-
pushFile(files, seen, abs, content, root);
|
|
31513
|
-
}
|
|
31514
|
-
}
|
|
31515
|
-
return { files, errors: errors3 };
|
|
31516
|
-
}
|
|
31517
|
-
function walkMarkdown(dir, errors3) {
|
|
31518
|
-
const out = [];
|
|
31519
|
-
let names;
|
|
31520
|
-
try {
|
|
31521
|
-
names = readdirSync(dir);
|
|
31522
|
-
} catch (e5) {
|
|
31523
|
-
errors3.push(`Failed to read ${dir}: ${e5 instanceof Error ? e5.message : String(e5)}`);
|
|
31524
|
-
return out;
|
|
31525
|
-
}
|
|
31526
|
-
for (const name of names) {
|
|
31527
|
-
const abs = resolve(dir, name);
|
|
31528
|
-
let st;
|
|
31529
|
-
try {
|
|
31530
|
-
st = lstatSync(abs);
|
|
31531
|
-
} catch {
|
|
31532
|
-
continue;
|
|
31533
|
-
}
|
|
31534
|
-
if (st.isSymbolicLink())
|
|
31535
|
-
continue;
|
|
31536
|
-
if (st.isDirectory()) {
|
|
31537
|
-
out.push(...walkMarkdown(abs, errors3));
|
|
31538
|
-
} else if (st.isFile() && name.toLowerCase().endsWith(".md")) {
|
|
31539
|
-
out.push(abs);
|
|
31540
|
-
}
|
|
31541
|
-
}
|
|
31542
|
-
return out;
|
|
31543
|
-
}
|
|
31544
|
-
function pushFile(files, seen, abs, content, root) {
|
|
31545
|
-
const key = relativeKey(abs, root);
|
|
31546
|
-
if (seen.has(key))
|
|
31547
|
-
return;
|
|
31548
|
-
seen.add(key);
|
|
31549
|
-
files.push({ path: key, content });
|
|
31550
|
-
}
|
|
31551
|
-
function relativeKey(abs, root) {
|
|
31552
|
-
const rel = abs.startsWith(root) ? abs.slice(root.length) : abs;
|
|
31553
|
-
return rel.replace(/^\/+/, "");
|
|
31554
|
-
}
|
|
31555
|
-
function safeRead(path2, errors3) {
|
|
31556
|
-
try {
|
|
31557
|
-
return readFileSync2(path2, "utf-8");
|
|
31558
|
-
} catch (e5) {
|
|
31559
|
-
errors3.push(`Failed to read ${path2}: ${e5 instanceof Error ? e5.message : String(e5)}`);
|
|
31560
|
-
return null;
|
|
31561
|
-
}
|
|
31562
|
-
}
|
|
31563
|
-
function extractMarkdownLinks(md) {
|
|
31564
|
-
const out = [];
|
|
31565
|
-
const re = /\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
31566
|
-
let m;
|
|
31567
|
-
while ((m = re.exec(md)) !== null) {
|
|
31568
|
-
const target = m[1].split("#")[0];
|
|
31569
|
-
if (target)
|
|
31570
|
-
out.push(target);
|
|
31571
|
-
}
|
|
31572
|
-
return out;
|
|
31573
|
-
}
|
|
31574
|
-
function isExternal(ref) {
|
|
31575
|
-
return /^[a-z][a-z0-9+.-]*:/i.test(ref) || ref.startsWith("//");
|
|
31576
|
-
}
|
|
31577
|
-
async function fetchAllMemoryFiles(auth) {
|
|
31578
|
-
const all = [];
|
|
31579
|
-
let offset = 0;
|
|
31580
|
-
const limit = 100;
|
|
31581
|
-
for (;; ) {
|
|
31582
|
-
const data = await amikoWebFetch(auth, "/api/memory-files", {
|
|
31583
|
-
query: { limit: String(limit), offset: String(offset) }
|
|
31584
|
-
});
|
|
31585
|
-
all.push(...data.items ?? []);
|
|
31586
|
-
if (!data.has_more || !data.items?.length)
|
|
31587
|
-
break;
|
|
31588
|
-
offset += data.items.length;
|
|
31589
|
-
}
|
|
31590
|
-
return all;
|
|
31591
|
-
}
|
|
31592
31287
|
|
|
31593
31288
|
// src/commands/wallets.ts
|
|
31594
31289
|
function formatBalance(b) {
|
|
@@ -31804,7 +31499,6 @@ var users = program2.command("users").description("Search users and view public
|
|
|
31804
31499
|
var post = program2.command("post").description("Create posts and comments on the feed");
|
|
31805
31500
|
var review = program2.command("review").description("Review queue — list, approve, or reject twin-drafted comments");
|
|
31806
31501
|
var composio = program2.command("composio").description("Manage Composio app connections");
|
|
31807
|
-
var conversation = program2.command("conversation").description("Conversations — list, find, create, send messages between users/agents");
|
|
31808
31502
|
var notifications = program2.command("notifications").description("Platform notifications — list, mark as read");
|
|
31809
31503
|
registerCreditsCommand(program2);
|
|
31810
31504
|
registerSearchCommand(markets);
|
|
@@ -31831,7 +31525,6 @@ registerFeedCommand(program2);
|
|
|
31831
31525
|
registerPostCommand(post);
|
|
31832
31526
|
registerReviewCommand(review);
|
|
31833
31527
|
registerComposioCommand(composio);
|
|
31834
|
-
registerConversationCommand(conversation);
|
|
31835
31528
|
registerNotificationsCommand(notifications);
|
|
31836
31529
|
registerMemoryCommand(program2);
|
|
31837
31530
|
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,7 +20,6 @@ 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` |
|
|
26
25
|
| "upload report.pdf to my drive" | shell → `amiko drive upload ./report.pdf` |
|
|
@@ -39,11 +38,13 @@ The CLI is installed globally and is pre-authenticated when you're inside your w
|
|
|
39
38
|
|
|
40
39
|
## Command groups
|
|
41
40
|
|
|
42
|
-
Discover everything via `amiko --help`. Groups: `markets` (paid MPP services), `wallets`, `credits`, `twin`, `drive` (files / folders / RAG; `docs` is an alias), `voice`, `avatar`, `friends`, `users`, `post`, `review`, `feed`, `composio`, `
|
|
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.
|
|
43
44
|
|
|
44
45
|
## Critical Rules
|
|
45
46
|
|
|
46
|
-
1. **Every paid / value-moving command is hard-gated on `--yes` in a non-interactive shell.** The CLI refuses to run `markets *`, `credits topup`, `wallets swap send`, `wallets bridge send`, `wallets transfer` (and destructive ops like `twin update --public`, `
|
|
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`, `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
48
|
2. **After every paid command, report the remaining balance.** The CLI prints a `Balance: N credits` line — include that figure in your reply.
|
|
48
49
|
3. **Never retry a failed command.** Report and stop. Every paid call costs tokens even on failure.
|
|
49
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`.
|
|
@@ -117,18 +118,13 @@ Workflow: start with `friends matches --limit 20 --json`; narrow with `--dimensi
|
|
|
117
118
|
|
|
118
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.
|
|
119
120
|
|
|
120
|
-
##
|
|
121
|
+
## Notifications
|
|
121
122
|
|
|
122
|
-
> **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.
|
|
123
124
|
>
|
|
124
|
-
> **
|
|
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.
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
- `conversation find` filters client-side for a 2-user direct conversation containing both you and `--user-id`.
|
|
128
|
-
- `conversation create` is idempotent — server reuses an existing direct DM (`reused_existing: true`).
|
|
129
|
-
- `--user-id` expects the **internal** user id (from `amiko users search`), not a Privy DID or handle.
|
|
130
|
-
- `conversation send --as-agent` sends as the twin (`sender_type=agent`).
|
|
131
|
-
- Platform notifications cover friend requests, mentions, system alerts, and post-related events. They don't replace `conversation read` for actual chat content.
|
|
127
|
+
Platform notifications cover friend requests, mentions, system alerts, and post-related events.
|
|
132
128
|
|
|
133
129
|
## Memory (cross-agent) — READ THIS BEFORE ANSWERING ANYTHING ABOUT THE OWNER
|
|
134
130
|
|
|
@@ -137,7 +133,7 @@ Memories are scoped to the **owner's `user_id`**, not your agent — every agent
|
|
|
137
133
|
**Two non-negotiable habits:**
|
|
138
134
|
|
|
139
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.
|
|
140
|
-
2. **
|
|
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.
|
|
141
137
|
|
|
142
138
|
### When to search — bias toward calling
|
|
143
139
|
|