@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.
Files changed (2) hide show
  1. package/dist/index.js +87 -254
  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 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(" Trades and strategies go through the gateway gate \u2014 automatically tracked on your dashboard.");
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(` 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")}`);
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 Computer \u2014 Operating Rules (READ FIRST)
642
+ var OPERATING_RULES = `# Boole Trading Box \u2014 Operating Rules
753
643
 
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.
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
- ## 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.
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
- ## 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\`.
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
- ## 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:
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 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\`)
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
- ## 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.
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.1.1";
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
- add(k, v);
713
+ flags[k] = v;
864
714
  } else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
865
- add(key, argv[++i]);
866
- } else add(key, true);
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 : 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.
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 an existing trading computer (caches SSH access)
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
- 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
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 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)
890
741
  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
742
+ balances [--venue <v>] Equity + open positions
743
+ computers List your trading computers
896
744
 
897
745
  ${c.bold("Other")}
898
- help, --help Show this 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 (!cmd || cmd === "help" || flags.help) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boole-digital/cli",
3
- "version": "0.1.1",
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" },