@darwin.finance/cli 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -12
- package/dist/darwin.js +1843 -484
- 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);
|
|
@@ -62,13 +58,14 @@ Waiting for you to approve… (the code expires in ${minutes} minutes)`,
|
|
|
62
58
|
needAgent: "This API key trades for all your active agents. Name one with --agent <name or id>, or set a default: `darwin profile set-agent <name>`. Your agents: `darwin agents`.",
|
|
63
59
|
mcpPageKey: "This API key is from the MCP page: it reads your whole account and can't run agent commands. Log in with an agent's API key.",
|
|
64
60
|
uncertainWrite: "Darwin may have received this order, but the answer didn't arrive. Don't run the command again — check `darwin orders` to see whether it went through.",
|
|
61
|
+
uncertainWritePrepared: "Darwin may have received this order, but its result didn't come back.",
|
|
65
62
|
paused: "This agent is paused, so it can't trade. Reads still work. Only you can resume it, on the agent's Darwin page.",
|
|
66
63
|
quoteFooter: (quoteId, seconds, sell, amount, forTok) => `Quote ${quoteId} expires in ${seconds}s. To place it: darwin order --quote ${quoteId} --sell ${sell} --amount ${amount} --for ${forTok}`,
|
|
67
64
|
updateAvailable: (latest, current) => `Darwin CLI ${latest} is available (you have ${current}). Update: ${INSTALL_LINE}`,
|
|
68
65
|
updateRequired: (minCli) => `This version of the Darwin CLI is no longer supported. Update to ${minCli} or later: ${INSTALL_LINE}`,
|
|
69
66
|
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
67
|
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} ·
|
|
68
|
+
spotSent: (amount, sell, forTok, agent, orderId, nonce) => `Sent: sell ${amount} ${sell} for ${forTok} on ${agent} · ${orderId ? `order ${orderId} · ` : ""}nonce ${nonce}`,
|
|
72
69
|
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
70
|
allAgentsOff: "API keys for all your agents aren't available on this site yet. Use an API key for one agent.",
|
|
74
71
|
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 +250,7 @@ function compareVersions(a, b) {
|
|
|
253
250
|
return x[i] < y[i] ? -1 : 1;
|
|
254
251
|
return 0;
|
|
255
252
|
}
|
|
256
|
-
var VERSION = "1.
|
|
253
|
+
var VERSION = "1.1.0";
|
|
257
254
|
|
|
258
255
|
// src/http.ts
|
|
259
256
|
function vendor(env) {
|
|
@@ -473,6 +470,7 @@ var init_config = __esm(() => {
|
|
|
473
470
|
var exports_agents = {};
|
|
474
471
|
__export(exports_agents, {
|
|
475
472
|
resolveAgent: () => resolveAgent,
|
|
473
|
+
recoveryAgent: () => recoveryAgent,
|
|
476
474
|
listAgents: () => listAgents
|
|
477
475
|
});
|
|
478
476
|
import { createHash } from "node:crypto";
|
|
@@ -504,16 +502,16 @@ async function listAgents(ctx, s, opts = {}) {
|
|
|
504
502
|
const list = res.json?.agents;
|
|
505
503
|
if (res.status !== 200 || !Array.isArray(list))
|
|
506
504
|
throw new CliError(EXIT.unexpected, `Couldn't list your agents (HTTP ${res.status}).`, "bad_response");
|
|
507
|
-
const
|
|
505
|
+
const agents2 = list.filter((a) => !!a && typeof a === "object").map((a) => ({
|
|
508
506
|
id: String(a.id ?? ""),
|
|
509
507
|
name: clean(unwrap(a.name)).slice(0, 120),
|
|
510
|
-
solanaAddress: typeof a.solanaAddress === "string" &&
|
|
508
|
+
solanaAddress: typeof a.solanaAddress === "string" && BASE582.test(a.solanaAddress) ? a.solanaAddress : undefined,
|
|
511
509
|
status: a.status === "active" || a.status === "paused" ? a.status : undefined
|
|
512
510
|
})).filter((a) => isAgentId(a.id));
|
|
513
511
|
try {
|
|
514
|
-
writePrivate(file, JSON.stringify({ at: ctx.now(), agents }));
|
|
512
|
+
writePrivate(file, JSON.stringify({ at: ctx.now(), agents: agents2 }));
|
|
515
513
|
} catch {}
|
|
516
|
-
return
|
|
514
|
+
return agents2;
|
|
517
515
|
}
|
|
518
516
|
async function resolveAgent(ctx, s, flag) {
|
|
519
517
|
const raw = (flag ?? ctx.env.DARWIN_AGENT ?? "").trim();
|
|
@@ -530,19 +528,29 @@ async function resolveAgent(ctx, s, flag) {
|
|
|
530
528
|
const want = raw || s.profile?.default_agent || "";
|
|
531
529
|
if (!want)
|
|
532
530
|
throw new CliError(EXIT.usage, copy.needAgent, "agent_required");
|
|
533
|
-
const
|
|
534
|
-
const byId =
|
|
531
|
+
const agents2 = await listAgents(ctx, s);
|
|
532
|
+
const byId = agents2.find((a) => a.id === want);
|
|
535
533
|
if (byId)
|
|
536
534
|
return byId.id;
|
|
537
|
-
const byName =
|
|
535
|
+
const byName = agents2.filter((a) => a.name.trim().toLowerCase() === want.toLowerCase());
|
|
538
536
|
if (byName.length === 1)
|
|
539
537
|
return byName[0].id;
|
|
540
538
|
if (byName.length > 1) {
|
|
541
539
|
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
540
|
}
|
|
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:
|
|
541
|
+
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 })) });
|
|
542
|
+
}
|
|
543
|
+
async function recoveryAgent(ctx, s, flag) {
|
|
544
|
+
const raw = ((flag ?? ctx.env.DARWIN_AGENT ?? "").trim() || (s.kind === "agents" ? s.profile?.default_agent ?? "" : "")).trim();
|
|
545
|
+
try {
|
|
546
|
+
return await resolveAgent(ctx, s, flag);
|
|
547
|
+
} catch (e) {
|
|
548
|
+
if (s.kind === "agents" && e instanceof CliError && e.code === "agent_not_found" && /^agr_[A-Za-z0-9_-]{1,64}$/.test(raw) && isAgentId(raw))
|
|
549
|
+
return raw;
|
|
550
|
+
throw e;
|
|
551
|
+
}
|
|
544
552
|
}
|
|
545
|
-
var
|
|
553
|
+
var BASE582, unwrap = (v) => v && typeof v === "object" && ("untrusted" in v) ? String(v.untrusted) : typeof v === "string" ? v : "";
|
|
546
554
|
var init_agents = __esm(() => {
|
|
547
555
|
init_context();
|
|
548
556
|
init_copy();
|
|
@@ -550,7 +558,7 @@ var init_agents = __esm(() => {
|
|
|
550
558
|
init_http();
|
|
551
559
|
init_config();
|
|
552
560
|
init_redact();
|
|
553
|
-
|
|
561
|
+
BASE582 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
554
562
|
});
|
|
555
563
|
|
|
556
564
|
// src/bin.ts
|
|
@@ -3332,7 +3340,7 @@ function cell(v) {
|
|
|
3332
3340
|
if (v === null || v === undefined)
|
|
3333
3341
|
return "";
|
|
3334
3342
|
if (isUntrusted(v))
|
|
3335
|
-
return
|
|
3343
|
+
return cell(v.untrusted);
|
|
3336
3344
|
if (typeof v === "string")
|
|
3337
3345
|
return clean(v);
|
|
3338
3346
|
if (typeof v === "number" || typeof v === "boolean")
|
|
@@ -3375,189 +3383,1466 @@ function table(rows, indent) {
|
|
|
3375
3383
|
return [line(cols.map(clean)), line(widths.map((w) => "-".repeat(w))), ...cells.map(line)];
|
|
3376
3384
|
}
|
|
3377
3385
|
|
|
3378
|
-
// src/
|
|
3379
|
-
init_realms();
|
|
3386
|
+
// src/human.ts
|
|
3380
3387
|
init_redact();
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
if (
|
|
3389
|
-
return
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3388
|
+
function txt(v) {
|
|
3389
|
+
if (v === null || v === undefined)
|
|
3390
|
+
return "";
|
|
3391
|
+
if (isUntrusted(v))
|
|
3392
|
+
return txt(v.untrusted);
|
|
3393
|
+
if (typeof v === "string")
|
|
3394
|
+
return clean(v).replace(/\s+/g, " ").trim();
|
|
3395
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint")
|
|
3396
|
+
return String(v);
|
|
3397
|
+
return "";
|
|
3398
|
+
}
|
|
3399
|
+
function label(v, max = 60) {
|
|
3400
|
+
const t = txt(v);
|
|
3401
|
+
return t.length > max ? `${t.slice(0, max - 1)}…` : t;
|
|
3402
|
+
}
|
|
3403
|
+
var obj = (v) => v && typeof v === "object" && !Array.isArray(v) && !isUntrusted(v) ? v : {};
|
|
3404
|
+
var arr = (v) => Array.isArray(v) ? v : [];
|
|
3405
|
+
function toDate(v) {
|
|
3406
|
+
let d = null;
|
|
3407
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0)
|
|
3408
|
+
d = new Date(v < 100000000000 ? v * 1000 : v);
|
|
3409
|
+
else if (typeof v === "string" && /^[0-9]{9,13}$/.test(v))
|
|
3410
|
+
return toDate(Number(v));
|
|
3411
|
+
else if (typeof v === "string" && /^\d{4}-\d{2}-\d{2}T/.test(v))
|
|
3412
|
+
d = new Date(v);
|
|
3413
|
+
if (!d || Number.isNaN(d.getTime()))
|
|
3414
|
+
return null;
|
|
3415
|
+
const y = d.getUTCFullYear();
|
|
3416
|
+
return y >= 2000 && y < 2100 ? d : null;
|
|
3394
3417
|
}
|
|
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;
|
|
3418
|
+
function clock(d) {
|
|
3419
|
+
return d.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", second: "2-digit" });
|
|
3400
3420
|
}
|
|
3401
|
-
function
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3421
|
+
function when(v, opts = {}) {
|
|
3422
|
+
const d = toDate(v);
|
|
3423
|
+
if (!d)
|
|
3424
|
+
return "";
|
|
3425
|
+
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]));
|
|
3426
|
+
return `${opts.weekday ? `${parts.weekday}, ` : ""}${parts.month} ${parts.day}, ${parts.hour}:${parts.minute} ${parts.dayPeriod} ${parts.timeZoneName}`;
|
|
3427
|
+
}
|
|
3428
|
+
function num(v) {
|
|
3429
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
3430
|
+
return String(v);
|
|
3431
|
+
const t = txt(v);
|
|
3432
|
+
if (!/^-?\d+(\.\d+)?$/.test(t))
|
|
3433
|
+
return t;
|
|
3434
|
+
return t.includes(".") ? t.replace(/0+$/, "").replace(/\.$/, "") : t;
|
|
3435
|
+
}
|
|
3436
|
+
function usd(v) {
|
|
3437
|
+
const n = typeof v === "number" ? v : typeof v === "string" && /^-?\d+(\.\d+)?$/.test(v.trim()) ? Number(v) : NaN;
|
|
3438
|
+
if (!Number.isFinite(n))
|
|
3439
|
+
return "";
|
|
3440
|
+
const abs = Math.abs(n);
|
|
3441
|
+
const dollars = (x) => x.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
3442
|
+
let s;
|
|
3443
|
+
if (abs === 0 || abs >= 0.995)
|
|
3444
|
+
s = dollars(abs);
|
|
3445
|
+
else if (abs < 0.000000000001)
|
|
3446
|
+
s = abs.toExponential(3);
|
|
3447
|
+
else {
|
|
3448
|
+
const places = Math.max(2, 3 - Math.floor(Math.log10(abs)));
|
|
3449
|
+
s = abs.toFixed(places).replace(/0+$/, "");
|
|
3450
|
+
const [, frac = ""] = s.split(".");
|
|
3451
|
+
if (frac.length < 2)
|
|
3452
|
+
s = `${s.split(".")[0]}.${frac.padEnd(2, "0")}`;
|
|
3410
3453
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3454
|
+
return `${n < 0 ? "−" : ""}$${s}`;
|
|
3455
|
+
}
|
|
3456
|
+
var HIDDEN_KEYS = new Set(["welcome", "next", "instructions", "instruction", "tellYourUser", "retrySafe", "retryable", "refusal", "ok", "untrustedNote"]);
|
|
3457
|
+
var TIME_KEY = /^(?:asOf(?:Ms|Secs?)?|[a-z][A-Za-z0-9]*(?:At|AtMs|AtSecs|AtSec|AsOf|AsOfMs|AsOfSecs|Ts|Time|Timestamp))$/;
|
|
3458
|
+
function words(key) {
|
|
3459
|
+
return clean(key).replace(/Iso$/, "").replace(/(Ms|Secs?)$/, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/_/g, " ").toLowerCase();
|
|
3460
|
+
}
|
|
3461
|
+
function tidy(v, depth = 0) {
|
|
3462
|
+
if (depth > 12)
|
|
3463
|
+
return null;
|
|
3464
|
+
if (isUntrusted(v))
|
|
3465
|
+
return tidy(v.untrusted, depth + 1);
|
|
3466
|
+
if (Array.isArray(v))
|
|
3467
|
+
return v.map((x) => tidy(x, depth + 1));
|
|
3468
|
+
if (!v || typeof v !== "object")
|
|
3469
|
+
return v;
|
|
3470
|
+
const src = v;
|
|
3471
|
+
const out = {};
|
|
3472
|
+
for (const [k, x] of Object.entries(src)) {
|
|
3473
|
+
if (HIDDEN_KEYS.has(k))
|
|
3474
|
+
continue;
|
|
3475
|
+
const has2 = (twin) => src[twin] !== null && src[twin] !== undefined && src[twin] !== "";
|
|
3476
|
+
if (/Atoms$/.test(k) && k !== "atoms" && (has2(k.slice(0, -5)) || has2(`${k.slice(0, -5)}Amount`)))
|
|
3477
|
+
continue;
|
|
3478
|
+
if (k === "atoms" && (has2("amount") || has2("uiAmount")))
|
|
3479
|
+
continue;
|
|
3480
|
+
if (/Iso$/.test(k) && typeof x === "string") {
|
|
3481
|
+
const base = k.slice(0, -3);
|
|
3482
|
+
const label2 = [base, `${base}Ms`, `${base}Sec`, `${base}Secs`].find((b) => (b in src)) ?? base;
|
|
3483
|
+
if (!(label2 in out))
|
|
3484
|
+
out[label2] = when(x) || x;
|
|
3485
|
+
continue;
|
|
3486
|
+
}
|
|
3487
|
+
if (TIME_KEY.test(k) && toDate(x)) {
|
|
3488
|
+
out[k] = when(x);
|
|
3489
|
+
continue;
|
|
3490
|
+
}
|
|
3491
|
+
out[k] = tidy(x, depth + 1);
|
|
3416
3492
|
}
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3493
|
+
return out;
|
|
3494
|
+
}
|
|
3495
|
+
function genericLines(v) {
|
|
3496
|
+
return renderText(humanKeys(tidy(v)));
|
|
3497
|
+
}
|
|
3498
|
+
function humanKeys(v) {
|
|
3499
|
+
if (Array.isArray(v))
|
|
3500
|
+
return v.map(humanKeys);
|
|
3501
|
+
if (!v || typeof v !== "object")
|
|
3502
|
+
return v;
|
|
3503
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [words(k), humanKeys(x)]));
|
|
3424
3504
|
}
|
|
3425
3505
|
|
|
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
|
-
|
|
3506
|
+
// src/help.ts
|
|
3507
|
+
init_redact();
|
|
3508
|
+
init_copy();
|
|
3509
|
+
|
|
3510
|
+
// src/market.ts
|
|
3511
|
+
init_context();
|
|
3512
|
+
init_http();
|
|
3513
|
+
init_realms();
|
|
3514
|
+
var MARKET_STATUS_PATH = "/api/agent/v1/market-status/us-equities";
|
|
3515
|
+
var MARKET_STATUS_TOOL = "get_us_market_status";
|
|
3516
|
+
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.";
|
|
3517
|
+
function localTime(iso) {
|
|
3518
|
+
if (typeof iso !== "string")
|
|
3519
|
+
return null;
|
|
3520
|
+
return when(iso, { weekday: true }) || null;
|
|
3521
|
+
}
|
|
3522
|
+
var SESSION = { pre_market: "pre-market", regular: "open (regular session)", after_hours: "after hours", closed: "closed" };
|
|
3523
|
+
var SESSION_ENDS = { pre_market: "Regular session opens", regular: "Regular session closes", after_hours: "After hours ends", closed: "Pre-market opens" };
|
|
3524
|
+
var REASON_REPEATS = { pre_market: "pre_market", after_hours: "after_hours" };
|
|
3525
|
+
function marketHeadline(s) {
|
|
3526
|
+
const session = SESSION[String(s.session)] ?? cell(s.session).replace(/_/g, " ");
|
|
3527
|
+
const reason = typeof s.reason === "string" ? s.reason : "";
|
|
3528
|
+
let why = "";
|
|
3529
|
+
if (reason && s.session !== "regular" && REASON_REPEATS[reason] !== s.session) {
|
|
3530
|
+
const holiday = reason === "holiday" && typeof s.holidayName === "string" && s.holidayName ? `: ${cell(s.holidayName)}` : "";
|
|
3531
|
+
why = ` (${cell(reason).replace(/_/g, " ")}${holiday})`;
|
|
3532
|
+
}
|
|
3533
|
+
const early = s.earlyClose === true && reason !== "early_close" ? " — early close today" : "";
|
|
3534
|
+
return `US stock market: ${session}${why}${early}`;
|
|
3535
|
+
}
|
|
3536
|
+
async function cmdMarketStatus(ctx, p) {
|
|
3537
|
+
onlyFlags(p, ["beta", "prod", "json", "format", "quiet", "no-color", "help"], "market-status");
|
|
3538
|
+
if (p.positionals.length > 1)
|
|
3539
|
+
throw new CliError(EXIT.usage, "`darwin market-status` takes no arguments.", "usage");
|
|
3540
|
+
let realm = "darwin.finance";
|
|
3541
|
+
if (has(p, "beta"))
|
|
3542
|
+
realm = "beta.darwin.finance";
|
|
3543
|
+
else if (!has(p, "prod") && ctx.env.DARWIN_REALM) {
|
|
3544
|
+
const r = parseRealm(ctx.env.DARWIN_REALM);
|
|
3545
|
+
if (!r)
|
|
3546
|
+
throw new CliError(EXIT.usage, "DARWIN_REALM must be prod or beta.", "usage");
|
|
3547
|
+
realm = r;
|
|
3456
3548
|
}
|
|
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;
|
|
3549
|
+
let res;
|
|
3469
3550
|
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;
|
|
3551
|
+
res = await request(ctx, realm, "GET", MARKET_STATUS_PATH, { timeoutMs: 15000 });
|
|
3473
3552
|
} catch (e) {
|
|
3474
|
-
if (
|
|
3475
|
-
throw e;
|
|
3476
|
-
|
|
3553
|
+
if (e instanceof NetworkError)
|
|
3554
|
+
throw new CliError(EXIT.network, e.message, "network_error");
|
|
3555
|
+
throw e;
|
|
3477
3556
|
}
|
|
3478
|
-
const
|
|
3479
|
-
if (
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3557
|
+
const s = res.json;
|
|
3558
|
+
if (res.status === 429)
|
|
3559
|
+
throw new CliError(EXIT.rateLimited, "Too many requests right now; try again in a minute.", "rate_limited");
|
|
3560
|
+
if (res.status !== 200 || !s || typeof s.open !== "boolean")
|
|
3561
|
+
throw new CliError(EXIT.unexpected, `${realm} didn't answer the market status (HTTP ${res.status}).`, "bad_response");
|
|
3562
|
+
if (wantsJson(ctx, { json: has(p, "json"), format: one(p, "format") })) {
|
|
3563
|
+
printJson(ctx, s);
|
|
3483
3564
|
return EXIT.ok;
|
|
3484
3565
|
}
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3566
|
+
say(ctx, marketHeadline(s));
|
|
3567
|
+
const line = (label2, v) => {
|
|
3568
|
+
const t = localTime(v);
|
|
3569
|
+
if (t)
|
|
3570
|
+
say(ctx, `${label2}: ${t}`);
|
|
3571
|
+
};
|
|
3572
|
+
line(SESSION_ENDS[String(s.session)] ?? "Session changes", s.sessionEndsAt);
|
|
3573
|
+
line("Next open", s.nextOpenAt);
|
|
3574
|
+
line("Next close", s.nextCloseAt);
|
|
3575
|
+
const a = s.agentStockOrders ?? {};
|
|
3576
|
+
if (a.accepted === true)
|
|
3577
|
+
say(ctx, `Darwin agent stock orders: accepted${localTime(a.closesAt) ? ` until ${localTime(a.closesAt)}` : ""}`);
|
|
3578
|
+
else if (a.accepted === false)
|
|
3579
|
+
say(ctx, `Darwin agent stock orders: not accepted now${localTime(a.opensAt) ? `; next accepted ${localTime(a.opensAt)}` : ""}`);
|
|
3580
|
+
say(ctx, "Tokenized stocks trade 24/7, but liquidity is much lower when the US market is closed.");
|
|
3581
|
+
return EXIT.ok;
|
|
3492
3582
|
}
|
|
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";
|
|
3583
|
+
|
|
3584
|
+
// src/help.ts
|
|
3585
|
+
var LOCAL_DESCRIPTION = { [MARKET_STATUS_TOOL]: MARKET_STATUS_HELP };
|
|
3586
|
+
var TERMINAL_DESCRIPTION = {
|
|
3587
|
+
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.",
|
|
3588
|
+
get_balances: "This agent's wallet balances, in whole tokens. Reporting: reads ONLY this agent's own account. Never counts against the transaction budget."
|
|
3589
|
+
};
|
|
3590
|
+
var describe = (t) => LOCAL_DESCRIPTION[t.name] ?? clean(t.description);
|
|
3591
|
+
function forTerminal(text, c) {
|
|
3592
|
+
let t = clean(text);
|
|
3593
|
+
const byName = new Map((c?.tools ?? []).map((x) => [x.name, `\`darwin ${x.cli.path.join(" ")}\``]));
|
|
3594
|
+
byName.set("get_api_reference", "`darwin docs`");
|
|
3595
|
+
t = t.replace(/(`?)\b([a-z][a-z0-9]*(?:_[a-z0-9]+)+)\b(`?)/g, (m, open, name, close) => {
|
|
3596
|
+
const cmd = byName.get(name);
|
|
3597
|
+
if (!cmd)
|
|
3598
|
+
return m;
|
|
3599
|
+
return open && close ? cmd : `${open}${cmd}${close}`;
|
|
3514
3600
|
});
|
|
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;
|
|
3601
|
+
t = t.replace(/`darwin docs`\s*§\s?([0-9]{1,2}[a-z]?)/g, "`darwin docs $1`");
|
|
3602
|
+
t = t.replace(/§\s?([0-9]{1,2}[a-z]?)/g, "`darwin docs $1`");
|
|
3603
|
+
t = t.replace(/\(summary\.sell\.amount\)/g, "(the amount the quote shows)");
|
|
3604
|
+
return t;
|
|
3526
3605
|
}
|
|
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
|
-
|
|
3606
|
+
var describeForTerminal = (t, c) => LOCAL_DESCRIPTION[t.name] ?? TERMINAL_DESCRIPTION[t.name] ?? forTerminal(t.description, c).trim();
|
|
3607
|
+
var STATIC_HELP = {
|
|
3608
|
+
login: {
|
|
3609
|
+
usage: "darwin login [--beta] [--client-name <name>] [--reconnect] [--start | --wait | --with-key | --key-file <file> | --from-skill] [--profile <name>] [--store keychain|file]",
|
|
3610
|
+
summary: "Connect this terminal to a Darwin agent and keep its API key in your system's secret store.",
|
|
3611
|
+
flags: {
|
|
3612
|
+
beta: "Use beta.darwin.finance instead of darwin.finance.",
|
|
3613
|
+
"client-name": `What Darwin shows your owner as the app asking (never "Darwin"). Default: the coding agent you're in, else "Command line".`,
|
|
3614
|
+
reconnect: "Get a new API key for an agent you already have, instead of creating a new agent.",
|
|
3615
|
+
start: "Start pairing and print the link and code, then exit (for tools whose calls time out).",
|
|
3616
|
+
wait: "Wait for a pairing started with --start, and save the key.",
|
|
3617
|
+
"with-key": "Import an API key from stdin, or paste it at a hidden prompt.",
|
|
3618
|
+
"key-file": "Import the key file downloaded from the agent's Manage tab (add --delete-file to delete it after).",
|
|
3619
|
+
"from-skill": "Import the key the official Darwin skill (darwin.py) saved (add --delete-skill-copy to remove its copy).",
|
|
3620
|
+
"agent-id": "With --from-skill: which saved key, when there are several.",
|
|
3621
|
+
profile: "Profile name for this key (default: from the agent's name).",
|
|
3622
|
+
store: "keychain (default) or file — a plain file only your user can read.",
|
|
3623
|
+
"no-browser": "Don't open the link in a browser."
|
|
3624
|
+
}
|
|
3625
|
+
},
|
|
3626
|
+
logout: {
|
|
3627
|
+
usage: "darwin logout [--profile <name> | --all] [--revoke [--force-local]]",
|
|
3628
|
+
summary: "Remove the API key from this computer. With --revoke, Darwin revokes the key first.",
|
|
3629
|
+
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." }
|
|
3630
|
+
},
|
|
3631
|
+
whoami: { usage: "darwin whoami [--profile <name>]", summary: "Which agent, site and API key this terminal uses (never any key characters).", flags: {} },
|
|
3632
|
+
profile: {
|
|
3633
|
+
usage: "darwin profile list | use <name> | rename <old> <new> | remove <name> | set-agent <name or id>",
|
|
3634
|
+
summary: "Manage profiles (one per API key). set-agent: the default agent for a key that trades for all your agents.",
|
|
3635
|
+
flags: {}
|
|
3636
|
+
},
|
|
3637
|
+
doctor: { usage: "darwin doctor", summary: "Check the install, the secret store, your profile and both Darwin sites.", flags: {} },
|
|
3638
|
+
api: { usage: "darwin api GET /api/agent/<path>", summary: "Read any agent API endpoint directly (GET only).", flags: {} },
|
|
3639
|
+
mcp: {
|
|
3640
|
+
usage: "darwin mcp [--profile <name>] | darwin mcp --print-config claude|cursor|gemini",
|
|
3641
|
+
summary: "Run Darwin as a local MCP server (stdio) for a desktop AI client, with the API key kept in your secret store.",
|
|
3642
|
+
flags: { "print-config": "Print the registration for a client, pointing at this installed copy." }
|
|
3643
|
+
},
|
|
3644
|
+
retry: {
|
|
3645
|
+
usage: "darwin retry <id> [--agent <name or id>]",
|
|
3646
|
+
summary: "What happened to an order whose result didn't come back: it went through, is still pending, failed, or was never sent. It only reads — it never sends anything again.",
|
|
3647
|
+
flags: {}
|
|
3648
|
+
},
|
|
3649
|
+
cancel: {
|
|
3650
|
+
usage: "darwin cancel <id> [--agent <name or id>]",
|
|
3651
|
+
summary: "Make sure an order that hasn't started never runs. One that already started can't be cancelled; it says what happened instead.",
|
|
3652
|
+
flags: {}
|
|
3653
|
+
},
|
|
3654
|
+
help: { usage: "darwin help [command] [--json]", summary: "This help. --json lists every command with its flags.", flags: {} },
|
|
3655
|
+
version: { usage: "darwin version", summary: "Print the Darwin CLI version.", flags: {} }
|
|
3656
|
+
};
|
|
3657
|
+
var GLOBAL_HELP = {
|
|
3658
|
+
json: "Print JSON (the default when output isn't a terminal).",
|
|
3659
|
+
profile: "Use this profile.",
|
|
3660
|
+
agent: "For an API key that trades for all your agents: which agent (name or id).",
|
|
3661
|
+
"dry-run": "Have Darwin check what a command would do — the real checks — and send nothing.",
|
|
3662
|
+
field: "For API fields without their own flag: --field name=value (repeatable).",
|
|
3663
|
+
quiet: "Less output."
|
|
3664
|
+
};
|
|
3665
|
+
function toolFlags(t) {
|
|
3666
|
+
const req = new Set((t.inputSchema.required ?? []).filter((r) => r !== t.idempotency?.field));
|
|
3667
|
+
return Object.entries(t.cli.args).map(([prop, a]) => {
|
|
3668
|
+
const p = t.inputSchema.properties?.[prop] ?? {};
|
|
3669
|
+
const type = Array.isArray(p.type) ? p.type.join("|") : p.type ?? "string";
|
|
3670
|
+
return { flag: `--${a.flag}`, property: prop, required: req.has(prop), type, description: clean(p.description ?? "") };
|
|
3671
|
+
});
|
|
3672
|
+
}
|
|
3673
|
+
function usageOf(t) {
|
|
3674
|
+
const pos = t.cli.positional.map((x) => `<${x}>`);
|
|
3675
|
+
const flags = toolFlags(t).map((f) => f.required ? `${f.flag} <${f.property}>` : `[${f.flag} <${f.property}>]`);
|
|
3676
|
+
const open = t.inputSchema.additionalProperties !== false && t.inputSchema.additionalProperties !== undefined;
|
|
3677
|
+
return ["darwin", ...t.cli.path, ...pos, ...flags, ...open ? ["[--field name=value]"] : []].join(" ");
|
|
3678
|
+
}
|
|
3679
|
+
function helpJson(c) {
|
|
3680
|
+
return {
|
|
3681
|
+
version: VERSION,
|
|
3682
|
+
realm: c.realm,
|
|
3683
|
+
credential: c.credential,
|
|
3684
|
+
catalogVersion: c.catalogVersion,
|
|
3685
|
+
static: Object.entries(STATIC_HELP).map(([command, h]) => ({ command, usage: h.usage, summary: h.summary, flags: h.flags })),
|
|
3686
|
+
globalFlags: GLOBAL_HELP,
|
|
3687
|
+
commands: c.tools.map((t) => ({
|
|
3688
|
+
command: t.cli.path.join(" "),
|
|
3689
|
+
aliases: t.cli.aliases.map((a) => a.join(" ")),
|
|
3690
|
+
tool: t.name,
|
|
3691
|
+
title: clean(t.title),
|
|
3692
|
+
write: t.write,
|
|
3693
|
+
costsTx: t.costsTx,
|
|
3694
|
+
usage: usageOf(t),
|
|
3695
|
+
positional: t.cli.positional,
|
|
3696
|
+
flags: toolFlags(t),
|
|
3697
|
+
description: describe(t),
|
|
3698
|
+
...t.deprecated ? { deprecated: t.deprecated } : {}
|
|
3699
|
+
}))
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
function printOverview(ctx, c, json) {
|
|
3703
|
+
if (json) {
|
|
3704
|
+
printJson(ctx, helpJson(c));
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
say(ctx, `Darwin CLI ${VERSION} — trade and check your Darwin agent from a terminal.`);
|
|
3708
|
+
say(ctx, "");
|
|
3709
|
+
say(ctx, "Darwin commands (from Darwin's catalogue; W = changes something):");
|
|
3710
|
+
const width = Math.max(...c.tools.map((t) => t.cli.path.join(" ").length)) + 2;
|
|
3711
|
+
for (const t of c.tools)
|
|
3712
|
+
say(ctx, ` ${t.cli.path.join(" ").padEnd(width)}${t.write ? "W " : " "}${forTerminal(t.title, c)}`);
|
|
3713
|
+
say(ctx, "");
|
|
3714
|
+
say(ctx, "This CLI's own commands:");
|
|
3715
|
+
for (const [k, h] of Object.entries(STATIC_HELP))
|
|
3716
|
+
say(ctx, ` ${k.padEnd(width)} ${h.summary}`);
|
|
3717
|
+
say(ctx, "");
|
|
3718
|
+
say(ctx, "Global flags: --json --profile <name> --agent <name or id> --dry-run --field name=value");
|
|
3719
|
+
say(ctx, "`darwin <command> --help` for a command's flags; `darwin help --json` for all of it.");
|
|
3720
|
+
say(ctx, "Output is text in a terminal and JSON when piped or redirected (or with --json); --format table forces text.");
|
|
3721
|
+
say(ctx, "");
|
|
3722
|
+
for (const l of terminalGuide(c.credential))
|
|
3723
|
+
say(ctx, l);
|
|
3724
|
+
}
|
|
3725
|
+
function terminalGuide(kind) {
|
|
3726
|
+
return [
|
|
3727
|
+
"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.",
|
|
3728
|
+
"Before your first order, `darwin grant` shows your limits and today's remaining budget. `darwin docs` is the full API reference.",
|
|
3729
|
+
"If an order's result is uncertain (exit code 6), don't run it again — run the `darwin retry <id>` it printed to see what happened (on a site without it, check `darwin orders`).",
|
|
3730
|
+
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."
|
|
3731
|
+
];
|
|
3732
|
+
}
|
|
3733
|
+
function printCommandHelp(ctx, t, json, c = null) {
|
|
3734
|
+
if (json) {
|
|
3735
|
+
printJson(ctx, helpJson({ tools: [t] }).commands[0]);
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
say(ctx, `${usageOf(t)}`);
|
|
3739
|
+
say(ctx, "");
|
|
3740
|
+
say(ctx, `${clean(t.title)}${t.write ? " — changes something" : ""}${t.costsTx ? "; counts against today's transaction budget" : ""}.`);
|
|
3741
|
+
say(ctx, describeForTerminal(t, c));
|
|
3742
|
+
const flags = toolFlags(t);
|
|
3743
|
+
if (flags.length || t.cli.positional.length)
|
|
3744
|
+
say(ctx, "");
|
|
3745
|
+
for (const p of t.cli.positional)
|
|
3746
|
+
say(ctx, ` <${p}> ${forTerminal(t.inputSchema.properties?.[p]?.description ?? "", c)}`);
|
|
3747
|
+
for (const f of flags)
|
|
3748
|
+
say(ctx, ` ${f.flag} <${f.type}>${f.required ? " (required)" : ""} ${forTerminal(f.description, c)}`);
|
|
3749
|
+
if (t.write)
|
|
3750
|
+
say(ctx, `
|
|
3751
|
+
There is no confirmation prompt: the command runs when you press enter. Use --dry-run to have Darwin check it without sending anything.`);
|
|
3752
|
+
if (t.deprecated)
|
|
3753
|
+
say(ctx, `
|
|
3754
|
+
Deprecated since ${t.deprecated.since}; removed after ${t.deprecated.removeAfter}.`);
|
|
3755
|
+
}
|
|
3756
|
+
function printStaticHelp(ctx, name, json) {
|
|
3757
|
+
const h = STATIC_HELP[name];
|
|
3758
|
+
if (json) {
|
|
3759
|
+
printJson(ctx, { command: name, ...h });
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
say(ctx, h.usage);
|
|
3763
|
+
say(ctx, "");
|
|
3764
|
+
say(ctx, h.summary);
|
|
3765
|
+
for (const [f, d] of Object.entries(h.flags))
|
|
3766
|
+
say(ctx, ` --${f} ${d}`);
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
// src/render.ts
|
|
3770
|
+
var BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
3771
|
+
var WSOL_MINT = "So11111111111111111111111111111111111111112";
|
|
3772
|
+
var ROUTER = { dflow: "dFlow", deflow: "dFlow", jupiter: "Jupiter" };
|
|
3773
|
+
var shortMint = (m) => {
|
|
3774
|
+
const s = txt(m);
|
|
3775
|
+
return BASE58.test(s) ? `${s.slice(0, 4)}…${s.slice(-4)}` : s;
|
|
3776
|
+
};
|
|
3777
|
+
function formatAtoms(atoms, decimals) {
|
|
3778
|
+
const a = txt(atoms);
|
|
3779
|
+
if (!/^[0-9]{1,40}$/.test(a) || typeof decimals !== "number" || !Number.isInteger(decimals) || decimals < 0 || decimals > 30)
|
|
3780
|
+
return "";
|
|
3781
|
+
if (decimals === 0)
|
|
3782
|
+
return BigInt(a).toString();
|
|
3783
|
+
const s = a.padStart(decimals + 1, "0");
|
|
3784
|
+
const whole = BigInt(s.slice(0, -decimals)).toString();
|
|
3785
|
+
const frac = s.slice(-decimals).replace(/0+$/, "");
|
|
3786
|
+
return frac ? `${whole}.${frac}` : whole;
|
|
3787
|
+
}
|
|
3788
|
+
var yesNo = (v) => v === true ? "yes" : v === false ? "no" : "";
|
|
3789
|
+
var router = (v) => ROUTER[txt(v).toLowerCase()] ?? txt(v);
|
|
3790
|
+
var plural = (n, one2, many = `${one2}s`) => `${n} ${n === 1 ? one2 : many}`;
|
|
3791
|
+
function tokenName(side, mint, x) {
|
|
3792
|
+
const sym = label(side.symbol, 24);
|
|
3793
|
+
if (sym)
|
|
3794
|
+
return sym;
|
|
3795
|
+
const m = txt(mint ?? side.mint);
|
|
3796
|
+
return x?.markets?.get(m)?.symbol || shortMint(m);
|
|
3797
|
+
}
|
|
3798
|
+
function columns(head, rows, indent = "") {
|
|
3799
|
+
if (rows.length === 0)
|
|
3800
|
+
return [];
|
|
3801
|
+
const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
3802
|
+
const line = (vals) => `${indent}${vals.map((v, i) => i === vals.length - 1 ? v : v.padEnd(widths[i])).join(" ")}`.trimEnd();
|
|
3803
|
+
return [line(head), ...rows.map(line)];
|
|
3804
|
+
}
|
|
3805
|
+
function welcomeLines(realm, hello, agentName) {
|
|
3806
|
+
const h = hello ?? {};
|
|
3807
|
+
const out = [`Agent: ${label(agentName, 120) || "your agent"} on ${realm}`];
|
|
3808
|
+
const addr = typeof h.solanaAddress === "string" && BASE58.test(h.solanaAddress) ? h.solanaAddress : null;
|
|
3809
|
+
if (addr)
|
|
3810
|
+
out.push(`Agent wallet (fund it to trade): ${addr}`);
|
|
3811
|
+
const page = sameRealmUrl(h.agentPageUrl, realm);
|
|
3812
|
+
if (page)
|
|
3813
|
+
out.push(`Agent page: ${page}`);
|
|
3814
|
+
out.push("Try: darwin market-status · darwin balances · darwin quote --sell USDC --amount 1 --for SOL");
|
|
3815
|
+
return out;
|
|
3816
|
+
}
|
|
3817
|
+
function sameRealmUrl(v, realm) {
|
|
3818
|
+
if (typeof v !== "string")
|
|
3819
|
+
return null;
|
|
3820
|
+
try {
|
|
3821
|
+
const u = new URL(v);
|
|
3822
|
+
return u.protocol === "https:" && u.host === realm && !u.username && !u.password ? txt(u.toString()) : null;
|
|
3823
|
+
} catch {
|
|
3824
|
+
return null;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
function hello(r, x) {
|
|
3828
|
+
const name = txt(obj(r.untrusted).agentName) || txt(r.agentName) || x.agentLabel;
|
|
3829
|
+
return welcomeLines(x.realm, r, name);
|
|
3830
|
+
}
|
|
3831
|
+
function agents(r) {
|
|
3832
|
+
const d = obj(r.data);
|
|
3833
|
+
const list = arr(d.agents).map(obj);
|
|
3834
|
+
if (list.length === 0)
|
|
3835
|
+
return ["No active agents for this API key."];
|
|
3836
|
+
const rows = list.map((a) => [label(a.name, 40) || "(unnamed)", txt(a.id), txt(a.status) || "", BASE58.test(txt(a.solanaAddress)) ? txt(a.solanaAddress) : ""]);
|
|
3837
|
+
const out = columns(["Name", "ID", "Status", "Wallet"], rows);
|
|
3838
|
+
const perms = txt(d.permissions);
|
|
3839
|
+
if (perms)
|
|
3840
|
+
out.push("", `This API key: ${perms}`);
|
|
3841
|
+
return out;
|
|
3842
|
+
}
|
|
3843
|
+
function capLine(key, c) {
|
|
3844
|
+
const name = words(key).replace(/\busd\b/, "").trim();
|
|
3845
|
+
if (c.refusesAll === true)
|
|
3846
|
+
return `${name}: none allowed`;
|
|
3847
|
+
if (c.unlimited === true || c.value === null)
|
|
3848
|
+
return `${name}: no limit`;
|
|
3849
|
+
return `${name}: ${/Usd$/.test(key) ? usd(c.value) : num(c.value)}`;
|
|
3850
|
+
}
|
|
3851
|
+
function grant(r) {
|
|
3852
|
+
const d = obj(r.data);
|
|
3853
|
+
const g = obj(d.grant);
|
|
3854
|
+
const t = obj(d.today);
|
|
3855
|
+
const out = [];
|
|
3856
|
+
const paused = g.paused === true;
|
|
3857
|
+
out.push(`Agent: ${label(g.agentName, 80) || "this agent"} · ${paused ? `paused${when(g.pausedAt) ? ` since ${when(g.pausedAt)}` : ""}` : txt(g.status) || "active"}`);
|
|
3858
|
+
if (BASE58.test(txt(g.wallet)))
|
|
3859
|
+
out.push(`Wallet: ${txt(g.wallet)}`);
|
|
3860
|
+
const actions = arr(g.allowedActions).map(txt).filter(Boolean);
|
|
3861
|
+
if (actions.length)
|
|
3862
|
+
out.push(`Can: ${actions.map((a) => a.replace(/_/g, " ")).join(", ")}`);
|
|
3863
|
+
const caps = Object.entries(obj(g.capLimits)).map(([k, c]) => capLine(k, obj(c)));
|
|
3864
|
+
if (caps.length)
|
|
3865
|
+
out.push(`Limits: ${caps.join(" · ")}`);
|
|
3866
|
+
const sl = obj(g.slippage);
|
|
3867
|
+
if (typeof sl.globalMaxBps === "number") {
|
|
3868
|
+
const overrides = arr(sl.overrides).length;
|
|
3869
|
+
out.push(`Max slippage: ${sl.globalMaxBps} bps${overrides ? ` (${plural(overrides, "token-specific override")})` : ""}`);
|
|
3870
|
+
}
|
|
3871
|
+
const rem = obj(t.remaining);
|
|
3872
|
+
const left = [
|
|
3873
|
+
rem.notionalUsd === null ? "no dollar limit" : usd(rem.notionalUsd) ? `${usd(rem.notionalUsd)} to trade` : "",
|
|
3874
|
+
rem.txCount === null ? "no trade-count limit" : typeof rem.txCount === "number" ? plural(rem.txCount, "trade") : "",
|
|
3875
|
+
rem.riskUsd === null ? "" : usd(rem.riskUsd) ? `${usd(rem.riskUsd)} of risk` : ""
|
|
3876
|
+
].filter(Boolean);
|
|
3877
|
+
if (left.length)
|
|
3878
|
+
out.push(`Left today: ${left.join(" · ")}`);
|
|
3879
|
+
const used = [usd(t.notionalUsd) ? `${usd(t.notionalUsd)} traded` : "", typeof t.txCount === "number" ? plural(t.txCount, "trade") : ""].filter(Boolean);
|
|
3880
|
+
if (used.length)
|
|
3881
|
+
out.push(`Used today: ${used.join(" · ")}${txt(t.day) ? ` (UTC day ${txt(t.day)}; resets at midnight UTC)` : ""}`);
|
|
3882
|
+
const u = obj(g.unlisted);
|
|
3883
|
+
if (u.enabled === true)
|
|
3884
|
+
out.push(`Tokens Darwin doesn't list: allowed — ${usd(u.dailyAcquisitionUsd)} a day to buy, ${usd(u.dailyDisposalUsd)} to sell`);
|
|
3885
|
+
else if (u.enabled === false)
|
|
3886
|
+
out.push("Tokens Darwin doesn't list: not allowed");
|
|
3887
|
+
return out;
|
|
3888
|
+
}
|
|
3889
|
+
function pause(r, x, kind) {
|
|
3890
|
+
const s = obj(r.summary);
|
|
3891
|
+
const at = when(s.pausedAtIso) || when(s.pausedAt) || when(obj(r.data).pausedAt);
|
|
3892
|
+
const changed = obj(r.data).changed;
|
|
3893
|
+
const head = changed === false ? `${x.agentLabel} was already paused.` : `Paused ${x.agentLabel}${at ? ` at ${at}` : ""}.`;
|
|
3894
|
+
return [
|
|
3895
|
+
head,
|
|
3896
|
+
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."
|
|
3897
|
+
];
|
|
3898
|
+
}
|
|
3899
|
+
var typed = (v) => txt(v).replace(/[^A-Za-z0-9._-]/g, "");
|
|
3900
|
+
function quote(r, x) {
|
|
3901
|
+
const s = obj(r.summary);
|
|
3902
|
+
const d = obj(r.data);
|
|
3903
|
+
const sell = obj(s.sell);
|
|
3904
|
+
const recv = obj(s.receive);
|
|
3905
|
+
const sellName = tokenName(sell, d.inputMint, x);
|
|
3906
|
+
const getName = tokenName(recv, d.outputMint, x);
|
|
3907
|
+
const amount = num(sell.amount) || num(x.args.amount);
|
|
3908
|
+
const quoted = num(recv.quoted);
|
|
3909
|
+
const atLeast = num(recv.atLeast);
|
|
3910
|
+
const out = [`Sell ${amount} ${sellName} → ${quoted ? `about ${quoted} ${getName}` : getName}${atLeast ? ` (at least ${atLeast} ${getName})` : ""}`];
|
|
3911
|
+
const parts = [];
|
|
3912
|
+
if (typeof d.maxSlippageBps === "number")
|
|
3913
|
+
parts.push(`Max slippage ${d.maxSlippageBps} bps`);
|
|
3914
|
+
if (txt(d.router))
|
|
3915
|
+
parts.push(`route ${router(d.router)}`);
|
|
3916
|
+
const exp = typeof d.expiresAtMs === "number" ? d.expiresAtMs : null;
|
|
3917
|
+
if (exp !== null)
|
|
3918
|
+
parts.push(`quote expires in ${Math.max(0, Math.ceil((exp - x.now) / 1000))}s (${clock(new Date(exp))})`);
|
|
3919
|
+
if (parts.length)
|
|
3920
|
+
out.push(parts.join(" · "));
|
|
3921
|
+
const id = txt(d.quoteId) || txt(s.quoteId);
|
|
3922
|
+
if (id && /^[A-Za-z0-9_-]{1,80}$/.test(id) && sell.amount !== null) {
|
|
3923
|
+
out.push(`To place it: darwin order --quote ${id} --sell ${typed(x.args.sell)} --amount ${amount} --for ${typed(x.args.for)}`);
|
|
3924
|
+
} else if (sell.amount === null) {
|
|
3925
|
+
out.push("This token's decimals aren't known, so this quote can't be placed by amount.");
|
|
3926
|
+
}
|
|
3927
|
+
return out;
|
|
3928
|
+
}
|
|
3929
|
+
function spotWrite(r, x, instant) {
|
|
3930
|
+
const s = obj(r.summary);
|
|
3931
|
+
const d = obj(r.data);
|
|
3932
|
+
const sell = obj(s.sell);
|
|
3933
|
+
const recv = obj(s.receive);
|
|
3934
|
+
const sellName = tokenName(sell, undefined, x) || typed(x.args.sell);
|
|
3935
|
+
const getName = tokenName(recv, undefined, x) || typed(x.args.for);
|
|
3936
|
+
const amount = num(sell.amount) || num(x.args.amount);
|
|
3937
|
+
const status = txt(s.status) || txt(d.status);
|
|
3938
|
+
const sig = txt(s.txSignature) || txt(d.txSignature);
|
|
3939
|
+
const orderId = txt(s.orderId) || txt(d.orderId);
|
|
3940
|
+
const nonce = x.nonce ?? txt(d.clientOrderNonce);
|
|
3941
|
+
const out = [`Sent: sell ${amount} ${sellName} for ${getName} on ${x.agentLabel} · ${orderId ? `order ${orderId} · ` : ""}nonce ${nonce}`];
|
|
3942
|
+
if (instant && d.replayed === true)
|
|
3943
|
+
out.push("(That nonce was already used, so this is the earlier order — nothing new was sent.)");
|
|
3944
|
+
const quoted = num(recv.quoted);
|
|
3945
|
+
const atLeast = num(recv.atLeast);
|
|
3946
|
+
if (quoted)
|
|
3947
|
+
out.push(`Expected: about ${quoted} ${getName}${atLeast ? ` (at least ${atLeast} ${getName})` : ""}`);
|
|
3948
|
+
if (status === "confirmed")
|
|
3949
|
+
out.push("Status: confirmed on chain");
|
|
3950
|
+
else if (status === "submitted")
|
|
3951
|
+
out.push("Status: sent to the network, not confirmed yet");
|
|
3952
|
+
else if (status)
|
|
3953
|
+
out.push(`Status: ${status}`);
|
|
3954
|
+
if (/^[1-9A-HJ-NP-Za-km-z]{64,90}$/.test(sig))
|
|
3955
|
+
out.push(`Transaction: ${sig}`);
|
|
3956
|
+
return out.filter(Boolean);
|
|
3957
|
+
}
|
|
3958
|
+
function balances(r, x) {
|
|
3959
|
+
const d = obj(r.data);
|
|
3960
|
+
const rows = arr(d.balances).map(obj);
|
|
3961
|
+
const out = [];
|
|
3962
|
+
if (BASE58.test(txt(d.wallet)))
|
|
3963
|
+
out.push(`Wallet: ${txt(d.wallet)}`);
|
|
3964
|
+
const lamports = txt(d.nativeSolLamports);
|
|
3965
|
+
const body = rows.map((b) => [
|
|
3966
|
+
tokenName(b, b.mint, x),
|
|
3967
|
+
num(b.amount) || formatAtoms(b.atoms, typeof b.decimals === "number" ? b.decimals : null) || `${txt(b.atoms)} (smallest units)`,
|
|
3968
|
+
b.listed === false ? `${txt(b.mint)} (not listed on Darwin)` : txt(b.mint)
|
|
3969
|
+
]);
|
|
3970
|
+
if (body.length)
|
|
3971
|
+
out.push(...columns(["Token", "Amount", "Mint"], body));
|
|
3972
|
+
else
|
|
3973
|
+
out.push("No token balances.");
|
|
3974
|
+
if (/^[0-9]{1,20}$/.test(lamports))
|
|
3975
|
+
out.push(`SOL for network fees: ${formatAtoms(lamports, 9)}`);
|
|
3976
|
+
if (d.gasSponsored === true)
|
|
3977
|
+
out.push("Network fees: paid by Darwin.");
|
|
3978
|
+
if (d.gasLow === true && d.gasSponsored !== true)
|
|
3979
|
+
out.push("⚠ Low on SOL for network fees — add SOL to keep trading.");
|
|
3980
|
+
if (d.partial === true)
|
|
3981
|
+
out.push("Some balances couldn't be read just now, so this list may be incomplete.");
|
|
3982
|
+
return out;
|
|
3983
|
+
}
|
|
3984
|
+
var atomsAs = (atoms, mint, x) => {
|
|
3985
|
+
const m = x.markets?.get(txt(mint));
|
|
3986
|
+
const amt = m ? formatAtoms(atoms, m.decimals) : "";
|
|
3987
|
+
return amt ? `${amt} ${m.symbol}` : `${txt(atoms) || "?"} units of ${shortMint(mint)}`;
|
|
3988
|
+
};
|
|
3989
|
+
function orders(r, x) {
|
|
3990
|
+
const d = obj(r.data);
|
|
3991
|
+
const list = arr(d.orders).map(obj);
|
|
3992
|
+
if (list.length === 0)
|
|
3993
|
+
return ["No spot orders yet."];
|
|
3994
|
+
const rows = list.map((o) => [
|
|
3995
|
+
when(o.createdAt),
|
|
3996
|
+
`${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)}`,
|
|
3997
|
+
`${txt(o.status)}${txt(o.errorCode) ? ` (${txt(o.errorCode)})` : ""}`,
|
|
3998
|
+
txt(o.orderId)
|
|
3999
|
+
]);
|
|
4000
|
+
const out = columns(["When", "Trade", "Status", "Order"], rows);
|
|
4001
|
+
if (txt(d.nextCursor))
|
|
4002
|
+
out.push("", `More: darwin orders --cursor ${txt(d.nextCursor)}`);
|
|
4003
|
+
return out;
|
|
4004
|
+
}
|
|
4005
|
+
function tx(r) {
|
|
4006
|
+
const d = obj(r.data);
|
|
4007
|
+
const st = txt(d.status);
|
|
4008
|
+
const what = st === "confirmed" ? "confirmed on chain" : st === "failed" ? "failed" : st === "submitted" ? "sent, not confirmed yet" : st;
|
|
4009
|
+
const out = [`Transaction ${txt(d.signature)}: ${what}${typeof d.slot === "number" ? ` (slot ${d.slot})` : ""}`];
|
|
4010
|
+
if (txt(d.error))
|
|
4011
|
+
out.push(`Reason: ${txt(d.error).replace(/_/g, " ")}`);
|
|
4012
|
+
return out;
|
|
4013
|
+
}
|
|
4014
|
+
function spotMarkets(r) {
|
|
4015
|
+
const d = obj(r.data);
|
|
4016
|
+
const list = arr(d.instruments).map(obj);
|
|
4017
|
+
if (list.length === 0)
|
|
4018
|
+
return ["No markets."];
|
|
4019
|
+
const rows = list.map((m) => [
|
|
4020
|
+
label(m.symbol, 20),
|
|
4021
|
+
txt(m.instrumentType),
|
|
4022
|
+
txt(m.assetClass).replace(/_/g, " "),
|
|
4023
|
+
m.instrumentType === "perp" ? m.tradeable === false || m.status === "paused" ? "paused" : "open" : m.marketOpen === false ? "closed now" : "open",
|
|
4024
|
+
m.instrumentType === "spot" ? txt(m.mint) : `max ${num(m.maxLeverage)}x`
|
|
4025
|
+
]);
|
|
4026
|
+
return columns(["Symbol", "Type", "Class", "Market", "Mint / leverage"], rows);
|
|
4027
|
+
}
|
|
4028
|
+
function unlistedInspect(r) {
|
|
4029
|
+
const d = obj(r.data);
|
|
4030
|
+
const name = label(d.name, 60);
|
|
4031
|
+
const sym = label(d.symbol, 24);
|
|
4032
|
+
const out = [`Token: ${[name, sym ? `(${sym})` : ""].filter(Boolean).join(" ") || "(no name)"}${txt(d.mint) ? ` · ${txt(d.mint)}` : ""}`];
|
|
4033
|
+
if (d.listing === "darwin")
|
|
4034
|
+
out.push("Darwin lists this token: trade it with `darwin quote`.");
|
|
4035
|
+
out.push(`Tradable as an unlisted token: ${d.eligible === true ? "yes" : "no"}`);
|
|
4036
|
+
for (const f of arr(d.refusals).map(obj))
|
|
4037
|
+
out.push(` ✗ ${txt(f.code).replace(/_/g, " ")}${txt(f.detail) ? ` — ${txt(f.detail)}` : ""}`);
|
|
4038
|
+
for (const f of arr(d.warnings).map(obj))
|
|
4039
|
+
out.push(` ! ${txt(f.code).replace(/_/g, " ")}${txt(f.detail) ? ` — ${txt(f.detail)}` : ""}`);
|
|
4040
|
+
const facts = [
|
|
4041
|
+
typeof d.decimals === "number" ? `${d.decimals} decimals` : "",
|
|
4042
|
+
typeof d.ageHours === "number" ? `${Math.round(d.ageHours)}h old` : "",
|
|
4043
|
+
typeof d.holders === "number" ? `${d.holders.toLocaleString("en-US")} holders` : "",
|
|
4044
|
+
usd(d.volume24hUsd) ? `24h volume ${usd(d.volume24hUsd)}` : "",
|
|
4045
|
+
usd(d.twapUsd) ? `price ${usd(d.twapUsd)} (${txt(d.twapWindow) || "1h"} average)` : ""
|
|
4046
|
+
].filter(Boolean);
|
|
4047
|
+
if (facts.length)
|
|
4048
|
+
out.push(facts.join(" · "));
|
|
4049
|
+
const b = obj(d.budget);
|
|
4050
|
+
if (usd(b.acquisitionRemainingUsd))
|
|
4051
|
+
out.push(`Unlisted budget left today: ${usd(b.acquisitionRemainingUsd)} to buy · ${usd(b.disposalRemainingUsd)} to sell`);
|
|
4052
|
+
return out;
|
|
4053
|
+
}
|
|
4054
|
+
var total = (t) => {
|
|
4055
|
+
const o = obj(t);
|
|
4056
|
+
if (o.usd === null || o.usd === undefined)
|
|
4057
|
+
return o.complete === false ? "unknown" : "";
|
|
4058
|
+
return `${usd(o.usd)}${o.complete === false ? " (incomplete)" : ""}`;
|
|
4059
|
+
};
|
|
4060
|
+
function trades(r, x) {
|
|
4061
|
+
const d = obj(r.data);
|
|
4062
|
+
const list = arr(d.trades).map(obj);
|
|
4063
|
+
if (list.length === 0)
|
|
4064
|
+
return ["No settled trades yet."];
|
|
4065
|
+
const rows = list.map((t) => {
|
|
4066
|
+
if (t.kind === "perp") {
|
|
4067
|
+
return [when(t.settledAtMs), "perps", `${txt(t.side)} ${num(t.sizeBase)} ${label(t.symbol, 16)}${usd(t.priceUsd) ? ` @ ${usd(t.priceUsd)}` : ""}`, usd(t.realizedPnlUsd)];
|
|
4068
|
+
}
|
|
4069
|
+
const i = obj(t.in);
|
|
4070
|
+
const o = obj(t.out);
|
|
4071
|
+
return [when(t.settledAtMs), "spot", `${atomsAs(i.atoms, i.mint, x)} → ${atomsAs(o.atoms, o.mint, x)}`, t.realizedPnlUsd === undefined ? "" : usd(t.realizedPnlUsd) || "unknown"];
|
|
4072
|
+
});
|
|
4073
|
+
const out = columns(["Settled", "Kind", "Trade", "Realized P&L"], rows);
|
|
4074
|
+
if (txt(d.nextCursor))
|
|
4075
|
+
out.push("", `More: darwin history trades --cursor ${txt(d.nextCursor)}`);
|
|
4076
|
+
return out;
|
|
4077
|
+
}
|
|
4078
|
+
function pnl(r) {
|
|
4079
|
+
const d = obj(r.data);
|
|
4080
|
+
const s = obj(d.spot);
|
|
4081
|
+
const p = obj(d.perps);
|
|
4082
|
+
const out = [`P&L${txt(d.period) ? ` (${txt(d.period)})` : ""}${when(d.asOfMs) ? ` as of ${when(d.asOfMs)}` : ""}`];
|
|
4083
|
+
const realized = obj(s.realizedUsd);
|
|
4084
|
+
const line = (name, parts) => {
|
|
4085
|
+
const shown = parts.map(([k, v]) => [k, total(v)]).filter(([, v]) => v);
|
|
4086
|
+
if (shown.length)
|
|
4087
|
+
out.push(`${name}: ${shown.map(([k, v]) => `${k} ${v}`).join(" · ")}`);
|
|
4088
|
+
};
|
|
4089
|
+
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]]);
|
|
4090
|
+
line("Perps", [["realized", p.realizedUsd], ["fees", p.feesUsd], ["funding", p.fundingUsd], ["unrealized", p.unrealizedUsd], ["equity", p.equityUsd]]);
|
|
4091
|
+
const f = obj(d.feesUsd);
|
|
4092
|
+
line("Fees", [["network", f.networkWalletPaid], ["perps trading", f.perpTrading]]);
|
|
4093
|
+
const pos = arr(s.positions).map(obj);
|
|
4094
|
+
if (pos.length) {
|
|
4095
|
+
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" : "")])));
|
|
4096
|
+
}
|
|
4097
|
+
for (const n of arr(d.notes))
|
|
4098
|
+
if (txt(n))
|
|
4099
|
+
out.push(`Note: ${txt(n)}`);
|
|
4100
|
+
return out;
|
|
4101
|
+
}
|
|
4102
|
+
function lots(r) {
|
|
4103
|
+
const d = obj(r.data);
|
|
4104
|
+
const list = arr(d.lots).map(obj);
|
|
4105
|
+
const out = [`Tax lots${txt(d.method) ? ` (method: ${txt(d.method)})` : ""}`];
|
|
4106
|
+
if (list.length === 0)
|
|
4107
|
+
return [...out, "No open tax lots."];
|
|
4108
|
+
out.push(...columns(["Asset", "Qty", "Available", "Cost basis", "Acquired", "Term", "Unrealized"], list.map((l) => [
|
|
4109
|
+
label(l.assetId, 24),
|
|
4110
|
+
num(l.qty),
|
|
4111
|
+
num(l.availableQty),
|
|
4112
|
+
usd(l.costBasisUsd),
|
|
4113
|
+
when(l.acquiredAtMs),
|
|
4114
|
+
txt(l.term),
|
|
4115
|
+
usd(l.unrealizedUsd) || (l.unpriced === true ? "unpriced" : "")
|
|
4116
|
+
])));
|
|
4117
|
+
return out;
|
|
4118
|
+
}
|
|
4119
|
+
function perpMarkets(r) {
|
|
4120
|
+
const list = arr(obj(r.data).markets).map(obj);
|
|
4121
|
+
if (list.length === 0)
|
|
4122
|
+
return ["No perps markets."];
|
|
4123
|
+
return columns(["Symbol", "Name", "Max leverage", "Min size", "Tick", "Status"], list.map((m) => [
|
|
4124
|
+
label(m.symbol, 16),
|
|
4125
|
+
label(m.name, 30),
|
|
4126
|
+
m.maxLeverage === undefined ? "" : `${num(m.maxLeverage)}x`,
|
|
4127
|
+
num(m.minOrderSize),
|
|
4128
|
+
usd(m.tick),
|
|
4129
|
+
m.tradeable === false ? "not tradeable" : txt(m.status)
|
|
4130
|
+
]));
|
|
4131
|
+
}
|
|
4132
|
+
function perpQuote(r) {
|
|
4133
|
+
const d = obj(r.data);
|
|
4134
|
+
const f = typeof d.fundingRatePct === "number" ? `${d.fundingRatePct >= 0 ? "+" : "−"}${Math.abs(d.fundingRatePct)}% funding (+ = longs pay)` : "";
|
|
4135
|
+
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(" · ")];
|
|
4136
|
+
}
|
|
4137
|
+
function perpAccount(r) {
|
|
4138
|
+
const d = obj(r.data);
|
|
4139
|
+
if (d.hasTraderAccount === false)
|
|
4140
|
+
return ["This agent has no perps account yet."];
|
|
4141
|
+
const a = obj(d.account);
|
|
4142
|
+
return [`Equity ${usd(a.equityUsd)} · collateral ${usd(a.collateralUsd)} · free ${usd(a.freeCollateralUsd)} · margin used ${usd(a.marginUsedUsd)} · maintenance ${usd(a.maintenanceMarginUsd)}`];
|
|
4143
|
+
}
|
|
4144
|
+
function perpPositions(r) {
|
|
4145
|
+
const d = obj(r.data);
|
|
4146
|
+
if (d.hasTraderAccount === false)
|
|
4147
|
+
return ["This agent has no perps account yet."];
|
|
4148
|
+
const list = arr(d.positions).map(obj);
|
|
4149
|
+
if (list.length === 0)
|
|
4150
|
+
return ["No open perps positions."];
|
|
4151
|
+
return columns(["Market", "Side", "Size", "Entry", "Mark", "Unrealized", "Liquidation", "TP / SL"], list.map((p) => [
|
|
4152
|
+
label(p.symbol, 16),
|
|
4153
|
+
txt(p.side),
|
|
4154
|
+
num(p.sizeBase),
|
|
4155
|
+
usd(p.entryUsd),
|
|
4156
|
+
usd(p.markUsd),
|
|
4157
|
+
usd(p.uPnlUsd),
|
|
4158
|
+
usd(p.liqUsd),
|
|
4159
|
+
[usd(p.takeProfitUsd), usd(p.stopLossUsd)].map((v) => v || "—").join(" / ")
|
|
4160
|
+
]));
|
|
4161
|
+
}
|
|
4162
|
+
function perpOrders(r) {
|
|
4163
|
+
const d = obj(r.data);
|
|
4164
|
+
if (d.hasTraderAccount === false)
|
|
4165
|
+
return ["This agent has no perps account yet."];
|
|
4166
|
+
const list = arr(d.orders).map(obj);
|
|
4167
|
+
const trig = arr(d.triggers).map(obj);
|
|
4168
|
+
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."];
|
|
4169
|
+
if (trig.length)
|
|
4170
|
+
out.push("", ...columns(["Market", "Trigger", "Price"], trig.map((t) => [label(t.symbol, 16), txt(t.kind).replace(/_/g, " "), usd(t.priceUsd)])));
|
|
4171
|
+
return out;
|
|
4172
|
+
}
|
|
4173
|
+
function perpProtections(r) {
|
|
4174
|
+
const d = obj(r.data);
|
|
4175
|
+
const list = arr(d.protections).map(obj);
|
|
4176
|
+
if (list.length === 0)
|
|
4177
|
+
return ["No TP/SL protections."];
|
|
4178
|
+
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)]));
|
|
4179
|
+
if (d.hasMore === true)
|
|
4180
|
+
out.push("", "Only the newest 200 are shown.");
|
|
4181
|
+
return out;
|
|
4182
|
+
}
|
|
4183
|
+
function indicators(r) {
|
|
4184
|
+
const d = obj(r.data);
|
|
4185
|
+
const bars = arr(d.bars).map(obj);
|
|
4186
|
+
const keys = arr(d.indicators).map((i) => txt(obj(i).key)).filter(Boolean);
|
|
4187
|
+
const out = [`Indicators for ${label(d.asset_id, 40)} on ${txt(d.timeframe)} candles${d.stale === true ? " (stale: the newest candle is old)" : ""}`];
|
|
4188
|
+
if (bars.length === 0)
|
|
4189
|
+
return [...out, "No bars."];
|
|
4190
|
+
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(" ");
|
|
4191
|
+
out.push(...columns(["Bar opened", "Close", ...keys], bars.map((b) => [when(b.open_time_ms), num(b.close), ...keys.map((k) => val(b[k]))])));
|
|
4192
|
+
return out;
|
|
4193
|
+
}
|
|
4194
|
+
function renderersFor(kind) {
|
|
4195
|
+
return {
|
|
4196
|
+
hello,
|
|
4197
|
+
list_agents: (r) => agents(r),
|
|
4198
|
+
get_grant: (r) => grant(r),
|
|
4199
|
+
pause_agent: (r, x) => pause(r, x, kind),
|
|
4200
|
+
get_spot_quote: quote,
|
|
4201
|
+
quote_unlisted_token: quote,
|
|
4202
|
+
place_spot_order: (r, x) => spotWrite(r, x, false),
|
|
4203
|
+
place_unlisted_order: (r, x) => spotWrite(r, x, false),
|
|
4204
|
+
spot_order_now: (r, x) => spotWrite(r, x, true),
|
|
4205
|
+
get_balances: balances,
|
|
4206
|
+
list_spot_orders: orders,
|
|
4207
|
+
get_tx_status: (r) => tx(r),
|
|
4208
|
+
list_spot_markets: (r) => spotMarkets(r),
|
|
4209
|
+
inspect_unlisted_token: (r) => unlistedInspect(r),
|
|
4210
|
+
list_trades: trades,
|
|
4211
|
+
get_pnl: (r) => pnl(r),
|
|
4212
|
+
list_lots: (r) => lots(r),
|
|
4213
|
+
list_perp_markets: (r) => perpMarkets(r),
|
|
4214
|
+
get_perp_quote: (r) => perpQuote(r),
|
|
4215
|
+
get_perp_account: (r) => perpAccount(r),
|
|
4216
|
+
list_perp_positions: (r) => perpPositions(r),
|
|
4217
|
+
list_perp_orders: (r) => perpOrders(r),
|
|
4218
|
+
list_perp_protections: (r) => perpProtections(r),
|
|
4219
|
+
get_indicators: (r) => indicators(r)
|
|
4220
|
+
};
|
|
4221
|
+
}
|
|
4222
|
+
function renderResult(tool, kind, result, x) {
|
|
4223
|
+
if (typeof result.text === "string")
|
|
4224
|
+
return forTerminal(result.text, x.catalogue).split(`
|
|
4225
|
+
`);
|
|
4226
|
+
const r = renderersFor(kind)[tool];
|
|
4227
|
+
if (r) {
|
|
4228
|
+
try {
|
|
4229
|
+
const lines2 = r(result, x);
|
|
4230
|
+
if (lines2.length)
|
|
4231
|
+
return lines2;
|
|
4232
|
+
} catch {}
|
|
4233
|
+
}
|
|
4234
|
+
const { summary, data } = result;
|
|
4235
|
+
const lines = [];
|
|
4236
|
+
if (summary !== undefined)
|
|
4237
|
+
lines.push(...genericLines(summary));
|
|
4238
|
+
if (data !== undefined)
|
|
4239
|
+
lines.push(...genericLines(data));
|
|
4240
|
+
return lines.length ? lines : ["Done."];
|
|
4241
|
+
}
|
|
4242
|
+
var NOTHING_SENT = "Nothing was sent.";
|
|
4243
|
+
var REFUSAL_TEXT = {
|
|
4244
|
+
market_closed: "That market is closed right now. `darwin market-status` shows when US stocks trade again.",
|
|
4245
|
+
quote_expired: "That quote expired (a quote lasts 30 seconds). Get a new one with `darwin quote`.",
|
|
4246
|
+
quote_not_found: "Darwin doesn't know that quote ID. Get a quote with `darwin quote` and place it within 30 seconds.",
|
|
4247
|
+
quote_required: "An order needs a quote first: run `darwin quote`, then `darwin order --quote <id>`.",
|
|
4248
|
+
quote_mismatch: "The order doesn't match its quote: use the same --sell, --amount and --for as the quote.",
|
|
4249
|
+
quote_already_used: "That quote was already used. Check `darwin orders`, or get a new quote.",
|
|
4250
|
+
quote_price_moved: "The price moved beyond the quote's slippage limit. Get a new quote and try again.",
|
|
4251
|
+
quote_wrong_agent: "That quote was made for a different agent.",
|
|
4252
|
+
quote_wrong_endpoint: "That quote is for a token Darwin doesn't list; it can't be placed with `darwin order`.",
|
|
4253
|
+
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).",
|
|
4254
|
+
per_trade_cap: "This order is over the agent's per-trade limit. Try a smaller amount (`darwin grant` shows the limits).",
|
|
4255
|
+
per_trade_risk: "This order puts more at risk than the agent's per-trade limit allows. Try a smaller amount.",
|
|
4256
|
+
daily_cap: "This agent has used today's trading budget. It resets at midnight UTC; `darwin grant` shows what's left.",
|
|
4257
|
+
daily_tx_count: "This agent has made today's maximum number of trades. It resets at midnight UTC.",
|
|
4258
|
+
daily_risk: "This agent has used today's risk budget. Selling into a stablecoin still works; it resets at midnight UTC.",
|
|
4259
|
+
asset_daily_acquisition: "Today's budget for buying this token is used up. Another token may still work.",
|
|
4260
|
+
below_min_trade: "That amount is below the minimum trade size.",
|
|
4261
|
+
insufficient_sol: "Not enough SOL: selling that much would leave too little for network fees.",
|
|
4262
|
+
self_pay_insolvent: "This agent's wallet can't pay its network fees. Add SOL to its wallet to trade again.",
|
|
4263
|
+
kill_switch: "Darwin has paused agent trading for now. Try again later.",
|
|
4264
|
+
sanctions_blocked: "Refused for compliance reasons.",
|
|
4265
|
+
account_locked: "The owner's Darwin account is locked, so this agent can't trade.",
|
|
4266
|
+
grant_invalid: "This agent can no longer trade with this API key (revoked or expired). Ask the owner, or run `darwin login` again.",
|
|
4267
|
+
rate_limited: "Too many requests with this API key right now. Wait a minute and try again.",
|
|
4268
|
+
relay_unavailable: "Darwin couldn't send the transaction right now. Try again in a few seconds.",
|
|
4269
|
+
relay_rate_limited: "Too many transactions from this wallet just now. Wait a few seconds and try again.",
|
|
4270
|
+
simulation_unavailable: "Darwin couldn't check the transaction just now. Try again in a few seconds.",
|
|
4271
|
+
quote_unavailable: "No price is available for that trade right now. Try again shortly.",
|
|
4272
|
+
price_sanity: "The quoted price looked wrong compared with the market, so Darwin refused it. Try again shortly.",
|
|
4273
|
+
mint_not_in_scope: "This agent isn't allowed to trade that token.",
|
|
4274
|
+
unlisted_disabled: "This agent isn't allowed to trade tokens Darwin doesn't list.",
|
|
4275
|
+
ambiguous_asset: "That symbol names more than one token. Pass the token's mint address instead:"
|
|
4276
|
+
};
|
|
4277
|
+
function refusalLines(result, catalogue, write = false) {
|
|
4278
|
+
const d = obj(result.data);
|
|
4279
|
+
const code = [result.error, d.error, d.refusal].map(txt).find((c) => /^[a-z][a-z0-9_]{0,63}$/.test(c)) ?? "";
|
|
4280
|
+
const rawDetail = txt(result.detail) || txt(d.detail) || txt(d.message);
|
|
4281
|
+
const detail = /^[a-z][a-z0-9_]*$/.test(rawDetail) ? "" : forTerminal(rawDetail, catalogue);
|
|
4282
|
+
const retrySafe = d.retrySafe ?? result.retrySafe;
|
|
4283
|
+
const sent = result.sent === false || retrySafe === true && result.sent !== true;
|
|
4284
|
+
const maybeSent = write && !sent;
|
|
4285
|
+
const tail = sent ? ` ${NOTHING_SENT}` : "";
|
|
4286
|
+
const out = [];
|
|
4287
|
+
const known2 = REFUSAL_TEXT[code];
|
|
4288
|
+
if (maybeSent)
|
|
4289
|
+
out.push(`Darwin couldn't confirm this order was sent${detail ? `: ${detail}` : "."}`, "It may have gone through — check `darwin orders` before trying again.");
|
|
4290
|
+
else if (known2)
|
|
4291
|
+
out.push(`${known2}${code === "ambiguous_asset" ? "" : tail}`);
|
|
4292
|
+
else if (detail)
|
|
4293
|
+
out.push(`Darwin refused this: ${detail}${tail}`);
|
|
4294
|
+
else if (code)
|
|
4295
|
+
out.push(`Darwin refused this (${code.replace(/_/g, " ")}).${tail}`);
|
|
4296
|
+
else
|
|
4297
|
+
out.push(`Darwin refused this${typeof result.status === "number" ? ` (HTTP ${result.status})` : ""}.`);
|
|
4298
|
+
if (!maybeSent && known2 && detail && code !== "ambiguous_asset" && code !== "rate_limited")
|
|
4299
|
+
out.push(`Darwin says: ${detail}`);
|
|
4300
|
+
for (const c of [...arr(result.candidates), ...arr(d.candidates)].map(obj).slice(0, 20)) {
|
|
4301
|
+
out.push(` ${label(c.symbol, 20) || "?"} ${txt(c.mint)}${label(c.issuer, 40) ? ` (${label(c.issuer, 40)})` : ""}`);
|
|
4302
|
+
}
|
|
4303
|
+
if (code && !out[0].includes(code))
|
|
4304
|
+
out.push(`(${code})`);
|
|
4305
|
+
return out;
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4308
|
+
// src/commands.ts
|
|
4309
|
+
init_realms();
|
|
4310
|
+
init_redact();
|
|
4311
|
+
|
|
4312
|
+
// src/session.ts
|
|
4313
|
+
init_context();
|
|
4314
|
+
init_config();
|
|
4315
|
+
init_realms();
|
|
4316
|
+
function realmFromEnv(ctx) {
|
|
4317
|
+
const raw = ctx.env.DARWIN_REALM;
|
|
4318
|
+
if (raw === undefined || raw === "")
|
|
4319
|
+
return "darwin.finance";
|
|
4320
|
+
const r = parseRealm(raw);
|
|
4321
|
+
if (!r)
|
|
4322
|
+
throw new CliError(EXIT.usage, "DARWIN_REALM must be prod or beta.", "usage");
|
|
4323
|
+
return r;
|
|
4324
|
+
}
|
|
4325
|
+
function profileName(ctx, flag) {
|
|
4326
|
+
const n = flag ?? ctx.env.DARWIN_PROFILE;
|
|
4327
|
+
if (n !== undefined && !PROFILE_RE.test(n))
|
|
4328
|
+
throw new CliError(EXIT.usage, "A profile name is 1–40 of a–z, 0–9, _ and -, starting with a letter or digit.", "usage");
|
|
4329
|
+
return n;
|
|
4330
|
+
}
|
|
4331
|
+
function openSession(ctx, flags) {
|
|
4332
|
+
assertInstalled(ctx);
|
|
4333
|
+
const explicit = profileName(ctx, flags.profile);
|
|
4334
|
+
if (!flags.profile) {
|
|
4335
|
+
const fromEnv = envKey(ctx);
|
|
4336
|
+
if (fromEnv !== null) {
|
|
4337
|
+
const { key: key2, kind: kind2 } = acceptKey(fromEnv);
|
|
4338
|
+
return { realm: realmFromEnv(ctx), kind: kind2, key: key2, profileName: "env", profile: null };
|
|
4339
|
+
}
|
|
4340
|
+
}
|
|
4341
|
+
const cfg = loadConfig(ctx);
|
|
4342
|
+
const name = explicit ?? cfg.default;
|
|
4343
|
+
const profile = name ? cfg.profiles[name] : undefined;
|
|
4344
|
+
if (!name || !profile) {
|
|
4345
|
+
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");
|
|
4346
|
+
}
|
|
4347
|
+
const stored = getKey(ctx, profile.store, profile.realm, name);
|
|
4348
|
+
if (!stored)
|
|
4349
|
+
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");
|
|
4350
|
+
const { key, kind } = acceptKey(stored);
|
|
4351
|
+
if (kind !== profile.kind)
|
|
4352
|
+
throw new CliError(EXIT.auth, `The key saved for "${name}" is not the kind this profile expects. Run \`darwin login\` again.`, "key_kind_mismatch");
|
|
4353
|
+
return { realm: profile.realm, kind, key, profileName: name, profile };
|
|
4354
|
+
}
|
|
4355
|
+
|
|
4356
|
+
// src/commands.ts
|
|
4357
|
+
init_agents();
|
|
4358
|
+
|
|
4359
|
+
// src/prepared.ts
|
|
4360
|
+
init_context();
|
|
4361
|
+
init_http();
|
|
4362
|
+
init_redact();
|
|
4363
|
+
init_copy();
|
|
4364
|
+
var PREPARED_ID = /^prp_[0-9A-Za-z]{24}$/;
|
|
4365
|
+
var SIG = /^[1-9A-HJ-NP-Za-km-z]{64,90}$/;
|
|
4366
|
+
function isPreparedWrite(tool) {
|
|
4367
|
+
return tool.write && tool.name !== "pause_agent";
|
|
4368
|
+
}
|
|
4369
|
+
var VERDICTS = new Set(["sent", "landed", "pending", "failed", "refused", "replayed", "uncertain", "never_sent", "not_started"]);
|
|
4370
|
+
var LAND_WAIT_MS = 8000;
|
|
4371
|
+
var POLL_MS = 2000;
|
|
4372
|
+
var obj2 = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
4373
|
+
var unwrap2 = (v) => v && typeof v === "object" && !Array.isArray(v) && ("untrusted" in v) ? v.untrusted : v;
|
|
4374
|
+
function cliLines(body, key = "lines") {
|
|
4375
|
+
const l = obj2(obj2(body).cli)[key];
|
|
4376
|
+
return Array.isArray(l) ? l.filter((x) => typeof x === "string").map((x) => clean(x).slice(0, 600)).filter(Boolean).slice(0, 12) : [];
|
|
4377
|
+
}
|
|
4378
|
+
function verdictOf(body) {
|
|
4379
|
+
const v = obj2(body).verdict;
|
|
4380
|
+
return typeof v === "string" && VERDICTS.has(v) ? v : null;
|
|
4381
|
+
}
|
|
4382
|
+
function refusalCode(body) {
|
|
4383
|
+
const r = obj2(obj2(body).result);
|
|
4384
|
+
const d = obj2(r.data);
|
|
4385
|
+
const or = obj2(obj2(obj2(body).outcome).result);
|
|
4386
|
+
const od = obj2(or.data);
|
|
4387
|
+
for (const c of [r.error, d.refusal, d.error, or.error, od.refusal, od.error, obj2(body).error]) {
|
|
4388
|
+
const s = unwrap2(c);
|
|
4389
|
+
if (typeof s === "string" && /^[a-z][a-z0-9_]{0,63}$/.test(s))
|
|
4390
|
+
return s;
|
|
4391
|
+
}
|
|
4392
|
+
return "";
|
|
4393
|
+
}
|
|
4394
|
+
function resultStatus(body) {
|
|
4395
|
+
for (const r of [obj2(obj2(body).result), obj2(obj2(obj2(body).outcome).result)])
|
|
4396
|
+
if (typeof r.status === "number")
|
|
4397
|
+
return r.status;
|
|
4398
|
+
return null;
|
|
4399
|
+
}
|
|
4400
|
+
function exitForVerdict(v, body) {
|
|
4401
|
+
switch (v) {
|
|
4402
|
+
case "sent":
|
|
4403
|
+
case "landed":
|
|
4404
|
+
case "replayed":
|
|
4405
|
+
return EXIT.ok;
|
|
4406
|
+
case "refused":
|
|
4407
|
+
case "failed":
|
|
4408
|
+
case "never_sent": {
|
|
4409
|
+
const code = refusalCode(body);
|
|
4410
|
+
if (code === "grant_paused")
|
|
4411
|
+
return EXIT.paused;
|
|
4412
|
+
if (code === "rate_limited" || resultStatus(body) === 429)
|
|
4413
|
+
return EXIT.rateLimited;
|
|
4414
|
+
return EXIT.refused;
|
|
4415
|
+
}
|
|
4416
|
+
default:
|
|
4417
|
+
return EXIT.uncertain;
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
function orderIdOf(body) {
|
|
4421
|
+
const r = obj2(obj2(body).result);
|
|
4422
|
+
const data = obj2(r.data);
|
|
4423
|
+
const summary = obj2(r.summary);
|
|
4424
|
+
const id = unwrap2(data.orderId) ?? unwrap2(summary.orderId);
|
|
4425
|
+
if (typeof id === "string" && /^[A-Za-z0-9_-]{1,80}$/.test(id))
|
|
4426
|
+
return id;
|
|
4427
|
+
const sig = unwrap2(data.txSignature) ?? unwrap2(summary.txSignature) ?? unwrap2(data.signature);
|
|
4428
|
+
if (typeof sig === "string" && SIG.test(sig))
|
|
4429
|
+
return `${sig.slice(0, 4)}…${sig.slice(-4)}`;
|
|
4430
|
+
return null;
|
|
4431
|
+
}
|
|
4432
|
+
function retryCommand(preparedId, ctxFlags = [], verb = "retry") {
|
|
4433
|
+
return ["darwin", verb, preparedId, ...ctxFlags].join(" ");
|
|
4434
|
+
}
|
|
4435
|
+
var SAFE = /^[A-Za-z0-9_-]{1,64}$/;
|
|
4436
|
+
function recoveryFlags(session, agentId) {
|
|
4437
|
+
const out = [];
|
|
4438
|
+
if (session.profile && SAFE.test(session.profileName))
|
|
4439
|
+
out.push("--profile", session.profileName);
|
|
4440
|
+
if (agentId && SAFE.test(agentId))
|
|
4441
|
+
out.push("--agent", agentId);
|
|
4442
|
+
return out;
|
|
4443
|
+
}
|
|
4444
|
+
async function post(o, path, body, timeoutMs) {
|
|
4445
|
+
return request(o.ctx, o.session.realm, "POST", path, { key: o.session.key, agent: o.agentId ?? undefined, body, timeoutMs });
|
|
4446
|
+
}
|
|
4447
|
+
function plainRefusal(res, catalogue, command, realm) {
|
|
4448
|
+
const err = typeof obj2(res.json).error === "string" ? String(obj2(res.json).error) : "";
|
|
4449
|
+
if (res.status === 401)
|
|
4450
|
+
return { exit: EXIT.auth, message: "Darwin refused this API key (or the agent it named). It may have been revoked or the agent paused; run `darwin whoami`, or ask the owner. Nothing was done." };
|
|
4451
|
+
if (res.status === 426) {
|
|
4452
|
+
const min = typeof obj2(res.json).minCli === "string" && /^\d+\.\d+\.\d+$/.test(String(obj2(res.json).minCli)) ? String(obj2(res.json).minCli) : catalogue.minCli;
|
|
4453
|
+
return { exit: EXIT.upgrade, message: copy.updateRequired(min) };
|
|
4454
|
+
}
|
|
4455
|
+
if (res.status === 429)
|
|
4456
|
+
return { exit: EXIT.rateLimited, message: "Too many requests with this API key right now. Wait a minute and try again. Nothing was done." };
|
|
4457
|
+
if (res.status === 404 && err === "cli_unavailable")
|
|
4458
|
+
return { exit: EXIT.refused, message: "The Darwin CLI isn't available on this site yet. Nothing was done." };
|
|
4459
|
+
if (res.status === 404 && err === "unknown_tool")
|
|
4460
|
+
return { exit: EXIT.usage, message: `\`${command}\` isn't available on ${realm} right now. Nothing was done. Run \`darwin help\` for the current list.` };
|
|
4461
|
+
if (res.status === 400 && err === "invalid_request")
|
|
4462
|
+
return { exit: EXIT.usage, message: "Darwin couldn't read that request. Nothing was done." };
|
|
4463
|
+
return null;
|
|
4464
|
+
}
|
|
4465
|
+
async function runPrepared(o) {
|
|
4466
|
+
const { tool } = o;
|
|
4467
|
+
const path = `/api/agent/v1/tools/call/${encodeURIComponent(tool.name)}/prepare`;
|
|
4468
|
+
let res;
|
|
4469
|
+
try {
|
|
4470
|
+
res = await post(o, path, { arguments: o.args, ...o.dryRun ? { dryRun: true } : {} }, 30000);
|
|
4471
|
+
} catch (e) {
|
|
4472
|
+
if (!(e instanceof NetworkError))
|
|
4473
|
+
throw e;
|
|
4474
|
+
return done(o, EXIT.network, null, { error: "network_error", detail: `${e.message} Nothing was sent; try again.`, sent: false }, [`${clean(e.message)} Nothing was sent; try again.`]);
|
|
4475
|
+
}
|
|
4476
|
+
const pj = obj2(res.json);
|
|
4477
|
+
if (res.status === 404 && pj.error !== "unknown_tool" && pj.error !== "cli_unavailable") {
|
|
4478
|
+
if (!o.dryRun)
|
|
4479
|
+
return { kind: "fallback" };
|
|
4480
|
+
const line = `Darwin can't check orders ahead of time on ${o.session.realm} yet, so nothing was checked or sent.`;
|
|
4481
|
+
return done(o, EXIT.ok, null, { dryRun: true, sent: false, checked: false, detail: line }, [line]);
|
|
4482
|
+
}
|
|
4483
|
+
const plain = plainRefusal(res, o.catalogue, `darwin ${tool.cli.path.join(" ")}`, o.session.realm);
|
|
4484
|
+
if (plain)
|
|
4485
|
+
return done(o, plain.exit, null, { error: pj.error ?? "refused", detail: plain.message, sent: false, status: res.status }, [plain.message]);
|
|
4486
|
+
const result = obj2(pj.result);
|
|
4487
|
+
if (res.status !== 200 || typeof pj.isError !== "boolean") {
|
|
4488
|
+
return done(o, EXIT.unexpected, null, { error: "bad_response", status: res.status, sent: false }, [`Darwin answered HTTP ${res.status}. Nothing was sent.`]);
|
|
4489
|
+
}
|
|
4490
|
+
if (pj.isError) {
|
|
4491
|
+
const code = refusalCode(pj);
|
|
4492
|
+
const exit = code === "grant_paused" ? EXIT.paused : code === "invalid_arguments" || code === "nonce_not_accepted" ? EXIT.usage : code === "rate_limited" ? EXIT.rateLimited : EXIT.refused;
|
|
4493
|
+
return done(o, exit, null, { ...pj }, cliLines(pj).length ? cliLines(pj) : [`Darwin refused this${code ? ` (${code})` : ""}. Nothing was sent.`]);
|
|
4494
|
+
}
|
|
4495
|
+
if (o.dryRun) {
|
|
4496
|
+
const lines = cliLines(pj);
|
|
4497
|
+
if (tool.costsTx)
|
|
4498
|
+
lines.push("Running it for real counts against today's transaction budget.");
|
|
4499
|
+
return done(o, EXIT.ok, null, { ...pj }, lines.length ? lines : ["Dry run: Darwin checked it, and nothing was sent."]);
|
|
4500
|
+
}
|
|
4501
|
+
const preparedId = typeof result.preparedId === "string" && PREPARED_ID.test(result.preparedId) ? result.preparedId : null;
|
|
4502
|
+
if (!preparedId)
|
|
4503
|
+
return done(o, EXIT.unexpected, null, { error: "bad_response", sent: false }, ["Darwin's answer didn't name the prepared order. Nothing was sent."]);
|
|
4504
|
+
return execute(o, preparedId, pj);
|
|
4505
|
+
}
|
|
4506
|
+
async function execute(o, preparedId, prepareBody) {
|
|
4507
|
+
let res = null;
|
|
4508
|
+
let lostAnswer = false;
|
|
4509
|
+
try {
|
|
4510
|
+
res = await post(o, "/api/agent/v1/tools/execute", { preparedId }, 70000);
|
|
4511
|
+
} catch (e) {
|
|
4512
|
+
if (e instanceof CliError && e.code === "redirect_refused") {
|
|
4513
|
+
lostAnswer = true;
|
|
4514
|
+
} else if (!(e instanceof NetworkError)) {
|
|
4515
|
+
throw e;
|
|
4516
|
+
} else if (!e.maybeSent) {
|
|
4517
|
+
return done(o, EXIT.network, preparedId, { error: "network_error", preparedId, sent: false, detail: `${e.message} Nothing was sent.` }, [`${clean(e.message)} Nothing was sent. (It can't run later by itself; \`${retryCommand(preparedId, o.recovery, "cancel")}\` makes sure.)`]);
|
|
4518
|
+
} else {
|
|
4519
|
+
lostAnswer = true;
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
const ej = obj2(res?.json);
|
|
4523
|
+
if (res && !lostAnswer) {
|
|
4524
|
+
if (res.status === 409 && ej.executed === false)
|
|
4525
|
+
return finish(o, preparedId, prepareBody, ej, null);
|
|
4526
|
+
if (res.status === 401) {
|
|
4527
|
+
return done(o, EXIT.auth, preparedId, { error: "unauthorized", preparedId, sent: false }, ["Darwin refused this API key (or the agent it named), so the order was not sent. Run `darwin whoami`, or ask the owner."]);
|
|
4528
|
+
}
|
|
4529
|
+
const plain = plainRefusal(res, o.catalogue, `darwin ${o.tool.cli.path.join(" ")}`, o.session.realm);
|
|
4530
|
+
if (plain)
|
|
4531
|
+
return done(o, plain.exit, preparedId, { error: ej.error ?? "refused", preparedId, sent: false, status: res.status }, [plain.message.replace("Nothing was done.", "The order was not sent.")]);
|
|
4532
|
+
if (res.status === 404) {
|
|
4533
|
+
return done(o, EXIT.refused, preparedId, { error: ej.error ?? "not_found", preparedId, sent: false, status: 404 }, ["Darwin couldn't find this checked order to send, so nothing was sent. Run the command again."]);
|
|
4534
|
+
}
|
|
4535
|
+
if (res.status === 200 && verdictOf(ej)) {
|
|
4536
|
+
const v = verdictOf(ej);
|
|
4537
|
+
if (v !== "uncertain")
|
|
4538
|
+
return finish(o, preparedId, prepareBody, ej, null);
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
const lines = res && verdictOf(ej) === "uncertain" ? cliLines(ej) : [];
|
|
4542
|
+
const status = await pollStatus(o, preparedId, (v) => v !== "pending" && v !== "uncertain" && v !== "not_started", LAND_WAIT_MS);
|
|
4543
|
+
return finish(o, preparedId, prepareBody, Object.keys(ej).length ? ej : { preparedId, state: "unknown", verdict: "uncertain" }, status, lines);
|
|
4544
|
+
}
|
|
4545
|
+
async function pollStatus(o, preparedId, stop, budgetMs) {
|
|
4546
|
+
if (o.quietForMcp)
|
|
4547
|
+
budgetMs = Math.min(budgetMs, 4000);
|
|
4548
|
+
const deadline = o.ctx.now() + budgetMs;
|
|
4549
|
+
let last = null;
|
|
4550
|
+
while (o.ctx.now() + POLL_MS <= deadline) {
|
|
4551
|
+
await o.ctx.sleep(POLL_MS);
|
|
4552
|
+
try {
|
|
4553
|
+
const r = await post(o, "/api/agent/v1/tools/status", { preparedId }, Math.max(1000, Math.min(5000, deadline - o.ctx.now())));
|
|
4554
|
+
if (r.status === 200 && verdictOf(r.json)) {
|
|
4555
|
+
last = obj2(r.json);
|
|
4556
|
+
if (stop(verdictOf(last)))
|
|
4557
|
+
return last;
|
|
4558
|
+
}
|
|
4559
|
+
} catch {}
|
|
4560
|
+
}
|
|
4561
|
+
return last;
|
|
4562
|
+
}
|
|
4563
|
+
async function finish(o, preparedId, prepareBody, executeBody, status, firstLines = []) {
|
|
4564
|
+
let st = status;
|
|
4565
|
+
let v = verdictOf(executeBody) ?? "uncertain";
|
|
4566
|
+
const lines = [...firstLines.length ? firstLines : cliLines(executeBody)];
|
|
4567
|
+
if (!st && v === "sent")
|
|
4568
|
+
st = await pollStatus(o, preparedId, (x) => x === "landed" || x === "failed" || x === "refused", LAND_WAIT_MS);
|
|
4569
|
+
const sv = verdictOf(st);
|
|
4570
|
+
if (st && sv) {
|
|
4571
|
+
const stillWaiting = v === "sent" && (sv === "sent" || sv === "pending");
|
|
4572
|
+
if (!stillWaiting) {
|
|
4573
|
+
for (const l of cliLines(st))
|
|
4574
|
+
if (!lines.includes(l))
|
|
4575
|
+
lines.push(l);
|
|
4576
|
+
v = sv;
|
|
4577
|
+
}
|
|
4578
|
+
}
|
|
4579
|
+
const exit = exitForVerdict(v, st ?? executeBody);
|
|
4580
|
+
if (exit === EXIT.uncertain && !lines.some((l) => l.startsWith("Darwin may have received")))
|
|
4581
|
+
lines.unshift(copy.uncertainWritePrepared);
|
|
4582
|
+
const orderId = orderIdOf(executeBody) ?? orderIdOf(st);
|
|
4583
|
+
if (orderId && (v === "sent" || v === "landed" || v === "replayed"))
|
|
4584
|
+
lines.push(`Order ID: ${orderId}`);
|
|
4585
|
+
if (v === "sent")
|
|
4586
|
+
lines.push(`Not confirmed yet. Check it with: ${retryCommand(preparedId, o.recovery)}`);
|
|
4587
|
+
if (exit === EXIT.uncertain)
|
|
4588
|
+
lines.push(`Don't run the command again. Check it with: ${retryCommand(preparedId, o.recovery)}`);
|
|
4589
|
+
const doc = {
|
|
4590
|
+
preparedId,
|
|
4591
|
+
tool: o.tool.name,
|
|
4592
|
+
verdict: v ?? "uncertain",
|
|
4593
|
+
prepare: obj2(prepareBody.result),
|
|
4594
|
+
execute: executeBody,
|
|
4595
|
+
...st && st !== executeBody ? { status: st } : {},
|
|
4596
|
+
...exit === EXIT.uncertain || v === "sent" ? { next: { command: retryCommand(preparedId, o.recovery) } } : {}
|
|
4597
|
+
};
|
|
4598
|
+
return done(o, exit, preparedId, doc, lines);
|
|
4599
|
+
}
|
|
4600
|
+
function done(o, exit, preparedId, doc, lines, toStderr = exit !== EXIT.ok) {
|
|
4601
|
+
if (!o.quietForMcp) {
|
|
4602
|
+
if (o.json) {
|
|
4603
|
+
printJson(o.ctx, doc);
|
|
4604
|
+
if (exit !== EXIT.ok)
|
|
4605
|
+
for (const l of lines)
|
|
4606
|
+
warn(o.ctx, l);
|
|
4607
|
+
} else {
|
|
4608
|
+
for (const l of lines)
|
|
4609
|
+
(toStderr ? warn : say)(o.ctx, l);
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
return { kind: "done", exit, preparedId, doc: { ...doc, lines } };
|
|
4613
|
+
}
|
|
4614
|
+
async function checkPrepared(o) {
|
|
4615
|
+
const { ctx, preparedId } = o;
|
|
4616
|
+
const flags = recoveryFlags(o.session, o.agentId);
|
|
4617
|
+
const again = retryCommand(preparedId, flags);
|
|
4618
|
+
let res;
|
|
4619
|
+
try {
|
|
4620
|
+
res = await post(o, o.cancel ? "/api/agent/v1/tools/cancel" : "/api/agent/v1/tools/status", { preparedId }, 20000);
|
|
4621
|
+
} catch (e) {
|
|
4622
|
+
if (e instanceof CliError && e.code === "redirect_refused") {
|
|
4623
|
+
const msg2 = `${clean(e.message)} ${o.cancel ? "Whether it was cancelled is unknown" : "Its outcome is still unknown"}; check again in a moment with \`${again}\`.`;
|
|
4624
|
+
if (o.json)
|
|
4625
|
+
printJson(ctx, { error: "redirect_refused", preparedId, detail: msg2 });
|
|
4626
|
+
warn(ctx, msg2);
|
|
4627
|
+
return EXIT.uncertain;
|
|
4628
|
+
}
|
|
4629
|
+
if (!(e instanceof NetworkError))
|
|
4630
|
+
throw e;
|
|
4631
|
+
const reached = e.maybeSent;
|
|
4632
|
+
const msg = o.cancel && !reached ? `${clean(e.message)} The cancel didn't reach Darwin; try again.` : `${clean(e.message)} ${o.cancel ? "Whether it was cancelled is unknown" : "Its outcome is still unknown"}; check again in a moment with \`${again}\`.`;
|
|
4633
|
+
if (o.json)
|
|
4634
|
+
printJson(ctx, { error: "network_error", preparedId, detail: msg });
|
|
4635
|
+
warn(ctx, msg);
|
|
4636
|
+
return o.cancel && !reached ? EXIT.network : EXIT.uncertain;
|
|
4637
|
+
}
|
|
4638
|
+
const j = obj2(res.json);
|
|
4639
|
+
const err = typeof j.error === "string" ? j.error : "";
|
|
4640
|
+
if (res.status === 401) {
|
|
4641
|
+
const msg = "Can't check this order with this API key and agent right now. Its outcome is still unknown, not failed. A paused agent is one possible cause. Check the agent's History on Darwin.";
|
|
4642
|
+
if (o.json)
|
|
4643
|
+
printJson(ctx, { error: "unauthorized", preparedId, detail: msg });
|
|
4644
|
+
warn(ctx, msg);
|
|
4645
|
+
return EXIT.uncertain;
|
|
4646
|
+
}
|
|
4647
|
+
if (res.status === 404 && (err === "not_found" || err === "prepared_unavailable" || err === "cli_unavailable")) {
|
|
4648
|
+
const msg = err === "not_found" ? "Darwin has no order with that ID for this API key and agent. (Use the ID this terminal printed, with the same profile and --agent.)" : `This site doesn't keep checked orders yet, so there's nothing to look up. Check \`darwin orders\` instead.`;
|
|
4649
|
+
if (o.json)
|
|
4650
|
+
printJson(ctx, { error: err, preparedId, detail: msg });
|
|
4651
|
+
warn(ctx, msg);
|
|
4652
|
+
return EXIT.refused;
|
|
4653
|
+
}
|
|
4654
|
+
if (res.status === 426) {
|
|
4655
|
+
const min = typeof j.minCli === "string" && /^\d+\.\d+\.\d+$/.test(j.minCli) ? j.minCli : "the latest version";
|
|
4656
|
+
const msg = copy.updateRequired(min);
|
|
4657
|
+
if (o.json)
|
|
4658
|
+
printJson(ctx, { error: "cli_upgrade_required", status: 426, preparedId, minCli: min, detail: msg });
|
|
4659
|
+
warn(ctx, msg);
|
|
4660
|
+
return EXIT.upgrade;
|
|
4661
|
+
}
|
|
4662
|
+
if (res.status === 429) {
|
|
4663
|
+
const msg = "Too many requests with this API key right now. Wait a minute and try again.";
|
|
4664
|
+
if (o.json)
|
|
4665
|
+
printJson(ctx, { error: "rate_limited", status: 429, preparedId, detail: msg });
|
|
4666
|
+
warn(ctx, msg);
|
|
4667
|
+
return EXIT.rateLimited;
|
|
4668
|
+
}
|
|
4669
|
+
const v = verdictOf(j);
|
|
4670
|
+
if (res.status !== 200 || !v) {
|
|
4671
|
+
if (o.json)
|
|
4672
|
+
printJson(ctx, { error: "bad_response", status: res.status, preparedId });
|
|
4673
|
+
warn(ctx, `Darwin answered HTTP ${res.status}. The order's outcome is still unknown; try again in a moment.`);
|
|
4674
|
+
return EXIT.uncertain;
|
|
4675
|
+
}
|
|
4676
|
+
const lines = cliLines(j);
|
|
4677
|
+
if (o.cancel && j.cancelled === false)
|
|
4678
|
+
lines.push("It had already started, so it couldn't be cancelled.");
|
|
4679
|
+
const exit = o.cancel ? j.cancelled === true ? EXIT.ok : exitForVerdict(v, j) : exitForVerdict(v, j);
|
|
4680
|
+
const orderId = orderIdOf(obj2(j.outcome));
|
|
4681
|
+
if (orderId && (v === "landed" || v === "sent"))
|
|
4682
|
+
lines.push(`Order ID: ${orderId}`);
|
|
4683
|
+
if (v === "pending" || v === "uncertain")
|
|
4684
|
+
lines.push(`Don't place it again. Check again in a minute: ${again}`);
|
|
4685
|
+
if (v === "not_started" && !o.cancel)
|
|
4686
|
+
lines.push(`To make sure it never runs: ${retryCommand(preparedId, flags, "cancel")}`);
|
|
4687
|
+
if (o.json)
|
|
4688
|
+
printJson(ctx, j);
|
|
4689
|
+
else
|
|
4690
|
+
for (const l of lines)
|
|
4691
|
+
say(ctx, l);
|
|
4692
|
+
return exit;
|
|
4693
|
+
}
|
|
4694
|
+
async function preparedRead(o, preparedId, cancel) {
|
|
4695
|
+
if (!PREPARED_ID.test(preparedId))
|
|
4696
|
+
return { ok: false, body: { error: "invalid_arguments", detail: "preparedId is the prp_… id a write returned." } };
|
|
4697
|
+
try {
|
|
4698
|
+
const r = await post(o, cancel ? "/api/agent/v1/tools/cancel" : "/api/agent/v1/tools/status", { preparedId }, 20000);
|
|
4699
|
+
const j = obj2(r.json);
|
|
4700
|
+
if (r.status === 200 && verdictOf(j))
|
|
4701
|
+
return { ok: true, body: j };
|
|
4702
|
+
if (r.status === 401)
|
|
4703
|
+
return { ok: false, body: { error: "unauthorized", preparedId, outcome: "unknown", detail: "Can't check this order with this API key and agent right now. Its outcome is still unknown, not failed. A paused agent is one possible cause." } };
|
|
4704
|
+
return { ok: false, body: { error: typeof j.error === "string" ? j.error : "bad_response", status: r.status, preparedId, detail: typeof j.detail === "string" ? j.detail : "Darwin couldn't answer that just now." } };
|
|
4705
|
+
} catch (e) {
|
|
4706
|
+
if (!(e instanceof NetworkError))
|
|
4707
|
+
throw e;
|
|
4708
|
+
return { ok: false, body: { error: "network_error", preparedId, outcome: "unknown", detail: `${e.message} Its outcome is still unknown.` } };
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4712
|
+
// src/commands.ts
|
|
4713
|
+
var OUT_FLAGS = ["json", "format", "quiet", "no-color", "help"];
|
|
4714
|
+
var jsonOut = (ctx, p) => wantsJson(ctx, { json: has(p, "json"), format: one(p, "format") });
|
|
4715
|
+
var unwrap3 = (v) => v && typeof v === "object" && ("untrusted" in v) ? String(v.untrusted ?? "") : typeof v === "string" ? v : "";
|
|
4716
|
+
async function cmdLogout(ctx, p) {
|
|
4717
|
+
onlyFlags(p, ["profile", "all", "revoke", "force-local", ...OUT_FLAGS], "logout");
|
|
4718
|
+
assertInstalled(ctx);
|
|
4719
|
+
if (has(p, "all")) {
|
|
4720
|
+
if (has(p, "revoke"))
|
|
4721
|
+
throw new CliError(EXIT.usage, "--revoke works on one profile at a time.", "usage");
|
|
4722
|
+
let failed = 0;
|
|
4723
|
+
for (const [name2, prof2] of Object.entries(loadConfig(ctx).profiles))
|
|
4724
|
+
failed += forgetProfile(ctx, name2, prof2) ? 0 : 1;
|
|
4725
|
+
return failed ? EXIT.unexpected : EXIT.ok;
|
|
4726
|
+
}
|
|
4727
|
+
const cfg = loadConfig(ctx);
|
|
4728
|
+
const name = profileName(ctx, one(p, "profile")) ?? cfg.default;
|
|
4729
|
+
const prof = name ? cfg.profiles[name] : undefined;
|
|
4730
|
+
if (!name || !prof)
|
|
4731
|
+
throw new CliError(EXIT.usage, "No profile to log out of. `darwin profile list` shows them.", "no_profile");
|
|
4732
|
+
const url = manageUrl(prof.realm, prof.agent_id);
|
|
4733
|
+
if (!has(p, "revoke")) {
|
|
4734
|
+
let key = null;
|
|
4735
|
+
try {
|
|
4736
|
+
key = getKey(ctx, prof.store, prof.realm, name);
|
|
4737
|
+
} catch {
|
|
4738
|
+
key = null;
|
|
4739
|
+
}
|
|
4740
|
+
return forgetProfile(ctx, name, prof, { expectedKey: key }) ? EXIT.ok : EXIT.unexpected;
|
|
4741
|
+
}
|
|
4742
|
+
const s = openSession(ctx, { profile: name });
|
|
4743
|
+
if (s.kind === "agents")
|
|
4744
|
+
warn(ctx, copy.revokeAllWarning);
|
|
4745
|
+
let keyName = "this API key";
|
|
4746
|
+
try {
|
|
4747
|
+
const w = await request(ctx, s.realm, "GET", "/api/agent/v1/tools/whoami", { key: s.key, timeoutMs: 15000 });
|
|
4748
|
+
const n = w.json?.name;
|
|
4749
|
+
if (w.status === 200 && typeof n === "string" && n)
|
|
4750
|
+
keyName = clean(n).slice(0, 120);
|
|
4751
|
+
} catch {}
|
|
4752
|
+
let confirmed = false;
|
|
4753
|
+
let networkFailed = false;
|
|
4754
|
+
try {
|
|
4755
|
+
const res = await request(ctx, s.realm, "POST", "/api/agent/v1/key/revoke", { key: s.key, body: {}, timeoutMs: 20000 });
|
|
4756
|
+
const j = res.json;
|
|
4757
|
+
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;
|
|
4758
|
+
} catch (e) {
|
|
4759
|
+
if (!(e instanceof NetworkError) && !(e instanceof CliError))
|
|
4760
|
+
throw e;
|
|
4761
|
+
networkFailed = e instanceof NetworkError;
|
|
4762
|
+
}
|
|
4763
|
+
const agent = prof.agent_name || prof.agent_id;
|
|
4764
|
+
if (confirmed) {
|
|
4765
|
+
if (!forgetProfile(ctx, name, prof, { expectedKey: s.key, quiet: true }))
|
|
4766
|
+
return EXIT.unexpected;
|
|
4767
|
+
say(ctx, copy.revoked(keyName, agent));
|
|
4768
|
+
return EXIT.ok;
|
|
4769
|
+
}
|
|
4770
|
+
if (networkFailed && has(p, "force-local")) {
|
|
4771
|
+
forgetProfile(ctx, name, prof, { expectedKey: s.key, quiet: true });
|
|
4772
|
+
warn(ctx, copy.revokeNotConfirmed(agent, url).replace(" and it's still saved here", ""));
|
|
4773
|
+
return EXIT.refused;
|
|
4774
|
+
}
|
|
4775
|
+
warn(ctx, copy.revokeNotConfirmed(agent, url));
|
|
4776
|
+
return EXIT.refused;
|
|
4777
|
+
}
|
|
4778
|
+
function forgetProfile(ctx, name, prof, opts = {}) {
|
|
4779
|
+
const outcome = updateConfig(ctx, (c) => {
|
|
4780
|
+
const now = c.profiles[name];
|
|
4781
|
+
if (!now || now.realm !== prof.realm || now.store !== prof.store || now.agent_id !== prof.agent_id)
|
|
4782
|
+
return "changed";
|
|
4783
|
+
if (opts.expectedKey !== undefined) {
|
|
4784
|
+
let current;
|
|
4785
|
+
try {
|
|
4786
|
+
current = getKey(ctx, now.store, now.realm, name);
|
|
4787
|
+
} catch {
|
|
4788
|
+
return "unreadable";
|
|
4789
|
+
}
|
|
4790
|
+
if (current !== null && current !== opts.expectedKey)
|
|
4791
|
+
return "changed";
|
|
4792
|
+
}
|
|
4793
|
+
if (!deleteKey(ctx, now.store, now.realm, name))
|
|
4794
|
+
return "stuck";
|
|
4795
|
+
delete c.profiles[name];
|
|
4796
|
+
if (c.default === name)
|
|
4797
|
+
c.default = Object.keys(c.profiles)[0] ?? "";
|
|
4798
|
+
return "ok";
|
|
4799
|
+
});
|
|
4800
|
+
if (outcome === "changed") {
|
|
4801
|
+
warn(ctx, `Profile "${name}" changed while this ran (another login?), so it was left as it is.`);
|
|
4802
|
+
return false;
|
|
4803
|
+
}
|
|
4804
|
+
if (outcome !== "ok") {
|
|
4805
|
+
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.`);
|
|
4806
|
+
return false;
|
|
4807
|
+
}
|
|
4808
|
+
if (!opts.quiet)
|
|
4809
|
+
say(ctx, copy.loggedOut(name, manageUrl(prof.realm, prof.agent_id)));
|
|
4810
|
+
return true;
|
|
4811
|
+
}
|
|
4812
|
+
async function cmdWhoami(ctx, p) {
|
|
4813
|
+
onlyFlags(p, ["profile", ...OUT_FLAGS], "whoami");
|
|
4814
|
+
const s = openSession(ctx, { profile: one(p, "profile") });
|
|
4815
|
+
let who = null;
|
|
4816
|
+
let note = null;
|
|
4817
|
+
let exit = EXIT.ok;
|
|
4818
|
+
try {
|
|
4819
|
+
const res = await request(ctx, s.realm, "GET", "/api/agent/v1/tools/whoami", { key: s.key, timeoutMs: 15000 });
|
|
4820
|
+
if (res.status === 200 && res.json && typeof res.json === "object")
|
|
4821
|
+
who = res.json;
|
|
4822
|
+
else if (res.status === 401) {
|
|
4823
|
+
note = "Darwin refused this API key — it may have been revoked.";
|
|
4824
|
+
exit = EXIT.auth;
|
|
4825
|
+
} else if (res.status === 429) {
|
|
4826
|
+
note = "Too many requests with this API key right now.";
|
|
4827
|
+
exit = EXIT.rateLimited;
|
|
4828
|
+
} else if (res.status === 426) {
|
|
4829
|
+
note = copy.updateRequired(typeof res.json?.minCli === "string" ? String(res.json.minCli) : "the latest version");
|
|
4830
|
+
exit = EXIT.upgrade;
|
|
4831
|
+
} else if (res.status === 404) {} else if (res.status === 403) {
|
|
4832
|
+
note = "Darwin refused this request.";
|
|
4833
|
+
exit = EXIT.refused;
|
|
4834
|
+
} else {
|
|
4835
|
+
note = `Darwin answered HTTP ${res.status}.`;
|
|
4836
|
+
exit = EXIT.unexpected;
|
|
4837
|
+
}
|
|
4838
|
+
} catch (e) {
|
|
4839
|
+
if (!(e instanceof NetworkError))
|
|
4840
|
+
throw e;
|
|
4841
|
+
note = e.message;
|
|
4842
|
+
exit = EXIT.network;
|
|
4843
|
+
}
|
|
4844
|
+
const keyName = who && typeof who.name === "string" ? clean(who.name) : "name unavailable";
|
|
4845
|
+
const agent = clean(unwrap3(who?.homeAgentName) || s.profile?.agent_name || s.profile?.agent_id || "unknown agent");
|
|
3561
4846
|
const permissions = s.kind === "agents" ? "Read: all agents · Trade: all active agents" : "Read: this agent · Trade: this agent";
|
|
3562
4847
|
const store = s.profile ? storeLabel(ctx, s.profile.store, s.realm, s.profileName) : "DARWIN_API_KEY (environment)";
|
|
3563
4848
|
if (jsonOut(ctx, p))
|
|
@@ -3570,6 +4855,18 @@ async function cmdWhoami(ctx, p) {
|
|
|
3570
4855
|
warn(ctx, `Reminder: this API key is in a plain file (${storeLabel(ctx, "file", s.realm, s.profileName).slice(8, -1)}).`);
|
|
3571
4856
|
return exit;
|
|
3572
4857
|
}
|
|
4858
|
+
var DOCTOR_LABEL = {
|
|
4859
|
+
version: "Darwin CLI",
|
|
4860
|
+
node: "Node",
|
|
4861
|
+
platform: "Platform",
|
|
4862
|
+
install: "Install",
|
|
4863
|
+
secretStore: "Secret store",
|
|
4864
|
+
config: "Config",
|
|
4865
|
+
profiles: "Profiles",
|
|
4866
|
+
defaultProfile: "Default profile",
|
|
4867
|
+
key: "API key",
|
|
4868
|
+
catalogue: "Command list"
|
|
4869
|
+
};
|
|
3573
4870
|
async function cmdDoctor(ctx, p) {
|
|
3574
4871
|
onlyFlags(p, ["profile", ...OUT_FLAGS], "doctor");
|
|
3575
4872
|
const checks = { version: VERSION, node: process.version, platform: `${ctx.platform}-${process.arch}` };
|
|
@@ -3613,9 +4910,11 @@ async function cmdDoctor(ctx, p) {
|
|
|
3613
4910
|
}
|
|
3614
4911
|
if (jsonOut(ctx, p))
|
|
3615
4912
|
printJson(ctx, checks);
|
|
3616
|
-
else
|
|
3617
|
-
|
|
3618
|
-
|
|
4913
|
+
else {
|
|
4914
|
+
const width = Math.max(...Object.keys(checks).map((k) => (DOCTOR_LABEL[k] ?? k).length));
|
|
4915
|
+
for (const [k, v] of Object.entries(checks))
|
|
4916
|
+
say(ctx, `${(DOCTOR_LABEL[k] ?? k).padEnd(width)} ${v === null ? "none" : String(v)}`);
|
|
4917
|
+
}
|
|
3619
4918
|
return EXIT.ok;
|
|
3620
4919
|
}
|
|
3621
4920
|
async function cmdProfile(ctx, p) {
|
|
@@ -3635,9 +4934,18 @@ async function cmdProfile(ctx, p) {
|
|
|
3635
4934
|
printJson(ctx, { profiles: rows });
|
|
3636
4935
|
else if (rows.length === 0)
|
|
3637
4936
|
say(ctx, "No profiles yet. Run `darwin login`.");
|
|
3638
|
-
else
|
|
3639
|
-
|
|
4937
|
+
else {
|
|
4938
|
+
const table2 = columns(["Profile", "Site", "Agent", "API key for", "Stored in"], rows.map((r) => [
|
|
4939
|
+
`${r.default ? "* " : " "}${r.profile}`,
|
|
4940
|
+
r.realm,
|
|
4941
|
+
clean(r.agent).slice(0, 40),
|
|
4942
|
+
r.kind === "agents" ? `all agents${r.defaultAgent ? " (default set)" : ""}` : "this agent",
|
|
4943
|
+
r.store === "keychain" ? ctx.keychain.description : "a plain file"
|
|
4944
|
+
]));
|
|
4945
|
+
for (const l of table2)
|
|
3640
4946
|
say(ctx, l);
|
|
4947
|
+
say(ctx, "* = the profile commands use. Change it: darwin profile use <name>");
|
|
4948
|
+
}
|
|
3641
4949
|
return EXIT.ok;
|
|
3642
4950
|
}
|
|
3643
4951
|
case "use":
|
|
@@ -3694,9 +5002,9 @@ async function cmdProfile(ctx, p) {
|
|
|
3694
5002
|
throw new CliError(EXIT.usage, "Only a profile with an API key for all your agents has a default agent.", "usage");
|
|
3695
5003
|
if (!a)
|
|
3696
5004
|
throw new CliError(EXIT.usage, "Name the agent: `darwin profile set-agent <name or id>`.", "usage");
|
|
3697
|
-
const
|
|
3698
|
-
const hit =
|
|
3699
|
-
const m =
|
|
5005
|
+
const agents2 = await listAgents(ctx, s, { fresh: true });
|
|
5006
|
+
const hit = agents2.find((x) => x.id === a) ?? (() => {
|
|
5007
|
+
const m = agents2.filter((x) => x.name.toLowerCase() === a.toLowerCase());
|
|
3700
5008
|
if (m.length > 1)
|
|
3701
5009
|
throw new CliError(EXIT.usage, `More than one agent is called that; use its id: ${m.map((x) => x.id).join(", ")}`, "agent_ambiguous");
|
|
3702
5010
|
return m[0];
|
|
@@ -3737,16 +5045,28 @@ async function cmdApi(ctx, p) {
|
|
|
3737
5045
|
if (jsonOut(ctx, p))
|
|
3738
5046
|
printJson(ctx, body);
|
|
3739
5047
|
else
|
|
3740
|
-
for (const l of
|
|
5048
|
+
for (const l of genericLines(body))
|
|
3741
5049
|
say(ctx, l);
|
|
3742
5050
|
return res.status === 200 ? EXIT.ok : res.status === 401 ? EXIT.auth : res.status === 429 ? EXIT.rateLimited : EXIT.refused;
|
|
3743
5051
|
}
|
|
5052
|
+
async function cmdCheckPrepared(ctx, p, cancel) {
|
|
5053
|
+
const name = cancel ? "cancel" : "retry";
|
|
5054
|
+
onlyFlags(p, ["profile", "agent", ...OUT_FLAGS], name);
|
|
5055
|
+
const id = p.positionals[1];
|
|
5056
|
+
if (!id || p.positionals.length > 2 || !PREPARED_ID.test(id)) {
|
|
5057
|
+
throw new CliError(EXIT.usage, `\`darwin ${name}\` takes the ID the order printed: \`darwin ${name} prp_…\`.`, "usage");
|
|
5058
|
+
}
|
|
5059
|
+
const s = openSession(ctx, { profile: one(p, "profile") });
|
|
5060
|
+
const { recoveryAgent: recoveryAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
5061
|
+
const agentId = await recoveryAgent2(ctx, s, one(p, "agent"));
|
|
5062
|
+
return checkPrepared({ ctx, session: s, agentId, json: jsonOut(ctx, p), preparedId: id, cancel });
|
|
5063
|
+
}
|
|
3744
5064
|
|
|
3745
5065
|
// src/login.ts
|
|
3746
5066
|
init_context();
|
|
3747
5067
|
import { linkSync, lstatSync as lstatSync3, readFileSync, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "node:fs";
|
|
3748
5068
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
3749
|
-
import { join as join7 } from "node:path";
|
|
5069
|
+
import { join as join7, sep } from "node:path";
|
|
3750
5070
|
init_copy();
|
|
3751
5071
|
init_config();
|
|
3752
5072
|
init_http();
|
|
@@ -4160,7 +5480,7 @@ function chooseStore(ctx, p) {
|
|
|
4160
5480
|
if (s === "file") {
|
|
4161
5481
|
if (!probeFileStore(ctx))
|
|
4162
5482
|
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"
|
|
5483
|
+
warn(ctx, copy.fileStoreWarning(`${join7(ctx.configDir, "credentials")}${sep}…`));
|
|
4164
5484
|
return "file";
|
|
4165
5485
|
}
|
|
4166
5486
|
if (!probeKeychain(ctx))
|
|
@@ -4272,7 +5592,7 @@ function readKeyFile(path) {
|
|
|
4272
5592
|
rememberSecret(j.key);
|
|
4273
5593
|
return { realm, agentId: j.agent_id, key: j.key };
|
|
4274
5594
|
}
|
|
4275
|
-
var
|
|
5595
|
+
var unwrap4 = (v) => v && typeof v === "object" && ("untrusted" in v) ? String(v.untrusted ?? "") : typeof v === "string" ? v : "";
|
|
4276
5596
|
async function identify(ctx, realm, key, kind) {
|
|
4277
5597
|
let res;
|
|
4278
5598
|
try {
|
|
@@ -4288,7 +5608,7 @@ async function identify(ctx, realm, key, kind) {
|
|
|
4288
5608
|
throw new CliError(EXIT.rateLimited, "Too many requests with this API key right now. Wait a minute and try again. Nothing was saved.", "rate_limited");
|
|
4289
5609
|
const w = res.json;
|
|
4290
5610
|
if (res.status === 200 && w && isAgentId(w.homeAgentId)) {
|
|
4291
|
-
return { kind: w.kind === "agents" ? "agents" : w.kind === "agent" ? "agent" : null, agentId: w.homeAgentId, agentName: clean(
|
|
5611
|
+
return { kind: w.kind === "agents" ? "agents" : w.kind === "agent" ? "agent" : null, agentId: w.homeAgentId, agentName: clean(unwrap4(w.homeAgentName)).slice(0, 120), keyName: typeof w.name === "string" ? clean(w.name).slice(0, 120) : null };
|
|
4292
5612
|
}
|
|
4293
5613
|
if (res.status !== 404)
|
|
4294
5614
|
throw new CliError(EXIT.unexpected, `Couldn't check this key with ${realm} (HTTP ${res.status}). Nothing was saved.`, "bad_response");
|
|
@@ -4307,7 +5627,7 @@ async function identify(ctx, realm, key, kind) {
|
|
|
4307
5627
|
const first = list[0];
|
|
4308
5628
|
if (!isAgentId(first.id))
|
|
4309
5629
|
throw new CliError(EXIT.unexpected, "Darwin's answer was not what the CLI expected. Nothing was saved.", "bad_response");
|
|
4310
|
-
return { kind, agentId: first.id, agentName: clean(
|
|
5630
|
+
return { kind, agentId: first.id, agentName: clean(unwrap4(first.name)).slice(0, 120), keyName: null };
|
|
4311
5631
|
}
|
|
4312
5632
|
async function importKey(ctx, o) {
|
|
4313
5633
|
const who = await identify(ctx, o.realm, o.key, o.kind);
|
|
@@ -4463,7 +5783,7 @@ async function finishPickup(ctx, p, j, json) {
|
|
|
4463
5783
|
rememberSecret(raw);
|
|
4464
5784
|
const agent = j.agent ?? {};
|
|
4465
5785
|
const agentId = isAgentId(agent.id) ? agent.id : "";
|
|
4466
|
-
const agentName = clean(
|
|
5786
|
+
const agentName = clean(unwrap4(agent.name)).slice(0, 120);
|
|
4467
5787
|
const keyName = typeof j.key_name === "string" ? clean(j.key_name).slice(0, 120) : null;
|
|
4468
5788
|
if (!agentId)
|
|
4469
5789
|
throw new CliError(EXIT.unexpected, "Darwin's pairing answer had no agent id. Revoke the newest key on the agent's Manage tab and pair again.", "bad_response");
|
|
@@ -4487,18 +5807,21 @@ async function finishPickup(ctx, p, j, json) {
|
|
|
4487
5807
|
if (!saved)
|
|
4488
5808
|
throw new CliError(EXIT.unexpected, lostKeyLine(k), "store_failed", { agentId, manageUrl: manageUrl(p.realm, agentId) });
|
|
4489
5809
|
let welcome = null;
|
|
5810
|
+
let hello2 = null;
|
|
4490
5811
|
try {
|
|
4491
5812
|
const h = await request(ctx, p.realm, "GET", "/api/agent/v1/hello", { key, agent: kind === "agents" ? agentId : undefined, timeoutMs: 20000 });
|
|
4492
5813
|
const w = h.json?.welcome;
|
|
4493
5814
|
if (h.status === 200 && typeof w === "string")
|
|
4494
5815
|
welcome = clean(w).slice(0, 8000);
|
|
5816
|
+
if (h.status === 200 && h.json && typeof h.json === "object" && !Array.isArray(h.json))
|
|
5817
|
+
hello2 = h.json;
|
|
4495
5818
|
} catch {}
|
|
4496
5819
|
if (json)
|
|
4497
5820
|
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
5821
|
else {
|
|
4499
|
-
if (welcome)
|
|
4500
|
-
say(ctx, welcome);
|
|
4501
5822
|
say(ctx, connectedLine(ctx, k));
|
|
5823
|
+
for (const l of welcomeLines(p.realm, hello2, agentName || agentId))
|
|
5824
|
+
say(ctx, l);
|
|
4502
5825
|
}
|
|
4503
5826
|
return EXIT.ok;
|
|
4504
5827
|
}
|
|
@@ -4513,6 +5836,7 @@ init_context();
|
|
|
4513
5836
|
init_copy();
|
|
4514
5837
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
4515
5838
|
init_http();
|
|
5839
|
+
init_redact();
|
|
4516
5840
|
init_agents();
|
|
4517
5841
|
var B62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
4518
5842
|
function newNonce() {
|
|
@@ -4616,10 +5940,10 @@ function buildArguments(tool, positionals, p, globals) {
|
|
|
4616
5940
|
}
|
|
4617
5941
|
return args;
|
|
4618
5942
|
}
|
|
4619
|
-
var
|
|
5943
|
+
var unwrap5 = (v) => v && typeof v === "object" && !Array.isArray(v) && ("untrusted" in v) ? v.untrusted : v;
|
|
4620
5944
|
var isPaused = (r) => {
|
|
4621
5945
|
const data = r.data ?? {};
|
|
4622
|
-
return [r.error, data.error, data.refusal].some((x) =>
|
|
5946
|
+
return [r.error, data.error, data.refusal].some((x) => unwrap5(x) === "grant_paused");
|
|
4623
5947
|
};
|
|
4624
5948
|
function exitFor(tool, body) {
|
|
4625
5949
|
if (body.isError !== true)
|
|
@@ -4638,7 +5962,7 @@ function exitFor(tool, body) {
|
|
|
4638
5962
|
return EXIT.usage;
|
|
4639
5963
|
return EXIT.refused;
|
|
4640
5964
|
}
|
|
4641
|
-
async function callTool(o, agentId) {
|
|
5965
|
+
async function callTool(o, agentId, limits = {}) {
|
|
4642
5966
|
const { ctx, session, tool } = o;
|
|
4643
5967
|
const args = { ...o.args };
|
|
4644
5968
|
let nonce = null;
|
|
@@ -4653,12 +5977,12 @@ async function callTool(o, agentId) {
|
|
|
4653
5977
|
key: session.key,
|
|
4654
5978
|
agent: agentId ?? undefined,
|
|
4655
5979
|
body: { arguments: args },
|
|
4656
|
-
timeoutMs: tool.write ? 30000 : 20000
|
|
5980
|
+
timeoutMs: limits.timeoutMs ?? (tool.write ? 30000 : 20000)
|
|
4657
5981
|
});
|
|
4658
5982
|
let res;
|
|
4659
5983
|
try {
|
|
4660
5984
|
res = await send();
|
|
4661
|
-
if (!tool.write && res.status === 429) {
|
|
5985
|
+
if (!tool.write && !limits.noRetry && res.status === 429) {
|
|
4662
5986
|
const ra = Number(res.headers.get("retry-after") ?? "NaN");
|
|
4663
5987
|
if (Number.isFinite(ra) && ra >= 0 && ra <= 10) {
|
|
4664
5988
|
await ctx.sleep(ra * 1000);
|
|
@@ -4687,7 +6011,7 @@ async function callTool(o, agentId) {
|
|
|
4687
6011
|
message = copy.uncertainWrite;
|
|
4688
6012
|
else if (exit === EXIT.paused)
|
|
4689
6013
|
message = copy.paused;
|
|
4690
|
-
else if (
|
|
6014
|
+
else if (unwrap5(data.refusal) === "duplicate" && unwrap5(data.detail) === "nonce_used_by_a_different_order") {
|
|
4691
6015
|
message = copy.nonceConflict;
|
|
4692
6016
|
exit = EXIT.refused;
|
|
4693
6017
|
}
|
|
@@ -4713,31 +6037,25 @@ async function callTool(o, agentId) {
|
|
|
4713
6037
|
}
|
|
4714
6038
|
async function runTool(o) {
|
|
4715
6039
|
const { ctx, session, tool, flags } = o;
|
|
4716
|
-
if (
|
|
4717
|
-
const
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
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;
|
|
6040
|
+
if (isPreparedWrite(tool)) {
|
|
6041
|
+
const agentId2 = await resolveAgent(ctx, session, flags.agent);
|
|
6042
|
+
const r = await runPrepared({ ctx, session, catalogue: o.catalogue, tool, args: o.args, agentId: agentId2, json: o.json, dryRun: flags.dryRun, recovery: recoveryFlags(session, agentId2) });
|
|
6043
|
+
if (r.kind === "done") {
|
|
6044
|
+
if (r.doc.error === "unknown_tool" && o.onUnknownTool)
|
|
6045
|
+
await o.onUnknownTool().catch(() => {});
|
|
6046
|
+
return r.exit;
|
|
6047
|
+
}
|
|
6048
|
+
if (flags.dryRun)
|
|
6049
|
+
return EXIT.ok;
|
|
6050
|
+
return runDirect(o, agentId2);
|
|
4739
6051
|
}
|
|
6052
|
+
if (flags.dryRun)
|
|
6053
|
+
return dryRun(o);
|
|
4740
6054
|
const agentId = tool.name === "list_agents" ? null : await resolveAgent(ctx, session, flags.agent);
|
|
6055
|
+
return runDirect(o, agentId);
|
|
6056
|
+
}
|
|
6057
|
+
async function runDirect(o, agentId) {
|
|
6058
|
+
const { ctx, session, tool } = o;
|
|
4741
6059
|
const out = await callTool(o, agentId);
|
|
4742
6060
|
if (out.message === "unknown_tool") {
|
|
4743
6061
|
if (o.onUnknownTool)
|
|
@@ -4749,41 +6067,159 @@ async function runTool(o) {
|
|
|
4749
6067
|
printJson(ctx, result ? { ...result, ...out.nonce ? { nonce: out.nonce } : {} } : { error: out.body?.error ?? "failed", detail: out.message, ...out.nonce ? { nonce: out.nonce } : {} });
|
|
4750
6068
|
if (out.message)
|
|
4751
6069
|
warn(ctx, out.message);
|
|
6070
|
+
for (const l of cliLines(out.body, "notes"))
|
|
6071
|
+
warn(ctx, l);
|
|
6072
|
+
if (out.exit === EXIT.ok)
|
|
6073
|
+
jsonFooter(o, out, result);
|
|
6074
|
+
else if (out.nonce && tool.write)
|
|
6075
|
+
warn(ctx, `Order nonce: ${out.nonce}`);
|
|
6076
|
+
return out.exit;
|
|
6077
|
+
}
|
|
6078
|
+
const x = await renderCtx(o, out, agentId);
|
|
6079
|
+
if (out.exit === EXIT.ok && result) {
|
|
6080
|
+
for (const l of renderResult(tool.name, session.kind, result, x))
|
|
6081
|
+
say(ctx, l);
|
|
6082
|
+
for (const l of cliLines(out.body, "notes"))
|
|
6083
|
+
say(ctx, l);
|
|
6084
|
+
try {
|
|
6085
|
+
if (await afterHumanSuccess(o, result, agentId) === "failed")
|
|
6086
|
+
return EXIT.refused;
|
|
6087
|
+
} catch {}
|
|
4752
6088
|
} 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
|
-
}
|
|
6089
|
+
const covered = out.exit === EXIT.paused || out.exit === EXIT.uncertain || out.message === copy.nonceConflict;
|
|
6090
|
+
if (result && !covered)
|
|
6091
|
+
for (const l of refusalLines(result, o.catalogue, tool.write))
|
|
6092
|
+
warn(ctx, l);
|
|
4773
6093
|
if (out.message)
|
|
4774
6094
|
warn(ctx, out.message);
|
|
6095
|
+
if (out.nonce && tool.write)
|
|
6096
|
+
warn(ctx, `Order nonce: ${out.nonce}`);
|
|
4775
6097
|
}
|
|
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
6098
|
return out.exit;
|
|
4781
6099
|
}
|
|
4782
|
-
|
|
6100
|
+
var QUOTE_TOOLS = new Set(["get_spot_quote"]);
|
|
6101
|
+
var EXTRA_READ_MS = 5000;
|
|
6102
|
+
var CONFIRM_BUDGET_MS = 12000;
|
|
6103
|
+
var SPOT_WRITES = new Set(["place_spot_order", "spot_order_now"]);
|
|
6104
|
+
async function readTool(o, name, args, agentId, deadline) {
|
|
6105
|
+
const t = o.catalogue.tools.find((x) => x.name === name);
|
|
6106
|
+
if (!t || t.write)
|
|
6107
|
+
return null;
|
|
6108
|
+
const left = deadline === undefined ? EXTRA_READ_MS : Math.min(EXTRA_READ_MS, deadline - o.ctx.now());
|
|
6109
|
+
if (left < 500)
|
|
6110
|
+
return null;
|
|
6111
|
+
try {
|
|
6112
|
+
const r = await callTool({ ctx: o.ctx, session: o.session, catalogue: o.catalogue, tool: t, args, flags: o.flags }, agentId, { timeoutMs: left, noRetry: true });
|
|
6113
|
+
return r.exit === EXIT.ok && r.body?.result && typeof r.body.result === "object" ? r.body.result : null;
|
|
6114
|
+
} catch {
|
|
6115
|
+
return null;
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
6118
|
+
async function renderCtx(o, out, agentId) {
|
|
6119
|
+
const { ctx, session, tool } = o;
|
|
6120
|
+
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");
|
|
6121
|
+
const x = { realm: session.realm, now: ctx.now(), args: o.args, catalogue: o.catalogue, agentLabel, nonce: out.nonce };
|
|
6122
|
+
if (out.exit === EXIT.ok && (tool.name === "list_spot_orders" || tool.name === "list_trades")) {
|
|
6123
|
+
const m = await readTool(o, "list_spot_markets", {}, agentId);
|
|
6124
|
+
if (m) {
|
|
6125
|
+
try {
|
|
6126
|
+
x.markets = marketsByMint(m);
|
|
6127
|
+
} catch {}
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
return x;
|
|
6131
|
+
}
|
|
6132
|
+
function marketsByMint(result) {
|
|
6133
|
+
const map = new Map;
|
|
6134
|
+
const list = result.data?.instruments ?? [];
|
|
6135
|
+
if (!Array.isArray(list))
|
|
6136
|
+
return map;
|
|
6137
|
+
for (const i of list) {
|
|
6138
|
+
if (!i || typeof i !== "object" || Array.isArray(i))
|
|
6139
|
+
continue;
|
|
6140
|
+
const r = i;
|
|
6141
|
+
const mint = typeof r.mint === "string" ? r.mint : "";
|
|
6142
|
+
if (!mint || r.instrumentType === "perp")
|
|
6143
|
+
continue;
|
|
6144
|
+
map.set(mint, { symbol: cell(r.symbol).slice(0, 24) || mint.slice(0, 4), decimals: typeof r.decimals === "number" ? r.decimals : null });
|
|
6145
|
+
}
|
|
6146
|
+
return map;
|
|
6147
|
+
}
|
|
6148
|
+
async function afterHumanSuccess(o, result, agentId) {
|
|
6149
|
+
const { ctx, tool } = o;
|
|
6150
|
+
const data = result.data ?? {};
|
|
6151
|
+
if (QUOTE_TOOLS.has(tool.name)) {
|
|
6152
|
+
const inMint = typeof data.inputMint === "string" ? data.inputMint : null;
|
|
6153
|
+
const inAtoms = typeof data.inAtoms === "string" && /^[0-9]{1,40}$/.test(data.inAtoms) ? BigInt(data.inAtoms) : null;
|
|
6154
|
+
if (inMint && inAtoms !== null && inMint !== WSOL_MINT) {
|
|
6155
|
+
const b = await readTool(o, "get_balances", {}, agentId);
|
|
6156
|
+
const bd = b?.data ?? null;
|
|
6157
|
+
const rows = Array.isArray(bd?.balances) ? bd.balances : null;
|
|
6158
|
+
if (rows && bd?.partial !== true) {
|
|
6159
|
+
const row = rows.find((r) => r?.mint === inMint);
|
|
6160
|
+
const held = row && typeof row.atoms === "string" && /^[0-9]{1,40}$/.test(row.atoms) ? BigInt(row.atoms) : 0n;
|
|
6161
|
+
if (held < inAtoms) {
|
|
6162
|
+
const sym = cell(result.summary?.sell?.symbol ?? "") || "of this token";
|
|
6163
|
+
const amt = row ? typeof row.amount === "string" ? cell(row.amount) : formatAtoms(row.atoms, typeof row.decimals === "number" ? row.decimals : null) : "0";
|
|
6164
|
+
warn(ctx, `⚠ This agent holds ${amt || "less than that"} ${sym}, so an order for this quote would fail.`);
|
|
6165
|
+
}
|
|
6166
|
+
}
|
|
6167
|
+
}
|
|
6168
|
+
return;
|
|
6169
|
+
}
|
|
6170
|
+
if (!SPOT_WRITES.has(tool.name))
|
|
6171
|
+
return;
|
|
6172
|
+
const summary = result.summary ?? {};
|
|
6173
|
+
const sig = typeof summary.txSignature === "string" ? summary.txSignature : typeof data.txSignature === "string" ? data.txSignature : null;
|
|
6174
|
+
const status = typeof summary.status === "string" ? summary.status : data.status;
|
|
6175
|
+
if (status === "failed") {
|
|
6176
|
+
warn(ctx, `The order failed, so nothing was traded.${sig ? ` Details: darwin tx ${sig}` : ""}`);
|
|
6177
|
+
return "failed";
|
|
6178
|
+
}
|
|
6179
|
+
if (sig && /^[1-9A-HJ-NP-Za-km-z]{64,90}$/.test(sig) && status !== "confirmed") {
|
|
6180
|
+
const deadline = ctx.now() + CONFIRM_BUDGET_MS;
|
|
6181
|
+
let landed = null;
|
|
6182
|
+
for (let i = 0;i < CONFIRM_POLLS && !landed && ctx.now() + CONFIRM_INTERVAL_MS < deadline; i++) {
|
|
6183
|
+
await ctx.sleep(CONFIRM_INTERVAL_MS);
|
|
6184
|
+
const t = await readTool(o, "get_tx_status", { signature: sig }, agentId, deadline);
|
|
6185
|
+
const st = t?.data?.status;
|
|
6186
|
+
if (st === "confirmed" || st === "failed")
|
|
6187
|
+
landed = st;
|
|
6188
|
+
}
|
|
6189
|
+
if (landed === "confirmed") {
|
|
6190
|
+
const got = await receivedFor(o, data.orderId ?? summary.orderId, agentId, ctx.now() + EXTRA_READ_MS);
|
|
6191
|
+
say(ctx, got ? `Confirmed on chain: received ${got}.` : "Confirmed on chain.");
|
|
6192
|
+
} else if (landed === "failed") {
|
|
6193
|
+
warn(ctx, `The transaction failed on chain, so nothing was traded. Details: darwin tx ${sig}`);
|
|
6194
|
+
return "failed";
|
|
6195
|
+
} else {
|
|
6196
|
+
say(ctx, `Not confirmed yet. Check it with: darwin tx ${sig}`);
|
|
6197
|
+
}
|
|
6198
|
+
}
|
|
6199
|
+
const g = await readTool(o, "get_grant", {}, agentId);
|
|
6200
|
+
const left = g?.data?.today?.remaining;
|
|
6201
|
+
if (left && typeof left.txCount === "number")
|
|
6202
|
+
say(ctx, `Trades left today: ${left.txCount}`);
|
|
6203
|
+
}
|
|
6204
|
+
var CONFIRM_POLLS = 4;
|
|
6205
|
+
var CONFIRM_INTERVAL_MS = 2000;
|
|
6206
|
+
async function receivedFor(o, orderId, agentId, deadline) {
|
|
6207
|
+
if (typeof orderId !== "string" || !orderId)
|
|
6208
|
+
return null;
|
|
6209
|
+
const [list, markets] = await Promise.all([readTool(o, "list_spot_orders", {}, agentId, deadline), readTool(o, "list_spot_markets", {}, agentId, deadline)]);
|
|
6210
|
+
const rows = list?.data?.orders ?? [];
|
|
6211
|
+
const row = Array.isArray(rows) ? rows.find((r) => r?.orderId === orderId) : undefined;
|
|
6212
|
+
if (!row || typeof row.outputAtoms !== "string")
|
|
6213
|
+
return null;
|
|
6214
|
+
const m = markets ? marketsByMint(markets).get(String(row.outputMint)) : undefined;
|
|
6215
|
+
const amt = m ? formatAtoms(row.outputAtoms, m.decimals) : "";
|
|
6216
|
+
return amt ? `${amt} ${m.symbol}` : null;
|
|
6217
|
+
}
|
|
6218
|
+
function jsonFooter(o, out, result) {
|
|
4783
6219
|
const { ctx, tool, args } = o;
|
|
4784
6220
|
const summary = result?.summary ?? {};
|
|
4785
6221
|
const data = result?.data ?? {};
|
|
4786
|
-
const text = (v) => cell(
|
|
6222
|
+
const text = (v) => cell(unwrap5(v));
|
|
4787
6223
|
if (tool.name === "get_spot_quote") {
|
|
4788
6224
|
const quoteId = typeof data.quoteId === "string" ? data.quoteId : typeof summary.quoteId === "string" ? summary.quoteId : null;
|
|
4789
6225
|
const exp = typeof data.expiresAtMs === "number" ? data.expiresAtMs : null;
|
|
@@ -4793,14 +6229,39 @@ function afterSuccess(o, out, result) {
|
|
|
4793
6229
|
warn(ctx, copy.quoteFooter(quoteId, secs, text(args.sell), typeof sellAmount === "string" ? text(sellAmount) : text(args.amount), text(args.for)));
|
|
4794
6230
|
}
|
|
4795
6231
|
}
|
|
4796
|
-
if ((tool.name
|
|
4797
|
-
const
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
6232
|
+
if (SPOT_WRITES.has(tool.name) && out.nonce) {
|
|
6233
|
+
const orderId = typeof data.orderId === "string" ? data.orderId : typeof summary.orderId === "string" ? summary.orderId : null;
|
|
6234
|
+
warn(ctx, copy.spotSent(text(args.amount), text(args.sell), text(args.for), agentFor(o, out), orderId ? cell(orderId) : null, out.nonce));
|
|
6235
|
+
}
|
|
6236
|
+
}
|
|
6237
|
+
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";
|
|
6238
|
+
function dryRun(o) {
|
|
6239
|
+
const { ctx, session, tool } = o;
|
|
6240
|
+
const named = (o.flags.agent ?? ctx.env.DARWIN_AGENT ?? "").trim() || (session.kind === "agents" ? session.profile?.default_agent ?? "" : "");
|
|
6241
|
+
if (session.kind === "agents" && tool.name !== "list_agents" && !named)
|
|
6242
|
+
throw new CliError(EXIT.usage, copy.needAgent, "agent_required");
|
|
6243
|
+
const agentId = named || null;
|
|
6244
|
+
const preview = {
|
|
6245
|
+
dryRun: true,
|
|
6246
|
+
sent: false,
|
|
6247
|
+
command: `darwin ${tool.cli.path.join(" ")}`,
|
|
6248
|
+
tool: tool.name,
|
|
6249
|
+
write: tool.write,
|
|
6250
|
+
costsTx: tool.costsTx,
|
|
6251
|
+
realm: session.realm,
|
|
6252
|
+
agent: agentId,
|
|
6253
|
+
arguments: o.args
|
|
6254
|
+
};
|
|
6255
|
+
if (o.json)
|
|
6256
|
+
printJson(ctx, preview);
|
|
6257
|
+
else {
|
|
6258
|
+
say(ctx, `Dry run — nothing was sent. Would run: darwin ${tool.cli.path.join(" ")} on ${session.realm}${agentId ? ` for agent ${clean(agentId)}` : ""}`);
|
|
6259
|
+
for (const [k, v] of Object.entries(o.args)) {
|
|
6260
|
+
const flag = tool.cli.args[k]?.flag;
|
|
6261
|
+
say(ctx, flag ? ` --${flag} ${cell(v)}` : tool.cli.positional.includes(k) ? ` <${clean(k)}> ${cell(v)}` : ` --field ${clean(k)}=${cell(v)}`);
|
|
6262
|
+
}
|
|
4803
6263
|
}
|
|
6264
|
+
return EXIT.ok;
|
|
4804
6265
|
}
|
|
4805
6266
|
|
|
4806
6267
|
// src/mcp.ts
|
|
@@ -4820,7 +6281,29 @@ function printConfig(ctx, client, profile) {
|
|
|
4820
6281
|
`;
|
|
4821
6282
|
throw new CliError(EXIT.usage, "--print-config takes claude, cursor or gemini.", "usage");
|
|
4822
6283
|
}
|
|
6284
|
+
var PREPARED_TOOL_DEFS = [
|
|
6285
|
+
{
|
|
6286
|
+
name: "check_prepared",
|
|
6287
|
+
title: "What happened to a write",
|
|
6288
|
+
description: "READ-ONLY. What happened to a write whose result was uncertain, by the preparedId the write returned: it went through, is still pending, failed, or was never sent. Never sends anything; call it instead of repeating a write.",
|
|
6289
|
+
inputSchema: { type: "object", properties: { preparedId: { type: "string", pattern: "^prp_[0-9A-Za-z]{24}$" } }, required: ["preparedId"], additionalProperties: false },
|
|
6290
|
+
annotations: { title: "What happened to a write", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
6291
|
+
},
|
|
6292
|
+
{
|
|
6293
|
+
name: "cancel_prepared",
|
|
6294
|
+
title: "Make sure a write never runs",
|
|
6295
|
+
description: "Make sure a prepared write that has not started never runs (by its preparedId). A write that already started can't be cancelled; the answer says what happened instead.",
|
|
6296
|
+
inputSchema: { type: "object", properties: { preparedId: { type: "string", pattern: "^prp_[0-9A-Za-z]{24}$" } }, required: ["preparedId"], additionalProperties: false },
|
|
6297
|
+
annotations: { title: "Make sure a write never runs", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
6298
|
+
}
|
|
6299
|
+
];
|
|
4823
6300
|
function toolDefs(c) {
|
|
6301
|
+
return [...catalogueToolDefs(c), ...PREPARED_TOOL_DEFS.map((d) => ({
|
|
6302
|
+
...d,
|
|
6303
|
+
inputSchema: c.globals.agentParam ? { ...d.inputSchema, properties: { ...d.inputSchema.properties, agent: AGENT_PROP } } : d.inputSchema
|
|
6304
|
+
}))];
|
|
6305
|
+
}
|
|
6306
|
+
function catalogueToolDefs(c) {
|
|
4824
6307
|
return c.tools.map((t) => {
|
|
4825
6308
|
let schema = t.inputSchema;
|
|
4826
6309
|
if (c.globals.agentParam && t.name !== "list_agents") {
|
|
@@ -4925,6 +6408,20 @@ function toolResult(value, isError) {
|
|
|
4925
6408
|
return { content: [{ type: "text", text: JSON.stringify(v) }], structuredContent: v, isError };
|
|
4926
6409
|
}
|
|
4927
6410
|
async function mcpCall(ctx, session, catalogue, params, refresh) {
|
|
6411
|
+
if (params.name === "check_prepared" || params.name === "cancel_prepared") {
|
|
6412
|
+
const raw2 = params.arguments ?? {};
|
|
6413
|
+
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2) || typeof raw2.preparedId !== "string")
|
|
6414
|
+
return toolResult({ error: "invalid_arguments", detail: "preparedId is required." }, true);
|
|
6415
|
+
let agentArg2;
|
|
6416
|
+
if (raw2.agent !== undefined) {
|
|
6417
|
+
if (typeof raw2.agent !== "string" || !raw2.agent.trim())
|
|
6418
|
+
return toolResult({ error: "invalid_arguments", detail: "`agent` must be an agent id or name." }, true);
|
|
6419
|
+
agentArg2 = raw2.agent.trim();
|
|
6420
|
+
}
|
|
6421
|
+
const agentId2 = await recoveryAgent(ctx, session, agentArg2);
|
|
6422
|
+
const r = await preparedRead({ ctx, session, agentId: agentId2 }, raw2.preparedId, params.name === "cancel_prepared");
|
|
6423
|
+
return toolResult(r.body, !r.ok);
|
|
6424
|
+
}
|
|
4928
6425
|
const tool = catalogue.tools.find((t) => t.name === params.name);
|
|
4929
6426
|
if (!tool)
|
|
4930
6427
|
return toolResult({ error: "unknown_tool", detail: "No such tool; list the tools again.", sent: false }, true);
|
|
@@ -4942,6 +6439,24 @@ async function mcpCall(ctx, session, catalogue, params, refresh) {
|
|
|
4942
6439
|
delete args.agent;
|
|
4943
6440
|
}
|
|
4944
6441
|
const agentId = tool.name === "list_agents" ? null : await resolveAgent(ctx, session, agentArg);
|
|
6442
|
+
if (isPreparedWrite(tool)) {
|
|
6443
|
+
const r = await runPrepared({ ctx, session, catalogue, tool, args, agentId, json: true, dryRun: false, quietForMcp: true, recovery: recoveryFlags(session, agentId) });
|
|
6444
|
+
if (r.kind === "done") {
|
|
6445
|
+
if (r.exit === EXIT.ok)
|
|
6446
|
+
return toolResult(r.doc, false);
|
|
6447
|
+
if (r.exit === EXIT.uncertain && r.preparedId) {
|
|
6448
|
+
return toolResult({
|
|
6449
|
+
...r.doc,
|
|
6450
|
+
error: "outcome_unknown",
|
|
6451
|
+
sent: "maybe",
|
|
6452
|
+
preparedId: r.preparedId,
|
|
6453
|
+
...agentId ? { agent: agentId } : {},
|
|
6454
|
+
detail: `Darwin may have received this write, but its result didn't come back. Do NOT call this tool again — call check_prepared with preparedId "${r.preparedId}"${agentId ? ` and agent "${agentId}"` : ""} to see what happened.`
|
|
6455
|
+
}, true);
|
|
6456
|
+
}
|
|
6457
|
+
return toolResult(r.doc, true);
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
4945
6460
|
const out = await callTool({ ctx, session, catalogue, tool, args, flags: { json: true, dryRun: false, quiet: true }, onStaleCatalogue: refresh }, agentId);
|
|
4946
6461
|
const result = out.body?.result ?? null;
|
|
4947
6462
|
if (out.exit === EXIT.uncertain) {
|
|
@@ -4952,219 +6467,58 @@ async function mcpCall(ctx, session, catalogue, params, refresh) {
|
|
|
4952
6467
|
return toolResult({ ...result, ...out.nonce ? { nonce: out.nonce } : {} }, out.exit !== EXIT.ok);
|
|
4953
6468
|
}
|
|
4954
6469
|
|
|
4955
|
-
// src/
|
|
6470
|
+
// src/local-flags.ts
|
|
4956
6471
|
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."
|
|
6472
|
+
var PARAMS = {
|
|
6473
|
+
get_indicators: [
|
|
6474
|
+
{ prop: "timeframe", flag: "timeframe", required: true, description: "Candle size: 1m, 5m, 15m, 30m, 1h, 4h, 6h, 12h or 1d." },
|
|
6475
|
+
{ prop: "indicators", flag: "indicators", required: true, description: "Comma-separated, e.g. rsi:14,macd:12:26:9." },
|
|
6476
|
+
{ prop: "instrumentId", flag: "instrument-id", description: "The market, as `darwin markets` lists it (spot:solana:<mint>). Give this or --asset." },
|
|
6477
|
+
{ prop: "asset", flag: "asset", description: "A Darwin asset id instead of --instrument-id." },
|
|
6478
|
+
{ prop: "bars", flag: "bars", type: "number", description: "How many bars (default 50)." },
|
|
6479
|
+
{ prop: "includeFormingBar", flag: "include-forming-bar", type: "boolean", description: "Also show the bar still forming." }
|
|
6480
|
+
],
|
|
6481
|
+
list_spot_orders: [
|
|
6482
|
+
{ prop: "limit", flag: "limit", type: "number", description: "How many (1–200, default 50)." },
|
|
6483
|
+
{ prop: "cursor", flag: "cursor", description: "The next page, from the line a previous page printed." }
|
|
6484
|
+
],
|
|
6485
|
+
list_trades: [
|
|
6486
|
+
{ prop: "kind", flag: "kind", description: "spot, perp or all (default all)." },
|
|
6487
|
+
{ prop: "since", flag: "since", type: "number", description: "Only trades settled after this time (epoch milliseconds)." },
|
|
6488
|
+
{ prop: "limit", flag: "limit", type: "number", description: "How many (at most 200)." },
|
|
6489
|
+
{ prop: "cursor", flag: "cursor", description: "The next page, from the line a previous page printed." }
|
|
6490
|
+
],
|
|
6491
|
+
get_pnl: [{ prop: "period", flag: "period", description: "ytd (default), 30d, 90d, all, or a tax year such as 2025." }],
|
|
6492
|
+
list_lots: [
|
|
6493
|
+
{ prop: "sort", flag: "sort", description: "unrealized_asc, acquired_asc or basis_desc." },
|
|
6494
|
+
{ prop: "mint", flag: "mint", description: "Only this token's lots." },
|
|
6495
|
+
{ prop: "instrumentId", flag: "instrument-id", description: "Only this market's lots." }
|
|
6496
|
+
],
|
|
6497
|
+
list_spot_markets: [{ prop: "type", flag: "type", description: "spot or perp (default both)." }]
|
|
5073
6498
|
};
|
|
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;
|
|
6499
|
+
function withLocalFlags(tool) {
|
|
6500
|
+
const extra = PARAMS[tool.name];
|
|
6501
|
+
if (!extra || tool.write)
|
|
6502
|
+
return tool;
|
|
6503
|
+
const props = { ...tool.inputSchema.properties ?? {} };
|
|
6504
|
+
const args = { ...tool.cli.args };
|
|
6505
|
+
const required = [...tool.inputSchema.required ?? []];
|
|
6506
|
+
const taken = new Set(Object.values(args).map((a) => a.flag));
|
|
6507
|
+
for (const p of extra) {
|
|
6508
|
+
if (props[p.prop] || taken.has(p.flag))
|
|
6509
|
+
continue;
|
|
6510
|
+
props[p.prop] = { type: p.type ?? "string", description: p.description };
|
|
6511
|
+
args[p.prop] = { flag: p.flag };
|
|
6512
|
+
taken.add(p.flag);
|
|
6513
|
+
if (p.required && !required.includes(p.prop))
|
|
6514
|
+
required.push(p.prop);
|
|
5138
6515
|
}
|
|
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}.`);
|
|
6516
|
+
return { ...tool, cli: { ...tool.cli, args }, inputSchema: { ...tool.inputSchema, properties: props, required } };
|
|
5156
6517
|
}
|
|
5157
|
-
function
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
printJson(ctx, { command: name, ...h });
|
|
5161
|
-
return;
|
|
6518
|
+
function checkLocalArguments(tool, args) {
|
|
6519
|
+
if (tool.name === "get_indicators" && PARAMS.get_indicators && args.instrumentId === undefined === (args.asset === undefined)) {
|
|
6520
|
+
throw new CliError(EXIT.usage, "`darwin indicators` needs exactly one of --instrument-id (from `darwin markets`) or --asset. See `darwin indicators --help`.", "usage");
|
|
5162
6521
|
}
|
|
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
6522
|
}
|
|
5169
6523
|
|
|
5170
6524
|
// src/main.ts
|
|
@@ -5222,10 +6576,10 @@ async function dispatch(p, ctx) {
|
|
|
5222
6576
|
printStaticHelp(ctx, rest[0], json);
|
|
5223
6577
|
return EXIT.ok;
|
|
5224
6578
|
}
|
|
5225
|
-
const t = resolve(c, rest)?.tool;
|
|
6579
|
+
const t = resolve(c, rest, !json)?.tool;
|
|
5226
6580
|
if (!t)
|
|
5227
6581
|
throw new CliError(EXIT.usage, `No command "${rest.join(" ").slice(0, 60)}". \`darwin help\` lists them.`, "unknown_command");
|
|
5228
|
-
printCommandHelp(ctx, t, json);
|
|
6582
|
+
printCommandHelp(ctx, t, json, c);
|
|
5229
6583
|
return EXIT.ok;
|
|
5230
6584
|
}
|
|
5231
6585
|
if (STATIC_HELP[first] && has(p, "help")) {
|
|
@@ -5247,6 +6601,10 @@ async function dispatch(p, ctx) {
|
|
|
5247
6601
|
return cmdApi(ctx, p);
|
|
5248
6602
|
case "mcp":
|
|
5249
6603
|
return cmdMcp(ctx, p);
|
|
6604
|
+
case "retry":
|
|
6605
|
+
return cmdCheckPrepared(ctx, p, false);
|
|
6606
|
+
case "cancel":
|
|
6607
|
+
return cmdCheckPrepared(ctx, p, true);
|
|
5250
6608
|
}
|
|
5251
6609
|
if (first === "market-status" && !has(p, "help"))
|
|
5252
6610
|
return cmdMarketStatus(ctx, p);
|
|
@@ -5254,20 +6612,20 @@ async function dispatch(p, ctx) {
|
|
|
5254
6612
|
throw new CliError(EXIT.usage, `\`darwin ${first}\` isn't in this version of the Darwin CLI.`, "unknown_command");
|
|
5255
6613
|
if (has(p, "help")) {
|
|
5256
6614
|
const c = await catalogueForHelp(ctx, p);
|
|
5257
|
-
const t = resolve(c, p.positionals)?.tool;
|
|
6615
|
+
const t = resolve(c, p.positionals, !json)?.tool;
|
|
5258
6616
|
if (!t)
|
|
5259
6617
|
throw new CliError(EXIT.usage, `No command "${p.positionals.join(" ").slice(0, 60)}". \`darwin help\` lists them.`, "unknown_command");
|
|
5260
|
-
printCommandHelp(ctx, t, json);
|
|
6618
|
+
printCommandHelp(ctx, t, json, c);
|
|
5261
6619
|
return EXIT.ok;
|
|
5262
6620
|
}
|
|
5263
6621
|
return runCatalogueCommand(ctx, p, json);
|
|
5264
6622
|
}
|
|
5265
|
-
function resolve(c,
|
|
6623
|
+
function resolve(c, words2, localFlags = true) {
|
|
5266
6624
|
const idx = commandIndex(c);
|
|
5267
|
-
for (let n = Math.min(3,
|
|
5268
|
-
const t = idx.get(
|
|
6625
|
+
for (let n = Math.min(3, words2.length);n >= 1; n--) {
|
|
6626
|
+
const t = idx.get(words2.slice(0, n).join(" "));
|
|
5269
6627
|
if (t)
|
|
5270
|
-
return { tool: t, rest:
|
|
6628
|
+
return { tool: localFlags ? withLocalFlags(t) : t, rest: words2.slice(n) };
|
|
5271
6629
|
}
|
|
5272
6630
|
return null;
|
|
5273
6631
|
}
|
|
@@ -5286,10 +6644,10 @@ async function catalogueForHelp(ctx, p) {
|
|
|
5286
6644
|
}
|
|
5287
6645
|
async function runCatalogueCommand(ctx, p, json) {
|
|
5288
6646
|
const session = openSession(ctx, { profile: one(p, "profile") });
|
|
5289
|
-
const
|
|
5290
|
-
let { catalogue } = await loadCatalogue(ctx, session.realm, session.kind, { key: session.key, refresh:
|
|
6647
|
+
const dryRun2 = has(p, "dry-run");
|
|
6648
|
+
let { catalogue } = await loadCatalogue(ctx, session.realm, session.kind, { key: session.key, refresh: dryRun2 ? "offline" : "never" });
|
|
5291
6649
|
let hit = resolve(catalogue, p.positionals);
|
|
5292
|
-
if (!hit
|
|
6650
|
+
if (!hit) {
|
|
5293
6651
|
catalogue = (await loadCatalogue(ctx, session.realm, session.kind, { key: session.key, refresh: "force" })).catalogue;
|
|
5294
6652
|
hit = resolve(catalogue, p.positionals);
|
|
5295
6653
|
}
|
|
@@ -5298,6 +6656,7 @@ async function runCatalogueCommand(ctx, p, json) {
|
|
|
5298
6656
|
if (compareVersions(VERSION, catalogue.minCli) < 0)
|
|
5299
6657
|
throw new CliError(EXIT.upgrade, copy.updateRequired(catalogue.minCli), "cli_upgrade_required");
|
|
5300
6658
|
const args = buildArguments(hit.tool, hit.rest, p, GLOBALS);
|
|
6659
|
+
checkLocalArguments(hit.tool, args);
|
|
5301
6660
|
if (hit.tool.deprecated)
|
|
5302
6661
|
warn(ctx, `\`darwin ${hit.tool.cli.path.join(" ")}\` is deprecated and goes away after ${hit.tool.deprecated.removeAfter}.`);
|
|
5303
6662
|
const code = await runTool({
|
|
@@ -5307,7 +6666,7 @@ async function runCatalogueCommand(ctx, p, json) {
|
|
|
5307
6666
|
tool: hit.tool,
|
|
5308
6667
|
args,
|
|
5309
6668
|
json,
|
|
5310
|
-
flags: { json, dryRun, agent: one(p, "agent"), quiet: has(p, "quiet") },
|
|
6669
|
+
flags: { json, dryRun: dryRun2, agent: one(p, "agent"), quiet: has(p, "quiet") },
|
|
5311
6670
|
onStaleCatalogue: async () => {
|
|
5312
6671
|
await loadCatalogue(ctx, session.realm, session.kind, { key: session.key, refresh: "force" });
|
|
5313
6672
|
},
|