@agentwonderland/mcp 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/core/api-client.d.ts +14 -0
- package/dist/core/api-client.js +98 -0
- package/dist/core/config.d.ts +77 -0
- package/dist/core/config.js +297 -0
- package/dist/core/formatters.d.ts +70 -0
- package/dist/core/formatters.js +193 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +6 -0
- package/dist/core/ows-adapter.d.ts +43 -0
- package/dist/core/ows-adapter.js +100 -0
- package/dist/core/payments.d.ts +41 -0
- package/dist/core/payments.js +254 -0
- package/dist/core/types.d.ts +27 -0
- package/dist/core/types.js +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +53 -0
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.js +89 -0
- package/dist/resources/agents.d.ts +2 -0
- package/dist/resources/agents.js +34 -0
- package/dist/resources/jobs.d.ts +2 -0
- package/dist/resources/jobs.js +15 -0
- package/dist/resources/wallet.d.ts +2 -0
- package/dist/resources/wallet.js +26 -0
- package/dist/tools/_token-cache.d.ts +5 -0
- package/dist/tools/_token-cache.js +9 -0
- package/dist/tools/agent-info.d.ts +2 -0
- package/dist/tools/agent-info.js +97 -0
- package/dist/tools/favorites.d.ts +2 -0
- package/dist/tools/favorites.js +51 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +9 -0
- package/dist/tools/jobs.d.ts +2 -0
- package/dist/tools/jobs.js +49 -0
- package/dist/tools/rate.d.ts +2 -0
- package/dist/tools/rate.js +44 -0
- package/dist/tools/run.d.ts +2 -0
- package/dist/tools/run.js +80 -0
- package/dist/tools/search.d.ts +2 -0
- package/dist/tools/search.js +81 -0
- package/dist/tools/solve.d.ts +2 -0
- package/dist/tools/solve.js +124 -0
- package/dist/tools/tip.d.ts +2 -0
- package/dist/tools/tip.js +40 -0
- package/dist/tools/wallet.d.ts +2 -0
- package/dist/tools/wallet.js +197 -0
- package/package.json +49 -0
- package/src/core/api-client.ts +114 -0
- package/src/core/config.ts +384 -0
- package/src/core/formatters.ts +256 -0
- package/src/core/index.ts +6 -0
- package/src/core/ows-adapter.ts +214 -0
- package/src/core/payments.ts +278 -0
- package/src/core/types.ts +28 -0
- package/src/index.ts +65 -0
- package/src/prompts/index.ts +120 -0
- package/src/resources/agents.ts +37 -0
- package/src/resources/jobs.ts +17 -0
- package/src/resources/wallet.ts +30 -0
- package/src/tools/_token-cache.ts +18 -0
- package/src/tools/agent-info.ts +120 -0
- package/src/tools/favorites.ts +74 -0
- package/src/tools/index.ts +9 -0
- package/src/tools/jobs.ts +69 -0
- package/src/tools/rate.ts +62 -0
- package/src/tools/run.ts +97 -0
- package/src/tools/search.ts +96 -0
- package/src/tools/solve.ts +162 -0
- package/src/tools/tip.ts +59 -0
- package/src/tools/wallet.ts +268 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
// ── Types ──────────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export interface WalletEntry {
|
|
8
|
+
id: string; // e.g. "evm-1"
|
|
9
|
+
keyType: "evm" | "ows"; // key format: raw EVM key or OWS-managed wallet
|
|
10
|
+
key?: string; // private key (legacy / raw EVM only)
|
|
11
|
+
owsWalletId?: string; // OWS wallet reference (when keyType === "ows")
|
|
12
|
+
chains: string[]; // enabled chains: "tempo", "base"
|
|
13
|
+
defaultChain?: string; // which chain to use by default for this wallet
|
|
14
|
+
label?: string; // user-friendly name
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CardConfig {
|
|
18
|
+
consumerToken: string;
|
|
19
|
+
paymentMethodId?: string;
|
|
20
|
+
last4: string;
|
|
21
|
+
brand: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface Config {
|
|
25
|
+
apiUrl: string;
|
|
26
|
+
apiKey: string | null;
|
|
27
|
+
userId: string | null;
|
|
28
|
+
wallets: WalletEntry[];
|
|
29
|
+
defaultWallet: string | null;
|
|
30
|
+
card: CardConfig | null;
|
|
31
|
+
favorites: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** All supported chain identifiers. */
|
|
35
|
+
export const SUPPORTED_CHAINS = ["tempo", "base", "solana"] as const;
|
|
36
|
+
export type Chain = (typeof SUPPORTED_CHAINS)[number];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Map chain name to the protocol used for payment.
|
|
40
|
+
* All chains now use MPP protocol (mppx).
|
|
41
|
+
*/
|
|
42
|
+
export function chainProtocol(_chain: string): "mpp" {
|
|
43
|
+
return "mpp";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Constants ──────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const CONFIG_DIR = join(homedir(), ".agentwonderland");
|
|
49
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
50
|
+
const DEFAULT_API_URL = "https://api.agentwonderland.com";
|
|
51
|
+
|
|
52
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
function ensureConfigDir(): void {
|
|
55
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
56
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Legacy config shape (for migration) ───────────────────────────
|
|
61
|
+
|
|
62
|
+
interface LegacyConfig {
|
|
63
|
+
apiUrl?: string;
|
|
64
|
+
apiKey?: string | null;
|
|
65
|
+
userId?: string | null;
|
|
66
|
+
tempoPrivateKey?: string | null;
|
|
67
|
+
evmPrivateKey?: string | null;
|
|
68
|
+
chain?: string | null;
|
|
69
|
+
defaultPaymentMethod?: string | null;
|
|
70
|
+
stripeConsumerToken?: string | null;
|
|
71
|
+
stripePaymentMethodId?: string | null;
|
|
72
|
+
cardLast4?: string | null;
|
|
73
|
+
cardBrand?: string | null;
|
|
74
|
+
// New fields may also be present
|
|
75
|
+
wallets?: WalletEntry[];
|
|
76
|
+
defaultWallet?: string | null;
|
|
77
|
+
card?: CardConfig | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Migrate a legacy flat-field config to the new wallet array model.
|
|
82
|
+
* Returns the new Config, and if migration happened, writes it to disk.
|
|
83
|
+
*/
|
|
84
|
+
function migrateIfNeeded(raw: LegacyConfig): Config {
|
|
85
|
+
// If wallets array already exists, treat as new format
|
|
86
|
+
if (Array.isArray(raw.wallets)) {
|
|
87
|
+
return {
|
|
88
|
+
apiUrl: raw.apiUrl ?? DEFAULT_API_URL,
|
|
89
|
+
apiKey: raw.apiKey ?? null,
|
|
90
|
+
userId: raw.userId ?? null,
|
|
91
|
+
wallets: raw.wallets,
|
|
92
|
+
defaultWallet: raw.defaultWallet ?? null,
|
|
93
|
+
card: raw.card ?? null,
|
|
94
|
+
favorites: (raw as Record<string, unknown>).favorites as string[] ?? [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Build wallets from legacy flat fields
|
|
99
|
+
const wallets: WalletEntry[] = [];
|
|
100
|
+
let defaultWallet: string | null = null;
|
|
101
|
+
|
|
102
|
+
if (raw.tempoPrivateKey && raw.evmPrivateKey && raw.tempoPrivateKey === raw.evmPrivateKey) {
|
|
103
|
+
// Same key for both — create a single wallet with both chains
|
|
104
|
+
const chains = ["tempo", raw.chain ?? "base"];
|
|
105
|
+
const id = "wallet-1";
|
|
106
|
+
wallets.push({
|
|
107
|
+
id,
|
|
108
|
+
keyType: "evm",
|
|
109
|
+
key: raw.tempoPrivateKey,
|
|
110
|
+
chains: [...new Set(chains)],
|
|
111
|
+
defaultChain: raw.defaultPaymentMethod === "base" ? (raw.chain ?? "base") : "tempo",
|
|
112
|
+
});
|
|
113
|
+
defaultWallet = id;
|
|
114
|
+
} else {
|
|
115
|
+
if (raw.tempoPrivateKey) {
|
|
116
|
+
const id = "tempo-1";
|
|
117
|
+
wallets.push({
|
|
118
|
+
id,
|
|
119
|
+
keyType: "evm",
|
|
120
|
+
key: raw.tempoPrivateKey,
|
|
121
|
+
chains: ["tempo"],
|
|
122
|
+
defaultChain: "tempo",
|
|
123
|
+
});
|
|
124
|
+
if (!defaultWallet || raw.defaultPaymentMethod === "tempo") defaultWallet = id;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (raw.evmPrivateKey) {
|
|
128
|
+
const chain = raw.chain ?? "base";
|
|
129
|
+
const id = "evm-1";
|
|
130
|
+
wallets.push({
|
|
131
|
+
id,
|
|
132
|
+
keyType: "evm",
|
|
133
|
+
key: raw.evmPrivateKey,
|
|
134
|
+
chains: [chain],
|
|
135
|
+
defaultChain: chain,
|
|
136
|
+
});
|
|
137
|
+
if (!defaultWallet || raw.defaultPaymentMethod === "base") defaultWallet = id;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Migrate card
|
|
142
|
+
let card: CardConfig | null = null;
|
|
143
|
+
if (raw.stripeConsumerToken && raw.cardLast4) {
|
|
144
|
+
card = {
|
|
145
|
+
consumerToken: raw.stripeConsumerToken,
|
|
146
|
+
paymentMethodId: raw.stripePaymentMethodId ?? undefined,
|
|
147
|
+
last4: raw.cardLast4,
|
|
148
|
+
brand: raw.cardBrand ?? "card",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// If default payment was card and no wallet default set
|
|
153
|
+
if (raw.defaultPaymentMethod === "card" && !defaultWallet && wallets.length > 0) {
|
|
154
|
+
defaultWallet = wallets[0].id;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const config: Config = {
|
|
158
|
+
apiUrl: raw.apiUrl ?? DEFAULT_API_URL,
|
|
159
|
+
apiKey: raw.apiKey ?? null,
|
|
160
|
+
userId: raw.userId ?? null,
|
|
161
|
+
wallets,
|
|
162
|
+
defaultWallet,
|
|
163
|
+
card,
|
|
164
|
+
favorites: [],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Write migrated config (only if there was something to migrate)
|
|
168
|
+
if (raw.tempoPrivateKey || raw.evmPrivateKey || raw.stripeConsumerToken) {
|
|
169
|
+
ensureConfigDir();
|
|
170
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return config;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── Public API ─────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
export function getConfig(): Config {
|
|
179
|
+
const defaults: Config = {
|
|
180
|
+
apiUrl: DEFAULT_API_URL,
|
|
181
|
+
apiKey: null,
|
|
182
|
+
userId: null,
|
|
183
|
+
wallets: [],
|
|
184
|
+
defaultWallet: null,
|
|
185
|
+
card: null,
|
|
186
|
+
favorites: [],
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
190
|
+
return defaults;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
const raw = readFileSync(CONFIG_FILE, "utf-8");
|
|
195
|
+
const parsed = JSON.parse(raw) as LegacyConfig;
|
|
196
|
+
return migrateIfNeeded(parsed);
|
|
197
|
+
} catch {
|
|
198
|
+
return defaults;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function saveConfig(updates: Partial<Config>): void {
|
|
203
|
+
ensureConfigDir();
|
|
204
|
+
const current = getConfig();
|
|
205
|
+
const merged = { ...current, ...updates };
|
|
206
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function getApiUrl(): string {
|
|
210
|
+
const envUrl = process.env.AGENTWONDERLAND_API_URL;
|
|
211
|
+
if (envUrl) return envUrl;
|
|
212
|
+
return getConfig().apiUrl;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function getApiKey(): string | null {
|
|
216
|
+
const envKey = process.env.AGENTWONDERLAND_API_KEY;
|
|
217
|
+
if (envKey) return envKey;
|
|
218
|
+
return getConfig().apiKey;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function isAuthenticated(): boolean {
|
|
222
|
+
return getApiKey() !== null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Wallet helpers ─────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get all wallets from config + env var synthetic wallets.
|
|
229
|
+
*/
|
|
230
|
+
export function getWallets(): WalletEntry[] {
|
|
231
|
+
const wallets = [...getConfig().wallets];
|
|
232
|
+
|
|
233
|
+
// Env var support: TEMPO_PRIVATE_KEY creates a synthetic wallet
|
|
234
|
+
const tempoEnv = process.env.TEMPO_PRIVATE_KEY;
|
|
235
|
+
if (tempoEnv && !wallets.some((w) => w.id === "env-tempo")) {
|
|
236
|
+
wallets.push({
|
|
237
|
+
id: "env-tempo",
|
|
238
|
+
keyType: "evm",
|
|
239
|
+
key: tempoEnv,
|
|
240
|
+
chains: ["tempo"],
|
|
241
|
+
defaultChain: "tempo",
|
|
242
|
+
label: "Tempo (env)",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Env var support: EVM_PRIVATE_KEY creates a synthetic wallet
|
|
247
|
+
const evmEnv = process.env.EVM_PRIVATE_KEY;
|
|
248
|
+
if (evmEnv && !wallets.some((w) => w.id === "env-evm")) {
|
|
249
|
+
wallets.push({
|
|
250
|
+
id: "env-evm",
|
|
251
|
+
keyType: "evm",
|
|
252
|
+
key: evmEnv,
|
|
253
|
+
chains: ["base"],
|
|
254
|
+
defaultChain: "base",
|
|
255
|
+
label: "Base (env)",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return wallets;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get a wallet by its ID.
|
|
264
|
+
*/
|
|
265
|
+
export function getWalletById(id: string): WalletEntry | undefined {
|
|
266
|
+
return getWallets().find((w) => w.id === id);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get the default wallet. Priority:
|
|
271
|
+
* 1. Configured defaultWallet
|
|
272
|
+
* 2. First wallet in the array
|
|
273
|
+
* 3. First env-var wallet
|
|
274
|
+
*/
|
|
275
|
+
export function getDefaultWallet(): WalletEntry | undefined {
|
|
276
|
+
const wallets = getWallets();
|
|
277
|
+
if (wallets.length === 0) return undefined;
|
|
278
|
+
|
|
279
|
+
const config = getConfig();
|
|
280
|
+
if (config.defaultWallet) {
|
|
281
|
+
const found = wallets.find((w) => w.id === config.defaultWallet);
|
|
282
|
+
if (found) return found;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return wallets[0];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Add a wallet entry. If a wallet with the same ID exists, replace it.
|
|
290
|
+
*/
|
|
291
|
+
export function addWallet(entry: WalletEntry): void {
|
|
292
|
+
const config = getConfig();
|
|
293
|
+
const existing = config.wallets.findIndex((w) => w.id === entry.id);
|
|
294
|
+
if (existing >= 0) {
|
|
295
|
+
config.wallets[existing] = entry;
|
|
296
|
+
} else {
|
|
297
|
+
config.wallets.push(entry);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// If no default wallet, set this as default
|
|
301
|
+
if (!config.defaultWallet) {
|
|
302
|
+
config.defaultWallet = entry.id;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
saveConfig({ wallets: config.wallets, defaultWallet: config.defaultWallet });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Remove a wallet by ID.
|
|
310
|
+
*/
|
|
311
|
+
export function removeWallet(id: string): void {
|
|
312
|
+
const config = getConfig();
|
|
313
|
+
config.wallets = config.wallets.filter((w) => w.id !== id);
|
|
314
|
+
|
|
315
|
+
// If we removed the default, pick the first remaining
|
|
316
|
+
if (config.defaultWallet === id) {
|
|
317
|
+
config.defaultWallet = config.wallets[0]?.id ?? null;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
saveConfig({ wallets: config.wallets, defaultWallet: config.defaultWallet });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get card configuration.
|
|
325
|
+
*/
|
|
326
|
+
export function getCardConfig(): CardConfig | null {
|
|
327
|
+
return getConfig().card;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Resolve a payment method string to a wallet + chain.
|
|
332
|
+
* Accepts: wallet ID, chain name, or "card".
|
|
333
|
+
* Returns null if unresolvable.
|
|
334
|
+
*/
|
|
335
|
+
export function resolveWalletAndChain(method?: string): { wallet: WalletEntry; chain: string } | null {
|
|
336
|
+
const wallets = getWallets();
|
|
337
|
+
|
|
338
|
+
if (!method) {
|
|
339
|
+
// Use default wallet with its default chain
|
|
340
|
+
const def = getDefaultWallet();
|
|
341
|
+
if (!def) return null;
|
|
342
|
+
return { wallet: def, chain: def.defaultChain ?? def.chains[0] };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Try as wallet ID first
|
|
346
|
+
const byId = wallets.find((w) => w.id === method);
|
|
347
|
+
if (byId) {
|
|
348
|
+
return { wallet: byId, chain: byId.defaultChain ?? byId.chains[0] };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Try as chain name
|
|
352
|
+
const byChain = wallets.find((w) => w.chains.includes(method));
|
|
353
|
+
if (byChain) {
|
|
354
|
+
return { wallet: byChain, chain: method };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ── Favorites helpers ───────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
export function getFavorites(): string[] {
|
|
363
|
+
return getConfig().favorites ?? [];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function addFavorite(agentId: string): void {
|
|
367
|
+
const config = getConfig();
|
|
368
|
+
if (!config.favorites) config.favorites = [];
|
|
369
|
+
if (!config.favorites.includes(agentId)) {
|
|
370
|
+
config.favorites.push(agentId);
|
|
371
|
+
saveConfig(config);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function removeFavorite(agentId: string): void {
|
|
376
|
+
const config = getConfig();
|
|
377
|
+
if (!config.favorites) return;
|
|
378
|
+
config.favorites = config.favorites.filter(id => id !== agentId);
|
|
379
|
+
saveConfig(config);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function isFavorite(agentId: string): boolean {
|
|
383
|
+
return getFavorites().includes(agentId);
|
|
384
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared display formatters for human-readable MCP and CLI output.
|
|
3
|
+
* Plain text only — no ANSI color codes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ── Stars ────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export function stars(rating: number | null | undefined): string {
|
|
9
|
+
if (rating == null) return "☆☆☆☆☆";
|
|
10
|
+
const r = Math.round(Math.min(5, Math.max(0, rating)));
|
|
11
|
+
return "★".repeat(r) + "☆".repeat(5 - r);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ── Compact number ───────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export function compactNumber(n: number | null | undefined): string {
|
|
17
|
+
if (n == null || n === 0) return "0";
|
|
18
|
+
if (n >= 1_000_000) return (n / 1_000_000).toFixed(1).replace(/\.0$/, "") + "M";
|
|
19
|
+
if (n >= 1_000) return (n / 1_000).toFixed(1).replace(/\.0$/, "") + "k";
|
|
20
|
+
return String(n);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Bytes formatting ─────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export function formatBytes(bytes: number): string {
|
|
26
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
27
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
28
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── File output detection ────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
export function isFileOutput(output: unknown): output is { type: "file"; url: string; mime_type?: string; size_bytes?: number } {
|
|
34
|
+
return output != null && typeof output === "object" && (output as Record<string, unknown>).type === "file" && !!(output as Record<string, unknown>).url;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Price formatting ─────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
export function formatPrice(pricePer1kTokens?: string | null, pricingModel?: string | null): string {
|
|
40
|
+
if (!pricePer1kTokens) return "free";
|
|
41
|
+
const p = parseFloat(pricePer1kTokens);
|
|
42
|
+
if (pricingModel === "fixed") return `$${p.toFixed(2)}/req`;
|
|
43
|
+
return `$${p.toFixed(3)}/1k tokens`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Agent line (compact, one line per agent) ─────────────────────
|
|
47
|
+
|
|
48
|
+
interface AgentLike {
|
|
49
|
+
id?: string;
|
|
50
|
+
name?: string;
|
|
51
|
+
avgRating?: number | null;
|
|
52
|
+
ratingCount?: number;
|
|
53
|
+
totalExecutions?: number;
|
|
54
|
+
pricePer1kTokens?: string;
|
|
55
|
+
pricingModel?: string;
|
|
56
|
+
stats?: { completedJobs?: number; avgRating?: number | null };
|
|
57
|
+
[key: string]: unknown;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function agentLine(agent: AgentLike): string {
|
|
61
|
+
const name = agent.name ?? "Unknown";
|
|
62
|
+
const rating = agent.avgRating ?? agent.stats?.avgRating ?? null;
|
|
63
|
+
const jobs = agent.stats?.completedJobs ?? agent.totalExecutions ?? 0;
|
|
64
|
+
const price = formatPrice(agent.pricePer1kTokens, agent.pricingModel);
|
|
65
|
+
const reliability = agent.successRate != null && Number(agent.successRate) < 1
|
|
66
|
+
? ` • ${(Number(agent.successRate) * 100).toFixed(0)}% reliable`
|
|
67
|
+
: "";
|
|
68
|
+
return `${name} ${stars(rating)} ${compactNumber(jobs)} jobs • ${price}${reliability}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatLastActive(lastActiveAt: string | null | undefined): string | null {
|
|
72
|
+
if (!lastActiveAt) return null;
|
|
73
|
+
const date = new Date(lastActiveAt);
|
|
74
|
+
const now = new Date();
|
|
75
|
+
const diffMs = now.getTime() - date.getTime();
|
|
76
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
77
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
78
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
79
|
+
|
|
80
|
+
if (diffMins < 5) return "Active just now";
|
|
81
|
+
if (diffMins < 60) return `Active ${diffMins}m ago`;
|
|
82
|
+
if (diffHours < 24) return `Active ${diffHours}h ago`;
|
|
83
|
+
if (diffDays < 7) return `Active ${diffDays}d ago`;
|
|
84
|
+
return `Last active: ${date.toLocaleDateString()}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Agent list (for MCP / formatted output) ──────────────────────
|
|
88
|
+
|
|
89
|
+
export function agentList(agents: AgentLike[], query?: string): string {
|
|
90
|
+
if (agents.length === 0) {
|
|
91
|
+
return query
|
|
92
|
+
? `No agents found matching "${query}".`
|
|
93
|
+
: "No agents found.";
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const header = query
|
|
97
|
+
? `Found ${agents.length} agent${agents.length === 1 ? "" : "s"} matching "${query}":`
|
|
98
|
+
: `Found ${agents.length} agent${agents.length === 1 ? "" : "s"}:`;
|
|
99
|
+
|
|
100
|
+
const lines = agents.map((a) => ` ${agentLine(a)}`);
|
|
101
|
+
return [header, "", ...lines].join("\n");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Run result (for MCP / formatted output) ──────────────────────
|
|
105
|
+
|
|
106
|
+
interface RunResultLike {
|
|
107
|
+
job_id?: string;
|
|
108
|
+
status?: string;
|
|
109
|
+
agent_id?: string;
|
|
110
|
+
agent_name?: string;
|
|
111
|
+
output?: unknown;
|
|
112
|
+
latency_ms?: number;
|
|
113
|
+
execution_latency_ms?: number;
|
|
114
|
+
cost?: number;
|
|
115
|
+
estimated_cost?: number;
|
|
116
|
+
input_tokens?: number;
|
|
117
|
+
tags?: string[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function formatRunResult(result: RunResultLike, opts?: { paymentMethod?: string }): string {
|
|
121
|
+
const lines: string[] = [];
|
|
122
|
+
|
|
123
|
+
// Output
|
|
124
|
+
if (result.output != null) {
|
|
125
|
+
const out = result.output as Record<string, unknown>;
|
|
126
|
+
if (isFileOutput(out)) {
|
|
127
|
+
const mime = (out.mime_type as string) ?? "file";
|
|
128
|
+
const size = out.size_bytes != null ? ` (${formatBytes(out.size_bytes as number)})` : "";
|
|
129
|
+
const category = mime.split("/")[0];
|
|
130
|
+
const label = category === "image" ? "Image"
|
|
131
|
+
: category === "video" ? "Video"
|
|
132
|
+
: category === "audio" ? "Audio"
|
|
133
|
+
: "File";
|
|
134
|
+
lines.push(`${label} output: ${mime}${size}`);
|
|
135
|
+
lines.push(`Download: ${out.url as string}`);
|
|
136
|
+
lines.push("(Link expires in 1 hour)");
|
|
137
|
+
} else if (typeof result.output === "string") {
|
|
138
|
+
lines.push(result.output);
|
|
139
|
+
} else {
|
|
140
|
+
lines.push(JSON.stringify(result.output, null, 2));
|
|
141
|
+
}
|
|
142
|
+
lines.push("");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Summary line
|
|
146
|
+
const cost = result.cost ?? result.estimated_cost;
|
|
147
|
+
const status = result.status === "success" || result.status === "completed" ? "✓" : "✗";
|
|
148
|
+
const agent = result.agent_name ?? result.agent_id ?? "";
|
|
149
|
+
const costStr = cost != null ? `$${cost.toFixed(cost < 0.01 ? 6 : 2)}` : "";
|
|
150
|
+
const latency = result.latency_ms != null ? `${result.latency_ms}ms` : "";
|
|
151
|
+
const method = opts?.paymentMethod ?? "";
|
|
152
|
+
|
|
153
|
+
const parts = [
|
|
154
|
+
`${status} ${agent}`,
|
|
155
|
+
costStr ? `cost: ${costStr}` : "",
|
|
156
|
+
method ? `via ${method}` : "",
|
|
157
|
+
latency ? `latency: ${latency}` : "",
|
|
158
|
+
].filter(Boolean);
|
|
159
|
+
|
|
160
|
+
lines.push(parts.join(" • "));
|
|
161
|
+
|
|
162
|
+
if (result.job_id) {
|
|
163
|
+
lines.push(`job: ${result.job_id}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return lines.join("\n");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── Output type hint ─────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Hint about what output type an agent typically returns, based on its tags.
|
|
173
|
+
* Returns null if no file-related tags found.
|
|
174
|
+
*/
|
|
175
|
+
export function outputTypeHint(tags?: string[] | null): string | null {
|
|
176
|
+
if (!tags || tags.length === 0) return null;
|
|
177
|
+
const fileIndicators: Record<string, string> = {
|
|
178
|
+
"image": "image files",
|
|
179
|
+
"image-generation": "image files",
|
|
180
|
+
"illustration": "image files",
|
|
181
|
+
"design": "design files",
|
|
182
|
+
"video": "video files",
|
|
183
|
+
"audio": "audio files",
|
|
184
|
+
"voice": "audio files",
|
|
185
|
+
"transcription": "text documents",
|
|
186
|
+
"pdf": "PDF documents",
|
|
187
|
+
"media": "media files",
|
|
188
|
+
"visual": "visual content",
|
|
189
|
+
"creative-design": "design files",
|
|
190
|
+
"visual-art": "image files",
|
|
191
|
+
};
|
|
192
|
+
for (const tag of tags) {
|
|
193
|
+
const hint = fileIndicators[tag.toLowerCase()];
|
|
194
|
+
if (hint) return `Typically returns ${hint}`;
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ── Web URL ─────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
const WEB_URL = process.env.AGENTWONDERLAND_WEB_URL ?? "https://agentwonderland.com";
|
|
202
|
+
|
|
203
|
+
export function agentWebUrl(agentId: string): string {
|
|
204
|
+
return `${WEB_URL}/agents/${agentId}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── Feedback summary ────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
export function formatFeedbackSummary(stats: Record<string, unknown>): string {
|
|
210
|
+
const lines: string[] = [];
|
|
211
|
+
|
|
212
|
+
const avgRating = stats.avgRating as number | null;
|
|
213
|
+
const ratingCount = (stats.ratingCount ?? 0) as number;
|
|
214
|
+
if (avgRating != null && ratingCount > 0) {
|
|
215
|
+
lines.push(`Rating: ${stars(avgRating)} (${avgRating.toFixed(1)}/5 from ${ratingCount} reviews)`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const tipCount = (stats.tipCount ?? 0) as number;
|
|
219
|
+
const totalTips = parseFloat(String(stats.totalTips ?? 0));
|
|
220
|
+
if (tipCount > 0) {
|
|
221
|
+
const avgTip = totalTips / tipCount;
|
|
222
|
+
lines.push(`Tips: ${tipCount} tips, avg $${avgTip.toFixed(2)}, total $${totalTips.toFixed(2)}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return lines.join("\n");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ── Wallet status (for MCP) ──────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
interface WalletStatusEntry {
|
|
231
|
+
id: string;
|
|
232
|
+
chains: string[];
|
|
233
|
+
address?: string | null;
|
|
234
|
+
label?: string;
|
|
235
|
+
isDefault?: boolean;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
interface WalletInfo {
|
|
239
|
+
wallets: WalletStatusEntry[];
|
|
240
|
+
card?: { brand: string; last4: string } | null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function formatWalletStatus(info: WalletInfo): string {
|
|
244
|
+
if (info.wallets.length === 0 && !info.card) {
|
|
245
|
+
return "No payment methods configured.\nRun: aw wallet setup";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const lines = ["Payment methods:"];
|
|
249
|
+
for (const w of info.wallets) {
|
|
250
|
+
const label = w.label ? ` (${w.label})` : "";
|
|
251
|
+
const def = w.isDefault ? " [default]" : "";
|
|
252
|
+
lines.push(` ${w.id}${label}: ${w.chains.join(", ")} — ${w.address ?? "unknown"}${def}`);
|
|
253
|
+
}
|
|
254
|
+
if (info.card) lines.push(` Card: ${info.card.brand} ****${info.card.last4}`);
|
|
255
|
+
return lines.join("\n");
|
|
256
|
+
}
|