@bobbyg603/mog 1.2.0 → 1.3.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/README.md CHANGED
@@ -19,7 +19,7 @@ That's it. `mog` will:
19
19
  ## Prerequisites
20
20
 
21
21
  - **macOS or Windows** (Docker sandbox microVMs require Docker Desktop)
22
- - **Docker Desktop** — running and up to date (must support `docker sandbox`)
22
+ - **Docker Desktop 4.40+** — running and up to date. Docker sandbox support (required by mog) was introduced in Docker Desktop 4.40. Verify with `docker sandbox ls`.
23
23
  - **Bun** — install from [bun.sh](https://bun.sh)
24
24
  - **GitHub CLI** (`gh`) — authenticated via `gh auth login`
25
25
  - **Git** with push access to your target repos
@@ -33,6 +33,9 @@ bun install -g @bobbyg603/mog
33
33
  ## Quick start
34
34
 
35
35
  ```bash
36
+ # 0. Verify Docker sandbox support is available
37
+ docker sandbox ls
38
+
36
39
  # 1. One-time setup: create sandbox & authenticate
37
40
  mog init
38
41
  # This launches Claude Code — use /login to authenticate with your Max subscription
@@ -131,6 +134,10 @@ git worktree remove ../repo-worktrees/123-fix-broken-login
131
134
 
132
135
  **"No changes detected"** — Claude may have struggled with the issue. Check the worktree manually, or re-run with a more detailed issue description.
133
136
 
137
+ **"Docker sandbox state is stale"** — Restart Docker Desktop, or remove and recreate the sandbox: `docker sandbox rm mog && mog init`.
138
+
139
+ **"docker: 'sandbox' is not a docker command"** — Your Docker Desktop version doesn't support sandboxes. Update Docker Desktop to **4.40 or later**, then verify with `docker sandbox ls`.
140
+
134
141
  **"Failed to push"** — Ensure `gh` is authenticated with push access. Try `gh auth login` and select HTTPS.
135
142
 
136
143
  ## Managing the sandbox
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobbyg603/mog",
3
- "version": "1.2.0",
3
+ "version": "1.3.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/github.ts CHANGED
@@ -28,6 +28,48 @@ export function fetchIssue(repo: string, issueNum: string): Issue {
28
28
  };
29
29
  }
30
30
 
31
+ export function listIssues(repo: string, verbose: boolean): void {
32
+ log.info(`Fetching open issues for ${repo}...`);
33
+
34
+ const fields = verbose
35
+ ? "number,title,body,labels,assignees"
36
+ : "number,title";
37
+
38
+ const proc = Bun.spawnSync([
39
+ "gh", "issue", "list",
40
+ "--repo", repo,
41
+ "--state", "open",
42
+ "--json", fields,
43
+ ]);
44
+
45
+ if (proc.exitCode !== 0) {
46
+ log.die(`Failed to fetch issues for ${repo}. Check the repo name.`);
47
+ }
48
+
49
+ const issues = JSON.parse(proc.stdout.toString());
50
+
51
+ if (issues.length === 0) {
52
+ log.info("No open issues found.");
53
+ return;
54
+ }
55
+
56
+ log.ok(`${issues.length} open issue(s):\n`);
57
+
58
+ for (const issue of issues) {
59
+ if (verbose) {
60
+ const labels = issue.labels?.map((l: { name: string }) => l.name).join(", ") || "none";
61
+ const assignees = issue.assignees?.map((a: { login: string }) => a.login).join(", ") || "unassigned";
62
+ console.log(` #${issue.number} ${issue.title}`);
63
+ console.log(` Labels: ${labels}`);
64
+ console.log(` Assignees: ${assignees}`);
65
+ console.log(` ${(issue.body || "No description.").split("\n")[0]}`);
66
+ console.log();
67
+ } else {
68
+ console.log(` #${issue.number} ${issue.title}`);
69
+ }
70
+ }
71
+ }
72
+
31
73
  export function pushAndCreatePR(
32
74
  repo: string,
33
75
  worktreeDir: string,
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { fetchIssue } from "./github";
4
- import { ensureRepo, createWorktree } from "./worktree";
3
+ import { fetchIssue, listIssues } from "./github";
4
+ import { detectRepo, ensureRepo, createWorktree } from "./worktree";
5
5
  import { runClaude } from "./sandbox";
6
6
  import { pushAndCreatePR } from "./github";
7
7
  import { log } from "./log";
@@ -54,7 +54,7 @@ async function init() {
54
54
  log.ok("Snapshot saved.");
55
55
  }
56
56
 
57
- log.ok("mog is ready. Run: mog <owner/repo> <issue_number>");
57
+ log.ok("mog is ready. Run: mog <issue_number> (from a git repo) or mog <owner/repo> <issue_number>");
58
58
  }
59
59
 
