@bobbyg603/mog 0.1.2 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobbyg603/mog",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "One command to go from GitHub issue to pull request, powered by Claude Code in a Docker sandbox",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import { pushAndCreatePR } from "./github";
8
8
  import { log } from "./log";
9
9
 
10
10
  const SANDBOX_NAME = "mog";
11
+ const TEMPLATE_TAG = "mog-template:latest";
11
12
 
12
13
  async function init() {
13
14
  log.info("Initializing mog sandbox...");
@@ -43,6 +44,17 @@ async function init() {
43
44
  log.die("Sandbox failed to run. Try 'docker sandbox ls' to check its status.");
44
45
  }
45
46
 
47
+ // Save sandbox as template so it can be restored after Docker restarts
48
+ log.info("Saving sandbox snapshot (preserves auth across Docker restarts)...");
49
+ const saveResult = spawnSync("docker", ["sandbox", "save", SANDBOX_NAME, TEMPLATE_TAG], {
50
+ stdio: "inherit",
51
+ });
52
+ if (saveResult.status !== 0) {
53
+ log.warn("Failed to save sandbox snapshot. Auth may not persist across Docker restarts.");
54
+ } else {
55
+ log.ok("Snapshot saved.");
56
+ }
57
+
46
58
  log.ok("mog is ready. Run: mog <owner/repo> <issue_number>");
47
59
  }
48
60
 
@@ -57,10 +69,13 @@ async function main() {
57
69
  }
58
70
  }
59
71
 
60
- // Check docker sandbox is available
72
+ // Check docker sandbox is available (may fail if sandbox state is stale after Docker restart)
61
73
  const sandboxCheck = Bun.spawnSync(["docker", "sandbox", "ls"]);
62
74
  if (sandboxCheck.exitCode !== 0) {
63
- log.die("Docker sandbox not available. Make sure Docker Desktop is running and up to date.");
75
+ const recovered = tryRecoverSandbox(getReposDir());
76
+ if (!recovered) {
77
+ log.die("Docker sandbox not available. Make sure Docker Desktop is running and up to date.");
78
+ }
64
79
  }
65
80
 
