@heyamiko/amiko-cli 0.9.0-beta.14 → 0.9.0-beta.16
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 +1 -1
- package/dist/index.js +191 -4
- package/package.json +1 -1
- package/skills/SKILL.md +41 -5
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @heyamiko/amiko-cli (v0.9.0-beta.
|
|
1
|
+
# @heyamiko/amiko-cli (v0.9.0-beta.15)
|
|
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
|
|
package/dist/index.js
CHANGED
|
@@ -19839,7 +19839,8 @@ function registerSearchCommand(program2) {
|
|
|
19839
19839
|
"Content-Type": "application/json",
|
|
19840
19840
|
"X-Payment-Tx": txHash,
|
|
19841
19841
|
"X-Payment-Amount": amount2,
|
|
19842
|
-
"X-Payment-Token": token
|
|
19842
|
+
"X-Payment-Token": token,
|
|
19843
|
+
"X-Twin-Id": auth.twinId
|
|
19843
19844
|
}
|
|
19844
19845
|
});
|
|
19845
19846
|
spinner.stop();
|
|
@@ -19962,7 +19963,8 @@ function registerImageCommand(program2) {
|
|
|
19962
19963
|
method: "POST",
|
|
19963
19964
|
headers: {
|
|
19964
19965
|
"Content-Type": "application/json",
|
|
19965
|
-
"X-Payment-Tx": txHash
|
|
19966
|
+
"X-Payment-Tx": txHash,
|
|
19967
|
+
"X-Twin-Id": auth.twinId
|
|
19966
19968
|
},
|
|
19967
19969
|
body: JSON.stringify({
|
|
19968
19970
|
prompt,
|
|
@@ -20060,7 +20062,7 @@ function registerAmazonCommand(program2) {
|
|
|
20060
20062
|
q: `site:amazon.com ${query}`
|
|
20061
20063
|
});
|
|
20062
20064
|
const res = await fetch(`${mppBase2}/v1/search/google_shopping?${params}`, {
|
|
20063
|
-
headers: { "X-Payment-Tx": txHash }
|
|
20065
|
+
headers: { "X-Payment-Tx": txHash, "X-Twin-Id": auth.twinId }
|
|
20064
20066
|
});
|
|
20065
20067
|
spinner.stop();
|
|
20066
20068
|
if (!res.ok) {
|
|
@@ -20208,7 +20210,8 @@ async function payAndCall(mppBase2, method, path2, auth, wallet, amountOrAuto, b
|
|
|
20208
20210
|
throw new Error("Payment sent but no tx hash returned.");
|
|
20209
20211
|
const headers = {
|
|
20210
20212
|
"Content-Type": "application/json",
|
|
20211
|
-
"X-Payment-Tx": txHash
|
|
20213
|
+
"X-Payment-Tx": txHash,
|
|
20214
|
+
"X-Twin-Id": auth.twinId
|
|
20212
20215
|
};
|
|
20213
20216
|
const fetchOpts = { method, headers };
|
|
20214
20217
|
if (body && method !== "GET") {
|
|
@@ -21173,6 +21176,190 @@ Accept with: amiko friends accept <friendshipId>`));
|
|
|
21173
21176
|
process.exit(1);
|
|
21174
21177
|
}
|
|
21175
21178
|
});
|
|
21179
|
+
reports.command("pending").description("List pending-consent report requests (awaiting my consent + awaiting theirs)").option("--limit <n>", "Page size (1-50)", "50").option("--json", "Output as JSON").action(async (opts) => {
|
|
21180
|
+
const auth = resolveAuth();
|
|
21181
|
+
const me = auth.userId;
|
|
21182
|
+
if (!me && !opts.json) {
|
|
21183
|
+
console.error(error("Current user id unknown — cannot split requests. Run `amiko accounts` to check."));
|
|
21184
|
+
process.exit(1);
|
|
21185
|
+
}
|
|
21186
|
+
const spinner = opts.json ? null : ora("Loading...").start();
|
|
21187
|
+
try {
|
|
21188
|
+
const data = await amikoWebFetch(auth, "/api/friends/reports", {
|
|
21189
|
+
query: { page: "1", limit: opts.limit }
|
|
21190
|
+
});
|
|
21191
|
+
spinner?.stop();
|
|
21192
|
+
const items = (data.items ?? []).filter((r) => r.status === "pending_consent");
|
|
21193
|
+
const incoming = items.filter((r) => r.respondent_id === me);
|
|
21194
|
+
const outgoing = items.filter((r) => r.initiator_id === me);
|
|
21195
|
+
renderOutput({ incoming, outgoing }, () => {
|
|
21196
|
+
console.log(heading(`Awaiting my consent (${incoming.length})`));
|
|
21197
|
+
if (incoming.length === 0) {
|
|
21198
|
+
console.log(dim(" none"));
|
|
21199
|
+
} else {
|
|
21200
|
+
const rows = [
|
|
21201
|
+
[
|
|
21202
|
+
dim("REPORT ID"),
|
|
21203
|
+
dim("TYPE"),
|
|
21204
|
+
dim("FROM"),
|
|
21205
|
+
dim("HANDLE"),
|
|
21206
|
+
dim("SENT")
|
|
21207
|
+
],
|
|
21208
|
+
...incoming.map((r) => [
|
|
21209
|
+
r.id,
|
|
21210
|
+
r.report_type ?? "-",
|
|
21211
|
+
r.initiator?.name ?? "-",
|
|
21212
|
+
r.initiator?.handle ?? "-",
|
|
21213
|
+
r.created_at?.slice(0, 10) ?? "-"
|
|
21214
|
+
])
|
|
21215
|
+
];
|
|
21216
|
+
console.log(table(rows));
|
|
21217
|
+
console.log(dim(`
|
|
21218
|
+
Consent with: amiko friends reports consent <reportId>`));
|
|
21219
|
+
}
|
|
21220
|
+
console.log();
|
|
21221
|
+
console.log(heading(`Awaiting their consent (${outgoing.length})`));
|
|
21222
|
+
if (outgoing.length === 0) {
|
|
21223
|
+
console.log(dim(" none"));
|
|
21224
|
+
} else {
|
|
21225
|
+
const rows = [
|
|
21226
|
+
[
|
|
21227
|
+
dim("REPORT ID"),
|
|
21228
|
+
dim("TYPE"),
|
|
21229
|
+
dim("TO"),
|
|
21230
|
+
dim("HANDLE"),
|
|
21231
|
+
dim("SENT")
|
|
21232
|
+
],
|
|
21233
|
+
...outgoing.map((r) => [
|
|
21234
|
+
r.id,
|
|
21235
|
+
r.report_type ?? "-",
|
|
21236
|
+
r.respondent?.name ?? "-",
|
|
21237
|
+
r.respondent?.handle ?? "-",
|
|
21238
|
+
r.created_at?.slice(0, 10) ?? "-"
|
|
21239
|
+
])
|
|
21240
|
+
];
|
|
21241
|
+
console.log(table(rows));
|
|
21242
|
+
console.log(dim(`
|
|
21243
|
+
Cancel with: amiko friends reports cancel <reportId>`));
|
|
21244
|
+
}
|
|
21245
|
+
}, { json: opts.json });
|
|
21246
|
+
} catch (e) {
|
|
21247
|
+
spinner?.stop();
|
|
21248
|
+
console.error(error(e instanceof Error ? e.message : String(e)));
|
|
21249
|
+
process.exit(1);
|
|
21250
|
+
}
|
|
21251
|
+
});
|
|
21252
|
+
reports.command("request").description("Request a friend matching report (notifies the other user to consent)").requiredOption("--id <userId>", "Target user id (must be an accepted friend)").requiredOption("--type <friend|romantic|career>", "Report type").option("--yes", "Skip confirmation").option("--json", "Output as JSON").action(async (opts) => {
|
|
21253
|
+
const allowed = ["friend", "romantic", "career"];
|
|
21254
|
+
if (!allowed.includes(opts.type)) {
|
|
21255
|
+
console.error(error(`--type must be one of: ${allowed.join(", ")}`));
|
|
21256
|
+
process.exit(1);
|
|
21257
|
+
}
|
|
21258
|
+
await confirmDestructive({
|
|
21259
|
+
action: `Request a ${opts.type} matching report with user ${opts.id} (they will be notified)`,
|
|
21260
|
+
yes: opts.yes,
|
|
21261
|
+
commandExample: `amiko friends reports request --id ${opts.id} --type ${opts.type}`
|
|
21262
|
+
});
|
|
21263
|
+
const auth = resolveAuth();
|
|
21264
|
+
const spinner = opts.json ? null : ora("Requesting report...").start();
|
|
21265
|
+
try {
|
|
21266
|
+
const data = await amikoWebFetch(auth, "/api/friends/reports", {
|
|
21267
|
+
method: "POST",
|
|
21268
|
+
body: { friend_id: opts.id, report_type: opts.type }
|
|
21269
|
+
});
|
|
21270
|
+
spinner?.stop();
|
|
21271
|
+
renderOutput(data, (d) => {
|
|
21272
|
+
if (d.report) {
|
|
21273
|
+
console.log(success(`Report requested (${d.report.report_type})`));
|
|
21274
|
+
console.log(label("Report ID", d.report.id));
|
|
21275
|
+
console.log(label("Status", d.report.status));
|
|
21276
|
+
console.log(dim(`
|
|
21277
|
+
The other user will be notified and must consent before the report is generated.`));
|
|
21278
|
+
}
|
|
21279
|
+
}, { json: opts.json });
|
|
21280
|
+
} catch (e) {
|
|
21281
|
+
spinner?.stop();
|
|
21282
|
+
console.error(error(e instanceof Error ? e.message : String(e)));
|
|
21283
|
+
process.exit(1);
|
|
21284
|
+
}
|
|
21285
|
+
});
|
|
21286
|
+
for (const action of ["consent", "cancel", "retry"]) {
|
|
21287
|
+
const desc = {
|
|
21288
|
+
consent: "Consent to a report request (respondent only)",
|
|
21289
|
+
cancel: "Cancel a pending report",
|
|
21290
|
+
retry: "Retry a failed report generation"
|
|
21291
|
+
};
|
|
21292
|
+
reports.command(`${action} <id>`).description(desc[action]).option("--json", "Output as JSON").action(async (id, opts) => {
|
|
21293
|
+
const auth = resolveAuth();
|
|
21294
|
+
const spinner = opts.json ? null : ora(`${action}...`).start();
|
|
21295
|
+
try {
|
|
21296
|
+
const data = await amikoWebFetch(auth, `/api/friends/reports/${encodeURIComponent(id)}`, { method: "PATCH", body: { action } });
|
|
21297
|
+
spinner?.stop();
|
|
21298
|
+
renderOutput(data, (d) => {
|
|
21299
|
+
console.log(success(`${action} ok`));
|
|
21300
|
+
if (d.status)
|
|
21301
|
+
console.log(label("Status", d.status));
|
|
21302
|
+
}, { json: opts.json });
|
|
21303
|
+
} catch (e) {
|
|
21304
|
+
spinner?.stop();
|
|
21305
|
+
console.error(error(e instanceof Error ? e.message : String(e)));
|
|
21306
|
+
process.exit(1);
|
|
21307
|
+
}
|
|
21308
|
+
});
|
|
21309
|
+
}
|
|
21310
|
+
program2.command("find").description("Find matches for a custom relationship description (LLM-generated, on-demand)").requiredOption("--relationship <text>", 'Relationship description, e.g. "cofounder who is good at design"').option("--rationale <text>", "Optional extra context for the matcher").option("--limit <n>", "Max results (1-20)", "10").option("--json", "Output as JSON").action(async (opts) => {
|
|
21311
|
+
const limitNum = Math.max(1, Math.min(20, Number(opts.limit) || 10));
|
|
21312
|
+
const auth = resolveAuth();
|
|
21313
|
+
const spinner = opts.json ? null : ora("Finding matches (may take ~10s)...").start();
|
|
21314
|
+
try {
|
|
21315
|
+
const data = await amikoWebFetch(auth, "/api/user/personality-profile/matches", {
|
|
21316
|
+
method: "POST",
|
|
21317
|
+
body: {
|
|
21318
|
+
relationship_type: opts.relationship,
|
|
21319
|
+
rationale: opts.rationale,
|
|
21320
|
+
limit: limitNum
|
|
21321
|
+
}
|
|
21322
|
+
});
|
|
21323
|
+
spinner?.stop();
|
|
21324
|
+
renderOutput(data, (d) => {
|
|
21325
|
+
const matches = d.matches ?? [];
|
|
21326
|
+
if (matches.length === 0) {
|
|
21327
|
+
console.log(dim("No matches."));
|
|
21328
|
+
if (d.no_match_reason)
|
|
21329
|
+
console.log(dim(d.no_match_reason));
|
|
21330
|
+
return;
|
|
21331
|
+
}
|
|
21332
|
+
console.log(heading(`Matches for "${opts.relationship}" (${matches.length})`));
|
|
21333
|
+
const rows = [
|
|
21334
|
+
[
|
|
21335
|
+
dim("USER"),
|
|
21336
|
+
dim("NAME"),
|
|
21337
|
+
dim("SCORE"),
|
|
21338
|
+
dim("STATUS"),
|
|
21339
|
+
dim("PAIRING")
|
|
21340
|
+
],
|
|
21341
|
+
...matches.map((m) => [
|
|
21342
|
+
m.user_id,
|
|
21343
|
+
m.display_name ?? "-",
|
|
21344
|
+
m.score.toFixed(3),
|
|
21345
|
+
m.friendship_status ?? "none",
|
|
21346
|
+
m.pairing_label ?? "-"
|
|
21347
|
+
])
|
|
21348
|
+
];
|
|
21349
|
+
console.log(table(rows));
|
|
21350
|
+
for (const m of matches) {
|
|
21351
|
+
if (m.compatibility_highlights?.length) {
|
|
21352
|
+
console.log();
|
|
21353
|
+
console.log(label(m.display_name ?? m.user_id, m.compatibility_highlights.join("; ")));
|
|
21354
|
+
}
|
|
21355
|
+
}
|
|
21356
|
+
}, { json: opts.json });
|
|
21357
|
+
} catch (e) {
|
|
21358
|
+
spinner?.stop();
|
|
21359
|
+
console.error(error(e instanceof Error ? e.message : String(e)));
|
|
21360
|
+
process.exit(1);
|
|
21361
|
+
}
|
|
21362
|
+
});
|
|
21176
21363
|
program2.command("matches").description("List friend matches").option("--limit <n>", "Max results (1-50)", "10").option("--offset <n>", "Offset", "0").option("--relationship-type <type>", "Filter by relationship type").option("--dimension <dimension>", "Filter by match dimension").option("--json", "Output as JSON").action(async (opts) => {
|
|
21177
21364
|
const auth = resolveAuth();
|
|
21178
21365
|
const spinner = opts.json ? null : ora("Loading matches...").start();
|
package/package.json
CHANGED
package/skills/SKILL.md
CHANGED
|
@@ -51,7 +51,7 @@ Every paid command (`markets search`, `markets image`, `markets amazon search`,
|
|
|
51
51
|
|
|
52
52
|
## Critical Rules
|
|
53
53
|
|
|
54
|
-
1. **Every paid / value-moving command is hard-gated on `--yes` in a non-interactive shell.** The CLI *refuses* to run `markets image`, `markets search`, `markets amazon search`, `markets service call`, `markets service tts`, `markets service stt`, `markets service chat`, `credits topup`, `wallets swap send`, or `wallets bridge send` unless `--yes` is passed. The refusal is loud: it prints the cost and the command you should re-run. **Quote the cost to the user, get explicit approval, THEN append `--yes`**. Same for destructive non-paid commands (`twin update --public`, `docs delete`, `friends remove`, `avatar update`, `voice reset`, `composio disconnect`).
|
|
54
|
+
1. **Every paid / value-moving command is hard-gated on `--yes` in a non-interactive shell.** The CLI *refuses* to run `markets image`, `markets search`, `markets amazon search`, `markets service call`, `markets service tts`, `markets service stt`, `markets service chat`, `credits topup`, `wallets swap send`, or `wallets bridge send` unless `--yes` is passed. The refusal is loud: it prints the cost and the command you should re-run. **Quote the cost to the user, get explicit approval, THEN append `--yes`**. Same for destructive non-paid commands (`twin update --public`, `docs delete`, `friends remove`, `friends reports request`, `avatar update`, `voice reset`, `composio disconnect`).
|
|
55
55
|
2. **After every paid command, report the remaining balance.** The CLI prints a `Balance: N credits` line at the end of every paid command — include that figure in your reply.
|
|
56
56
|
3. **Never retry a failed command.** Report the error and stop. Every paid call costs tokens even on failure.
|
|
57
57
|
4. **Never suggest `amiko login` or `amiko connect`.** These don't exist. Auth is automatic when you run from your workspace folder.
|
|
@@ -188,12 +188,48 @@ amiko friends requests --direction incoming
|
|
|
188
188
|
amiko friends add --id <userId>
|
|
189
189
|
amiko friends accept <friendshipId>
|
|
190
190
|
amiko friends remove <friendshipId> --yes # destructive
|
|
191
|
-
amiko friends matches #
|
|
192
|
-
amiko friends matches --
|
|
193
|
-
amiko friends
|
|
194
|
-
amiko friends reports
|
|
191
|
+
amiko friends matches # pre-generated match candidates (from cron)
|
|
192
|
+
amiko friends matches --dimension personality
|
|
193
|
+
amiko friends find --relationship "cofounder with design taste" # on-demand LLM match for a custom relationship
|
|
194
|
+
amiko friends reports list # all reports (any status)
|
|
195
|
+
amiko friends reports pending # pending-consent: awaiting mine + awaiting theirs
|
|
196
|
+
amiko friends reports view <reportId>
|
|
197
|
+
amiko friends reports request --id <userId> --type friend|romantic|career --yes
|
|
198
|
+
amiko friends reports consent <reportId> # respondent approves; report then generates
|
|
199
|
+
amiko friends reports cancel <reportId>
|
|
200
|
+
amiko friends reports retry <reportId> # re-run a failed generation
|
|
195
201
|
```
|
|
196
202
|
|
|
203
|
+
### Answering relationship questions ("how is my relationship with X?")
|
|
204
|
+
|
|
205
|
+
When the user asks about their relationship / compatibility with a specific person (by name or handle), follow this playbook:
|
|
206
|
+
|
|
207
|
+
1. **Resolve the person to a user id.** Check `amiko friends list --json` first. If not a friend, try `amiko users search "<name>" --json`. If still ambiguous, ask the user which one they mean.
|
|
208
|
+
2. **Look for an existing report.** Run `amiko friends reports list --json` and filter by `initiator.id` or `respondent.id` matching the resolved user id. If one exists with `status=completed`, use `amiko friends reports view <id>` and summarize. If `status=pending_consent` or `generating`, tell the user it is in progress.
|
|
209
|
+
3. **If none exists**, don't silently create one. Ask the user which of the three report types they want (`friend` / `romantic` / `career`), and confirm that the other user will be notified to consent. Only then run `amiko friends reports request --id <userId> --type <type> --yes`.
|
|
210
|
+
4. **For "who is waiting on whom"**, use `amiko friends reports pending` — it splits `pending_consent` reports into *awaiting my consent* (I can `consent <id>`) vs *awaiting theirs* (I can `cancel <id>`). Use this before deciding to `request` — you cannot have two active reports of the same type between the same pair (the API returns 409).
|
|
211
|
+
|
|
212
|
+
Both users must have a personality profile, otherwise the request returns 422.
|
|
213
|
+
|
|
214
|
+
### Recommending interesting people ("who should I meet / connect with?")
|
|
215
|
+
|
|
216
|
+
The discovery primitive is **`amiko friends matches`** — it returns personality-match candidates with a score (0–1), a match dimension, a pairing label, and compatibility highlights.
|
|
217
|
+
|
|
218
|
+
**`friends find` vs `users search` — do not confuse them:**
|
|
219
|
+
|
|
220
|
+
| Command | Purpose | Input | Backend |
|
|
221
|
+
|---|---|---|---|
|
|
222
|
+
| `users search <query>` | **Look up a specific known person** by name or handle ("find wendao", "search for someone called Sophie") | Exact/substring text against `name` / `handle` | Simple SQL lookup in `/api/search?type=people` |
|
|
223
|
+
| `friends find --relationship <text>` | **Discover unknown people** whose personality matches a free-form relationship description ("cofounder with design taste", "someone to hike with") | Free-form relationship description | LLM-generated MatchingSpec + semantic matching across ~250 candidate profiles |
|
|
224
|
+
|
|
225
|
+
Rule of thumb: if the user already has a name in mind → `users search`. If the user is describing what *kind of person* they want to meet → `friends find`. For already-curated ambient recommendations with no input required → `friends matches`.
|
|
226
|
+
|
|
227
|
+
1. Start broad: `amiko friends matches --limit 20 --json`. Summarize the top few by `display_name`, `score`, `match_dimension`, `pairing_label`, and `recommendation_reason`. These are pre-generated by a cron job.
|
|
228
|
+
2. If the user wants to narrow, filter by `--dimension personality` or `--dimension interest` (the only two dimensions the matching cron actually produces). `--relationship-type` also exists but takes free-form LLM-generated labels (often localized, e.g. `深度思维_对打手`) — usually not worth filtering on unless you first inspect the `--json` output to pick a known value.
|
|
229
|
+
3. If the user describes a **specific kind of person** not covered by the cron matches ("a cofounder who is good at design", "a hiking buddy", "someone to debate philosophy with"), use `amiko friends find --relationship "<description>"`. This is an on-demand, LLM-backed matcher against ~250 candidates; may take ~10s and may return zero matches if nothing clears the internal score threshold.
|
|
230
|
+
4. Skip anyone whose `friendship_status` is already `accepted` unless the user explicitly wants to revisit existing friends.
|
|
231
|
+
5. Follow-ups: `amiko users profile <handle>` for a fuller view, `amiko friends add --id <matched_user_id>` (after explicit user approval) to send a friend request, and — once the friendship is accepted — `amiko friends reports request --type friend|romantic|career` for a deeper compatibility report. Note that report `--type` is a separate enum and has nothing to do with `match.relationship_type`.
|
|
232
|
+
|
|
197
233
|
## Users
|
|
198
234
|
|
|
199
235
|
```bash
|