@boole-digital/cli 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +68 -40
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -16,7 +16,8 @@ import {
16
16
  chmodSync,
17
17
  rmSync
18
18
  } from "node:fs";
19
- var API_BASE = (process.env.BOOLE_API_BASE || "https://trade.boole.markets").replace(/\/+$/, "");
19
+ var API_BASE = (process.env.BOOLE_API_BASE || "https://api.cest-finyx.com").replace(/\/+$/, "");
20
+ var WEB_BASE = (process.env.BOOLE_WEB_BASE || process.env.BOOLE_API_BASE || "https://trade.boole.markets").replace(/\/+$/, "");
20
21
  var CONFIG_DIR = process.env.BOOLE_HOME || join(homedir(), ".boole");
21
22
  var CRED_PATH = join(CONFIG_DIR, "credentials.json");
22
23
  var SESSION_PATH = join(CONFIG_DIR, "session.env");
@@ -145,12 +146,12 @@ function decodePayload(code) {
145
146
  };
146
147
  }
147
148
  function successHtml(email, dashboardUrl) {
148
- const who = email ? `<p>Signed in as <b style="color:#eaeaea">${email.replace(/[<>&]/g, "")}</b></p>` : "";
149
+ const who = email ? `<p class="who">Signed in as: <b>${email.replace(/[<>&]/g, "")}</b></p>` : "";
149
150
  const url = dashboardUrl.replace(/"/g, "");
150
151
  return `<!doctype html><meta charset=utf-8><title>Boole</title>
151
- <style>body{font:16px -apple-system,system-ui,sans-serif;background:#0b0b0b;color:#eaeaea;display:grid;place-items:center;height:100vh;margin:0}div{text-align:center}h1{font-size:22px}p{color:#9a9a9a;margin:6px 0}.btn{display:inline-block;margin-top:18px;padding:10px 20px;border-radius:8px;background:#6d5efc;color:#fff;text-decoration:none;font-weight:600}.btn:hover{background:#5a4ef0}</style>
152
- <div><h1>\u2713 Signed in to Boole</h1>${who}<p style="color:#34d399;font-weight:600">Your CLI is authenticated.</p><p>You can close this tab now and return to your terminal.</p>
153
- <a class="btn" href="${url}">See dashboard \u2192</a></div>`;
152
+ <style>body{font:16px -apple-system,system-ui,sans-serif;background:#0b0b0b;color:#eaeaea;display:grid;place-items:center;height:100vh;margin:0}div{text-align:center}h1{font-size:22px;font-weight:600;margin:0 0 14px}p{margin:6px 0;color:#9a9a9a}.who b{color:#eaeaea;font-weight:600}.tag{color:#7a7a7a;margin-top:16px}.btn{display:inline-block;margin-top:22px;padding:10px 22px;border-radius:8px;background:#1c1c22;color:#c8c8d0;text-decoration:none;font-weight:500;border:1px solid #2e2e38}.btn:hover{background:#26262e;color:#eaeaea}</style>
153
+ <div><h1>Authenticated to Boole</h1>${who}<p class="tag">Go build something great</p>
154
+ <a class="btn" href="${url}">Continue to Boole</a></div>`;
154
155
  }
155
156
  function emailFromCode(code) {
156
157
  try {
@@ -160,7 +161,7 @@ function emailFromCode(code) {
160
161
  }
161
162
  }
162
163
  async function loginPaste() {
163
- const url = `${API_BASE}/cli-auth?paste=1`;
164
+ const url = `${WEB_BASE}/cli-auth?paste=1`;
164
165
  info("Opening the Boole login page\u2026");
165
166
  log(` ${c.dim("If it does not open, visit:")} ${c.cyan(url)}`);
166
167
  openBrowser(url);
@@ -190,7 +191,7 @@ async function loginLoopback() {
190
191
  return;
191
192
  }
192
193
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
193
- res.end(successHtml(emailFromCode(code), API_BASE));
194
+ res.end(successHtml(emailFromCode(code), WEB_BASE));
194
195
  queueMicrotask(() => resolveCode(code));
195
196
  });
196
197
  const port = await new Promise((resolve2) => {
@@ -199,7 +200,7 @@ async function loginLoopback() {
199
200
  resolve2(typeof addr === "object" && addr ? addr.port : 0);
200
201
  });
201
202
  });
202
- const url = `${API_BASE}/cli-auth?port=${port}`;
203
+ const url = `${WEB_BASE}/cli-auth?port=${port}`;
203
204
  info("Opening the Boole login page in your browser\u2026");
204
205
  log(` ${c.dim("If it does not open, visit:")} ${c.cyan(url)}`);
205
206
  openBrowser(url);
@@ -283,14 +284,27 @@ var BooleApi = class {
283
284
  async request(path, init2 = {}) {
284
285
  await this.ensureFresh();
285
286
  if (!this.creds) throw new AuthError("Not logged in. Run `boole login`.");
286
- const doFetch = () => fetch(`${API_BASE}${path}`, {
287
- ...init2,
288
- headers: {
289
- "Content-Type": "application/json",
290
- ...init2.headers || {},
291
- Authorization: `Bearer ${this.creds.access_token}`
287
+ const TIMEOUT_MS = Number(process.env.BOOLE_TIMEOUT_MS) || 12e3;
288
+ const doFetch = async () => {
289
+ const ac = new AbortController();
290
+ const t = setTimeout(() => ac.abort(), TIMEOUT_MS);
291
+ try {
292
+ return await fetch(`${API_BASE}${path}`, {
293
+ ...init2,
294
+ signal: ac.signal,
295
+ headers: {
296
+ "Content-Type": "application/json",
297
+ ...init2.headers || {},
298
+ Authorization: `Bearer ${this.creds.access_token}`
299
+ }
300
+ });
301
+ } catch (e) {
302
+ if (e?.name === "AbortError") throw new Error(`${path}: timed out after ${TIMEOUT_MS / 1e3}s (network or box waking up)`);
303
+ throw e;
304
+ } finally {
305
+ clearTimeout(t);
292
306
  }
293
- });
307
+ };
294
308
  let res = await doFetch();
295
309
  if (res.status === 401) {
296
310
  this.creds.expires_at = 0;
@@ -368,7 +382,7 @@ function onboardingNotice() {
368
382
  warn("No trading computer yet \u2014 and you can\u2019t deploy your first one from the CLI.");
369
383
  log(" Finish onboarding in the Boole app first (this is required):");
370
384
  log(` 1. Create your account 2. Agree to the terms 3. Set up billing`);
371
- log(` ${c.cyan(API_BASE)}`);
385
+ log(` ${c.cyan(WEB_BASE)}`);
372
386
  log(` Deploy your first trading computer there, then come back and run ${c.cyan("boole connect")}.`);
373
387
  log("");
374
388
  }
@@ -389,7 +403,7 @@ function orient() {
389
403
  log(`${c.bold("Do this next:")} ${c.cyan("boole login")}`);
390
404
  } else if (!sess) {
391
405
  log(`${c.bold("You are:")} logged in as ${c.bold(creds.user.email || creds.user.id)}, ${c.yellow("no box connected")}.`);
392
- log(`${c.bold("Do this next:")} ${c.cyan("boole connect")} ${c.dim(`# no trading computer yet? finish onboarding at ${API_BASE}`)}`);
406
+ log(`${c.bold("Do this next:")} ${c.cyan("boole connect")} ${c.dim(`# no trading computer yet? finish onboarding at ${WEB_BASE}`)}`);
393
407
  } else {
394
408
  log(`${c.bold("You are:")} connected to ${c.bold(sess.agent || sess.ip)} ${c.green("\u2014 ready")}.`);
395
409
  log(c.bold("Do this next:"));
@@ -403,14 +417,14 @@ function orient() {
403
417
  }
404
418
  function operatingBrief() {
405
419
  log("");
406
- log(c.bold("How to operate this computer"));
407
- log(` Your coding agent drives the box. Open this folder in ${c.bold("Claude Code")}, ${c.bold("Codex")}, or ${c.bold("Gemini")}`);
408
- log(` (run ${c.cyan("boole init")} here first), then just say what you want \u2014 e.g. ${c.dim('"buy $100 of BTC"')}.`);
409
- log("");
410
- log(` The authoritative operating guide lives on the box:`);
420
+ log(c.bold("You're connected. Just tell your agent what you want \u2014 in plain English."));
421
+ log(` It drives the box's harness (tracked on your dashboard). Guide on the box:`);
411
422
  log(` ${c.cyan('boole ssh "cat /srv/cust/OPERATOR.md"')}`);
412
- log(` The agent SSHes in, reads it, and drives the on-box harness (which tracks everything on your dashboard).`);
413
- log(` ${c.dim("The CLI has no trade commands \u2014 trading happens on the box, through the harness.")}`);
423
+ log("");
424
+ log(c.bold("Try asking:"));
425
+ log(` \xB7 ${c.dim('"buy $100 of BTC"')} \xB7 ${c.dim('"short $50 of ETH"')}`);
426
+ log(` \xB7 ${c.dim('"run a grid on SOL"')} \xB7 ${c.dim('"watch the BTC price every 10s"')}`);
427
+ log(` \xB7 ${c.dim('"what are my balances?"')} \xB7 ${c.dim('"show my running strategies"')}`);
414
428
  log("");
415
429
  }
416
430
  function printAgents(droplets) {
@@ -445,7 +459,8 @@ function pickAgentId(droplets) {
445
459
  if (ready.length === 1) return { id: ready[0].id, name: ready[0].name };
446
460
  return null;
447
461
  }
448
- async function summary() {
462
+ async function summary(opts = {}) {
463
+ const snapshot = opts.snapshot !== false;
449
464
  const creds = loadCredentials();
450
465
  if (!creds) {
451
466
  warn("Not logged in. Run `boole login`.");
@@ -483,30 +498,43 @@ async function summary() {
483
498
  log(c.bold(`Trading computers (${droplets.length})`));
484
499
  printAgents(droplets);
485
500
  const pick = pickAgentId(droplets);
486
- if (pick) {
501
+ if (pick && snapshot) {
487
502
  log("");
488
503
  log(c.bold(`Snapshot \u2014 ${pick.name}`));
489
504
  try {
490
505
  const health = await api.getHealth(pick.id);
491
506
  ok(`online${health.defaultModel ? c.dim(` \xB7 model ${health.defaultModel}`) : ""}`);
492
- await printBalances(api, pick.id, "hyperliquid");
507
+ await printBalances(api, pick.id);
493
508
  } catch {
494
509
  warn("trading computer unreachable (tunnel may be waking up \u2014 retry in a moment)");
495
510
  }
511
+ } else if (pick) {
512
+ log(c.dim(` Run ${c.cyan("boole status")} for balances + live snapshot.`));
496
513
  }
497
514
  operatingBrief();
498
515
  }
499
- async function printBalances(api, id, exchange) {
500
- try {
501
- const acct = await api.getAccount(id, exchange);
502
- const positions = Array.isArray(acct.positions) ? acct.positions : [];
503
- info(`${exchange}: equity ${c.bold(fmtUsd(acct.equity))} \xB7 available ${fmtUsd(acct.available)} \xB7 ${positions.length} open position(s)`);
504
- for (const p of positions.slice(0, 12)) {
505
- log(` ${c.bold(String(p.market ?? "?"))} ${p.size ?? ""}`);
516
+ var BALANCE_VENUES = ["hyperliquid", "portara-testnet", "rise", "polymarket"];
517
+ async function printBalances(api, id, venue) {
518
+ const venues = venue ? [venue] : BALANCE_VENUES;
519
+ const results = await Promise.all(venues.map(async (v) => {
520
+ try {
521
+ return { v, acct: await api.getAccount(id, v), err: "" };
522
+ } catch (e) {
523
+ return { v, acct: null, err: e?.message || String(e) };
506
524
  }
507
- } catch (e) {
508
- warn(`${exchange} balances unavailable: ${e.message}`);
525
+ }));
526
+ let shown = 0;
527
+ for (const { v, acct, err: err2 } of results) {
528
+ if (!acct) {
529
+ if (venue) warn(`${v} balances unavailable: ${err2}`);
530
+ continue;
531
+ }
532
+ const positions = Array.isArray(acct.positions) ? acct.positions : [];
533
+ info(`${v}: equity ${c.bold(fmtUsd(acct.equity))} \xB7 available ${fmtUsd(acct.available)} \xB7 ${positions.length} open position(s)`);
534
+ for (const p of positions.slice(0, 12)) log(` ${c.bold(String(p.market ?? "?"))} ${p.size ?? ""}`);
535
+ shown++;
509
536
  }
537
+ if (!shown && !venue) warn("No connected venue returned a balance. Deposit funds, or confirm the box is connected.");
510
538
  }
511
539
  async function status() {
512
540
  await summary();
@@ -517,7 +545,7 @@ async function balances(opts = {}) {
517
545
  const pick = pickAgentId(droplets);
518
546
  if (!pick) die("No trading computer connected. Run `boole connect` first.");
519
547
  log(c.bold(`${pick.name}`));
520
- await printBalances(api, pick.id, opts.venue || "hyperliquid");
548
+ await printBalances(api, pick.id, opts.venue);
521
549
  }
522
550
  async function logout() {
523
551
  clearCredentials();
@@ -700,7 +728,7 @@ function init(opts = {}) {
700
728
  }
701
729
 
702
730
  // src/index.ts
703
- var VERSION = "0.2.0";
731
+ var VERSION = "0.2.3";
704
732
  function parse(argv) {
705
733
  const _ = [];
706
734
  const flags = {};
@@ -745,7 +773,7 @@ ${c.bold("Operate the box")}
745
773
  ${c.bold("Other")}
746
774
  help, --help \xB7 version, --version
747
775
 
748
- Env: ${c.dim("BOOLE_API_BASE")} (default https://trade.boole.markets), ${c.dim("BOOLE_HOME")}
776
+ Env: ${c.dim("BOOLE_API_BASE")} (REST API) \xB7 ${c.dim("BOOLE_WEB_BASE")} (login page) \xB7 ${c.dim("BOOLE_HOME")} \xB7 ${c.dim("BOOLE_TIMEOUT_MS")}
749
777
  `;
750
778
  async function main() {
751
779
  const { _, flags } = parse(process.argv.slice(2));
@@ -765,7 +793,7 @@ async function main() {
765
793
  switch (cmd) {
766
794
  case "login":
767
795
  await login({ paste: !!flags.paste });
768
- await summary();
796
+ await summary({ snapshot: false });
769
797
  break;
770
798
  case "logout":
771
799
  await logout();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boole-digital/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "description": "Boole — install, sign in, and operate your trading computer from the terminal (Claude Code, Codex, Gemini).",
5
5
  "type": "module",
6
6
  "bin": { "boole": "dist/index.js" },