devin-bugs 0.2.0 → 0.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/dist/cli.js +21 -20
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
|
-
var
|
|
4
|
-
`)})}function
|
|
3
|
+
var y=Object.defineProperty;var f=(z)=>z;function u(z,Q){this[z]=f.bind(null,Q)}var h=(z,Q)=>{for(var X in Q)y(z,X,{get:Q[X],enumerable:!0,configurable:!0,set:u.bind(Q,X)})};var x=(z,Q)=>()=>(z&&(Q=z(z=0)),Q);import{homedir as _}from"node:os";import{join as K}from"node:path";var q="https://app.devin.ai/api",P="https://app.devin.ai",o,d,L,Ez,A=300;var I=x(()=>{o=K(_(),".config","devin-bugs"),d=K(_(),".cache","devin-bugs"),L=K(o,"token.json"),Ez=K(d,"browser-profile")});var b={};h(b,{getToken:()=>U,forceReauth:()=>S,clearAuth:()=>Wz});import{existsSync as D,mkdirSync as c,readFileSync as s,writeFileSync as t,unlinkSync as a}from"node:fs";import{dirname as r}from"node:path";import{createServer as e}from"node:http";import{execFile as zz}from"node:child_process";function w(z){let Q=z.replace(/-/g,"+").replace(/_/g,"/");return Buffer.from(Q,"base64").toString("utf-8")}function Qz(z){let Q=z.split(".");if(Q.length!==3)throw Error("Invalid JWT format");let X=JSON.parse(w(Q[1]));if(typeof X.exp!=="number")throw Error("JWT missing exp claim");return X.exp*1000}function Xz(z){if(!D(z))c(z,{recursive:!0})}function Yz(){try{if(!D(L))return null;let z=s(L,"utf-8"),Q=JSON.parse(z);if(!Q.accessToken||!Q.expiresAt)return null;return Q}catch{return null}}function N(z,Q){Xz(r(L));let X=Qz(z),Z={accessToken:z,obtainedAt:Date.now(),expiresAt:X,...Q&&Object.keys(Q).length>0?{auth0Cache:Q}:{}};return t(L,JSON.stringify(Z,null,2)),Z}async function Zz(z){if(!z.auth0Cache)return null;let Q=null,X=null,Z=null;for(let[Y,J]of Object.entries(z.auth0Cache)){if(!Y.startsWith("@@auth0spajs@@"))continue;let G=Y.split("::"),W=G[1],B=G[2];try{let V=JSON.parse(J);if(V.body?.refresh_token){Q=V.body.refresh_token,X=W??null,Z=B??null;break}}catch{continue}}if(!Q||!X)return null;let $=null;for(let Y of Object.keys(z.auth0Cache))if(Y.includes("https://")&&Y.includes("auth0")){let J=Y.match(/https:\/\/([^/]+)/);if(J?.[1]){$=J[1];break}}if(!$)try{let Y=JSON.parse(w(z.accessToken.split(".")[1]));if(typeof Y.iss==="string")$=new URL(Y.iss).hostname}catch{}if(!$)return console.error("\x1B[33m▸ Could not determine Auth0 domain for token refresh.\x1B[0m"),null;try{console.error("\x1B[33m▸ Refreshing token...\x1B[0m");let Y=await fetch(`https://${$}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"refresh_token",client_id:X,refresh_token:Q,...Z?{audience:Z}:{}})});if(!Y.ok){let G=await Y.text().catch(()=>"");return console.error(`\x1B[33m▸ Refresh failed (${Y.status}): ${G.slice(0,100)}\x1B[0m`),null}let J=await Y.json();if(!J.access_token)return null;if(J.refresh_token&&z.auth0Cache)for(let[G,W]of Object.entries(z.auth0Cache)){if(!G.startsWith("@@auth0spajs@@"))continue;try{let B=JSON.parse(W);if(B.body?.refresh_token){B.body.refresh_token=J.refresh_token,B.body.access_token=J.access_token,z.auth0Cache[G]=JSON.stringify(B);break}}catch{continue}}return console.error(`\x1B[32m✓ Token refreshed!\x1B[0m
|
|
4
|
+
`),J.access_token}catch(Y){return console.error(`\x1B[33m▸ Refresh error: ${Y instanceof Error?Y.message:Y}\x1B[0m`),null}}function E(){try{if(D(L))a(L)}catch{}}function $z(z){return z.expiresAt-Date.now()>A*1000}function Jz(z){let Q=process.platform==="darwin"?{cmd:"open",args:[z]}:process.platform==="win32"?{cmd:"cmd",args:["/c","start","",z]}:{cmd:"xdg-open",args:[z]};zz(Q.cmd,Q.args,(X)=>{if(X)console.error("\x1B[33m▸ Could not open browser automatically.\x1B[0m"),console.error(` Open this URL manually: ${z}
|
|
5
|
+
`)})}function Mz(z){return`<!DOCTYPE html>
|
|
5
6
|
<html lang="en">
|
|
6
7
|
<head>
|
|
7
8
|
<meta charset="utf-8">
|
|
@@ -66,7 +67,7 @@ var m=Object.defineProperty;var f=(z)=>z;function g(z,Q){this[z]=f.bind(null,Q)}
|
|
|
66
67
|
<div class="step">
|
|
67
68
|
<div class="step-num">1</div>
|
|
68
69
|
<div class="step-text">
|
|
69
|
-
<a href="${
|
|
70
|
+
<a href="${P}" target="_blank" rel="noopener">
|
|
70
71
|
Open app.devin.ai</a> and log in with GitHub
|
|
71
72
|
</div>
|
|
72
73
|
</div>
|
|
@@ -75,7 +76,7 @@ var m=Object.defineProperty;var f=(z)=>z;function g(z,Q){this[z]=f.bind(null,Q)}
|
|
|
75
76
|
<div class="step-num">2</div>
|
|
76
77
|
<div class="step-text">
|
|
77
78
|
Open the browser console (<strong>F12</strong> → Console tab) and paste:
|
|
78
|
-
<code id="snippet" onclick="copySnippet()">fetch('http://localhost:${z}/callback',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token:
|
|
79
|
+
<code id="snippet" onclick="copySnippet()">{let t=await __HACK__getAccessToken(),c={};for(let i=0;i<localStorage.length;i++){let k=localStorage.key(i);if(k&&k.includes('auth0'))c[k]=localStorage.getItem(k)}fetch('http://localhost:${z}/callback',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token:t,auth0Cache:c})}).then(()=>document.title='✓ Token sent!')}</code>
|
|
79
80
|
</div>
|
|
80
81
|
</div>
|
|
81
82
|
|
|
@@ -114,24 +115,24 @@ var m=Object.defineProperty;var f=(z)=>z;function g(z,Q){this[z]=f.bind(null,Q)}
|
|
|
114
115
|
poll();
|
|
115
116
|
</script>
|
|
116
117
|
</body>
|
|
117
|
-
</html>`}function
|
|
118
|
-
`)
|
|
119
|
-
`),
|
|
120
|
-
`),
|
|
121
|
-
Expected: owner/repo#123 or https://github.com/owner/repo/pull/123`);let[,X,
|
|
122
|
-
`)[0]?.slice(0,120).trim()??""}function
|
|
118
|
+
</html>`}function C(){return new Promise((z,Q)=>{let X=null,Z=e(($,Y)=>{if(Y.setHeader("Access-Control-Allow-Origin","*"),Y.setHeader("Access-Control-Allow-Methods","POST, GET, OPTIONS"),Y.setHeader("Access-Control-Allow-Headers","Content-Type"),$.method==="OPTIONS"){Y.writeHead(204),Y.end();return}if($.method==="GET"&&$.url==="/status"){Y.writeHead(200,{"Content-Type":"application/json"}),Y.end(JSON.stringify({received:X!==null}));return}if($.method==="POST"&&$.url==="/callback"){let J="";$.on("data",(G)=>J+=G.toString()),$.on("end",()=>{try{let G=JSON.parse(J);if(typeof G.token==="string"&&G.token.length>20){X=G.token;let W=G.auth0Cache;if(W&&Object.keys(W).length>0)console.error(`\x1B[36m▸ Captured ${Object.keys(W).length} Auth0 cache entries\x1B[0m`);Y.writeHead(200,{"Content-Type":"application/json"}),Y.end(JSON.stringify({ok:!0})),setTimeout(()=>{Z.close(),z({token:X,auth0Cache:W,server:Z})},500);return}}catch{}Y.writeHead(400,{"Content-Type":"application/json"}),Y.end(JSON.stringify({error:"Invalid token"}))});return}if($.method==="GET"&&($.url==="/"||$.url==="/login")){let J=Z.address().port;Y.writeHead(200,{"Content-Type":"text/html"}),Y.end(Mz(J));return}Y.writeHead(404),Y.end("Not found")});Z.listen(0,"127.0.0.1",()=>{let Y=Z.address().port;console.error("\x1B[33m▸ Opening browser for Devin login...\x1B[0m"),console.error(` Local server: http://localhost:${Y}
|
|
119
|
+
`),Jz(`http://localhost:${Y}`),setTimeout(()=>{if(!X)Z.close(),Q(Error("Login timed out after 5 minutes."))},300000)}),Z.on("error",Q)})}async function U(z){let Q=process.env.DEVIN_TOKEN;if(Q&&Q.length>0)return Q;if(!z?.noCache){let $=Yz();if($&&$z($))return $.accessToken;if($?.auth0Cache){let Y=await Zz($);if(Y)return N(Y,$.auth0Cache),Y}}let{token:X,auth0Cache:Z}=await C();return console.error(`\x1B[32m✓ Authentication successful!\x1B[0m
|
|
120
|
+
`),N(X,Z),X}async function S(){E();let{token:z,auth0Cache:Q}=await C();return console.error(`\x1B[32m✓ Authentication successful!\x1B[0m
|
|
121
|
+
`),N(z,Q),z}function Wz(){E(),console.error("Cleared cached token.")}var H=x(()=>{I()});import{parseArgs as jz}from"util";var p=/(?:https?:\/\/)?github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/,l=/^([^/#]+)\/([^/#]+)#(\d+)$/,i=/^([^/#]+)\/([^/#]+)\/pull\/(\d+)$/,n=/(?:https?:\/\/)?app\.devin\.ai\/review\/([^/]+)\/([^/]+)\/pull\/(\d+)/;function R(z){let Q=z.match(p)??z.match(n)??z.match(l)??z.match(i);if(!Q)throw Error(`Invalid PR reference: ${z}
|
|
122
|
+
Expected: owner/repo#123 or https://github.com/owner/repo/pull/123`);let[,X,Z,$]=Q;return{owner:X,repo:Z,number:parseInt($,10),prPath:`github.com/${X}/${Z}/pull/${$}`}}H();I();class F extends Error{constructor(){super("Authentication expired. Re-authenticating...");this.name="AuthExpiredError"}}class O extends Error{status;body;constructor(z,Q){super(`Devin API error ${z}: ${Q}`);this.status=z;this.body=Q;this.name="ApiError"}}async function Gz(z,Q){let X=`${q}/${z}`,Z=await fetch(X,{headers:{Authorization:`Bearer ${Q}`,Accept:"application/json"}});if(!Z.ok){if(Z.status===401||Z.status===403)throw new F;let $=await Z.text().catch(()=>"");throw new O(Z.status,$)}return Z.json()}async function j(z,Q){return Gz(`pr-review/digest?pr_path=${encodeURIComponent(z)}`,Q)}function Bz(z){if(!z)return null;let Q=z.match(/<!--\s*devin-review-comment\s*(\{.+\})\s*-->/);if(!Q?.[1])return null;try{let X=JSON.parse(Q[1]);return{id:String(X.id??""),file_path:String(X.file_path??""),start_line:typeof X.start_line==="number"?X.start_line:0,end_line:typeof X.end_line==="number"?X.end_line:0,side:X.side==="LEFT"?"LEFT":"RIGHT"}}catch{return null}}function Vz(z){if(z.startsWith("\uD83D\uDD34"))return"severe";if(z.startsWith("\uD83D\uDFE1"))return"warning";if(z.startsWith("\uD83D\uDFE2"))return"info";return"info"}function Lz(z){return z.match(/\*\*(.+?)\*\*/)?.[1]?.trim()??z.split(`
|
|
123
|
+
`)[0]?.slice(0,120).trim()??""}function Kz(z){return z.split(`
|
|
123
124
|
`).slice(1).join(`
|
|
124
|
-
`).trim()}function Uz(z){return z.match(/(?:recommendation|suggested fix|fix):\s*(.+?)(?:\n\n|\n#+|\n🔴|\n🟡|$)/is)?.[1]?.trim()??""}function
|
|
125
|
-
`)}function T(z,Q){let X=[],
|
|
126
|
-
${
|
|
125
|
+
`).trim()}function Uz(z){return z.match(/(?:recommendation|suggested fix|fix):\s*(.+?)(?:\n\n|\n#+|\n🔴|\n🟡|$)/is)?.[1]?.trim()??""}function Fz(z,Q){if(z.startsWith("BUG_"))return"lifeguard-bug";if(z.startsWith("ANALYSIS_")||z.startsWith("INFO_"))return"lifeguard-analysis";let X=Q.toLowerCase();if(X.includes("potential bug")||X.includes("\uD83D\uDD34")||X.includes("bug:")||X.includes("race condition")||X.includes("vulnerability")||X.includes("double-charge")||X.includes("sql injection"))return"lifeguard-bug";return"lifeguard-analysis"}function Oz(z,Q){let X=Bz(Q.hidden_header),Z=Q.body??"";if(!Z&&!X)return null;let $=X?.id??String(Q.devin_review_id??""),Y=Fz($,Z);return{filePath:X?.file_path??"",startLine:X?.start_line??null,endLine:X?.end_line??null,side:X?.side??"RIGHT",title:Lz(Z),description:Kz(Z),severity:Vz(Z),recommendation:Uz(Z),needsInvestigation:Z.toLowerCase().includes("needs investigation"),type:Y,isResolved:z.is_resolved,isOutdated:z.is_outdated,htmlUrl:Q.html_url??null}}function Iz(z){return z.devin_review_id!=null||z.hidden_header?.includes("devin-review-comment")===!0||z.author?.login==="devin-ai-integration"||z.author?.login==="devin-ai-integration[bot]"||z.author?.login==="devin-ai[bot]"}function v(z,Q){let X=[];for(let Z of z.review_threads){if(!Q?.includeResolved&&Z.is_resolved)continue;if(!Q?.includeOutdated&&Z.is_outdated)continue;for(let $ of Z.comments){if(!Iz($))continue;let Y=Oz(Z,$);if(Y){X.push(Y);break}}}if(!Q?.includeAnalysis)return X.filter((Z)=>Z.type==="lifeguard-bug");return X}var M={reset:"\x1B[0m",bold:"\x1B[1m",dim:"\x1B[2m",red:"\x1B[31m",green:"\x1B[32m",yellow:"\x1B[33m",cyan:"\x1B[36m",white:"\x1B[37m",bgRed:"\x1B[41m",bgYellow:"\x1B[43m",bgBlue:"\x1B[44m"};function Nz(z){let Q=z.toUpperCase();switch(z.toLowerCase()){case"severe":case"critical":return`${M.bgRed}${M.white}${M.bold} ${Q} ${M.reset}`;case"warning":return`${M.bgYellow}${M.bold} ${Q} ${M.reset}`;default:return`${M.bgBlue}${M.white} ${Q} ${M.reset}`}}function Dz(z){if(z==="lifeguard-bug")return`${M.red}${M.bold}BUG${M.reset}`;return`${M.cyan}${M.bold}INFO${M.reset}`}function Sz(z){if(!z.filePath)return"";let Q=`${M.cyan}${z.filePath}${M.reset}`;if(z.startLine==null)return Q;let X=z.endLine!=null&&z.endLine!==z.startLine?`${M.dim}:${z.startLine}-${z.endLine}${M.reset}`:`${M.dim}:${z.startLine}${M.reset}`;return`${Q}${X}`}function Hz(z,Q,X){let Z=" ".repeat(Q),$=z.split(/\s+/),Y=[],J="";for(let G of $)if(J.length+G.length+1>X-Q)Y.push(Z+J),J=G;else J=J?`${J} ${G}`:G;if(J)Y.push(Z+J);return Y.join(`
|
|
126
|
+
`)}function T(z,Q){let X=[],Z=z.filter((J)=>J.type==="lifeguard-bug").length,$=z.filter((J)=>J.type==="lifeguard-analysis").length,Y=[];if(Z>0)Y.push(`${M.red}${M.bold}${Z} bug${Z===1?"":"s"}${M.reset}`);if($>0)Y.push(`${M.cyan}${$} suggestion${$===1?"":"s"}${M.reset}`);if(Y.length===0)return X.push(`
|
|
127
|
+
${M.green}${M.bold}No unresolved bugs${M.reset} in ${M.dim}${Q.owner}/${Q.repo}#${Q.number}${M.reset}
|
|
127
128
|
`),X.join(`
|
|
128
129
|
`);X.push(`
|
|
129
|
-
${
|
|
130
|
-
`);for(let
|
|
130
|
+
${Y.join(", ")} in ${M.dim}${Q.owner}/${Q.repo}#${Q.number}${M.reset}
|
|
131
|
+
`);for(let J of z){let G=Dz(J.type),W=Sz(J),B=Nz(J.severity);if(X.push(` ${G} ${W} ${B}`),J.title)X.push(` ${M.bold}${M.white}${J.title}${M.reset}`);if(J.description&&J.description!==J.title){let V=J.description.replace(/<details>[\s\S]*?<\/details>/g,"").replace(/<!--[\s\S]*?-->/g,"").replace(/^\[.*?\]\(.*?\)$/gm,"").replace(/<a[\s\S]*?<\/a>/g,"").replace(/<picture>[\s\S]*?<\/picture>/g,"").replace(/<img[^>]*>/g,"").replace(/^---\s*$/gm,"").replace(/^\*Was this helpful\?.*$/gm,"").replace(/^#+\s*.+$/gm,"").replace(/\*\*(.+?)\*\*/g,"$1").replace(/`([^`]+)`/g,"$1").trim().split(`
|
|
131
132
|
|
|
132
133
|
`)[0].split(`
|
|
133
|
-
`).filter((
|
|
134
|
-
`)}function
|
|
134
|
+
`).filter((m)=>m.trim()).join(" ").trim();if(V)X.push(Hz(`${M.dim}${V}${M.reset}`,2,100))}if(J.recommendation)X.push(` ${M.green}→ ${J.recommendation}${M.reset}`);X.push("")}return X.join(`
|
|
135
|
+
`)}function k(z){return JSON.stringify(z,null,2)}var xz=`
|
|
135
136
|
\x1B[1mdevin-bugs\x1B[0m \u2014 Extract unresolved bugs from Devin AI code reviews
|
|
136
137
|
|
|
137
138
|
\x1B[1mUsage:\x1B[0m
|
|
@@ -161,6 +162,6 @@ Expected: owner/repo#123 or https://github.com/owner/repo/pull/123`);let[,X,Y,J]
|
|
|
161
162
|
devin-bugs owner/repo#46 --json
|
|
162
163
|
devin-bugs owner/repo#46 --all --raw
|
|
163
164
|
DEVIN_TOKEN=xxx devin-bugs owner/repo#46
|
|
164
|
-
`;function
|
|
165
|
-
`);try{let
|
|
166
|
-
`),
|
|
165
|
+
`;function g(){console.log(xz)}function Rz(){console.log("devin-bugs 0.3.0")}async function _z(){let z;try{z=jz({allowPositionals:!0,options:{json:{type:"boolean",default:!1},all:{type:"boolean",default:!1},raw:{type:"boolean",default:!1},"no-cache":{type:"boolean",default:!1},login:{type:"boolean",default:!1},logout:{type:"boolean",default:!1},help:{type:"boolean",short:"h",default:!1},version:{type:"boolean",short:"v",default:!1}}})}catch(W){console.error(`\x1B[31mError:\x1B[0m ${W.message}`),process.exit(1)}let{values:Q,positionals:X}=z;if(Q.help){g();return}if(Q.version){Rz();return}if(Q.logout){let{clearAuth:W}=await Promise.resolve().then(() => (H(),b));W();return}if(Q.login){let W=await U({noCache:Q["no-cache"]});console.error("\x1B[32m\u2713 Authenticated successfully.\x1B[0m"),console.error(` Token cached for future use.
|
|
166
|
+
`);try{let B=JSON.parse(Buffer.from(W.split(".")[1],"base64url").toString()),V=new Date(B.exp*1000);console.error(` Expires: ${V.toLocaleString()}`)}catch{}return}if(X.length===0)console.error(`\x1B[31mError:\x1B[0m Missing PR argument.
|
|
167
|
+
`),g(),process.exit(1);let Z=X[0],$;try{$=R(Z)}catch(W){console.error(`\x1B[31mError:\x1B[0m ${W.message}`),process.exit(1)}let Y;try{Y=await U({noCache:Q["no-cache"]})}catch(W){console.error(`\x1B[31mAuth error:\x1B[0m ${W.message}`),process.exit(1)}let J;try{J=await j($.prPath,Y)}catch(W){if(W instanceof F){console.error("\x1B[33m\u25B8 Token expired, re-authenticating...\x1B[0m");try{Y=await S(),J=await j($.prPath,Y)}catch(B){console.error(`\x1B[31mError:\x1B[0m ${B.message}`),process.exit(1)}}else if(W instanceof O){if(W.status===404)console.error(`\x1B[31mError:\x1B[0m PR not found or no Devin review exists for ${$.owner}/${$.repo}#${$.number}`);else console.error(`\x1B[31mAPI error ${W.status}:\x1B[0m ${W.body}`);process.exit(1)}else throw W}if(Q.raw){console.log(JSON.stringify(J,null,2));return}let G=v(J,{includeAnalysis:Q.all});if(Q.json)console.log(k(G));else console.log(T(G,$))}_z().catch((z)=>{console.error(`\x1B[31mFatal error:\x1B[0m ${z.message??z}`),process.exit(1)});
|