@heyamiko/amiko-cli 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1618 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/browse.ts
7
+ import chalk2 from "chalk";
8
+ import ora from "ora";
9
+
10
+ // src/lib/config.ts
11
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
12
+ import { homedir } from "os";
13
+ import { join } from "path";
14
+ var CONFIG_DIR = join(homedir(), ".amiko");
15
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
16
+ var DEFAULTS = {
17
+ apiBase: "https://mpp.heyamiko.com",
18
+ billingBase: "https://billing.heyamiko.com",
19
+ siteBase: "https://amikomarkets.com"
20
+ };
21
+ var LEGACY_SITE_BASES = /* @__PURE__ */ new Set([
22
+ "https://amiko-markets.vercel.app",
23
+ "https://amiko-markets.vercel.app/"
24
+ ]);
25
+ function loadConfig() {
26
+ if (!existsSync(CONFIG_FILE)) return { ...DEFAULTS };
27
+ try {
28
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
29
+ const parsed = JSON.parse(raw);
30
+ return normalizeConfig({ ...DEFAULTS, ...parsed });
31
+ } catch {
32
+ return { ...DEFAULTS };
33
+ }
34
+ }
35
+ function saveConfig(config) {
36
+ const current = loadConfig();
37
+ const merged = normalizeConfig({ ...current, ...config });
38
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
39
+ writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n");
40
+ return merged;
41
+ }
42
+ function getConfigPath() {
43
+ return CONFIG_FILE;
44
+ }
45
+ function resetConfig() {
46
+ if (!existsSync(CONFIG_FILE)) {
47
+ return false;
48
+ }
49
+ unlinkSync(CONFIG_FILE);
50
+ return true;
51
+ }
52
+ function getPrivyToken(config) {
53
+ return config.privyToken || config.creatorToken;
54
+ }
55
+ function normalizeConfig(config) {
56
+ const normalized = { ...config };
57
+ if (!normalized.privyToken && normalized.creatorToken) {
58
+ normalized.privyToken = normalized.creatorToken;
59
+ }
60
+ if (LEGACY_SITE_BASES.has(normalized.siteBase)) {
61
+ normalized.siteBase = DEFAULTS.siteBase;
62
+ }
63
+ return normalized;
64
+ }
65
+
66
+ // src/lib/api.ts
67
+ function mppBase() {
68
+ return trimBase(loadConfig().apiBase);
69
+ }
70
+ function billingBase() {
71
+ return trimBase(loadConfig().billingBase);
72
+ }
73
+ function authHeaders() {
74
+ const config = loadConfig();
75
+ const headers = { "Content-Type": "application/json" };
76
+ if (config.apiKey) {
77
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
78
+ }
79
+ return headers;
80
+ }
81
+ function creatorHeaders() {
82
+ const config = loadConfig();
83
+ const headers = { "Content-Type": "application/json" };
84
+ const token = config.privyToken || config.creatorToken;
85
+ if (token) {
86
+ headers["Authorization"] = `Bearer ${token}`;
87
+ }
88
+ return headers;
89
+ }
90
+ async function get(path) {
91
+ const res = await fetch(`${mppBase()}${path}`, { headers: authHeaders() });
92
+ if (!res.ok) {
93
+ const text = await res.text().catch(() => "");
94
+ throw new Error(`${res.status} ${res.statusText}: ${text}`);
95
+ }
96
+ return res.json();
97
+ }
98
+ async function post(path, body) {
99
+ const res = await fetch(`${mppBase()}${path}`, {
100
+ method: "POST",
101
+ headers: authHeaders(),
102
+ body: body ? JSON.stringify(body) : void 0
103
+ });
104
+ if (res.status === 402) {
105
+ const data = await res.json();
106
+ const err = new Error("Payment required");
107
+ err.status = 402;
108
+ err.data = data;
109
+ throw err;
110
+ }
111
+ if (!res.ok) {
112
+ const text = await res.text().catch(() => "");
113
+ throw new Error(`${res.status} ${res.statusText}: ${text}`);
114
+ }
115
+ return res.json();
116
+ }
117
+ async function creatorPost(path, body) {
118
+ const res = await fetch(`${billingBase()}${path}`, {
119
+ method: "POST",
120
+ headers: creatorHeaders(),
121
+ body: body ? JSON.stringify(body) : void 0
122
+ });
123
+ if (!res.ok) {
124
+ const text = await res.text().catch(() => "");
125
+ throw new Error(`${res.status} ${res.statusText}: ${text}`);
126
+ }
127
+ return res.json();
128
+ }
129
+ async function creatorGet(path) {
130
+ const res = await fetch(`${billingBase()}${path}`, { headers: creatorHeaders() });
131
+ if (!res.ok) {
132
+ const text = await res.text().catch(() => "");
133
+ throw new Error(`${res.status} ${res.statusText}: ${text}`);
134
+ }
135
+ return res.json();
136
+ }
137
+ async function bootstrapPrivyAccount(token, base = loadConfig().billingBase) {
138
+ const res = await fetch(`${trimBase(base)}/api/auth/bootstrap`, {
139
+ headers: {
140
+ Authorization: `Bearer ${token}`,
141
+ "Content-Type": "application/json"
142
+ }
143
+ });
144
+ if (!res.ok) {
145
+ const text = await res.text().catch(() => "");
146
+ throw new Error(`${res.status} ${res.statusText}: ${text}`);
147
+ }
148
+ return res.json();
149
+ }
150
+ async function createConnectedKeyTopupCheckout(input) {
151
+ const siteBase = trimBase(input.siteBase);
152
+ const res = await fetch(`${trimBase(input.billingBase)}/api/user/keys/${input.keyId}/topup/stripe`, {
153
+ method: "POST",
154
+ headers: {
155
+ Authorization: `Bearer ${input.token}`,
156
+ "Content-Type": "application/json"
157
+ },
158
+ body: JSON.stringify({
159
+ amount: input.amount,
160
+ successUrl: `${siteBase}/?topup=success`,
161
+ cancelUrl: `${siteBase}/?topup=cancelled`
162
+ })
163
+ });
164
+ if (!res.ok) {
165
+ const text = await res.text().catch(() => "");
166
+ throw new Error(`${res.status} ${res.statusText}: ${text}`);
167
+ }
168
+ return res.json();
169
+ }
170
+ function trimBase(base) {
171
+ return base.replace(/\/$/, "");
172
+ }
173
+
174
+ // src/lib/format.ts
175
+ import chalk from "chalk";
176
+ function table(rows) {
177
+ if (rows.length === 0) return "";
178
+ const cols = rows[0].length;
179
+ const widths = Array.from(
180
+ { length: cols },
181
+ (_, i) => Math.max(...rows.map((r) => stripAnsi(r[i] || "").length))
182
+ );
183
+ return rows.map(
184
+ (row) => row.map((cell, i) => pad(cell, widths[i])).join(" ")
185
+ ).join("\n");
186
+ }
187
+ function pad(str, width) {
188
+ const visible = stripAnsi(str).length;
189
+ return str + " ".repeat(Math.max(0, width - visible));
190
+ }
191
+ function stripAnsi(str) {
192
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
193
+ }
194
+ function usd(amount) {
195
+ if (!Number.isFinite(amount) || amount <= 0) return chalk.dim("$0.00");
196
+ if (amount < 0.01) return chalk.green(`$${amount}`);
197
+ return chalk.green(`$${amount.toFixed(2)}`);
198
+ }
199
+ function truncAddr(addr) {
200
+ if (addr.length <= 12) return addr;
201
+ return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
202
+ }
203
+ function heading(text) {
204
+ return chalk.bold.white(text);
205
+ }
206
+ function dim(text) {
207
+ return chalk.dim(text);
208
+ }
209
+ function label(key, value) {
210
+ return `${chalk.dim(key + ":")} ${value}`;
211
+ }
212
+ function error(text) {
213
+ return chalk.red("\u2717 ") + text;
214
+ }
215
+ function success(text) {
216
+ return chalk.green("\u2713 ") + text;
217
+ }
218
+ function warn(text) {
219
+ return chalk.yellow("\u26A0 ") + text;
220
+ }
221
+
222
+ // src/commands/browse.ts
223
+ function siteUrl(agentSlug) {
224
+ const config = loadConfig();
225
+ return `${config.siteBase}/agents/${agentSlug}`;
226
+ }
227
+ function registerBrowseCommand(program2) {
228
+ program2.command("browse").description("Browse marketplace agents").option("-c, --category <cat>", "Filter by category").option("-s, --sort <sort>", "Sort by: popular, newest, rating, revenue", "popular").option("-l, --limit <n>", "Max results", "20").option("-q, --search <query>", "Search by name, description, or tags").action(async (opts) => {
229
+ const spinner = ora("Fetching agents...").start();
230
+ try {
231
+ const params = new URLSearchParams({
232
+ limit: opts.limit,
233
+ sort: opts.sort
234
+ });
235
+ if (opts.category) params.set("category", opts.category);
236
+ const data = await get(`/v1/marketplace/listings?${params}`);
237
+ spinner.stop();
238
+ let agents = data.agents || data.listings || [];
239
+ if (opts.search) {
240
+ const q = opts.search.toLowerCase();
241
+ agents = agents.filter(
242
+ (a) => a.name.toLowerCase().includes(q) || a.description.toLowerCase().includes(q) || a.tags?.some((t) => t.toLowerCase().includes(q))
243
+ );
244
+ }
245
+ if (agents.length === 0) {
246
+ console.log(dim("No agents found."));
247
+ return;
248
+ }
249
+ console.log(heading(`Marketplace \u2014 ${agents.length} agents
250
+ `));
251
+ const rows = agents.map((a) => {
252
+ const price = a.pricing?.per_message ?? a.price_per_message;
253
+ const interactions = a.stats?.interactions ?? a.total_interactions ?? 0;
254
+ const rating = a.stats?.rating ?? a.average_rating ?? 0;
255
+ return [
256
+ chalk2.white.bold(a.name),
257
+ chalk2.dim(a.category),
258
+ price != null ? usd(price) + dim("/msg") : dim("\u2014"),
259
+ interactions > 0 ? chalk2.cyan(interactions.toLocaleString()) + dim(" uses") : dim("0"),
260
+ rating > 0 ? chalk2.yellow(`\u2605 ${rating.toFixed(1)}`) : dim("\u2014")
261
+ ];
262
+ });
263
+ rows.unshift([
264
+ chalk2.dim.underline("NAME"),
265
+ chalk2.dim.underline("CATEGORY"),
266
+ chalk2.dim.underline("PRICE"),
267
+ chalk2.dim.underline("USAGE"),
268
+ chalk2.dim.underline("RATING")
269
+ ]);
270
+ console.log(table(rows));
271
+ console.log("");
272
+ console.log(dim("Tip: amiko info <agent> for details, or view online:"));
273
+ console.log(dim(` ${loadConfig().siteBase}`));
274
+ } catch (err) {
275
+ spinner.stop();
276
+ console.error(chalk2.red(err instanceof Error ? err.message : "Failed to fetch agents"));
277
+ process.exit(1);
278
+ }
279
+ });
280
+ program2.command("info").description("Show details for a specific agent").argument("<agent>", "Agent ID or slug").action(async (agentId) => {
281
+ const spinner = ora("Fetching agent...").start();
282
+ try {
283
+ let agent = null;
284
+ try {
285
+ agent = await get(`/v1/marketplace/listings/${agentId}`);
286
+ } catch {
287
+ const data = await get("/v1/marketplace/listings?limit=200");
288
+ const agents = data.agents || data.listings || [];
289
+ agent = agents.find(
290
+ (a) => a.agent_id === agentId || a.id === agentId || a.name.toLowerCase() === agentId.toLowerCase()
291
+ ) || null;
292
+ }
293
+ spinner.stop();
294
+ if (!agent) {
295
+ console.error(chalk2.red(`Agent "${agentId}" not found.`));
296
+ process.exit(1);
297
+ }
298
+ const priceMsg = agent.pricing?.per_message ?? agent.price_per_message;
299
+ const priceTask = agent.pricing?.per_task ?? agent.price_per_task;
300
+ const interactions = agent.stats?.interactions ?? agent.total_interactions ?? 0;
301
+ const rating = agent.stats?.rating ?? agent.average_rating ?? 0;
302
+ const revenue = agent.stats?.revenue ?? 0;
303
+ const creator = agent.creator?.name ?? agent.creator_name ?? "Unknown";
304
+ const slug = agent.agent_id || agentId;
305
+ console.log(heading(agent.name));
306
+ console.log(chalk2.dim(agent.description) + "\n");
307
+ console.log(label("Agent ID", slug));
308
+ console.log(label("Category", agent.category));
309
+ console.log(label("Creator", creator));
310
+ if (agent.creator?.wallet) {
311
+ console.log(label("Wallet", truncAddr(agent.creator.wallet)));
312
+ }
313
+ console.log(label("Tags", agent.tags?.length > 0 ? agent.tags.join(", ") : dim("none")));
314
+ console.log("");
315
+ if (agent.capabilities && agent.capabilities.length > 0) {
316
+ console.log(heading("Capabilities"));
317
+ for (const cap of agent.capabilities) {
318
+ console.log(` ${chalk2.green("\u2713")} ${cap}`);
319
+ }
320
+ console.log("");
321
+ }
322
+ console.log(heading("Pricing"));
323
+ if (priceMsg != null) console.log(label(" Per message", usd(priceMsg)));
324
+ if (priceTask != null) console.log(label(" Per task", usd(priceTask)));
325
+ if (priceMsg == null && priceTask == null) console.log(dim(" No pricing set"));
326
+ console.log("");
327
+ console.log(heading("Stats"));
328
+ console.log(label(" Usage", `${interactions.toLocaleString()} paid uses`));
329
+ console.log(label(" Rating", rating > 0 ? chalk2.yellow(`\u2605 ${rating.toFixed(1)}`) : dim("no ratings")));
330
+ if (revenue > 0) console.log(label(" Revenue", usd(revenue)));
331
+ if (agent.tars?.score) {
332
+ console.log(label(" TARS", chalk2.yellow(`\u2605 ${Math.min(agent.tars.score, 5).toFixed(1)}`) + dim(` (${agent.tars.totalJobs ?? 0} jobs)`)));
333
+ }
334
+ console.log("");
335
+ if (agent.example_prompts && agent.example_prompts.length > 0) {
336
+ console.log(heading("Example prompts"));
337
+ for (const p of agent.example_prompts) {
338
+ console.log(dim(` "${p}"`));
339
+ }
340
+ console.log("");
341
+ }
342
+ console.log(heading("Quick start"));
343
+ console.log(dim(` amiko call ${slug} "${agent.example_prompts?.[0] || "your message here"}"`));
344
+ console.log(dim(` amiko call ${slug} -i`) + dim(" (interactive mode)"));
345
+ console.log("");
346
+ console.log(dim(` View online: ${siteUrl(slug)}`));
347
+ } catch (err) {
348
+ spinner.stop();
349
+ console.error(chalk2.red(err instanceof Error ? err.message : "Failed to fetch agent"));
350
+ process.exit(1);
351
+ }
352
+ });
353
+ }
354
+
355
+ // src/commands/call.ts
356
+ import chalk3 from "chalk";
357
+ import ora2 from "ora";
358
+ import { createInterface } from "readline";
359
+
360
+ // src/lib/tempo.ts
361
+ import { execFile, execFileSync } from "child_process";
362
+ import { existsSync as existsSync2 } from "fs";
363
+ import { homedir as homedir2 } from "os";
364
+ import { join as join2 } from "path";
365
+ var TEMPO_PATHS = [
366
+ join2(homedir2(), ".tempo", "bin", "tempo"),
367
+ "tempo"
368
+ // on PATH
369
+ ];
370
+ function findTempo() {
371
+ for (const p of TEMPO_PATHS) {
372
+ if (p === "tempo") {
373
+ try {
374
+ execFileSync(p, ["--version"], { stdio: "pipe", timeout: 3e3 });
375
+ return p;
376
+ } catch {
377
+ continue;
378
+ }
379
+ } else if (existsSync2(p)) {
380
+ return p;
381
+ }
382
+ }
383
+ return null;
384
+ }
385
+ function hasTempo() {
386
+ return findTempo() !== null;
387
+ }
388
+ function runTempo(args, timeoutMs = 6e4) {
389
+ const bin = findTempo();
390
+ if (!bin) throw new Error("Tempo CLI not installed. Install: curl -fsSL https://tempo.xyz/install | bash");
391
+ return new Promise((resolve, reject) => {
392
+ execFile(bin, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024 * 10 }, (err, stdout, stderr) => {
393
+ if (err) {
394
+ const msg = stderr?.trim() || stdout?.trim() || err.message;
395
+ reject(new Error(msg));
396
+ return;
397
+ }
398
+ resolve(stdout.trim() || stderr.trim());
399
+ });
400
+ });
401
+ }
402
+ async function tempoRequest(opts) {
403
+ const args = ["request", "-t"];
404
+ if (opts.dryRun) args.push("--dry-run");
405
+ args.push("-X", opts.method.toUpperCase());
406
+ if (opts.body) {
407
+ args.push("--json", opts.body);
408
+ }
409
+ args.push(opts.url);
410
+ const output = await runTempo(args, opts.timeoutMs ?? 12e4);
411
+ return { status: 200, body: output };
412
+ }
413
+ async function tempoWhoami() {
414
+ return runTempo(["wallet", "-t", "whoami"], 1e4);
415
+ }
416
+ async function tempoFund() {
417
+ return runTempo(["wallet", "fund"], 3e4);
418
+ }
419
+
420
+ // src/commands/call.ts
421
+ function buildHeaders() {
422
+ const config = loadConfig();
423
+ const headers = { "Content-Type": "application/json" };
424
+ if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
425
+ return headers;
426
+ }
427
+ function extractResponse(data) {
428
+ const text = data.response ?? data.result ?? data.content ?? data.message ?? data.text ?? data.output;
429
+ if (typeof text === "string") return text;
430
+ if (text != null) return JSON.stringify(text, null, 2);
431
+ return JSON.stringify(data, null, 2);
432
+ }
433
+ function printPaymentFooter(res, data, opts) {
434
+ if (opts.raw) return;
435
+ const paymentMethod = res.headers.get("x-payment-method");
436
+ const paymentBalance = res.headers.get("x-payment-balance");
437
+ const paymentTx = res.headers.get("x-payment-tx");
438
+ const paid = data.paid !== false;
439
+ if (!paid) return;
440
+ const cost = typeof data.cost === "number" ? data.cost : null;
441
+ let footer = success("Paid");
442
+ if (cost != null) footer += ` ${usd(cost)}`;
443
+ if (paymentMethod) footer += dim(` via ${paymentMethod}`);
444
+ if (paymentBalance) footer += dim(` (balance: $${paymentBalance})`);
445
+ console.log(footer);
446
+ if (paymentTx) console.log(dim(`tx: ${paymentTx}`));
447
+ }
448
+ async function readStdin() {
449
+ if (process.stdin.isTTY) return null;
450
+ const chunks = [];
451
+ for await (const chunk of process.stdin) {
452
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
453
+ }
454
+ const text = Buffer.concat(chunks).toString("utf-8").trim();
455
+ return text || null;
456
+ }
457
+ function getPaymentMode() {
458
+ const config = loadConfig();
459
+ if (hasTempo()) return "tempo";
460
+ if (config.apiKey) return "apikey";
461
+ return "none";
462
+ }
463
+ async function callViaTempo(agentId, message, opts) {
464
+ const config = loadConfig();
465
+ const base = config.apiBase.replace(/\/$/, "");
466
+ const endpoint = opts.task ? "tasks" : "chat";
467
+ const url = `${base}/v1/agents/${agentId}/${endpoint}`;
468
+ if (opts.dryRun) {
469
+ const spinner2 = ora2(`Dry run: ${agentId}...`).start();
470
+ try {
471
+ const result = await tempoRequest({
472
+ method: "POST",
473
+ url,
474
+ body: JSON.stringify({ message }),
475
+ dryRun: true
476
+ });
477
+ spinner2.stop();
478
+ console.log(result.body);
479
+ } catch (err) {
480
+ spinner2.stop();
481
+ console.error(error(err instanceof Error ? err.message : "Dry run failed"));
482
+ process.exit(1);
483
+ }
484
+ return;
485
+ }
486
+ const spinner = ora2(`Calling ${agentId} via Tempo...`).start();
487
+ try {
488
+ const result = await tempoRequest({
489
+ method: "POST",
490
+ url,
491
+ body: JSON.stringify({ message })
492
+ });
493
+ spinner.stop();
494
+ if (opts.raw) {
495
+ console.log(result.body);
496
+ return;
497
+ }
498
+ try {
499
+ const data = JSON.parse(result.body);
500
+ console.log(extractResponse(data));
501
+ console.log("");
502
+ console.log(success("Paid via Tempo wallet"));
503
+ } catch {
504
+ console.log(result.body);
505
+ }
506
+ } catch (err) {
507
+ spinner.stop();
508
+ console.error(error(err instanceof Error ? err.message : "Call failed"));
509
+ process.exit(1);
510
+ }
511
+ }
512
+ async function callViaApiKey(agentId, message, opts) {
513
+ const config = loadConfig();
514
+ const base = config.apiBase.replace(/\/$/, "");
515
+ const endpoint = opts.task ? "tasks" : "chat";
516
+ const spinner = ora2(`Calling ${agentId}...`).start();
517
+ try {
518
+ const res = await fetch(`${base}/v1/agents/${agentId}/${endpoint}`, {
519
+ method: "POST",
520
+ headers: buildHeaders(),
521
+ body: JSON.stringify({ message })
522
+ });
523
+ if (res.status === 402) {
524
+ spinner.stop();
525
+ const body = await res.json().catch(() => ({}));
526
+ const challengeId = body.challengeId ?? body.challenge_id ?? "unknown";
527
+ const price = body.price ?? body.amount;
528
+ console.log(warn("Insufficient balance"));
529
+ if (price) console.log(dim(` Price: $${price}`));
530
+ console.log(dim(" Top up: amiko wallet topup <amount>"));
531
+ console.log(dim(`
532
+ Challenge: ${challengeId}`));
533
+ return;
534
+ }
535
+ if (!res.ok) {
536
+ spinner.stop();
537
+ const text = await res.text().catch(() => "");
538
+ console.error(error(`${res.status} ${res.statusText}`));
539
+ if (text) console.error(dim(text));
540
+ process.exit(1);
541
+ }
542
+ const data = await res.json();
543
+ spinner.stop();
544
+ if (opts.raw) {
545
+ console.log(JSON.stringify(data, null, 2));
546
+ return;
547
+ }
548
+ console.log(extractResponse(data));
549
+ console.log("");
550
+ printPaymentFooter(res, data, opts);
551
+ } catch (err) {
552
+ spinner.stop();
553
+ console.error(error(err instanceof Error ? err.message : "Call failed"));
554
+ process.exit(1);
555
+ }
556
+ }
557
+ async function interactiveMode(agentId, opts, paymentMode) {
558
+ console.log(heading(`Interactive session with ${agentId}`));
559
+ console.log(dim(`Payment: ${paymentMode === "tempo" ? "Tempo wallet" : "API key"}`));
560
+ console.log(dim('Type your messages. Ctrl+C or "exit" to quit.\n'));
561
+ const rl = createInterface({
562
+ input: process.stdin,
563
+ output: process.stdout,
564
+ prompt: chalk3.cyan("you \u203A ")
565
+ });
566
+ rl.prompt();
567
+ rl.on("line", async (line) => {
568
+ const msg = line.trim();
569
+ if (!msg) {
570
+ rl.prompt();
571
+ return;
572
+ }
573
+ if (msg === "exit" || msg === "quit" || msg === ".exit") {
574
+ console.log(dim("\nSession ended."));
575
+ rl.close();
576
+ process.exit(0);
577
+ }
578
+ process.stdout.write(chalk3.dim(" thinking..."));
579
+ try {
580
+ if (paymentMode === "tempo") {
581
+ const config = loadConfig();
582
+ const base = config.apiBase.replace(/\/$/, "");
583
+ const endpoint = opts.task ? "tasks" : "chat";
584
+ const result = await tempoRequest({
585
+ method: "POST",
586
+ url: `${base}/v1/agents/${agentId}/${endpoint}`,
587
+ body: JSON.stringify({ message: msg })
588
+ });
589
+ process.stdout.write("\r" + " ".repeat(40) + "\r");
590
+ try {
591
+ const data = JSON.parse(result.body);
592
+ console.log(chalk3.green("agent \u203A ") + extractResponse(data));
593
+ } catch {
594
+ console.log(chalk3.green("agent \u203A ") + result.body);
595
+ }
596
+ } else {
597
+ const config = loadConfig();
598
+ const base = config.apiBase.replace(/\/$/, "");
599
+ const endpoint = opts.task ? "tasks" : "chat";
600
+ const res = await fetch(`${base}/v1/agents/${agentId}/${endpoint}`, {
601
+ method: "POST",
602
+ headers: buildHeaders(),
603
+ body: JSON.stringify({ message: msg })
604
+ });
605
+ process.stdout.write("\r" + " ".repeat(40) + "\r");
606
+ if (res.status === 402) {
607
+ console.log(warn("Payment required \u2014 insufficient balance."));
608
+ } else if (!res.ok) {
609
+ console.log(chalk3.red(` Error: ${res.status} ${res.statusText}`));
610
+ } else {
611
+ const data = await res.json();
612
+ if (opts.raw) {
613
+ console.log(JSON.stringify(data, null, 2));
614
+ } else {
615
+ console.log(chalk3.green("agent \u203A ") + extractResponse(data));
616
+ const cost = typeof data.cost === "number" ? data.cost : null;
617
+ if (cost != null) {
618
+ const balance = res.headers.get("x-payment-balance");
619
+ let meta = dim(` ${usd(cost)}`);
620
+ if (balance) meta += dim(` \xB7 bal $${balance}`);
621
+ console.log(meta);
622
+ }
623
+ }
624
+ }
625
+ }
626
+ } catch (err) {
627
+ process.stdout.write("\r" + " ".repeat(40) + "\r");
628
+ console.log(chalk3.red(` ${err instanceof Error ? err.message : "Request failed"}`));
629
+ }
630
+ console.log("");
631
+ rl.prompt();
632
+ });
633
+ rl.on("close", () => {
634
+ process.exit(0);
635
+ });
636
+ }
637
+ function registerCallCommand(program2) {
638
+ program2.command("call").description("Call a paid agent service (supports piping, interactive mode, and Tempo wallet)").argument("<agent>", "Agent ID (e.g. titan-research)").argument("[message]", "Message to send (omit for interactive mode, or pipe from stdin)").option("--task", "Use task endpoint instead of chat").option("--raw", "Output raw JSON response").option("-i, --interactive", "Start interactive conversation mode").option("--dry-run", "Preview cost without executing (Tempo wallet only)").option("--no-tempo", "Skip Tempo wallet, use API key only").action(async (agentId, message, opts) => {
639
+ const paymentMode = opts.tempo === false ? loadConfig().apiKey ? "apikey" : "none" : getPaymentMode();
640
+ if (paymentMode === "none") {
641
+ console.error(error("No payment method available."));
642
+ console.log("");
643
+ console.log(dim("Option 1 \u2014 Install the Tempo wallet (handles payments automatically):"));
644
+ console.log(dim(" curl -fsSL https://tempo.xyz/install | bash"));
645
+ console.log(dim(" tempo wallet login"));
646
+ console.log("");
647
+ console.log(dim("Option 2 \u2014 Use an API key:"));
648
+ console.log(dim(" amiko config set apiKey amk_<your-key>"));
649
+ console.log(dim(" amiko wallet topup 10"));
650
+ process.exit(1);
651
+ }
652
+ if (!message && !opts.interactive) {
653
+ const piped = await readStdin();
654
+ if (piped) {
655
+ message = piped;
656
+ }
657
+ }
658
+ if (opts.interactive || !message && process.stdin.isTTY) {
659
+ await interactiveMode(agentId, opts, paymentMode);
660
+ return;
661
+ }
662
+ if (!message) {
663
+ console.error(error("No message provided. Pass a message, pipe stdin, or use --interactive."));
664
+ process.exit(1);
665
+ }
666
+ if (paymentMode === "tempo") {
667
+ await callViaTempo(agentId, message, opts);
668
+ } else {
669
+ await callViaApiKey(agentId, message, opts);
670
+ }
671
+ });
672
+ }
673
+
674
+ // src/commands/wallet.ts
675
+ import chalk4 from "chalk";
676
+ import ora3 from "ora";
677
+ function sanitizeTempoWhoamiOutput(output) {
678
+ return output.replace(/^(\s*key:\s*)"[^"\n]*"$/m, '$1"[redacted]"');
679
+ }
680
+ function buildConnectedKeyTopupInput(config, amount) {
681
+ const token = getPrivyToken(config);
682
+ if (!token) {
683
+ throw new Error('Not signed in. Run "amiko login" first.');
684
+ }
685
+ if (!config.connectedKeyId) {
686
+ throw new Error("No connected API key is attached to this account. Sign in again after creating a server-side key.");
687
+ }
688
+ return {
689
+ token,
690
+ keyId: config.connectedKeyId,
691
+ amount,
692
+ billingBase: config.billingBase,
693
+ siteBase: config.siteBase
694
+ };
695
+ }
696
+ function registerWalletCommand(program2) {
697
+ const cmd = program2.command("wallet").description("Manage MPP wallets");
698
+ cmd.command("create").description("Create a new MPP wallet on Tempo").option("--save", "Save the wallet address to CLI config").action(async (opts) => {
699
+ const spinner = ora3("Creating wallet...").start();
700
+ try {
701
+ const data = await post("/v1/wallet/create");
702
+ spinner.stop();
703
+ console.log(success("Wallet created"));
704
+ console.log("");
705
+ console.log(label("Address", chalk4.cyan(data.address)));
706
+ console.log(label("Wallet ID", data.walletId));
707
+ if (data.chain) console.log(label("Chain", data.chain));
708
+ console.log("");
709
+ console.log(dim("Fund it with USDC.e on Tempo to start using paid services."));
710
+ if (opts.save) {
711
+ saveConfig({ wallet: data.address });
712
+ console.log(success("Saved to CLI config"));
713
+ } else {
714
+ console.log(dim(`
715
+ Tip: amiko config set wallet ${data.address}`));
716
+ }
717
+ } catch (err) {
718
+ spinner.stop();
719
+ console.error(error(err instanceof Error ? err.message : "Failed to create wallet"));
720
+ process.exit(1);
721
+ }
722
+ });
723
+ cmd.command("balance").description("Check wallet balance").argument("[address]", "Wallet address (uses configured wallet if omitted)").option("-w, --watch", "Poll balance every 5 seconds").option("--interval <seconds>", "Poll interval in seconds (with --watch)", "5").action(async (address, opts) => {
724
+ const addr = address || loadConfig().wallet;
725
+ if (!addr) {
726
+ console.error(error("No wallet address. Provide one or run: amiko config set wallet <address>"));
727
+ process.exit(1);
728
+ }
729
+ const fetchBalance = async () => {
730
+ const data = await get(`/v1/wallet/${addr}/balance`);
731
+ return parseFloat(data.balance || data.usdce || "0").toFixed(4);
732
+ };
733
+ if (opts.watch) {
734
+ const intervalMs = Math.max(2, parseInt(opts.interval || "5", 10)) * 1e3;
735
+ let last = "";
736
+ console.log(heading("Watching wallet balance"));
737
+ console.log(label("Address", truncAddr(addr)));
738
+ console.log(dim(`Polling every ${intervalMs / 1e3}s \u2014 Ctrl+C to stop
739
+ `));
740
+ const poll = async () => {
741
+ try {
742
+ const bal = await fetchBalance();
743
+ const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString();
744
+ const changed = last !== "" && bal !== last;
745
+ const prefix = changed ? chalk4.yellow("\u2195") : chalk4.dim("\xB7");
746
+ const value = changed ? chalk4.white.bold(`$${bal}`) : `$${bal}`;
747
+ process.stdout.write(`\r${prefix} ${dim(ts)} ${value} `);
748
+ last = bal;
749
+ } catch {
750
+ process.stdout.write(`\r${chalk4.red("!")} ${dim((/* @__PURE__ */ new Date()).toLocaleTimeString())} ${dim("fetch error")} `);
751
+ }
752
+ };
753
+ await poll();
754
+ setInterval(() => void poll(), intervalMs);
755
+ return;
756
+ }
757
+ const spinner = ora3("Fetching balance...").start();
758
+ try {
759
+ const bal = await fetchBalance();
760
+ spinner.stop();
761
+ console.log(heading("Wallet Balance"));
762
+ console.log("");
763
+ console.log(label("Address", truncAddr(addr)));
764
+ console.log(label("USDC.e", usd(parseFloat(bal))));
765
+ } catch (err) {
766
+ spinner.stop();
767
+ console.error(error(err instanceof Error ? err.message : "Failed to fetch balance"));
768
+ process.exit(1);
769
+ }
770
+ });
771
+ cmd.command("login").description("Log in to Tempo wallet (opens browser for passkey auth)").action(async () => {
772
+ if (!hasTempo()) {
773
+ console.error(error("Tempo CLI not installed."));
774
+ console.log(dim("Install: curl -fsSL https://tempo.xyz/install | bash"));
775
+ process.exit(1);
776
+ }
777
+ console.log(dim("Opening browser for passkey authentication...\n"));
778
+ try {
779
+ const output = await runTempo(["wallet", "login"], 12e4);
780
+ console.log(output);
781
+ console.log(success("Logged in to Tempo wallet"));
782
+ } catch (err) {
783
+ console.error(error(err instanceof Error ? err.message : "Login failed"));
784
+ process.exit(1);
785
+ }
786
+ });
787
+ cmd.command("whoami").description("Show Tempo wallet identity").action(async () => {
788
+ if (!hasTempo()) {
789
+ console.error(error("Tempo CLI not installed."));
790
+ console.log(dim("Install: curl -fsSL https://tempo.xyz/install | bash"));
791
+ process.exit(1);
792
+ }
793
+ try {
794
+ const output = await tempoWhoami();
795
+ console.log(heading("Tempo Wallet"));
796
+ console.log(sanitizeTempoWhoamiOutput(output).trim());
797
+ } catch (err) {
798
+ console.error(error(err instanceof Error ? err.message : "Failed to get wallet info"));
799
+ console.log(dim("Try: amiko wallet login"));
800
+ process.exit(1);
801
+ }
802
+ });
803
+ cmd.command("fund").description("Fund Tempo wallet via dashboard").action(async () => {
804
+ if (!hasTempo()) {
805
+ console.error(error("Tempo CLI not installed."));
806
+ console.log(dim("Install: curl -fsSL https://tempo.xyz/install | bash"));
807
+ console.log("");
808
+ console.log(dim("Or top up your connected Amiko account instead:"));
809
+ console.log(dim(" amiko wallet topup <amount>"));
810
+ process.exit(1);
811
+ }
812
+ try {
813
+ const output = await tempoFund();
814
+ console.log(output.trim());
815
+ } catch (err) {
816
+ console.error(error(err instanceof Error ? err.message : "Failed to fund"));
817
+ process.exit(1);
818
+ }
819
+ });
820
+ cmd.command("topup").description("Top up your connected Amiko account via Stripe").argument("<amount>", "Amount in USD ($1\u2013$100)").option("--open", "Automatically open the Stripe checkout URL in your browser").action(async (amountStr, opts) => {
821
+ const amount = parseFloat(amountStr.replace("$", ""));
822
+ if (isNaN(amount) || amount < 1 || amount > 100) {
823
+ console.error(error("Amount must be between $1 and $100."));
824
+ process.exit(1);
825
+ }
826
+ let spinner;
827
+ try {
828
+ const input = buildConnectedKeyTopupInput(loadConfig(), amount);
829
+ spinner = ora3(`Creating Stripe checkout for $${amount.toFixed(2)}...`).start();
830
+ const data = await createConnectedKeyTopupCheckout(input);
831
+ spinner.stop();
832
+ console.log(success(`Checkout created \u2014 $${amount.toFixed(2)} \u2192 ${input.keyId}`));
833
+ console.log("");
834
+ console.log(label("Checkout URL", chalk4.cyan(data.payment.checkoutUrl)));
835
+ console.log(label("Session ID", dim(data.payment.sessionId)));
836
+ console.log(label("Connected Key", input.keyId));
837
+ if (opts.open && data.payment.checkoutUrl) {
838
+ const { exec } = await import("child_process");
839
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
840
+ exec(`${openCmd} "${data.payment.checkoutUrl}"`);
841
+ console.log(dim("\nOpened in browser."));
842
+ } else if (data.payment.checkoutUrl) {
843
+ console.log(dim("\nTip: add --open to auto-launch in browser."));
844
+ }
845
+ } catch (err) {
846
+ spinner?.stop();
847
+ console.error(error(err instanceof Error ? err.message : "Failed to create checkout"));
848
+ process.exit(1);
849
+ }
850
+ });
851
+ cmd.command("deposit").description("Show deposit address for direct USDC.e transfer on Tempo").argument("[address]", "Wallet address (uses configured wallet if omitted)").action(async (address) => {
852
+ const addr = address || loadConfig().wallet;
853
+ if (!addr) {
854
+ console.error(error("No wallet address. Provide one or run: amiko config set wallet <address>"));
855
+ process.exit(1);
856
+ }
857
+ console.log(heading("Deposit USDC.e"));
858
+ console.log("");
859
+ console.log(dim("Send USDC.e on Tempo directly to your wallet:"));
860
+ console.log("");
861
+ console.log(label("Address", chalk4.cyan(addr)));
862
+ console.log(label("Chain", "Tempo (Chain ID 4217)"));
863
+ console.log(label("Token", "USDC.e"));
864
+ console.log("");
865
+ console.log(dim("After sending, check balance:"));
866
+ console.log(dim(` amiko wallet balance ${addr}`));
867
+ });
868
+ }
869
+
870
+ // src/commands/creator.ts
871
+ import chalk5 from "chalk";
872
+ import ora4 from "ora";
873
+ function requireCreatorToken() {
874
+ const config = loadConfig();
875
+ const token = getPrivyToken(config);
876
+ if (!token) {
877
+ console.error(error("Not signed in."));
878
+ console.log(dim("Run: amiko login"));
879
+ process.exit(1);
880
+ }
881
+ return token;
882
+ }
883
+ function registerCreatorCommand(program2) {
884
+ const cmd = program2.command("creator").description("Creator dashboard \u2014 manage listings and payouts");
885
+ cmd.command("summary").description("Show creator earnings and listings summary").action(async () => {
886
+ requireCreatorToken();
887
+ const config = loadConfig();
888
+ const spinner = ora4("Loading creator dashboard...").start();
889
+ try {
890
+ const data = await creatorGet("/api/creator/summary");
891
+ spinner.stop();
892
+ console.log(heading("Creator Dashboard\n"));
893
+ console.log(heading("Earnings"));
894
+ console.log(label(" Available", usd(data.earnings.available)));
895
+ console.log(label(" Lifetime", usd(data.earnings.lifetime)));
896
+ console.log(label(" Paid out", usd(data.earnings.paidOut)));
897
+ console.log(
898
+ label(" Status", data.earnings.eligible ? chalk5.green("Eligible for payout") : dim("Not yet eligible"))
899
+ );
900
+ console.log("");
901
+ const published = data.agents.filter((a) => a.status === "published");
902
+ const drafts = data.agents.filter((a) => a.status !== "published");
903
+ console.log(heading(`Listings (${published.length} published, ${drafts.length} drafts)`));
904
+ if (data.agents.length > 0) {
905
+ const rows = data.agents.map((a) => [
906
+ chalk5.white(a.name),
907
+ a.status === "published" ? chalk5.green("live") : chalk5.yellow("draft"),
908
+ dim(a.category),
909
+ `${(a.stats?.interactions ?? 0).toLocaleString()} uses`,
910
+ usd(a.stats?.revenue ?? 0)
911
+ ]);
912
+ rows.unshift([
913
+ chalk5.dim.underline("NAME"),
914
+ chalk5.dim.underline("STATUS"),
915
+ chalk5.dim.underline("CATEGORY"),
916
+ chalk5.dim.underline("USAGE"),
917
+ chalk5.dim.underline("REVENUE")
918
+ ]);
919
+ console.log(table(rows));
920
+ } else {
921
+ console.log(dim(" No listings yet."));
922
+ }
923
+ console.log("");
924
+ if (data.payouts.length > 0) {
925
+ console.log(heading("Recent Payouts"));
926
+ for (const p of data.payouts.slice(0, 5)) {
927
+ const statusColor = p.status === "completed" ? chalk5.green : p.status === "pending" ? chalk5.yellow : chalk5.dim;
928
+ console.log(
929
+ ` ${usd(p.amount)} ${statusColor(p.status)} ${dim(new Date(p.created_at).toLocaleDateString())}`
930
+ );
931
+ }
932
+ }
933
+ console.log("");
934
+ console.log(dim(`View full dashboard: ${config.siteBase}/creator`));
935
+ } catch (err) {
936
+ spinner.stop();
937
+ console.error(error(err instanceof Error ? err.message : "Failed to load dashboard"));
938
+ process.exit(1);
939
+ }
940
+ });
941
+ cmd.command("listings").description("List all your agent listings").action(async () => {
942
+ requireCreatorToken();
943
+ const config = loadConfig();
944
+ const spinner = ora4("Fetching listings...").start();
945
+ try {
946
+ const data = await creatorGet("/api/creator/summary");
947
+ spinner.stop();
948
+ if (data.agents.length === 0) {
949
+ console.log(dim("No listings yet."));
950
+ return;
951
+ }
952
+ console.log(heading(`Your Listings \u2014 ${data.agents.length} total
953
+ `));
954
+ for (const agent of data.agents) {
955
+ const live = agent.status === "published";
956
+ console.log(
957
+ `${live ? chalk5.green("\u25CF") : chalk5.yellow("\u25CB")} ${chalk5.white.bold(agent.name)} ${dim(`(${agent.agent_id})`)}`
958
+ );
959
+ console.log(dim(` ${agent.category} \xB7 ${(agent.stats?.interactions ?? 0).toLocaleString()} uses \xB7 ${usd(agent.stats?.revenue ?? 0)} revenue`));
960
+ if (live) console.log(dim(` ${config.siteBase}/agents/${agent.agent_id}`));
961
+ console.log("");
962
+ }
963
+ } catch (err) {
964
+ spinner.stop();
965
+ console.error(error(err instanceof Error ? err.message : "Failed"));
966
+ process.exit(1);
967
+ }
968
+ });
969
+ cmd.command("publish").description("Publish a draft listing").argument("<listingId>", "Listing ID to publish").action(async (listingId) => {
970
+ requireCreatorToken();
971
+ const config = loadConfig();
972
+ const spinner = ora4("Publishing listing...").start();
973
+ try {
974
+ await creatorPost(`/api/v1/agents/${listingId}/publish`);
975
+ spinner.stop();
976
+ console.log(success(`Published listing ${listingId}`));
977
+ console.log(dim(`View: ${config.siteBase}/agents/${listingId}`));
978
+ } catch (err) {
979
+ spinner.stop();
980
+ console.error(error(err instanceof Error ? err.message : "Failed to publish"));
981
+ process.exit(1);
982
+ }
983
+ });
984
+ cmd.command("payout").description("Request a payout of available earnings").action(async () => {
985
+ requireCreatorToken();
986
+ const spinner = ora4("Requesting payout...").start();
987
+ try {
988
+ const data = await creatorPost(
989
+ "/api/creator/payouts/request"
990
+ );
991
+ spinner.stop();
992
+ if (data.paid) {
993
+ console.log(success(`Payout sent: ${data.amount != null ? usd(data.amount) : ""}`));
994
+ if (data.txHash) console.log(dim(`tx: ${data.txHash}`));
995
+ } else {
996
+ console.log(warn("No payout processed \u2014 balance may not be eligible yet."));
997
+ }
998
+ } catch (err) {
999
+ spinner.stop();
1000
+ console.error(error(err instanceof Error ? err.message : "Payout failed"));
1001
+ process.exit(1);
1002
+ }
1003
+ });
1004
+ }
1005
+
1006
+ // src/commands/reputation.ts
1007
+ import chalk6 from "chalk";
1008
+ import ora5 from "ora";
1009
+ function registerReputationCommand(program2) {
1010
+ program2.command("reputation").alias("rep").description("Look up TARS reputation for a wallet address").argument("[address]", "Wallet address (uses configured wallet if omitted)").action(async (address) => {
1011
+ const addr = address || loadConfig().wallet;
1012
+ if (!addr) {
1013
+ console.error(error("No address. Provide one or run: amiko config set wallet <address>"));
1014
+ process.exit(1);
1015
+ }
1016
+ const spinner = ora5(`Looking up ${truncAddr(addr)}...`).start();
1017
+ try {
1018
+ const data = await get(`/v1/marketplace/reputation/${addr}`);
1019
+ spinner.stop();
1020
+ const tars = data.tars;
1021
+ console.log(heading("TARS Reputation"));
1022
+ console.log(label("Address", chalk6.cyan(truncAddr(addr))));
1023
+ console.log("");
1024
+ if (tars.score > 0) {
1025
+ const displayScore = Math.min(tars.score, 5);
1026
+ const stars = renderStars(displayScore);
1027
+ const tier = scoreTier(displayScore);
1028
+ console.log(` ${chalk6.white.bold(displayScore.toFixed(2))} ${stars} ${tier}`);
1029
+ console.log("");
1030
+ console.log(label(" Total jobs", tars.totalJobs.toLocaleString()));
1031
+ console.log(label(" Volume", usd(tars.totalVolume)));
1032
+ console.log(label(" Ratings", tars.feedbackCount.toString()));
1033
+ console.log(label(" Confidence", `${Math.round(tars.confidence * 100)}%`));
1034
+ console.log(
1035
+ label(" On-chain", tars.onChain ? chalk6.green("verified") : dim("not yet"))
1036
+ );
1037
+ console.log("");
1038
+ console.log(dim(` VWA = ${tars.scoreBps} bps (${(tars.scoreBps / 1e4).toFixed(2)} / 5.00)`));
1039
+ } else {
1040
+ console.log(dim(" No reputation data yet."));
1041
+ console.log(dim(" Score builds from verified MPP payments and feedback."));
1042
+ }
1043
+ } catch (err) {
1044
+ spinner.stop();
1045
+ console.error(error(err instanceof Error ? err.message : "Lookup failed"));
1046
+ process.exit(1);
1047
+ }
1048
+ });
1049
+ }
1050
+ function renderStars(score) {
1051
+ const full = Math.floor(score);
1052
+ const half = score - full >= 0.5;
1053
+ const empty = 5 - full - (half ? 1 : 0);
1054
+ return chalk6.yellow("\u2605".repeat(full)) + (half ? chalk6.yellow("\xBD") : "") + chalk6.dim("\u2606".repeat(empty));
1055
+ }
1056
+ function scoreTier(score) {
1057
+ if (score >= 4.5) return chalk6.green("Excellent");
1058
+ if (score >= 3.5) return chalk6.blue("Good");
1059
+ if (score >= 2.5) return chalk6.yellow("Average");
1060
+ return chalk6.dim("Developing");
1061
+ }
1062
+
1063
+ // src/commands/config.ts
1064
+ import chalk7 from "chalk";
1065
+ var VALID_KEYS = [
1066
+ "apiBase",
1067
+ "billingBase",
1068
+ "siteBase",
1069
+ "wallet",
1070
+ "apiKey",
1071
+ "privyToken",
1072
+ "creatorToken",
1073
+ "userId",
1074
+ "connectedKeyId",
1075
+ "connectedKeyName",
1076
+ "connectedKeyPreview"
1077
+ ];
1078
+ function registerConfigCommand(program2) {
1079
+ const cmd = program2.command("config").description("View or update CLI configuration");
1080
+ cmd.command("show").description("Show current configuration").action(() => {
1081
+ const config = loadConfig();
1082
+ const privyToken = getPrivyToken(config);
1083
+ console.log(heading("Amiko CLI Configuration"));
1084
+ console.log(chalk7.dim(`File: ${getConfigPath()}
1085
+ `));
1086
+ console.log(heading("Endpoints"));
1087
+ console.log(label(" MPP service", config.apiBase));
1088
+ console.log(label(" Billing API", config.billingBase));
1089
+ console.log(label(" Marketplace", config.siteBase));
1090
+ console.log("");
1091
+ console.log(heading("Identity"));
1092
+ console.log(label(" Wallet", config.wallet || chalk7.dim("(not set)")));
1093
+ console.log(label(" Signed in", privyToken ? chalk7.green("yes") : chalk7.dim("no")));
1094
+ console.log(label(" User ID", config.userId || chalk7.dim("(not set)")));
1095
+ console.log(
1096
+ label(
1097
+ " Connected Key",
1098
+ config.connectedKeyPreview ? `${config.connectedKeyName || "API key"} (${config.connectedKeyPreview})` : chalk7.dim("(not set)")
1099
+ )
1100
+ );
1101
+ console.log(label(" API Key", config.apiKey ? maskKey(config.apiKey) : chalk7.dim("(not set)")));
1102
+ console.log("");
1103
+ if (!privyToken && !config.apiKey) {
1104
+ console.log(dim("Get started:"));
1105
+ console.log(dim(" amiko login Sign in with Privy"));
1106
+ console.log(dim(" amiko config set apiKey Set an API key"));
1107
+ }
1108
+ });
1109
+ cmd.command("set").description("Set a configuration value").argument("<key>", `Config key (${VALID_KEYS.join(", ")})`).argument("<value>", "Config value").action((key, value) => {
1110
+ if (!VALID_KEYS.includes(key)) {
1111
+ console.error(chalk7.red(`Invalid key "${key}".`));
1112
+ console.log(chalk7.dim(`Valid keys: ${VALID_KEYS.join(", ")}`));
1113
+ process.exit(1);
1114
+ }
1115
+ saveConfig({ [key]: value });
1116
+ console.log(chalk7.green(`\u2713 Set ${key}`));
1117
+ });
1118
+ cmd.command("reset").description("Reset configuration to defaults").action(() => {
1119
+ if (resetConfig()) {
1120
+ console.log(chalk7.green("\u2713 Config reset to defaults"));
1121
+ } else {
1122
+ console.log(chalk7.dim("Already at defaults."));
1123
+ }
1124
+ });
1125
+ cmd.command("path").description("Print config file path").action(() => {
1126
+ console.log(getConfigPath());
1127
+ });
1128
+ }
1129
+ function maskKey(key) {
1130
+ if (key.length <= 8) return "****";
1131
+ return key.slice(0, 4) + "..." + key.slice(-4);
1132
+ }
1133
+
1134
+ // src/commands/discovery.ts
1135
+ import chalk8 from "chalk";
1136
+ import ora6 from "ora";
1137
+ function registerDiscoveryCommand(program2) {
1138
+ program2.command("discover").alias("status").description("Show MPP service info, endpoints, and pricing").action(async () => {
1139
+ const config = loadConfig();
1140
+ const spinner = ora6("Fetching service discovery...").start();
1141
+ try {
1142
+ const data = await get("/.well-known/mpp");
1143
+ spinner.stop();
1144
+ console.log(heading(data.name || "MPP Service"));
1145
+ if (data.version) console.log(dim(`v${data.version}`));
1146
+ if (data.description) console.log(dim(data.description));
1147
+ console.log("");
1148
+ console.log(label("Endpoint", config.apiBase));
1149
+ if (data.payment) {
1150
+ console.log("");
1151
+ console.log(heading("Payment"));
1152
+ if (data.payment.chain) console.log(label(" Chain", data.payment.chain));
1153
+ if (data.payment.token) console.log(label(" Token", data.payment.token));
1154
+ if (data.payment.feePayerAddress) console.log(label(" Fee payer", data.payment.feePayerAddress));
1155
+ if (data.payment.treasuryAddress) console.log(label(" Treasury", data.payment.treasuryAddress));
1156
+ }
1157
+ if (data.services && data.services.length > 0) {
1158
+ console.log("");
1159
+ console.log(heading("Endpoints"));
1160
+ const rows = data.services.map((s) => [
1161
+ chalk8.cyan(s.method),
1162
+ chalk8.white(s.endpoint),
1163
+ usd(s.price),
1164
+ dim(s.description || "")
1165
+ ]);
1166
+ rows.unshift([
1167
+ chalk8.dim.underline("METHOD"),
1168
+ chalk8.dim.underline("ENDPOINT"),
1169
+ chalk8.dim.underline("PRICE"),
1170
+ chalk8.dim.underline("DESCRIPTION")
1171
+ ]);
1172
+ console.log(table(rows));
1173
+ }
1174
+ if (data.docs) {
1175
+ console.log("");
1176
+ console.log(heading("Documentation"));
1177
+ if (data.docs.llmTxt) console.log(label(" llm.txt", data.docs.llmTxt));
1178
+ if (data.docs.skillMd) console.log(label(" skill.md", data.docs.skillMd));
1179
+ if (data.docs.mppSpec) console.log(label(" MPP spec", data.docs.mppSpec));
1180
+ }
1181
+ } catch {
1182
+ spinner.stop();
1183
+ try {
1184
+ const health = await get("/health");
1185
+ console.log(heading("MPP Service"));
1186
+ console.log(label("Endpoint", config.apiBase));
1187
+ console.log(label("Status", health.status === "ok" ? chalk8.green("online") : chalk8.yellow(health.status)));
1188
+ if (health.version) console.log(label("Version", health.version));
1189
+ console.log("");
1190
+ console.log(dim("Full discovery not available at /.well-known/mpp"));
1191
+ } catch (err) {
1192
+ console.error(error("Service unreachable"));
1193
+ console.error(dim(config.apiBase));
1194
+ process.exit(1);
1195
+ }
1196
+ }
1197
+ });
1198
+ program2.command("ping").description("Check if the MPP service is online").action(async () => {
1199
+ const config = loadConfig();
1200
+ const start = Date.now();
1201
+ try {
1202
+ await get("/health");
1203
+ const ms = Date.now() - start;
1204
+ console.log(chalk8.green("\u25CF") + ` ${config.apiBase} ${dim(`${ms}ms`)}`);
1205
+ } catch {
1206
+ const ms = Date.now() - start;
1207
+ console.log(chalk8.red("\u25CF") + ` ${config.apiBase} ${dim(`${ms}ms \u2014 unreachable`)}`);
1208
+ process.exit(1);
1209
+ }
1210
+ });
1211
+ }
1212
+
1213
+ // src/commands/marketplace.ts
1214
+ import chalk9 from "chalk";
1215
+ import ora7 from "ora";
1216
+ function timeAgo(timestamp) {
1217
+ const seconds = Math.floor((Date.now() - new Date(timestamp).getTime()) / 1e3);
1218
+ if (seconds < 60) return "just now";
1219
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
1220
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
1221
+ return `${Math.floor(seconds / 86400)}d ago`;
1222
+ }
1223
+ function registerMarketplaceCommand(program2) {
1224
+ program2.command("activity").description("Show recent marketplace payment activity").option("-l, --limit <n>", "Number of entries", "20").option("-a, --agent <id>", "Filter by agent ID").action(async (opts) => {
1225
+ const spinner = ora7("Fetching activity...").start();
1226
+ try {
1227
+ const data = await get(
1228
+ `/v1/marketplace/activity?limit=${opts.limit}`
1229
+ );
1230
+ spinner.stop();
1231
+ let items = data.activity || [];
1232
+ if (opts.agent) {
1233
+ const q = opts.agent.toLowerCase();
1234
+ items = items.filter(
1235
+ (a) => a.service?.toLowerCase().includes(q) || a.agent_name?.toLowerCase().includes(q)
1236
+ );
1237
+ }
1238
+ if (items.length === 0) {
1239
+ console.log(dim("No recent activity."));
1240
+ return;
1241
+ }
1242
+ console.log(heading(`Recent Activity \u2014 ${items.length} payments
1243
+ `));
1244
+ const rows = items.map((item) => [
1245
+ chalk9.white(item.agent_name || item.service),
1246
+ chalk9.dim(truncAddr(item.client)),
1247
+ chalk9.green(`$${item.amount.toFixed(4)}`),
1248
+ dim(timeAgo(item.timestamp))
1249
+ ]);
1250
+ rows.unshift([
1251
+ chalk9.dim.underline("AGENT"),
1252
+ chalk9.dim.underline("CLIENT"),
1253
+ chalk9.dim.underline("AMOUNT"),
1254
+ chalk9.dim.underline("WHEN")
1255
+ ]);
1256
+ console.log(table(rows));
1257
+ } catch (err) {
1258
+ spinner.stop();
1259
+ console.error(error(err instanceof Error ? err.message : "Failed to fetch activity"));
1260
+ process.exit(1);
1261
+ }
1262
+ });
1263
+ program2.command("leaderboard").alias("top").description("Show top creators by revenue or usage").option("-s, --sort <by>", "Sort by: revenue, usage, rating", "revenue").option("-l, --limit <n>", "Number of entries", "10").action(async (opts) => {
1264
+ const spinner = ora7("Fetching leaderboard...").start();
1265
+ try {
1266
+ const data = await get(
1267
+ `/v1/marketplace/leaderboard?limit=${opts.limit}&sort=${opts.sort}`
1268
+ );
1269
+ spinner.stop();
1270
+ const entries = data.leaderboard || [];
1271
+ if (entries.length === 0) {
1272
+ console.log(dim("No leaderboard data yet."));
1273
+ return;
1274
+ }
1275
+ console.log(heading(`Creator Leaderboard \u2014 Top ${entries.length} by ${opts.sort}
1276
+ `));
1277
+ const rows = entries.map((e) => {
1278
+ const medal = e.rank === 1 ? chalk9.yellow("1st") : e.rank === 2 ? chalk9.gray("2nd") : e.rank === 3 ? chalk9.hex("#CD7F32")("3rd") : dim(`${e.rank}th`);
1279
+ const agentNames = e.agents.map((a) => a.name).join(", ");
1280
+ return [
1281
+ medal,
1282
+ chalk9.white.bold(e.creator.name.length > 16 ? truncAddr(e.creator.name) : e.creator.name),
1283
+ chalk9.green(usd(e.stats.revenue)),
1284
+ chalk9.cyan(e.stats.interactions.toLocaleString()) + dim(" uses"),
1285
+ dim(`${e.stats.agent_count} agents`),
1286
+ dim(agentNames.length > 40 ? agentNames.slice(0, 37) + "..." : agentNames)
1287
+ ];
1288
+ });
1289
+ rows.unshift([
1290
+ chalk9.dim.underline("#"),
1291
+ chalk9.dim.underline("CREATOR"),
1292
+ chalk9.dim.underline("REVENUE"),
1293
+ chalk9.dim.underline("USAGE"),
1294
+ chalk9.dim.underline("AGENTS"),
1295
+ chalk9.dim.underline("TOP LISTINGS")
1296
+ ]);
1297
+ console.log(table(rows));
1298
+ } catch (err) {
1299
+ spinner.stop();
1300
+ console.error(error(err instanceof Error ? err.message : "Failed to fetch leaderboard"));
1301
+ process.exit(1);
1302
+ }
1303
+ });
1304
+ program2.command("popular").description("Show trending/popular agents").option("-p, --period <period>", "Time period: day, week, month, all", "week").option("-l, --limit <n>", "Number of entries", "10").action(async (opts) => {
1305
+ const config = loadConfig();
1306
+ const spinner = ora7("Fetching popular agents...").start();
1307
+ try {
1308
+ const data = await get(
1309
+ `/v1/marketplace/popular?limit=${opts.limit}&period=${opts.period}`
1310
+ );
1311
+ spinner.stop();
1312
+ const agents = data.popular || [];
1313
+ if (agents.length === 0) {
1314
+ console.log(dim("No data for this period."));
1315
+ return;
1316
+ }
1317
+ console.log(heading(`Popular \u2014 ${opts.period}
1318
+ `));
1319
+ const rows = agents.map((a, i) => {
1320
+ const price = a.pricing?.per_message;
1321
+ const uses = a.period_interactions ?? a.stats?.interactions ?? 0;
1322
+ const rev = a.period_revenue ?? a.stats?.revenue ?? 0;
1323
+ return [
1324
+ dim(`${i + 1}.`),
1325
+ chalk9.white.bold(a.name),
1326
+ chalk9.dim(a.category),
1327
+ price != null ? usd(price) + dim("/msg") : dim("\u2014"),
1328
+ chalk9.cyan(uses.toLocaleString()) + dim(" uses"),
1329
+ rev > 0 ? chalk9.green(usd(rev)) : dim("\u2014")
1330
+ ];
1331
+ });
1332
+ rows.unshift([
1333
+ chalk9.dim.underline("#"),
1334
+ chalk9.dim.underline("NAME"),
1335
+ chalk9.dim.underline("CATEGORY"),
1336
+ chalk9.dim.underline("PRICE"),
1337
+ chalk9.dim.underline("USAGE"),
1338
+ chalk9.dim.underline("REVENUE")
1339
+ ]);
1340
+ console.log(table(rows));
1341
+ console.log("");
1342
+ console.log(dim(`View all: ${config.siteBase}`));
1343
+ } catch (err) {
1344
+ spinner.stop();
1345
+ console.error(error(err instanceof Error ? err.message : "Failed"));
1346
+ process.exit(1);
1347
+ }
1348
+ });
1349
+ }
1350
+
1351
+ // src/commands/login.ts
1352
+ import chalk10 from "chalk";
1353
+ import ora8 from "ora";
1354
+ import { createServer } from "http";
1355
+ import { randomBytes } from "crypto";
1356
+ import { execSync } from "child_process";
1357
+ function buildVerifiedAuthConfig(token, bootstrap) {
1358
+ return {
1359
+ privyToken: token,
1360
+ creatorToken: void 0,
1361
+ userId: bootstrap.userId,
1362
+ connectedKeyId: bootstrap.connectedKey?.id,
1363
+ connectedKeyName: bootstrap.connectedKey?.name,
1364
+ connectedKeyPreview: bootstrap.connectedKey?.keyPreview
1365
+ };
1366
+ }
1367
+ function openBrowser(url) {
1368
+ const platform = process.platform;
1369
+ try {
1370
+ if (platform === "darwin") {
1371
+ execSync(`open "${url}"`);
1372
+ } else if (platform === "win32") {
1373
+ execSync(`start "" "${url}"`);
1374
+ } else {
1375
+ try {
1376
+ execSync(`xdg-open "${url}" 2>/dev/null`);
1377
+ } catch {
1378
+ try {
1379
+ execSync(`sensible-browser "${url}" 2>/dev/null`);
1380
+ } catch {
1381
+ return;
1382
+ }
1383
+ }
1384
+ }
1385
+ } catch {
1386
+ }
1387
+ }
1388
+ function registerLoginCommand(program2) {
1389
+ program2.command("login").description("Sign in with your Amiko account (opens browser)").option("--no-browser", "Print the login URL instead of opening a browser").action(async (opts) => {
1390
+ const config = loadConfig();
1391
+ const existingToken = getPrivyToken(config);
1392
+ const state = randomBytes(16).toString("hex");
1393
+ console.log(heading("Amiko Login"));
1394
+ console.log(dim("Authenticate your CLI with your Amiko account.\n"));
1395
+ if (existingToken) {
1396
+ console.log(dim("Already signed in."));
1397
+ if (config.userId) console.log(label("User ID", dim(config.userId)));
1398
+ if (config.connectedKeyPreview) {
1399
+ console.log(label("Connected Key", `${config.connectedKeyName || "API key"} (${config.connectedKeyPreview})`));
1400
+ }
1401
+ console.log(dim('\nRun "amiko login" again to re-authenticate, or "amiko logout" to sign out.\n'));
1402
+ }
1403
+ const spinner = ora8("Starting authentication server...").start();
1404
+ const { port, token: tokenPromise } = await startCallbackServer(state);
1405
+ spinner.stop();
1406
+ const loginUrl = `${config.siteBase}/cli-auth?port=${port}&state=${state}`;
1407
+ if (opts.browser !== false) {
1408
+ console.log(dim("Opening browser for authentication...\n"));
1409
+ openBrowser(loginUrl);
1410
+ }
1411
+ console.log(dim("If your browser does not open, visit:\n"));
1412
+ console.log(chalk10.cyan(loginUrl));
1413
+ console.log("");
1414
+ const waitSpinner = ora8("Waiting for authentication...").start();
1415
+ try {
1416
+ const result = await tokenPromise;
1417
+ waitSpinner.text = "Verifying account with api-service...";
1418
+ let bootstrap;
1419
+ try {
1420
+ bootstrap = await bootstrapPrivyAccount(result.token, config.billingBase);
1421
+ saveConfig(buildVerifiedAuthConfig(result.token, bootstrap));
1422
+ result.acknowledge({ ok: true });
1423
+ } catch (err) {
1424
+ result.acknowledge({
1425
+ ok: false,
1426
+ error: err instanceof Error ? err.message : "Authentication failed."
1427
+ });
1428
+ throw err;
1429
+ }
1430
+ waitSpinner.stop();
1431
+ console.log("");
1432
+ console.log(success("Authenticated successfully!"));
1433
+ console.log(label("User ID", dim(bootstrap.userId)));
1434
+ if (bootstrap.connectedKey) {
1435
+ console.log(
1436
+ label(
1437
+ "Connected Key",
1438
+ `${bootstrap.connectedKey.name} (${bootstrap.connectedKey.keyPreview})`
1439
+ )
1440
+ );
1441
+ } else {
1442
+ console.log(label("Connected Key", dim("none attached yet")));
1443
+ }
1444
+ console.log("");
1445
+ console.log(dim("Your credentials are stored at ~/.amiko/config.json"));
1446
+ console.log(dim("You can now use creator commands and authenticated account actions."));
1447
+ } catch (err) {
1448
+ waitSpinner.stop();
1449
+ console.error(error(err instanceof Error ? err.message : "Authentication failed."));
1450
+ process.exit(1);
1451
+ }
1452
+ });
1453
+ program2.command("logout").description("Sign out and clear stored credentials").action(() => {
1454
+ const config = loadConfig();
1455
+ const privyToken = getPrivyToken(config);
1456
+ if (!privyToken && !config.apiKey && !config.userId && !config.connectedKeyId) {
1457
+ console.log(dim("Not signed in."));
1458
+ return;
1459
+ }
1460
+ saveConfig({
1461
+ privyToken: void 0,
1462
+ creatorToken: void 0,
1463
+ userId: void 0,
1464
+ connectedKeyId: void 0,
1465
+ connectedKeyName: void 0,
1466
+ connectedKeyPreview: void 0
1467
+ });
1468
+ console.log(success("Signed out."));
1469
+ console.log(dim("Privy account linkage cleared from ~/.amiko/config.json"));
1470
+ });
1471
+ program2.command("whoami").description("Show current authentication status").action(() => {
1472
+ const config = loadConfig();
1473
+ const privyToken = getPrivyToken(config);
1474
+ console.log(heading("Amiko Identity\n"));
1475
+ if (privyToken) {
1476
+ console.log(label("Auth", chalk10.green("signed in via Privy")));
1477
+ } else {
1478
+ console.log(label("Auth", dim("not signed in")));
1479
+ }
1480
+ if (config.userId) {
1481
+ console.log(label("User ID", dim(config.userId)));
1482
+ }
1483
+ if (config.connectedKeyPreview) {
1484
+ const connectedKeyLabel = config.connectedKeyName ? `${config.connectedKeyName} (${config.connectedKeyPreview})` : config.connectedKeyPreview;
1485
+ console.log(label("Connected Key", connectedKeyLabel));
1486
+ }
1487
+ if (config.wallet) {
1488
+ console.log(label("Wallet", config.wallet));
1489
+ }
1490
+ if (config.apiKey) {
1491
+ const masked = config.apiKey.slice(0, 6) + "..." + config.apiKey.slice(-4);
1492
+ console.log(label("API Key", masked));
1493
+ }
1494
+ console.log(label("MPP", config.apiBase));
1495
+ console.log(label("Billing", config.billingBase));
1496
+ console.log(label("Site", config.siteBase));
1497
+ if (!privyToken && !config.apiKey) {
1498
+ console.log("");
1499
+ console.log(dim('Run "amiko login" to authenticate, or'));
1500
+ console.log(dim(' "amiko config set apiKey <key>" for API key auth.'));
1501
+ }
1502
+ });
1503
+ }
1504
+ function startCallbackServer(expectedState, timeoutMs = 12e4, acknowledgementTimeoutMs = 3e4) {
1505
+ return new Promise((resolveServer) => {
1506
+ let resolveToken;
1507
+ let rejectToken;
1508
+ const tokenPromise = new Promise((resolve, reject) => {
1509
+ resolveToken = resolve;
1510
+ rejectToken = reject;
1511
+ });
1512
+ const server = createServer((req, res) => {
1513
+ res.setHeader("Access-Control-Allow-Origin", "*");
1514
+ res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
1515
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1516
+ if (req.method === "OPTIONS") {
1517
+ res.writeHead(204);
1518
+ res.end();
1519
+ return;
1520
+ }
1521
+ if (req.method === "POST" && req.url === "/callback") {
1522
+ let body = "";
1523
+ req.on("data", (chunk) => body += chunk);
1524
+ req.on("end", () => {
1525
+ try {
1526
+ const payload = JSON.parse(body);
1527
+ if (payload.state !== expectedState) {
1528
+ res.writeHead(403, { "Content-Type": "application/json" });
1529
+ res.end(JSON.stringify({ error: "State mismatch" }));
1530
+ return;
1531
+ }
1532
+ if (!payload.token) {
1533
+ res.writeHead(400, { "Content-Type": "application/json" });
1534
+ res.end(JSON.stringify({ error: "Missing token" }));
1535
+ return;
1536
+ }
1537
+ let acknowledged = false;
1538
+ let resolveAcknowledgement;
1539
+ const acknowledgementPromise = new Promise((resolve) => {
1540
+ resolveAcknowledgement = resolve;
1541
+ });
1542
+ const acknowledgementTimeout = setTimeout(() => {
1543
+ if (acknowledged) return;
1544
+ acknowledged = true;
1545
+ resolveAcknowledgement({
1546
+ ok: false,
1547
+ error: 'Authentication verification timed out. Return to your terminal and run "amiko login" again.'
1548
+ });
1549
+ }, acknowledgementTimeoutMs);
1550
+ resolveToken({
1551
+ ...payload,
1552
+ acknowledge: (result) => {
1553
+ if (acknowledged) return;
1554
+ acknowledged = true;
1555
+ clearTimeout(acknowledgementTimeout);
1556
+ resolveAcknowledgement(result);
1557
+ }
1558
+ });
1559
+ void acknowledgementPromise.then((result) => {
1560
+ clearTimeout(acknowledgementTimeout);
1561
+ if (result.ok) {
1562
+ res.writeHead(200, { "Content-Type": "application/json" });
1563
+ res.end(JSON.stringify({ ok: true, authenticated: true }));
1564
+ } else {
1565
+ res.writeHead(500, { "Content-Type": "application/json" });
1566
+ res.end(
1567
+ JSON.stringify({
1568
+ ok: false,
1569
+ error: result.error || "Authentication failed."
1570
+ })
1571
+ );
1572
+ }
1573
+ setTimeout(() => server.close(), 500);
1574
+ });
1575
+ } catch {
1576
+ res.writeHead(400, { "Content-Type": "application/json" });
1577
+ res.end(JSON.stringify({ error: "Invalid JSON" }));
1578
+ }
1579
+ });
1580
+ return;
1581
+ }
1582
+ if (req.method === "GET" && req.url === "/health") {
1583
+ res.writeHead(200, { "Content-Type": "application/json" });
1584
+ res.end(JSON.stringify({ ok: true }));
1585
+ return;
1586
+ }
1587
+ res.writeHead(404);
1588
+ res.end();
1589
+ });
1590
+ server.listen(0, "127.0.0.1", () => {
1591
+ const addr = server.address();
1592
+ const port = typeof addr === "object" && addr ? addr.port : 0;
1593
+ const timeout = setTimeout(() => {
1594
+ server.close();
1595
+ rejectToken(new Error('Authentication timed out (2 minutes). Try again with "amiko login".'));
1596
+ }, timeoutMs);
1597
+ tokenPromise.then(() => clearTimeout(timeout)).catch(() => clearTimeout(timeout));
1598
+ resolveServer({ port, token: tokenPromise });
1599
+ });
1600
+ server.on("error", (err) => {
1601
+ rejectToken(new Error(`Server error: ${err.message}`));
1602
+ });
1603
+ });
1604
+ }
1605
+
1606
+ // src/index.ts
1607
+ var program = new Command();
1608
+ program.name("amiko").description("CLI for the Amiko AI Agent Marketplace \u2014 browse, call, and manage agents on the MPP payment rail").version("0.1.0");
1609
+ registerBrowseCommand(program);
1610
+ registerCallCommand(program);
1611
+ registerWalletCommand(program);
1612
+ registerCreatorCommand(program);
1613
+ registerReputationCommand(program);
1614
+ registerConfigCommand(program);
1615
+ registerDiscoveryCommand(program);
1616
+ registerMarketplaceCommand(program);
1617
+ registerLoginCommand(program);
1618
+ program.parse();