60
60
  async function main() {
@@ -84,22 +84,75 @@ async function main() {
84
84
  return;
85
85
  }
86
86
 
87
- if (args.length < 2) {
87
+ // mog list [--verbose] or mog <owner/repo> list [--verbose]
88
+ if (args[0] === "list" || args[1] === "list") {
89
+ let repo: string;
90
+ const verbose = args.includes("--verbose");
91
+
92
+ if (args[0] === "list") {
93
+ const detected = detectRepo();
94
+ if (!detected) {
95
+ log.die("Could not detect repo from git remote. Run from inside a git repo or use: mog <owner/repo> list");
96
+ }
97
+ repo = detected;
98
+ } else {
99
+ repo = args[0];
100
+ }
101
+
102
+ listIssues(repo, verbose);
103
+ return;
104
+ }
105
+
106
+ if (args.length < 1) {
88
107
  console.log("Usage:");
89
108
  console.log(" mog init — one-time setup (create sandbox & login)");
109
+ console.log(" mog <issue_num> — auto-detect repo from git remote");
90
110
  console.log(" mog <owner/repo> <issue_num> — fetch issue, run Claude, open PR");
111
+ console.log(" mog list [--verbose] — list open issues (auto-detect repo)");
112
+ console.log(" mog <owner/repo> list [--verbose] — list open issues for a repo");
91
113
  console.log();
92
114
  console.log("Example:");
93
115
  console.log(" mog init");
116
+ console.log(" mog 123");
94
117
  console.log(" mog workingdevshero/automate-it 123");
118
+ console.log(" mog list");
119
+ console.log(" mog list --verbose");
95
120
  return;
96
121
  }
97
122
 
98
- const repo = args[0] as string;
99
- const issueNum = args[1] as string;
123
+ let repo: string;
124
+ let issueNum: string;
100
125
 
101
- if (!repo || !issueNum || !/^\d+$/.test(issueNum)) {
102
- log.die(`Invalid issue number: '${issueNum}'. Must be a positive integer.`);
126
+ if (/^\d+$/.test(args[0])) {
127
+ // mog <issue_number> auto-detect repo
128
+ const detected = detectRepo();
129
+ if (!detected) {
130
+ log.die("Could not detect repo from git remote. Run from inside a git repo or use: mog <owner/repo> <issue_num>");
131
+ }
132
+ repo = detected;
133
+ issueNum = args[0];
134
+ } else if (args.length >= 2) {
135
+ // mog <owner/repo> <issue_number>
136
+ repo = args[0];
137
+ issueNum = args[1];
138
+ if (!/^\d+$/.test(issueNum)) {
139
+ log.die(`Invalid issue number: '${issueNum}'. Must be a positive integer.`);
140
+ }
141
+ } else {
142
+ console.log("Usage:");
143
+ console.log(" mog init — one-time setup (create sandbox & login)");
144
+ console.log(" mog <issue_num> — auto-detect repo from git remote");
145
+ console.log(" mog <owner/repo> <issue_num> — fetch issue, run Claude, open PR");
146
+ console.log(" mog list [--verbose] — list open issues (auto-detect repo)");
147
+ console.log(" mog <owner/repo> list [--verbose] — list open issues for a repo");
148
+ console.log();
149
+ console.log("Example:");
150
+ console.log(" mog init");
151
+ console.log(" mog 123");
152
+ console.log(" mog workingdevshero/automate-it 123");
153
+ console.log(" mog list");
154
+ console.log(" mog list --verbose");
155
+ return;
103
156
  }
104
157
 
105
158
  const parts = repo.split("/");
package/src/worktree.ts CHANGED
@@ -1,6 +1,29 @@
1
1
  import fs from "fs";
2
2
  import { log } from "./log";
3
3
 
4
+ export function detectRepo(): string | null {
5
+ const result = Bun.spawnSync(["git", "remote", "get-url", "origin"]);
6
+ if (result.exitCode !== 0) {
7
+ return null;
8
+ }
9
+
10
+ const url = result.stdout.toString().trim();
11
+
12
+ // SSH: git@github.com:owner/repo.git
13
+ const sshMatch = url.match(/^git@[^:]+:([^/]+)\/([^/]+?)(?:\.git)?$/);
14
+ if (sshMatch) {
15
+ return `${sshMatch[1]}/${sshMatch[2]}`;
16
+ }
17
+
18
+ // HTTPS: https://github.com/owner/repo.git
19
+ const httpsMatch = url.match(/^https?:\/\/[^/]+\/([^/]+)\/([^/]+?)(?:\.git)?$/);
20
+ if (httpsMatch) {
21
+ return `${httpsMatch[1]}/${httpsMatch[2]}`;
22
+ }
23
+
24
+ return null;
25
+ }
26
+
4
27
  export function ensureRepo(
5
28
  repo: string,
6
29
  owner: string,