@darwinso/cli 0.1.0 → 0.4.0

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/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
3
4
  import { homedir } from 'node:os';
4
5
  import { dirname, join } from 'node:path';
5
- const defaultBaseUrl = 'https://api.darwin.so/api/v1';
6
+ const defaultBaseUrl = 'https://api.darwin.so/api/v2';
6
7
  const configRoot = process.env.XDG_CONFIG_HOME?.trim() || join(homedir(), '.config');
7
8
  const configPath = join(configRoot, 'darwin', 'config.json');
8
9
  class DarwinApiError extends Error {
@@ -25,9 +26,11 @@ async function readConfig() {
25
26
  }
26
27
  async function credentials() {
27
28
  const config = await readConfig();
29
+ const baseUrl = process.env.DARWIN_API_URL?.trim() || config.baseUrl?.trim() || defaultBaseUrl;
28
30
  return {
29
31
  apiKey: process.env.DARWIN_API_KEY?.trim() || config.apiKey?.trim(),
30
- baseUrl: process.env.DARWIN_API_URL?.trim() || config.baseUrl?.trim() || defaultBaseUrl,
32
+ baseUrl,
33
+ v2BaseUrl: /\/v2\/?$/.test(baseUrl) ? baseUrl : baseUrl.replace(/\/v1\/?$/, '/v2'),
31
34
  };
32
35
  }
33
36
  async function request(apiKey, baseUrl, path, options = {}) {
@@ -42,7 +45,11 @@ async function request(apiKey, baseUrl, path, options = {}) {
42
45
  headers: {
43
46
  Accept: 'application/json',
44
47
  Authorization: `Bearer ${apiKey}`,
48
+ 'X-Darwin-Access-Point': 'cli',
49
+ 'X-Darwin-Client': '@darwinso/cli',
50
+ 'X-Darwin-Client-Version': '0.4.0',
45
51
  ...(options.body === undefined ? {} : { 'Content-Type': 'application/json' }),
52
+ ...options.headers,
46
53
  },
47
54
  body: options.body === undefined ? undefined : JSON.stringify(options.body),
48
55
  });
@@ -82,6 +89,30 @@ function integerOption(args, name) {
82
89
  }
83
90
  return parsed;
84
91
  }
92
+ function repeatedOption(args, name) {
93
+ const values = [];
94
+ for (let index = 0; index < args.length; index += 1) {
95
+ if (args[index] !== name)
96
+ continue;
97
+ const value = args[index + 1];
98
+ if (!value || value.startsWith('--')) {
99
+ throw new Error(`${name} requires a value.`);
100
+ }
101
+ values.push(value);
102
+ index += 1;
103
+ }
104
+ return values;
105
+ }
106
+ function booleanOption(args, name) {
107
+ const value = option(args, name);
108
+ if (value === undefined)
109
+ return undefined;
110
+ if (value === 'true')
111
+ return true;
112
+ if (value === 'false')
113
+ return false;
114
+ throw new Error(`${name} must be true or false.`);
115
+ }
85
116
  function jsonObjectOption(args, name) {
86
117
  const value = option(args, name);
87
118
  if (!value)
@@ -98,6 +129,126 @@ function jsonObjectOption(args, name) {
98
129
  }
99
130
  return parsed;
100
131
  }
