@netlify/agent-runner-cli 2.16.0-server.0 → 2.16.0-server.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.
@@ -79,6 +79,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
79
79
  `))};function pf(e){return{log:(...t)=>{Ts()&&console.log(`[AR]-[${e}]${Ps()}`,...t)},error:(...t)=>{Ts()&&hz(console.error,`[AR]-[ERROR]-[${e}]${Ps()}`,t,"error")},warn:(...t)=>{Ts()&&hz(console.warn,`[AR]-[WARN]-[${e}]${Ps()}`,t,"warn")},info:(...t)=>{Ts()&&console.info(`[AR]-[${e}]${Ps()}`,...t)},debug:(...t)=>{Ts()&&console.debug(`[AR]-[DEBUG]-[${e}]${Ps()}`,...t)}}}import{AsyncLocalStorage as MC}from"async_hooks";import LC from"dgram";import uo from"process";var VC="buildbot.agent_runner.",qC=8125,FC=e=>{let t=Object.keys(e);return t.length===0?"":"|#"+t.map(n=>`${n}:${e[n]}`).join(",")},Os=(e,t,n,i={})=>`${VC}${e}:${t}|${n}${FC(i)}`,BC={service:"buildbot"},JC={},KC=new MC;var Es=e=>({...JC,...KC.getStore()??{},...e,...BC}),GC=(e,t)=>{let n=!1,i=LC.createSocket("udp4");return i.unref(),i.once("error",r=>{if(!n){let o=r.code??r.message;uo.stderr.write(`[metrics] UDP error: ${o}
80
80
  `),n=!0}}),r=>{n||(Uy++,i.send(r,t,e,o=>{if(Uy--,Uy===0)for(let a of HC)a();if(o&&!n){let a=o.code??o.message;uo.stderr.write(`[metrics] UDP send to ${e}:${t} failed: ${a}
81
81
  `),n=!0}}))}},Uy=0,HC=new Set;var Zy=()=>{},WC=()=>!!uo.env.VITEST;var XC=()=>{if(WC())return Zy;let e=uo.env.HOST_NODE_IP;if(!e)return Zy;let t=uo.env.DD_AGENT_PORT,n=t===void 0?qC:Number(t);return!Number.isInteger(n)||n<=0||n>65535?(uo.stderr.write(`[metrics] DD_AGENT_PORT="${t}" is not a valid port; metrics disabled
