@dawnai/cli 1.0.7 → 1.1.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.
Files changed (2) hide show
  1. package/dist/index.js +132 -128
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs/promises';
3
- import http from 'node:http';
4
3
  import os from 'node:os';
5
4
  import path from 'node:path';
6
5
  import process from 'node:process';
6
+ import readline from 'node:readline/promises';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { fileURLToPath } from 'node:url';
9
- const CLI_VERSION = '1.0.7';
9
+ const CLI_VERSION = '1.1.0';
10
10
  const DAWN_API_BASE_URL = 'https://api.dawn.ai';
11
11
  const FUNDING_LINK_TEMPLATE = process.env.DAWN_HELIO_LINK_TEMPLATE || '';
12
12
  const CLI_HOME = process.env.DAWN_CLI_HOME || path.join(os.homedir(), '.dawn-cli');
@@ -136,6 +136,12 @@ function formatNumeric(value, fallback = 0) {
136
136
  const n = Number(value);
137
137
  return Number.isFinite(n) ? n : fallback;
138
138
  }
139
+ function parseDecimalAmount(value, fallback = 0) {
140
+ if (typeof value === 'string') {
141
+ return formatNumeric(value.replace(/,/g, '').trim(), fallback);
142
+ }
143
+ return formatNumeric(value, fallback);
144
+ }
139
145
  function formatUsd(value) {
140
146
  return `$${formatNumeric(value).toLocaleString('en-US', {
141
147
  minimumFractionDigits: 2,
@@ -353,6 +359,76 @@ async function getLatestStrategyVersion(config, conversationId, withCode) {
353
359
  const latest = (await apiFetch(config, `/conversations/${conversationId}/latest-version?withCode=${withCode ? 'true' : 'false'}`));
354
360
  return latest || null;
355
361
  }
362
+ async function resolveLaunchVaultId(config, isPaper, budget) {
363
+ if (isPaper) {
364
+ console.log('Creating new paper vault...');
365
+ const createResponse = (await apiFetch(config, '/v1/vaults/create', {
366
+ method: 'POST',
367
+ body: JSON.stringify({ walletType: 'paper' }),
368
+ }));
369
+ const createdVaultId = createResponse?.vault?.id;
370
+ if (!createResponse?.success || !createdVaultId) {
371
+ throw new Error(createResponse?.error ||
372
+ 'Failed to create paper vault: invalid create-vault response.');
373
+ }
374
+ const vaultId = String(createdVaultId);
375
+ console.log(`Paper vault created: ${shortId(vaultId)}.`);
376
+ console.log(`Auto-funding paper vault with ${formatUsd(budget)}...`);
377
+ await apiFetch(config, `/v1/vaults/${vaultId}/fund`, {
378
+ method: 'POST',
379
+ body: JSON.stringify({ amount: String(budget) }),
380
+ });
381
+ console.log(`Paper vault funded: ${formatUsd(budget)}.`);
382
+ return vaultId;
383
+ }
384
+ const vaultsResponse = (await apiFetch(config, '/v1/vaults/all/strategy?isArchived=false'));
385
+ const vaults = vaultsResponse?.vaults || [];
386
+ let strategyVaultId = '';
387
+ let strategyVaultCurrentAmount = 0;
388
+ if (vaults.length > 0) {
389
+ strategyVaultId = String(vaults[0].id);
390
+ strategyVaultCurrentAmount = formatNumeric(vaults[0].currentAmount);
391
+ }
392
+ else {
393
+ console.log('No strategy vault found, creating strategy vault...');
394
+ const createResponse = (await apiFetch(config, '/v1/vaults/create', {
395
+ method: 'POST',
396
+ body: JSON.stringify({ walletType: 'strategy' }),
397
+ }));
398
+ const createdVaultId = createResponse?.vault?.id;
399
+ if (!createResponse?.success || !createdVaultId) {
400
+ throw new Error(createResponse?.error ||
401
+ 'Failed to create strategy vault: invalid create-vault response.');
402
+ }
403
+ strategyVaultId = String(createdVaultId);
404
+ strategyVaultCurrentAmount = 0;
405
+ console.log(`Strategy vault created: ${shortId(createdVaultId)}.`);
406
+ }
407
+ const fundingDeficit = budget - strategyVaultCurrentAmount;
408
+ if (fundingDeficit > 1e-9) {
409
+ const walletBalance = (await apiFetch(config, '/wallet/balance'));
410
+ const availableFundingBalance = parseDecimalAmount(walletBalance.availableBalance ?? walletBalance.usdcBalance ?? '0', 0);
411
+ console.log(`Strategy vault funding is ${formatUsd(strategyVaultCurrentAmount)}; funding additional ${formatUsd(fundingDeficit)} to reach ${formatUsd(budget)}...`);
412
+ if (availableFundingBalance + 1e-9 < fundingDeficit) {
413
+ throw new Error(`Insufficient available funding balance for live launch: available ${formatUsd(availableFundingBalance)}, required ${formatUsd(fundingDeficit)}.`);
414
+ }
415
+ try {
416
+ await apiFetch(config, `/v1/vaults/${strategyVaultId}/fund`, {
417
+ method: 'POST',
418
+ body: JSON.stringify({ amount: String(fundingDeficit) }),
419
+ });
420
+ console.log(`Strategy vault funded: +${formatUsd(fundingDeficit)}.`);
421
+ }
422
+ catch (error) {
423
+ const message = error instanceof Error ? error.message : String(error);
424
+ console.log(`Failed to auto-fund strategy vault: ${message}`);
425
+ }
426
+ }
427
+ else {
428
+ console.log(`Strategy vault already funded (${formatUsd(strategyVaultCurrentAmount)} >= ${formatUsd(budget)}).`);
429
+ }
430
+ return strategyVaultId;
431
+ }
356
432
  async function apiFetch(config, endpoint, options = {}) {
357
433
  const apiBaseUrl = await getApiBaseUrl(config);
358
434
  const token = getAuthToken(config);
@@ -386,20 +462,27 @@ async function apiFetch(config, endpoint, options = {}) {
386
462
  }
387
463
  return body;
388
464
  }
389
- function openBrowser(url) {
390
- const platform = process.platform;
391
- if (platform === 'darwin') {
392
- spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
393
- return;
465
+ async function promptForHeadlessCode() {
466
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
467
+ throw new Error('Interactive input is unavailable. Set DAWN_JWT_TOKEN for non-interactive environments.');
394
468
  }
395
- if (platform === 'win32') {
396
- spawn('cmd', ['/c', 'start', '', url], {
397
- stdio: 'ignore',
398
- detached: true,
399
- }).unref();
400
- return;
469
+ const rl = readline.createInterface({
470
+ input: process.stdin,
471
+ output: process.stdout,
472
+ });
473
+ try {
474
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
475
+ const raw = (await rl.question('Enter the 6-digit code: ')).trim();
476
+ if (/^\d{6}$/.test(raw)) {
477
+ return raw;
478
+ }
479
+ console.log('Invalid code format. Please enter exactly 6 digits.');
480
+ }
401
481
  }
402
- spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
482
+ finally {
483
+ rl.close();
484
+ }
485
+ throw new Error('Too many invalid code entry attempts.');
403
486
  }
404
487
  async function handleAuth(args) {
405
488
  if (args.includes('--help') || args.includes('-h')) {
@@ -408,11 +491,11 @@ async function handleAuth(args) {
408
491
  dawn auth status
409
492
  dawn auth logout
410
493
 
411
- For headless/CI environments, set the DAWN_JWT_TOKEN environment variable
412
- instead of using interactive login.`);
494
+ Login uses a browser URL + 6-digit code flow.
495
+ For non-interactive headless/CI environments, set DAWN_JWT_TOKEN.`);
413
496
  return;
414
497
  }
415
- const [subcommand, ...rest] = args;
498
+ const [subcommand] = args;
416
499
  if (!subcommand) {
417
500
  throw new Error('Usage: dawn auth <login|status|logout>');
418
501
  }
@@ -437,103 +520,36 @@ instead of using interactive login.`);
437
520
  if (subcommand !== 'login') {
438
521
  throw new Error(`Unknown auth command: ${subcommand}`);
439
522
  }