132
+ async function jsonObjectFileOption(args, name) {
133
+ const path = option(args, name);
134
+ if (!path)
135
+ return undefined;
136
+ let parsed;
137
+ try {
138
+ parsed = JSON.parse(await readFile(path, 'utf8'));
139
+ }
140
+ catch {
141
+ throw new Error(`${name} must reference a readable JSON file.`);
142
+ }
143
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
144
+ throw new Error(`${name} must contain a JSON object.`);
145
+ }
146
+ return parsed;
147
+ }
148
+ function jsonArrayOption(args, name) {
149
+ const value = option(args, name);
150
+ if (!value)
151
+ return [];
152
+ let parsed;
153
+ try {
154
+ parsed = JSON.parse(value);
155
+ }
156
+ catch {
157
+ throw new Error(`${name} must be valid JSON.`);
158
+ }
159
+ if (!Array.isArray(parsed)) {
160
+ throw new Error(`${name} must be a JSON array.`);
161
+ }
162
+ return parsed;
163
+ }
164
+ function dataOption(args) {
165
+ return args.includes('--data') ? jsonObjectOption(args, '--data') : {};
166
+ }
167
+ function requiredOption(args, name) {
168
+ const value = option(args, name)?.trim();
169
+ if (!value) {
170
+ throw new Error(`${name} is required.`);
171
+ }
172
+ return value;
173
+ }
174
+ function enumOption(args, name, values) {
175
+ const value = option(args, name);
176
+ if (value === undefined)
177
+ return undefined;
178
+ const normalized = value.toUpperCase();
179
+ if (!values.includes(normalized)) {
180
+ throw new Error(`${name} must be one of: ${values.join(', ')}.`);
181
+ }
182
+ return normalized;
183
+ }
184
+ function lowerEnumOption(args, name, values) {
185
+ const value = option(args, name);
186
+ if (value === undefined)
187
+ return undefined;
188
+ const normalized = value.toLowerCase();
189
+ if (!values.includes(normalized)) {
190
+ throw new Error(`${name} must be one of: ${values.join(', ')}.`);
191
+ }
192
+ return normalized;
193
+ }
194
+ function withOptionalString(body, key, value) {
195
+ if (value !== undefined)
196
+ body[key] = value;
197
+ }
198
+ function idempotencyHeaders(args) {
199
+ const supplied = option(args, '--idempotency-key')?.trim();
200
+ if (supplied && supplied.length > 200) {
201
+ throw new Error('--idempotency-key must be at most 200 characters.');
202
+ }
203
+ return {
204
+ 'Idempotency-Key': supplied || randomUUID(),
205
+ };
206
+ }
207
+ function id(value, usage) {
208
+ const normalized = value?.trim();
209
+ if (!normalized) {
210
+ throw new Error(usage);
211
+ }
212
+ return encodeURIComponent(normalized);
213
+ }
214
+ function textArgument(args, start, valueOptions) {
215
+ const options = new Set(valueOptions);
216
+ const words = [];
217
+ for (let index = start; index < args.length; index += 1) {
218
+ const value = args[index];
219
+ if (!value)
220
+ continue;
221
+ if (options.has(value)) {
222
+ index += 1;
223
+ continue;
224
+ }
225
+ if (value.startsWith('--')) {
226
+ throw new Error(`Unknown option: ${value}`);
227
+ }
228
+ words.push(value);
229
+ }
230
+ return words.join(' ').trim();
231
+ }
232
+ function aiQuery(args) {
233
+ return { aiId: option(args, '--ai') };
234
+ }
235
+ function mergeFields(body, fields) {
236
+ for (const [key, value] of fields)
237
+ withOptionalString(body, key, value);
238
+ return body;
239
+ }
240
+ function normalizeCapabilitySelections(body) {
241
+ if (body.capabilitySelections === undefined && Array.isArray(body.listingSelections)) {
242
+ body.capabilitySelections = body.listingSelections.map((selection) => {
243
+ if (!selection || typeof selection !== 'object' || Array.isArray(selection))
244
+ return selection;
245
+ const { listingId, ...rest } = selection;
246
+ return { ...rest, capabilityId: listingId };
247
+ });
248
+ }
249
+ delete body.listingSelections;
250
+ return body;
251
+ }
101
252
  function redact(value) {
102
253
  if (!value)
103
254
  return null;
@@ -145,27 +296,60 @@ Usage:
145
296
  darwin configure --api-key <key> [--base-url <url>]
146
297
  darwin config show
147
298
  darwin logout
148
- darwin agent message <text> [--request-id <id>]
149
- darwin conversation [--limit <number>] [--cursor <cursor>]
150
- darwin goals list
151
- darwin goals get <id>
152
- darwin goals create --intent <text> [--title <text>] [--kind <kind>]
153
- darwin approvals list [--status <status>]
154
- darwin approvals decide <id> <approve|reject> [--reason <text>]
155
- darwin integrations
156
- darwin tools list
157
- darwin tools execute <tool> [--input <json-object>]
299
+ darwin account show
300
+ darwin ais <list|get|create|update|activity> [...]
301
+ darwin ais skills
302
+ darwin ais integrations
303
+ darwin requests <list|action> [...]
304
+ darwin conversations <send|list|create|get|message> [...]
305
+ darwin goals <list|get|create|update|action|publish> [...]
306
+ darwin deals <list|get|create|update|action|payments> [...]
307
+ darwin transactions <list|get|action> [...]
308
+ darwin outcomes <list|get|evidence> [...]
309
+ darwin supply businesses <list|get|create|update> [...]
310
+ darwin host capabilities <list|get|create|update|archive> [...]
311
+ darwin supply orders <list|get> [...]
312
+ darwin supply earnings get <business-id>
313
+ darwin applications <list|get|create|update|archive|usage> [...]
314
+ darwin applications webhooks <list|create|revoke|deliveries|retry> [...]
158
315
 
159
316
  Options:
317
+ --ai <id> Explicitly target an accessible AI
318
+ --data <json> Supply or extend a JSON request body
160
319
  -h, --help Show help
161
320
  -v, --version Show the installed version
162
321
 
163
322
  Set DARWIN_API_KEY and optionally DARWIN_API_URL instead of storing local
164
323
  configuration when running in CI.
324
+
325
+ Run "darwin <resource> --help" for examples in the documentation:
326
+ https://darwin.so/docs/cli
165
327
  `);
166
328
  }
167
329
  async function main() {
168
- const args = process.argv.slice(2);
330
+ let args = process.argv.slice(2);
331
+ // Keep the old command spelling as an input alias, but route and describe
332
+ // capabilities through Host from here on.
333
+ if (args[0] === 'supply' && args[1] === 'listings') {
334
+ args = ['host', 'capabilities', ...args.slice(2)];
335
+ }
336
+ if (args[0] === 'agents')
337
+ args[0] = 'ais';
338
+ if (args[0] === 'agent')
339
+ args[0] = 'ai';
340
+ args = args.map((value) => (value === '--agent' ? '--ai' : value === '--exclude-agent' ? '--exclude-ai' : value));
341
+ if (args[0] === 'applications' && args[1] === 'webhooks') {
342
+ const operation = args[2] === 'create'
343
+ ? 'create-webhook'
344
+ : args[2] === 'revoke'
345
+ ? 'revoke-webhook'
346
+ : args[2] === 'deliveries'
347
+ ? 'webhook-deliveries'
348
+ : args[2] === 'retry'
349
+ ? 'retry-webhook'
350
+ : 'webhooks';
351
+ args = ['applications', operation, ...args.slice(3)];
352
+ }
169
353
  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
170
354
  help();
171
355
  return;
@@ -186,6 +370,9 @@ async function main() {
186
370
  await logout();
187
371
  return;
188
372
  }
373
+ if (args[0] === 'account' && args[1] === 'show') {
374
+ args = ['account'];
375
+ }
189
376
  const auth = await credentials();
190
377
  if (!auth.apiKey) {
191
378
  throw new Error('No API key found. Run darwin configure --api-key <key> or set DARWIN_API_KEY.');
@@ -193,94 +380,941 @@ async function main() {
193
380
  const resource = args[0];
194
381
  const operation = args[1];
195
382
  let result;
196
- if (resource === 'agent' && operation === 'message') {
197
- const requestIdIndex = args.indexOf('--request-id');
198
- const contentEnd = requestIdIndex === -1 ? args.length : requestIdIndex;
199
- const content = args.slice(2, contentEnd).join(' ').trim();
383
+ if (resource === 'account') {
384
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/account');
385
+ }
386
+ else if (resource === 'ais' && operation === 'skills') {
387
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/host/skills');
388
+ }
389
+ else if (resource === 'ais' && operation === 'integrations') {
390
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/host/integrations');
391
+ }
392
+ else if (resource === 'requests' && operation === 'list') {
393
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/requests', {
394
+ query: {
395
+ ...aiQuery(args),
396
+ limit: integerOption(args, '--limit'),
397
+ },
398
+ });
399
+ }
400
+ else if (resource === 'requests' && operation === 'action') {
401
+ const action = args[3]?.toUpperCase();
402
+ if (action !== 'ACCEPT' && action !== 'DECLINE') {
403
+ throw new Error('Request action must be accept or decline.');
404
+ }
405
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/requests/${id(args[2], 'Pass a request ID after "darwin requests action".')}/${action.toLowerCase()}`, {
406
+ method: 'POST',
407
+ body: {
408
+ ...(option(args, '--ai') ? { aiId: option(args, '--ai') } : {}),
409
+ },
410
+ headers: idempotencyHeaders(args),
411
+ });
412
+ }
413
+ else if ((resource === 'ai' && operation === 'message') || (resource === 'conversations' && operation === 'send')) {
414
+ const content = textArgument(args, 2, ['--ai', '--request-id']);
200
415
  if (!content) {
201
- throw new Error('Pass a message after "darwin agent message".');
416
+ throw new Error('Pass a message after "darwin conversations send".');
202
417
  }
203
- result = await request(auth.apiKey, auth.baseUrl, '/agent/messages', {
418
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/ai/messages', {
204
419
  method: 'POST',
205
420
  body: {
206
421
  content,
422
+ aiId: option(args, '--ai'),
207
423
  requestId: option(args, '--request-id'),
208
424
  },
209
425
  });
210
426
  }
211
427
  else if (resource === 'conversation') {
212
- result = await request(auth.apiKey, auth.baseUrl, '/agent/conversation', {
428
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/ai/conversation', {
213
429
  query: {
430
+ ...aiQuery(args),
214
431
  limit: integerOption(args, '--limit'),
215
432
  cursor: option(args, '--cursor'),
216
433
  },
217
434
  });
218
435
  }
219
- else if (resource === 'goals' && operation === 'list') {
220
- result = await request(auth.apiKey, auth.baseUrl, '/goals');
436
+ else if (resource === 'ais' && operation === 'list') {
437
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/host/ais');
221
438
  }
222
- else if (resource === 'goals' && operation === 'get') {
223
- const goalId = args[2]?.trim();
224
- if (!goalId) {
225
- throw new Error('Pass a goal ID after "darwin goals get".');
439
+ else if (resource === 'ais' && operation === 'get') {
440
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[2], 'Pass an AI ID after "darwin ais get".')}`);
441
+ }
442
+ else if (resource === 'ais' && operation === 'create') {
443
+ const body = mergeFields(dataOption(args), [
444
+ ['name', option(args, '--name')],
445
+ ['handle', option(args, '--handle')],
446
+ ['avatarUrl', option(args, '--avatar-url')],
447
+ ['description', option(args, '--description')],
448
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
449
+ ]);
450
+ if (typeof body.name !== 'string' || !body.name.trim()) {
451
+ throw new Error('Pass an AI name with --name or in --data.');
226
452
  }
227
- result = await request(auth.apiKey, auth.baseUrl, `/goals/${encodeURIComponent(goalId)}`);
453
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/host/ais', { method: 'POST', body });
228
454
  }
229
- else if (resource === 'goals' && operation === 'create') {
230
- const intent = option(args, '--intent');
231
- if (!intent?.trim()) {
232
- throw new Error('Pass a goal intent with --intent.');
455
+ else if (resource === 'ais' && operation === 'update') {
456
+ const body = mergeFields(dataOption(args), [
457
+ ['name', option(args, '--name')],
458
+ ['handle', option(args, '--handle')],
459
+ ['avatarUrl', option(args, '--avatar-url')],
460
+ ['description', option(args, '--description')],
461
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
462
+ ]);
463
+ if (Object.keys(body).length === 0) {
464
+ throw new Error('Pass at least one editable field or --data.');
233
465
  }
234
- result = await request(auth.apiKey, auth.baseUrl, '/goals', {
466
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[2], 'Pass an AI ID after "darwin ais update".')}`, { method: 'PATCH', body });
467
+ }
468
+ else if (resource === 'ais' && operation === 'activity') {
469
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[2], 'Pass an AI ID after "darwin ais activity".')}/activity`, {
470
+ query: {
471
+ limit: integerOption(args, '--limit'),
472
+ cursor: option(args, '--cursor'),
473
+ },
474
+ });
475
+ }
476
+ else if (resource === 'ais' && operation === 'members') {
477
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[2], 'Pass an AI ID after "darwin ais members".')}/members`);
478
+ }
479
+ else if (resource === 'ais' && operation === 'invite') {
480
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[2], 'Pass an AI ID after "darwin ais invite".')}/invitations`, {
235
481
  method: 'POST',
236
482
  body: {
237
- intent,
238
- title: option(args, '--title'),
239
- kind: option(args, '--kind')?.toUpperCase(),
483
+ email: requiredOption(args, '--email'),
484
+ role: option(args, '--role'),
240
485
  },
241
486
  });
242
487
  }
243
- else if (resource === 'approvals' && operation === 'list') {
244
- result = await request(auth.apiKey, auth.baseUrl, '/approvals', {
245
- query: { status: option(args, '--status') },
488
+ else if (resource === 'ais' && operation === 'invitations') {
489
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[2], 'Pass an AI ID after "darwin ais invitations".')}/invitations`);
490
+ }
491
+ else if (resource === 'ais' && operation === 'revoke-invitation') {
492
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[2], 'Pass an AI ID.')}/invitations/${id(args[3], 'Pass an invitation ID.')}`, { method: 'DELETE' });
493
+ }
494
+ else if (resource === 'ais' && operation === 'update-member') {
495
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[2], 'Pass an AI ID.')}/members/${id(args[3], 'Pass a membership ID.')}`, {
496
+ method: 'PATCH',
497
+ body: { role: requiredOption(args, '--role') },
246
498
  });
