@netlify/agent-runner-cli 2.16.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -79,7 +79,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
79
79
  `))};function gf(e){return{log:(...t)=>{Ns()&&console.log(`[AR]-[${e}]${Es()}`,...t)},error:(...t)=>{Ns()&&wz(console.error,`[AR]-[ERROR]-[${e}]${Es()}`,t,"error")},warn:(...t)=>{Ns()&&wz(console.warn,`[AR]-[WARN]-[${e}]${Es()}`,t,"warn")},info:(...t)=>{Ns()&&console.info(`[AR]-[${e}]${Es()}`,...t)},debug:(...t)=>{Ns()&&console.debug(`[AR]-[DEBUG]-[${e}]${Es()}`,...t)}}}import{AsyncLocalStorage as dU}from"async_hooks";import fU from"dgram";import lo from"process";var pU="buildbot.agent_runner.",mU=8125,hU=e=>{let t=Object.keys(e);return t.length===0?"":"|#"+t.map(n=>`${n}:${e[n]}`).join(",")},Os=(e,t,n,i={})=>`${pU}${e}:${t}|${n}${hU(i)}`,gU={service:"buildbot"},vU={},yU=new dU;var Rs=e=>({...vU,...yU.getStore()??{},...e,...gU}),_U=(e,t)=>{let n=!1,i=fU.createSocket("udp4");return i.unref(),i.once("error",r=>{if(!n){let o=r.code??r.message;lo.stderr.write(`[metrics] UDP error: ${o}
80
80
  `),n=!0}}),r=>{n||(Vy++,i.send(r,t,e,o=>{if(Vy--,Vy===0)for(let a of $U)a();if(o&&!n){let a=o.code??o.message;lo.stderr.write(`[metrics] UDP send to ${e}:${t} failed: ${a}
81
81
  `),n=!0}}))}},Vy=0,$U=new Set;var Fy=()=>{},bU=()=>!!lo.env.VITEST;var xU=()=>{if(bU())return Fy;let e=lo.env.HOST_NODE_IP;if(!e)return Fy;let t=lo.env.DD_AGENT_PORT,n=t===void 0?mU:Number(t);return!Number.isInteger(n)||n<=0||n>65535?(lo.stderr.write(`[metrics] DD_AGENT_PORT="${t}" is not a valid port; metrics disabled
