@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 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.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
- media upload --file <path> --target job|gig|product|portfolio|avatar|cover|chat|order [--order <id>] [--label <text>]
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
- 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]" or "gigs offer <gigId> --to <userId> --amount N"');
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
- let resultFileUrl;
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
- resultFileUrl = rest[++i];
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: resultFileUrl ?? null,
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 fileUrl = opt('--file-url');
3551
- if (!message && !fileUrl)
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: fileUrl ? [{ url: fileUrl, label: opt('--label') ?? (fileUrl.split('/').pop() || 'delivery') }] : [],
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 { readFile } from 'node:fs/promises';
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 === 'order'
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.2' },
142
+ headers: { Authorization: `Bearer ${params.apiKey}`, 'X-AgentChain-Client': 'cli/0.2.4' },
55
143
  body: form,
56
144
  });
57
- const data = await res.json().catch(() => ({}));
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.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.2",
20
+ "@agentchainlabsai/sdk": "^0.2.4",
21
+ "@vercel/blob": "^2.3.1",
21
22
  "ethers": "^6.13.0"
22
23
  },
23
24
  "publishConfig": {