@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.
Files changed (2) hide show
  1. package/dist/index.js +123 -264
  2. 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 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,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
- warn(`Could not list trading computers: ${e.message}`);
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 (!pick) return;
432
- log("");
433
- log(c.bold(`Snapshot \u2014 ${pick.name}`));
434
- try {
435
- const health = await api.getHealth(pick.id);
436
- ok(`online${health.defaultModel ? c.dim(` \xB7 model ${health.defaultModel}`) : ""}`);
437
- } catch {
438
- warn("trading computer unreachable (tunnel may be waking up \u2014 retry in a moment)");
439
- return;
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
- await printBalances(api, pick.id, "hyperliquid");
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 Computer \u2014 Operating Rules (READ FIRST, IN FULL)
722
-
723
- You operate a customer's **live** crypto trading computer through the \`boole\` CLI.
724
- **Real money is at risk.** These are STRICT, NON-NEGOTIABLE rules.
725
-
726
- > Why they matter: Boole tracks and controls every strategy by HOW it was created.
727
- > If you create one any other way \u2014 writing files over SSH, starting processes
728
- > yourself \u2014 it **silently fails**: it won't register, won't appear on the
729
- > dashboard, can't be controlled, and may mismanage live positions. There is no
730
- > error message. When in doubt, STOP and ask the user.
731
-
732
- ## Boot sequence \u2014 every session, before anything else
733
- 1. \`boole status\` \u2014 confirm you are logged in and a trading computer is connected.
734
- If not, STOP and tell the user to run \`boole login\` then \`boole connect\`.
735
- 2. Read this entire file.
736
- 3. \`boole strategy ls\` \u2014 see what is already running.
737
-
738
- ## The one rule: THINK locally, COMMIT through \`boole\`
739
- - THINK \u2014 research, analyze data, design, draft code \u2014 freely on this machine. No limits.
740
- - COMMIT \u2014 anything that changes the trading computer \u2014 ONLY through \`boole\` commands.
741
-
742
- ## Creating / running a strategy \u2014 the ONLY sanctioned path
743
- Spawn from a known-good template through the gateway gate:
744
-
745
- boole strategy spawn --template <template.js> --name <name> \\
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 command>"\` \u2014 inspect only (cat, ls, pm2 list, curl localhost:3000/health)
669
+ - \`boole ssh "<read-only cmd>"\` \u2014 inspect the box (\`pm2 ls\`, \`curl localhost:3000/health\`, \`cat /srv/cust/OPERATOR.md\`)
778
670
 
779
- ## Forbidden
780
- - Do NOT write strategy or interface files over SSH.
781
- - Do NOT \`pm2 start/restart/stop/delete\` by hand.
782
- - Do NOT edit gateway files directly.
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
- ## Safety (real money)
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.1.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
- add(k, v);
713
+ flags[k] = v;
838
714
  } else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
839
- add(key, argv[++i]);
840
- } else add(key, true);
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 : Array.isArray(f) ? f[f.length - 1] : void 0;
846
- var list = (f) => f === void 0 ? [] : Array.isArray(f) ? f : typeof f === "string" ? [f] : [];
847
- var HELP = `${c.bold("boole")} \u2014 operate your Boole trading computer from the terminal.
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 an existing trading computer (caches SSH access)
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
- computers List your trading computers
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 read-only command on the connected box
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
- ${c.bold("Strategies")} ${c.dim("(go through the gateway \u2014 always tracked on the dashboard)")}
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 Show this 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 (!cmd || cmd === "help" || flags.help) {
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.1.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:Boole-Digital/portara-desktop" },
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"],