@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 +8 -1
- package/package.json +1 -1
- package/src/github.ts +42 -0
- package/src/index.ts +61 -8
- package/src/worktree.ts +23 -0
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
|
|
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
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
123
|
+
let repo: string;
|
|
124
|
+
let issueNum: string;
|
|
100
125
|
|
|
101
|
-
if (
|
|
102
|
-
|
|
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,
|