@lunora/cli
Version:
The Lunora CLI: init, dev, deploy, codegen, migrate, seed, doctor, insights, logs, registry, and the rest of the project commands
2 lines (1 loc) • 46.4 kB
JavaScript
import{d as Ue}from"../packem_shared/command-B6vu47iR.mjs";import{r as De}from"../packem_shared/deploy-target-resolve-BTCW1Iv4.mjs";import{exitCodeForStatus as j,EXIT_CODE as u}from"../packem_shared/EXIT_CODE-DVjUgIaG.mjs";import{writeFileSync as Ae,mkdirSync as Re,renameSync as Me,rmSync as je}from"node:fs";import{findWranglerFile as W,readWranglerJsonc as H,applyModify as Le}from"@lunora/config/cloudflare";import{c as L}from"../packem_shared/cap-error-body-YBKO32BF.mjs";import{r as Y,n as ee,c as Pe}from"../packem_shared/cloudflare-credentials-BVmZnDZk.mjs";import{c as Ge,b as Fe}from"../packem_shared/variables-CPYyJtsL.mjs";import{b as xe}from"../packem_shared/tui-prompts-B3YwUhGw.mjs";import{i as qe}from"../packem_shared/docker-DpVxvYpL.mjs";import{defaultSpawner as P}from"../packem_shared/createRecordingSpawner-01ZngIwK.mjs";import{execArgsFor as K,detectPackageManager as V}from"@lunora/config/cli";import{a as I}from"./cli.mjs";import{resolve as Be,dirname as Je}from"node:path";const We="https://api.cloudflare.com/client/v4/accounts",te=64,re={byok:"https://developers.cloudflare.com/ai-gateway/configuration/bring-your-own-keys/",unifiedBilling:"https://developers.cloudflare.com/ai-gateway/features/unified-billing/"},$=(e,t,r)=>(e.error(r),{code:t,error:r}),oe=async(e,t,r,o={})=>{const n=await e(t,{...o,headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"}}),a=await n.text();let s;try{s=JSON.parse(a)}catch{}return{body:s,ok:n.ok&&s?.success!==!1,status:n.status,text:a}},ne=e=>{const t=(e.body?.errors??[]).map(r=>r.message).filter(r=>typeof r=="string");return`${String(e.status)}: ${L(t.length>0?t.join("; "):e.text)}`},He=(e,t,r)=>{let o=e;const n=[];for(const[a,s]of Object.entries(r))t?.[a]!==s&&(o=Le(o,["vars",a],s),n.push(a));return{changed:n,text:o}},Ye=(e,t)=>{e.info(`next: \`<provider>/<model>\` slugs (e.g. "anthropic/claude-sonnet-5") now route through gateway "${t}". Before the first call,`),e.info(` configure how the provider is paid for in the Cloudflare dashboard (AI > AI Gateway > ${t}):`),e.info(` - Unified Billing (OpenAI, Anthropic, Google, xAI, Groq, DeepSeek): load credits — ${re.unifiedBilling}`),e.info(` - a key stored on the gateway (Mistral, Perplexity, OpenRouter, …): ${re.byok}`),e.info(" Then redeploy (`lunora deploy`) so the Worker picks up the new vars.")},Ke=async(e,t,r,o,n,a)=>{const s=`${We}/${encodeURIComponent(r)}/ai-gateway/gateways`,i=await oe(t,`${s}/${encodeURIComponent(n)}`,o);if(i.ok){const d=i.body?.result?.collect_logs;return{action:"existing",collectLogs:typeof d=="boolean"?d:a}}if(i.status===401||i.status===403)return $(e,j(i.status),`ai-gateway: could not read AI Gateway "${n}" (${ne(i)})`);const c=await oe(t,s,o,{body:JSON.stringify({cache_invalidate_on_update:!0,cache_ttl:0,collect_logs:a,id:n,rate_limiting_interval:0,rate_limiting_limit:0}),method:"POST"});return c.ok?{action:"created",collectLogs:a}:$(e,j(c.status),`ai-gateway: could not create AI Gateway "${n}" (${ne(c)})`)},Ve=e=>{const{logger:t}=e,r=W(e.cwd);if(r===void 0)return $(t,u.NOT_FOUND,"ai-gateway: no wrangler.jsonc found — run `lunora init` (or `lunora dev`) first.");const{parsed:o,text:n}=H(r);if(o===void 0)return $(t,u.USAGE,`ai-gateway: could not parse ${r} as JSONC.`);const a=ee(e.id)??ee(o.name);return a===void 0?$(t,u.USAGE,"ai-gateway: no gateway id — pass --id <id>, or set `name` in wrangler.jsonc."):a.length>te?$(t,u.USAGE,`ai-gateway: gateway id "${a}" is longer than ${String(te)} characters — pass a shorter --id.`):{gatewayId:a,parsed:o,text:n,wranglerPath:r}},G=e=>e?"on":"off",ze=(e,t)=>(e.info(`ai-gateway (dry run): would create or reuse AI Gateway "${t.gatewayId}" (log collection ${G(t.collectLogs)}).`),e.info(t.varsWritten.length===0?`ai-gateway (dry run): ${t.wranglerPath} vars are already up to date.`:`ai-gateway (dry run): would write ${t.varsWritten.join(", ")} into ${t.wranglerPath} vars.`),t.accountId===void 0&&e.warn("ai-gateway (dry run): no CLOUDFLARE_ACCOUNT_ID (or `account_id` in wrangler.jsonc) — a real run will stop there."),{code:0,data:t}),Xe=async e=>{const{logger:t}=e,r=Ve(e);if("code"in r)return r;const{gatewayId:o,parsed:n,text:a,wranglerPath:s}=r,i=e.logs!==!1,{accountId:c,token:d}=Y(e.environment??process.env,n.account_id),f={[Fe]:o,...c===void 0?{}:{[Ge]:c}},l=He(a,n.vars,f);if(e.dryRun===!0)return ze(t,{accountId:c,action:"planned",collectLogs:i,dryRun:!0,gatewayId:o,varsWritten:l.changed,wranglerPath:s});if(d===void 0||c===void 0){const g=[d===void 0?"CLOUDFLARE_API_TOKEN (an API token with AI Gateway Write)":void 0,c===void 0?"CLOUDFLARE_ACCOUNT_ID (or `account_id` in wrangler.jsonc)":void 0].filter(y=>y!==void 0);return $(t,u.AUTH,`ai-gateway: missing ${g.join(" and ")}.`)}const h=await Ke(t,e.fetch??globalThis.fetch.bind(globalThis),c,d,o,i);return"code"in h?h:(h.action==="created"?t.success(`ai-gateway: created AI Gateway "${o}" (log collection ${G(h.collectLogs)}).`):t.info(`ai-gateway: AI Gateway "${o}" already exists — reusing it (log collection ${G(h.collectLogs)}; change it in the dashboard).`),l.changed.length>0?(Ae(s,l.text,"utf8"),t.success(`ai-gateway: wrote ${l.changed.join(", ")} into ${s} vars.`)):t.info(`ai-gateway: ${s} vars are already up to date.`),Ye(t,o),{code:0,data:{accountId:c,action:h.action,collectLogs:h.collectLogs,dryRun:!1,gatewayId:o,varsWritten:l.changed,wranglerPath:s}})},Qe=15e3,Ze="https://api.cloudflare.com/client/v4/graphql",E={analytics:"Account Analytics Read",notificationsRead:"Notifications Read",notificationsWrite:"Notifications Write"},et={auth:u.AUTH,failure:u.FAILURE,"not-found":u.NOT_FOUND,permission:u.PERMISSION,"rate-limited":u.RATE_LIMITED,rejected:u.USAGE,unavailable:u.UNAVAILABLE};class _ extends Error{kind;status;constructor(t,r,o){super(t),this.name="CloudflareApiError",this.kind=r,this.status=o}}const tt=e=>e===401?"auth":e===403?"permission":e===404?"not-found":e===429?"rate-limited":e>=500?"unavailable":"rejected",Se=e=>{try{const r=JSON.parse(e);if(Array.isArray(r.errors)){const o=r.errors.map(n=>typeof n=="object"&&n!==null&&"message"in n?String(n.message):void 0).filter(n=>n!==void 0&&n.length>0);if(o.length>0)return o}}catch{}const t=e.trim().slice(0,200);return t.length>0?[t]:[]},rt=9106,ot=(e,t)=>{try{const r=JSON.parse(e);return Array.isArray(r.errors)&&r.errors.some(o=>typeof o=="object"&&o!==null&&"code"in o&&o.code===t)}catch{return!1}},ae=(e,t,r,o)=>{const n=t===400&&ot(r,rt)?"auth":tt(t),a=Se(r).join("; ");let s="";return n==="auth"?s=` — check that CLOUDFLARE_API_TOKEN is a valid token; it needs the "${o}" permission on this account`:n==="permission"&&(s=` — the API token needs the "${o}" permission on this account`),new _(`${e} failed (HTTP ${String(t)})${a.length>0?`: ${a}`:""}${s}.`,n,t)},se=(e,t)=>{const r=t instanceof Error&&(t.name==="TimeoutError"||t.name==="AbortError");let o=t instanceof Error?t.message:String(t);return r&&(o="no answer in time"),new _(`${e} failed: ${o}.`,"unavailable")},nt=10,at=e=>{const{accountId:t,token:r}=e,o=e.timeoutMs??Qe,n=e.fetch??globalThis.fetch.bind(globalThis),a=async(s,i,c,d)=>{let f;try{f=await Pe({accountId:t,apiToken:r,fetch:n,init:{method:i,signal:AbortSignal.timeout(o),...d===void 0?{}:{body:JSON.stringify(d)}},path:c})}catch(l){throw se(s,l)}if(!f.ok)throw ae(s,f.status,f.text,i==="GET"?E.notificationsRead:E.notificationsWrite);return f.body};return{accountId:t,graphql:async(s,i,c={})=>{let d;try{d=await n(Ze,{body:JSON.stringify({query:i,variables:c}),headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(o)})}catch(l){throw se(s,l)}const f=await d.text();if(!d.ok)throw ae(s,d.status,f,E.analytics);try{const l=JSON.parse(f);return{data:l.data??void 0,errors:Array.isArray(l.errors)&&l.errors.length>0?Se(f):[]}}catch{throw new _(`${s} failed: the GraphQL API answered with something that is not JSON.`,"unavailable",d.status)}},notifications:async(s,i,c,d)=>(await a(s,i,c,d)).result,notificationsList:async(s,i,c=nt)=>{const d=async f=>{const l=await a(s,"GET",`${i}${i.includes("?")?"&":"?"}page=${String(f)}`),h=Array.isArray(l.result)?l.result:[],g=l.result_info;return typeof g?.total_pages=="number"&&f<g.total_pages&&f<c?[...h,...await d(f+1)]:h};return d(1)}}},A="billing_usage_alert",ie={"do-duration":"worker_durable_objects_duration","do-requests":"worker_durable_objects_requests","do-rows-read":"worker_durable_objects_storage_reads","do-rows-written":"worker_durable_objects_storage_writes","workers-requests":"worker_requests"},st=6,C=e=>typeof e=="object"&&e!==null&&!Array.isArray(e)?e:void 0,U=(e,t)=>{const r=t.toLowerCase(),o=Object.keys(e).find(n=>n.toLowerCase()===r);return o===void 0?void 0:e[o]},F=(e,t)=>{for(const r of t){const o=U(e,r);if(typeof o=="string"&&o.length>0)return o}},it=e=>{if(typeof e=="string"&&e.length>0)return{id:e,source:"available-alerts"};const t=C(e),r=t===void 0?void 0:F(t,["id","value","key","name"]);if(t===void 0||r===void 0)return;const o=F(t,["description","label","display_name","displayName","name"]);return{id:r,source:"available-alerts",...o===void 0||o===r?{}:{label:o}}},ct=["AvailableValues","available_values","values","options","enum","choices"],_e=e=>e.map(t=>it(t)).filter(t=>t!==void 0),dt=e=>F(e,["key","name","filter","id"])!=="product"?[]:ct.flatMap(t=>{const r=U(e,t);return Array.isArray(r)?_e(r):[]}),x=(e,t,r)=>{if(t>st)return;if(Array.isArray(e)){for(const n of e)x(n,t+1,r);return}const o=C(e);if(o!==void 0){r.push(...dt(o));for(const[n,a]of Object.entries(o))n.toLowerCase()==="product"&&Array.isArray(a)?r.push(..._e(a)):typeof a=="object"&&a!==null&&x(a,t+1,r)}},lt=e=>{const t=C(e);if(t!==void 0){for(const r of Object.values(t))if(Array.isArray(r)){const o=r.map(n=>C(n)).find(n=>n!==void 0&&U(n,"type")===A);if(o!==void 0)return o}}},ut=e=>e.filter(t=>t.alert_type===A&&Array.isArray(t.filters?.product)).flatMap(t=>(t.filters?.product).filter(r=>typeof r=="string"&&r.length>0)).map(t=>({id:t,source:"existing-policy"})),ft=(e,t,r)=>{const o=lt(e),n=[];o!==void 0&&x(U(o,"filter_options"),0,n);const a=new Map;for(const s of[...n,...ut(r)])a.has(s.id)||a.set(s.id,s);return{eligible:t?o!==void 0:void 0,products:[...a.values()]}},mt=/[^a-z\d]+/u,ht=e=>`${e.id} ${e.label??""}`.toLowerCase().split(mt).filter(t=>t.length>0),gt={"d1-rows-read":{all:[["d1"],["read"]]},"d1-rows-written":{all:[["d1"],["writ"]]},"do-duration":{all:[["durable"],["duration"]]},"do-requests":{all:[["durable"],["request"]]},"do-rows-read":{all:[["durable"],["read"]],none:["request"]},"do-rows-written":{all:[["durable"],["writ"]]},"workers-cpu":{all:[["worker"],["cpu"]],none:["durable"]},"workers-requests":{all:[["worker"],["request"]],none:["durable","cpu","kv","d1"]}},pt=(e,t)=>{const r=ht(t),o=s=>r.some(i=>i.startsWith(s)),{all:n,none:a=[]}=gt[e];return n.every(s=>s.some(i=>o(i)))&&!a.some(s=>o(s))},Ee=(e,t)=>{const r=ie[e],o=r===void 0?void 0:t.find(s=>s.id===r);if(o!==void 0)return{product:o,status:"matched"};const n=new Set(Object.values(ie)),a=t.filter(s=>!n.has(s.id)&&pt(e,s));return a.length===1&&a[0]!==void 0?{product:a[0],status:"matched"}:a.length>1?{candidates:a.map(s=>s.id),status:"ambiguous"}:r===void 0?{status:"no-product"}:{product:{id:r,source:"documented"},status:"matched"}},ce="workersInvocationsAdaptive",yt="durableObjectsInvocationsAdaptiveGroups",M="durableObjectsPeriodicGroups",de="d1AnalyticsAdaptiveGroups",S=[{candidates:[{field:"requests",unit:"count"}],dataset:ce,filter:"datetime",id:"workers-requests",label:"Workers requests"},{candidates:[{field:"cpuTimeUs",unit:"microseconds"}],dataset:ce,filter:"datetime",id:"workers-cpu",label:"Workers CPU time"},{candidates:[{field:"requests",unit:"count"}],dataset:yt,filter:"date",id:"do-requests",label:"Durable Objects requests"},{candidates:[{field:"activeTime",unit:"unknown"}],dataset:M,filter:"date",id:"do-duration",label:"Durable Objects duration"},{candidates:[{field:"rowsRead",unit:"count"}],dataset:M,filter:"date",id:"do-rows-read",label:"Durable Objects rows read"},{candidates:[{field:"rowsWritten",unit:"count"}],dataset:M,filter:"date",id:"do-rows-written",label:"Durable Objects rows written"},{candidates:[{field:"rowsRead",unit:"count"}],dataset:de,filter:"datetime-hour",id:"d1-rows-read",label:"D1 rows read"},{candidates:[{field:"rowsWritten",unit:"count"}],dataset:de,filter:"datetime-hour",id:"d1-rows-written",label:"D1 rows written"}],le=e=>String(e).padStart(2,"0"),vt=e=>{const t=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth()-1,1)),r=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),0)),o=new Date(Date.UTC(e.getUTCFullYear(),e.getUTCMonth(),1)),n=a=>`${String(a.getUTCFullYear())}-${le(a.getUTCMonth()+1)}-${le(a.getUTCDate())}`;return{endDate:n(r),nextStartDate:n(o),startDate:n(t)}},wt=(e,t)=>{const r=JSON.stringify(`${t.startDate}T00:00:00Z`),o=JSON.stringify(`${t.nextStartDate}T00:00:00Z`);switch(e){case"date":return`{ date_geq: ${JSON.stringify(t.startDate)}, date_lt: ${JSON.stringify(t.nextStartDate)} }`;case"datetime":return`{ datetime_geq: ${r}, datetime_lt: ${o} }`;default:return`{ AND: [{ datetimeHour_geq: ${r}, datetimeHour_lt: ${o} }] }`}},bt=e=>`query LunoraTypeFields { __type(name: ${JSON.stringify(e)}) { fields { name type { name ofType { name ofType { name ofType { name } } } } } } }`,$t=e=>{let t=e;for(;t!=null;){if(typeof t.name=="string"&&t.name.length>0)return t.name;t=t.ofType}},At=e=>{const t=new Map;return async r=>{const o=t.get(r);if(o!==void 0)return o;const n=e.graphql(`Reading the analytics schema (${r})`,bt(r)).then(a=>{if(a.errors.length>0)throw new _(`Reading the analytics schema failed: ${a.errors.join("; ")}.`,"rejected");const i=a.data?.__type?.fields??[];return new Map(i.map(c=>[c.name,$t(c.type)]))});return t.set(r,n),n}},Te=async(e,t,r)=>{const o=await e(t),[n,...a]=r;if(n===void 0)return o;const s=o.get(n);if(s===void 0)throw new _(`the analytics schema has no \`${n}\` on \`${t}\``,"rejected");return Te(e,s,a)},St=e=>e instanceof Error?e.message:String(e),_t=async(e,t,r,o,n)=>{const{id:a,label:s}=o;try{const i=await Te(t,r,["viewer","accounts",o.dataset,"sum"]),c=o.candidates.find(y=>i.has(y.field));if(c===void 0){const y=o.candidates.map(m=>`\`${m.field}\``).join(", ");return{id:a,label:s,reason:`\`${o.dataset}.sum\` exposes no ${y} field`,status:"unavailable"}}const d=`query LunoraUsage($accountTag: string) { viewer { accounts(filter: { accountTag: $accountTag }) { ${o.dataset}(limit: 1, filter: ${wt(o.filter,n)}) { sum { ${c.field} } } } } }`,f=await e.graphql(`Reading ${s}`,d,{accountTag:e.accountId});if(f.errors.length>0)return{id:a,label:s,reason:f.errors.join("; "),status:"unavailable"};const l=f.data?.viewer?.accounts?.[0]?.[o.dataset],h=Array.isArray(l)?l[0]:void 0,g=h===void 0?0:h.sum?.[c.field];return typeof g!="number"||!Number.isFinite(g)?{id:a,label:s,reason:`the response carried no number for \`${c.field}\``,status:"unavailable"}:{field:`${o.dataset}.sum.${c.field}`,id:a,label:s,status:"ok",unit:c.unit,value:g}}catch(i){return{id:a,label:s,reason:St(i),status:"unavailable"}}},Et=async(e,t)=>{const r=await e.graphql("Reading the analytics schema","query LunoraQueryRoot { __schema { queryType { name } } }"),o=r.data?.__schema?.queryType?.name;if(r.errors.length>0||o===void 0){const a=r.errors.length>0?r.errors.join("; "):"the schema root could not be read";return S.map(({id:s,label:i})=>({id:s,label:i,reason:a,status:"unavailable"}))}const n=At(e);return Promise.all(S.map(async a=>_t(e,n,o,a,t)))},D="Lunora usage: ",Tt=3,z="Cloudflare does not document the unit of a usage alert's limit; limits are sent as plain request counts. Check the stored limit in the dashboard.",kt={"do-requests":1e6,"workers-requests":1e7},Ot=100,It=e=>{const t=Math.ceil(e);if(t<=0)return 0;const r=10**Math.max(0,Math.floor(Math.log10(t))-1);return Math.ceil(t/r)*r},Ct=(e,t,r)=>{const o=It(e*t);return o>r?{basis:"history",threshold:o}:{basis:"floor",threshold:r}},q=e=>{const t=e.filters?.product;return Array.isArray(t)?t.filter(r=>typeof r=="string"):[]},Nt=e=>{const t=e.filters?.limit,r=Array.isArray(t)?t[0]:void 0,o=typeof r=="string"?Number(r):Number.NaN;return Number.isFinite(o)?o:void 0},X=e=>{const t={};for(const[r,o]of Object.entries(e?.mechanisms??{}))Array.isArray(o)&&(t[r]=o.filter(n=>typeof n=="object"&&n!==null&&"id"in n&&typeof n.id=="string"));return t},Ut=(e,t)=>{const r={};for(const[o,n]of[...Object.entries(e),...Object.entries(t)]){const a=new Set((r[o]??[]).map(s=>s.id));r[o]=[...r[o]??[],...n.filter(s=>!a.has(s.id))]}return Object.fromEntries(Object.entries(r).filter(([,o])=>o.length>0))},N=e=>Object.entries(e).flatMap(([t,r])=>r.map(o=>`${t}:${o.id}`)),Dt=(e,t)=>JSON.stringify(N(e).toSorted((r,o)=>r.localeCompare(o)))===JSON.stringify(N(t).toSorted((r,o)=>r.localeCompare(o))),Rt=(e,t)=>e.enabled===!0&&e.name===t.name&&JSON.stringify(e.filters?.limit)===JSON.stringify(t.filters.limit)&&JSON.stringify(q(e))===JSON.stringify(t.filters.product)&&Dt(X(e),t.mechanisms),Mt=(e,t)=>{const r={};return e.length>0&&(r.email=e.map(o=>({id:o}))),t.length>0&&(r.webhooks=t.map(o=>({id:o}))),r},jt=(e,t,r)=>{const o=r.thresholds?.[e],n=r.usage.find(c=>c.id===e),a=n?.status==="ok"?n.value:void 0;if(o!==void 0)return{basis:"explicit",lastMonth:a,threshold:o};const s=kt[e];return s===void 0?{kind:"unit-unverified",reason:`the unit Cloudflare meters "${t.id}" in is not documented and cannot be matched to the usage reading; set one yourself with --threshold ${e}=<n>`}:a!==void 0?{...Ct(a,r.multiplier,s),lastMonth:a}:r.allowFloor===!0?{basis:"floor-usage-unavailable",lastMonth:void 0,threshold:s}:{kind:"usage-unavailable",reason:`last month's usage could not be read (${n?.status==="unavailable"?n.reason:"no reading"}), and an alert at the floor could fire every month on an account that already exceeds it; pass --threshold ${e}=<n>, or --allow-floor`}},Lt=(e,t)=>{const r=Nt(e);if(r===void 0||t===void 0||r<=0||t<=0)return;const o=Math.max(r/t,t/r);return o>=Ot?`its limit ${String(r)} differs from the ${String(t)} this would plan by ${String(Math.round(o))}× — one of the two may be in the wrong unit`:void 0},B=e=>`${D}${e}`,Pt=(e,t)=>e===void 0?"create":Rt(e,t)?"unchanged":"update",Gt=(e,t)=>{switch(e){case"explicit":return"set with --threshold";case"floor-usage-unavailable":return"the included monthly allowance, applied with --allow-floor because usage could not be read";case"history":return`${String(t)}× last month`;default:return"the included monthly allowance (floor)"}},Ft=(e,t,r)=>{const{id:o,label:n}=e,a=Ee(o,t.products);if(a.status==="no-product")return{kind:"no-product",metric:o,reason:"Cloudflare has no usage alert for this product; the account-wide budget alert is its guard"};if(a.status==="ambiguous")return{kind:"ambiguous",metric:o,reason:`several offered products could mean this metric (${a.candidates.join(", ")})`};const{product:s}=a,i=r.find(p=>p.name===B(n)),c=r.find(p=>p.enabled===!0&&p.name?.startsWith(D)!==!0&&q(p).includes(s.id)),d=jt(o,s,t);if(i===void 0&&c!==void 0){const p=Lt(c,"threshold"in d?d.threshold:void 0);return{kind:"covered",metric:o,reason:`already alerted by the existing policy "${c.name??c.id??"?"}"`,...p===void 0?{}:{warning:p}}}if(!("threshold"in d))return{...d,metric:o};const f=Mt(t.emails,t.webhooks),l=X(i),h=i===void 0||t.replaceRecipients===!0?f:Ut(l,f),g=new Set(N(l)),y=new Set(N(h)),m={alert_type:A,description:`Managed by \`lunora cloudflare alerts setup\`: ${Gt(d.basis,t.multiplier)}.`,enabled:!0,filters:{limit:[String(d.threshold)],product:[s.id]},mechanisms:h,name:B(n)},v=i===void 0?[]:q(i);return{action:Pt(i,m),basis:d.basis,body:m,lastMonth:d.lastMonth,metric:o,...i?.id===void 0?{}:{policyId:i.id},...i!==void 0&&JSON.stringify(v)!==JSON.stringify([s.id])?{previousProduct:v}:{},product:s.id,productSource:s.source,recipients:{added:[...y].filter(p=>!g.has(p)),removed:[...g].filter(p=>!y.has(p))},threshold:d.threshold}},xt=e=>{const t=e.policies.filter(n=>n.alert_type===A),r=new Set(S.map(n=>B(n.label))),o={alerts:[],duplicates:[],orphans:t.filter(n=>n.name?.startsWith(D)===!0&&!r.has(n.name)),skipped:[]};for(const n of r)o.duplicates.push(...t.filter(a=>a.name===n).slice(1));for(const n of S){const a=Ft(n,e,t);"action"in a?o.alerts.push(a):o.skipped.push(a)}return o},qt="Cloudflare's API has no endpoint that sends a test notification — not for webhooks and not for email — so `lunora cloudflare alerts test` sends nothing.",Bt="To test a webhook for real, create it in the dashboard (Alerts > Destinations > Webhooks > Create) and finish with Save and Test.",Jt="Email destinations cannot be tested at all: Cloudflare offers no test send for them. Check the address on each policy below instead.",ue=e=>Array.isArray(e)?e:[],fe=30,Wt=async(e,t,r,o,n)=>{const a=t.filter(m=>m.alert_type===A&&m.name?.startsWith(D)===!0),s=a.map(m=>X(m)),i=new Set([...s.flatMap(m=>(m.webhooks??[]).map(v=>v.id)),...r]),c=[...new Set(s.flatMap(m=>(m.email??[]).map(v=>v.id)))],d=new Set(a.map(m=>m.id)),f=new Date(n.getTime()-fe*24*60*60*1e3).toISOString(),[l,h]=await Promise.all([e.notifications("Listing webhook destinations","GET","/alerting/v3/destinations/webhooks"),a.length===0?Promise.resolve([]):e.notificationsList("Reading notification history",`/alerting/v3/history?per_page=25&since=${encodeURIComponent(f)}`)]),g=ue(l).map(m=>({...m,referenced:m.id!==void 0&&i.has(m.id)})),y=ue(h).filter(m=>m.alert_type===A&&m.policy_id!==void 0&&d.has(m.policy_id));o.warn(qt),a.length===0&&o.info("No `lunora cloudflare alerts setup` policies exist on this account yet — run `lunora cloudflare alerts setup` first.");for(const m of[...i].filter(v=>!g.some(p=>p.id===v)))o.warn(`Webhook destination ${m} is referenced but does not exist on this account.`);for(const m of g.filter(v=>v.referenced))o.info(`webhook "${m.name??m.id??"?"}" (${m.type??"generic"}): last delivered ${m.last_success??"never"}, last failed ${m.last_failure??"never"}`);if(o.info(Bt),c.length>0&&o.info(`${Jt} Addresses on the policies: ${c.join(", ")}.`),y.length===0)o.info(`Cloudflare's notification history shows nothing sent for these policies in the last ${String(fe)} days (they only fire once a threshold is crossed).`);else for(const m of y)o.info(`sent ${m.sent??"?"}: "${m.name??"?"}" via ${m.mechanism_type??"?"} to ${m.mechanism??"?"}`);return{emails:c,history:y,testSent:!1,webhooks:g}},R="Budget alert (account-wide USD threshold): Cloudflare documents no API to create or read one, so this cannot be checked. Create it in the dashboard: Manage Account > Billing > Billable Usage > Create budget alert.",Ht="Alerts > Overview > Create an Alert, alert type Usage Based Billing",b=(e,t,r,o)=>(e.error(r),{code:t,error:r,...o===void 0?{}:{data:o}}),Q=(e,t,r)=>{if(t instanceof _)return b(e,et[t.kind],t.message,r);throw t},Z=(e,t)=>({accountId:e.accountId,budgetAlert:{checkable:!1,dashboardPath:"Manage Account > Billing > Billable Usage"},limitUnitCaveat:z,...t===void 0?{}:{eligible:t.discovery.eligible,period:t.period,products:t.discovery.products}}),O=e=>e.toLocaleString("en-US",{maximumFractionDigits:0}),ke=(e,t)=>{t===!1&&e.warn("This account is not eligible for Usage Based Billing notifications (they are offered to Pay-as-you-go accounts; most Enterprise contracts are not supported).")},Oe=e=>({code:u.SUCCESS,data:e}),Yt=/^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/u,Ie=new Set(S.map(e=>e.id)),Kt=e=>Ie.has(e),Vt=e=>{const t={};for(const r of e.flatMap(o=>o.split(","))){const[o="",n=""]=r.split("=").map(s=>s.trim()),a=Number(n);if(!Kt(o))return{error:`--threshold "${r}": unknown metric "${o}" — one of ${[...Ie].join(", ")}.`};if(!Number.isFinite(a)||a<=0)return{error:`--threshold "${r}": the limit must be a positive number.`};t[o]=Math.ceil(a)}return t},zt=e=>{const t=(e.emails??[]).flatMap(s=>s.split(",")).map(s=>s.trim()).filter(s=>s.length>0),r=(e.webhooks??[]).map(s=>s.trim()).filter(s=>s.length>0),o=e.multiplier===void 0?Tt:Number(e.multiplier),n=Vt(e.thresholds??[]);if(t.length===0&&r.length===0)return{error:"alerts setup needs somewhere to send the alerts: pass --email <address> and/or --webhook <destination-id>."};const a=t.find(s=>!Yt.test(s));return a!==void 0?{error:`--email "${a}" is not an email address.`}:!Number.isFinite(o)||o<1?{error:`--multiplier must be a number of at least 1 (got "${e.multiplier??""}").`}:"error"in n?n:{allowFloor:e.allowFloor===!0,emails:t,multiplier:o,replaceRecipients:e.replaceRecipients===!0,thresholds:n,webhooks:r}},Xt={explicit:()=>"set with --threshold",floor:e=>`floor: included monthly allowance; last month ${O(e.lastMonth??0)}`,"floor-usage-unavailable":()=>"floor: usage could not be read, applied with --allow-floor",history:e=>`last month ${O(e.lastMonth??0)}`},Qt=e=>{const t=[...e.added.map(r=>`+${r}`),...e.removed.map(r=>`-${r}`)];return t.length===0?"recipients unchanged":`recipients ${t.join(" ")}`},Zt=e=>{const t=e.productSource==="documented"?"documented id":"offered by the account",r=e.previousProduct===void 0?"":`, product was ${e.previousProduct.join(", ")||"none"}`;return`${e.action.padEnd(9)} ${e.body.name} — product ${e.product} (${t}${r}), limit ${O(e.threshold)} (${Xt[e.basis](e)}); ${Qt(e.recipients)}`},J=e=>S.find(t=>t.id===e)?.label??e,er=(e,t,r,o)=>{o.info(`Usage alerts at ${String(t)}× last month (${r.startDate} to ${r.endDate}), never below the included allowance:`);for(const n of e.alerts)o.info(` ${Zt(n)}`);for(const n of e.skipped)o.info(` skipped ${J(n.metric)} — ${n.reason}`),n.warning!==void 0&&o.warn(`${J(n.metric)}: the existing alert ${n.warning}.`);for(const n of e.orphans)o.warn(`"${n.name??"?"}" (${n.id??"?"}) is named like a \`lunora cloudflare alerts\` policy but matches no metric; it was left alone.`);for(const n of e.duplicates)o.warn(`"${n.name??"?"}" exists more than once; ${n.id??"?"} was left alone — delete the extra one in the dashboard.`);e.skipped.some(n=>n.kind==="unit-unverified"||n.kind==="ambiguous")&&o.info(`To set the skipped ones by hand: ${Ht}.`),o.info(z),o.info(R)},tr=async(e,t)=>{if(e.yes===!0)return;if(e.confirm===void 0&&(!process.stdin.isTTY||e.format==="json"))return b(e.logger,u.USAGE,"alerts setup: refusing to change notification policies without confirmation — re-run with --yes.");const r=t.flatMap(a=>a.recipients.removed),o=r.length===0?"":` This REMOVES recipients: ${[...new Set(r)].join(", ")}.`;if(!await(e.confirm??xe)(`Apply ${String(t.length)} notification policy change(s)?${o}`))return e.logger.info("alerts setup: aborted"),{code:u.CANCELLED,error:"alerts setup: aborted at the confirmation prompt"}},rr=async(e,t,r,o)=>{const a=(await Promise.all(t.map(async s=>e.notifications(`Reading webhook destination ${s}`,"GET",`/alerting/v3/destinations/webhooks/${encodeURIComponent(s)}`).then(()=>{},i=>({error:i,id:s}))))).find(s=>s!==void 0);if(a!==void 0)return a.error instanceof _&&a.error.kind==="not-found"?b(r,u.USAGE,`--webhook ${a.id}: no such webhook destination on account ${e.accountId}.`,o):Q(r,a.error,o)},or=async(e,t)=>{const r=t.action==="create"?await e.notifications(`Creating "${t.body.name}"`,"POST","/alerting/v3/policies",t.body):await e.notifications(`Updating "${t.body.name}"`,"PUT",`/alerting/v3/policies/${encodeURIComponent(t.policyId??"")}`,t.body);return typeof r=="object"&&r!==null&&"id"in r&&typeof r.id=="string"?r.id:t.policyId},nr=async(e,t,r)=>await Promise.all(t.map(async({alert:n,id:a})=>{try{const s=await e.notifications(`Reading back "${n.body.name}"`,"GET",`/alerting/v3/policies/${encodeURIComponent(a)}`),i={enabled:s?.enabled,id:a,limit:s?.filters?.limit,name:s?.name,product:s?.filters?.product};return r.info(`Cloudflare stored "${i.name??n.body.name}": product ${JSON.stringify(i.product)}, limit ${JSON.stringify(i.limit)}.`),JSON.stringify(i.limit)!==JSON.stringify(n.body.filters.limit)&&r.warn(`"${n.body.name}": Cloudflare stored limit ${JSON.stringify(i.limit)}, not the ${JSON.stringify(n.body.filters.limit)} sent.`),i}catch(s){return r.warn(`Could not read "${n.body.name}" back: ${s instanceof Error?s.message:String(s)}`),{id:a}}})),ar=async(e,t,r,o)=>{const n=[],a=[];let s;for(const c of t)try{const d=await or(e,c);n.push({action:c.action,metric:c.metric,...d===void 0?{}:{policyId:d}}),d!==void 0&&a.push({alert:c,id:d}),r.success(`${c.action==="create"?"Created":"Updated"} "${c.body.name}".`)}catch(d){s=d;break}const i={...o,applied:n,stored:await nr(e,a,r)};return s===void 0?i:Q(r,s,i)},me=(e,t,r)=>{const o=t.skipped.filter(n=>n.kind==="usage-unavailable");return o.length>0?b(r,u.FAILURE,`alerts setup: no alert was set for ${o.map(n=>J(n.metric)).join(", ")} because last month's usage could not be read — pass --threshold <metric>=<n>, or --allow-floor to accept the floor.`,e):{code:u.SUCCESS,data:e}},sr=async(e,t,r,o)=>{const{logger:n}=r,a=Z(e,t),s=await rr(e,o.webhooks,n,a);if(s!==void 0)return s;if(t.discovery.eligible===!1)return ke(n,!1),n.info(R),b(n,u.PERMISSION,"alerts setup: the account cannot create Usage Based Billing notifications.",a);const i=xt({...o,policies:t.policies,products:t.discovery.products,usage:t.usage});a.plan=i,er(i,o.multiplier,t.period,n);const c=i.alerts.filter(l=>l.action!=="unchanged");if(r.dryRun===!0||c.length===0)return n.info(r.dryRun===!0?"Dry run: nothing was changed.":"Every alert is already up to date."),me(a,i,n);const d=await tr(r,c);if(d!==void 0)return{...d,data:a};const f=await ar(e,c,n,a);return"code"in f?f:me(f,i,n)},ir=e=>e.status!=="ok"?`unavailable (${e.reason})`:e.unit==="microseconds"?`${O(e.value)} µs`:O(e.value),cr=(e,t,r)=>t?"no-product":e===void 0?"unknown":r.some(o=>o.enabled)?"active":r.length>0?"disabled-only":"none",he=e=>e.map(t=>`"${t.name??t.id??"?"}" (limit ${JSON.stringify(t.limit)})${t.enabled?"":" — disabled, not protecting you"}`).join(", "),dr={active:e=>`alert: ${he(e.alerts)}`,"disabled-only":e=>`alert: none active; ${he(e.alerts)}`,"no-product":()=>"alert: none possible — Cloudflare has no usage alert for this; the budget alert is its guard",none:()=>"alert: none",unknown:()=>"alert: unknown (several offered products could mean this)"},lr=(e,t,r)=>{const o=Ee(e.id,t.discovery.products),n=o.status==="matched"?o.product:void 0,a=(n===void 0?[]:r.filter(s=>Array.isArray(s.filters?.product)&&s.filters.product.includes(n.id))).map(s=>({enabled:s.enabled===!0,id:s.id,limit:s.filters?.limit,name:s.name}));return{alerts:a,coverage:cr(n?.id,o.status==="no-product",a),id:e.id,label:e.label,...n===void 0?{}:{product:n.id,productSource:n.source},usage:e}},ur=(e,t,r)=>{const o=t.policies.filter(a=>a.alert_type===A),n=t.usage.map(a=>lr(a,t,o));r.info(`Cloudflare account ${e.accountId} — usage ${t.period.startDate} to ${t.period.endDate}`);for(const a of n)r.info(` ${a.label}: ${ir(a.usage)} — ${dr[a.coverage](a)}`);return r.info(`Usage Based Billing notifications on the account: ${String(o.length)} (${String(o.filter(a=>a.enabled===!0).length)} enabled).`),r.info(z),ke(r,t.discovery.eligible),r.info(R),Oe({...Z(e,t),metrics:n})},fr=e=>{const t=W(e);return t===void 0?void 0:H(t).parsed?.account_id},mr=e=>{const{accountId:t,token:r}=Y(e.environment??process.env,fr(e.cwd));return r===void 0?b(e.logger,u.AUTH,`CLOUDFLARE_API_TOKEN is not set. \`lunora cloudflare alerts\` calls the Cloudflare API with an API token (a wrangler login session is not reused); create one with the "${E.notificationsWrite}" and "${E.analytics}" permissions ("${E.notificationsRead}" is enough for status and test).`):t===void 0?b(e.logger,u.USAGE,"No Cloudflare account: set CLOUDFLARE_ACCOUNT_ID or `account_id` in wrangler.jsonc."):at({accountId:t,token:r,...e.fetch===void 0?{}:{fetch:e.fetch}})},Ce=async e=>{const t=await e.notifications("Listing notification policies","GET","/alerting/v3/policies");return Array.isArray(t)?t:[]},hr=async(e,t)=>{const r=vt(t.now??new Date),{logger:o}=t,[n,a,s]=await Promise.all([Ce(e),e.notifications("Listing the alert types this account is eligible for","GET","/alerting/v3/available_alerts").then(i=>({ok:!0,value:i}),i=>({error:i,ok:!1})),Et(e,r).catch(i=>{const c=i instanceof Error?i.message:String(i);return o.warn(`Usage could not be read: ${c}`),S.map(({id:d,label:f})=>({id:d,label:f,reason:c,status:"unavailable"}))})]);return a.ok||o.warn(`Could not read which alert types the account is eligible for: ${a.error instanceof Error?a.error.message:String(a.error)}`),{discovery:ft(a.ok?a.value:void 0,a.ok,n),period:r,policies:n,usage:s}},gr=async(e,t)=>{const r=await Ce(e),o=await Wt(e,r,t.webhooks??[],t.logger,t.now??new Date);return t.logger.info(R),Oe({...Z(e),deliveries:o})},pr=async e=>{const t=e.subcommand==="setup"?zt(e):void 0;if(t!==void 0&&"error"in t)return b(e.logger,u.USAGE,t.error);const r=mr(e);if(!("graphql"in r))return r;try{if(e.subcommand==="test")return await gr(r,e);const o=await hr(r,e);return t===void 0?ur(r,o,e.logger):await sr(r,o,e,t)}catch(o){return Q(e.logger,o)}},ge=new Set(["build","delete","images","info","list","push"]),yr=new Set(["build","push"]),pe=new Set(["images list","info","list"]),vr=async e=>{const[t,...r]=e.argument;if(t===void 0||!ge.has(t)){const l=`lunora cloudflare containers requires a subcommand: ${[...ge].toSorted((h,g)=>h.localeCompare(g)).join(" | ")}. Example: lunora cloudflare containers build ./containers/app --tag app:v1 --push`;return e.logger.error(l),{code:u.USAGE,error:l}}const o=e.format==="json",n=t==="images"?`images ${r[0]??""}`.trim():t;if(o&&!pe.has(n)){const l=`containers ${n}: --format json is only available for the read subcommands (${[...pe].toSorted((h,g)=>h.localeCompare(g)).join(" | ")}) — wrangler has no JSON rendering for the rest.`;return e.logger.error(l),{code:u.USAGE,error:l}}if(yr.has(t)&&!(e.dockerAvailable??qe)()){const l=`containers ${t} needs a running Docker-compatible engine (it builds/pushes images locally). Start Docker or Colima and retry. Note: container images must target linux/amd64.`;return e.logger.error(l),{code:u.MISSING_DEPENDENCY,error:l}}const a=["containers",t,...r];o&&a.push("--json"),e.tag!==void 0&&a.push("--tag",e.tag),e.push===!0&&a.push("--push"),e.env!==void 0&&a.push("--env",e.env);const s=e.cwd??process.cwd(),i=K(V(s),"wrangler",a),c={args:i.args,command:i.command,cwd:s};return e.logger.info(`running ${c.command} ${c.args.join(" ")}`),{code:(await(e.spawner??P)(c)).code,delegated:o,descriptor:c}},T=(e,t)=>(t!==void 0&&e.push("--env",t),e),wr=(e,t)=>{const r=T(["deployments",t],e.env);return e.format==="json"&&r.push("--json"),r},br=(e,t,r)=>e.from===void 0?{error:`deployments promote --percent below 100 needs --from <live-version-id>, the version that keeps the other ${String(100-r)}% of traffic. List them with \`lunora cloudflare deployments list\`.`}:e.from===t?{error:"deployments promote --from must name a different version than the one being promoted."}:e.confirmSharedState!==!0?{error:"A partial promote runs two versions against one Durable Object database. Wrangler does not report whether their schemas or migrations match, so re-run with --confirm-shared-state once you have checked both. Durable Object class migrations must go out at 100%."}:{args:T(["versions","deploy",`${t}@${String(r)}%`,`${e.from}@${String(100-r)}%`,"--yes"],e.env)},$r=(e,t)=>{const r=e.percent??100;return!Number.isInteger(r)||r<1||r>100?{error:`deployments promote --percent must be a whole number from 1 to 100, got ${String(r)}.`}:e.yes?r<100?br(e,t,r):e.from!==void 0?{error:"deployments promote --from applies only to a partial promote (--percent below 100)."}:{args:T(["versions","deploy",`${t}@100%`,"--yes"],e.env)}:{error:`deployments promote shifts ${String(r)}% of live traffic. Re-run with --yes to confirm.`}},Ar=e=>{if(!(e.subcommand==="promote"||e.percent===void 0&&e.from===void 0&&e.confirmSharedState!==!0))return`deployments ${e.subcommand}: --percent, --from and --confirm-shared-state apply to deployments promote only.`},Sr=e=>{const t=Ar(e);if(t!==void 0)return{error:t};switch(e.subcommand){case"inspect":return e.versionId===void 0?{error:"deployments inspect requires a version id. Usage: lunora cloudflare deployments inspect <version-id>"}:{args:T(["versions","view",e.versionId],e.env)};case"list":case"status":return{args:wr(e,e.subcommand)};case"promote":{if(e.versionId===void 0)return{error:"deployments promote requires a version id. Usage: lunora cloudflare deployments promote <version-id> --yes"};const{args:r,error:o}=$r(e,e.versionId);return r===void 0||e.message===void 0?{args:r,error:o}:(r.push("--message",e.message),{args:r})}case"rollback":{if(!e.yes)return{error:"deployments rollback changes the live version. Re-run with --yes to confirm."};const r=T(["rollback"],e.env);return e.versionId!==void 0&&r.push(e.versionId),r.push("--yes"),e.message!==void 0&&r.push("--message",e.message),{args:r}}default:return{error:`deployments: unknown subcommand "${e.subcommand}"`}}},_r=e=>{let t;try{t=JSON.parse(e??"")}catch{return}const r=t?.versions;if(Array.isArray(r))return r.flatMap(o=>{const n=o?.version_id;return typeof n=="string"?[n]:[]})},Er=async(e,t,r)=>{const o=K(V(r),"wrangler",T(["deployments","status","--json"],e.env)),n=await t({args:o.args,captureStdoutSilently:!0,command:o.command,cwd:r}),a=n.code===0?_r(n.stdout):void 0;if(a===void 0){const s="deployments promote --from: could not read the live deployment (`wrangler deployments status`), so --from cannot be verified. Nothing was changed.";return e.logger.error(s),{code:u.FAILURE,descriptor:void 0,error:s}}if(e.from!==void 0&&!a.includes(e.from)){const s=`deployments promote --from ${e.from} is not a live version (live: ${a.join(", ")||"none"}). Check \`lunora cloudflare deployments status\`.`;return e.logger.error(s),{code:u.USAGE,descriptor:void 0,error:s}}},Tr=async e=>{if(e.format==="json"&&e.subcommand!=="list"&&e.subcommand!=="status"){const d=`deployments ${e.subcommand}: --format json is only available for \`deployments list\` and \`deployments status\` — wrangler has no JSON rendering for the others.`;return e.logger.error(d),{code:u.USAGE,descriptor:void 0,error:d}}const{args:t,error:r}=Sr(e);if(r!==void 0||t===void 0)return e.logger.error(r??"deployments: nothing to run"),{code:u.USAGE,descriptor:void 0,error:r};const{logger:o}=e,n=e.cwd??process.cwd();if(e.subcommand==="promote"&&e.from!==void 0){const d=await Er(e,e.spawner??P,n);if(d!==void 0)return d}const a=K(V(n),"wrangler",t),s={args:a.args,command:a.command,cwd:n};return o.info(`${s.command} ${s.args.join(" ")}`),{code:(await(e.spawner??P)(s)).code,delegated:e.format==="json",descriptor:s}},kr=e=>e==="list"||e==="inspect"||e==="rollback"||e==="promote"||e==="status",Or="https://api.cloudflare.com/client/v4/accounts",Ne="Workers Scripts Read",ye=1e3,ve=5e4,Ir=1e4,Cr=3e4,we=[31,139],be=["cpu","heap"],Nr=/^[\da-f]{64}$/iu,w=(e,t,r)=>(e.error(r),{code:t,error:r}),$e=e=>{try{const t=JSON.parse(e);if(Array.isArray(t.errors)){const r=t.errors.map(o=>typeof o=="object"&&o!==null&&"message"in o?String(o.message):"").filter(o=>o.length>0);if(r.length>0)return L(r.join("; "))}}catch{}return L(e.trim())},Ur=e=>{switch(e){case 401:case 403:return` — check that CLOUDFLARE_API_TOKEN is valid and has the "${Ne}" permission on this account`;case 404:return" — check the worker name, version, and (for a Durable Object) the namespace and actor ids";case 429:return" — rate limited; wait and retry";default:return""}},Dr=e=>{const t=e.type??"cpu";if(!be.includes(t))return{error:`profile: invalid --type "${e.type??""}" — expected ${be.join(" | ")}`};const r=e.durationMs===void 0?Ir:Number(e.durationMs);return!Number.isInteger(r)||r<ye||r>ve?{error:`profile: invalid --duration-ms "${e.durationMs??""}" — expected an integer from ${String(ye)} to ${String(ve)}`}:e.namespaceId===void 0!=(e.actorId===void 0)?{error:"profile: --namespace-id and --actor-id must be passed together to profile a Durable Object"}:e.actorId!==void 0&&!Nr.test(e.actorId)?{error:"profile: --actor-id must be a 64-character hexadecimal Durable Object id"}:{durationMs:r,profileType:t}},Rr=e=>{const{logger:t}=e,r=W(e.cwd),o=r===void 0?void 0:H(r).parsed,{accountId:n,token:a}=Y(e.environment??process.env,o?.account_id);if(a===void 0)return w(t,u.AUTH,`CLOUDFLARE_API_TOKEN is not set. \`lunora cloudflare profile\` calls the Cloudflare API with an API token (a wrangler login session is not reused); create one with the "${Ne}" permission.`);if(n===void 0)return w(t,u.USAGE,"No Cloudflare account: set CLOUDFLARE_ACCOUNT_ID or `account_id` in wrangler.jsonc.");const s=typeof o?.name=="string"&&o.name.length>0?o.name:void 0,i=e.worker??s;if(i===void 0)return w(t,u.USAGE,"profile: no Worker name — pass one, or set `name` in wrangler.jsonc.");const c=e.env===void 0?void 0:o?.env?.[e.env]?.name;let d=i;return e.worker===void 0&&e.env!==void 0&&(d=typeof c=="string"&&c.length>0?c:`${i}-${e.env}`),{accountId:n,sourceMapsMissing:o!==void 0&&o.upload_source_maps!==!0,token:a,worker:d}},Mr=(e,t)=>{const r=`${e}.${String(process.pid)}.tmp`;try{Re(Je(e),{recursive:!0}),Ae(r,t),Me(r,e);return}catch(o){try{je(r,{force:!0})}catch{}return o instanceof Error?o.message:String(o)}},jr=async(e,{accountId:t,token:r,worker:o},{durationMs:n,profileType:a,version:s})=>{const{logger:i}=e,c={duration_ms:n,profile_type:a,...e.namespaceId===void 0||e.actorId===void 0?{}:{actor_id:e.actorId,namespace_id:e.namespaceId}},d=`${Or}/${encodeURIComponent(t)}/workers/workers/${encodeURIComponent(o)}/versions/${encodeURIComponent(s)}/profile`,f=e.fetch??globalThis.fetch.bind(globalThis);let l,h;try{l=await f(d,{body:JSON.stringify(c),headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},method:"POST",signal:AbortSignal.timeout(n+Cr)}),h=new Uint8Array(await l.arrayBuffer())}catch(g){return w(i,u.UNAVAILABLE,`profile failed: ${g instanceof Error?g.message:String(g)}.`)}if(!l.ok){const g=$e(new TextDecoder().decode(h));return w(i,j(l.status),`profile failed (HTTP ${String(l.status)})${g.length>0?`: ${g}`:""}${Ur(l.status)}.`)}if(h[0]!==we[0]||h[1]!==we[1]){const g=$e(new TextDecoder().decode(h));return w(i,u.FAILURE,`profile failed: the API answered 200 with something that is not a gzip profile${g.length>0?`: ${g}`:""}.`)}return{bytes:h}},Lr=async e=>{const{logger:t}=e,r=Dr(e);if("error"in r)return w(t,u.USAGE,r.error);const o=Rr(e);if("code"in o)return o;const{durationMs:n,profileType:a}=r,{worker:s}=o,i=e.version??"latest";t.info(`Profiling ${s}@${i} (${a}) for ${String(n)} ms — keep traffic flowing to it…`);const c=await jr(e,o,{durationMs:n,profileType:a,version:i});if("code"in c)return c;const{bytes:d}=c,f=(e.now??new Date).toISOString().replaceAll(/[:.]/gu,"-"),l=Be(e.cwd,e.out??`${s}-${a}-${f}.pprof.gz`),h=Mr(l,d);return h!==void 0?w(t,u.FAILURE,`profile failed: could not write ${l}: ${h}.`):(t.success(`Wrote ${String(d.byteLength)} bytes to ${l}`),t.info("Open it with `go tool pprof -http=: <file>` (pprof reads gzip directly) or upload it to a flamegraph viewer."),o.sourceMapsMissing&&t.warn("wrangler config has no `upload_source_maps: true` — TypeScript function names in the profile may be mangled."),{code:u.SUCCESS,data:{bytes:d.byteLength,durationMs:n,file:l,profileType:a,version:i,worker:s}})},Pr={ai:{absorbs:"gateway",tool:"ai-gateway"},alert:{tool:"alerts"}},Gr=(e,t)=>{const r=Pr[e];if(r!==void 0)return{rest:r.absorbs!==void 0&&t[0]===r.absorbs?t.slice(1):t,tool:r.tool};const o=I.find(n=>n.name===e)?.name;return o===void 0?void 0:{rest:t,tool:o}},Fr={celld:"celld",node:"Node"},xr=e=>e==="status"||e==="setup"||e==="test",k=(e,t,r)=>(e.error(r),{code:t,error:r}),qr=e=>{const t=Math.max(...I.map(r=>r.name.length));e.info("lunora cloudflare <tool> — tools for an app deployed to your own Cloudflare account:");for(const r of I)e.info(` ${r.name.padEnd(t)} ${r.summary}`),r.usage!==""&&e.info(` ${" ".repeat(t)} lunora cloudflare ${r.name} ${r.usage}`);return e.info("Run `lunora cloudflare --help` for every tool's arguments and options."),{code:0}},Br=(e,t,r)=>{const{error:o,target:n}=De(e,r);return n===void 0?o??"no deploy target":n==="cloudflare"?void 0:`${r===void 0?`this project deploys to ${Fr[n]??`"${n}"`}`:`\`--target ${n}\``} — \`lunora cloudflare ${t}\` only applies to Cloudflare.`},Jr=(e,{logger:t,rest:r})=>r.length===0?void 0:k(t,u.USAGE,`cloudflare ${e}: takes no arguments, got "${r.join(" ")}"`),Wr=async e=>{const{cwd:t,logger:r,options:o}=e;return Jr("ai-gateway",e)??Xe({cwd:t,dryRun:o.dryRun===!0,id:o.id,logger:r,logs:o.logs})},Hr=async({cwd:e,format:t,logger:r,options:o,rest:n})=>{const a=n[0]??"status";return xr(a)?pr({allowFloor:o.allowFloor===!0,cwd:e,dryRun:o.dryRun===!0,format:t,logger:r,replaceRecipients:o.replaceRecipients===!0,subcommand:a,yes:o.yes===!0,...o.email===void 0?{}:{emails:o.email},...o.webhook===void 0?{}:{webhooks:o.webhook},...o.multiplier===void 0?{}:{multiplier:o.multiplier},...o.threshold===void 0?{}:{thresholds:o.threshold}}):k(r,u.USAGE,`cloudflare alerts: unknown subcommand "${a}" — expected status | setup | test`)},Yr=async({cwd:e,format:t,logger:r,options:o,rest:n})=>{const a=await vr({argument:n,cwd:e,env:o.env,format:t,logger:r,push:o.push===!0,tag:o.tag});return{code:a.code,delegated:a.delegated,error:a.error}},Kr=async({cwd:e,format:t,logger:r,options:o,rest:n})=>{const[a,s]=n;if(!kr(a))return k(r,u.USAGE,`cloudflare deployments: unknown subcommand "${a??""}" — expected list | status | inspect | rollback | promote`);const i=await Tr({confirmSharedState:o.confirmSharedState===!0,cwd:e,env:o.env,format:t,from:o.from,logger:r,message:o.message,percent:o.percent===void 0?void 0:Number(o.percent),subcommand:a,versionId:s,yes:o.yes===!0});return{code:i.code,delegated:i.delegated,error:i.error}},Vr=async({cwd:e,logger:t,options:r,rest:o})=>o.length>1?k(t,u.USAGE,`cloudflare profile: takes at most one argument (the worker), got "${o.join(" ")}"`):Lr({actorId:r.actorId,cwd:e,durationMs:r.durationMs,env:r.env,logger:t,namespaceId:r.namespaceId,out:r.out,type:r.type,version:r.versionId,worker:o[0]}),zr={"ai-gateway":Wr,alerts:Hr,containers:Yr,deployments:Kr,profile:Vr},fo=Ue(async({argument:e,cwd:t,format:r,logger:o,options:n})=>{const[a,...s]=e;if(a===void 0||a==="")return qr(o);const i=Gr(a,s);if(i===void 0)return k(o,u.USAGE,`cloudflare: unknown tool "${a}" — expected ${I.map(f=>f.name).join(" | ")}`);const{tool:c}=i,d=Br(t,c,n.target);return d!==void 0?k(o,u.USAGE,d):zr[c]({cwd:t,format:r,logger:o,options:n,rest:i.rest})});export{fo as execute};