@abuaboud/box 0.7.8 → 0.7.9

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 +100 -183
  2. package/package.json +1 -1
package/dist/cbox.mjs CHANGED
@@ -12,151 +12,17 @@ import { join as join2 } from "node:path";
12
12
  import { styleText as styleText2 } from "node:util";
13
13
 
14
14
  // src/laptop.ts
15
- import { execFile, execFileSync, spawnSync } from "node:child_process";
15
+ import { execFile, execFileSync } from "node:child_process";
16
16
  import { gzip } from "node:zlib";
17
- import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmdirSync, statSync, writeFileSync } from "node:fs";
18
- import { homedir as homedir2, userInfo } from "node:os";
19
- import { join, sep } from "node:path";
17
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
18
+ import { homedir, userInfo } from "node:os";
19
+ import { join } from "node:path";
20
20
  import ssh2 from "ssh2";
21
-
22
- // src/ui.ts
23
- import { homedir } from "node:os";
24
- import { styleText } from "node:util";
25
- var dim = (s) => styleText("dim", s);
26
- var ok = (s) => console.log(`${styleText("green", "\u2713")} ${s}`);
27
- var miss = (s) => console.log(`${styleText("yellow", "!")} ${s}`);
28
- var fail = (s) => {
29
- active?.();
30
- console.error(`${styleText("red", "\u2717")} ${s}`);
31
- process.exit(1);
32
- };
33
- var active;
34
- var stopProgress = () => active?.();
35
- function progress(steps, { pace = 5e3 } = {}) {
36
- const tty = process.stdout.isTTY;
37
- if (!tty) {
38
- console.log(dim(`${steps[0][1]}\u2026`));
39
- return Object.assign((done) => {
40
- if (done) ok(done);
41
- }, { say: (label2) => console.log(dim(`${label2}\u2026`)) });
42
- }
43
- const spin = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F", t0 = Date.now(), width = 16;
44
- let i = 0, said;
45
- process.stdout.write("\x1B[?25l");
46
- const restore = () => process.stdout.write("\x1B[?25h");
47
- process.once("exit", restore);
48
- const draw = () => {
49
- const ms = Date.now() - t0;
50
- const label2 = said ?? steps.filter(([at2]) => at2 <= ms).at(-1)[1];
51
- const at = Math.min(width - 1, Math.floor((1 - Math.exp(-ms / pace)) * width));
52
- const track = [...Array(width)].map((_, k) => k < at ? styleText("cyan", "\u2501") : k === at ? "\u{1F4E6}" : styleText("dim", "\u2500")).join("");
53
- const secs = ms / 1e3, clock = secs < 60 ? `${secs.toFixed(1)}s` : `${Math.floor(secs / 60)}m ${String(Math.floor(secs % 60)).padStart(2, "0")}s`;
54
- process.stdout.write(`\r\x1B[2K${styleText("cyan", spin[i++ % spin.length])} ${track} ${label2.slice(0, Math.max(20, (process.stdout.columns ?? 100) - width - 16))}\u2026 ${dim(clock)}`);
55
- };
56
- draw();
57
- const timer = setInterval(draw, 80);
58
- const stop = (done) => {
59
- clearInterval(timer);
60
- process.stdout.write("\r\x1B[2K");
61
- restore();
62
- active = void 0;
63
- if (done) ok(done);
64
- };
65
- active = stop;
66
- return Object.assign(stop, { say: (label2) => {
67
- said = label2;
68
- } });
69
- }
70
- var span = (ms) => {
71
- const m = Math.max(0, Math.round(ms / 6e4));
72
- return m < 60 ? `${m}m` : m < 1440 ? `${Math.floor(m / 60)}h ${m % 60}m` : `${Math.round(m / 1440)} days`;
73
- };
74
- var tilde = (p) => p.startsWith(homedir()) ? `~${p.slice(homedir().length)}` : p;
75
- function label(b) {
76
- if (b.state === "paused") return styleText("blue", b.asleepSince ? `asleep ${span(Date.now() - new Date(b.asleepSince).getTime())}` : "asleep");
77
- if (b.state !== "running") return styleText("yellow", b.state);
78
- if (b.busy) return styleText("green", "\u25CF working");
79
- if (!b.idleMinutes) return styleText("green", "awake");
80
- const left = b.idleMinutes * 6e4 - (Date.now() - new Date(b.lastActiveAt ?? Date.now()).getTime());
81
- return styleText("yellow", `\u263E sleeps in ${span(Math.max(6e4, left))}`);
82
- }
83
-
84
- // src/laptop.ts
85
- var OLD_CONFIG = join(homedir2(), ".config", "craftboxes");
86
- var OLD_BOXES = join(homedir2(), ".craftboxes");
87
- var NEW_CONFIG = join(homedir2(), ".config", "box");
88
- var NEW_BOXES = join(homedir2(), ".box");
89
- var CONFIG_DIR = existsSync(OLD_CONFIG) ? OLD_CONFIG : NEW_CONFIG;
90
- var BOXES = existsSync(OLD_BOXES) ? OLD_BOXES : NEW_BOXES;
21
+ var CONFIG_DIR = join(homedir(), ".config", "box");
22
+ var BOXES = join(homedir(), ".box");
91
23
  var SERVICES = { 5432: "Postgres", 6379: "Redis", 3306: "MySQL", 27017: "MongoDB", 8123: "ClickHouse" };
