@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.
- package/dist/index.js +68 -40
- 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://
|
|
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
|
|
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:
|
|
152
|
-
<div><h1
|
|
153
|
-
<a class="btn" href="${url}">
|
|
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 = `${
|
|
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),
|
|
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 = `${
|
|
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
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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(
|
|
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 ${
|
|
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("
|
|
407
|
-
log(`
|
|
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(
|
|
413
|
-
log(
|
|
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
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
}
|
|
508
|
-
|
|
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
|
|
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.
|
|
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")} (
|
|
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