66
81
  if (args[0] === "init") {
@@ -87,9 +102,18 @@ async function main() {
87
102
  log.die("Invalid repo format. Use: owner/repo");
88
103
  }
89
104
 
90
- // Verify sandbox exists
105
+ // Verify sandbox exists, try to restore from template if missing
91
106
  if (!(await sandboxExists(SANDBOX_NAME))) {
92
- log.die(`Sandbox '${SANDBOX_NAME}' not found. Run 'mog init' first.`);
107
+ if (!templateExists()) {
108
+ log.die(`Sandbox '${SANDBOX_NAME}' not found. Run 'mog init' first.`);
109
+ }
110
+ log.info("Sandbox missing — restoring from saved snapshot...");
111
+ const reposDir = getReposDir();
112
+ const restored = restoreSandboxFromTemplate(SANDBOX_NAME, reposDir);
113
+ if (!restored) {
114
+ log.die("Failed to restore sandbox from snapshot. Run 'mog init' to recreate.");
115
+ }
116
+ log.ok("Sandbox restored from snapshot (auth preserved).");
93
117
  }
94
118
 
95
119
  // Fetch issue
@@ -130,6 +154,46 @@ async function sandboxExists(name: string): Promise<boolean> {
130
154
  return output.includes(name);
131
155
  }
132
156
 
157
+ function templateExists(): boolean {
158
+ const result = Bun.spawnSync(["docker", "image", "inspect", TEMPLATE_TAG]);
159
+ return result.exitCode === 0;
160
+ }
161
+
162
+ function restoreSandboxFromTemplate(name: string, reposDir: string): boolean {
163
+ const create = spawnSync("docker", ["sandbox", "create", "--template", TEMPLATE_TAG, "--name", name, "claude", reposDir], {
164
+ stdio: "inherit",
165
+ });
166
+ return create.status === 0;
167
+ }
168
+
169
+ function tryRecoverSandbox(reposDir: string): boolean {
170
+ log.warn("Docker sandbox state is stale — attempting recovery...");
171
+
172
+ // Clean up stale sandbox
173
+ spawnSync("docker", ["sandbox", "rm", SANDBOX_NAME], { stdio: "ignore" });
174
+
175
+ // Check if docker sandbox ls works now
176
+ const check = Bun.spawnSync(["docker", "sandbox", "ls"]);
177
+ if (check.exitCode !== 0) {
178
+ // docker sandbox itself is broken, not just stale state
179
+ return false;
180
+ }
181
+
182
+ // If we have a saved template, restore from it
183
+ if (templateExists()) {
184
+ log.info("Restoring sandbox from saved snapshot...");
185
+ const restored = restoreSandboxFromTemplate(SANDBOX_NAME, reposDir);
186
+ if (restored) {
187
+ log.ok("Sandbox restored from snapshot (auth preserved).");
188
+ return true;
189
+ }
190
+ }
191
+
192
+ // Recovered docker sandbox command but no template — user needs to mog init
193
+ log.warn("No saved snapshot found. Run 'mog init' to set up the sandbox.");
194
+ return true;
195
+ }
196
+
133
197
  async function run(cmd: string[]): Promise<string> {
134
198
  const proc = Bun.spawnSync(cmd);
135
199
  if (proc.exitCode !== 0) {
package/src/sandbox.ts CHANGED
@@ -15,14 +15,25 @@ interface StreamEvent {
15
15
  is_error?: boolean;
16
16
  }
17
17
 
18
- export async function ensureSandbox(name: string, reposDir: string): Promise<void> {
18
+ export async function ensureSandbox(name: string, reposDir: string, templateTag?: string): Promise<void> {
19
19
  const ls = Bun.spawnSync(["docker", "sandbox", "ls"]);
20
20
  if (ls.stdout.toString().includes(name)) {
21
21
  return;
22
22
  }
23
23
 
24
+ // Try to restore from template if available
25
+ const createArgs = ["sandbox", "create"];
26
+ if (templateTag) {
27
+ const inspect = Bun.spawnSync(["docker", "image", "inspect", templateTag]);
28
+ if (inspect.exitCode === 0) {
29
+ log.info(`Restoring sandbox '${name}' from saved snapshot...`);
30
+ createArgs.push("--template", templateTag);
31
+ }
32
+ }
33
+ createArgs.push("--name", name, "claude", reposDir);
34
+
24
35
  log.info(`Creating persistent sandbox '${name}'...`);
25
- const create = Bun.spawnSync(["docker", "sandbox", "create", "--name", name, "claude", reposDir]);
36
+ const create = Bun.spawnSync(["docker", ...createArgs]);
26
37
  if (create.exitCode !== 0) {
27
38
  log.die(`Failed to create sandbox: ${create.stderr.toString()}`);
28
39
  }
package/src/worktree.ts CHANGED
@@ -13,7 +13,7 @@ export async function ensureRepo(
13
13
  log.info(`Cloning ${repo} into ${repoDir}...`);
14
14
  fs.mkdirSync(`${reposDir}/${owner}`, { recursive: true });
15
15
 
16
- const clone = Bun.spawnSync(["gh", "repo", "clone", repo, repoDir], {
16
+ const clone = Bun.spawnSync(["gh", "repo", "clone", repo, repoDir, "--", "--recurse-submodules"], {
17
17
  stdout: "inherit",
18
18
  stderr: "inherit",
19
19
  });
@@ -86,6 +86,9 @@ export async function createWorktree(
86
86
  }
87
87
  }
88
88
 
89
+ // Init submodules in the worktree if present
90
+ Bun.spawnSync(["git", "submodule", "update", "--init", "--recursive"], { cwd: worktreeDir });
91
+
89
92
  log.ok(`Worktree created at ${worktreeDir}`);
90
93
  return { worktreeDir, branchName };
91
94
  }