everyapp 0.2.2 → 0.3.0-rc.2

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.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -77
  3. package/bin/everyapp.js +235 -0
  4. package/dist/access-LGEN3ABJ.js +1 -0
  5. package/dist/activity-RSD4D66F.js +12 -0
  6. package/dist/apps-4RCFXT74.js +13 -0
  7. package/dist/chunk-23HJ5I2A.js +2 -0
  8. package/dist/chunk-2Y5Q6A5K.js +6 -0
  9. package/dist/chunk-4JRIS6S6.js +1 -0
  10. package/dist/chunk-5PTNYZK6.js +4 -0
  11. package/dist/chunk-6OGVO7QK.js +6 -0
  12. package/dist/chunk-6PGUCTJ6.js +11 -0
  13. package/dist/chunk-AGTCKFRB.js +22 -0
  14. package/dist/chunk-BHMRY53V.js +2 -0
  15. package/dist/chunk-CB4U7H2G.js +4 -0
  16. package/dist/chunk-CCWBPAKH.js +4 -0
  17. package/dist/chunk-CZTEDWZ2.js +22 -0
  18. package/dist/chunk-CZXHKPRZ.js +2 -0
  19. package/dist/chunk-E3EPK27P.js +10 -0
  20. package/dist/chunk-EGEWRFVY.js +1 -0
  21. package/dist/chunk-ELK7QZ2M.js +1 -0
  22. package/dist/chunk-FJJSSVET.js +33 -0
  23. package/dist/chunk-GKBPYVP3.js +25 -0
  24. package/dist/chunk-H6QQMY7B.js +18 -0
  25. package/dist/chunk-H7PDL3X5.js +1 -0
  26. package/dist/chunk-HOUHG236.js +20 -0
  27. package/dist/chunk-IFCPALVC.js +46 -0
  28. package/dist/chunk-IUOBIB5M.js +30 -0
  29. package/dist/chunk-J35DDCNU.js +1 -0
  30. package/dist/chunk-J455FAEN.js +7 -0
  31. package/dist/chunk-JYKRUS5C.js +1 -0
  32. package/dist/chunk-LXYMDZER.js +1 -0
  33. package/dist/chunk-LYNBZ7RV.js +57 -0
  34. package/dist/chunk-M2QQ2UD7.js +132 -0
  35. package/dist/chunk-N4E5YE4X.js +5 -0
  36. package/dist/chunk-N5GQLGEC.js +2 -0
  37. package/dist/chunk-NFU4YFXB.js +1 -0
  38. package/dist/chunk-OM2HDWVP.js +1 -0
  39. package/dist/chunk-ONLNJXVQ.js +32 -0
  40. package/dist/chunk-OP4DYXYI.js +2 -0
  41. package/dist/chunk-PNLU5U3P.js +1 -0
  42. package/dist/chunk-PS7S74DG.js +6 -0
  43. package/dist/chunk-RMF4YGD6.js +98 -0
  44. package/dist/chunk-T2GD7RHS.js +10 -0
  45. package/dist/chunk-TLRM3P7V.js +15 -0
  46. package/dist/chunk-W3JCQHHU.js +1 -0
  47. package/dist/chunk-WOVS3GV5.js +10 -0
  48. package/dist/chunk-YHJZMJYZ.js +1 -0
  49. package/dist/chunk-ZECBN24L.js +15 -0
  50. package/dist/chunk-ZEXVHB22.js +2 -0
  51. package/dist/chunk-ZFCUH5PK.js +7 -0
  52. package/dist/configure-F7UZCJUG.js +8 -0
  53. package/dist/create-2E5HVWYK.js +22 -0
  54. package/dist/db-HEQVTDTM.js +9 -0
  55. package/dist/delete-XCSIQIRS.js +3 -0
  56. package/dist/deploy-3QOZEZHC.js +1 -0
  57. package/dist/dev-JL3ZP5BT.js +86 -0
  58. package/dist/email-RLZFUFUN.js +10 -0
  59. package/dist/events-2V5VRBWP.js +15 -0
  60. package/dist/groups-HDG5DVFY.js +8 -0
  61. package/dist/index.js +97 -1
  62. package/dist/install-Z5FYXUYM.js +1 -0
  63. package/dist/login-KHJBC64Z.js +1 -0
  64. package/dist/mcp-EFDUKJ5V.js +1 -0
  65. package/dist/provider-EOI5PTFH.js +13 -0
  66. package/dist/release-74J5ZIL3.js +1 -0
  67. package/dist/restore-FFGYV7NO.js +3 -0
  68. package/dist/secret-7FKKTLHB.js +1 -0
  69. package/dist/setup-PAKRCAFO.js +1 -0
  70. package/dist/signin-AP3D4XM4.js +12 -0
  71. package/dist/status-RMWNHOEK.js +4 -0
  72. package/dist/teardown-E4JNKAZJ.js +22 -0
  73. package/dist/update-DWVZFNPS.js +1 -0
  74. package/dist/workspace-CJC5I66L.js +21 -0
  75. package/package.json +19 -16
  76. package/skills/build-every-app/SKILL.md +199 -0
  77. package/skills/build-every-app/patterns.md +140 -0
  78. package/skills/everyapp/SKILL.md +292 -0
  79. package/skills/everyapp/mcp.md +350 -0
  80. package/skills/everyapp/security-review.md +61 -0
  81. package/skills/everyapp/setup/README.md +274 -0
  82. package/skills/everyapp/setup/access.md +178 -0
  83. package/skills/everyapp/setup/context.md +117 -0
  84. package/skills/everyapp/setup/dataforseo.md +287 -0
  85. package/skills/everyapp/setup/email.md +366 -0
  86. package/skills/everyapp/setup/gateway.md +243 -0
  87. package/skills/everyapp/setup/google.md +360 -0
  88. package/skills/everyapp/setup/microsoft-365.md +281 -0
  89. package/skills/everyapp/setup/openai.md +318 -0
  90. package/skills/everyapp/setup/openrouter.md +309 -0
  91. package/skills/everyapp/setup/providers.md +1253 -0
  92. package/skills/everyapp/setup/remote-mcp.md +199 -0
  93. package/skills/everyapp/setup/signin-apple.md +178 -0
  94. package/skills/everyapp/setup/signin-cloudflare.md +148 -0
  95. package/skills/everyapp/setup/signin-github.md +139 -0
  96. package/skills/everyapp/setup/signin-google.md +178 -0
  97. package/skills/everyapp/setup/signin-microsoft.md +183 -0
  98. package/skills/everyapp/setup/signin-oidc.md +163 -0
  99. package/skills/everyapp/setup/slack.md +344 -0
  100. package/skills/everyapp/setup/workers-ai.md +138 -0
  101. package/bin/__everyapp_bash_complete.js +0 -12
  102. package/dist/bash-complete.js +0 -3
  103. package/dist/chunk-7BOQJFI5.js +0 -8
  104. package/dist/chunk-7YVJFS4A.js +0 -1
  105. package/dist/chunk-BIXKPCDH.js +0 -2
  106. package/dist/chunk-DLGPLHBH.js +0 -47
  107. package/dist/chunk-FD6K2YA7.js +0 -9
  108. package/dist/chunk-GAJEQYJH.js +0 -39
  109. package/dist/chunk-O2KDOP5R.js +0 -13
  110. package/dist/chunk-OI2MBGBG.js +0 -4
  111. package/dist/chunk-POSDLD3G.js +0 -8
  112. package/dist/chunk-RB5D2R45.js +0 -1
  113. package/dist/chunk-U4LIQ5VR.js +0 -1
  114. package/dist/chunk-VEYSNPDH.js +0 -3
  115. package/dist/chunk-VP6SKW44.js +0 -7
  116. package/dist/chunk-WO3NE7ZU.js +0 -7
  117. package/dist/chunk-XTO2VTPY.js +0 -6
  118. package/dist/chunk-YJ2DKIZS.js +0 -1
  119. package/dist/create-DYQ3NBWJ.js +0 -22
  120. package/dist/deploy-PYTCMDGV.js +0 -7
  121. package/dist/deploy-RMAHOA4Z.js +0 -55
  122. package/dist/dev-SAE237FS.js +0 -0
  123. package/dist/generateConfig-46Z3ERF3.js +0 -1
  124. package/dist/login-B6EF3SMX.js +0 -1
  125. package/dist/logout-XMKZBLNV.js +0 -1
  126. package/dist/remoteD1Shell-A67JMO3H.js +0 -6
  127. package/dist/setupLocal-TZJUW5ES.js +0 -6
  128. package/gateway/every-app-gateway-build.tar.gz +0 -0
