devin-bugs 0.3.0 → 0.3.1
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 +10 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
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:()=>
|
|
4
|
-
`),
|
|
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:()=>F,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],V=G[2];try{let B=JSON.parse(J);if(B.body?.refresh_token){Q=B.body.refresh_token,X=W??null,Z=V??null;break}}catch{continue}}if(!Q||!X)return null;let $=null;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{let Y=`https://${$}/oauth/token`;console.error(`\x1B[33m▸ Refreshing token via ${Y}\x1B[0m`);let J=await fetch(Y,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"refresh_token",client_id:X,refresh_token:Q,...Z?{audience:Z}:{}})});if(!J.ok){let W=await J.text().catch(()=>"");return console.error(`\x1B[33m▸ Refresh failed (${J.status}): ${W.slice(0,100)}\x1B[0m`),null}let G=await J.json();if(!G.access_token)return null;if(G.refresh_token&&z.auth0Cache)for(let[W,V]of Object.entries(z.auth0Cache)){if(!W.startsWith("@@auth0spajs@@"))continue;try{let B=JSON.parse(V);if(B.body?.refresh_token){B.body.refresh_token=G.refresh_token,B.body.access_token=G.access_token,z.auth0Cache[W]=JSON.stringify(B);break}}catch{continue}}return console.error(`\x1B[32m✓ Token refreshed!\x1B[0m
|
|
4
|
+
`),G.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
5
|
`)})}function Mz(z){return`<!DOCTYPE html>
|
|
6
6
|
<html lang="en">
|
|
7
7
|
<head>
|
|
@@ -116,22 +116,22 @@ var y=Object.defineProperty;var f=(z)=>z;function u(z,Q){this[z]=f.bind(null,Q)}
|
|
|
116
116
|
</script>
|
|
117
117
|
</body>
|
|
118
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
|
|
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 F(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
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
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
|
|
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 U 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 U;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
123
|
`)[0]?.slice(0,120).trim()??""}function Kz(z){return z.split(`
|
|
124
124
|
`).slice(1).join(`
|
|
125
|
-
`).trim()}function
|
|
125
|
+
`).trim()}function Fz(z){return z.match(/(?:recommendation|suggested fix|fix):\s*(.+?)(?:\n\n|\n#+|\n🔴|\n🟡|$)/is)?.[1]?.trim()??""}function Uz(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=Uz($,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:Fz(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
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
127
|
${M.green}${M.bold}No unresolved bugs${M.reset} in ${M.dim}${Q.owner}/${Q.repo}#${Q.number}${M.reset}
|
|
128
128
|
`),X.join(`
|
|
129
129
|
`);X.push(`
|
|
130
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),
|
|
131
|
+
`);for(let J of z){let G=Dz(J.type),W=Sz(J),V=Nz(J.severity);if(X.push(` ${G} ${W} ${V}`),J.title)X.push(` ${M.bold}${M.white}${J.title}${M.reset}`);if(J.description&&J.description!==J.title){let B=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(`
|
|
132
132
|
|
|
133
133
|
`)[0].split(`
|
|
134
|
-
`).filter((m)=>m.trim()).join(" ").trim();if(
|
|
134
|
+
`).filter((m)=>m.trim()).join(" ").trim();if(B)X.push(Hz(`${M.dim}${B}${M.reset}`,2,100))}if(J.recommendation)X.push(` ${M.green}→ ${J.recommendation}${M.reset}`);X.push("")}return X.join(`
|
|
135
135
|
`)}function k(z){return JSON.stringify(z,null,2)}var xz=`
|
|
136
136
|
\x1B[1mdevin-bugs\x1B[0m \u2014 Extract unresolved bugs from Devin AI code reviews
|
|
137
137
|
|
|
@@ -162,6 +162,6 @@ Expected: owner/repo#123 or https://github.com/owner/repo/pull/123`);let[,X,Z,$]
|
|
|
162
162
|
devin-bugs owner/repo#46 --json
|
|
163
163
|
devin-bugs owner/repo#46 --all --raw
|
|
164
164
|
DEVIN_TOKEN=xxx devin-bugs owner/repo#46
|
|
165
|
-
`;function g(){console.log(xz)}function Rz(){console.log("devin-bugs 0.3.
|
|
166
|
-
`);try{let
|
|
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
|
|
165
|
+
`;function g(){console.log(xz)}function Rz(){console.log("devin-bugs 0.3.1")}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 F({noCache:Q["no-cache"]});console.error("\x1B[32m\u2713 Authenticated successfully.\x1B[0m"),console.error(` Token cached for future use.
|
|
166
|
+
`);try{let V=JSON.parse(Buffer.from(W.split(".")[1],"base64url").toString()),B=new Date(V.exp*1000);console.error(` Expires: ${B.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 F({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 U){console.error("\x1B[33m\u25B8 Token expired, re-authenticating...\x1B[0m");try{Y=await S(),J=await j($.prPath,Y)}catch(V){console.error(`\x1B[31mError:\x1B[0m ${V.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)});
|