yolobox 0.1.4 → 0.1.6
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/index.js +318 -54
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,12 +7,74 @@ import { defineCommand } from "citty";
|
|
|
7
7
|
// src/lib/docker.ts
|
|
8
8
|
import { execSync, spawn, spawnSync } from "child_process";
|
|
9
9
|
import { existsSync } from "fs";
|
|
10
|
+
import { join as join2 } from "path";
|
|
11
|
+
|
|
12
|
+
// src/lib/debug.ts
|
|
13
|
+
import { appendFileSync, writeFileSync } from "fs";
|
|
10
14
|
import { join } from "path";
|
|
15
|
+
import pc from "picocolors";
|
|
16
|
+
var _enabled = false;
|
|
17
|
+
var _logPath = null;
|
|
18
|
+
function enable() {
|
|
19
|
+
_enabled = true;
|
|
20
|
+
_logPath = join(process.cwd(), "yolobox-debug.log");
|
|
21
|
+
writeFileSync(
|
|
22
|
+
_logPath,
|
|
23
|
+
`yolobox debug log \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
24
|
+
${"=".repeat(60)}
|
|
25
|
+
|
|
26
|
+
`
|
|
27
|
+
);
|
|
28
|
+
return _logPath;
|
|
29
|
+
}
|
|
30
|
+
function isEnabled() {
|
|
31
|
+
return _enabled;
|
|
32
|
+
}
|
|
33
|
+
function getLogPath() {
|
|
34
|
+
return _logPath;
|
|
35
|
+
}
|
|
36
|
+
function timestamp() {
|
|
37
|
+
return (/* @__PURE__ */ new Date()).toTimeString().slice(0, 8);
|
|
38
|
+
}
|
|
39
|
+
function log(message) {
|
|
40
|
+
if (!_enabled || !_logPath) return;
|
|
41
|
+
appendFileSync(_logPath, `[${timestamp()}] ${message}
|
|
42
|
+
`);
|
|
43
|
+
}
|
|
44
|
+
function error(message) {
|
|
45
|
+
if (!_enabled || !_logPath) return;
|
|
46
|
+
const ts = timestamp();
|
|
47
|
+
appendFileSync(_logPath, `[${ts}] ERROR: ${message}
|
|
48
|
+
`);
|
|
49
|
+
process.stderr.write(`${pc.dim(`[${ts}]`)} ${pc.red(message)}
|
|
50
|
+
`);
|
|
51
|
+
}
|
|
52
|
+
function logCommand(cmd, args, result) {
|
|
53
|
+
if (!_enabled || !_logPath) return;
|
|
54
|
+
const lines = [`$ ${cmd} ${args.join(" ")}`, `exit code: ${result.status}`];
|
|
55
|
+
if (result.stdout.trim()) lines.push(`stdout:
|
|
56
|
+
${result.stdout.trim()}`);
|
|
57
|
+
if (result.stderr.trim()) lines.push(`stderr:
|
|
58
|
+
${result.stderr.trim()}`);
|
|
59
|
+
log(lines.join("\n"));
|
|
60
|
+
if (result.status !== 0) {
|
|
61
|
+
const errorOutput = result.stderr.trim() || result.stdout.trim();
|
|
62
|
+
if (errorOutput) {
|
|
63
|
+
process.stderr.write(`${pc.dim("[debug]")} ${pc.red(errorOutput)}
|
|
64
|
+
`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/lib/docker.ts
|
|
11
70
|
function isDockerRunning() {
|
|
71
|
+
log("Checking Docker status...");
|
|
12
72
|
try {
|
|
13
73
|
execSync("docker info", { stdio: ["pipe", "pipe", "pipe"] });
|
|
74
|
+
log("Docker is running");
|
|
14
75
|
return true;
|
|
15
76
|
} catch {
|
|
77
|
+
error("Docker is not running or not installed");
|
|
16
78
|
return false;
|
|
17
79
|
}
|
|
18
80
|
}
|
|
@@ -35,12 +97,15 @@ function resolveDockerImage(options) {
|
|
|
35
97
|
};
|
|
36
98
|
}
|
|
37
99
|
function imageExists(imageName) {
|
|
100
|
+
log(`Checking if image exists: ${imageName}`);
|
|
38
101
|
try {
|
|
39
102
|
execSync(`docker image inspect ${imageName}`, {
|
|
40
103
|
stdio: ["pipe", "pipe", "pipe"]
|
|
41
104
|
});
|
|
105
|
+
log(`Image found: ${imageName}`);
|
|
42
106
|
return true;
|
|
43
107
|
} catch {
|
|
108
|
+
log(`Image not found locally: ${imageName}`);
|
|
44
109
|
return false;
|
|
45
110
|
}
|
|
46
111
|
}
|
|
@@ -79,12 +144,42 @@ function pullImage(imageName, onProgress) {
|
|
|
79
144
|
proc.stdout?.on("data", handleOutput);
|
|
80
145
|
proc.stderr?.on("data", handleOutput);
|
|
81
146
|
proc.on("close", (code) => {
|
|
147
|
+
log(
|
|
148
|
+
`Image pull ${code === 0 ? "succeeded" : "failed"}: ${imageName}`
|
|
149
|
+
);
|
|
82
150
|
resolve(code === 0);
|
|
83
151
|
});
|
|
84
152
|
});
|
|
85
153
|
}
|
|
86
154
|
function isYoloboxDevRepo(repoRoot) {
|
|
87
|
-
return existsSync(
|
|
155
|
+
return existsSync(join2(repoRoot, "docker", "Dockerfile"));
|
|
156
|
+
}
|
|
157
|
+
function canDockerAccessPath(hostFilePath, image) {
|
|
158
|
+
log(`Checking Docker file access for: ${hostFilePath}`);
|
|
159
|
+
try {
|
|
160
|
+
const result = spawnSync(
|
|
161
|
+
"docker",
|
|
162
|
+
[
|
|
163
|
+
"run",
|
|
164
|
+
"--rm",
|
|
165
|
+
"-v",
|
|
166
|
+
`${hostFilePath}:/test-file:ro`,
|
|
167
|
+
image,
|
|
168
|
+
"cat",
|
|
169
|
+
"/test-file"
|
|
170
|
+
],
|
|
171
|
+
{ stdio: ["pipe", "pipe", "pipe"], timeout: 1e4 }
|
|
172
|
+
);
|
|
173
|
+
const ok = result.status === 0;
|
|
174
|
+
if (!ok) {
|
|
175
|
+
log(
|
|
176
|
+
`Docker cannot access path: ${result.stderr?.toString().trim()}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return ok;
|
|
180
|
+
} catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
88
183
|
}
|
|
89
184
|
function buildDockerArgs(opts) {
|
|
90
185
|
const args = [
|
|
@@ -123,20 +218,46 @@ function buildExecArgs(id, command) {
|
|
|
123
218
|
}
|
|
124
219
|
function startContainer(opts) {
|
|
125
220
|
const args = buildDockerArgs(opts);
|
|
221
|
+
log(`Starting container yolobox-${opts.id}`);
|
|
126
222
|
const result = spawnSync("docker", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
223
|
+
const stdout = result.stdout?.toString() || "";
|
|
224
|
+
const stderr = result.stderr?.toString() || "";
|
|
225
|
+
logCommand("docker", args, {
|
|
226
|
+
status: result.status,
|
|
227
|
+
stdout,
|
|
228
|
+
stderr
|
|
229
|
+
});
|
|
127
230
|
return result.status === 0;
|
|
128
231
|
}
|
|
129
232
|
function restartContainer(id) {
|
|
130
|
-
|
|
233
|
+
log(`Restarting container yolobox-${id}`);
|
|
234
|
+
const args = ["start", `yolobox-${id}`];
|
|
235
|
+
const result = spawnSync("docker", args, {
|
|
131
236
|
stdio: ["pipe", "pipe", "pipe"]
|
|
132
237
|
});
|
|
238
|
+
const stdout = result.stdout?.toString() || "";
|
|
239
|
+
const stderr = result.stderr?.toString() || "";
|
|
240
|
+
logCommand("docker", args, { status: result.status, stdout, stderr });
|
|
133
241
|
return result.status === 0;
|
|
134
242
|
}
|
|
135
243
|
function execInContainer(id, command) {
|
|
136
244
|
const args = buildExecArgs(id, command);
|
|
245
|
+
log(`Exec in container: docker ${args.join(" ")}`);
|
|
137
246
|
const result = spawnSync("docker", args, { stdio: "inherit" });
|
|
247
|
+
log(`Exec exited with code ${result.status}`);
|
|
138
248
|
return result.status ?? 1;
|
|
139
249
|
}
|
|
250
|
+
function execInContainerNonInteractive(id, command) {
|
|
251
|
+
const args = ["exec", `yolobox-${id}`, ...command];
|
|
252
|
+
log(`Exec (non-interactive) in container: docker ${args.join(" ")}`);
|
|
253
|
+
const result = spawnSync("docker", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
254
|
+
logCommand("docker", args, {
|
|
255
|
+
status: result.status,
|
|
256
|
+
stdout: result.stdout?.toString() || "",
|
|
257
|
+
stderr: result.stderr?.toString() || ""
|
|
258
|
+
});
|
|
259
|
+
return result.status === 0;
|
|
260
|
+
}
|
|
140
261
|
function timeAgo(dateStr) {
|
|
141
262
|
const date = new Date(dateStr);
|
|
142
263
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
@@ -150,7 +271,7 @@ function timeAgo(dateStr) {
|
|
|
150
271
|
}
|
|
151
272
|
function getWorktreeBranch(repoPath, id) {
|
|
152
273
|
try {
|
|
153
|
-
const worktreePath =
|
|
274
|
+
const worktreePath = join2(repoPath, ".yolobox", id);
|
|
154
275
|
return execSync(`git -C "${worktreePath}" rev-parse --abbrev-ref HEAD`, {
|
|
155
276
|
encoding: "utf-8"
|
|
156
277
|
}).trim();
|
|
@@ -180,30 +301,43 @@ function listContainers() {
|
|
|
180
301
|
}
|
|
181
302
|
}
|
|
182
303
|
function killContainer(id) {
|
|
183
|
-
|
|
304
|
+
log(`Killing container yolobox-${id}`);
|
|
305
|
+
const stopArgs = ["stop", `yolobox-${id}`];
|
|
306
|
+
const stop = spawnSync("docker", stopArgs, {
|
|
184
307
|
stdio: ["pipe", "pipe", "pipe"]
|
|
185
308
|
});
|
|
309
|
+
logCommand("docker", stopArgs, {
|
|
310
|
+
status: stop.status,
|
|
311
|
+
stdout: stop.stdout?.toString() || "",
|
|
312
|
+
stderr: stop.stderr?.toString() || ""
|
|
313
|
+
});
|
|
186
314
|
if (stop.status !== 0) return false;
|
|
187
|
-
const
|
|
315
|
+
const rmArgs = ["rm", `yolobox-${id}`];
|
|
316
|
+
const rm = spawnSync("docker", rmArgs, {
|
|
188
317
|
stdio: ["pipe", "pipe", "pipe"]
|
|
189
318
|
});
|
|
319
|
+
logCommand("docker", rmArgs, {
|
|
320
|
+
status: rm.status,
|
|
321
|
+
stdout: rm.stdout?.toString() || "",
|
|
322
|
+
stderr: rm.stderr?.toString() || ""
|
|
323
|
+
});
|
|
190
324
|
return rm.status === 0;
|
|
191
325
|
}
|
|
192
326
|
|
|
193
327
|
// src/lib/ui.ts
|
|
194
328
|
import * as p from "@clack/prompts";
|
|
195
|
-
import
|
|
329
|
+
import pc2 from "picocolors";
|
|
196
330
|
function intro2() {
|
|
197
|
-
p.intro(
|
|
331
|
+
p.intro(pc2.bgCyan(pc2.black(` yolobox v${"0.1.6"} `)));
|
|
198
332
|
}
|
|
199
333
|
function success(message) {
|
|
200
334
|
p.log.success(message);
|
|
201
335
|
}
|
|
202
|
-
function
|
|
203
|
-
p.log.error(
|
|
336
|
+
function error2(message) {
|
|
337
|
+
p.log.error(pc2.red(message));
|
|
204
338
|
}
|
|
205
339
|
function warn(message) {
|
|
206
|
-
p.log.warn(
|
|
340
|
+
p.log.warn(pc2.yellow(message));
|
|
207
341
|
}
|
|
208
342
|
function info(message) {
|
|
209
343
|
p.log.info(message);
|
|
@@ -227,14 +361,14 @@ var attach_default = defineCommand({
|
|
|
227
361
|
},
|
|
228
362
|
run: async ({ args }) => {
|
|
229
363
|
if (!isDockerRunning()) {
|
|
230
|
-
|
|
364
|
+
error2("Docker is not running.");
|
|
231
365
|
return process.exit(1);
|
|
232
366
|
}
|
|
233
367
|
let id = args.id;
|
|
234
368
|
if (!id) {
|
|
235
369
|
const containers = listContainers().filter((c) => c.status === "running");
|
|
236
370
|
if (containers.length === 0) {
|
|
237
|
-
|
|
371
|
+
error2("No running yolobox containers found.");
|
|
238
372
|
return process.exit(1);
|
|
239
373
|
}
|
|
240
374
|
if (containers.length === 1) {
|
|
@@ -255,13 +389,13 @@ var attach_default = defineCommand({
|
|
|
255
389
|
const containers = listContainers();
|
|
256
390
|
const match = containers.find((c) => c.id === id);
|
|
257
391
|
if (!match) {
|
|
258
|
-
|
|
392
|
+
error2(`No yolobox container found with ID "${id}".`);
|
|
259
393
|
return process.exit(1);
|
|
260
394
|
}
|
|
261
395
|
if (match.status !== "running") {
|
|
262
396
|
info(`Restarting stopped container "${id}"...`);
|
|
263
397
|
if (!restartContainer(id)) {
|
|
264
|
-
|
|
398
|
+
error2(`Failed to restart container "${id}".`);
|
|
265
399
|
return process.exit(1);
|
|
266
400
|
}
|
|
267
401
|
}
|
|
@@ -281,17 +415,17 @@ import {
|
|
|
281
415
|
mkdirSync,
|
|
282
416
|
readFileSync,
|
|
283
417
|
unlinkSync,
|
|
284
|
-
writeFileSync
|
|
418
|
+
writeFileSync as writeFileSync2
|
|
285
419
|
} from "fs";
|
|
286
420
|
import { homedir } from "os";
|
|
287
|
-
import { join as
|
|
421
|
+
import { join as join3 } from "path";
|
|
288
422
|
var AUTH_DIR_NAME = ".yolobox";
|
|
289
423
|
var AUTH_FILE_NAME = "auth.json";
|
|
290
424
|
function getAuthDir() {
|
|
291
|
-
return
|
|
425
|
+
return join3(homedir(), AUTH_DIR_NAME);
|
|
292
426
|
}
|
|
293
427
|
function getAuthFilePath() {
|
|
294
|
-
return
|
|
428
|
+
return join3(getAuthDir(), AUTH_FILE_NAME);
|
|
295
429
|
}
|
|
296
430
|
function saveToken(token) {
|
|
297
431
|
const dir = getAuthDir();
|
|
@@ -299,7 +433,7 @@ function saveToken(token) {
|
|
|
299
433
|
mkdirSync(dir, { mode: 448, recursive: true });
|
|
300
434
|
}
|
|
301
435
|
const data = JSON.stringify({ claudeOauthToken: token }, null, 2);
|
|
302
|
-
|
|
436
|
+
writeFileSync2(getAuthFilePath(), `${data}
|
|
303
437
|
`, { mode: 384 });
|
|
304
438
|
}
|
|
305
439
|
function loadToken() {
|
|
@@ -380,7 +514,7 @@ var auth_default = defineCommand2({
|
|
|
380
514
|
if (args.token) {
|
|
381
515
|
const token = args.token;
|
|
382
516
|
if (!isValidToken(token)) {
|
|
383
|
-
|
|
517
|
+
error2(
|
|
384
518
|
'Invalid token. Expected a token starting with "sk-ant-".\nRun `claude setup-token` to generate a valid token.'
|
|
385
519
|
);
|
|
386
520
|
process.exit(1);
|
|
@@ -393,7 +527,7 @@ var auth_default = defineCommand2({
|
|
|
393
527
|
const envToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
394
528
|
if (envToken) {
|
|
395
529
|
if (!isValidToken(envToken)) {
|
|
396
|
-
|
|
530
|
+
error2(
|
|
397
531
|
"CLAUDE_CODE_OAUTH_TOKEN is set but does not look like a valid token."
|
|
398
532
|
);
|
|
399
533
|
process.exit(1);
|
|
@@ -446,10 +580,11 @@ function showStatus() {
|
|
|
446
580
|
import { defineCommand as defineCommand3 } from "citty";
|
|
447
581
|
|
|
448
582
|
// src/lib/container-setup.ts
|
|
583
|
+
import fs2 from "fs";
|
|
449
584
|
import path2 from "path";
|
|
450
585
|
|
|
451
586
|
// src/lib/git.ts
|
|
452
|
-
import { execSync as execSync2 } from "child_process";
|
|
587
|
+
import { execSync as execSync2, spawnSync as spawnSync2 } from "child_process";
|
|
453
588
|
function exec(cmd) {
|
|
454
589
|
return execSync2(cmd, {
|
|
455
590
|
encoding: "utf-8",
|
|
@@ -468,7 +603,7 @@ function getRepoRoot() {
|
|
|
468
603
|
return exec("git rev-parse --show-toplevel");
|
|
469
604
|
}
|
|
470
605
|
function getGitDir() {
|
|
471
|
-
return exec("git rev-parse --git-dir");
|
|
606
|
+
return exec("git rev-parse --absolute-git-dir");
|
|
472
607
|
}
|
|
473
608
|
function getBranches() {
|
|
474
609
|
const output = exec('git branch --list --format="%(refname:short)"');
|
|
@@ -514,6 +649,28 @@ function getGitIdentity() {
|
|
|
514
649
|
return { name: "", email: "" };
|
|
515
650
|
}
|
|
516
651
|
}
|
|
652
|
+
function listUntrackedFiles(repoRoot, pathspec) {
|
|
653
|
+
const result = spawnSync2(
|
|
654
|
+
"git",
|
|
655
|
+
[
|
|
656
|
+
"-C",
|
|
657
|
+
repoRoot,
|
|
658
|
+
"ls-files",
|
|
659
|
+
"--others",
|
|
660
|
+
"--exclude-standard",
|
|
661
|
+
"--",
|
|
662
|
+
pathspec
|
|
663
|
+
],
|
|
664
|
+
{
|
|
665
|
+
encoding: "utf-8",
|
|
666
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
667
|
+
}
|
|
668
|
+
);
|
|
669
|
+
if (result.status !== 0) return [];
|
|
670
|
+
const output = result.stdout?.trim() ?? "";
|
|
671
|
+
if (!output) return [];
|
|
672
|
+
return output.split("\n").filter(Boolean);
|
|
673
|
+
}
|
|
517
674
|
|
|
518
675
|
// src/lib/id.ts
|
|
519
676
|
var ADJECTIVES = [
|
|
@@ -1285,10 +1442,51 @@ function getExistingWorktreeIds(repoRoot) {
|
|
|
1285
1442
|
}
|
|
1286
1443
|
|
|
1287
1444
|
// src/lib/container-setup.ts
|
|
1445
|
+
function copyUntrackedFilesFromCwd(options) {
|
|
1446
|
+
const { repoRoot, worktreePath, cwd } = options;
|
|
1447
|
+
const relativeCwd = path2.relative(repoRoot, cwd);
|
|
1448
|
+
if (relativeCwd.startsWith("..") || path2.isAbsolute(relativeCwd)) {
|
|
1449
|
+
log(`Skipping untracked copy: cwd outside repo (${cwd})`);
|
|
1450
|
+
return 0;
|
|
1451
|
+
}
|
|
1452
|
+
if (relativeCwd.split(path2.sep).includes(".yolobox")) {
|
|
1453
|
+
log(`Skipping untracked copy: cwd inside .yolobox (${cwd})`);
|
|
1454
|
+
return 0;
|
|
1455
|
+
}
|
|
1456
|
+
const pathspec = relativeCwd === "" ? "." : relativeCwd;
|
|
1457
|
+
const files = listUntrackedFiles(repoRoot, pathspec);
|
|
1458
|
+
if (files.length === 0) return 0;
|
|
1459
|
+
let copied = 0;
|
|
1460
|
+
for (const file of files) {
|
|
1461
|
+
const src = path2.join(repoRoot, file);
|
|
1462
|
+
const dest = path2.join(worktreePath, file);
|
|
1463
|
+
try {
|
|
1464
|
+
if (fs2.existsSync(dest)) continue;
|
|
1465
|
+
fs2.mkdirSync(path2.dirname(dest), { recursive: true });
|
|
1466
|
+
const stat = fs2.lstatSync(src);
|
|
1467
|
+
if (stat.isSymbolicLink()) {
|
|
1468
|
+
const target = fs2.readlinkSync(src);
|
|
1469
|
+
fs2.symlinkSync(target, dest);
|
|
1470
|
+
} else if (stat.isDirectory()) {
|
|
1471
|
+
fs2.mkdirSync(dest, { recursive: true });
|
|
1472
|
+
} else {
|
|
1473
|
+
fs2.copyFileSync(src, dest);
|
|
1474
|
+
}
|
|
1475
|
+
copied++;
|
|
1476
|
+
} catch (err) {
|
|
1477
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1478
|
+
log(`Failed to copy untracked file "${file}": ${message}`);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
return copied;
|
|
1482
|
+
}
|
|
1288
1483
|
async function setupContainer(options = {}) {
|
|
1289
1484
|
intro2();
|
|
1485
|
+
if (isEnabled()) {
|
|
1486
|
+
info(`Debug log: ${getLogPath()}`);
|
|
1487
|
+
}
|
|
1290
1488
|
if (!isDockerRunning()) {
|
|
1291
|
-
|
|
1489
|
+
error2("Docker is not running. Start Docker and try again.");
|
|
1292
1490
|
process.exit(1);
|
|
1293
1491
|
}
|
|
1294
1492
|
if (!isInsideGitRepo()) {
|
|
@@ -1296,7 +1494,7 @@ async function setupContainer(options = {}) {
|
|
|
1296
1494
|
message: "No git repo found. Initialize one here?"
|
|
1297
1495
|
});
|
|
1298
1496
|
if (p.isCancel(shouldInit) || !shouldInit) {
|
|
1299
|
-
|
|
1497
|
+
error2("yolobox needs a git repo for worktrees.");
|
|
1300
1498
|
process.exit(1);
|
|
1301
1499
|
}
|
|
1302
1500
|
initRepo();
|
|
@@ -1308,6 +1506,8 @@ async function setupContainer(options = {}) {
|
|
|
1308
1506
|
}
|
|
1309
1507
|
const repoRoot = getRepoRoot();
|
|
1310
1508
|
const gitDir = getGitDir();
|
|
1509
|
+
log(`Repo root: ${repoRoot}`);
|
|
1510
|
+
log(`Git dir: ${gitDir}`);
|
|
1311
1511
|
let id;
|
|
1312
1512
|
if (options.name) {
|
|
1313
1513
|
id = options.name;
|
|
@@ -1321,7 +1521,7 @@ async function setupContainer(options = {}) {
|
|
|
1321
1521
|
const containers = listContainers();
|
|
1322
1522
|
const existing = containers.find((c) => c.id === id);
|
|
1323
1523
|
if (existing && existing.status === "running") {
|
|
1324
|
-
|
|
1524
|
+
error2(
|
|
1325
1525
|
`Container "${id}" is already running. Use "yolobox attach ${id}" to connect.`
|
|
1326
1526
|
);
|
|
1327
1527
|
process.exit(1);
|
|
@@ -1343,10 +1543,26 @@ async function setupContainer(options = {}) {
|
|
|
1343
1543
|
success(`Created worktree .yolobox/${id} (branch: yolo/${id})`);
|
|
1344
1544
|
}
|
|
1345
1545
|
ensureGitignore(repoRoot);
|
|
1546
|
+
if (!worktreeAlreadyExists && !branchAlreadyExists) {
|
|
1547
|
+
const copied = copyUntrackedFilesFromCwd({
|
|
1548
|
+
repoRoot,
|
|
1549
|
+
worktreePath,
|
|
1550
|
+
cwd: process.cwd()
|
|
1551
|
+
});
|
|
1552
|
+
if (copied > 0) {
|
|
1553
|
+
const cwdRel = path2.relative(repoRoot, process.cwd()) || ".";
|
|
1554
|
+
const label = copied === 1 ? "file" : "files";
|
|
1555
|
+
info(`Copied ${copied} untracked ${label} from ${cwdRel}`);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1346
1558
|
const gitIdentity = getGitIdentity();
|
|
1559
|
+
log("Resolving Docker image...");
|
|
1347
1560
|
const imageResolution = resolveDockerImage({
|
|
1348
1561
|
envImage: process.env.YOLOBOX_IMAGE
|
|
1349
1562
|
});
|
|
1563
|
+
log(
|
|
1564
|
+
`Resolved image: ${imageResolution.image} (source: ${imageResolution.source})`
|
|
1565
|
+
);
|
|
1350
1566
|
if (imageResolution.source === "env") {
|
|
1351
1567
|
info(`Using custom Docker image: ${imageResolution.image}`);
|
|
1352
1568
|
} else if (imageResolution.source === "local") {
|
|
@@ -1366,17 +1582,35 @@ async function setupContainer(options = {}) {
|
|
|
1366
1582
|
);
|
|
1367
1583
|
if (!pulled) {
|
|
1368
1584
|
spinner.stop("Failed to pull image", 1);
|
|
1369
|
-
|
|
1585
|
+
error2("Failed to pull Docker image.");
|
|
1370
1586
|
process.exit(1);
|
|
1371
1587
|
}
|
|
1372
1588
|
spinner.stop("Docker image pulled");
|
|
1373
1589
|
}
|
|
1590
|
+
const testFile = path2.join(gitDir, "HEAD");
|
|
1591
|
+
if (!canDockerAccessPath(testFile, imageResolution.image)) {
|
|
1592
|
+
error2("Docker cannot access files in this directory.");
|
|
1593
|
+
if (process.platform === "darwin" && repoRoot.includes("/Library/Mobile Documents/")) {
|
|
1594
|
+
error2(
|
|
1595
|
+
'This repo is in iCloud Drive. Docker Desktop needs "Full Disk Access" to read these files.\n \u2192 Open System Settings > Privacy & Security > Full Disk Access\n \u2192 Enable Docker Desktop, then restart Docker.'
|
|
1596
|
+
);
|
|
1597
|
+
} else if (process.platform === "darwin") {
|
|
1598
|
+
error2(
|
|
1599
|
+
'Docker Desktop may need "Full Disk Access" to read files in this location.\n \u2192 Open System Settings > Privacy & Security > Full Disk Access\n \u2192 Enable Docker Desktop, then restart Docker.'
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
process.exit(1);
|
|
1603
|
+
}
|
|
1374
1604
|
const claudeOauthToken = resolveToken();
|
|
1375
1605
|
if (claudeOauthToken) {
|
|
1376
1606
|
success("Claude auth token configured");
|
|
1377
1607
|
} else {
|
|
1378
1608
|
warn('No Claude auth token. Run "yolobox auth" to set up.');
|
|
1379
1609
|
}
|
|
1610
|
+
log(`Starting container with id=${id}, image=${imageResolution.image}`);
|
|
1611
|
+
log(`Worktree path: ${worktreePath}`);
|
|
1612
|
+
log(`Git identity: ${gitIdentity.name} <${gitIdentity.email}>`);
|
|
1613
|
+
log(`Claude token: ${claudeOauthToken ? "set" : "not set"}`);
|
|
1380
1614
|
const started = startContainer({
|
|
1381
1615
|
id,
|
|
1382
1616
|
worktreePath,
|
|
@@ -1387,9 +1621,26 @@ async function setupContainer(options = {}) {
|
|
|
1387
1621
|
claudeOauthToken: claudeOauthToken ?? void 0
|
|
1388
1622
|
});
|
|
1389
1623
|
if (!started) {
|
|
1390
|
-
|
|
1624
|
+
if (isEnabled()) {
|
|
1625
|
+
error2(
|
|
1626
|
+
`Failed to start container. Check ${getLogPath()} for details.`
|
|
1627
|
+
);
|
|
1628
|
+
} else {
|
|
1629
|
+
error2("Failed to start container. Run with --debug for details.");
|
|
1630
|
+
}
|
|
1391
1631
|
process.exit(1);
|
|
1392
1632
|
}
|
|
1633
|
+
const safeDirOk = execInContainerNonInteractive(id, [
|
|
1634
|
+
"git",
|
|
1635
|
+
"config",
|
|
1636
|
+
"--global",
|
|
1637
|
+
"--add",
|
|
1638
|
+
"safe.directory",
|
|
1639
|
+
"/workspace"
|
|
1640
|
+
]);
|
|
1641
|
+
if (!safeDirOk) {
|
|
1642
|
+
log("Failed to mark /workspace as a safe git directory");
|
|
1643
|
+
}
|
|
1393
1644
|
return { id, repoRoot };
|
|
1394
1645
|
}
|
|
1395
1646
|
|
|
@@ -1422,7 +1673,7 @@ var claude_default = defineCommand3({
|
|
|
1422
1673
|
});
|
|
1423
1674
|
|
|
1424
1675
|
// src/commands/help.ts
|
|
1425
|
-
import { spawnSync as
|
|
1676
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1426
1677
|
import { defineCommand as defineCommand4 } from "citty";
|
|
1427
1678
|
var help_default = defineCommand4({
|
|
1428
1679
|
meta: {
|
|
@@ -1430,7 +1681,7 @@ var help_default = defineCommand4({
|
|
|
1430
1681
|
description: "Show help information"
|
|
1431
1682
|
},
|
|
1432
1683
|
run: async () => {
|
|
1433
|
-
const result =
|
|
1684
|
+
const result = spawnSync3(process.argv[0], [process.argv[1], "--help"], {
|
|
1434
1685
|
stdio: "inherit"
|
|
1435
1686
|
});
|
|
1436
1687
|
process.exit(result.status || 0);
|
|
@@ -1453,14 +1704,14 @@ var kill_default = defineCommand5({
|
|
|
1453
1704
|
},
|
|
1454
1705
|
run: async ({ args }) => {
|
|
1455
1706
|
if (!isDockerRunning()) {
|
|
1456
|
-
|
|
1707
|
+
error2("Docker is not running.");
|
|
1457
1708
|
return process.exit(1);
|
|
1458
1709
|
}
|
|
1459
1710
|
let id = args.id;
|
|
1460
1711
|
if (!id) {
|
|
1461
1712
|
const containers = listContainers();
|
|
1462
1713
|
if (containers.length === 0) {
|
|
1463
|
-
|
|
1714
|
+
error2("No yolobox containers found.");
|
|
1464
1715
|
return process.exit(1);
|
|
1465
1716
|
}
|
|
1466
1717
|
const selected = await p.select({
|
|
@@ -1486,13 +1737,13 @@ var kill_default = defineCommand5({
|
|
|
1486
1737
|
const containers = listContainers();
|
|
1487
1738
|
const match = containers.find((c) => c.id === id);
|
|
1488
1739
|
if (!match) {
|
|
1489
|
-
|
|
1740
|
+
error2(`No yolobox container found with ID "${id}".`);
|
|
1490
1741
|
return process.exit(1);
|
|
1491
1742
|
}
|
|
1492
1743
|
}
|
|
1493
1744
|
const killed = killContainer(id);
|
|
1494
1745
|
if (!killed) {
|
|
1495
|
-
|
|
1746
|
+
error2(`Failed to kill yolobox-${id}. Is it running?`);
|
|
1496
1747
|
process.exit(1);
|
|
1497
1748
|
}
|
|
1498
1749
|
success(`Killed yolobox-${id}`);
|
|
@@ -1522,7 +1773,7 @@ var ls_default = defineCommand6({
|
|
|
1522
1773
|
},
|
|
1523
1774
|
run: async () => {
|
|
1524
1775
|
if (!isDockerRunning()) {
|
|
1525
|
-
|
|
1776
|
+
error2("Docker is not running.");
|
|
1526
1777
|
process.exit(1);
|
|
1527
1778
|
}
|
|
1528
1779
|
const containers = listContainers();
|
|
@@ -1536,12 +1787,12 @@ var ls_default = defineCommand6({
|
|
|
1536
1787
|
const statusW = Math.max(8, ...containers.map((c) => c.status.length)) + 2;
|
|
1537
1788
|
const createdW = Math.max(9, ...containers.map((c) => c.created.length)) + 2;
|
|
1538
1789
|
const header = `${"ID".padEnd(idW)}${"BRANCH".padEnd(branchW)}${"STATUS".padEnd(statusW)}${"CREATED".padEnd(createdW)}PATH`;
|
|
1539
|
-
console.log(
|
|
1790
|
+
console.log(pc2.dim(header));
|
|
1540
1791
|
for (let i = 0; i < containers.length; i++) {
|
|
1541
1792
|
const c = containers[i];
|
|
1542
|
-
const statusColor = c.status === "running" ?
|
|
1793
|
+
const statusColor = c.status === "running" ? pc2.green : pc2.yellow;
|
|
1543
1794
|
console.log(
|
|
1544
|
-
`${c.id.padEnd(idW)}${c.branch.padEnd(branchW)}${statusColor(c.status.padEnd(statusW))}${
|
|
1795
|
+
`${c.id.padEnd(idW)}${c.branch.padEnd(branchW)}${statusColor(c.status.padEnd(statusW))}${pc2.dim(c.created.padEnd(createdW))}${pc2.dim(paths[i])}`
|
|
1545
1796
|
);
|
|
1546
1797
|
}
|
|
1547
1798
|
}
|
|
@@ -1604,7 +1855,7 @@ var nuke_default = defineCommand7({
|
|
|
1604
1855
|
const targets = [...targetMap.values()];
|
|
1605
1856
|
if (targets.length === 0) {
|
|
1606
1857
|
if (!dockerRunning) {
|
|
1607
|
-
|
|
1858
|
+
error2(
|
|
1608
1859
|
"Docker is not running and no local branches or worktrees found."
|
|
1609
1860
|
);
|
|
1610
1861
|
} else {
|
|
@@ -1622,18 +1873,18 @@ Found ${targets.length} yolobox resource${targets.length === 1 ? "" : "s"}:
|
|
|
1622
1873
|
for (const target of targets) {
|
|
1623
1874
|
const parts = [];
|
|
1624
1875
|
if (target.container) {
|
|
1625
|
-
const statusColor = target.container.status === "running" ?
|
|
1876
|
+
const statusColor = target.container.status === "running" ? pc2.green : pc2.dim;
|
|
1626
1877
|
parts.push(`container ${statusColor(target.container.status)}`);
|
|
1627
1878
|
}
|
|
1628
1879
|
if (target.hasBranch) {
|
|
1629
|
-
parts.push(`branch ${
|
|
1880
|
+
parts.push(`branch ${pc2.cyan(`yolo/${target.id}`)}`);
|
|
1630
1881
|
}
|
|
1631
1882
|
if (target.hasWorktree) {
|
|
1632
1883
|
parts.push("worktree");
|
|
1633
1884
|
}
|
|
1634
|
-
const pathSuffix = args.all && target.container ? ` ${
|
|
1885
|
+
const pathSuffix = args.all && target.container ? ` ${pc2.dim(target.container.path)}` : "";
|
|
1635
1886
|
console.log(
|
|
1636
|
-
` ${
|
|
1887
|
+
` ${pc2.bold(target.id)} ${parts.join(pc2.dim(" \xB7 "))}${pathSuffix}`
|
|
1637
1888
|
);
|
|
1638
1889
|
}
|
|
1639
1890
|
console.log();
|
|
@@ -1676,10 +1927,10 @@ Found ${targets.length} yolobox resource${targets.length === 1 ? "" : "s"}:
|
|
|
1676
1927
|
if (!target.container) continue;
|
|
1677
1928
|
const success2 = killContainer(target.id);
|
|
1678
1929
|
if (success2) {
|
|
1679
|
-
success(`Killed container ${
|
|
1930
|
+
success(`Killed container ${pc2.bold(target.id)}`);
|
|
1680
1931
|
containersKilled++;
|
|
1681
1932
|
} else {
|
|
1682
|
-
|
|
1933
|
+
error2(`Failed to kill container ${target.id}`);
|
|
1683
1934
|
killFailures++;
|
|
1684
1935
|
}
|
|
1685
1936
|
}
|
|
@@ -1692,7 +1943,7 @@ Found ${targets.length} yolobox resource${targets.length === 1 ? "" : "s"}:
|
|
|
1692
1943
|
const removed = removeWorktree(repoRoot, target.id);
|
|
1693
1944
|
if (removed) {
|
|
1694
1945
|
success(
|
|
1695
|
-
`Removed worktree ${
|
|
1946
|
+
`Removed worktree ${pc2.bold(`.yolobox/${target.id}`)}`
|
|
1696
1947
|
);
|
|
1697
1948
|
worktreesRemoved++;
|
|
1698
1949
|
} else {
|
|
@@ -1703,7 +1954,7 @@ Found ${targets.length} yolobox resource${targets.length === 1 ? "" : "s"}:
|
|
|
1703
1954
|
const branchName = `yolo/${target.id}`;
|
|
1704
1955
|
const deleted = deleteBranch(branchName);
|
|
1705
1956
|
if (deleted) {
|
|
1706
|
-
success(`Deleted branch ${
|
|
1957
|
+
success(`Deleted branch ${pc2.bold(branchName)}`);
|
|
1707
1958
|
branchesDeleted++;
|
|
1708
1959
|
} else {
|
|
1709
1960
|
warn(`Could not delete branch ${branchName}`);
|
|
@@ -1733,7 +1984,7 @@ Found ${targets.length} yolobox resource${targets.length === 1 ? "" : "s"}:
|
|
|
1733
1984
|
summaryParts.push(
|
|
1734
1985
|
`${killFailures} container${s(killFailures)} failed to stop`
|
|
1735
1986
|
);
|
|
1736
|
-
|
|
1987
|
+
error2(`${summaryParts.join(", ")}.`);
|
|
1737
1988
|
process.exit(1);
|
|
1738
1989
|
} else {
|
|
1739
1990
|
const msg = summaryParts.join(", ");
|
|
@@ -1759,7 +2010,7 @@ var rm_default = defineCommand8({
|
|
|
1759
2010
|
},
|
|
1760
2011
|
run: async ({ args }) => {
|
|
1761
2012
|
if (!isDockerRunning()) {
|
|
1762
|
-
|
|
2013
|
+
error2("Docker is not running.");
|
|
1763
2014
|
return process.exit(1);
|
|
1764
2015
|
}
|
|
1765
2016
|
const repoRoot = getRepoRoot();
|
|
@@ -1767,7 +2018,7 @@ var rm_default = defineCommand8({
|
|
|
1767
2018
|
if (!id) {
|
|
1768
2019
|
const containers = listContainers();
|
|
1769
2020
|
if (containers.length === 0) {
|
|
1770
|
-
|
|
2021
|
+
error2("No yolobox containers found.");
|
|
1771
2022
|
return process.exit(1);
|
|
1772
2023
|
}
|
|
1773
2024
|
const selected = await p.select({
|
|
@@ -1794,7 +2045,7 @@ var rm_default = defineCommand8({
|
|
|
1794
2045
|
const hasWorktree = getExistingWorktreeIds(repoRoot).includes(id);
|
|
1795
2046
|
const hasBranch = getBranches().includes(`yolo/${id}`);
|
|
1796
2047
|
if (!hasContainer && !hasWorktree && !hasBranch) {
|
|
1797
|
-
|
|
2048
|
+
error2(`No yolobox found with ID "${id}".`);
|
|
1798
2049
|
return process.exit(1);
|
|
1799
2050
|
}
|
|
1800
2051
|
}
|
|
@@ -1831,7 +2082,7 @@ var start_default = defineCommand9({
|
|
|
1831
2082
|
run: async ({ args }) => {
|
|
1832
2083
|
if (args.name) {
|
|
1833
2084
|
if (!isDockerRunning()) {
|
|
1834
|
-
|
|
2085
|
+
error2("Docker is not running.");
|
|
1835
2086
|
return process.exit(1);
|
|
1836
2087
|
}
|
|
1837
2088
|
const containers = listContainers();
|
|
@@ -1844,7 +2095,9 @@ var start_default = defineCommand9({
|
|
|
1844
2095
|
}
|
|
1845
2096
|
info(`Restarting stopped container "${args.name}"...`);
|
|
1846
2097
|
if (!restartContainer(args.name)) {
|
|
1847
|
-
|
|
2098
|
+
error2(
|
|
2099
|
+
`Failed to restart container "${args.name}". Run with --debug for details.`
|
|
2100
|
+
);
|
|
1848
2101
|
return process.exit(1);
|
|
1849
2102
|
}
|
|
1850
2103
|
outro2(`Launching shell in ${args.name}...`);
|
|
@@ -1861,10 +2114,21 @@ var start_default = defineCommand9({
|
|
|
1861
2114
|
});
|
|
1862
2115
|
|
|
1863
2116
|
// src/index.ts
|
|
2117
|
+
var debugIndex = process.argv.indexOf("--debug");
|
|
2118
|
+
if (debugIndex !== -1) {
|
|
2119
|
+
process.argv.splice(debugIndex, 1);
|
|
2120
|
+
const logPath = enable();
|
|
2121
|
+
log(`yolobox v${"0.1.6"}`);
|
|
2122
|
+
log(`args: ${process.argv.slice(2).join(" ")}`);
|
|
2123
|
+
log(`cwd: ${process.cwd()}`);
|
|
2124
|
+
log(`node: ${process.version}`);
|
|
2125
|
+
log(`platform: ${process.platform} ${process.arch}`);
|
|
2126
|
+
log(`log file: ${logPath}`);
|
|
2127
|
+
}
|
|
1864
2128
|
var main = defineCommand10({
|
|
1865
2129
|
meta: {
|
|
1866
2130
|
name: "yolobox",
|
|
1867
|
-
version: "0.1.
|
|
2131
|
+
version: "0.1.6",
|
|
1868
2132
|
description: "Run Claude Code in Docker containers. YOLO safely."
|
|
1869
2133
|
},
|
|
1870
2134
|
subCommands: {
|