@darwin.finance/cli 1.0.0 → 1.0.1
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 +7 -3
- package/dist/darwin.js +1453 -483
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/darwin.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
3
|
var __export = (target, all) => {
|
|
8
4
|
for (var name in all)
|
|
9
5
|
__defProp(target, name, {
|
|
10
6
|
get: all[name],
|
|
11
7
|
enumerable: true,
|
|
12
8
|
configurable: true,
|
|
13
|
-
set:
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
14
10
|
});
|
|
15
11
|
};
|
|
16
12
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -68,7 +64,7 @@ Waiting for you to approve… (the code expires in ${minutes} minutes)`,
|
|
|
68
64
|
updateRequired: (minCli) => `This version of the Darwin CLI is no longer supported. Update to ${minCli} or later: ${INSTALL_LINE}`,
|
|
69
65
|
snapshotUsed: (realm) => `Couldn't load the latest command list from ${realm}; using the one built into this version. Some newer commands may be missing.`,
|
|
70
66
|
mcpNeedsKey: "`darwin mcp` needs an API key: an agent's API key, or one from the MCP page. Chat apps connect to Darwin directly instead.",
|
|
71
|
-
spotSent: (amount, sell, forTok, agent, nonce) => `Sent: sell ${amount} ${sell} for ${forTok} on ${agent} ·
|
|
67
|
+
spotSent: (amount, sell, forTok, agent, orderId, nonce) => `Sent: sell ${amount} ${sell} for ${forTok} on ${agent} · ${orderId ? `order ${orderId} · ` : ""}nonce ${nonce}`,
|
|
72
68
|
envKeySet: "DARWIN_API_KEY is set, so this terminal already has a key and there is nowhere to save a new one. Unset it to log in, or keep using it.",
|
|
73
69
|
allAgentsOff: "API keys for all your agents aren't available on this site yet. Use an API key for one agent.",
|
|
74
70
|
printConfigNpx: "Install the Darwin CLI first (`npm i -g @darwin.finance/cli`), then run `darwin mcp --print-config` again. A saved config must point at an installed copy, not npx.",
|
|
@@ -253,7 +249,7 @@ function compareVersions(a, b) {
|
|
|
253
249
|
return x[i] < y[i] ? -1 : 1;
|
|
254
250
|
return 0;
|
|
255
251
|
}
|
|
256
|
-
var VERSION = "1.0.
|
|
252
|
+
var VERSION = "1.0.1";
|
|
257
253
|
|
|
258
254
|
// src/http.ts
|
|
259
255
|
function vendor(env) {
|
|
@@ -504,16 +500,16 @@ async function listAgents(ctx, s, opts = {}) {
|
|
|
504
500
|
const list = res.json?.agents;
|
|
505
501
|
if (res.status !== 200 || !Array.isArray(list))
|
|
506
502
|
throw new CliError(EXIT.unexpected, `Couldn't list your agents (HTTP ${res.status}).`, "bad_response");
|
|
507
|
-
const
|
|
503
|
+
const agents2 = list.filter((a) => !!a && typeof a === "object").map((a) => ({
|
|
508
504
|
id: String(a.id ?? ""),
|
|
509
505
|
name: clean(unwrap(a.name)).slice(0, 120),
|
|
510
|
-
solanaAddress: typeof a.solanaAddress === "string" &&
|
|
506
|
+
solanaAddress: typeof a.solanaAddress === "string" && BASE582.test(a.solanaAddress) ? a.solanaAddress : undefined,
|
|
511
507
|
status: a.status === "active" || a.status === "paused" ? a.status : undefined
|
|
512
508
|
})).filter((a) => isAgentId(a.id));
|
|
513
509
|
try {
|
|
514
|
-
writePrivate(file, JSON.stringify({ at: ctx.now(), agents }));
|
|
510
|
+
writePrivate(file, JSON.stringify({ at: ctx.now(), agents: agents2 }));
|
|
515
511
|
} catch {}
|
|
516
|
-
return
|
|
512
|
+
return agents2;
|
|
517
513
|
}
|
|
518
514
|
async function resolveAgent(ctx, s, flag) {
|
|
519
515
|
const raw = (flag ?? ctx.env.DARWIN_AGENT ?? "").trim();
|
|
@@ -530,19 +526,19 @@ async function resolveAgent(ctx, s, flag) {
|
|
|
530
526
|
const want = raw || s.profile?.default_agent || "";
|
|
531
527
|
if (!want)
|
|
532
528
|
throw new CliError(EXIT.usage, copy.needAgent, "agent_required");
|
|
533
|
-
const
|
|
534
|
-
const byId =
|
|
529
|
+
const agents2 = await listAgents(ctx, s);
|
|
530
|
+
const byId = agents2.find((a) => a.id === want);
|
|
535
531
|
if (byId)
|
|
536
532
|
return byId.id;
|
|
537
|
-
const byName =
|
|
533
|
+
const byName = agents2.filter((a) => a.name.trim().toLowerCase() === want.toLowerCase());
|
|
538
534
|
if (byName.length === 1)
|
|
539
535
|
return byName[0].id;
|
|
540
536
|
if (byName.length > 1) {
|
|
541
537
|
throw new CliError(EXIT.usage, `More than one of your agents is called that; pass its id with --agent: ${byName.map((a) => a.id).join(", ")}`, "agent_ambiguous", { candidates: byName });
|
|
542
538
|
}
|
|
543
|
-
throw new CliError(EXIT.usage, "No active agent of yours matches that (a paused, stopped or archived agent isn't listed). Your agents: `darwin agents`.", "agent_not_found", { candidates:
|
|
539
|
+
throw new CliError(EXIT.usage, "No active agent of yours matches that (a paused, stopped or archived agent isn't listed). Your agents: `darwin agents`.", "agent_not_found", { candidates: agents2.map((a) => ({ id: a.id, name: a.name })) });
|
|
544
540
|
}
|
|
545
|
-
var
|
|
541
|
+
var BASE582, unwrap = (v) => v && typeof v === "object" && ("untrusted" in v) ? String(v.untrusted) : typeof v === "string" ? v : "";
|
|
546
542
|
var init_agents = __esm(() => {
|
|
547
543
|
init_context();
|
|
548
544
|
init_copy();
|
|
@@ -550,14 +546,14 @@ var init_agents = __esm(() => {
|
|
|
550
546
|
init_http();
|
|
551
547
|
init_config();
|
|
552
548
|
init_redact();
|
|
553
|
-
|
|
549
|
+
BASE582 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
554
550
|
});
|
|
555
551
|
|
|
556
552
|
// src/bin.ts
|
|
557
553
|
import { spawn as spawn2 } from "node:child_process";
|
|
558
554
|
import { existsSync } from "node:fs";
|
|
559
555
|
import { homedir as homedir2 } from "node:os";
|
|
560
|
-
import { join as
|
|
556
|
+
import { join as join10 } from "node:path";
|
|
561
557
|
import { createInterface } from "node:readline";
|
|
562
558
|
import { fileURLToPath } from "node:url";
|
|
563
559
|
|
|
@@ -590,7 +586,7 @@ function osKeychain(platform) {
|
|
|
590
586
|
}
|
|
591
587
|
|
|
592
588
|
// src/main.ts
|
|
593
|
-
import { join as
|
|
589
|
+
import { join as join9 } from "node:path";
|
|
594
590
|
|
|
595
591
|
// src/args.ts
|
|
596
592
|
init_context();
|
|
@@ -3332,7 +3328,7 @@ function cell(v) {
|
|
|
3332
3328
|
if (v === null || v === undefined)
|
|
3333
3329
|
return "";
|
|
3334
3330
|
if (isUntrusted(v))
|
|
3335
|
-
return
|
|
3331
|
+
return cell(v.untrusted);
|
|
3336
3332
|
if (typeof v === "string")
|
|
3337
3333
|
return clean(v);
|
|
3338
3334
|
if (typeof v === "number" || typeof v === "boolean")
|
|
@@ -3375,189 +3371,1101 @@ function table(rows, indent) {
|
|
|
3375
3371
|
return [line(cols.map(clean)), line(widths.map((w) => "-".repeat(w))), ...cells.map(line)];
|
|
3376
3372
|
}
|
|
3377
3373
|
|
|
3378
|
-
// src/
|
|
3379
|
-
init_realms();
|
|
3374
|
+
// src/human.ts
|
|
3380
3375
|
init_redact();
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
if (
|
|
3389
|
-
return
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3376
|
+
function txt(v) {
|
|
3377
|
+
if (v === null || v === undefined)
|
|
3378
|
+
return "";
|
|
3379
|
+
if (isUntrusted(v))
|
|
3380
|
+
return txt(v.untrusted);
|
|
3381
|
+
if (typeof v === "string")
|
|
3382
|
+
return clean(v).replace(/\s+/g, " ").trim();
|
|
3383
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint")
|
|
3384
|
+
return String(v);
|
|
3385
|
+
return "";
|
|
3386
|
+
}
|
|
3387
|
+
function label(v, max = 60) {
|
|
3388
|
+
const t = txt(v);
|
|
3389
|
+
return t.length > max ? `${t.slice(0, max - 1)}…` : t;
|
|
3390
|
+
}
|
|
3391
|
+
var obj = (v) => v && typeof v === "object" && !Array.isArray(v) && !isUntrusted(v) ? v : {};
|
|
3392
|
+
var arr = (v) => Array.isArray(v) ? v : [];
|
|
3393
|
+
function toDate(v) {
|
|
3394
|
+
let d = null;
|
|
3395
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0)
|
|
3396
|
+
d = new Date(v < 100000000000 ? v * 1000 : v);
|
|
3397
|
+
else if (typeof v === "string" && /^[0-9]{9,13}$/.test(v))
|
|
3398
|
+
return toDate(Number(v));
|
|
3399
|
+
else if (typeof v === "string" && /^\d{4}-\d{2}-\d{2}T/.test(v))
|
|
3400
|
+
d = new Date(v);
|
|
3401
|
+
if (!d || Number.isNaN(d.getTime()))
|
|
3402
|
+
return null;
|
|
3403
|
+
const y = d.getUTCFullYear();
|
|
3404
|
+
return y >= 2000 && y < 2100 ? d : null;
|
|
3394
3405
|
}
|
|
3395
|
-
function
|
|
3396
|
-
|
|
3397
|
-
if (n !== undefined && !PROFILE_RE.test(n))
|
|
3398
|
-
throw new CliError(EXIT.usage, "A profile name is 1–40 of a–z, 0–9, _ and -, starting with a letter or digit.", "usage");
|
|
3399
|
-
return n;
|
|
3406
|
+
function clock(d) {
|
|
3407
|
+
return d.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", second: "2-digit" });
|
|
3400
3408
|
}
|
|
3401
|
-
function
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3409
|
+
function when(v, opts = {}) {
|
|
3410
|
+
const d = toDate(v);
|
|
3411
|
+
if (!d)
|
|
3412
|
+
return "";
|
|
3413
|
+
const parts = Object.fromEntries(new Intl.DateTimeFormat("en-US", { weekday: "short", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", timeZoneName: "short" }).formatToParts(d).map((p) => [p.type, p.value]));
|
|
3414
|
+
return `${opts.weekday ? `${parts.weekday}, ` : ""}${parts.month} ${parts.day}, ${parts.hour}:${parts.minute} ${parts.dayPeriod} ${parts.timeZoneName}`;
|
|
3415
|
+
}
|
|
3416
|
+
function num(v) {
|
|
3417
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
3418
|
+
return String(v);
|
|
3419
|
+
const t = txt(v);
|
|
3420
|
+
if (!/^-?\d+(\.\d+)?$/.test(t))
|
|
3421
|
+
return t;
|
|
3422
|
+
return t.includes(".") ? t.replace(/0+$/, "").replace(/\.$/, "") : t;
|
|
3423
|
+
}
|
|
3424
|
+
function usd(v) {
|
|
3425
|
+
const n = typeof v === "number" ? v : typeof v === "string" && /^-?\d+(\.\d+)?$/.test(v.trim()) ? Number(v) : NaN;
|
|
3426
|
+
if (!Number.isFinite(n))
|
|
3427
|
+
return "";
|
|
3428
|
+
const abs = Math.abs(n);
|
|
3429
|
+
const dollars = (x) => x.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
3430
|
+
let s;
|
|
3431
|
+
if (abs === 0 || abs >= 0.995)
|
|
3432
|
+
s = dollars(abs);
|
|
3433
|
+
else if (abs < 0.000000000001)
|
|
3434
|
+
s = abs.toExponential(3);
|
|
3435
|
+
else {
|
|
3436
|
+
const places = Math.max(2, 3 - Math.floor(Math.log10(abs)));
|
|
3437
|
+
s = abs.toFixed(places).replace(/0+$/, "");
|
|
3438
|
+
const [, frac = ""] = s.split(".");
|
|
3439
|
+
if (frac.length < 2)
|
|
3440
|
+
s = `${s.split(".")[0]}.${frac.padEnd(2, "0")}`;
|
|
3410
3441
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3442
|
+
return `${n < 0 ? "−" : ""}$${s}`;
|
|
3443
|
+
}
|
|
3444
|
+
var HIDDEN_KEYS = new Set(["welcome", "next", "instructions", "instruction", "tellYourUser", "retrySafe", "retryable", "refusal", "ok", "untrustedNote"]);
|
|
3445
|
+
var TIME_KEY = /^(?:asOf(?:Ms|Secs?)?|[a-z][A-Za-z0-9]*(?:At|AtMs|AtSecs|AtSec|AsOf|AsOfMs|AsOfSecs|Ts|Time|Timestamp))$/;
|
|
3446
|
+
function words(key) {
|
|
3447
|
+
return clean(key).replace(/Iso$/, "").replace(/(Ms|Secs?)$/, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/_/g, " ").toLowerCase();
|
|
3448
|
+
}
|
|
3449
|
+
function tidy(v, depth = 0) {
|
|
3450
|
+
if (depth > 12)
|
|
3451
|
+
return null;
|
|
3452
|
+
if (isUntrusted(v))
|
|
3453
|
+
return tidy(v.untrusted, depth + 1);
|
|
3454
|
+
if (Array.isArray(v))
|
|
3455
|
+
return v.map((x) => tidy(x, depth + 1));
|
|
3456
|
+
if (!v || typeof v !== "object")
|
|
3457
|
+
return v;
|
|
3458
|
+
const src = v;
|
|
3459
|
+
const out = {};
|
|
3460
|
+
for (const [k, x] of Object.entries(src)) {
|
|
3461
|
+
if (HIDDEN_KEYS.has(k))
|
|
3462
|
+
continue;
|
|
3463
|
+
const has2 = (twin) => src[twin] !== null && src[twin] !== undefined && src[twin] !== "";
|
|
3464
|
+
if (/Atoms$/.test(k) && k !== "atoms" && (has2(k.slice(0, -5)) || has2(`${k.slice(0, -5)}Amount`)))
|
|
3465
|
+
continue;
|
|
3466
|
+
if (k === "atoms" && (has2("amount") || has2("uiAmount")))
|
|
3467
|
+
continue;
|
|
3468
|
+
if (/Iso$/.test(k) && typeof x === "string") {
|
|
3469
|
+
const base = k.slice(0, -3);
|
|
3470
|
+
const label2 = [base, `${base}Ms`, `${base}Sec`, `${base}Secs`].find((b) => (b in src)) ?? base;
|
|
3471
|
+
if (!(label2 in out))
|
|
3472
|
+
out[label2] = when(x) || x;
|
|
3473
|
+
continue;
|
|
3474
|
+
}
|
|
3475
|
+
if (TIME_KEY.test(k) && toDate(x)) {
|
|
3476
|
+
out[k] = when(x);
|
|
3477
|
+
continue;
|
|
3478
|
+
}
|
|
3479
|
+
out[k] = tidy(x, depth + 1);
|
|
3416
3480
|
}
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3481
|
+
return out;
|
|
3482
|
+
}
|
|
3483
|
+
function genericLines(v) {
|
|
3484
|
+
return renderText(humanKeys(tidy(v)));
|
|
3485
|
+
}
|
|
3486
|
+
function humanKeys(v) {
|
|
3487
|
+
if (Array.isArray(v))
|
|
3488
|
+
return v.map(humanKeys);
|
|
3489
|
+
if (!v || typeof v !== "object")
|
|
3490
|
+
return v;
|
|
3491
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [words(k), humanKeys(x)]));
|
|
3424
3492
|
}
|
|
3425
3493
|
|
|
3426
|
-
// src/
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
const
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3494
|
+
// src/help.ts
|
|
3495
|
+
init_redact();
|
|
3496
|
+
init_copy();
|
|
3497
|
+
|
|
3498
|
+
// src/market.ts
|
|
3499
|
+
init_context();
|
|
3500
|
+
init_http();
|
|
3501
|
+
init_realms();
|
|
3502
|
+
var MARKET_STATUS_PATH = "/api/agent/v1/market-status/us-equities";
|
|
3503
|
+
var MARKET_STATUS_TOOL = "get_us_market_status";
|
|
3504
|
+
var MARKET_STATUS_HELP = "Is the US stock market open right now? Shows the session, why it's closed (weekend, holiday, early close), the next open and close in your local time, and whether Darwin accepts agent stock orders now. Tokenized stocks trade 24/7, but liquidity is much lower when the US market is closed. No API key needed.";
|
|
3505
|
+
function localTime(iso) {
|
|
3506
|
+
if (typeof iso !== "string")
|
|
3507
|
+
return null;
|
|
3508
|
+
return when(iso, { weekday: true }) || null;
|
|
3509
|
+
}
|
|
3510
|
+
var SESSION = { pre_market: "pre-market", regular: "open (regular session)", after_hours: "after hours", closed: "closed" };
|
|
3511
|
+
var SESSION_ENDS = { pre_market: "Regular session opens", regular: "Regular session closes", after_hours: "After hours ends", closed: "Pre-market opens" };
|
|
3512
|
+
var REASON_REPEATS = { pre_market: "pre_market", after_hours: "after_hours" };
|
|
3513
|
+
function marketHeadline(s) {
|
|
3514
|
+
const session = SESSION[String(s.session)] ?? cell(s.session).replace(/_/g, " ");
|
|
3515
|
+
const reason = typeof s.reason === "string" ? s.reason : "";
|
|
3516
|
+
let why = "";
|
|
3517
|
+
if (reason && s.session !== "regular" && REASON_REPEATS[reason] !== s.session) {
|
|
3518
|
+
const holiday = reason === "holiday" && typeof s.holidayName === "string" && s.holidayName ? `: ${cell(s.holidayName)}` : "";
|
|
3519
|
+
why = ` (${cell(reason).replace(/_/g, " ")}${holiday})`;
|
|
3520
|
+
}
|
|
3521
|
+
const early = s.earlyClose === true && reason !== "early_close" ? " — early close today" : "";
|
|
3522
|
+
return `US stock market: ${session}${why}${early}`;
|
|
3523
|
+
}
|
|
3524
|
+
async function cmdMarketStatus(ctx, p) {
|
|
3525
|
+
onlyFlags(p, ["beta", "prod", "json", "format", "quiet", "no-color", "help"], "market-status");
|
|
3526
|
+
if (p.positionals.length > 1)
|
|
3527
|
+
throw new CliError(EXIT.usage, "`darwin market-status` takes no arguments.", "usage");
|
|
3528
|
+
let realm = "darwin.finance";
|
|
3529
|
+
if (has(p, "beta"))
|
|
3530
|
+
realm = "beta.darwin.finance";
|
|
3531
|
+
else if (!has(p, "prod") && ctx.env.DARWIN_REALM) {
|
|
3532
|
+
const r = parseRealm(ctx.env.DARWIN_REALM);
|
|
3533
|
+
if (!r)
|
|
3534
|
+
throw new CliError(EXIT.usage, "DARWIN_REALM must be prod or beta.", "usage");
|
|
3535
|
+
realm = r;
|
|
3456
3536
|
}
|
|
3457
|
-
|
|
3458
|
-
if (s.kind === "agents")
|
|
3459
|
-
warn(ctx, copy.revokeAllWarning);
|
|
3460
|
-
let keyName = "this API key";
|
|
3461
|
-
try {
|
|
3462
|
-
const w = await request(ctx, s.realm, "GET", "/api/agent/v1/tools/whoami", { key: s.key, timeoutMs: 15000 });
|
|
3463
|
-
const n = w.json?.name;
|
|
3464
|
-
if (w.status === 200 && typeof n === "string" && n)
|
|
3465
|
-
keyName = clean(n).slice(0, 120);
|
|
3466
|
-
} catch {}
|
|
3467
|
-
let confirmed = false;
|
|
3468
|
-
let networkFailed = false;
|
|
3537
|
+
let res;
|
|
3469
3538
|
try {
|
|
3470
|
-
|
|
3471
|
-
const j = res.json;
|
|
3472
|
-
confirmed = res.status === 200 && mediaType(res.contentType) === "application/json" && !!j && j.revoked === true && typeof j.keyId === "string" && typeof j.changed === "boolean" && Object.keys(j).length === 3;
|
|
3539
|
+
res = await request(ctx, realm, "GET", MARKET_STATUS_PATH, { timeoutMs: 15000 });
|
|
3473
3540
|
} catch (e) {
|
|
3474
|
-
if (
|
|
3475
|
-
throw e;
|
|
3476
|
-
|
|
3541
|
+
if (e instanceof NetworkError)
|
|
3542
|
+
throw new CliError(EXIT.network, e.message, "network_error");
|
|
3543
|
+
throw e;
|
|
3477
3544
|
}
|
|
3478
|
-
const
|
|
3479
|
-
if (
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3545
|
+
const s = res.json;
|
|
3546
|
+
if (res.status === 429)
|
|
3547
|
+
throw new CliError(EXIT.rateLimited, "Too many requests right now; try again in a minute.", "rate_limited");
|
|
3548
|
+
if (res.status !== 200 || !s || typeof s.open !== "boolean")
|
|
3549
|
+
throw new CliError(EXIT.unexpected, `${realm} didn't answer the market status (HTTP ${res.status}).`, "bad_response");
|
|
3550
|
+
if (wantsJson(ctx, { json: has(p, "json"), format: one(p, "format") })) {
|
|
3551
|
+
printJson(ctx, s);
|
|
3483
3552
|
return EXIT.ok;
|
|
3484
3553
|
}
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3554
|
+
say(ctx, marketHeadline(s));
|
|
3555
|
+
const line = (label2, v) => {
|
|
3556
|
+
const t = localTime(v);
|
|
3557
|
+
if (t)
|
|
3558
|
+
say(ctx, `${label2}: ${t}`);
|
|
3559
|
+
};
|
|
3560
|
+
line(SESSION_ENDS[String(s.session)] ?? "Session changes", s.sessionEndsAt);
|
|
3561
|
+
line("Next open", s.nextOpenAt);
|
|
3562
|
+
line("Next close", s.nextCloseAt);
|
|
3563
|
+
const a = s.agentStockOrders ?? {};
|
|
3564
|
+
if (a.accepted === true)
|
|
3565
|
+
say(ctx, `Darwin agent stock orders: accepted${localTime(a.closesAt) ? ` until ${localTime(a.closesAt)}` : ""}`);
|
|
3566
|
+
else if (a.accepted === false)
|
|
3567
|
+
say(ctx, `Darwin agent stock orders: not accepted now${localTime(a.opensAt) ? `; next accepted ${localTime(a.opensAt)}` : ""}`);
|
|
3568
|
+
say(ctx, "Tokenized stocks trade 24/7, but liquidity is much lower when the US market is closed.");
|
|
3569
|
+
return EXIT.ok;
|
|
3492
3570
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
delete c.profiles[name];
|
|
3511
|
-
if (c.default === name)
|
|
3512
|
-
c.default = Object.keys(c.profiles)[0] ?? "";
|
|
3513
|
-
return "ok";
|
|
3571
|
+
|
|
3572
|
+
// src/help.ts
|
|
3573
|
+
var LOCAL_DESCRIPTION = { [MARKET_STATUS_TOOL]: MARKET_STATUS_HELP };
|
|
3574
|
+
var TERMINAL_DESCRIPTION = {
|
|
3575
|
+
hello: "Says hello as this API key: which agent it acts for, the agent's wallet address and its Darwin page. Never counts against the transaction budget.",
|
|
3576
|
+
get_balances: "This agent's wallet balances, in whole tokens. Reporting: reads ONLY this agent's own account. Never counts against the transaction budget."
|
|
3577
|
+
};
|
|
3578
|
+
var describe = (t) => LOCAL_DESCRIPTION[t.name] ?? clean(t.description);
|
|
3579
|
+
function forTerminal(text, c) {
|
|
3580
|
+
let t = clean(text);
|
|
3581
|
+
const byName = new Map((c?.tools ?? []).map((x) => [x.name, `\`darwin ${x.cli.path.join(" ")}\``]));
|
|
3582
|
+
byName.set("get_api_reference", "`darwin docs`");
|
|
3583
|
+
t = t.replace(/(`?)\b([a-z][a-z0-9]*(?:_[a-z0-9]+)+)\b(`?)/g, (m, open, name, close) => {
|
|
3584
|
+
const cmd = byName.get(name);
|
|
3585
|
+
if (!cmd)
|
|
3586
|
+
return m;
|
|
3587
|
+
return open && close ? cmd : `${open}${cmd}${close}`;
|
|
3514
3588
|
});
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
if (outcome !== "ok") {
|
|
3520
|
-
warn(ctx, `Couldn't remove the API key for "${name}" from ${prof.store === "keychain" ? ctx.keychain.description : "its file"}, so the profile was kept. Unlock it and try again.`);
|
|
3521
|
-
return false;
|
|
3522
|
-
}
|
|
3523
|
-
if (!opts.quiet)
|
|
3524
|
-
say(ctx, copy.loggedOut(name, manageUrl(prof.realm, prof.agent_id)));
|
|
3525
|
-
return true;
|
|
3589
|
+
t = t.replace(/`darwin docs`\s*§\s?([0-9]{1,2}[a-z]?)/g, "`darwin docs $1`");
|
|
3590
|
+
t = t.replace(/§\s?([0-9]{1,2}[a-z]?)/g, "`darwin docs $1`");
|
|
3591
|
+
t = t.replace(/\(summary\.sell\.amount\)/g, "(the amount the quote shows)");
|
|
3592
|
+
return t;
|
|
3526
3593
|
}
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
}
|
|
3559
|
-
|
|
3560
|
-
|
|
3594
|
+
var describeForTerminal = (t, c) => LOCAL_DESCRIPTION[t.name] ?? TERMINAL_DESCRIPTION[t.name] ?? forTerminal(t.description, c).trim();
|
|
3595
|
+
var STATIC_HELP = {
|
|
3596
|
+
login: {
|
|
3597
|
+
usage: "darwin login [--beta] [--client-name <name>] [--reconnect] [--start | --wait | --with-key | --key-file <file> | --from-skill] [--profile <name>] [--store keychain|file]",
|
|
3598
|
+
summary: "Connect this terminal to a Darwin agent and keep its API key in your system's secret store.",
|
|
3599
|
+
flags: {
|
|
3600
|
+
beta: "Use beta.darwin.finance instead of darwin.finance.",
|
|
3601
|
+
"client-name": `What Darwin shows your owner as the app asking (never "Darwin"). Default: the coding agent you're in, else "Command line".`,
|
|
3602
|
+
reconnect: "Get a new API key for an agent you already have, instead of creating a new agent.",
|
|
3603
|
+
start: "Start pairing and print the link and code, then exit (for tools whose calls time out).",
|
|
3604
|
+
wait: "Wait for a pairing started with --start, and save the key.",
|
|
3605
|
+
"with-key": "Import an API key from stdin, or paste it at a hidden prompt.",
|
|
3606
|
+
"key-file": "Import the key file downloaded from the agent's Manage tab (add --delete-file to delete it after).",
|
|
3607
|
+
"from-skill": "Import the key the official Darwin skill (darwin.py) saved (add --delete-skill-copy to remove its copy).",
|
|
3608
|
+
"agent-id": "With --from-skill: which saved key, when there are several.",
|
|
3609
|
+
profile: "Profile name for this key (default: from the agent's name).",
|
|
3610
|
+
store: "keychain (default) or file — a plain file only your user can read.",
|
|
3611
|
+
"no-browser": "Don't open the link in a browser."
|
|
3612
|
+
}
|
|
3613
|
+
},
|
|
3614
|
+
logout: {
|
|
3615
|
+
usage: "darwin logout [--profile <name> | --all] [--revoke [--force-local]]",
|
|
3616
|
+
summary: "Remove the API key from this computer. With --revoke, Darwin revokes the key first.",
|
|
3617
|
+
flags: { revoke: "Ask Darwin to revoke this key everywhere, then remove it here.", all: "Every profile.", "force-local": "With --revoke: remove the local copy even if Darwin couldn't be reached." }
|
|
3618
|
+
},
|
|
3619
|
+
whoami: { usage: "darwin whoami [--profile <name>]", summary: "Which agent, site and API key this terminal uses (never any key characters).", flags: {} },
|
|
3620
|
+
profile: {
|
|
3621
|
+
usage: "darwin profile list | use <name> | rename <old> <new> | remove <name> | set-agent <name or id>",
|
|
3622
|
+
summary: "Manage profiles (one per API key). set-agent: the default agent for a key that trades for all your agents.",
|
|
3623
|
+
flags: {}
|
|
3624
|
+
},
|
|
3625
|
+
doctor: { usage: "darwin doctor", summary: "Check the install, the secret store, your profile and both Darwin sites.", flags: {} },
|
|
3626
|
+
api: { usage: "darwin api GET /api/agent/<path>", summary: "Read any agent API endpoint directly (GET only).", flags: {} },
|
|
3627
|
+
mcp: {
|
|
3628
|
+
usage: "darwin mcp [--profile <name>] | darwin mcp --print-config claude|cursor|gemini",
|
|
3629
|
+
summary: "Run Darwin as a local MCP server (stdio) for a desktop AI client, with the API key kept in your secret store.",
|
|
3630
|
+
flags: { "print-config": "Print the registration for a client, pointing at this installed copy." }
|
|
3631
|
+
},
|
|
3632
|
+
help: { usage: "darwin help [command] [--json]", summary: "This help. --json lists every command with its flags.", flags: {} },
|
|
3633
|
+
version: { usage: "darwin version", summary: "Print the Darwin CLI version.", flags: {} }
|
|
3634
|
+
};
|
|
3635
|
+
var GLOBAL_HELP = {
|
|
3636
|
+
json: "Print JSON (the default when output isn't a terminal).",
|
|
3637
|
+
profile: "Use this profile.",
|
|
3638
|
+
agent: "For an API key that trades for all your agents: which agent (name or id).",
|
|
3639
|
+
"dry-run": "Show what a command would send, and send nothing.",
|
|
3640
|
+
field: "For API fields without their own flag: --field name=value (repeatable).",
|
|
3641
|
+
quiet: "Less output."
|
|
3642
|
+
};
|
|
3643
|
+
function toolFlags(t) {
|
|
3644
|
+
const req = new Set((t.inputSchema.required ?? []).filter((r) => r !== t.idempotency?.field));
|
|
3645
|
+
return Object.entries(t.cli.args).map(([prop, a]) => {
|
|
3646
|
+
const p = t.inputSchema.properties?.[prop] ?? {};
|
|
3647
|
+
const type = Array.isArray(p.type) ? p.type.join("|") : p.type ?? "string";
|
|
3648
|
+
return { flag: `--${a.flag}`, property: prop, required: req.has(prop), type, description: clean(p.description ?? "") };
|
|
3649
|
+
});
|
|
3650
|
+
}
|
|
3651
|
+
function usageOf(t) {
|
|
3652
|
+
const pos = t.cli.positional.map((x) => `<${x}>`);
|
|
3653
|
+
const flags = toolFlags(t).map((f) => f.required ? `${f.flag} <${f.property}>` : `[${f.flag} <${f.property}>]`);
|
|
3654
|
+
const open = t.inputSchema.additionalProperties !== false && t.inputSchema.additionalProperties !== undefined;
|
|
3655
|
+
return ["darwin", ...t.cli.path, ...pos, ...flags, ...open ? ["[--field name=value]"] : []].join(" ");
|
|
3656
|
+
}
|
|
3657
|
+
function helpJson(c) {
|
|
3658
|
+
return {
|
|
3659
|
+
version: VERSION,
|
|
3660
|
+
realm: c.realm,
|
|
3661
|
+
credential: c.credential,
|
|
3662
|
+
catalogVersion: c.catalogVersion,
|
|
3663
|
+
static: Object.entries(STATIC_HELP).map(([command, h]) => ({ command, usage: h.usage, summary: h.summary, flags: h.flags })),
|
|
3664
|
+
globalFlags: GLOBAL_HELP,
|
|
3665
|
+
commands: c.tools.map((t) => ({
|
|
3666
|
+
command: t.cli.path.join(" "),
|
|
3667
|
+
aliases: t.cli.aliases.map((a) => a.join(" ")),
|
|
3668
|
+
tool: t.name,
|
|
3669
|
+
title: clean(t.title),
|
|
3670
|
+
write: t.write,
|
|
3671
|
+
costsTx: t.costsTx,
|
|
3672
|
+
usage: usageOf(t),
|
|
3673
|
+
positional: t.cli.positional,
|
|
3674
|
+
flags: toolFlags(t),
|
|
3675
|
+
description: describe(t),
|
|
3676
|
+
...t.deprecated ? { deprecated: t.deprecated } : {}
|
|
3677
|
+
}))
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3680
|
+
function printOverview(ctx, c, json) {
|
|
3681
|
+
if (json) {
|
|
3682
|
+
printJson(ctx, helpJson(c));
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
say(ctx, `Darwin CLI ${VERSION} — trade and check your Darwin agent from a terminal.`);
|
|
3686
|
+
say(ctx, "");
|
|
3687
|
+
say(ctx, "Darwin commands (from Darwin's catalogue; W = changes something):");
|
|
3688
|
+
const width = Math.max(...c.tools.map((t) => t.cli.path.join(" ").length)) + 2;
|
|
3689
|
+
for (const t of c.tools)
|
|
3690
|
+
say(ctx, ` ${t.cli.path.join(" ").padEnd(width)}${t.write ? "W " : " "}${forTerminal(t.title, c)}`);
|
|
3691
|
+
say(ctx, "");
|
|
3692
|
+
say(ctx, "This CLI's own commands:");
|
|
3693
|
+
for (const [k, h] of Object.entries(STATIC_HELP))
|
|
3694
|
+
say(ctx, ` ${k.padEnd(width)} ${h.summary}`);
|
|
3695
|
+
say(ctx, "");
|
|
3696
|
+
say(ctx, "Global flags: --json --profile <name> --agent <name or id> --dry-run --field name=value");
|
|
3697
|
+
say(ctx, "`darwin <command> --help` for a command's flags; `darwin help --json` for all of it.");
|
|
3698
|
+
say(ctx, "Output is text in a terminal and JSON when piped or redirected (or with --json); --format table forces text.");
|
|
3699
|
+
say(ctx, "");
|
|
3700
|
+
for (const l of terminalGuide(c.credential))
|
|
3701
|
+
say(ctx, l);
|
|
3702
|
+
}
|
|
3703
|
+
function terminalGuide(kind) {
|
|
3704
|
+
return [
|
|
3705
|
+
"Spot trades take two steps: `darwin quote --sell <token> --amount <n> --for <token>`, then `darwin order --quote <id>` with the same sell, amount and for, within 30 seconds. `darwin instant` quotes and executes in one step.",
|
|
3706
|
+
"Before your first order, `darwin grant` shows your limits and today's remaining budget. `darwin docs` is the full API reference.",
|
|
3707
|
+
"If an order's result is uncertain (exit code 6), don't run it again — check `darwin orders` to see whether it went through.",
|
|
3708
|
+
kind === "agents" ? "This API key trades for all your active agents: name one with --agent <name or id> (`darwin agents` lists them). `darwin pause --agent <name>` stops one; only you can resume it, on Darwin." : "`darwin pause` stops this agent trading; only you can resume it, on Darwin."
|
|
3709
|
+
];
|
|
3710
|
+
}
|
|
3711
|
+
function printCommandHelp(ctx, t, json, c = null) {
|
|
3712
|
+
if (json) {
|
|
3713
|
+
printJson(ctx, helpJson({ tools: [t] }).commands[0]);
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
say(ctx, `${usageOf(t)}`);
|
|
3717
|
+
say(ctx, "");
|
|
3718
|
+
say(ctx, `${clean(t.title)}${t.write ? " — changes something" : ""}${t.costsTx ? "; counts against today's transaction budget" : ""}.`);
|
|
3719
|
+
say(ctx, describeForTerminal(t, c));
|
|
3720
|
+
const flags = toolFlags(t);
|
|
3721
|
+
if (flags.length || t.cli.positional.length)
|
|
3722
|
+
say(ctx, "");
|
|
3723
|
+
for (const p of t.cli.positional)
|
|
3724
|
+
say(ctx, ` <${p}> ${forTerminal(t.inputSchema.properties?.[p]?.description ?? "", c)}`);
|
|
3725
|
+
for (const f of flags)
|
|
3726
|
+
say(ctx, ` ${f.flag} <${f.type}>${f.required ? " (required)" : ""} ${forTerminal(f.description, c)}`);
|
|
3727
|
+
if (t.write)
|
|
3728
|
+
say(ctx, `
|
|
3729
|
+
There is no confirmation prompt: the command runs when you press enter. Use --dry-run to see what it would send.`);
|
|
3730
|
+
if (t.deprecated)
|
|
3731
|
+
say(ctx, `
|
|
3732
|
+
Deprecated since ${t.deprecated.since}; removed after ${t.deprecated.removeAfter}.`);
|
|
3733
|
+
}
|
|
3734
|
+
function printStaticHelp(ctx, name, json) {
|
|
3735
|
+
const h = STATIC_HELP[name];
|
|
3736
|
+
if (json) {
|
|
3737
|
+
printJson(ctx, { command: name, ...h });
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
say(ctx, h.usage);
|
|
3741
|
+
say(ctx, "");
|
|
3742
|
+
say(ctx, h.summary);
|
|
3743
|
+
for (const [f, d] of Object.entries(h.flags))
|
|
3744
|
+
say(ctx, ` --${f} ${d}`);
|
|
3745
|
+
}
|
|
3746
|
+
|
|
3747
|
+
// src/render.ts
|
|
3748
|
+
var BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
3749
|
+
var WSOL_MINT = "So11111111111111111111111111111111111111112";
|
|
3750
|
+
var ROUTER = { dflow: "dFlow", deflow: "dFlow", jupiter: "Jupiter" };
|
|
3751
|
+
var shortMint = (m) => {
|
|
3752
|
+
const s = txt(m);
|
|
3753
|
+
return BASE58.test(s) ? `${s.slice(0, 4)}…${s.slice(-4)}` : s;
|
|
3754
|
+
};
|
|
3755
|
+
function formatAtoms(atoms, decimals) {
|
|
3756
|
+
const a = txt(atoms);
|
|
3757
|
+
if (!/^[0-9]{1,40}$/.test(a) || typeof decimals !== "number" || !Number.isInteger(decimals) || decimals < 0 || decimals > 30)
|
|
3758
|
+
return "";
|
|
3759
|
+
if (decimals === 0)
|
|
3760
|
+
return BigInt(a).toString();
|
|
3761
|
+
const s = a.padStart(decimals + 1, "0");
|
|
3762
|
+
const whole = BigInt(s.slice(0, -decimals)).toString();
|
|
3763
|
+
const frac = s.slice(-decimals).replace(/0+$/, "");
|
|
3764
|
+
return frac ? `${whole}.${frac}` : whole;
|
|
3765
|
+
}
|
|
3766
|
+
var yesNo = (v) => v === true ? "yes" : v === false ? "no" : "";
|
|
3767
|
+
var router = (v) => ROUTER[txt(v).toLowerCase()] ?? txt(v);
|
|
3768
|
+
var plural = (n, one2, many = `${one2}s`) => `${n} ${n === 1 ? one2 : many}`;
|
|
3769
|
+
function tokenName(side, mint, x) {
|
|
3770
|
+
const sym = label(side.symbol, 24);
|
|
3771
|
+
if (sym)
|
|
3772
|
+
return sym;
|
|
3773
|
+
const m = txt(mint ?? side.mint);
|
|
3774
|
+
return x?.markets?.get(m)?.symbol || shortMint(m);
|
|
3775
|
+
}
|
|
3776
|
+
function columns(head, rows, indent = "") {
|
|
3777
|
+
if (rows.length === 0)
|
|
3778
|
+
return [];
|
|
3779
|
+
const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
3780
|
+
const line = (vals) => `${indent}${vals.map((v, i) => i === vals.length - 1 ? v : v.padEnd(widths[i])).join(" ")}`.trimEnd();
|
|
3781
|
+
return [line(head), ...rows.map(line)];
|
|
3782
|
+
}
|
|
3783
|
+
function welcomeLines(realm, hello, agentName) {
|
|
3784
|
+
const h = hello ?? {};
|
|
3785
|
+
const out = [`Agent: ${label(agentName, 120) || "your agent"} on ${realm}`];
|
|
3786
|
+
const addr = typeof h.solanaAddress === "string" && BASE58.test(h.solanaAddress) ? h.solanaAddress : null;
|
|
3787
|
+
if (addr)
|
|
3788
|
+
out.push(`Agent wallet (fund it to trade): ${addr}`);
|
|
3789
|
+
const page = sameRealmUrl(h.agentPageUrl, realm);
|
|
3790
|
+
if (page)
|
|
3791
|
+
out.push(`Agent page: ${page}`);
|
|
3792
|
+
out.push("Try: darwin market-status · darwin balances · darwin quote --sell USDC --amount 1 --for SOL");
|
|
3793
|
+
return out;
|
|
3794
|
+
}
|
|
3795
|
+
function sameRealmUrl(v, realm) {
|
|
3796
|
+
if (typeof v !== "string")
|
|
3797
|
+
return null;
|
|
3798
|
+
try {
|
|
3799
|
+
const u = new URL(v);
|
|
3800
|
+
return u.protocol === "https:" && u.host === realm && !u.username && !u.password ? txt(u.toString()) : null;
|
|
3801
|
+
} catch {
|
|
3802
|
+
return null;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
function hello(r, x) {
|
|
3806
|
+
const name = txt(obj(r.untrusted).agentName) || txt(r.agentName) || x.agentLabel;
|
|
3807
|
+
return welcomeLines(x.realm, r, name);
|
|
3808
|
+
}
|
|
3809
|
+
function agents(r) {
|
|
3810
|
+
const d = obj(r.data);
|
|
3811
|
+
const list = arr(d.agents).map(obj);
|
|
3812
|
+
if (list.length === 0)
|
|
3813
|
+
return ["No active agents for this API key."];
|
|
3814
|
+
const rows = list.map((a) => [label(a.name, 40) || "(unnamed)", txt(a.id), txt(a.status) || "", BASE58.test(txt(a.solanaAddress)) ? txt(a.solanaAddress) : ""]);
|
|
3815
|
+
const out = columns(["Name", "ID", "Status", "Wallet"], rows);
|
|
3816
|
+
const perms = txt(d.permissions);
|
|
3817
|
+
if (perms)
|
|
3818
|
+
out.push("", `This API key: ${perms}`);
|
|
3819
|
+
return out;
|
|
3820
|
+
}
|
|
3821
|
+
function capLine(key, c) {
|
|
3822
|
+
const name = words(key).replace(/\busd\b/, "").trim();
|
|
3823
|
+
if (c.refusesAll === true)
|
|
3824
|
+
return `${name}: none allowed`;
|
|
3825
|
+
if (c.unlimited === true || c.value === null)
|
|
3826
|
+
return `${name}: no limit`;
|
|
3827
|
+
return `${name}: ${/Usd$/.test(key) ? usd(c.value) : num(c.value)}`;
|
|
3828
|
+
}
|
|
3829
|
+
function grant(r) {
|
|
3830
|
+
const d = obj(r.data);
|
|
3831
|
+
const g = obj(d.grant);
|
|
3832
|
+
const t = obj(d.today);
|
|
3833
|
+
const out = [];
|
|
3834
|
+
const paused = g.paused === true;
|
|
3835
|
+
out.push(`Agent: ${label(g.agentName, 80) || "this agent"} · ${paused ? `paused${when(g.pausedAt) ? ` since ${when(g.pausedAt)}` : ""}` : txt(g.status) || "active"}`);
|
|
3836
|
+
if (BASE58.test(txt(g.wallet)))
|
|
3837
|
+
out.push(`Wallet: ${txt(g.wallet)}`);
|
|
3838
|
+
const actions = arr(g.allowedActions).map(txt).filter(Boolean);
|
|
3839
|
+
if (actions.length)
|
|
3840
|
+
out.push(`Can: ${actions.map((a) => a.replace(/_/g, " ")).join(", ")}`);
|
|
3841
|
+
const caps = Object.entries(obj(g.capLimits)).map(([k, c]) => capLine(k, obj(c)));
|
|
3842
|
+
if (caps.length)
|
|
3843
|
+
out.push(`Limits: ${caps.join(" · ")}`);
|
|
3844
|
+
const sl = obj(g.slippage);
|
|
3845
|
+
if (typeof sl.globalMaxBps === "number") {
|
|
3846
|
+
const overrides = arr(sl.overrides).length;
|
|
3847
|
+
out.push(`Max slippage: ${sl.globalMaxBps} bps${overrides ? ` (${plural(overrides, "token-specific override")})` : ""}`);
|
|
3848
|
+
}
|
|
3849
|
+
const rem = obj(t.remaining);
|
|
3850
|
+
const left = [
|
|
3851
|
+
rem.notionalUsd === null ? "no dollar limit" : usd(rem.notionalUsd) ? `${usd(rem.notionalUsd)} to trade` : "",
|
|
3852
|
+
rem.txCount === null ? "no trade-count limit" : typeof rem.txCount === "number" ? plural(rem.txCount, "trade") : "",
|
|
3853
|
+
rem.riskUsd === null ? "" : usd(rem.riskUsd) ? `${usd(rem.riskUsd)} of risk` : ""
|
|
3854
|
+
].filter(Boolean);
|
|
3855
|
+
if (left.length)
|
|
3856
|
+
out.push(`Left today: ${left.join(" · ")}`);
|
|
3857
|
+
const used = [usd(t.notionalUsd) ? `${usd(t.notionalUsd)} traded` : "", typeof t.txCount === "number" ? plural(t.txCount, "trade") : ""].filter(Boolean);
|
|
3858
|
+
if (used.length)
|
|
3859
|
+
out.push(`Used today: ${used.join(" · ")}${txt(t.day) ? ` (UTC day ${txt(t.day)}; resets at midnight UTC)` : ""}`);
|
|
3860
|
+
const u = obj(g.unlisted);
|
|
3861
|
+
if (u.enabled === true)
|
|
3862
|
+
out.push(`Tokens Darwin doesn't list: allowed — ${usd(u.dailyAcquisitionUsd)} a day to buy, ${usd(u.dailyDisposalUsd)} to sell`);
|
|
3863
|
+
else if (u.enabled === false)
|
|
3864
|
+
out.push("Tokens Darwin doesn't list: not allowed");
|
|
3865
|
+
return out;
|
|
3866
|
+
}
|
|
3867
|
+
function pause(r, x, kind) {
|
|
3868
|
+
const s = obj(r.summary);
|
|
3869
|
+
const at = when(s.pausedAtIso) || when(s.pausedAt) || when(obj(r.data).pausedAt);
|
|
3870
|
+
const changed = obj(r.data).changed;
|
|
3871
|
+
const head = changed === false ? `${x.agentLabel} was already paused.` : `Paused ${x.agentLabel}${at ? ` at ${at}` : ""}.`;
|
|
3872
|
+
return [
|
|
3873
|
+
head,
|
|
3874
|
+
kind === "agents" ? "It can't trade until you resume it on its Darwin page, and this API key can't act on it until then. It still works for your other active agents." : "It can't trade until you resume it on its Darwin page. This API key still reads its balances and orders."
|
|
3875
|
+
];
|
|
3876
|
+
}
|
|
3877
|
+
var typed = (v) => txt(v).replace(/[^A-Za-z0-9._-]/g, "");
|
|
3878
|
+
function quote(r, x) {
|
|
3879
|
+
const s = obj(r.summary);
|
|
3880
|
+
const d = obj(r.data);
|
|
3881
|
+
const sell = obj(s.sell);
|
|
3882
|
+
const recv = obj(s.receive);
|
|
3883
|
+
const sellName = tokenName(sell, d.inputMint, x);
|
|
3884
|
+
const getName = tokenName(recv, d.outputMint, x);
|
|
3885
|
+
const amount = num(sell.amount) || num(x.args.amount);
|
|
3886
|
+
const quoted = num(recv.quoted);
|
|
3887
|
+
const atLeast = num(recv.atLeast);
|
|
3888
|
+
const out = [`Sell ${amount} ${sellName} → ${quoted ? `about ${quoted} ${getName}` : getName}${atLeast ? ` (at least ${atLeast} ${getName})` : ""}`];
|
|
3889
|
+
const parts = [];
|
|
3890
|
+
if (typeof d.maxSlippageBps === "number")
|
|
3891
|
+
parts.push(`Max slippage ${d.maxSlippageBps} bps`);
|
|
3892
|
+
if (txt(d.router))
|
|
3893
|
+
parts.push(`route ${router(d.router)}`);
|
|
3894
|
+
const exp = typeof d.expiresAtMs === "number" ? d.expiresAtMs : null;
|
|
3895
|
+
if (exp !== null)
|
|
3896
|
+
parts.push(`quote expires in ${Math.max(0, Math.ceil((exp - x.now) / 1000))}s (${clock(new Date(exp))})`);
|
|
3897
|
+
if (parts.length)
|
|
3898
|
+
out.push(parts.join(" · "));
|
|
3899
|
+
const id = txt(d.quoteId) || txt(s.quoteId);
|
|
3900
|
+
if (id && /^[A-Za-z0-9_-]{1,80}$/.test(id) && sell.amount !== null) {
|
|
3901
|
+
out.push(`To place it: darwin order --quote ${id} --sell ${typed(x.args.sell)} --amount ${amount} --for ${typed(x.args.for)}`);
|
|
3902
|
+
} else if (sell.amount === null) {
|
|
3903
|
+
out.push("This token's decimals aren't known, so this quote can't be placed by amount.");
|
|
3904
|
+
}
|
|
3905
|
+
return out;
|
|
3906
|
+
}
|
|
3907
|
+
function spotWrite(r, x, instant) {
|
|
3908
|
+
const s = obj(r.summary);
|
|
3909
|
+
const d = obj(r.data);
|
|
3910
|
+
const sell = obj(s.sell);
|
|
3911
|
+
const recv = obj(s.receive);
|
|
3912
|
+
const sellName = tokenName(sell, undefined, x) || typed(x.args.sell);
|
|
3913
|
+
const getName = tokenName(recv, undefined, x) || typed(x.args.for);
|
|
3914
|
+
const amount = num(sell.amount) || num(x.args.amount);
|
|
3915
|
+
const status = txt(s.status) || txt(d.status);
|
|
3916
|
+
const sig = txt(s.txSignature) || txt(d.txSignature);
|
|
3917
|
+
const orderId = txt(s.orderId) || txt(d.orderId);
|
|
3918
|
+
const nonce = x.nonce ?? txt(d.clientOrderNonce);
|
|
3919
|
+
const out = [`Sent: sell ${amount} ${sellName} for ${getName} on ${x.agentLabel} · ${orderId ? `order ${orderId} · ` : ""}nonce ${nonce}`];
|
|
3920
|
+
if (instant && d.replayed === true)
|
|
3921
|
+
out.push("(That nonce was already used, so this is the earlier order — nothing new was sent.)");
|
|
3922
|
+
const quoted = num(recv.quoted);
|
|
3923
|
+
const atLeast = num(recv.atLeast);
|
|
3924
|
+
if (quoted)
|
|
3925
|
+
out.push(`Expected: about ${quoted} ${getName}${atLeast ? ` (at least ${atLeast} ${getName})` : ""}`);
|
|
3926
|
+
if (status === "confirmed")
|
|
3927
|
+
out.push("Status: confirmed on chain");
|
|
3928
|
+
else if (status === "submitted")
|
|
3929
|
+
out.push("Status: sent to the network, not confirmed yet");
|
|
3930
|
+
else if (status)
|
|
3931
|
+
out.push(`Status: ${status}`);
|
|
3932
|
+
if (/^[1-9A-HJ-NP-Za-km-z]{64,90}$/.test(sig))
|
|
3933
|
+
out.push(`Transaction: ${sig}`);
|
|
3934
|
+
return out.filter(Boolean);
|
|
3935
|
+
}
|
|
3936
|
+
function balances(r, x) {
|
|
3937
|
+
const d = obj(r.data);
|
|
3938
|
+
const rows = arr(d.balances).map(obj);
|
|
3939
|
+
const out = [];
|
|
3940
|
+
if (BASE58.test(txt(d.wallet)))
|
|
3941
|
+
out.push(`Wallet: ${txt(d.wallet)}`);
|
|
3942
|
+
const lamports = txt(d.nativeSolLamports);
|
|
3943
|
+
const body = rows.map((b) => [
|
|
3944
|
+
tokenName(b, b.mint, x),
|
|
3945
|
+
num(b.amount) || formatAtoms(b.atoms, typeof b.decimals === "number" ? b.decimals : null) || `${txt(b.atoms)} (smallest units)`,
|
|
3946
|
+
b.listed === false ? `${txt(b.mint)} (not listed on Darwin)` : txt(b.mint)
|
|
3947
|
+
]);
|
|
3948
|
+
if (body.length)
|
|
3949
|
+
out.push(...columns(["Token", "Amount", "Mint"], body));
|
|
3950
|
+
else
|
|
3951
|
+
out.push("No token balances.");
|
|
3952
|
+
if (/^[0-9]{1,20}$/.test(lamports))
|
|
3953
|
+
out.push(`SOL for network fees: ${formatAtoms(lamports, 9)}`);
|
|
3954
|
+
if (d.gasSponsored === true)
|
|
3955
|
+
out.push("Network fees: paid by Darwin.");
|
|
3956
|
+
if (d.gasLow === true && d.gasSponsored !== true)
|
|
3957
|
+
out.push("⚠ Low on SOL for network fees — add SOL to keep trading.");
|
|
3958
|
+
if (d.partial === true)
|
|
3959
|
+
out.push("Some balances couldn't be read just now, so this list may be incomplete.");
|
|
3960
|
+
return out;
|
|
3961
|
+
}
|
|
3962
|
+
var atomsAs = (atoms, mint, x) => {
|
|
3963
|
+
const m = x.markets?.get(txt(mint));
|
|
3964
|
+
const amt = m ? formatAtoms(atoms, m.decimals) : "";
|
|
3965
|
+
return amt ? `${amt} ${m.symbol}` : `${txt(atoms) || "?"} units of ${shortMint(mint)}`;
|
|
3966
|
+
};
|
|
3967
|
+
function orders(r, x) {
|
|
3968
|
+
const d = obj(r.data);
|
|
3969
|
+
const list = arr(d.orders).map(obj);
|
|
3970
|
+
if (list.length === 0)
|
|
3971
|
+
return ["No spot orders yet."];
|
|
3972
|
+
const rows = list.map((o) => [
|
|
3973
|
+
when(o.createdAt),
|
|
3974
|
+
`${atomsAs(o.inputAtoms, o.inputMint, x)} → ${o.outputAtoms === null ? x.markets?.get(txt(o.outputMint))?.symbol || shortMint(o.outputMint) : atomsAs(o.outputAtoms, o.outputMint, x)}`,
|
|
3975
|
+
`${txt(o.status)}${txt(o.errorCode) ? ` (${txt(o.errorCode)})` : ""}`,
|
|
3976
|
+
txt(o.orderId)
|
|
3977
|
+
]);
|
|
3978
|
+
const out = columns(["When", "Trade", "Status", "Order"], rows);
|
|
3979
|
+
if (txt(d.nextCursor))
|
|
3980
|
+
out.push("", `More: darwin orders --cursor ${txt(d.nextCursor)}`);
|
|
3981
|
+
return out;
|
|
3982
|
+
}
|
|
3983
|
+
function tx(r) {
|
|
3984
|
+
const d = obj(r.data);
|
|
3985
|
+
const st = txt(d.status);
|
|
3986
|
+
const what = st === "confirmed" ? "confirmed on chain" : st === "failed" ? "failed" : st === "submitted" ? "sent, not confirmed yet" : st;
|
|
3987
|
+
const out = [`Transaction ${txt(d.signature)}: ${what}${typeof d.slot === "number" ? ` (slot ${d.slot})` : ""}`];
|
|
3988
|
+
if (txt(d.error))
|
|
3989
|
+
out.push(`Reason: ${txt(d.error).replace(/_/g, " ")}`);
|
|
3990
|
+
return out;
|
|
3991
|
+
}
|
|
3992
|
+
function spotMarkets(r) {
|
|
3993
|
+
const d = obj(r.data);
|
|
3994
|
+
const list = arr(d.instruments).map(obj);
|
|
3995
|
+
if (list.length === 0)
|
|
3996
|
+
return ["No markets."];
|
|
3997
|
+
const rows = list.map((m) => [
|
|
3998
|
+
label(m.symbol, 20),
|
|
3999
|
+
txt(m.instrumentType),
|
|
4000
|
+
txt(m.assetClass).replace(/_/g, " "),
|
|
4001
|
+
m.instrumentType === "perp" ? m.tradeable === false || m.status === "paused" ? "paused" : "open" : m.marketOpen === false ? "closed now" : "open",
|
|
4002
|
+
m.instrumentType === "spot" ? txt(m.mint) : `max ${num(m.maxLeverage)}x`
|
|
4003
|
+
]);
|
|
4004
|
+
return columns(["Symbol", "Type", "Class", "Market", "Mint / leverage"], rows);
|
|
4005
|
+
}
|
|
4006
|
+
function unlistedInspect(r) {
|
|
4007
|
+
const d = obj(r.data);
|
|
4008
|
+
const name = label(d.name, 60);
|
|
4009
|
+
const sym = label(d.symbol, 24);
|
|
4010
|
+
const out = [`Token: ${[name, sym ? `(${sym})` : ""].filter(Boolean).join(" ") || "(no name)"}${txt(d.mint) ? ` · ${txt(d.mint)}` : ""}`];
|
|
4011
|
+
if (d.listing === "darwin")
|
|
4012
|
+
out.push("Darwin lists this token: trade it with `darwin quote`.");
|
|
4013
|
+
out.push(`Tradable as an unlisted token: ${d.eligible === true ? "yes" : "no"}`);
|
|
4014
|
+
for (const f of arr(d.refusals).map(obj))
|
|
4015
|
+
out.push(` ✗ ${txt(f.code).replace(/_/g, " ")}${txt(f.detail) ? ` — ${txt(f.detail)}` : ""}`);
|
|
4016
|
+
for (const f of arr(d.warnings).map(obj))
|
|
4017
|
+
out.push(` ! ${txt(f.code).replace(/_/g, " ")}${txt(f.detail) ? ` — ${txt(f.detail)}` : ""}`);
|
|
4018
|
+
const facts = [
|
|
4019
|
+
typeof d.decimals === "number" ? `${d.decimals} decimals` : "",
|
|
4020
|
+
typeof d.ageHours === "number" ? `${Math.round(d.ageHours)}h old` : "",
|
|
4021
|
+
typeof d.holders === "number" ? `${d.holders.toLocaleString("en-US")} holders` : "",
|
|
4022
|
+
usd(d.volume24hUsd) ? `24h volume ${usd(d.volume24hUsd)}` : "",
|
|
4023
|
+
usd(d.twapUsd) ? `price ${usd(d.twapUsd)} (${txt(d.twapWindow) || "1h"} average)` : ""
|
|
4024
|
+
].filter(Boolean);
|
|
4025
|
+
if (facts.length)
|
|
4026
|
+
out.push(facts.join(" · "));
|
|
4027
|
+
const b = obj(d.budget);
|
|
4028
|
+
if (usd(b.acquisitionRemainingUsd))
|
|
4029
|
+
out.push(`Unlisted budget left today: ${usd(b.acquisitionRemainingUsd)} to buy · ${usd(b.disposalRemainingUsd)} to sell`);
|
|
4030
|
+
return out;
|
|
4031
|
+
}
|
|
4032
|
+
var total = (t) => {
|
|
4033
|
+
const o = obj(t);
|
|
4034
|
+
if (o.usd === null || o.usd === undefined)
|
|
4035
|
+
return o.complete === false ? "unknown" : "";
|
|
4036
|
+
return `${usd(o.usd)}${o.complete === false ? " (incomplete)" : ""}`;
|
|
4037
|
+
};
|
|
4038
|
+
function trades(r, x) {
|
|
4039
|
+
const d = obj(r.data);
|
|
4040
|
+
const list = arr(d.trades).map(obj);
|
|
4041
|
+
if (list.length === 0)
|
|
4042
|
+
return ["No settled trades yet."];
|
|
4043
|
+
const rows = list.map((t) => {
|
|
4044
|
+
if (t.kind === "perp") {
|
|
4045
|
+
return [when(t.settledAtMs), "perps", `${txt(t.side)} ${num(t.sizeBase)} ${label(t.symbol, 16)}${usd(t.priceUsd) ? ` @ ${usd(t.priceUsd)}` : ""}`, usd(t.realizedPnlUsd)];
|
|
4046
|
+
}
|
|
4047
|
+
const i = obj(t.in);
|
|
4048
|
+
const o = obj(t.out);
|
|
4049
|
+
return [when(t.settledAtMs), "spot", `${atomsAs(i.atoms, i.mint, x)} → ${atomsAs(o.atoms, o.mint, x)}`, t.realizedPnlUsd === undefined ? "" : usd(t.realizedPnlUsd) || "unknown"];
|
|
4050
|
+
});
|
|
4051
|
+
const out = columns(["Settled", "Kind", "Trade", "Realized P&L"], rows);
|
|
4052
|
+
if (txt(d.nextCursor))
|
|
4053
|
+
out.push("", `More: darwin history trades --cursor ${txt(d.nextCursor)}`);
|
|
4054
|
+
return out;
|
|
4055
|
+
}
|
|
4056
|
+
function pnl(r) {
|
|
4057
|
+
const d = obj(r.data);
|
|
4058
|
+
const s = obj(d.spot);
|
|
4059
|
+
const p = obj(d.perps);
|
|
4060
|
+
const out = [`P&L${txt(d.period) ? ` (${txt(d.period)})` : ""}${when(d.asOfMs) ? ` as of ${when(d.asOfMs)}` : ""}`];
|
|
4061
|
+
const realized = obj(s.realizedUsd);
|
|
4062
|
+
const line = (name, parts) => {
|
|
4063
|
+
const shown = parts.map(([k, v]) => [k, total(v)]).filter(([, v]) => v);
|
|
4064
|
+
if (shown.length)
|
|
4065
|
+
out.push(`${name}: ${shown.map(([k, v]) => `${k} ${v}`).join(" · ")}`);
|
|
4066
|
+
};
|
|
4067
|
+
line("Spot", [["realized", realized.total], ["short-term", realized.shortTerm], ["long-term", realized.longTerm], ["unrealized", s.unrealizedUsd], ["cost basis", s.costBasisUsd], ["market value", s.marketValueUsd], ["cash", s.cashUsd]]);
|
|
4068
|
+
line("Perps", [["realized", p.realizedUsd], ["fees", p.feesUsd], ["funding", p.fundingUsd], ["unrealized", p.unrealizedUsd], ["equity", p.equityUsd]]);
|
|
4069
|
+
const f = obj(d.feesUsd);
|
|
4070
|
+
line("Fees", [["network", f.networkWalletPaid], ["perps trading", f.perpTrading]]);
|
|
4071
|
+
const pos = arr(s.positions).map(obj);
|
|
4072
|
+
if (pos.length) {
|
|
4073
|
+
out.push("", ...columns(["Asset", "Qty", "Avg cost", "Mark", "Unrealized"], pos.map((x) => [label(x.assetId, 24), num(x.qty), usd(x.avgCostUsd), usd(x.markUsd), usd(x.unrealizedUsd) || (x.unpriced === true ? "unpriced" : "")])));
|
|
4074
|
+
}
|
|
4075
|
+
for (const n of arr(d.notes))
|
|
4076
|
+
if (txt(n))
|
|
4077
|
+
out.push(`Note: ${txt(n)}`);
|
|
4078
|
+
return out;
|
|
4079
|
+
}
|
|
4080
|
+
function lots(r) {
|
|
4081
|
+
const d = obj(r.data);
|
|
4082
|
+
const list = arr(d.lots).map(obj);
|
|
4083
|
+
const out = [`Tax lots${txt(d.method) ? ` (method: ${txt(d.method)})` : ""}`];
|
|
4084
|
+
if (list.length === 0)
|
|
4085
|
+
return [...out, "No open tax lots."];
|
|
4086
|
+
out.push(...columns(["Asset", "Qty", "Available", "Cost basis", "Acquired", "Term", "Unrealized"], list.map((l) => [
|
|
4087
|
+
label(l.assetId, 24),
|
|
4088
|
+
num(l.qty),
|
|
4089
|
+
num(l.availableQty),
|
|
4090
|
+
usd(l.costBasisUsd),
|
|
4091
|
+
when(l.acquiredAtMs),
|
|
4092
|
+
txt(l.term),
|
|
4093
|
+
usd(l.unrealizedUsd) || (l.unpriced === true ? "unpriced" : "")
|
|
4094
|
+
])));
|
|
4095
|
+
return out;
|
|
4096
|
+
}
|
|
4097
|
+
function perpMarkets(r) {
|
|
4098
|
+
const list = arr(obj(r.data).markets).map(obj);
|
|
4099
|
+
if (list.length === 0)
|
|
4100
|
+
return ["No perps markets."];
|
|
4101
|
+
return columns(["Symbol", "Name", "Max leverage", "Min size", "Tick", "Status"], list.map((m) => [
|
|
4102
|
+
label(m.symbol, 16),
|
|
4103
|
+
label(m.name, 30),
|
|
4104
|
+
m.maxLeverage === undefined ? "" : `${num(m.maxLeverage)}x`,
|
|
4105
|
+
num(m.minOrderSize),
|
|
4106
|
+
usd(m.tick),
|
|
4107
|
+
m.tradeable === false ? "not tradeable" : txt(m.status)
|
|
4108
|
+
]));
|
|
4109
|
+
}
|
|
4110
|
+
function perpQuote(r) {
|
|
4111
|
+
const d = obj(r.data);
|
|
4112
|
+
const f = typeof d.fundingRatePct === "number" ? `${d.fundingRatePct >= 0 ? "+" : "−"}${Math.abs(d.fundingRatePct)}% funding (+ = longs pay)` : "";
|
|
4113
|
+
return [[`${label(d.symbol, 16)} perps: mark ${usd(d.markUsd)}`, usd(d.indexUsd) ? `index ${usd(d.indexUsd)}` : "", f, d.tradeable === false ? "not tradeable now" : ""].filter(Boolean).join(" · ")];
|
|
4114
|
+
}
|
|
4115
|
+
function perpAccount(r) {
|
|
4116
|
+
const d = obj(r.data);
|
|
4117
|
+
if (d.hasTraderAccount === false)
|
|
4118
|
+
return ["This agent has no perps account yet."];
|
|
4119
|
+
const a = obj(d.account);
|
|
4120
|
+
return [`Equity ${usd(a.equityUsd)} · collateral ${usd(a.collateralUsd)} · free ${usd(a.freeCollateralUsd)} · margin used ${usd(a.marginUsedUsd)} · maintenance ${usd(a.maintenanceMarginUsd)}`];
|
|
4121
|
+
}
|
|
4122
|
+
function perpPositions(r) {
|
|
4123
|
+
const d = obj(r.data);
|
|
4124
|
+
if (d.hasTraderAccount === false)
|
|
4125
|
+
return ["This agent has no perps account yet."];
|
|
4126
|
+
const list = arr(d.positions).map(obj);
|
|
4127
|
+
if (list.length === 0)
|
|
4128
|
+
return ["No open perps positions."];
|
|
4129
|
+
return columns(["Market", "Side", "Size", "Entry", "Mark", "Unrealized", "Liquidation", "TP / SL"], list.map((p) => [
|
|
4130
|
+
label(p.symbol, 16),
|
|
4131
|
+
txt(p.side),
|
|
4132
|
+
num(p.sizeBase),
|
|
4133
|
+
usd(p.entryUsd),
|
|
4134
|
+
usd(p.markUsd),
|
|
4135
|
+
usd(p.uPnlUsd),
|
|
4136
|
+
usd(p.liqUsd),
|
|
4137
|
+
[usd(p.takeProfitUsd), usd(p.stopLossUsd)].map((v) => v || "—").join(" / ")
|
|
4138
|
+
]));
|
|
4139
|
+
}
|
|
4140
|
+
function perpOrders(r) {
|
|
4141
|
+
const d = obj(r.data);
|
|
4142
|
+
if (d.hasTraderAccount === false)
|
|
4143
|
+
return ["This agent has no perps account yet."];
|
|
4144
|
+
const list = arr(d.orders).map(obj);
|
|
4145
|
+
const trig = arr(d.triggers).map(obj);
|
|
4146
|
+
const out = list.length ? columns(["Market", "Side", "Type", "Price", "Size", "Reduce-only", "Order"], list.map((o) => [label(o.symbol, 16), txt(o.side), txt(o.orderType), usd(o.priceUsd), num(o.sizeBase), yesNo(o.reduceOnly), txt(o.orderSequenceNumber)])) : ["No resting perps orders."];
|
|
4147
|
+
if (trig.length)
|
|
4148
|
+
out.push("", ...columns(["Market", "Trigger", "Price"], trig.map((t) => [label(t.symbol, 16), txt(t.kind).replace(/_/g, " "), usd(t.priceUsd)])));
|
|
4149
|
+
return out;
|
|
4150
|
+
}
|
|
4151
|
+
function perpProtections(r) {
|
|
4152
|
+
const d = obj(r.data);
|
|
4153
|
+
const list = arr(d.protections).map(obj);
|
|
4154
|
+
if (list.length === 0)
|
|
4155
|
+
return ["No TP/SL protections."];
|
|
4156
|
+
const out = columns(["Market", "Kind", "Trigger", "State", "Updated"], list.map((p) => [label(p.symbol, 16), txt(p.kind).replace(/_/g, " "), usd(p.triggerPriceUsd) || num(p.triggerPriceUsd), txt(p.state), when(p.updatedAt)]));
|
|
4157
|
+
if (d.hasMore === true)
|
|
4158
|
+
out.push("", "Only the newest 200 are shown.");
|
|
4159
|
+
return out;
|
|
4160
|
+
}
|
|
4161
|
+
function indicators(r) {
|
|
4162
|
+
const d = obj(r.data);
|
|
4163
|
+
const bars = arr(d.bars).map(obj);
|
|
4164
|
+
const keys = arr(d.indicators).map((i) => txt(obj(i).key)).filter(Boolean);
|
|
4165
|
+
const out = [`Indicators for ${label(d.asset_id, 40)} on ${txt(d.timeframe)} candles${d.stale === true ? " (stale: the newest candle is old)" : ""}`];
|
|
4166
|
+
if (bars.length === 0)
|
|
4167
|
+
return [...out, "No bars."];
|
|
4168
|
+
const val = (v) => typeof v === "number" ? String(Math.round(v * 1e6) / 1e6) : Object.entries(obj(v)).map(([k, n]) => `${k}=${typeof n === "number" ? Math.round(n * 1e6) / 1e6 : txt(n)}`).join(" ");
|
|
4169
|
+
out.push(...columns(["Bar opened", "Close", ...keys], bars.map((b) => [when(b.open_time_ms), num(b.close), ...keys.map((k) => val(b[k]))])));
|
|
4170
|
+
return out;
|
|
4171
|
+
}
|
|
4172
|
+
function renderersFor(kind) {
|
|
4173
|
+
return {
|
|
4174
|
+
hello,
|
|
4175
|
+
list_agents: (r) => agents(r),
|
|
4176
|
+
get_grant: (r) => grant(r),
|
|
4177
|
+
pause_agent: (r, x) => pause(r, x, kind),
|
|
4178
|
+
get_spot_quote: quote,
|
|
4179
|
+
quote_unlisted_token: quote,
|
|
4180
|
+
place_spot_order: (r, x) => spotWrite(r, x, false),
|
|
4181
|
+
place_unlisted_order: (r, x) => spotWrite(r, x, false),
|
|
4182
|
+
spot_order_now: (r, x) => spotWrite(r, x, true),
|
|
4183
|
+
get_balances: balances,
|
|
4184
|
+
list_spot_orders: orders,
|
|
4185
|
+
get_tx_status: (r) => tx(r),
|
|
4186
|
+
list_spot_markets: (r) => spotMarkets(r),
|
|
4187
|
+
inspect_unlisted_token: (r) => unlistedInspect(r),
|
|
4188
|
+
list_trades: trades,
|
|
4189
|
+
get_pnl: (r) => pnl(r),
|
|
4190
|
+
list_lots: (r) => lots(r),
|
|
4191
|
+
list_perp_markets: (r) => perpMarkets(r),
|
|
4192
|
+
get_perp_quote: (r) => perpQuote(r),
|
|
4193
|
+
get_perp_account: (r) => perpAccount(r),
|
|
4194
|
+
list_perp_positions: (r) => perpPositions(r),
|
|
4195
|
+
list_perp_orders: (r) => perpOrders(r),
|
|
4196
|
+
list_perp_protections: (r) => perpProtections(r),
|
|
4197
|
+
get_indicators: (r) => indicators(r)
|
|
4198
|
+
};
|
|
4199
|
+
}
|
|
4200
|
+
function renderResult(tool, kind, result, x) {
|
|
4201
|
+
if (typeof result.text === "string")
|
|
4202
|
+
return forTerminal(result.text, x.catalogue).split(`
|
|
4203
|
+
`);
|
|
4204
|
+
const r = renderersFor(kind)[tool];
|
|
4205
|
+
if (r) {
|
|
4206
|
+
try {
|
|
4207
|
+
const lines2 = r(result, x);
|
|
4208
|
+
if (lines2.length)
|
|
4209
|
+
return lines2;
|
|
4210
|
+
} catch {}
|
|
4211
|
+
}
|
|
4212
|
+
const { summary, data } = result;
|
|
4213
|
+
const lines = [];
|
|
4214
|
+
if (summary !== undefined)
|
|
4215
|
+
lines.push(...genericLines(summary));
|
|
4216
|
+
if (data !== undefined)
|
|
4217
|
+
lines.push(...genericLines(data));
|
|
4218
|
+
return lines.length ? lines : ["Done."];
|
|
4219
|
+
}
|
|
4220
|
+
var NOTHING_SENT = "Nothing was sent.";
|
|
4221
|
+
var REFUSAL_TEXT = {
|
|
4222
|
+
market_closed: "That market is closed right now. `darwin market-status` shows when US stocks trade again.",
|
|
4223
|
+
quote_expired: "That quote expired (a quote lasts 30 seconds). Get a new one with `darwin quote`.",
|
|
4224
|
+
quote_not_found: "Darwin doesn't know that quote ID. Get a quote with `darwin quote` and place it within 30 seconds.",
|
|
4225
|
+
quote_required: "An order needs a quote first: run `darwin quote`, then `darwin order --quote <id>`.",
|
|
4226
|
+
quote_mismatch: "The order doesn't match its quote: use the same --sell, --amount and --for as the quote.",
|
|
4227
|
+
quote_already_used: "That quote was already used. Check `darwin orders`, or get a new quote.",
|
|
4228
|
+
quote_price_moved: "The price moved beyond the quote's slippage limit. Get a new quote and try again.",
|
|
4229
|
+
quote_wrong_agent: "That quote was made for a different agent.",
|
|
4230
|
+
quote_wrong_endpoint: "That quote is for a token Darwin doesn't list; it can't be placed with `darwin order`.",
|
|
4231
|
+
slippage_exceeds_grant: "That slippage is looser than your owner's maximum for this pair. Use a smaller --max-slippage-bps (`darwin grant` shows the maximum).",
|
|
4232
|
+
per_trade_cap: "This order is over the agent's per-trade limit. Try a smaller amount (`darwin grant` shows the limits).",
|
|
4233
|
+
per_trade_risk: "This order puts more at risk than the agent's per-trade limit allows. Try a smaller amount.",
|
|
4234
|
+
daily_cap: "This agent has used today's trading budget. It resets at midnight UTC; `darwin grant` shows what's left.",
|
|
4235
|
+
daily_tx_count: "This agent has made today's maximum number of trades. It resets at midnight UTC.",
|
|
4236
|
+
daily_risk: "This agent has used today's risk budget. Selling into a stablecoin still works; it resets at midnight UTC.",
|
|
4237
|
+
asset_daily_acquisition: "Today's budget for buying this token is used up. Another token may still work.",
|
|
4238
|
+
below_min_trade: "That amount is below the minimum trade size.",
|
|
4239
|
+
insufficient_sol: "Not enough SOL: selling that much would leave too little for network fees.",
|
|
4240
|
+
self_pay_insolvent: "This agent's wallet can't pay its network fees. Add SOL to its wallet to trade again.",
|
|
4241
|
+
kill_switch: "Darwin has paused agent trading for now. Try again later.",
|
|
4242
|
+
sanctions_blocked: "Refused for compliance reasons.",
|
|
4243
|
+
account_locked: "The owner's Darwin account is locked, so this agent can't trade.",
|
|
4244
|
+
grant_invalid: "This agent can no longer trade with this API key (revoked or expired). Ask the owner, or run `darwin login` again.",
|
|
4245
|
+
rate_limited: "Too many requests with this API key right now. Wait a minute and try again.",
|
|
4246
|
+
relay_unavailable: "Darwin couldn't send the transaction right now. Try again in a few seconds.",
|
|
4247
|
+
relay_rate_limited: "Too many transactions from this wallet just now. Wait a few seconds and try again.",
|
|
4248
|
+
simulation_unavailable: "Darwin couldn't check the transaction just now. Try again in a few seconds.",
|
|
4249
|
+
quote_unavailable: "No price is available for that trade right now. Try again shortly.",
|
|
4250
|
+
price_sanity: "The quoted price looked wrong compared with the market, so Darwin refused it. Try again shortly.",
|
|
4251
|
+
mint_not_in_scope: "This agent isn't allowed to trade that token.",
|
|
4252
|
+
unlisted_disabled: "This agent isn't allowed to trade tokens Darwin doesn't list.",
|
|
4253
|
+
ambiguous_asset: "That symbol names more than one token. Pass the token's mint address instead:"
|
|
4254
|
+
};
|
|
4255
|
+
function refusalLines(result, catalogue, write = false) {
|
|
4256
|
+
const d = obj(result.data);
|
|
4257
|
+
const code = [result.error, d.error, d.refusal].map(txt).find((c) => /^[a-z][a-z0-9_]{0,63}$/.test(c)) ?? "";
|
|
4258
|
+
const rawDetail = txt(result.detail) || txt(d.detail) || txt(d.message);
|
|
4259
|
+
const detail = /^[a-z][a-z0-9_]*$/.test(rawDetail) ? "" : forTerminal(rawDetail, catalogue);
|
|
4260
|
+
const retrySafe = d.retrySafe ?? result.retrySafe;
|
|
4261
|
+
const sent = result.sent === false || retrySafe === true && result.sent !== true;
|
|
4262
|
+
const maybeSent = write && !sent;
|
|
4263
|
+
const tail = sent ? ` ${NOTHING_SENT}` : "";
|
|
4264
|
+
const out = [];
|
|
4265
|
+
const known2 = REFUSAL_TEXT[code];
|
|
4266
|
+
if (maybeSent)
|
|
4267
|
+
out.push(`Darwin couldn't confirm this order was sent${detail ? `: ${detail}` : "."}`, "It may have gone through — check `darwin orders` before trying again.");
|
|
4268
|
+
else if (known2)
|
|
4269
|
+
out.push(`${known2}${code === "ambiguous_asset" ? "" : tail}`);
|
|
4270
|
+
else if (detail)
|
|
4271
|
+
out.push(`Darwin refused this: ${detail}${tail}`);
|
|
4272
|
+
else if (code)
|
|
4273
|
+
out.push(`Darwin refused this (${code.replace(/_/g, " ")}).${tail}`);
|
|
4274
|
+
else
|
|
4275
|
+
out.push(`Darwin refused this${typeof result.status === "number" ? ` (HTTP ${result.status})` : ""}.`);
|
|
4276
|
+
if (!maybeSent && known2 && detail && code !== "ambiguous_asset" && code !== "rate_limited")
|
|
4277
|
+
out.push(`Darwin says: ${detail}`);
|
|
4278
|
+
for (const c of [...arr(result.candidates), ...arr(d.candidates)].map(obj).slice(0, 20)) {
|
|
4279
|
+
out.push(` ${label(c.symbol, 20) || "?"} ${txt(c.mint)}${label(c.issuer, 40) ? ` (${label(c.issuer, 40)})` : ""}`);
|
|
4280
|
+
}
|
|
4281
|
+
if (code && !out[0].includes(code))
|
|
4282
|
+
out.push(`(${code})`);
|
|
4283
|
+
return out;
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
// src/commands.ts
|
|
4287
|
+
init_realms();
|
|
4288
|
+
init_redact();
|
|
4289
|
+
|
|
4290
|
+
// src/session.ts
|
|
4291
|
+
init_context();
|
|
4292
|
+
init_config();
|
|
4293
|
+
init_realms();
|
|
4294
|
+
function realmFromEnv(ctx) {
|
|
4295
|
+
const raw = ctx.env.DARWIN_REALM;
|
|
4296
|
+
if (raw === undefined || raw === "")
|
|
4297
|
+
return "darwin.finance";
|
|
4298
|
+
const r = parseRealm(raw);
|
|
4299
|
+
if (!r)
|
|
4300
|
+
throw new CliError(EXIT.usage, "DARWIN_REALM must be prod or beta.", "usage");
|
|
4301
|
+
return r;
|
|
4302
|
+
}
|
|
4303
|
+
function profileName(ctx, flag) {
|
|
4304
|
+
const n = flag ?? ctx.env.DARWIN_PROFILE;
|
|
4305
|
+
if (n !== undefined && !PROFILE_RE.test(n))
|
|
4306
|
+
throw new CliError(EXIT.usage, "A profile name is 1–40 of a–z, 0–9, _ and -, starting with a letter or digit.", "usage");
|
|
4307
|
+
return n;
|
|
4308
|
+
}
|
|
4309
|
+
function openSession(ctx, flags) {
|
|
4310
|
+
assertInstalled(ctx);
|
|
4311
|
+
const explicit = profileName(ctx, flags.profile);
|
|
4312
|
+
if (!flags.profile) {
|
|
4313
|
+
const fromEnv = envKey(ctx);
|
|
4314
|
+
if (fromEnv !== null) {
|
|
4315
|
+
const { key: key2, kind: kind2 } = acceptKey(fromEnv);
|
|
4316
|
+
return { realm: realmFromEnv(ctx), kind: kind2, key: key2, profileName: "env", profile: null };
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
const cfg = loadConfig(ctx);
|
|
4320
|
+
const name = explicit ?? cfg.default;
|
|
4321
|
+
const profile = name ? cfg.profiles[name] : undefined;
|
|
4322
|
+
if (!name || !profile) {
|
|
4323
|
+
throw new CliError(EXIT.auth, name ? `There's no profile "${name}". \`darwin profile list\` shows them; \`darwin login\` makes one.` : "This terminal isn't connected to Darwin yet. Run `darwin login` (or set DARWIN_API_KEY).", "no_key");
|
|
4324
|
+
}
|
|
4325
|
+
const stored = getKey(ctx, profile.store, profile.realm, name);
|
|
4326
|
+
if (!stored)
|
|
4327
|
+
throw new CliError(EXIT.auth, `The API key for "${name}" is missing from ${profile.store === "keychain" ? ctx.keychain.description : "its file"}. Run \`darwin login\` again.`, "no_key");
|
|
4328
|
+
const { key, kind } = acceptKey(stored);
|
|
4329
|
+
if (kind !== profile.kind)
|
|
4330
|
+
throw new CliError(EXIT.auth, `The key saved for "${name}" is not the kind this profile expects. Run \`darwin login\` again.`, "key_kind_mismatch");
|
|
4331
|
+
return { realm: profile.realm, kind, key, profileName: name, profile };
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
// src/commands.ts
|
|
4335
|
+
init_agents();
|
|
4336
|
+
var OUT_FLAGS = ["json", "format", "quiet", "no-color", "help"];
|
|
4337
|
+
var jsonOut = (ctx, p) => wantsJson(ctx, { json: has(p, "json"), format: one(p, "format") });
|
|
4338
|
+
var unwrap2 = (v) => v && typeof v === "object" && ("untrusted" in v) ? String(v.untrusted ?? "") : typeof v === "string" ? v : "";
|
|
4339
|
+
async function cmdLogout(ctx, p) {
|
|
4340
|
+
onlyFlags(p, ["profile", "all", "revoke", "force-local", ...OUT_FLAGS], "logout");
|
|
4341
|
+
assertInstalled(ctx);
|
|
4342
|
+
if (has(p, "all")) {
|
|
4343
|
+
if (has(p, "revoke"))
|
|
4344
|
+
throw new CliError(EXIT.usage, "--revoke works on one profile at a time.", "usage");
|
|
4345
|
+
let failed = 0;
|
|
4346
|
+
for (const [name2, prof2] of Object.entries(loadConfig(ctx).profiles))
|
|
4347
|
+
failed += forgetProfile(ctx, name2, prof2) ? 0 : 1;
|
|
4348
|
+
return failed ? EXIT.unexpected : EXIT.ok;
|
|
4349
|
+
}
|
|
4350
|
+
const cfg = loadConfig(ctx);
|
|
4351
|
+
const name = profileName(ctx, one(p, "profile")) ?? cfg.default;
|
|
4352
|
+
const prof = name ? cfg.profiles[name] : undefined;
|
|
4353
|
+
if (!name || !prof)
|
|
4354
|
+
throw new CliError(EXIT.usage, "No profile to log out of. `darwin profile list` shows them.", "no_profile");
|
|
4355
|
+
const url = manageUrl(prof.realm, prof.agent_id);
|
|
4356
|
+
if (!has(p, "revoke")) {
|
|
4357
|
+
let key = null;
|
|
4358
|
+
try {
|
|
4359
|
+
key = getKey(ctx, prof.store, prof.realm, name);
|
|
4360
|
+
} catch {
|
|
4361
|
+
key = null;
|
|
4362
|
+
}
|
|
4363
|
+
return forgetProfile(ctx, name, prof, { expectedKey: key }) ? EXIT.ok : EXIT.unexpected;
|
|
4364
|
+
}
|
|
4365
|
+
const s = openSession(ctx, { profile: name });
|
|
4366
|
+
if (s.kind === "agents")
|
|
4367
|
+
warn(ctx, copy.revokeAllWarning);
|
|
4368
|
+
let keyName = "this API key";
|
|
4369
|
+
try {
|
|
4370
|
+
const w = await request(ctx, s.realm, "GET", "/api/agent/v1/tools/whoami", { key: s.key, timeoutMs: 15000 });
|
|
4371
|
+
const n = w.json?.name;
|
|
4372
|
+
if (w.status === 200 && typeof n === "string" && n)
|
|
4373
|
+
keyName = clean(n).slice(0, 120);
|
|
4374
|
+
} catch {}
|
|
4375
|
+
let confirmed = false;
|
|
4376
|
+
let networkFailed = false;
|
|
4377
|
+
try {
|
|
4378
|
+
const res = await request(ctx, s.realm, "POST", "/api/agent/v1/key/revoke", { key: s.key, body: {}, timeoutMs: 20000 });
|
|
4379
|
+
const j = res.json;
|
|
4380
|
+
confirmed = res.status === 200 && mediaType(res.contentType) === "application/json" && !!j && j.revoked === true && typeof j.keyId === "string" && typeof j.changed === "boolean" && Object.keys(j).length === 3;
|
|
4381
|
+
} catch (e) {
|
|
4382
|
+
if (!(e instanceof NetworkError) && !(e instanceof CliError))
|
|
4383
|
+
throw e;
|
|
4384
|
+
networkFailed = e instanceof NetworkError;
|
|
4385
|
+
}
|
|
4386
|
+
const agent = prof.agent_name || prof.agent_id;
|
|
4387
|
+
if (confirmed) {
|
|
4388
|
+
if (!forgetProfile(ctx, name, prof, { expectedKey: s.key, quiet: true }))
|
|
4389
|
+
return EXIT.unexpected;
|
|
4390
|
+
say(ctx, copy.revoked(keyName, agent));
|
|
4391
|
+
return EXIT.ok;
|
|
4392
|
+
}
|
|
4393
|
+
if (networkFailed && has(p, "force-local")) {
|
|
4394
|
+
forgetProfile(ctx, name, prof, { expectedKey: s.key, quiet: true });
|
|
4395
|
+
warn(ctx, copy.revokeNotConfirmed(agent, url).replace(" and it's still saved here", ""));
|
|
4396
|
+
return EXIT.refused;
|
|
4397
|
+
}
|
|
4398
|
+
warn(ctx, copy.revokeNotConfirmed(agent, url));
|
|
4399
|
+
return EXIT.refused;
|
|
4400
|
+
}
|
|
4401
|
+
function forgetProfile(ctx, name, prof, opts = {}) {
|
|
4402
|
+
const outcome = updateConfig(ctx, (c) => {
|
|
4403
|
+
const now = c.profiles[name];
|
|
4404
|
+
if (!now || now.realm !== prof.realm || now.store !== prof.store || now.agent_id !== prof.agent_id)
|
|
4405
|
+
return "changed";
|
|
4406
|
+
if (opts.expectedKey !== undefined) {
|
|
4407
|
+
let current;
|
|
4408
|
+
try {
|
|
4409
|
+
current = getKey(ctx, now.store, now.realm, name);
|
|
4410
|
+
} catch {
|
|
4411
|
+
return "unreadable";
|
|
4412
|
+
}
|
|
4413
|
+
if (current !== null && current !== opts.expectedKey)
|
|
4414
|
+
return "changed";
|
|
4415
|
+
}
|
|
4416
|
+
if (!deleteKey(ctx, now.store, now.realm, name))
|
|
4417
|
+
return "stuck";
|
|
4418
|
+
delete c.profiles[name];
|
|
4419
|
+
if (c.default === name)
|
|
4420
|
+
c.default = Object.keys(c.profiles)[0] ?? "";
|
|
4421
|
+
return "ok";
|
|
4422
|
+
});
|
|
4423
|
+
if (outcome === "changed") {
|
|
4424
|
+
warn(ctx, `Profile "${name}" changed while this ran (another login?), so it was left as it is.`);
|
|
4425
|
+
return false;
|
|
4426
|
+
}
|
|
4427
|
+
if (outcome !== "ok") {
|
|
4428
|
+
warn(ctx, `Couldn't remove the API key for "${name}" from ${prof.store === "keychain" ? ctx.keychain.description : "its file"}, so the profile was kept. Unlock it and try again.`);
|
|
4429
|
+
return false;
|
|
4430
|
+
}
|
|
4431
|
+
if (!opts.quiet)
|
|
4432
|
+
say(ctx, copy.loggedOut(name, manageUrl(prof.realm, prof.agent_id)));
|
|
4433
|
+
return true;
|
|
4434
|
+
}
|
|
4435
|
+
async function cmdWhoami(ctx, p) {
|
|
4436
|
+
onlyFlags(p, ["profile", ...OUT_FLAGS], "whoami");
|
|
4437
|
+
const s = openSession(ctx, { profile: one(p, "profile") });
|
|
4438
|
+
let who = null;
|
|
4439
|
+
let note = null;
|
|
4440
|
+
let exit = EXIT.ok;
|
|
4441
|
+
try {
|
|
4442
|
+
const res = await request(ctx, s.realm, "GET", "/api/agent/v1/tools/whoami", { key: s.key, timeoutMs: 15000 });
|
|
4443
|
+
if (res.status === 200 && res.json && typeof res.json === "object")
|
|
4444
|
+
who = res.json;
|
|
4445
|
+
else if (res.status === 401) {
|
|
4446
|
+
note = "Darwin refused this API key — it may have been revoked.";
|
|
4447
|
+
exit = EXIT.auth;
|
|
4448
|
+
} else if (res.status === 429) {
|
|
4449
|
+
note = "Too many requests with this API key right now.";
|
|
4450
|
+
exit = EXIT.rateLimited;
|
|
4451
|
+
} else if (res.status === 426) {
|
|
4452
|
+
note = copy.updateRequired(typeof res.json?.minCli === "string" ? String(res.json.minCli) : "the latest version");
|
|
4453
|
+
exit = EXIT.upgrade;
|
|
4454
|
+
} else if (res.status === 404) {} else if (res.status === 403) {
|
|
4455
|
+
note = "Darwin refused this request.";
|
|
4456
|
+
exit = EXIT.refused;
|
|
4457
|
+
} else {
|
|
4458
|
+
note = `Darwin answered HTTP ${res.status}.`;
|
|
4459
|
+
exit = EXIT.unexpected;
|
|
4460
|
+
}
|
|
4461
|
+
} catch (e) {
|
|
4462
|
+
if (!(e instanceof NetworkError))
|
|
4463
|
+
throw e;
|
|
4464
|
+
note = e.message;
|
|
4465
|
+
exit = EXIT.network;
|
|
4466
|
+
}
|
|
4467
|
+
const keyName = who && typeof who.name === "string" ? clean(who.name) : "name unavailable";
|
|
4468
|
+
const agent = clean(unwrap2(who?.homeAgentName) || s.profile?.agent_name || s.profile?.agent_id || "unknown agent");
|
|
3561
4469
|
const permissions = s.kind === "agents" ? "Read: all agents · Trade: all active agents" : "Read: this agent · Trade: this agent";
|
|
3562
4470
|
const store = s.profile ? storeLabel(ctx, s.profile.store, s.realm, s.profileName) : "DARWIN_API_KEY (environment)";
|
|
3563
4471
|
if (jsonOut(ctx, p))
|
|
@@ -3570,6 +4478,18 @@ async function cmdWhoami(ctx, p) {
|
|
|
3570
4478
|
warn(ctx, `Reminder: this API key is in a plain file (${storeLabel(ctx, "file", s.realm, s.profileName).slice(8, -1)}).`);
|
|
3571
4479
|
return exit;
|
|
3572
4480
|
}
|
|
4481
|
+
var DOCTOR_LABEL = {
|
|
4482
|
+
version: "Darwin CLI",
|
|
4483
|
+
node: "Node",
|
|
4484
|
+
platform: "Platform",
|
|
4485
|
+
install: "Install",
|
|
4486
|
+
secretStore: "Secret store",
|
|
4487
|
+
config: "Config",
|
|
4488
|
+
profiles: "Profiles",
|
|
4489
|
+
defaultProfile: "Default profile",
|
|
4490
|
+
key: "API key",
|
|
4491
|
+
catalogue: "Command list"
|
|
4492
|
+
};
|
|
3573
4493
|
async function cmdDoctor(ctx, p) {
|
|
3574
4494
|
onlyFlags(p, ["profile", ...OUT_FLAGS], "doctor");
|
|
3575
4495
|
const checks = { version: VERSION, node: process.version, platform: `${ctx.platform}-${process.arch}` };
|
|
@@ -3613,9 +4533,11 @@ async function cmdDoctor(ctx, p) {
|
|
|
3613
4533
|
}
|
|
3614
4534
|
if (jsonOut(ctx, p))
|
|
3615
4535
|
printJson(ctx, checks);
|
|
3616
|
-
else
|
|
3617
|
-
|
|
3618
|
-
|
|
4536
|
+
else {
|
|
4537
|
+
const width = Math.max(...Object.keys(checks).map((k) => (DOCTOR_LABEL[k] ?? k).length));
|
|
4538
|
+
for (const [k, v] of Object.entries(checks))
|
|
4539
|
+
say(ctx, `${(DOCTOR_LABEL[k] ?? k).padEnd(width)} ${v === null ? "none" : String(v)}`);
|
|
4540
|
+
}
|
|
3619
4541
|
return EXIT.ok;
|
|
3620
4542
|
}
|
|
3621
4543
|
async function cmdProfile(ctx, p) {
|
|
@@ -3635,9 +4557,18 @@ async function cmdProfile(ctx, p) {
|
|
|
3635
4557
|
printJson(ctx, { profiles: rows });
|
|
3636
4558
|
else if (rows.length === 0)
|
|
3637
4559
|
say(ctx, "No profiles yet. Run `darwin login`.");
|
|
3638
|
-
else
|
|
3639
|
-
|
|
4560
|
+
else {
|
|
4561
|
+
const table2 = columns(["Profile", "Site", "Agent", "API key for", "Stored in"], rows.map((r) => [
|
|
4562
|
+
`${r.default ? "* " : " "}${r.profile}`,
|
|
4563
|
+
r.realm,
|
|
4564
|
+
clean(r.agent).slice(0, 40),
|
|
4565
|
+
r.kind === "agents" ? `all agents${r.defaultAgent ? " (default set)" : ""}` : "this agent",
|
|
4566
|
+
r.store === "keychain" ? ctx.keychain.description : "a plain file"
|
|
4567
|
+
]));
|
|
4568
|
+
for (const l of table2)
|
|
3640
4569
|
say(ctx, l);
|
|
4570
|
+
say(ctx, "* = the profile commands use. Change it: darwin profile use <name>");
|
|
4571
|
+
}
|
|
3641
4572
|
return EXIT.ok;
|
|
3642
4573
|
}
|
|
3643
4574
|
case "use":
|
|
@@ -3694,9 +4625,9 @@ async function cmdProfile(ctx, p) {
|
|
|
3694
4625
|
throw new CliError(EXIT.usage, "Only a profile with an API key for all your agents has a default agent.", "usage");
|
|
3695
4626
|
if (!a)
|
|
3696
4627
|
throw new CliError(EXIT.usage, "Name the agent: `darwin profile set-agent <name or id>`.", "usage");
|
|
3697
|
-
const
|
|
3698
|
-
const hit =
|
|
3699
|
-
const m =
|
|
4628
|
+
const agents2 = await listAgents(ctx, s, { fresh: true });
|
|
4629
|
+
const hit = agents2.find((x) => x.id === a) ?? (() => {
|
|
4630
|
+
const m = agents2.filter((x) => x.name.toLowerCase() === a.toLowerCase());
|
|
3700
4631
|
if (m.length > 1)
|
|
3701
4632
|
throw new CliError(EXIT.usage, `More than one agent is called that; use its id: ${m.map((x) => x.id).join(", ")}`, "agent_ambiguous");
|
|
3702
4633
|
return m[0];
|
|
@@ -3737,7 +4668,7 @@ async function cmdApi(ctx, p) {
|
|
|
3737
4668
|
if (jsonOut(ctx, p))
|
|
3738
4669
|
printJson(ctx, body);
|
|
3739
4670
|
else
|
|
3740
|
-
for (const l of
|
|
4671
|
+
for (const l of genericLines(body))
|
|
3741
4672
|
say(ctx, l);
|
|
3742
4673
|
return res.status === 200 ? EXIT.ok : res.status === 401 ? EXIT.auth : res.status === 429 ? EXIT.rateLimited : EXIT.refused;
|
|
3743
4674
|
}
|
|
@@ -3746,7 +4677,7 @@ async function cmdApi(ctx, p) {
|
|
|
3746
4677
|
init_context();
|
|
3747
4678
|
import { linkSync, lstatSync as lstatSync3, readFileSync, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "node:fs";
|
|
3748
4679
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3749
|
-
import { join as join7 } from "node:path";
|
|
4680
|
+
import { join as join7, sep } from "node:path";
|
|
3750
4681
|
init_copy();
|
|
3751
4682
|
init_config();
|
|
3752
4683
|
init_http();
|
|
@@ -4160,7 +5091,7 @@ function chooseStore(ctx, p) {
|
|
|
4160
5091
|
if (s === "file") {
|
|
4161
5092
|
if (!probeFileStore(ctx))
|
|
4162
5093
|
throw new CliError(EXIT.auth, `Nothing was started: the Darwin CLI can't keep a private file in ${join7(ctx.configDir, "credentials")} (it must be yours and chmod 700).`, "no_secret_store");
|
|
4163
|
-
warn(ctx, copy.fileStoreWarning(join7(ctx.configDir, "credentials"
|
|
5094
|
+
warn(ctx, copy.fileStoreWarning(`${join7(ctx.configDir, "credentials")}${sep}…`));
|
|
4164
5095
|
return "file";
|
|
4165
5096
|
}
|
|
4166
5097
|
if (!probeKeychain(ctx))
|
|
@@ -4487,18 +5418,21 @@ async function finishPickup(ctx, p, j, json) {
|
|
|
4487
5418
|
if (!saved)
|
|
4488
5419
|
throw new CliError(EXIT.unexpected, lostKeyLine(k), "store_failed", { agentId, manageUrl: manageUrl(p.realm, agentId) });
|
|
4489
5420
|
let welcome = null;
|
|
5421
|
+
let hello2 = null;
|
|
4490
5422
|
try {
|
|
4491
5423
|
const h = await request(ctx, p.realm, "GET", "/api/agent/v1/hello", { key, agent: kind === "agents" ? agentId : undefined, timeoutMs: 20000 });
|
|
4492
5424
|
const w = h.json?.welcome;
|
|
4493
5425
|
if (h.status === 200 && typeof w === "string")
|
|
4494
5426
|
welcome = clean(w).slice(0, 8000);
|
|
5427
|
+
if (h.status === 200 && h.json && typeof h.json === "object" && !Array.isArray(h.json))
|
|
5428
|
+
hello2 = h.json;
|
|
4495
5429
|
} catch {}
|
|
4496
5430
|
if (json)
|
|
4497
5431
|
printJson(ctx, { status: "connected", profile, realm: p.realm, kind, agentId, agent: agentName, keyName, store: k.store, permissions: kind === "agents" ? "Read: all agents · Trade: all active agents" : "Read: this agent · Trade: this agent", welcome });
|
|
4498
5432
|
else {
|
|
4499
|
-
if (welcome)
|
|
4500
|
-
say(ctx, welcome);
|
|
4501
5433
|
say(ctx, connectedLine(ctx, k));
|
|
5434
|
+
for (const l of welcomeLines(p.realm, hello2, agentName || agentId))
|
|
5435
|
+
say(ctx, l);
|
|
4502
5436
|
}
|
|
4503
5437
|
return EXIT.ok;
|
|
4504
5438
|
}
|
|
@@ -4513,6 +5447,10 @@ init_context();
|
|
|
4513
5447
|
init_copy();
|
|
4514
5448
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
4515
5449
|
init_http();
|
|
5450
|
+
init_fsx();
|
|
5451
|
+
init_redact();
|
|
5452
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
5453
|
+
import { join as join8 } from "node:path";
|
|
4516
5454
|
init_agents();
|
|
4517
5455
|
var B62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
4518
5456
|
function newNonce() {
|
|
@@ -4638,7 +5576,7 @@ function exitFor(tool, body) {
|
|
|
4638
5576
|
return EXIT.usage;
|
|
4639
5577
|
return EXIT.refused;
|
|
4640
5578
|
}
|
|
4641
|
-
async function callTool(o, agentId) {
|
|
5579
|
+
async function callTool(o, agentId, limits = {}) {
|
|
4642
5580
|
const { ctx, session, tool } = o;
|
|
4643
5581
|
const args = { ...o.args };
|
|
4644
5582
|
let nonce = null;
|
|
@@ -4653,12 +5591,12 @@ async function callTool(o, agentId) {
|
|
|
4653
5591
|
key: session.key,
|
|
4654
5592
|
agent: agentId ?? undefined,
|
|
4655
5593
|
body: { arguments: args },
|
|
4656
|
-
timeoutMs: tool.write ? 30000 : 20000
|
|
5594
|
+
timeoutMs: limits.timeoutMs ?? (tool.write ? 30000 : 20000)
|
|
4657
5595
|
});
|
|
4658
5596
|
let res;
|
|
4659
5597
|
try {
|
|
4660
5598
|
res = await send();
|
|
4661
|
-
if (!tool.write && res.status === 429) {
|
|
5599
|
+
if (!tool.write && !limits.noRetry && res.status === 429) {
|
|
4662
5600
|
const ra = Number(res.headers.get("retry-after") ?? "NaN");
|
|
4663
5601
|
if (Number.isFinite(ra) && ra >= 0 && ra <= 10) {
|
|
4664
5602
|
await ctx.sleep(ra * 1000);
|
|
@@ -4713,30 +5651,8 @@ async function callTool(o, agentId) {
|
|
|
4713
5651
|
}
|
|
4714
5652
|
async function runTool(o) {
|
|
4715
5653
|
const { ctx, session, tool, flags } = o;
|
|
4716
|
-
if (flags.dryRun)
|
|
4717
|
-
|
|
4718
|
-
if (session.kind === "agents" && tool.name !== "list_agents" && !named)
|
|
4719
|
-
throw new CliError(EXIT.usage, copy.needAgent, "agent_required");
|
|
4720
|
-
const agentId2 = named || null;
|
|
4721
|
-
const preview = {
|
|
4722
|
-
dryRun: true,
|
|
4723
|
-
sent: false,
|
|
4724
|
-
command: `darwin ${tool.cli.path.join(" ")}`,
|
|
4725
|
-
tool: tool.name,
|
|
4726
|
-
write: tool.write,
|
|
4727
|
-
costsTx: tool.costsTx,
|
|
4728
|
-
realm: session.realm,
|
|
4729
|
-
agent: agentId2,
|
|
4730
|
-
arguments: o.args,
|
|
4731
|
-
note: tool.idempotency ? "An order ID (nonce) is generated when the command really runs." : undefined
|
|
4732
|
-
};
|
|
4733
|
-
if (o.json)
|
|
4734
|
-
printJson(ctx, preview);
|
|
4735
|
-
else
|
|
4736
|
-
for (const l of renderText(preview))
|
|
4737
|
-
say(ctx, l);
|
|
4738
|
-
return EXIT.ok;
|
|
4739
|
-
}
|
|
5654
|
+
if (flags.dryRun)
|
|
5655
|
+
return dryRun(o);
|
|
4740
5656
|
const agentId = tool.name === "list_agents" ? null : await resolveAgent(ctx, session, flags.agent);
|
|
4741
5657
|
const out = await callTool(o, agentId);
|
|
4742
5658
|
if (out.message === "unknown_tool") {
|
|
@@ -4745,41 +5661,157 @@ async function runTool(o) {
|
|
|
4745
5661
|
out.message = `\`darwin ${tool.cli.path.join(" ")}\` isn't available on ${session.realm} right now. Nothing was done. The command list was refreshed — see \`darwin help\`.`;
|
|
4746
5662
|
}
|
|
4747
5663
|
const result = out.body?.result ?? null;
|
|
5664
|
+
if (out.exit === EXIT.ok && result && QUOTE_TOOLS.has(tool.name))
|
|
5665
|
+
rememberQuote(o, result);
|
|
4748
5666
|
if (o.json) {
|
|
4749
5667
|
printJson(ctx, result ? { ...result, ...out.nonce ? { nonce: out.nonce } : {} } : { error: out.body?.error ?? "failed", detail: out.message, ...out.nonce ? { nonce: out.nonce } : {} });
|
|
4750
5668
|
if (out.message)
|
|
4751
5669
|
warn(ctx, out.message);
|
|
5670
|
+
if (out.exit === EXIT.ok)
|
|
5671
|
+
jsonFooter(o, out, result);
|
|
5672
|
+
else if (out.nonce && tool.write)
|
|
5673
|
+
warn(ctx, `Order nonce: ${out.nonce}`);
|
|
5674
|
+
return out.exit;
|
|
5675
|
+
}
|
|
5676
|
+
const x = await renderCtx(o, out, agentId);
|
|
5677
|
+
if (out.exit === EXIT.ok && result) {
|
|
5678
|
+
for (const l of renderResult(tool.name, session.kind, result, x))
|
|
5679
|
+
say(ctx, l);
|
|
5680
|
+
try {
|
|
5681
|
+
if (await afterHumanSuccess(o, result, agentId) === "failed")
|
|
5682
|
+
return EXIT.refused;
|
|
5683
|
+
} catch {}
|
|
4752
5684
|
} else {
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
const { summary, data, welcome, ...rest } = result;
|
|
4758
|
-
if (typeof welcome === "string")
|
|
4759
|
-
say(ctx, cell(welcome));
|
|
4760
|
-
if (summary !== undefined)
|
|
4761
|
-
for (const l of renderText(summary))
|
|
4762
|
-
say(ctx, l);
|
|
4763
|
-
if (data !== undefined)
|
|
4764
|
-
for (const l of renderText(data))
|
|
4765
|
-
say(ctx, l);
|
|
4766
|
-
for (const l of renderText(Object.fromEntries(Object.entries(rest).filter(([k]) => k !== "status"))))
|
|
4767
|
-
if (l.trim())
|
|
4768
|
-
say(ctx, l);
|
|
4769
|
-
}
|
|
4770
|
-
} else if (result) {
|
|
4771
|
-
warn(ctx, `${cell(result.detail ?? result.error ?? "Refused.")}${typeof result.error === "string" ? ` (${cell(result.error)})` : ""}`);
|
|
4772
|
-
}
|
|
5685
|
+
const covered = out.exit === EXIT.paused || out.exit === EXIT.uncertain || out.message === copy.nonceConflict;
|
|
5686
|
+
if (result && !covered)
|
|
5687
|
+
for (const l of refusalLines(result, o.catalogue, tool.write))
|
|
5688
|
+
warn(ctx, l);
|
|
4773
5689
|
if (out.message)
|
|
4774
5690
|
warn(ctx, out.message);
|
|
5691
|
+
if (out.nonce && tool.write)
|
|
5692
|
+
warn(ctx, `Order nonce: ${out.nonce}`);
|
|
4775
5693
|
}
|
|
4776
|
-
if (out.exit === EXIT.ok)
|
|
4777
|
-
afterSuccess(o, out, result);
|
|
4778
|
-
else if (out.nonce && tool.write)
|
|
4779
|
-
warn(ctx, `Order ID: ${out.nonce}`);
|
|
4780
5694
|
return out.exit;
|
|
4781
5695
|
}
|
|
4782
|
-
|
|
5696
|
+
var QUOTE_TOOLS = new Set(["get_spot_quote"]);
|
|
5697
|
+
var EXTRA_READ_MS = 5000;
|
|
5698
|
+
var CONFIRM_BUDGET_MS = 12000;
|
|
5699
|
+
var SPOT_WRITES = new Set(["place_spot_order", "spot_order_now"]);
|
|
5700
|
+
async function readTool(o, name, args, agentId, deadline) {
|
|
5701
|
+
const t = o.catalogue.tools.find((x) => x.name === name);
|
|
5702
|
+
if (!t || t.write)
|
|
5703
|
+
return null;
|
|
5704
|
+
const left = deadline === undefined ? EXTRA_READ_MS : Math.min(EXTRA_READ_MS, deadline - o.ctx.now());
|
|
5705
|
+
if (left < 500)
|
|
5706
|
+
return null;
|
|
5707
|
+
try {
|
|
5708
|
+
const r = await callTool({ ctx: o.ctx, session: o.session, catalogue: o.catalogue, tool: t, args, flags: o.flags }, agentId, { timeoutMs: left, noRetry: true });
|
|
5709
|
+
return r.exit === EXIT.ok && r.body?.result && typeof r.body.result === "object" ? r.body.result : null;
|
|
5710
|
+
} catch {
|
|
5711
|
+
return null;
|
|
5712
|
+
}
|
|
5713
|
+
}
|
|
5714
|
+
async function renderCtx(o, out, agentId) {
|
|
5715
|
+
const { ctx, session, tool } = o;
|
|
5716
|
+
const agentLabel = session.kind === "agents" ? clean(o.flags.agent || (agentId && agentId === session.profile?.agent_id ? session.profile.agent_name : "") || agentId || "this agent") : clean(session.profile?.agent_name || "this agent");
|
|
5717
|
+
const x = { realm: session.realm, now: ctx.now(), args: o.args, catalogue: o.catalogue, agentLabel, nonce: out.nonce };
|
|
5718
|
+
if (out.exit === EXIT.ok && (tool.name === "list_spot_orders" || tool.name === "list_trades")) {
|
|
5719
|
+
const m = await readTool(o, "list_spot_markets", {}, agentId);
|
|
5720
|
+
if (m) {
|
|
5721
|
+
try {
|
|
5722
|
+
x.markets = marketsByMint(m);
|
|
5723
|
+
} catch {}
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5726
|
+
return x;
|
|
5727
|
+
}
|
|
5728
|
+
function marketsByMint(result) {
|
|
5729
|
+
const map = new Map;
|
|
5730
|
+
const list = result.data?.instruments ?? [];
|
|
5731
|
+
if (!Array.isArray(list))
|
|
5732
|
+
return map;
|
|
5733
|
+
for (const i of list) {
|
|
5734
|
+
if (!i || typeof i !== "object" || Array.isArray(i))
|
|
5735
|
+
continue;
|
|
5736
|
+
const r = i;
|
|
5737
|
+
const mint = typeof r.mint === "string" ? r.mint : "";
|
|
5738
|
+
if (!mint || r.instrumentType === "perp")
|
|
5739
|
+
continue;
|
|
5740
|
+
map.set(mint, { symbol: cell(r.symbol).slice(0, 24) || mint.slice(0, 4), decimals: typeof r.decimals === "number" ? r.decimals : null });
|
|
5741
|
+
}
|
|
5742
|
+
return map;
|
|
5743
|
+
}
|
|
5744
|
+
async function afterHumanSuccess(o, result, agentId) {
|
|
5745
|
+
const { ctx, tool } = o;
|
|
5746
|
+
const data = result.data ?? {};
|
|
5747
|
+
if (QUOTE_TOOLS.has(tool.name)) {
|
|
5748
|
+
const inMint = typeof data.inputMint === "string" ? data.inputMint : null;
|
|
5749
|
+
const inAtoms = typeof data.inAtoms === "string" && /^[0-9]{1,40}$/.test(data.inAtoms) ? BigInt(data.inAtoms) : null;
|
|
5750
|
+
if (inMint && inAtoms !== null && inMint !== WSOL_MINT) {
|
|
5751
|
+
const b = await readTool(o, "get_balances", {}, agentId);
|
|
5752
|
+
const bd = b?.data ?? null;
|
|
5753
|
+
const rows = Array.isArray(bd?.balances) ? bd.balances : null;
|
|
5754
|
+
if (rows && bd?.partial !== true) {
|
|
5755
|
+
const row = rows.find((r) => r?.mint === inMint);
|
|
5756
|
+
const held = row && typeof row.atoms === "string" && /^[0-9]{1,40}$/.test(row.atoms) ? BigInt(row.atoms) : 0n;
|
|
5757
|
+
if (held < inAtoms) {
|
|
5758
|
+
const sym = cell(result.summary?.sell?.symbol ?? "") || "of this token";
|
|
5759
|
+
const amt = row ? typeof row.amount === "string" ? cell(row.amount) : formatAtoms(row.atoms, typeof row.decimals === "number" ? row.decimals : null) : "0";
|
|
5760
|
+
warn(ctx, `⚠ This agent holds ${amt || "less than that"} ${sym}, so an order for this quote would fail.`);
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5764
|
+
return;
|
|
5765
|
+
}
|
|
5766
|
+
if (!SPOT_WRITES.has(tool.name))
|
|
5767
|
+
return;
|
|
5768
|
+
const summary = result.summary ?? {};
|
|
5769
|
+
const sig = typeof summary.txSignature === "string" ? summary.txSignature : typeof data.txSignature === "string" ? data.txSignature : null;
|
|
5770
|
+
const status = typeof summary.status === "string" ? summary.status : data.status;
|
|
5771
|
+
if (status === "failed") {
|
|
5772
|
+
warn(ctx, `The order failed, so nothing was traded.${sig ? ` Details: darwin tx ${sig}` : ""}`);
|
|
5773
|
+
return "failed";
|
|
5774
|
+
}
|
|
5775
|
+
if (sig && /^[1-9A-HJ-NP-Za-km-z]{64,90}$/.test(sig) && status !== "confirmed") {
|
|
5776
|
+
const deadline = ctx.now() + CONFIRM_BUDGET_MS;
|
|
5777
|
+
let landed = null;
|
|
5778
|
+
for (let i = 0;i < CONFIRM_POLLS && !landed && ctx.now() + CONFIRM_INTERVAL_MS < deadline; i++) {
|
|
5779
|
+
await ctx.sleep(CONFIRM_INTERVAL_MS);
|
|
5780
|
+
const t = await readTool(o, "get_tx_status", { signature: sig }, agentId, deadline);
|
|
5781
|
+
const st = t?.data?.status;
|
|
5782
|
+
if (st === "confirmed" || st === "failed")
|
|
5783
|
+
landed = st;
|
|
5784
|
+
}
|
|
5785
|
+
if (landed === "confirmed") {
|
|
5786
|
+
const got = await receivedFor(o, data.orderId ?? summary.orderId, agentId, ctx.now() + EXTRA_READ_MS);
|
|
5787
|
+
say(ctx, got ? `Confirmed on chain: received ${got}.` : "Confirmed on chain.");
|
|
5788
|
+
} else if (landed === "failed") {
|
|
5789
|
+
warn(ctx, `The transaction failed on chain, so nothing was traded. Details: darwin tx ${sig}`);
|
|
5790
|
+
return "failed";
|
|
5791
|
+
} else {
|
|
5792
|
+
say(ctx, `Not confirmed yet. Check it with: darwin tx ${sig}`);
|
|
5793
|
+
}
|
|
5794
|
+
}
|
|
5795
|
+
const g = await readTool(o, "get_grant", {}, agentId);
|
|
5796
|
+
const left = g?.data?.today?.remaining;
|
|
5797
|
+
if (left && typeof left.txCount === "number")
|
|
5798
|
+
say(ctx, `Trades left today: ${left.txCount}`);
|
|
5799
|
+
}
|
|
5800
|
+
var CONFIRM_POLLS = 4;
|
|
5801
|
+
var CONFIRM_INTERVAL_MS = 2000;
|
|
5802
|
+
async function receivedFor(o, orderId, agentId, deadline) {
|
|
5803
|
+
if (typeof orderId !== "string" || !orderId)
|
|
5804
|
+
return null;
|
|
5805
|
+
const [list, markets] = await Promise.all([readTool(o, "list_spot_orders", {}, agentId, deadline), readTool(o, "list_spot_markets", {}, agentId, deadline)]);
|
|
5806
|
+
const rows = list?.data?.orders ?? [];
|
|
5807
|
+
const row = Array.isArray(rows) ? rows.find((r) => r?.orderId === orderId) : undefined;
|
|
5808
|
+
if (!row || typeof row.outputAtoms !== "string")
|
|
5809
|
+
return null;
|
|
5810
|
+
const m = markets ? marketsByMint(markets).get(String(row.outputMint)) : undefined;
|
|
5811
|
+
const amt = m ? formatAtoms(row.outputAtoms, m.decimals) : "";
|
|
5812
|
+
return amt ? `${amt} ${m.symbol}` : null;
|
|
5813
|
+
}
|
|
5814
|
+
function jsonFooter(o, out, result) {
|
|
4783
5815
|
const { ctx, tool, args } = o;
|
|
4784
5816
|
const summary = result?.summary ?? {};
|
|
4785
5817
|
const data = result?.data ?? {};
|
|
@@ -4793,14 +5825,112 @@ function afterSuccess(o, out, result) {
|
|
|
4793
5825
|
warn(ctx, copy.quoteFooter(quoteId, secs, text(args.sell), typeof sellAmount === "string" ? text(sellAmount) : text(args.amount), text(args.for)));
|
|
4794
5826
|
}
|
|
4795
5827
|
}
|
|
4796
|
-
if ((tool.name
|
|
4797
|
-
const
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
5828
|
+
if (SPOT_WRITES.has(tool.name) && out.nonce) {
|
|
5829
|
+
const orderId = typeof data.orderId === "string" ? data.orderId : typeof summary.orderId === "string" ? summary.orderId : null;
|
|
5830
|
+
warn(ctx, copy.spotSent(text(args.amount), text(args.sell), text(args.for), agentFor(o, out), orderId ? cell(orderId) : null, out.nonce));
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5833
|
+
var agentFor = (o, out) => o.session.kind === "agents" ? o.flags.agent || o.session.profile?.default_agent || out.agentId || "this agent" : o.session.profile?.agent_name || "this agent";
|
|
5834
|
+
function quotesFile(o) {
|
|
5835
|
+
return join8(o.ctx.configDir, "cache", `quotes-${o.session.realm}-${createHash2("sha256").update(o.session.key).digest("hex").slice(0, 16)}.json`);
|
|
5836
|
+
}
|
|
5837
|
+
function readQuotes(o) {
|
|
5838
|
+
try {
|
|
5839
|
+
const t = readPrivate(quotesFile(o), 64 * 1024, { requirePrivateMode: false });
|
|
5840
|
+
const v = t ? JSON.parse(t) : [];
|
|
5841
|
+
return Array.isArray(v) ? v.filter((q) => !!q && typeof q.id === "string" && typeof q.expiresAtMs === "number") : [];
|
|
5842
|
+
} catch {
|
|
5843
|
+
return [];
|
|
5844
|
+
}
|
|
5845
|
+
}
|
|
5846
|
+
function rememberQuote(o, result) {
|
|
5847
|
+
const data = result.data ?? {};
|
|
5848
|
+
const sell = result.summary?.sell ?? {};
|
|
5849
|
+
if (typeof data.quoteId !== "string" || !/^[A-Za-z0-9_-]{1,80}$/.test(data.quoteId) || typeof data.expiresAtMs !== "number")
|
|
5850
|
+
return;
|
|
5851
|
+
const rec = {
|
|
5852
|
+
id: data.quoteId,
|
|
5853
|
+
sell: String(o.args.sell ?? ""),
|
|
5854
|
+
for: String(o.args.for ?? ""),
|
|
5855
|
+
amount: typeof sell.amount === "string" ? sell.amount : String(o.args.amount ?? ""),
|
|
5856
|
+
expiresAtMs: data.expiresAtMs
|
|
5857
|
+
};
|
|
5858
|
+
const now = o.ctx.now();
|
|
5859
|
+
const keep = readQuotes(o).filter((q) => q.expiresAtMs > now - 3600000 && q.id !== rec.id).slice(-19);
|
|
5860
|
+
try {
|
|
5861
|
+
writePrivate(quotesFile(o), JSON.stringify([...keep, rec]));
|
|
5862
|
+
} catch {}
|
|
5863
|
+
}
|
|
5864
|
+
var sameToken = (a, b) => {
|
|
5865
|
+
const mint = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
5866
|
+
return mint.test(a.trim()) || mint.test(b.trim()) ? a.trim() === b.trim() : a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
5867
|
+
};
|
|
5868
|
+
var sameAmount = (a, b) => {
|
|
5869
|
+
const n = (s) => (s.includes(".") ? s.replace(/0+$/, "").replace(/\.$/, "") : s).replace(/^0+(?=\d)/, "");
|
|
5870
|
+
return n(a.trim()) === n(b.trim());
|
|
5871
|
+
};
|
|
5872
|
+
function dryRun(o) {
|
|
5873
|
+
const { ctx, session, tool } = o;
|
|
5874
|
+
const named = (o.flags.agent ?? ctx.env.DARWIN_AGENT ?? "").trim() || (session.kind === "agents" ? session.profile?.default_agent ?? "" : "");
|
|
5875
|
+
if (session.kind === "agents" && tool.name !== "list_agents" && !named)
|
|
5876
|
+
throw new CliError(EXIT.usage, copy.needAgent, "agent_required");
|
|
5877
|
+
const agentId = named || null;
|
|
5878
|
+
const checked = [];
|
|
5879
|
+
const problems = [];
|
|
5880
|
+
const unchecked = [];
|
|
5881
|
+
if (tool.name === "place_spot_order" && typeof o.args.quoteId === "string") {
|
|
5882
|
+
const q = readQuotes(o).find((r) => r.id === o.args.quoteId);
|
|
5883
|
+
if (!q) {
|
|
5884
|
+
unchecked.push("the quote: this terminal didn't make it (or made it over an hour ago), so only Darwin can say whether it exists");
|
|
5885
|
+
} else {
|
|
5886
|
+
if (q.expiresAtMs <= ctx.now())
|
|
5887
|
+
problems.push(`the quote expired at ${new Date(q.expiresAtMs).toLocaleTimeString("en-US")}; get a new one with \`darwin quote\``);
|
|
5888
|
+
else
|
|
5889
|
+
checked.push(`the quote is one this terminal made, and it has ${Math.ceil((q.expiresAtMs - ctx.now()) / 1000)}s left`);
|
|
5890
|
+
const diffs = [
|
|
5891
|
+
!sameToken(String(o.args.sell ?? ""), q.sell) ? `--sell (quote: ${clean(q.sell)})` : "",
|
|
5892
|
+
!sameToken(String(o.args.for ?? ""), q.for) ? `--for (quote: ${clean(q.for)})` : "",
|
|
5893
|
+
!sameAmount(String(o.args.amount ?? ""), q.amount) ? `--amount (quote: ${q.amount})` : ""
|
|
5894
|
+
].filter(Boolean);
|
|
5895
|
+
if (diffs.length)
|
|
5896
|
+
problems.push(`this doesn't match the quote: ${diffs.join(", ")}`);
|
|
5897
|
+
else
|
|
5898
|
+
checked.push("--sell, --amount and --for match the quote");
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
if (tool.write)
|
|
5902
|
+
unchecked.push("the balance, the agent's limits and the price — Darwin checks those when the command really runs");
|
|
5903
|
+
const preview = {
|
|
5904
|
+
dryRun: true,
|
|
5905
|
+
sent: false,
|
|
5906
|
+
command: `darwin ${tool.cli.path.join(" ")}`,
|
|
5907
|
+
tool: tool.name,
|
|
5908
|
+
write: tool.write,
|
|
5909
|
+
costsTx: tool.costsTx,
|
|
5910
|
+
realm: session.realm,
|
|
5911
|
+
agent: agentId,
|
|
5912
|
+
arguments: o.args,
|
|
5913
|
+
note: tool.idempotency ? "An order ID (nonce) is generated when the command really runs." : undefined
|
|
5914
|
+
};
|
|
5915
|
+
if (o.json) {
|
|
5916
|
+
printJson(ctx, preview);
|
|
5917
|
+
for (const p of problems)
|
|
5918
|
+
warn(ctx, `Dry run check failed: ${p}`);
|
|
5919
|
+
return EXIT.ok;
|
|
5920
|
+
} else {
|
|
5921
|
+
say(ctx, `Dry run — nothing was sent. Would run: darwin ${tool.cli.path.join(" ")} on ${session.realm}${agentId ? ` for agent ${clean(agentId)}` : ""}`);
|
|
5922
|
+
for (const [k, v] of Object.entries(o.args)) {
|
|
5923
|
+
const flag = tool.cli.args[k]?.flag;
|
|
5924
|
+
say(ctx, flag ? ` --${flag} ${cell(v)}` : tool.cli.positional.includes(k) ? ` <${clean(k)}> ${cell(v)}` : ` --field ${clean(k)}=${cell(v)}`);
|
|
5925
|
+
}
|
|
5926
|
+
for (const c of checked)
|
|
5927
|
+
say(ctx, `✓ ${c}`);
|
|
5928
|
+
for (const p of problems)
|
|
5929
|
+
say(ctx, `✗ ${p}`);
|
|
5930
|
+
for (const u of unchecked)
|
|
5931
|
+
say(ctx, `Not checked: ${u}`);
|
|
4803
5932
|
}
|
|
5933
|
+
return problems.length ? EXIT.refused : EXIT.ok;
|
|
4804
5934
|
}
|
|
4805
5935
|
|
|
4806
5936
|
// src/mcp.ts
|
|
@@ -4952,219 +6082,58 @@ async function mcpCall(ctx, session, catalogue, params, refresh) {
|
|
|
4952
6082
|
return toolResult({ ...result, ...out.nonce ? { nonce: out.nonce } : {} }, out.exit !== EXIT.ok);
|
|
4953
6083
|
}
|
|
4954
6084
|
|
|
4955
|
-
// src/
|
|
6085
|
+
// src/local-flags.ts
|
|
4956
6086
|
init_context();
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
}
|
|
4984
|
-
let res;
|
|
4985
|
-
try {
|
|
4986
|
-
res = await request(ctx, realm, "GET", MARKET_STATUS_PATH, { timeoutMs: 15000 });
|
|
4987
|
-
} catch (e) {
|
|
4988
|
-
if (e instanceof NetworkError)
|
|
4989
|
-
throw new CliError(EXIT.network, e.message, "network_error");
|
|
4990
|
-
throw e;
|
|
4991
|
-
}
|
|
4992
|
-
const s = res.json;
|
|
4993
|
-
if (res.status === 429)
|
|
4994
|
-
throw new CliError(EXIT.rateLimited, "Too many requests right now; try again in a minute.", "rate_limited");
|
|
4995
|
-
if (res.status !== 200 || !s || typeof s.open !== "boolean")
|
|
4996
|
-
throw new CliError(EXIT.unexpected, `${realm} didn't answer the market status (HTTP ${res.status}).`, "bad_response");
|
|
4997
|
-
if (wantsJson(ctx, { json: has(p, "json"), format: one(p, "format") })) {
|
|
4998
|
-
printJson(ctx, s);
|
|
4999
|
-
return EXIT.ok;
|
|
5000
|
-
}
|
|
5001
|
-
const session = SESSION[String(s.session)] ?? cell(s.session);
|
|
5002
|
-
const why = typeof s.reason === "string" && s.reason ? ` (${cell(s.reason).replace(/_/g, " ")}${typeof s.holidayName === "string" && s.holidayName ? `: ${cell(s.holidayName)}` : ""})` : "";
|
|
5003
|
-
say(ctx, `US stock market: ${session}${why}${s.earlyClose === true ? " — early close today" : ""}`);
|
|
5004
|
-
const line = (label, v) => {
|
|
5005
|
-
const t = localTime(v);
|
|
5006
|
-
if (t)
|
|
5007
|
-
say(ctx, `${label}: ${t}`);
|
|
5008
|
-
};
|
|
5009
|
-
line("Session changes", s.sessionEndsAt);
|
|
5010
|
-
line("Next open", s.nextOpenAt);
|
|
5011
|
-
line("Next close", s.nextCloseAt);
|
|
5012
|
-
const a = s.agentStockOrders ?? {};
|
|
5013
|
-
if (a.accepted === true)
|
|
5014
|
-
say(ctx, `Darwin agent stock orders: accepted${localTime(a.closesAt) ? ` until ${localTime(a.closesAt)}` : ""}`);
|
|
5015
|
-
else if (a.accepted === false)
|
|
5016
|
-
say(ctx, `Darwin agent stock orders: not accepted now${localTime(a.opensAt) ? `; next accepted ${localTime(a.opensAt)}` : ""}`);
|
|
5017
|
-
say(ctx, "Tokenized stocks trade 24/7, but liquidity is much lower when the US market is closed.");
|
|
5018
|
-
return EXIT.ok;
|
|
5019
|
-
}
|
|
5020
|
-
|
|
5021
|
-
// src/help.ts
|
|
5022
|
-
init_redact();
|
|
5023
|
-
init_copy();
|
|
5024
|
-
var LOCAL_DESCRIPTION = { [MARKET_STATUS_TOOL]: MARKET_STATUS_HELP };
|
|
5025
|
-
var describe = (t) => LOCAL_DESCRIPTION[t.name] ?? clean(t.description);
|
|
5026
|
-
var STATIC_HELP = {
|
|
5027
|
-
login: {
|
|
5028
|
-
usage: "darwin login [--beta] [--client-name <name>] [--reconnect] [--start | --wait | --with-key | --key-file <file> | --from-skill] [--profile <name>] [--store keychain|file]",
|
|
5029
|
-
summary: "Connect this terminal to a Darwin agent and keep its API key in your system's secret store.",
|
|
5030
|
-
flags: {
|
|
5031
|
-
beta: "Use beta.darwin.finance instead of darwin.finance.",
|
|
5032
|
-
"client-name": `What Darwin shows your owner as the app asking (never "Darwin"). Default: the coding agent you're in, else "Command line".`,
|
|
5033
|
-
reconnect: "Get a new API key for an agent you already have, instead of creating a new agent.",
|
|
5034
|
-
start: "Start pairing and print the link and code, then exit (for tools whose calls time out).",
|
|
5035
|
-
wait: "Wait for a pairing started with --start, and save the key.",
|
|
5036
|
-
"with-key": "Import an API key from stdin, or paste it at a hidden prompt.",
|
|
5037
|
-
"key-file": "Import the key file downloaded from the agent's Manage tab (add --delete-file to delete it after).",
|
|
5038
|
-
"from-skill": "Import the key the official Darwin skill (darwin.py) saved (add --delete-skill-copy to remove its copy).",
|
|
5039
|
-
"agent-id": "With --from-skill: which saved key, when there are several.",
|
|
5040
|
-
profile: "Profile name for this key (default: from the agent's name).",
|
|
5041
|
-
store: "keychain (default) or file — a plain file only your user can read.",
|
|
5042
|
-
"no-browser": "Don't open the link in a browser."
|
|
5043
|
-
}
|
|
5044
|
-
},
|
|
5045
|
-
logout: {
|
|
5046
|
-
usage: "darwin logout [--profile <name> | --all] [--revoke [--force-local]]",
|
|
5047
|
-
summary: "Remove the API key from this computer. With --revoke, Darwin revokes the key first.",
|
|
5048
|
-
flags: { revoke: "Ask Darwin to revoke this key everywhere, then remove it here.", all: "Every profile.", "force-local": "With --revoke: remove the local copy even if Darwin couldn't be reached." }
|
|
5049
|
-
},
|
|
5050
|
-
whoami: { usage: "darwin whoami [--profile <name>]", summary: "Which agent, site and API key this terminal uses (never any key characters).", flags: {} },
|
|
5051
|
-
profile: {
|
|
5052
|
-
usage: "darwin profile list | use <name> | rename <old> <new> | remove <name> | set-agent <name or id>",
|
|
5053
|
-
summary: "Manage profiles (one per API key). set-agent: the default agent for a key that trades for all your agents.",
|
|
5054
|
-
flags: {}
|
|
5055
|
-
},
|
|
5056
|
-
doctor: { usage: "darwin doctor", summary: "Check the install, the secret store, your profile and both Darwin sites.", flags: {} },
|
|
5057
|
-
api: { usage: "darwin api GET /api/agent/<path>", summary: "Read any agent API endpoint directly (GET only).", flags: {} },
|
|
5058
|
-
mcp: {
|
|
5059
|
-
usage: "darwin mcp [--profile <name>] | darwin mcp --print-config claude|cursor|gemini",
|
|
5060
|
-
summary: "Run Darwin as a local MCP server (stdio) for a desktop AI client, with the API key kept in your secret store.",
|
|
5061
|
-
flags: { "print-config": "Print the registration for a client, pointing at this installed copy." }
|
|
5062
|
-
},
|
|
5063
|
-
help: { usage: "darwin help [command] [--json]", summary: "This help. --json lists every command with its flags.", flags: {} },
|
|
5064
|
-
version: { usage: "darwin version", summary: "Print the Darwin CLI version.", flags: {} }
|
|
5065
|
-
};
|
|
5066
|
-
var GLOBAL_HELP = {
|
|
5067
|
-
json: "Print JSON (the default when output isn't a terminal).",
|
|
5068
|
-
profile: "Use this profile.",
|
|
5069
|
-
agent: "For an API key that trades for all your agents: which agent (name or id).",
|
|
5070
|
-
"dry-run": "Show what a command would send, and send nothing.",
|
|
5071
|
-
field: "For API fields without their own flag: --field name=value (repeatable).",
|
|
5072
|
-
quiet: "Less output."
|
|
6087
|
+
var PARAMS = {
|
|
6088
|
+
get_indicators: [
|
|
6089
|
+
{ prop: "timeframe", flag: "timeframe", required: true, description: "Candle size: 1m, 5m, 15m, 30m, 1h, 4h, 6h, 12h or 1d." },
|
|
6090
|
+
{ prop: "indicators", flag: "indicators", required: true, description: "Comma-separated, e.g. rsi:14,macd:12:26:9." },
|
|
6091
|
+
{ prop: "instrumentId", flag: "instrument-id", description: "The market, as `darwin markets` lists it (spot:solana:<mint>). Give this or --asset." },
|
|
6092
|
+
{ prop: "asset", flag: "asset", description: "A Darwin asset id instead of --instrument-id." },
|
|
6093
|
+
{ prop: "bars", flag: "bars", type: "number", description: "How many bars (default 50)." },
|
|
6094
|
+
{ prop: "includeFormingBar", flag: "include-forming-bar", type: "boolean", description: "Also show the bar still forming." }
|
|
6095
|
+
],
|
|
6096
|
+
list_spot_orders: [
|
|
6097
|
+
{ prop: "limit", flag: "limit", type: "number", description: "How many (1–200, default 50)." },
|
|
6098
|
+
{ prop: "cursor", flag: "cursor", description: "The next page, from the line a previous page printed." }
|
|
6099
|
+
],
|
|
6100
|
+
list_trades: [
|
|
6101
|
+
{ prop: "kind", flag: "kind", description: "spot, perp or all (default all)." },
|
|
6102
|
+
{ prop: "since", flag: "since", type: "number", description: "Only trades settled after this time (epoch milliseconds)." },
|
|
6103
|
+
{ prop: "limit", flag: "limit", type: "number", description: "How many (at most 200)." },
|
|
6104
|
+
{ prop: "cursor", flag: "cursor", description: "The next page, from the line a previous page printed." }
|
|
6105
|
+
],
|
|
6106
|
+
get_pnl: [{ prop: "period", flag: "period", description: "ytd (default), 30d, 90d, all, or a tax year such as 2025." }],
|
|
6107
|
+
list_lots: [
|
|
6108
|
+
{ prop: "sort", flag: "sort", description: "unrealized_asc, acquired_asc or basis_desc." },
|
|
6109
|
+
{ prop: "mint", flag: "mint", description: "Only this token's lots." },
|
|
6110
|
+
{ prop: "instrumentId", flag: "instrument-id", description: "Only this market's lots." }
|
|
6111
|
+
],
|
|
6112
|
+
list_spot_markets: [{ prop: "type", flag: "type", description: "spot or perp (default both)." }]
|
|
5073
6113
|
};
|
|
5074
|
-
function
|
|
5075
|
-
const
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
version: VERSION,
|
|
5091
|
-
realm: c.realm,
|
|
5092
|
-
credential: c.credential,
|
|
5093
|
-
catalogVersion: c.catalogVersion,
|
|
5094
|
-
static: Object.entries(STATIC_HELP).map(([command, h]) => ({ command, usage: h.usage, summary: h.summary, flags: h.flags })),
|
|
5095
|
-
globalFlags: GLOBAL_HELP,
|
|
5096
|
-
commands: c.tools.map((t) => ({
|
|
5097
|
-
command: t.cli.path.join(" "),
|
|
5098
|
-
aliases: t.cli.aliases.map((a) => a.join(" ")),
|
|
5099
|
-
tool: t.name,
|
|
5100
|
-
title: clean(t.title),
|
|
5101
|
-
write: t.write,
|
|
5102
|
-
costsTx: t.costsTx,
|
|
5103
|
-
usage: usageOf(t),
|
|
5104
|
-
positional: t.cli.positional,
|
|
5105
|
-
flags: toolFlags(t),
|
|
5106
|
-
description: describe(t),
|
|
5107
|
-
...t.deprecated ? { deprecated: t.deprecated } : {}
|
|
5108
|
-
}))
|
|
5109
|
-
};
|
|
5110
|
-
}
|
|
5111
|
-
function printOverview(ctx, c, json) {
|
|
5112
|
-
if (json) {
|
|
5113
|
-
printJson(ctx, helpJson(c));
|
|
5114
|
-
return;
|
|
5115
|
-
}
|
|
5116
|
-
say(ctx, `Darwin CLI ${VERSION} — trade and check your Darwin agent from a terminal.`);
|
|
5117
|
-
say(ctx, "");
|
|
5118
|
-
say(ctx, "Darwin commands (from Darwin's catalogue; W = changes something):");
|
|
5119
|
-
const width = Math.max(...c.tools.map((t) => t.cli.path.join(" ").length)) + 2;
|
|
5120
|
-
for (const t of c.tools)
|
|
5121
|
-
say(ctx, ` ${t.cli.path.join(" ").padEnd(width)}${t.write ? "W " : " "}${clean(t.title)}`);
|
|
5122
|
-
say(ctx, "");
|
|
5123
|
-
say(ctx, "This CLI's own commands:");
|
|
5124
|
-
for (const [k, h] of Object.entries(STATIC_HELP))
|
|
5125
|
-
say(ctx, ` ${k.padEnd(width)} ${h.summary}`);
|
|
5126
|
-
say(ctx, "");
|
|
5127
|
-
say(ctx, "Global flags: --json --profile <name> --agent <name or id> --dry-run --field name=value");
|
|
5128
|
-
say(ctx, "`darwin <command> --help` for a command's flags; `darwin help --json` for all of it.");
|
|
5129
|
-
say(ctx, "");
|
|
5130
|
-
for (const l of clean(c.instructions).split(`
|
|
5131
|
-
`))
|
|
5132
|
-
say(ctx, l);
|
|
5133
|
-
}
|
|
5134
|
-
function printCommandHelp(ctx, t, json) {
|
|
5135
|
-
if (json) {
|
|
5136
|
-
printJson(ctx, helpJson({ tools: [t] }).commands[0]);
|
|
5137
|
-
return;
|
|
6114
|
+
function withLocalFlags(tool) {
|
|
6115
|
+
const extra = PARAMS[tool.name];
|
|
6116
|
+
if (!extra || tool.write)
|
|
6117
|
+
return tool;
|
|
6118
|
+
const props = { ...tool.inputSchema.properties ?? {} };
|
|
6119
|
+
const args = { ...tool.cli.args };
|
|
6120
|
+
const required = [...tool.inputSchema.required ?? []];
|
|
6121
|
+
const taken = new Set(Object.values(args).map((a) => a.flag));
|
|
6122
|
+
for (const p of extra) {
|
|
6123
|
+
if (props[p.prop] || taken.has(p.flag))
|
|
6124
|
+
continue;
|
|
6125
|
+
props[p.prop] = { type: p.type ?? "string", description: p.description };
|
|
6126
|
+
args[p.prop] = { flag: p.flag };
|
|
6127
|
+
taken.add(p.flag);
|
|
6128
|
+
if (p.required && !required.includes(p.prop))
|
|
6129
|
+
required.push(p.prop);
|
|
5138
6130
|
}
|
|
5139
|
-
|
|
5140
|
-
say(ctx, "");
|
|
5141
|
-
say(ctx, `${clean(t.title)}${t.write ? " — changes something" : ""}${t.costsTx ? "; counts against today's transaction budget" : ""}.`);
|
|
5142
|
-
say(ctx, describe(t));
|
|
5143
|
-
const flags = toolFlags(t);
|
|
5144
|
-
if (flags.length || t.cli.positional.length)
|
|
5145
|
-
say(ctx, "");
|
|
5146
|
-
for (const p of t.cli.positional)
|
|
5147
|
-
say(ctx, ` <${p}> ${clean(t.inputSchema.properties?.[p]?.description ?? "")}`);
|
|
5148
|
-
for (const f of flags)
|
|
5149
|
-
say(ctx, ` ${f.flag} <${f.type}>${f.required ? " (required)" : ""} ${f.description}`);
|
|
5150
|
-
if (t.write)
|
|
5151
|
-
say(ctx, `
|
|
5152
|
-
There is no confirmation prompt: the command runs when you press enter. Use --dry-run to see what it would send.`);
|
|
5153
|
-
if (t.deprecated)
|
|
5154
|
-
say(ctx, `
|
|
5155
|
-
Deprecated since ${t.deprecated.since}; removed after ${t.deprecated.removeAfter}.`);
|
|
6131
|
+
return { ...tool, cli: { ...tool.cli, args }, inputSchema: { ...tool.inputSchema, properties: props, required } };
|
|
5156
6132
|
}
|
|
5157
|
-
function
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
printJson(ctx, { command: name, ...h });
|
|
5161
|
-
return;
|
|
6133
|
+
function checkLocalArguments(tool, args) {
|
|
6134
|
+
if (tool.name === "get_indicators" && PARAMS.get_indicators && args.instrumentId === undefined === (args.asset === undefined)) {
|
|
6135
|
+
throw new CliError(EXIT.usage, "`darwin indicators` needs exactly one of --instrument-id (from `darwin markets`) or --asset. See `darwin indicators --help`.", "usage");
|
|
5162
6136
|
}
|
|
5163
|
-
say(ctx, h.usage);
|
|
5164
|
-
say(ctx, "");
|
|
5165
|
-
say(ctx, h.summary);
|
|
5166
|
-
for (const [f, d] of Object.entries(h.flags))
|
|
5167
|
-
say(ctx, ` --${f} ${d}`);
|
|
5168
6137
|
}
|
|
5169
6138
|
|
|
5170
6139
|
// src/main.ts
|
|
@@ -5222,10 +6191,10 @@ async function dispatch(p, ctx) {
|
|
|
5222
6191
|
printStaticHelp(ctx, rest[0], json);
|
|
5223
6192
|
return EXIT.ok;
|
|
5224
6193
|
}
|
|
5225
|
-
const t = resolve(c, rest)?.tool;
|
|
6194
|
+
const t = resolve(c, rest, !json)?.tool;
|
|
5226
6195
|
if (!t)
|
|
5227
6196
|
throw new CliError(EXIT.usage, `No command "${rest.join(" ").slice(0, 60)}". \`darwin help\` lists them.`, "unknown_command");
|
|
5228
|
-
printCommandHelp(ctx, t, json);
|
|
6197
|
+
printCommandHelp(ctx, t, json, c);
|
|
5229
6198
|
return EXIT.ok;
|
|
5230
6199
|
}
|
|
5231
6200
|
if (STATIC_HELP[first] && has(p, "help")) {
|
|
@@ -5254,20 +6223,20 @@ async function dispatch(p, ctx) {
|
|
|
5254
6223
|
throw new CliError(EXIT.usage, `\`darwin ${first}\` isn't in this version of the Darwin CLI.`, "unknown_command");
|
|
5255
6224
|
if (has(p, "help")) {
|
|
5256
6225
|
const c = await catalogueForHelp(ctx, p);
|
|
5257
|
-
const t = resolve(c, p.positionals)?.tool;
|
|
6226
|
+
const t = resolve(c, p.positionals, !json)?.tool;
|
|
5258
6227
|
if (!t)
|
|
5259
6228
|
throw new CliError(EXIT.usage, `No command "${p.positionals.join(" ").slice(0, 60)}". \`darwin help\` lists them.`, "unknown_command");
|
|
5260
|
-
printCommandHelp(ctx, t, json);
|
|
6229
|
+
printCommandHelp(ctx, t, json, c);
|
|
5261
6230
|
return EXIT.ok;
|
|
5262
6231
|
}
|
|
5263
6232
|
return runCatalogueCommand(ctx, p, json);
|
|
5264
6233
|
}
|
|
5265
|
-
function resolve(c,
|
|
6234
|
+
function resolve(c, words2, localFlags = true) {
|
|
5266
6235
|
const idx = commandIndex(c);
|
|
5267
|
-
for (let n = Math.min(3,
|
|
5268
|
-
const t = idx.get(
|
|
6236
|
+
for (let n = Math.min(3, words2.length);n >= 1; n--) {
|
|
6237
|
+
const t = idx.get(words2.slice(0, n).join(" "));
|
|
5269
6238
|
if (t)
|
|
5270
|
-
return { tool: t, rest:
|
|
6239
|
+
return { tool: localFlags ? withLocalFlags(t) : t, rest: words2.slice(n) };
|
|
5271
6240
|
}
|
|
5272
6241
|
return null;
|
|
5273
6242
|
}
|
|
@@ -5286,10 +6255,10 @@ async function catalogueForHelp(ctx, p) {
|
|
|
5286
6255
|
}
|
|
5287
6256
|
async function runCatalogueCommand(ctx, p, json) {
|
|
5288
6257
|
const session = openSession(ctx, { profile: one(p, "profile") });
|
|
5289
|
-
const
|
|
5290
|
-
let { catalogue } = await loadCatalogue(ctx, session.realm, session.kind, { key: session.key, refresh:
|
|
6258
|
+
const dryRun2 = has(p, "dry-run");
|
|
6259
|
+
let { catalogue } = await loadCatalogue(ctx, session.realm, session.kind, { key: session.key, refresh: dryRun2 ? "offline" : "never" });
|
|
5291
6260
|
let hit = resolve(catalogue, p.positionals);
|
|
5292
|
-
if (!hit && !
|
|
6261
|
+
if (!hit && !dryRun2) {
|
|
5293
6262
|
catalogue = (await loadCatalogue(ctx, session.realm, session.kind, { key: session.key, refresh: "force" })).catalogue;
|
|
5294
6263
|
hit = resolve(catalogue, p.positionals);
|
|
5295
6264
|
}
|
|
@@ -5298,6 +6267,7 @@ async function runCatalogueCommand(ctx, p, json) {
|
|
|
5298
6267
|
if (compareVersions(VERSION, catalogue.minCli) < 0)
|
|
5299
6268
|
throw new CliError(EXIT.upgrade, copy.updateRequired(catalogue.minCli), "cli_upgrade_required");
|
|
5300
6269
|
const args = buildArguments(hit.tool, hit.rest, p, GLOBALS);
|
|
6270
|
+
checkLocalArguments(hit.tool, args);
|
|
5301
6271
|
if (hit.tool.deprecated)
|
|
5302
6272
|
warn(ctx, `\`darwin ${hit.tool.cli.path.join(" ")}\` is deprecated and goes away after ${hit.tool.deprecated.removeAfter}.`);
|
|
5303
6273
|
const code = await runTool({
|
|
@@ -5307,7 +6277,7 @@ async function runCatalogueCommand(ctx, p, json) {
|
|
|
5307
6277
|
tool: hit.tool,
|
|
5308
6278
|
args,
|
|
5309
6279
|
json,
|
|
5310
|
-
flags: { json, dryRun, agent: one(p, "agent"), quiet: has(p, "quiet") },
|
|
6280
|
+
flags: { json, dryRun: dryRun2, agent: one(p, "agent"), quiet: has(p, "quiet") },
|
|
5311
6281
|
onStaleCatalogue: async () => {
|
|
5312
6282
|
await loadCatalogue(ctx, session.realm, session.kind, { key: session.key, refresh: "force" });
|
|
5313
6283
|
},
|
|
@@ -5323,7 +6293,7 @@ function updateNotice(ctx, c) {
|
|
|
5323
6293
|
return;
|
|
5324
6294
|
if (compareVersions(VERSION, c.latestCli) >= 0)
|
|
5325
6295
|
return;
|
|
5326
|
-
const stamp =
|
|
6296
|
+
const stamp = join9(ctx.configDir, "cache", "update-notice");
|
|
5327
6297
|
try {
|
|
5328
6298
|
const last = Number(readPrivate(stamp, 64, { requirePrivateMode: false }) ?? "0");
|
|
5329
6299
|
if (ctx.now() - last < 24 * 3600000)
|
|
@@ -5340,8 +6310,8 @@ function configDir() {
|
|
|
5340
6310
|
if (process.env.DARWIN_CONFIG_DIR)
|
|
5341
6311
|
return process.env.DARWIN_CONFIG_DIR;
|
|
5342
6312
|
if (process.platform === "win32")
|
|
5343
|
-
return
|
|
5344
|
-
return
|
|
6313
|
+
return join10(process.env.APPDATA || join10(homedir2(), "AppData", "Roaming"), "darwin");
|
|
6314
|
+
return join10(process.env.XDG_CONFIG_HOME || join10(homedir2(), ".config"), "darwin");
|
|
5345
6315
|
}
|
|
5346
6316
|
async function readAll() {
|
|
5347
6317
|
const chunks = [];
|
|
@@ -5396,7 +6366,7 @@ var ctx = {
|
|
|
5396
6366
|
cwd: process.cwd(),
|
|
5397
6367
|
platform: process.platform,
|
|
5398
6368
|
openUrl: (url) => {
|
|
5399
|
-
const [cmd, args] = process.platform === "darwin" ? ["/usr/bin/open", [url]] : process.platform === "win32" ? [
|
|
6369
|
+
const [cmd, args] = process.platform === "darwin" ? ["/usr/bin/open", [url]] : process.platform === "win32" ? [join10(process.env.SystemRoot || "C:\\Windows", "System32", "rundll32.exe"), ["url.dll,FileProtocolHandler", url]] : ["/usr/bin/xdg-open", [url]];
|
|
5400
6370
|
if (!existsSync(cmd))
|
|
5401
6371
|
return;
|
|
5402
6372
|
spawn2(cmd, args, { stdio: "ignore", detached: true }).unref();
|