@heyamiko/amiko-cli 0.9.4 → 0.9.6-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 +2 -2
- package/dist/index.js +198 -2
- package/package.json +1 -1
- package/skills/SKILL.md +20 -0
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ amiko wallets swap send 0.5 USDC AMIKO --yes
|
|
|
138
138
|
amiko wallets swap send 100 AMIKO SOL --slippage 100
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
-
Swaps execute via Jupiter on Solana. Supports SOL, USDC, USDT, AMIKO, PYUSD, BONK, JUP, RAY, JitoSOL, mSOL, or any mint address. `swap send` is hard-gated: in a non-interactive shell you must pass `--yes` after quoting the cost to the user (`Solana gas +
|
|
141
|
+
Swaps execute via Jupiter on Solana. Supports SOL, USDC, USDT, AMIKO, PYUSD, BONK, JUP, RAY, JitoSOL, mSOL, or any mint address. `swap send` is hard-gated: in a non-interactive shell you must pass `--yes` after quoting the cost to the user (`Solana gas + Jupiter referral fee`).
|
|
142
142
|
|
|
143
143
|
## Bridge
|
|
144
144
|
|
|
@@ -197,7 +197,7 @@ Every paid / value-moving command (`image`, `search`, `amazon search`, `service
|
|
|
197
197
|
amiko markets image "a sunset over mountains" --yes
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
-
This makes cost confirmation effectively mandatory regardless of which model drives the agent — weak models can no longer silently burn tokens by ignoring the skill docs. The same refusal fires for `swap send` (Solana gas +
|
|
200
|
+
This makes cost confirmation effectively mandatory regardless of which model drives the agent — weak models can no longer silently burn tokens by ignoring the skill docs. The same refusal fires for `swap send` (Solana gas + Jupiter referral fee) and `bridge send` (Across fee + gas), so an agent cannot move tokens without a round-trip through user approval.
|
|
201
201
|
|
|
202
202
|
## Error Output
|
|
203
203
|
|
package/dist/index.js
CHANGED
|
@@ -17305,7 +17305,8 @@ function requireAuth() {
|
|
|
17305
17305
|
var DEFAULTS = {
|
|
17306
17306
|
apiBase: "https://mpp.amikomarkets.com",
|
|
17307
17307
|
platformUrl: "https://platform.heyamiko.com",
|
|
17308
|
-
siteBase: "https://platform.heyamiko.com"
|
|
17308
|
+
siteBase: "https://platform.heyamiko.com",
|
|
17309
|
+
chatBase: "https://amiko-chat.up.railway.app"
|
|
17309
17310
|
};
|
|
17310
17311
|
function loadConfig() {
|
|
17311
17312
|
const auth = resolveAuth();
|
|
@@ -17313,6 +17314,7 @@ function loadConfig() {
|
|
|
17313
17314
|
apiBase: auth.apiBase ?? DEFAULTS.apiBase,
|
|
17314
17315
|
platformUrl: auth.platformUrl ?? DEFAULTS.platformUrl,
|
|
17315
17316
|
siteBase: DEFAULTS.siteBase,
|
|
17317
|
+
chatBase: DEFAULTS.chatBase,
|
|
17316
17318
|
twinToken: auth.twinToken,
|
|
17317
17319
|
twinId: auth.twinId,
|
|
17318
17320
|
userId: auth.userId
|
|
@@ -19536,7 +19538,7 @@ function registerSwapCommand(program2) {
|
|
|
19536
19538
|
console.log(label("Wallet", truncAddr(wallet)));
|
|
19537
19539
|
console.log("");
|
|
19538
19540
|
await requireApproval({
|
|
19539
|
-
cost: "Solana gas +
|
|
19541
|
+
cost: "Solana gas + Jupiter referral fee",
|
|
19540
19542
|
summary: `swap ${amountStr} ${fromToken.toUpperCase()} → ~${(Number(quote.outAmount) / 10 ** output.decimals).toFixed(6)} ${output.symbol}`,
|
|
19541
19543
|
yes: opts.yes,
|
|
19542
19544
|
commandExample: `amiko wallets swap send ${amountStr} ${fromToken} ${toToken}`
|
|
@@ -21762,6 +21764,198 @@ function registerComposioCommand(program2) {
|
|
|
21762
21764
|
});
|
|
21763
21765
|
}
|
|
21764
21766
|
|
|
21767
|
+
// src/commands/conversation.ts
|
|
21768
|
+
function describeConversation(c, selfUserId) {
|
|
21769
|
+
if (c.title)
|
|
21770
|
+
return c.title;
|
|
21771
|
+
const others = c.participants?.filter((p) => !(p.participant_type === "user" && p.participant_id === selfUserId)) ?? [];
|
|
21772
|
+
return others.map((p) => p.name ?? p.participant_id).join(", ") || c.id;
|
|
21773
|
+
}
|
|
21774
|
+
function findDirectWithUser(list, selfUserId, otherUserId) {
|
|
21775
|
+
return list.find((c) => {
|
|
21776
|
+
if (c.conversation_type !== "direct")
|
|
21777
|
+
return false;
|
|
21778
|
+
const ps = c.participants ?? [];
|
|
21779
|
+
const userIds = ps.filter((p) => p.participant_type === "user" && !p.left_at).map((p) => p.participant_id);
|
|
21780
|
+
if (userIds.length !== 2)
|
|
21781
|
+
return false;
|
|
21782
|
+
if (selfUserId && !userIds.includes(selfUserId))
|
|
21783
|
+
return false;
|
|
21784
|
+
return userIds.includes(otherUserId);
|
|
21785
|
+
});
|
|
21786
|
+
}
|
|
21787
|
+
function registerConversationCommand(program2) {
|
|
21788
|
+
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) => {
|
|
21789
|
+
const auth = resolveAuth();
|
|
21790
|
+
const config2 = loadConfig();
|
|
21791
|
+
const spinner = opts.json ? null : ora("Loading conversations...").start();
|
|
21792
|
+
try {
|
|
21793
|
+
const data = await amikoWebFetch(auth, "/api/conversations", {
|
|
21794
|
+
query: {
|
|
21795
|
+
archived: opts.archived,
|
|
21796
|
+
limit: opts.limit,
|
|
21797
|
+
offset: opts.offset
|
|
21798
|
+
}
|
|
21799
|
+
});
|
|
21800
|
+
spinner?.stop();
|
|
21801
|
+
renderOutput(data, (d) => {
|
|
21802
|
+
if (!d.conversations.length) {
|
|
21803
|
+
console.log(dim("No conversations."));
|
|
21804
|
+
return;
|
|
21805
|
+
}
|
|
21806
|
+
console.log(heading(`Conversations — ${d.conversations.length} of ${d.pagination.total}`));
|
|
21807
|
+
for (const c of d.conversations) {
|
|
21808
|
+
console.log("");
|
|
21809
|
+
console.log(label("ID", c.id));
|
|
21810
|
+
console.log(label("With", describeConversation(c, config2.userId)));
|
|
21811
|
+
console.log(label("Type", c.conversation_type));
|
|
21812
|
+
if (c.last_activity_at)
|
|
21813
|
+
console.log(label("Last activity", c.last_activity_at));
|
|
21814
|
+
if (c.unread_count)
|
|
21815
|
+
console.log(label("Unread", String(c.unread_count)));
|
|
21816
|
+
}
|
|
21817
|
+
if (d.pagination.has_more) {
|
|
21818
|
+
console.log("");
|
|
21819
|
+
console.log(dim(`More available — pass --offset ${Number(opts.offset ?? 0) + d.conversations.length}`));
|
|
21820
|
+
}
|
|
21821
|
+
}, { json: opts.json });
|
|
21822
|
+
} catch (e) {
|
|
21823
|
+
spinner?.stop();
|
|
21824
|
+
console.error(error(e instanceof Error ? e.message : String(e)));
|
|
21825
|
+
process.exit(1);
|
|
21826
|
+
}
|
|
21827
|
+
});
|
|
21828
|
+
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) => {
|
|
21829
|
+
const auth = resolveAuth();
|
|
21830
|
+
const config2 = loadConfig();
|
|
21831
|
+
const spinner = opts.json ? null : ora("Searching...").start();
|
|
21832
|
+
try {
|
|
21833
|
+
const data = await amikoWebFetch(auth, "/api/conversations", { query: { archived: "all", limit: 100 } });
|
|
21834
|
+
spinner?.stop();
|
|
21835
|
+
const match = findDirectWithUser(data.conversations, config2.userId, opts.userId);
|
|
21836
|
+
if (opts.json) {
|
|
21837
|
+
console.log(JSON.stringify({ conversation: match ?? null }, null, 2));
|
|
21838
|
+
return;
|
|
21839
|
+
}
|
|
21840
|
+
if (!match) {
|
|
21841
|
+
console.log(dim(`No direct conversation with user ${opts.userId}. Run \`amiko conversation create --user-id ${opts.userId}\` to start one.`));
|
|
21842
|
+
return;
|
|
21843
|
+
}
|
|
21844
|
+
console.log(success("Found conversation"));
|
|
21845
|
+
console.log(label("ID", match.id));
|
|
21846
|
+
console.log(label("With", describeConversation(match, config2.userId)));
|
|
21847
|
+
if (match.last_activity_at)
|
|
21848
|
+
console.log(label("Last activity", match.last_activity_at));
|
|
21849
|
+
} catch (e) {
|
|
21850
|
+
spinner?.stop();
|
|
21851
|
+
console.error(error(e instanceof Error ? e.message : String(e)));
|
|
21852
|
+
process.exit(1);
|
|
21853
|
+
}
|
|
21854
|
+
});
|
|
21855
|
+
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) => {
|
|
21856
|
+
const auth = resolveAuth();
|
|
21857
|
+
const spinner = opts.json ? null : ora("Creating conversation...").start();
|
|
21858
|
+
try {
|
|
21859
|
+
const data = await amikoWebFetch(auth, "/api/conversations", {
|
|
21860
|
+
method: "POST",
|
|
21861
|
+
body: {
|
|
21862
|
+
participant_ids: [opts.userId],
|
|
21863
|
+
participant_types: ["user"],
|
|
21864
|
+
conversation_type: "direct"
|
|
21865
|
+
}
|
|
21866
|
+
});
|
|
21867
|
+
spinner?.stop();
|
|
21868
|
+
if (opts.json) {
|
|
21869
|
+
console.log(JSON.stringify(data, null, 2));
|
|
21870
|
+
return;
|
|
21871
|
+
}
|
|
21872
|
+
console.log(success(data.reused_existing ? "Reused existing conversation" : "Conversation created"));
|
|
21873
|
+
console.log(label("ID", data.conversation.id));
|
|
21874
|
+
} catch (e) {
|
|
21875
|
+
spinner?.stop();
|
|
21876
|
+
console.error(error(e instanceof Error ? e.message : String(e)));
|
|
21877
|
+
process.exit(1);
|
|
21878
|
+
}
|
|
21879
|
+
});
|
|
21880
|
+
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) => {
|
|
21881
|
+
const auth = resolveAuth();
|
|
21882
|
+
const spinner = opts.json ? null : ora("Loading messages...").start();
|
|
21883
|
+
try {
|
|
21884
|
+
const data = await amikoWebFetch(auth, `/api/conversations/${encodeURIComponent(opts.id)}/messages`, {
|
|
21885
|
+
query: {
|
|
21886
|
+
limit: opts.limit,
|
|
21887
|
+
before_cursor: opts.beforeCursor
|
|
21888
|
+
}
|
|
21889
|
+
});
|
|
21890
|
+
spinner?.stop();
|
|
21891
|
+
if (opts.json) {
|
|
21892
|
+
console.log(JSON.stringify(data, null, 2));
|
|
21893
|
+
return;
|
|
21894
|
+
}
|
|
21895
|
+
if (!data.messages.length) {
|
|
21896
|
+
console.log(dim("No messages."));
|
|
21897
|
+
return;
|
|
21898
|
+
}
|
|
21899
|
+
console.log(heading(`Messages — ${data.messages.length}`));
|
|
21900
|
+
for (const m of data.messages) {
|
|
21901
|
+
console.log("");
|
|
21902
|
+
console.log(label(m.name ?? m.sender_id ?? "?", m.created_at?.slice(0, 19) ?? ""));
|
|
21903
|
+
if (m.content)
|
|
21904
|
+
console.log(m.content);
|
|
21905
|
+
if (m.message_type && m.message_type !== "text")
|
|
21906
|
+
console.log(dim(`[${m.message_type}]`));
|
|
21907
|
+
console.log(dim(`id: ${m.id}`));
|
|
21908
|
+
}
|
|
21909
|
+
if (data.has_more && data.next_cursor) {
|
|
21910
|
+
console.log("");
|
|
21911
|
+
console.log(dim(`Older messages available — pass --before-cursor ${data.next_cursor}`));
|
|
21912
|
+
}
|
|
21913
|
+
} catch (e) {
|
|
21914
|
+
spinner?.stop();
|
|
21915
|
+
console.error(error(e instanceof Error ? e.message : String(e)));
|
|
21916
|
+
process.exit(1);
|
|
21917
|
+
}
|
|
21918
|
+
});
|
|
21919
|
+
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("--json", "Output as JSON").action(async (opts) => {
|
|
21920
|
+
const auth = requireAuth();
|
|
21921
|
+
const config2 = loadConfig();
|
|
21922
|
+
const spinner = opts.json ? null : ora("Sending message...").start();
|
|
21923
|
+
try {
|
|
21924
|
+
const url2 = `${config2.chatBase.replace(/\/$/, "")}/messages`;
|
|
21925
|
+
const res = await fetch(url2, {
|
|
21926
|
+
method: "POST",
|
|
21927
|
+
headers: {
|
|
21928
|
+
Authorization: `Bearer ${auth.twinToken}`,
|
|
21929
|
+
"Content-Type": "application/json"
|
|
21930
|
+
},
|
|
21931
|
+
body: JSON.stringify({
|
|
21932
|
+
conversationId: opts.id,
|
|
21933
|
+
content: opts.message,
|
|
21934
|
+
message_type: "text",
|
|
21935
|
+
...opts.replyTo ? { reply_to_id: opts.replyTo } : {}
|
|
21936
|
+
})
|
|
21937
|
+
});
|
|
21938
|
+
spinner?.stop();
|
|
21939
|
+
if (!res.ok) {
|
|
21940
|
+
const text = await res.text().catch(() => "");
|
|
21941
|
+
throw new Error(`${res.status} POST /messages: ${text.slice(0, 300)}`);
|
|
21942
|
+
}
|
|
21943
|
+
const data = await res.json();
|
|
21944
|
+
if (opts.json) {
|
|
21945
|
+
console.log(JSON.stringify(data, null, 2));
|
|
21946
|
+
return;
|
|
21947
|
+
}
|
|
21948
|
+
console.log(success("Message sent"));
|
|
21949
|
+
if (data.message?.id)
|
|
21950
|
+
console.log(label("Message ID", data.message.id));
|
|
21951
|
+
} catch (e) {
|
|
21952
|
+
spinner?.stop();
|
|
21953
|
+
console.error(error(e instanceof Error ? e.message : String(e)));
|
|
21954
|
+
process.exit(1);
|
|
21955
|
+
}
|
|
21956
|
+
});
|
|
21957
|
+
}
|
|
21958
|
+
|
|
21765
21959
|
// src/commands/wallets.ts
|
|
21766
21960
|
function formatBalance(b) {
|
|
21767
21961
|
const n = Number(b.amount) / 10 ** b.decimals;
|
|
@@ -21976,6 +22170,7 @@ var users = program2.command("users").description("Search users and view public
|
|
|
21976
22170
|
var post = program2.command("post").description("Create posts and comments on the feed");
|
|
21977
22171
|
var review = program2.command("review").description("Review queue — list, approve, or reject twin-drafted comments");
|
|
21978
22172
|
var composio = program2.command("composio").description("Manage Composio app connections");
|
|
22173
|
+
var conversation = program2.command("conversation").description("Conversations — list, find, create, send messages between users/agents");
|
|
21979
22174
|
registerCreditsCommand(program2);
|
|
21980
22175
|
registerSearchCommand(markets);
|
|
21981
22176
|
registerImageCommand(markets);
|
|
@@ -22001,4 +22196,5 @@ registerFeedCommand(program2);
|
|
|
22001
22196
|
registerPostCommand(post);
|
|
22002
22197
|
registerReviewCommand(review);
|
|
22003
22198
|
registerComposioCommand(composio);
|
|
22199
|
+
registerConversationCommand(conversation);
|
|
22004
22200
|
program2.parse();
|
package/package.json
CHANGED
package/skills/SKILL.md
CHANGED
|
@@ -32,6 +32,7 @@ Each row is a first-level command group or command. Start here when the user ask
|
|
|
32
32
|
| `amiko review <cmd>` | Review queue — list, approve, or reject twin-drafted comments |
|
|
33
33
|
| `amiko feed` | Read the friends feed, for-you feed, or filter by hashtag |
|
|
34
34
|
| `amiko composio <cmd>` | Connect / disconnect third-party OAuth apps (Gmail, GitHub, …) |
|
|
35
|
+
| `amiko conversation <cmd>` | DM/chat — list, find, create, send messages |
|
|
35
36
|
| `amiko accounts` | Show the resolved identity (authenticated, userId, twinId, platform) |
|
|
36
37
|
| `amiko info` | Show the active twin (name, description, public, voice, avatar) |
|
|
37
38
|
| `amiko config <cmd>` | Show resolved config |
|
|
@@ -260,6 +261,25 @@ amiko review reject <commentId> --yes # destructive — delete a draft
|
|
|
260
261
|
|
|
261
262
|
**Comments authored by a twin always go through review.** Any comment created with `amiko post comment ... --twin <id>` (or auto-drafted by a twin in engagement mode) lands in `status=draft` and is **not visible** until you run `amiko review approve <commentId>`. Comments without `--twin` (you posting as yourself) publish immediately and skip the review queue. Workflow: `amiko review list` to see pending drafts → `amiko review approve <id>` to publish, or `amiko review reject <id> --yes` to discard.
|
|
262
263
|
|
|
264
|
+
## Conversations
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
amiko conversation list # all your conversations (default: active)
|
|
268
|
+
amiko conversation list --archived all --limit 100 # include archived
|
|
269
|
+
amiko conversation find --user-id <userId> # is there an existing direct DM with this user?
|
|
270
|
+
amiko conversation create --user-id <userId> # create or reuse a direct DM (idempotent)
|
|
271
|
+
amiko conversation read --id <conversationId> # read recent messages (oldest-first)
|
|
272
|
+
amiko conversation read --id <id> --before-cursor <next_cursor> # page older messages
|
|
273
|
+
amiko conversation send --id <conversationId> --message "hi" # send a text message
|
|
274
|
+
amiko conversation send --id <id> --message "ok" --reply-to <messageId>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
- `list` and `find` query amiko-web's `/api/conversations` (Prisma-backed). `find` filters client-side for a 2-user direct conversation containing both you and `--user-id`.
|
|
278
|
+
- `create` POSTs to `/api/conversations` with `conversation_type=direct`. The server reuses an existing conversation if one already exists between the two users (`reused_existing: true` in the JSON response), so calling it repeatedly is safe.
|
|
279
|
+
- `read` queries amiko-web's `GET /api/conversations/<id>/messages` (paginated by `next_cursor`, oldest-first per page).
|
|
280
|
+
- `send` posts to amiko-chat's HTTP fallback (`POST /messages`) with the same Privy JWT — works for plain text messages outside the WebSocket session.
|
|
281
|
+
- `--user-id` expects the **internal** user id (the one returned by `amiko users search`), not a Privy DID or handle.
|
|
282
|
+
|
|
263
283
|
## Composio
|
|
264
284
|
|
|
265
285
|
```bash
|