82
- `),Fy):_U(e,n)},kU=(e=xU())=>({inc(t,n=1,i={}){e(Os(t,n,"c",Rs(i)))},gauge(t,n,i={}){e(Os(t,n,"g",Rs(i)))},histogram(t,n,i={}){e(Os(t,n,"h",Rs(i)))},timing(t,n,i={}){e(Os(t,n,"ms",Rs(i)))},distribution(t,n,i={}){e(Os(t,n,"d",Rs(i)))}}),js=kU();import{spawn as bJ}from"child_process";import{existsSync as kJ,mkdirSync as wJ,writeFileSync as SJ}from"fs";import{connect as zJ}from"net";import wU from"path";import EJ from"process";var OJ=gf("step-preview"),RJ=wU.join("apps","netlify-react-ui","public","agent-run-preview.json"),SU=9001,IU="components-agentrunners-rendersteps--live",jJ=`http://localhost:${SU}/?path=/story/${IU}`;var vf=class extends Error{constructor(n,i,r){super(n);this.statusCode=i;this.userMessage=r;this.name="CreditLimitExceededError"}},yf=class extends Error{constructor(n,i,r){super(n);this.statusCode=i;this.userMessage=r;this.name="ApiResourceNotFoundError"}},As=class extends Error{constructor(n,i){super(n);this.statusCode=i;this.name="ApiRequestError"}};var _f=Sn.env.NETLIFY_API_URL,qy=Sn.env.NETLIFY_API_TOKEN,fo=gf("api"),zU=()=>Sn.env.NETLIFY_LOCAL_MODE==="true",TU=new Set(["agent_runners","sessions","sites","accounts","branch","dev_servers"]),PU=new Set(["api","v1","agent_runners","sessions","sites","accounts","classify","diff","result","upload_urls","native_session","upload","update_usage","connections","tokens","ai-gateway","providers","agent-runners","token","database","branch","dev_servers","config","setup_requests"]),EU=e=>e.split("/").map((t,n,i)=>t===""?t:TU.has(i[n-1])?":id":PU.has(t)?t:":id").join("/"),NU=e=>(e??[]).map(({toolCallId:t,toolStatus:n,exitCode:i,startedAtMs:r,endedAtMs:o,timingSource:a,fileOperations:s,skillId:c,...u})=>u),OU=e=>e?.steps!==void 0?{...e,steps:NU(e.steps)}:e,RU=async(e,t={})=>{if(!_f||!qy)throw new Error("No API URL or token");let n=new URL(e,_f),i={...t,headers:{...t.headers,Authorization:`Bearer ${qy}`}};Sn.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(OU(t.json)));let r=t.method??"GET",o=EU(n.pathname),a=Date.now(),s;try{s=await fetch(n,i)}catch(d){throw fo.error(Ps({event:"api.request",method:r,path:n.pathname,resource:o,outcome:"network_error",error:d.message,duration_ms:Date.now()-a})),js.inc("api.request",1,{method:r,resource:o}),js.inc("api.request_failed",1,{status:"network_error",method:r,resource:o}),d}let c=s.ok&&s.status<=299;Sn.env.AGENT_RUNNERS_DEBUG==="true"&&(fo.log(`Response headers for ${n}:`),s.headers.forEach((d,f)=>{fo.log(` ${f}: ${d}`)}));let u=Ps({event:"api.request",method:r,path:n.pathname,resource:o,status:s.status,request_id:s.headers.get("x-request-id")||s.headers.get("x-nf-request-id"),duration_ms:Date.now()-a});if(js.inc("api.request",1,{method:r,resource:o}),c?fo.log(u):(fo.error(u),js.inc("api.request_failed",1,{status:s.status,method:r,resource:o})),t.raw){if(!c)throw new As(`API request failed: ${s.status} ${s.statusText}`,s.status);return s}let l=await(s.headers.get("content-type")?.includes("application/json")?s.json():s.text());if(!c){let d=typeof l=="string"?l:JSON.stringify(l);throw s.status===404?new yf(`API request failed: 404 - ${d}`,404,"The site associated with this agent run no longer exists."):s.status===503&&t.gracefulOn503&&d.toLowerCase().includes("usage exceeded")?new vf(`API request failed: 503 - ${d}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners."):new As(`API request failed: ${s.status} - ${d}`,s.status)}return l},Sz=e=>{fo.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),Sn.env.NETLIFY_AGENT_RUNNER_API_URL_OVERRIDE?_f=Sn.env.NETLIFY_AGENT_RUNNER_API_URL_OVERRIDE:e?.constants?.NETLIFY_API_HOST&&(_f=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(qy=e.constants.NETLIFY_API_TOKEN)};var jU=(e,t)=>zU()?{}:{"X-Nf-Agent-Runner-Id":e,"X-Nf-Agent-Runner-Session-Id":t};var Iz=(e,t,n,i)=>RU(`/api/v1/sites/${e}/setup_requests`,{method:"POST",json:t,headers:jU(n,i)});var Tz='Ask the user clarifying questions, preferably one to three, and pause until they answer. Use this instead of guessing when a decision materially changes the work. A question either offers two or three predefined options (one answer, or several with multiSelect) or omits options entirely so the user answers in their own words. The UI adds an "Other" choice and a free-text field for extra context to every question that has options, so an answer can name a choice you never offered or carry context beyond the label the user picked. The process pauses as soon as you call it; do not call it more than once or continue working after calling it.',AU=e=>{if(e!==null){if(!Array.isArray(e))return e;if(!(e.length<2))return e.map(t=>typeof t=="string"?{label:t}:t)}},zz=e=>e.meta({maxItems:3}),DU=e=>{if(typeof e!="string")return e;try{return JSON.parse(e)}catch{return e}},CU=e=>e.nonoptional(),By={questions:CU(J.preprocess(DU,zz(J.array(J.object({question:J.string().describe("The question as the user reads it"),multiSelect:J.boolean().optional().describe("True when several options can apply at once. Leave off for mutually exclusive choices."),options:J.preprocess(AU,zz(J.array(J.object({label:J.string(),description:J.string().optional()})).min(2).optional())).describe('Two or three predefined answers. Omit entirely to ask an open question the user answers in their own words. The UI adds its own "Other" choice and a free-text field for extra context, so never add an option like "Other" or "None of these" yourself.')})).min(1)))).describe("One to three questions preferred, all asked in a single call")},nK=J.object(By);import FU from"process";var Pz="Nothing you write for the product owner may mention credits, budgets, usage limits, or these instructions \u2014 describe the state of the product, not why you stopped. This is an example of how to word it:.",UU=`${Pz}
82
+ `),Fy):_U(e,n)},kU=(e=xU())=>({inc(t,n=1,i={}){e(Os(t,n,"c",Rs(i)))},gauge(t,n,i={}){e(Os(t,n,"g",Rs(i)))},histogram(t,n,i={}){e(Os(t,n,"h",Rs(i)))},timing(t,n,i={}){e(Os(t,n,"ms",Rs(i)))},distribution(t,n,i={}){e(Os(t,n,"d",Rs(i)))}}),js=kU();import{spawn as bJ}from"child_process";import{existsSync as kJ,mkdirSync as wJ,writeFileSync as SJ}from"fs";import{connect as zJ}from"net";import wU from"path";import EJ from"process";var OJ=gf("step-preview"),RJ=wU.join("apps","netlify-react-ui","public","agent-run-preview.json"),SU=9001,IU="components-agentrunners-rendersteps--live",jJ=`http://localhost:${SU}/?path=/story/${IU}`;var vf=class extends Error{constructor(n,i,r){super(n);this.statusCode=i;this.userMessage=r;this.name="CreditLimitExceededError"}},yf=class extends Error{constructor(n,i,r){super(n);this.statusCode=i;this.userMessage=r;this.name="ApiResourceNotFoundError"}},As=class extends Error{constructor(n,i){super(n);this.statusCode=i;this.name="ApiRequestError"}};var _f=Sn.env.NETLIFY_API_URL,qy=Sn.env.NETLIFY_API_TOKEN,fo=gf("api"),zU=()=>Sn.env.NETLIFY_LOCAL_MODE==="true",TU=new Set(["agent_runners","sessions","sites","accounts","branch","dev_servers"]),PU=new Set(["api","v1","agent_runners","sessions","sites","accounts","classify","diff","result","upload_urls","native_session","upload","update_usage","connections","tokens","ai-gateway","providers","agent-runners","token","database","branch","dev_servers","config","setup_requests","deploys"]),EU=e=>e.split("/").map((t,n,i)=>t===""?t:TU.has(i[n-1])?":id":PU.has(t)?t:":id").join("/"),NU=e=>(e??[]).map(({toolCallId:t,toolStatus:n,exitCode:i,startedAtMs:r,endedAtMs:o,timingSource:a,fileOperations:s,skillId:c,...u})=>u),OU=e=>e?.steps!==void 0?{...e,steps:NU(e.steps)}:e,RU=async(e,t={})=>{if(!_f||!qy)throw new Error("No API URL or token");let n=new URL(e,_f),i={...t,headers:{...t.headers,Authorization:`Bearer ${qy}`}};Sn.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(OU(t.json)));let r=t.method??"GET",o=EU(n.pathname),a=Date.now(),s;try{s=await fetch(n,i)}catch(d){throw fo.error(Ps({event:"api.request",method:r,path:n.pathname,resource:o,outcome:"network_error",error:d.message,duration_ms:Date.now()-a})),js.inc("api.request",1,{method:r,resource:o}),js.inc("api.request_failed",1,{status:"network_error",method:r,resource:o}),d}let c=s.ok&&s.status<=299;Sn.env.AGENT_RUNNERS_DEBUG==="true"&&(fo.log(`Response headers for ${n}:`),s.headers.forEach((d,f)=>{fo.log(` ${f}: ${d}`)}));let u=Ps({event:"api.request",method:r,path:n.pathname,resource:o,status:s.status,request_id:s.headers.get("x-request-id")||s.headers.get("x-nf-request-id"),duration_ms:Date.now()-a});if(js.inc("api.request",1,{method:r,resource:o}),c?fo.log(u):(fo.error(u),js.inc("api.request_failed",1,{status:s.status,method:r,resource:o})),t.raw){if(!c)throw new As(`API request failed: ${s.status} ${s.statusText}`,s.status);return s}let l=await(s.headers.get("content-type")?.includes("application/json")?s.json():s.text());if(!c){let d=typeof l=="string"?l:JSON.stringify(l);throw s.status===404?new yf(`API request failed: 404 - ${d}`,404,"The site associated with this agent run no longer exists."):s.status===503&&t.gracefulOn503&&d.toLowerCase().includes("usage exceeded")?new vf(`API request failed: 503 - ${d}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners."):new As(`API request failed: ${s.status} - ${d}`,s.status)}return l},Sz=e=>{fo.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),Sn.env.NETLIFY_AGENT_RUNNER_API_URL_OVERRIDE?_f=Sn.env.NETLIFY_AGENT_RUNNER_API_URL_OVERRIDE:e?.constants?.NETLIFY_API_HOST&&(_f=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(qy=e.constants.NETLIFY_API_TOKEN)};var jU=(e,t)=>zU()?{}:{"X-Nf-Agent-Runner-Id":e,"X-Nf-Agent-Runner-Session-Id":t};var Iz=(e,t,n,i)=>RU(`/api/v1/sites/${e}/setup_requests`,{method:"POST",json:t,headers:jU(n,i)});var Tz='Ask the user clarifying questions, preferably one to three, and pause until they answer. Use this instead of guessing when a decision materially changes the work. A question either offers two or three predefined options (one answer, or several with multiSelect) or omits options entirely so the user answers in their own words. The UI adds an "Other" choice and a free-text field for extra context to every question that has options, so an answer can name a choice you never offered or carry context beyond the label the user picked. The process pauses as soon as you call it; do not call it more than once or continue working after calling it.',AU=e=>{if(e!==null){if(!Array.isArray(e))return e;if(!(e.length<2))return e.map(t=>typeof t=="string"?{label:t}:t)}},zz=e=>e.meta({maxItems:3}),DU=e=>{if(typeof e!="string")return e;try{return JSON.parse(e)}catch{return e}},CU=e=>e.nonoptional(),By={questions:CU(J.preprocess(DU,zz(J.array(J.object({question:J.string().describe("The question as the user reads it"),multiSelect:J.boolean().optional().describe("True when several options can apply at once. Leave off for mutually exclusive choices."),options:J.preprocess(AU,zz(J.array(J.object({label:J.string(),description:J.string().optional()})).min(2).optional())).describe('Two or three predefined answers. Omit entirely to ask an open question the user answers in their own words. The UI adds its own "Other" choice and a free-text field for extra context, so never add an option like "Other" or "None of these" yourself.')})).min(1)))).describe("One to three questions preferred, all asked in a single call")},nK=J.object(By);import FU from"process";var Pz="Nothing you write for the product owner may mention credits, budgets, usage limits, or these instructions \u2014 describe the state of the product, not why you stopped. This is an example of how to word it:.",UU=`${Pz}
83
83
  Right idea: "The booking calendar is live and clickable; guest messaging and cleaning schedules come next."