@@ -0,0 +1,4 @@
1
+ import{a as Ge}from"./chunk-N4E5YE4X.js";import{a as ce,f as me}from"./chunk-E3EPK27P.js";import{a as Pe}from"./chunk-WOVS3GV5.js";import{a as J,b as be,d as Ae,e as $e,j as Ne,l as ke,p as Ee}from"./chunk-OM2HDWVP.js";import{b as We}from"./chunk-OP4DYXYI.js";import{b as xe,h as De,i as Oe,l as Te}from"./chunk-H6QQMY7B.js";import{p as _e}from"./chunk-6OGVO7QK.js";import{f as Ue}from"./chunk-PS7S74DG.js";import"./chunk-YHJZMJYZ.js";import"./chunk-N5GQLGEC.js";import{b as Ce}from"./chunk-W3JCQHHU.js";import{c as Ie}from"./chunk-ZFCUH5PK.js";import"./chunk-4JRIS6S6.js";import{a as Re}from"./chunk-23HJ5I2A.js";import{c as ve}from"./chunk-ZECBN24L.js";import"./chunk-ONLNJXVQ.js";import"./chunk-6PGUCTJ6.js";import{a as ge,b as L}from"./chunk-HOUHG236.js";import{la as ye,na as he,ra as we}from"./chunk-RMF4YGD6.js";import{b as j}from"./chunk-NFU4YFXB.js";import"./chunk-T2GD7RHS.js";import"./chunk-CCWBPAKH.js";import"./chunk-JYKRUS5C.js";import{Fb as ue,K as se,c as S,ca as z,da as Y,ea as ae,f as k,g as _,ga as le,hc as pe,ic as fe,k as A,oc as q,pc as X,qb as de,wc as Se}from"./chunk-LYNBZ7RV.js";var T=e=>Number(e?.[0]?.n??0);async function Fe(e,t,n=Date.now()){let[r,o,i,s,u]=await e.run([{sql:"SELECT count(*) AS n FROM members WHERE organization_id = ?",params:[t]},{sql:"SELECT count(*) AS n FROM invitations WHERE organization_id = ? AND status = 'pending' AND expires_at > ?",params:[t,n]},{sql:"SELECT count(*) AS n FROM mcp_connections WHERE organization_id = ? AND revoked_at IS NULL",params:[t]},{sql:"SELECT count(*) AS n FROM user_access_tokens WHERE organization_id = ? AND revoked_at IS NULL AND expires_at > ?",params:[t,n]},{sql:"SELECT count(*) AS n FROM app_tool_catalogs WHERE organization_id = ? AND status = 'ok' AND tool_count > 0",params:[t]}]);return{members:T(r),invitations:T(o),connections:T(i),tokens:T(s),toolApps:T(u)}}import nt from"chalk";function Me(e){return e.severity==="error"||e.severity==="warning"}function v(e,...t){return{text:e.reduce((n,r,o)=>n+r+(t[o]??""),""),human:e.reduce((n,r,o)=>n+r+(o<t.length?nt.bold(t[o]):""),"")}}function Q(e){return e.bound&&(e.status==="active"||e.status==="binding")}function R(e){return Q(e)&&e.setup!==null&&!e.setup.ready}var P=`${S} setup`,B=`${S} deploy`,rt=`${S} update`;function Le(){return{code:"GATEWAY_NOT_INSTALLED",severity:"warning",headline:"Setup stopped before it recorded the Gateway's install",detail:["The Gateway's database exists, but nothing else was recorded."],run:v`${P}`,runNote:"asks for the hostname again"}}function Be({install:e,desired:t,apps:n,unboundApps:r,owner:o,servingVersionId:i,inSync:s,waitingCertificates:u,certificates:h,access:y,leases:p,checkout:f,emailProblems:m,settings:w}){let g=[],N=e.releaseVersion;if(f.behind){let d=f.checkout?.version;g.push({code:"CHECKOUT_BEHIND",severity:"warning",headline:"This checkout is behind the live Gateway",detail:[...d&&d!==N?[`This checkout has ${d}; ${N} is live.`]:[],"Deploys from here are refused until it catches up."],run:v`${"git pull"}`})}else f.upToDate===!1&&e.liveVersionId!==null&&g.push({code:"GATEWAY_OUTDATED",severity:"info",headline:"Your every-app folder has a newer Gateway than the one online",detail:["Run update now, or leave it: the next new app's deploy updates it too."],run:v`${rt}`,runNote:"updates it now"});e.liveVersionId===null?g.push({code:"GATEWAY_NOT_UPLOADED",severity:"warning",headline:"The Gateway's install stopped before its first upload",detail:["Setup finishes it, then prints the link that creates the owner account."],run:v`${P}`}):(i!==e.liveVersionId?g.push({code:"GATEWAY_CHANGED",severity:"warning",headline:"The Gateway Worker was changed outside everyapp",detail:["It serves a version from the dashboard or API, not the one setup uploaded."],run:v`${P}`,runNote:`redeploys ${N}, asking first`}):s||g.push(it(r)),o||g.push({code:"NO_OWNER",severity:"warning",headline:"No owner yet, so nobody can sign in",detail:["Apps still deploy. Setup prints a one-time link that creates the owner account."],run:v`${P}`}));for(let d of t.missing)g.push({code:"APP_WORKER_MISSING",severity:"error",headline:`${d.appId}'s Worker no longer exists`,detail:[],run:v`${B} from ${d.appId}'s directory, or ${`${S} delete ${d.appId}`}`});for(let d of t.skipped)g.push({code:"APP_NOT_BOUND",severity:"error",headline:`The Gateway can't bind ${d.appId}`,detail:[`Its Worker isn't named the way the Gateway names ${d.appId}'s.`],run:v`${`${S} delete ${d.appId}`}`});let b=new Set(t.missing.map(d=>d.workerName));for(let d of n)b.has(d.workerName)||(d.status==="pending"?g.push({code:"APP_NOT_UPLOADED",severity:"warning",headline:`${d.id} was registered, but none of its deploys finished`,detail:[],run:v`${B} from the app's directory, or ${`${S} delete ${d.id}`}`}):d.status==="binding"&&d.bound&&g.push({code:"APP_BINDING",severity:"settling",headline:`${d.id} goes live on its first visit to https://${d.hostname}`,detail:[]}));w&&g.push({code:"SETTINGS_UNKNOWN",severity:"warning",headline:"Couldn't list the Workers' secrets",detail:[`Cloudflare said: ${w.error}. Whether the apps' settings are set isn't known.`]});for(let d of n)R(d)&&g.push(ot(d.id,d.setup));"error"in m&&g.push({code:"EMAIL_ROUTES_UNKNOWN",severity:"warning",headline:"Couldn't check the apps' Email Routing rules",detail:[`Cloudflare said: ${m.error}. Deleted rules won't show above.`]});for(let d of n){let $=st(d,"error"in m?[]:m);$&&g.push($)}h&&g.push({code:"CERTIFICATES_UNKNOWN",severity:"warning",headline:`Couldn't list ${e.zoneName}'s certificates`,detail:[`Cloudflare said: ${h.error}.`]}),u.length>0&&g.push({code:"CERT_PENDING",severity:"settling",headline:`No certificate yet for ${u.join(", ")}`,detail:["It usually comes within minutes."]}),y&&g.push({code:"ACCESS_UNKNOWN",severity:"warning",headline:"Couldn't check whether Cloudflare Access is on",detail:[`Cloudflare said: ${y.error}.`]});for(let d of p)g.push({code:"DEPLOY_RUNNING",severity:"settling",headline:`${_(L(d))} is running`,detail:[]});return[...g.filter(d=>d.severity!=="info"),...g.filter(d=>d.severity==="info")]}function ot(e,t){return{code:"APP_NEEDS_SETUP",severity:"warning",headline:`${e} needs setup`,detail:Ee(t.unmet),...t.command?{run:v`${t.command.command}`}:{}}}function it(e){if(e.length===0)return{code:"BINDINGS_DIFFER",severity:"warning",headline:"The live Gateway's apps or hostnames are out of date",detail:["It still serves something that was removed, or not yet what was added."],run:v`${P}`};let t=A(e);return{code:"BINDINGS_DIFFER",severity:"warning",headline:`The live Gateway doesn't serve ${t}`,detail:[e.length===1?`${t} is uploaded, but the live Gateway doesn't bind it.`:`${t} are uploaded, but the live Gateway doesn't bind them.`],run:v`${P}`}}function st(e,t){let{inbound:n,routed:r,declaresInbound:o}=e.email;if(n.length>0&&!o){let f=A(n);return{code:"EMAIL_INBOUND_UNDECLARED",severity:"warning",headline:`${e.id} has ${n.length===1?"an inbound address":"inbound addresses"}, but its config doesn't turn inbound mail on`,detail:[`${e.id} was deployed without email: { inbound: true }, so nothing routes ${f}.`],fix:v`declare it in everyapp.config.ts and run ${B}, or drop ${n.length===1?"the address":"the addresses"}: ${`${S} email unset inbound`}`,where:`Run either from ${e.id}'s directory.`}}let i=n.filter(f=>!r.includes(f)),s=r.filter(f=>!n.includes(f)),u=t.filter(f=>f.workerName===e.workerName);if(i.length+s.length+u.length===0)return null;let h=[...i.length>0?[`${A(i)} ${i.length===1?"isn't":"aren't"} routed yet`]:[],...u.map(f=>`${f.address} gets no mail: ${f.problem}`),...s.length>0?[`${A(s)} ${s.length===1?"is":"are"} still routed, though no longer listed`]:[]],y=i.length+u.length,p=s.length>0?`${e.id}'s mail routes don't match its addresses`:u.length===0?`${e.id} doesn't get mail at ${A(i)} yet`:y===1?`${e.id} doesn't get mail at ${u[0].address}`:`${e.id} doesn't get mail at ${y} of its addresses`;return{code:u.length>0?"EMAIL_ROUTE_MISSING":"EMAIL_ROUTE_PENDING",severity:"warning",headline:p,detail:s.length===0&&u.length===0?[]:s.length===0&&y===1?[`${_(u[0].problem)}.`]:[`${h.join("; ")}.`],run:v`${B} from ${e.id}'s directory`}}var at=4,lt="https://everyapp.com/docs/connect-ai",He=Object.values(se).map(e=>e.keyEnv);function Z(e,t){return e.some(n=>R(n)&&n.setup.unmet.some(r=>r.names.includes(t)))}function Ve(e,t){return e.filter(n=>n.setup?.settings.some(r=>r.kind==="capability"&&r.id===t)).map(n=>n.id)}function dt(e){let{apps:t,gatewayNames:n}=e;if(!n||He.some(i=>n.includes(i)))return null;let r=[],o=[];for(let i of t){let s=(i.setup?.settings??[]).filter(u=>u.kind==="provider"&&u.set===!1).flatMap(u=>u.names.filter(h=>He.includes(h))).filter(u=>!Z(t,u));if(s.length!==0){o.push(i.id);for(let u of s)r.includes(u)||r.push(u)}}return r.length===0?null:{code:"AI_KEY",command:`${S} secret set ${r.join(" ")}`,note:`AI for ${A(o)}`,why:`${A(o)} ${o.length===1?"uses":"use"} AI once ${A(r)} ${r.length===1?"is":"are"} set; no AI key is set on the Gateway.`}}function ut(e){let{gatewayNames:t,providers:n,uploaded:r,apps:o}=e;return!t||!r?[]:n.filter(i=>!i.on&&t.includes(i.keySetting)&&Ve(o,i.capability).length===0).map(i=>({code:"PROVIDER_OFFER",command:`${S} mcp on ${i.id}`,note:`offer ${i.name}'s tools to AI clients (each call spends ${i.name} credit)`,why:`${i.keySetting} is set and ${i.name} is off.`}))}function ct(e){let{gatewayNames:t,providers:n,apps:r}=e;return t?n.filter(o=>o.on&&!t.includes(o.keySetting)&&!Z(r,o.keySetting)).map(o=>({code:"PROVIDER_KEY",command:`${S} secret set ${o.keySetting}`,note:`${o.name} is on and needs its key`,why:`${o.name} shows "needs setup" and serves no tools until ${o.keySetting} is set.`})):[]}function mt(e){let{mode:t,uploaded:n,owner:r,access:o}=e;return t==="hosted"||!n||!r||!o||o.on?null:{code:"ACCESS_OFF",command:`${S} access on`,note:"put Cloudflare Access in front (recommended)",why:"Cloudflare Access is off; the Gateway's own sign-in is the only check in front of your apps."}}function pt(e){let{counts:t,owner:n,apps:r,gatewayUrl:o}=e;return!t||!n||t.members!==1||t.invitations>0||!r.some(Q)?null:{code:"INVITE",url:`${o}/admin/users`,note:"invite people",why:`${n.email} is the only member.`}}function ft(e){let{counts:t,gatewayUrl:n}=e;return!t||t.connections>0||t.tokens>0||t.toolApps===0?null:{code:"CONNECT_AI",url:lt,note:`connect an AI client to ${n}/mcp`,why:`No AI client is connected; ${k(t.toolApps,"app")} ${t.toolApps===1?"serves":"serve"} tools.`}}function gt(e){return!e.uploaded||e.apps.length>0?null:{code:"CREATE_APP",command:`${S} create my-app`,note:"start your first app",why:"No apps yet."}}function yt(e){let{gatewayNames:t,counts:n,apps:r}=e;if(!t||t.includes("EMAIL_FROM")||Z(r,"EMAIL_FROM"))return null;let o=Ve(r,"email").filter(s=>!r.some(u=>u.id===s&&R(u)));return!((n?.members??0)>1)&&o.length===0?null:{code:"EMAIL_UNSET",command:`${S} secret set EMAIL_FROM EMAIL_FROM_NAME`,note:"email invites and password resets; needs Cloudflare Email Sending",why:`No sender is set: invites and resets go out by link${o.length>0?`, and ${A(o)} ${o.length===1?"sends":"send"} mail once there is one`:""}.`}}function Ke(e){return[dt(e),...ut(e),...ct(e),mt(e),pt(e),ft(e),gt(e),yt(e)].filter(n=>n!==null).filter(n=>!n.command||!e.problems.some(r=>r.run?.text===n.command)).slice(0,at)}import l from"chalk";var ht={["error"]:l.red("\u2717"),["warning"]:l.yellow("!"),["info"]:l.dim("i")};function ze(e,t){return[...q([{label:"Cloudflare",value:e},{label:"Gateway",value:l.yellow("install not finished")}]),...je([t])].join(`
2
+ `)}function Ye({install:e,accountName:t,owner:n,servingRecorded:r,checkout:o,certificates:i,waitingCertificates:s,access:u,leases:h,apps:y,missingWorkers:p,skippedWorkers:f,emailProblems:m,problems:w}){let g=e.liveVersionId!==null,N=[{label:"Gateway",value:`${l.cyan(`https://${e.gatewayHost}`)}${e.deploymentMode?l.dim(` \xB7 ${e.deploymentMode.replace("_","-")}`):""}`},{label:"Cloudflare",value:t},{label:"Owner",value:n?`${n.email}${n.organizationName?l.dim(` \xB7 ${n.organizationName}`):""}`:l.yellow("none yet")},...u?[{label:"Access",value:St(u)}]:[],{label:"Release",value:wt(e,r,o)},{label:"TLS",value:vt(i,s)},...h.map((c,D)=>({label:D===0?"Deploys":"",value:_(L(c))}))],b=new Set(h.flatMap(c=>c.app?[c.app]:[])),d=new Set(s),$=c=>Nt(c,{missing:p.has(c.workerName),skipped:f.has(c.workerName),waitingCertificate:d.has(c.hostname),deploying:b.has(c.id)}),C=y.filter(c=>c.email.senders.length>0||c.email.inbound.length>0||c.email.routed.length>0),U=y.flatMap(c=>c.setup?Ne(c.id,c.setup.settings):[]);return[...q(N),"",...y.length>0?X(["APP","STATUS","URL","DEPLOYED BY","PUBLIC"],y.map(c=>[c.id,$(c),l.cyan(`https://${c.hostname}`),bt(c.deployedBy),$t(c)])).map(c=>c.trimEnd()):[l.dim("No apps yet.")],...C.length>0?["",...kt(C,m)]:[],...U.length>0?["",...ke(U)]:[],...je(w)].join(`
3
+ `)}function wt(e,t,n){let r=e.releaseVersion,o=i=>`${r} ${l.dim("\xB7")} ${l.yellow(i)}`;return e.liveVersionId===null?l.yellow(`${r}, never uploaded`):t?n.behind?o("this checkout is behind"):n.upToDate===!1?`${r}${l.dim(" \xB7 this checkout has newer Gateway changes")}`:n.upToDate===!0?`${r}${l.dim(" \xB7 matches this checkout")}`:r:o("changed outside everyapp")}function St(e){return"error"in e?l.yellow("! couldn't check"):e.on?`${l.green("on")}${l.dim(" \xB7 everyone signs in through Cloudflare Access first")}`:l.dim("off")}function vt(e,t){return e===null?l.yellow("! couldn't check"):e.length===0?l.dim("no hostnames yet"):t.length>0?`waiting for ${t.join(", ")}${l.dim(" \xB7 usually a few minutes")}`:`ready on ${k(e.length,"hostname")}`}function bt(e){return e?e.startsWith("api-token:")?"API token":e:""}var At=3;function $t(e){let t=[];if(e.publicRoutes.length>0){let{shown:n,more:r}=fe(e.publicRoutes,At);t.push(`${n}${r>0?l.dim(` +${r} more`):""}`)}return e.mcp&&t.push(pe(e.mcp)),t.join(l.dim(" \xB7 "))}function Nt(e,{missing:t,skipped:n,waitingCertificate:r,deploying:o}){let i=t?`${l.red("\u2717")} Worker missing`:n?`${l.red("\u2717")} can't be bound`:e.status==="pending"?`${l.yellow("!")} never deployed`:e.bound?R(e)?`${l.yellow("!")} needs setup`:e.status==="active"?`${l.green("\u2713")} live`:e.status==="binding"?`${l.dim("\u25CB")} ${r?"waiting for certificate":"live on first visit"}`:l.dim(e.status==="disabled"?"switched off":e.status):`${l.yellow("!")} not bound`;return o?`${i} \xB7 deploying`:i}function kt(e,t){let n=e.flatMap(r=>{let{senders:o,inbound:i,routed:s,declaresInbound:u}=r.email,h=new Set(t.filter(m=>m.workerName===r.workerName).map(m=>m.address)),y=i.length>0&&!u?i.map(m=>`${l.yellow("!")} ${m} ${l.dim("(inbound off)")}`):[...i.map(m=>h.has(m)?`${m} ${l.yellow("! not delivered")}`:s.includes(m)?m:`${m} ${l.yellow("! not routed yet")}`),...s.filter(m=>!i.includes(m)).map(m=>`${m} ${l.yellow("! still routed, no longer listed")}`)],p=o.length>0?o:[l.dim("the Gateway's sender")],f=Math.max(p.length,y.length,1);return Array.from({length:f},(m,w)=>[w===0?r.id:"",p[w]??"",y[w]??(w===0?l.dim("nothing"):"")])});return X(["EMAIL","SENDS AS","RECEIVES"],n)}function je(e){let t=[];for(let n of e){if(n.severity==="settling")continue;let r=n.run?t.find(o=>o[0].run?.text===n.run.text):void 0;r?r.push(n):t.push([n])}return t.length===0?[]:["",...t.flatMap(Et)]}function Et([e,...t]){let n=e,r=[n,...t].map(i=>i.severity),o=r.includes("error")?"error":r.includes("warning")?"warning":"info";return[`${ht[o]} ${n.headline}`,...[...n.detail,...t.map(i=>`${i.headline}.`)].map(i=>l.dim(` ${i}`)),...n.fix?[` Fix: ${n.fix.human}`]:[],...n.run?[` Run: ${n.run.human}${n.runNote?` ${l.dim(n.runNote)}`:""}`]:[],...n.where?[l.dim(` ${n.where}`)]:[]]}async function Pn(e){await Se(e,async t=>{let n=await we({json:e.json,out:t,accountId:e["account-id"]}),r=Re();await It({gateway:n,out:t,checkout:r?()=>xe(r):null})})}function qe(e){return{code:e.code,severity:e.severity,message:[e.headline,...e.detail].join(`
4
+ `),run:e.run?.text??null,...e.runNote?{note:e.runNote}:{},...e.fix?{fix:e.fix.text}:{}}}async function It({gateway:e,out:t,checkout:n}){let{cf:r,account:o,db:i,install:s}=e,u=o.name??o.id;if(!s){let a=Le();t.emit({type:"done",message:`${u} has the Gateway's database, but its install didn't finish`,display:ze(u,a),data:{deployed:!0,installed:!1,accountId:o.id,fixes:[qe(a)]}});return}let h=s.liveVersionId!==null,y=s.deploymentMode!=="hosted"&&h,[p,f,m,w,g,N,b,d]=await Promise.all([Oe(i,De(r)),he(i),ge(i),Ge(i),Ie(r,de),ve(i,"email-route"),h?x(()=>J(r)):null,y?x(()=>ce(i)):null]),$=w?.owner??null,C=f.filter(a=>a.tier!==z),U=ae.map(a=>({id:a,name:Y[a].name,capability:Y[a].capability,keySetting:le(a),on:f.some(I=>I.tier===z&&I.appSlug===a)})),c=y?b&&"error"in b?b:d&&"error"in d?d:{value:me(b.value,d.value)}:null,D=c&&"value"in c?c.value:null,ee=c&&"error"in c?c:null,te=new Set(p.missing.map(a=>a.workerName)),[W,O,ne,Xe]=await Promise.all([x(()=>We(r,s.zoneId,s.liveDomains)),x(async()=>N.length===0?[]:Ue(r,N)),Pe(i,s).then(a=>xt(r,i,C,te,b,a)),w?Fe(i,w.organizationId).catch(()=>null):null]),H=`https://${s.gatewayHost}`,V=_e({gatewayHost:s.gatewayHost,appHosts:p.appHosts.filter(a=>Ce(a,s.appBaseDomain))}),Je=V.filter(a=>!s.liveDomains.includes(a)),Qe=s.liveDomains.filter(a=>!V.includes(a)),re=p.appWorkers.filter(a=>!s.liveAppWorkers.includes(a)),Ze=s.liveAppWorkers.filter(a=>!p.appWorkers.includes(a)),oe=!!g?.versionId&&g?.versionId===s.liveVersionId,ie=oe&&re.length===0&&Ze.length===0&&Je.length===0&&Qe.length===0,E=C.map(a=>({id:a.appSlug,hostname:a.hostname,workerName:a.workerName,status:a.status,bound:s.liveAppWorkers.includes(a.workerName),deployedBy:a.deployedBy,email:{senders:a.email.senders,inbound:a.email.inbound,routed:N.filter(I=>I.workerName===a.workerName).map(I=>I.name),declaresInbound:a.declaresInbound},publicRoutes:a.publicRoutes,mcp:a.mcp,setup:ne.setups.get(a.appSlug)??null})),G="error"in W?null:W.value,K=(G??[]).filter(a=>!a.ready).map(a=>a.host),F=await Ct(s,n),M=Be({install:s,desired:p,apps:E,unboundApps:E.filter(a=>re.includes(a.workerName)).map(a=>a.id),owner:$,servingVersionId:g?.versionId??null,inSync:ie,waitingCertificates:K,certificates:"error"in W?W:null,access:ee,leases:m,checkout:F,emailProblems:"error"in O?O:O.value,settings:ne.unread}),et=Ke({mode:s.deploymentMode,uploaded:h,gatewayUrl:H,owner:$,apps:E,gatewayNames:b&&"value"in b?b.value:null,access:D,providers:U,counts:Xe,problems:M}),tt=Ye({install:s,accountName:u,owner:$,servingRecorded:oe,checkout:F,certificates:G,waitingCertificates:K,access:ee??D,leases:m,apps:E,missingWorkers:te,skippedWorkers:new Set(p.skipped.map(a=>a.workerName)),emailProblems:"error"in O?[]:O.value,problems:M});t.emit({type:"done",message:Rt(H,E,M),display:tt,data:{deployed:!0,installed:!0,gatewayUrl:H,accountId:o.id,mode:s.deploymentMode,release:{version:s.releaseVersion,sha256:s.releaseSha256},versionId:g?.versionId??null,recordedVersionId:s.liveVersionId,owner:$,claimed:$!==null,apps:E,desiredApps:p.appWorkers,liveApps:s.liveAppWorkers,appBaseDomain:s.appBaseDomain,domains:V,liveDomains:s.liveDomains,inSync:ie,upToDate:F.upToDate,checkout:F.checkout,tls:G===null?null:!K.includes(s.gatewayHost),certificates:G,access:D,leases:m,fixes:M.map(qe),next:et.map(Pt)}})}function Pt(e){return{code:e.code,...e.command?{command:e.command}:{},...e.url?{url:e.url}:{},note:e.note,why:e.why}}function Rt(e,t,n){let r=t.filter(i=>i.status==="active"&&i.bound).length,o=n.filter(Me).length;return`Gateway ${e}: ${k(r,"app")} live, ${o===0?"nothing to fix":`${k(o,"problem")} to fix`}`}async function xt(e,t,n,r,o,i){let s=new Map,u=n.filter(p=>p.manifest&&$e(p.manifest,i)&&p.tier!=="dispatch"&&p.status!=="pending"&&!r.has(p.workerName));if(u.length===0)return{setups:s,unread:null};let h=null,y=o??await x(()=>J(e));"error"in y&&(h=y);for(let p of u){let f=await x(()=>be(e,p.workerName));"error"in f&&(h??=f);let m="error"in f?null:f.value;m&&(p.secretNames===null||j(m)!==j(p.secretNames))&&await ye(t,p.id,m);let{setup:w}=Ae({manifest:p.manifest,appId:p.appSlug,gatewayNames:"error"in y?null:y.value,secretNames:m,lookup:i});s.set(p.appSlug,w)}return{setups:s,unread:h}}async function x(e){try{return{value:await e()}}catch(t){return{error:ue(t)}}}async function Ct(e,t){let n=t?await t().catch(()=>null):null;if(!n)return{upToDate:null,behind:!1,checkout:null};let r=n.treeSha256&&e.sourceTreeSha256?n.treeSha256===e.sourceTreeSha256:null;return{upToDate:r,behind:r===!1&&Te(n,e),checkout:{version:n.version,treeSha256:n.treeSha256}}}export{It as reportStatus,Pn as status};
@@ -0,0 +1,22 @@
1
+ import{a as it,d as ct}from"./chunk-PNLU5U3P.js";import{a as Ze,b as He,c as je}from"./chunk-AGTCKFRB.js";import"./chunk-E3EPK27P.js";import"./chunk-IFCPALVC.js";import"./chunk-WOVS3GV5.js";import"./chunk-OM2HDWVP.js";import{a as ee,c as st,d as de,e as at}from"./chunk-OP4DYXYI.js";import{a as tt,e as ot,i as nt,j as rt}from"./chunk-EGEWRFVY.js";import{a as Ve,c as Ge,d as Ye,h as Je,i as Xe,j as qe,k as Q,l as Qe}from"./chunk-YHJZMJYZ.js";import{a as q,b as Be,i as et}from"./chunk-N5GQLGEC.js";import"./chunk-W3JCQHHU.js";import{a as ve,h as $e,j as Ce,l as Re,p as Te,v as Pe}from"./chunk-TLRM3P7V.js";import{a as Me,d as Ue}from"./chunk-ZFCUH5PK.js";import"./chunk-4JRIS6S6.js";import{a as W,d as Fe}from"./chunk-ZECBN24L.js";import"./chunk-HOUHG236.js";import{a as Oe,k as Se,oa as We,q as le,r as Le,t as xe}from"./chunk-RMF4YGD6.js";import"./chunk-NFU4YFXB.js";import{a as _e}from"./chunk-T2GD7RHS.js";import"./chunk-H7PDL3X5.js";import{a as De,f as Ie}from"./chunk-CCWBPAKH.js";import{d as be,j as Ne}from"./chunk-JYKRUS5C.js";import{Bb as $,Db as ke,Fb as x,Hb as U,Ib as Ae,ac as Ee,cc as ce,d as ye,f as E,k as D,oc as ze,qb as ie,wc as Ke}from"./chunk-LYNBZ7RV.js";import F from"chalk";import $t from"node:fs";import Ct from"node:os";import lt from"node:path";var mt=["23bc28f0b638fbfc1c7f838573c17ee3"];var Rt=/^[0-9a-f]{32}$/,pt=e=>Rt.test(e),me="EVERYAPP_TEST_ACCOUNTS";function Tt(e=process.env){let o=e.XDG_CONFIG_HOME||lt.join(Ct.homedir(),".config");return lt.join(o,"everyapp","test-accounts")}function dt(e){let o=new Set,a=[];for(let n of e.split(/\r?\n/)){let c=n.replace(/#.*$/,"");for(let i of c.split(/[\s,]+/)){if(!i)continue;let m=i.toLowerCase();pt(m)?o.add(m):a.push(i)}}return{ids:[...o],ignored:a}}function bt(e=process.env){let o=Tt(e),a="";try{a=$t.readFileSync(o,"utf8")}catch(i){if(i.code!=="ENOENT")throw i}let n=dt(e[me]??""),c=dt(a);return{ids:[...new Set([...n.ids,...c.ids])],ignored:[...n.ignored,...c.ignored],file:o}}function Nt(e,o){return["only for a dedicated test account that holds nothing else (docs/runbooks/onboarding-loop-test-accounts.md):",`set ${me}=${e} for this one command, or add a line ${e} to ${o}`].join(" ")}function ft({accountId:e,out:o,env:a=process.env}){let n=e.trim().toLowerCase();if(!pt(n))throw new $("TEARDOWN_INVALID_ACCOUNT_ID",`"${e.trim()}" isn't a Cloudflare account id
2
+ An account id is 32 hex characters (Compute (Workers) \u2192 Overview, or any dashboard URL dash.cloudflare.com/<account id>/...).`,{fix:"pass the test account's id from the dashboard as --account-id",data:{accountId:e.trim()}});if(mt.includes(n))throw new $("TEARDOWN_ACCOUNT_FORBIDDEN",`Cloudflare account ${n} can never be torn down
3
+ It is a live account, refused by id before any allowlist is read.`,{data:{accountId:n}});let c=bt(a);if(c.ignored.length>0&&o.emit({type:"warning",code:"TEARDOWN_ALLOWLIST_IGNORED",message:`Ignored ${E(c.ignored.length,"entry")} of the teardown allowlist that ${c.ignored.length===1?"isn't":"aren't"} account ids: ${D(c.ignored)}
4
+ An account id is 32 hex characters (${me}, ${c.file}).`,data:{ignored:c.ignored,file:c.file}}),!c.ids.includes(n))throw new $("TEARDOWN_NOT_ALLOWLISTED",`Cloudflare account ${n} isn't on the teardown allowlist
5
+ teardown deletes everything Every App made in an account, so it runs only against accounts listed as dedicated test accounts.`,{fix:Nt(n,c.file),data:{accountId:n,file:c.file}});if(!a.CLOUDFLARE_API_TOKEN)throw new $("TEARDOWN_NEEDS_TOKEN",`teardown needs CLOUDFLARE_API_TOKEN
6
+ A Cloudflare login is a person, who sees every account they belong to; teardown runs only with an API token scoped to the test account (runbook step 5).`,{fix:"set CLOUDFLARE_API_TOKEN to the test account's token for this one command, and run it again",data:{accountId:n}});return{accountId:n,allowlist:c.ids}}function ut({accountId:e,allowlist:o,whoami:a}){if(!a.authenticated||a.tokenValid===!1)throw new $("CLOUDFLARE_TOKEN_REJECTED",`Cloudflare didn't accept CLOUDFLARE_API_TOKEN
7
+ It may be mistyped, expired or revoked.`,{fix:"set the test account's token (runbook step 5)"});if(a.authSource&&!/CLOUDFLARE_API_TOKEN/.test(a.authSource))throw new $("TEARDOWN_NEEDS_TOKEN",`teardown needs CLOUDFLARE_API_TOKEN, but cf authenticated with ${a.authSource}
8
+ A Cloudflare login is a person, who sees every account they belong to; teardown runs only with an API token scoped to the test account (runbook step 5).`,{fix:"set CLOUDFLARE_API_TOKEN to the test account's token for this one command, and run it again",data:{accountId:e,authSource:a.authSource}});let n=(a.accounts??[]).map(i=>({id:i.id.toLowerCase(),name:i.name??null})),c=n.filter(i=>mt.includes(i.id)||!o.includes(i.id));if(c.length>0)throw new $("TEARDOWN_TOKEN_TOO_BROAD",[`CLOUDFLARE_API_TOKEN sees ${E(c.length,"account")} that ${c.length===1?"isn't a":"aren't"} test account${c.length===1?"":"s"}: ${D(c.map(i=>i.name?`${i.name} (${i.id})`:i.id))}`,"A teardown token must be scoped to the test account alone (Account Resources \u2192 Include \u2192 that account), so there is no path from here to a live account."].join(`
9
+ `),{fix:"mint a token for the test account only (runbook step 5)",data:{accountId:e,accounts:c}});if(!n.some(i=>i.id===e))throw new $("TEARDOWN_ACCOUNT_NOT_VISIBLE",`CLOUDFLARE_API_TOKEN doesn't list Cloudflare account ${e}
10
+ ${n.length===0?"It lists no accounts.":`It lists ${D(n.map(i=>i.id))}.`}`,{fix:"use that account's token, with Account Settings: Read on it (runbook step 5)",data:{accountId:e,accounts:n}})}import gt from"chalk";var te="every-",I=e=>e.startsWith(te),oe=e=>e.type==="MX"&&/\.mx\.cloudflare\.net\.?$/i.test(e.content??"")||e.type==="TXT"&&/_spf\.mx\.cloudflare\.net/i.test(e.content??""),Pt=new Set(["A","AAAA","CNAME"]),Z=e=>Pt.has(e.type.toUpperCase()),z=[["access","Access application or policy","Access applications and policy"],["email","Email Routing change","Email Routing changes"],["workflows","Workflow","Workflows"],["workers","Worker","Workers"],["certificates","certificate pack","certificate packs"],["d1","D1 database","D1 databases"],["kv","KV namespace","KV namespaces"],["r2","R2 bucket","R2 buckets"],["hyperdrive","Hyperdrive config","Hyperdrive configs"],["dns","DNS record","DNS records"]],H={access:"Zero Trust \u2192 Access \u2192 Applications and Policies",email:"the zone \u2192 Email \u2192 Email Routing",workflows:"Compute (Workers) \u2192 Workflows",workers:"Compute (Workers) \u2192 Workers & Pages",certificates:"the zone \u2192 SSL/TLS \u2192 Edge Certificates",d1:"Storage & Databases \u2192 D1",kv:"Storage & Databases \u2192 KV",r2:"Storage & Databases \u2192 R2 Object Storage",hyperdrive:"Storage & Databases \u2192 Hyperdrive",dns:"the zone \u2192 DNS \u2192 Records"},j=e=>e.reduce((o,a)=>o+a.rules.length+(a.catchAllWorker?1:0)+(a.disable?1:0),0),pe=e=>e.reduce((o,a)=>o+a.records.length,0);function K(e){return{access:e.access.applications.length+(e.access.policy?1:0),email:j(e.email),workflows:e.workflows.length,workers:e.workers.length,certificates:e.certificates.length,d1:e.d1.length,kv:e.kv.length,r2:e.r2.length,hyperdrive:e.hyperdrive.length,dns:pe(e.dns)}}function _(e){return Object.values(K(e)).reduce((o,a)=>o+a,0)}var k={worker:e=>`${W.worker} "${e}"`,workflow:e=>`Workflow "${e}"`,d1:e=>`${W.d1} "${e}"`,kv:e=>`${W.kv} "${e}"`,r2:e=>`${W.r2} "${e}"`,hyperdrive:e=>`${W.hyperdrive} "${e}"`,application:e=>`Access application "${e}"`,policy:e=>`Access policy "${e}"`,rule:(e,o)=>`Email Routing rule for ${e??"(no address)"} on ${o}`,catchAll:e=>`catch-all of ${e}`,routing:e=>`Email Routing on ${e}`,pack:(e,o)=>`certificate pack ${e} for ${o}`,record:(e,o)=>`DNS ${e} record at ${o}`};function Dt(e){let o=K(e),a={access:[...e.access.applications.map(n=>`application "${n.name}"`),...e.access.policy?[`policy "${e.access.policy.name}"`]:[]],email:e.email.flatMap(n=>[...n.rules.map(c=>`rule for ${c.address??"(no address)"} on ${n.zoneName}`),...n.catchAllWorker?[`${n.zoneName}'s catch-all back to dropping mail`]:[],...n.disable?[`Email Routing off for ${n.zoneName}`]:[]]),workflows:e.workflows,workers:e.workers.map(n=>n.domains.length>0?`${n.name} (${n.domains.join(", ")})`:n.name),certificates:e.certificates.map(n=>`${n.packId} (${n.hostname})`),d1:e.d1.map(n=>n.name),kv:e.kv.map(n=>n.title),r2:e.r2,hyperdrive:e.hyperdrive.map(n=>n.name),dns:e.dns.flatMap(n=>n.records.map(c=>`${c.type} at ${n.name}`))};return z.flatMap(([n,,c])=>o[n]===0?[]:[{label:`${c} (${o[n]})`,items:a[n]}])}function ht(e){let o=Dt(e).map(({label:a,items:n})=>({label:a,value:n.join(", ")}));return[...o.length>0?ze(o):[gt.dim("Nothing of Every App's to delete.")],...e.kept.length>0?["",gt.dim(`Kept: ${e.kept.join("; ")}.`)]:[]]}function ne(e){let o=K(e),a=z.flatMap(([n,c,i])=>o[n]===0?[]:[o[n]===1?`1 ${c}`:`${o[n]} ${i}`]);return`${E(_(e),"resource")}${a.length>0?`: ${a.join(", ")}`:""}`}function B(e){return{accountId:e.accountId,gateway:e.gateway,counts:K(e),total:_(e),workers:e.workers,workflows:e.workflows,d1:e.d1,kv:e.kv,r2:e.r2,hyperdrive:e.hyperdrive,access:e.access,email:e.email,certificates:e.certificates,dns:e.dns,kept:e.kept}}var It=e=>typeof e=="object"&&e!==null&&"removedAs"in e,_t={name:"",enabled:!1,matchers:[{type:"all"}],actions:[{type:"drop"}]},re="teardown.delete",Ot=5e3;async function yt({cf:e,plan:o,out:a,sleep:n=c=>new Promise(i=>setTimeout(i,c))}){let c=[],i=[],m=async(r,d,A)=>{try{let w=await A();if(w===!1)return;c.push(It(w)?w.removedAs:d)}catch(w){if(U(w,"not_found"))return;let f=x(w).split(`
11
+ `)[0]??"";i.push({kind:r,described:d,place:H[r],reason:f}),a.emit({type:"warning",code:"TEARDOWN_DELETE_FAILED",step:re,message:`Couldn't delete the ${d}
12
+ ${f}`,data:{kind:r,described:d,reason:f,fix:`delete it in the Cloudflare dashboard (${H[r]})`}})}},l=async(r,d,A)=>{if(d===0)return;let[,w,f]=z.find(([C])=>C===r),v=d===1?w:f,O=c.length,S=i.length;a.emit({type:"step",step:re,status:"start",message:`Deleting ${d} ${v}...`,data:{kind:r,count:d}}),await A();let N=c.length-O,L=i.length-S;a.emit({type:"step",step:re,status:"ok",message:L===0?`Deleted ${N} ${N===1?w:f}`:`Deleted ${N} ${N===1?w:f}; kept ${L}`,data:{kind:r,removed:c.slice(O),kept:i.slice(S).map(C=>C.described)}})};await l("access",o.access.applications.length+(o.access.policy?1:0),async()=>{for(let r of o.access.applications)await m("access",k.application(r.name),()=>Te(e,r.id));if(o.access.policy){let{id:r,name:d}=o.access.policy;await m("access",k.policy(d),()=>Ce(e,r))}}),await l("email",j(o.email),async()=>{for(let r of o.email){for(let d of r.rules)await m("email",k.rule(d.address,r.zoneName),()=>Je(e,r.zoneId,d.id));r.catchAllWorker&&await m("email",k.catchAll(r.zoneName),()=>qe(e,r.zoneId,_t)),r.disable&&await m("email",k.routing(r.zoneName),()=>Ge(e,r.zoneId))}}),await l("workflows",o.workflows.length,async()=>{for(let r of o.workflows)await m("workflows",k.workflow(r),()=>rt(e,r))}),await l("workers",o.workers.length,async()=>{for(let r of o.workers)await m("workers",k.worker(r.name),()=>Ue(e,r.name,{withReferences:!0}))}),await l("certificates",o.certificates.length,async()=>{for(let r of o.certificates){let d=k.pack(r.packId,r.hostname);await m("certificates",d,async()=>{try{await at(e,r.zoneId,r.packId);return}catch(A){if(U(A,"not_found"))throw A;let w=x(A).split(`
13
+ `)[0]??"",f=()=>ee(e,r.zoneId,{allStatuses:!0}),v=await f().catch(()=>null);if(v&&!de(v,r.packId)&&(await n(Ot),v=await f().catch(()=>v)),v&&de(v,r.packId))return{removedAs:`${d} (gone with its Custom Domain)`};throw new Error(`${w}. Cloudflare removes a hostname's pack with its Custom Domain, so it is likely on its way out; if the zone still lists it, finish it in the dashboard`)}})}});let h=o.d1.find(r=>r.id===o.gateway?.databaseId)??null,T=o.d1.filter(r=>r!==h);await l("d1",T.length,async()=>{for(let r of T)await m("d1",k.d1(r.name),()=>le(e,r.id))}),await l("kv",o.kv.length,async()=>{for(let r of o.kv)await m("kv",k.kv(r.title),()=>Le(e,r.id))}),await l("r2",o.r2.length,async()=>{for(let r of o.r2)await m("r2",k.r2(r),()=>ct(e,r))}),await l("hyperdrive",o.hyperdrive.length,async()=>{for(let r of o.hyperdrive)await m("hyperdrive",k.hyperdrive(r.name),()=>ot(e,r.id))});let b=[];for(let r of o.dns)try{for(let d of await q(e,r.zoneId,r.name))Z(d)&&(oe({type:d.type,content:d.content??null})||b.push({zoneId:r.zoneId,name:r.name,id:d.id,type:d.type}))}catch(d){a.emit({type:"warning",code:"TEARDOWN_DELETE_FAILED",step:re,message:`Couldn't read the DNS records at ${r.name}
14
+ ${x(d).split(`
15
+ `)[0]}`,data:{kind:"dns",name:r.name,fix:`check them in the Cloudflare dashboard (${H.dns})`}})}return await l("dns",b.length,async()=>{for(let r of b)await m("dns",k.record(r.type,r.name),()=>Be(e,r.zoneId,r.id))}),await l("d1",h?1:0,async()=>{if(!h)return;let r=i.filter(d=>d.kind==="dns");if(r.length>0){i.push({kind:"d1",described:k.d1(h.name),place:H.d1,reason:`kept on purpose: the next run reads the hostnames of the ${E(r.length,"kept DNS record")} above from it`});return}await m("d1",k.d1(h.name),()=>le(e,h.id))}),{removed:c,kept:i}}var fe="teardown.inventory";function St(e,o){return e===ie?1:o===ie?-1:e.localeCompare(o)}var Lt=(e,o)=>e===o.name||e.endsWith(`.${o.name}`);async function kt({cf:e,accountId:o,out:a}){a.emit({type:"step",step:fe,status:"start",message:`Looking for what Every App created in ${o}...`,data:{accountId:o}});let n=(t,s,p)=>a.emit({type:"step",step:"teardown.found",status:"ok",message:`${s} ${z.find(([u])=>u===t)[s===1?1:2]}`,data:{kind:t,count:s,items:p}}),c=[],i=async(t,s,p)=>{try{return await p()}catch(u){let g=x(u).split(`
16
+ `)[0]??"";if(Ae(u)){let R=U(u,"r2_not_enabled")?"R2":"Cloudflare Access";return a.emit({type:"step",step:"teardown.found",status:"skip",message:`${R} isn't enabled in this account: nothing of its to delete`,data:{what:t,service:R,reason:g}}),s}return c.push(`${t} (couldn't be listed: ${g})`),a.emit({type:"warning",code:"TEARDOWN_READ_FAILED",step:fe,message:`Couldn't list ${t}; whatever is there is kept
17
+ ${g}`,data:{what:t,reason:g,fix:"check the token's permissions (runbook step 5), then run again"}}),s}},m=await i("the Gateway's database",null,()=>xe(e)),l=m?Oe(e,m):null,h=l?await i("the Gateway's install row",null,()=>Se(l)):null,T=l?await i("the Gateway's apps",[],()=>We(l)):[],b=l?await i("the apps' recorded resources",[],()=>Fe(l)):[],r=t=>new Set(b.filter(s=>s.kind===t).map(s=>s.name)),d=(t,s)=>I(s)||r(t).has(s),A=new Set(T.flatMap(t=>t.workerName?[t.workerName]:[])),w=await i("the Workers",[],()=>Me(e)),f=w.filter(t=>d("worker",t.name)||A.has(t.name)).sort((t,s)=>St(t.name,s.name)).map(t=>({name:t.name,domains:(t.references?.domains??[]).map(s=>s.hostname.toLowerCase()).sort()}));n("workers",f.length,f.map(t=>t.name));let v=(await i("the Workflows",[],()=>nt(e,te))).filter(t=>I(t.script_name??"")||I(t.name)).map(t=>t.name).sort();n("workflows",v.length,v);let O=(await i("the D1 databases",[],()=>ce(e,["d1","list"],be,{perPage:100,maxPages:10}))).filter(t=>d("d1",t.name)).map(t=>({name:t.name,id:t.uuid})).sort((t,s)=>t.id===m?1:s.id===m?-1:t.name.localeCompare(s.name));n("d1",O.length,O.map(t=>t.name));let S=(await i("the KV namespaces",[],()=>ce(e,["kv","namespaces","list"],Ne,{perPage:100,maxPages:50}))).filter(t=>d("kv",t.title)).map(t=>({title:t.title,id:t.id})).sort((t,s)=>t.title.localeCompare(s.title));n("kv",S.length,S.map(t=>t.title));let N=(await i("the R2 buckets",[],()=>it(e,te))).filter(I).sort();n("r2",N.length,N);let L=(await i("the Hyperdrive configs",[],()=>tt(e))).filter(t=>d("hyperdrive",t.name)).map(t=>({name:t.name,id:t.id})).sort((t,s)=>t.name.localeCompare(s.name));n("hyperdrive",L.length,L.map(t=>t.name));let C={applications:[],policy:null},ue=await i("the Zero Trust team",null,()=>ve(e));if(ue){let t=h?{gatewayHost:h.gatewayHost.toLowerCase(),appBaseDomain:h.appBaseDomain.toLowerCase()}:null,s=await i("the Access applications",[],()=>Re(e)),p=t?Pe(s,t):{gateway:null,apps:null},u=s.filter(y=>y.name===He||y.name===je),g=new Map;for(let y of[p.gateway,p.apps,...u])y&&g.set(y.id,y);C.applications=[...g.values()].map(y=>({id:y.id,name:y.name??y.id}));let R=(await i("the Access policies",[],()=>$e(e))).find(y=>y.name===Ze);C.policy=R?{id:R.id,name:R.name}:null,c.push(`the Zero Trust team ${ue.auth_domain} and its identity providers`)}n("access",C.applications.length+(C.policy?1:0),{applications:C.applications.map(t=>t.name),policy:C.policy?.name??null});let M=new Set;if(h){M.add(h.gatewayHost.toLowerCase());for(let t of h.liveDomains)M.add(t.toLowerCase())}for(let t of T)t.hostname&&M.add(t.hostname.toLowerCase());for(let t of f)for(let s of t.domains)M.add(s);let ge=new Map((await i("the account's zones",[],()=>et(e,o))).map(t=>[t.id,{id:t.id,name:t.name}])),V=new Map,he=(t,s)=>{let p=ge.get(t);p?V.set(p.id,p):c.push(`the zone ${s.toLowerCase()} (${t}), which isn't in account ${o}`)};h&&he(h.zoneId,h.zoneName);for(let t of w)if(f.some(s=>s.name===t.name))for(let s of t.references?.domains??[])s.zone_id&&s.zone_name&&he(s.zone_id,s.zone_name);let Et=t=>{let s=[...ge.values()].filter(p=>Lt(t,p)).sort((p,u)=>u.name.length-p.name.length)[0];return s&&V.set(s.id,s),s??null},se=new Map;for(let t of[...M].sort()){let s=Et(t);s&&se.set(t,s)}for(let t of V.values())c.push(`the zone ${t.name}`);c.push("the Workers Paid subscription","the workers.dev subdomain");let G=[];for(let t of[...V.values()].sort((s,p)=>s.name.localeCompare(p.name))){let s=await i(`Email Routing on ${t.name}`,null,async()=>({settings:await Ve(e,t.id),rules:await Ye(e,t.id),catchAll:await Xe(e,t.id)}));if(!s)continue;let{settings:p,rules:u,catchAll:g}=s,R=u.filter(P=>P.id!==null&&I(Q(P)??"")),y=Q(g),ae=y!==null&&I(y);if(R.length===0&&!ae)continue;let vt=!g.enabled||g.actions.every(P=>P.type==="drop");G.push({zoneId:t.id,zoneName:t.name,rules:R.map(P=>({id:P.id,address:Qe(P),worker:Q(P)})),catchAllWorker:ae?y:null,disable:p.enabled&&R.length===u.length&&(ae||vt)})}n("email",j(G),G);let Y=[],we=new Map;for(let[t,s]of se){let p=we.get(s.id);p||(p=await i(`the certificate packs of ${s.name}`,[],()=>ee(e,s.id)),we.set(s.id,p));for(let u of st(p,t))p.splice(p.indexOf(u),1),Y.push({zoneId:s.id,zoneName:s.name,hostname:t,packId:u.id})}n("certificates",Y.length,Y.map(t=>`${t.packId} (${t.hostname})`));let J=[];for(let[t,s]of[...se].sort(([p],[u])=>p.localeCompare(u))){let p=await i(`the DNS records at ${t}`,[],()=>q(e,s.id,t)),u=[...new Set(p.filter(g=>!Z(g)&&!oe({type:g.type,content:g.content??null})).map(g=>g.type.toUpperCase()))].sort();u.length>0&&c.push(`the ${D(u)} ${u.length===1?"record":"records"} at ${t}, which Every App never created`),J.push({zoneId:s.id,zoneName:s.name,name:t,records:p.filter(Z).map(g=>({id:g.id,type:g.type,content:g.content??null}))})}n("dns",pe(J),J.filter(t=>t.records.length>0));let X={accountId:o,gateway:m?{databaseId:m,gatewayHost:h?.gatewayHost??null}:null,workers:f,workflows:v,d1:O,kv:S,r2:N,hyperdrive:L,access:C,email:G,certificates:Y,dns:J,kept:c};return a.emit({type:"step",step:fe,status:"ok",message:`Found ${E(_(X),"resource")} of Every App's in ${o}.`,data:{accountId:o,counts:K(X),total:_(X)}}),X}var At="teardown.check";async function Yo(e){await Ke(e,o=>xt({flags:e,out:o}))}async function xt(e){let{flags:o,out:a}=e,n=e.env??process.env,c=e.createCf??(f=>Ee({accountId:f,env:n})),i=e.interactive??De(o.json??!1),m=o["dry-run"]??!1;a.emit({type:"step",step:At,status:"start",message:"Checking the account may be torn down..."});let{accountId:l,allowlist:h}=ft({accountId:o["account-id"],out:a,env:n}),T=o.confirm?.trim().toLowerCase();if(T!==void 0&&T!==l)throw new $("TEARDOWN_CONFIRM_MISMATCH",`--confirm ${o.confirm} isn't the account id
18
+ It must repeat --account-id ${l} exactly. Nothing was read or changed.`,{fix:`pass --confirm ${l}`,data:{accountId:l,confirm:o.confirm}});ut({accountId:l,allowlist:h,whoami:await _e(c())}),a.emit({type:"step",step:At,status:"ok",message:`${l} is a test account, and the token sees it alone`,data:{accountId:l}});let b=c(l),r=await kt({cf:b,accountId:l,out:a}),d=_(r),A=ht(r);if(m){let f=d===0?`Nothing of Every App is left in ${l}.`:`Dry run: ${ne(r)} would be removed from ${l}. Nothing was changed.`;a.emit({type:"done",message:f,display:[F.bold(d===0?f:`Would remove ${ne(r)} from ${l}`),...d===0?A.slice(1):A,...d===0?[]:["",F.dim("Nothing was changed (--dry-run).")]].join(`
19
+ `),data:{accountId:l,dryRun:!0,removed:[],kept:[],plan:B(r)}});return}if(d===0){let f=`Nothing of Every App is left in ${l}.`;a.emit({type:"done",message:f,display:[F.green(f),...A.slice(1)].join(`
20
+ `),data:{accountId:l,dryRun:!1,removed:[],kept:[],plan:B(r)}});return}if(T===void 0){if(!i)throw new $("TEARDOWN_CONFIRMATION_REQUIRED",`Tearing down ${l} needs its id typed at a terminal, or --confirm ${l}
21
+ ${ne(r)} would be deleted for good. Nothing was changed.`,{rerun:ye(["--confirm",l]),rerunNote:"only for a dedicated test account",authorize:!0,data:{accountId:l,plan:B(r)}});let f=(await(e.askConfirmation??Wt)(l,A)).trim().toLowerCase();if(f!==l)throw new ke(f?`"${f}" doesn't match ${l}. Nothing deleted.`:"Cancelled. Nothing deleted.")}else i&&a.emit({type:"step",step:"teardown.plan",status:"ok",message:`Deleting everything Every App created in ${l}:`,data:{accountId:l,lines:A}});let w=await yt({cf:b,plan:r,out:a,...e.sleep?{sleep:e.sleep}:{}});a.emit({type:"done",...zt(l,r,w)})}function Wt(e,o){return Ie(`Type ${e} to delete all of this`,{context:[`This deletes everything Every App created in Cloudflare account ${e}, for good:`,"",...o,""]})}function zt(e,o,{removed:a,kept:n}){let c=n.length===0?`Removed ${E(a.length,"resource")} from ${e}.`:`Removed ${E(a.length,"resource")} from ${e}; kept ${n.length} (${D(n.map(m=>`${m.described}: ${m.reason}`))}).`,i=[F.green(`Removed ${E(a.length,"resource")} from ${e}`),...n.length>0?["",`Kept ${E(n.length,"resource")}:`,...n.map(m=>` ${m.described} ${F.dim(`\xB7 ${m.reason} \xB7 ${m.place}`)}`)]:[],...o.kept.length>0?["",F.dim(`Left as they were: ${o.kept.join("; ")}.`)]:[]].join(`
22
+ `);return{message:c,display:i,data:{accountId:e,dryRun:!1,removed:a,kept:n,plan:B(o)}}}export{Yo as teardown,xt as teardownAccount};
@@ -0,0 +1 @@
1
+ import{b as e}from"./chunk-ZEXVHB22.js";import{c as o}from"./chunk-CZTEDWZ2.js";import"./chunk-N4E5YE4X.js";import"./chunk-AGTCKFRB.js";import"./chunk-E3EPK27P.js";import"./chunk-IFCPALVC.js";import"./chunk-WOVS3GV5.js";import"./chunk-OM2HDWVP.js";import"./chunk-2Y5Q6A5K.js";import"./chunk-H6QQMY7B.js";import"./chunk-6OGVO7QK.js";import"./chunk-N5GQLGEC.js";import"./chunk-W3JCQHHU.js";import"./chunk-TLRM3P7V.js";import"./chunk-ZFCUH5PK.js";import"./chunk-4JRIS6S6.js";import"./chunk-23HJ5I2A.js";import"./chunk-ZECBN24L.js";import"./chunk-ONLNJXVQ.js";import"./chunk-6PGUCTJ6.js";import"./chunk-HOUHG236.js";import"./chunk-RMF4YGD6.js";import"./chunk-NFU4YFXB.js";import"./chunk-T2GD7RHS.js";import"./chunk-H7PDL3X5.js";import"./chunk-CCWBPAKH.js";import"./chunk-JYKRUS5C.js";import"./chunk-LYNBZ7RV.js";async function m(a){await e(a,(t,y)=>o(a,t,y))}export{m as update};
@@ -0,0 +1,21 @@
1
+ import{a as w,b as X,e as v,f as Z,g as Q,h as $,i as b}from"./chunk-ELK7QZ2M.js";import{a as S,b as G,f as L,i as j,j as D,k as E,l as F,m as K,n as Y,o as q,p as B,q as J,r as V,s as H}from"./chunk-M2QQ2UD7.js";import{a as T}from"./chunk-4JRIS6S6.js";import{$ as W,qa as _,sa as P}from"./chunk-RMF4YGD6.js";import"./chunk-NFU4YFXB.js";import"./chunk-T2GD7RHS.js";import{a as R,i as N}from"./chunk-CCWBPAKH.js";import"./chunk-JYKRUS5C.js";import{Bb as f,Ia as x,c as h,f as y,k as z,oc as C,pc as U,qb as O,rc as M,wc as l}from"./chunk-LYNBZ7RV.js";import{stripVTControlCharacters as A}from"node:util";import d from"chalk";var u="workspace.update",ee="workspace.rename",te="workspace.role";async function Ue(e,n){await l(e,t=>ce({flags:e,out:t},n))}async function Me(e,n,t){await l(e,o=>re({flags:e,out:o},n,t))}function ie(e){let{flags:n,out:t}=e;return P({gateway:_(n.gateway),accountId:n["account-id"],interactive:R(n.json??!1),login:{json:n.json,out:t,step:"login"},createCf:e.createCf})}var ae=e=>`${/^[aeiou]/.test(e)?"an":"a"} ${e}`;async function re(e,n,t){let{out:o}=e,i=n.trim().toLowerCase(),c=await ie(e),s=await W(c.db);if(!s)throw new f("MEMBER_NOT_FOUND",`This Gateway has no workspace yet
2
+ Nobody has claimed its owner link, so it has no members.`,{rerun:`${h} setup`,rerunNote:"to claim it"});let{organization:r}=s,m={id:r.id,name:r.name};o.emit({type:"step",step:te,status:"start",message:`Setting ${i}'s role...`});let a=await S(c.db,r.id,i,t);if(a.kind==="not_found")throw new f("MEMBER_NOT_FOUND",`No member ${i} in ${r.name}
3
+ Someone still invited isn't a member until they accept.`,{fix:"check the address against Manage workspace \u2192 Users, or invite them first",data:{workspace:m,email:i}});let p={id:a.member.id,email:a.member.email,name:a.member.name};if(a.kind==="last_owner")throw new f("LAST_OWNER",`${i} is the last owner of ${r.name}
4
+ The workspace always keeps one owner, so their role stays until someone else is an owner.`,{rerun:`${h} workspace role <email> owner`,rerunNote:"for someone else, then this command again",data:{workspace:m,member:p,role:t}});let g=a.kind==="changed",k=g?`${i} is now ${ae(t)} of ${r.name}`:`${i} is already ${ae(t)} of ${r.name}`;o.emit({type:"step",step:te,status:"ok",message:k,data:{email:i,role:t,changed:g}});let I=g?"The Gateway picks up the change within 30 seconds.":"Nothing changed.";o.emit({type:"done",message:A(`${k}. ${I}`),display:[g?M(k):d.dim(`${k}.`),d.dim(I)].join(`
5
+ `),data:{workspace:m,member:p,role:t,changed:g,...g?{previousRole:a.previousRole}:{}}})}async function ce(e,n){let{out:t}=e,o=x(n);if(!o)throw new f("WORKSPACE_NAME_INVALID",`Use 1 to ${64} characters with no line breaks
6
+ A name is trimmed, and runs of spaces become one.`,{fix:"give the name as one argument, quoted when it has spaces"});let i=await ie(e);t.emit({type:"step",step:ee,status:"start",message:"Renaming the workspace..."});let{organization:c,previousName:s,changed:r}=await G(i.db,o);t.emit({type:"step",step:ee,status:"ok",message:r?`Renamed ${s} to ${o}`:`${o} already`,data:{id:c.id,name:o,previousName:s,changed:r}});let m=r?`Renamed the workspace to ${o}.`:`The workspace is named ${o} already.`,a=r?"Members see the name on their next page.":"Nothing changed.";t.emit({type:"done",message:A(`${m} ${a}`),display:[m,d.dim(a)].join(`
7
+ `),data:{organization:{id:c.id,name:c.name,slug:c.slug},previousName:s,changed:r,gateway:i.gatewayUrl}})}async function Se(e){await l(e,n=>de({flags:e,out:n}))}async function Ge(e){await l(e,n=>pe({flags:e,out:n}))}async function Le(e,n){await l(e,t=>le({flags:e,out:t},n,e.role))}async function je(e,n){await l(e,t=>ge({flags:e,out:t},n))}async function De(e,n){await l(e,t=>we({flags:e,out:t},n))}async function Fe(e,n){await l(e,t=>ue({flags:e,out:t},n))}async function Ke(e,n){await l(e,t=>ye({flags:e,out:t},n))}async function Ye(e,n){await l(e,t=>ve({flags:e,out:t},n))}async function qe(e,n){await l(e,t=>ke({flags:e,out:t},n))}var ne=["CLOUDFLARE_ACCESS_AUTO_JOIN_DOMAINS","CLOUDFLARE_ACCESS_OUTER_RING_AUTO_JOIN"];function me(e){return e.includes("EMAIL_FROM")&&(e.includes("RESEND_API_KEY")||e.includes("EMAIL_REST_API_TOKEN"))}async function de(e){let{out:n}=e,{gateway:t,ws:o,gatewayUrl:i}=await w(e),[c,s]=await Promise.all([j(o),T(t.cf,O)]),{organization:r,owners:m,counts:a}=c,p=o.operator,g=me(s),k=p.email===null?`${p.actor} (an API token; no account)`:p.userId?`${p.email}`:`${p.email} (no account on this Gateway)`,I=[{label:"Organization",value:`${r.name} ${d.dim(`(${r.slug}, ${r.id})`)}`},{label:"Gateway",value:d.cyan(i)},{label:"Owners",value:m.length>0?m.join(", "):d.yellow("none yet (claim the Gateway: everyapp setup)")},{label:"You",value:k},{label:"Email",value:g?"configured: the Gateway can mail invitations from Admin \u2192 Users":d.dim("not configured: this CLI prints links to hand over")},{label:"Members",value:`${a.members}${a.deactivated>0?d.dim(` (${a.deactivated} deactivated)`):""}`},{label:"Invitations",value:`${a.invitations} pending`},{label:"Groups",value:String(a.groups)},{label:"Apps",value:String(a.apps)}],oe=`${r.name}: ${y(a.members,"member")}, ${y(a.invitations,"pending invitation")}, ${y(a.groups,"group")}, ${y(a.apps,"app")}`;n.emit({type:"done",message:oe,display:C(I).join(`
8
+ `),data:{organization:r,gateway:i,owners:m,operator:{actor:p.actor,email:p.email,userId:p.userId},email:{configured:g},counts:a}})}async function pe(e){let{out:n}=e,{ws:t}=await w(e),{members:o,invitations:i}=await D(t),c=[...o.map(a=>[a.email,a.name&&a.name!==a.email?a.name:d.dim("\u2013"),a.primaryRole??a.role,Q(a),v(a.createdAt)]),...i.map(a=>[a.email,d.dim("\u2013"),a.role,a.live?d.cyan(`invited, expires ${v(a.expiresAt)}`):d.dim("invitation expired"),v(a.createdAt)])],s=U(["EMAIL","NAME","ROLE","STATUS","SINCE"],c),r=i.filter(a=>!a.live).length,m=`${y(o.length,"member")} and ${y(i.length-r,"pending invitation")}${r>0?` (${r} expired)`:""} in ${t.organization.name}`;n.emit({type:"done",message:m,display:c.length>0?s.join(`
9
+ `):d.dim(`Nobody in ${t.organization.name} yet.`),data:{organization:t.organization,members:o.map($),invitations:i.map(b)}})}async function le(e,n,t){let{out:o}=e,{ws:i,gatewayUrl:c}=await w(e);o.emit({type:"step",step:u,status:"start",message:`Inviting ${n}...`});let{invitation:s,token:r}=await K(i,n,t),m=E(c,r);o.emit({type:"step",step:u,status:"ok",message:`Invited ${s.email} as ${t}`,data:{invitationId:s.id}});let a=[`Invited ${s.email} to ${i.organization.name} as ${t}.`,`Send them this link (it is the invitation's only secret; it expires ${v(s.expiresAt)} UTC):`,` ${m}`,"The Gateway mailed nothing; Admin \u2192 Users mails invitations when email is configured."];o.emit({type:"done",message:A(a.join(`
10
+ `)),display:[a[0],d.dim(a[1]),d.cyan(a[2]),d.dim(a[3])].join(`
11
+ `),data:{organization:i.organization,invitation:b(s),url:m,emailed:!1,next:[{command:`${h} workspace invite-link ${s.email}`,note:"a new link, if this one is lost"}]}})}async function ge(e,n){let{out:t}=e,{ws:o,gatewayUrl:i}=await w(e),{invitation:c,token:s}=await Y(o,n),r=E(i,s),m=[`A new link for ${c.email}'s invitation to ${o.organization.name} (expires ${v(c.expiresAt)} UTC):`,` ${r}`,"Earlier links keep working until the invitation is accepted or canceled."];t.emit({type:"done",message:A(m.join(`
12
+ `)),display:[m[0],d.cyan(m[1]),d.dim(m[2])].join(`
13
+ `),data:{organization:o.organization,invitation:b(c),url:r}})}async function we(e,n){let{out:t}=e,{ws:o}=await w(e);t.emit({type:"step",step:u,status:"start",message:`Canceling the invitation for ${n}...`});let i=await q(o,n);t.emit({type:"step",step:u,status:"ok",message:`Canceled ${i.email}'s invitation`}),t.emit({type:"done",message:`Canceled ${i.email}'s invitation to ${o.organization.name}. Its links no longer work.`,data:{organization:o.organization,invitation:b(i)}})}async function ue(e,n){let{out:t}=e,{ws:o}=await w(e);t.emit({type:"step",step:u,status:"start",message:`Deactivating ${n}...`});let{member:i,already:c}=await B(o,n);t.emit({type:"step",step:u,status:"ok",message:`Deactivated ${i.email}`,data:{userId:i.id,already:c}});let s=c?[`${i.email} was deactivated already.`,"Their sessions, tokens and AI connections are revoked again, in case any came back."]:[`Deactivated ${i.email}.`,"They can't sign in anywhere on this Gateway; their sessions are ended and their personal access tokens and AI connections revoked.",`${h} workspace reactivate ${i.email} lets them back in (revoked tokens and connections stay revoked).`];t.emit({type:"done",message:s.join(`
14
+ `),display:[s[0],...s.slice(1).map(r=>d.dim(r))].join(`
15
+ `),data:{organization:o.organization,member:$(i),already:c}})}async function ye(e,n){let{out:t}=e,{ws:o}=await w(e);t.emit({type:"step",step:u,status:"start",message:`Reactivating ${n}...`});let{member:i,already:c}=await J(o,n);t.emit({type:"step",step:u,status:"ok",message:`Reactivated ${i.email}`,data:{userId:i.id,already:c}});let s=c?[`${i.email} was active already. Nothing changed.`]:[`Reactivated ${i.email}.`,"They can sign in again. Tokens and AI connections revoked at deactivation stay revoked: they make new ones."];t.emit({type:"done",message:s.join(`
16
+ `),display:[s[0],...s.slice(1).map(r=>d.dim(r))].join(`
17
+ `),data:{organization:o.organization,member:$(i),already:c}})}async function ve(e,n){let{out:t,flags:o}=e,{gateway:i,ws:c}=await w(e),s=await L(c,n);await N({yes:o.yes,interactive:X(e),confirm:e.confirm,question:`Remove ${s.email} from ${c.organization.name}?`,unattended:"Removing a member needs confirmation at a terminal, or --yes",context:["Their membership, app grants, group places, tokens and AI connections for the organization go;","their account too, when it belongs to no other organization."],cancelled:"Cancelled. Nothing removed.",data:{userId:s.id,email:s.email}}),t.emit({type:"step",step:u,status:"start",message:`Removing ${s.email}...`});let r=i.install.deploymentMode==="hosted",m=await V(c,s.id,{deleteAccounts:!r,accessMayLetBackIn:async()=>{let p=await T(i.cf,O);return ne.some(g=>p.includes(g))}});t.emit({type:"step",step:u,status:"ok",message:m.deactivatedInstead?`Deactivated ${s.email} instead`:`Removed ${s.email}`,data:{userId:s.id,deactivatedInstead:m.deactivatedInstead,accountDeleted:m.accountDeleted}});let a=m.deactivatedInstead?[`Deactivated ${s.email} instead of removing them.`,`The Gateway has ${z(ne)} or one of them set, so Cloudflare Access may let them straight back in with a new account; a deactivation keeps them out. They stay listed.`,`To delete the account anyway, unset the setting first (${h} secret unset <NAME>) and run this again.`]:[`Removed ${s.email} from ${c.organization.name}.`,"Their grants and group places went with the membership; their tokens for it and AI connections to it are revoked.",m.accountDeleted?"Their account is deleted: it belonged to no other organization.":m.otherOrganizations>0?`Their account stays: they belong to ${y(m.otherOrganizations,"other organization")}.`:"Their account stays: this Gateway keeps accounts."];t.emit({type:"done",message:a.join(`
18
+ `),display:[a[0],...a.slice(1).map(p=>d.dim(p))].join(`
19
+ `),data:{organization:c.organization,member:$(m.member),deactivatedInstead:m.deactivatedInstead,accountDeleted:m.accountDeleted,otherOrganizations:m.otherOrganizations}})}async function ke(e,n){let{out:t}=e,{ws:o,gatewayUrl:i}=await w(e),{member:c,token:s,expiresAt:r}=await H(o,n),m=F(i,s),a=[`A password-reset link for ${c.email} (single use; expires ${v(r)} UTC):`,` ${m}`,"Whoever opens it sets the account's password: hand it to them yourself."];t.emit({type:"done",message:A(a.join(`
20
+ `)),display:[a[0],d.cyan(a[1]),d.dim(a[2])].join(`
21
+ `),data:{organization:o.organization,member:$(c),url:m,expiresAt:Z(r)}})}export{we as cancelInvitation,Fe as deactivate,ue as deactivateMember,Le as invite,je as inviteLink,le as inviteMember,pe as listUsers,ge as newInviteLink,ke as passwordResetLink,Ke as reactivate,ye as reactivateMember,Ye as remove,ve as removeMember,Ue as rename,ce as renameWorkspace,qe as resetLink,Me as role,re as setRole,Se as show,de as showWorkspace,De as uninvite,Ge as users};
package/package.json CHANGED
@@ -4,23 +4,21 @@
4
4
  "description": "Every App CLI - Deploy and manage apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "version": "0.2.2",
7
+ "version": "0.3.0-rc.2",
8
8
  "files": [
9
9
  "dist",
10
10
  "bin",
11
- "gateway"
11
+ "skills"
12
12
  ],
13
13
  "bin": {
14
- "everyapp": "./bin/everyapp.js",
15
- "__everyapp_bash_complete": "./bin/__everyapp_bash_complete.js"
14
+ "everyapp": "./bin/everyapp.js"
16
15
  },
17
16
  "engines": {
18
- "node": ">=22"
17
+ "node": ">=22.18.0"
19
18
  },
20
19
  "tsup": {
21
20
  "entry": {
22
- "index": "src/bin/cli.ts",
23
- "bash-complete": "src/bin/bash-complete.ts"
21
+ "index": "src/bin/cli.ts"
24
22
  },
25
23
  "format": [
26
24
  "esm"
@@ -31,19 +29,21 @@
31
29
  "minify": true,
32
30
  "noExternal": [
33
31
  "@every-app/perimeter",
34
- "@every-app/sdk"
32
+ "@every-app/sdk",
33
+ "@every-app/toolbus"
35
34
  ]
36
35
  },
37
36
  "dependencies": {
38
- "@stricli/auto-complete": "^1.2.4",
39
- "@stricli/core": "^1.2.4",
37
+ "@cloudflare/config": "0.24.1",
38
+ "@inquirer/prompts": "^8.7.3",
39
+ "@stricli/core": "^1.3.0",
40
+ "cf": "1.0.0-beta.14",
40
41
  "chalk": "^5.6.2",
41
- "enquirer": "^2.4.1",
42
+ "cli-cursor": "^5.0.0",
42
43
  "execa": "^9.6.0",
43
44
  "jiti": "^2.6.1",
44
- "jsonc-parser": "^3.3.1",
45
- "smol-toml": "^1.4.2",
46
- "zod": "^4.1.13"
45
+ "undici": "^7.30.1",
46
+ "zod": "^4.2.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^22.18.13",
@@ -51,6 +51,8 @@
51
51
  "tsup": "^6.7.0",
52
52
  "typescript": "5.9.3",
53
53
  "vitest": "^3.2.4",
54
+ "@every-app/sdk": "0.3.0-rc.2",
55
+ "@every-app/toolbus": "0.1.0",
54
56
  "@every-app/perimeter": "0.1.0"
55
57
  },
56
58
  "scripts": {
@@ -59,8 +61,9 @@
59
61
  "build": "tsup --silent",
60
62
  "dev": "tsup --watch",
61
63
  "install:global": "pnpm run build && npm link --ignore-scripts",
62
- "build:prepack": "pnpm install --ignore-scripts && npm run types:check && npm run build && node scripts/bundle-gateway.js",
63
64
  "knip": "knip",
64
- "test": "vitest run --silent"
65
+ "test": "vitest run --silent",
66
+ "test:built": "EVERYAPP_BUILT_CLI=1 vitest run src/bin/builtCli.test.ts",
67
+ "docs:update": "UPDATE_DOCS=1 vitest run src/docs --silent"
65
68
  }
66
69
  }
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: build-every-app
3
+ description: Build or change an app on Every App: create one, add a feature, a page, a table, an AI tool, a capability, a public route or a secret, the way the apps already on Every App do it. Use before writing any code for an Every App app, for "build me an app", "add X to the app" or "make the app do Y".
4
+ ---
5
+
6
+ # Build an app on Every App
7
+
8
+ An Every App app is a private web app on the person's own Cloudflare account,
9
+ behind their Gateway. The Gateway signs people in, holds every key, serves the
10
+ app's tools to AI clients, and gives the app nothing it doesn't declare in
11
+ `everyapp.config.ts`. You build the app; the plumbing is done.
12
+
13
+ ## Read first
14
+
15
+ 1. `patterns.md`, beside this file (in an app,
16
+ `.agents/skills/build-every-app/patterns.md`; in the checkout,
17
+ `packages/cli/skills/build-every-app/patterns.md`; on the web,
18
+ https://everyapp.com/docs/agent/patterns): how the apps on Every App do
19
+ each thing, and the file to copy from, in the starter or in one of four
20
+ open apps (OpenCRM, OpenAnalytics, OpenSEO, Agentic Inbox), at
21
+ `https://github.com/every-app/<name>`. Read the sections for the job
22
+ before the first edit, every time: identity and tests for a table, tools
23
+ for a tool, the manifest for a capability. Copy a working file over
24
+ inventing one; when a cited file is out of reach, the row's "What it is"
25
+ and "Copy this" columns are the pattern.
26
+ 2. The everyapp skill beside this one drives the CLI: every command,
27
+ `--json`, setup, secrets, what only a person can do. Read it before running
28
+ `pnpm everyapp`; never guess a flag.
29
+ 3. https://everyapp.com/llms.txt lists every agent page with a one-line
30
+ summary; a page is Markdown at its URL plus `.md`. In an Every App checkout
31
+ they are `landing-page/content/agent/*.md`. Read the page for the job:
32
+ manifest (`everyapp.config.ts`: resources, public, providers,
33
+ capabilities, secrets, migrations), identity (what the Worker receives,
34
+ the SDK's exports), secrets (where a key lives, `.dev.vars`), mcp (tools,
35
+ the connector, MCP apps),
36
+ dev (dev vs production), deploy (release and install), email.
37
+
38
+ ## The shape of an app
39
+
40
+ `pnpm everyapp create ../my-app`, from the every-app folder, copies the starter
41
+ (`starter/` in the checkout; in the app it made, a `starter/` path is that path
42
+ in the app) to `my-app` beside the checkout (the path's last part is the app's
43
+ id), a git repository of its own linked to the checkout: a TanStack Start app
44
+ on a Worker with a D1 database, a todo list whose first screen shows who you
45
+ are and what the app was allowed. One repository per app is the default. (With
46
+ many apps, keep them inside the checkout instead: `pnpm everyapp create my-app`
47
+ there makes `apps/my-app`, untracked by git.)
48
+ Read the app's `README.md`, `AGENTS.md` and every file under `src/` once; they
49
+ are short. An app may carry skills of its own at its top level
50
+ (`.agents/skills/<name>`, `.claude/skills/<name>`), as OpenSEO's repository
51
+ does; read them before changing the app.
52
+
53
+ Keep, the plumbing: `everyapp.config.ts` (the manifest), `cloudflare.config.ts`
54
+ (builds the Worker from it), `src/entry.worker.ts` (`everyApp()` verifies the
55
+ identity and serves the tools), `src/middleware/ensureUser.ts` (who the
56
+ Gateway signed in), `src/db/index.ts`, `src/env.d.ts`, `vite.config.ts`,
57
+ `drizzle.config.ts`, `vitest.config.ts`, `src/routes/__root.tsx`,
58
+ `src/router.tsx`.
59
+
60
+ The example, which the app outgrows: `src/routes/index.tsx` (the page),
61
+ `src/serverFunctions/` (what the page calls, each behind
62
+ `ensureUserMiddleware`), `src/server/todos.ts` and its test (the queries),
63
+ `src/db/schema.ts` and `drizzle/` (the table and its migrations),
64
+ `src/tools.ts` (the same queries as tools), `src/routes/health.ts` (a JSON
65
+ endpoint without a page; private, like every route not under `public`). Keep
66
+ the layering: page, then server function, then a query in `src/server/`, so the
67
+ page, the tools and the test share one set of queries.
68
+
69
+ Add means add. A feature the person asks for goes beside what the app has:
70
+ the todo list, its first screen and its migration stay until the person asks
71
+ for a different app or says to take them out. Replace the example when the app
72
+ becomes something else, not on the first feature.
73
+
74
+ ## The rules
75
+
76
+ 1. Identity only through the SDK: `context.user` behind `ensureUserMiddleware`
77
+ in a server function, the `user` a tool handler receives,
78
+ `getEveryAppUser()` on a route without a page (`null` only on a public
79
+ route, for anyone not signed in). Never from a header, cookie, query or
80
+ body a client sent.
81
+ 2. Scope every query by `user.id` unless sharing is the app's intent. Then the
82
+ unit is the organization: the four apps key every row by the organization
83
+ in the signed identity (`user.orgId`), every member sees the organization's
84
+ rows, and owners and admins (`user.orgRole`) manage. An app for the whole
85
+ organization also says so in the manifest: `access: "everyone"` (the
86
+ default, `admins`, opens it to the deployer, owners and admins alone).
87
+ Never an allowlist of users of your own: the Gateway decides who may open
88
+ the app.
89
+ 3. Routes are private unless listed under `public` in `everyapp.config.ts`. A
90
+ public route exists only when the app needs one (a webhook, a collector, an
91
+ OAuth callback); the starter has none, and its manifest's comment shows a
92
+ webhook. It is open to the internet and checks what it must itself (a
93
+ signature, a token the app issued, a rate limit), and returns nothing that
94
+ is anyone's without that check.
95
+ 4. Never a login page, session, password, token check or MCP server of the
96
+ app's own. The Gateway signs people in, and its one `/mcp` is the connector
97
+ for every app; an app's host answers `/mcp` only with the manifest opt-in
98
+ `mcp: { by, why }`, which none of the four apps needs. Never set a cookie:
99
+ the Gateway strips every cookie before a request reaches the app.
100
+ 5. A key only this app uses goes under `secrets` (required by default;
101
+ `optional: true` when one feature can wait; `anyOf` for one of a group),
102
+ typed in `src/env.d.ts`, read from `env`. After the first deploy,
103
+ `pnpm everyapp configure <app>` is the one command that sets up everything
104
+ the app declares, each value at a hidden prompt (before that deploy,
105
+ `pnpm everyapp configure` in the app's directory sets the Gateway's
106
+ settings); `pnpm everyapp secret set NAME` sets one name, and is the form
107
+ for a script. Locally, `.dev.vars`. Never ask for, print or repeat a value
108
+ in the chat; never put one in a file you commit or on a command line.
109
+ 6. AI through the Gateway: the provider under `providers` with a `why`, called
110
+ with `createGatewayFetch({ env, for: request })`; `ai.models` lists the
111
+ models the app may ask for. The Gateway holds the key; a provider the
112
+ manifest doesn't list is refused.
113
+ 7. A capability (`members`, `email`, `dataforseo`, `context`, an OAuth client)
114
+ under `capabilities` with `access` and a `why`; an OAuth client
115
+ (`google-workspace`, `microsoft-365`, `slack`) also with `scopes: [...]`,
116
+ the least that does the job. Called with `listMembers()`, `sendEmail()`,
117
+ `integrationFetch()`; a refusal is a state the page shows, never a crash.
118
+ 8. Tools in `defineTools()` over the same `src/server/` queries, each with an
119
+ honest `risk` (`read`, `write`, `destructive`), a description the model can
120
+ act on, and `instructions` for the set. Throw `ToolError` for a message
121
+ the model may read. A destructive tool stays hidden until the Gateway has
122
+ approvals; declare it anyway.
123
+ 9. Schema changes through drizzle: edit `src/db/schema.ts`, run
124
+ `pnpm run db:generate`, commit the migration it writes. Never edit a
125
+ migration that has been deployed.
126
+ 10. A test over the queries (`src/server/*.test.ts`: an in-memory SQLite with
127
+ the app's migrations applied), including one that two people's rows stay
128
+ apart.
129
+ 11. Before a pull request: `pnpm types:check`, `pnpm test`, and a security
130
+ review when identity, public routes, tools, secrets or queries changed:
131
+ `/security-review` in Claude Code against the app boundaries in
132
+ `.agents/skills/everyapp/security-review.md` (in the checkout,
133
+ `packages/cli/skills/everyapp/security-review.md`); any other agent
134
+ reads `git diff` against them. No CI job, no API key.
135
+ 12. `description` in `everyapp.config.ts` is the one line the app library
136
+ shows; change it, and `name`, as soon as the app stops being a todo list.
137
+ Add an `icon` (an SVG the app serves) when it has a look.
138
+ 13. The way back: a link to the Gateway. The starter's header has one ("All
139
+ apps", `launcher` in `src/serverFunctions/home.ts`: the identity issuer,
140
+ `EVERYAPP_IDENTITY_ISSUER`, or the dev host under `everyapp dev`; the four
141
+ apps read `GATEWAY_URL` first, then the issuer); keep it. When the app
142
+ gets a sidebar, the convention the identity page states is the first row:
143
+ muted, "‹ Every App", aria-label "Back to Every App", above the app's own
144
+ name. OpenCRM and OpenSEO keep theirs in the sidebar footer today,
145
+ labelled "Back to Every App".
146
+ 14. After editing `everyapp.config.ts`, restart `pnpm dev`: the local Gateway
147
+ reads it when it starts (the tools catalog is the exception). Every
148
+ manifest change reaches the real Gateway on the next deploy.
149
+
150
+ ## Working with the person
151
+
152
+ For a new app, or when the ask leaves these open, ask five short questions:
153
+ what the app is for; who uses it (just them, or everyone in the organization:
154
+ then `access: "everyone"`); what it stores (the tables); what it does on its
155
+ own (sends mail, calls a model, runs on a schedule, serves a script to a
156
+ website); what stays private to each person. Repeat the answers back in one
157
+ paragraph, then build. For a clear request, build.
158
+
159
+ The loop: `pnpm everyapp create ../my-app` from the every-app folder (or work
160
+ in the app you are in); the schema and the queries, the server functions, the
161
+ page, the tools, the test; `pnpm dev` in the background, open the printed URL
162
+ as the test user; iterate; `pnpm run deploy` when they say so (it prints the
163
+ app's address and who can open it: tell them). Manage the workspace (people,
164
+ groups, who can open an app, caps, usage, what agents did):
165
+ `pnpm everyapp workspace --help`, or the `everyapp_workspace_*` tools on
166
+ `/mcp` as an owner or admin. A required secret, provider or capability keeps
167
+ the app at "Needs setup" until it is set: say which, and the one command,
168
+ `pnpm everyapp configure <app>`. For anything the CLI does, read the everyapp
169
+ skill. Before building a tool over a data provider the Gateway offers as an
170
+ MCP app, say so: DataForSEO's tools reach the person's AI client with no page
171
+ and nothing to deploy, `pnpm everyapp mcp on dataforseo`
172
+ (https://everyapp.com/docs/agent/mcp#mcp-apps).
173
+
174
+ For a non-technical person, say in plain words: what they will see (the page,
175
+ its first screen); where the app is (its own address, behind their Gateway, on
176
+ their Cloudflare account); who can open it (every member of the organization,
177
+ or owners and admins; a public route, if there is one, is open to anyone);
178
+ what it costs (Cloudflare usage, and any paid service a `why` line names); and
179
+ that they can use it from Claude, ChatGPT or Cursor through the Gateway's MCP
180
+ connector. No stack traces, no file paths.
181
+
182
+ ## Patterns, the ten most used
183
+
184
+ | Pattern | Copy from |
185
+ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
186
+ | Identity in a server function | `starter/src/middleware/ensureUser.ts`, `starter/src/serverFunctions/todos.ts` |
187
+ | Identity in a tool handler | `starter/src/tools.ts`; `open-crm/src/server/everyapp/crm-tool.ts` |
188
+ | Identity on a public route | `open-analytics/src/lib/edge/collector/collect.ts`; `open-crm/src/server/features/tracking/trackingRoutes.ts` |
189
+ | Organization-wide rows | `open-crm/src/middleware/ensure-user/everyApp.ts`; `agentic-inbox/workers/everyapp/identity.ts` |
190
+ | A public route with its own token | `open-analytics/src/lib/edge/auth/collect-token.ts`, `open-analytics/src/lib/edge/collector/collect.ts` |
191
+ | AI through the Gateway | `open-crm/src/server/lib/openrouter.ts`; `agentic-inbox/workers/everyapp/gateway.ts` |
192
+ | Sending and receiving email | `agentic-inbox/workers/everyapp/email.ts`; `agentic-inbox/workers/app.ts` |
193
+ | A Durable Object agent | `open-crm/src/server/features/agent/CrmAgent.ts`; `agentic-inbox/workers/agent/index.ts` |
194
+ | Tools per feature, with output schemas | `open-crm/src/server/everyapp/tools.ts`, `open-crm/src/server/everyapp/tools/companies.ts` |
195
+ | Needs setup inside the app | `open-crm/src/client/features/agent/AgentPaused.tsx`; `agentic-inbox/app/components/SendingOffBanner.tsx` |
196
+
197
+ All of them, with every manifest field, Workflows and a second Worker, cookies,
198
+ the lean Worker bundle, tests, dev vs production, release and install:
199
+ `patterns.md` beside this file.