82
- `),Zy):GC(e,n)},YC=(e=XC())=>({inc(t,n=1,i={}){e(Os(t,n,"c",Es(i)))},gauge(t,n,i={}){e(Os(t,n,"g",Es(i)))},histogram(t,n,i={}){e(Os(t,n,"h",Es(i)))},timing(t,n,i={}){e(Os(t,n,"ms",Es(i)))},distribution(t,n,i={}){e(Os(t,n,"d",Es(i)))}}),Ns=YC();import{spawn as LB}from"child_process";import{existsSync as qB,mkdirSync as FB,writeFileSync as BB}from"fs";import{connect as KB}from"net";import QC from"path";import WB from"process";var YB=pf("step-preview"),QB=QC.join("apps","netlify-react-ui","public","agent-run-preview.json"),eU=9001,tU="components-agentrunners-rendersteps--live",eJ=`http://localhost:${eU}/?path=/story/${tU}`;var mf=class extends Error{constructor(n,i,r){super(n);this.statusCode=i;this.userMessage=r;this.name="CreditLimitExceededError"}},hf=class extends Error{constructor(n,i,r){super(n);this.statusCode=i;this.userMessage=r;this.name="ApiResourceNotFoundError"}},js=class extends Error{constructor(n,i){super(n);this.statusCode=i;this.name="ApiRequestError"}};var gf=wn.env.NETLIFY_API_URL,My=wn.env.NETLIFY_API_TOKEN,lo=pf("api"),rU=()=>wn.env.NETLIFY_LOCAL_MODE==="true",nU=new Set(["agent_runners","sessions","sites","accounts","branch","dev_servers"]),iU=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"]),oU=e=>e.split("/").map((t,n,i)=>t===""?t:nU.has(i[n-1])?":id":iU.has(t)?t:":id").join("/"),aU=e=>(e??[]).map(({toolCallId:t,toolStatus:n,exitCode:i,startedAtMs:r,endedAtMs:o,timingSource:a,fileOperations:s,skillId:c,...u})=>u),sU=e=>e?.steps!==void 0?{...e,steps:aU(e.steps)}:e,cU=async(e,t={})=>{if(!gf||!My)throw new Error("No API URL or token");let n=new URL(e,gf),i={...t,headers:{...t.headers,Authorization:`Bearer ${My}`}};wn.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(sU(t.json)));let r=t.method??"GET",o=oU(n.pathname),a=Date.now(),s;try{s=await fetch(n,i)}catch(d){throw lo.error(zs({event:"api.request",method:r,path:n.pathname,resource:o,outcome:"network_error",error:d.message,duration_ms:Date.now()-a})),Ns.inc("api.request",1,{method:r,resource:o}),Ns.inc("api.request_failed",1,{status:"network_error",method:r,resource:o}),d}let c=s.ok&&s.status<=299;wn.env.AGENT_RUNNERS_DEBUG==="true"&&(lo.log(`Response headers for ${n}:`),s.headers.forEach((d,f)=>{lo.log(` ${f}: ${d}`)}));let u=zs({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(Ns.inc("api.request",1,{method:r,resource:o}),c?lo.log(u):(lo.error(u),Ns.inc("api.request_failed",1,{status:s.status,method:r,resource:o})),t.raw){if(!c)throw new js(`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 hf(`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 mf(`API request failed: 503 - ${d}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners."):new js(`API request failed: ${s.status} - ${d}`,s.status)}return l},gz=e=>{lo.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),wn.env.NETLIFY_AGENT_RUNNER_API_URL_OVERRIDE?gf=wn.env.NETLIFY_AGENT_RUNNER_API_URL_OVERRIDE:e?.constants?.NETLIFY_API_HOST&&(gf=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(My=e.constants.NETLIFY_API_TOKEN)};var uU=(e,t)=>rU()?{}:{"X-Nf-Agent-Runner-Id":e,"X-Nf-Agent-Runner-Session-Id":t};var vz=(e,t,n,i)=>cU(`/api/v1/sites/${e}/setup_requests`,{method:"POST",json:t,headers:uU(n,i)});var _z='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.',lU=e=>{if(e!==null){if(!Array.isArray(e))return e;if(!(e.length<2))return e.map(t=>typeof t=="string"?{label:t}:t)}},yz=e=>e.meta({maxItems:3}),dU=e=>{if(typeof e!="string")return e;try{return JSON.parse(e)}catch{return e}},fU=e=>e.nonoptional(),Ly={questions:fU($e.preprocess(dU,yz($e.array($e.object({question:$e.string().describe("The question as the user reads it"),multiSelect:$e.boolean().optional().describe("True when several options can apply at once. Leave off for mutually exclusive choices."),options:$e.preprocess(lU,yz($e.array($e.object({label:$e.string(),description:$e.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")},kJ=$e.object(Ly);import{renameSync as pU,writeFileSync as mU}from"fs";import vf from"process";var Vy=(e,t)=>{let n=`${e}.partial`;mU(n,JSON.stringify(t)),pU(n,e)},qy=()=>new Promise(()=>{}),$z=()=>{let e=()=>vf.exit(0);vf.stdin.on("close",e),vf.stdin.on("end",e)},bz=()=>{console.log=console.error,console.info=console.error,console.debug=console.error},As=(e,t=vf.argv)=>{let n=t.indexOf(e);return n===-1?void 0:t[n+1]};var qr="netlify_interactions";var Ds="request_project_setup",xz="--questions-file",Fy="netlify_connections",By="request_service_authorization";var kz="--runner-id",wz="--session-id";var Sz="--setup-file",Jy=(e,t)=>`mcp__${e}__${t}`,Ky=(e,t)=>`mcp_${e}_${t}`,Gy=(e,t)=>`${e}_${t}`,PJ=Jy(qr,"ask"),TJ=Ky(qr,"ask"),OJ=Gy(qr,"ask"),EJ=Jy(Fy,By),NJ=Ky(Fy,By),jJ=Gy(Fy,By),AJ=Jy(qr,Ds),DJ=Ky(qr,Ds),RJ=Gy(qr,Ds);var hU=["production","deploy-preview","branch-deploy","dev"],gU=["exist","change"],vU="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.",yU=(e,t)=>$e.array($e.object({name:e,required:$e.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:vU}).describe(t),_U=$e.string().min(1).describe("One sentence, shown to the user next to the field: what you are building and why it needs this."),$U=$e.object({kind:$e.literal("env_var"),reason:_U,key:$e.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:$e.boolean().default(!0).describe("True for anything that must not be shown again once set: API keys, tokens, passwords."),mode:$e.enum(gU).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:yU($e.enum(hU),"The deploy contexts the task actually needs this in. Do not list contexts the task does not touch.")}),bU=$e.discriminatedUnion("kind",[$U]),Hy={items:$e.array(bU).min(1).max(10).describe("Everything this project is missing for the task. Send them in one call, not one call per item")},ZJ=$e.object(Hy);var Iz='Ask the user to configure something this project needs. Environment variables only, for now. Check what is already configured first with the `netlify` CLI, then raise only what is genuinely missing or wrong: `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 use this for 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`.',zz=e=>`Nothing was asked of the user and nothing changed: ${e.items.map(({key:n})=>`\`${n}\``).join(", ")} already had a value in every context you asked about, and this run is continuing on those existing values. Do not tell the user you requested anything, or that any of it is now set or configured. 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.`,xU=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}]},Pz=(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(({kind:r,reason:o,key:a,secret:s,mode:c,targets:u})=>({kind:r,reason:o,config:{key:a,secret:s,mode:c},targets:xU(u.map(({name:l,required:d})=>({name:l,required:d})))}))});var Wy=new lf({name:qr,version:"1.0.0"}),Tz=As(xz),Oz=As(Sz),Ez=As(kz),Nz=As(wz),jz=fo.env.NETLIFY_SITE_ID||fo.env.SITE_ID,SU=(e,t)=>{fo.stderr.write(`[interactions-mcp] ${e}${t?` ${JSON.stringify(t)}`:""}
82
+ `),Zy):GC(e,n)},YC=(e=XC())=>({inc(t,n=1,i={}){e(Os(t,n,"c",Es(i)))},gauge(t,n,i={}){e(Os(t,n,"g",Es(i)))},histogram(t,n,i={}){e(Os(t,n,"h",Es(i)))},timing(t,n,i={}){e(Os(t,n,"ms",Es(i)))},distribution(t,n,i={}){e(Os(t,n,"d",Es(i)))}}),Ns=YC();import{spawn as LB}from"child_process";import{existsSync as qB,mkdirSync as FB,writeFileSync as BB}from"fs";import{connect as KB}from"net";import QC from"path";import WB from"process";var YB=pf("step-preview"),QB=QC.join("apps","netlify-react-ui","public","agent-run-preview.json"),eU=9001,tU="components-agentrunners-rendersteps--live",eJ=`http://localhost:${eU}/?path=/story/${tU}`;var mf=class extends Error{constructor(n,i,r){super(n);this.statusCode=i;this.userMessage=r;this.name="CreditLimitExceededError"}},hf=class extends Error{constructor(n,i,r){super(n);this.statusCode=i;this.userMessage=r;this.name="ApiResourceNotFoundError"}},js=class extends Error{constructor(n,i){super(n);this.statusCode=i;this.name="ApiRequestError"}};var gf=wn.env.NETLIFY_API_URL,My=wn.env.NETLIFY_API_TOKEN,lo=pf("api"),rU=()=>wn.env.NETLIFY_LOCAL_MODE==="true",nU=new Set(["agent_runners","sessions","sites","accounts","branch","dev_servers"]),iU=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"]),oU=e=>e.split("/").map((t,n,i)=>t===""?t:nU.has(i[n-1])?":id":iU.has(t)?t:":id").join("/"),aU=e=>(e??[]).map(({toolCallId:t,toolStatus:n,exitCode:i,startedAtMs:r,endedAtMs:o,timingSource:a,fileOperations:s,skillId:c,...u})=>u),sU=e=>e?.steps!==void 0?{...e,steps:aU(e.steps)}:e,cU=async(e,t={})=>{if(!gf||!My)throw new Error("No API URL or token");let n=new URL(e,gf),i={...t,headers:{...t.headers,Authorization:`Bearer ${My}`}};wn.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(sU(t.json)));let r=t.method??"GET",o=oU(n.pathname),a=Date.now(),s;try{s=await fetch(n,i)}catch(d){throw lo.error(zs({event:"api.request",method:r,path:n.pathname,resource:o,outcome:"network_error",error:d.message,duration_ms:Date.now()-a})),Ns.inc("api.request",1,{method:r,resource:o}),Ns.inc("api.request_failed",1,{status:"network_error",method:r,resource:o}),d}let c=s.ok&&s.status<=299;wn.env.AGENT_RUNNERS_DEBUG==="true"&&(lo.log(`Response headers for ${n}:`),s.headers.forEach((d,f)=>{lo.log(` ${f}: ${d}`)}));let u=zs({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(Ns.inc("api.request",1,{method:r,resource:o}),c?lo.log(u):(lo.error(u),Ns.inc("api.request_failed",1,{status:s.status,method:r,resource:o})),t.raw){if(!c)throw new js(`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 hf(`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 mf(`API request failed: 503 - ${d}`,503,"Credit limit reached. Please add more credits to continue using Agent Runners."):new js(`API request failed: ${s.status} - ${d}`,s.status)}return l},gz=e=>{lo.log("Setting details for api",{apiUrl:e?.constants?.NETLIFY_API_HOST,token:!!e?.constants?.NETLIFY_API_TOKEN}),wn.env.NETLIFY_AGENT_RUNNER_API_URL_OVERRIDE?gf=wn.env.NETLIFY_AGENT_RUNNER_API_URL_OVERRIDE:e?.constants?.NETLIFY_API_HOST&&(gf=`https://${e.constants.NETLIFY_API_HOST}`),e?.constants?.NETLIFY_API_TOKEN&&(My=e.constants.NETLIFY_API_TOKEN)};var uU=(e,t)=>rU()?{}:{"X-Nf-Agent-Runner-Id":e,"X-Nf-Agent-Runner-Session-Id":t};var vz=(e,t,n,i)=>cU(`/api/v1/sites/${e}/setup_requests`,{method:"POST",json:t,headers:uU(n,i)});var _z='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.',lU=e=>{if(e!==null){if(!Array.isArray(e))return e;if(!(e.length<2))return e.map(t=>typeof t=="string"?{label:t}:t)}},yz=e=>e.meta({maxItems:3}),dU=e=>{if(typeof e!="string")return e;try{return JSON.parse(e)}catch{return e}},fU=e=>e.nonoptional(),Ly={questions:fU($e.preprocess(dU,yz($e.array($e.object({question:$e.string().describe("The question as the user reads it"),multiSelect:$e.boolean().optional().describe("True when several options can apply at once. Leave off for mutually exclusive choices."),options:$e.preprocess(lU,yz($e.array($e.object({label:$e.string(),description:$e.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")},kJ=$e.object(Ly);import{renameSync as pU,writeFileSync as mU}from"fs";import vf from"process";var Vy=(e,t)=>{let n=`${e}.partial`;mU(n,JSON.stringify(t)),pU(n,e)},qy=()=>new Promise(()=>{}),$z=()=>{let e=()=>vf.exit(0);vf.stdin.on("close",e),vf.stdin.on("end",e)},bz=()=>{console.log=console.error,console.info=console.error,console.debug=console.error},As=(e,t=vf.argv)=>{let n=t.indexOf(e);return n===-1?void 0:t[n+1]};var qr="netlify_interactions";var Ds="request_project_setup",xz="--questions-file",Fy="netlify_connections",By="request_service_authorization";var kz="--runner-id",wz="--session-id";var Sz="--setup-file",Jy=(e,t)=>`mcp__${e}__${t}`,Ky=(e,t)=>`mcp_${e}_${t}`,Gy=(e,t)=>`${e}_${t}`,PJ=Jy(qr,"ask"),TJ=Ky(qr,"ask"),OJ=Gy(qr,"ask"),EJ=Jy(Fy,By),NJ=Ky(Fy,By),jJ=Gy(Fy,By),AJ=Jy(qr,Ds),DJ=Ky(qr,Ds),RJ=Gy(qr,Ds);var hU=["production","deploy-preview","branch-deploy","dev"],gU=["exist","change"],vU="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.",yU=(e,t)=>$e.array($e.object({name:e,required:$e.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:vU}).describe(t),_U=$e.string().min(1).describe("One sentence, shown to the user next to the field: what you are building and why it needs this."),$U=$e.object({kind:$e.literal("env_var"),reason:_U,key:$e.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:$e.boolean().default(!0).describe("True for anything that must not be shown again once set: API keys, tokens, passwords."),mode:$e.enum(gU).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:yU($e.enum(hU),"The deploy contexts the task actually needs this in. Do not list contexts the task does not touch.")}),bU=$e.discriminatedUnion("kind",[$U]),Hy={items:$e.array(bU).min(1).max(10).describe("Everything this project is missing for the task. Send them in one call, not one call per item")},ZJ=$e.object(Hy);var Iz='Ask the user to configure something this project needs. Environment variables only, for now. Check what is already configured first with the `netlify` CLI, then raise only what is genuinely missing or wrong: `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 use this for 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`.',zz=e=>`Nothing was asked of the user and nothing changed: ${e.items.map(({key:n})=>`\`${n}\``).join(", ")} already had a value in every context you asked about, and this run is continuing on those existing values. Do not tell the user you requested anything, or that any of it is now set or configured. 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.`,xU=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}]},Pz=(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(({kind:r,reason:o,key:a,secret:s,mode:c,targets:u})=>({kind:r,reason:o,config:{key:a,secret:s,mode:c},targets:xU(u.map(({name:l,required:d})=>({name:l,required:d})))}))});var Wy=new lf({name:qr,version:"1.0.0"}),Tz=As(xz),Oz=As(Sz),Ez=As(kz),Nz=As(wz),jz=fo.env.NETLIFY_SITE_ID||fo.env.SITE_ID,SU=(e,t)=>{fo.stderr.write(`[interactions-mcp] ${e}${t?` ${JSON.stringify(t)}`:""}
83
83
  `)};bz();gz({constants:{NETLIFY_API_HOST:fo.env.NETLIFY_API_HOST||"api.netlify.com",NETLIFY_API_TOKEN:fo.env.NETLIFY_AUTH_TOKEN||fo.env.NETLIFY_API_TOKEN}});Wy.registerTool("ask",{description:_z,inputSchema:Ly},e=>(Tz&&Vy(Tz,e),qy()));var yf=(e,t=!1)=>({content:[{type:"text",text:e}],...t?{isError:!0}:{}});Oz&&Wy.registerTool(Ds,{description:Iz,inputSchema:Hy},async e=>{if(!jz)return yf("No project id available, so this project cannot be checked.",!0);let t=wU(),n;try{n=await vz(jz,Pz(e,{interactionRefId:t,runnerId:Ez,sessionId:Nz}),Ez,Nz)}catch(r){return yf(`Could not raise the setup request: ${r?.message}`,!0)}if(SU("setup request created",{id:n?.id,satisfied:n?.satisfied,items:e.items.length}),n?.satisfied)return yf(zz(e));let i=String(n?.id??"");return i?(Vy(Oz,{refId:t,setupRequestId:i}),qy()):yf("Could not raise the setup request: the API returned no request id.",!0)});$z();await Wy.connect(new ff);
84
84
  //# sourceMappingURL=interactions-mcp-server.js.map
@@ -164,9 +164,7 @@ See the [changelog post](https://www.netlify.com/changelog/2026-10-07-context-us
164
164
 
165
165
  The AI Gateway works with zero configuration: provider credentials and base URLs (`OPENAI_API_KEY`, `OPENAI_BASE_URL`, and equivalents for other providers) are injected into `process.env` automatically. See the `netlify-ai-gateway` skill for supported models and usage.
166
166
 
167
- AI Gateway, Netlify Blobs, and Netlify Database clients can be created once at module scope and reused: the platform keeps their credentials current. One exception: `NETLIFY_PURGE_API_TOKEN` expires after 15 minutes, so read it from `process.env` each time you call the purge API and never store it in a module-level variable. On a server, pass the site ID to `purgeCache` explicitly: `purgeCache({ siteID: getContext().site.id, tags })`.
168
-
169
- Use site-wide Blobs stores (`getStore`). `getDeployStore()` doesn't work on production deploys yet.
167
+ AI Gateway, Netlify Blobs, and Netlify Database clients can be created once at module scope and reused: the platform keeps their credentials current. One exception: `NETLIFY_PURGE_API_TOKEN` expires after 15 minutes, so read it from `process.env` each time you call the purge API and never store it in a module-level variable.
170
168
 
171
169
  ```javascript
