yolobox 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roger Garcia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # yolobox
2
+
3
+ Run Claude Code in Docker containers with `--dangerously-skip-permissions`. Each yolobox gets its own git worktree and branch, so multiple AI agents can work on the same repo simultaneously without conflicts.
4
+
5
+ ```bash
6
+ yolobox run # Interactive Claude session
7
+ yolobox run -p "fix the login bug" # Start Claude with a prompt
8
+ yolobox run --shell # Drop into bash instead of Claude
9
+ yolobox run --name cool-tiger # Use a specific ID instead of random
10
+ ```
11
+
12
+ ---
13
+
14
+ ## Development
15
+
16
+ ### Setup
17
+
18
+ ```bash
19
+ # Install dependencies
20
+ npm install
21
+
22
+ # Build the Docker image (only needed once, takes ~5 min)
23
+ npm run docker:build
24
+
25
+ # Link the CLI globally so you can use the `yolobox` command
26
+ npm link
27
+ ```
28
+
29
+ ### Testing
30
+
31
+ **End-to-end test:**
32
+
33
+ ```bash
34
+ # In this repo (or any git repo)
35
+ yolobox run --shell
36
+ ```
37
+
38
+ This will:
39
+ - ✓ Check Docker is running
40
+ - ✓ Check you're in a git repo
41
+ - ✓ Generate a random ID (e.g., `swift-falcon`)
42
+ - ✓ Create `.yolobox/swift-falcon/` worktree
43
+ - ✓ Launch you into a bash shell inside the container
44
+
45
+ Once inside the container, verify:
46
+ ```bash
47
+ pwd # Should be /workspace
48
+ git branch # Should show your yolobox branch
49
+ git config user.name # Should show your host identity
50
+ ssh-add -l # Should show your SSH keys (on macOS)
51
+ ```
52
+
53
+ Type `exit` to leave the container.
54
+
55
+ **Quick verification without Docker:**
56
+
57
+ ```bash
58
+ npm run build # Compile TypeScript
59
+ node bin/yolobox.js --help
60
+ node bin/yolobox.js run --help
61
+ npm test # Run unit tests (18 tests)
62
+ ```
63
+
64
+ ### Build & Watch
65
+
66
+ ```bash
67
+ npm run build # One-shot build
68
+ npm run dev # Watch mode (rebuild on change)
69
+ npm test # Run tests
70
+ ```
71
+
72
+ ---
73
+
74
+ ## License
75
+
76
+ MIT
package/bin/yolobox.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/index.js')
package/dist/index.js ADDED
@@ -0,0 +1,1210 @@
1
+ // src/index.ts
2
+ import { defineCommand as defineCommand7, runMain } from "citty";
3
+
4
+ // src/commands/claude.ts
5
+ import { defineCommand } from "citty";
6
+
7
+ // src/lib/docker.ts
8
+ import { execSync, spawnSync } from "child_process";
9
+ function isDockerRunning() {
10
+ try {
11
+ execSync("docker info", { stdio: ["pipe", "pipe", "pipe"] });
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+ function buildDockerArgs(opts) {
18
+ const args = [
19
+ "run",
20
+ "-d",
21
+ "--name",
22
+ `yolobox-${opts.id}`,
23
+ "-v",
24
+ `${opts.worktreePath}:/workspace`,
25
+ "-v",
26
+ `${opts.gitDir}:/repo/.git`,
27
+ "-e",
28
+ `YOLOBOX_ID=${opts.id}`,
29
+ "--label",
30
+ "yolobox=true",
31
+ "--label",
32
+ `yolobox.path=${opts.repoPath}`
33
+ ];
34
+ if (opts.gitIdentity.name) {
35
+ args.push("-e", `GIT_AUTHOR_NAME=${opts.gitIdentity.name}`);
36
+ args.push("-e", `GIT_COMMITTER_NAME=${opts.gitIdentity.name}`);
37
+ }
38
+ if (opts.gitIdentity.email) {
39
+ args.push("-e", `GIT_AUTHOR_EMAIL=${opts.gitIdentity.email}`);
40
+ args.push("-e", `GIT_COMMITTER_EMAIL=${opts.gitIdentity.email}`);
41
+ }
42
+ args.push(opts.image);
43
+ args.push("sleep", "infinity");
44
+ return args;
45
+ }
46
+ function buildExecArgs(id, command) {
47
+ return ["exec", "-it", `yolobox-${id}`, ...command];
48
+ }
49
+ function startContainer(opts) {
50
+ const args = buildDockerArgs(opts);
51
+ const result = spawnSync("docker", args, { stdio: ["pipe", "pipe", "pipe"] });
52
+ return result.status === 0;
53
+ }
54
+ function execInContainer(id, command) {
55
+ const args = buildExecArgs(id, command);
56
+ const result = spawnSync("docker", args, { stdio: "inherit" });
57
+ return result.status ?? 1;
58
+ }
59
+ function timeAgo(dateStr) {
60
+ const date = new Date(dateStr);
61
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
62
+ if (seconds < 60) return `${seconds}s ago`;
63
+ const minutes = Math.floor(seconds / 60);
64
+ if (minutes < 60) return `${minutes} min ago`;
65
+ const hours = Math.floor(minutes / 60);
66
+ if (hours < 24) return `${hours}h ago`;
67
+ const days = Math.floor(hours / 24);
68
+ return `${days}d ago`;
69
+ }
70
+ function listContainers() {
71
+ try {
72
+ const result = execSync(
73
+ 'docker ps -a --filter "label=yolobox" --format "{{.Names}} {{.Status}} {{.CreatedAt}} {{.Label \\"yolobox.path\\"}}"',
74
+ { encoding: "utf-8" }
75
+ );
76
+ return result.trim().split("\n").filter(Boolean).map((line) => {
77
+ const [name, status, created, path2] = line.split(" ");
78
+ const id = name.replace(/^yolobox-/, "");
79
+ return {
80
+ id,
81
+ branch: id,
82
+ status: status.startsWith("Up") ? "running" : "stopped",
83
+ created: timeAgo(created),
84
+ path: path2 || ""
85
+ };
86
+ });
87
+ } catch {
88
+ return [];
89
+ }
90
+ }
91
+ function killContainer(id) {
92
+ const stop = spawnSync("docker", ["stop", `yolobox-${id}`], {
93
+ stdio: ["pipe", "pipe", "pipe"]
94
+ });
95
+ if (stop.status !== 0) return false;
96
+ const rm = spawnSync("docker", ["rm", `yolobox-${id}`], {
97
+ stdio: ["pipe", "pipe", "pipe"]
98
+ });
99
+ return rm.status === 0;
100
+ }
101
+
102
+ // src/lib/git.ts
103
+ import { execSync as execSync2 } from "child_process";
104
+ function exec(cmd) {
105
+ return execSync2(cmd, {
106
+ encoding: "utf-8",
107
+ stdio: ["pipe", "pipe", "pipe"]
108
+ }).trim();
109
+ }
110
+ function isInsideGitRepo() {
111
+ try {
112
+ exec("git rev-parse --is-inside-work-tree");
113
+ return true;
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+ function getRepoRoot() {
119
+ return exec("git rev-parse --show-toplevel");
120
+ }
121
+ function getGitDir() {
122
+ return exec("git rev-parse --git-dir");
123
+ }
124
+ function getBranches() {
125
+ const output = exec('git branch --list --format="%(refname:short)"');
126
+ return output.split("\n").filter(Boolean);
127
+ }
128
+ function initRepo() {
129
+ exec("git init");
130
+ createInitialCommit();
131
+ }
132
+ function hasCommits() {
133
+ try {
134
+ exec("git rev-parse HEAD");
135
+ return true;
136
+ } catch {
137
+ return false;
138
+ }
139
+ }
140
+ function createInitialCommit() {
141
+ exec('git commit --allow-empty -m "Initial commit"');
142
+ }
143
+ function getGitIdentity() {
144
+ try {
145
+ const name = exec("git config user.name");
146
+ const email = exec("git config user.email");
147
+ return { name, email };
148
+ } catch {
149
+ return { name: "", email: "" };
150
+ }
151
+ }
152
+
153
+ // src/lib/id.ts
154
+ var ADJECTIVES = [
155
+ "able",
156
+ "adept",
157
+ "agile",
158
+ "alert",
159
+ "alive",
160
+ "ample",
161
+ "apt",
162
+ "avid",
163
+ "aware",
164
+ "azure",
165
+ "bare",
166
+ "basic",
167
+ "bliss",
168
+ "blunt",
169
+ "bold",
170
+ "brash",
171
+ "brave",
172
+ "brief",
173
+ "brisk",
174
+ "broad",
175
+ "busy",
176
+ "calm",
177
+ "civic",
178
+ "clean",
179
+ "clear",
180
+ "close",
181
+ "cool",
182
+ "core",
183
+ "cozy",
184
+ "crisp",
185
+ "curly",
186
+ "daily",
187
+ "dark",
188
+ "dear",
189
+ "deep",
190
+ "deft",
191
+ "dense",
192
+ "dizzy",
193
+ "dry",
194
+ "dual",
195
+ "eager",
196
+ "early",
197
+ "easy",
198
+ "edgy",
199
+ "elfin",
200
+ "elite",
201
+ "epic",
202
+ "equal",
203
+ "exact",
204
+ "extra",
205
+ "faint",
206
+ "fair",
207
+ "fancy",
208
+ "fast",
209
+ "feral",
210
+ "fierce",
211
+ "fiery",
212
+ "fine",
213
+ "firm",
214
+ "first",
215
+ "fit",
216
+ "fleet",
217
+ "fluid",
218
+ "flush",
219
+ "focal",
220
+ "fond",
221
+ "frank",
222
+ "free",
223
+ "fresh",
224
+ "full",
225
+ "fun",
226
+ "fuzzy",
227
+ "game",
228
+ "glad",
229
+ "gold",
230
+ "good",
231
+ "grand",
232
+ "great",
233
+ "green",
234
+ "grim",
235
+ "gross",
236
+ "grown",
237
+ "gusty",
238
+ "happy",
239
+ "hardy",
240
+ "hasty",
241
+ "hazel",
242
+ "hefty",
243
+ "high",
244
+ "home",
245
+ "hot",
246
+ "huge",
247
+ "human",
248
+ "icy",
249
+ "ideal",
250
+ "inner",
251
+ "ionic",
252
+ "iron",
253
+ "ivory",
254
+ "jade",
255
+ "jazzy",
256
+ "jolly",
257
+ "just",
258
+ "keen",
259
+ "kind",
260
+ "known",
261
+ "lanky",
262
+ "large",
263
+ "laser",
264
+ "late",
265
+ "leafy",
266
+ "lean",
267
+ "level",
268
+ "light",
269
+ "lithe",
270
+ "live",
271
+ "local",
272
+ "lone",
273
+ "long",
274
+ "loud",
275
+ "loyal",
276
+ "lucid",
277
+ "lucky",
278
+ "lunar",
279
+ "lusty",
280
+ "magic",
281
+ "main",
282
+ "major",
283
+ "maple",
284
+ "meek",
285
+ "merry",
286
+ "mild",
287
+ "mint",
288
+ "misty",
289
+ "mixed",
290
+ "modal",
291
+ "moist",
292
+ "moody",
293
+ "moral",
294
+ "muddy",
295
+ "muted",
296
+ "naive",
297
+ "naval",
298
+ "neat",
299
+ "new",
300
+ "next",
301
+ "nice",
302
+ "nimble",
303
+ "noble",
304
+ "noted",
305
+ "novel",
306
+ "oaken",
307
+ "odd",
308
+ "olive",
309
+ "only",
310
+ "open",
311
+ "opted",
312
+ "oral",
313
+ "other",
314
+ "outer",
315
+ "oval",
316
+ "own",
317
+ "pale",
318
+ "pasty",
319
+ "peak",
320
+ "perky",
321
+ "petty",
322
+ "pilot",
323
+ "pine",
324
+ "pink",
325
+ "plain",
326
+ "plumb",
327
+ "plump",
328
+ "plush",
329
+ "polar",
330
+ "polka",
331
+ "prime",
332
+ "prior",
333
+ "proud",
334
+ "pure",
335
+ "pushy",
336
+ "quick",
337
+ "quiet",
338
+ "rabid",
339
+ "rapid",
340
+ "rare",
341
+ "raspy",
342
+ "raw",
343
+ "ready",
344
+ "real",
345
+ "red",
346
+ "regal",
347
+ "rich",
348
+ "rigid",
349
+ "ripe",
350
+ "risen",
351
+ "risky",
352
+ "ritzy",
353
+ "rival",
354
+ "roast",
355
+ "rocky",
356
+ "roman",
357
+ "roomy",
358
+ "rosy",
359
+ "rough",
360
+ "round",
361
+ "royal",
362
+ "ruby",
363
+ "runic",
364
+ "rural",
365
+ "rusty",
366
+ "safe",
367
+ "sage",
368
+ "salty",
369
+ "same",
370
+ "sandy",
371
+ "sane",
372
+ "savvy",
373
+ "scaly",
374
+ "sharp",
375
+ "sheer",
376
+ "shiny",
377
+ "short",
378
+ "shy",
379
+ "silky",
380
+ "slim",
381
+ "slow",
382
+ "small",
383
+ "smart",
384
+ "smoky",
385
+ "snowy",
386
+ "snug",
387
+ "sober",
388
+ "soft",
389
+ "solar",
390
+ "solid",
391
+ "sonic",
392
+ "sorry",
393
+ "sound",
394
+ "south",
395
+ "spare",
396
+ "spicy",
397
+ "spry",
398
+ "squat",
399
+ "stale",
400
+ "stark",
401
+ "steel",
402
+ "steep",
403
+ "stern",
404
+ "stiff",
405
+ "still",
406
+ "stoic",
407
+ "stone",
408
+ "stout",
409
+ "suave",
410
+ "sunny",
411
+ "super",
412
+ "sure",
413
+ "sweet",
414
+ "swift",
415
+ "tall",
416
+ "tame",
417
+ "tan",
418
+ "tangy",
419
+ "tart",
420
+ "taut",
421
+ "teal",
422
+ "tepid",
423
+ "thick",
424
+ "thin",
425
+ "tidy",
426
+ "tight",
427
+ "tiny",
428
+ "token",
429
+ "top",
430
+ "total",
431
+ "tough",
432
+ "trim",
433
+ "true",
434
+ "tubby",
435
+ "twin",
436
+ "ultra",
437
+ "uncut",
438
+ "under",
439
+ "undue",
440
+ "unfit",
441
+ "upper",
442
+ "urban",
443
+ "used",
444
+ "usual",
445
+ "utter",
446
+ "vague",
447
+ "valid",
448
+ "vast",
449
+ "velvet",
450
+ "vivid",
451
+ "vocal",
452
+ "wacky",
453
+ "warm",
454
+ "wary",
455
+ "wavy",
456
+ "waxy",
457
+ "weak",
458
+ "weary",
459
+ "weird",
460
+ "west",
461
+ "wet",
462
+ "white",
463
+ "whole",
464
+ "wide",
465
+ "wild",
466
+ "wily",
467
+ "windy",
468
+ "wired",
469
+ "wise",
470
+ "witty",
471
+ "woke",
472
+ "woody",
473
+ "wry",
474
+ "young",
475
+ "zany",
476
+ "zappy",
477
+ "zeal",
478
+ "zen",
479
+ "zesty",
480
+ "zippy"
481
+ ];
482
+ var NOUNS = [
483
+ "ace",
484
+ "acorn",
485
+ "agent",
486
+ "alder",
487
+ "alloy",
488
+ "amber",
489
+ "anvil",
490
+ "apex",
491
+ "arch",
492
+ "arrow",
493
+ "aspen",
494
+ "atlas",
495
+ "atom",
496
+ "axle",
497
+ "badge",
498
+ "basin",
499
+ "bass",
500
+ "beam",
501
+ "bench",
502
+ "birch",
503
+ "blade",
504
+ "blaze",
505
+ "bloom",
506
+ "bluff",
507
+ "board",
508
+ "bolt",
509
+ "bond",
510
+ "bower",
511
+ "brace",
512
+ "brand",
513
+ "brass",
514
+ "brick",
515
+ "brook",
516
+ "brush",
517
+ "bugle",
518
+ "cairn",
519
+ "canal",
520
+ "cape",
521
+ "cargo",
522
+ "cedar",
523
+ "chain",
524
+ "charm",
525
+ "chess",
526
+ "chime",
527
+ "chord",
528
+ "clamp",
529
+ "clay",
530
+ "cliff",
531
+ "clock",
532
+ "cloud",
533
+ "clove",
534
+ "coach",
535
+ "coast",
536
+ "comet",
537
+ "coral",
538
+ "core",
539
+ "crane",
540
+ "crate",
541
+ "creek",
542
+ "crest",
543
+ "cross",
544
+ "crown",
545
+ "crush",
546
+ "crypt",
547
+ "cube",
548
+ "curve",
549
+ "cycle",
550
+ "darts",
551
+ "dawn",
552
+ "delta",
553
+ "depot",
554
+ "dew",
555
+ "digit",
556
+ "dingo",
557
+ "disk",
558
+ "ditch",
559
+ "dock",
560
+ "dome",
561
+ "dove",
562
+ "draft",
563
+ "drake",
564
+ "drift",
565
+ "drone",
566
+ "drum",
567
+ "dune",
568
+ "dwarf",
569
+ "eagle",
570
+ "earth",
571
+ "easel",
572
+ "edge",
573
+ "elm",
574
+ "ember",
575
+ "facet",
576
+ "fawn",
577
+ "ferry",
578
+ "fiber",
579
+ "finch",
580
+ "fjord",
581
+ "flame",
582
+ "flask",
583
+ "flax",
584
+ "fleet",
585
+ "flint",
586
+ "flock",
587
+ "flora",
588
+ "flute",
589
+ "focus",
590
+ "forge",
591
+ "fort",
592
+ "frame",
593
+ "frost",
594
+ "fruit",
595
+ "gale",
596
+ "gauge",
597
+ "gavel",
598
+ "gecko",
599
+ "geyser",
600
+ "ghost",
601
+ "glade",
602
+ "gleam",
603
+ "glint",
604
+ "globe",
605
+ "glyph",
606
+ "gnome",
607
+ "goose",
608
+ "gorge",
609
+ "grain",
610
+ "grape",
611
+ "graph",
612
+ "grasp",
613
+ "grove",
614
+ "guild",
615
+ "gull",
616
+ "gusto",
617
+ "haven",
618
+ "hawk",
619
+ "hazel",
620
+ "heath",
621
+ "hedge",
622
+ "heron",
623
+ "hinge",
624
+ "holly",
625
+ "hound",
626
+ "hull",
627
+ "hyena",
628
+ "index",
629
+ "ingot",
630
+ "inlet",
631
+ "ivory",
632
+ "jay",
633
+ "jewel",
634
+ "joint",
635
+ "kelp",
636
+ "ketch",
637
+ "knack",
638
+ "knoll",
639
+ "knot",
640
+ "kraft",
641
+ "lance",
642
+ "larch",
643
+ "lark",
644
+ "latch",
645
+ "lathe",
646
+ "ledge",
647
+ "lever",
648
+ "light",
649
+ "lilac",
650
+ "linen",
651
+ "links",
652
+ "llama",
653
+ "lodge",
654
+ "loft",
655
+ "lotus",
656
+ "lunar",
657
+ "lynch",
658
+ "lynx",
659
+ "mango",
660
+ "manor",
661
+ "maple",
662
+ "marsh",
663
+ "mast",
664
+ "maxim",
665
+ "mesa",
666
+ "meter",
667
+ "mills",
668
+ "miner",
669
+ "mint",
670
+ "mirth",
671
+ "moat",
672
+ "model",
673
+ "molar",
674
+ "moose",
675
+ "morph",
676
+ "morse",
677
+ "motor",
678
+ "mound",
679
+ "mulch",
680
+ "mural",
681
+ "myrrh",
682
+ "nerve",
683
+ "nexus",
684
+ "niche",
685
+ "node",
686
+ "notch",
687
+ "novel",
688
+ "oak",
689
+ "oasis",
690
+ "ocean",
691
+ "olive",
692
+ "onset",
693
+ "onyx",
694
+ "opera",
695
+ "orbit",
696
+ "orca",
697
+ "osprey",
698
+ "otter",
699
+ "oxide",
700
+ "ozone",
701
+ "panda",
702
+ "panel",
703
+ "patch",
704
+ "path",
705
+ "pearl",
706
+ "pecan",
707
+ "perch",
708
+ "phase",
709
+ "pilot",
710
+ "pine",
711
+ "pixel",
712
+ "plank",
713
+ "plant",
714
+ "plaza",
715
+ "plume",
716
+ "point",
717
+ "polar",
718
+ "pond",
719
+ "poppy",
720
+ "port",
721
+ "pouch",
722
+ "press",
723
+ "prism",
724
+ "probe",
725
+ "prose",
726
+ "prowl",
727
+ "pulse",
728
+ "pylon",
729
+ "quail",
730
+ "quest",
731
+ "quill",
732
+ "quota",
733
+ "rafter",
734
+ "rail",
735
+ "range",
736
+ "raven",
737
+ "realm",
738
+ "reed",
739
+ "reef",
740
+ "reign",
741
+ "relay",
742
+ "ridge",
743
+ "rivet",
744
+ "robin",
745
+ "roost",
746
+ "rover",
747
+ "ruby",
748
+ "sage",
749
+ "sail",
750
+ "salon",
751
+ "scale",
752
+ "scone",
753
+ "scope",
754
+ "scout",
755
+ "seal",
756
+ "sedge",
757
+ "shade",
758
+ "shaft",
759
+ "shell",
760
+ "shelf",
761
+ "shoal",
762
+ "shore",
763
+ "shrew",
764
+ "shrub",
765
+ "sigma",
766
+ "siren",
767
+ "skimp",
768
+ "slate",
769
+ "sleet",
770
+ "slope",
771
+ "smith",
772
+ "snare",
773
+ "solar",
774
+ "sonic",
775
+ "spark",
776
+ "spear",
777
+ "spire",
778
+ "spoke",
779
+ "spore",
780
+ "spray",
781
+ "squid",
782
+ "staff",
783
+ "stage",
784
+ "stake",
785
+ "stalk",
786
+ "steam",
787
+ "steel",
788
+ "stern",
789
+ "stone",
790
+ "storm",
791
+ "stove",
792
+ "stump",
793
+ "surge",
794
+ "swamp",
795
+ "swift",
796
+ "thorn",
797
+ "tiger",
798
+ "tile",
799
+ "timer",
800
+ "token",
801
+ "torch",
802
+ "totem",
803
+ "tower",
804
+ "trace",
805
+ "trail",
806
+ "trend",
807
+ "tribe",
808
+ "trout",
809
+ "trunk",
810
+ "tulip",
811
+ "tundra",
812
+ "tuner",
813
+ "tweed",
814
+ "twig",
815
+ "umbra",
816
+ "union",
817
+ "valve",
818
+ "vapor",
819
+ "vault",
820
+ "venom",
821
+ "verge",
822
+ "verse",
823
+ "vigor",
824
+ "vine",
825
+ "viola",
826
+ "viper",
827
+ "visor",
828
+ "vivid",
829
+ "vortex",
830
+ "wafer",
831
+ "warden",
832
+ "watch",
833
+ "wedge",
834
+ "whale",
835
+ "wheat",
836
+ "wheel",
837
+ "whirl",
838
+ "wicker",
839
+ "willow",
840
+ "wing",
841
+ "wren",
842
+ "yacht",
843
+ "yew",
844
+ "yield",
845
+ "zebra",
846
+ "zinc",
847
+ "zone"
848
+ ];
849
+ function randomElement(arr) {
850
+ return arr[Math.floor(Math.random() * arr.length)];
851
+ }
852
+ function generateId(existingIds, maxRetries = 100) {
853
+ for (let i = 0; i < maxRetries; i++) {
854
+ const id = `${randomElement(ADJECTIVES)}-${randomElement(NOUNS)}`;
855
+ if (!existingIds.has(id)) return id;
856
+ }
857
+ throw new Error("Could not generate a unique ID after 100 attempts");
858
+ }
859
+
860
+ // src/lib/ui.ts
861
+ import * as p from "@clack/prompts";
862
+ import pc from "picocolors";
863
+ function intro2() {
864
+ p.intro(pc.bgCyan(pc.black(" yolobox v0.0.1 ")));
865
+ }
866
+ function success(message) {
867
+ p.log.success(message);
868
+ }
869
+ function error(message) {
870
+ p.log.error(pc.red(message));
871
+ }
872
+ function info(message) {
873
+ p.log.info(message);
874
+ }
875
+ function outro2(message) {
876
+ p.outro(message);
877
+ }
878
+
879
+ // src/lib/worktree.ts
880
+ import { execSync as execSync3 } from "child_process";
881
+ import fs from "fs";
882
+ import path from "path";
883
+ function exec2(cmd, cwd) {
884
+ return execSync3(cmd, {
885
+ encoding: "utf-8",
886
+ cwd,
887
+ stdio: ["pipe", "pipe", "pipe"]
888
+ }).trim();
889
+ }
890
+ function createWorktree(repoRoot, id) {
891
+ const yoloboxDir = path.join(repoRoot, ".yolobox");
892
+ fs.mkdirSync(yoloboxDir, { recursive: true });
893
+ const worktreePath = path.join(yoloboxDir, id);
894
+ exec2(`git worktree add "${worktreePath}" -b "yolo/${id}"`, repoRoot);
895
+ return worktreePath;
896
+ }
897
+ function ensureGitignore(repoRoot) {
898
+ const gitignorePath = path.join(repoRoot, ".gitignore");
899
+ const entry = ".yolobox/";
900
+ if (fs.existsSync(gitignorePath)) {
901
+ const content = fs.readFileSync(gitignorePath, "utf-8");
902
+ if (content.includes(entry)) return;
903
+ const separator = content.endsWith("\n") ? "" : "\n";
904
+ fs.appendFileSync(gitignorePath, `${separator}${entry}
905
+ `);
906
+ } else {
907
+ fs.writeFileSync(gitignorePath, `${entry}
908
+ `);
909
+ }
910
+ }
911
+ function getExistingWorktreeIds(repoRoot) {
912
+ const yoloboxDir = path.join(repoRoot, ".yolobox");
913
+ if (!fs.existsSync(yoloboxDir)) return [];
914
+ return fs.readdirSync(yoloboxDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
915
+ }
916
+
917
+ // src/lib/container-setup.ts
918
+ var DOCKER_IMAGE = process.env.YOLOBOX_IMAGE || "yolobox:local";
919
+ async function setupContainer(options = {}) {
920
+ intro2();
921
+ if (!isDockerRunning()) {
922
+ error("Docker is not running. Start Docker Desktop and try again.");
923
+ process.exit(1);
924
+ }
925
+ success("Docker is running");
926
+ if (!isInsideGitRepo()) {
927
+ const shouldInit = await p.confirm({
928
+ message: "No git repo found. Initialize one here?"
929
+ });
930
+ if (p.isCancel(shouldInit) || !shouldInit) {
931
+ error("yolobox needs a git repo for worktrees.");
932
+ process.exit(1);
933
+ }
934
+ initRepo();
935
+ success("Initialized git repo");
936
+ } else {
937
+ success("Git repo detected");
938
+ }
939
+ if (!hasCommits()) {
940
+ createInitialCommit();
941
+ success("Created initial commit");
942
+ }
943
+ const repoRoot = getRepoRoot();
944
+ const gitDir = getGitDir();
945
+ let id;
946
+ if (options.name) {
947
+ id = options.name;
948
+ } else {
949
+ const branches = new Set(getBranches());
950
+ const existingWorktrees = new Set(getExistingWorktreeIds(repoRoot));
951
+ const taken = /* @__PURE__ */ new Set([...branches, ...existingWorktrees]);
952
+ id = generateId(taken);
953
+ }
954
+ const worktreePath = createWorktree(repoRoot, id);
955
+ success(`Created worktree .yolobox/${id} (branch: ${id})`);
956
+ ensureGitignore(repoRoot);
957
+ const gitIdentity = getGitIdentity();
958
+ const started = startContainer({
959
+ id,
960
+ worktreePath,
961
+ gitDir,
962
+ gitIdentity,
963
+ image: DOCKER_IMAGE,
964
+ repoPath: repoRoot
965
+ });
966
+ if (!started) {
967
+ error("Failed to start container.");
968
+ process.exit(1);
969
+ }
970
+ return { id, repoRoot };
971
+ }
972
+
973
+ // src/commands/claude.ts
974
+ var claude_default = defineCommand({
975
+ meta: {
976
+ name: "claude",
977
+ description: "Launch Claude Code with skip permissions"
978
+ },
979
+ args: {
980
+ prompt: {
981
+ type: "string",
982
+ alias: "p",
983
+ description: "Pass an initial prompt to Claude"
984
+ },
985
+ name: {
986
+ type: "string",
987
+ alias: "n",
988
+ description: "Use a specific name instead of random"
989
+ }
990
+ },
991
+ run: async ({ args }) => {
992
+ const { id } = await setupContainer({ name: args.name });
993
+ const command = args.prompt ? ["claude", "--dangerously-skip-permissions", "-p", args.prompt] : ["claude", "--dangerously-skip-permissions"];
994
+ outro2(`Launching Claude in ${id}...`);
995
+ execInContainer(id, command);
996
+ }
997
+ });
998
+
999
+ // src/commands/help.ts
1000
+ import { spawnSync as spawnSync2 } from "child_process";
1001
+ import { defineCommand as defineCommand2 } from "citty";
1002
+ var help_default = defineCommand2({
1003
+ meta: {
1004
+ name: "help",
1005
+ description: "Show help information"
1006
+ },
1007
+ run: async () => {
1008
+ const result = spawnSync2(process.argv[0], [process.argv[1], "--help"], {
1009
+ stdio: "inherit"
1010
+ });
1011
+ process.exit(result.status || 0);
1012
+ }
1013
+ });
1014
+
1015
+ // src/commands/kill.ts
1016
+ import { defineCommand as defineCommand3 } from "citty";
1017
+ var kill_default = defineCommand3({
1018
+ meta: {
1019
+ name: "kill",
1020
+ description: "Stop and remove a running yolobox container"
1021
+ },
1022
+ args: {
1023
+ id: {
1024
+ type: "positional",
1025
+ description: "The yolobox ID to kill",
1026
+ required: true
1027
+ }
1028
+ },
1029
+ run: async ({ args }) => {
1030
+ const id = args.id;
1031
+ const killed = killContainer(id);
1032
+ if (!killed) {
1033
+ error(`Failed to kill yolobox-${id}. Is it running?`);
1034
+ process.exit(1);
1035
+ }
1036
+ success(`Killed yolobox-${id}`);
1037
+ }
1038
+ });
1039
+
1040
+ // src/commands/ls.ts
1041
+ import { homedir } from "os";
1042
+ import { defineCommand as defineCommand4 } from "citty";
1043
+ function shortenPath(path2, maxLen = 40) {
1044
+ const home = homedir();
1045
+ const display = path2.startsWith(home) ? `~${path2.slice(home.length)}` : path2;
1046
+ if (display.length <= maxLen) return display;
1047
+ const parts = display.split("/");
1048
+ let short = "";
1049
+ for (let i = parts.length - 1; i >= 0; i--) {
1050
+ const candidate = parts.slice(i).join("/");
1051
+ if (candidate.length > maxLen - 2) break;
1052
+ short = candidate;
1053
+ }
1054
+ return short ? `\u2026/${short}` : `\u2026/${parts[parts.length - 1]}`;
1055
+ }
1056
+ var ls_default = defineCommand4({
1057
+ meta: {
1058
+ name: "ls",
1059
+ description: "List running yolobox containers"
1060
+ },
1061
+ run: async () => {
1062
+ if (!isDockerRunning()) {
1063
+ error("Docker is not running.");
1064
+ process.exit(1);
1065
+ }
1066
+ const containers = listContainers();
1067
+ if (containers.length === 0) {
1068
+ info("No yolobox containers found.");
1069
+ return;
1070
+ }
1071
+ const paths = containers.map((c) => shortenPath(c.path));
1072
+ const idW = Math.max(4, ...containers.map((c) => c.id.length)) + 2;
1073
+ const branchW = Math.max(8, ...containers.map((c) => c.branch.length)) + 2;
1074
+ const statusW = Math.max(8, ...containers.map((c) => c.status.length)) + 2;
1075
+ const createdW = Math.max(9, ...containers.map((c) => c.created.length)) + 2;
1076
+ const header = `${"ID".padEnd(idW)}${"BRANCH".padEnd(branchW)}${"STATUS".padEnd(statusW)}${"CREATED".padEnd(createdW)}PATH`;
1077
+ console.log(pc.dim(header));
1078
+ for (let i = 0; i < containers.length; i++) {
1079
+ const c = containers[i];
1080
+ const statusColor = c.status === "running" ? pc.green : pc.yellow;
1081
+ console.log(
1082
+ `${c.id.padEnd(idW)}${c.branch.padEnd(branchW)}${statusColor(c.status.padEnd(statusW))}${pc.dim(c.created.padEnd(createdW))}${pc.dim(paths[i])}`
1083
+ );
1084
+ }
1085
+ }
1086
+ });
1087
+
1088
+ // src/commands/nuke.ts
1089
+ import { defineCommand as defineCommand5 } from "citty";
1090
+ var nuke_default = defineCommand5({
1091
+ meta: {
1092
+ name: "nuke",
1093
+ description: "Kill all yolobox containers from the current directory"
1094
+ },
1095
+ args: {
1096
+ all: {
1097
+ type: "boolean",
1098
+ description: "Kill all yolobox containers from all directories",
1099
+ default: false
1100
+ }
1101
+ },
1102
+ run: async ({ args }) => {
1103
+ intro2("yolobox nuke");
1104
+ if (!isDockerRunning()) {
1105
+ error("Docker is not running. Please start Docker and try again.");
1106
+ process.exit(1);
1107
+ }
1108
+ const allContainers = listContainers();
1109
+ let matchingContainers;
1110
+ let locationDescription;
1111
+ if (args.all) {
1112
+ matchingContainers = allContainers;
1113
+ locationDescription = "across all directories";
1114
+ } else {
1115
+ const currentPath = isInsideGitRepo() ? getRepoRoot() : process.cwd();
1116
+ matchingContainers = allContainers.filter(
1117
+ (container) => container.path === currentPath
1118
+ );
1119
+ locationDescription = `from ${currentPath}`;
1120
+ }
1121
+ if (matchingContainers.length === 0) {
1122
+ const message = args.all ? "No yolobox containers found." : "No yolobox containers found from this directory.";
1123
+ info(message);
1124
+ process.exit(0);
1125
+ }
1126
+ console.log(
1127
+ `
1128
+ Found ${matchingContainers.length} container${matchingContainers.length === 1 ? "" : "s"} ${locationDescription}:
1129
+ `
1130
+ );
1131
+ for (const container of matchingContainers) {
1132
+ const pathDisplay = args.all ? ` [${container.path}]` : "";
1133
+ console.log(
1134
+ ` ${container.id} (${container.branch}) - ${container.status}${pathDisplay}`
1135
+ );
1136
+ }
1137
+ console.log();
1138
+ const confirmed = await p.confirm({
1139
+ message: `Kill all ${matchingContainers.length} container${matchingContainers.length === 1 ? "" : "s"}?`,
1140
+ initialValue: false
1141
+ });
1142
+ if (!confirmed || p.isCancel(confirmed)) {
1143
+ info("Cancelled.");
1144
+ process.exit(0);
1145
+ }
1146
+ const results = [];
1147
+ for (const container of matchingContainers) {
1148
+ const success2 = killContainer(container.id);
1149
+ results.push({ id: container.id, success: success2 });
1150
+ if (success2) {
1151
+ success(`Killed container ${container.id}`);
1152
+ } else {
1153
+ error(`Failed to kill container ${container.id}`);
1154
+ }
1155
+ }
1156
+ const successCount = results.filter((r) => r.success).length;
1157
+ const failureCount = results.length - successCount;
1158
+ console.log();
1159
+ if (failureCount === 0) {
1160
+ success(
1161
+ `Successfully killed all ${successCount} container${successCount === 1 ? "" : "s"}.`
1162
+ );
1163
+ process.exit(0);
1164
+ } else {
1165
+ error(
1166
+ `Killed ${successCount} container${successCount === 1 ? "" : "s"}, but ${failureCount} failed.`
1167
+ );
1168
+ process.exit(1);
1169
+ }
1170
+ }
1171
+ });
1172
+
1173
+ // src/commands/run.ts
1174
+ import { defineCommand as defineCommand6 } from "citty";
1175
+ var run_default = defineCommand6({
1176
+ meta: {
1177
+ name: "run",
1178
+ description: "Launch a shell in a new yolobox"
1179
+ },
1180
+ args: {
1181
+ name: {
1182
+ type: "string",
1183
+ alias: "n",
1184
+ description: "Use a specific name instead of random"
1185
+ }
1186
+ },
1187
+ run: async ({ args }) => {
1188
+ const { id } = await setupContainer({ name: args.name });
1189
+ outro2(`Launching shell in ${id}...`);
1190
+ execInContainer(id, ["bash"]);
1191
+ }
1192
+ });
1193
+
1194
+ // src/index.ts
1195
+ var main = defineCommand7({
1196
+ meta: {
1197
+ name: "yolobox",
1198
+ version: "0.0.1",
1199
+ description: "Run Claude Code in Docker containers. YOLO safely."
1200
+ },
1201
+ subCommands: {
1202
+ run: run_default,
1203
+ claude: claude_default,
1204
+ kill: kill_default,
1205
+ ls: ls_default,
1206
+ help: help_default,
1207
+ nuke: nuke_default
1208
+ }
1209
+ });
1210
+ runMain(main);
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "yolobox",
3
+ "version": "0.1.0",
4
+ "description": "Run Claude Code in a Docker container with --dangerously-skip-permissions. YOLO safely.",
5
+ "type": "module",
6
+ "bin": {
7
+ "yolobox": "./bin/yolobox.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "dist"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "dev": "tsup --watch",
19
+ "lint": "biome check .",
20
+ "lint:fix": "biome check --write .",
21
+ "test": "vitest",
22
+ "docker:build": "docker build -t yolobox:local -f docker/Dockerfile docker/",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "dependencies": {
26
+ "@clack/prompts": "^0.9.1",
27
+ "citty": "^0.1.6",
28
+ "picocolors": "^1.1.1"
29
+ },
30
+ "devDependencies": {
31
+ "@biomejs/biome": "^2.3.14",
32
+ "@types/node": "^22.0.0",
33
+ "tsup": "^8.3.0",
34
+ "typescript": "^5.7.0",
35
+ "vitest": "^2.1.0"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/roginn/yolobox.git"
40
+ },
41
+ "keywords": [
42
+ "claude",
43
+ "code",
44
+ "sandbox",
45
+ "docker",
46
+ "container",
47
+ "cli"
48
+ ],
49
+ "author": "Roger Garcia <npm@rawrger.com>",
50
+ "license": "MIT",
51
+ "bugs": {
52
+ "url": "https://github.com/roginn/yolobox/issues"
53
+ },
54
+ "homepage": "https://github.com/roginn/yolobox#readme"
55
+ }