@agentchainlabsai/cli 0.2.2 → 0.2.3
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 +32 -13
- package/dist/media.d.ts +3 -1
- package/dist/media.js +89 -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.3';
|
|
17
17
|
function print(data) {
|
|
18
18
|
console.log(JSON.stringify(data, null, 2));
|
|
19
19
|
}
|
|
@@ -333,7 +333,7 @@ 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
|
-
media upload --file <path> --target job|gig|product|portfolio|avatar|cover|chat|order [--order <id>] [--label <text>]
|
|
336
|
+
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
337
|
profile avatar --file <path>
|
|
338
338
|
profile cover --file <path>
|
|
339
339
|
profile portfolio upload --file <path>
|
|
@@ -348,7 +348,8 @@ Commands (need --api-key / AGENTCHAIN_API_KEY unless marked public or local):
|
|
|
348
348
|
orders get <orderId>
|
|
349
349
|
orders pay <orderId> [--idempotency-key <key>] Pay a gig order or accept a gig offer (AgentChain Wallet)
|
|
350
350
|
orders requirements <orderId> --text <text>
|
|
351
|
-
orders deliver <orderId> --text <text> [--file-url <url>]
|
|
351
|
+
orders deliver <orderId> --text <text> [--file-url <url>]...
|
|
352
|
+
deal get <jobId> | deal <action> <jobId> [--body '<json>'] Cancel, extend and milestones of a hired job
|
|
352
353
|
orders accept <orderId> --rating 1-5 [--review <text>] Accept + rate (rating required)
|
|
353
354
|
orders revision <orderId> --text <text>
|
|
354
355
|
orders rate <orderId> --rating 1-5 [--review <text>]
|
|
@@ -3346,12 +3347,13 @@ async function main() {
|
|
|
3346
3347
|
}
|
|
3347
3348
|
if (cmd === 'media') {
|
|
3348
3349
|
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>]';
|
|
3350
|
+
const targets = ['job', 'gig', 'product', 'portfolio', 'avatar', 'cover', 'chat', 'order', 'job-delivery'];
|
|
3351
|
+
const usage = 'media upload --file <path> --target job|gig|product|portfolio|avatar|cover|chat|order|job-delivery [--order <orderId>] [--job <jobId>] [--label <text>]';
|
|
3351
3352
|
if (sub === 'upload') {
|
|
3352
3353
|
let filePath = '';
|
|
3353
3354
|
let target = '';
|
|
3354
3355
|
let orderId = '';
|
|
3356
|
+
let jobId = '';
|
|
3355
3357
|
let label = '';
|
|
3356
3358
|
for (let i = 2; i < rest.length; i++) {
|
|
3357
3359
|
if (rest[i] === '--file' && rest[i + 1])
|
|
@@ -3360,6 +3362,8 @@ async function main() {
|
|
|
3360
3362
|
target = rest[++i];
|
|
3361
3363
|
else if (rest[i] === '--order' && rest[i + 1])
|
|
3362
3364
|
orderId = rest[++i];
|
|
3365
|
+
else if (rest[i] === '--job' && rest[i + 1])
|
|
3366
|
+
jobId = rest[++i];
|
|
3363
3367
|
else if (rest[i] === '--label' && rest[i + 1])
|
|
3364
3368
|
label = rest[++i];
|
|
3365
3369
|
}
|
|
@@ -3369,12 +3373,15 @@ async function main() {
|
|
|
3369
3373
|
err(`media upload --target must be one of ${targets.join(', ')}`);
|
|
3370
3374
|
if (target === 'order' && !orderId)
|
|
3371
3375
|
err('media upload --target order needs --order <orderId> (seller deliverable)');
|
|
3376
|
+
if (target === 'job-delivery' && !jobId)
|
|
3377
|
+
err('media upload --target job-delivery needs --job <jobId> (hired agent deliverable)');
|
|
3372
3378
|
print(await uploadAgentMediaFile({
|
|
3373
3379
|
baseUrl: requireBase(g),
|
|
3374
3380
|
apiKey: requireKey(g),
|
|
3375
3381
|
filePath,
|
|
3376
3382
|
target: target,
|
|
3377
3383
|
orderId: orderId || undefined,
|
|
3384
|
+
jobId: jobId || undefined,
|
|
3378
3385
|
label: label || undefined,
|
|
3379
3386
|
}));
|
|
3380
3387
|
return;
|
|
@@ -3495,25 +3502,37 @@ async function main() {
|
|
|
3495
3502
|
}
|
|
3496
3503
|
err('proposals: use "proposals list", "proposals submit …", or "proposals accept <proposalId> [--payment-method WALLET]"');
|
|
3497
3504
|
}
|
|
3505
|
+
if (cmd === 'deal') {
|
|
3506
|
+
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>\']';
|
|
3507
|
+
const sub = rest[1];
|
|
3508
|
+
const jobId = rest[2];
|
|
3509
|
+
if (!sub || !jobId)
|
|
3510
|
+
err(usage);
|
|
3511
|
+
if (sub === 'get')
|
|
3512
|
+
return print(await client.getJobDeal(jobId));
|
|
3513
|
+
const raw = flagValue(rest, '--body');
|
|
3514
|
+
return print(await client.jobDealAction(jobId, sub, raw ? JSON.parse(raw) : {}));
|
|
3515
|
+
}
|
|
3498
3516
|
if (cmd === 'deliver') {
|
|
3499
3517
|
let jobId = '';
|
|
3500
3518
|
let resultText = '';
|
|
3501
|
-
|
|
3519
|
+
const fileUrls = [];
|
|
3502
3520
|
for (let i = 1; i < rest.length; i++) {
|
|
3503
3521
|
if (rest[i] === '--job' && rest[i + 1])
|
|
3504
3522
|
jobId = rest[++i];
|
|
3505
3523
|
else if (rest[i] === '--text' && rest[i + 1])
|
|
3506
3524
|
resultText = rest[++i];
|
|
3507
3525
|
else if (rest[i] === '--file-url' && rest[i + 1])
|
|
3508
|
-
|
|
3526
|
+
fileUrls.push(rest[++i]);
|
|
3509
3527
|
}
|
|
3510
3528
|
if (!jobId || !resultText) {
|
|
3511
|
-
err('deliver --job <jobId> --text <text> [--file-url <url>] [--idempotency-key <key>]');
|
|
3529
|
+
err('deliver --job <jobId> --text <text> [--file-url <url>]... [--idempotency-key <key>]');
|
|
3512
3530
|
}
|
|
3513
3531
|
print(await client.deliver({
|
|
3514
3532
|
jobId,
|
|
3515
3533
|
resultText,
|
|
3516
|
-
resultFileUrl:
|
|
3534
|
+
resultFileUrl: fileUrls[0] ?? null,
|
|
3535
|
+
...(fileUrls.length > 1 ? { deliverables: fileUrls.map((url) => ({ url, label: url.split('/').pop() || 'delivery' })) } : {}),
|
|
3517
3536
|
idempotencyKey: takeIdempotencyKey(rest, 1),
|
|
3518
3537
|
}));
|
|
3519
3538
|
return;
|
|
@@ -3547,12 +3566,12 @@ async function main() {
|
|
|
3547
3566
|
}
|
|
3548
3567
|
if (sub === 'deliver') {
|
|
3549
3568
|
const message = opt('--text');
|
|
3550
|
-
const
|
|
3551
|
-
if (!message && !
|
|
3552
|
-
err('orders deliver <orderId> --text <message> [--file-url <https url>]');
|
|
3569
|
+
const fileUrls = rest.flatMap((a, i) => (a === '--file-url' && rest[i + 1] ? [rest[i + 1]] : []));
|
|
3570
|
+
if (!message && !fileUrls.length)
|
|
3571
|
+
err('orders deliver <orderId> --text <message> [--file-url <https url>]...');
|
|
3553
3572
|
return print(await client.v1Request('POST', `${path}/deliver`, {
|
|
3554
3573
|
message: message ?? '',
|
|
3555
|
-
files:
|
|
3574
|
+
files: fileUrls.map((url) => ({ url, label: opt('--label') ?? (url.split('/').pop() || 'delivery') })),
|
|
3556
3575
|
}));
|
|
3557
3576
|
}
|
|
3558
3577
|
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,90 @@ 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.3' };
|
|
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
|
+
/** Large chat file: ask for a one-file ticket, then upload directly. Null when this server has no direct storage. */
|
|
88
|
+
async function uploadChatFileDirect(params) {
|
|
89
|
+
const res = await fetch(`${params.baseUrl.replace(/\/$/, '')}/api/v1/agent/messages/media/upload-url`, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: {
|
|
92
|
+
Authorization: `Bearer ${params.apiKey}`,
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
'X-AgentChain-Client': 'cli/0.2.3',
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify({ filename: basename(params.filePath) }),
|
|
97
|
+
});
|
|
98
|
+
const ticket = (await readJsonResponse(res));
|
|
99
|
+
if (ticket.mode !== 'direct' || !ticket.pathname || !ticket.clientToken)
|
|
100
|
+
return null;
|
|
101
|
+
const { put } = await import('@vercel/blob/client');
|
|
102
|
+
const blob = await put(ticket.pathname, createReadStream(params.filePath), {
|
|
103
|
+
access: 'public',
|
|
104
|
+
token: ticket.clientToken,
|
|
105
|
+
contentType: ticket.contentType,
|
|
106
|
+
multipart: true,
|
|
107
|
+
});
|
|
108
|
+
return { url: blob.url, contentType: ticket.contentType, filename: ticket.filename, kind: ticket.kind };
|
|
109
|
+
}
|
|
40
110
|
/** Multipart upload to Agent v1 media endpoints (same as MCP HTTP guidance). */
|
|
41
111
|
export async function uploadAgentMediaFile(params) {
|
|
112
|
+
if (params.target === 'order' || params.target === 'job-delivery') {
|
|
113
|
+
return uploadDeliveryFile({
|
|
114
|
+
baseUrl: params.baseUrl,
|
|
115
|
+
apiKey: params.apiKey,
|
|
116
|
+
filePath: params.filePath,
|
|
117
|
+
target: params.target === 'order' ? 'order' : 'job',
|
|
118
|
+
targetId: (params.target === 'order' ? params.orderId : params.jobId) ?? '',
|
|
119
|
+
label: params.label,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (params.target === 'chat' && (await stat(params.filePath)).size > CHAT_API_UPLOAD_MAX_BYTES) {
|
|
123
|
+
const direct = await uploadChatFileDirect(params);
|
|
124
|
+
if (direct)
|
|
125
|
+
return direct;
|
|
126
|
+
}
|
|
42
127
|
const buffer = await readFile(params.filePath);
|
|
43
128
|
const contentType = guessContentType(params.filePath);
|
|
44
129
|
const form = new FormData();
|
|
@@ -46,19 +131,11 @@ export async function uploadAgentMediaFile(params) {
|
|
|
46
131
|
form.append('file', blob, basename(params.filePath));
|
|
47
132
|
if (params.label)
|
|
48
133
|
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];
|
|
134
|
+
const path = AGENT_MEDIA_UPLOAD_PATHS[params.target];
|
|
52
135
|
const res = await fetch(`${params.baseUrl.replace(/\/$/, '')}${path}`, {
|
|
53
136
|
method: 'POST',
|
|
54
|
-
headers: { Authorization: `Bearer ${params.apiKey}`, 'X-AgentChain-Client': 'cli/0.2.
|
|
137
|
+
headers: { Authorization: `Bearer ${params.apiKey}`, 'X-AgentChain-Client': 'cli/0.2.3' },
|
|
55
138
|
body: form,
|
|
56
139
|
});
|
|
57
|
-
|
|
58
|
-
if (!res.ok) {
|
|
59
|
-
throw new Error(typeof data.error === 'string'
|
|
60
|
-
? data.error
|
|
61
|
-
: `HTTP ${res.status}`);
|
|
62
|
-
}
|
|
63
|
-
return data;
|
|
140
|
+
return readJsonResponse(res);
|
|
64
141
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentchainlabsai/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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.3",
|
|
21
|
+
"@vercel/blob": "^2.3.1",
|
|
21
22
|
"ethers": "^6.13.0"
|
|
22
23
|
},
|
|
23
24
|
"publishConfig": {
|