@abuaboud/box 0.7.7 → 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.
- package/dist/cbox.mjs +100 -183
- 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
|
|
15
|
+
import { execFile, execFileSync } from "node:child_process";
|
|
16
16
|
import { gzip } from "node:zlib";
|
|
17
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync,
|
|
18
|
-
import { homedir
|
|
19
|
-
import { join
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")) &&
|
|
643
|
-
if (existsSync5(repo))
|
|
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
|
-
|
|
574
|
+
rmSync(repo, { recursive: true, force: true });
|
|
651
575
|
}
|
|
652
|
-
if (existsSync5(`${repo}.mirror`))
|
|
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
|
-
|
|
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) =>
|
|
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() -
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 || !
|
|
1177
|
+
if (!latest || !newer(latest, VERSION)) return;
|
|
1260
1178
|
const stop = progress([[0, `Updating box ${VERSION} \u2192 ${latest}`]]);
|
|
1261
|
-
const done =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
1293
|
-
|
|
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 (!
|
|
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
|
-
|
|
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