247
499
  }
248
- else if (resource === 'approvals' && operation === 'decide') {
249
- const decision = args[3]?.toUpperCase();
250
- if (decision !== 'APPROVE' && decision !== 'REJECT') {
251
- throw new Error('Decision must be approve or reject.');
500
+ else if (resource === 'ais' && operation === 'remove-member') {
501
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[2], 'Pass an AI ID.')}/members/${id(args[3], 'Pass a membership ID.')}`, { method: 'DELETE' });
502
+ }
503
+ else if (resource === 'ais' && operation === 'policies') {
504
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[2], 'Pass an AI ID after "darwin ais policies".')}/access-policies`);
505
+ }
506
+ else if (resource === 'ais' && operation === 'create-policy') {
507
+ const body = mergeFields(dataOption(args), [
508
+ ['name', option(args, '--name')],
509
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
510
+ ['naturalLanguage', option(args, '--natural-language')],
511
+ ]);
512
+ if (typeof body.name !== 'string' || typeof body.visibility !== 'string') {
513
+ throw new Error('Pass --name and --visibility, or provide both in --data.');
252
514
  }
253
- const approvalId = args[2]?.trim();
254
- if (!approvalId) {
255
- throw new Error('Pass the approval ID before the decision.');
515
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[2], 'Pass an AI ID.')}/access-policies`, {
516
+ method: 'POST',
517
+ body,
518
+ });
519
+ }
520
+ else if (resource === 'ais' && operation === 'update-policy') {
521
+ const body = mergeFields(dataOption(args), [
522
+ ['name', option(args, '--name')],
523
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
524
+ ['naturalLanguage', option(args, '--natural-language')],
525
+ ]);
526
+ if (Object.keys(body).length === 0)
527
+ throw new Error('Pass editable policy fields or --data.');
528
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[2], 'Pass an AI ID.')}/access-policies/${id(args[3], 'Pass a policy ID.')}`, { method: 'PATCH', body });
529
+ }
530
+ else if (resource === 'ais' && operation === 'conversations') {
531
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/ais/${id(args[2], 'Pass an AI ID after "darwin ais conversations".')}/conversations`);
532
+ }
533
+ else if (resource === 'ais' && operation === 'create-conversation') {
534
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/ais/${id(args[2], 'Pass an AI ID after "darwin ais create-conversation".')}/conversations`, { method: 'POST' });
535
+ }
536
+ else if (resource === 'conversations' && operation === 'get') {
537
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/conversations/${id(args[2], 'Pass a conversation ID after "darwin conversations get".')}`, {
538
+ query: {
539
+ limit: integerOption(args, '--limit'),
540
+ cursor: option(args, '--cursor'),
541
+ },
542
+ });
543
+ }
544
+ else if (resource === 'conversations' && operation === 'message') {
545
+ const content = textArgument(args, 3, ['--request-id']);
546
+ if (!content)
547
+ throw new Error('Pass message text after the conversation ID.');
548
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/conversations/${id(args[2], 'Pass a conversation ID.')}/messages`, {
549
+ method: 'POST',
550
+ body: { content, requestId: option(args, '--request-id') },
551
+ });
552
+ }
553
+ else if (resource === 'conversations' && (operation === 'list' || operation === 'create')) {
554
+ const path = `/act/ais/${id(args[2], `Pass an AI ID after "darwin conversations ${operation}".`)}/conversations`;
555
+ result = await request(auth.apiKey, auth.v2BaseUrl, path, {
556
+ method: operation === 'create' ? 'POST' : 'GET',
557
+ });
558
+ }
559
+ else if ((resource === 'tasks' || resource === 'goals') && operation === 'list') {
560
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/goals', {
561
+ query: {
562
+ ...aiQuery(args),
563
+ mode: enumOption(args, '--mode', ['BUY', 'SELL', 'CHAT']),
564
+ },
565
+ });
566
+ }
567
+ else if ((resource === 'tasks' || resource === 'goals') && operation === 'get') {
568
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/goals/${id(args[2], `Pass a goal ID after "darwin ${resource} get".`)}`, { query: aiQuery(args) });
569
+ }
570
+ else if ((resource === 'tasks' || resource === 'goals') && operation === 'create') {
571
+ const body = mergeFields(dataOption(args), [
572
+ ['aiId', option(args, '--ai')],
573
+ ['intent', option(args, '--intent')],
574
+ ['title', option(args, '--title')],
575
+ ['mode', enumOption(args, '--mode', ['BUY', 'SELL', 'CHAT'])],
576
+ ['type', enumOption(args, '--type', ['DEMAND', 'SUPPLY', 'CHAT'])],
577
+ ['lifecycleStatus', enumOption(args, '--status', ['DRAFT', 'ACTIVE', 'PAUSED', 'COMPLETED', 'ARCHIVED'])],
578
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
579
+ ]);
580
+ if (typeof body.intent !== 'string' || !body.intent.trim()) {
581
+ throw new Error('Pass a goal intent with --intent or in --data.');
582
+ }
583
+ if (option(args, '--kind') || body.kind !== undefined) {
584
+ throw new Error('Goal kind is not supported by v2. Use --mode BUY, SELL, or CHAT.');
256
585
  }
257
- result = await request(auth.apiKey, auth.baseUrl, `/approvals/${encodeURIComponent(approvalId)}/decisions`, {
586
+ if (body.type !== undefined) {
587
+ const type = String(body.type).toUpperCase();
588
+ if (!['DEMAND', 'SUPPLY', 'CHAT'].includes(type))
589
+ throw new Error('Goal type must be DEMAND, SUPPLY, or CHAT.');
590
+ const mode = type === 'DEMAND' ? 'BUY' : type === 'SUPPLY' ? 'SELL' : 'CHAT';
591
+ if (body.mode !== undefined && body.mode !== mode)
592
+ throw new Error('Goal --type and --mode disagree.');
593
+ body.mode = mode;
594
+ delete body.type;
595
+ }
596
+ if (body.agentId !== undefined) {
597
+ if (body.aiId !== undefined && body.aiId !== body.agentId)
598
+ throw new Error('Goal AI IDs disagree.');
599
+ body.aiId = body.agentId;
600
+ delete body.agentId;
601
+ }
602
+ if (body.listingIds !== undefined) {
603
+ if (body.capabilityIds !== undefined)
604
+ throw new Error('Pass capabilityIds only once.');
605
+ body.capabilityIds = body.listingIds;
606
+ delete body.listingIds;
607
+ }
608
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/goals', {
609
+ method: 'POST',
610
+ body,
611
+ });
612
+ }
613
+ else if ((resource === 'tasks' || resource === 'goals') && operation === 'update') {
614
+ const body = mergeFields(dataOption(args), [
615
+ ['intent', option(args, '--intent')],
616
+ ['title', option(args, '--title')],
617
+ ['mode', enumOption(args, '--mode', ['BUY', 'SELL', 'CHAT'])],
618
+ ['lifecycleStatus', enumOption(args, '--status', ['DRAFT', 'ACTIVE', 'PAUSED', 'COMPLETED', 'ARCHIVED'])],
619
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
620
+ ['pausedUntil', option(args, '--paused-until')],
621
+ ]);
622
+ if (args.includes('--type') || body.type !== undefined) {
623
+ const type = String(option(args, '--type') ?? body.type).toUpperCase();
624
+ if (!['DEMAND', 'SUPPLY', 'CHAT'].includes(type))
625
+ throw new Error('Goal type must be DEMAND, SUPPLY, or CHAT.');
626
+ const mode = type === 'DEMAND' ? 'BUY' : type === 'SUPPLY' ? 'SELL' : 'CHAT';
627
+ if (body.mode !== undefined && body.mode !== mode)
628
+ throw new Error('Goal --type and --mode disagree.');
629
+ body.mode = mode;
630
+ delete body.type;
631
+ }
632
+ if (Object.keys(body).length === 0)
633
+ throw new Error('Pass editable goal fields or --data.');
634
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/goals/${id(args[2], `Pass a goal ID after "darwin ${resource} update".`)}`, { method: 'PATCH', body });
635
+ }
636
+ else if ((resource === 'tasks' || resource === 'goals') && operation === 'action') {
637
+ const action = args[3]?.toLowerCase();
638
+ if (!action || !['pause', 'resume', 'complete', 'archive'].includes(action)) {
639
+ throw new Error('Action must be pause, resume, complete, or archive.');
640
+ }
641
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/goals/${id(args[2], `Pass a goal ID after "darwin ${resource} action".`)}/${action}`, {
642
+ method: 'POST',
643
+ ...(action === 'pause' && option(args, '--paused-until')
644
+ ? { body: { pausedUntil: option(args, '--paused-until') } }
645
+ : {}),
646
+ });
647
+ }
648
+ else if ((resource === 'tasks' || resource === 'goals') && operation === 'publish') {
649
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/goals/${id(args[2], `Pass a goal ID after "darwin ${resource} publish".`)}/publication-requests`, { method: 'POST', body: dataOption(args), headers: idempotencyHeaders(args) });
650
+ }
651
+ else if (resource === 'deals' && operation === 'list') {
652
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/deals', { query: aiQuery(args) });
653
+ }
654
+ else if (resource === 'deals' && operation === 'get') {
655
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/deals/${id(args[2], 'Pass a deal ID after "darwin deals get".')}`);
656
+ }
657
+ else if (resource === 'deals' && operation === 'create') {
658
+ const body = mergeFields(dataOption(args), [
659
+ ['aiId', option(args, '--ai')],
660
+ ['mode', enumOption(args, '--mode', ['BUY', 'SELL'])],
661
+ ['direction', enumOption(args, '--direction', ['DEMAND', 'SUPPLY'])],
662
+ ['title', option(args, '--title')],
663
+ ['goalId', option(args, '--goal')],
664
+ ['taskId', option(args, '--task')],
665
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
666
+ ]);
667
+ if (body.mode === undefined && body.direction !== undefined) {
668
+ if (body.direction !== 'DEMAND' && body.direction !== 'SUPPLY') {
669
+ throw new Error('Deal direction must be DEMAND or SUPPLY.');
670
+ }
671
+ body.mode = body.direction === 'DEMAND' ? 'BUY' : 'SELL';
672
+ }
673
+ if (body.goalId === undefined && body.taskId !== undefined)
674
+ body.goalId = body.taskId;
675
+ delete body.direction;
676
+ delete body.taskId;
677
+ normalizeCapabilitySelections(body);
678
+ if (typeof body.mode !== 'string' || typeof body.title !== 'string') {
679
+ throw new Error('Pass --mode and --title, or provide both in --data. --direction remains a deprecated alias.');
680
+ }
681
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/deals', { method: 'POST', body });
682
+ }
683
+ else if (resource === 'deals' && operation === 'update') {
684
+ const body = mergeFields(dataOption(args), [
685
+ ['title', option(args, '--title')],
686
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
687
+ ]);
688
+ if (Object.keys(body).length === 0)
689
+ throw new Error('Pass editable deal fields or --data.');
690
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/deals/${id(args[2], 'Pass a deal ID after "darwin deals update".')}`, { method: 'PATCH', body });
691
+ }
692
+ else if (resource === 'deals' && operation === 'action') {
693
+ const action = args[3]?.toUpperCase();
694
+ if (!action || !['SEND', 'ACCEPT', 'REJECT', 'WITHDRAW'].includes(action)) {
695
+ throw new Error('Action must be send, accept, reject, or withdraw.');
696
+ }
697
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/deals/${id(args[2], 'Pass a deal ID after "darwin deals action".')}/${action.toLowerCase()}`, { method: 'POST' });
698
+ }
699
+ else if (resource === 'deals' && operation === 'payments') {
700
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/deals/${id(args[2], 'Pass a deal ID after "darwin deals payments".')}/payments`);
701
+ }
702
+ else if (resource === 'transactions' && operation === 'list') {
703
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/transactions', { query: aiQuery(args) });
704
+ }
705
+ else if (resource === 'transactions' && operation === 'get') {
706
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/transactions/${id(args[2], 'Pass a transaction ID after "darwin transactions get".')}`, { query: aiQuery(args) });
707
+ }
708
+ else if (resource === 'transactions' && operation === 'action') {
709
+ const action = args[3]?.toUpperCase();
710
+ if (!action || !['CANCEL', 'REQUEST_REFUND'].includes(action)) {
711
+ throw new Error('Transaction action must be cancel or request_refund.');
712
+ }
713
+ const body = mergeFields(dataOption(args), [
714
+ ['aiId', option(args, '--ai')],
715
+ ['amountMinor', integerOption(args, '--amount-minor')],
716
+ ]);
717
+ delete body.action;
718
+ if (typeof body.aiId !== 'string' || !body.aiId.trim()) {
719
+ throw new Error('Pass the transaction AI ID with --ai or aiId in --data.');
720
+ }
721
+ if (action === 'CANCEL')
722
+ delete body.amountMinor;
723
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/transactions/${id(args[2], 'Pass a transaction ID after "darwin transactions action".')}/${action === 'CANCEL' ? 'cancel' : 'refunds'}`, { method: 'POST', body, headers: idempotencyHeaders(args) });
724
+ }
725
+ else if (resource === 'outcomes' && operation === 'list') {
726
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/outcomes', { query: aiQuery(args) });
727
+ }
728
+ else if (resource === 'outcomes' && operation === 'get') {
729
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/outcomes/${id(args[2], 'Pass an outcome ID after "darwin outcomes get".')}`);
730
+ }
731
+ else if (resource === 'outcomes' && operation === 'evidence') {
732
+ const body = mergeFields(dataOption(args), [
733
+ ['evidenceDigest', option(args, '--digest')],
734
+ ['signedReference', option(args, '--signed-reference')],
735
+ ['dealId', option(args, '--deal')],
736
+ ]);
737
+ if (typeof body.evidenceDigest !== 'string')
738
+ throw new Error('Pass --digest or evidenceDigest in --data.');
739
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/outcomes/${id(args[2], 'Pass an outcome ID after "darwin outcomes evidence".')}/evidence`, { method: 'POST', body, headers: idempotencyHeaders(args) });
740
+ }
741
+ else if (resource === 'sessions' && operation === 'list') {
742
+ result = await request(auth.apiKey, auth.baseUrl, '/sessions', {
743
+ query: {
744
+ aiId: option(args, '--ai'),
745
+ status: lowerEnumOption(args, '--status', [
746
+ 'pending_provider',
747
+ 'planning',
748
+ 'active',
749
+ 'completed',
750
+ 'canceled',
751
+ 'failed',
752
+ ]),
753
+ limit: integerOption(args, '--limit'),
754
+ cursor: option(args, '--cursor'),
755
+ },
756
+ });
757
+ }
758
+ else if (resource === 'sessions' && operation === 'invitations') {
759
+ result = await request(auth.apiKey, auth.baseUrl, '/session-invitations', {
760
+ query: {
761
+ aiId: option(args, '--ai'),
762
+ limit: integerOption(args, '--limit'),
763
+ },
764
+ });
765
+ }
766
+ else if (resource === 'sessions' && operation === 'invitation') {
767
+ const action = args[3]?.toLowerCase();
768
+ if (action !== 'accept' && action !== 'decline') {
769
+ throw new Error('Session invitation action must be accept or decline.');
770
+ }
771
+ result = await request(auth.apiKey, auth.baseUrl, `/session-invitations/${id(args[2], 'Pass an invitation ID after "darwin sessions invitation".')}/actions`, {
258
772
  method: 'POST',
259
773
  body: {
260
- decision,
261
- reason: option(args, '--reason'),
774
+ action,
775
+ ...(option(args, '--ai') ? { aiId: option(args, '--ai') } : {}),
776
+ },
777
+ headers: idempotencyHeaders(args),
778
+ });
779
+ }
780
+ else if (resource === 'sessions' && operation === 'get') {
781
+ result = await request(auth.apiKey, auth.baseUrl, `/sessions/${id(args[2], 'Pass a session ID after "darwin sessions get".')}`);
782
+ }
783
+ else if (resource === 'sessions' && operation === 'participants') {
784
+ const sessionResult = await request(auth.apiKey, auth.baseUrl, `/sessions/${id(args[2], 'Pass a session ID after "darwin sessions participants".')}`);
785
+ const session = sessionResult &&
786
+ typeof sessionResult === 'object' &&
787
+ 'session' in sessionResult &&
788
+ sessionResult.session &&
789
+ typeof sessionResult.session === 'object'
790
+ ? sessionResult.session
791
+ : undefined;
792
+ result = {
793
+ participants: Array.isArray(session?.participants) ? session.participants : [],
794
+ };
795
+ }
796
+ else if (resource === 'sessions' && operation === 'create') {
797
+ const body = mergeFields(dataOption(args), [
798
+ ['aiId', option(args, '--ai')],
799
+ ['kind', lowerEnumOption(args, '--kind', ['direct', 'discovery', 'buy', 'sell', 'coordination'])],
800
+ ['goalId', option(args, '--goal')],
801
+ ['conversationId', option(args, '--conversation')],
802
+ ['discoveryMode', lowerEnumOption(args, '--discovery-mode', ['none', 'if_needed', 'always'])],
803
+ ['activationMode', lowerEnumOption(args, '--activation-mode', ['immediate', 'plan_only'])],
804
+ ]);
805
+ const intentDescription = option(args, '--intent');
806
+ const intentTitle = option(args, '--title');
807
+ if (intentDescription || intentTitle) {
808
+ const existingIntent = body.intent && typeof body.intent === 'object' && !Array.isArray(body.intent)
809
+ ? body.intent
810
+ : {};
811
+ const description = intentDescription ?? (typeof existingIntent.description === 'string' ? existingIntent.description : undefined);
812
+ if (!description) {
813
+ throw new Error('--title requires an intent description from --intent or --data.');
814
+ }
815
+ body.intent = {
816
+ ...existingIntent,
817
+ title: intentTitle ?? (typeof existingIntent.title === 'string' ? existingIntent.title : description.slice(0, 120)),
818
+ description,
819
+ };
820
+ }
821
+ const targetAIIds = repeatedOption(args, '--target');
822
+ if (targetAIIds.length > 0)
823
+ body.targetAIIds = targetAIIds;
824
+ const descriptor = option(args, '--discovery-descriptor');
825
+ if (descriptor !== undefined) {
826
+ body.discoveryDescriptor = jsonObjectOption(args, '--discovery-descriptor');
827
+ }
828
+ const existingDataPolicy = body.dataPolicy && typeof body.dataPolicy === 'object' && !Array.isArray(body.dataPolicy)
829
+ ? body.dataPolicy
830
+ : {};
831
+ const dataPolicy = mergeFields({ ...existingDataPolicy }, [
832
+ ['contentMode', lowerEnumOption(args, '--content-mode', ['managed', 'sealed'])],
833
+ [
834
+ 'learningMode',
835
+ lowerEnumOption(args, '--learning-mode', [
836
+ 'none',
837
+ 'outcomes_only',
838
+ 'derived_and_outcomes',
839
+ 'content_and_outcomes',
840
+ ]),
841
+ ],
842
+ ['keyManagement', lowerEnumOption(args, '--key-management', ['darwin_managed', 'tenant_managed'])],
843
+ ]);
844
+ if (Object.keys(dataPolicy).length > 0)
845
+ body.dataPolicy = dataPolicy;
846
+ const effectiveContentMode = typeof dataPolicy.contentMode === 'string' ? dataPolicy.contentMode.toLowerCase() : 'sealed';
847
+ if (effectiveContentMode === 'sealed' && body.intent !== undefined) {
848
+ throw new Error('Sealed sessions cannot include plaintext intent. Remove --intent/intent and use --discovery-descriptor only when discovery is needed.');
849
+ }
850
+ if (effectiveContentMode === 'managed' && body.discoveryDescriptor !== undefined && body.intent === undefined) {
851
+ throw new Error('Managed session discovery descriptors require a structured intent.');
852
+ }
853
+ if (typeof body.kind !== 'string') {
854
+ throw new Error('Pass a session kind with --kind or in --data.');
855
+ }
856
+ result = await request(auth.apiKey, auth.baseUrl, '/sessions', {
857
+ method: 'POST',
858
+ body,
859
+ headers: idempotencyHeaders(args),
860
+ });
861
+ }
862
+ else if (resource === 'sessions' && operation === 'send') {
863
+ const content = textArgument(args, 3, [
864
+ '--kind',
865
+ '--context-scope',
866
+ '--idempotency-key',
867
+ '--data',
868
+ '--protected-content',
869
+ '--protected-content-file',
870
+ '--sealed-content',
871
+ '--sealed-content-file',
872
+ ]);
873
+ const body = mergeFields(dataOption(args), [
874
+ [
875
+ 'kind',
876
+ lowerEnumOption(args, '--kind', [
877
+ 'message',
878
+ 'question',
879
+ 'clarification',
880
+ 'proposal',
881
+ 'counterproposal',
882
+ 'notice',
883
+ 'result',
884
+ 'artifact',
885
+ ]),
886
+ ],
887
+ ['content', content || undefined],
888
+ ['contextScopeId', option(args, '--context-scope')],
889
+ ]);
890
+ if (args.includes('--protected-content') && args.includes('--sealed-content')) {
891
+ throw new Error('Pass only one of --sealed-content or --protected-content.');
892
+ }
893
+ if (args.includes('--protected-content-file') && args.includes('--sealed-content-file')) {
894
+ throw new Error('Pass only one of --sealed-content-file or --protected-content-file.');
895
+ }
896
+ const protectedContentOption = args.includes('--protected-content') ? '--protected-content' : '--sealed-content';
897
+ if (args.includes(protectedContentOption)) {
898
+ body.protectedContent = jsonObjectOption(args, protectedContentOption);
899
+ }
900
+ const protectedContentFileOption = args.includes('--protected-content-file')
901
+ ? '--protected-content-file'
902
+ : '--sealed-content-file';
903
+ const protectedContentFile = await jsonObjectFileOption(args, protectedContentFileOption);
904
+ if (protectedContentFile) {
905
+ if (body.protectedContent) {
906
+ throw new Error('Pass only one sealed-content value or file.');
907
+ }
908
+ body.protectedContent = protectedContentFile;
909
+ }
910
+ if (!body.kind)
911
+ body.kind = 'message';
912
+ const hasContent = typeof body.content === 'string' && body.content.trim().length > 0;
913
+ const hasSealedContent = !!body.protectedContent && typeof body.protectedContent === 'object' && !Array.isArray(body.protectedContent);
914
+ if ((!hasContent && !hasSealedContent) || (hasContent && hasSealedContent)) {
915
+ throw new Error('Pass either message text or sealed content, but not both.');
916
+ }
917
+ result = await request(auth.apiKey, auth.baseUrl, `/sessions/${id(args[2], 'Pass a session ID after "darwin sessions send".')}/interactions`, { method: 'POST', body, headers: idempotencyHeaders(args) });
918
+ }
919
+ else if (resource === 'sessions' && operation === 'watch') {
920
+ result = await request(auth.apiKey, auth.baseUrl, `/sessions/${id(args[2], 'Pass a session ID after "darwin sessions watch".')}/interactions`, {
921
+ query: {
922
+ limit: integerOption(args, '--limit'),
923
+ cursor: option(args, '--cursor'),
924
+ contextScopeId: option(args, '--context-scope'),
925
+ },
926
+ });
927
+ }
928
+ else if (resource === 'sessions' && operation === 'mesh') {
929
+ result = await request(auth.apiKey, auth.baseUrl, `/sessions/${id(args[2], 'Pass a session ID after "darwin sessions mesh".')}/mesh`);
930
+ }
931
+ else if (resource === 'sessions' && operation === 'replan') {
932
+ const body = dataOption(args);
933
+ const limit = integerOption(args, '--limit');
934
+ if (limit !== undefined)
935
+ body.limit = limit;
936
+ const excludeAIIds = repeatedOption(args, '--exclude-ai');
937
+ if (excludeAIIds.length > 0)
938
+ body.excludeAIIds = excludeAIIds;
939
+ result = await request(auth.apiKey, auth.baseUrl, `/sessions/${id(args[2], 'Pass a session ID after "darwin sessions replan".')}/resolutions`, {
940
+ method: 'POST',
941
+ body,
942
+ headers: idempotencyHeaders(args),
943
+ });
944
+ }
945
+ else if (resource === 'sessions' && ['complete', 'cancel'].includes(operation ?? '')) {
946
+ result = await request(auth.apiKey, auth.baseUrl, `/sessions/${id(args[2], `Pass a session ID after "darwin sessions ${operation}".`)}/actions`, {
947
+ method: 'POST',
948
+ body: {
949
+ ...dataOption(args),
950
+ action: operation?.toUpperCase(),
951
+ },
952
+ headers: idempotencyHeaders(args),
953
+ });
954
+ }
955
+ else if (resource === 'sessions' && operation === 'outcome') {
956
+ const body = mergeFields(dataOption(args), [
957
+ ['completionState', lowerEnumOption(args, '--completion-state', ['completed', 'partial', 'failed', 'cancelled'])],
958
+ [
959
+ 'durationBand',
960
+ lowerEnumOption(args, '--duration-band', ['seconds', 'minutes', 'hours', 'days', 'weeks', 'months']),
961
+ ],
962
+ ['disputeState', lowerEnumOption(args, '--dispute-state', ['none', 'open', 'resolved'])],
963
+ ['refundState', lowerEnumOption(args, '--refund-state', ['none', 'partial', 'full'])],
964
+ [
965
+ 'evaluatorType',
966
+ lowerEnumOption(args, '--evaluator-type', ['requester', 'provider', 'platform', 'third_party']),
967
+ ],
968
+ ]);
969
+ const confidence = option(args, '--confidence');
970
+ if (confidence !== undefined) {
971
+ const parsed = Number(confidence);
972
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
973
+ throw new Error('--confidence must be a number from 0 to 1.');
974
+ }
975
+ body.confidence = parsed;
976
+ }
977
+ const success = booleanOption(args, '--success');
978
+ if (success !== undefined)
979
+ body.success = success;
980
+ if (args.includes('--scores'))
981
+ body.scores = jsonObjectOption(args, '--scores');
982
+ if (args.includes('--signals'))
983
+ body.signals = jsonArrayOption(args, '--signals');
984
+ if (args.includes('--cost-band'))
985
+ body.costBand = jsonObjectOption(args, '--cost-band');
986
+ const criterionResultsOption = args.includes('--criterion-results')
987
+ ? '--criterion-results'
988
+ : '--success-criteria-results';
989
+ if (args.includes(criterionResultsOption)) {
990
+ body.criterionResults = jsonArrayOption(args, criterionResultsOption);
991
+ }
992
+ const decisionIds = repeatedOption(args, '--decision-id');
993
+ if (decisionIds.length > 0)
994
+ body.decisionIds = decisionIds;
995
+ const evidenceDigests = repeatedOption(args, '--evidence-digest');
996
+ if (evidenceDigests.some((value) => !/^[a-f0-9]{64}$/i.test(value))) {
997
+ throw new Error('--evidence-digest must be a 64-character SHA-256 hex digest.');
998
+ }
999
+ if (evidenceDigests.length > 0)
1000
+ body.evidenceDigests = evidenceDigests;
1001
+ if (typeof body.completionState !== 'string') {
1002
+ throw new Error('Pass --completion-state or include completionState in --data.');
1003
+ }
1004
+ result = await request(auth.apiKey, auth.baseUrl, `/sessions/${id(args[2], 'Pass a session ID after "darwin sessions outcome".')}/outcomes`, { method: 'POST', body, headers: idempotencyHeaders(args) });
1005
+ }
1006
+ else if (resource === 'sessions' && operation === 'feedback') {
1007
+ const content = textArgument(args, 3, [
1008
+ '--rating',
1009
+ '--outcome',
1010
+ '--context-scope',
1011
+ '--idempotency-key',
1012
+ '--data',
1013
+ '--protected-content',
1014
+ '--protected-content-file',
1015
+ '--sealed-content',
1016
+ '--sealed-content-file',
1017
+ ]);
1018
+ const body = mergeFields(dataOption(args), [
1019
+ ['outcomeId', option(args, '--outcome')],
1020
+ ['contextScopeId', option(args, '--context-scope')],
1021
+ ['content', content || undefined],
1022
+ ]);
1023
+ const rating = integerOption(args, '--rating');
1024
+ if (rating !== undefined) {
1025
+ if (rating > 5)
1026
+ throw new Error('--rating must be an integer from 1 to 5.');
1027
+ body.rating = rating;
1028
+ }
1029
+ if (args.includes('--protected-content') && args.includes('--sealed-content')) {
1030
+ throw new Error('Pass only one of --sealed-content or --protected-content.');
1031
+ }
1032
+ if (args.includes('--protected-content-file') && args.includes('--sealed-content-file')) {
1033
+ throw new Error('Pass only one of --sealed-content-file or --protected-content-file.');
1034
+ }
1035
+ const protectedContentOption = args.includes('--protected-content') ? '--protected-content' : '--sealed-content';
1036
+ if (args.includes(protectedContentOption)) {
1037
+ body.protectedContent = jsonObjectOption(args, protectedContentOption);
1038
+ }
1039
+ const protectedContentFileOption = args.includes('--protected-content-file')
1040
+ ? '--protected-content-file'
1041
+ : '--sealed-content-file';
1042
+ const protectedContentFile = await jsonObjectFileOption(args, protectedContentFileOption);
1043
+ if (protectedContentFile) {
1044
+ if (body.protectedContent)
1045
+ throw new Error('Pass only one sealed-content value or file.');
1046
+ body.protectedContent = protectedContentFile;
1047
+ }
1048
+ const hasContent = typeof body.content === 'string' && body.content.trim().length > 0;
1049
+ const hasProtectedContent = !!body.protectedContent && typeof body.protectedContent === 'object' && !Array.isArray(body.protectedContent);
1050
+ if (hasContent && hasProtectedContent) {
1051
+ throw new Error('Pass plaintext feedback or protected content, not both.');
1052
+ }
1053
+ if (body.rating === undefined && !hasContent && !hasProtectedContent) {
1054
+ throw new Error('Pass a rating, feedback text, or protected content.');
1055
+ }
1056
+ result = await request(auth.apiKey, auth.baseUrl, `/sessions/${id(args[2], 'Pass a session ID after "darwin sessions feedback".')}/feedback`, { method: 'POST', body, headers: idempotencyHeaders(args) });
1057
+ }
1058
+ else if (resource === 'directory' && operation === 'search') {
1059
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/search/directory/ais', {
1060
+ query: {
1061
+ query: textArgument(args, 2, ['--limit']) || undefined,
1062
+ limit: integerOption(args, '--limit'),
1063
+ },
1064
+ });
1065
+ }
1066
+ else if (resource === 'directory' && operation === 'get') {
1067
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/search/directory/ais/${id(args[2], 'Pass a directory AI ID.')}`);
1068
+ }
1069
+ else if (resource === 'offers' && operation === 'list') {
1070
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/deals', {
1071
+ query: { aiId: id(option(args, '--ai'), 'Pass an AI ID with --ai.') },
1072
+ });
1073
+ }
1074
+ else if (resource === 'offers' && operation === 'get') {
1075
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/deals/${id(args[2], 'Pass an offer ID after "darwin offers get".')}`);
1076
+ }
1077
+ else if (resource === 'offers' && operation === 'create') {
1078
+ const body = mergeFields(dataOption(args), [
1079
+ ['direction', enumOption(args, '--direction', ['DEMAND', 'SUPPLY'])],
1080
+ ['title', option(args, '--title')],
1081
+ ['goalId', option(args, '--goal')],
1082
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
1083
+ ]);
1084
+ if (typeof body.direction !== 'string' || typeof body.title !== 'string') {
1085
+ throw new Error('Pass --direction and --title, or provide both in --data.');
1086
+ }
1087
+ if (body.direction !== 'DEMAND' && body.direction !== 'SUPPLY') {
1088
+ throw new Error('Offer direction must be DEMAND or SUPPLY.');
1089
+ }
1090
+ body.mode = body.direction === 'DEMAND' ? 'BUY' : 'SELL';
1091
+ body.aiId = id(option(args, '--ai'), 'Pass an AI ID with --ai.');
1092
+ delete body.direction;
1093
+ normalizeCapabilitySelections(body);
1094
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/deals', { method: 'POST', body });
1095
+ }
1096
+ else if (resource === 'offers' && operation === 'update') {
1097
+ const body = mergeFields(dataOption(args), [
1098
+ ['title', option(args, '--title')],
1099
+ ['visibility', enumOption(args, '--visibility', ['PUBLIC', 'RESTRICTED', 'PRIVATE'])],
1100
+ ]);
1101
+ if (Object.keys(body).length === 0)
1102
+ throw new Error('Pass editable offer fields or --data.');
1103
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/deals/${id(args[2], 'Pass an offer ID after "darwin offers update".')}`, { method: 'PATCH', body });
1104
+ }
1105
+ else if (resource === 'offers' && operation === 'action') {
1106
+ const action = args[3]?.toUpperCase();
1107
+ if (!action || !['SEND', 'ACCEPT', 'REJECT', 'WITHDRAW'].includes(action)) {
1108
+ throw new Error('Action must be send, accept, reject, or withdraw.');
1109
+ }
1110
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/deals/${id(args[2], 'Pass an offer ID after "darwin offers action".')}/${action.toLowerCase()}`, { method: 'POST' });
1111
+ }
1112
+ else if (resource === 'payments' && operation === 'account') {
1113
+ result = await request(auth.apiKey, auth.baseUrl, `/ais/${id(option(args, '--ai'), 'Pass an AI ID with --ai.')}/payment-account`);
1114
+ }
1115
+ else if (resource === 'payments' && operation === 'list') {
1116
+ result = await request(auth.apiKey, auth.baseUrl, `/ais/${id(option(args, '--ai'), 'Pass an AI ID with --ai.')}/payments`);
1117
+ }
1118
+ else if (resource === 'payments' && operation === 'get') {
1119
+ result = await request(auth.apiKey, auth.baseUrl, `/payments/${id(args[2], 'Pass a payment ID after "darwin payments get".')}`);
1120
+ }
1121
+ else if (resource === 'fee-quotes' && operation === 'create') {
1122
+ const body = dataOption(args);
1123
+ if (Object.keys(body).length === 0)
1124
+ throw new Error('Pass fee quote inputs with --data.');
1125
+ result = await request(auth.apiKey, auth.baseUrl, '/fee-quotes', { method: 'POST', body });
1126
+ }
1127
+ else if (resource === 'fee-quotes' && operation === 'get') {
1128
+ result = await request(auth.apiKey, auth.baseUrl, `/fee-quotes/${id(args[2], 'Pass a fee quote ID.')}`);
1129
+ }
1130
+ else if (resource === 'fee-quotes' && operation === 'accept') {
1131
+ result = await request(auth.apiKey, auth.baseUrl, `/fee-quotes/${id(args[2], 'Pass a fee quote ID.')}/accept`, {
1132
+ method: 'POST',
1133
+ });
1134
+ }
1135
+ else if (resource === 'supply' && operation === 'businesses' && args[2] === 'list') {
1136
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/host/ais');
1137
+ }
1138
+ else if (resource === 'supply' && operation === 'businesses' && args[2] === 'get') {
1139
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[3], 'Pass a business ID.')}`);
1140
+ }
1141
+ else if (resource === 'supply' && operation === 'businesses' && args[2] === 'create') {
1142
+ const body = mergeFields({ type: 'business', ...dataOption(args) }, [
1143
+ ['name', option(args, '--name')],
1144
+ ['handle', option(args, '--handle')],
1145
+ ['description', option(args, '--description')],
1146
+ ]);
1147
+ if (typeof body.name !== 'string')
1148
+ throw new Error('Pass --name or include name in --data.');
1149
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/host/ais', { method: 'POST', body });
1150
+ }
1151
+ else if (resource === 'supply' && operation === 'businesses' && args[2] === 'update') {
1152
+ const body = mergeFields(dataOption(args), [
1153
+ ['name', option(args, '--name')],
1154
+ ['handle', option(args, '--handle')],
1155
+ ['description', option(args, '--description')],
1156
+ ]);
1157
+ if (Object.keys(body).length === 0)
1158
+ throw new Error('Pass editable business fields or --data.');
1159
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[3], 'Pass a business ID.')}`, {
1160
+ method: 'PATCH',
1161
+ body,
1162
+ });
1163
+ }
1164
+ else if (resource === 'host' && operation === 'capabilities' && args[2] === 'list') {
1165
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[3], 'Pass a business ID.')}/capabilities`, {
1166
+ query: {
1167
+ limit: integerOption(args, '--limit'),
1168
+ cursor: option(args, '--cursor'),
1169
+ q: option(args, '--query'),
1170
+ type: option(args, '--type'),
1171
+ status: option(args, '--status'),
262
1172
  },
263
1173
  });
264
1174
  }
265
- else if (resource === 'integrations') {
266
- result = await request(auth.apiKey, auth.baseUrl, '/integrations');
1175
+ else if (resource === 'host' && operation === 'capabilities' && args[2] === 'get') {
1176
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[3], 'Pass a business ID.')}/capabilities/${id(args[4], 'Pass a capability ID.')}`);
1177
+ }
1178
+ else if (resource === 'host' && operation === 'capabilities' && args[2] === 'create') {
1179
+ const body = dataOption(args);
1180
+ if (Object.keys(body).length === 0)
1181
+ throw new Error('Pass capability fields with --data.');
1182
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[3], 'Pass a business ID.')}/capabilities`, {
1183
+ method: 'POST',
1184
+ body,
1185
+ });
1186
+ }
1187
+ else if (resource === 'host' && operation === 'capabilities' && args[2] === 'update') {
1188
+ const body = dataOption(args);
1189
+ if (Object.keys(body).length === 0)
1190
+ throw new Error('Pass capability fields with --data.');
1191
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[3], 'Pass a business ID.')}/capabilities/${id(args[4], 'Pass a capability ID.')}`, { method: 'PATCH', body });
1192
+ }
1193
+ else if (resource === 'host' && operation === 'capabilities' && args[2] === 'archive') {
1194
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/host/ais/${id(args[3], 'Pass a business ID.')}/capabilities/${id(args[4], 'Pass a capability ID.')}`, { method: 'DELETE', query: { expectedRevision: integerOption(args, '--expected-revision') } });
1195
+ }
1196
+ else if (resource === 'supply' && operation === 'orders' && args[2] === 'list') {
1197
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/transactions', { query: { aiId: args[3] } });
1198
+ }
1199
+ else if (resource === 'supply' && operation === 'orders' && args[2] === 'get') {
1200
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/transactions/${id(args[4], 'Pass an order ID.')}`, {
1201
+ query: { aiId: args[3] },
1202
+ });
1203
+ }
1204
+ else if (resource === 'supply' && operation === 'earnings' && args[2] === 'get') {
1205
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/ais/${id(args[3], 'Pass a business ID.')}/billing`);
1206
+ }
1207
+ else if (resource === 'applications' && operation === 'list') {
1208
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/account/applications');
1209
+ }
1210
+ else if (resource === 'applications' && operation === 'get') {
1211
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/applications/${id(args[2], 'Pass an application ID.')}`);
1212
+ }
1213
+ else if (resource === 'applications' && operation === 'create') {
1214
+ const body = mergeFields(dataOption(args), [['name', option(args, '--name')]]);
1215
+ if (typeof body.name !== 'string')
1216
+ throw new Error('Pass --name or include name in --data.');
1217
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/account/applications', { method: 'POST', body });
1218
+ }
1219
+ else if (resource === 'applications' && operation === 'update') {
1220
+ const body = mergeFields(dataOption(args), [['name', option(args, '--name')]]);
1221
+ if (Object.keys(body).length === 0)
1222
+ throw new Error('Pass editable application fields or --data.');
1223
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/applications/${id(args[2], 'Pass an application ID.')}`, {
1224
+ method: 'PATCH',
1225
+ body,
1226
+ });
1227
+ }
1228
+ else if (resource === 'applications' && operation === 'archive') {
1229
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/applications/${id(args[2], 'Pass an application ID.')}`, {
1230
+ method: 'DELETE',
1231
+ });
1232
+ }
1233
+ else if (resource === 'applications' && operation === 'usage') {
1234
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/applications/${id(args[2], 'Pass an application ID.')}/usage`, { query: { days: integerOption(args, '--days') } });
1235
+ }
1236
+ else if (resource === 'applications' && operation === 'webhooks') {
1237
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/applications/${id(args[2], 'Pass an application ID.')}/webhooks`);
1238
+ }
1239
+ else if (resource === 'applications' && operation === 'create-webhook') {
1240
+ const body = mergeFields(dataOption(args), [['url', option(args, '--url')]]);
1241
+ if (typeof body.url !== 'string')
1242
+ throw new Error('Pass --url or include url in --data.');
1243
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/applications/${id(args[2], 'Pass an application ID.')}/webhooks`, { method: 'POST', body });
1244
+ }
1245
+ else if (resource === 'applications' && operation === 'revoke-webhook') {
1246
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/applications/${id(args[2], 'Pass an application ID.')}/webhooks/${id(args[3], 'Pass a webhook ID.')}`, { method: 'DELETE' });
1247
+ }
1248
+ else if (resource === 'applications' && operation === 'webhook-deliveries') {
1249
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/applications/${id(args[2], 'Pass an application ID.')}/webhooks/${id(args[3], 'Pass a webhook ID.')}/deliveries`, {
1250
+ query: {
1251
+ limit: integerOption(args, '--limit'),
1252
+ cursor: option(args, '--cursor'),
1253
+ },
1254
+ });
1255
+ }
1256
+ else if (resource === 'applications' && operation === 'retry-webhook') {
1257
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/account/applications/${id(args[2], 'Pass an application ID.')}/webhooks/${id(args[3], 'Pass a webhook ID.')}/deliveries/${id(args[4], 'Pass a delivery ID.')}/retry`, { method: 'POST' });
267
1258
  }
