@boole-digital/cli 0.1.0 → 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 +123 -264
- package/package.json +2 -2
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,6 +372,47 @@ 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
|
+
}
|
|
404
|
+
function operatingBrief() {
|
|
405
|
+
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:`);
|
|
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.")}`);
|
|
414
|
+
log("");
|
|
415
|
+
}
|
|
375
416
|
function printAgents(droplets) {
|
|
376
417
|
if (!droplets.length) {
|
|
377
418
|
onboardingNotice();
|
|
@@ -414,13 +455,27 @@ async function summary() {
|
|
|
414
455
|
ok(`Logged in as ${c.bold(creds.user.email || creds.user.id)}${creds.user.role ? c.dim(` \xB7 ${creds.user.role}`) : ""}`);
|
|
415
456
|
const api = new BooleApi();
|
|
416
457
|
let droplets = [];
|
|
458
|
+
let listErr = "";
|
|
417
459
|
try {
|
|
418
460
|
droplets = await api.listDroplets();
|
|
419
461
|
} catch (e) {
|
|
420
|
-
|
|
421
|
-
return;
|
|
462
|
+
listErr = e?.message || String(e);
|
|
422
463
|
}
|
|
423
464
|
if (!droplets.length) {
|
|
465
|
+
const sess = loadSession();
|
|
466
|
+
if (sess?.ip) {
|
|
467
|
+
log("");
|
|
468
|
+
ok(`Connected to ${c.bold(sess.agent || sess.ip)} ${c.dim(`(${sess.ip})`)} ${c.dim("\xB7 cached session")}`);
|
|
469
|
+
if (listErr) log(c.dim(` (couldn't reach the account API: ${listErr})`));
|
|
470
|
+
else log(c.dim(` This account has no computer on file, but \`boole ssh\` + \`boole strategy \u2026\` work against the connected box.`));
|
|
471
|
+
log(c.dim(` If you expected a computer here, you may be logged in as a different account than the one that owns it.`));
|
|
472
|
+
operatingBrief();
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (listErr) {
|
|
476
|
+
warn(`Could not list trading computers: ${listErr}`);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
424
479
|
onboardingNotice();
|
|
425
480
|
return;
|
|
426
481
|
}
|
|
@@ -428,17 +483,18 @@ async function summary() {
|
|
|
428
483
|
log(c.bold(`Trading computers (${droplets.length})`));
|
|
429
484
|
printAgents(droplets);
|
|
430
485
|
const pick = pickAgentId(droplets);
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
486
|
+
if (pick) {
|
|
487
|
+
log("");
|
|
488
|
+
log(c.bold(`Snapshot \u2014 ${pick.name}`));
|
|
489
|
+
try {
|
|
490
|
+
const health = await api.getHealth(pick.id);
|
|
491
|
+
ok(`online${health.defaultModel ? c.dim(` \xB7 model ${health.defaultModel}`) : ""}`);
|
|
492
|
+
await printBalances(api, pick.id, "hyperliquid");
|
|
493
|
+
} catch {
|
|
494
|
+
warn("trading computer unreachable (tunnel may be waking up \u2014 retry in a moment)");
|
|
495
|
+
}
|
|
440
496
|
}
|
|
441
|
-
|
|
497
|
+
operatingBrief();
|
|
442
498
|
}
|
|
443
499
|
async function printBalances(api, id, exchange) {
|
|
444
500
|
try {
|
|
@@ -563,28 +619,6 @@ function open(s) {
|
|
|
563
619
|
});
|
|
564
620
|
});
|
|
565
621
|
}
|
|
566
|
-
async function runRemote(command) {
|
|
567
|
-
const s = requireSession();
|
|
568
|
-
const conn = await open(s);
|
|
569
|
-
try {
|
|
570
|
-
return await new Promise((resolve2, reject) => {
|
|
571
|
-
conn.exec(command, (err2, stream) => {
|
|
572
|
-
if (err2) return reject(new Error(friendly(err2)));
|
|
573
|
-
let stdout = "";
|
|
574
|
-
let stderr = "";
|
|
575
|
-
stream.on("close", (code) => resolve2({ code: code ?? 0, stdout, stderr }));
|
|
576
|
-
stream.on("data", (d) => {
|
|
577
|
-
stdout += d.toString();
|
|
578
|
-
});
|
|
579
|
-
stream.stderr.on("data", (d) => {
|
|
580
|
-
stderr += d.toString();
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
});
|
|
584
|
-
} finally {
|
|
585
|
-
conn.end();
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
622
|
async function runRemoteInherit(command) {
|
|
589
623
|
const s = requireSession();
|
|
590
624
|
const conn = await open(s);
|
|
@@ -602,196 +636,44 @@ async function runRemoteInherit(command) {
|
|
|
602
636
|
}
|
|
603
637
|
}
|
|
604
638
|
|
|
605
|
-
// src/strategy.ts
|
|
606
|
-
var NAME_RE = /^[a-z0-9][a-z0-9_-]{0,48}$/;
|
|
607
|
-
var GATE = "http://localhost:3000";
|
|
608
|
-
function paramLiteral(raw) {
|
|
609
|
-
const t = raw.trim();
|
|
610
|
-
if (/^-?\d+(\.\d+)?$/.test(t)) return t;
|
|
611
|
-
if (t === "true" || t === "false") return t;
|
|
612
|
-
if (t.startsWith("[") || t.startsWith("{")) return t;
|
|
613
|
-
return `'${raw.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
614
|
-
}
|
|
615
|
-
function parseParams(pairs) {
|
|
616
|
-
const out = {};
|
|
617
|
-
for (const p of pairs) {
|
|
618
|
-
const i = p.indexOf("=");
|
|
619
|
-
if (i < 0) die(`Bad --param "${p}" (expected KEY=VALUE).`);
|
|
620
|
-
const key = p.slice(0, i).trim();
|
|
621
|
-
if (!/^[A-Z][A-Z0-9_]*$/.test(key)) die(`Bad param key "${key}" (UPPER_SNAKE_CASE).`);
|
|
622
|
-
out[key] = paramLiteral(p.slice(i + 1));
|
|
623
|
-
}
|
|
624
|
-
return out;
|
|
625
|
-
}
|
|
626
|
-
async function strategySpawn(opts) {
|
|
627
|
-
const template = opts.template;
|
|
628
|
-
const name = opts.name;
|
|
629
|
-
if (!template || !name) {
|
|
630
|
-
die("Usage: boole strategy spawn --template <template.js> --name <name> [--param KEY=VALUE]...");
|
|
631
|
-
}
|
|
632
|
-
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).");
|
|
633
|
-
if (!NAME_RE.test(name)) die(`Invalid --name "${name}". Use lowercase letters, digits, "-" or "_" (max 49 chars).`);
|
|
634
|
-
const payload = {
|
|
635
|
-
templateFile: template,
|
|
636
|
-
name,
|
|
637
|
-
params: parseParams(opts.params || []),
|
|
638
|
-
autoStart: opts.autostart !== false
|
|
639
|
-
};
|
|
640
|
-
info(`Spawning ${c.bold(name)} from ${c.bold(template)} through the gateway\u2026`);
|
|
641
|
-
const b64 = Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
642
|
-
const cmd = `printf %s '${b64}' | base64 -d | curl -s --max-time 60 -XPOST ${GATE}/api/strategy/spawn -H 'content-type: application/json' -d @-`;
|
|
643
|
-
const r = await runRemote(cmd);
|
|
644
|
-
let res = null;
|
|
645
|
-
try {
|
|
646
|
-
res = JSON.parse(r.stdout.trim());
|
|
647
|
-
} catch {
|
|
648
|
-
}
|
|
649
|
-
if (!res) {
|
|
650
|
-
err("The gateway did not return a valid response.");
|
|
651
|
-
if (r.stdout.trim()) log(c.dim(` ${r.stdout.trim().slice(0, 400)}`));
|
|
652
|
-
if (r.stderr.trim()) log(c.dim(` ${r.stderr.trim().slice(0, 200)}`));
|
|
653
|
-
process.exit(1);
|
|
654
|
-
}
|
|
655
|
-
if (res.error || res.ok === false) {
|
|
656
|
-
err(`Spawn rejected: ${res.error || "unknown error"}`);
|
|
657
|
-
if (res.suggested_name) log(c.dim(` Try --name ${res.suggested_name}`));
|
|
658
|
-
process.exit(1);
|
|
659
|
-
}
|
|
660
|
-
ok(`Spawned ${c.bold(name)}${res.runId ? c.dim(` \xB7 run ${res.runId}`) : ""} \u2014 it will appear on your dashboard.`);
|
|
661
|
-
const dropped = Array.isArray(res.skipped) ? res.skipped.filter((k) => k !== "RUN_ID") : [];
|
|
662
|
-
if (dropped.length) warn(`Ignored param(s) with no matching template field: ${dropped.join(", ")} \u2014 check the template's required params.`);
|
|
663
|
-
if (typeof res.placed === "number" || typeof res.total === "number") {
|
|
664
|
-
info(`Orders placed: ${res.placed ?? 0}/${res.total ?? 0}${res.failed ? c.yellow(` \xB7 ${res.failed} failed`) : ""}`);
|
|
665
|
-
}
|
|
666
|
-
if (res.firstError) warn(`First error: ${res.firstError}`);
|
|
667
|
-
}
|
|
668
|
-
async function strategyList() {
|
|
669
|
-
const r = await runRemote("pm2 jlist 2>/dev/null");
|
|
670
|
-
let procs = [];
|
|
671
|
-
try {
|
|
672
|
-
procs = JSON.parse(r.stdout.trim());
|
|
673
|
-
} catch {
|
|
674
|
-
}
|
|
675
|
-
if (!Array.isArray(procs)) {
|
|
676
|
-
warn("Could not read process list from the box.");
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
if (!procs.length) {
|
|
680
|
-
info("No strategies running.");
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
log(c.bold("Strategies"));
|
|
684
|
-
for (const p of procs) {
|
|
685
|
-
const st = p.pm2_env?.status || "?";
|
|
686
|
-
const dot = st === "online" ? c.green("\u25CF") : c.dim("\u25CB");
|
|
687
|
-
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`) : "";
|
|
688
|
-
const restarts = p.pm2_env?.restart_time ? c.dim(` \xB7 \u21BA${p.pm2_env.restart_time}`) : "";
|
|
689
|
-
log(` ${dot} ${c.bold(p.name)} ${st === "online" ? c.green(st) : c.yellow(st)}${up}${restarts}`);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
async function strategyDoctor(name) {
|
|
693
|
-
const health = await runRemote(`curl -sf --max-time 15 ${GATE}/health`);
|
|
694
|
-
if (health.code !== 0) {
|
|
695
|
-
err("Gateway is not responding on the box.");
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
ok("Gateway healthy.");
|
|
699
|
-
if (!name) return;
|
|
700
|
-
if (!NAME_RE.test(name)) die(`Invalid name "${name}".`);
|
|
701
|
-
const r = await runRemote("pm2 jlist 2>/dev/null");
|
|
702
|
-
let procs = [];
|
|
703
|
-
try {
|
|
704
|
-
procs = JSON.parse(r.stdout.trim());
|
|
705
|
-
} catch {
|
|
706
|
-
}
|
|
707
|
-
const p = procs.find((x) => x.name === name);
|
|
708
|
-
if (!p) {
|
|
709
|
-
warn(`No process named "${name}" \u2014 spawn it with \`boole strategy spawn\`.`);
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
if (p.pm2_env?.status === "online") ok(`${c.bold(name)} is online and tracked \u2014 it will show on the dashboard.`);
|
|
713
|
-
else {
|
|
714
|
-
warn(`${c.bold(name)} is ${p.pm2_env?.status || "not online"} \u2014 check \`boole logs ${name}\`.`);
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
639
|
// src/rules.ts
|
|
719
640
|
import { writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
|
|
720
641
|
import { join as join2, resolve } from "node:path";
|
|
721
|
-
var OPERATING_RULES = `# Boole Trading
|
|
722
|
-
|
|
723
|
-
You operate a customer's **live**
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
--param KEY=VALUE --param KEY=VALUE ...
|
|
747
|
-
|
|
748
|
-
- This registers the run and starts it tracked, so it appears on the dashboard.
|
|
749
|
-
- \`--name\`: lowercase letters, digits, "-" or "_" (e.g. btc-grid).
|
|
750
|
-
- \`--param\`: pass raw values (EXCHANGE=hyperliquid, TOTAL_USD=50). The CLI quotes them.
|
|
751
|
-
- Verify: \`boole strategy doctor <name>\` \u2192 should report online and tracked.
|
|
752
|
-
|
|
753
|
-
### Templates (required params)
|
|
754
|
-
- order_template.js \u2014 single order: EXCHANGE, MARKET, SIDE(buy|sell), ORDER_TYPE(market|limit), TOTAL_USD, LIMIT_PRICE(0 for market)
|
|
755
|
-
- grid_template.js \u2014 grid: EXCHANGE, SYMBOL, LEVELS_PER_SIDE, ORDER_SIZE_USD, LEVERAGE, MAX_EXPOSURE_USD
|
|
756
|
-
- twap_template.js \u2014 TWAP: EXCHANGE, MARKET, SIDE, TOTAL_USD, DURATION_MIN, SLICES
|
|
757
|
-
- scale_template.js \u2014 ladder: EXCHANGE, MARKET, SIDE, TOTAL_USD, PRICE_LOW, PRICE_HIGH, LEVELS
|
|
758
|
-
- 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)
|
|
759
|
-
- loop_template.js \u2014 generic poll loop / price watcher: EXCHANGE, MARKET, POLL_SEC
|
|
760
|
-
|
|
761
|
-
Example:
|
|
762
|
-
|
|
763
|
-
boole strategy spawn --template loop_template.js --name btc-watch \\
|
|
764
|
-
--param EXCHANGE=hyperliquid --param MARKET=BTC --param POLL_SEC=10
|
|
765
|
-
|
|
766
|
-
If you omit a required param, the gate returns an error naming it \u2014 fix and retry.
|
|
767
|
-
If the gate says a template is not found, it is not installed on this box \u2014 pick
|
|
768
|
-
another from the list or ask the user.
|
|
769
|
-
Only use a template above. Do NOT hand-roll custom strategy files. If you need
|
|
770
|
-
something no template covers, STOP and ask the user.
|
|
771
|
-
|
|
772
|
-
## Allowed \u2014 read-only inspection
|
|
642
|
+
var OPERATING_RULES = `# Boole Trading Box \u2014 Operating Rules
|
|
643
|
+
|
|
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.
|
|
649
|
+
|
|
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.
|
|
657
|
+
|
|
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\`.
|
|
665
|
+
|
|
666
|
+
## Read-only helpers (fine anytime, from your machine)
|
|
773
667
|
- \`boole status\` / \`boole balances [--venue <v>]\` \u2014 identity, equity, positions
|
|
774
|
-
- \`boole strategy ls\` \u2014 running strategies
|
|
775
|
-
- \`boole strategy doctor <name>\` \u2014 health + tracking check
|
|
776
668
|
- \`boole logs <name>\` \u2014 tail a strategy's logs
|
|
777
|
-
- \`boole ssh "<read-only
|
|
669
|
+
- \`boole ssh "<read-only cmd>"\` \u2014 inspect the box (\`pm2 ls\`, \`curl localhost:3000/health\`, \`cat /srv/cust/OPERATOR.md\`)
|
|
778
670
|
|
|
779
|
-
##
|
|
780
|
-
-
|
|
781
|
-
-
|
|
782
|
-
-
|
|
783
|
-
- \`boole ssh\` is for READ-ONLY inspection. To change the box, use a \`boole\` verb.
|
|
784
|
-
- If there is no verb for a change you need, STOP and ask. A missing verb is not permission to freelance.
|
|
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.
|
|
785
675
|
|
|
786
|
-
|
|
787
|
-
- Trading is live. Prefer capital preservation. Confirm exchange state before acting.
|
|
788
|
-
- Never print or exfiltrate .env, API keys, or credentials.
|
|
789
|
-
- Never restart the gateway.
|
|
790
|
-
- Ask the user before destructive actions (closing positions, stopping strategies).
|
|
791
|
-
|
|
792
|
-
## Notes
|
|
793
|
-
- Some gateway internals are intentionally not readable from your account \u2014 by design. Use the \`boole\` verbs; you don't need them.
|
|
794
|
-
- A commit is not done until \`boole strategy doctor\` confirms it. If it did not register, fix it before moving on.
|
|
676
|
+
**Now run \`boole ssh "cat /srv/cust/OPERATOR.md"\` and follow it.**
|
|
795
677
|
`;
|
|
796
678
|
var FILES = ["CLAUDE.md", "AGENTS.md", "GEMINI.md"];
|
|
797
679
|
function init(opts = {}) {
|
|
@@ -818,33 +700,30 @@ function init(opts = {}) {
|
|
|
818
700
|
}
|
|
819
701
|
|
|
820
702
|
// src/index.ts
|
|
821
|
-
var VERSION = "0.
|
|
703
|
+
var VERSION = "0.2.0";
|
|
822
704
|
function parse(argv) {
|
|
823
705
|
const _ = [];
|
|
824
706
|
const flags = {};
|
|
825
|
-
const add = (k, v) => {
|
|
826
|
-
if (k in flags) {
|
|
827
|
-
const cur = flags[k];
|
|
828
|
-
flags[k] = Array.isArray(cur) ? [...cur, String(v)] : [String(cur), String(v)];
|
|
829
|
-
} else flags[k] = v;
|
|
830
|
-
};
|
|
831
707
|
for (let i = 0; i < argv.length; i++) {
|
|
832
708
|
const a = argv[i];
|
|
833
709
|
if (a.startsWith("--")) {
|
|
834
710
|
const key = a.slice(2);
|
|
835
711
|
if (key.includes("=")) {
|
|
836
712
|
const [k, v] = key.split(/=(.*)/s);
|
|
837
|
-
|
|
713
|
+
flags[k] = v;
|
|
838
714
|
} else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
|
|
839
|
-
|
|
840
|
-
} else
|
|
715
|
+
flags[key] = argv[++i];
|
|
716
|
+
} else flags[key] = true;
|
|
841
717
|
} else _.push(a);
|
|
842
718
|
}
|
|
843
719
|
return { _, flags };
|
|
844
720
|
}
|
|
845
|
-
var str = (f) => typeof f === "string" ? f :
|
|
846
|
-
var
|
|
847
|
-
|
|
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"')}.
|
|
848
727
|
|
|
849
728
|
${c.bold("Usage")}
|
|
850
729
|
boole <command> [options]
|
|
@@ -852,25 +731,19 @@ ${c.bold("Usage")}
|
|
|
852
731
|
${c.bold("Getting started")}
|
|
853
732
|
login [--paste] Sign in (opens the browser; --paste for manual code)
|
|
854
733
|
provision [--name <n>] Create a new trading computer (requires app onboarding)
|
|
855
|
-
connect [name] Connect to
|
|
734
|
+
connect [name] Connect to a trading computer (caches SSH access)
|
|
856
735
|
init [dir] [--force] Scaffold an agent workspace (CLAUDE.md / AGENTS.md / GEMINI.md)
|
|
857
|
-
|
|
858
|
-
status Who you are + your trading computers + a live snapshot
|
|
859
|
-
balances [--venue <v>] Equity + open positions for the connected trading computer
|
|
736
|
+
status Who you are + your trading computer + how to operate it
|
|
860
737
|
logout Sign out and clear the cached session
|
|
861
738
|
|
|
862
|
-
${c.bold("Operate")}
|
|
863
|
-
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)
|
|
864
741
|
logs [name] Tail a strategy's logs
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
strategy spawn --template <t.js> --name <n> [--param KEY=VALUE]... [--no-autostart]
|
|
868
|
-
strategy ls List running strategies
|
|
869
|
-
strategy doctor [name] Gateway health + a strategy's tracking status
|
|
742
|
+
balances [--venue <v>] Equity + open positions
|
|
743
|
+
computers List your trading computers
|
|
870
744
|
|
|
871
745
|
${c.bold("Other")}
|
|
872
|
-
help, --help
|
|
873
|
-
version, --version Show version
|
|
746
|
+
help, --help \xB7 version, --version
|
|
874
747
|
|
|
875
748
|
Env: ${c.dim("BOOLE_API_BASE")} (default https://trade.boole.markets), ${c.dim("BOOLE_HOME")}
|
|
876
749
|
`;
|
|
@@ -881,10 +754,14 @@ async function main() {
|
|
|
881
754
|
log(`boole ${VERSION}`);
|
|
882
755
|
return;
|
|
883
756
|
}
|
|
884
|
-
if (
|
|
757
|
+
if (cmd === "help" || flags.help) {
|
|
885
758
|
log(HELP);
|
|
886
759
|
return;
|
|
887
760
|
}
|
|
761
|
+
if (!cmd) {
|
|
762
|
+
orient();
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
888
765
|
switch (cmd) {
|
|
889
766
|
case "login":
|
|
890
767
|
await login({ paste: !!flags.paste });
|
|
@@ -925,24 +802,6 @@ async function main() {
|
|
|
925
802
|
const remote = proc ? `pm2 logs '${proc.replace(/'/g, "")}' --lines 50 --nostream` : "pm2 logs --lines 40 --nostream";
|
|
926
803
|
process.exit(await runRemoteInherit(remote));
|
|
927
804
|
}
|
|
928
|
-
case "strategy": {
|
|
929
|
-
const sub = _[1];
|
|
930
|
-
if (sub === "spawn") {
|
|
931
|
-
await strategySpawn({
|
|
932
|
-
template: str(flags.template),
|
|
933
|
-
name: str(flags.name),
|
|
934
|
-
params: list(flags.param),
|
|
935
|
-
autostart: !flags["no-autostart"]
|
|
936
|
-
});
|
|
937
|
-
} else if (sub === "ls" || sub === "list") {
|
|
938
|
-
await strategyList();
|
|
939
|
-
} else if (sub === "doctor") {
|
|
940
|
-
await strategyDoctor(_[2]);
|
|
941
|
-
} else {
|
|
942
|
-
die("Usage: boole strategy <spawn|ls|doctor> \u2026 (see `boole help`)");
|
|
943
|
-
}
|
|
944
|
-
break;
|
|
945
|
-
}
|
|
946
805
|
default:
|
|
947
806
|
err(`Unknown command: ${cmd}`);
|
|
948
807
|
log(HELP);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boole-digital/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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" },
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"files": ["dist/index.js", "README.md", "LICENSE"],
|
|
9
9
|
"license": "SEE LICENSE IN LICENSE",
|
|
10
10
|
"engines": { "node": ">=18" },
|
|
11
|
-
"repository": { "type": "git", "url": "github
|
|
11
|
+
"repository": { "type": "git", "url": "git+https://github.com/Boole-Digital/portara-desktop.git" },
|
|
12
12
|
"homepage": "https://trade.boole.markets",
|
|
13
13
|
"publishConfig": { "access": "public" },
|
|
14
14
|
"keywords": ["boole", "trading", "cli", "claude-code", "codex", "gemini"],
|