@boole-digital/cli 0.1.1 → 0.2.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/index.js +110 -266
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -317,8 +317,8 @@ var BooleApi = class {
|
|
|
317
317
|
// ---- droplets ----------------------------------------------------------
|
|
318
318
|
async listDroplets() {
|
|
319
319
|
const body = await this.request("/api/v1/droplets");
|
|
320
|
-
const
|
|
321
|
-
return Array.isArray(
|
|
320
|
+
const list = unwrap(body);
|
|
321
|
+
return Array.isArray(list) ? list : [];
|
|
322
322
|
}
|
|
323
323
|
async getDroplet(id) {
|
|
324
324
|
const body = await this.request(`/api/v1/droplets/${id}`);
|
|
@@ -372,20 +372,45 @@ function onboardingNotice() {
|
|
|
372
372
|
log(` Deploy your first trading computer there, then come back and run ${c.cyan("boole connect")}.`);
|
|
373
373
|
log("");
|
|
374
374
|
}
|
|
375
|
-
function
|
|
375
|
+
function orient() {
|
|
376
|
+
const creds = loadCredentials();
|
|
377
|
+
const sess = loadSession();
|
|
378
|
+
log("");
|
|
379
|
+
log(`${c.bold("boole")} \u2014 operate your Boole trading computer from the terminal.`);
|
|
376
380
|
log("");
|
|
377
|
-
log(c.bold("How
|
|
378
|
-
log("
|
|
381
|
+
log(c.bold("How it works"));
|
|
382
|
+
log(" This CLI is a thin shell; your trading box is the brain. The flow is:");
|
|
383
|
+
log(` ${c.cyan("log in")} \u2192 ${c.cyan("connect")} \u2192 ${c.cyan("SSH into the box")} \u2192 ${c.cyan("read its operator guide")} \u2192 ${c.cyan("operate in PLAIN ENGLISH")}.`);
|
|
384
|
+
log(` You take the user's request (${c.dim('"buy $100 of BTC"')}) and carry it out on the box through`);
|
|
385
|
+
log(" its harness, which executes and tracks it on the dashboard. The CLI has no trade commands.");
|
|
379
386
|
log("");
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
387
|
+
if (!creds) {
|
|
388
|
+
log(`${c.bold("You are:")} ${c.yellow("not logged in")}.`);
|
|
389
|
+
log(`${c.bold("Do this next:")} ${c.cyan("boole login")}`);
|
|
390
|
+
} else if (!sess) {
|
|
391
|
+
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}`)}`);
|
|
393
|
+
} else {
|
|
394
|
+
log(`${c.bold("You are:")} connected to ${c.bold(sess.agent || sess.ip)} ${c.green("\u2014 ready")}.`);
|
|
395
|
+
log(c.bold("Do this next:"));
|
|
396
|
+
log(` 1. ${c.cyan("boole init")} ${c.dim("# write the agent rules into this folder (CLAUDE/AGENTS/GEMINI)")}`);
|
|
397
|
+
log(` 2. ${c.cyan('boole ssh "cat /srv/cust/OPERATOR.md"')} ${c.dim("# the authoritative guide \u2014 READ IT, then follow it")}`);
|
|
398
|
+
log(` 3. Operate in plain English. Inspect: ${c.cyan("boole status")} \xB7 ${c.cyan("boole logs <name>")} \xB7 ${c.cyan('boole ssh "pm2 ls"')}`);
|
|
399
|
+
}
|
|
384
400
|
log("");
|
|
385
|
-
log(
|
|
386
|
-
log(
|
|
387
|
-
|
|
388
|
-
|
|
401
|
+
log(c.dim("Full command reference: boole help"));
|
|
402
|
+
log("");
|
|
403
|
+
}
|
|
404
|
+
function operatingBrief() {
|
|
405
|
+
log("");
|
|
406
|
+
log(c.bold("You're connected. Just tell your agent what you want \u2014 in plain English."));
|
|
407
|
+
log(` It drives the box's harness (tracked on your dashboard). Guide on the box:`);
|
|
408
|
+
log(` ${c.cyan('boole ssh "cat /srv/cust/OPERATOR.md"')}`);
|
|
409
|
+
log("");
|
|
410
|
+
log(c.bold("Try asking:"));
|
|
411
|
+
log(` \xB7 ${c.dim('"buy $100 of BTC"')} \xB7 ${c.dim('"short $50 of ETH"')}`);
|
|
412
|
+
log(` \xB7 ${c.dim('"run a grid on SOL"')} \xB7 ${c.dim('"watch the BTC price every 10s"')}`);
|
|
413
|
+
log(` \xB7 ${c.dim('"what are my balances?"')} \xB7 ${c.dim('"show my running strategies"')}`);
|
|
389
414
|
log("");
|
|
390
415
|
}
|
|
391
416
|
function printAgents(droplets) {
|
|
@@ -464,24 +489,35 @@ async function summary() {
|
|
|
464
489
|
try {
|
|
465
490
|
const health = await api.getHealth(pick.id);
|
|
466
491
|
ok(`online${health.defaultModel ? c.dim(` \xB7 model ${health.defaultModel}`) : ""}`);
|
|
467
|
-
await printBalances(api, pick.id
|
|
492
|
+
await printBalances(api, pick.id);
|
|
468
493
|
} catch {
|
|
469
494
|
warn("trading computer unreachable (tunnel may be waking up \u2014 retry in a moment)");
|
|
470
495
|
}
|
|
471
496
|
}
|
|
472
497
|
operatingBrief();
|
|
473
498
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
499
|
+
var BALANCE_VENUES = ["hyperliquid", "portara-testnet", "rise", "polymarket"];
|
|
500
|
+
async function printBalances(api, id, venue) {
|
|
501
|
+
const venues = venue ? [venue] : BALANCE_VENUES;
|
|
502
|
+
const results = await Promise.all(venues.map(async (v) => {
|
|
503
|
+
try {
|
|
504
|
+
return { v, acct: await api.getAccount(id, v), err: "" };
|
|
505
|
+
} catch (e) {
|
|
506
|
+
return { v, acct: null, err: e?.message || String(e) };
|
|
481
507
|
}
|
|
482
|
-
}
|
|
483
|
-
|
|
508
|
+
}));
|
|
509
|
+
let shown = 0;
|
|
510
|
+
for (const { v, acct, err: err2 } of results) {
|
|
511
|
+
if (!acct) {
|
|
512
|
+
if (venue) warn(`${v} balances unavailable: ${err2}`);
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
const positions = Array.isArray(acct.positions) ? acct.positions : [];
|
|
516
|
+
info(`${v}: equity ${c.bold(fmtUsd(acct.equity))} \xB7 available ${fmtUsd(acct.available)} \xB7 ${positions.length} open position(s)`);
|
|
517
|
+
for (const p of positions.slice(0, 12)) log(` ${c.bold(String(p.market ?? "?"))} ${p.size ?? ""}`);
|
|
518
|
+
shown++;
|
|
484
519
|
}
|
|
520
|
+
if (!shown && !venue) warn("No connected venue returned a balance. Deposit funds, or confirm the box is connected.");
|
|
485
521
|
}
|
|
486
522
|
async function status() {
|
|
487
523
|
await summary();
|
|
@@ -492,7 +528,7 @@ async function balances(opts = {}) {
|
|
|
492
528
|
const pick = pickAgentId(droplets);
|
|
493
529
|
if (!pick) die("No trading computer connected. Run `boole connect` first.");
|
|
494
530
|
log(c.bold(`${pick.name}`));
|
|
495
|
-
await printBalances(api, pick.id, opts.venue
|
|
531
|
+
await printBalances(api, pick.id, opts.venue);
|
|
496
532
|
}
|
|
497
533
|
async function logout() {
|
|
498
534
|
clearCredentials();
|
|
@@ -594,28 +630,6 @@ function open(s) {
|
|
|
594
630
|
});
|
|
595
631
|
});
|
|
596
632
|
}
|
|
597
|
-
async function runRemote(command) {
|
|
598
|
-
const s = requireSession();
|
|
599
|
-
const conn = await open(s);
|
|
600
|
-
try {
|
|
601
|
-
return await new Promise((resolve2, reject) => {
|
|
602
|
-
conn.exec(command, (err2, stream) => {
|
|
603
|
-
if (err2) return reject(new Error(friendly(err2)));
|
|
604
|
-
let stdout = "";
|
|
605
|
-
let stderr = "";
|
|
606
|
-
stream.on("close", (code) => resolve2({ code: code ?? 0, stdout, stderr }));
|
|
607
|
-
stream.on("data", (d) => {
|
|
608
|
-
stdout += d.toString();
|
|
609
|
-
});
|
|
610
|
-
stream.stderr.on("data", (d) => {
|
|
611
|
-
stderr += d.toString();
|
|
612
|
-
});
|
|
613
|
-
});
|
|
614
|
-
});
|
|
615
|
-
} finally {
|
|
616
|
-
conn.end();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
633
|
async function runRemoteInherit(command) {
|
|
620
634
|
const s = requireSession();
|
|
621
635
|
const conn = await open(s);
|
|
@@ -633,191 +647,44 @@ async function runRemoteInherit(command) {
|
|
|
633
647
|
}
|
|
634
648
|
}
|
|
635
649
|
|
|
636
|
-
// src/strategy.ts
|
|
637
|
-
var NAME_RE = /^[a-z0-9][a-z0-9_-]{0,48}$/;
|
|
638
|
-
var GATE = "http://localhost:3000";
|
|
639
|
-
function paramLiteral(raw) {
|
|
640
|
-
const t = raw.trim();
|
|
641
|
-
if (/^-?\d+(\.\d+)?$/.test(t)) return t;
|
|
642
|
-
if (t === "true" || t === "false") return t;
|
|
643
|
-
if (t.startsWith("[") || t.startsWith("{")) return t;
|
|
644
|
-
return `'${raw.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
645
|
-
}
|
|
646
|
-
function parseParams(pairs) {
|
|
647
|
-
const out = {};
|
|
648
|
-
for (const p of pairs) {
|
|
649
|
-
const i = p.indexOf("=");
|
|
650
|
-
if (i < 0) die(`Bad --param "${p}" (expected KEY=VALUE).`);
|
|
651
|
-
const key = p.slice(0, i).trim();
|
|
652
|
-
if (!/^[A-Z][A-Z0-9_]*$/.test(key)) die(`Bad param key "${key}" (UPPER_SNAKE_CASE).`);
|
|
653
|
-
out[key] = paramLiteral(p.slice(i + 1));
|
|
654
|
-
}
|
|
655
|
-
return out;
|
|
656
|
-
}
|
|
657
|
-
async function strategySpawn(opts) {
|
|
658
|
-
const template = opts.template;
|
|
659
|
-
const name = opts.name;
|
|
660
|
-
if (!template || !name) {
|
|
661
|
-
die("Usage: boole strategy spawn --template <template.js> --name <name> [--param KEY=VALUE]...");
|
|
662
|
-
}
|
|
663
|
-
if (!template.endsWith("_template.js")) die("--template must be a gateway template ending in _template.js (e.g. order_template.js, grid_template.js, loop_template.js).");
|
|
664
|
-
if (!NAME_RE.test(name)) die(`Invalid --name "${name}". Use lowercase letters, digits, "-" or "_" (max 49 chars).`);
|
|
665
|
-
const payload = {
|
|
666
|
-
templateFile: template,
|
|
667
|
-
name,
|
|
668
|
-
params: parseParams(opts.params || []),
|
|
669
|
-
autoStart: opts.autostart !== false
|
|
670
|
-
};
|
|
671
|
-
info(`Spawning ${c.bold(name)} from ${c.bold(template)} through the gateway\u2026`);
|
|
672
|
-
const b64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
673
|
-
const cmd = `printf %s '${b64}' | base64 -d | curl -s --max-time 60 -XPOST ${GATE}/api/strategy/spawn -H 'content-type: application/json' -d @-`;
|
|
674
|
-
const r = await runRemote(cmd);
|
|
675
|
-
let res = null;
|
|
676
|
-
try {
|
|
677
|
-
res = JSON.parse(r.stdout.trim());
|
|
678
|
-
} catch {
|
|
679
|
-
}
|
|
680
|
-
if (!res) {
|
|
681
|
-
err("The gateway did not return a valid response.");
|
|
682
|
-
if (r.stdout.trim()) log(c.dim(` ${r.stdout.trim().slice(0, 400)}`));
|
|
683
|
-
if (r.stderr.trim()) log(c.dim(` ${r.stderr.trim().slice(0, 200)}`));
|
|
684
|
-
process.exit(1);
|
|
685
|
-
}
|
|
686
|
-
if (res.error || res.ok === false) {
|
|
687
|
-
err(`Spawn rejected: ${res.error || "unknown error"}`);
|
|
688
|
-
if (res.suggested_name) log(c.dim(` Try --name ${res.suggested_name}`));
|
|
689
|
-
process.exit(1);
|
|
690
|
-
}
|
|
691
|
-
ok(`Spawned ${c.bold(name)}${res.runId ? c.dim(` \xB7 run ${res.runId}`) : ""} \u2014 it will appear on your dashboard.`);
|
|
692
|
-
const dropped = Array.isArray(res.skipped) ? res.skipped.filter((k) => k !== "RUN_ID") : [];
|
|
693
|
-
if (dropped.length) warn(`Ignored param(s) with no matching template field: ${dropped.join(", ")} \u2014 check the template's required params.`);
|
|
694
|
-
if (typeof res.placed === "number" || typeof res.total === "number") {
|
|
695
|
-
info(`Orders placed: ${res.placed ?? 0}/${res.total ?? 0}${res.failed ? c.yellow(` \xB7 ${res.failed} failed`) : ""}`);
|
|
696
|
-
}
|
|
697
|
-
if (res.firstError) warn(`First error: ${res.firstError}`);
|
|
698
|
-
}
|
|
699
|
-
async function strategyList() {
|
|
700
|
-
const r = await runRemote("pm2 jlist 2>/dev/null");
|
|
701
|
-
let procs = [];
|
|
702
|
-
try {
|
|
703
|
-
procs = JSON.parse(r.stdout.trim());
|
|
704
|
-
} catch {
|
|
705
|
-
}
|
|
706
|
-
if (!Array.isArray(procs)) {
|
|
707
|
-
warn("Could not read process list from the box.");
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
if (!procs.length) {
|
|
711
|
-
info("No strategies running.");
|
|
712
|
-
return;
|
|
713
|
-
}
|
|
714
|
-
log(c.bold("Strategies"));
|
|
715
|
-
for (const p of procs) {
|
|
716
|
-
const st = p.pm2_env?.status || "?";
|
|
717
|
-
const dot = st === "online" ? c.green("\u25CF") : c.dim("\u25CB");
|
|
718
|
-
const up = p.pm2_env?.pm_uptime && st === "online" ? c.dim(` \xB7 up ${Math.max(0, Math.round((Date.now() - p.pm2_env.pm_uptime) / 6e4))}m`) : "";
|
|
719
|
-
const restarts = p.pm2_env?.restart_time ? c.dim(` \xB7 \u21BA${p.pm2_env.restart_time}`) : "";
|
|
720
|
-
log(` ${dot} ${c.bold(p.name)} ${st === "online" ? c.green(st) : c.yellow(st)}${up}${restarts}`);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
async function strategyDoctor(name) {
|
|
724
|
-
const health = await runRemote(`curl -sf --max-time 15 ${GATE}/health`);
|
|
725
|
-
if (health.code !== 0) {
|
|
726
|
-
err("Gateway is not responding on the box.");
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
ok("Gateway healthy.");
|
|
730
|
-
if (!name) return;
|
|
731
|
-
if (!NAME_RE.test(name)) die(`Invalid name "${name}".`);
|
|
732
|
-
const r = await runRemote("pm2 jlist 2>/dev/null");
|
|
733
|
-
let procs = [];
|
|
734
|
-
try {
|
|
735
|
-
procs = JSON.parse(r.stdout.trim());
|
|
736
|
-
} catch {
|
|
737
|
-
}
|
|
738
|
-
const p = procs.find((x) => x.name === name);
|
|
739
|
-
if (!p) {
|
|
740
|
-
warn(`No process named "${name}" \u2014 spawn it with \`boole strategy spawn\`.`);
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
if (p.pm2_env?.status === "online") ok(`${c.bold(name)} is online and tracked \u2014 it will show on the dashboard.`);
|
|
744
|
-
else {
|
|
745
|
-
warn(`${c.bold(name)} is ${p.pm2_env?.status || "not online"} \u2014 check \`boole logs ${name}\`.`);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
|
|
749
650
|
// src/rules.ts
|
|
750
651
|
import { writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
|
|
751
652
|
import { join as join2, resolve } from "node:path";
|
|
752
|
-
var OPERATING_RULES = `# Boole Trading
|
|
753
|
-
|
|
754
|
-
You operate the customer's **live** crypto trading computer by working ON the box
|
|
755
|
-
(over \`boole ssh\`) and using its on-box **harness** \u2014 the gateway's spawn GATE,
|
|
756
|
-
which places and TRACKS every order and strategy on the dashboard. Real money is
|
|
757
|
-
at risk, so be deliberate \u2014 but the user asked you to operate their computer, so
|
|
758
|
-
OPERATE IT. Place the trades they ask for; don't refuse or interrogate over
|
|
759
|
-
routine orders.
|
|
760
|
-
|
|
761
|
-
## What "the harness" is (and why you go through it)
|
|
762
|
-
The box runs a gateway that exposes a spawn GATE. You place trades and run
|
|
763
|
-
strategies by spawning a known-good TEMPLATE through that gate \u2014 NEVER by writing
|
|
764
|
-
files or starting processes yourself. The gate registers the run, wires up
|
|
765
|
-
tracking, sets STRATEGY_NAME/RUN_ID, and starts it, so it always shows on the
|
|
766
|
-
dashboard with accounting. \`boole strategy spawn\` is simply how you call that
|
|
767
|
-
gate over SSH. Going around it (hand-writing a file, raw \`pm2 start\`) makes the
|
|
768
|
-
order **silently** untracked \u2014 that is the one thing you must never do.
|
|
769
|
-
|
|
770
|
-
## Boot (every session)
|
|
771
|
-
1. \`boole status\` \u2014 see who you're logged in as.
|
|
772
|
-
2. \`boole ssh "whoami"\` \u2014 if it connects, you HAVE a usable box; proceed. A
|
|
773
|
-
connected session is enough. Do NOT refuse to work just because \`status\`
|
|
774
|
-
doesn't list a computer for this account \u2014 that only means the web API has
|
|
775
|
-
none on file, not that you can't operate the connected box. If \`boole ssh\`
|
|
776
|
-
fails, tell the user to run \`boole connect\`.
|
|
653
|
+
var OPERATING_RULES = `# Boole Trading Box \u2014 Operating Rules
|
|
777
654
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
655
|
+
You operate a customer's **live** Boole trading computer. The \`boole\` CLI is just
|
|
656
|
+
the shell that connects you to it. Everything about HOW to operate \u2014 placing
|
|
657
|
+
trades, running strategies, the harness, safety \u2014 lives **ON THE BOX** in an
|
|
658
|
+
authoritative operator guide. Read it FIRST and follow it; it overrides anything
|
|
659
|
+
here.
|
|
782
660
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
661
|
+
## Start every session
|
|
662
|
+
1. \`boole status\` \u2014 confirm a box is connected (if not: \`boole login\`, then \`boole connect\`).
|
|
663
|
+
2. Read the on-box operator guide \u2014 **AUTHORITATIVE**:
|
|
664
|
+
\`\`\`
|
|
665
|
+
boole ssh "cat /srv/cust/OPERATOR.md"
|
|
666
|
+
\`\`\`
|
|
667
|
+
3. Operate the box exactly as that guide says.
|
|
786
668
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
669
|
+
## The model
|
|
670
|
+
- The box IS the gateway. You SSH into it with \`boole ssh\` and drive its **harness**.
|
|
671
|
+
- You take the user's request **in plain English** ("buy $100 of BTC") and carry
|
|
672
|
+
it out through the harness, exactly as \`OPERATOR.md\` describes. Real money \u2014 be
|
|
673
|
+
deliberate, but do what the user asks; don't refuse routine orders.
|
|
674
|
+
- The CLI has **no trade commands**. There is no \`boole buy\`, no \`boole strategy\`.
|
|
675
|
+
Trading happens on the box, through the harness, per \`OPERATOR.md\`.
|
|
793
676
|
|
|
794
|
-
##
|
|
795
|
-
boole strategy spawn --template <template.js> --name <name> --param KEY=VALUE ...
|
|
796
|
-
|
|
797
|
-
Templates (required params):
|
|
798
|
-
- order_template.js \u2014 single order: EXCHANGE, MARKET, SIDE(buy|sell), ORDER_TYPE(market|limit), TOTAL_USD, LIMIT_PRICE(0 for market)
|
|
799
|
-
- grid_template.js \u2014 grid: EXCHANGE, SYMBOL, LEVELS_PER_SIDE, ORDER_SIZE_USD, LEVERAGE, MAX_EXPOSURE_USD
|
|
800
|
-
- twap_template.js \u2014 TWAP: EXCHANGE, MARKET, SIDE, TOTAL_USD, DURATION_MIN, SLICES
|
|
801
|
-
- scale_template.js \u2014 ladder: EXCHANGE, MARKET, SIDE, TOTAL_USD, PRICE_LOW, PRICE_HIGH, LEVELS
|
|
802
|
-
- stop_template.js \u2014 conditional (no position yet): EXCHANGE, MARKET, SIDE, STOP_KIND(stop_market|stop_limit|tp_market|tp_limit), TOTAL_USD, TRIGGER_PRICE, LIMIT_PRICE(0 for *_market)
|
|
803
|
-
- loop_template.js \u2014 poll loop / price watcher: EXCHANGE, MARKET, POLL_SEC
|
|
804
|
-
|
|
805
|
-
If the gate names a missing param, add it and retry. If it says a template is not
|
|
806
|
-
found, that one isn't installed on this box \u2014 pick another or ask.
|
|
807
|
-
|
|
808
|
-
## Inspect (read-only) anytime
|
|
677
|
+
## Read-only helpers (fine anytime, from your machine)
|
|
809
678
|
- \`boole status\` / \`boole balances [--venue <v>]\` \u2014 identity, equity, positions
|
|
810
|
-
- \`boole strategy ls\` / \`boole strategy doctor <name>\` \u2014 running strategies + tracking
|
|
811
679
|
- \`boole logs <name>\` \u2014 tail a strategy's logs
|
|
812
|
-
- \`boole ssh "<read-only cmd>"\` \u2014
|
|
680
|
+
- \`boole ssh "<read-only cmd>"\` \u2014 inspect the box (\`pm2 ls\`, \`curl localhost:3000/health\`, \`cat /srv/cust/OPERATOR.md\`)
|
|
813
681
|
|
|
814
|
-
##
|
|
815
|
-
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
strategy). A plain market buy the user asked for is NOT destructive \u2014 place it.
|
|
682
|
+
## Never
|
|
683
|
+
- Never operate the box except through the harness \`OPERATOR.md\` describes
|
|
684
|
+
(no hand-written strategy files, no \`pm2 start\`) \u2014 that silently breaks tracking.
|
|
685
|
+
- Never print \`.env\` / keys; never restart the gateway.
|
|
686
|
+
|
|
687
|
+
**Now run \`boole ssh "cat /srv/cust/OPERATOR.md"\` and follow it.**
|
|
821
688
|
`;
|
|
822
689
|
var FILES = ["CLAUDE.md", "AGENTS.md", "GEMINI.md"];
|
|
823
690
|
function init(opts = {}) {
|
|
@@ -844,33 +711,30 @@ function init(opts = {}) {
|
|
|
844
711
|
}
|
|
845
712
|
|
|
846
713
|
// src/index.ts
|
|
847
|
-
var VERSION = "0.
|
|
714
|
+
var VERSION = "0.2.1";
|
|
848
715
|
function parse(argv) {
|
|
849
716
|
const _ = [];
|
|
850
717
|
const flags = {};
|
|
851
|
-
const add = (k, v) => {
|
|
852
|
-
if (k in flags) {
|
|
853
|
-
const cur = flags[k];
|
|
854
|
-
flags[k] = Array.isArray(cur) ? [...cur, String(v)] : [String(cur), String(v)];
|
|
855
|
-
} else flags[k] = v;
|
|
856
|
-
};
|
|
857
718
|
for (let i = 0; i < argv.length; i++) {
|
|
858
719
|
const a = argv[i];
|
|
859
720
|
if (a.startsWith("--")) {
|
|
860
721
|
const key = a.slice(2);
|
|
861
722
|
if (key.includes("=")) {
|
|
862
723
|
const [k, v] = key.split(/=(.*)/s);
|
|
863
|
-
|
|
724
|
+
flags[k] = v;
|
|
864
725
|
} else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
|
|
865
|
-
|
|
866
|
-
} else
|
|
726
|
+
flags[key] = argv[++i];
|
|
727
|
+
} else flags[key] = true;
|
|
867
728
|
} else _.push(a);
|
|
868
729
|
}
|
|
869
730
|
return { _, flags };
|
|
870
731
|
}
|
|
871
|
-
var str = (f) => typeof f === "string" ? f :
|
|
872
|
-
var
|
|
873
|
-
|
|
732
|
+
var str = (f) => typeof f === "string" ? f : void 0;
|
|
733
|
+
var HELP = `${c.bold("boole")} \u2014 the shell for your Boole trading computer.
|
|
734
|
+
|
|
735
|
+
Log in and connect; your coding agent (${c.bold("Claude Code")} / ${c.bold("Codex")} / ${c.bold("Gemini")}) then operates the
|
|
736
|
+
box over SSH. Trading happens ON the box, through its harness \u2014 the authoritative
|
|
737
|
+
guide lives there: ${c.cyan('boole ssh "cat /srv/cust/OPERATOR.md"')}.
|
|
874
738
|
|
|
875
739
|
${c.bold("Usage")}
|
|
876
740
|
boole <command> [options]
|
|
@@ -878,25 +742,19 @@ ${c.bold("Usage")}
|
|
|
878
742
|
${c.bold("Getting started")}
|
|
879
743
|
login [--paste] Sign in (opens the browser; --paste for manual code)
|
|
880
744
|
provision [--name <n>] Create a new trading computer (requires app onboarding)
|
|
881
|
-
connect [name] Connect to
|
|
745
|
+
connect [name] Connect to a trading computer (caches SSH access)
|
|
882
746
|
init [dir] [--force] Scaffold an agent workspace (CLAUDE.md / AGENTS.md / GEMINI.md)
|
|
883
|
-
|
|
884
|
-
status Who you are + your trading computers + a live snapshot
|
|
885
|
-
balances [--venue <v>] Equity + open positions for the connected trading computer
|
|
747
|
+
status Who you are + your trading computer + how to operate it
|
|
886
748
|
logout Sign out and clear the cached session
|
|
887
749
|
|
|
888
|
-
${c.bold("Operate")}
|
|
889
|
-
ssh "<command>" Run a
|
|
750
|
+
${c.bold("Operate the box")}
|
|
751
|
+
ssh "<command>" Run a command on the connected box (this is how you operate it)
|
|
890
752
|
logs [name] Tail a strategy's logs
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
strategy spawn --template <t.js> --name <n> [--param KEY=VALUE]... [--no-autostart]
|
|
894
|
-
strategy ls List running strategies
|
|
895
|
-
strategy doctor [name] Gateway health + a strategy's tracking status
|
|
753
|
+
balances [--venue <v>] Equity + open positions
|
|
754
|
+
computers List your trading computers
|
|
896
755
|
|
|
897
756
|
${c.bold("Other")}
|
|
898
|
-
help, --help
|
|
899
|
-
version, --version Show version
|
|
757
|
+
help, --help \xB7 version, --version
|
|
900
758
|
|
|
901
759
|
Env: ${c.dim("BOOLE_API_BASE")} (default https://trade.boole.markets), ${c.dim("BOOLE_HOME")}
|
|
902
760
|
`;
|
|
@@ -907,10 +765,14 @@ async function main() {
|
|
|
907
765
|
log(`boole ${VERSION}`);
|
|
908
766
|
return;
|
|
909
767
|
}
|
|
910
|
-
if (
|
|
768
|
+
if (cmd === "help" || flags.help) {
|
|
911
769
|
log(HELP);
|
|
912
770
|
return;
|
|
913
771
|
}
|
|
772
|
+
if (!cmd) {
|
|
773
|
+
orient();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
914
776
|
switch (cmd) {
|
|
915
777
|
case "login":
|
|
916
778
|
await login({ paste: !!flags.paste });
|
|
@@ -951,24 +813,6 @@ async function main() {
|
|
|
951
813
|
const remote = proc ? `pm2 logs '${proc.replace(/'/g, "")}' --lines 50 --nostream` : "pm2 logs --lines 40 --nostream";
|
|
952
814
|
process.exit(await runRemoteInherit(remote));
|
|
953
815
|
}
|
|
954
|
-
case "strategy": {
|
|
955
|
-
const sub = _[1];
|
|
956
|
-
if (sub === "spawn") {
|
|
957
|
-
await strategySpawn({
|
|
958
|
-
template: str(flags.template),
|
|
959
|
-
name: str(flags.name),
|
|
960
|
-
params: list(flags.param),
|
|
961
|
-
autostart: !flags["no-autostart"]
|
|
962
|
-
});
|
|
963
|
-
} else if (sub === "ls" || sub === "list") {
|
|
964
|
-
await strategyList();
|
|
965
|
-
} else if (sub === "doctor") {
|
|
966
|
-
await strategyDoctor(_[2]);
|
|
967
|
-
} else {
|
|
968
|
-
die("Usage: boole strategy <spawn|ls|doctor> \u2026 (see `boole help`)");
|
|
969
|
-
}
|
|
970
|
-
break;
|
|
971
|
-
}
|
|
972
816
|
default:
|
|
973
817
|
err(`Unknown command: ${cmd}`);
|
|
974
818
|
log(HELP);
|
package/package.json
CHANGED