92
- function moveHome() {
93
- const moves = [[OLD_CONFIG, NEW_CONFIG], [OLD_BOXES, NEW_BOXES]].filter(([from]) => existsSync(from));
94
- if (!moves.length) return;
95
- if (otherBoxRunning()) return miss(`Close your other box windows so box can move ~/.craftboxes to ~/.box`);
96
- const clash = moves.flatMap(([from, to]) => existsSync(to) ? readdirSync(from).filter((f) => existsSync(join(to, f))).map((f) => join(to, f)) : []);
97
- if (clash.length) return miss(`box can't move ~/.craftboxes to ~/.box while ${clash.join(", ")} exists`);
98
- try {
99
- for (const [from, to] of moves) {
100
- if (!existsSync(to)) {
101
- renameSync(from, to);
102
- continue;
103
- }
104
- for (const f of readdirSync(from)) renameSync(join(from, f), join(to, f));
105
- rmdirSync(from);
106
- }
107
- } catch (e) {
108
- return miss(`Couldn't move ~/.craftboxes to ~/.box yet: ${e.message}`);
109
- }
110
- if (moves.some(([from]) => from === OLD_BOXES)) {
111
- repairWorktrees();
112
- moveClaudeProjects();
113
- }
114
- ok(`Moved ~/.craftboxes to ~/.box ${dim("\xB7 reopen VS Code windows with box code")}`);
115
- const again = spawnSync(process.execPath, process.argv.slice(1), { stdio: "inherit", env: { ...process.env, CBOX_UPDATED: "1" } });
116
- process.exit(again.status ?? 0);
117
- }
118
- function otherBoxRunning() {
119
- try {
120
- return execFileSync("ps", ["-A", "-o", "pid=,args="], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 3e3 }).split("\n").some((line) => {
121
- const [pid, ...args] = line.trim().split(/\s+/);
122
- const command = args.join(" ");
123
- return ![process.pid, process.ppid].includes(Number(pid)) && /(^|[\s/])c?box(\.mjs)?(\s|$)/.test(command) && !/\s(proxy|sync)(\s|$)/.test(command);
124
- });
125
- } catch {
126
- return false;
127
- }
128
- }
129
- function repairWorktrees() {
130
- for (const env of readdirSync(NEW_BOXES)) {
131
- const clone = join(NEW_BOXES, env, ".clone");
132
- if (env.startsWith(".") || !existsSync(join(clone, ".git"))) continue;
133
- const worktrees = readdirSync(join(NEW_BOXES, env)).map((d) => join(NEW_BOXES, env, d, "repo")).filter((r) => existsSync(join(r, ".git")) && statSync(join(r, ".git")).isFile());
134
- try {
135
- if (worktrees.length) execFileSync("git", ["-C", clone, "worktree", "repair", ...worktrees], { stdio: "ignore", timeout: 3e4 });
136
- } catch {
137
- miss(`Couldn't repair the ${env} worktrees. Run: git -C ${clone} worktree repair`);
138
- }
139
- }
140
- }
141
- function moveClaudeProjects() {
142
- const projects = join(homedir2(), ".claude", "projects");
143
- const [from, to] = [OLD_BOXES, NEW_BOXES].map((p) => p.replace(/[^a-zA-Z0-9]/g, "-"));
144
- for (const dir of existsSync(projects) ? readdirSync(projects) : []) {
145
- const target = join(projects, to + dir.slice(from.length));
146
- if ((dir === from || dir.startsWith(`${from}-`)) && !existsSync(target)) renameSync(join(projects, dir), target);
147
- }
148
- const file2 = join(homedir2(), ".claude.json");
149
- try {
150
- const config = JSON.parse(readFileSync(file2, "utf8"));
151
- const keys = Object.keys(config.projects ?? {});
152
- if (!keys.some((k) => k.startsWith(OLD_BOXES + sep))) return;
153
- config.projects = Object.fromEntries(Object.entries(config.projects).map(([k, v]) => [k.startsWith(OLD_BOXES + sep) ? NEW_BOXES + k.slice(OLD_BOXES.length) : k, v]));
154
- writeFileSync(file2, JSON.stringify(config, null, 2));
155
- } catch {
156
- }
157
- }
158
24
  function localMcp() {
159
- const claudeJson = join(homedir2(), ".claude.json");
25
+ const claudeJson = join(homedir(), ".claude.json");
160
26
  const servers = existsSync(claudeJson) ? JSON.parse(readFileSync(claudeJson, "utf8")).mcpServers ?? {} : {};
161
27
  const skipped = [];
162
28
  const mcpServers = {};
@@ -172,7 +38,7 @@ function localMcp() {
172
38
  return { mcpServers, mcpOAuth, skipped };
173
39
  }
174
40
  function localClaudeHome() {
175
- const root = join(homedir2(), ".claude");
41
+ const root = join(homedir(), ".claude");
176
42
  const json = (p) => {
177
43
  try {
178
44
  return JSON.parse(readFileSync(join(root, p), "utf8"));
@@ -209,12 +75,12 @@ function laptopKey() {
209
75
  return { privateKey: readFileSync(priv), publicKey: readFileSync(priv + ".pub", "utf8").trim() };
210
76
  }
211
77
  function sshKeys(boxKey) {
212
- const home2 = join(homedir2(), ".ssh");
78
+ const home2 = join(homedir(), ".ssh");
213
79
  const mine = existsSync(home2) ? readdirSync(home2).filter((f) => f.endsWith(".pub")).map((f) => readFileSync(join(home2, f), "utf8").trim().split(/\s+/).slice(0, 2).join(" ")) : [];
214
80
  return [boxKey, ...mine.filter((k) => /^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)|sk-ssh-ed25519@openssh\.com|sk-ecdsa-sha2-nistp256@openssh\.com) [A-Za-z0-9+/=]+$/.test(k))].join("\n");
215
81
  }
216
82
  function localCreds() {
217
- const file2 = join(homedir2(), ".claude", ".credentials.json");
83
+ const file2 = join(homedir(), ".claude", ".credentials.json");
218
84
  if (process.platform !== "darwin") return existsSync(file2) ? JSON.parse(readFileSync(file2, "utf8")) : {};
219
85
  for (const account of [["-a", userInfo().username], []]) {
220
86
  try {
@@ -274,12 +140,73 @@ function localCopy() {
274
140
  return { dir: top, repo, branch: git2("-C", top, "branch", "--show-current") || void 0, files, services };
275
141
  }
276
142
 
143
+ // src/ui.ts
144
+ import { homedir as homedir2 } from "node:os";
145
+ import { styleText } from "node:util";
146
+ var dim = (s) => styleText("dim", s);
147
+ var ok = (s) => console.log(`${styleText("green", "\u2713")} ${s}`);
148
+ var miss = (s) => console.log(`${styleText("yellow", "!")} ${s}`);
149
+ var fail = (s) => {
150
+ active?.();
151
+ console.error(`${styleText("red", "\u2717")} ${s}`);
152
+ process.exit(1);
153
+ };
154
+ var active;
155
+ var stopProgress = () => active?.();
156
+ function progress(steps, { pace = 5e3 } = {}) {
157
+ const tty = process.stdout.isTTY;
158
+ if (!tty) {
159
+ console.log(dim(`${steps[0][1]}\u2026`));
160
+ return Object.assign((done) => {
161
+ if (done) ok(done);
162
+ }, { say: (label2) => console.log(dim(`${label2}\u2026`)) });
163
+ }
164
+ const spin = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F", t0 = Date.now(), width = 16;
165
+ let i = 0, said;
166
+ process.stdout.write("\x1B[?25l");
167
+ const restore = () => process.stdout.write("\x1B[?25h");
168
+ process.once("exit", restore);
169
+ const draw = () => {
170
+ const ms = Date.now() - t0;
171
+ const label2 = said ?? steps.filter(([at2]) => at2 <= ms).at(-1)[1];
172
+ const at = Math.min(width - 1, Math.floor((1 - Math.exp(-ms / pace)) * width));
173
+ const track = [...Array(width)].map((_, k) => k < at ? styleText("cyan", "\u2501") : k === at ? "\u{1F4E6}" : styleText("dim", "\u2500")).join("");
174
+ const secs = ms / 1e3, clock = secs < 60 ? `${secs.toFixed(1)}s` : `${Math.floor(secs / 60)}m ${String(Math.floor(secs % 60)).padStart(2, "0")}s`;
175
+ process.stdout.write(`\r\x1B[2K${styleText("cyan", spin[i++ % spin.length])} ${track} ${label2.slice(0, Math.max(20, (process.stdout.columns ?? 100) - width - 16))}\u2026 ${dim(clock)}`);
176
+ };
177
+ draw();
178
+ const timer = setInterval(draw, 80);
179
+ const stop = (done) => {
180
+ clearInterval(timer);
181
+ process.stdout.write("\r\x1B[2K");
182
+ restore();
183
+ active = void 0;
184
+ if (done) ok(done);
185
+ };
186
+ active = stop;
187
+ return Object.assign(stop, { say: (label2) => {
188
+ said = label2;
189
+ } });
190
+ }
191
+ var span = (ms) => {
192
+ const m = Math.max(0, Math.round(ms / 6e4));
193
+ return m < 60 ? `${m}m` : m < 1440 ? `${Math.floor(m / 60)}h ${m % 60}m` : `${Math.round(m / 1440)} days`;
194
+ };
195
+ var tilde = (p) => p.startsWith(homedir2()) ? `~${p.slice(homedir2().length)}` : p;
196
+ function label(b) {
197
+ if (b.state === "paused") return styleText("blue", b.asleepSince ? `asleep ${span(Date.now() - new Date(b.asleepSince).getTime())}` : "asleep");
198
+ if (b.state !== "running") return styleText("yellow", b.state);
199
+ if (b.busy) return styleText("green", "\u25CF working");
200
+ if (!b.idleMinutes) return styleText("green", "awake");
201
+ const left = b.idleMinutes * 6e4 - (Date.now() - new Date(b.lastActiveAt ?? Date.now()).getTime());
202
+ return styleText("yellow", `\u263E sleeps in ${span(Math.max(6e4, left))}`);
203
+ }
204
+
277
205
  // src/api.ts
278
206
  var file = join2(CONFIG_DIR, "config.json");
279
207
  var load = () => {
280
208
  if (!existsSync2(file)) return { server: process.env.CB_SERVER ?? "https://box.abuaboud.me" };
281
- const c = JSON.parse(readFileSync2(file, "utf8"));
282
- return { ...c, server: c.server.replace("://craftboxes.abuaboud.me", "://box.abuaboud.me") };
209
+ return JSON.parse(readFileSync2(file, "utf8"));
283
210
  };
284
211
  var save = (c) => {
285
212
  mkdirSync2(CONFIG_DIR, { recursive: true });
@@ -490,13 +417,13 @@ ${styleText3("yellow", "!")} Connect GitHub so Box can clone ${repo} and your bo
490
417
 
491
418
  // src/local.ts
492
419
  import { execFileSync as execFileSync3, spawn as spawn3 } from "node:child_process";
493
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync2, watch, writeFileSync as writeFileSync4 } from "node:fs";
420
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, renameSync, rmSync, statSync, watch, writeFileSync as writeFileSync4 } from "node:fs";
494
421
  import { homedir as homedir4 } from "node:os";
495
422
  import { basename, dirname, join as join5 } from "node:path";
496
423
  import { select as select2 } from "@inquirer/prompts";
497
424
 
498
425
  // src/ssh.ts
499
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
426
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
500
427
  import { connect } from "node:net";
501
428
  import { homedir as homedir3 } from "node:os";
502
429
  import { join as join4 } from "node:path";
@@ -519,12 +446,9 @@ function ensureSshConfig() {
519
446
  ].join("\n"));
520
447
  const config = join4(sshDir, "config");
521
448
  const now = existsSync4(config) ? readFileSync4(config, "utf8") : "";
522
- const renamed = now.replace(/^Include ~\/\.ssh\/craftboxes$/m, "Include ~/.ssh/box");
523
- const next = /^Include ~\/\.ssh\/box$/m.test(renamed) ? renamed : `Include ~/.ssh/box
449
+ if (!/^Include ~\/\.ssh\/box$/m.test(now)) writeFileSync3(config, `Include ~/.ssh/box
524
450
 
525
- ${renamed}`;
526
- if (next !== now) writeFileSync3(config, next, { mode: 384 });
527
- rmSync(join4(sshDir, "craftboxes"), { force: true });
451
+ ${now}`, { mode: 384 });
528
452
  }
529
453
  async function proxy(host) {
530
454
  const name = host.replace(/\.box$/, "");
@@ -601,7 +525,7 @@ function prepareFolder(name, env, here, mcp) {
601
525
  const legacy = join5(BOXES, name);
602
526
  if (!existsSync5(folder) && existsSync5(join5(legacy, ".box.json"))) {
603
527
  mkdirSync4(dirname(folder), { recursive: true });
604
- renameSync2(legacy, folder);
528
+ renameSync(legacy, folder);
605
529
  }
606
530
  mkdirSync4(join5(folder, ".claude"), { recursive: true });
607
531
  const stateFile = join5(folder, ".box.json");
@@ -624,7 +548,7 @@ async function ensureClone(env) {
624
548
  return clone;
625
549
  }
626
550
  const partial = `${clone}.partial`;
627
- rmSync2(partial, { recursive: true, force: true });
551
+ rmSync(partial, { recursive: true, force: true });
628
552
  mkdirSync4(dirname(clone), { recursive: true });
629
553
  const bar = progress([[0, `Cloning ${env.repo} once for every ${env.name} box`]], { pace: 6e4 });
630
554
  const cloned = await git([...auth, "clone", "-q", "--no-checkout", "--filter=blob:none", token ? `https://github.com/${env.repo}.git` : `git@github.com:${env.repo}.git`, partial]);
@@ -633,23 +557,23 @@ async function ensureClone(env) {
633
557
  fail(`Couldn't clone ${env.repo}: ${cloned.err.split("\n").at(-1)}. Run gh auth login and try again.`);
634
558
  }
635
559
  await git(["-C", partial, "update-ref", "--no-deref", "HEAD", "HEAD"]);
636
- renameSync2(partial, clone);
560
+ renameSync(partial, clone);
637
561
  bar(`Cloned ${env.repo} into ${tilde(clone)}`);
638
562
  return clone;
639
563
  }
640
564
  async function ensureWorktree({ clone, folder, name, branch }) {
641
565
  const repo = join5(folder, "repo");
642
- if (existsSync5(join5(repo, ".git")) && statSync2(join5(repo, ".git")).isFile()) return false;
643
- if (existsSync5(repo)) renameSync2(repo, `${repo}.mirror`);
566
+ if (existsSync5(join5(repo, ".git")) && statSync(join5(repo, ".git")).isFile()) return false;
567
+ if (existsSync5(repo)) renameSync(repo, `${repo}.mirror`);
644
568
  await git(["-C", clone, "worktree", "prune"]);
645
569
  const onOrigin = branch && (await git(["-C", clone, "rev-parse", "-q", "--verify", `refs/remotes/origin/${branch}`])).ok;
646
570
  const tries = onOrigin ? [["worktree", "add", "-q", repo, branch], ["worktree", "add", "-q", "--detach", repo, `origin/${branch}`]] : [["worktree", "add", "-q", "--no-track", "-b", `cb/${name}`, repo, "origin/HEAD"], ["worktree", "add", "-q", repo, `cb/${name}`]];
647
571
  for (const args of tries) {
648
572
  const r = await git(["-C", clone, ...args]);
649
573
  if (r.ok) return true;
650
- rmSync2(repo, { recursive: true, force: true });
574
+ rmSync(repo, { recursive: true, force: true });
651
575
  }
652
- if (existsSync5(`${repo}.mirror`)) renameSync2(`${repo}.mirror`, repo);
576
+ if (existsSync5(`${repo}.mirror`)) renameSync(`${repo}.mirror`, repo);
653
577
  return fail(`Couldn't make a worktree for ${name} in ${tilde(clone)}.`);
654
578
  }
655
579
  async function setUpWorktree({ clone, folder, name, branch, fresh }) {
@@ -661,7 +585,7 @@ async function setUpWorktree({ clone, folder, name, branch, fresh }) {
661
585
  if (copied && fresh) await alignBox(name, repo);
662
586
  bar(copied ? `${tilde(repo)} is a worktree of ${tilde(clone)}` : void 0);
663
587
  if (!copied) fail(`Couldn't sync ${tilde(repo)} with ${name}. Check that ssh ${name}.box works.`);
664
- rmSync2(`${repo}.mirror`, { recursive: true, force: true });
588
+ rmSync(`${repo}.mirror`, { recursive: true, force: true });
665
589
  return true;
666
590
  }
667
591
  async function alignBox(name, repo) {
@@ -673,7 +597,7 @@ async function alignBox(name, repo) {
673
597
  }
674
598
  function newestTranscript(folder) {
675
599
  const sessions = join5(homedir4(), ".claude", "projects", folder.replace(/[^a-zA-Z0-9]/g, "-"));
676
- return existsSync5(sessions) ? readdirSync2(sessions).filter((f) => f.endsWith(".jsonl")).map((f) => join5(sessions, f)).sort((a, b) => statSync2(b).mtimeMs - statSync2(a).mtimeMs)[0] : void 0;
600
+ return existsSync5(sessions) ? readdirSync2(sessions).filter((f) => f.endsWith(".jsonl")).map((f) => join5(sessions, f)).sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)[0] : void 0;
677
601
  }
678
602
  function backgroundSession(folder) {
679
603
  try {
@@ -700,7 +624,7 @@ function laptopState(folder) {
700
624
  return void 0;
701
625
  }
702
626
  })() : void 0;
703
- return { branch: branch || void 0, task, working: !!newest && Date.now() - statSync2(newest).mtimeMs < 9e4 };
627
+ return { branch: branch || void 0, task, working: !!newest && Date.now() - statSync(newest).mtimeMs < 9e4 };
704
628
  }
705
629
  var git = (args) => new Promise((done) => {
706
630
  const p = spawn3("git", args, { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } });
@@ -814,7 +738,7 @@ function ignored(repo, path) {
814
738
  }
815
739
  function isDirectory(path) {
816
740
  try {
817
- return statSync2(path).isDirectory();
741
+ return statSync(path).isDirectory();
818
742
  } catch {
819
743
  return false;
820
744
  }
@@ -831,7 +755,7 @@ async function prune(boxes) {
831
755
  }
832
756
  }
833
757
  async function dropFolder(b) {
834
- rmSync2(b.folder, { recursive: true, force: true });
758
+ rmSync(b.folder, { recursive: true, force: true });
835
759
  const clone = join5(BOXES, b.env, CLONE);
836
760
  if (existsSync5(clone)) await git(["-C", clone, "worktree", "prune"]);
837
761
  }
@@ -1226,14 +1150,13 @@ function wakingScreen(box, keys) {
1226
1150
  var TERMINAL_RESET = "\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1005l\x1B[?1006l\x1B[?1015l\x1B[?1004l\x1B[?2004l\x1B[<u\x1B[?1049l\x1B[?25h\x1B[0m";
1227
1151
 
1228
1152
  // src/update.ts
1229
- import { execFileSync as execFileSync4, spawn as spawn4, spawnSync as spawnSync2 } from "node:child_process";
1153
+ import { execFileSync as execFileSync4, spawn as spawn4, spawnSync } from "node:child_process";
1230
1154
  import { mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
1231
1155
  import { basename as basename2, dirname as dirname2, join as join6 } from "node:path";
1232
1156
  import { fileURLToPath } from "node:url";
1233
1157
  var PACKAGE = "@abuaboud/box";
1234
1158
  var LATEST = `https://registry.npmjs.org/${PACKAGE.replace("/", "%2f")}/latest`;
1235
- var INSTALLED = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
1236
- var VERSION = INSTALLED.version;
1159
+ var VERSION = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8")).version;
1237
1160
  function npmPrefix() {
1238
1161
  const pkgDir = dirname2(dirname2(fileURLToPath(import.meta.url)));
1239
1162
  const modules = basename2(dirname2(pkgDir)).startsWith("@") ? dirname2(dirname2(pkgDir)) : dirname2(pkgDir);
@@ -1245,20 +1168,15 @@ var newer = (a, b) => {
1245
1168
  for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) > (y[i] ?? 0);
1246
1169
  return false;
1247
1170
  };
1248
- var due = (latest) => INSTALLED.name !== PACKAGE || newer(latest, VERSION);
1249
1171
  var NPM = process.platform === "win32" ? "npm.cmd" : "npm";
1250
- function installSteps(version, prefix) {
1251
- const at = prefix ? ["--prefix", prefix] : [];
1252
- const box = `${PACKAGE}@${version}`;
1253
- return INSTALLED.name === PACKAGE ? [["i", "-g", "--prefer-online", ...at, box]] : [["i", "-g", "--prefer-online", "--force", ...at, box], ["rm", "-g", ...at, INSTALLED.name], ["i", "-g", "--prefer-offline", "--force", ...at, box]];
1254
- }
1172
+ var installArgs = (version, prefix) => ["i", "-g", "--prefer-online", ...prefix ? ["--prefix", prefix] : [], `${PACKAGE}@${version}`];
1255
1173
  async function updateFirst() {
1256
1174
  const prefix = npmPrefix();
1257
1175
  if (!prefix || process.env.CBOX_NO_UPDATE || process.env.CBOX_UPDATED) return;
1258
1176
  const latest = await fetch(LATEST, { signal: AbortSignal.timeout(2500) }).then((r) => r.json()).then((j) => j.version, () => void 0);
1259
- if (!latest || !due(latest)) return;
1177
+ if (!latest || !newer(latest, VERSION)) return;
1260
1178
  const stop = progress([[0, `Updating box ${VERSION} \u2192 ${latest}`]]);
1261
- const done = installSteps(latest, prefix).every((args) => spawnSync2(NPM, args, { stdio: "ignore" }).status === 0);
1179
+ const done = spawnSync(NPM, installArgs(latest, prefix), { stdio: "ignore" }).status === 0;
1262
1180
  stop();
1263
1181
  if (!done) return miss(`Couldn't update box to ${latest}, so carrying on with ${VERSION}`);
1264
1182
  ok(`Updated box ${dim(VERSION)} \u2192 ${latest}`);
@@ -1266,7 +1184,7 @@ async function updateFirst() {
1266
1184
  writeFileSync5(join6(CONFIG_DIR, "update.json"), JSON.stringify({ version: latest, checkedAt: Date.now() }));
1267
1185
  } catch {
1268
1186
  }
1269
- const again = spawnSync2(process.execPath, process.argv.slice(1), { stdio: "inherit", env: { ...process.env, CBOX_UPDATED: "1" } });
1187
+ const again = spawnSync(process.execPath, process.argv.slice(1), { stdio: "inherit", env: { ...process.env, CBOX_UPDATED: "1" } });
1270
1188
  process.exit(again.status ?? 0);
1271
1189
  }
1272
1190
  function autoUpdate() {
@@ -1279,7 +1197,7 @@ function autoUpdate() {
1279
1197
  } catch {
1280
1198
  }
1281
1199
  if (state.version && state.version !== VERSION) ok(`Updated box ${dim(state.version)} \u2192 ${VERSION}`);
1282
- const check = INSTALLED.name !== PACKAGE || !state.checkedAt || Date.now() - state.checkedAt > 12 * 36e5;
1200
+ const check = !state.checkedAt || Date.now() - state.checkedAt > 12 * 36e5;
1283
1201
  try {
1284
1202
  mkdirSync5(CONFIG_DIR, { recursive: true });
1285
1203
  writeFileSync5(file2, JSON.stringify({ version: VERSION, checkedAt: check ? Date.now() : state.checkedAt }));
@@ -1289,18 +1207,18 @@ function autoUpdate() {
1289
1207
  if (!check) return;
1290
1208
  const job = `fetch(${JSON.stringify(LATEST)}).then(r => r.json()).then(({ version }) => {
1291
1209
  const newer = (a, b) => { const x = a.split('.').map(Number), y = b.split('.').map(Number); for (let i = 0; i < 3; i++) if ((x[i] ?? 0) !== (y[i] ?? 0)) return (x[i] ?? 0) > (y[i] ?? 0); return false }
1292
- if (${JSON.stringify(INSTALLED.name === PACKAGE)} && !newer(version, ${JSON.stringify(VERSION)})) return
1293
- for (const args of ${JSON.stringify(installSteps("{v}", prefix))}) require('node:child_process').execFileSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', args.map((a) => a.replace('{v}', version)), { stdio: 'ignore' })
1210
+ if (!newer(version, ${JSON.stringify(VERSION)})) return
1211
+ require('node:child_process').execFileSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ${JSON.stringify(installArgs("{v}", prefix))}.map((a) => a.replace('{v}', version)), { stdio: 'ignore' })
1294
1212
  }).catch(() => {})`;
1295
1213
  spawn4(process.execPath, ["-e", job], { detached: true, stdio: "ignore" }).on("error", () => {
1296
1214
  }).unref();
1297
1215
  }
1298
1216
  async function upgrade() {
1299
1217
  const latest = await fetch(LATEST).then((r) => r.json()).then((j) => j.version).catch(() => fail("Could not reach the npm registry."));
1300
- if (!due(latest)) return ok(`box ${VERSION} is the latest`);
1218
+ if (!newer(latest, VERSION)) return ok(`box ${VERSION} is the latest`);
1301
1219
  console.log(dim(`Updating box ${VERSION} \u2192 ${latest}\u2026`));
1302
1220
  try {
1303
- for (const args of installSteps(latest, npmPrefix())) execFileSync4(NPM, args, { stdio: "inherit" });
1221
+ execFileSync4(NPM, installArgs(latest, npmPrefix()), { stdio: "inherit" });
1304
1222
  } catch {
1305
1223
  fail(`npm could not update box. Try: npm i -g ${PACKAGE}@latest`);
1306
1224
  }
@@ -1368,7 +1286,6 @@ var quiet = ["proxy", "sync"].includes(process.argv[2] ?? "");
1368
1286
  if (quiet) console.log = console.error;
1369
1287
  if (!quiet && !process.env.CBOX_UPDATED && !process.argv.slice(2).some((a) => ["-V", "--version", "-h", "--help", "help"].includes(a))) console.log(dim(`box ${VERSION}`));
1370
1288
  if (!quiet) {
1371
- moveHome();
1372
1289
  autoUpdate();
1373
1290
  ensureSshConfig();
1374
1291
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abuaboud/box",
3
- "version": "0.7.8",
3
+ "version": "0.7.9",
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": {