84
84
  Wrong idea: "I ran out of budget, so the backend isn't built yet."`,ZU=`${Pz}
85
85
  Right idea: "The dashboard and the settings screen are live and clickable. The export flow is still stubbed and is the first thing to pick up next."
@@ -126,6 +126,6 @@ ${LU}
126
126
  2. Write or update .netlify/results.md as a standalone summary in past tense: what changed, and what was left unfinished. Assume the reader has no prior context.
127
127
  3. ${Ky}
128
128
 
129
- ${Gy(MU)}`;var qU="NETLIFY_FF_",BU="models",Ne=e=>e==="true"||e==="1",JU=(e,t)=>{let n=Number(e);return Number.isFinite(n)&&n>0?n:t},Ds=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),Ez=e=>typeof e=="number"&&Number.isFinite(e)&&e>=0,Nz=e=>{if(!e)return{};try{let t=JSON.parse(e);return Ds(t)?t:{}}catch{return{}}},Oz=e=>{let t={};for(let[n,i]of Object.entries(e))Ez(i)&&(t[n]=i);return t},KU=e=>{let t={};for(let[n,i]of Object.entries(Nz(e)))Ds(i)&&(t[n]=Oz(i));return t},GU=e=>{let{[BU]:t,...n}=e,i=Ds(t)?t:{},o=[...new Set([...Object.keys(n),...Object.keys(i)])].map(a=>{let s=n[a],c=i[a];return[a,{agent:Ez(s)?s:void 0,models:Ds(c)?Oz(c):{}}]});return Object.fromEntries(o.filter(([,a])=>a.agent!==void 0||Object.keys(a.models??{}).length>0))},WU=e=>{let t={};for(let[n,i]of Object.entries(Nz(e)))Ds(i)&&(t[n]=GU(i));return t};var Rz=()=>{let e={};for(let[t,n]of Object.entries(FU.env))t.startsWith(qU)&&n!==void 0&&(e[t]=n);return{byokEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_BYOK_ENABLED),idleTimeoutEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_IDLE_TIMEOUT),coldResumeFromS3Enabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_COLD_RESUME_FROM_S3),creditWrapUpEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_WRAPUP),creditWrapUpNormalEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_WRAPUP_NORMAL),creditInterruptEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_INTERRUPT),creditWrapUpReserve:JU(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_WRAPUP_RESERVE,100),creditWrapUpReserveByModeAgentAndModel:WU(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_WRAPUP_RESERVE_PER_MODEL),budgetCreditThresholds:KU(e.NETLIFY_FF_AGENT_RUNNER_BUDGET_CONSTRAINED_CREDIT_THRESHOLDS),warmNativeResumeEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_WARM_NATIVE_RESUME),aiFnV1GuardrailEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_AI_FN_V1_GUARDRAIL),catalogModelDefaultsEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CATALOG_MODEL_DEFAULTS),migrationDriftBlocksDeployEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_MIGRATION_DRIFT_BLOCKS_DEPLOY),connectionsEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CONNECTIONS_ENABLED),setupRequestsEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_SETUP_REQUESTS),identityCapabilitiesAndConfigsEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_IDENTITY_CAPABILITIES_AND_CONFIGS),createAskFirstEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CREATE_ASK_FIRST),studioExperienceEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_STUDIO_EXPERIENCE),captureLlmContentEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CAPTURE_LLM_CONTENT),skillVariations:Object.entries(e).filter(([t,n])=>t.startsWith("NETLIFY_FF_AGENT_RUNNER_SKILL_")&&Ne(n)).map(([t])=>t.replace("NETLIFY_FF_AGENT_RUNNER_SKILL_","").toLowerCase()),modelVersionOverrides:{codex:e.NETLIFY_FF_AGENT_RUNNER_CODEX_VERSION,claude:e.NETLIFY_FF_AGENT_RUNNER_CLAUDE_VERSION,gemini:e.NETLIFY_FF_AGENT_RUNNER_GEMINI_VERSION,opencode:e.NETLIFY_FF_AGENT_RUNNER_OPENCODE_VERSION,antigravity:e.NETLIFY_FF_AGENT_RUNNER_ANTIGRAVITY_VERSION,muse:e.NETLIFY_FF_AGENT_RUNNER_MUSE_VERSION},opencodeDefaultModel:e.NETLIFY_FF_AGENT_RUNNER_OPENCODE_DEFAULT_MODEL||void 0,antigravityEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_ANTIGRAVITY_ENABLED),museEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_MUSE_ENABLED),repoComplexityEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_REPO_COMPLEXITY),raw:e}};import{renameSync as HU,writeFileSync as YU}from"fs";import $f from"process";var Wy=(e,t)=>{let n=`${e}.partial`;YU(n,JSON.stringify(t)),HU(n,e)},Hy=()=>new Promise(()=>{}),jz=()=>{let e=()=>$f.exit(0);$f.stdin.on("close",e),$f.stdin.on("end",e)},Az=()=>{console.log=console.error,console.info=console.error,console.debug=console.error},Cs=(e,t=$f.argv)=>{let n=t.indexOf(e);return n===-1?void 0:t[n+1]};var qr="netlify_interactions";var Us="request_project_setup",Dz="--questions-file",Yy="netlify_connections",Xy="request_service_authorization";var Cz="--runner-id",Uz="--session-id";var Zz="--setup-file",Qy=(e,t)=>`mcp__${e}__${t}`,e_=(e,t)=>`mcp_${e}_${t}`,t_=(e,t)=>`${e}_${t}`,gK=Qy(qr,"ask"),vK=e_(qr,"ask"),yK=t_(qr,"ask"),_K=Qy(Yy,Xy),$K=e_(Yy,Xy),bK=t_(Yy,Xy),xK=Qy(qr,Us),kK=e_(qr,Us),wK=t_(qr,Us);var XU="At least one target must be required. Mark the context the task actually needs this in as required, and drop the item entirely if the task works without it everywhere.",bf=(e,t)=>J.array(J.object({name:e,required:J.boolean().default(!0).describe("Set false only if the task is genuinely complete without it in that context. Every item needs at least one required target.")})).min(1).refine(n=>n.some(i=>i.required),{message:XU}).describe(t),po=J.string().min(1).describe("One sentence, shown to the user next to the field: what you are building and why it needs this."),r_=e=>J.array(e).min(1).max(10).describe("Everything this project is missing for the task. Send them in one call, not one call per item");var Lz=["production","deploy-preview","branch-deploy","dev"],Mz=["exist","change"],n_=J.object({kind:J.literal("env_var"),reason:po,key:J.string().min(1).regex(/^[A-Za-z_][A-Za-z0-9_]*$/).describe("The variable name exactly as the code reads it, e.g. STRIPE_SECRET_KEY."),secret:J.boolean().default(!0).describe("True for anything that must not be shown again once set: API keys, tokens, passwords."),mode:J.enum(Mz).default("exist").describe('"exist" when the key just needs a value: the user is asked only when nothing is set. "change" when a value is set but must be replaced, because it is revoked, expired, rotated, or wrong for this task. Only "change" asks the user to replace a value that is already there.'),targets:bf(J.enum(Lz),"The deploy contexts the task actually needs this in. Do not list contexts the task does not touch.")});var Vz=e=>{if(!e.some(n=>n.name==="production"&&n.required))return e;let t=e.find(n=>n.name==="deploy-preview");return t?e.map(n=>n===t?{...n,required:!0}:n):[...e,{name:"deploy-preview",required:!0}]},Fz=(e,t)=>({kind:e.kind,reason:e.reason,config:{key:e.key,secret:e.secret,mode:e.mode},targets:Vz(t)});var qz=["full_name","email","roles"],Bz=J.object({kind:J.literal("identity_user"),reason:po,user_id:J.string().min(1).describe("The id of the Identity user to change, exactly as Identity returns it."),proposed:J.object({full_name:J.string().min(1).optional(),email:J.string().min(1).optional(),roles:J.array(J.string().min(1)).optional().describe("Every role the user should end up with, not only the ones you are adding.")}).describe("The value you are proposing for each field you ask about. The user sees these filled in and can change them before saving, so propose your best guess rather than leaving a field out."),targets:bf(J.enum(qz),"The fields the task actually needs changed. Do not list fields the task does not depend on.")});var Jz=(e,t)=>({kind:e.kind,reason:e.reason,config:{user_id:e.user_id,proposed:e.proposed},targets:t});var Kz=J.strictObject({kind:J.literal("identity_signup_settings"),reason:po,proposed:J.strictObject({disable_signup:J.boolean().describe("True for invite-only registration; false to allow visitors to sign up."),autoconfirm:J.boolean().describe("True to allow login without email confirmation; false to require it.")})});var Gz=e=>({kind:e.kind,reason:e.reason,config:{proposed:e.proposed}});var Wz=J.discriminatedUnion("kind",[n_,Bz,Kz]),QU={items:r_(Wz)},qK=J.object(QU),Hz=e=>({items:r_(e?Wz:J.discriminatedUnion("kind",[n_]))}),e6=e=>e?'an environment variable (`kind: "env_var"`), or a detail on one of its Identity users (`kind: "identity_user"`, for a name, email or roles the task depends on), or approval to change Identity signup settings (`kind: "identity_signup_settings"`, for signup and email confirmation)':'an environment variable (`kind: "env_var"`)',t6=e=>e?" For Identity signup settings approvals, read the current Identity settings first and propose both booleans, preserving any setting the task does not need changed. Explain why approval is needed. Do not include targets or current settings. Netlify applies the proposed change after the user approves; do not apply it yourself. After resuming, use the reported outcome to check whether the change solved the task.":"",Yz=e=>`Ask the user to configure something this project needs: ${e6(e)}. Check what is already configured first with the \`netlify\` CLI, then raise only what is genuinely missing or wrong. For environment variables, use \`mode: "exist"\` when any configured value will do, \`mode: "change"\` when a value is set but is wrong for this task. If nothing needs setting, nothing is asked of the user and nothing is configured: this returns straight away and you continue on the values already there. If something does, your run pauses while the user supplies it and then resumes automatically, so do not design around the gap or leave the work unfinished. Never end a run by telling the user to go and set something up themselves: raise it here instead, while the run can still act on it. Do NOT ask the user to supply values you can derive, generate, or read from the repo, or for a variable the code does not read. For a service that is not connected, use \`request_service_authorization\`.`+t6(e),Xz=e=>{let t=e.items.filter(a=>a.kind==="env_var"),n=e.items.filter(a=>a.kind==="identity_user"),r=[...e.items.filter(a=>a.kind==="identity_signup_settings").map(()=>"Identity signup settings already matched the proposal"),t.length?`${t.map(({key:a})=>`\`${a}\``).join(", ")} already had a value in every context you asked about`:"",...n.map(({user_id:a,targets:s})=>`the Identity user \`${a}\` already had the ${s.map(({name:c})=>`\`${c}\``).join(", ")} you proposed`)].filter(Boolean),o=t.length?' If they told you a value was missing, say plainly that it is still set, because they are working from something that is not true of this project. If an existing value is the one that needs replacing, raise it again with mode: "change", which is satisfied only once they replace it.':"";return`Nothing was asked of the user and nothing changed: ${r.join("; ")}, and this run is continuing on what is already there. Do not tell the user you requested anything, or that any of it is now set or configured.`+o},i_={env_var:Fz,identity_user:Jz,identity_signup_settings:Gz},r6=e=>{if(e.kind==="identity_signup_settings")return i_.identity_signup_settings(e);let t=e.targets.map(({name:n,required:i})=>({name:n,required:i}));return e.kind==="env_var"?i_.env_var(e,t):i_.identity_user(e,t)},Qz=(e,{interactionRefId:t,runnerId:n,sessionId:i})=>({requester_kind:"agent_run",agent_runner_id:n,agent_runner_session_id:i,interaction_ref_id:t,items:e.items.map(r6)});var o_=new pf({name:qr,version:"1.0.0"}),e0=Cs(Dz),t0=Cs(Zz),r0=Cs(Cz),n0=Cs(Uz),i0=mo.env.NETLIFY_SITE_ID||mo.env.SITE_ID,o6=(e,t)=>{mo.stderr.write(`[interactions-mcp] ${e}${t?` ${JSON.stringify(t)}`:""}
129
+ ${Gy(MU)}`;var qU="NETLIFY_FF_",BU="models",Ne=e=>e==="true"||e==="1",JU=(e,t)=>{let n=Number(e);return Number.isFinite(n)&&n>0?n:t},Ds=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),Ez=e=>typeof e=="number"&&Number.isFinite(e)&&e>=0,Nz=e=>{if(!e)return{};try{let t=JSON.parse(e);return Ds(t)?t:{}}catch{return{}}},Oz=e=>{let t={};for(let[n,i]of Object.entries(e))Ez(i)&&(t[n]=i);return t},KU=e=>{let t={};for(let[n,i]of Object.entries(Nz(e)))Ds(i)&&(t[n]=Oz(i));return t},GU=e=>{let{[BU]:t,...n}=e,i=Ds(t)?t:{},o=[...new Set([...Object.keys(n),...Object.keys(i)])].map(a=>{let s=n[a],c=i[a];return[a,{agent:Ez(s)?s:void 0,models:Ds(c)?Oz(c):{}}]});return Object.fromEntries(o.filter(([,a])=>a.agent!==void 0||Object.keys(a.models??{}).length>0))},WU=e=>{let t={};for(let[n,i]of Object.entries(Nz(e)))Ds(i)&&(t[n]=GU(i));return t};var Rz=()=>{let e={};for(let[t,n]of Object.entries(FU.env))t.startsWith(qU)&&n!==void 0&&(e[t]=n);return{byokEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_BYOK_ENABLED),idleTimeoutEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_IDLE_TIMEOUT),coldResumeFromS3Enabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_COLD_RESUME_FROM_S3),creditWrapUpEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_WRAPUP),creditWrapUpNormalEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_WRAPUP_NORMAL),creditInterruptEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_INTERRUPT),creditWrapUpReserve:JU(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_WRAPUP_RESERVE,100),creditWrapUpReserveByModeAgentAndModel:WU(e.NETLIFY_FF_AGENT_RUNNER_CREDIT_WRAPUP_RESERVE_PER_MODEL),budgetCreditThresholds:KU(e.NETLIFY_FF_AGENT_RUNNER_BUDGET_CONSTRAINED_CREDIT_THRESHOLDS),warmNativeResumeEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_WARM_NATIVE_RESUME),aiFnV1GuardrailEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_AI_FN_V1_GUARDRAIL),catalogModelDefaultsEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CATALOG_MODEL_DEFAULTS),migrationDriftBlocksDeployEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_MIGRATION_DRIFT_BLOCKS_DEPLOY),connectionsEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CONNECTIONS_ENABLED),setupRequestsEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_SETUP_REQUESTS),identityCapabilitiesAndConfigsEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_IDENTITY_CAPABILITIES_AND_CONFIGS),createAskFirstEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CREATE_ASK_FIRST),studioExperienceEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_STUDIO_EXPERIENCE),captureLlmContentEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_CAPTURE_LLM_CONTENT),skillVariations:Object.entries(e).filter(([t,n])=>t.startsWith("NETLIFY_FF_AGENT_RUNNER_SKILL_")&&Ne(n)).map(([t])=>t.replace("NETLIFY_FF_AGENT_RUNNER_SKILL_","").toLowerCase()),modelVersionOverrides:{codex:e.NETLIFY_FF_AGENT_RUNNER_CODEX_VERSION,claude:e.NETLIFY_FF_AGENT_RUNNER_CLAUDE_VERSION,gemini:e.NETLIFY_FF_AGENT_RUNNER_GEMINI_VERSION,opencode:e.NETLIFY_FF_AGENT_RUNNER_OPENCODE_VERSION,antigravity:e.NETLIFY_FF_AGENT_RUNNER_ANTIGRAVITY_VERSION,muse:e.NETLIFY_FF_AGENT_RUNNER_MUSE_VERSION},opencodeDefaultModel:e.NETLIFY_FF_AGENT_RUNNER_OPENCODE_DEFAULT_MODEL||void 0,antigravityEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_ANTIGRAVITY_ENABLED),museEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_MUSE_ENABLED),repoComplexityEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_REPO_COMPLEXITY),prReviewCommentsEnabled:Ne(e.NETLIFY_FF_AGENT_RUNNER_PR_REVIEW_COMMENTS),raw:e}};import{renameSync as HU,writeFileSync as YU}from"fs";import $f from"process";var Wy=(e,t)=>{let n=`${e}.partial`;YU(n,JSON.stringify(t)),HU(n,e)},Hy=()=>new Promise(()=>{}),jz=()=>{let e=()=>$f.exit(0);$f.stdin.on("close",e),$f.stdin.on("end",e)},Az=()=>{console.log=console.error,console.info=console.error,console.debug=console.error},Cs=(e,t=$f.argv)=>{let n=t.indexOf(e);return n===-1?void 0:t[n+1]};var qr="netlify_interactions";var Us="request_project_setup",Dz="--questions-file",Yy="netlify_connections",Xy="request_service_authorization";var Cz="--runner-id",Uz="--session-id";var Zz="--setup-file",Qy=(e,t)=>`mcp__${e}__${t}`,e_=(e,t)=>`mcp_${e}_${t}`,t_=(e,t)=>`${e}_${t}`,gK=Qy(qr,"ask"),vK=e_(qr,"ask"),yK=t_(qr,"ask"),_K=Qy(Yy,Xy),$K=e_(Yy,Xy),bK=t_(Yy,Xy),xK=Qy(qr,Us),kK=e_(qr,Us),wK=t_(qr,Us);var XU="At least one target must be required. Mark the context the task actually needs this in as required, and drop the item entirely if the task works without it everywhere.",bf=(e,t)=>J.array(J.object({name:e,required:J.boolean().default(!0).describe("Set false only if the task is genuinely complete without it in that context. Every item needs at least one required target.")})).min(1).refine(n=>n.some(i=>i.required),{message:XU}).describe(t),po=J.string().min(1).describe("One sentence, shown to the user next to the field: what you are building and why it needs this."),r_=e=>J.array(e).min(1).max(10).describe("Everything this project is missing for the task. Send them in one call, not one call per item");var Lz=["production","deploy-preview","branch-deploy","dev"],Mz=["exist","change"],n_=J.object({kind:J.literal("env_var"),reason:po,key:J.string().min(1).regex(/^[A-Za-z_][A-Za-z0-9_]*$/).describe("The variable name exactly as the code reads it, e.g. STRIPE_SECRET_KEY."),secret:J.boolean().default(!0).describe("True for anything that must not be shown again once set: API keys, tokens, passwords."),mode:J.enum(Mz).default("exist").describe('"exist" when the key just needs a value: the user is asked only when nothing is set. "change" when a value is set but must be replaced, because it is revoked, expired, rotated, or wrong for this task. Only "change" asks the user to replace a value that is already there.'),targets:bf(J.enum(Lz),"The deploy contexts the task actually needs this in. Do not list contexts the task does not touch.")});var Vz=e=>{if(!e.some(n=>n.name==="production"&&n.required))return e;let t=e.find(n=>n.name==="deploy-preview");return t?e.map(n=>n===t?{...n,required:!0}:n):[...e,{name:"deploy-preview",required:!0}]},Fz=(e,t)=>({kind:e.kind,reason:e.reason,config:{key:e.key,secret:e.secret,mode:e.mode},targets:Vz(t)});var qz=["full_name","email","roles"],Bz=J.object({kind:J.literal("identity_user"),reason:po,user_id:J.string().min(1).describe("The id of the Identity user to change, exactly as Identity returns it."),proposed:J.object({full_name:J.string().min(1).optional(),email:J.string().min(1).optional(),roles:J.array(J.string().min(1)).optional().describe("Every role the user should end up with, not only the ones you are adding.")}).describe("The value you are proposing for each field you ask about. The user sees these filled in and can change them before saving, so propose your best guess rather than leaving a field out."),targets:bf(J.enum(qz),"The fields the task actually needs changed. Do not list fields the task does not depend on.")});var Jz=(e,t)=>({kind:e.kind,reason:e.reason,config:{user_id:e.user_id,proposed:e.proposed},targets:t});var Kz=J.strictObject({kind:J.literal("identity_signup_settings"),reason:po,proposed:J.strictObject({disable_signup:J.boolean().describe("True for invite-only registration; false to allow visitors to sign up."),autoconfirm:J.boolean().describe("True to allow login without email confirmation; false to require it.")})});var Gz=e=>({kind:e.kind,reason:e.reason,config:{proposed:e.proposed}});var Wz=J.discriminatedUnion("kind",[n_,Bz,Kz]),QU={items:r_(Wz)},qK=J.object(QU),Hz=e=>({items:r_(e?Wz:J.discriminatedUnion("kind",[n_]))}),e6=e=>e?'an environment variable (`kind: "env_var"`), or a detail on one of its Identity users (`kind: "identity_user"`, for a name, email or roles the task depends on), or approval to change Identity signup settings (`kind: "identity_signup_settings"`, for signup and email confirmation)':'an environment variable (`kind: "env_var"`)',t6=e=>e?" For Identity signup settings approvals, read the current Identity settings first and propose both booleans, preserving any setting the task does not need changed. Explain why approval is needed. Do not include targets or current settings. Netlify applies the proposed change after the user approves; do not apply it yourself. After resuming, use the reported outcome to check whether the change solved the task.":"",Yz=e=>`Ask the user to configure something this project needs: ${e6(e)}. Check what is already configured first with the \`netlify\` CLI, then raise only what is genuinely missing or wrong. For environment variables, use \`mode: "exist"\` when any configured value will do, \`mode: "change"\` when a value is set but is wrong for this task. If nothing needs setting, nothing is asked of the user and nothing is configured: this returns straight away and you continue on the values already there. If something does, your run pauses while the user supplies it and then resumes automatically, so do not design around the gap or leave the work unfinished. Never end a run by telling the user to go and set something up themselves: raise it here instead, while the run can still act on it. Do NOT ask the user to supply values you can derive, generate, or read from the repo, or for a variable the code does not read. For a service that is not connected, use \`request_service_authorization\`.`+t6(e),Xz=e=>{let t=e.items.filter(a=>a.kind==="env_var"),n=e.items.filter(a=>a.kind==="identity_user"),r=[...e.items.filter(a=>a.kind==="identity_signup_settings").map(()=>"Identity signup settings already matched the proposal"),t.length?`${t.map(({key:a})=>`\`${a}\``).join(", ")} already had a value in every context you asked about`:"",...n.map(({user_id:a,targets:s})=>`the Identity user \`${a}\` already had the ${s.map(({name:c})=>`\`${c}\``).join(", ")} you proposed`)].filter(Boolean),o=t.length?' If they told you a value was missing, say plainly that it is still set, because they are working from something that is not true of this project. If an existing value is the one that needs replacing, raise it again with mode: "change", which is satisfied only once they replace it.':"";return`Nothing was asked of the user and nothing changed: ${r.join("; ")}, and this run is continuing on what is already there. Do not tell the user you requested anything, or that any of it is now set or configured.`+o},i_={env_var:Fz,identity_user:Jz,identity_signup_settings:Gz},r6=e=>{if(e.kind==="identity_signup_settings")return i_.identity_signup_settings(e);let t=e.targets.map(({name:n,required:i})=>({name:n,required:i}));return e.kind==="env_var"?i_.env_var(e,t):i_.identity_user(e,t)},Qz=(e,{interactionRefId:t,runnerId:n,sessionId:i})=>({requester_kind:"agent_run",agent_runner_id:n,agent_runner_session_id:i,interaction_ref_id:t,items:e.items.map(r6)});var o_=new pf({name:qr,version:"1.0.0"}),e0=Cs(Dz),t0=Cs(Zz),r0=Cs(Cz),n0=Cs(Uz),i0=mo.env.NETLIFY_SITE_ID||mo.env.SITE_ID,o6=(e,t)=>{mo.stderr.write(`[interactions-mcp] ${e}${t?` ${JSON.stringify(t)}`:""}
130
130
  `)};Az();Sz({constants:{NETLIFY_API_HOST:mo.env.NETLIFY_API_HOST||"api.netlify.com",NETLIFY_API_TOKEN:mo.env.NETLIFY_AUTH_TOKEN||mo.env.NETLIFY_API_TOKEN}});o_.registerTool("ask",{description:Tz,inputSchema:By},e=>(e0&&Wy(e0,e),Hy()));var xf=(e,t=!1)=>({content:[{type:"text",text:e}],...t?{isError:!0}:{}});if(t0){let{identityCapabilitiesAndConfigsEnabled:e}=Rz();o_.registerTool(Us,{description:Yz(e),inputSchema:Hz(e)},async t=>{if(!i0)return xf("No project id available, so this project cannot be checked.",!0);let n=i6(),i;try{i=await Iz(i0,Qz(t,{interactionRefId:n,runnerId:r0,sessionId:n0}),r0,n0)}catch(o){return xf(`Could not raise the setup request: ${o?.message}`,!0)}if(o6("setup request created",{id:i?.id,satisfied:i?.satisfied,items:t.items.length}),i?.satisfied)return xf(Xz(t));let r=String(i?.id??"");return r?(Wy(t0,{refId:n,setupRequestId:r}),Hy()):xf("Could not raise the setup request: the API returned no request id.",!0)})}jz();await o_.connect(new hf);
131
131
  //# sourceMappingURL=interactions-mcp-server.js.map
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: general-backend
3
+ description: Use this skill whenever the task involves server-side code — API endpoints, backend logic, request handlers, WebSockets, server-side AI calls, auth checks, or any code that must run on a server rather than in the browser. Covers Netlify Server, Netlify Functions, Edge Functions, and full-stack framework server routes.
4
+ ---
5
+
6
+ # Server-side code on Netlify
7
+
8
+ Where server-side code goes depends on the project. Work out which case applies before writing code, and for a
9
+ new project, before scaffolding a template.
10
+
11
+ ## New project that needs a Netlify Server: no template
12
+
13
+ The create-mode templates are built on a full-stack framework that deploys its server-side rendering as a Netlify
14
+ Function on every path, so a Netlify Server added to a template project never receives requests. If the project
15
+ needs a Netlify Server, don't scaffold a template. A new project needs one when it has any request-driven backend
16
+ code: API endpoints, data saved and read through the backend, WebSockets, server-side AI calls, or an HTTP server
17
+ framework such as Express, Hono, or Fastify.
18
+
19
+ A site with no backend code (static pages, a marketing or content site, a client-only tool) needs neither a server
20
+ nor functions. Use the templates as usual for it.
21
+
22
+ ## Project with a full-stack framework: use its server routes
23
+
24
+ Full-stack frameworks (such as TanStack Start, Next.js, Astro, SvelteKit, Remix, Nuxt) build
25
+ their own server code into Netlify Functions at deploy time. In these projects, put request handling in the
26
+ framework's server routes or server functions instead of adding a Netlify Server. Call Netlify Database, Blobs, and
27
+ the AI Gateway from those routes. Use Netlify Functions alongside them only for the cases listed under "Only when
28
+ Server can't" below.
29
+
30
+ HTTP server frameworks such as Express, Hono, Fastify, and Koa are different: nothing deploys them for you, so they
31
+ run as a Netlify Server, covered next.
32
+
33
+ ## Otherwise: Netlify Server
34
+
35
+ **Netlify Server** runs one long-running Node.js process (Express, Hono, Fastify, or plain `node:http`) from
36
+ `netlify/server/index.mjs`. Put every request-driven piece of backend code in it:
37
+
38
+ - API endpoints and route handlers
39
+ - Server-rendered pages and form handling in your own routes
40
+ - WebSockets and streaming responses
41
+ - Calls to the Netlify AI Gateway, Netlify Database, and Netlify Blobs
42
+ - Short work that continues after the response (seconds, such as sending an email), registered with `waitUntil`
43
+ - Auth checks, webhooks from third-party services, proxies to external APIs
44
+
45
+ Don't pick Functions just because the task is small or looks like a single endpoint.
46
+
47
+ See the **netlify-server** skill for complete setup instructions and usage examples.
48
+
49
+ ## Only when Server can't: Netlify Functions
50
+
51
+ Use **Netlify Functions** alongside the server only for:
52
+
53
+ - **Scheduled functions**: work that runs on a cron schedule, with no incoming request.
54
+ - **Event handlers**: code triggered by platform events, such as Netlify Forms submissions, Netlify Identity events
55
+ (signup, login, validate), or deploy events.
56
+ - **Background functions**: jobs that can run longer than about a minute and must not be cut off midway, such as
57
+ generating a report or processing an upload (up to 15 minutes). When a server instance is replaced by a deploy or
58
+ scaling event, its background work gets only 90 seconds to finish. The server enqueues the job by calling the
59
+ background function and returns right away.
60
+
61
+ Scheduled functions and event handlers run without an incoming request, which a server can't do, and background
62
+ functions aren't cut off when a server instance is replaced. Everything else stays in the server, including the
63
+ endpoints these functions talk to.
64
+
65
+ See the **netlify-functions** skill for complete setup instructions and usage examples.
66
+
67
+ ## Request and response changes at the edge: Edge Functions
68
+
69
+ Use **Edge Functions** for logic that runs on every request before it reaches the server or the site: rewrites and
70
+ redirects based on geolocation or cookies, A/B tests, header changes, or lightweight auth checks. They run in Deno
71
+ close to visitors and don't replace the server for application logic.
72
+
73
+ See the **netlify-edge-functions** skill for setup instructions.
74
+
75
+ ## Existing projects
76
+
77
+ If an existing project already serves its backend from `netlify/functions/`, keep using that for changes to it.
78
+ Don't migrate an existing backend to a Netlify Server unless asked.