@dawnai/cli 1.0.8 → 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.
- package/dist/index.js +53 -113
- 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
|
|
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');
|
|
@@ -462,20 +462,27 @@ async function apiFetch(config, endpoint, options = {}) {
|
|
|
462
462
|
}
|
|
463
463
|
return body;
|
|
464
464
|
}
|
|
465
|
-
function
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
469
|
-
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.');
|
|
470
468
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
+
}
|
|
481
|
+
}
|
|
482
|
+
finally {
|
|
483
|
+
rl.close();
|
|
477
484
|
}
|
|
478
|
-
|
|
485
|
+
throw new Error('Too many invalid code entry attempts.');
|
|
479
486
|
}
|
|
480
487
|
async function handleAuth(args) {
|
|
481
488
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -484,11 +491,11 @@ async function handleAuth(args) {
|
|
|
484
491
|
dawn auth status
|
|
485
492
|
dawn auth logout
|
|
486
493
|
|
|
487
|
-
|
|
488
|
-
|
|
494
|
+
Login uses a browser URL + 6-digit code flow.
|
|
495
|
+
For non-interactive headless/CI environments, set DAWN_JWT_TOKEN.`);
|
|
489
496
|
return;
|
|
490
497
|
}
|
|
491
|
-
const [subcommand
|
|
498
|
+
const [subcommand] = args;
|
|
492
499
|
if (!subcommand) {
|
|
493
500
|
throw new Error('Usage: dawn auth <login|status|logout>');
|
|
494
501
|
}
|
|
@@ -513,103 +520,36 @@ instead of using interactive login.`);
|
|
|
513
520
|
if (subcommand !== 'login') {
|
|
514
521
|
throw new Error(`Unknown auth command: ${subcommand}`);
|
|
515
522
|
}
|
|
516
|
-
const loginArgs = rest;
|
|
517
|
-
const { flags } = parseFlags(loginArgs);
|
|
518
523
|
const config = await loadConfig();
|
|
519
|
-
await
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const server = http.createServer(async (req, res) => {
|
|
549
|
-
try {
|
|
550
|
-
const reqUrl = new URL(req.url || '/', 'http://127.0.0.1');
|
|
551
|
-
if (reqUrl.pathname !== '/callback') {
|
|
552
|
-
res.statusCode = 404;
|
|
553
|
-
res.end('Not found');
|
|
554
|
-
return;
|
|
555
|
-
}
|
|
556
|
-
const token = reqUrl.searchParams.get('token');
|
|
557
|
-
const error = reqUrl.searchParams.get('error');
|
|
558
|
-
if (error) {
|
|
559
|
-
res.statusCode = 200;
|
|
560
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
561
|
-
res.end('<h3>Authentication failed. You can close this window.</h3>');
|
|
562
|
-
closeServer();
|
|
563
|
-
rejectOnce(new Error(`Auth failed: ${error}`));
|
|
564
|
-
return;
|
|
565
|
-
}
|
|
566
|
-
if (!token) {
|
|
567
|
-
res.statusCode = 200;
|
|
568
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
569
|
-
res.end('<h3>Missing token. You can close this window.</h3>');
|
|
570
|
-
closeServer();
|
|
571
|
-
rejectOnce(new Error('Missing token in auth callback.'));
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
const latestConfig = await loadConfig();
|
|
575
|
-
await saveConfig({ ...latestConfig, token });
|
|
576
|
-
res.statusCode = 200;
|
|
577
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
578
|
-
res.end('<h3>Authentication complete. You can close this window.</h3>');
|
|
579
|
-
closeServer();
|
|
580
|
-
resolveOnce();
|
|
581
|
-
}
|
|
582
|
-
catch (error) {
|
|
583
|
-
closeServer();
|
|
584
|
-
rejectOnce(error);
|
|
585
|
-
}
|
|
586
|
-
});
|
|
587
|
-
server.listen(0, '127.0.0.1', () => {
|
|
588
|
-
const addr = server.address();
|
|
589
|
-
if (!addr || typeof addr === 'string') {
|
|
590
|
-
rejectOnce(new Error('Failed to open local auth callback server.'));
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
const redirect = `http://127.0.0.1:${addr.port}/callback`;
|
|
594
|
-
void (async () => {
|
|
595
|
-
try {
|
|
596
|
-
const apiBaseUrl = await getApiBaseUrl(config);
|
|
597
|
-
const authUrl = `${apiBaseUrl}/v1/auth/google/?redirect=${encodeURIComponent(redirect)}`;
|
|
598
|
-
console.log(`Using API: ${apiBaseUrl}`);
|
|
599
|
-
console.log('Opening browser for Dawn auth...');
|
|
600
|
-
openBrowser(authUrl);
|
|
601
|
-
console.log('Waiting for authentication callback...');
|
|
602
|
-
}
|
|
603
|
-
catch (error) {
|
|
604
|
-
rejectOnce(error);
|
|
605
|
-
}
|
|
606
|
-
})();
|
|
607
|
-
});
|
|
608
|
-
timeoutId = setTimeout(() => {
|
|
609
|
-
closeServer();
|
|
610
|
-
rejectOnce(new Error('Timed out waiting for authentication callback.'));
|
|
611
|
-
}, timeoutMs);
|
|
612
|
-
});
|
|
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 });
|
|
613
553
|
console.log('Authentication successful.');
|
|
614
554
|
}
|
|
615
555
|
async function ensureWallet(config) {
|