440
- const loginArgs = rest;
441
- const { flags } = parseFlags(loginArgs);
442
523
  const config = await loadConfig();
443
- await new Promise((resolve, reject) => {
444
- const timeoutMs = 180000;
445
- let isSettled = false;
446
- let timeoutId = null;
447
- const settle = (callback) => {
448
- if (isSettled) {
449
- return;
450
- }
451
- isSettled = true;
452
- if (timeoutId) {
453
- clearTimeout(timeoutId);
454
- timeoutId = null;
455
- }
456
- callback();
457
- };
458
- const resolveOnce = () => {
459
- settle(resolve);
460
- };
461
- const rejectOnce = (error) => {
462
- settle(() => reject(error));
463
- };
464
- const closeServer = () => {
465
- try {
466
- server.close();
467
- }
468
- catch {
469
- // no-op
470
- }
471
- };
472
- const server = http.createServer(async (req, res) => {
473
- try {
474
- const reqUrl = new URL(req.url || '/', 'http://127.0.0.1');
475
- if (reqUrl.pathname !== '/callback') {
476
- res.statusCode = 404;
477
- res.end('Not found');
478
- return;
479
- }
480
- const token = reqUrl.searchParams.get('token');
481
- const error = reqUrl.searchParams.get('error');
482
- if (error) {
483
- res.statusCode = 200;
484
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
485
- res.end('<h3>Authentication failed. You can close this window.</h3>');
486
- closeServer();
487
- rejectOnce(new Error(`Auth failed: ${error}`));
488
- return;
489
- }
490
- if (!token) {
491
- res.statusCode = 200;
492
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
493
- res.end('<h3>Missing token. You can close this window.</h3>');
494
- closeServer();
495
- rejectOnce(new Error('Missing token in auth callback.'));
496
- return;
497
- }
498
- const latestConfig = await loadConfig();
499
- await saveConfig({ ...latestConfig, token });
500
- res.statusCode = 200;
501
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
502
- res.end('<h3>Authentication complete. You can close this window.</h3>');
503
- closeServer();
504
- resolveOnce();
505
- }
506
- catch (error) {
507
- closeServer();
508
- rejectOnce(error);
509
- }
510
- });
511
- server.listen(0, '127.0.0.1', () => {
512
- const addr = server.address();
513
- if (!addr || typeof addr === 'string') {
514
- rejectOnce(new Error('Failed to open local auth callback server.'));
515
- return;
516
- }
517
- const redirect = `http://127.0.0.1:${addr.port}/callback`;
518
- void (async () => {
519
- try {
520
- const apiBaseUrl = await getApiBaseUrl(config);
521
- const authUrl = `${apiBaseUrl}/v1/auth/google/?redirect=${encodeURIComponent(redirect)}`;
522
- console.log(`Using API: ${apiBaseUrl}`);
523
- console.log('Opening browser for Dawn auth...');
524
- openBrowser(authUrl);
525
- console.log('Waiting for authentication callback...');
526
- }
527
- catch (error) {
528
- rejectOnce(error);
529
- }
530
- })();
531
- });
532
- timeoutId = setTimeout(() => {
533
- closeServer();
534
- rejectOnce(new Error('Timed out waiting for authentication callback.'));
535
- }, timeoutMs);
536
- });
524
+ const apiBaseUrl = await getApiBaseUrl(config);
525
+ const initiateResponse = (await apiFetch(config, '/v1/auth/google/?headless=true'));
526
+ const authUrl = initiateResponse.authUrl;
527
+ const loginRequestId = initiateResponse.loginRequestId;
528
+ if (!authUrl || !loginRequestId) {
529
+ throw new Error('Failed to initialize headless authentication flow.');
530
+ }
531
+ console.log(`Using API: ${apiBaseUrl}`);
532
+ console.log('');
533
+ console.log('Open this URL in any browser/device and complete Google login:');
534
+ console.log(authUrl);
535
+ if (typeof initiateResponse.expiresInSeconds === 'number') {
536
+ console.log(`Code expires in about ${Math.max(1, Math.floor(initiateResponse.expiresInSeconds / 60))} minute(s).`);
537
+ }
538
+ console.log('');
539
+ const code = await promptForHeadlessCode();
540
+ const completeResponse = (await apiFetch(config, '/v1/auth/google/headless/complete', {
541
+ method: 'POST',
542
+ body: JSON.stringify({
543
+ loginRequestId,
544
+ code,
545
+ }),
546
+ }));
547
+ const token = completeResponse.jwtToken;
548
+ if (!token) {
549
+ throw new Error('Authentication did not return a JWT token.');
550
+ }
551
+ const latestConfig = await loadConfig();
552
+ await saveConfig({ ...latestConfig, token });
537
553
  console.log('Authentication successful.');
