@abuaboud/box 0.7.10 → 0.7.12

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/cbox.mjs +23 -13
  2. package/package.json +1 -1
package/dist/cbox.mjs CHANGED
@@ -20,6 +20,7 @@ import { join } from "node:path";
20
20
  import ssh2 from "ssh2";
21
21
  var CONFIG_DIR = join(homedir(), ".config", "box");
22
22
  var BOXES = join(homedir(), ".box");
23
+ var MAX_SETUP_MB = 70;
23
24
  var SERVICES = { 5432: "Postgres", 6379: "Redis", 3306: "MySQL", 27017: "MongoDB", 8123: "ClickHouse" };
24
25
  function localMcp() {
25
26
  const claudeJson = join(homedir(), ".claude.json");
@@ -351,14 +352,14 @@ async function setUpEnvironment(repo, dir, envs) {
351
352
  const detected = detectSetup(dir), sized = detectSize(dir), names = secretNames(dir);
352
353
  let name = repo.split("/")[1].toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+/, "").slice(0, 28) || "env";
353
354
  for (let i = 2, base = name; envs.some((e) => e.name === name); i++) name = `${base}-${i}`;
354
- let setup = detected?.setup ?? "", size = sized.size, secrets = "";
355
+ let setup2 = detected?.setup ?? "", size = sized.size, secrets = "";
355
356
  const sizes = { small: "2 vCPU, 4 GB", medium: "4 vCPU, 8 GB", large: "8 vCPU, 16 GB" };
356
357
  for (; ; ) {
357
358
  console.log();
358
359
  console.log(` ${dim("Name ")} ${name}`);
359
360
  console.log(` ${dim("Repo ")} ${repo}`);
360
361
  console.log(` ${dim("Machine ")} ${size[0].toUpperCase() + size.slice(1)} ${dim(`\xB7 ${sizes[size]}${size === sized.size ? ` (${sized.why})` : ""}`)}`);
361
- console.log(` ${dim("Setup ")} ${setup ? styleText3("cyan", setup) : dim("none")} ${dim(detected && setup === detected.setup ? `\xB7 from ${detected.from}` : setup ? "" : "\xB7 boxes start from a plain clone")}`);
362
+ console.log(` ${dim("Setup ")} ${setup2 ? styleText3("cyan", setup2) : dim("none")} ${dim(detected && setup2 === detected.setup ? `\xB7 from ${detected.from}` : setup2 ? "" : "\xB7 boxes start from a plain clone")}`);
362
363
  if (names.length) console.log(` ${dim("Secrets ")} ${names.join(", ")} ${dim(secrets ? "\xB7 values copied from .env" : "\xB7 names from .env.example, values empty")}`);
363
364
  console.log();
364
365
  const choice = await select({ message: "Create it?", choices: [
@@ -377,13 +378,13 @@ async function setUpEnvironment(repo, dir, envs) {
377
378
  if (choice === "edit") {
378
379
  name = await input({ message: "Name", default: name, validate: (v) => /^[a-z0-9][a-z0-9-]{0,30}$/.test(v) || "Lowercase letters, digits and dashes" });
379
380
  size = await select({ message: "Machine", default: size, choices: Object.entries(sizes).map(([value, spec]) => ({ name: `${value.padEnd(8)}${dim(spec)}`, value })) });
380
- setup = (await input({ message: "Setup script (runs once in the snapshot; empty for none)", default: setup })).trim();
381
+ setup2 = (await input({ message: "Setup script (runs once in the snapshot; empty for none)", default: setup2 })).trim();
381
382
  continue;
382
383
  }
383
384
  break;
384
385
  }
385
386
  await ensureGitHub(repo);
386
- const { id } = await api("/api/environments", { method: "POST", body: JSON.stringify({ name, repo, size, setup, secrets }) });
387
+ const { id } = await api("/api/environments", { method: "POST", body: JSON.stringify({ name, repo, size, setup: setup2, secrets }) });
387
388
  ok(`Environment ${styleText3("cyan", name)} created`);
388
389
  return { id, name, repo, status: "building", size, progress: "Starting a build machine" };
389
390
  }
@@ -782,7 +783,7 @@ async function openLocal({ name, env, here, branch, fresh = false }) {
782
783
  api("/api/environments")
783
784
  ]).finally(() => stop());
784
785
  const environment = envs.find((e) => e.name === env) ?? fail(`No environment named ${env}.`);
785
- void sendSetup();
786
+ void sendSetup().catch(() => void 0);
786
787
  const folder = prepareFolder(name, env, here, got.mcp);
787
788
  try {
788
789
  writeFileSync4(join5(BOXES, "llms.txt"), await fetch(new URL("/llms.txt", load().server)).then((r) => r.ok ? r.text() : Promise.reject()));
@@ -805,14 +806,12 @@ async function openLocal({ name, env, here, branch, fresh = false }) {
805
806
  process.exit(code2);
806
807
  }
807
808
  async function sendSetup() {
808
- const { tar, ...home2 } = localClaudeHome();
809
+ const { tar, skills, enabled, memory, ...home2 } = localClaudeHome();
809
810
  const buf = await tar;
810
- const sha = buf && buf.length <= 30 << 20 ? createHash("sha256").update(buf).digest("hex") : void 0;
811
- const put = (body) => api("/api/laptop-setup", { method: "PUT", body: JSON.stringify({ home: home2, sha, ...body }) });
812
- try {
813
- if ((await put({})).need && buf) await put({ tgz: buf.toString("base64") });
814
- } catch {
815
- }
811
+ const sha = buf && buf.length <= MAX_SETUP_MB << 20 ? createHash("sha256").update(buf).digest("hex") : void 0;
812
+ const put = (body) => api("/api/laptop-setup", { method: "PUT", body: JSON.stringify({ home: { ...home2, memory }, sha, ...body }) });
813
+ if ((await put({})).need && buf) await put({ tgz: buf.toString("base64") });
814
+ return { skills, plugins: enabled.length, memory: !!memory.trim(), tooBig: !!buf && !sha };
816
815
  }
817
816
  var claude = (folder, args) => new Promise((done) => spawn3("claude", args, { cwd: folder, stdio: "inherit", env: { ...process.env, ...process.getuid?.() === 0 && { IS_SANDBOX: "1" } } }).on("error", () => fail("Claude Code is not installed on this laptop. npm i -g @anthropic-ai/claude-code, or use box --remote.")).on("close", (c) => done(c ?? 0)));
818
817
  function trustFolder(folder) {
@@ -996,7 +995,7 @@ async function newRemoteBox(env, branch, name, o) {
996
995
  }
997
996
  async function sendHome(name, tar) {
998
997
  const buf = await tar;
999
- if (!buf || buf.length > 30 << 20) return api(`/api/boxes/${name}/home`, { method: "POST", body: "{}" }).catch(() => void 0);
998
+ if (!buf || buf.length > MAX_SETUP_MB << 20) return api(`/api/boxes/${name}/home`, { method: "POST", body: "{}" }).catch(() => void 0);
1000
999
  const sha = createHash2("sha256").update(buf).digest("hex");
1001
1000
  const post = (body) => api(`/api/boxes/${name}/home`, { method: "POST", body: JSON.stringify(body) });
1002
1001
  if ((await post({ sha }).catch(() => ({ need: false }))).need) await post({ sha, tgz: buf.toString("base64") }).catch(() => void 0);
@@ -1294,6 +1293,16 @@ async function ls() {
1294
1293
  console.log(`${b.name.padEnd(width)}${l}${" ".repeat(Math.max(1, 20 - l.replace(/\x1b\[[0-9;]*m/g, "").length))}${dim(b.agent === "local" ? "laptop" : "remote")}`);
1295
1294
  }
1296
1295
  }
1296
+ async function setup() {
1297
+ const sent = await sendSetup();
1298
+ const n = (k, word) => `${k} ${word}${k === 1 ? "" : "s"}`;
1299
+ ok(`Sent ${[n(sent.skills, "skill"), n(sent.plugins, "plugin"), ...sent.memory ? ["CLAUDE.md"] : []].join(", ")} ${dim("\xB7 @Box and webhooks start boxes with these")}`);
1300
+ if (sent.tooBig) miss(`Your skills and plugins are over ${MAX_SETUP_MB} MB, so only CLAUDE.md and the plugin list were sent.`);
1301
+ const { data } = await api("/api/accounts");
1302
+ const missing = ["claude", "github"].filter((id) => data.find((a) => a.id === id)?.status !== "ready").map((id) => id === "claude" ? "sign in to Claude" : "connect GitHub");
1303
+ if (missing.length) miss(`Before @Box can start boxes as you, ${missing.join(" and ")}: ${new URL("/boxes?new", load().server)}`);
1304
+ else ok("Claude and GitHub are connected");
1305
+ }
1297
1306
  var quiet = ["proxy", "sync"].includes(process.argv[2] ?? "");
1298
1307
  if (quiet) console.log = console.error;
1299
1308
  if (!quiet && !process.env.CBOX_UPDATED && !process.argv.slice(2).some((a) => ["-V", "--version", "-h", "--help", "help"].includes(a))) console.log(dim(`box ${VERSION}`));
@@ -1307,6 +1316,7 @@ boxOptions(cli.command("new").description("always make a fresh box")).action((o)
1307
1316
  cli.command("ls").description("list your boxes").action(ls);
1308
1317
  cli.command("code [env]").description("open an environment's boxes in one VS Code window, each a git worktree, and sync your saves").action((env) => code(env));
1309
1318
  cli.command("rm <box>").option("-f, --force", "delete even while someone is attached or Claude is working").description("delete a box, pushing unpushed work to cb/<box>").action(rm);
1319
+ cli.command("setup").description("send your skills, plugins and CLAUDE.md for @Box and webhooks, and check Claude and GitHub").action(setup);
1310
1320
  cli.command("login").option("--server <url>", "box server").description("sign in from the browser").action((o) => login(o.server));
1311
1321
  cli.command("upgrade").description("update box to the latest version now").action(upgrade);
1312
1322
  cli.command("attach [box]", { hidden: true }).action((name) => name ? openBox(name) : home(void 0, {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abuaboud/box",
3
- "version": "0.7.10",
3
+ "version": "0.7.12",
4
4
  "description": "box: Claude Code on your laptop, the work on a cloud dev machine you reach as <name>.box, with your team databases through MCP.",
5
5
  "type": "module",
6
6
  "bin": {