@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.
Files changed (2) hide show
  1. package/dist/index.js +110 -266
  2. 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 list2 = unwrap(body);
321
- return Array.isArray(list2) ? list2 : [];
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 operatingBrief() {
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 to operate this computer"));
378
- log(" Trades and strategies go through the gateway gate \u2014 automatically tracked on your dashboard.");
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
- 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"));
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(` Sell with ${c.dim("SIDE=sell")}. Other templates: ${c.dim("grid, twap, scale, stop, loop")}.`);
386
- log(` Inspect: ${c.cyan("boole balances")} \xB7 ${c.cyan("boole strategy ls")} \xB7 ${c.cyan("boole logs <name>")} \xB7 ${c.cyan('boole ssh "<read cmd>"')}`);
387
- log(` ${c.yellow("Never")} write files or run pm2 by hand \u2014 always use the gate (that is what keeps tracking correct).`);
388
- log(` Drop the full rules into your coding agent: ${c.cyan("boole init")} ${c.dim("\u2192 CLAUDE.md / AGENTS.md / GEMINI.md")}`);
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, "hyperliquid");
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
- async function printBalances(api, id, exchange) {
475
- try {
476
- const acct = await api.getAccount(id, exchange);
477
- const positions = Array.isArray(acct.positions) ? acct.positions : [];
478
- info(`${exchange}: equity ${c.bold(fmtUsd(acct.equity))} \xB7 available ${fmtUsd(acct.available)} \xB7 ${positions.length} open position(s)`);
479
- for (const p of positions.slice(0, 12)) {
480
- log(` ${c.bold(String(p.market ?? "?"))} ${p.size ?? ""}`);
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
- } catch (e) {
483
- warn(`${exchange} balances unavailable: ${e.message}`);
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 || "hyperliquid");
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 Computer \u2014 Operating Rules (READ FIRST)
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
- ## Placing a trade \u2014 just do it
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:
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
- 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
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
- 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.
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
- ## 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
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 cat / ls / pm2 list / curl localhost:3000/health
680
+ - \`boole ssh "<read-only cmd>"\` \u2014 inspect the box (\`pm2 ls\`, \`curl localhost:3000/health\`, \`cat /srv/cust/OPERATOR.md\`)
813
681
 
814
- ## The hard lines (protect real money + tracking)
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.
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.1.1";
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
- add(k, v);
724
+ flags[k] = v;
864
725
  } else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
865
- add(key, argv[++i]);
866
- } else add(key, true);
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 : Array.isArray(f) ? f[f.length - 1] : void 0;
872
- var list = (f) => f === void 0 ? [] : Array.isArray(f) ? f : typeof f === "string" ? [f] : [];
873
- var HELP = `${c.bold("boole")} \u2014 operate your Boole trading computer from the terminal.
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 an existing trading computer (caches SSH access)
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
- computers List your trading computers
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 read-only command on the connected box
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
- ${c.bold("Strategies")} ${c.dim("(go through the gateway \u2014 always tracked on the dashboard)")}
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 Show this 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 (!cmd || cmd === "help" || flags.help) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boole-digital/cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
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" },