@netlify/agent-runner-cli 1.88.0 → 1.89.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/bin-local.js +1 -1
- package/dist/bin.js +1 -1
- package/dist/index.js +1 -1
- package/dist/skills/netlify-identity/SKILL.md +409 -30
- package/package.json +2 -2
package/dist/bin-local.js
CHANGED
|
@@ -5,7 +5,7 @@ import U from"process";import Jr from"path";import Kr from"fs";import Ci from"mi
|
|
|
5
5
|
${s}
|
|
6
6
|
</extracted_error_chunk>`).join(`
|
|
7
7
|
|
|
8
|
-
`);return o.length>e.length*.8?e:o}import{execSync as Vn}from"child_process";import Rr from"fs/promises";import zn from"path";import ge from"process";import{getTracer as Xn}from"@netlify/otel";import Ae from"process";var X=class extends Error{constructor(r,i,n,o=!1){super(r);this.statusCode=i;this.userMessage=n;this.isCreditLimitExceeded=o;this.name="GracefulShutdownError"}},Ge=e=>e instanceof X;var je=Ae.env.NETLIFY_API_URL,Ye=Ae.env.NETLIFY_API_TOKEN,W=x("api"),ye=()=>Ae.env.NETLIFY_LOCAL_MODE==="true",oe=async(e,t={})=>{if(!je||!Ye)throw new Error("No API URL or token");let r=new URL(e,je),i={...t,headers:{...t.headers,Authorization:`Bearer ${Ye}`}};Ae.env.AGENT_RUNNERS_DEBUG==="true"&&(i.headers["x-nf-debug-logging"]="true"),t.json&&(i.headers||={},i.headers["Content-Type"]="application/json",i.body=JSON.stringify(t.json));let n=await fetch(r,i),o=n.ok&&n.status<=299;if(Ae.env.AGENT_RUNNERS_DEBUG==="true")W.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{W.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");W.log(`Request ID for ${r}: ${a||"N/A"}`)}if(o||W.error(`Got status ${n.status} for request ${r}`),t.raw){if(!o)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!o){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new X(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===503&&t.gracefulOn503&&a.toLowerCase().includes("usage exceeded")?new X(`API request failed: 503 - ${a}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners.",!0):new Error(`API request failed: ${n.status} - ${a}`)}return s},Lt=e=>{W.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(je=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(Ye=e.constants.NETLIFY_API_TOKEN)},Mt=()=>({apiUrl:je,token:Ye}),Ce=async(e,t)=>ye()?(W.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):oe(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),J=async(e,t,r)=>ye()?(W.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):oe(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var Ut=async e=>ye()?(W.log("Mock API: getSite called",{siteId:e}),{id:e,published_deploy:{id:"id"}}):oe(`/api/v1/sites/${e}`),Gt=async(e,t)=>ye()?(W.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):oe(`/api/v1/agent_runners/${e}/sessions/${t}`),jt=(e,t,r)=>oe(`/api/v1/accounts/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Yt=(e,t,r)=>oe(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),qt=async(e,t)=>ye()?(W.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):oe(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Bt=async(e,t)=>ye()?(W.log("Mock API: updateSessionUsage called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,usage:0}):oe(`/api/v1/agent_runners/${e}/sessions/${t}/update_usage`,{method:"POST"}),at=async(e,t)=>{W.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var we=x("ai_gateway"),lt=null;var Ht=async()=>{if(lt)return lt;we.log("Fetching available AI gateway providers");let e=await fetch(`${Mt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return lt=t,we.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},rn=async(e,t)=>{let i=(await Ht()).providers[e];if(!i)return we.log(`Provider '${e}' not found`),!1;let n=i.models.includes(t);return we.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Wt=async({config:e})=>{let t,r,i,n,o=!e.site?.published_deploy;if(!(o?e.accountId:e.siteId))throw new Error(`No entity id for ${o?"account":"site"}`);let a=async()=>{clearTimeout(i),we.log("Requesting AI gateway information");let l=await(o?jt(e.accountId,e.id,e.sessionId):Yt(e.siteId,e.id,e.sessionId));if({token:t,url:n}=l,r=l.expires_at?l.expires_at*1e3:void 0,we.log("Got AI gateway information",{token:!!t,expiresAt:r,url:n}),r){let d=r-Date.now()-6e4;d>0&&(i=setTimeout(()=>{a()},d))}};return await Promise.all([a(),Ht()]),{get url(){return n},get token(){return t},isModelAvailableForProvider:rn}};import Q from"process";import ne from"path";import qe from"fs";import{fileURLToPath as un}from"url";import{createRequire as dn}from"module";import{execa as pn,execaCommand as lo}from"execa";import{Transform as nn}from"stream";function on(){let e=process.env.NETLIFY_SENSITIVE_ENV_KEYS;return e?e.split(",").map(t=>t.trim()).filter(Boolean):[]}function sn(e){let t=e.toLowerCase();return t==="true"||t==="false"?!0:e.trim().length<4}function an(){let t=on().map(r=>process.env[r]).filter(r=>!(!r||sn(r)));return[...new Set(t)].sort((r,i)=>i.length-r.length)}function re(e){if(typeof e!="string")return e;let t=an();if(t.length===0)return e;let r=e;return t.forEach(i=>{let n=new RegExp(ln(i),"g");r=r.replace(n,"******")}),r}function ln(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var _e=class extends nn{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,i){let n=t.toString(),o=re(n);i(null,o)}};function Jt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(i,n,o){let s=typeof i=="string"?re(i):i;return typeof n=="function"?t(s,n):t(s,n,o)},process.stderr.write=function(i,n,o){let s=typeof i=="string"?re(i):i;return typeof n=="function"?r(s,n):r(s,n,o)}}var ke=null,Kt=e=>(ke&&ke.destroy(),ke=new se({totalAllowedTime:e}),ke),Vt=()=>ke;var se=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,i)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),o=null,s=null;i!==void 0&&(s=new Promise((a,c)=>{o=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${i}ms`))},i)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),o&&clearTimeout(o)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var zt="netlify-agent-runner-context.md",ct="task-history",Z=".netlify",ue="results.md",ut="assets";var Xt="free";var de=1800*1e3,w={Environment:"environment",UserMessage:"user-message",AgentMessage:"agent-message",Task:"task",RunCommand:"run-command",Explore:"explore",Plan:"plan",FileRead:"file-read",FileWrite:"file-write",Notebook:"notebook",Web:"web",Todo:"todo",Reasoning:"reasoning",Skill:"skill",Memorize:"memorize",Deployment:"deployment",SiteGeneration:"site-generation"};var Zt={name:"@netlify/agent-runner-cli",type:"module",version:"1.88.0",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts","test:integration:create-stage":"vitest run test/integration/create.test.ts","test:integration:skill-invocation":"vitest run test/integration/skill-invocation.test.ts","check:types":"tsc --noEmit",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.63","@anthropic-ai/sdk":"0.78.0","@google/gemini-cli":"0.31.0","@netlify/otel":"^5.1.2","@openai/codex":"0.110.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.212.0",execa:"^9.6.1",kaddidlehopper:"^0.7.2",minimist:"^1.2.8",openai:"6.26.0"}};var mn=un(import.meta.url),gn=ne.dirname(mn),fn=dn(import.meta.url),Ee=x("shell"),dt=new Set,hn={preferLocal:!0},A=(e,t,r)=>{let[i,n]=yn(t,r),o={...hn,...n},s=pn(e,i,o);wn(s,o),En(s);let a=r?.idleTimeout;return a&&a>0&&_n(s,a),s};var yn=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},wn=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(Q.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new _e).pipe(Q.stdout),e.stdout?.pipe(new _e).pipe(Q.stdout),e.stderr?.pipe(new _e).pipe(Q.stderr);return}e.stdout?.pipe(Q.stdout),e.stderr?.pipe(Q.stderr)},pt=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(Q.kill(-e.pid,t),Ee.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return Ee.error("Error killing process:",r),!1}},Qt=e=>pt(e,"SIGKILL"),_n=(e,t)=>{let r=null,i=()=>{Ee.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),pt(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(Ee.log(`Force killing idle process ${e.pid}`),Qt(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(i,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let o=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",o),e.on("error",o)},En=e=>{dt.add(e);let t=Vt();if(t){let r=t.onTimesUp(()=>{Ee.log(`Global timer expired, killing process ${e.pid}`),pt(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(Ee.log(`Force killing process ${e.pid} after timeout`),Qt(e))},5e3)});e.on("exit",()=>{dt.delete(e),r()}),e.on("error",()=>{dt.delete(e),r()})}};function Be(e,t){return!!ie(e,t)}function ie(e,t){if(!Q.env.NETLIFY_LOCAL_MODE)try{let n=fn.resolve(Zt.name),o=ne.dirname(n);for(;o!==ne.dirname(o);){let s=ne.dirname(o);if(ne.basename(s)==="node_modules"){let a=ne.join(s,".bin",t);if(qe.existsSync(a))return a;break}o=s}}catch(n){console.error("Could not resolve package.json",n)}if(Q.env.NODE_PATH){let n=ne.join(Q.env.NODE_PATH,".bin",t);if(qe.existsSync(n))return n}let r=ne.join(e,"node_modules",".bin",t);if(qe.existsSync(r))return r;let i=ne.join(gn,"..","node_modules",".bin",t);if(qe.existsSync(i))return i}var xn=x("utils"),Tn=e=>new Promise(t=>{setTimeout(t,e)}),He=(e,t=3e3)=>{let r=!1,i=null,n=[],o=null,s=(...a)=>{if(r)return i=a,new Promise(d=>{n.push(d)});r=!0;let c,l=new Promise(d=>{c=d});return o=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await Tn(t),!i)return r=!1,o=null,d;let p=i,u=n;i=null,n=[],d=await e(...p),u.forEach(y=>{y(d)})}})(),l};return s.flush=async()=>{if((r||i)&&o)return await o,s.flush()},s},xe=(e,t,r=!1)=>{let i=null,n=null,o=null,s=function(...a){n=a,o=this;let c=r&&!i;clearTimeout(i),i=setTimeout(()=>{i=null,r||(e.apply(o,n),n=null,o=null)},t),c&&(e.apply(o,n),n=null,o=null)};return s.cancel=()=>{clearTimeout(i),i=null,n=null,o=null},s.flush=()=>{if(i){clearTimeout(i);let a=n,c=o;i=null,n=null,o=null,e.apply(c,a)}},s},er=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(i){t&&(r?.error?r.error("Could not parse JSON",i):xn.error("Could not parse JSON",i))}},mt=e=>e.charAt(0).toUpperCase()+e.slice(1),ae=e=>e.split("-").map(t=>t.length===2?t.toUpperCase():mt(t)).join(" ");function pe(e,t){t&&e.log(`Skill invoked: ${t}`)}var tr=e=>Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0)),rr=(e,t)=>{let n=".netlify.app",o="agent-";if(!t)return`${o}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=o.length+6){let l=Math.min(c-o.length,e.length);return`${o}${e.slice(0,l)}`}return e.slice(0,c)};var In=50*1024,gt=(e,t=In)=>{if(!e||typeof e!="string"||e.length<=t)return e;let i=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+i};import{Buffer as nr}from"buffer";import Sn from"path";var ir=x("repo"),sr=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{ir.info("Getting runner diffs");let i=await Rn(r),{hasChanges:n}=i,{status:o}=i;if(!n)return{hasChanges:!1};if(!t){let _=An(o);await Cn(_,r)}ir.info("Changes after processing"),await ht(r);let s=await yt(o,r);if(await ft(s,r),n=await bn(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await A("git",["diff",e.runSha,"HEAD"],a),l=String(c.stdout??"");if(n=!!l,!n)return await or(r),{hasChanges:!1,ignored:s};let d=await A("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),u,y;if(e.sha){let _=await A("git",["diff",e.sha,"HEAD"],a);u=String(_.stdout??"");let S=await A("git",["diff",e.sha,"HEAD","--binary"],a),h=String(S.stdout??"");u!==h&&(y=nr.from(h).toString("base64"))}await or(r);let E={hasChanges:!0,diff:l,resultDiff:u,ignored:s};return l!==p&&(E.diffBinary=nr.from(p).toString("base64")),y&&(E.resultDiffBinary=y),E},or=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await A("git",["reset","--soft","HEAD~1"],{cwd:e})},ft=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},ht=async(e=process.cwd())=>{let t=await A("git",["status","-s"],{cwd:e});return String(t.stdout??"")},ar=/.. (.+)?\.log$/,vn=[ar],Rn=async(e=process.cwd())=>{let t=await ht(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
8
|
+
`);return o.length>e.length*.8?e:o}import{execSync as Vn}from"child_process";import Rr from"fs/promises";import zn from"path";import ge from"process";import{getTracer as Xn}from"@netlify/otel";import Ae from"process";var X=class extends Error{constructor(r,i,n,o=!1){super(r);this.statusCode=i;this.userMessage=n;this.isCreditLimitExceeded=o;this.name="GracefulShutdownError"}},Ge=e=>e instanceof X;var je=Ae.env.NETLIFY_API_URL,Ye=Ae.env.NETLIFY_API_TOKEN,W=x("api"),ye=()=>Ae.env.NETLIFY_LOCAL_MODE==="true",oe=async(e,t={})=>{if(!je||!Ye)throw new Error("No API URL or token");let r=new URL(e,je),i={...t,headers:{...t.headers,Authorization:`Bearer ${Ye}`}};Ae.env.AGENT_RUNNERS_DEBUG==="true"&&(i.headers["x-nf-debug-logging"]="true"),t.json&&(i.headers||={},i.headers["Content-Type"]="application/json",i.body=JSON.stringify(t.json));let n=await fetch(r,i),o=n.ok&&n.status<=299;if(Ae.env.AGENT_RUNNERS_DEBUG==="true")W.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{W.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");W.log(`Request ID for ${r}: ${a||"N/A"}`)}if(o||W.error(`Got status ${n.status} for request ${r}`),t.raw){if(!o)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!o){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new X(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===503&&t.gracefulOn503&&a.toLowerCase().includes("usage exceeded")?new X(`API request failed: 503 - ${a}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners.",!0):new Error(`API request failed: ${n.status} - ${a}`)}return s},Lt=e=>{W.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(je=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(Ye=e.constants.NETLIFY_API_TOKEN)},Mt=()=>({apiUrl:je,token:Ye}),Ce=async(e,t)=>ye()?(W.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):oe(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),J=async(e,t,r)=>ye()?(W.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):oe(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var Ut=async e=>ye()?(W.log("Mock API: getSite called",{siteId:e}),{id:e,published_deploy:{id:"id"}}):oe(`/api/v1/sites/${e}`),Gt=async(e,t)=>ye()?(W.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):oe(`/api/v1/agent_runners/${e}/sessions/${t}`),jt=(e,t,r)=>oe(`/api/v1/accounts/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Yt=(e,t,r)=>oe(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),qt=async(e,t)=>ye()?(W.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):oe(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Bt=async(e,t)=>ye()?(W.log("Mock API: updateSessionUsage called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,usage:0}):oe(`/api/v1/agent_runners/${e}/sessions/${t}/update_usage`,{method:"POST"}),at=async(e,t)=>{W.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var we=x("ai_gateway"),lt=null;var Ht=async()=>{if(lt)return lt;we.log("Fetching available AI gateway providers");let e=await fetch(`${Mt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return lt=t,we.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},rn=async(e,t)=>{let i=(await Ht()).providers[e];if(!i)return we.log(`Provider '${e}' not found`),!1;let n=i.models.includes(t);return we.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Wt=async({config:e})=>{let t,r,i,n,o=!e.site?.published_deploy;if(!(o?e.accountId:e.siteId))throw new Error(`No entity id for ${o?"account":"site"}`);let a=async()=>{clearTimeout(i),we.log("Requesting AI gateway information");let l=await(o?jt(e.accountId,e.id,e.sessionId):Yt(e.siteId,e.id,e.sessionId));if({token:t,url:n}=l,r=l.expires_at?l.expires_at*1e3:void 0,we.log("Got AI gateway information",{token:!!t,expiresAt:r,url:n}),r){let d=r-Date.now()-6e4;d>0&&(i=setTimeout(()=>{a()},d))}};return await Promise.all([a(),Ht()]),{get url(){return n},get token(){return t},isModelAvailableForProvider:rn}};import Q from"process";import ne from"path";import qe from"fs";import{fileURLToPath as un}from"url";import{createRequire as dn}from"module";import{execa as pn,execaCommand as lo}from"execa";import{Transform as nn}from"stream";function on(){let e=process.env.NETLIFY_SENSITIVE_ENV_KEYS;return e?e.split(",").map(t=>t.trim()).filter(Boolean):[]}function sn(e){let t=e.toLowerCase();return t==="true"||t==="false"?!0:e.trim().length<4}function an(){let t=on().map(r=>process.env[r]).filter(r=>!(!r||sn(r)));return[...new Set(t)].sort((r,i)=>i.length-r.length)}function re(e){if(typeof e!="string")return e;let t=an();if(t.length===0)return e;let r=e;return t.forEach(i=>{let n=new RegExp(ln(i),"g");r=r.replace(n,"******")}),r}function ln(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var _e=class extends nn{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,i){let n=t.toString(),o=re(n);i(null,o)}};function Jt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(i,n,o){let s=typeof i=="string"?re(i):i;return typeof n=="function"?t(s,n):t(s,n,o)},process.stderr.write=function(i,n,o){let s=typeof i=="string"?re(i):i;return typeof n=="function"?r(s,n):r(s,n,o)}}var ke=null,Kt=e=>(ke&&ke.destroy(),ke=new se({totalAllowedTime:e}),ke),Vt=()=>ke;var se=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,i)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),o=null,s=null;i!==void 0&&(s=new Promise((a,c)=>{o=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${i}ms`))},i)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),o&&clearTimeout(o)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var zt="netlify-agent-runner-context.md",ct="task-history",Z=".netlify",ue="results.md",ut="assets";var Xt="free";var de=1800*1e3,w={Environment:"environment",UserMessage:"user-message",AgentMessage:"agent-message",Task:"task",RunCommand:"run-command",Explore:"explore",Plan:"plan",FileRead:"file-read",FileWrite:"file-write",Notebook:"notebook",Web:"web",Todo:"todo",Reasoning:"reasoning",Skill:"skill",Memorize:"memorize",Deployment:"deployment",SiteGeneration:"site-generation"};var Zt={name:"@netlify/agent-runner-cli",type:"module",version:"1.89.1",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts","test:integration:create-stage":"vitest run test/integration/create.test.ts","test:integration:skill-invocation":"vitest run test/integration/skill-invocation.test.ts","check:types":"tsc --noEmit",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.63","@anthropic-ai/sdk":"0.78.0","@google/gemini-cli":"0.31.0","@netlify/otel":"^5.1.2","@openai/codex":"0.110.0","@opentelemetry/exporter-trace-otlp-grpc":"0.57.2",execa:"^9.6.1",kaddidlehopper:"^0.7.2",minimist:"^1.2.8",openai:"6.26.0"}};var mn=un(import.meta.url),gn=ne.dirname(mn),fn=dn(import.meta.url),Ee=x("shell"),dt=new Set,hn={preferLocal:!0},A=(e,t,r)=>{let[i,n]=yn(t,r),o={...hn,...n},s=pn(e,i,o);wn(s,o),En(s);let a=r?.idleTimeout;return a&&a>0&&_n(s,a),s};var yn=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},wn=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(Q.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new _e).pipe(Q.stdout),e.stdout?.pipe(new _e).pipe(Q.stdout),e.stderr?.pipe(new _e).pipe(Q.stderr);return}e.stdout?.pipe(Q.stdout),e.stderr?.pipe(Q.stderr)},pt=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(Q.kill(-e.pid,t),Ee.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return Ee.error("Error killing process:",r),!1}},Qt=e=>pt(e,"SIGKILL"),_n=(e,t)=>{let r=null,i=()=>{Ee.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),pt(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(Ee.log(`Force killing idle process ${e.pid}`),Qt(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(i,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let o=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",o),e.on("error",o)},En=e=>{dt.add(e);let t=Vt();if(t){let r=t.onTimesUp(()=>{Ee.log(`Global timer expired, killing process ${e.pid}`),pt(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(Ee.log(`Force killing process ${e.pid} after timeout`),Qt(e))},5e3)});e.on("exit",()=>{dt.delete(e),r()}),e.on("error",()=>{dt.delete(e),r()})}};function Be(e,t){return!!ie(e,t)}function ie(e,t){if(!Q.env.NETLIFY_LOCAL_MODE)try{let n=fn.resolve(Zt.name),o=ne.dirname(n);for(;o!==ne.dirname(o);){let s=ne.dirname(o);if(ne.basename(s)==="node_modules"){let a=ne.join(s,".bin",t);if(qe.existsSync(a))return a;break}o=s}}catch(n){console.error("Could not resolve package.json",n)}if(Q.env.NODE_PATH){let n=ne.join(Q.env.NODE_PATH,".bin",t);if(qe.existsSync(n))return n}let r=ne.join(e,"node_modules",".bin",t);if(qe.existsSync(r))return r;let i=ne.join(gn,"..","node_modules",".bin",t);if(qe.existsSync(i))return i}var xn=x("utils"),Tn=e=>new Promise(t=>{setTimeout(t,e)}),He=(e,t=3e3)=>{let r=!1,i=null,n=[],o=null,s=(...a)=>{if(r)return i=a,new Promise(d=>{n.push(d)});r=!0;let c,l=new Promise(d=>{c=d});return o=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await Tn(t),!i)return r=!1,o=null,d;let p=i,u=n;i=null,n=[],d=await e(...p),u.forEach(y=>{y(d)})}})(),l};return s.flush=async()=>{if((r||i)&&o)return await o,s.flush()},s},xe=(e,t,r=!1)=>{let i=null,n=null,o=null,s=function(...a){n=a,o=this;let c=r&&!i;clearTimeout(i),i=setTimeout(()=>{i=null,r||(e.apply(o,n),n=null,o=null)},t),c&&(e.apply(o,n),n=null,o=null)};return s.cancel=()=>{clearTimeout(i),i=null,n=null,o=null},s.flush=()=>{if(i){clearTimeout(i);let a=n,c=o;i=null,n=null,o=null,e.apply(c,a)}},s},er=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(i){t&&(r?.error?r.error("Could not parse JSON",i):xn.error("Could not parse JSON",i))}},mt=e=>e.charAt(0).toUpperCase()+e.slice(1),ae=e=>e.split("-").map(t=>t.length===2?t.toUpperCase():mt(t)).join(" ");function pe(e,t){t&&e.log(`Skill invoked: ${t}`)}var tr=e=>Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0)),rr=(e,t)=>{let n=".netlify.app",o="agent-";if(!t)return`${o}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=o.length+6){let l=Math.min(c-o.length,e.length);return`${o}${e.slice(0,l)}`}return e.slice(0,c)};var In=50*1024,gt=(e,t=In)=>{if(!e||typeof e!="string"||e.length<=t)return e;let i=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+i};import{Buffer as nr}from"buffer";import Sn from"path";var ir=x("repo"),sr=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{ir.info("Getting runner diffs");let i=await Rn(r),{hasChanges:n}=i,{status:o}=i;if(!n)return{hasChanges:!1};if(!t){let _=An(o);await Cn(_,r)}ir.info("Changes after processing"),await ht(r);let s=await yt(o,r);if(await ft(s,r),n=await bn(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await A("git",["diff",e.runSha,"HEAD"],a),l=String(c.stdout??"");if(n=!!l,!n)return await or(r),{hasChanges:!1,ignored:s};let d=await A("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),u,y;if(e.sha){let _=await A("git",["diff",e.sha,"HEAD"],a);u=String(_.stdout??"");let S=await A("git",["diff",e.sha,"HEAD","--binary"],a),h=String(S.stdout??"");u!==h&&(y=nr.from(h).toString("base64"))}await or(r);let E={hasChanges:!0,diff:l,resultDiff:u,ignored:s};return l!==p&&(E.diffBinary=nr.from(p).toString("base64")),y&&(E.resultDiffBinary=y),E},or=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await A("git",["reset","--soft","HEAD~1"],{cwd:e})},ft=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await A("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},ht=async(e=process.cwd())=>{let t=await A("git",["status","-s"],{cwd:e});return String(t.stdout??"")},ar=/.. (.+)?\.log$/,vn=[ar],Rn=async(e=process.cwd())=>{let t=await ht(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
9
9
|
`).filter(n=>vn.some(s=>s instanceof RegExp?s.test(n):n===s)?!1:n[1]?.trim()!=="")).length!==0,status:t}},bn=async(e=process.cwd())=>{try{return await A("git",["diff","--staged","--quiet"],{cwd:e}),!1}catch{return!0}},Pe=async(e=process.cwd())=>{let{stdout:t}=await A("git",["rev-parse","HEAD"],{cwd:e});return String(t??"").trim()},lr=async(e=process.cwd())=>{let{stdout:t}=await A("git",["rev-list","--max-parents=0","HEAD"],{cwd:e});return String(t??"").trim()},yt=async(e,t=process.cwd())=>{e||=await ht(t);let r=[".netlify","node_modules","dist",".next","out",".nuxt",".output",".cache",".turbo",".parcel-cache","coverage",".nyc_output","storybook-static","public/build","CLAUDE.local.md"],i=[];return e.split(`
|
|
10
10
|
`).forEach(n=>{r.forEach(s=>{let a=n===`?? ${s}`,c=n.startsWith(`?? ${s}/`)||n.startsWith(`?? ${s}${Sn.sep}`);(a||c)&&i.push(`:!${s}`)});let o=n.match(ar)?.[1];o&&i.push(`:!${o}.log`)}),i},wt=async(e=process.cwd())=>{await A("git",["reset","--hard","HEAD"],{cwd:e})},An=e=>{let t=e.split(`
|
|
11
11
|
`).reduce((r,i)=>{if(!i)return r;let[n,o,,...s]=i,a=s.join(""),c=n.trim(),l=o.trim();return r[a]?r[a].change=l:r[a]={filePath:a,stage:c,change:l},r},{});return Object.values(t)},Cn=async(e,t=process.cwd())=>{let r=e.filter(i=>i.stage&&!i.change).map(i=>i.filePath);r.length!==0&&await A("git",["restore","--staged","--worktree","--pathspec-from-file=-"],{cwd:t,input:r.join(`
|
package/dist/bin.js
CHANGED
|
@@ -5,7 +5,7 @@ import $t from"process";import qo from"minimist";import{createRequire as No}from
|
|
|
5
5
|
${s}
|
|
6
6
|
</extracted_error_chunk>`).join(`
|
|
7
7
|
|
|
8
|
-
`);return i.length>e.length*.8?e:i}import{execSync as eo}from"child_process";import Nr from"fs/promises";import to from"path";import pe from"process";import{getTracer as ro}from"@netlify/otel";import Ae from"process";var V=class extends Error{constructor(r,o,n,i=!1){super(r);this.statusCode=o;this.userMessage=n;this.isCreditLimitExceeded=i;this.name="GracefulShutdownError"}},Ue=e=>e instanceof V;var Ge=Ae.env.NETLIFY_API_URL,je=Ae.env.NETLIFY_API_TOKEN,H=x("api"),fe=()=>Ae.env.NETLIFY_LOCAL_MODE==="true",re=async(e,t={})=>{if(!Ge||!je)throw new Error("No API URL or token");let r=new URL(e,Ge),o={...t,headers:{...t.headers,Authorization:`Bearer ${je}`}};Ae.env.AGENT_RUNNERS_DEBUG==="true"&&(o.headers["x-nf-debug-logging"]="true"),t.json&&(o.headers||={},o.headers["Content-Type"]="application/json",o.body=JSON.stringify(t.json));let n=await fetch(r,o),i=n.ok&&n.status<=299;if(Ae.env.AGENT_RUNNERS_DEBUG==="true")H.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{H.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");H.log(`Request ID for ${r}: ${a||"N/A"}`)}if(i||H.error(`Got status ${n.status} for request ${r}`),t.raw){if(!i)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!i){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new V(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===503&&t.gracefulOn503&&a.toLowerCase().includes("usage exceeded")?new V(`API request failed: 503 - ${a}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners.",!0):new Error(`API request failed: ${n.status} - ${a}`)}return s},Gt=e=>{H.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Ge=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(je=e.constants.NETLIFY_API_TOKEN)},jt=()=>({apiUrl:Ge,token:je}),Ce=async(e,t)=>fe()?(H.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):re(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),W=async(e,t,r)=>fe()?(H.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):re(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var Yt=async e=>fe()?(H.log("Mock API: getSite called",{siteId:e}),{id:e,published_deploy:{id:"id"}}):re(`/api/v1/sites/${e}`),qt=async(e,t)=>fe()?(H.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):re(`/api/v1/agent_runners/${e}/sessions/${t}`),Bt=(e,t,r)=>re(`/api/v1/accounts/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Ht=(e,t,r)=>re(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Wt=async(e,t)=>fe()?(H.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):re(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Kt=async(e,t)=>fe()?(H.log("Mock API: updateSessionUsage called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,usage:0}):re(`/api/v1/agent_runners/${e}/sessions/${t}/update_usage`,{method:"POST"}),st=async(e,t)=>{H.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var he=x("ai_gateway"),at=null;var Jt=async()=>{if(at)return at;he.log("Fetching available AI gateway providers");let e=await fetch(`${jt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return at=t,he.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},ln=async(e,t)=>{let o=(await Jt()).providers[e];if(!o)return he.log(`Provider '${e}' not found`),!1;let n=o.models.includes(t);return he.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Vt=async({config:e})=>{let t,r,o,n,i=!e.site?.published_deploy;if(!(i?e.accountId:e.siteId))throw new Error(`No entity id for ${i?"account":"site"}`);let a=async()=>{clearTimeout(o),he.log("Requesting AI gateway information");let l=await(i?Bt(e.accountId,e.id,e.sessionId):Ht(e.siteId,e.id,e.sessionId));if({token:t,url:n}=l,r=l.expires_at?l.expires_at*1e3:void 0,he.log("Got AI gateway information",{token:!!t,expiresAt:r,url:n}),r){let d=r-Date.now()-6e4;d>0&&(o=setTimeout(()=>{a()},d))}};return await Promise.all([a(),Jt()]),{get url(){return n},get token(){return t},isModelAvailableForProvider:ln}};import X from"process";import te from"path";import Ye from"fs";import{fileURLToPath as fn}from"url";import{createRequire as hn}from"module";import{execa as yn,execaCommand as xi}from"execa";import{Transform as cn}from"stream";function un(){let e=process.env.NETLIFY_SENSITIVE_ENV_KEYS;return e?e.split(",").map(t=>t.trim()).filter(Boolean):[]}function dn(e){let t=e.toLowerCase();return t==="true"||t==="false"?!0:e.trim().length<4}function pn(){let t=un().map(r=>process.env[r]).filter(r=>!(!r||dn(r)));return[...new Set(t)].sort((r,o)=>o.length-r.length)}function ee(e){if(typeof e!="string")return e;let t=pn();if(t.length===0)return e;let r=e;return t.forEach(o=>{let n=new RegExp(mn(o),"g");r=r.replace(n,"******")}),r}function mn(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var ye=class extends cn{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,o){let n=t.toString(),i=ee(n);o(null,i)}};function zt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(o,n,i){let s=typeof o=="string"?ee(o):o;return typeof n=="function"?t(s,n):t(s,n,i)},process.stderr.write=function(o,n,i){let s=typeof o=="string"?ee(o):o;return typeof n=="function"?r(s,n):r(s,n,i)}}var be=null,Xt=e=>(be&&be.destroy(),be=new ne({totalAllowedTime:e}),be),Zt=()=>be;var ne=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,o)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),i=null,s=null;o!==void 0&&(s=new Promise((a,c)=>{i=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${o}ms`))},o)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),i&&clearTimeout(i)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var Qt="netlify-agent-runner-context.md",lt="task-history",z=".netlify",le="results.md",ct="assets",ut="other",dt="personal";var pt="enterprise",Pe="free",er=[dt,"pro",pt,Pe],tr=["normal","redeploy","create","ask","dtn-prod-iteration","rebase"],rr="The production deploy has changed since you started working. Please reapply the changes to the current codebase, resolving any conflicts that arise. Use the attached diff file if present, otherwise review the previous session context to reproduce the changes.",ce=1800*1e3,_={Environment:"environment",UserMessage:"user-message",AgentMessage:"agent-message",Task:"task",RunCommand:"run-command",Explore:"explore",Plan:"plan",FileRead:"file-read",FileWrite:"file-write",Notebook:"notebook",Web:"web",Todo:"todo",Reasoning:"reasoning",Skill:"skill",Memorize:"memorize",Deployment:"deployment",SiteGeneration:"site-generation"};var nr={name:"@netlify/agent-runner-cli",type:"module",version:"1.88.0",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts","test:integration:create-stage":"vitest run test/integration/create.test.ts","test:integration:skill-invocation":"vitest run test/integration/skill-invocation.test.ts","check:types":"tsc --noEmit",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.63","@anthropic-ai/sdk":"0.78.0","@google/gemini-cli":"0.31.0","@netlify/otel":"^5.1.2","@openai/codex":"0.110.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.212.0",execa:"^9.6.1",kaddidlehopper:"^0.7.2",minimist:"^1.2.8",openai:"6.26.0"}};var _n=fn(import.meta.url),En=te.dirname(_n),wn=hn(import.meta.url),_e=x("shell"),mt=new Set,xn={preferLocal:!0},k=(e,t,r)=>{let[o,n]=Tn(t,r),i={...xn,...n},s=yn(e,o,i);In(s,i),Sn(s);let a=r?.idleTimeout;return a&&a>0&&vn(s,a),s};var Tn=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},In=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(X.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new ye).pipe(X.stdout),e.stdout?.pipe(new ye).pipe(X.stdout),e.stderr?.pipe(new ye).pipe(X.stderr);return}e.stdout?.pipe(X.stdout),e.stderr?.pipe(X.stderr)},gt=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(X.kill(-e.pid,t),_e.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return _e.error("Error killing process:",r),!1}},or=e=>gt(e,"SIGKILL"),vn=(e,t)=>{let r=null,o=()=>{_e.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),gt(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(_e.log(`Force killing idle process ${e.pid}`),or(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(o,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let i=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",i),e.on("error",i)},Sn=e=>{mt.add(e);let t=Zt();if(t){let r=t.onTimesUp(()=>{_e.log(`Global timer expired, killing process ${e.pid}`),gt(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(_e.log(`Force killing process ${e.pid} after timeout`),or(e))},5e3)});e.on("exit",()=>{mt.delete(e),r()}),e.on("error",()=>{mt.delete(e),r()})}};function oe(e,t){if(!X.env.NETLIFY_LOCAL_MODE)try{let n=wn.resolve(nr.name),i=te.dirname(n);for(;i!==te.dirname(i);){let s=te.dirname(i);if(te.basename(s)==="node_modules"){let a=te.join(s,".bin",t);if(Ye.existsSync(a))return a;break}i=s}}catch(n){console.error("Could not resolve package.json",n)}if(X.env.NODE_PATH){let n=te.join(X.env.NODE_PATH,".bin",t);if(Ye.existsSync(n))return n}let r=te.join(e,"node_modules",".bin",t);if(Ye.existsSync(r))return r;let o=te.join(En,"..","node_modules",".bin",t);if(Ye.existsSync(o))return o}var ir=x("utils"),Rn=e=>new Promise(t=>{setTimeout(t,e)}),qe=(e,t=3e3)=>{let r=!1,o=null,n=[],i=null,s=(...a)=>{if(r)return o=a,new Promise(d=>{n.push(d)});r=!0;let c,l=new Promise(d=>{c=d});return i=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await Rn(t),!o)return r=!1,i=null,d;let p=o,u=n;o=null,n=[],d=await e(...p),u.forEach(y=>{y(d)})}})(),l};return s.flush=async()=>{if((r||o)&&i)return await i,s.flush()},s},Ee=(e,t,r=!1)=>{let o=null,n=null,i=null,s=function(...a){n=a,i=this;let c=r&&!o;clearTimeout(o),o=setTimeout(()=>{o=null,r||(e.apply(i,n),n=null,i=null)},t),c&&(e.apply(i,n),n=null,i=null)};return s.cancel=()=>{clearTimeout(o),o=null,n=null,i=null},s.flush=()=>{if(o){clearTimeout(o);let a=n,c=i;o=null,n=null,i=null,e.apply(c,a)}},s},Be=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(o){t&&(r?.error?r.error("Could not parse JSON",o):ir.error("Could not parse JSON",o))}},ft=e=>e.charAt(0).toUpperCase()+e.slice(1),ie=e=>e.split("-").map(t=>t.length===2?t.toUpperCase():ft(t)).join(" ");function ue(e,t){t&&e.log(`Skill invoked: ${t}`)}var sr=e=>Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0)),ar=(e,t)=>{let n=".netlify.app",i="agent-";if(!t)return`${i}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=i.length+6){let l=Math.min(c-i.length,e.length);return`${i}${e.slice(0,l)}`}return e.slice(0,c)},An=e=>!e||typeof e!="object"||Array.isArray(e)||Object.keys(e).length===0?!1:!!er.some(t=>t in e),lr=()=>{let e={},t={codex:process.env.NETLIFY_FF_AGENT_RUNNER_CODEX_VERSION,claude:process.env.NETLIFY_FF_AGENT_RUNNER_CLAUDE_VERSION,gemini:process.env.NETLIFY_FF_AGENT_RUNNER_GEMINI_VERSION};return Object.entries(t).forEach(([r,o])=>{if(o){let n=`NETLIFY_FF_AGENT_RUNNER_${r.toUpperCase()}_VERSION`;try{let i=JSON.parse(o);An(i)&&(e[r]=i)}catch(i){let a=i instanceof SyntaxError?"Invalid JSON":i.message;ir.error(`Could not parse ${r} model version override from ${n}: ${a}`)}}}),e},Cn=50*1024,ht=(e,t=Cn)=>{if(!e||typeof e!="string"||e.length<=t)return e;let o=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+o};import{Buffer as cr}from"buffer";import bn from"path";var ur=x("repo"),pr=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{ur.info("Getting runner diffs");let o=await kn(r),{hasChanges:n}=o,{status:i}=o;if(!n)return{hasChanges:!1};if(!t){let E=On(i);await $n(E,r)}ur.info("Changes after processing"),await _t(r);let s=await wt(i,r);if(await yt(s,r),n=await Nn(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await k("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await k("git",["diff",e.runSha,"HEAD"],a),l=String(c.stdout??"");if(n=!!l,!n)return await dr(r),{hasChanges:!1,ignored:s};let d=await k("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),u,y;if(e.sha){let E=await k("git",["diff",e.sha,"HEAD"],a);u=String(E.stdout??"");let I=await k("git",["diff",e.sha,"HEAD","--binary"],a),f=String(I.stdout??"");u!==f&&(y=cr.from(f).toString("base64"))}await dr(r);let w={hasChanges:!0,diff:l,resultDiff:u,ignored:s};return l!==p&&(w.diffBinary=cr.from(p).toString("base64")),y&&(w.resultDiffBinary=y),w},dr=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await k("git",["reset","--soft","HEAD~1"],{cwd:e})},yt=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await k("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},_t=async(e=process.cwd())=>{let t=await k("git",["status","-s"],{cwd:e});return String(t.stdout??"")},mr=/.. (.+)?\.log$/,Pn=[mr],kn=async(e=process.cwd())=>{let t=await _t(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
8
|
+
`);return i.length>e.length*.8?e:i}import{execSync as eo}from"child_process";import Nr from"fs/promises";import to from"path";import pe from"process";import{getTracer as ro}from"@netlify/otel";import Ae from"process";var V=class extends Error{constructor(r,o,n,i=!1){super(r);this.statusCode=o;this.userMessage=n;this.isCreditLimitExceeded=i;this.name="GracefulShutdownError"}},Ue=e=>e instanceof V;var Ge=Ae.env.NETLIFY_API_URL,je=Ae.env.NETLIFY_API_TOKEN,H=x("api"),fe=()=>Ae.env.NETLIFY_LOCAL_MODE==="true",re=async(e,t={})=>{if(!Ge||!je)throw new Error("No API URL or token");let r=new URL(e,Ge),o={...t,headers:{...t.headers,Authorization:`Bearer ${je}`}};Ae.env.AGENT_RUNNERS_DEBUG==="true"&&(o.headers["x-nf-debug-logging"]="true"),t.json&&(o.headers||={},o.headers["Content-Type"]="application/json",o.body=JSON.stringify(t.json));let n=await fetch(r,o),i=n.ok&&n.status<=299;if(Ae.env.AGENT_RUNNERS_DEBUG==="true")H.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{H.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");H.log(`Request ID for ${r}: ${a||"N/A"}`)}if(i||H.error(`Got status ${n.status} for request ${r}`),t.raw){if(!i)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!i){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new V(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===503&&t.gracefulOn503&&a.toLowerCase().includes("usage exceeded")?new V(`API request failed: 503 - ${a}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners.",!0):new Error(`API request failed: ${n.status} - ${a}`)}return s},Gt=e=>{H.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Ge=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(je=e.constants.NETLIFY_API_TOKEN)},jt=()=>({apiUrl:Ge,token:je}),Ce=async(e,t)=>fe()?(H.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):re(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),W=async(e,t,r)=>fe()?(H.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):re(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var Yt=async e=>fe()?(H.log("Mock API: getSite called",{siteId:e}),{id:e,published_deploy:{id:"id"}}):re(`/api/v1/sites/${e}`),qt=async(e,t)=>fe()?(H.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):re(`/api/v1/agent_runners/${e}/sessions/${t}`),Bt=(e,t,r)=>re(`/api/v1/accounts/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Ht=(e,t,r)=>re(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Wt=async(e,t)=>fe()?(H.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):re(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Kt=async(e,t)=>fe()?(H.log("Mock API: updateSessionUsage called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,usage:0}):re(`/api/v1/agent_runners/${e}/sessions/${t}/update_usage`,{method:"POST"}),st=async(e,t)=>{H.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var he=x("ai_gateway"),at=null;var Jt=async()=>{if(at)return at;he.log("Fetching available AI gateway providers");let e=await fetch(`${jt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return at=t,he.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},ln=async(e,t)=>{let o=(await Jt()).providers[e];if(!o)return he.log(`Provider '${e}' not found`),!1;let n=o.models.includes(t);return he.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Vt=async({config:e})=>{let t,r,o,n,i=!e.site?.published_deploy;if(!(i?e.accountId:e.siteId))throw new Error(`No entity id for ${i?"account":"site"}`);let a=async()=>{clearTimeout(o),he.log("Requesting AI gateway information");let l=await(i?Bt(e.accountId,e.id,e.sessionId):Ht(e.siteId,e.id,e.sessionId));if({token:t,url:n}=l,r=l.expires_at?l.expires_at*1e3:void 0,he.log("Got AI gateway information",{token:!!t,expiresAt:r,url:n}),r){let d=r-Date.now()-6e4;d>0&&(o=setTimeout(()=>{a()},d))}};return await Promise.all([a(),Jt()]),{get url(){return n},get token(){return t},isModelAvailableForProvider:ln}};import X from"process";import te from"path";import Ye from"fs";import{fileURLToPath as fn}from"url";import{createRequire as hn}from"module";import{execa as yn,execaCommand as xi}from"execa";import{Transform as cn}from"stream";function un(){let e=process.env.NETLIFY_SENSITIVE_ENV_KEYS;return e?e.split(",").map(t=>t.trim()).filter(Boolean):[]}function dn(e){let t=e.toLowerCase();return t==="true"||t==="false"?!0:e.trim().length<4}function pn(){let t=un().map(r=>process.env[r]).filter(r=>!(!r||dn(r)));return[...new Set(t)].sort((r,o)=>o.length-r.length)}function ee(e){if(typeof e!="string")return e;let t=pn();if(t.length===0)return e;let r=e;return t.forEach(o=>{let n=new RegExp(mn(o),"g");r=r.replace(n,"******")}),r}function mn(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var ye=class extends cn{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,o){let n=t.toString(),i=ee(n);o(null,i)}};function zt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(o,n,i){let s=typeof o=="string"?ee(o):o;return typeof n=="function"?t(s,n):t(s,n,i)},process.stderr.write=function(o,n,i){let s=typeof o=="string"?ee(o):o;return typeof n=="function"?r(s,n):r(s,n,i)}}var be=null,Xt=e=>(be&&be.destroy(),be=new ne({totalAllowedTime:e}),be),Zt=()=>be;var ne=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,o)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),i=null,s=null;o!==void 0&&(s=new Promise((a,c)=>{i=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${o}ms`))},o)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),i&&clearTimeout(i)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var Qt="netlify-agent-runner-context.md",lt="task-history",z=".netlify",le="results.md",ct="assets",ut="other",dt="personal";var pt="enterprise",Pe="free",er=[dt,"pro",pt,Pe],tr=["normal","redeploy","create","ask","dtn-prod-iteration","rebase"],rr="The production deploy has changed since you started working. Please reapply the changes to the current codebase, resolving any conflicts that arise. Use the attached diff file if present, otherwise review the previous session context to reproduce the changes.",ce=1800*1e3,_={Environment:"environment",UserMessage:"user-message",AgentMessage:"agent-message",Task:"task",RunCommand:"run-command",Explore:"explore",Plan:"plan",FileRead:"file-read",FileWrite:"file-write",Notebook:"notebook",Web:"web",Todo:"todo",Reasoning:"reasoning",Skill:"skill",Memorize:"memorize",Deployment:"deployment",SiteGeneration:"site-generation"};var nr={name:"@netlify/agent-runner-cli",type:"module",version:"1.89.1",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts","test:integration:create-stage":"vitest run test/integration/create.test.ts","test:integration:skill-invocation":"vitest run test/integration/skill-invocation.test.ts","check:types":"tsc --noEmit",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.63","@anthropic-ai/sdk":"0.78.0","@google/gemini-cli":"0.31.0","@netlify/otel":"^5.1.2","@openai/codex":"0.110.0","@opentelemetry/exporter-trace-otlp-grpc":"0.57.2",execa:"^9.6.1",kaddidlehopper:"^0.7.2",minimist:"^1.2.8",openai:"6.26.0"}};var _n=fn(import.meta.url),En=te.dirname(_n),wn=hn(import.meta.url),_e=x("shell"),mt=new Set,xn={preferLocal:!0},k=(e,t,r)=>{let[o,n]=Tn(t,r),i={...xn,...n},s=yn(e,o,i);In(s,i),Sn(s);let a=r?.idleTimeout;return a&&a>0&&vn(s,a),s};var Tn=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},In=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(X.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new ye).pipe(X.stdout),e.stdout?.pipe(new ye).pipe(X.stdout),e.stderr?.pipe(new ye).pipe(X.stderr);return}e.stdout?.pipe(X.stdout),e.stderr?.pipe(X.stderr)},gt=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(X.kill(-e.pid,t),_e.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return _e.error("Error killing process:",r),!1}},or=e=>gt(e,"SIGKILL"),vn=(e,t)=>{let r=null,o=()=>{_e.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),gt(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(_e.log(`Force killing idle process ${e.pid}`),or(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(o,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let i=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",i),e.on("error",i)},Sn=e=>{mt.add(e);let t=Zt();if(t){let r=t.onTimesUp(()=>{_e.log(`Global timer expired, killing process ${e.pid}`),gt(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(_e.log(`Force killing process ${e.pid} after timeout`),or(e))},5e3)});e.on("exit",()=>{mt.delete(e),r()}),e.on("error",()=>{mt.delete(e),r()})}};function oe(e,t){if(!X.env.NETLIFY_LOCAL_MODE)try{let n=wn.resolve(nr.name),i=te.dirname(n);for(;i!==te.dirname(i);){let s=te.dirname(i);if(te.basename(s)==="node_modules"){let a=te.join(s,".bin",t);if(Ye.existsSync(a))return a;break}i=s}}catch(n){console.error("Could not resolve package.json",n)}if(X.env.NODE_PATH){let n=te.join(X.env.NODE_PATH,".bin",t);if(Ye.existsSync(n))return n}let r=te.join(e,"node_modules",".bin",t);if(Ye.existsSync(r))return r;let o=te.join(En,"..","node_modules",".bin",t);if(Ye.existsSync(o))return o}var ir=x("utils"),Rn=e=>new Promise(t=>{setTimeout(t,e)}),qe=(e,t=3e3)=>{let r=!1,o=null,n=[],i=null,s=(...a)=>{if(r)return o=a,new Promise(d=>{n.push(d)});r=!0;let c,l=new Promise(d=>{c=d});return i=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await Rn(t),!o)return r=!1,i=null,d;let p=o,u=n;o=null,n=[],d=await e(...p),u.forEach(y=>{y(d)})}})(),l};return s.flush=async()=>{if((r||o)&&i)return await i,s.flush()},s},Ee=(e,t,r=!1)=>{let o=null,n=null,i=null,s=function(...a){n=a,i=this;let c=r&&!o;clearTimeout(o),o=setTimeout(()=>{o=null,r||(e.apply(i,n),n=null,i=null)},t),c&&(e.apply(i,n),n=null,i=null)};return s.cancel=()=>{clearTimeout(o),o=null,n=null,i=null},s.flush=()=>{if(o){clearTimeout(o);let a=n,c=i;o=null,n=null,i=null,e.apply(c,a)}},s},Be=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(o){t&&(r?.error?r.error("Could not parse JSON",o):ir.error("Could not parse JSON",o))}},ft=e=>e.charAt(0).toUpperCase()+e.slice(1),ie=e=>e.split("-").map(t=>t.length===2?t.toUpperCase():ft(t)).join(" ");function ue(e,t){t&&e.log(`Skill invoked: ${t}`)}var sr=e=>Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0)),ar=(e,t)=>{let n=".netlify.app",i="agent-";if(!t)return`${i}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=i.length+6){let l=Math.min(c-i.length,e.length);return`${i}${e.slice(0,l)}`}return e.slice(0,c)},An=e=>!e||typeof e!="object"||Array.isArray(e)||Object.keys(e).length===0?!1:!!er.some(t=>t in e),lr=()=>{let e={},t={codex:process.env.NETLIFY_FF_AGENT_RUNNER_CODEX_VERSION,claude:process.env.NETLIFY_FF_AGENT_RUNNER_CLAUDE_VERSION,gemini:process.env.NETLIFY_FF_AGENT_RUNNER_GEMINI_VERSION};return Object.entries(t).forEach(([r,o])=>{if(o){let n=`NETLIFY_FF_AGENT_RUNNER_${r.toUpperCase()}_VERSION`;try{let i=JSON.parse(o);An(i)&&(e[r]=i)}catch(i){let a=i instanceof SyntaxError?"Invalid JSON":i.message;ir.error(`Could not parse ${r} model version override from ${n}: ${a}`)}}}),e},Cn=50*1024,ht=(e,t=Cn)=>{if(!e||typeof e!="string"||e.length<=t)return e;let o=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+o};import{Buffer as cr}from"buffer";import bn from"path";var ur=x("repo"),pr=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{ur.info("Getting runner diffs");let o=await kn(r),{hasChanges:n}=o,{status:i}=o;if(!n)return{hasChanges:!1};if(!t){let E=On(i);await $n(E,r)}ur.info("Changes after processing"),await _t(r);let s=await wt(i,r);if(await yt(s,r),n=await Nn(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await k("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await k("git",["diff",e.runSha,"HEAD"],a),l=String(c.stdout??"");if(n=!!l,!n)return await dr(r),{hasChanges:!1,ignored:s};let d=await k("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),u,y;if(e.sha){let E=await k("git",["diff",e.sha,"HEAD"],a);u=String(E.stdout??"");let I=await k("git",["diff",e.sha,"HEAD","--binary"],a),f=String(I.stdout??"");u!==f&&(y=cr.from(f).toString("base64"))}await dr(r);let w={hasChanges:!0,diff:l,resultDiff:u,ignored:s};return l!==p&&(w.diffBinary=cr.from(p).toString("base64")),y&&(w.resultDiffBinary=y),w},dr=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await k("git",["reset","--soft","HEAD~1"],{cwd:e})},yt=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await k("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},_t=async(e=process.cwd())=>{let t=await k("git",["status","-s"],{cwd:e});return String(t.stdout??"")},mr=/.. (.+)?\.log$/,Pn=[mr],kn=async(e=process.cwd())=>{let t=await _t(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
9
9
|
`).filter(n=>Pn.some(s=>s instanceof RegExp?s.test(n):n===s)?!1:n[1]?.trim()!=="")).length!==0,status:t}},Nn=async(e=process.cwd())=>{try{return await k("git",["diff","--staged","--quiet"],{cwd:e}),!1}catch{return!0}},Et=async(e=process.cwd())=>{let{stdout:t}=await k("git",["rev-parse","HEAD"],{cwd:e});return String(t??"").trim()},gr=async(e=process.cwd())=>{let{stdout:t}=await k("git",["rev-list","--max-parents=0","HEAD"],{cwd:e});return String(t??"").trim()},wt=async(e,t=process.cwd())=>{e||=await _t(t);let r=[".netlify","node_modules","dist",".next","out",".nuxt",".output",".cache",".turbo",".parcel-cache","coverage",".nyc_output","storybook-static","public/build","CLAUDE.local.md"],o=[];return e.split(`
|
|
10
10
|
`).forEach(n=>{r.forEach(s=>{let a=n===`?? ${s}`,c=n.startsWith(`?? ${s}/`)||n.startsWith(`?? ${s}${bn.sep}`);(a||c)&&o.push(`:!${s}`)});let i=n.match(mr)?.[1];i&&o.push(`:!${i}.log`)}),o},xt=async(e=process.cwd())=>{await k("git",["reset","--hard","HEAD"],{cwd:e})},On=e=>{let t=e.split(`
|
|
11
11
|
`).reduce((r,o)=>{if(!o)return r;let[n,i,,...s]=o,a=s.join(""),c=n.trim(),l=i.trim();return r[a]?r[a].change=l:r[a]={filePath:a,stage:c,change:l},r},{});return Object.values(t)},$n=async(e,t=process.cwd())=>{let r=e.filter(o=>o.stage&&!o.change).map(o=>o.filePath);r.length!==0&&await k("git",["restore","--staged","--worktree","--pathspec-from-file=-"],{cwd:t,input:r.join(`
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import{createRequire as hi}from"module";import{createTracerProvider as Gr}from"@
|
|
|
4
4
|
${s}
|
|
5
5
|
</extracted_error_chunk>`).join(`
|
|
6
6
|
|
|
7
|
-
`);return o.length>e.length*.8?e:o}import{execSync as Un}from"child_process";import Er from"fs/promises";import Gn from"path";import de from"process";import{getTracer as jn}from"@netlify/otel";import Ie from"process";var J=class extends Error{constructor(r,i,n,o=!1){super(r);this.statusCode=i;this.userMessage=n;this.isCreditLimitExceeded=o;this.name="GracefulShutdownError"}},De=e=>e instanceof J;var Le=Ie.env.NETLIFY_API_URL,Me=Ie.env.NETLIFY_API_TOKEN,B=x("api"),ge=()=>Ie.env.NETLIFY_LOCAL_MODE==="true",te=async(e,t={})=>{if(!Le||!Me)throw new Error("No API URL or token");let r=new URL(e,Le),i={...t,headers:{...t.headers,Authorization:`Bearer ${Me}`}};Ie.env.AGENT_RUNNERS_DEBUG==="true"&&(i.headers["x-nf-debug-logging"]="true"),t.json&&(i.headers||={},i.headers["Content-Type"]="application/json",i.body=JSON.stringify(t.json));let n=await fetch(r,i),o=n.ok&&n.status<=299;if(Ie.env.AGENT_RUNNERS_DEBUG==="true")B.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{B.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");B.log(`Request ID for ${r}: ${a||"N/A"}`)}if(o||B.error(`Got status ${n.status} for request ${r}`),t.raw){if(!o)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!o){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new J(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===503&&t.gracefulOn503&&a.toLowerCase().includes("usage exceeded")?new J(`API request failed: 503 - ${a}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners.",!0):new Error(`API request failed: ${n.status} - ${a}`)}return s},Pt=e=>{B.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Le=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(Me=e.constants.NETLIFY_API_TOKEN)},Nt=()=>({apiUrl:Le,token:Me}),Re=async(e,t)=>ge()?(B.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):te(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),W=async(e,t,r)=>ge()?(B.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):te(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var Ot=async e=>ge()?(B.log("Mock API: getSite called",{siteId:e}),{id:e,published_deploy:{id:"id"}}):te(`/api/v1/sites/${e}`),$t=async(e,t)=>ge()?(B.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):te(`/api/v1/agent_runners/${e}/sessions/${t}`),Ft=(e,t,r)=>te(`/api/v1/accounts/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Dt=(e,t,r)=>te(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Lt=async(e,t)=>ge()?(B.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):te(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Mt=async(e,t)=>ge()?(B.log("Mock API: updateSessionUsage called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,usage:0}):te(`/api/v1/agent_runners/${e}/sessions/${t}/update_usage`,{method:"POST"}),rt=async(e,t)=>{B.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var fe=x("ai_gateway"),nt=null;var Ut=async()=>{if(nt)return nt;fe.log("Fetching available AI gateway providers");let e=await fetch(`${Nt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return nt=t,fe.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},Hr=async(e,t)=>{let i=(await Ut()).providers[e];if(!i)return fe.log(`Provider '${e}' not found`),!1;let n=i.models.includes(t);return fe.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Gt=async({config:e})=>{let t,r,i,n,o=!e.site?.published_deploy;if(!(o?e.accountId:e.siteId))throw new Error(`No entity id for ${o?"account":"site"}`);let a=async()=>{clearTimeout(i),fe.log("Requesting AI gateway information");let l=await(o?Ft(e.accountId,e.id,e.sessionId):Dt(e.siteId,e.id,e.sessionId));if({token:t,url:n}=l,r=l.expires_at?l.expires_at*1e3:void 0,fe.log("Got AI gateway information",{token:!!t,expiresAt:r,url:n}),r){let d=r-Date.now()-6e4;d>0&&(i=setTimeout(()=>{a()},d))}};return await Promise.all([a(),Ut()]),{get url(){return n},get token(){return t},isModelAvailableForProvider:Hr}};import z from"process";import ee from"path";import Ue from"fs";import{fileURLToPath as Qr}from"url";import{createRequire as en}from"module";import{execa as tn,execaCommand as zi}from"execa";import{Transform as Kr}from"stream";function Jr(){let e=process.env.NETLIFY_SENSITIVE_ENV_KEYS;return e?e.split(",").map(t=>t.trim()).filter(Boolean):[]}function Vr(e){let t=e.toLowerCase();return t==="true"||t==="false"?!0:e.trim().length<4}function zr(){let t=Jr().map(r=>process.env[r]).filter(r=>!(!r||Vr(r)));return[...new Set(t)].sort((r,i)=>i.length-r.length)}function Q(e){if(typeof e!="string")return e;let t=zr();if(t.length===0)return e;let r=e;return t.forEach(i=>{let n=new RegExp(Xr(i),"g");r=r.replace(n,"******")}),r}function Xr(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var he=class extends Kr{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,i){let n=t.toString(),o=Q(n);i(null,o)}};function jt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(i,n,o){let s=typeof i=="string"?Q(i):i;return typeof n=="function"?t(s,n):t(s,n,o)},process.stderr.write=function(i,n,o){let s=typeof i=="string"?Q(i):i;return typeof n=="function"?r(s,n):r(s,n,o)}}var be=null,Yt=e=>(be&&be.destroy(),be=new re({totalAllowedTime:e}),be),qt=()=>be;var re=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,i)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),o=null,s=null;i!==void 0&&(s=new Promise((a,c)=>{o=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${i}ms`))},i)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),o&&clearTimeout(o)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var Bt="netlify-agent-runner-context.md",it="task-history",V=".netlify",ae="results.md",ot="assets";var Wt="free";var le=1800*1e3,w={Environment:"environment",UserMessage:"user-message",AgentMessage:"agent-message",Task:"task",RunCommand:"run-command",Explore:"explore",Plan:"plan",FileRead:"file-read",FileWrite:"file-write",Notebook:"notebook",Web:"web",Todo:"todo",Reasoning:"reasoning",Skill:"skill",Memorize:"memorize",Deployment:"deployment",SiteGeneration:"site-generation"};var Ht={name:"@netlify/agent-runner-cli",type:"module",version:"1.88.0",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts","test:integration:create-stage":"vitest run test/integration/create.test.ts","test:integration:skill-invocation":"vitest run test/integration/skill-invocation.test.ts","check:types":"tsc --noEmit",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.63","@anthropic-ai/sdk":"0.78.0","@google/gemini-cli":"0.31.0","@netlify/otel":"^5.1.2","@openai/codex":"0.110.0","@opentelemetry/exporter-trace-otlp-grpc":"^0.212.0",execa:"^9.6.1",kaddidlehopper:"^0.7.2",minimist:"^1.2.8",openai:"6.26.0"}};var rn=Qr(import.meta.url),nn=ee.dirname(rn),on=en(import.meta.url),ye=x("shell"),st=new Set,sn={preferLocal:!0},P=(e,t,r)=>{let[i,n]=an(t,r),o={...sn,...n},s=tn(e,i,o);ln(s,o),un(s);let a=r?.idleTimeout;return a&&a>0&&cn(s,a),s};var an=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},ln=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(z.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new he).pipe(z.stdout),e.stdout?.pipe(new he).pipe(z.stdout),e.stderr?.pipe(new he).pipe(z.stderr);return}e.stdout?.pipe(z.stdout),e.stderr?.pipe(z.stderr)},at=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(z.kill(-e.pid,t),ye.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return ye.error("Error killing process:",r),!1}},Kt=e=>at(e,"SIGKILL"),cn=(e,t)=>{let r=null,i=()=>{ye.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),at(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ye.log(`Force killing idle process ${e.pid}`),Kt(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(i,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let o=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",o),e.on("error",o)},un=e=>{st.add(e);let t=qt();if(t){let r=t.onTimesUp(()=>{ye.log(`Global timer expired, killing process ${e.pid}`),at(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ye.log(`Force killing process ${e.pid} after timeout`),Kt(e))},5e3)});e.on("exit",()=>{st.delete(e),r()}),e.on("error",()=>{st.delete(e),r()})}};function ne(e,t){if(!z.env.NETLIFY_LOCAL_MODE)try{let n=on.resolve(Ht.name),o=ee.dirname(n);for(;o!==ee.dirname(o);){let s=ee.dirname(o);if(ee.basename(s)==="node_modules"){let a=ee.join(s,".bin",t);if(Ue.existsSync(a))return a;break}o=s}}catch(n){console.error("Could not resolve package.json",n)}if(z.env.NODE_PATH){let n=ee.join(z.env.NODE_PATH,".bin",t);if(Ue.existsSync(n))return n}let r=ee.join(e,"node_modules",".bin",t);if(Ue.existsSync(r))return r;let i=ee.join(nn,"..","node_modules",".bin",t);if(Ue.existsSync(i))return i}var dn=x("utils"),pn=e=>new Promise(t=>{setTimeout(t,e)}),Ge=(e,t=3e3)=>{let r=!1,i=null,n=[],o=null,s=(...a)=>{if(r)return i=a,new Promise(d=>{n.push(d)});r=!0;let c,l=new Promise(d=>{c=d});return o=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await pn(t),!i)return r=!1,o=null,d;let p=i,u=n;i=null,n=[],d=await e(...p),u.forEach(y=>{y(d)})}})(),l};return s.flush=async()=>{if((r||i)&&o)return await o,s.flush()},s},we=(e,t,r=!1)=>{let i=null,n=null,o=null,s=function(...a){n=a,o=this;let c=r&&!i;clearTimeout(i),i=setTimeout(()=>{i=null,r||(e.apply(o,n),n=null,o=null)},t),c&&(e.apply(o,n),n=null,o=null)};return s.cancel=()=>{clearTimeout(i),i=null,n=null,o=null},s.flush=()=>{if(i){clearTimeout(i);let a=n,c=o;i=null,n=null,o=null,e.apply(c,a)}},s},Jt=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(i){t&&(r?.error?r.error("Could not parse JSON",i):dn.error("Could not parse JSON",i))}},lt=e=>e.charAt(0).toUpperCase()+e.slice(1),ie=e=>e.split("-").map(t=>t.length===2?t.toUpperCase():lt(t)).join(" ");function ce(e,t){t&&e.log(`Skill invoked: ${t}`)}var Vt=e=>Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0)),zt=(e,t)=>{let n=".netlify.app",o="agent-";if(!t)return`${o}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=o.length+6){let l=Math.min(c-o.length,e.length);return`${o}${e.slice(0,l)}`}return e.slice(0,c)};var mn=50*1024,ct=(e,t=mn)=>{if(!e||typeof e!="string"||e.length<=t)return e;let i=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+i};import{Buffer as Xt}from"buffer";import gn from"path";var Zt=x("repo"),er=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{Zt.info("Getting runner diffs");let i=await hn(r),{hasChanges:n}=i,{status:o}=i;if(!n)return{hasChanges:!1};if(!t){let _=wn(o);await _n(_,r)}Zt.info("Changes after processing"),await dt(r);let s=await mt(o,r);if(await ut(s,r),n=await yn(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await P("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await P("git",["diff",e.runSha,"HEAD"],a),l=String(c.stdout??"");if(n=!!l,!n)return await Qt(r),{hasChanges:!1,ignored:s};let d=await P("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),u,y;if(e.sha){let _=await P("git",["diff",e.sha,"HEAD"],a);u=String(_.stdout??"");let v=await P("git",["diff",e.sha,"HEAD","--binary"],a),h=String(v.stdout??"");u!==h&&(y=Xt.from(h).toString("base64"))}await Qt(r);let E={hasChanges:!0,diff:l,resultDiff:u,ignored:s};return l!==p&&(E.diffBinary=Xt.from(p).toString("base64")),y&&(E.resultDiffBinary=y),E},Qt=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await P("git",["reset","--soft","HEAD~1"],{cwd:e})},ut=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await P("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},dt=async(e=process.cwd())=>{let t=await P("git",["status","-s"],{cwd:e});return String(t.stdout??"")},tr=/.. (.+)?\.log$/,fn=[tr],hn=async(e=process.cwd())=>{let t=await dt(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
7
|
+
`);return o.length>e.length*.8?e:o}import{execSync as Un}from"child_process";import Er from"fs/promises";import Gn from"path";import de from"process";import{getTracer as jn}from"@netlify/otel";import Ie from"process";var J=class extends Error{constructor(r,i,n,o=!1){super(r);this.statusCode=i;this.userMessage=n;this.isCreditLimitExceeded=o;this.name="GracefulShutdownError"}},De=e=>e instanceof J;var Le=Ie.env.NETLIFY_API_URL,Me=Ie.env.NETLIFY_API_TOKEN,B=x("api"),ge=()=>Ie.env.NETLIFY_LOCAL_MODE==="true",te=async(e,t={})=>{if(!Le||!Me)throw new Error("No API URL or token");let r=new URL(e,Le),i={...t,headers:{...t.headers,Authorization:`Bearer ${Me}`}};Ie.env.AGENT_RUNNERS_DEBUG==="true"&&(i.headers["x-nf-debug-logging"]="true"),t.json&&(i.headers||={},i.headers["Content-Type"]="application/json",i.body=JSON.stringify(t.json));let n=await fetch(r,i),o=n.ok&&n.status<=299;if(Ie.env.AGENT_RUNNERS_DEBUG==="true")B.log(`Response headers for ${r}:`),n.headers.forEach((a,c)=>{B.log(` ${c}: ${a}`)});else{let a=n.headers.get("x-request-id")||n.headers.get("x-nf-request-id");B.log(`Request ID for ${r}: ${a||"N/A"}`)}if(o||B.error(`Got status ${n.status} for request ${r}`),t.raw){if(!o)throw new Error(`API request failed: ${n.status} ${n.statusText}`);return n}let s=await(n.headers.get("content-type")?.includes("application/json")?n.json():n.text());if(!o){let a=typeof s=="string"?s:JSON.stringify(s);throw n.status===404?new J(`API request failed: 404 - ${a}`,404,"The site associated with this agent run no longer exists."):n.status===503&&t.gracefulOn503&&a.toLowerCase().includes("usage exceeded")?new J(`API request failed: 503 - ${a}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners.",!0):new Error(`API request failed: ${n.status} - ${a}`)}return s},Pt=e=>{B.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),e?.constants?.NETLIFY_API_HOST&&(Le=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(Me=e.constants.NETLIFY_API_TOKEN)},Nt=()=>({apiUrl:Le,token:Me}),Re=async(e,t)=>ge()?(B.log("Mock API: updateRunner called",{runnerId:e,data:t}),{id:e,...t}):te(`/api/v1/agent_runners/${e}`,{method:"PUT",json:t}),W=async(e,t,r)=>ge()?(B.log("Mock API: updateRunnerSession called",JSON.stringify({runnerId:e,sessionId:t,data:r},null,2)),{id:e,sessionId:t,...r}):te(`/api/v1/agent_runners/${e}/sessions/${t}`,{method:"PUT",json:r});var Ot=async e=>ge()?(B.log("Mock API: getSite called",{siteId:e}),{id:e,published_deploy:{id:"id"}}):te(`/api/v1/sites/${e}`),$t=async(e,t)=>ge()?(B.log("Mock API: getRunnerSession called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,state:"running"}):te(`/api/v1/agent_runners/${e}/sessions/${t}`),Ft=(e,t,r)=>te(`/api/v1/accounts/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Dt=(e,t,r)=>te(`/api/v1/sites/${e}/ai-gateway/token`,{headers:{"X-Nf-Agent-Runner-Id":t,"X-Nf-Agent-Runner-Session-Id":r},gracefulOn503:!0}),Lt=async(e,t)=>ge()?(B.log("Mock API: getDiffUploadUrls called",{runnerId:e,sessionId:t}),{result:{upload_url:"https://s3.mock.com/mock-upload-url-result",s3_key:"mock-s3-key-result"},cumulative:{upload_url:"https://s3.mock.com/mock-upload-url-cumulative",s3_key:"mock-s3-key-cumulative"}}):te(`/api/v1/agent_runners/${e}/sessions/${t}/diff/upload_urls`,{method:"POST"}),Mt=async(e,t)=>ge()?(B.log("Mock API: updateSessionUsage called",{runnerId:e,sessionId:t}),{id:t,runnerId:e,usage:0}):te(`/api/v1/agent_runners/${e}/sessions/${t}/update_usage`,{method:"POST"}),rt=async(e,t)=>{B.log(`Uploading diff to S3: ${e.substring(0,50)}...`);let r=await fetch(e,{method:"PUT",body:t,headers:{"Content-Type":"text/plain"}});if(!r.ok)throw new Error(`S3 upload failed with status ${r.status}`);return r};var fe=x("ai_gateway"),nt=null;var Ut=async()=>{if(nt)return nt;fe.log("Fetching available AI gateway providers");let e=await fetch(`${Nt().apiUrl}/api/v1/ai-gateway/providers`);if(!e.ok)throw new Error(`Failed to fetch AI gateway providers: ${e.statusText}`);let t=await e.json();return nt=t,fe.log("Cached AI gateway providers",{providerCount:Object.keys(t.providers).length}),t},Hr=async(e,t)=>{let i=(await Ut()).providers[e];if(!i)return fe.log(`Provider '${e}' not found`),!1;let n=i.models.includes(t);return fe.log(`Model validation for ${e}/${t}`,{isAvailable:n}),n},Gt=async({config:e})=>{let t,r,i,n,o=!e.site?.published_deploy;if(!(o?e.accountId:e.siteId))throw new Error(`No entity id for ${o?"account":"site"}`);let a=async()=>{clearTimeout(i),fe.log("Requesting AI gateway information");let l=await(o?Ft(e.accountId,e.id,e.sessionId):Dt(e.siteId,e.id,e.sessionId));if({token:t,url:n}=l,r=l.expires_at?l.expires_at*1e3:void 0,fe.log("Got AI gateway information",{token:!!t,expiresAt:r,url:n}),r){let d=r-Date.now()-6e4;d>0&&(i=setTimeout(()=>{a()},d))}};return await Promise.all([a(),Ut()]),{get url(){return n},get token(){return t},isModelAvailableForProvider:Hr}};import z from"process";import ee from"path";import Ue from"fs";import{fileURLToPath as Qr}from"url";import{createRequire as en}from"module";import{execa as tn,execaCommand as zi}from"execa";import{Transform as Kr}from"stream";function Jr(){let e=process.env.NETLIFY_SENSITIVE_ENV_KEYS;return e?e.split(",").map(t=>t.trim()).filter(Boolean):[]}function Vr(e){let t=e.toLowerCase();return t==="true"||t==="false"?!0:e.trim().length<4}function zr(){let t=Jr().map(r=>process.env[r]).filter(r=>!(!r||Vr(r)));return[...new Set(t)].sort((r,i)=>i.length-r.length)}function Q(e){if(typeof e!="string")return e;let t=zr();if(t.length===0)return e;let r=e;return t.forEach(i=>{let n=new RegExp(Xr(i),"g");r=r.replace(n,"******")}),r}function Xr(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var he=class extends Kr{constructor(t={}){super({...t,objectMode:!1})}_transform(t,r,i){let n=t.toString(),o=Q(n);i(null,o)}};function jt(){if(!(process.env.NETLIFY_MASK_LOGS!=="false"))return;let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr);process.stdout.write=function(i,n,o){let s=typeof i=="string"?Q(i):i;return typeof n=="function"?t(s,n):t(s,n,o)},process.stderr.write=function(i,n,o){let s=typeof i=="string"?Q(i):i;return typeof n=="function"?r(s,n):r(s,n,o)}}var be=null,Yt=e=>(be&&be.destroy(),be=new re({totalAllowedTime:e}),be),qt=()=>be;var re=class{constructor({totalAllowedTime:t}){this.withStageTimer=async(t,r,i)=>{if(this.isTimeExpired())throw new Error(`${t} stage did not complete in the allowed time. Time has already expired.`);let n=this.onTimesUp(()=>{throw new Error(`${t} stage did not complete in the allowed time.`)}),o=null,s=null;i!==void 0&&(s=new Promise((a,c)=>{o=setTimeout(()=>{c(new Error(`${t} stage exceeded its maximum duration of ${i}ms`))},i)}));try{return s?await Promise.race([r(),s]):await r()}finally{n(),o&&clearTimeout(o)}};this.startTime=Date.now(),this.totalAllowedTime=t,this.globalTimeoutId=null,this.subscribers=[],this.hasTimedOut=!1,this.setupGlobalTimeout()}getElapsedTime(){return Date.now()-this.startTime}getRemainingTime(){let t=this.getElapsedTime(),r=this.totalAllowedTime-t;return Math.max(0,r)}isTimeExpired(){return this.getRemainingTime()===0||this.hasTimedOut}setupGlobalTimeout(){this.globalTimeoutId&&clearTimeout(this.globalTimeoutId),this.globalTimeoutId=setTimeout(()=>{this.notifyTimeUp()},this.totalAllowedTime)}notifyTimeUp(){this.hasTimedOut=!0;for(let t=this.subscribers.length-1;t>=0;t--)try{this.subscribers[t]()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}}onTimesUp(t){if(this.subscribers.push(t),this.hasTimedOut)try{t()}catch(r){console.error("TimeKeeper: Error in time up callback:",r)}return()=>{let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}}off(t){let r=this.subscribers.indexOf(t);r>-1&&this.subscribers.splice(r,1)}clearSubscribers(){this.subscribers.length=0}getSubscriberCount(){return this.subscribers.length}destroy(){this.globalTimeoutId&&(clearTimeout(this.globalTimeoutId),this.globalTimeoutId=null),this.clearSubscribers()}static{this.timeUnits={seconds:t=>t*1e3,minutes:t=>t*60*1e3,hours:t=>t*60*60*1e3}}};var Bt="netlify-agent-runner-context.md",it="task-history",V=".netlify",ae="results.md",ot="assets";var Wt="free";var le=1800*1e3,w={Environment:"environment",UserMessage:"user-message",AgentMessage:"agent-message",Task:"task",RunCommand:"run-command",Explore:"explore",Plan:"plan",FileRead:"file-read",FileWrite:"file-write",Notebook:"notebook",Web:"web",Todo:"todo",Reasoning:"reasoning",Skill:"skill",Memorize:"memorize",Deployment:"deployment",SiteGeneration:"site-generation"};var Ht={name:"@netlify/agent-runner-cli",type:"module",version:"1.89.1",description:"CLI tool for running Netlify agents",main:"./dist/index.js",types:"./dist/index.d.ts",exports:"./dist/index.js",bin:{"agent-runner-cli":"./dist/bin.js","agent-runner-cli-local":"./dist/bin-local.js"},files:["dist/**/*.js","dist/**/*.d.ts","dist/skills/**","patches","scripts"],scripts:{build:"tsup",dev:"tsup --watch",prepare:"husky install node_modules/@netlify/eslint-config-node/.husky/",prepublishOnly:"npm ci && npm test",prepack:"npm run build",test:"run-s build format test:dev",format:"run-s build format:check-fix:*","format:ci":"run-s build format:check:*","format:check-fix:lint":"run-e format:check:lint format:fix:lint","format:check:lint":"cross-env-shell eslint $npm_package_config_eslint","format:fix:lint":"cross-env-shell eslint --fix $npm_package_config_eslint","format:check-fix:prettier":"run-e format:check:prettier format:fix:prettier","format:check:prettier":"cross-env-shell prettier --check $npm_package_config_prettier","format:fix:prettier":"cross-env-shell prettier --write $npm_package_config_prettier","test:dev":"run-s build test:dev:*","test:ci":"run-s build test:ci:*","test:dev:vitest":"LOG=0 vitest --exclude '**/integration/**'","test:ci:vitest":"LOG=0 c8 -r lcovonly -r text -r json vitest --exclude '**/integration/**'","test:integration":"vitest run test/integration/","test:integration:codex":"vitest run test/integration/codex.test.ts","test:integration:claude":"vitest run test/integration/claude.test.ts","test:integration:gemini":"vitest run test/integration/gemini.test.ts","test:integration:create-stage":"vitest run test/integration/create.test.ts","test:integration:skill-invocation":"vitest run test/integration/skill-invocation.test.ts","check:types":"tsc --noEmit",postinstall:"node scripts/postinstall.js"},config:{eslint:'--cache --format=codeframe --max-warnings=0 "{src,scripts,test,.github}/**/*.{js,ts,md,html}"',prettier:'--ignore-path .gitignore --loglevel=warn "{src,scripts,test,.github}/**/*.{js,ts,md,yml,json,html}" "*.{js,ts,yml,json,html}" ".*.{js,ts,yml,json,html}" "!**/package-lock.json" "!package-lock.json" "!src/skills/**/*.md"'},keywords:[],license:"MIT",repository:"netlify/agent-runner-cli",bugs:{url:"https://github.com/netlify/agent-runner-cli/issues"},author:"Netlify Inc.",directories:{test:"test"},devDependencies:{"@commitlint/cli":"^20.0.0","@commitlint/config-conventional":"^20.0.0","@eslint/compat":"^2.0.0","@eslint/js":"^9.35.0","@netlify/eslint-config-node":"^7.0.1","@types/node":"^24.5.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitest/eslint-plugin":"^1.6.6",c8:"^10.0.0","eslint-config-prettier":"^10.1.8","eslint-plugin-n":"^17.0.0",husky:"^9.0.0","patch-package":"^8.0.0",tsup:"^8.5.0",typescript:"^5.0.0","typescript-eslint":"^8.44.0",vitest:"^4.0.16"},dependencies:{"@anthropic-ai/claude-code":"2.1.63","@anthropic-ai/sdk":"0.78.0","@google/gemini-cli":"0.31.0","@netlify/otel":"^5.1.2","@openai/codex":"0.110.0","@opentelemetry/exporter-trace-otlp-grpc":"0.57.2",execa:"^9.6.1",kaddidlehopper:"^0.7.2",minimist:"^1.2.8",openai:"6.26.0"}};var rn=Qr(import.meta.url),nn=ee.dirname(rn),on=en(import.meta.url),ye=x("shell"),st=new Set,sn={preferLocal:!0},P=(e,t,r)=>{let[i,n]=an(t,r),o={...sn,...n},s=tn(e,i,o);ln(s,o),un(s);let a=r?.idleTimeout;return a&&a>0&&cn(s,a),s};var an=function(e,t){return Array.isArray(e)?[e,t]:typeof e=="object"&&e!==null?[[],e]:[[],void 0]},ln=(e,t)=>{if(t.stdio!==void 0||t.stdout!==void 0||t.stderr!==void 0)return;if(z.env.NETLIFY_MASK_LOGS!=="false"){e.all?.pipe(new he).pipe(z.stdout),e.stdout?.pipe(new he).pipe(z.stdout),e.stderr?.pipe(new he).pipe(z.stderr);return}e.stdout?.pipe(z.stdout),e.stderr?.pipe(z.stderr)},at=(e,t="SIGTERM")=>{try{return e.pid&&!e.killed?(z.kill(-e.pid,t),ye.log(`Killed process ${e.pid} with signal ${t}`),!0):!1}catch(r){return ye.error("Error killing process:",r),!1}},Kt=e=>at(e,"SIGKILL"),cn=(e,t)=>{let r=null,i=()=>{ye.log(`Process ${e.pid} killed due to idle timeout (no output for ${t}ms)`),at(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ye.log(`Force killing idle process ${e.pid}`),Kt(e))},5e3)},n=()=>{r&&clearTimeout(r),r=setTimeout(i,t)};n(),e.stdout?.on("data",n),e.stderr?.on("data",n);let o=()=>{r&&(clearTimeout(r),r=null)};e.on("exit",o),e.on("error",o)},un=e=>{st.add(e);let t=qt();if(t){let r=t.onTimesUp(()=>{ye.log(`Global timer expired, killing process ${e.pid}`),at(e,"SIGTERM"),setTimeout(()=>{e.pid&&!e.killed&&(ye.log(`Force killing process ${e.pid} after timeout`),Kt(e))},5e3)});e.on("exit",()=>{st.delete(e),r()}),e.on("error",()=>{st.delete(e),r()})}};function ne(e,t){if(!z.env.NETLIFY_LOCAL_MODE)try{let n=on.resolve(Ht.name),o=ee.dirname(n);for(;o!==ee.dirname(o);){let s=ee.dirname(o);if(ee.basename(s)==="node_modules"){let a=ee.join(s,".bin",t);if(Ue.existsSync(a))return a;break}o=s}}catch(n){console.error("Could not resolve package.json",n)}if(z.env.NODE_PATH){let n=ee.join(z.env.NODE_PATH,".bin",t);if(Ue.existsSync(n))return n}let r=ee.join(e,"node_modules",".bin",t);if(Ue.existsSync(r))return r;let i=ee.join(nn,"..","node_modules",".bin",t);if(Ue.existsSync(i))return i}var dn=x("utils"),pn=e=>new Promise(t=>{setTimeout(t,e)}),Ge=(e,t=3e3)=>{let r=!1,i=null,n=[],o=null,s=(...a)=>{if(r)return i=a,new Promise(d=>{n.push(d)});r=!0;let c,l=new Promise(d=>{c=d});return o=(async()=>{await Promise.resolve();let d=await e(...a);for(c(d);;){if(await pn(t),!i)return r=!1,o=null,d;let p=i,u=n;i=null,n=[],d=await e(...p),u.forEach(y=>{y(d)})}})(),l};return s.flush=async()=>{if((r||i)&&o)return await o,s.flush()},s},we=(e,t,r=!1)=>{let i=null,n=null,o=null,s=function(...a){n=a,o=this;let c=r&&!i;clearTimeout(i),i=setTimeout(()=>{i=null,r||(e.apply(o,n),n=null,o=null)},t),c&&(e.apply(o,n),n=null,o=null)};return s.cancel=()=>{clearTimeout(i),i=null,n=null,o=null},s.flush=()=>{if(i){clearTimeout(i);let a=n,c=o;i=null,n=null,o=null,e.apply(c,a)}},s},Jt=(e,t=!0,r)=>{if(e)try{return JSON.parse(e)}catch(i){t&&(r?.error?r.error("Could not parse JSON",i):dn.error("Could not parse JSON",i))}},lt=e=>e.charAt(0).toUpperCase()+e.slice(1),ie=e=>e.split("-").map(t=>t.length===2?t.toUpperCase():lt(t)).join(" ");function ce(e,t){t&&e.log(`Skill invoked: ${t}`)}var Vt=e=>Object.fromEntries(Object.entries(e).filter(([,t])=>t!==void 0)),zt=(e,t)=>{let n=".netlify.app",o="agent-";if(!t)return`${o}${e.slice(0,6)}`;let a=`--${t}${n}`;if(a.length>55)return"";let c=60-a.length;if(c<=0)return"";if(c>=o.length+6){let l=Math.min(c-o.length,e.length);return`${o}${e.slice(0,l)}`}return e.slice(0,c)};var mn=50*1024,ct=(e,t=mn)=>{if(!e||typeof e!="string"||e.length<=t)return e;let i=e.startsWith("```")?"\n... [truncated]\n```":"... [truncated]";return e.slice(0,t)+i};import{Buffer as Xt}from"buffer";import gn from"path";var Zt=x("repo"),er=async({config:e,isRetry:t,cwd:r=process.cwd()})=>{Zt.info("Getting runner diffs");let i=await hn(r),{hasChanges:n}=i,{status:o}=i;if(!n)return{hasChanges:!1};if(!t){let _=wn(o);await _n(_,r)}Zt.info("Changes after processing"),await dt(r);let s=await mt(o,r);if(await ut(s,r),n=await yn(r),!n)return{hasChanges:!1,ignored:s};process.env.NETLIFY_INTERNAL_GIT="1";try{await P("git",["commit","-m","Agent runner"],{cwd:r})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}let a={stdio:["ignore","pipe","pipe"],cwd:r},c=await P("git",["diff",e.runSha,"HEAD"],a),l=String(c.stdout??"");if(n=!!l,!n)return await Qt(r),{hasChanges:!1,ignored:s};let d=await P("git",["diff",e.runSha,"HEAD","--binary"],a),p=String(d.stdout??""),u,y;if(e.sha){let _=await P("git",["diff",e.sha,"HEAD"],a);u=String(_.stdout??"");let v=await P("git",["diff",e.sha,"HEAD","--binary"],a),h=String(v.stdout??"");u!==h&&(y=Xt.from(h).toString("base64"))}await Qt(r);let E={hasChanges:!0,diff:l,resultDiff:u,ignored:s};return l!==p&&(E.diffBinary=Xt.from(p).toString("base64")),y&&(E.resultDiffBinary=y),E},Qt=async(e=process.cwd())=>{process.env.NETLIFY_LOCAL_MODE&&await P("git",["reset","--soft","HEAD~1"],{cwd:e})},ut=async(e=[],t=process.cwd())=>{process.env.NETLIFY_INTERNAL_GIT="1";try{await P("git",["add",".",...e],{cwd:t})}finally{process.env.NETLIFY_INTERNAL_GIT="0"}},dt=async(e=process.cwd())=>{let t=await P("git",["status","-s"],{cwd:e});return String(t.stdout??"")},tr=/.. (.+)?\.log$/,fn=[tr],hn=async(e=process.cwd())=>{let t=await dt(e);return{hasChanges:(t.trim().length===0?[]:t.split(`
|
|
8
8
|
`).filter(n=>fn.some(s=>s instanceof RegExp?s.test(n):n===s)?!1:n[1]?.trim()!=="")).length!==0,status:t}},yn=async(e=process.cwd())=>{try{return await P("git",["diff","--staged","--quiet"],{cwd:e}),!1}catch{return!0}},pt=async(e=process.cwd())=>{let{stdout:t}=await P("git",["rev-parse","HEAD"],{cwd:e});return String(t??"").trim()},rr=async(e=process.cwd())=>{let{stdout:t}=await P("git",["rev-list","--max-parents=0","HEAD"],{cwd:e});return String(t??"").trim()},mt=async(e,t=process.cwd())=>{e||=await dt(t);let r=[".netlify","node_modules","dist",".next","out",".nuxt",".output",".cache",".turbo",".parcel-cache","coverage",".nyc_output","storybook-static","public/build","CLAUDE.local.md"],i=[];return e.split(`
|
|
9
9
|
`).forEach(n=>{r.forEach(s=>{let a=n===`?? ${s}`,c=n.startsWith(`?? ${s}/`)||n.startsWith(`?? ${s}${gn.sep}`);(a||c)&&i.push(`:!${s}`)});let o=n.match(tr)?.[1];o&&i.push(`:!${o}.log`)}),i},gt=async(e=process.cwd())=>{await P("git",["reset","--hard","HEAD"],{cwd:e})},wn=e=>{let t=e.split(`
|
|
10
10
|
`).reduce((r,i)=>{if(!i)return r;let[n,o,,...s]=i,a=s.join(""),c=n.trim(),l=o.trim();return r[a]?r[a].change=l:r[a]={filePath:a,stage:c,change:l},r},{});return Object.values(t)},_n=async(e,t=process.cwd())=>{let r=e.filter(i=>i.stage&&!i.change).map(i=>i.filePath);r.length!==0&&await P("git",["restore","--staged","--worktree","--pathspec-from-file=-"],{cwd:t,input:r.join(`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: netlify-identity
|
|
3
|
-
description:
|
|
3
|
+
description: Use whenever the task involves authentication, signups, logins, password recovery, user accounts, role-based access control, OAuth providers, protected routes, protecting Netlify Functions, server-side auth in SSR frameworks, or Netlify Identity. Always use `@netlify/identity`. Never use `netlify-identity-widget` or `gotrue-js` as they are deprecated.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Netlify Identity
|
|
@@ -8,15 +8,9 @@ description: Add user authentication with Netlify Identity. Use when implementin
|
|
|
8
8
|
Netlify Identity is a user management service for signups, logins, password recovery, user metadata, and role-based
|
|
9
9
|
access control. It is built on [GoTrue](https://github.com/netlify/gotrue) and issues JSON Web Tokens (JWTs).
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
API that works in both browser and server contexts (Netlify Functions, Edge Functions, SSR frameworks).
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- **`@netlify/identity`** — Default choice. Headless TypeScript API for browser and server. Full control over auth UI.
|
|
16
|
-
Works with SSR frameworks (Next.js, Remix, Astro, SvelteKit, TanStack Start).
|
|
17
|
-
- **`netlify-identity-widget`** — Browser-only drop-in UI modal for login, signup, and password recovery. Zero custom
|
|
18
|
-
code needed. **Does not work server-side** — no SSR, no Netlify Functions, no Edge Functions. Use only when the user
|
|
19
|
-
wants a pre-built auth modal with no custom design.
|
|
11
|
+
**NEVER use `netlify-identity-widget` or `gotrue-js`. Always use `@netlify/identity`.** It provides a unified, headless
|
|
12
|
+
TypeScript API that works in both browser and server contexts (Netlify Functions, Edge Functions, SSR frameworks). It
|
|
13
|
+
replaces all previous Identity client libraries.
|
|
20
14
|
|
|
21
15
|
## Setup
|
|
22
16
|
|
|
@@ -26,8 +20,8 @@ npm install @netlify/identity
|
|
|
26
20
|
|
|
27
21
|
Identity is automatically enabled when the deploy includes Identity code. Default settings:
|
|
28
22
|
|
|
29
|
-
- **Registration**
|
|
30
|
-
- **Autoconfirm**
|
|
23
|
+
- **Registration** - Open (anyone can sign up). Change to Invite only in **Project configuration > Identity** if needed.
|
|
24
|
+
- **Autoconfirm** - Off (new signups require email confirmation). Enable in **Project configuration > Identity** to skip
|
|
31
25
|
confirmation during development.
|
|
32
26
|
|
|
33
27
|
For local development, use `netlify dev` so the Identity endpoint is available.
|
|
@@ -60,13 +54,321 @@ export default async (req: Request, context: Context) => {
|
|
|
60
54
|
}
|
|
61
55
|
```
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
## Error Handling
|
|
58
|
+
|
|
59
|
+
`@netlify/identity` throws two error classes:
|
|
60
|
+
|
|
61
|
+
- **`AuthError`** - Thrown by auth operations (login, signup, logout, etc.). Has `message`, optional `status` (HTTP
|
|
62
|
+
status code from GoTrue), and optional `cause` (original error).
|
|
63
|
+
- **`MissingIdentityError`** - Thrown when Identity is not configured in the current environment (site doesn't have
|
|
64
|
+
Identity enabled, or not running via `netlify dev`).
|
|
65
|
+
|
|
66
|
+
`getUser()` and `isAuthenticated()` never throw - they return `null` and `false` respectively on failure.
|
|
67
|
+
|
|
68
|
+
### Try/Catch Pattern
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { login, AuthError, MissingIdentityError } from '@netlify/identity'
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const user = await login(email, password)
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error instanceof MissingIdentityError) {
|
|
77
|
+
// Identity not configured - show setup instructions
|
|
78
|
+
showError('Identity is not enabled on this site.')
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
if (error instanceof AuthError) {
|
|
82
|
+
switch (error.status) {
|
|
83
|
+
case 401:
|
|
84
|
+
showError('Invalid email or password.')
|
|
85
|
+
break
|
|
86
|
+
case 403:
|
|
87
|
+
showError('Signups are not allowed for this site.')
|
|
88
|
+
break
|
|
89
|
+
case 422:
|
|
90
|
+
showError('Invalid input. Check your email and password.')
|
|
91
|
+
break
|
|
92
|
+
case 404:
|
|
93
|
+
showError('User not found.')
|
|
94
|
+
break
|
|
95
|
+
default:
|
|
96
|
+
showError(error.message)
|
|
97
|
+
}
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
throw error
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Common Status Codes
|
|
105
|
+
|
|
106
|
+
| Status | Meaning |
|
|
107
|
+
|--------|---------|
|
|
108
|
+
| 401 | Invalid credentials or expired token |
|
|
109
|
+
| 403 | Action not allowed (e.g., signups disabled) |
|
|
110
|
+
| 422 | Validation error (e.g., weak password, malformed email) |
|
|
111
|
+
| 404 | User or resource not found |
|
|
112
|
+
|
|
113
|
+
## Authentication Flows
|
|
114
|
+
|
|
115
|
+
### Login
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { login, AuthError } from '@netlify/identity'
|
|
119
|
+
|
|
120
|
+
let loading = false
|
|
121
|
+
|
|
122
|
+
async function handleLogin(email: string, password: string) {
|
|
123
|
+
loading = true
|
|
124
|
+
try {
|
|
125
|
+
const user = await login(email, password)
|
|
126
|
+
showSuccess(`Welcome back, ${user.name ?? user.email}`)
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof AuthError) {
|
|
129
|
+
showError(error.status === 401 ? 'Invalid email or password.' : error.message)
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
loading = false
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Signup
|
|
138
|
+
|
|
139
|
+
After signup, check `user.emailVerified` to determine if the user was auto-confirmed (logged in immediately) or needs
|
|
140
|
+
to confirm their email first.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { signup, AuthError } from '@netlify/identity'
|
|
144
|
+
|
|
145
|
+
async function handleSignup(email: string, password: string, name: string) {
|
|
146
|
+
try {
|
|
147
|
+
const user = await signup(email, password, { full_name: name })
|
|
148
|
+
if (user.emailVerified) {
|
|
149
|
+
// Autoconfirm is ON - user is logged in
|
|
150
|
+
showSuccess('Account created. You are now logged in.')
|
|
151
|
+
} else {
|
|
152
|
+
// Autoconfirm is OFF - confirmation email sent, user is NOT logged in
|
|
153
|
+
showSuccess('Check your email to confirm your account.')
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error instanceof AuthError) {
|
|
157
|
+
showError(error.status === 403 ? 'Signups are not allowed.' : error.message)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
When autoconfirm is off, the confirmation email contains a link that redirects the user back to the site with
|
|
164
|
+
`#confirmation_token=<token>` in the URL hash. `handleAuthCallback()` processes this automatically - it calls
|
|
165
|
+
`confirmEmail()` under the hood, logs the user in, and returns `{ type: 'confirmation', user }`. This is why
|
|
166
|
+
`handleAuthCallback()` must be called on page load (see the OAuth section below for the full switch).
|
|
167
|
+
|
|
168
|
+
### OAuth
|
|
169
|
+
|
|
170
|
+
OAuth is a two-step flow: `oauthLogin(provider)` redirects away from the site, then `handleAuthCallback()` processes
|
|
171
|
+
the redirect when the user returns.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { oauthLogin } from '@netlify/identity'
|
|
175
|
+
|
|
176
|
+
// Step 1: Redirect to OAuth provider (this navigates away - never returns)
|
|
177
|
+
function handleOAuthClick(provider: 'google' | 'github' | 'gitlab' | 'bitbucket') {
|
|
178
|
+
oauthLogin(provider)
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { handleAuthCallback, AuthError } from '@netlify/identity'
|
|
184
|
+
|
|
185
|
+
// Step 2: Process the redirect on page load
|
|
186
|
+
async function processCallback() {
|
|
187
|
+
try {
|
|
188
|
+
const result = await handleAuthCallback()
|
|
189
|
+
if (!result) return // No callback hash present - normal page load
|
|
190
|
+
|
|
191
|
+
switch (result.type) {
|
|
192
|
+
case 'oauth':
|
|
193
|
+
showSuccess(`Logged in as ${result.user?.email}`)
|
|
194
|
+
break
|
|
195
|
+
case 'confirmation':
|
|
196
|
+
showSuccess('Email confirmed. You are now logged in.')
|
|
197
|
+
break
|
|
198
|
+
case 'recovery':
|
|
199
|
+
// User is authenticated but must set a new password
|
|
200
|
+
showPasswordResetForm(result.user)
|
|
201
|
+
break
|
|
202
|
+
case 'invite':
|
|
203
|
+
// User must set a password to accept the invite
|
|
204
|
+
showInviteAcceptForm(result.token)
|
|
205
|
+
break
|
|
206
|
+
case 'email_change':
|
|
207
|
+
showSuccess('Email address updated.')
|
|
208
|
+
break
|
|
209
|
+
}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (error instanceof AuthError) {
|
|
212
|
+
showError(error.message)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Always call `handleAuthCallback()` on page load in any app that uses OAuth, password recovery, invites, or email
|
|
219
|
+
confirmation. It handles all callback types via the URL hash.
|
|
220
|
+
|
|
221
|
+
### Password Recovery
|
|
222
|
+
|
|
223
|
+
Three-step flow: request recovery email, handle the callback, then set a new password.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { requestPasswordRecovery, handleAuthCallback, updateUser, AuthError } from '@netlify/identity'
|
|
227
|
+
|
|
228
|
+
// Step 1: Send recovery email
|
|
229
|
+
async function handleForgotPassword(email: string) {
|
|
230
|
+
try {
|
|
231
|
+
await requestPasswordRecovery(email)
|
|
232
|
+
showSuccess('Check your email for a password reset link.')
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error instanceof AuthError) showError(error.message)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Step 2: handleAuthCallback() returns { type: 'recovery', user } - show password reset form
|
|
239
|
+
// (See the handleAuthCallback switch above)
|
|
240
|
+
|
|
241
|
+
// Step 3: Set new password
|
|
242
|
+
async function handlePasswordReset(newPassword: string) {
|
|
243
|
+
try {
|
|
244
|
+
await updateUser({ password: newPassword })
|
|
245
|
+
showSuccess('Password updated.')
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (error instanceof AuthError) showError(error.message)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Note: The recovery callback fires a `'recovery'` auth event, not `'login'`. The user is authenticated but should be
|
|
253
|
+
prompted to set a new password before navigating away.
|
|
254
|
+
|
|
255
|
+
### Invite Acceptance
|
|
256
|
+
|
|
257
|
+
When a user clicks an invite link, `handleAuthCallback()` returns `{ type: 'invite', user: null, token }`. Use the
|
|
258
|
+
token to accept the invite and set a password.
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { acceptInvite, AuthError } from '@netlify/identity'
|
|
262
|
+
|
|
263
|
+
async function handleAcceptInvite(token: string, password: string) {
|
|
264
|
+
try {
|
|
265
|
+
const user = await acceptInvite(token, password)
|
|
266
|
+
showSuccess(`Welcome, ${user.email}! Your account is ready.`)
|
|
267
|
+
} catch (error) {
|
|
268
|
+
if (error instanceof AuthError) showError(error.message)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Email Change
|
|
274
|
+
|
|
275
|
+
When a user verifies an email change, `handleAuthCallback()` returns `{ type: 'email_change', user }`. This requires an
|
|
276
|
+
active browser session - the user must be logged in when clicking the verification link.
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { verifyEmailChange, AuthError } from '@netlify/identity'
|
|
280
|
+
|
|
281
|
+
// If you need to verify programmatically with a token:
|
|
282
|
+
async function handleEmailChangeVerification(token: string) {
|
|
283
|
+
try {
|
|
284
|
+
const user = await verifyEmailChange(token)
|
|
285
|
+
showSuccess(`Email updated to ${user.email}`)
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (error instanceof AuthError) showError(error.message)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Session Hydration
|
|
293
|
+
|
|
294
|
+
`hydrateSession()` bridges server-set cookies to the browser session. Call it on page load when using server-side login
|
|
295
|
+
(e.g., login inside a Netlify Function followed by a redirect).
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { hydrateSession } from '@netlify/identity'
|
|
299
|
+
|
|
300
|
+
// On page load
|
|
301
|
+
const user = await hydrateSession()
|
|
302
|
+
if (user) {
|
|
303
|
+
// Browser session is now in sync with server-set cookies
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Note: `getUser()` auto-hydrates from the `nf_jwt` cookie if no browser session exists, so explicit `hydrateSession()`
|
|
308
|
+
is only needed when you want to restore the full session (including token refresh timers) after a server-side login.
|
|
309
|
+
|
|
310
|
+
## Auth Events
|
|
311
|
+
|
|
312
|
+
Subscribe to auth state changes with `onAuthChange`. Returns an unsubscribe function. No-op on server.
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { onAuthChange, AUTH_EVENTS } from '@netlify/identity'
|
|
316
|
+
|
|
317
|
+
const unsubscribe = onAuthChange((event, user) => {
|
|
318
|
+
switch (event) {
|
|
319
|
+
case AUTH_EVENTS.LOGIN:
|
|
320
|
+
console.log('User logged in:', user?.email)
|
|
321
|
+
break
|
|
322
|
+
case AUTH_EVENTS.LOGOUT:
|
|
323
|
+
console.log('User logged out')
|
|
324
|
+
break
|
|
325
|
+
case AUTH_EVENTS.TOKEN_REFRESH:
|
|
326
|
+
// Token auto-refreshed in background
|
|
327
|
+
break
|
|
328
|
+
case AUTH_EVENTS.USER_UPDATED:
|
|
329
|
+
console.log('User profile updated:', user?.email)
|
|
330
|
+
break
|
|
331
|
+
case AUTH_EVENTS.RECOVERY:
|
|
332
|
+
// Recovery token processed - prompt for new password
|
|
333
|
+
console.log('Password recovery initiated')
|
|
334
|
+
break
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
// Later: unsubscribe()
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Auth events are automatically detected across browser tabs via the storage event listener - no extra setup needed.
|
|
342
|
+
|
|
343
|
+
## Settings-Driven UI
|
|
344
|
+
|
|
345
|
+
Fetch the project's Identity settings to conditionally render signup forms and OAuth buttons.
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { getSettings } from '@netlify/identity'
|
|
349
|
+
|
|
350
|
+
const settings = await getSettings()
|
|
351
|
+
// settings.autoconfirm - boolean, whether email confirmation is skipped
|
|
352
|
+
// settings.disableSignup - boolean, whether registration is closed
|
|
353
|
+
// settings.providers - Record<AuthProvider, boolean>, e.g. { google: true, github: true, ... }
|
|
354
|
+
|
|
355
|
+
// Conditionally render signup
|
|
356
|
+
if (!settings.disableSignup) {
|
|
357
|
+
showSignupForm()
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Conditionally render OAuth buttons
|
|
361
|
+
for (const [provider, enabled] of Object.entries(settings.providers)) {
|
|
362
|
+
if (enabled) {
|
|
363
|
+
showOAuthButton(provider)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
```
|
|
65
367
|
|
|
66
368
|
## Full API Reference
|
|
67
369
|
|
|
68
|
-
For the complete API reference
|
|
69
|
-
management, auth events, and framework-specific integration examples
|
|
370
|
+
For the complete API reference - all function signatures, type definitions, OAuth helpers, admin operations, session
|
|
371
|
+
management, auth events, and framework-specific integration examples - read the package README:
|
|
70
372
|
|
|
71
373
|
```
|
|
72
374
|
node_modules/@netlify/identity/README.md
|
|
@@ -88,7 +390,7 @@ The library also supports server-side mutations (`login()`, `signup()`, `logout(
|
|
|
88
390
|
require the Netlify Functions runtime to set cookies. After a server-side mutation, use a full page navigation so the
|
|
89
391
|
browser sends the new cookie.
|
|
90
392
|
|
|
91
|
-
|
|
393
|
+
Always use `window.location.href` (not framework router navigation) after server-side auth mutations in Next.js,
|
|
92
394
|
TanStack Start, and SvelteKit. Remix `redirect()` is safe because Remix actions return real HTTP responses.
|
|
93
395
|
|
|
94
396
|
## Identity Event Functions
|
|
@@ -96,14 +398,14 @@ TanStack Start, and SvelteKit. Remix `redirect()` is safe because Remix actions
|
|
|
96
398
|
Special serverless functions that trigger on Identity lifecycle events. These use the **legacy named `handler` export**
|
|
97
399
|
(not the modern default export) because they receive `event.body` containing the user payload.
|
|
98
400
|
|
|
99
|
-
|
|
401
|
+
Always use the legacy named `handler` export (not default export) for Identity event functions. The filename must match
|
|
100
402
|
the event name exactly (e.g., `netlify/functions/identity-signup.mts`).
|
|
101
403
|
|
|
102
404
|
**Event names:** `identity-validate`, `identity-signup`, `identity-login`
|
|
103
405
|
|
|
104
|
-
- `identity-signup`
|
|
105
|
-
- `identity-login`
|
|
106
|
-
- `identity-validate`
|
|
406
|
+
- `identity-signup` - fires when a new user signs up (email/password or OAuth)
|
|
407
|
+
- `identity-login` - fires on each login
|
|
408
|
+
- `identity-validate` - fires during signup before the user is created; return a non-200 status to reject
|
|
107
409
|
|
|
108
410
|
### Example: Assign Default Role on Signup
|
|
109
411
|
|
|
@@ -128,7 +430,7 @@ const handler: Handler = async (event: HandlerEvent, context: HandlerContext) =>
|
|
|
128
430
|
export { handler }
|
|
129
431
|
```
|
|
130
432
|
|
|
131
|
-
The response body replaces `app_metadata` and/or `user_metadata` on the user record
|
|
433
|
+
The response body replaces `app_metadata` and/or `user_metadata` on the user record - include all fields you want to
|
|
132
434
|
keep, not just new ones.
|
|
133
435
|
|
|
134
436
|
For bulk user management or role changes outside lifecycle events, use the `admin` API instead of Identity event
|
|
@@ -138,9 +440,9 @@ functions.
|
|
|
138
440
|
|
|
139
441
|
### Metadata Types
|
|
140
442
|
|
|
141
|
-
- **`app_metadata.roles`**
|
|
443
|
+
- **`app_metadata.roles`** - Server-controlled. Only settable via the Netlify UI, admin API, or Identity event functions.
|
|
142
444
|
Do not allow users to set their own roles.
|
|
143
|
-
- **`user_metadata`**
|
|
445
|
+
- **`user_metadata`** - User-controlled. Users can update this via `updateUser({ data: { ... } })`.
|
|
144
446
|
|
|
145
447
|
### Role-Based Redirects
|
|
146
448
|
|
|
@@ -176,16 +478,48 @@ cookie, role-based redirects will not work.
|
|
|
176
478
|
1. Change to Open in **Project configuration > Identity**
|
|
177
479
|
2. Or invite users from the Identity tab in the Netlify UI
|
|
178
480
|
|
|
481
|
+
```typescript
|
|
482
|
+
if (error instanceof AuthError && error.status === 403) {
|
|
483
|
+
showError('Signups are disabled. Contact the site admin for an invite.')
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Invalid credentials (401)
|
|
488
|
+
|
|
489
|
+
**Cause:** Wrong email or password on login.
|
|
490
|
+
|
|
491
|
+
**Fix:** Show a user-facing error and let the user retry. Do not reveal whether the email or password was wrong.
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
if (error instanceof AuthError && error.status === 401) {
|
|
495
|
+
showError('Invalid email or password.')
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### "Email not confirmed"
|
|
500
|
+
|
|
501
|
+
**Cause:** User tries to log in before confirming their email (autoconfirm is off).
|
|
502
|
+
|
|
503
|
+
**Fix:** Tell the user to check their inbox. Optionally provide a way to resend the confirmation email via `signup()`
|
|
504
|
+
with the same credentials.
|
|
505
|
+
|
|
179
506
|
### "Token expired" / 401 on API calls
|
|
180
507
|
|
|
181
508
|
**Cause:** Stale access token.
|
|
182
509
|
|
|
183
510
|
**Fix:**
|
|
184
511
|
|
|
185
|
-
1. Always use `getUser()` before authenticated requests
|
|
186
|
-
2. In the browser, the library auto-refreshes tokens
|
|
512
|
+
1. Always use `getUser()` before authenticated requests - it auto-hydrates from cookies
|
|
513
|
+
2. In the browser, the library auto-refreshes tokens via `startTokenRefresh()`
|
|
187
514
|
3. On the server, call `refreshSession()` in middleware to handle near-expiry tokens
|
|
188
515
|
|
|
516
|
+
```typescript
|
|
517
|
+
const newToken = await refreshSession()
|
|
518
|
+
if (newToken) {
|
|
519
|
+
// Token was refreshed - retry the request
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
189
523
|
### Identity event function not triggering
|
|
190
524
|
|
|
191
525
|
**Cause:** Filename or export format does not match expected convention.
|
|
@@ -205,7 +539,13 @@ cookie, role-based redirects will not work.
|
|
|
205
539
|
1. Ensure Identity is enabled on the project
|
|
206
540
|
2. Use `netlify dev` for local development so the Identity endpoint is available
|
|
207
541
|
|
|
208
|
-
|
|
542
|
+
```typescript
|
|
543
|
+
if (error instanceof MissingIdentityError) {
|
|
544
|
+
showError('Identity is not enabled. Run "netlify dev" or enable Identity in project settings.')
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### `AuthError` on server - missing Netlify runtime
|
|
209
549
|
|
|
210
550
|
**Cause:** Server-side `login()`, `signup()`, or `logout()` require the Netlify Functions runtime to set cookies.
|
|
211
551
|
|
|
@@ -227,7 +567,46 @@ cookie, role-based redirects will not work.
|
|
|
227
567
|
|
|
228
568
|
**Cause:** Browser-side session is not bootstrapped from server-set cookies.
|
|
229
569
|
|
|
230
|
-
**Fix:**
|
|
570
|
+
**Fix:** Call `hydrateSession()` on page load to bridge server-set cookies to the browser session. Then use
|
|
571
|
+
`updateUser()`, `verifyEmailChange()`, or other account operations.
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
import { hydrateSession, updateUser } from '@netlify/identity'
|
|
575
|
+
|
|
576
|
+
// On page load after server-side login
|
|
577
|
+
await hydrateSession()
|
|
578
|
+
|
|
579
|
+
// Now account operations work
|
|
580
|
+
await updateUser({ data: { full_name: 'Jane' } })
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### "Email change verification requires an active browser session"
|
|
584
|
+
|
|
585
|
+
**Cause:** `verifyEmailChange()` was called without an active session. The user must be logged in when clicking the
|
|
586
|
+
email change verification link.
|
|
587
|
+
|
|
588
|
+
**Fix:** Ensure the user is logged in before processing the `email_change` callback. If the session expired, prompt
|
|
589
|
+
the user to log in first.
|
|
590
|
+
|
|
591
|
+
### "No user is currently logged in"
|
|
592
|
+
|
|
593
|
+
**Cause:** An account operation (`updateUser`, `verifyEmailChange`) was called without an authenticated user.
|
|
594
|
+
|
|
595
|
+
**Fix:** Check `getUser()` before calling account operations. If `null`, redirect to login.
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
const user = await getUser()
|
|
599
|
+
if (!user) {
|
|
600
|
+
redirectToLogin()
|
|
601
|
+
return
|
|
602
|
+
}
|
|
603
|
+
await updateUser({ data: { full_name: 'Jane' } })
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Stale session - user deleted server-side
|
|
607
|
+
|
|
608
|
+
**Cause:** `getUser()` returns `null` when the `nf_jwt` cookie is gone but localStorage still has a stale
|
|
609
|
+
session. This happens when a user is deleted via the admin API or Netlify UI.
|
|
231
610
|
|
|
232
|
-
|
|
233
|
-
|
|
611
|
+
**Fix:** `getUser()` handles this gracefully - it returns `null` and the stale localStorage entry is ignored. Always
|
|
612
|
+
check for `null` before using the user object.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/agent-runner-cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.89.1",
|
|
5
5
|
"description": "CLI tool for running Netlify agents",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"@google/gemini-cli": "0.31.0",
|
|
86
86
|
"@netlify/otel": "^5.1.2",
|
|
87
87
|
"@openai/codex": "0.110.0",
|
|
88
|
-
"@opentelemetry/exporter-trace-otlp-grpc": "
|
|
88
|
+
"@opentelemetry/exporter-trace-otlp-grpc": "0.57.2",
|
|
89
89
|
"execa": "^9.6.1",
|
|
90
90
|
"kaddidlehopper": "^0.7.2",
|
|
91
91
|
"minimist": "^1.2.8",
|