@boole-digital/cli 0.2.3 → 0.2.4

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 +28 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -347,8 +347,15 @@ var BooleApi = class {
347
347
  }
348
348
  // Returns the plaintext SSH password for a droplet. Field name varies by
349
349
  // server version, so probe the likely shapes.
350
- async getSshPassword(id) {
351
- const body = await this.request(`/api/v1/droplets/${id}/ssh-password`, { method: "POST" });
350
+ // The ssh-password endpoint is gated by a server-side connect access code
351
+ // (env.CLAUDE_CONNECT_ACCESS_CODE) with brute-force lockout, so we MUST send it
352
+ // in the POST body (the web app does the same). Without it the backend replies
353
+ // "Incorrect access code".
354
+ async getSshPassword(id, accessCode) {
355
+ const body = await this.request(`/api/v1/droplets/${id}/ssh-password`, {
356
+ method: "POST",
357
+ body: JSON.stringify({ accessCode: (accessCode || "").trim() })
358
+ });
352
359
  const pw = body?.password ?? body?.ssh_password ?? body?.data?.password ?? body?.data?.ssh_password;
353
360
  if (!pw) throw new Error("SSH password not present in API response (check field name in api.ts:getSshPassword).");
354
361
  return String(pw);
@@ -600,7 +607,23 @@ async function connect(opts = {}) {
600
607
  if (!READY(target)) die(`Trading computer "${target.name}" is not ready yet (status=${target.status}).`);
601
608
  if (!target.ip_address) die(`Trading computer "${target.name}" has no IP yet. Try again shortly.`);
602
609
  info(`Fetching access for ${c.bold(target.name)}\u2026`);
603
- const password = await api.getSshPassword(target.id);
610
+ let password;
611
+ try {
612
+ password = await api.getSshPassword(target.id, opts.code || "");
613
+ } catch (e) {
614
+ if (!/access code/i.test(e?.message || "")) throw e;
615
+ let code = (opts.code || "").trim();
616
+ if (!code && process.stdin.isTTY) {
617
+ code = (await prompt("This computer needs a connect access code (from the Boole app):")).trim();
618
+ }
619
+ if (!code) die("This computer needs a connect access code: run `boole connect --code <code>` (from the Boole app).");
620
+ try {
621
+ password = await api.getSshPassword(target.id, code);
622
+ } catch (e2) {
623
+ if (/access code/i.test(e2?.message || "")) die("Connect access code was rejected \u2014 re-run `boole connect --code <code>` with the correct code.");
624
+ throw e2;
625
+ }
626
+ }
604
627
  saveSession({ agent: target.name, dropletId: target.id, ip: target.ip_address, password, sshUser: "customer" });
605
628
  ok(`Connected to ${c.bold(target.name)} (${target.ip_address}).`);
606
629
  log("");
@@ -728,7 +751,7 @@ function init(opts = {}) {
728
751
  }
729
752
 
730
753
  // src/index.ts
731
- var VERSION = "0.2.3";
754
+ var VERSION = "0.2.4";
732
755
  function parse(argv) {
733
756
  const _ = [];
734
757
  const flags = {};
@@ -815,7 +838,7 @@ async function main() {
815
838
  await provision({ name: str(flags.name), region: str(flags.region), size: str(flags.size) });
816
839
  break;
817
840
  case "connect":
818
- await connect({ name: _[1] });
841
+ await connect({ name: _[1], code: str(flags.code) });
819
842
  break;
820
843
  case "init":
821
844
  init({ dir: _[1], force: !!flags.force });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boole-digital/cli",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
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" },