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