538
554
  }
539
555
  async function ensureWallet(config) {
@@ -583,13 +599,7 @@ async function handleFund(args) {
583
599
  console.log('Deposit instructions:');
584
600
  console.log('- Send USDC on Polygon (Polygon PoS) to the wallet above.');
585
601
  console.log('- Avoid sending tokens on other networks unless you bridge first.');
586
- const link = fundingLinkForAddress(wallet.proxyAddress);
587
- if (link) {
588
- console.log(`Funding link: ${link}`);
589
- }
590
- else {
591
- console.log('Funding link: not generated (legacy Helio /pay links are deprecated).');
592
- }
602
+ console.log('Use @moonpay/cli to deposit USDC.e to the wallet above.');
593
603
  }
594
604
  async function handleOverview(args) {
595
605
  const { flags } = parseFlags(args);
@@ -1152,13 +1162,6 @@ async function handleStrategy(args) {
1152
1162
  throw new Error('--budget must be a positive number.');
1153
1163
  }
1154
1164
  const isPaper = !flags.live;
1155
- const vaultType = isPaper ? 'paper' : 'strategy';
1156
- const vaultsResponse = (await apiFetch(config, `/v1/vaults/all/${vaultType}?isArchived=false`));
1157
- const vaults = vaultsResponse?.vaults || [];
1158
- if (vaults.length === 0) {
1159
- throw new Error(`No ${vaultType} vault found. Create one in the Dawn app before launching.`);
1160
- }
1161
- const vault = vaults[0];
1162
1165
  const latestVersion = await getLatestStrategyVersion(config, conversationId, true);
1163
1166
  if (!latestVersion?.id) {
1164
1167
  throw new Error(`No code-ready strategy version found. Run: dawn strategy rules ${conversationId} approve-all, then dawn strategy code ${conversationId} generate.`);
@@ -1175,13 +1178,14 @@ async function handleStrategy(args) {
1175
1178
  throw new Error('--hours must be a positive number.');
1176
1179
  }
1177
1180
  const terminateAt = new Date(Date.now() + hours * 60 * 60 * 1000).toISOString();
1181
+ const vaultId = await resolveLaunchVaultId(config, isPaper, budget);
1178
1182
  const result = (await apiFetch(config, `/strategy/${conversationId}/run-strategy-v2`, {
1179
1183
  method: 'POST',
1180
1184
  body: JSON.stringify({
1181
1185
  strategyVersionId: latestVersion.id,
1182
1186
  isPaper,
1183
1187
  terminateAt,
1184
- walletId: vault.id,
1188
+ walletId: vaultId,
1185
1189
  }),
1186
1190
  }));
1187
1191
  console.log(`Launched strategy. Strategy ID: ${result?.strategy?.id ?? 'unknown'}${isPaper ? ' (paper)' : ' (live)'}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dawnai/cli",
3
- "version": "1.0.7",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "User-facing Dawn CLI",
6
6
  "license": "MIT",