@dawnai/cli 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # @dawnai/cli
2
+
3
+ Standalone user-facing CLI for Dawn.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @dawnai/cli
9
+ ```
10
+
11
+ Verify:
12
+
13
+ ```bash
14
+ dawn --version
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ dawn auth login
21
+ dawn auth status
22
+ dawn auth logout
23
+
24
+ dawn account overview
25
+ dawn account fund
26
+ dawn account wallet
27
+
28
+ dawn strategy list
29
+ dawn strategy create "<text>"
30
+ dawn strategy status <id>
31
+ dawn strategy revise <id> "<text>"
32
+ dawn strategy rules <id> list
33
+ dawn strategy rules <id> approve <rule-index>
34
+ dawn strategy rules <id> approve-all
35
+ dawn strategy code <id> status
36
+ dawn strategy code <id> generate
37
+ dawn strategy code <id> export [--out <path>] [--json]
38
+ dawn strategy code <id> upload <path-to-file>
39
+ dawn strategy launch <id> --budget <usd> [--live] [--hours N]
40
+
41
+ dawn run list
42
+ dawn run status <id>
43
+ dawn run logs <id> [--limit N]
44
+ dawn run stop <id>
45
+ ```
46
+
47
+ ## Environment Variables
48
+
49
+ | Variable | Description |
50
+ |---|---|
51
+ | `DAWN_JWT_TOKEN` | Auth token for headless/CI/agent environments. Set this to skip `dawn auth login`. |
52
+ | `DAWN_CLI_HOME` | Override config directory (default: `~/.dawn-cli`). |
53
+
54
+ ## Notes
55
+
56
+ - Auth token is stored at `~/.dawn-cli/config.json`.
57
+ - `dawn account fund` prints the wallet address and Polygon USDC deposit instructions.
package/dist/index.js ADDED
@@ -0,0 +1,1266 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import http from 'node:http';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import process from 'node:process';
7
+ import { spawn } from 'node:child_process';
8
+ const CLI_VERSION = '1.0.3';
9
+ const DAWN_API_BASE_URL = 'https://api.dawn.ai';
10
+ const FUNDING_LINK_TEMPLATE = process.env.DAWN_HELIO_LINK_TEMPLATE || '';
11
+ const CLI_HOME = process.env.DAWN_CLI_HOME || path.join(os.homedir(), '.dawn-cli');
12
+ const CONFIG_PATH = path.join(CLI_HOME, 'config.json');
13
+ async function ensureDir() {
14
+ await fs.mkdir(CLI_HOME, { recursive: true });
15
+ }
16
+ async function loadConfig() {
17
+ try {
18
+ const raw = await fs.readFile(CONFIG_PATH, 'utf8');
19
+ const parsed = JSON.parse(raw);
20
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
21
+ return parsed;
22
+ }
23
+ return {};
24
+ }
25
+ catch {
26
+ return {};
27
+ }
28
+ }
29
+ async function saveConfig(config) {
30
+ await ensureDir();
31
+ await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
32
+ }
33
+ async function getApiBaseUrl(config) {
34
+ const envBaseUrl = process.env.DAWN_API_BASE_URL;
35
+ const configBaseUrl = typeof config.apiBaseUrl === 'string' ? config.apiBaseUrl : undefined;
36
+ const explicitBaseUrl = envBaseUrl || configBaseUrl;
37
+ if (explicitBaseUrl && explicitBaseUrl !== DAWN_API_BASE_URL) {
38
+ throw new Error(`Invalid DAWN_API_BASE_URL: ${explicitBaseUrl}. This CLI only supports ${DAWN_API_BASE_URL}.`);
39
+ }
40
+ return DAWN_API_BASE_URL;
41
+ }
42
+ function getAuthToken(config) {
43
+ const configToken = typeof config.token === 'string' ? config.token : null;
44
+ return process.env.DAWN_JWT_TOKEN || configToken;
45
+ }
46
+ function formatNumeric(value, fallback = 0) {
47
+ const n = Number(value);
48
+ return Number.isFinite(n) ? n : fallback;
49
+ }
50
+ function formatUsd(value) {
51
+ return `$${formatNumeric(value).toLocaleString('en-US', {
52
+ minimumFractionDigits: 2,
53
+ maximumFractionDigits: 2,
54
+ })}`;
55
+ }
56
+ function formatPercent(value) {
57
+ return `${formatNumeric(value).toLocaleString('en-US', {
58
+ minimumFractionDigits: 2,
59
+ maximumFractionDigits: 2,
60
+ })}%`;
61
+ }
62
+ function printSection(title) {
63
+ console.log('');
64
+ console.log(title);
65
+ console.log('-'.repeat(title.length));
66
+ }
67
+ function printKeyValue(label, value) {
68
+ console.log(`${label.padEnd(22)} ${value}`);
69
+ }
70
+ function shortId(id) {
71
+ const raw = String(id ?? '');
72
+ if (raw.length <= 14) {
73
+ return raw;
74
+ }
75
+ return `${raw.slice(0, 8)}...${raw.slice(-4)}`;
76
+ }
77
+ function formatTimestamp(iso) {
78
+ if (!iso) {
79
+ return 'unknown';
80
+ }
81
+ const date = new Date(iso);
82
+ if (Number.isNaN(date.getTime())) {
83
+ return iso;
84
+ }
85
+ return date.toLocaleString('en-US', {
86
+ year: 'numeric',
87
+ month: 'short',
88
+ day: '2-digit',
89
+ hour: '2-digit',
90
+ minute: '2-digit',
91
+ });
92
+ }
93
+ function printUsage() {
94
+ console.log(`Usage:
95
+ dawn auth login
96
+ dawn auth status
97
+ dawn auth logout
98
+
99
+ dawn account overview
100
+ dawn account fund
101
+ dawn account wallet
102
+
103
+ dawn strategy list
104
+ dawn strategy create <text>
105
+ dawn strategy status <id>
106
+ dawn strategy revise <id> <text>
107
+ dawn strategy rules <id> list
108
+ dawn strategy rules <id> approve <rule-index>
109
+ dawn strategy rules <id> approve-all
110
+ dawn strategy code <id> status
111
+ dawn strategy code <id> generate
112
+ dawn strategy code <id> export [--out <path>] [--json]
113
+ dawn strategy code <id> upload <path-to-file>
114
+ dawn strategy launch <id> --budget <usd> [--live] [--hours N]
115
+
116
+ dawn run list
117
+ dawn run status <id>
118
+ dawn run logs <id> [--limit N]
119
+ dawn run stop <id>
120
+
121
+ dawn --version
122
+
123
+ Environment variables:
124
+ DAWN_JWT_TOKEN Auth token for headless/CI environments (skip dawn auth login)
125
+ DAWN_CLI_HOME Override config directory (default: ~/.dawn-cli)
126
+ `);
127
+ }
128
+ function parseFlags(args) {
129
+ const flags = {};
130
+ const positional = [];
131
+ for (let i = 0; i < args.length; i += 1) {
132
+ const arg = args[i];
133
+ if (arg.startsWith('--')) {
134
+ const key = arg.slice(2);
135
+ const next = args[i + 1];
136
+ if (!next || next.startsWith('--')) {
137
+ flags[key] = true;
138
+ }
139
+ else {
140
+ flags[key] = next;
141
+ i += 1;
142
+ }
143
+ }
144
+ else {
145
+ positional.push(arg);
146
+ }
147
+ }
148
+ return { flags, positional };
149
+ }
150
+ function isStringMap(value) {
151
+ return (!!value &&
152
+ typeof value === 'object' &&
153
+ !Array.isArray(value) &&
154
+ Object.values(value).every((v) => typeof v === 'string'));
155
+ }
156
+ function extractCodeMap(payload) {
157
+ if (isStringMap(payload)) {
158
+ return payload;
159
+ }
160
+ if (!payload || typeof payload !== 'object') {
161
+ return null;
162
+ }
163
+ const preferredKeys = ['code', 'files', 'rawCode', 'sourceFiles'];
164
+ const record = payload;
165
+ for (const key of preferredKeys) {
166
+ if (key in record) {
167
+ const extracted = extractCodeMap(record[key]);
168
+ if (extracted) {
169
+ return extracted;
170
+ }
171
+ }
172
+ }
173
+ if (Array.isArray(payload)) {
174
+ for (const item of payload) {
175
+ const extracted = extractCodeMap(item);
176
+ if (extracted) {
177
+ return extracted;
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+ for (const value of Object.values(record)) {
183
+ const extracted = extractCodeMap(value);
184
+ if (extracted) {
185
+ return extracted;
186
+ }
187
+ }
188
+ return null;
189
+ }
190
+ function printConversationStatus(conversationId, conversation, latestVersion, generationStatus, strategies) {
191
+ const convoName = conversation.name || '(untitled)';
192
+ const messageStatus = conversation.status || 'unknown';
193
+ const agentActivity = conversation.lastHeartbeat ? 'active' : 'idle';
194
+ const updated = conversation.updatedAt || 'unknown';
195
+ console.log(`Conversation: ${conversationId} (${convoName})`);
196
+ console.log(`Conversation state: messages=${messageStatus}, agent=${agentActivity}`);
197
+ console.log(`Conversation updatedAt: ${updated}`);
198
+ if (!latestVersion?.id) {
199
+ console.log('Latest version: none');
200
+ console.log('Code generation: no version available');
201
+ }
202
+ else {
203
+ const versionId = String(latestVersion.id);
204
+ const generationFromVersion = latestVersion.metadata?.generation || null;
205
+ const effectiveGeneration = generationStatus || generationFromVersion;
206
+ const generationState = effectiveGeneration?.status ||
207
+ (latestVersion.codeCreatedAt ? 'completed' : 'pending');
208
+ const generationRunning = generationStatus?.isRunning || generationState === 'running';
209
+ const hasCode = Boolean(latestVersion.codeCreatedAt);
210
+ console.log(`Latest version: ${versionId}${latestVersion.elapsedTime ? ` (${latestVersion.elapsedTime})` : ''}`);
211
+ console.log(`Code generation: ${generationState}${generationRunning ? ' (in progress)' : ''}${hasCode ? ' | code_ready=true' : ' | code_ready=false'}`);
212
+ if (effectiveGeneration?.error) {
213
+ console.log(`Generation error: ${effectiveGeneration.error}`);
214
+ }
215
+ if (effectiveGeneration?.completedAt) {
216
+ console.log(`Generation completedAt: ${effectiveGeneration.completedAt}`);
217
+ }
218
+ }
219
+ if (!strategies.length) {
220
+ console.log('Execution: not launched');
221
+ return;
222
+ }
223
+ const runningCount = strategies.filter((s) => Boolean(s.isRunning)).length;
224
+ const latestRun = strategies[0];
225
+ const latestRunId = latestRun?.id ?? 'unknown';
226
+ const latestRunType = latestRun?.strategyType || 'unknown';
227
+ const latestRunState = latestRun?.isRunning ? 'running' : 'stopped';
228
+ const latestRunTerminateAt = latestRun?.terminateAt || 'unknown';
229
+ const latestRunPnl = latestRun?.totalPnl || '0';
230
+ console.log(`Execution: ${runningCount}/${strategies.length} running`);
231
+ console.log(`Latest run: id=${latestRunId}, type=${latestRunType}, state=${latestRunState}, terminateAt=${latestRunTerminateAt}, totalPnl=${latestRunPnl}`);
232
+ }
233
+ function renderStrategyRules(version) {
234
+ const rules = Array.isArray(version.strategy?.rules) ? version.strategy?.rules : [];
235
+ const approved = new Set(version.metadata?.generation?.approvedRuleIndices || []);
236
+ const generationStatus = version.metadata?.generation?.status || 'unknown';
237
+ console.log(`Version: ${version.id ?? 'unknown'}`);
238
+ console.log(`Generation status: ${generationStatus}`);
239
+ console.log(`Rules: ${rules.length}`);
240
+ if (!rules.length) {
241
+ console.log('No rules found on latest version.');
242
+ return;
243
+ }
244
+ for (let i = 0; i < rules.length; i += 1) {
245
+ const rule = rules[i];
246
+ const isApproved = approved.has(i);
247
+ const type = rule.type || 'execution';
248
+ const name = rule.name || '(unnamed rule)';
249
+ const condition = (rule.condition || '').replace(/\s+/g, ' ').trim();
250
+ const preview = condition.length > 140 ? `${condition.slice(0, 137)}...` : condition;
251
+ console.log(`[${i}] ${isApproved ? '[approved]' : '[needs-approval]'} (${type}) ${name}`);
252
+ if (preview) {
253
+ console.log(` ${preview}`);
254
+ }
255
+ }
256
+ }
257
+ async function getLatestStrategyVersion(config, conversationId, withCode) {
258
+ const latest = (await apiFetch(config, `/conversations/${conversationId}/latest-version?withCode=${withCode ? 'true' : 'false'}`));
259
+ return latest || null;
260
+ }
261
+ async function apiFetch(config, endpoint, options = {}) {
262
+ const apiBaseUrl = await getApiBaseUrl(config);
263
+ const token = getAuthToken(config);
264
+ const headers = {
265
+ 'Content-Type': 'application/json',
266
+ ...options.headers,
267
+ };
268
+ if (token) {
269
+ headers.Authorization = `Bearer ${token}`;
270
+ }
271
+ const response = await fetch(`${apiBaseUrl}${endpoint}`, {
272
+ ...options,
273
+ headers,
274
+ });
275
+ const text = await response.text();
276
+ let body = null;
277
+ try {
278
+ body = text ? JSON.parse(text) : null;
279
+ }
280
+ catch {
281
+ body = text || null;
282
+ }
283
+ if (!response.ok) {
284
+ const errorBody = body && typeof body === 'object' ? body : null;
285
+ const message = (typeof errorBody?.message === 'string' && errorBody.message) ||
286
+ (typeof errorBody?.error === 'string' && errorBody.error) ||
287
+ (typeof body === 'string' && body) ||
288
+ response.statusText ||
289
+ `Request failed: ${response.status}`;
290
+ throw new Error(`${message} (${response.status} ${response.statusText} — ${endpoint})`);
291
+ }
292
+ return body;
293
+ }
294
+ function openBrowser(url) {
295
+ const platform = process.platform;
296
+ if (platform === 'darwin') {
297
+ spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
298
+ return;
299
+ }
300
+ if (platform === 'win32') {
301
+ spawn('cmd', ['/c', 'start', '', url], {
302
+ stdio: 'ignore',
303
+ detached: true,
304
+ }).unref();
305
+ return;
306
+ }
307
+ spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
308
+ }
309
+ async function handleAuth(args) {
310
+ if (args.includes('--help') || args.includes('-h')) {
311
+ console.log(`Usage:
312
+ dawn auth login
313
+ dawn auth status
314
+ dawn auth logout
315
+
316
+ For headless/CI environments, set the DAWN_JWT_TOKEN environment variable
317
+ instead of using interactive login.`);
318
+ return;
319
+ }
320
+ const [subcommand, ...rest] = args;
321
+ if (!subcommand) {
322
+ throw new Error('Usage: dawn auth <login|status|logout>');
323
+ }
324
+ if (subcommand === 'status') {
325
+ const config = await loadConfig();
326
+ const token = getAuthToken(config);
327
+ if (!token) {
328
+ console.log('Not authenticated.');
329
+ return;
330
+ }
331
+ console.log('Authenticated.');
332
+ return;
333
+ }
334
+ if (subcommand === 'logout') {
335
+ const config = await loadConfig();
336
+ const nextConfig = { ...config };
337
+ delete nextConfig.token;
338
+ await saveConfig(nextConfig);
339
+ console.log('Logged out. Local token removed.');
340
+ return;
341
+ }
342
+ if (subcommand !== 'login') {
343
+ throw new Error(`Unknown auth command: ${subcommand}`);
344
+ }
345
+ const loginArgs = rest;
346
+ const { flags } = parseFlags(loginArgs);
347
+ const config = await loadConfig();
348
+ await new Promise((resolve, reject) => {
349
+ const timeoutMs = 180000;
350
+ let isSettled = false;
351
+ let timeoutId = null;
352
+ const settle = (callback) => {
353
+ if (isSettled) {
354
+ return;
355
+ }
356
+ isSettled = true;
357
+ if (timeoutId) {
358
+ clearTimeout(timeoutId);
359
+ timeoutId = null;
360
+ }
361
+ callback();
362
+ };
363
+ const resolveOnce = () => {
364
+ settle(resolve);
365
+ };
366
+ const rejectOnce = (error) => {
367
+ settle(() => reject(error));
368
+ };
369
+ const closeServer = () => {
370
+ try {
371
+ server.close();
372
+ }
373
+ catch {
374
+ // no-op
375
+ }
376
+ };
377
+ const server = http.createServer(async (req, res) => {
378
+ try {
379
+ const reqUrl = new URL(req.url || '/', 'http://127.0.0.1');
380
+ if (reqUrl.pathname !== '/callback') {
381
+ res.statusCode = 404;
382
+ res.end('Not found');
383
+ return;
384
+ }
385
+ const token = reqUrl.searchParams.get('token');
386
+ const error = reqUrl.searchParams.get('error');
387
+ if (error) {
388
+ res.statusCode = 200;
389
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
390
+ res.end('<h3>Authentication failed. You can close this window.</h3>');
391
+ closeServer();
392
+ rejectOnce(new Error(`Auth failed: ${error}`));
393
+ return;
394
+ }
395
+ if (!token) {
396
+ res.statusCode = 200;
397
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
398
+ res.end('<h3>Missing token. You can close this window.</h3>');
399
+ closeServer();
400
+ rejectOnce(new Error('Missing token in auth callback.'));
401
+ return;
402
+ }
403
+ const latestConfig = await loadConfig();
404
+ await saveConfig({ ...latestConfig, token });
405
+ res.statusCode = 200;
406
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
407
+ res.end('<h3>Authentication complete. You can close this window.</h3>');
408
+ closeServer();
409
+ resolveOnce();
410
+ }
411
+ catch (error) {
412
+ closeServer();
413
+ rejectOnce(error);
414
+ }
415
+ });
416
+ server.listen(0, '127.0.0.1', () => {
417
+ const addr = server.address();
418
+ if (!addr || typeof addr === 'string') {
419
+ rejectOnce(new Error('Failed to open local auth callback server.'));
420
+ return;
421
+ }
422
+ const redirect = `http://127.0.0.1:${addr.port}/callback`;
423
+ void (async () => {
424
+ try {
425
+ const apiBaseUrl = await getApiBaseUrl(config);
426
+ const authUrl = `${apiBaseUrl}/v1/auth/google/?redirect=${encodeURIComponent(redirect)}`;
427
+ console.log(`Using API: ${apiBaseUrl}`);
428
+ console.log('Opening browser for Dawn auth...');
429
+ openBrowser(authUrl);
430
+ console.log('Waiting for authentication callback...');
431
+ }
432
+ catch (error) {
433
+ rejectOnce(error);
434
+ }
435
+ })();
436
+ });
437
+ timeoutId = setTimeout(() => {
438
+ closeServer();
439
+ rejectOnce(new Error('Timed out waiting for authentication callback.'));
440
+ }, timeoutMs);
441
+ });
442
+ console.log('Authentication successful.');
443
+ }
444
+ async function ensureWallet(config) {
445
+ let wallet = (await apiFetch(config, '/wallet/my-wallet'));
446
+ if (wallet && wallet.wallet === null) {
447
+ wallet = null;
448
+ }
449
+ if (!wallet) {
450
+ const created = (await apiFetch(config, '/wallet/create', { method: 'POST' }));
451
+ if (!created?.success || !created.wallet) {
452
+ throw new Error(created?.error || 'Failed to create wallet.');
453
+ }
454
+ return created.wallet;
455
+ }
456
+ return wallet;
457
+ }
458
+ function helioLinkForAddress(address) {
459
+ return FUNDING_LINK_TEMPLATE.replace('{address}', encodeURIComponent(address));
460
+ }
461
+ function fundingLinkForAddress(address) {
462
+ if (!FUNDING_LINK_TEMPLATE) {
463
+ return null;
464
+ }
465
+ // Legacy Helio /pay links now return 404; disable them by default.
466
+ const isLegacyHelioPayLink = /https?:\/\/(?:app|moonpay)\.hel\.io\/pay\?/i.test(FUNDING_LINK_TEMPLATE) &&
467
+ FUNDING_LINK_TEMPLATE.includes('recipientAddress=');
468
+ if (isLegacyHelioPayLink) {
469
+ return null;
470
+ }
471
+ return helioLinkForAddress(address);
472
+ }
473
+ async function handleFund(args) {
474
+ const { flags } = parseFlags(args);
475
+ if (flags.help || flags.h) {
476
+ console.log('Usage:\n dawn fund');
477
+ return;
478
+ }
479
+ const config = await loadConfig();
480
+ if (!getAuthToken(config)) {
481
+ throw new Error('Not authenticated. Run: dawn auth login');
482
+ }
483
+ const wallet = await ensureWallet(config);
484
+ if (!wallet?.proxyAddress) {
485
+ throw new Error('Could not determine wallet proxy address.');
486
+ }
487
+ console.log(`Wallet: ${wallet.proxyAddress}`);
488
+ console.log('Deposit instructions:');
489
+ console.log('- Send USDC on Polygon (Polygon PoS) to the wallet above.');
490
+ console.log('- Avoid sending tokens on other networks unless you bridge first.');
491
+ const link = fundingLinkForAddress(wallet.proxyAddress);
492
+ if (link) {
493
+ console.log(`Funding link: ${link}`);
494
+ }
495
+ else {
496
+ console.log('Funding link: not generated (legacy Helio /pay links are deprecated).');
497
+ }
498
+ }
499
+ async function handleOverview(args) {
500
+ const { flags } = parseFlags(args);
501
+ if (flags.help || flags.h) {
502
+ console.log('Usage:\n dawn overview');
503
+ return;
504
+ }
505
+ const config = await loadConfig();
506
+ if (!getAuthToken(config)) {
507
+ throw new Error('Not authenticated. Run: dawn auth login');
508
+ }
509
+ const wallet = await ensureWallet(config);
510
+ if (!wallet?.proxyAddress) {
511
+ throw new Error('Could not determine wallet proxy address.');
512
+ }
513
+ const [walletBalanceRaw, generatedBalancesRaw] = await Promise.all([
514
+ apiFetch(config, '/wallet/balance'),
515
+ apiFetch(config, '/wallet/generated-balances'),
516
+ ]);
517
+ const walletBalance = (walletBalanceRaw || {});
518
+ const generatedBalances = (generatedBalancesRaw || {});
519
+ const walletTypes = ['funding', 'strategy', 'paper'];
520
+ const vaultLists = await Promise.all(walletTypes.map(async (walletType) => {
521
+ try {
522
+ const response = (await apiFetch(config, `/v1/vaults/all/${walletType}?isArchived=false`));
523
+ return response.vaults || [];
524
+ }
525
+ catch {
526
+ return [];
527
+ }
528
+ }));
529
+ const vaults = vaultLists.flat();
530
+ const vaultPositions = await Promise.all(vaults.map(async (vault) => {
531
+ try {
532
+ const response = (await apiFetch(config, `/v1/vault-metadata/${vault.id}/positions`));
533
+ return { vaultId: vault.id, positions: response.positions || [] };
534
+ }
535
+ catch {
536
+ return { vaultId: vault.id, positions: [] };
537
+ }
538
+ }));
539
+ const positionsByVaultId = new Map(vaultPositions.map((item) => [item.vaultId, item.positions]));
540
+ const aggregateVaultCurrent = vaults.reduce((sum, vault) => sum + formatNumeric(vault.totalCurrentValue), 0);
541
+ const aggregateVaultCost = vaults.reduce((sum, vault) => sum + formatNumeric(vault.totalCostBasis), 0);
542
+ const aggregateVaultPnl = vaults.reduce((sum, vault) => sum + formatNumeric(vault.totalPnl), 0);
543
+ console.log('Dawn Overview');
544
+ console.log('=============');
545
+ printKeyValue('Generated', formatTimestamp(new Date().toISOString()));
546
+ printSection('Account Balances');
547
+ printKeyValue('Funding wallet', wallet.proxyAddress);
548
+ printKeyValue('Total balance', formatUsd(walletBalance.walletBalance || '0'));
549
+ printKeyValue('Available (USDC.e)', formatUsd(walletBalance.availableBalance || '0'));
550
+ printKeyValue('Allocated to vaults', formatUsd(walletBalance.allocatedBalance || '0'));
551
+ printKeyValue('Wallet USDC.e', formatUsd(walletBalance.usdcBalance || '0'));
552
+ printKeyValue('Wallet POL', formatNumeric(generatedBalances.pol).toFixed(4));
553
+ printKeyValue('Wallet USDC', formatUsd(generatedBalances.usdc || '0'));
554
+ printKeyValue('Wallet USDC.e', formatUsd(generatedBalances.usdce || '0'));
555
+ printSection('Vault Summary');
556
+ printKeyValue('Vault count', String(vaults.length));
557
+ printKeyValue('Total current value', formatUsd(aggregateVaultCurrent));
558
+ printKeyValue('Total cost basis', formatUsd(aggregateVaultCost));
559
+ printKeyValue('Total PnL', `${formatUsd(aggregateVaultPnl)} (${formatPercent(aggregateVaultCost === 0 ? 0 : (aggregateVaultPnl / aggregateVaultCost) * 100)})`);
560
+ if (!vaults.length) {
561
+ console.log('No vaults found.');
562
+ return;
563
+ }
564
+ printSection('Vault Portfolios');
565
+ for (const vault of vaults) {
566
+ const positions = positionsByVaultId.get(vault.id) || [];
567
+ console.log(`\n[${vault.walletType.toUpperCase()}] ${shortId(vault.id)} | current=${formatUsd(vault.totalCurrentValue)} | cost=${formatUsd(vault.totalCostBasis)} | pnl=${formatUsd(vault.totalPnl)} (${formatPercent(vault.totalPnlPercentage)}) | strategies=${vault.strategyCount}`);
568
+ if (!positions.length) {
569
+ console.log(' (no portfolio positions)');
570
+ continue;
571
+ }
572
+ const sorted = [...positions].sort((a, b) => formatNumeric(b.currentValue) - formatNumeric(a.currentValue));
573
+ for (const position of sorted) {
574
+ const title = position.marketTitle ||
575
+ position.groupItemTitle ||
576
+ `asset:${shortId(position.assetId || position.id)}`;
577
+ console.log(` - ${title} | value=${formatUsd(position.currentValue || '0')} | cost=${formatUsd(position.costBasis || '0')} | pnl=${formatUsd(position.unrealizedPnl || '0')} (${formatPercent(position.unrealizedPnlPercentage || '0')}) | amount=${formatNumeric(position.amount).toFixed(4)}`);
578
+ }
579
+ }
580
+ }
581
+ async function handleAccount(args) {
582
+ if (args.includes('--help') || args.includes('-h')) {
583
+ console.log(`Usage:
584
+ dawn account overview
585
+ dawn account fund
586
+ dawn account wallet`);
587
+ return;
588
+ }
589
+ const [subcommand, ...rest] = args;
590
+ if (!subcommand) {
591
+ throw new Error('Missing account command.');
592
+ }
593
+ if (subcommand === 'overview') {
594
+ await handleOverview(rest);
595
+ return;
596
+ }
597
+ if (subcommand === 'fund') {
598
+ await handleFund(rest);
599
+ return;
600
+ }
601
+ if (subcommand === 'wallet') {
602
+ const config = await loadConfig();
603
+ if (!getAuthToken(config)) {
604
+ throw new Error('Not authenticated. Run: dawn auth login');
605
+ }
606
+ const wallet = await ensureWallet(config);
607
+ if (!wallet?.proxyAddress) {
608
+ throw new Error('Could not determine wallet proxy address.');
609
+ }
610
+ const [walletBalanceRaw, generatedBalancesRaw] = await Promise.all([
611
+ apiFetch(config, '/wallet/balance'),
612
+ apiFetch(config, '/wallet/generated-balances'),
613
+ ]);
614
+ const walletBalance = (walletBalanceRaw || {});
615
+ const generatedBalances = (generatedBalancesRaw || {});
616
+ console.log('Wallet');
617
+ console.log('======');
618
+ printKeyValue('Address', wallet.proxyAddress);
619
+ printKeyValue('Total balance', formatUsd(walletBalance.walletBalance || '0'));
620
+ printKeyValue('Available (USDC.e)', formatUsd(walletBalance.availableBalance || '0'));
621
+ printKeyValue('Allocated to vaults', formatUsd(walletBalance.allocatedBalance || '0'));
622
+ printKeyValue('Wallet USDC.e', formatUsd(walletBalance.usdcBalance || '0'));
623
+ printKeyValue('Wallet POL', formatNumeric(generatedBalances.pol).toFixed(4));
624
+ printKeyValue('Wallet USDC', formatUsd(generatedBalances.usdc || '0'));
625
+ printKeyValue('Wallet USDC.e', formatUsd(generatedBalances.usdce || '0'));
626
+ return;
627
+ }
628
+ throw new Error(`Unknown account command: ${subcommand}`);
629
+ }
630
+ function renderConversationList(conversations) {
631
+ if (!Array.isArray(conversations) || conversations.length === 0) {
632
+ console.log('No strategies found.');
633
+ return;
634
+ }
635
+ const rows = conversations.map((c) => ({
636
+ id: String(c.id ?? ''),
637
+ name: c.name || '(untitled)',
638
+ }));
639
+ const idWidth = Math.max(10, ...rows.map((row) => row.id.length));
640
+ console.log(`${'ID'.padEnd(idWidth)} NAME`);
641
+ console.log(`${'-'.repeat(idWidth)} ${'-'.repeat(40)}`);
642
+ for (const row of rows) {
643
+ console.log(`${row.id.padEnd(idWidth)} ${row.name}`);
644
+ }
645
+ }
646
+ function renderAgentOverview(conversationId, conversation, strategies) {
647
+ console.log(`Agent ${conversationId}`);
648
+ console.log('='.repeat(`Agent ${conversationId}`.length));
649
+ printSection('Conversation');
650
+ printKeyValue('Name', conversation.name || '(untitled)');
651
+ printKeyValue('Status', conversation.status || 'unknown');
652
+ printKeyValue('Updated', formatTimestamp(conversation.updatedAt));
653
+ printKeyValue('Heartbeat', conversation.lastHeartbeat ? formatTimestamp(conversation.lastHeartbeat) : 'none');
654
+ printSection('Strategies');
655
+ if (!strategies.length) {
656
+ console.log('No strategies found for this agent.');
657
+ return;
658
+ }
659
+ const runningCount = strategies.filter((s) => Boolean(s.isRunning)).length;
660
+ printKeyValue('Total', String(strategies.length));
661
+ printKeyValue('Running', String(runningCount));
662
+ for (const strategy of strategies) {
663
+ const label = strategy.isRunning ? 'running' : 'stopped';
664
+ console.log(`- ${strategy.id ?? 'unknown'} | ${strategy.strategyType || 'unknown'} | ${label} | pnl=${formatUsd(strategy.totalPnl || '0')} | terminateAt=${formatTimestamp(strategy.terminateAt)}`);
665
+ }
666
+ }
667
+ async function handleStrategy(args) {
668
+ if (args.includes('--help') || args.includes('-h')) {
669
+ console.log(`Usage:
670
+ dawn strategy list
671
+ dawn strategy create <text>
672
+ dawn strategy status <id>
673
+ dawn strategy revise <id> <text>
674
+ dawn strategy rules <id> list
675
+ dawn strategy rules <id> approve <rule-index>
676
+ dawn strategy rules <id> approve-all
677
+ dawn strategy code <id> status
678
+ dawn strategy code <id> generate
679
+ dawn strategy code <id> export [--out <path>] [--json]
680
+ dawn strategy code <id> upload <path-to-file>
681
+ dawn strategy launch <id> --budget <usd> [--live] [--hours N]`);
682
+ return;
683
+ }
684
+ const config = await loadConfig();
685
+ if (!getAuthToken(config)) {
686
+ throw new Error('Not authenticated. Run: dawn auth login');
687
+ }
688
+ const [primaryCommand, ...rest] = args;
689
+ if (!primaryCommand) {
690
+ throw new Error('Missing strategy command.');
691
+ }
692
+ if (!['list', 'create', 'status', 'revise', 'rules', 'code', 'launch'].includes(primaryCommand)) {
693
+ throw new Error(`Unknown strategy command: ${primaryCommand}. Run \`dawn strategy --help\` for available commands.`);
694
+ }
695
+ let subcommand = primaryCommand;
696
+ if (primaryCommand === 'rules') {
697
+ const conversationId = rest[0];
698
+ const action = rest[1];
699
+ if (!conversationId) {
700
+ throw new Error('Usage: dawn strategy rules <id> <list|approve <rule-index>|approve-all>');
701
+ }
702
+ if (action === 'list') {
703
+ subcommand = '__rules-list';
704
+ rest.splice(0, rest.length, conversationId);
705
+ }
706
+ else if (action === 'approve') {
707
+ subcommand = '__rules-approve';
708
+ rest.splice(0, rest.length, conversationId, rest[2] || '');
709
+ }
710
+ else if (action === 'approve-all') {
711
+ subcommand = '__rules-approve-all';
712
+ rest.splice(0, rest.length, conversationId);
713
+ }
714
+ else {
715
+ throw new Error('Usage: dawn strategy rules <id> <list|approve <rule-index>|approve-all>');
716
+ }
717
+ }
718
+ if (primaryCommand === 'code') {
719
+ const conversationId = rest[0];
720
+ const action = rest[1];
721
+ const tail = rest.slice(2);
722
+ if (!conversationId || !action) {
723
+ throw new Error('Usage: dawn strategy code <id> <status|generate|export|upload> [args]');
724
+ }
725
+ if (action === 'status') {
726
+ subcommand = '__status';
727
+ rest.splice(0, rest.length, conversationId);
728
+ }
729
+ else if (action === 'generate') {
730
+ subcommand = '__generate';
731
+ rest.splice(0, rest.length, conversationId);
732
+ }
733
+ else if (action === 'export') {
734
+ subcommand = '__raw-code';
735
+ rest.splice(0, rest.length, conversationId, ...tail);
736
+ }
737
+ else if (action === 'upload') {
738
+ if (!tail[0]) {
739
+ throw new Error('Usage: dawn strategy code <id> upload <path-to-file>');
740
+ }
741
+ subcommand = '__upload';
742
+ rest.splice(0, rest.length, conversationId, tail[0]);
743
+ }
744
+ else {
745
+ throw new Error(`Unknown strategy code command: ${action}`);
746
+ }
747
+ }
748
+ if (primaryCommand === 'status') {
749
+ const conversationId = rest[0];
750
+ if (!conversationId) {
751
+ throw new Error('Usage: dawn strategy status <id>');
752
+ }
753
+ subcommand = '__status';
754
+ rest.splice(0, rest.length, conversationId);
755
+ }
756
+ if (primaryCommand === 'revise') {
757
+ const conversationId = rest[0];
758
+ const text = rest.slice(1).join(' ').trim();
759
+ if (!conversationId || !text) {
760
+ throw new Error('Usage: dawn strategy revise <id> <text>');
761
+ }
762
+ subcommand = '__edit';
763
+ rest.splice(0, rest.length, conversationId, text);
764
+ }
765
+ if (subcommand === 'list') {
766
+ const data = await apiFetch(config, '/conversations?isArchived=false');
767
+ renderConversationList(data);
768
+ return;
769
+ }
770
+ if (subcommand === 'create') {
771
+ const text = rest.join(' ').trim();
772
+ if (!text) {
773
+ throw new Error('Usage: dawn strategy create <text>');
774
+ }
775
+ const result = (await apiFetch(config, '/conversations', {
776
+ method: 'POST',
777
+ body: JSON.stringify({ userMessage: text }),
778
+ }));
779
+ console.log(`Created strategy conversation: ${result.conversationId}`);
780
+ return;
781
+ }
782
+ if (subcommand === '__upload') {
783
+ const conversationId = rest[0];
784
+ const filePath = rest[1];
785
+ if (!conversationId || !filePath) {
786
+ throw new Error('Usage: dawn strategy code <id> upload <path-to-file>');
787
+ }
788
+ const latestVersion = (await apiFetch(config, `/conversations/${conversationId}/latest-version?withCode=true`));
789
+ if (!latestVersion?.id) {
790
+ throw new Error('No strategy version found for conversation.');
791
+ }
792
+ const absolutePath = path.resolve(process.cwd(), filePath);
793
+ const content = await fs.readFile(absolutePath, 'utf8');
794
+ let codePayload = null;
795
+ if (absolutePath.endsWith('.json')) {
796
+ try {
797
+ const parsed = JSON.parse(content);
798
+ const validMap = parsed &&
799
+ typeof parsed === 'object' &&
800
+ !Array.isArray(parsed) &&
801
+ Object.values(parsed).every((v) => typeof v === 'string');
802
+ if (validMap) {
803
+ codePayload = parsed;
804
+ }
805
+ }
806
+ catch {
807
+ // fall through to single-file mode
808
+ }
809
+ }
810
+ if (!codePayload) {
811
+ codePayload = {
812
+ [path.basename(absolutePath)]: content,
813
+ };
814
+ }
815
+ await apiFetch(config, '/v2/strategy/update-code', {
816
+ method: 'POST',
817
+ body: JSON.stringify({
818
+ versionId: latestVersion.id,
819
+ code: codePayload,
820
+ }),
821
+ });
822
+ console.log(`Uploaded code to version ${latestVersion.id}.`);
823
+ return;
824
+ }
825
+ if (subcommand === '__rules-list') {
826
+ const conversationId = rest[0];
827
+ if (!conversationId) {
828
+ throw new Error('Usage: dawn strategy rules <id> list');
829
+ }
830
+ const latestVersion = await getLatestStrategyVersion(config, conversationId, false);
831
+ if (!latestVersion?.id) {
832
+ throw new Error('No strategy version found for conversation.');
833
+ }
834
+ renderStrategyRules(latestVersion);
835
+ return;
836
+ }
837
+ if (subcommand === '__rules-approve') {
838
+ const conversationId = rest[0];
839
+ const ruleIndexRaw = rest[1];
840
+ if (!conversationId || !ruleIndexRaw) {
841
+ throw new Error('Usage: dawn strategy rules <id> approve <rule-index>');
842
+ }
843
+ const ruleIndex = Number(ruleIndexRaw);
844
+ if (!Number.isInteger(ruleIndex) || ruleIndex < 0) {
845
+ throw new Error('rule-index must be a non-negative integer.');
846
+ }
847
+ const latestVersion = await getLatestStrategyVersion(config, conversationId, false);
848
+ if (!latestVersion?.id) {
849
+ throw new Error('No strategy version found for conversation.');
850
+ }
851
+ const updated = (await apiFetch(config, `/v2/strategy/approve-rule/${latestVersion.id}`, {
852
+ method: 'POST',
853
+ body: JSON.stringify({ ruleIndex }),
854
+ }));
855
+ const approvedRuleIndices = updated.approvedRuleIndices || [];
856
+ const isApproved = approvedRuleIndices.includes(ruleIndex);
857
+ console.log(`${isApproved ? 'Approved' : 'Unapproved'} rule ${ruleIndex} on version ${latestVersion.id}.`);
858
+ console.log(`Approved indices: ${approvedRuleIndices.join(', ') || '(none)'}`);
859
+ return;
860
+ }
861
+ if (subcommand === '__rules-approve-all') {
862
+ const conversationId = rest[0];
863
+ if (!conversationId) {
864
+ throw new Error('Usage: dawn strategy rules <id> approve-all');
865
+ }
866
+ const latestVersion = await getLatestStrategyVersion(config, conversationId, false);
867
+ if (!latestVersion?.id) {
868
+ throw new Error('No strategy version found for conversation.');
869
+ }
870
+ const rules = Array.isArray(latestVersion.strategy?.rules)
871
+ ? latestVersion.strategy?.rules
872
+ : [];
873
+ if (!rules.length) {
874
+ console.log('No rules to approve.');
875
+ return;
876
+ }
877
+ const approved = new Set(latestVersion.metadata?.generation?.approvedRuleIndices || []);
878
+ for (let i = 0; i < rules.length; i += 1) {
879
+ if (approved.has(i)) {
880
+ continue;
881
+ }
882
+ const response = (await apiFetch(config, `/v2/strategy/approve-rule/${latestVersion.id}`, {
883
+ method: 'POST',
884
+ body: JSON.stringify({ ruleIndex: i }),
885
+ }));
886
+ const nextApproved = response.approvedRuleIndices || [];
887
+ approved.clear();
888
+ for (const idx of nextApproved) {
889
+ approved.add(idx);
890
+ }
891
+ }
892
+ console.log(`Approved all rules for version ${latestVersion.id}. Total approved: ${approved.size}/${rules.length}.`);
893
+ return;
894
+ }
895
+ if (subcommand === '__generate') {
896
+ const conversationId = rest[0];
897
+ if (!conversationId) {
898
+ throw new Error('Usage: dawn strategy code <id> generate');
899
+ }
900
+ const latestVersion = await getLatestStrategyVersion(config, conversationId, false);
901
+ if (!latestVersion?.id) {
902
+ throw new Error('No strategy version found for conversation.');
903
+ }
904
+ const rules = Array.isArray(latestVersion.strategy?.rules)
905
+ ? latestVersion.strategy?.rules
906
+ : [];
907
+ const approved = new Set(latestVersion.metadata?.generation?.approvedRuleIndices || []);
908
+ const unapproved = rules
909
+ .map((_, i) => i)
910
+ .filter((i) => !approved.has(i));
911
+ if (unapproved.length > 0) {
912
+ throw new Error(`Cannot generate code: unapproved rule indices: ${unapproved.join(', ')}. Run: dawn strategy rules ${conversationId} approve-all`);
913
+ }
914
+ const result = (await apiFetch(config, `/v2/strategy/generate-code/${latestVersion.id}`, {
915
+ method: 'POST',
916
+ }));
917
+ console.log(result?.message ||
918
+ `Code generation requested for version ${latestVersion.id}.`);
919
+ return;
920
+ }
921
+ if (subcommand === '__raw-code') {
922
+ const conversationId = rest[0];
923
+ const { flags } = parseFlags(rest.slice(1));
924
+ if (!conversationId) {
925
+ throw new Error('Usage: dawn strategy code <id> export [--out <path>] [--json]');
926
+ }
927
+ const latestVersionWithCode = await apiFetch(config, `/conversations/${conversationId}/latest-version?withCode=true`);
928
+ const codeMap = extractCodeMap(latestVersionWithCode);
929
+ if (!codeMap) {
930
+ throw new Error('No raw code payload found on latest strategy version.');
931
+ }
932
+ const wantsJson = flags.json === true ||
933
+ (typeof flags.out === 'string' && flags.out.toLowerCase().endsWith('.json'));
934
+ const defaultPath = wantsJson
935
+ ? `dawn-strategy-${conversationId}-code.json`
936
+ : `dawn-strategy-${conversationId}.py`;
937
+ const outPath = typeof flags.out === 'string' ? flags.out : defaultPath;
938
+ const absolutePath = path.resolve(process.cwd(), outPath);
939
+ if (wantsJson) {
940
+ await fs.writeFile(absolutePath, JSON.stringify(codeMap, null, 2));
941
+ console.log(`Saved raw strategy code map to ${absolutePath}`);
942
+ return;
943
+ }
944
+ const pythonEntry = codeMap['strategy.py'] ||
945
+ Object.entries(codeMap).find(([filename]) => filename.endsWith('.py'))?.[1];
946
+ if (!pythonEntry) {
947
+ throw new Error('No Python source file found in strategy code map. Re-run with --json to inspect all files.');
948
+ }
949
+ await fs.writeFile(absolutePath, pythonEntry);
950
+ console.log(`Saved strategy Python file to ${absolutePath}`);
951
+ return;
952
+ }
953
+ if (subcommand === '__edit') {
954
+ const conversationId = rest[0];
955
+ const text = rest.slice(1).join(' ').trim();
956
+ if (!conversationId || !text) {
957
+ throw new Error('Usage: dawn strategy revise <id> <text>');
958
+ }
959
+ await apiFetch(config, `/conversations/${conversationId}/messages`, {
960
+ method: 'POST',
961
+ body: JSON.stringify({ userMessage: text }),
962
+ });
963
+ console.log(`Sent edit request to strategy ${conversationId}.`);
964
+ return;
965
+ }
966
+ if (subcommand === 'launch') {
967
+ const conversationId = rest[0];
968
+ const { flags } = parseFlags(rest.slice(1));
969
+ if (!conversationId) {
970
+ throw new Error('Usage: dawn strategy launch <id> --budget <usd> [--live] [--hours N]');
971
+ }
972
+ if (typeof flags.budget !== 'string') {
973
+ throw new Error('Missing required --budget <usd>. Example: dawn strategy launch 123 --budget 50 --hours 24');
974
+ }
975
+ const budget = Number(flags.budget);
976
+ if (!Number.isFinite(budget) || budget <= 0) {
977
+ throw new Error('--budget must be a positive number.');
978
+ }
979
+ const isPaper = !flags.live;
980
+ const vaultType = isPaper ? 'paper' : 'strategy';
981
+ const vaultsResponse = (await apiFetch(config, `/v1/vaults/all/${vaultType}?isArchived=false`));
982
+ const vaults = vaultsResponse?.vaults || [];
983
+ if (vaults.length === 0) {
984
+ throw new Error(`No ${vaultType} vault found. Create one in the Dawn app before launching.`);
985
+ }
986
+ const vault = vaults[0];
987
+ const latestVersion = await getLatestStrategyVersion(config, conversationId, true);
988
+ if (!latestVersion?.id) {
989
+ throw new Error(`No code-ready strategy version found. Run: dawn strategy rules ${conversationId} approve-all, then dawn strategy code ${conversationId} generate.`);
990
+ }
991
+ const configuredBudget = Number(latestVersion.strategy?.initialAmount);
992
+ if (!Number.isFinite(configuredBudget) || configuredBudget <= 0) {
993
+ throw new Error(`Latest strategy version ${latestVersion.id} is missing a valid initialAmount. Update strategy rules/spec and regenerate code before launch.`);
994
+ }
995
+ if (Math.abs(configuredBudget - budget) > 1e-9) {
996
+ throw new Error(`Budget mismatch: launch budget is ${budget}, but latest version initialAmount is ${configuredBudget}. Re-run with --budget ${configuredBudget} or update/regenerate the strategy amount first.`);
997
+ }
998
+ const hours = Number(flags.hours || 24);
999
+ if (!Number.isFinite(hours) || hours <= 0) {
1000
+ throw new Error('--hours must be a positive number.');
1001
+ }
1002
+ const terminateAt = new Date(Date.now() + hours * 60 * 60 * 1000).toISOString();
1003
+ const result = (await apiFetch(config, `/strategy/${conversationId}/run-strategy-v2`, {
1004
+ method: 'POST',
1005
+ body: JSON.stringify({
1006
+ strategyVersionId: latestVersion.id,
1007
+ isPaper,
1008
+ terminateAt,
1009
+ walletId: vault.id,
1010
+ }),
1011
+ }));
1012
+ console.log(`Launched strategy. Strategy ID: ${result?.strategy?.id ?? 'unknown'}${isPaper ? ' (paper)' : ' (live)'}`);
1013
+ return;
1014
+ }
1015
+ if (subcommand === '__status') {
1016
+ const conversationId = rest[0];
1017
+ if (!conversationId) {
1018
+ throw new Error('Usage: dawn strategy status <id>');
1019
+ }
1020
+ const [conversationRaw, latestVersionRaw, strategiesRaw] = await Promise.all([
1021
+ apiFetch(config, `/conversations/${conversationId}`),
1022
+ apiFetch(config, `/conversations/${conversationId}/latest-version?withCode=false`),
1023
+ apiFetch(config, `/strategy/conversation/${conversationId}`),
1024
+ ]);
1025
+ const conversation = conversationRaw;
1026
+ const latestVersion = (latestVersionRaw || null);
1027
+ const strategies = Array.isArray(strategiesRaw)
1028
+ ? strategiesRaw
1029
+ : [];
1030
+ let generationStatus = null;
1031
+ if (latestVersion?.id) {
1032
+ try {
1033
+ const generationRaw = (await apiFetch(config, `/v2/strategy/generation-status/${latestVersion.id}`));
1034
+ generationStatus = generationRaw.generationStatus || null;
1035
+ }
1036
+ catch {
1037
+ // Generation status endpoint is best-effort; latest version metadata remains fallback.
1038
+ }
1039
+ }
1040
+ printConversationStatus(conversationId, conversation, latestVersion, generationStatus, strategies);
1041
+ return;
1042
+ }
1043
+ throw new Error('Unknown strategy command.');
1044
+ }
1045
+ async function handleAgent(args) {
1046
+ if (args.includes('--help') || args.includes('-h')) {
1047
+ console.log(`Usage:
1048
+ dawn agent list
1049
+ dawn agent <id>
1050
+ dawn agent stop <id>`);
1051
+ return;
1052
+ }
1053
+ const config = await loadConfig();
1054
+ if (!getAuthToken(config)) {
1055
+ throw new Error('Not authenticated. Run: dawn auth login');
1056
+ }
1057
+ const [subcommand, ...rest] = args;
1058
+ if (!subcommand) {
1059
+ throw new Error('Missing agent command.');
1060
+ }
1061
+ if (subcommand === 'list') {
1062
+ const data = await apiFetch(config, '/conversations?isArchived=false');
1063
+ renderConversationList(data);
1064
+ return;
1065
+ }
1066
+ if (subcommand === 'stop') {
1067
+ const conversationId = rest[0];
1068
+ if (!conversationId) {
1069
+ throw new Error('Usage: dawn agent stop <id>');
1070
+ }
1071
+ const strategies = (await apiFetch(config, `/strategy/conversation/${conversationId}`));
1072
+ if (!Array.isArray(strategies) || strategies.length === 0) {
1073
+ throw new Error('No strategies found for this agent.');
1074
+ }
1075
+ const running = strategies.find((s) => s?.isRunning) ||
1076
+ strategies.find((s) => s?.status === 'running') ||
1077
+ strategies[0];
1078
+ const strategyId = running?.id;
1079
+ if (!strategyId) {
1080
+ throw new Error('Could not determine strategy ID to stop.');
1081
+ }
1082
+ await apiFetch(config, `/strategy/${strategyId}/terminate`, { method: 'PATCH' });
1083
+ console.log(`Stopped strategy ${strategyId} for agent ${conversationId}.`);
1084
+ return;
1085
+ }
1086
+ if (args.length === 1) {
1087
+ const [conversation, strategies] = await Promise.all([
1088
+ apiFetch(config, `/conversations/${subcommand}`),
1089
+ apiFetch(config, `/strategy/conversation/${subcommand}`),
1090
+ ]);
1091
+ const conversationSummary = conversation;
1092
+ const strategySummaries = Array.isArray(strategies)
1093
+ ? strategies
1094
+ : [];
1095
+ renderAgentOverview(subcommand, conversationSummary, strategySummaries);
1096
+ return;
1097
+ }
1098
+ throw new Error('Unknown agent command.');
1099
+ }
1100
+ async function handleRun(args) {
1101
+ if (args.includes('--help') || args.includes('-h')) {
1102
+ console.log(`Usage:
1103
+ dawn run list
1104
+ dawn run status <id>
1105
+ dawn run logs <id> [--limit N]
1106
+ dawn run stop <id>`);
1107
+ return;
1108
+ }
1109
+ const [subcommand, ...rest] = args;
1110
+ if (!subcommand) {
1111
+ throw new Error('Missing run command.');
1112
+ }
1113
+ if (subcommand === 'list') {
1114
+ await handleAgent(['list']);
1115
+ return;
1116
+ }
1117
+ if (subcommand === 'status') {
1118
+ const conversationId = rest[0];
1119
+ if (!conversationId) {
1120
+ throw new Error('Usage: dawn run status <id>');
1121
+ }
1122
+ await handleAgent([conversationId]);
1123
+ return;
1124
+ }
1125
+ if (subcommand === 'logs') {
1126
+ const { flags, positional } = parseFlags(rest);
1127
+ const conversationId = positional[0];
1128
+ if (!conversationId) {
1129
+ throw new Error('Usage: dawn run logs <id> [--limit N]');
1130
+ }
1131
+ const config = await loadConfig();
1132
+ if (!getAuthToken(config)) {
1133
+ throw new Error('Not authenticated. Run: dawn auth login');
1134
+ }
1135
+ const sessionLimit = Number(flags.limit || 10);
1136
+ // Find strategies for this conversation
1137
+ const strategies = (await apiFetch(config, `/strategy/conversation/${conversationId}`));
1138
+ if (!Array.isArray(strategies) || strategies.length === 0) {
1139
+ throw new Error(`No strategies found for conversation ${conversationId}.`);
1140
+ }
1141
+ // Prefer the running strategy, otherwise most recent
1142
+ const strategy = strategies.find((s) => s.isRunning) || strategies[0];
1143
+ const strategyId = strategy.id;
1144
+ console.log(`Strategy ${strategyId} (${strategy.strategyType || 'unknown'}${strategy.isRunning ? ', running' : ', stopped'})`);
1145
+ console.log('');
1146
+ // Fetch sessions
1147
+ const sessionsResp = (await apiFetch(config, `/strategy/${strategyId}/sessions?limit=${sessionLimit}&offset=0`));
1148
+ const sessions = sessionsResp.sessions || [];
1149
+ if (sessions.length === 0) {
1150
+ console.log('No execution sessions found yet.');
1151
+ return;
1152
+ }
1153
+ console.log(`Sessions: ${sessions.length} of ${sessionsResp.total} total\n`);
1154
+ // Fetch details for each session
1155
+ for (const session of sessions) {
1156
+ const details = (await apiFetch(config, `/strategy/session/${session.id}/details`));
1157
+ console.log(`--- Session ${session.id} (${formatTimestamp(session.createdAt)}) ---`);
1158
+ if (details.messages && details.messages.length > 0) {
1159
+ for (const msg of details.messages) {
1160
+ const ts = formatTimestamp(msg.createdAt);
1161
+ // Try to extract structured logs from JSON content
1162
+ let parsed = null;
1163
+ try {
1164
+ parsed =
1165
+ typeof msg.content === 'string'
1166
+ ? JSON.parse(msg.content)
1167
+ : msg.content;
1168
+ }
1169
+ catch {
1170
+ // not JSON
1171
+ }
1172
+ if (parsed) {
1173
+ // Extract stdout from execution_logs or execution_result messages
1174
+ const logType = parsed.type;
1175
+ if (logType === 'execution_logs' || logType === 'execution_result') {
1176
+ const logs = (logType === 'execution_logs'
1177
+ ? parsed.logs
1178
+ : null) ||
1179
+ parsed.result?.logs?.stdout;
1180
+ if (Array.isArray(logs)) {
1181
+ for (const entry of logs) {
1182
+ if (typeof entry === 'string') {
1183
+ // execution_result stdout array
1184
+ process.stdout.write(entry);
1185
+ }
1186
+ else if (entry && typeof entry === 'object' && 'message' in entry) {
1187
+ // execution_logs array
1188
+ process.stdout.write(entry.message);
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+ else if (msg.role === 'tool') {
1194
+ // tool messages contain the run_code_v2 output; skip (redundant with execution_logs)
1195
+ continue;
1196
+ }
1197
+ else {
1198
+ console.log(`[${ts}] [${msg.role}] ${JSON.stringify(parsed).slice(0, 200)}`);
1199
+ }
1200
+ }
1201
+ else {
1202
+ const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
1203
+ console.log(`[${ts}] [${msg.role}] ${content}`);
1204
+ }
1205
+ }
1206
+ }
1207
+ else {
1208
+ console.log(' (no messages)');
1209
+ }
1210
+ if (details.transactions && details.transactions.length > 0) {
1211
+ console.log(' Transactions:');
1212
+ for (const tx of details.transactions) {
1213
+ const symbol = tx.asset?.symbol || '???';
1214
+ console.log(` ${tx.type} ${tx.amount} ${symbol} (${formatTimestamp(tx.createdAt)})`);
1215
+ }
1216
+ }
1217
+ console.log('');
1218
+ }
1219
+ return;
1220
+ }
1221
+ if (subcommand === 'stop') {
1222
+ const conversationId = rest[0];
1223
+ if (!conversationId) {
1224
+ throw new Error('Usage: dawn run stop <id>');
1225
+ }
1226
+ await handleAgent(['stop', conversationId]);
1227
+ return;
1228
+ }
1229
+ throw new Error(`Unknown run command: ${subcommand}`);
1230
+ }
1231
+ async function main() {
1232
+ const [cmd, ...args] = process.argv.slice(2);
1233
+ if (cmd === '--version' || cmd === '-V' || cmd === 'version') {
1234
+ console.log(`dawn-cli ${CLI_VERSION}`);
1235
+ return;
1236
+ }
1237
+ if (!cmd || cmd === '--help' || cmd === '-h') {
1238
+ printUsage();
1239
+ return;
1240
+ }
1241
+ try {
1242
+ if (cmd === 'auth') {
1243
+ await handleAuth(args);
1244
+ return;
1245
+ }
1246
+ if (cmd === 'account') {
1247
+ await handleAccount(args);
1248
+ return;
1249
+ }
1250
+ if (cmd === 'strategy') {
1251
+ await handleStrategy(args);
1252
+ return;
1253
+ }
1254
+ if (cmd === 'run') {
1255
+ await handleRun(args);
1256
+ return;
1257
+ }
1258
+ throw new Error(`Unknown command: ${cmd}`);
1259
+ }
1260
+ catch (error) {
1261
+ const message = error instanceof Error ? error.message : String(error);
1262
+ console.error(`Error: ${message}`);
1263
+ process.exit(1);
1264
+ }
1265
+ }
1266
+ await main();
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@dawnai/cli",
3
+ "version": "1.0.3",
4
+ "type": "module",
5
+ "description": "User-facing Dawn CLI",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "bin": {
11
+ "dawn": "dist/index.js"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md",
16
+ "CLAUDE.md",
17
+ "postinstall.js"
18
+ ],
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "postinstall": "node postinstall.js",
25
+ "prepare": "npm run build",
26
+ "prepack": "npm run build",
27
+ "dev": "tsx src/index.ts",
28
+ "start": "node dist/index.js",
29
+ "test": "echo \"No tests configured\"",
30
+ "pack:check": "npm pack --dry-run"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^24.3.0",
34
+ "tsx": "^4.20.3",
35
+ "typescript": "^5.9.2"
36
+ }
37
+ }
package/postinstall.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Only show the banner for global installs (skip when installing as a dependency).
4
+ const isGlobal =
5
+ process.env.npm_config_global === 'true' ||
6
+ (process.env.npm_lifecycle_event === 'postinstall' &&
7
+ !process.env.npm_package_name);
8
+
9
+ if (!isGlobal) {
10
+ process.exit(0);
11
+ }
12
+
13
+ console.log(`
14
+ @dawnai/cli installed successfully.
15
+
16
+ Get started:
17
+ dawn --version Check installed version
18
+ dawn auth login Authenticate (opens browser)
19
+ dawn --help Show all commands
20
+
21
+ Headless / CI / agent environments:
22
+ export DAWN_JWT_TOKEN="<your-token>"
23
+
24
+ Config is stored at ~/.dawn-cli/config.json
25
+ `);