@boole-digital/cli 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +87 -254
- 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 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.`);
|
|
380
|
+
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.");
|
|
386
|
+
log("");
|
|
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
|
+
}
|
|
400
|
+
log("");
|
|
401
|
+
log(c.dim("Full command reference: boole help"));
|
|
402
|
+
log("");
|
|
403
|
+
}
|
|
375
404
|
function operatingBrief() {
|
|
376
405
|
log("");
|
|
377
406
|
log(c.bold("How to operate this computer"));
|
|
378
|
-
log(
|
|
379
|
-
log("");
|
|
380
|
-
log(` ${c.dim("# buy $100 of BTC (market)")}`);
|
|
381
|
-
log(c.cyan(" boole strategy spawn --template order_template.js --name btc-buy \\"));
|
|
382
|
-
log(c.cyan(" --param EXCHANGE=hyperliquid --param MARKET=BTC --param SIDE=buy \\"));
|
|
383
|
-
log(c.cyan(" --param ORDER_TYPE=market --param TOTAL_USD=100 --param LIMIT_PRICE=0"));
|
|
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"')}.`);
|
|
384
409
|
log("");
|
|
385
|
-
log(`
|
|
386
|
-
log(`
|
|
387
|
-
log(`
|
|
388
|
-
log(`
|
|
410
|
+
log(` The authoritative operating guide lives on the box:`);
|
|
411
|
+
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.")}`);
|
|
389
414
|
log("");
|
|
390
415
|
}
|
|
391
416
|
function printAgents(droplets) {
|
|
@@ -594,28 +619,6 @@ function open(s) {
|
|
|
594
619
|
});
|
|
595
620
|
});
|
|
596
621
|
}
|
|
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
622
|
async function runRemoteInherit(command) {
|
|
620
623
|
const s = requireSession();
|
|
621
624
|
const conn = await open(s);
|
|
@@ -633,191 +636,44 @@ async function runRemoteInherit(command) {
|
|
|
633
636
|
}
|
|
634
637
|
}
|
|
635
638
|
|
|
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
639
|
// src/rules.ts
|
|
750
640
|
import { writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
|
|
751
641
|
import { join as join2, resolve } from "node:path";
|
|
752
|
-
var OPERATING_RULES = `# Boole Trading
|
|
642
|
+
var OPERATING_RULES = `# Boole Trading Box \u2014 Operating Rules
|
|
753
643
|
|
|
754
|
-
You operate
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
routine orders.
|
|
644
|
+
You operate a customer's **live** Boole trading computer. The \`boole\` CLI is just
|
|
645
|
+
the shell that connects you to it. Everything about HOW to operate \u2014 placing
|
|
646
|
+
trades, running strategies, the harness, safety \u2014 lives **ON THE BOX** in an
|
|
647
|
+
authoritative operator guide. Read it FIRST and follow it; it overrides anything
|
|
648
|
+
here.
|
|
760
649
|
|
|
761
|
-
##
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
order **silently** untracked \u2014 that is the one thing you must never do.
|
|
650
|
+
## Start every session
|
|
651
|
+
1. \`boole status\` \u2014 confirm a box is connected (if not: \`boole login\`, then \`boole connect\`).
|
|
652
|
+
2. Read the on-box operator guide \u2014 **AUTHORITATIVE**:
|
|
653
|
+
\`\`\`
|
|
654
|
+
boole ssh "cat /srv/cust/OPERATOR.md"
|
|
655
|
+
\`\`\`
|
|
656
|
+
3. Operate the box exactly as that guide says.
|
|
769
657
|
|
|
770
|
-
##
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
658
|
+
## The model
|
|
659
|
+
- The box IS the gateway. You SSH into it with \`boole ssh\` and drive its **harness**.
|
|
660
|
+
- You take the user's request **in plain English** ("buy $100 of BTC") and carry
|
|
661
|
+
it out through the harness, exactly as \`OPERATOR.md\` describes. Real money \u2014 be
|
|
662
|
+
deliberate, but do what the user asks; don't refuse routine orders.
|
|
663
|
+
- The CLI has **no trade commands**. There is no \`boole buy\`, no \`boole strategy\`.
|
|
664
|
+
Trading happens on the box, through the harness, per \`OPERATOR.md\`.
|
|
777
665
|
|
|
778
|
-
##
|
|
779
|
-
"buy/sell $X of <coin>" is one market order. Place it through the gate with
|
|
780
|
-
order_template. Do NOT quiz the user about venue/leverage for a routine order \u2014
|
|
781
|
-
use the defaults and report the fill:
|
|
782
|
-
|
|
783
|
-
boole strategy spawn --template order_template.js --name <coin>-buy \\
|
|
784
|
-
--param EXCHANGE=hyperliquid --param MARKET=<COIN> --param SIDE=buy \\
|
|
785
|
-
--param ORDER_TYPE=market --param TOTAL_USD=<X> --param LIMIT_PRICE=0
|
|
786
|
-
|
|
787
|
-
Defaults when unspecified: EXCHANGE=hyperliquid, ORDER_TYPE=market, LIMIT_PRICE=0,
|
|
788
|
-
no leverage. Sell with SIDE=sell. Limit order: ORDER_TYPE=limit + LIMIT_PRICE=<p>.
|
|
789
|
-
Pick a unique --name (if the gate says it exists, add a suffix). After it returns,
|
|
790
|
-
tell the user what filled. Ask FIRST only if genuinely ambiguous (which coin? which
|
|
791
|
-
account has funds?) or clearly risky (size far beyond their balance, leverage they
|
|
792
|
-
didn't request) \u2014 a plain market buy the user requested is NOT one of those.
|
|
793
|
-
|
|
794
|
-
## Running a strategy \u2014 same gate, different template
|
|
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
|
|
666
|
+
## Read-only helpers (fine anytime, from your machine)
|
|
809
667
|
- \`boole status\` / \`boole balances [--venue <v>]\` \u2014 identity, equity, positions
|
|
810
|
-
- \`boole strategy ls\` / \`boole strategy doctor <name>\` \u2014 running strategies + tracking
|
|
811
668
|
- \`boole logs <name>\` \u2014 tail a strategy's logs
|
|
812
|
-
- \`boole ssh "<read-only cmd>"\` \u2014
|
|
669
|
+
- \`boole ssh "<read-only cmd>"\` \u2014 inspect the box (\`pm2 ls\`, \`curl localhost:3000/health\`, \`cat /srv/cust/OPERATOR.md\`)
|
|
670
|
+
|
|
671
|
+
## Never
|
|
672
|
+
- Never operate the box except through the harness \`OPERATOR.md\` describes
|
|
673
|
+
(no hand-written strategy files, no \`pm2 start\`) \u2014 that silently breaks tracking.
|
|
674
|
+
- Never print \`.env\` / keys; never restart the gateway.
|
|
813
675
|
|
|
814
|
-
|
|
815
|
-
- Place trades + strategies ONLY through the gate (\`boole strategy spawn\`). NEVER
|
|
816
|
-
write strategy files over SSH or \`pm2 start\` by hand \u2014 that bypasses tracking and
|
|
817
|
-
the order won't appear on the dashboard.
|
|
818
|
-
- Never print or exfiltrate .env, keys, or credentials. Never restart the gateway.
|
|
819
|
-
- Ask before genuinely destructive actions (closing positions, stopping a running
|
|
820
|
-
strategy). A plain market buy the user asked for is NOT destructive \u2014 place it.
|
|
676
|
+
**Now run \`boole ssh "cat /srv/cust/OPERATOR.md"\` and follow it.**
|
|
821
677
|
`;
|
|
822
678
|
var FILES = ["CLAUDE.md", "AGENTS.md", "GEMINI.md"];
|
|
823
679
|
function init(opts = {}) {
|
|
@@ -844,33 +700,30 @@ function init(opts = {}) {
|
|
|
844
700
|
}
|
|
845
701
|
|
|
846
702
|
// src/index.ts
|
|
847
|
-
var VERSION = "0.
|
|
703
|
+
var VERSION = "0.2.0";
|
|
848
704
|
function parse(argv) {
|
|
849
705
|
const _ = [];
|
|
850
706
|
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
707
|
for (let i = 0; i < argv.length; i++) {
|
|
858
708
|
const a = argv[i];
|
|
859
709
|
if (a.startsWith("--")) {
|
|
860
710
|
const key = a.slice(2);
|
|
861
711
|
if (key.includes("=")) {
|
|
862
712
|
const [k, v] = key.split(/=(.*)/s);
|
|
863
|
-
|
|
713
|
+
flags[k] = v;
|
|
864
714
|
} else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
|
|
865
|
-
|
|
866
|
-
} else
|
|
715
|
+
flags[key] = argv[++i];
|
|
716
|
+
} else flags[key] = true;
|
|
867
717
|
} else _.push(a);
|
|
868
718
|
}
|
|
869
719
|
return { _, flags };
|
|
870
720
|
}
|
|
871
|
-
var str = (f) => typeof f === "string" ? f :
|
|
872
|
-
var
|
|
873
|
-
|
|
721
|
+
var str = (f) => typeof f === "string" ? f : void 0;
|
|
722
|
+
var HELP = `${c.bold("boole")} \u2014 the shell for your Boole trading computer.
|
|
723
|
+
|
|
724
|
+
Log in and connect; your coding agent (${c.bold("Claude Code")} / ${c.bold("Codex")} / ${c.bold("Gemini")}) then operates the
|
|
725
|
+
box over SSH. Trading happens ON the box, through its harness \u2014 the authoritative
|
|
726
|
+
guide lives there: ${c.cyan('boole ssh "cat /srv/cust/OPERATOR.md"')}.
|
|
874
727
|
|
|
875
728
|
${c.bold("Usage")}
|
|
876
729
|
boole <command> [options]
|
|
@@ -878,25 +731,19 @@ ${c.bold("Usage")}
|
|
|
878
731
|
${c.bold("Getting started")}
|
|
879
732
|
login [--paste] Sign in (opens the browser; --paste for manual code)
|
|
880
733
|
provision [--name <n>] Create a new trading computer (requires app onboarding)
|
|
881
|
-
connect [name] Connect to
|
|
734
|
+
connect [name] Connect to a trading computer (caches SSH access)
|
|
882
735
|
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
|
|
736
|
+
status Who you are + your trading computer + how to operate it
|
|
886
737
|
logout Sign out and clear the cached session
|
|
887
738
|
|
|
888
|
-
${c.bold("Operate")}
|
|
889
|
-
ssh "<command>" Run a
|
|
739
|
+
${c.bold("Operate the box")}
|
|
740
|
+
ssh "<command>" Run a command on the connected box (this is how you operate it)
|
|
890
741
|
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
|
|
742
|
+
balances [--venue <v>] Equity + open positions
|
|
743
|
+
computers List your trading computers
|
|
896
744
|
|
|
897
745
|
${c.bold("Other")}
|
|
898
|
-
help, --help
|
|
899
|
-
version, --version Show version
|
|
746
|
+
help, --help \xB7 version, --version
|
|
900
747
|
|
|
901
748
|
Env: ${c.dim("BOOLE_API_BASE")} (default https://trade.boole.markets), ${c.dim("BOOLE_HOME")}
|
|
902
749
|
`;
|
|
@@ -907,10 +754,14 @@ async function main() {
|
|
|
907
754
|
log(`boole ${VERSION}`);
|
|
908
755
|
return;
|
|
909
756
|
}
|
|
910
|
-
if (
|
|
757
|
+
if (cmd === "help" || flags.help) {
|
|
911
758
|
log(HELP);
|
|
912
759
|
return;
|
|
913
760
|
}
|
|
761
|
+
if (!cmd) {
|
|
762
|
+
orient();
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
914
765
|
switch (cmd) {
|
|
915
766
|
case "login":
|
|
916
767
|
await login({ paste: !!flags.paste });
|
|
@@ -951,24 +802,6 @@ async function main() {
|
|
|
951
802
|
const remote = proc ? `pm2 logs '${proc.replace(/'/g, "")}' --lines 50 --nostream` : "pm2 logs --lines 40 --nostream";
|
|
952
803
|
process.exit(await runRemoteInherit(remote));
|
|
953
804
|
}
|
|
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
805
|
default:
|
|
973
806
|
err(`Unknown command: ${cmd}`);
|
|
974
807
|
log(HELP);
|
package/package.json
CHANGED