create-sati-agent 0.1.0 → 0.1.1

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/bin/cli.js CHANGED
@@ -1,5 +1,755 @@
1
1
  #!/usr/bin/env node
2
- import { run } from "@stricli/core";
3
- import { app } from "../app.js";
4
- import { buildContext } from "../context.js";
2
+ import { buildApplication, buildCommand, buildRouteMap, numberParser, run } from "@stricli/core";
3
+ import { cancel, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
4
+ import pc from "picocolors";
5
+
6
+ //#region src/lib/api.ts
7
+ const DEFAULT_BASE_URL = "https://sati.cascade.fyi";
8
+ var PaymentRequiredError = class extends Error {
9
+ constructor(paymentHeaders, body) {
10
+ super("Payment required ($0.30 USDC)");
11
+ this.paymentHeaders = paymentHeaders;
12
+ this.body = body;
13
+ }
14
+ };
15
+ var ApiError = class extends Error {
16
+ constructor(status, message) {
17
+ super(message);
18
+ this.status = status;
19
+ }
20
+ };
21
+ var SatiApiClient = class {
22
+ baseUrl;
23
+ constructor(baseUrl) {
24
+ this.baseUrl = baseUrl ?? process.env.SATI_API_URL ?? DEFAULT_BASE_URL;
25
+ }
26
+ async register(data, paymentHeader) {
27
+ const url = `${this.baseUrl}/api/register`;
28
+ const awUrl = process.env.AGENT_WALLET_URL;
29
+ const awUsername = process.env.AGENT_WALLET_USERNAME;
30
+ if (awUrl && awUsername) {
31
+ const proxyUrl = `${awUrl}/api/wallets/${awUsername}/actions/x402/fetch`;
32
+ const res = await fetch(proxyUrl, {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ body: JSON.stringify({
36
+ url,
37
+ method: "POST",
38
+ body: data,
39
+ preferredChain: "solana"
40
+ })
41
+ });
42
+ if (!res.ok) {
43
+ const body = await res.json().catch(() => ({}));
44
+ throw new ApiError(res.status, body.error ?? "AgentWallet proxy request failed");
45
+ }
46
+ const json = await res.json();
47
+ return json.response?.body ?? json;
48
+ }
49
+ const headers = { "Content-Type": "application/json" };
50
+ if (paymentHeader) headers["X-PAYMENT"] = paymentHeader;
51
+ const res = await fetch(url, {
52
+ method: "POST",
53
+ headers,
54
+ body: JSON.stringify(data)
55
+ });
56
+ if (res.status === 402) {
57
+ const paymentHeaders = {};
58
+ for (const [key, value] of res.headers.entries()) paymentHeaders[key] = value;
59
+ throw new PaymentRequiredError(paymentHeaders, await res.text());
60
+ }
61
+ if (!res.ok) {
62
+ const body = await res.json().catch(() => ({}));
63
+ throw new ApiError(res.status, body.error ?? `Request failed (${res.status})`);
64
+ }
65
+ return res.json();
66
+ }
67
+ async listAgents(opts) {
68
+ const params = new URLSearchParams();
69
+ params.set("network", opts?.network ?? "mainnet");
70
+ if (opts?.name) params.set("name", opts.name);
71
+ if (opts?.owner) params.set("owner", opts.owner);
72
+ if (opts?.limit) params.set("limit", String(opts.limit));
73
+ const res = await fetch(`${this.baseUrl}/api/agents?${params}`);
74
+ if (!res.ok) {
75
+ const body = await res.json().catch(() => ({}));
76
+ throw new ApiError(res.status, body.error ?? "Failed to list agents");
77
+ }
78
+ return res.json();
79
+ }
80
+ async getAgent(mint, network = "mainnet") {
81
+ const res = await fetch(`${this.baseUrl}/api/agents/${mint}?network=${network}`);
82
+ if (!res.ok) {
83
+ const body = await res.json().catch(() => ({}));
84
+ throw new ApiError(res.status, body.error ?? "Failed to load agent");
85
+ }
86
+ return res.json();
87
+ }
88
+ async submitFeedback(data) {
89
+ const res = await fetch(`${this.baseUrl}/api/feedback`, {
90
+ method: "POST",
91
+ headers: { "Content-Type": "application/json" },
92
+ body: JSON.stringify(data)
93
+ });
94
+ if (!res.ok) {
95
+ const body = await res.json().catch(() => ({}));
96
+ throw new ApiError(res.status, body.error ?? "Failed to submit feedback");
97
+ }
98
+ return res.json();
99
+ }
100
+ async listFeedback(mint, opts) {
101
+ const params = new URLSearchParams();
102
+ params.set("network", opts?.network ?? "mainnet");
103
+ if (opts?.clientAddress) params.set("clientAddress", opts.clientAddress);
104
+ if (opts?.tag1) params.set("tag1", opts.tag1);
105
+ if (opts?.tag2) params.set("tag2", opts.tag2);
106
+ const res = await fetch(`${this.baseUrl}/api/feedback/${mint}?${params}`);
107
+ if (!res.ok) {
108
+ const body = await res.json().catch(() => ({}));
109
+ throw new ApiError(res.status, body.error ?? "Failed to list feedback");
110
+ }
111
+ return res.json();
112
+ }
113
+ async getReputation(mint, opts) {
114
+ const params = new URLSearchParams();
115
+ params.set("network", opts?.network ?? "mainnet");
116
+ if (opts?.tag1) params.set("tag1", opts.tag1);
117
+ if (opts?.tag2) params.set("tag2", opts.tag2);
118
+ const res = await fetch(`${this.baseUrl}/api/reputation/${mint}?${params}`);
119
+ if (!res.ok) {
120
+ const body = await res.json().catch(() => ({}));
121
+ throw new ApiError(res.status, body.error ?? "Failed to get reputation");
122
+ }
123
+ return res.json();
124
+ }
125
+ };
126
+
127
+ //#endregion
128
+ //#region src/lib/format.ts
129
+ function truncateAddress(addr, len = 4) {
130
+ if (addr.length <= len * 2 + 3) return addr;
131
+ return `${addr.slice(0, len)}...${addr.slice(-len)}`;
132
+ }
133
+ function formatAgent(agent) {
134
+ const lines = [];
135
+ lines.push(`${pc.bold(agent.name)} ${pc.dim(`#${agent.memberNumber}`)}`);
136
+ lines.push(` ${pc.dim("Mint:")} ${pc.cyan(agent.mint)}`);
137
+ lines.push(` ${pc.dim("Owner:")} ${agent.owner}`);
138
+ lines.push(` ${pc.dim("Status:")} ${agent.active ? pc.green("active") : pc.red("inactive")}`);
139
+ if (agent.description) lines.push(` ${pc.dim("About:")} ${agent.description}`);
140
+ if (agent.services?.length) {
141
+ const serviceNames = agent.services.map((s) => s.name).join(", ");
142
+ lines.push(` ${pc.dim("Services:")} ${serviceNames}`);
143
+ for (const svc of agent.services) lines.push(` ${pc.dim("-")} ${svc.name}: ${pc.blue(svc.endpoint)}${svc.version ? pc.dim(` v${svc.version}`) : ""}`);
144
+ }
145
+ if (agent.reputation) {
146
+ lines.push("");
147
+ lines.push(` ${pc.dim("Reputation:")}`);
148
+ lines.push(` ${formatReputation(agent.reputation)}`);
149
+ }
150
+ if (agent.uri) lines.push(` ${pc.dim("URI:")} ${agent.uri}`);
151
+ return lines.join("\n");
152
+ }
153
+ function formatAgentList(agents) {
154
+ if (agents.length === 0) return pc.dim(" No agents found");
155
+ const lines = [];
156
+ for (const [i, agent] of agents.entries()) {
157
+ const num = pc.dim(`${String(i + 1).padStart(3)}.`);
158
+ const name = pc.bold(agent.name);
159
+ const mint = pc.cyan(truncateAddress(agent.mint, 6));
160
+ const owner = truncateAddress(agent.owner, 4);
161
+ const services = agent.services?.length ? agent.services.map((s) => s.name).join(", ") : pc.dim("none");
162
+ lines.push(`${num} ${name}`);
163
+ lines.push(` ${pc.dim("Mint:")} ${mint} ${pc.dim("Owner:")} ${owner} ${pc.dim("Services:")} ${services}`);
164
+ if (agent.description) {
165
+ const desc = agent.description.length > 80 ? `${agent.description.slice(0, 77)}...` : agent.description;
166
+ lines.push(` ${pc.dim(desc)}`);
167
+ }
168
+ }
169
+ return lines.join("\n");
170
+ }
171
+ function formatReputation(rep) {
172
+ if (rep.count === 0) return pc.dim("No feedback yet");
173
+ return `${pc.bold(String(rep.summaryValue))}/100 from ${rep.count} review${rep.count === 1 ? "" : "s"}`;
174
+ }
175
+ function formatRegistration(result) {
176
+ const lines = [];
177
+ lines.push(` ${pc.dim("Mint:")} ${pc.green(result.mint)}`);
178
+ lines.push(` ${pc.dim("Agent ID:")} ${result.agentId}`);
179
+ lines.push(` ${pc.dim("Member:")} #${result.memberNumber}`);
180
+ lines.push(` ${pc.dim("IPFS:")} ${result.uri}`);
181
+ lines.push(` ${pc.dim("Tx:")} ${result.signature}`);
182
+ return lines.join("\n");
183
+ }
184
+
185
+ //#endregion
186
+ //#region src/commands/register.ts
187
+ const registerCommand = buildCommand({
188
+ docs: { brief: "Register a new AI agent on-chain ($0.30 USDC via x402)" },
189
+ parameters: {
190
+ flags: {
191
+ name: {
192
+ kind: "parsed",
193
+ parse: String,
194
+ brief: "Agent name (max 32 chars)",
195
+ optional: true
196
+ },
197
+ description: {
198
+ kind: "parsed",
199
+ parse: String,
200
+ brief: "What the agent does",
201
+ optional: true
202
+ },
203
+ image: {
204
+ kind: "parsed",
205
+ parse: String,
206
+ brief: "Avatar image URL",
207
+ optional: true
208
+ },
209
+ owner: {
210
+ kind: "parsed",
211
+ parse: String,
212
+ brief: "Solana wallet address (NFT minted to this address)",
213
+ optional: true
214
+ },
215
+ mcpEndpoint: {
216
+ kind: "parsed",
217
+ parse: String,
218
+ brief: "MCP server endpoint URL",
219
+ optional: true
220
+ },
221
+ a2aEndpoint: {
222
+ kind: "parsed",
223
+ parse: String,
224
+ brief: "A2A endpoint URL",
225
+ optional: true
226
+ },
227
+ network: {
228
+ kind: "enum",
229
+ values: ["devnet", "mainnet"],
230
+ brief: "Solana network",
231
+ default: "mainnet"
232
+ },
233
+ paymentHeader: {
234
+ kind: "parsed",
235
+ parse: String,
236
+ brief: "Pre-computed x402 payment header",
237
+ optional: true
238
+ },
239
+ json: {
240
+ kind: "boolean",
241
+ brief: "Output raw JSON",
242
+ optional: true
243
+ }
244
+ },
245
+ positional: {
246
+ kind: "tuple",
247
+ parameters: []
248
+ }
249
+ },
250
+ async func(flags) {
251
+ const isJson = flags.json;
252
+ if (!isJson) {
253
+ intro(pc.cyan("SATI - Register Agent"));
254
+ log.info("Registration costs $0.30 USDC via x402 protocol");
255
+ }
256
+ let name = flags.name;
257
+ let description = flags.description;
258
+ let image = flags.image;
259
+ let owner = flags.owner;
260
+ if (!name) {
261
+ const result = await text({
262
+ message: "Agent name:",
263
+ validate: (v) => {
264
+ if (!v.trim()) return "Name is required";
265
+ if (new TextEncoder().encode(v).length > 32) return "Max 32 bytes";
266
+ }
267
+ });
268
+ if (isCancel(result)) {
269
+ cancel("Cancelled");
270
+ process.exit(0);
271
+ }
272
+ name = result;
273
+ }
274
+ if (!description) {
275
+ const result = await text({
276
+ message: "Description:",
277
+ validate: (v) => !v.trim() ? "Description is required" : void 0
278
+ });
279
+ if (isCancel(result)) {
280
+ cancel("Cancelled");
281
+ process.exit(0);
282
+ }
283
+ description = result;
284
+ }
285
+ if (!image) {
286
+ const result = await text({
287
+ message: "Avatar image URL:",
288
+ validate: (v) => !v.trim() ? "Image URL is required" : void 0
289
+ });
290
+ if (isCancel(result)) {
291
+ cancel("Cancelled");
292
+ process.exit(0);
293
+ }
294
+ image = result;
295
+ }
296
+ if (!owner) {
297
+ const result = await text({
298
+ message: "Solana wallet address (NFT owner):",
299
+ validate: (v) => v.length < 32 ? "Must be a valid Solana address" : void 0
300
+ });
301
+ if (isCancel(result)) {
302
+ cancel("Cancelled");
303
+ process.exit(0);
304
+ }
305
+ owner = result;
306
+ }
307
+ const services = [];
308
+ if (flags.mcpEndpoint) services.push({
309
+ name: "MCP",
310
+ endpoint: flags.mcpEndpoint,
311
+ version: "2025-06-18"
312
+ });
313
+ if (flags.a2aEndpoint) services.push({
314
+ name: "A2A",
315
+ endpoint: flags.a2aEndpoint
316
+ });
317
+ if (!flags.mcpEndpoint && !flags.a2aEndpoint && !isJson) {
318
+ const mcpResult = await text({ message: "MCP endpoint (leave empty to skip):" });
319
+ if (isCancel(mcpResult)) {
320
+ cancel("Cancelled");
321
+ process.exit(0);
322
+ }
323
+ if (mcpResult.trim()) services.push({
324
+ name: "MCP",
325
+ endpoint: mcpResult.trim(),
326
+ version: "2025-06-18"
327
+ });
328
+ }
329
+ const s = !isJson ? spinner() : null;
330
+ s?.start("Registering agent on Solana...");
331
+ try {
332
+ const result = await new SatiApiClient().register({
333
+ name,
334
+ description,
335
+ image,
336
+ ownerAddress: owner,
337
+ services: services.length > 0 ? services : void 0,
338
+ active: true,
339
+ supportedTrust: ["reputation"],
340
+ network: flags.network
341
+ }, flags.paymentHeader);
342
+ if (isJson) {
343
+ console.log(JSON.stringify(result, null, 2));
344
+ return;
345
+ }
346
+ s?.stop(pc.green("Agent registered!"));
347
+ console.log();
348
+ console.log(formatRegistration(result));
349
+ console.log();
350
+ outro(pc.dim("Your agent identity is now on Solana"));
351
+ } catch (error) {
352
+ s?.stop(pc.red("Registration failed"));
353
+ if (error instanceof PaymentRequiredError) {
354
+ log.warn("Registration requires x402 payment ($0.30 USDC on Solana)");
355
+ console.log();
356
+ console.log(pc.bold(" How to pay:"));
357
+ console.log();
358
+ console.log(` ${pc.dim("1.")} Set up AgentWallet (recommended for agents):`);
359
+ console.log(` Set AGENT_WALLET_URL and AGENT_WALLET_USERNAME env vars`);
360
+ console.log(` ${pc.dim("See:")} https://agentwallet.mcpay.tech/skill.md`);
361
+ console.log();
362
+ console.log(` ${pc.dim("2.")} Provide a pre-computed payment header:`);
363
+ console.log(` create-sati-agent register --payment-header "<header>"`);
364
+ console.log();
365
+ return;
366
+ }
367
+ throw error;
368
+ }
369
+ }
370
+ });
371
+
372
+ //#endregion
373
+ //#region src/commands/discover.ts
374
+ const discoverCommand = buildCommand({
375
+ docs: { brief: "Search and list registered agents" },
376
+ parameters: {
377
+ flags: {
378
+ name: {
379
+ kind: "parsed",
380
+ parse: String,
381
+ brief: "Filter agents by name",
382
+ optional: true
383
+ },
384
+ owner: {
385
+ kind: "parsed",
386
+ parse: String,
387
+ brief: "Filter by owner wallet address",
388
+ optional: true
389
+ },
390
+ limit: {
391
+ kind: "parsed",
392
+ parse: numberParser,
393
+ brief: "Max results (1-50)",
394
+ optional: true
395
+ },
396
+ network: {
397
+ kind: "enum",
398
+ values: ["devnet", "mainnet"],
399
+ brief: "Solana network",
400
+ default: "mainnet"
401
+ },
402
+ json: {
403
+ kind: "boolean",
404
+ brief: "Output raw JSON",
405
+ optional: true
406
+ }
407
+ },
408
+ positional: {
409
+ kind: "tuple",
410
+ parameters: []
411
+ }
412
+ },
413
+ async func(flags) {
414
+ if (flags.json) {
415
+ const result = await new SatiApiClient().listAgents({
416
+ name: flags.name,
417
+ owner: flags.owner,
418
+ limit: flags.limit,
419
+ network: flags.network
420
+ });
421
+ console.log(JSON.stringify(result, null, 2));
422
+ return;
423
+ }
424
+ intro(pc.cyan("SATI - Discover Agents"));
425
+ const s = spinner();
426
+ s.start("Searching agents...");
427
+ try {
428
+ const result = await new SatiApiClient().listAgents({
429
+ name: flags.name,
430
+ owner: flags.owner,
431
+ limit: flags.limit,
432
+ network: flags.network
433
+ });
434
+ s.stop(`Found ${result.count} agent(s) on ${flags.network}`);
435
+ console.log();
436
+ console.log(formatAgentList(result.agents));
437
+ console.log();
438
+ outro(pc.dim("Use 'create-sati-agent info <mint>' for details"));
439
+ } catch (error) {
440
+ s.stop(pc.red("Failed"));
441
+ throw error;
442
+ }
443
+ }
444
+ });
445
+
446
+ //#endregion
447
+ //#region src/commands/info.ts
448
+ const infoCommand = buildCommand({
449
+ docs: { brief: "Get detailed agent information and reputation" },
450
+ parameters: {
451
+ flags: {
452
+ network: {
453
+ kind: "enum",
454
+ values: ["devnet", "mainnet"],
455
+ brief: "Solana network",
456
+ default: "mainnet"
457
+ },
458
+ json: {
459
+ kind: "boolean",
460
+ brief: "Output raw JSON",
461
+ optional: true
462
+ }
463
+ },
464
+ positional: {
465
+ kind: "tuple",
466
+ parameters: [{
467
+ brief: "Agent mint address",
468
+ parse: String,
469
+ placeholder: "mint"
470
+ }]
471
+ }
472
+ },
473
+ async func(flags, mint) {
474
+ if (flags.json) {
475
+ const agent = await new SatiApiClient().getAgent(mint, flags.network);
476
+ console.log(JSON.stringify(agent, null, 2));
477
+ return;
478
+ }
479
+ intro(pc.cyan("SATI - Agent Info"));
480
+ const s = spinner();
481
+ s.start("Loading agent...");
482
+ try {
483
+ const agent = await new SatiApiClient().getAgent(mint, flags.network);
484
+ s.stop("Agent loaded");
485
+ console.log();
486
+ console.log(formatAgent(agent));
487
+ console.log();
488
+ outro(pc.dim(`Network: ${flags.network}`));
489
+ } catch (error) {
490
+ s.stop(pc.red("Failed"));
491
+ throw error;
492
+ }
493
+ }
494
+ });
495
+
496
+ //#endregion
497
+ //#region src/commands/feedback.ts
498
+ const feedbackCommand = buildCommand({
499
+ docs: { brief: "Give feedback on an agent (free, recorded on-chain)" },
500
+ parameters: {
501
+ flags: {
502
+ agent: {
503
+ kind: "parsed",
504
+ parse: String,
505
+ brief: "Agent mint address to review",
506
+ optional: true
507
+ },
508
+ value: {
509
+ kind: "parsed",
510
+ parse: numberParser,
511
+ brief: "Score value",
512
+ optional: true
513
+ },
514
+ valueDecimals: {
515
+ kind: "parsed",
516
+ parse: numberParser,
517
+ brief: "Decimal places for value",
518
+ optional: true
519
+ },
520
+ tag1: {
521
+ kind: "parsed",
522
+ parse: String,
523
+ brief: "Primary dimension (starred, reachable, uptime, etc.)",
524
+ optional: true
525
+ },
526
+ tag2: {
527
+ kind: "parsed",
528
+ parse: String,
529
+ brief: "Secondary dimension",
530
+ optional: true
531
+ },
532
+ endpoint: {
533
+ kind: "parsed",
534
+ parse: String,
535
+ brief: "Specific service endpoint being reviewed",
536
+ optional: true
537
+ },
538
+ reviewer: {
539
+ kind: "parsed",
540
+ parse: String,
541
+ brief: "Your Solana address (for attribution)",
542
+ optional: true
543
+ },
544
+ network: {
545
+ kind: "enum",
546
+ values: ["devnet", "mainnet"],
547
+ brief: "Solana network",
548
+ default: "mainnet"
549
+ },
550
+ json: {
551
+ kind: "boolean",
552
+ brief: "Output raw JSON",
553
+ optional: true
554
+ }
555
+ },
556
+ positional: {
557
+ kind: "tuple",
558
+ parameters: []
559
+ }
560
+ },
561
+ async func(flags) {
562
+ const isJson = flags.json;
563
+ if (!isJson) intro(pc.cyan("SATI - Give Feedback"));
564
+ let agentMint = flags.agent;
565
+ let value = flags.value;
566
+ let tag1 = flags.tag1;
567
+ if (!agentMint) {
568
+ const result = await text({
569
+ message: "Agent mint address:",
570
+ validate: (v) => v.length < 32 ? "Must be a valid Solana address" : void 0
571
+ });
572
+ if (isCancel(result)) {
573
+ cancel("Cancelled");
574
+ process.exit(0);
575
+ }
576
+ agentMint = result;
577
+ }
578
+ if (!tag1) {
579
+ const result = await select({
580
+ message: "Feedback category:",
581
+ options: [
582
+ {
583
+ value: "starred",
584
+ label: "Overall Rating (0-100)"
585
+ },
586
+ {
587
+ value: "reachable",
588
+ label: "Reachability (0 or 1)"
589
+ },
590
+ {
591
+ value: "uptime",
592
+ label: "Uptime %"
593
+ },
594
+ {
595
+ value: "responseTime",
596
+ label: "Response Time (ms)"
597
+ },
598
+ {
599
+ value: "successRate",
600
+ label: "Success Rate %"
601
+ }
602
+ ]
603
+ });
604
+ if (isCancel(result)) {
605
+ cancel("Cancelled");
606
+ process.exit(0);
607
+ }
608
+ tag1 = result;
609
+ }
610
+ if (value === void 0) {
611
+ const result = await text({
612
+ message: "Value:",
613
+ validate: (v) => {
614
+ const n = Number(v);
615
+ return Number.isNaN(n) ? "Must be a number" : void 0;
616
+ }
617
+ });
618
+ if (isCancel(result)) {
619
+ cancel("Cancelled");
620
+ process.exit(0);
621
+ }
622
+ value = Number(result);
623
+ }
624
+ const s = !isJson ? spinner() : null;
625
+ s?.start("Submitting feedback on-chain...");
626
+ try {
627
+ const result = await new SatiApiClient().submitFeedback({
628
+ agentMint,
629
+ value,
630
+ valueDecimals: flags.valueDecimals,
631
+ tag1,
632
+ tag2: flags.tag2,
633
+ endpoint: flags.endpoint,
634
+ reviewerAddress: flags.reviewer,
635
+ network: flags.network
636
+ });
637
+ if (isJson) {
638
+ console.log(JSON.stringify(result, null, 2));
639
+ return;
640
+ }
641
+ s?.stop(pc.green("Feedback submitted!"));
642
+ console.log();
643
+ console.log(` ${pc.dim("Tx:")} ${result.txSignature}`);
644
+ console.log(` ${pc.dim("Attestation:")} ${result.attestationAddress}`);
645
+ console.log();
646
+ outro(pc.dim("Feedback recorded on Solana"));
647
+ } catch (error) {
648
+ s?.stop(pc.red("Failed"));
649
+ throw error;
650
+ }
651
+ }
652
+ });
653
+
654
+ //#endregion
655
+ //#region src/commands/reputation.ts
656
+ const reputationCommand = buildCommand({
657
+ docs: { brief: "Get reputation summary for an agent" },
658
+ parameters: {
659
+ flags: {
660
+ tag1: {
661
+ kind: "parsed",
662
+ parse: String,
663
+ brief: "Filter by primary tag (starred, reachable, uptime, etc.)",
664
+ optional: true
665
+ },
666
+ tag2: {
667
+ kind: "parsed",
668
+ parse: String,
669
+ brief: "Filter by secondary tag",
670
+ optional: true
671
+ },
672
+ network: {
673
+ kind: "enum",
674
+ values: ["devnet", "mainnet"],
675
+ brief: "Solana network",
676
+ default: "mainnet"
677
+ },
678
+ json: {
679
+ kind: "boolean",
680
+ brief: "Output raw JSON",
681
+ optional: true
682
+ }
683
+ },
684
+ positional: {
685
+ kind: "tuple",
686
+ parameters: [{
687
+ brief: "Agent mint address",
688
+ parse: String,
689
+ placeholder: "mint"
690
+ }]
691
+ }
692
+ },
693
+ async func(flags, mint) {
694
+ if (flags.json) {
695
+ const rep = await new SatiApiClient().getReputation(mint, {
696
+ tag1: flags.tag1,
697
+ tag2: flags.tag2,
698
+ network: flags.network
699
+ });
700
+ console.log(JSON.stringify(rep, null, 2));
701
+ return;
702
+ }
703
+ intro(pc.cyan("SATI - Reputation"));
704
+ const s = spinner();
705
+ s.start("Fetching reputation...");
706
+ try {
707
+ const rep = await new SatiApiClient().getReputation(mint, {
708
+ tag1: flags.tag1,
709
+ tag2: flags.tag2,
710
+ network: flags.network
711
+ });
712
+ s.stop("Reputation loaded");
713
+ console.log();
714
+ console.log(` ${pc.dim("Agent:")} ${truncateAddress(mint, 6)}`);
715
+ console.log(` ${pc.dim("Score:")} ${formatReputation(rep)}`);
716
+ if (flags.tag1) console.log(` ${pc.dim("Tag:")} ${flags.tag1}`);
717
+ console.log();
718
+ outro(pc.dim(`Network: ${flags.network}`));
719
+ } catch (error) {
720
+ s.stop(pc.red("Failed"));
721
+ throw error;
722
+ }
723
+ }
724
+ });
725
+
726
+ //#endregion
727
+ //#region src/app.ts
728
+ const routes = buildRouteMap({
729
+ routes: {
730
+ register: registerCommand,
731
+ discover: discoverCommand,
732
+ info: infoCommand,
733
+ feedback: feedbackCommand,
734
+ reputation: reputationCommand
735
+ },
736
+ docs: { brief: "On-chain identity for AI agents on Solana" }
737
+ });
738
+ const app = buildApplication(routes, {
739
+ name: "create-sati-agent",
740
+ versionInfo: { currentVersion: "0.1.1" },
741
+ scanner: { caseStyle: "allow-kebab-for-camel" }
742
+ });
743
+
744
+ //#endregion
745
+ //#region src/context.ts
746
+ function buildContext(process) {
747
+ return { process };
748
+ }
749
+
750
+ //#endregion
751
+ //#region src/bin/cli.ts
5
752
  await run(app, process.argv.slice(2), buildContext(process));
753
+
754
+ //#endregion
755
+ export { };