@agentchainlabsai/cli 0.2.2 → 0.2.4
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/cli.js +84 -14
- package/dist/media.d.ts +3 -1
- package/dist/media.js +94 -12
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { runWatch } from './watch.js';
|
|
|
13
13
|
import { runWake } from './wake.js';
|
|
14
14
|
import { uploadAgentMediaFile } from './media.js';
|
|
15
15
|
/** Sent as `X-AgentChain-Client` on every request so the owner's activity feed can say "via CLI". Keep in sync with package.json. */
|
|
16
|
-
const CLI_CLIENT_TAG = 'cli/0.2.
|
|
16
|
+
const CLI_CLIENT_TAG = 'cli/0.2.4';
|
|
17
17
|
function print(data) {
|
|
18
18
|
console.log(JSON.stringify(data, null, 2));
|
|
19
19
|
}
|
|
@@ -333,7 +333,10 @@ Commands (need --api-key / AGENTCHAIN_API_KEY unless marked public or local):
|
|
|
333
333
|
gigs create --body '<json>'
|
|
334
334
|
gigs order <gigId> [--tier BASIC|STANDARD|PREMIUM] [--promo CODE] Unpaid order; pay with "orders pay"
|
|
335
335
|
gigs offer <gigId> --to <buyerUserId> --amount N [--days N] [--note <text>] Seller's custom price
|
|
336
|
-
|
|
336
|
+
gigs make-offer <gigId> --amount N [--note <text>] Buyer: propose your own price (USDC)
|
|
337
|
+
gigs offers [--role buyer|seller] [--status PENDING|ACCEPTED|REJECTED|CANCELLED|EXPIRED] Gig offers you made or received
|
|
338
|
+
gigs offer-get <offerId> | gigs offer-respond <offerId> accept|reject|cancel [--note <text>] Answer a gig offer
|
|
339
|
+
media upload --file <path> --target job|gig|product|portfolio|avatar|cover|chat|order|job-delivery [--order <id>] [--job <id>] [--label <text>] chat and deliveries: any document, video or archive up to 1 GB
|
|
337
340
|
profile avatar --file <path>
|
|
338
341
|
profile cover --file <path>
|
|
339
342
|
profile portfolio upload --file <path>
|
|
@@ -348,7 +351,8 @@ Commands (need --api-key / AGENTCHAIN_API_KEY unless marked public or local):
|
|
|
348
351
|
orders get <orderId>
|
|
349
352
|
orders pay <orderId> [--idempotency-key <key>] Pay a gig order or accept a gig offer (AgentChain Wallet)
|
|
350
353
|
orders requirements <orderId> --text <text>
|
|
351
|
-
orders deliver <orderId> --text <text> [--file-url <url>]
|
|
354
|
+
orders deliver <orderId> --text <text> [--file-url <url>]...
|
|
355
|
+
deal get <jobId> | deal <action> <jobId> [--body '<json>'] Cancel, extend and milestones of a hired job
|
|
352
356
|
orders accept <orderId> --rating 1-5 [--review <text>] Accept + rate (rating required)
|
|
353
357
|
orders revision <orderId> --text <text>
|
|
354
358
|
orders rate <orderId> --rating 1-5 [--review <text>]
|
|
@@ -3342,16 +3346,65 @@ async function main() {
|
|
|
3342
3346
|
}));
|
|
3343
3347
|
return;
|
|
3344
3348
|
}
|
|
3345
|
-
|
|
3349
|
+
if (sub === 'make-offer' && rest[2]) {
|
|
3350
|
+
let amount = NaN;
|
|
3351
|
+
let message;
|
|
3352
|
+
let idempotencyKey;
|
|
3353
|
+
for (let i = 3; i < rest.length; i++) {
|
|
3354
|
+
if (rest[i] === '--amount' && rest[i + 1])
|
|
3355
|
+
amount = Number(rest[++i]);
|
|
3356
|
+
else if (rest[i] === '--note' && rest[i + 1])
|
|
3357
|
+
message = rest[++i];
|
|
3358
|
+
else if (rest[i] === '--idempotency-key' && rest[i + 1])
|
|
3359
|
+
idempotencyKey = rest[++i];
|
|
3360
|
+
}
|
|
3361
|
+
if (!Number.isFinite(amount))
|
|
3362
|
+
err('gigs make-offer <gigId> --amount <usdc> [--note <text>]');
|
|
3363
|
+
print(await client.v1Request('POST', '/agent/gig-offers', {
|
|
3364
|
+
gigId: rest[2],
|
|
3365
|
+
amount,
|
|
3366
|
+
...(message ? { message } : {}),
|
|
3367
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
3368
|
+
}));
|
|
3369
|
+
return;
|
|
3370
|
+
}
|
|
3371
|
+
if (sub === 'offers') {
|
|
3372
|
+
const q = new URLSearchParams();
|
|
3373
|
+
for (let i = 2; i < rest.length; i++) {
|
|
3374
|
+
if (rest[i] === '--role' && rest[i + 1])
|
|
3375
|
+
q.set('role', rest[++i]);
|
|
3376
|
+
else if (rest[i] === '--status' && rest[i + 1])
|
|
3377
|
+
q.set('status', rest[++i]);
|
|
3378
|
+
}
|
|
3379
|
+
const qs = q.toString();
|
|
3380
|
+
print(await client.v1Request('GET', `/agent/gig-offers${qs ? `?${qs}` : ''}`));
|
|
3381
|
+
return;
|
|
3382
|
+
}
|
|
3383
|
+
if (sub === 'offer-get' && rest[2]) {
|
|
3384
|
+
print(await client.v1Request('GET', `/agent/gig-offers/${encodeURIComponent(rest[2])}`));
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
if (sub === 'offer-respond' && rest[2]) {
|
|
3388
|
+
const action = rest[3];
|
|
3389
|
+
if (action !== 'accept' && action !== 'reject' && action !== 'cancel') {
|
|
3390
|
+
err('gigs offer-respond <offerId> accept|reject|cancel [--note <text>]');
|
|
3391
|
+
}
|
|
3392
|
+
const noteAt = rest.indexOf('--note');
|
|
3393
|
+
const note = noteAt >= 0 ? rest[noteAt + 1] : undefined;
|
|
3394
|
+
print(await client.v1Request('PATCH', `/agent/gig-offers/${encodeURIComponent(rest[2])}`, { action, ...(note ? { note } : {}) }));
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
3397
|
+
err('gigs: use "gigs list", "gigs get <gigId>", "gigs patch <gigId> --body \'<json>\'", "gigs delete <gigId>", "gigs create --body \'<json>\'", "gigs order <gigId> [--tier T]" "gigs offer <gigId> --to <userId> --amount N", "gigs make-offer <gigId> --amount N", "gigs offers" or "gigs offer-respond <offerId> accept|reject|cancel"');
|
|
3346
3398
|
}
|
|
3347
3399
|
if (cmd === 'media') {
|
|
3348
3400
|
const sub = rest[1];
|
|
3349
|
-
const targets = ['job', 'gig', 'product', 'portfolio', 'avatar', 'cover', 'chat', 'order'];
|
|
3350
|
-
const usage = 'media upload --file <path> --target job|gig|product|portfolio|avatar|cover|chat|order [--order <orderId>] [--label <text>]';
|
|
3401
|
+
const targets = ['job', 'gig', 'product', 'portfolio', 'avatar', 'cover', 'chat', 'order', 'job-delivery'];
|
|
3402
|
+
const usage = 'media upload --file <path> --target job|gig|product|portfolio|avatar|cover|chat|order|job-delivery [--order <orderId>] [--job <jobId>] [--label <text>]';
|
|
3351
3403
|
if (sub === 'upload') {
|
|
3352
3404
|
let filePath = '';
|
|
3353
3405
|
let target = '';
|
|
3354
3406
|
let orderId = '';
|
|
3407
|
+
let jobId = '';
|
|
3355
3408
|
let label = '';
|
|
3356
3409
|
for (let i = 2; i < rest.length; i++) {
|
|
3357
3410
|
if (rest[i] === '--file' && rest[i + 1])
|
|
@@ -3360,6 +3413,8 @@ async function main() {
|
|
|
3360
3413
|
target = rest[++i];
|
|
3361
3414
|
else if (rest[i] === '--order' && rest[i + 1])
|
|
3362
3415
|
orderId = rest[++i];
|
|
3416
|
+
else if (rest[i] === '--job' && rest[i + 1])
|
|
3417
|
+
jobId = rest[++i];
|
|
3363
3418
|
else if (rest[i] === '--label' && rest[i + 1])
|
|
3364
3419
|
label = rest[++i];
|
|
3365
3420
|
}
|
|
@@ -3369,12 +3424,15 @@ async function main() {
|
|
|
3369
3424
|
err(`media upload --target must be one of ${targets.join(', ')}`);
|
|
3370
3425
|
if (target === 'order' && !orderId)
|
|
3371
3426
|
err('media upload --target order needs --order <orderId> (seller deliverable)');
|
|
3427
|
+
if (target === 'job-delivery' && !jobId)
|
|
3428
|
+
err('media upload --target job-delivery needs --job <jobId> (hired agent deliverable)');
|
|
3372
3429
|
print(await uploadAgentMediaFile({
|
|
3373
3430
|
baseUrl: requireBase(g),
|
|
3374
3431
|
apiKey: requireKey(g),
|
|
3375
3432
|
filePath,
|
|
3376
3433
|
target: target,
|
|
3377
3434
|
orderId: orderId || undefined,
|
|
3435
|
+
jobId: jobId || undefined,
|
|
3378
3436
|
label: label || undefined,
|
|
3379
3437
|
}));
|
|
3380
3438
|
return;
|
|
@@ -3495,25 +3553,37 @@ async function main() {
|
|
|
3495
3553
|
}
|
|
3496
3554
|
err('proposals: use "proposals list", "proposals submit …", or "proposals accept <proposalId> [--payment-method WALLET]"');
|
|
3497
3555
|
}
|
|
3556
|
+
if (cmd === 'deal') {
|
|
3557
|
+
const usage = 'deal get <jobId> | deal <request_cancel|request_extension|respond_resolution|propose_milestones|accept_milestones|submit_milestone|request_milestone_changes|release_milestone> <jobId> [--body \'<json>\']';
|
|
3558
|
+
const sub = rest[1];
|
|
3559
|
+
const jobId = rest[2];
|
|
3560
|
+
if (!sub || !jobId)
|
|
3561
|
+
err(usage);
|
|
3562
|
+
if (sub === 'get')
|
|
3563
|
+
return print(await client.getJobDeal(jobId));
|
|
3564
|
+
const raw = flagValue(rest, '--body');
|
|
3565
|
+
return print(await client.jobDealAction(jobId, sub, raw ? JSON.parse(raw) : {}));
|
|
3566
|
+
}
|
|
3498
3567
|
if (cmd === 'deliver') {
|
|
3499
3568
|
let jobId = '';
|
|
3500
3569
|
let resultText = '';
|
|
3501
|
-
|
|
3570
|
+
const fileUrls = [];
|
|
3502
3571
|
for (let i = 1; i < rest.length; i++) {
|
|
3503
3572
|
if (rest[i] === '--job' && rest[i + 1])
|
|
3504
3573
|
jobId = rest[++i];
|
|
3505
3574
|
else if (rest[i] === '--text' && rest[i + 1])
|
|
3506
3575
|
resultText = rest[++i];
|
|
3507
3576
|
else if (rest[i] === '--file-url' && rest[i + 1])
|
|
3508
|
-
|
|
3577
|
+
fileUrls.push(rest[++i]);
|
|
3509
3578
|
}
|
|
3510
3579
|
if (!jobId || !resultText) {
|
|
3511
|
-
err('deliver --job <jobId> --text <text> [--file-url <url>] [--idempotency-key <key>]');
|
|
3580
|
+
err('deliver --job <jobId> --text <text> [--file-url <url>]... [--idempotency-key <key>]');
|
|
3512
3581
|
}
|
|
3513
3582
|
print(await client.deliver({
|
|
3514
3583
|
jobId,
|
|
3515
3584
|
resultText,
|
|
3516
|
-
resultFileUrl:
|
|
3585
|
+
resultFileUrl: fileUrls[0] ?? null,
|
|
3586
|
+
...(fileUrls.length > 1 ? { deliverables: fileUrls.map((url) => ({ url, label: url.split('/').pop() || 'delivery' })) } : {}),
|
|
3517
3587
|
idempotencyKey: takeIdempotencyKey(rest, 1),
|
|
3518
3588
|
}));
|
|
3519
3589
|
return;
|
|
@@ -3547,12 +3617,12 @@ async function main() {
|
|
|
3547
3617
|
}
|
|
3548
3618
|
if (sub === 'deliver') {
|
|
3549
3619
|
const message = opt('--text');
|
|
3550
|
-
const
|
|
3551
|
-
if (!message && !
|
|
3552
|
-
err('orders deliver <orderId> --text <message> [--file-url <https url>]');
|
|
3620
|
+
const fileUrls = rest.flatMap((a, i) => (a === '--file-url' && rest[i + 1] ? [rest[i + 1]] : []));
|
|
3621
|
+
if (!message && !fileUrls.length)
|
|
3622
|
+
err('orders deliver <orderId> --text <message> [--file-url <https url>]...');
|
|
3553
3623
|
return print(await client.v1Request('POST', `${path}/deliver`, {
|
|
3554
3624
|
message: message ?? '',
|
|
3555
|
-
files:
|
|
3625
|
+
files: fileUrls.map((url) => ({ url, label: opt('--label') ?? (url.split('/').pop() || 'delivery') })),
|
|
3556
3626
|
}));
|
|
3557
3627
|
}
|
|
3558
3628
|
if (sub === 'accept') {
|
package/dist/media.d.ts
CHANGED
|
@@ -13,9 +13,11 @@ export declare function uploadAgentMediaFile(params: {
|
|
|
13
13
|
baseUrl: string;
|
|
14
14
|
apiKey: string;
|
|
15
15
|
filePath: string;
|
|
16
|
-
target: AgentMediaUploadTarget | 'order';
|
|
16
|
+
target: AgentMediaUploadTarget | 'order' | 'job-delivery';
|
|
17
17
|
/** Order id for target "order" (seller deliverable upload). */
|
|
18
18
|
orderId?: string;
|
|
19
|
+
/** Job id for target "job-delivery" (hired agent's deliverable upload). */
|
|
20
|
+
jobId?: string;
|
|
19
21
|
/** Label shown to the buyer for an order deliverable. */
|
|
20
22
|
label?: string;
|
|
21
23
|
}): Promise<unknown>;
|
package/dist/media.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
3
|
import { basename } from 'node:path';
|
|
3
4
|
export const AGENT_MEDIA_UPLOAD_PATHS = {
|
|
4
5
|
job: '/api/v1/agent/jobs/media/upload',
|
|
@@ -9,6 +10,8 @@ export const AGENT_MEDIA_UPLOAD_PATHS = {
|
|
|
9
10
|
cover: '/api/v1/agent/profile/cover/upload',
|
|
10
11
|
chat: '/api/v1/agent/messages/media/upload',
|
|
11
12
|
};
|
|
13
|
+
/** Larger chat files skip the API (serverless body limit) and go straight to storage. */
|
|
14
|
+
const CHAT_API_UPLOAD_MAX_BYTES = 4 * 1024 * 1024;
|
|
12
15
|
function guessContentType(filePath) {
|
|
13
16
|
const lower = filePath.toLowerCase();
|
|
14
17
|
if (lower.endsWith('.png'))
|
|
@@ -37,8 +40,95 @@ function guessContentType(filePath) {
|
|
|
37
40
|
return 'text/csv';
|
|
38
41
|
return 'application/octet-stream';
|
|
39
42
|
}
|
|
43
|
+
async function readJsonResponse(res) {
|
|
44
|
+
const data = await res.json().catch(() => ({}));
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
throw new Error(typeof data.error === 'string'
|
|
47
|
+
? data.error
|
|
48
|
+
: `HTTP ${res.status}`);
|
|
49
|
+
}
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
/** Delivery file of any size: small via the API, large with a ticket, then the server checks it. */
|
|
53
|
+
async function uploadDeliveryFile(params) {
|
|
54
|
+
const base = params.baseUrl.replace(/\/$/, '');
|
|
55
|
+
const auth = { Authorization: `Bearer ${params.apiKey}`, 'X-AgentChain-Client': 'cli/0.2.4' };
|
|
56
|
+
const json = { ...auth, 'Content-Type': 'application/json' };
|
|
57
|
+
const ids = { target: params.target, targetId: params.targetId };
|
|
58
|
+
if ((await stat(params.filePath)).size > CHAT_API_UPLOAD_MAX_BYTES) {
|
|
59
|
+
const ticket = (await readJsonResponse(await fetch(`${base}/api/v1/agent/deliverables/upload-url`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: json,
|
|
62
|
+
body: JSON.stringify({ ...ids, filename: basename(params.filePath) }),
|
|
63
|
+
})));
|
|
64
|
+
if (ticket.mode === 'direct' && ticket.pathname && ticket.clientToken) {
|
|
65
|
+
const { put } = await import('@vercel/blob/client');
|
|
66
|
+
const blob = await put(ticket.pathname, createReadStream(params.filePath), {
|
|
67
|
+
access: 'public',
|
|
68
|
+
token: ticket.clientToken,
|
|
69
|
+
contentType: ticket.contentType,
|
|
70
|
+
multipart: true,
|
|
71
|
+
});
|
|
72
|
+
return readJsonResponse(await fetch(`${base}/api/v1/agent/deliverables/complete`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: json,
|
|
75
|
+
body: JSON.stringify({ ...ids, url: blob.url, label: params.label }),
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const form = new FormData();
|
|
80
|
+
form.append('file', new Blob([await readFile(params.filePath)], { type: guessContentType(params.filePath) }), basename(params.filePath));
|
|
81
|
+
form.append('target', params.target);
|
|
82
|
+
form.append('targetId', params.targetId);
|
|
83
|
+
if (params.label)
|
|
84
|
+
form.append('label', params.label);
|
|
85
|
+
return readJsonResponse(await fetch(`${base}/api/v1/agent/deliverables/upload`, { method: 'POST', headers: auth, body: form }));
|
|
86
|
+
}
|
|
87
|
+
/** Hosted API request bodies stop at about 4.5 MB. */
|
|
88
|
+
const LISTING_API_UPLOAD_MAX_BYTES = 4 * 1024 * 1024;
|
|
89
|
+
/** Large chat file: ask for a one-file ticket, then upload directly. Null when this server has no direct storage. */
|
|
90
|
+
async function uploadChatFileDirect(params) {
|
|
91
|
+
const res = await fetch(`${params.baseUrl.replace(/\/$/, '')}/api/v1/agent/messages/media/upload-url`, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: {
|
|
94
|
+
Authorization: `Bearer ${params.apiKey}`,
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
'X-AgentChain-Client': 'cli/0.2.4',
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify({ filename: basename(params.filePath) }),
|
|
99
|
+
});
|
|
100
|
+
const ticket = (await readJsonResponse(res));
|
|
101
|
+
if (ticket.mode !== 'direct' || !ticket.pathname || !ticket.clientToken)
|
|
102
|
+
return null;
|
|
103
|
+
const { put } = await import('@vercel/blob/client');
|
|
104
|
+
const blob = await put(ticket.pathname, createReadStream(params.filePath), {
|
|
105
|
+
access: 'public',
|
|
106
|
+
token: ticket.clientToken,
|
|
107
|
+
contentType: ticket.contentType,
|
|
108
|
+
multipart: true,
|
|
109
|
+
});
|
|
110
|
+
return { url: blob.url, contentType: ticket.contentType, filename: ticket.filename, kind: ticket.kind };
|
|
111
|
+
}
|
|
40
112
|
/** Multipart upload to Agent v1 media endpoints (same as MCP HTTP guidance). */
|
|
41
113
|
export async function uploadAgentMediaFile(params) {
|
|
114
|
+
if (params.target === 'order' || params.target === 'job-delivery') {
|
|
115
|
+
return uploadDeliveryFile({
|
|
116
|
+
baseUrl: params.baseUrl,
|
|
117
|
+
apiKey: params.apiKey,
|
|
118
|
+
filePath: params.filePath,
|
|
119
|
+
target: params.target === 'order' ? 'order' : 'job',
|
|
120
|
+
targetId: (params.target === 'order' ? params.orderId : params.jobId) ?? '',
|
|
121
|
+
label: params.label,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (params.target === 'chat' && (await stat(params.filePath)).size > CHAT_API_UPLOAD_MAX_BYTES) {
|
|
125
|
+
const direct = await uploadChatFileDirect(params);
|
|
126
|
+
if (direct)
|
|
127
|
+
return direct;
|
|
128
|
+
}
|
|
129
|
+
if (params.target !== 'chat' && (await stat(params.filePath)).size > LISTING_API_UPLOAD_MAX_BYTES) {
|
|
130
|
+
throw new Error('File too large: a local file can have at most 4 MB here. Export a smaller JPEG or WebP.');
|
|
131
|
+
}
|
|
42
132
|
const buffer = await readFile(params.filePath);
|
|
43
133
|
const contentType = guessContentType(params.filePath);
|
|
44
134
|
const form = new FormData();
|
|
@@ -46,19 +136,11 @@ export async function uploadAgentMediaFile(params) {
|
|
|
46
136
|
form.append('file', blob, basename(params.filePath));
|
|
47
137
|
if (params.label)
|
|
48
138
|
form.append('label', params.label);
|
|
49
|
-
const path = params.target
|
|
50
|
-
? `/api/v1/agent/marketplace-orders/${encodeURIComponent(params.orderId ?? '')}/deliverables/upload`
|
|
51
|
-
: AGENT_MEDIA_UPLOAD_PATHS[params.target];
|
|
139
|
+
const path = AGENT_MEDIA_UPLOAD_PATHS[params.target];
|
|
52
140
|
const res = await fetch(`${params.baseUrl.replace(/\/$/, '')}${path}`, {
|
|
53
141
|
method: 'POST',
|
|
54
|
-
headers: { Authorization: `Bearer ${params.apiKey}`, 'X-AgentChain-Client': 'cli/0.2.
|
|
142
|
+
headers: { Authorization: `Bearer ${params.apiKey}`, 'X-AgentChain-Client': 'cli/0.2.4' },
|
|
55
143
|
body: form,
|
|
56
144
|
});
|
|
57
|
-
|
|
58
|
-
if (!res.ok) {
|
|
59
|
-
throw new Error(typeof data.error === 'string'
|
|
60
|
-
? data.error
|
|
61
|
-
: `HTTP ${res.status}`);
|
|
62
|
-
}
|
|
63
|
-
return data;
|
|
145
|
+
return readJsonResponse(res);
|
|
64
146
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentchainlabsai/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Command-line interface for the AgentChain Agent API v1 (REST, same contract as MCP).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"node": ">=18"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@agentchainlabsai/sdk": "^0.2.
|
|
20
|
+
"@agentchainlabsai/sdk": "^0.2.4",
|
|
21
|
+
"@vercel/blob": "^2.3.1",
|
|
21
22
|
"ethers": "^6.13.0"
|
|
22
23
|
},
|
|
23
24
|
"publishConfig": {
|