oh-my-pr 2.5.0 → 2.6.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 +5 -18
- package/dist/index.cjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Oh-my-PR
|
|
2
2
|
|
|
3
|
-
**Local-first GitHub PR babysitter for Codex and Claude
|
|
3
|
+
**Local-first GitHub PR babysitter for Codex and Claude**
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<img width="409" height="409" alt="Code Factory logo" src="https://github.com/user-attachments/assets/ca339a71-40d9-4619-900f-55825f30a57f" />
|
|
@@ -12,9 +12,7 @@
|
|
|
12
12
|
[](https://nodejs.org/)
|
|
13
13
|
[](https://www.typescriptlang.org/)
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
No hosted service. No agent edits inside your working copy. Your PR automation stays on your machine.
|
|
15
|
+
Oh-my-pr babysits your PRs from your local machine, reads all PR comments and CI/CD logs, and gets your PR ready for merge to main. It uses your local Claude Code or Codex to address any issues identified in the PR or CI/CD pipeline and to ensure that any documentation is up to date. You can push a PR, walk away, and come back to a clean PR ready to be merged.
|
|
18
16
|
|
|
19
17
|
<img width="1365" height="686" alt="Code Factory dashboard" src="https://github.com/user-attachments/assets/66dfa082-c732-4989-8b05-f19aa550acb5" />
|
|
20
18
|
|
|
@@ -22,23 +20,12 @@ No hosted service. No agent edits inside your working copy. Your PR automation s
|
|
|
22
20
|
|
|
23
21
|
- Watch multiple repositories or add a single PR by URL.
|
|
24
22
|
- Auto-register open PRs, archive closed or merged PRs, and keep syncing review activity.
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- Queue repo sync, babysit/apply runs, PR questions, release processing, and social changelog generation in a durable SQLite-backed dispatcher that survives restarts.
|
|
28
|
-
- Triage feedback into `accept`, `reject`, or `flag`, with manual overrides and retry for failed or warned items.
|
|
29
|
-
- Run `codex` or `claude` in isolated worktrees under `~/.oh-my-pr`, then push verified fixes back to the PR branch.
|
|
30
|
-
- Evaluate review comments and failing CI statuses, post GitHub follow-ups, resolve review threads, and persist CI healing sessions per PR head.
|
|
31
|
-
- Detect merge conflicts and optionally let the agent resolve them automatically.
|
|
23
|
+
- Evaluate review comments and failing CI statuses, post GitHub follow-ups, resolve review threads, and heal any CI failures.
|
|
24
|
+
- Detect merge conflicts and let the agent resolve them automatically.
|
|
32
25
|
- Ask natural-language questions about any tracked PR from the dashboard or via MCP.
|
|
33
|
-
- Configure trusted reviewers, ignored bots, polling, batching, run limits, and CI-healing retry budgets from settings.
|
|
34
|
-
- Enable drain mode to stop claiming new queued work and optionally wait for active queue handlers to finish before deploys or upgrades.
|
|
35
|
-
- Check onboarding status, install Claude or Codex review workflows, and generate social changelogs every 5 PRs merged to `main`.
|
|
36
|
-
- Use the React dashboard, local REST API, MCP server, or optional Tauri desktop shell.
|
|
37
26
|
|
|
38
27
|
## How It Works
|
|
39
28
|
|
|
40
|
-
<img width="969" height="572" alt="Code Factory workflow" src="https://github.com/user-attachments/assets/b9dbd102-ae2e-4837-a862-a0282bdfa0b8" />
|
|
41
|
-
|
|
42
29
|
1. Add a repository to the watch list or register a PR directly by URL.
|
|
43
30
|
2. The watcher enqueues a durable repo-sync job in SQLite.
|
|
44
31
|
3. That sync job polls GitHub, auto-registers open PRs, syncs reviews and comments, archives PRs that closed upstream, records failing CI on the current head SHA, and queues babysitter runs for tracked PRs whose background watch is enabled.
|
package/dist/index.cjs
CHANGED
|
@@ -842,7 +842,7 @@ ${e}`:e}function yi(t,e){return[t,...e].join(" ")}function er(t){return t.stderr
|
|
|
842
842
|
`)).join(`
|
|
843
843
|
`);return["Respond with ONLY valid JSON and nothing else.","Schema:",'{"shouldRelease":boolean,"reason":string,"bump":"patch"|"minor"|"major"|null,"title":string|null,"notes":string|null}',"","You are deciding whether a merged set of pull requests should be published as a GitHub release.","Be conservative. Release only when the merged changes are meaningful enough for users or operators to care about.","",`Repository: ${t.repo}`,`Release branch: ${t.baseBranch}`,`Latest release tag: ${t.latestTag??"none"}`,"",`Trigger PR: #${t.triggerPr.number} "${t.triggerPr.title}"`,`Trigger merged at: ${t.triggerPr.mergedAt}`,`Trigger merge SHA: ${t.triggerPr.mergeSha}`,"","Merged PRs included in this candidate release:",e||"(none)","","Decision rules:","- shouldRelease=true only when the merged changes are worth announcing as a GitHub release.","- bump must be null when shouldRelease=false.","- bump must be one of patch, minor, or major when shouldRelease=true.","- Use patch for fixes or small improvements, minor for additive user-facing features, and major for breaking changes.","- title should be short and release-note ready when shouldRelease=true, else null.","- notes should be GitHub-release Markdown focused on user-visible/operator-visible impact when shouldRelease=true, else null.","- notes must have exactly TWO sections in this order:"," 1. '## Why This Matters' \u2014 a user-friendly, value-driven summary at the top that explains how the release makes users' lives better."," 2. '## Detailed Changes' \u2014 a line-by-line changelog of the included changes in plain English, more detailed than a headline but not deeply technical.","- notes should mention the key merged PRs in plain language, not internal process commentary.","- Prefer user outcomes, workflow improvements, and visible behavior over implementation details."].join(`
|
|
844
844
|
`)}function gv(t){let e=wA(t.trim());if(!e||typeof e!="object")throw new Error(`Could not parse release evaluation JSON: ${t.slice(0,500)}`);let a=e;if(typeof a.shouldRelease!="boolean")throw new Error("Release evaluation missing boolean 'shouldRelease'");if(typeof a.reason!="string"||a.reason.trim().length===0)throw new Error("Release evaluation missing string 'reason'");if(!a.shouldRelease)return{shouldRelease:!1,reason:a.reason.trim(),bump:null,title:null,notes:null};if(a.bump!=="patch"&&a.bump!=="minor"&&a.bump!=="major")throw new Error("Release evaluation returned invalid 'bump'");if(typeof a.title!="string"||a.title.trim().length===0)throw new Error("Release evaluation missing non-empty 'title'");if(typeof a.notes!="string"||a.notes.trim().length===0)throw new Error("Release evaluation missing non-empty 'notes'");return{shouldRelease:!0,reason:a.reason.trim(),bump:a.bump,title:a.title.trim(),notes:a.notes.trim()}}function wA(t){try{return JSON.parse(t)}catch{}let e=t.indexOf("{"),a=t.lastIndexOf("}");if(e===-1||a===-1||a<=e)return null;try{return JSON.parse(t.slice(e,a+1))}catch{return null}}var _i=class{storage;github;evaluateRelease;scheduleBackgroundJob;inProgress=new Set;repoLocks=new Map;constructor(e,a){this.storage=e,this.github=a.github,this.evaluateRelease=a.evaluateRelease??vv,this.scheduleBackgroundJob=a.scheduleBackgroundJob}getActiveRunCount(){return this.inProgress.size}async waitForIdle(e=12e4){let a=Date.now();for(;this.inProgress.size>0;){if(Date.now()-a>=e)return!1;await TA(50)}return!0}async enqueueMergedPullReleaseEvaluation(e){let a=await this.storage.getReleaseRunByTrigger(e.repo,e.triggerPrNumber,e.triggerMergeSha);if(a)return EA(a.status)||this.scheduleProcessing(a.id),a;let r=await this.storage.createReleaseRun({repo:e.repo,baseBranch:e.baseBranch,triggerPrNumber:e.triggerPrNumber,triggerPrTitle:e.triggerPrTitle,triggerPrUrl:e.triggerPrUrl,triggerMergeSha:e.triggerMergeSha,triggerMergedAt:e.triggerMergedAt,status:"detected",decisionReason:null,recommendedBump:null,proposedVersion:null,releaseTitle:null,releaseNotes:null,includedPrs:[],targetSha:e.triggerMergeSha,githubReleaseId:null,githubReleaseUrl:null,error:null,completedAt:null});return this.scheduleProcessing(r.id),r}async retryReleaseRun(e){if(!await this.storage.getReleaseRun(e))return;let r=await this.storage.updateReleaseRun(e,{status:"detected",error:null,completedAt:null});if(r)return this.scheduleProcessing(e),r}async processReleaseRun(e){let a=await this.storage.getReleaseRun(e);if(a)return this.withRepoLock(a.repo,async()=>{if(this.inProgress.has(e))return this.storage.getReleaseRun(e);this.inProgress.add(e);try{let r=await this.storage.getReleaseRun(e);if(!r)return;if(r.status==="published"||r.status==="skipped")return r;let n=lt(r.repo);if(!n)return this.failRun(e,`Invalid repository slug: ${r.repo}`);let i=await this.storage.getConfig();if(!i.autoCreateReleases)return await this.storage.updateReleaseRun(e,{status:"skipped",decisionReason:"Automatic release creation is disabled in settings",completedAt:new Date().toISOString()})??void 0;await this.storage.updateReleaseRun(e,{status:"evaluating",error:null,completedAt:null});let s=await this.github.buildOctokit(i),o=await this.github.findLatestSemverReleaseTag(s,n),u=_A(r),c=await this.loadIncludedPulls(s,n,r,u),l=c.map(RA),p=await this.evaluateRelease({preferredAgent:i.codingAgent,repo:r.repo,baseBranch:r.baseBranch,latestTag:o,triggerPr:u,includedPulls:c});if(!p.shouldRelease)return await this.storage.updateReleaseRun(e,{status:"skipped",decisionReason:p.reason,recommendedBump:null,proposedVersion:null,releaseTitle:null,releaseNotes:null,includedPrs:l,targetSha:r.triggerMergeSha,completedAt:new Date().toISOString()})??void 0;if(!p.bump)throw new Error("Release evaluation approved publishing but did not provide a semver bump");let d=this.github.bumpReleaseTag(o,p.bump),f=kA(p,d),y=p.notes??`Release ${d}`;await this.storage.updateReleaseRun(e,{status:"proposed",decisionReason:p.reason,recommendedBump:p.bump,proposedVersion:d,releaseTitle:f,releaseNotes:y,includedPrs:l,targetSha:r.triggerMergeSha});let v=this.github.findReleaseByTag?await this.github.findReleaseByTag(s,n,d):null;if(v)return await this.storage.updateReleaseRun(e,{status:"published",githubReleaseId:v.id,githubReleaseUrl:v.url,completedAt:new Date().toISOString()})??void 0;await this.storage.updateReleaseRun(e,{status:"publishing"});let x=await this.github.createGitHubRelease(s,n,{tagName:d,targetCommitish:r.triggerMergeSha,name:f,body:y});return await this.storage.updateReleaseRun(e,{status:"published",githubReleaseId:x.id,githubReleaseUrl:x.url,completedAt:new Date().toISOString()})??void 0}catch(r){return this.failRun(e,SA(r))}finally{this.inProgress.delete(e)}})}async loadIncludedPulls(e,a,r,n){if(!this.github.listMergedPullsForReleaseCandidate)return[n];let i=await this.github.listMergedPullsForReleaseCandidate(e,a,{baseBranch:r.baseBranch,untilMergedAt:r.triggerMergedAt,triggerPr:n}),s=new Map;for(let o of i)s.set(o.mergeSha||`${o.repo}#${o.number}`,o);return s.has(n.mergeSha)||s.set(n.mergeSha,n),Array.from(s.values()).sort((o,u)=>o.mergedAt.localeCompare(u.mergedAt))}async failRun(e,a){return this.storage.updateReleaseRun(e,{status:"error",error:a,completedAt:new Date().toISOString()})}async withRepoLock(e,a){let r=this.repoLocks.get(e)??Promise.resolve(),n,i=new Promise(o=>{n=()=>o()}),s=r.then(()=>i);this.repoLocks.set(e,s),await r;try{return await a()}finally{n?.(),this.repoLocks.get(e)===s&&this.repoLocks.delete(e)}}scheduleProcessing(e){if(this.scheduleBackgroundJob){this.scheduleBackgroundJob("process_release_run",e,Fe("process_release_run",e),{releaseRunId:e}).catch(a=>{console.error(`Failed to schedule release run ${e}:`,a)});return}this.processReleaseRun(e).catch(()=>{})}};function _A(t){return{number:t.triggerPrNumber,title:t.triggerPrTitle,url:t.triggerPrUrl,author:"unknown",repo:t.repo,mergedAt:t.triggerMergedAt,mergeSha:t.triggerMergeSha}}function RA(t){return{number:t.number,title:t.title,url:t.url,author:t.author,mergedAt:t.mergedAt,mergeSha:t.mergeSha}}function kA(t,e){let a=t.title?.trim();return a?a.startsWith(e)?a:`${e} - ${a}`:e}function SA(t){return(t instanceof Error?t.message:String(t)).trim().slice(0,2e3)}function EA(t){return t==="skipped"||t==="published"}function TA(t){return new Promise(e=>setTimeout(e,t))}function bv(t){return t instanceof Error?t.message:String(t)}function Ri(t,e){if(e instanceof ce){t.status(e.statusCode).json({error:e.message});return}t.status(500).json({error:bv(e)})}async function yv(t,e,a={}){let r=a.storage??Kg(),n=a.backgroundJobQueue??new vi(r),i=async(...R)=>{let b=await n.enqueue(...R);return u.wake(),b},s=a.releaseManager??new _i(r,{github:{buildOctokit:kt,findLatestSemverReleaseTag:vh,bumpReleaseTag:hh,listMergedPullsForReleaseCandidate:async(R,b,g)=>{let _=await Ph(R,b,{baseRef:g.baseBranch}),h=Date.parse(g.untilMergedAt);return _.filter(T=>!Number.isFinite(h)||Date.parse(T.mergedAt)<=h).map(T=>({number:T.number,title:T.title,url:T.url,author:T.author,repo:T.repo,mergedAt:T.mergedAt,mergeSha:T.mergeCommitSha??`${T.repo}#${T.number}`}))},findReleaseByTag:async(R,b,g)=>{let h=(await di(R,b)).find(T=>!T.draft&&T.tagName===g);return h?{id:h.id,url:h.htmlUrl,tagName:h.tagName,name:h.name}:null},createGitHubRelease:async(R,b,g)=>{let _=await bh(R,b,{tagName:g.tagName,targetCommitish:g.targetCommitish,name:g.name,body:g.body});return{id:_.id,url:_.htmlUrl,tagName:_.tagName,name:_.name}}},scheduleBackgroundJob:i}),o=a.babysitter??new xi(r,void 0,void 0,s,i),u=a.backgroundJobDispatcher??new wi({storage:r,queue:n,handlers:mv({storage:r,babysitter:o,releaseManager:s})}),c=null,l=0,p=a.watcherScheduler??fv(async()=>{await i("sync_watched_repos","runtime:1",Fe("sync_watched_repos","runtime:1"))},R=>{console.error("Repository babysitter watcher failed",R)}),d=p.run,f=a.startBackgroundServices??!0,y=a.startWatcher??f,v=async()=>({...await r.getRuntimeState(),activeRuns:u.getActiveRunCount()}),x=async R=>{let[b,g,_]=await Promise.all([u.waitForIdle(R),o.waitForIdle(R),s.waitForIdle(R)]);return b&&g&&_},I=async()=>{let R=await r.getConfig(),b=Math.max(1e4,R.pollIntervalMs||12e4);c&&l===b||(c&&(clearInterval(c),c=null),l=b,c=setInterval(()=>{d()},b))};return f&&await u.start(),y&&(await I(),o.resumeInterruptedRuns(),d()),t.on("close",()=>{u.stop(),c&&(clearInterval(c),c=null)}),e.get("/api/runtime",async(R,b)=>{b.json(await v())}),e.post("/api/runtime/drain",async(R,b)=>{try{let g=m.object({enabled:m.boolean(),reason:m.string().optional(),waitForIdle:m.boolean().optional(),timeoutMs:m.number().int().positive().max(6e5).optional()}).parse(R.body),_=await r.updateRuntimeState({drainMode:g.enabled,drainRequestedAt:g.enabled?new Date().toISOString():null,drainReason:g.enabled?g.reason??null:null});if(g.enabled&&g.waitForIdle){let T=await x(g.timeoutMs??12e4),C=await v();return b.status(T?200:202).json({..._,...C,drained:T})}let h=await v();b.json({..._,...h})}catch(g){if(g instanceof m.ZodError)return b.status(400).json({error:g.errors[0].message});let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.get("/api/repos",async(R,b)=>{let g=await r.getConfig(),_=await r.getPRs(),h=Array.from(new Set([...g.watchedRepos,..._.map(T=>T.repo)])).sort((T,C)=>T.localeCompare(C));b.json(h)}),e.post("/api/repos",async(R,b)=>{try{let g=m.object({repo:m.string().min(1)}).parse(R.body).repo,_=lt(g);if(!_)return b.status(400).json({error:"Invalid repository. Use owner/repo or https://github.com/owner/repo"});let h=pa(_),T=await r.getConfig();T.watchedRepos.includes(h)||await r.updateConfig({watchedRepos:[...T.watchedRepos,h].sort((C,D)=>C.localeCompare(D))}),d(),b.status(201).json({repo:h})}catch(g){if(g instanceof m.ZodError)return b.status(400).json({error:g.errors[0].message});let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.post("/api/repos/sync",async(R,b)=>{if((await r.getRuntimeState()).drainMode)return b.status(409).json({error:"Drain mode is enabled. Sync-triggered runs are blocked until drain mode is disabled."});try{await p.runAndReportErrors(),b.json({ok:!0})}catch(_){let h=_ instanceof Error?_.message:String(_);b.status(500).json({error:h})}}),e.get("/api/prs",async(R,b)=>{let g=await r.getPRs();b.json(g)}),e.get("/api/prs/archived",async(R,b)=>{let g=await r.getArchivedPRs();b.json(g)}),e.get("/api/prs/:id",async(R,b)=>{let g=await r.getPR(R.params.id);if(!g)return b.status(404).json({error:"PR not found"});b.json(g)}),e.post("/api/prs",async(R,b)=>{try{let{url:g}=Bg.parse(R.body),_=ui(g);if(!_)return b.status(400).json({error:"Invalid GitHub PR URL. Expected: https://github.com/owner/repo/pull/123"});let h=`${_.owner}/${_.repo}`,T=await r.getPRByRepoAndNumber(h,_.number);if(T)return b.status(200).json(T);let C=await r.getConfig(),D=await kt(C),N=await pi(D,_),q=await r.addPR({number:_.number,title:N.title,repo:h,branch:N.branch,author:N.author,url:N.url,status:"watching",feedbackItems:[],accepted:0,rejected:0,flagged:0,testsPassed:null,lintPassed:null,lastChecked:null});await r.addLog(q.id,"info",`Registered PR #${_.number} from ${h}`),await r.addLog(q.id,"info",`Repository ${h} added to auto-babysit watch list`),C.watchedRepos.includes(h)||await r.updateConfig({watchedRepos:[...C.watchedRepos,h].sort((E,K)=>E.localeCompare(K))}),await i("babysit_pr",q.id,Fe("babysit_pr",q.id),{preferredAgent:C.codingAgent}),b.status(201).json(q)}catch(g){if(g instanceof m.ZodError)return b.status(400).json({error:g.errors[0].message});Ri(b,g)}}),e.delete("/api/prs/:id",async(R,b)=>{if(!await r.removePR(R.params.id))return b.status(404).json({error:"PR not found"});b.json({ok:!0})}),e.patch("/api/prs/:id/watch",async(R,b)=>{try{let{enabled:g}=m.object({enabled:m.boolean()}).parse(R.body),_=await r.getPR(R.params.id);if(!_)return b.status(404).json({error:"PR not found"});let h=await r.updatePR(_.id,{watchEnabled:g});if(!h)return b.status(404).json({error:"PR not found"});_.watchEnabled!==g&&(await r.addLog(_.id,"info",g?"Background watch resumed":"Background watch paused"),g&&d()),b.json(h)}catch(g){if(g instanceof m.ZodError)return b.status(400).json({error:g.errors[0].message});let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.post("/api/prs/:id/fetch",async(R,b)=>{let g=await r.getPR(R.params.id);if(!g)return b.status(404).json({error:"PR not found"});await r.updatePR(g.id,{status:"processing",lastChecked:new Date().toISOString()}),await r.addLog(g.id,"info","Syncing GitHub comments/reviews...");try{let _=await o.syncFeedbackForPR(g.id);b.json(_)}catch(_){let h=bv(_);await r.updatePR(g.id,{status:"error",lastChecked:new Date().toISOString()}),await r.addLog(g.id,"error",`Fetch failed: ${h}`),Ri(b,_)}}),e.post("/api/prs/:id/triage",async(R,b)=>{let g=await r.getPR(R.params.id);if(!g)return b.status(404).json({error:"PR not found"});await r.updatePR(g.id,{status:"processing"}),await r.addLog(g.id,"info","Triaging feedback...");let _=g.feedbackItems.map(N=>{if(N.decision)return N;let q=N.body.toLowerCase();return q.includes("lgtm")||q.includes("looks good")?da(N,!1,"Acknowledgement, no code change requested"):q.includes("please")||q.includes("should")||q.includes("fix")||q.includes("error")||q.includes("fail")?{...da(N,!0,"Likely actionable request"),action:N.body}:Kh(N,"Unclear actionability, flagged for manual review")}),h=_.filter(N=>N.decision==="accept").length,T=_.filter(N=>N.decision==="reject").length,C=_.filter(N=>N.decision==="flag").length,D=await r.updatePR(g.id,{feedbackItems:_,accepted:h,rejected:T,flagged:C,status:"watching"});await r.addLog(g.id,"info",`Triage complete: ${h} accept, ${T} reject, ${C} flag`),b.json(D)}),e.post("/api/prs/:id/apply",async(R,b)=>{let g=await r.getPR(R.params.id);if(!g)return b.status(404).json({error:"PR not found"});if((await r.getRuntimeState()).drainMode)return b.status(409).json({error:"Drain mode is enabled. Manual runs are blocked until drain mode is disabled."});let h=await r.getConfig();await r.updatePR(g.id,{status:"processing"}),await r.addLog(g.id,"info",`Launching autonomous babysitter run using ${h.codingAgent}`),await i("babysit_pr",g.id,Fe("babysit_pr",g.id),{preferredAgent:h.codingAgent});let T=await r.getPR(g.id);if(!T)return b.status(500).json({error:"PR disappeared after apply run"});b.json(T)}),e.post("/api/prs/:id/babysit",async(R,b)=>{let g=await r.getPR(R.params.id);if(!g)return b.status(404).json({error:"PR not found"});if((await r.getRuntimeState()).drainMode)return b.status(409).json({error:"Drain mode is enabled. Manual runs are blocked until drain mode is disabled."});let h=await r.getConfig();await r.addLog(g.id,"info",`Manual babysitter trigger using ${h.codingAgent}`),await i("babysit_pr",g.id,Fe("babysit_pr",g.id),{preferredAgent:h.codingAgent});let T=await r.getPR(g.id);if(!T)return b.status(500).json({error:"PR disappeared after babysit run"});b.json(T)}),e.patch("/api/prs/:id/feedback/:feedbackId",async(R,b)=>{try{let g=await r.getPR(R.params.id);if(!g)return b.status(404).json({error:"PR not found"});let{decision:_}=R.body;if(!["accept","reject","flag"].includes(_))return b.status(400).json({error:"Invalid decision"});let h=_,T=await uv({storage:r,pr:g,feedbackId:R.params.feedbackId,decision:h});b.json(T)}catch(g){Ri(b,g)}}),e.post("/api/prs/:id/feedback/:feedbackId/retry",async(R,b)=>{let g=await o.retryFeedbackItem(R.params.id,R.params.feedbackId);if(g.kind==="pr_not_found")return b.status(404).json({error:"PR not found"});if(g.kind==="feedback_not_found")return b.status(404).json({error:"Feedback item not found"});if(g.kind==="feedback_not_retryable")return b.status(400).json({error:"Only failed or warning items can be retried"});await r.addLog(R.params.id,"info",`Feedback item ${R.params.feedbackId} queued for retry`);let _=await r.getConfig();await i("babysit_pr",R.params.id,Fe("babysit_pr",R.params.id),{preferredAgent:_.codingAgent}),b.json(g.updated)}),e.get("/api/prs/:id/questions",async(R,b)=>{let g=await r.getPR(R.params.id);if(!g)return b.status(404).json({error:"PR not found"});let _=await r.getQuestions(g.id);b.json(_)}),e.post("/api/prs/:id/questions",async(R,b)=>{try{let g=await r.getPR(R.params.id);if(!g)return b.status(404).json({error:"PR not found"});let{question:_}=Ug.parse(R.body),h=await r.addQuestion(g.id,_);try{await i("answer_pr_question",h.id,Fe("answer_pr_question",h.id),{prId:g.id})}catch(T){let C=T instanceof Error?T.message:String(T);throw await r.updateQuestion(h.id,{status:"error",error:C.trim().slice(0,2e3)}),T}b.status(201).json(h)}catch(g){if(g instanceof m.ZodError)return b.status(400).json({error:g.errors[0].message});let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.get("/api/logs",async(R,b)=>{let g=R.query.prId,_=await r.getLogs(g);b.json(_)}),e.get("/api/onboarding/status",async(R,b)=>{let g=await r.getConfig(),_=await dh(g,g.watchedRepos);b.json(_)}),e.post("/api/onboarding/install-review",async(R,b)=>{try{let{repo:g,tool:_}=m.object({repo:m.string().min(1),tool:m.enum(["claude","codex"])}).parse(R.body),h=await r.getConfig(),T=await mh(h,g,_);b.json(T)}catch(g){if(g instanceof m.ZodError)return b.status(400).json({error:g.errors[0].message});Ri(b,g)}}),e.get("/api/healing-sessions",async(R,b)=>{try{let g=await r.listHealingSessions();b.json(g)}catch(g){let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.get("/api/healing-sessions/:id",async(R,b)=>{try{let g=await r.getHealingSession(R.params.id);if(!g)return b.status(404).json({error:"Healing session not found"});b.json(g)}catch(g){let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.get("/api/config",async(R,b)=>{let g=await r.getConfig();b.json({...g,githubToken:g.githubToken?"***"+g.githubToken.slice(-4):""})}),e.get("/api/changelogs",async(R,b)=>{try{let g=await r.getSocialChangelogs();b.json(g)}catch(g){let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.get("/api/changelogs/:id",async(R,b)=>{try{let g=await r.getSocialChangelog(R.params.id);if(!g)return b.status(404).json({error:"Changelog not found"});b.json(g)}catch(g){let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.get("/api/releases",async(R,b)=>{try{let g=await r.listReleaseRuns();b.json(g)}catch(g){let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.get("/api/releases/:id",async(R,b)=>{try{let g=await r.getReleaseRun(R.params.id);if(!g)return b.status(404).json({error:"Release run not found"});b.json(g)}catch(g){let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.post("/api/releases/:id/retry",async(R,b)=>{try{let g=await s.retryReleaseRun(R.params.id);if(!g)return b.status(404).json({error:"Release run not found"});b.json(g)}catch(g){let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),e.patch("/api/config",async(R,b)=>{try{let g=ni.partial().parse(R.body),_=await r.updateConfig(g);await I(),b.json({..._,githubToken:_.githubToken?"***"+_.githubToken.slice(-4):""})}catch(g){if(g instanceof m.ZodError)return b.status(400).json({error:g.errors[0].message});let _=g instanceof Error?g.message:String(g);b.status(500).json({error:_})}}),t}var xv=je(Uo(),1),wv=je(require("fs"),1),jc=je(require("path"),1);function _v(t){let e=jc.default.resolve(__dirname,"public");if(!wv.default.existsSync(e))throw new Error(`Could not find the build directory: ${e}, make sure to build the client first`);t.use(xv.default.static(e)),t.use("/{*path}",(a,r)=>{r.sendFile(jc.default.resolve(e,"index.html"))})}var AA=new Set(["127.0.0.1","::1","::ffff:127.0.0.1","localhost"]);function CA(t){if(!t)return!1;let e=t.replace(/^\[|\]$/g,"");return AA.has(e)?!0:e.startsWith("::ffff:")?e.slice(7).startsWith("127."):!!e.startsWith("127.")}function Rv(t,e,a){let r=t.ip??t.socket?.remoteAddress;if(!CA(r)){e.status(403).json({error:"Forbidden",message:"Code Factory only accepts connections from the local machine. External access is not permitted."});return}a()}var Sv=require("http"),Ev=je(require("open"),1),ma=(0,ki.default)(),kv=(0,Sv.createServer)(ma);ma.use(ki.default.json({verify:(t,e,a)=>{t.rawBody=a}}));ma.use(ki.default.urlencoded({extended:!1}));ma.use("/api",Rv);function Tv(t,e="express"){let a=new Date().toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",second:"2-digit",hour12:!0});console.log(`${a} [${e}] ${t}`)}(async()=>{await yv(kv,ma),ma.use((e,a,r,n)=>{let i=e,s=i.status||i.statusCode||500,o=i.message||"Internal Server Error";return console.error("Internal Server Error:",e),r.headersSent?n(e):r.status(s).json({message:o})}),_v(ma);let t=parseInt(process.env.PORT||"5001",10);kv.listen({port:t,host:"0.0.0.0"},()=>{let e=`http://localhost:${t}`;console.log(`
|
|
845
|
-
oh-my-pr v2.
|
|
845
|
+
oh-my-pr v2.6.0
|
|
846
846
|
Dashboard: ${e}
|
|
847
847
|
`),process.env.TAURI_DEV||(0,Ev.default)(e).catch(r=>{Tv(`Could not open browser automatically: ${r.message}`)})})})();0&&(module.exports={log});
|
|
848
848
|
/*! Bundled license information:
|