@darwin.finance/cli 1.0.0 → 1.0.1

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