172
170
  import OpenAI from 'openai'
@@ -9,7 +9,7 @@ A Netlify Server is one long-running Node.js process deployed alongside your sit
9
9
 
10
10
  ## When to use this skill: explicit request only
11
11
 
12
- Netlify Server is an experimental primitive. The rule for choosing it is strict:
12
+ Netlify Server is in Early Access. The rule for choosing it is strict:
13
13
 
14
14
  - ONLY build with a Netlify Server when the prompt **explicitly requests a "Netlify Server"** by name (for example "build this as a Netlify Server" or "use the Netlify Server primitive").
15
15
  - If the prompt does not name it, discard this skill entirely, even when the request mentions Express, Hono, WebSockets, servers in general, or would otherwise seem like a good fit. In those cases use **Netlify Functions** (see the `netlify-functions` skill) and the other platform primitives as usual.
@@ -168,9 +168,7 @@ See the [changelog post](https://www.netlify.com/changelog/2026-10-07-context-us
168
168
 
169
169
  The AI Gateway works with zero configuration: provider credentials and base URLs (`OPENAI_API_KEY`, `OPENAI_BASE_URL`, and equivalents for other providers) are injected into `process.env` automatically. See the `netlify-ai-gateway` skill for supported models and usage.
170
170
 
171
- AI Gateway, Netlify Blobs, and Netlify Database clients can be created once at module scope and reused: the platform keeps their credentials current. One exception: `NETLIFY_PURGE_API_TOKEN` expires after 15 minutes, so read it from `process.env` each time you call the purge API and never store it in a module-level variable. On a server, pass the site ID to `purgeCache` explicitly: `purgeCache({ siteID: getContext().site.id, tags })`.
172
-
173
- Use site-wide Blobs stores (`getStore`). `getDeployStore()` doesn't work on production deploys yet.
171
+ AI Gateway, Netlify Blobs, and Netlify Database clients can be created once at module scope and reused: the platform keeps their credentials current. One exception: `NETLIFY_PURGE_API_TOKEN` expires after 15 minutes, so read it from `process.env` each time you call the purge API and never store it in a module-level variable.
174
172
 
175
173
  ```javascript
176
174
  import OpenAI from 'openai'
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@netlify/agent-runner-cli",
3
3
  "type": "module",
4
- "version": "2.16.0-server.0",
4
+ "version": "2.16.0-server.1",
5
5
  "description": "CLI tool for running Netlify agents",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",