268
1259
  else if (resource === 'tools' && operation === 'list') {
269
- result = await request(auth.apiKey, auth.baseUrl, '/tools');
1260
+ result = await request(auth.apiKey, auth.v2BaseUrl, '/act/tools');
270
1261
  }
271
1262
  else if (resource === 'tools' && operation === 'execute') {
272
1263
  const tool = args[2]?.trim();
273
1264
  if (!tool) {
274
1265
  throw new Error('Pass a tool name after "darwin tools execute".');
275
1266
  }
276
- result = await request(auth.apiKey, auth.baseUrl, `/tools/${encodeURIComponent(tool)}/executions`, {
1267
+ result = await request(auth.apiKey, auth.v2BaseUrl, `/act/tools/${encodeURIComponent(tool)}/executions`, {
277
1268
  method: 'POST',
278
1269
  body: { input: jsonObjectOption(args, '--input') },
279
1270
  });
280
1271
  }
1272
+ else if (resource === 'api') {
1273
+ const method = operation?.toUpperCase();
1274
+ if (!method || !['GET', 'POST', 'PATCH', 'DELETE'].includes(method)) {
1275
+ throw new Error('API method must be GET, POST, PATCH, or DELETE.');
1276
+ }
1277
+ const path = args[2]?.trim();
1278
+ if (!path || path.includes('://') || !/^\/(?:search|act|host|account)(?:\/|$)/u.test(path)) {
1279
+ throw new Error('Pass a v2 API path under /search, /act, /host, or /account.');
1280
+ }
1281
+ const family = /^\/(search|act|host|account)(?:\/|$)/u.exec(path)?.[1];
1282
+ const v2Prefix = `${new URL(auth.v2BaseUrl).pathname.replace(/\/+$/u, '')}/${family}`;
1283
+ const resolved = new URL(`${auth.v2BaseUrl.replace(/\/+$/u, '')}/${path.replace(/^\/+/, '')}`);
1284
+ if (resolved.pathname !== v2Prefix && !resolved.pathname.startsWith(`${v2Prefix}/`)) {
1285
+ throw new Error('The API path must stay within the v2 endpoint.');
1286
+ }
1287
+ const body = dataOption(args);
1288
+ result = await request(auth.apiKey, auth.v2BaseUrl, path, {
1289
+ method: method,
1290
+ ...(Object.keys(body).length > 0 ? { body } : {}),
1291
+ });
1292
+ }
281
1293
  else {
282
1294
  throw new Error(`Unknown command: ${args.join(' ')}`);
283
1295
  }
1296
+ if (resource === 'tasks' && result && typeof result === 'object' && !Array.isArray(result)) {
1297
+ const { goal, goals, ...rest } = result;
1298
+ result = {
1299
+ ...rest,
1300
+ ...(goal && typeof goal === 'object' && !Array.isArray(goal)
1301
+ ? { task: { ...goal, taskId: goal.id } }
1302
+ : {}),
1303
+ ...(Array.isArray(goals)
1304
+ ? {
1305
+ tasks: goals.map((item) => item && typeof item === 'object' && !Array.isArray(item) ? { ...item, taskId: item.id } : item),
1306
+ }
1307
+ : {}),
1308
+ };
1309
+ }
1310
+ if (resource === 'offers' && result && typeof result === 'object' && !Array.isArray(result)) {
1311
+ const { deal, deals, ...rest } = result;
1312
+ result = {
1313
+ ...rest,
1314
+ ...(deal !== undefined ? { offer: deal } : {}),
1315
+ ...(deals !== undefined ? { offers: deals } : {}),
1316
+ };
1317
+ }
284
1318
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
285
1319
  }
286
1320
  main().catch((error) => {