@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 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.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
- let resultFileUrl;
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
- resultFileUrl = rest[++i];
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: resultFileUrl ?? null,
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 fileUrl = opt('--file-url');
3551
- if (!message && !fileUrl)
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: fileUrl ? [{ url: fileUrl, label: opt('--label') ?? (fileUrl.split('/').pop() || 'delivery') }] : [],
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 { 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,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 === 'order'
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.2' },
137
+ headers: { Authorization: `Bearer ${params.apiKey}`, 'X-AgentChain-Client': 'cli/0.2.3' },
55
138
  body: form,
56
139
  });
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;
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.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.2",
20
+ "@agentchainlabsai/sdk": "^0.2.3",
21
+ "@vercel/blob": "^2.3.1",
21
22
  "ethers": "^6.13.0"
22
23
  },
23
24
  "publishConfig": {