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 +753 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.js +744 -0
- package/package.json +31 -3
- package/dist/app.d.ts +0 -1
- package/dist/app.js +0 -27
- package/dist/bin/cli.d.ts +0 -2
- package/dist/commands/discover.d.ts +0 -1
- package/dist/commands/discover.js +0 -78
- package/dist/commands/feedback.d.ts +0 -1
- package/dist/commands/feedback.js +0 -148
- package/dist/commands/info.d.ts +0 -1
- package/dist/commands/info.js +0 -59
- package/dist/commands/register.d.ts +0 -1
- package/dist/commands/register.js +0 -204
- package/dist/commands/reputation.d.ts +0 -1
- package/dist/commands/reputation.js +0 -83
- package/dist/context.d.ts +0 -5
- package/dist/context.js +0 -3
- package/dist/lib/api.d.ts +0 -34
- package/dist/lib/api.js +0 -145
- package/dist/lib/format.d.ts +0 -13
- package/dist/lib/format.js +0 -86
- package/dist/lib/types.d.ts +0 -96
- package/dist/lib/types.js +0 -2
package/dist/bin/cli.js
CHANGED
|
@@ -1,5 +1,755 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { run } from "@stricli/core";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
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 { };
|