snoutdata 0.10.1 → 0.10.3

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.
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- var wi=Object.defineProperty;var M=(e,t)=>()=>(e&&(t=e(e=0)),t);var Sr=(e,t)=>{for(var n in t)wi(e,n,{get:t[n],enumerable:!0})};function xr(e){let t=[],n={};for(let r=0;r<e.length;r++){let o=e[r];if(!o.startsWith("-")){t.push(o);continue}if(o==="--"){t.push(...e.slice(r+1));break}let[s,a]=xi(o);if(bi.has(s)){let l=a??e[++r];if(l===void 0)throw new y(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new y(`--${s} does not take a value`);if(!Si.has(s))throw new y(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function xi(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function O(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function H(e,t){return e.flags[t]===!0}function ke(e,t){let n=O(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new y(`--${t} must be a whole number, not "${n}"`);return r}var y,bi,Si,re=M(()=>{"use strict";y=class extends Error{},bi=new Set(["name","at","region","ref","file","provider","domain","expires","project","timeout","out","team","dir","days","schema","db-url","data-api-version","postgrest-version","port","entrypoint","memory","concurrency","client-id","site-url","allow","subject","p8","key-id","team-id","topic","environment"]);Si=new Set(["json","window","help","version","force","no-verify-jwt","reset","stdin","quiet","status","no-wait","no-browser","device","sso","env","dry-run","out-of-order","allow-delete","local","no-migrations","history"])});import{chmodSync as _i,existsSync as _r,mkdirSync as kr,readFileSync as Er,rmSync as ki,writeFileSync as vr}from"node:fs";import{homedir as gt}from"node:os";import{dirname as nn,join as Pr,parse as Ei,resolve as vi}from"node:path";function rn(e=gt()){return Pr(e,".snoutdata","auth.json")}function $r(e=gt()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=rn(e);if(!_r(n))return null;try{let r=JSON.parse(Er(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function ie(e,t=gt()){let n=rn(t);kr(nn(n),{recursive:!0}),vr(n,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),_i(n,384)}function Tr(e=gt()){ki(rn(e),{force:!0})}function Ar(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function Rr(e){return Pr(e,".snoutdata","project.json")}function qe(e){let t=vi(e),n=Ei(t).root;for(;;){let r=Rr(t);if(_r(r))try{let o=JSON.parse(Er(r,"utf8"));if(typeof o.ref=="string"&&o.ref)return typeof o.local=="string"&&o.local?{ref:o.ref,directory:t,local:o.local}:{ref:o.ref,directory:t}}catch{}if(t===n)return null;t=nn(t)}}function Ge(e,t){let n=Rr(e);return kr(nn(n),{recursive:!0}),vr(n,`${JSON.stringify(t,null,2)}
4
- `),n}function Ke(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:qe(e.cwd??process.cwd())?.ref??null}var Ee=M(()=>{"use strict"});import{existsSync as on,readFileSync as Or}from"node:fs";import{homedir as Pi}from"node:os";import{isAbsolute as $i,join as fe,resolve as Te}from"node:path";function Ti(e=process.env,t=process.platform,n=Pi()){let r;return t==="win32"?r=e.APPDATA??fe(n,"AppData","Roaming"):t==="darwin"?r=fe(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??fe(n,".config"),["snoutdata","snoutdata-dev"].map(o=>fe(r,o,"localProjects.json"))}function ht(e=Ti()){let t=[];for(let n of e)if(on(n))try{let r=JSON.parse(Or(n,"utf8"));for(let o of r.projects??[])typeof o.folder=="string"&&o.folder&&!t.some(s=>s.folder===o.folder)&&t.push({name:o.name??null,folder:o.folder,ref:o.ref??null})}catch{}return t}function yt(e){let t=new Map;for(let n of e.split(/\r?\n/)){let r=n.trim();if(!r||r.startsWith("#"))continue;let o=r.indexOf("=");if(o<=0)continue;let s=r.slice(0,o).trim(),a=r.slice(o+1).trim();a.length>=2&&(a[0]==='"'||a[0]==="'")&&a.endsWith(a[0])&&(a=a.slice(1,-1)),t.set(s,a)}return t}function Je(e){return on(fe(e,"compose.yaml"))&&on(fe(e,".env"))}function pe(e,t=null){let n=Te(e);if(!Je(n))throw new Error(`${n} is not a SnoutData stack folder: it needs compose.yaml and .env (see github.com/snoutdata/snout-stack)`);let r=yt(Or(fe(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${fe(n,".env")} has no ${o?"POSTGRES_OWNER_PASSWORD":"SNOUT_REF"}, so this stack was not set up with \`snout-stack init\``);let a=Number(r.get("API_PORT")??8e3)||8e3,l=Number(r.get("DB_PORT")??5432)||5432;return{folder:n,ref:o,name:t,apiUrl:(r.get("API_EXTERNAL_URL")||`http://127.0.0.1:${a}`).replace(/\/$/,""),apiPort:a,dbPort:l,anonKey:r.get("ANON_KEY")||null,serviceRoleKey:r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function Nr(e,t=process.cwd(),n=ht()){if(!e){if(Je(t))return pe(t);let[i]=n;if(n.length===1&&i)return pe(i.folder,i.name);throw n.length===0?new Error("there is no local project to link: set one up in Studio (Projects, Local), or pass the folder of a stack you made with snout-stack"):new Error(`there are ${n.length} local projects; name one: ${n.map(d=>d.name??d.folder).join(", ")}`)}let r=$i(e)?e:Te(t,e);if(Je(r)){let i=n.find(d=>Te(d.folder)===Te(r));return pe(r,i?.name??null)}let o=e.toLowerCase(),s=n.filter(i=>i.name?.toLowerCase()===o||i.ref===e),[a]=s;if(s.length===1&&a)return pe(a.folder,a.name);let l=n.map(i=>i.name??i.folder);throw new Error(s.length>1?`more than one local project is called ${e}; link it by its folder`:`no local project called ${e}, and it is not a stack folder${l.length?`. The local projects are ${l.join(", ")}`:""}`)}function Dr(e){let t=`${e.ref}_owner`;return{ref:e.ref,host:"127.0.0.1",port:e.dbPort,database:e.ref,user:t,password:e.ownerPassword,ssl:"disable",uri:`postgresql://${t}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.dbPort}/${e.ref}`,anonKey:e.anonKey,serviceRoleKey:e.serviceRoleKey,keysIssuedAt:null,keysExpireAt:null,wakesInstantly:!0,state:"local"}}function Mr(e,t,n=ht()){if(t&&Je(t))try{let r=n.find(s=>Te(s.folder)===Te(t)),o=pe(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&Je(r.folder))return pe(r.folder,r.name);return null}var We=M(()=>{"use strict"});function R(e,t,n){throw new U(e,t,n)}function wt(e){return e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429?"quota":e===503?"not-ready":e===504?"timeout":e>=500?"server":"failed"}function Ir(e){let t=e;return!t||typeof t!="object"||t.code!=="ENOENT"||typeof t.path!="string"||t.syscall==="spawn"?null:`no file at ${t.path}`}function jr(e){let t=[e],n=e;for(let r=0;r<4&&n instanceof Error&&n.cause;r+=1)n=n.cause,t.push(n);for(let r of t){let o=typeof r=="object"&&r&&"code"in r?String(r.code):"";if(Ai.has(o))return"network";if(o==="ABORT_ERR"||o==="ETIMEDOUT")return"timeout"}return e instanceof Error&&/fetch failed|network|socket hang up/i.test(e.message)?"network":"failed"}var ce,U,Ai,Z=M(()=>{"use strict";ce={ok:0,failed:1,usage:2,"not-signed-in":3,"not-ready":4,forbidden:5,"not-found":6,conflict:7,quota:8,network:9,timeout:10,"tool-missing":127,server:1},U=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return ce[this.code]}};Ai=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function bt(e){let t=e.env.SNOUTDATA_NO_INTERACTIVE;if(t!==void 0&&t!==""&&t!=="0"&&t!=="false")return{canAsk:!1,reason:"opted-out"};if(e.json)return{canAsk:!1,reason:"json"};for(let n of Ri){let r=e.env[n];if(r!==void 0&&r!==""&&r!=="0"&&r!=="false")return{canAsk:!1,reason:"ci"}}return e.isTty?{canAsk:!0}:{canAsk:!1,reason:"not-a-tty"}}function Ae(e){switch(e){case"json":return"running with --json, so nothing was asked";case"opted-out":return"SNOUTDATA_NO_INTERACTIVE is set, so nothing was asked";case"ci":return"this looks like CI, so nothing was asked";case"not-a-tty":return"there is no terminal to ask at"}}function Cr(e){sn=e}function ge(){return(sn??bt({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function Re(){return(sn??bt({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Ri,sn,Oe=M(()=>{"use strict";Ri=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];sn=null});import{existsSync as Oi,readFileSync as Ni}from"node:fs";import{homedir as an,hostname as Di}from"node:os";import{join as Ne}from"node:path";function Mi(e,t,n){if(t==="win32"){let r=n.APPDATA??Ne(an(),"AppData","Roaming");return Ne(r,e)}return t==="darwin"?Ne(an(),"Library","Application Support",e):Ne(n.XDG_CONFIG_HOME??Ne(an(),".config"),e)}function Ur(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>Ne(Mi(n,e,t),"mcp.json"))}function Ii(e=process.platform,t=process.env){return Ur(e,t)[0]}function ji(e=Ii()){if(!Oi(e))return null;try{let t=JSON.parse(Ni(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function Ci(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}async function St(e){if(e!==void 0)return Lr(e);let t="no-config";for(let n of Ur()){let r=ji(n);if(!r)continue;let o=await Lr(r);if(o.available)return o;(o.skip!=="not-running"||t==="no-config")&&(t=o.skip)}return{available:!1,skip:t}}async function Lr(e){let t=Ci(e);if(t)return{available:!1,skip:t};let n=e,r;try{let o=await fetch(`http://127.0.0.1:${n.port}/cli/whoami`,{headers:{Authorization:`Bearer ${n.token}`},signal:AbortSignal.timeout(Li)});if(!o.ok)return{available:!1,skip:"not-running"};r=await o.json()}catch{return{available:!1,skip:"not-running"}}return r.signedIn?r.canMintToken?{available:!0,who:r,config:n}:{available:!1,skip:"cannot-mint"}:{available:!1,skip:"signed-out"}}async function Fr(e,t){let n=await fetch(`http://127.0.0.1:${e.port}/cli/token`,{method:"POST",headers:{Authorization:`Bearer ${e.token}`,"Content-Type":"application/json"},body:JSON.stringify({client:"snoutdata-cli",command:t}),signal:AbortSignal.timeout(6e4)}),r=await n.json().catch(()=>({}));return n.ok&&r.ok&&r.token?{token:r.token,name:r.name??"SnoutData CLI"}:{declined:!0,reason:r.error??`SnoutData Studio refused (${n.status})`}}function xt(){return Di()}async function cn(e,t,n,r){let o=await fetch(`http://127.0.0.1:${e.port}/mcp`,{method:"POST",headers:{Authorization:`Bearer ${e.token}`,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:t,params:n}),signal:AbortSignal.timeout(r)});if(!o.ok)throw new Error(`the app answered ${o.status}`);let s=await o.json();if(s.error)throw new Error(s.error.message??"SnoutData Studio refused");return s.result}async function Br(e){return await cn(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await cn(e,"tools/list",{},3e3)).tools??[]}async function Hr(e,t,n){return cn(e,"tools/call",{name:t,arguments:n},12e4)}var Li,_t=M(()=>{"use strict";Li=1500});async function Gr(e,t){let n=await fetch(`${oe}/functions/v1/${e}`,{method:"POST",headers:{apikey:le,"Content-Type":"application/json"},body:JSON.stringify(t)}),r=await n.json().catch(()=>({}));return{status:n.status,body:r}}async function kt(e={}){let{status:t,body:n}=await Gr("cloud-device-start",{client:e});return t===429&&R("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&R("not-found","this SnoutData does not have the pairing flow (cloud-device-start is not deployed). Use `snoutdata login` to sign in with a browser, or set SNOUTDATA_ACCESS_TOKEN."),(t!==200||!n.code||!n.secret)&&R("server",n.error??`could not start a pairing (${t})`),n}async function Et(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&R("timeout","that pairing code expired before it was approved. Run the command again for a new one."),await new Promise(a=>setTimeout(a,n));let{status:o,body:s}=await Gr("cloud-device-poll",{secret:e});switch(o===404&&R("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&R("usage",s.error??"that is not a pairing secret"),o!==200&&R("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":R("forbidden",s.error??"that pairing was refused in the browser");case"used":R("conflict",s.error??"that pairing has already been completed");case"expired":R("timeout",s.error??"that pairing code expired before it was approved");case"slow-down":n=Math.max(n*2,(s.interval??2)*1e3);break;default:s.interval&&(n=Math.max(n,s.interval*1e3));break}t.onWait?.(Math.max(0,Math.round((r-Date.now())/1e3)))}}var ln=M(()=>{"use strict";G();Z()});function ze(e){vt=e}function Jr(e){Kr=e}function S(e){return Wr?`\x1B[2m${e}\x1B[0m`:e}function $(e){return Wr?`\x1B[1m${e}\x1B[0m`:e}function g(e,t){if(vt){process.stdout.write(`${JSON.stringify(e,null,2)}
5
- `);return}t()}function c(e){!vt&&!Kr&&process.stderr.write(`${e}
6
- `)}function he(e,t,n){process.stderr.write(`${t}
7
- `),vt&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
8
- `)}function V(e){process.stderr.write(`${e}
2
+ var cc=Object.defineProperty;var D=(e,t)=>()=>(e&&(t=e(e=0)),t);var Hr=(e,t)=>{for(var n in t)cc(e,n,{get:t[n],enumerable:!0})};function Gr(e){let t=[],n={};for(let r=0;r<e.length;r++){let o=e[r];if(!o.startsWith("-")){t.push(o);continue}if(o==="--"){t.push(...e.slice(r+1));break}let[s,a]=uc(o);if(lc.has(s)){let l=a??e[++r];if(l===void 0)throw new h(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new h(`--${s} does not take a value`);if(!dc.has(s))throw new h(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function uc(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function A(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function G(e,t){return e.flags[t]===!0}function Ae(e,t){let n=A(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new h(`--${t} must be a whole number, not "${n}"`);return r}var h,lc,dc,te=D(()=>{"use strict";h=class extends Error{},lc=new Set(["name","at","region","ref","file","provider","domain","expires","project","timeout","out","team","dir","days","schema","db-url","data-api-version","postgrest-version","port","entrypoint","memory","concurrency","client-id","site-url","allow","subject","p8","key-id","team-id","topic","environment","email","channel","since","level"]);dc=new Set(["json","window","help","version","force","no-verify-jwt","reset","stdin","quiet","status","no-wait","no-browser","device","sso","env","dry-run","out-of-order","allow-delete","local","no-migrations","history","show-url","check","watch"])});import{chmodSync as mc,existsSync as Kr,mkdirSync as Wr,readFileSync as Jr,rmSync as fc,writeFileSync as zr}from"node:fs";import{homedir as kt}from"node:os";import{dirname as _n,join as Vr,parse as pc,resolve as gc}from"node:path";function xn(e=kt()){return Vr(e,".snoutdata","auth.json")}function vt(e=kt()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=xn(e);if(!Kr(n))return null;try{let r=JSON.parse(Jr(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function ae(e,t=kt()){let n=xn(t);Wr(_n(n),{recursive:!0}),zr(n,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),mc(n,384)}function Yr(e=kt()){fc(xn(e),{force:!0})}function Xr(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function Zr(e){return Vr(e,".snoutdata","project.json")}function Ve(e){let t=gc(e),n=pc(t).root;for(;;){let r=Zr(t);if(Kr(r))try{let o=JSON.parse(Jr(r,"utf8"));if(typeof o.ref=="string"&&o.ref)return typeof o.local=="string"&&o.local?{ref:o.ref,directory:t,local:o.local}:{ref:o.ref,directory:t}}catch{}if(t===n)return null;t=_n(t)}}function Ye(e,t){let n=Zr(e);return Wr(_n(n),{recursive:!0}),zr(n,`${JSON.stringify(t,null,2)}
4
+ `),n}function Xe(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:Ve(e.cwd??process.cwd())?.ref??null}var we=D(()=>{"use strict"});import{existsSync as kn,readFileSync as Qr}from"node:fs";import{homedir as hc}from"node:os";import{isAbsolute as yc,join as be,resolve as De}from"node:path";function wc(e=process.env,t=process.platform,n=hc()){let r;return t==="win32"?r=e.APPDATA??be(n,"AppData","Roaming"):t==="darwin"?r=be(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??be(n,".config"),["snoutdata","snoutdata-dev"].map(o=>be(r,o,"localProjects.json"))}function Et(e=wc()){let t=[];for(let n of e)if(kn(n))try{let r=JSON.parse(Qr(n,"utf8"));for(let o of r.projects??[])typeof o.folder=="string"&&o.folder&&!t.some(s=>s.folder===o.folder)&&t.push({name:o.name??null,folder:o.folder,ref:o.ref??null})}catch{}return t}function $t(e){let t=new Map;for(let n of e.split(/\r?\n/)){let r=n.trim();if(!r||r.startsWith("#"))continue;let o=r.indexOf("=");if(o<=0)continue;let s=r.slice(0,o).trim(),a=r.slice(o+1).trim();a.length>=2&&(a[0]==='"'||a[0]==="'")&&a.endsWith(a[0])&&(a=a.slice(1,-1)),t.set(s,a)}return t}function Ze(e){return kn(be(e,"compose.yaml"))&&kn(be(e,".env"))}function Se(e,t=null){let n=De(e);if(!Ze(n))throw new Error(`${n} is not a SnoutData stack folder: it needs compose.yaml and .env (see github.com/snoutdata/snout-stack)`);let r=$t(Qr(be(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${be(n,".env")} has no ${o?"POSTGRES_OWNER_PASSWORD":"SNOUT_REF"}, so this stack was not set up with \`snout-stack init\``);let a=Number(r.get("API_PORT")??8e3)||8e3,l=Number(r.get("DB_PORT")??5432)||5432;return{folder:n,ref:o,name:t,apiUrl:(r.get("API_EXTERNAL_URL")||`http://127.0.0.1:${a}`).replace(/\/$/,""),apiPort:a,dbPort:l,anonKey:r.get("ANON_KEY")||null,serviceRoleKey:r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function eo(e,t=process.cwd(),n=Et()){if(!e){if(Ze(t))return Se(t);let[i]=n;if(n.length===1&&i)return Se(i.folder,i.name);throw n.length===0?new Error("there is no local project to link: set one up in Studio (Projects, Local), or pass the folder of a stack you made with snout-stack"):new Error(`there are ${n.length} local projects; name one: ${n.map(u=>u.name??u.folder).join(", ")}`)}let r=yc(e)?e:De(t,e);if(Ze(r)){let i=n.find(u=>De(u.folder)===De(r));return Se(r,i?.name??null)}let o=e.toLowerCase(),s=n.filter(i=>i.name?.toLowerCase()===o||i.ref===e),[a]=s;if(s.length===1&&a)return Se(a.folder,a.name);let l=n.map(i=>i.name??i.folder);throw new Error(s.length>1?`more than one local project is called ${e}; link it by its folder`:`no local project called ${e}, and it is not a stack folder${l.length?`. The local projects are ${l.join(", ")}`:""}`)}function to(e){let t=`${e.ref}_owner`;return{ref:e.ref,host:"127.0.0.1",port:e.dbPort,database:e.ref,user:t,password:e.ownerPassword,ssl:"disable",uri:`postgresql://${t}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.dbPort}/${e.ref}`,anonKey:e.anonKey,serviceRoleKey:e.serviceRoleKey,keysIssuedAt:null,keysExpireAt:null,wakesInstantly:!0,state:"local"}}function no(e,t,n=Et()){if(t&&Ze(t))try{let r=n.find(s=>De(s.folder)===De(t)),o=Se(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&Ze(r.folder))return Se(r.folder,r.name);return null}var Qe=D(()=>{"use strict"});function O(e,t,n){throw new N(e,t,n)}function At(e){return e===403?"forbidden":e===404?"not-found":e===409?"conflict":e===402||e===429?"quota":e===503?"not-ready":e===504?"timeout":e===410?"outdated":e>=500?"server":"failed"}function ro(e){let t=e;return!t||typeof t!="object"||t.code!=="ENOENT"||typeof t.path!="string"||t.syscall==="spawn"?null:`no file at ${t.path}`}function oo(e){let t=[e],n=e;for(let r=0;r<4&&n instanceof Error&&n.cause;r+=1)n=n.cause,t.push(n);for(let r of t){let o=typeof r=="object"&&r&&"code"in r?String(r.code):"";if(bc.has(o))return"network";if(o==="ABORT_ERR"||o==="ETIMEDOUT")return"timeout"}return e instanceof Error&&/fetch failed|network|socket hang up/i.test(e.message)?"network":"failed"}var de,N,bc,Q=D(()=>{"use strict";de={ok:0,failed:1,usage:2,"not-signed-in":3,"not-ready":4,forbidden:5,"not-found":6,conflict:7,quota:8,network:9,timeout:10,outdated:11,"tool-missing":127,server:1},N=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return de[this.code]}};bc=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function Pt(e){let t=e.env.SNOUTDATA_NO_INTERACTIVE;if(t!==void 0&&t!==""&&t!=="0"&&t!=="false")return{canAsk:!1,reason:"opted-out"};if(e.json)return{canAsk:!1,reason:"json"};for(let n of Sc){let r=e.env[n];if(r!==void 0&&r!==""&&r!=="0"&&r!=="false")return{canAsk:!1,reason:"ci"}}return e.isTty?{canAsk:!0}:{canAsk:!1,reason:"not-a-tty"}}function Ie(e){switch(e){case"json":return"running with --json, so nothing was asked";case"opted-out":return"SNOUTDATA_NO_INTERACTIVE is set, so nothing was asked";case"ci":return"this looks like CI, so nothing was asked";case"not-a-tty":return"there is no terminal to ask at"}}function so(e){vn=e}function _e(){return(vn??Pt({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function Me(){return(vn??Pt({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Sc,vn,Ce=D(()=>{"use strict";Sc=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];vn=null});import{existsSync as _c,readFileSync as xc}from"node:fs";import{homedir as En,hostname as kc}from"node:os";import{join as je}from"node:path";function vc(e,t,n){if(t==="win32"){let r=n.APPDATA??je(En(),"AppData","Roaming");return je(r,e)}return t==="darwin"?je(En(),"Library","Application Support",e):je(n.XDG_CONFIG_HOME??je(En(),".config"),e)}function io(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>je(vc(n,e,t),"mcp.json"))}function Ec(e=process.platform,t=process.env){return io(e,t)[0]}function $c(e=Ec()){if(!_c(e))return null;try{let t=JSON.parse(xc(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function Ac(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}async function Rt(e){if(e!==void 0)return ao(e);let t="no-config";for(let n of io()){let r=$c(n);if(!r)continue;let o=await ao(r);if(o.available)return o;(o.skip!=="not-running"||t==="no-config")&&(t=o.skip)}return{available:!1,skip:t}}async function ao(e){let t=Ac(e);if(t)return{available:!1,skip:t};let n=e,r;try{let o=await fetch(`http://127.0.0.1:${n.port}/cli/whoami`,{headers:{Authorization:`Bearer ${n.token}`},signal:AbortSignal.timeout(Pc)});if(!o.ok)return{available:!1,skip:"not-running"};r=await o.json()}catch{return{available:!1,skip:"not-running"}}return r.signedIn?r.canMintToken?{available:!0,who:r,config:n}:{available:!1,skip:"cannot-mint"}:{available:!1,skip:"signed-out"}}async function co(e,t){let n=await fetch(`http://127.0.0.1:${e.port}/cli/token`,{method:"POST",headers:{Authorization:`Bearer ${e.token}`,"Content-Type":"application/json"},body:JSON.stringify({client:"snoutdata-cli",command:t}),signal:AbortSignal.timeout(6e4)}),r=await n.json().catch(()=>({}));return n.ok&&r.ok&&r.token?{token:r.token,name:r.name??"SnoutData CLI"}:{declined:!0,reason:r.error??`SnoutData Studio refused (${n.status})`}}function Tt(){return kc()}async function $n(e,t,n,r){let o=await fetch(`http://127.0.0.1:${e.port}/mcp`,{method:"POST",headers:{Authorization:`Bearer ${e.token}`,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:t,params:n}),signal:AbortSignal.timeout(r)});if(!o.ok)throw new Error(`the app answered ${o.status}`);let s=await o.json();if(s.error)throw new Error(s.error.message??"SnoutData Studio refused");return s.result}async function lo(e){return await $n(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await $n(e,"tools/list",{},3e3)).tools??[]}async function uo(e,t,n){return $n(e,"tools/call",{name:t,arguments:n},12e4)}var Pc,Ot=D(()=>{"use strict";Pc=1500});import{basename as Rc}from"node:path";function Dt(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=Rc(e.execPath.replace(/\\/g,"/")).toLowerCase();if(e.bun&&t.startsWith("snoutdata"))return"binary";let n=e.script.replace(/\\/g,"/");return/\/_npx\//.test(n)?"npx":/\/node_modules\/snoutdata\//.test(n)?"npm":"source"}function Tc(e=Dt(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?ue:et}}function An(e={}){let t=e.kind??Dt(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?ue:`${et} (or ${ue})`;return`snoutdata ${V} is installed and ${r}. Run \`${Tc(t,n)}\`, or install again: ${o}`}function fo(e,t){if(e==="dev"||t==="dev")return!1;let n=e.split("-")[0].split(".").map(Number),r=t.split("-")[0].split(".").map(Number);for(let o=0;o<Math.max(n.length,r.length);o+=1){let s=n[o]??0,a=r[o]??0;if(Number.isNaN(s)||Number.isNaN(a))return!1;if(s!==a)return s<a}return!1}var V,Nt,et,ue,tt=D(()=>{"use strict";V="0.10.3",Nt=`snoutdata-cli/${V}`,et="curl -fsSL https://snoutdata.com/install.sh | sh",ue="npm install -g snoutdata@latest"});async function po(e,t){let n=await fetch(`${ie}/functions/v1/${e}`,{method:"POST",headers:{apikey:me,"Content-Type":"application/json","x-client-info":Nt},body:JSON.stringify(t)});Ct(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw jt(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function It(e={}){let{status:t,body:n}=await po("cloud-device-start",{client:e});return t===429&&O("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&O("not-found","this SnoutData does not have the pairing flow (cloud-device-start is not deployed). Use `snoutdata login` to sign in with a browser, or set SNOUTDATA_ACCESS_TOKEN."),(t!==200||!n.code||!n.secret)&&O("server",n.error??`could not start a pairing (${t})`),n}async function Mt(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&O("timeout","that pairing code expired before it was approved. Run the command again for a new one."),await new Promise(a=>setTimeout(a,n));let{status:o,body:s}=await po("cloud-device-poll",{secret:e});switch(o===404&&O("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&O("usage",s.error??"that is not a pairing secret"),o!==200&&O("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":O("forbidden",s.error??"that pairing was refused in the browser");case"used":O("conflict",s.error??"that pairing has already been completed");case"expired":O("timeout",s.error??"that pairing code expired before it was approved");case"slow-down":n=Math.max(n*2,(s.interval??2)*1e3);break;default:s.interval&&(n=Math.max(n,s.interval*1e3));break}t.onWait?.(Math.max(0,Math.round((r-Date.now())/1e3)))}}var Pn=D(()=>{"use strict";W();Q();tt()});function rt(e){nt=e}function ho(e){go=e}function yo(){return nt}function b(e){return wo?`\x1B[2m${e}\x1B[0m`:e}function v(e){return wo?`\x1B[1m${e}\x1B[0m`:e}function g(e,t){if(nt){process.stdout.write(`${JSON.stringify(e,null,2)}
5
+ `);return}t()}function c(e){!nt&&!go&&process.stderr.write(`${e}
6
+ `)}function xe(e,t,n){process.stderr.write(`${t}
7
+ `),nt&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
8
+ `)}function J(e){process.stderr.write(`${e}
9
9
  `)}function F(e){if(e.length===0)return"";let t=e[0].map((n,r)=>Math.max(...e.map(o=>(o[r]??"").length)));return e.map(n=>n.map((r,o)=>r.padEnd(t[o]??0)).join(" ").trimEnd()).join(`
10
- `)}function ue(e){if(!e)return"never";let t=Math.round((Date.now()-Date.parse(e))/1e3),n=t<0,r=Math.abs(t);if(r<60)return n?"in a moment":"just now";let o=[[60,"minute"],[60,"hour"],[24,"day"],[30,"month"]],s=r,a="second";for(let[i,d]of o){if(s<i)break;s=Math.round(s/i),a=d}let l=`${s} ${a}${s===1?"":"s"}`;return n?`in ${l}`:`${l} ago`}var vt,Kr,Wr,K=M(()=>{"use strict";vt=!1,Kr=!1;Wr=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as Yr,randomBytes as un}from"node:crypto";import{createServer as Fi}from"node:http";import{createInterface as Bi}from"node:readline/promises";import{spawn as Hi}from"node:child_process";function Pt(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function zr(e,t){let n=e==="ok"?"#e0b256":"#e0765a",r=e==="ok"?"Signed in":"Sign-in failed",o=e==="ok"?"You can close this tab and go back to your terminal.":oc(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
10
+ `)}function fe(e){if(!e)return"never";let t=Math.round((Date.now()-Date.parse(e))/1e3),n=t<0,r=Math.abs(t);if(r<60)return n?"in a moment":"just now";let o=[[60,"minute"],[60,"hour"],[24,"day"],[30,"month"]],s=r,a="second";for(let[i,u]of o){if(s<i)break;s=Math.round(s/i),a=u}let l=`${s} ${a}${s===1?"":"s"}`;return n?`in ${l}`:`${l} ago`}var nt,go,wo,q=D(()=>{"use strict";nt=!1,go=!1;wo=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as _o,randomBytes as Rn}from"node:crypto";import{createServer as Nc}from"node:http";import{createInterface as Dc}from"node:readline/promises";import{spawn as Ic}from"node:child_process";function Lt(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function bo(e,t){let n=e==="ok"?"#e0b256":"#e0765a",r=e==="ok"?"Signed in":"Sign-in failed",o=e==="ok"?"You can close this tab and go back to your terminal.":Xc(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
11
11
  <meta name="viewport" content="width=device-width,initial-scale=1"></head>
12
12
  <body style="font-family:ui-sans-serif,system-ui,-apple-system,sans-serif;background:#050403;color:#e9e4dc;display:grid;place-items:center;min-height:100vh;margin:0">
13
13
  <div style="text-align:center;max-width:460px;padding:40px 24px">
@@ -19,32 +19,32 @@ var wi=Object.defineProperty;var M=(e,t)=>()=>(e&&(t=e(e=0)),t);var Sr=(e,t)=>{f
19
19
  <p style="color:#8f887e;margin:0;font-size:15px;line-height:1.5">${o}</p>
20
20
  ${e==="ok"?"":'<p style="color:#8f887e;margin:14px 0 0;font-size:14px">Run <code style="background:#1b1917;border:1px solid #262320;border-radius:6px;padding:2px 6px;font-size:13px">snoutdata login</code> to try again.</p>'}
21
21
  </div>
22
- </body></html>`}function dn(){let e=Pt(un(32)),t=Pt(Yr("sha256").update(e).digest());return{verifier:e,challenge:t}}function qi(e,t){return e==="darwin"?{command:"open",args:[t],verbatim:!1}:e==="win32"?{command:"cmd.exe",args:["/c","start",'""',`"${t}"`],verbatim:!0}:{command:"xdg-open",args:[t],verbatim:!1}}function Gi(e){let{command:t,args:n,verbatim:r}=qi(process.platform,e),o=Hi(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function Xr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function Wi(){return"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"}function zi(e){let t=e??Pt(un(32));return{raw:t,hashed:Yr("sha256").update(t).digest("hex")}}function Vi(e){return`${Ki}?${new URLSearchParams({client_id:Xr(),redirect_uri:e.redirectUri,response_type:"code",scope:"openid email profile",code_challenge:e.challenge,code_challenge_method:"S256",nonce:e.hashedNonce,state:e.state,prompt:"select_account"}).toString()}`}function Yi(e,t){let n=new URL(e,"http://127.0.0.1");if(!n.searchParams.has("code")&&!n.searchParams.has("error"))return{kind:"ignore"};let r=n.searchParams.get("error_description")??n.searchParams.get("error");if(r)return{kind:"error",message:r};let o=n.searchParams.get("code");return o?t!==void 0&&n.searchParams.get("state")!==t?{kind:"error",message:"the browser came back with a state that did not match"}:{kind:"ok",code:o}:{kind:"error",message:"the browser came back without a code"}}async function mn(e){return new Promise((t,n)=>{let r="",o=Fi((a,l)=>{let i=Yi(a.url??"/",e.state);if(i.kind==="ignore"){l.writeHead(404).end();return}l.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),l.end(i.kind==="ok"?zr("ok"):zr("failed",i.message)),o.close(),i.kind==="ok"?t({code:i.code,redirectUri:r}):n(new Error(i.message))});o.listen(0,"127.0.0.1",()=>{let a=o.address();r=`http://127.0.0.1:${typeof a=="object"&&a?a.port:0}${e.callbackPath??""}`,Promise.resolve().then(()=>e.authorizeUrl(r)).then(i=>{process.stdout.write(`${i}
23
- `),e.noBrowser?c("Open that in a browser, on any machine, and sign in."):(c("Opening that in your browser. If nothing opens, paste it into one."),Gi(i))},i=>{o.close(),n(i)})}),o.on("error",n),setTimeout(()=>{o.close(),n(new Error("timed out waiting for the browser"))},e.timeoutMs??3e5).unref()})}async function Zr(e,t){let n=await fetch(`${oe}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{apikey:le,"Content-Type":"application/json"},body:JSON.stringify({auth_code:e,code_verifier:t})});if(!n.ok)throw new Error(`could not exchange the code: ${(await n.text()).slice(0,200)}`);return Qr(await n.json())}async function Xi(e,t,n){let r=await fetch(Ji,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:Xr(),client_secret:Wi(),code:e,code_verifier:t,grant_type:"authorization_code",redirect_uri:n})}),o=await r.json().catch(()=>({}));if(!r.ok||!o.id_token)throw new Error(`Google would not exchange the code: ${o.error_description??o.error??`HTTP ${r.status}`}`);return o.id_token}async function Zi(e){let{verifier:t,challenge:n}=dn(),{raw:r,hashed:o}=zi(),s=Pt(un(16)),{code:a,redirectUri:l}=await mn({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:u=>Vi({redirectUri:u,challenge:n,hashedNonce:o,state:s})}),i=await Xi(a,t,l),d=await fetch(`${oe}/auth/v1/token?grant_type=id_token`,{method:"POST",headers:{apikey:le,"Content-Type":"application/json"},body:JSON.stringify({provider:"google",id_token:i,nonce:r})});if(!d.ok)throw new Error(`could not turn the Google sign-in into a session: ${(await d.text()).slice(0,200)}`);return Qr(await d.json())}function Qr(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return ie(n),n}function Vr(e){let t=e.trim().toLowerCase();if(!t||/\s/.test(t))return null;let n=t.indexOf("@");if(n===0||t.indexOf("@",n+1)!==-1)return null;let r=n===-1?t:t.slice(n+1);if(!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/.test(r))return null;let o=r.slice(r.lastIndexOf(".")+1);return/^[a-z]{2,}$/.test(o)?r:null}function Qi(e){try{let t=JSON.parse(e);for(let n of["error_description","msg","message","error"]){let r=t[n];if(typeof r=="string"&&r)return r}}catch{return""}return""}function ec(e,t,n){let r=Qi(t);return e===404||/no sso provider/i.test(r)?new U("not-found",`No single sign-on is set up for ${n}. Check the address, or ask whoever administers your team to connect your identity provider.`,{domain:n}):/saml.*(disabled|not enabled)/i.test(r)?new U("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new U(wt(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function tc(e){let t=await fetch(`${oe}/auth/v1/sso`,{method:"POST",headers:{apikey:le,"Content-Type":"application/json"},body:JSON.stringify({domain:e.domain,redirect_to:e.redirectTo,skip_http_redirect:!0,code_challenge:e.challenge,code_challenge_method:"s256"})}),n=await t.text();if(!t.ok)throw ec(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new U("server",`Single sign-on for ${e.domain} was accepted but came back without somewhere to send the browser.`,{domain:e.domain});return r}async function nc(e){if(e!==void 0){let r=Vr(e);if(!r)throw new y(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!ge()){let r=Re();throw new y(`login --sso needs --domain <your work email or company domain>${r?`, because ${Ae(r)}`:""}`)}let t=Bi({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new y("nothing left to read: pass --domain <your work email or company domain>")))});n.catch(()=>{});try{for(let r=0;r<3;r+=1){let o=await Promise.race([t.question("Your work email address: "),n]),s=Vr(o);if(s)return s;c("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new y("no domain to sign in with: pass --domain <your work email or company domain>")}async function rc(e){let t=await nc(e.domain),{verifier:n,challenge:r}=dn();c(`Signing in with the identity provider for ${$(t)}.`);let{code:o}=await mn({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>tc({domain:t,redirectTo:s,challenge:r})});return Zr(o,n)}async function $t(e={}){if(e.sso)return rc(e);let t=e.provider??"google";if(t==="google")return Zi(e);let{verifier:n,challenge:r}=dn(),{code:o}=await mn({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>{let a=new URL(`${oe}/auth/v1/authorize`);return a.searchParams.set("provider",t),a.searchParams.set("redirect_to",s),a.searchParams.set("code_challenge",r),a.searchParams.set("code_challenge_method","s256"),a.searchParams.set("prompt","select_account"),a.toString()}});return Zr(o,n)}function oc(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var Ki,Ji,fn=M(()=>{"use strict";G();re();Ee();Z();Oe();K();Ki="https://accounts.google.com/o/oauth2/v2/auth",Ji="https://oauth2.googleapis.com/token"});import{createInterface as sc}from"node:readline/promises";async function pn(e){let t=sc({input:process.stdin,output:process.stderr});try{let n=(await t.question(`${e} [Y/n] `)).trim().toLowerCase();return n===""||n==="y"||n==="yes"}finally{t.close()}}async function ac(){let e=await St();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&c(S("SnoutData Studio is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";if(c(""),c(`SnoutData Studio is open here, signed in as ${$(t)}.`),c(S("It can create an access token for this terminal. Your sign-in itself is not handed over,")),c(S("and you can revoke the token later with `snoutdata tokens revoke`.")),!await pn("Use it?"))return null;c("Asking SnoutData Studio. Approve it there.");let n=await Fr(e.config,process.argv.slice(2).join(" ").slice(0,60)||"snoutdata");if("declined"in n)return c(`The app did not create a token: ${n.reason}`),null;let r={accessToken:n.token};return ie(r),c(`Signed in with "${n.name}".`),r}async function ic(){if(!await pn("Sign in with a browser?"))return null;try{let e=await $t({});return c(`Signed in${e.email?` as ${e.email}`:""}.`),{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt}}catch(e){return c(`That did not finish: ${e instanceof Error?e.message:String(e)}`),null}}async function cc(){if(!await pn("Sign in by typing a code into a browser on another machine?"))return null;let e=await kt({name:xt(),platform:process.platform});c(""),c(` Open ${$(e.verifyUrl)}`),c(` Type ${$(e.code)}`),c(""),c(S("The code is typed rather than in the link on purpose: it is what proves the person")),c(S("at the browser is the person at this terminal. Waiting\u2026"));let t=await Et(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return ie(n),c(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function eo(){if(!ge())return null;let e=await ac();if(e)return e;let t=await ic();return t||cc()}var to=M(()=>{"use strict";Ee();_t();ln();Oe();fn();K()});var be={};Sr(be,{ACCOUNTS_URL:()=>oe,ANON_KEY:()=>le,ApiError:()=>se,FUNCTIONS:()=>no,NotSignedIn:()=>ye,call:()=>x,connection:()=>Y,exportStatus:()=>Me,isSettled:()=>ro,listProjects:()=>Q,listTeams:()=>Tt,localStack:()=>we,requestExport:()=>Ve,waitForReady:()=>Xe,waitForSettled:()=>Ye,whoami:()=>De});function uc(){let e=Re();return e?`not signed in, and ${Ae(e)}. Set SNOUTDATA_ACCESS_TOKEN to a token made at https://dashboard.snoutdata.com/#/tokens, or by \`snoutdata tokens create\` on a machine that is signed in.`:"not signed in: run `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN"}async function dc(){let e=$r();if(e||(e=await eo()),!e)throw new ye;if(!Ar(e)||!e.refreshToken)return e.accessToken;let t=await fetch(`${oe}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{apikey:le,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new ye;let n=await t.json(),r={accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at,email:e.email};return ie(r),r.accessToken}async function x(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let d=we(r);if(d)throw new U("usage",`${r} is a local project (${d.folder}), and this command is about SnoutData Cloud. Locally: db url, db psql, db push, gen types, keys, status, start, stop, functions, secrets.`,{ref:r,local:d.folder})}let o=await dc(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await fetch(`${no}/${e}${s}`,{method:n,headers:{Authorization:`Bearer ${o}`,apikey:le,"Content-Type":"application/json","x-client-info":lc},body:n==="GET"?void 0:JSON.stringify(t)}),l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new se(a.status,l.slice(0,300)||a.statusText)}if(!a.ok){let d=typeof i=="object"&&i&&"error"in i?String(i.error):`${a.status} ${a.statusText}`;throw a.status===401?new ye(d):new se(a.status,d)}return i}async function De(){return x("cloud-whoami")}async function Q(){return x("cloud-project-list")}async function Y(e){let t=we(e);return t?Dr(t):x("cloud-project-connection",{ref:e})}function we(e){return Mr(e,qe(process.cwd())?.local??null)}async function Tt(){return x("cloud-team-list")}async function Ve(e){return x("cloud-project-export",{ref:e})}async function Me(e){return x("cloud-project-export",{ref:e},"GET")}async function Ye(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await Q(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),ro(t,a))return s;if(Date.now()>=r)throw new se(504,`${e} is still ${a} after waiting`);await new Promise(l=>setTimeout(l,2e3))}}function ro(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function Xe(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await Q(),o=r.find(s=>s.ref===e);if(!o)throw new se(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new se(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var lc,oe,le,no,se,ye,G=M(()=>{"use strict";Ee();We();Z();Oe();to();lc="snoutdata-cli/0.10.1",oe=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",le=process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyZWYiOiJ6ZjdnNjVtYjhmYXJhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3OTA4MDcwNDcsImV4cCI6MjEwNDg3NTUyNH0.M-Cb0wFEhJXU21cXtQxZAn-WnRcq4rK2uNSkeHdLucY",no=`${oe}/functions/v1`,se=class extends Error{status;constructor(t,n){super(n),this.status=t}},ye=class extends Error{constructor(t){super(t??uc())}}});function Jn(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function Is(e,t,n){let r="";for(;r.length<n;)r+=Jn(e,t);return r}var Kn,Ms,js=M(()=>{"use strict";Kn="0123456789abcdefghjkmnpqrstvwxyz",Ms="abcdefghjkmnpqrstvwxyz"});function Us(e){return Jn(e,Cs)+Is(e,Kn,Ls-1)}function nu(e){return tu.test(e)}function X(e){if(!nu(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function j(e,t=ru){return X(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var Cs,Ls,tu,ru,Se=M(()=>{"use strict";js();Cs=Ms,Ls=13,tu=new RegExp(`^[${Cs}][${Kn}]{${Ls-1}}$`);ru="db.snoutdata.com"});var Fs=M(()=>{"use strict";Se()});function Hs(e,t){let n=Bs(e,7),r=Bs(t,24);return Math.max(2,Math.ceil(n*24/r))}function Bs(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var Wn=M(()=>{"use strict"});function zn(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var Vn=M(()=>{"use strict"});var qs=M(()=>{"use strict"});var mp,Gs=M(()=>{"use strict";Se();mp=7*24});var pp,Ks=M(()=>{"use strict";pp=7*24*60*60*1e3});var ou,Js=M(()=>{"use strict";Ks();ou=60*60*1e3});var Ue,Sp,Ws=M(()=>{"use strict";Ue="snout_storage_admin",Sp=`do $$ begin
22
+ </body></html>`}function Tn(){let e=Lt(Rn(32)),t=Lt(_o("sha256").update(e).digest());return{verifier:e,challenge:t}}function Mc(e,t){return e==="darwin"?{command:"open",args:[t],verbatim:!1}:e==="win32"?{command:"cmd.exe",args:["/c","start",'""',`"${t}"`],verbatim:!0}:{command:"xdg-open",args:[t],verbatim:!1}}function Cc(e){let{command:t,args:n,verbatim:r}=Mc(process.platform,e),o=Ic(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function xo(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function Uc(){return"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"}function Fc(e){let t=e??Lt(Rn(32));return{raw:t,hashed:_o("sha256").update(t).digest("hex")}}function Bc(e){return`${jc}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:xo(),redirect_uri:e.redirectUri,response_type:"code",scope:"openid email profile",code_challenge:e.challenge,code_challenge_method:"S256",nonce:e.hashedNonce,state:e.state,prompt:"select_account"}).toString()}`}function Hc(e,t){let n=new URL(e,"http://127.0.0.1");if(!n.searchParams.has("code")&&!n.searchParams.has("error"))return{kind:"ignore"};let r=n.searchParams.get("error_description")??n.searchParams.get("error");if(r)return{kind:"error",message:r};let o=n.searchParams.get("code");return o?t!==void 0&&n.searchParams.get("state")!==t?{kind:"error",message:"the browser came back with a state that did not match"}:{kind:"ok",code:o}:{kind:"error",message:"the browser came back without a code"}}async function On(e){return new Promise((t,n)=>{let r="",o=Nc((a,l)=>{let i=Hc(a.url??"/",e.state);if(i.kind==="ignore"){l.writeHead(404).end();return}l.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),l.end(i.kind==="ok"?bo("ok"):bo("failed",i.message)),o.close(),i.kind==="ok"?t({code:i.code,redirectUri:r}):n(new Error(i.message))});o.listen(0,"127.0.0.1",()=>{let a=o.address();r=`http://127.0.0.1:${typeof a=="object"&&a?a.port:0}${e.callbackPath??""}`,Promise.resolve().then(()=>e.authorizeUrl(r)).then(i=>{process.stdout.write(`${i}
23
+ `),e.noBrowser?c("Open that in a browser, on any machine, and sign in."):(c("Opening that in your browser. If nothing opens, paste it into one."),Cc(i))},i=>{o.close(),n(i)})}),o.on("error",n),setTimeout(()=>{o.close(),n(new Error("timed out waiting for the browser"))},e.timeoutMs??3e5).unref()})}async function ko(e,t){let n=await fetch(`${ie}/auth/v1/token?grant_type=pkce`,{method:"POST",headers:{apikey:me,"Content-Type":"application/json"},body:JSON.stringify({auth_code:e,code_verifier:t})});if(!n.ok)throw new Error(`could not exchange the code: ${(await n.text()).slice(0,200)}`);return vo(await n.json())}async function Gc(e,t,n){let r=await fetch(Lc,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:xo(),client_secret:Uc(),code:e,code_verifier:t,grant_type:"authorization_code",redirect_uri:n})}),o=await r.json().catch(()=>({}));if(!r.ok||!o.id_token)throw new Error(`Google would not exchange the code: ${o.error_description??o.error??`HTTP ${r.status}`}`);return o.id_token}async function qc(e){let{verifier:t,challenge:n}=Tn(),{raw:r,hashed:o}=Fc(),s=Lt(Rn(16)),{code:a,redirectUri:l}=await On({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:d=>Bc({redirectUri:d,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await Gc(a,t,l),u=await fetch(`${ie}/auth/v1/token?grant_type=id_token`,{method:"POST",headers:{apikey:me,"Content-Type":"application/json"},body:JSON.stringify({provider:"google",id_token:i,nonce:r})});if(!u.ok)throw new Error(`could not turn the Google sign-in into a session: ${(await u.text()).slice(0,200)}`);return vo(await u.json())}function vo(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return ae(n),n}function So(e){let t=e.trim().toLowerCase();if(!t||/\s/.test(t))return null;let n=t.indexOf("@");if(n===0||t.indexOf("@",n+1)!==-1)return null;let r=n===-1?t:t.slice(n+1);if(!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/.test(r))return null;let o=r.slice(r.lastIndexOf(".")+1);return/^[a-z]{2,}$/.test(o)?r:null}function Kc(e){try{let t=JSON.parse(e);for(let n of["error_description","msg","message","error"]){let r=t[n];if(typeof r=="string"&&r)return r}}catch{return""}return""}function Wc(e,t,n){let r=Kc(t);return e===404||/no sso provider/i.test(r)?new N("not-found",`No single sign-on is set up for ${n}. Check the address, or ask whoever administers your team to connect your identity provider.`,{domain:n}):/saml.*(disabled|not enabled)/i.test(r)?new N("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new N(At(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function Jc(e){let t=await fetch(`${ie}/auth/v1/sso`,{method:"POST",headers:{apikey:me,"Content-Type":"application/json"},body:JSON.stringify({domain:e.domain,redirect_to:e.redirectTo,skip_http_redirect:!0,code_challenge:e.challenge,code_challenge_method:"s256"})}),n=await t.text();if(!t.ok)throw Wc(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new N("server",`Single sign-on for ${e.domain} was accepted but came back without somewhere to send the browser.`,{domain:e.domain});return r}async function zc(e){if(e!==void 0){let r=So(e);if(!r)throw new h(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!_e()){let r=Me();throw new h(`login --sso needs --domain <your work email or company domain>${r?`, because ${Ie(r)}`:""}`)}let t=Dc({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new h("nothing left to read: pass --domain <your work email or company domain>")))});n.catch(()=>{});try{for(let r=0;r<3;r+=1){let o=await Promise.race([t.question("Your work email address: "),n]),s=So(o);if(s)return s;c("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new h("no domain to sign in with: pass --domain <your work email or company domain>")}async function Vc(e){let t=await zc(e.domain),{verifier:n,challenge:r}=Tn();c(`Signing in with the identity provider for ${v(t)}.`);let{code:o}=await On({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>Jc({domain:t,redirectTo:s,challenge:r})});return ko(o,n)}async function Ut(e={}){return Nn(e.email,await Yc(e))}function Nn(e,t){if(!e)return t;let n={...t,expectedEmail:e};return ae(n),t.email&&!Dn(t.email,e)&&J(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function Dn(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function Yc(e){if(e.sso)return Vc({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return qc(e);let{verifier:n,challenge:r}=Tn(),{code:o}=await On({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>{let a=new URL(`${ie}/auth/v1/authorize`);return a.searchParams.set("provider",t),a.searchParams.set("redirect_to",s),a.searchParams.set("code_challenge",r),a.searchParams.set("code_challenge_method","s256"),a.searchParams.set("prompt","select_account"),a.toString()}});return ko(o,n)}function Xc(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var jc,Lc,Ft=D(()=>{"use strict";W();te();we();Q();Ce();q();jc="https://accounts.google.com/o/oauth2/v2/auth",Lc="https://oauth2.googleapis.com/token"});import{createInterface as Zc}from"node:readline/promises";async function In(e){let t=Zc({input:process.stdin,output:process.stderr});try{let n=(await t.question(`${e} [Y/n] `)).trim().toLowerCase();return n===""||n==="y"||n==="yes"}finally{t.close()}}async function Qc(){let e=await Rt();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&c(b("SnoutData Studio is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";if(c(""),c(`SnoutData Studio is open here, signed in as ${v(t)}.`),c(b("It can create an access token for this terminal. Your sign-in itself is not handed over,")),c(b("and you can revoke the token later with `snoutdata tokens revoke`.")),!await In("Use it?"))return null;c("Asking SnoutData Studio. Approve it there.");let n=await co(e.config,process.argv.slice(2).join(" ").slice(0,60)||"snoutdata");if("declined"in n)return c(`The app did not create a token: ${n.reason}`),null;let r={accessToken:n.token};return ae(r),c(`Signed in with "${n.name}".`),r}async function el(){if(!await In("Sign in with a browser?"))return null;try{let e=await Ut({});return c(`Signed in${e.email?` as ${e.email}`:""}.`),{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt}}catch(e){return c(`That did not finish: ${e instanceof Error?e.message:String(e)}`),null}}async function tl(){if(!await In("Sign in by typing a code into a browser on another machine?"))return null;let e=await It({name:Tt(),platform:process.platform});c(""),c(` Open ${v(e.verifyUrl)}`),c(` Type ${v(e.code)}`),c(""),c(b("The code is typed rather than in the link on purpose: it is what proves the person")),c(b("at the browser is the person at this terminal. Waiting\u2026"));let t=await Mt(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return ae(n),c(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Eo(){if(!_e())return null;let e=await Qc();if(e)return e;let t=await el();return t||tl()}var $o=D(()=>{"use strict";we();Ot();Pn();Ce();Ft();q()});var pe={};Hr(pe,{ACCOUNTS_URL:()=>ie,ANON_KEY:()=>me,ApiError:()=>ce,FUNCTIONS:()=>Po,NotSignedIn:()=>ke,call:()=>_,connection:()=>ee,exportStatus:()=>Le,isSettled:()=>Ro,listProjects:()=>ne,listTeams:()=>Bt,localStack:()=>ve,noticeFrom:()=>Ct,outdated:()=>jt,requestExport:()=>ot,waitForReady:()=>at,waitForSettled:()=>st,whoami:()=>Pe});function nl(){let e=Me();return e?`not signed in, and ${Ie(e)}. Set SNOUTDATA_ACCESS_TOKEN to a token made at https://dashboard.snoutdata.com/#/tokens, or by \`snoutdata tokens create\` on a machine that is signed in.`:"not signed in: run `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN"}async function rl(){let e=vt();if(e||(e=await Eo()),!e)throw new ke;if(!Xr(e)||!e.refreshToken)return e.accessToken;let t=await fetch(`${ie}/auth/v1/token?grant_type=refresh_token`,{method:"POST",headers:{apikey:me,"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new ke;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return ae(r),r.accessToken}async function _(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=ve(r);if(u)throw new N("usage",`${r} is a local project (${u.folder}), and this command is about SnoutData Cloud. Locally: db url, db psql, db push, gen types, keys, status, start, stop, functions, secrets.`,{ref:r,local:u.folder})}let o=await rl(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await fetch(`${Po}/${e}${s}`,{method:n,headers:{Authorization:`Bearer ${o}`,apikey:me,"Content-Type":"application/json","x-client-info":Nt},body:n==="GET"?void 0:JSON.stringify(t)});Ct(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new ce(a.status,l.slice(0,300)||a.statusText)}if(!a.ok){let u=typeof i=="object"&&i&&"error"in i?String(i.error):`${a.status} ${a.statusText}`;throw a.status===401?new ke(u):a.status===410?jt(a,u):new ce(a.status,u)}return i}function Ct(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||Ao||(Ao=!0,J(`${t} ${An({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function jt(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new N("outdated",`${t.replace(/\s+$/,"")} ${An({minimum:n})}`,{status:410,installed:V,minimum:n??null})}async function Pe(){return _("cloud-whoami")}async function ne(){return _("cloud-project-list")}async function ee(e){let t=ve(e);return t?to(t):_("cloud-project-connection",{ref:e})}function ve(e){return no(e,Ve(process.cwd())?.local??null)}async function Bt(){return _("cloud-team-list")}async function ot(e){return _("cloud-project-export",{ref:e})}async function Le(e){return _("cloud-project-export",{ref:e},"GET")}async function st(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await ne(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Ro(t,a))return s;if(Date.now()>=r)throw new ce(504,`${e} is still ${a} after waiting`);await new Promise(l=>setTimeout(l,2e3))}}function Ro(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function at(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await ne(),o=r.find(s=>s.ref===e);if(!o)throw new ce(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new ce(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var ie,me,Po,ce,ke,Ao,W=D(()=>{"use strict";we();Qe();Q();Ce();$o();q();tt();ie=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",me=process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyZWYiOiJ6ZjdnNjVtYjhmYXJhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3OTA4MDcwNDcsImV4cCI6MjEwNDg3NTUyNH0.M-Cb0wFEhJXU21cXtQxZAn-WnRcq4rK2uNSkeHdLucY",Po=`${ie}/functions/v1`,ce=class extends Error{status;constructor(t,n){super(n),this.status=t}},ke=class extends Error{constructor(t){super(t??nl())}};Ao=!1});function mr(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function ua(e,t,n){let r="";for(;r.length<n;)r+=mr(e,t);return r}var ur,da,ma=D(()=>{"use strict";ur="0123456789abcdefghjkmnpqrstvwxyz",da="abcdefghjkmnpqrstvwxyz"});function ga(e){return mr(e,fa)+ua(e,ur,pa-1)}function iu(e){return au.test(e)}function Z(e){if(!iu(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function j(e,t=cu){return Z(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var fa,pa,au,cu,he=D(()=>{"use strict";ma();fa=da,pa=13,au=new RegExp(`^[${fa}][${ur}]{${pa-1}}$`);cu="db.snoutdata.com"});var ha=D(()=>{"use strict";he()});function wa(e,t){let n=ya(e,7),r=ya(t,24);return Math.max(2,Math.ceil(n*24/r))}function ya(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var fr=D(()=>{"use strict"});function pr(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var gr=D(()=>{"use strict"});var ba=D(()=>{"use strict"});var Wg,Sa=D(()=>{"use strict";he();Wg=7*24});var zg,_a=D(()=>{"use strict";zg=7*24*60*60*1e3});var lu,xa=D(()=>{"use strict";_a();lu=60*60*1e3});var Ge,eh,ka=D(()=>{"use strict";Ge="snout_storage_admin",eh=`do $$ begin
24
24
  if exists (select from pg_roles where rolname = 'supabase_storage_admin') then
25
- if exists (select from pg_roles where rolname = '${Ue}') then
26
- perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${Ue}';
27
- reassign owned by ${Ue} to supabase_storage_admin;
28
- drop owned by ${Ue};
29
- drop role ${Ue};
25
+ if exists (select from pg_roles where rolname = '${Ge}') then
26
+ perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${Ge}';
27
+ reassign owned by ${Ge} to supabase_storage_admin;
28
+ drop owned by ${Ge};
29
+ drop role ${Ge};
30
30
  end if;
31
- alter role supabase_storage_admin rename to ${Ue};
31
+ alter role supabase_storage_admin rename to ${Ge};
32
32
  end if;
33
- end $$;`});var xe,_p,Gt,zs,kp,Vs=M(()=>{"use strict";xe="snout_realtime_admin",_p=`do $snoutpod$
33
+ end $$;`});var Ee,nh,on,va,rh,Ea=D(()=>{"use strict";Ee="snout_realtime_admin",nh=`do $snoutpod$
34
34
  begin
35
35
  if exists (select from pg_roles where rolname = 'supabase_admin') then
36
- if exists (select from pg_roles where rolname = '${xe}') then
37
- perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${xe}';
38
- reassign owned by ${xe} to supabase_admin;
39
- drop owned by ${xe};
40
- drop role ${xe};
36
+ if exists (select from pg_roles where rolname = '${Ee}') then
37
+ perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${Ee}';
38
+ reassign owned by ${Ee} to supabase_admin;
39
+ drop owned by ${Ee};
40
+ drop role ${Ee};
41
41
  end if;
42
- alter role supabase_admin rename to ${xe};
42
+ alter role supabase_admin rename to ${Ee};
43
43
  end if;
44
44
  if exists (select from pg_roles where rolname = 'supabase_realtime_admin')
45
- and exists (select from pg_roles where rolname = '${xe}') then
45
+ and exists (select from pg_roles where rolname = '${Ee}') then
46
46
  begin
47
- reassign owned by supabase_realtime_admin to ${xe};
47
+ reassign owned by supabase_realtime_admin to ${Ee};
48
48
  drop owned by supabase_realtime_admin;
49
49
  drop role supabase_realtime_admin;
50
50
  exception when others then
@@ -53,26 +53,26 @@ begin
53
53
  end if;
54
54
  end
55
55
  $snoutpod$;
56
- `,Gt="snoutdata_realtime",zs="supabase_realtime",kp=`do $snoutpod$
56
+ `,on="snoutdata_realtime",va="supabase_realtime",rh=`do $snoutpod$
57
57
  begin
58
- if exists (select 1 from pg_publication where pubname = '${Gt}') then
58
+ if exists (select 1 from pg_publication where pubname = '${on}') then
59
59
  return;
60
60
  end if;
61
- if exists (select 1 from pg_publication where pubname = '${zs}') then
62
- alter publication ${zs} rename to ${Gt};
61
+ if exists (select 1 from pg_publication where pubname = '${va}') then
62
+ alter publication ${va} rename to ${on};
63
63
  return;
64
64
  end if;
65
- create publication ${Gt};
66
- execute format('alter publication ${Gt} owner to %I',
65
+ create publication ${on};
66
+ execute format('alter publication ${on} owner to %I',
67
67
  (select pg_get_userbyid(datdba) from pg_database where datname = current_database()));
68
68
  end
69
69
  $snoutpod$;
70
- `});var vp,Pp,Ys=M(()=>{"use strict";vp=2*1024*1024,Pp=64*1024});var Xs,Zs,su,au,iu,Qs=M(()=>{"use strict";Xs="/snoutfn",Zs="/_snoutpod/health",su="x-snoutdata-door",au="SNOUT_FUNCTIONS_DOOR_SECRET",iu=`// Written by the SnoutData host agent. Do not edit on the host: it is rewritten
70
+ `});var sh,ah,$a=D(()=>{"use strict";sh=2*1024*1024,ah=64*1024});var Aa,Pa,du,uu,mu,Ra=D(()=>{"use strict";Aa="/snoutfn",Pa="/_snoutpod/health",du="x-snoutdata-door",uu="SNOUT_FUNCTIONS_DOOR_SECRET",mu=`// Written by the SnoutData host agent. Do not edit on the host: it is rewritten
71
71
  // every time the agent starts, from packages/snoutpod/src/host/functionMain.ts.
72
- const MOUNT = ${JSON.stringify(Xs)};
73
- const HEALTH = ${JSON.stringify(Zs)};
74
- const DOOR_HEADER = ${JSON.stringify(su)};
75
- const DOOR = new TextEncoder().encode(Deno.env.get(${JSON.stringify(au)}) ?? '');
72
+ const MOUNT = ${JSON.stringify(Aa)};
73
+ const HEALTH = ${JSON.stringify(Pa)};
74
+ const DOOR_HEADER = ${JSON.stringify(du)};
75
+ const DOOR = new TextEncoder().encode(Deno.env.get(${JSON.stringify(uu)}) ?? '');
76
76
  if (DOOR.length === 0) {
77
77
  console.error('WARNING: no door secret is set, so a request is not asked to prove it came through the front door');
78
78
  }
@@ -233,9 +233,265 @@ Deno.serve(async (req) => {
233
233
  return refuse(500, 'This function could not be run.', detail);
234
234
  }
235
235
  });
236
- `});function Yn(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function Kt(e){let t=["network","create"],n=e.allow,r=e.deny;if(n!=null&&r!=null)throw new Error("a network is policed by an allow-list or by a deny-list, not by both");if(r!=null)return lu(e,r);if(n==null){if(e.subnet||e.gateway)throw new Error("a subnet or gateway was given with no egress policy, which does nothing");return t.push(e.name),t}if(n.length>0)throw new Error("an egress allow-list is not expressible on this runtime: --internal blocks everything or nothing, and a route-based block does not block (measured on podman 5.8.6 / netavark 1.17.2)");if(e.subnet||e.gateway)throw new Error("a subnet or gateway was given with no allowance to route through it");return t.push("--internal",e.name),t}function lu(e,t){if(t.length===0)throw new Error("an empty deny-list is not a policy: leave it absent to create an ordinary network");let n=e.subnet,r=e.sink;if(!n||!r)throw new Error("a deny-list needs a pinned subnet and a sink address on it");if(Yn("the subnet",n,ea),Yn("the sink",r,cu),!na(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!na(e.gateway,n))throw new Error(`the gateway ${e.gateway} is not inside ${n}`);if(e.gateway&&e.gateway===r)throw new Error("the sink is the gateway, which routes denied traffic at the host rather than nowhere");let o=["network","create","--subnet",n];e.gateway&&o.push("--gateway",e.gateway);for(let s of t)o.push("--route",`${Yn("a denied prefix",s,ea)},${r}`);return o.push(e.name),o}function ta(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function na(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(ta(e)&s)>>>0===(ta(n??"")&s)>>>0}function ra(e){return e?.internal===!0}var ea,cu,Xn=M(()=>{"use strict";ea=/^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\/(3[0-2]|[12]?[0-9])$/,cu=/^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$/});var uu,Jt=M(()=>{"use strict";uu=7*24*60*60});var oa=M(()=>{"use strict";Se();Ys();Qs();Xn();Jt()});function aa(e){let t=[];for(let n of e.split(`
237
- `)){let r=n.trim();if(!r)continue;let[o,s,a,l]=r.split("|");!o||s===void 0||t.push({name:o,active:s==="t",walStatus:a??"",inactiveSeconds:l?Number(l):null})}return t}var sa,Zn=M(()=>{"use strict";sa="select slot_name, active, wal_status, coalesce(extract(epoch from (now() - inactive_since))::bigint::text, '') from pg_replication_slots"});var zp,Vp,ia=M(()=>{"use strict";Se();Jt();zp=6*60*60,Vp=5*60});var ca=M(()=>{"use strict"});var la,Ng,Dg,Mg,Ig,jg,Cg,ua=M(()=>{"use strict";Fs();Wn();Vn();qs();Gs();Js();Ws();Vs();oa();Zn();ia();ca();Jt();la=120,Ng=2*6e4,Dg=15*60*1e3,Mg=60*60*1e3,Ig=60*60*1e3,jg=5*60*1e3,Cg=10*60*1e3});import{execFile as mu}from"node:child_process";function da(){return(e,t,n={})=>new Promise((r,o)=>{let s=mu(e,[...t],{timeout:n.timeoutMs??0,maxBuffer:8388608,env:n.env?{...process.env,...n.env}:process.env},(a,l,i)=>{if(a&&typeof a.code=="string"){o(a);return}let d=a&&typeof a.code=="number"?a.code:0;r({code:d,stdout:String(l),stderr:String(i)})});n.stdin!==void 0&&s.stdin?.end(n.stdin)})}async function C(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
238
- `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var Wt=M(()=>{"use strict"});import{readdir as fu,readFile as Hg}from"node:fs/promises";async function ma(e,t,n={}){let r=n.binary??"podman",o=j(t).pod,s=await e(r,["volume","inspect",o,"--format","json"]);if(s.code!==0)return!1;let a=JSON.parse(s.stdout||"[]"),i=(Array.isArray(a)?a[0]:a)?.Mountpoint;return i?await(n.look??pu)(i)!=="empty":!1}async function pu(e){try{return(await fu(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var fa=M(()=>{"use strict";Se();Wt()});function pa(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can pass to a pod: ${JSON.stringify(t)}`);return t}async function ya(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function wa(e){let t=!!(e?.key&&e?.secret),n=["--env",`SNOUTPOD_S3_KEY_TYPE=${t?"shared":"auto"}`];return e&&(t&&(n.push("--env",`SNOUTPOD_S3_KEY=${pa("the access key",e.key,hu)}`),n.push("--env",ga),e.token&&n.push("--env",ha)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${pa("the endpoint",e.endpoint,gu)}`),e.uriStyle&&n.push("--env",`SNOUTPOD_S3_URI_STYLE=${e.uriStyle==="path"?"path":"host"}`),e.verifyTls===!1&&n.push("--env","SNOUTPOD_S3_VERIFY_TLS=n")),n}function ba(e){if(!e?.key||!e?.secret)return{};if(/[\r\n]/.test(e.secret))throw new Error("the repository secret contains a newline, which would write a second setting");if(e.token&&/[\r\n]/.test(e.token))throw new Error("the session token contains a newline, which would write a second setting");return{[ga]:e.secret,...e.token?{[ha]:e.token}:{}}}var ga,ha,gu,hu,Sa=M(()=>{"use strict";ga="SNOUTPOD_S3_SECRET",ha="SNOUTPOD_S3_TOKEN",gu=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,hu=/^[A-Za-z0-9/+=_.-]{1,256}$/});function zt(e){return yu[e]??e}var xa,_a,me,yu,ka,Ea=M(()=>{"use strict";xa="authenticator",_a=`do $snoutpod$
236
+ `});function hr(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function sn(e){let t=["network","create"],n=e.allow,r=e.deny;if(n!=null&&r!=null)throw new Error("a network is policed by an allow-list or by a deny-list, not by both");if(r!=null)return pu(e,r);if(n==null){if(e.subnet||e.gateway)throw new Error("a subnet or gateway was given with no egress policy, which does nothing");return t.push(e.name),t}if(n.length>0)throw new Error("an egress allow-list is not expressible on this runtime: --internal blocks everything or nothing, and a route-based block does not block (measured on podman 5.8.6 / netavark 1.17.2)");if(e.subnet||e.gateway)throw new Error("a subnet or gateway was given with no allowance to route through it");return t.push("--internal",e.name),t}function pu(e,t){if(t.length===0)throw new Error("an empty deny-list is not a policy: leave it absent to create an ordinary network");let n=e.subnet,r=e.sink;if(!n||!r)throw new Error("a deny-list needs a pinned subnet and a sink address on it");if(hr("the subnet",n,Ta),hr("the sink",r,fu),!Na(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Na(e.gateway,n))throw new Error(`the gateway ${e.gateway} is not inside ${n}`);if(e.gateway&&e.gateway===r)throw new Error("the sink is the gateway, which routes denied traffic at the host rather than nowhere");let o=["network","create","--subnet",n];e.gateway&&o.push("--gateway",e.gateway);for(let s of t)o.push("--route",`${hr("a denied prefix",s,Ta)},${r}`);return o.push(e.name),o}function Oa(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Na(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(Oa(e)&s)>>>0===(Oa(n??"")&s)>>>0}function Da(e){return e?.internal===!0}var Ta,fu,yr=D(()=>{"use strict";Ta=/^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\/(3[0-2]|[12]?[0-9])$/,fu=/^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$/});var gu,an=D(()=>{"use strict";gu=7*24*60*60});var Ia=D(()=>{"use strict";he();$a();Ra();yr();an()});function Ca(e){let t=[];for(let n of e.split(`
237
+ `)){let r=n.trim();if(!r)continue;let[o,s,a,l]=r.split("|");!o||s===void 0||t.push({name:o,active:s==="t",walStatus:a??"",inactiveSeconds:l?Number(l):null})}return t}var Ma,wr=D(()=>{"use strict";Ma="select slot_name, active, wal_status, coalesce(extract(epoch from (now() - inactive_since))::bigint::text, '') from pg_replication_slots"});var Ph,Rh,ja=D(()=>{"use strict";he();an();Ph=6*60*60,Rh=5*60});var La=D(()=>{"use strict"});function Fa(e){if(!Array.isArray(e))throw new Error("the database access list is not a list");let t=new Set;return e.map(n=>{let r=n,o=r?.role,s=r?.level;if(typeof o!="string"||!wu.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!yu.includes(s))throw new Error(`not a database access level: ${JSON.stringify(s)}`);if(t.has(o))throw new Error(`${o} is on the database access list twice`);return t.add(o),{role:o,level:s}})}function Ba(e,t){return`${e}.nspname !~ '^pg_'
238
+ and ${e}.nspname <> all (array[${Ua.map(n=>`'${n}'`).join(", ")}]::text[])
239
+ and not ${t}.rolsuper
240
+ and ${t}.rolname !~ '${bu.source}'`}function Ga(e){let t=[];for(let n of e.split(`
241
+ `)){let r=/^(created-group|created|applied|dropped-group|dropped|refused|ignored) (\S+)$/.exec(n.trim());r&&t.push({action:r[1],role:r[2]})}return t}function qa(e,t){return Z(e),JSON.stringify(Fa(t).map(n=>({role:n.role,level:n.level})))}var yu,qe,wu,U,Ua,bu,ln,Su,cn,_u,Ha,br=D(()=>{"use strict";he();yu=["full","read"],qe="snout_oauth",wu=/^oauth_[0-9a-f]{32}$/,U="snout_oauth_read",Ua=["information_schema","auth","storage","realtime","snout_realtime","_realtime","push","graphql","graphql_public","extensions","cron","net","vault","pgsodium","pgbouncer","snouttime","snouttime_internal","supabase_functions"],bu=/^(snout_[a-z]+_admin|supabase_[a-z_]+|authenticator|pgbouncer)$/;ln="SNOUTPOD_DB_ACCESS",Su=/^[a-z_][a-z0-9_]*$/;for(let e of Ua)if(!Su.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);cn="snoutpod_oauth_read_new_schema",_u=`create schema if not exists extensions;
242
+ create or replace function extensions.${cn}() returns event_trigger
243
+ language plpgsql
244
+ security invoker
245
+ set search_path = pg_catalog, pg_temp
246
+ as $snoutpod$
247
+ declare
248
+ created record;
249
+ owner_role text := pg_catalog.current_database() || '_owner';
250
+ begin
251
+ if not exists (select from pg_catalog.pg_roles where rolname = '${U}') then
252
+ return;
253
+ end if;
254
+ for created in
255
+ select n.nspname
256
+ from pg_catalog.pg_event_trigger_ddl_commands() c
257
+ join pg_catalog.pg_namespace n on n.oid = c.objid
258
+ join pg_catalog.pg_roles r on r.oid = n.nspowner
259
+ where c.command_tag = 'CREATE SCHEMA'
260
+ and c.classid = 'pg_catalog.pg_namespace'::pg_catalog.regclass
261
+ and not c.in_extension
262
+ and ${Ba("n","r")}
263
+ loop
264
+ begin
265
+ execute pg_catalog.format('grant usage on schema %I to ${U}', created.nspname);
266
+ execute pg_catalog.format('grant select on all tables in schema %I to ${U}', created.nspname);
267
+ execute pg_catalog.format('grant select on all sequences in schema %I to ${U}', created.nspname);
268
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${U}', owner_role, created.nspname);
269
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${U}', owner_role, created.nspname);
270
+ exception when others then
271
+ raise warning 'snoutpod: read access reaches schema % at the next apply, not now: %', created.nspname, sqlerrm;
272
+ end;
273
+ end loop;
274
+ end
275
+ $snoutpod$;
276
+ do $snoutpod$
277
+ begin
278
+ if not exists (select from pg_catalog.pg_event_trigger where evtname = '${cn}') then
279
+ create event trigger ${cn} on ddl_command_end
280
+ when tag in ('CREATE SCHEMA')
281
+ execute function extensions.${cn}();
282
+ end if;
283
+ end
284
+ $snoutpod$;
285
+ `,Ha=`\\getenv db_access ${ln}
286
+ ${_u}create function pg_temp.snoutpod_member(member_name text, group_name text)
287
+ returns boolean
288
+ language sql
289
+ stable
290
+ as $snoutpod$
291
+ -- Asked of pg_auth_members, never pg_has_role: that is always true for a superuser.
292
+ select exists (
293
+ select from pg_auth_members am
294
+ join pg_roles g on g.oid = am.roleid
295
+ join pg_roles m on m.oid = am.member
296
+ where g.rolname = group_name and m.rolname = member_name
297
+ )
298
+ $snoutpod$;
299
+ create function pg_temp.snoutpod_customer_schema(schema_oid oid)
300
+ returns boolean
301
+ language sql
302
+ stable
303
+ as $snoutpod$
304
+ -- isCustomerSchema in dbAccess.ts, from the same SYSTEM_SCHEMAS and PLATFORM_OWNER.
305
+ select ${Ba("n","r")}
306
+ from pg_namespace n
307
+ join pg_roles r on r.oid = n.nspowner
308
+ where n.oid = schema_oid
309
+ $snoutpod$;
310
+ create function pg_temp.snoutpod_db_access(wanted jsonb)
311
+ returns table(change text, role_name text)
312
+ language plpgsql
313
+ as $snoutpod$
314
+ #variable_conflict use_column
315
+ declare
316
+ db text := current_database();
317
+ owner_role text := current_database() || '_owner';
318
+ entry jsonb;
319
+ wanted_role text;
320
+ wanted_level text;
321
+ found_role text;
322
+ any_read boolean;
323
+ target record;
324
+ begin
325
+ if jsonb_typeof(wanted) is distinct from 'array' then
326
+ raise exception 'snoutpod: the database access list is not an array';
327
+ end if;
328
+ for entry in select value from jsonb_array_elements(wanted) loop
329
+ if coalesce(entry->>'role', '') !~ '^oauth_[0-9a-f]{32}$'
330
+ or coalesce(entry->>'level', '') not in ('full', 'read') then
331
+ raise exception 'snoutpod: refusing a database access entry: %', entry;
332
+ end if;
333
+ end loop;
334
+ if not exists (select from pg_roles where rolname = owner_role) then
335
+ raise exception 'snoutpod: this database has no owner role %', owner_role;
336
+ end if;
337
+ any_read := exists (select from jsonb_array_elements(wanted) w where w->>'level' = 'read');
338
+
339
+ if not exists (select from pg_roles where rolname = '${qe}') then
340
+ create role ${qe} nologin;
341
+ change := 'created-group'; role_name := '${qe}'; return next;
342
+ end if;
343
+
344
+ -- Taken away: every member of the group the list does not name.
345
+ for found_role in
346
+ select m.rolname
347
+ from pg_auth_members am
348
+ join pg_roles g on g.oid = am.roleid
349
+ join pg_roles m on m.oid = am.member
350
+ where g.rolname = '${qe}'
351
+ order by 1
352
+ loop
353
+ continue when exists (select from jsonb_array_elements(wanted) w where w->>'role' = found_role);
354
+ if found_role !~ '^oauth_[0-9a-f]{32}$' then
355
+ -- In the group, but not a name this ever makes: somebody with the superuser put it
356
+ -- there. Left exactly as it is.
357
+ change := 'ignored'; role_name := found_role; return next;
358
+ continue;
359
+ end if;
360
+ perform pg_terminate_backend(pid) from pg_stat_activity where usename = found_role;
361
+ execute format('reassign owned by %I to %I', found_role, owner_role);
362
+ execute format('drop owned by %I', found_role);
363
+ execute format('drop role %I', found_role);
364
+ change := 'dropped'; role_name := found_role; return next;
365
+ end loop;
366
+
367
+ -- What a read person may read: the customer's schemas, and nothing of ours (dbAccess.ts).
368
+ if any_read then
369
+ if not exists (select from pg_roles where rolname = '${U}') then
370
+ create role ${U} nologin;
371
+ change := 'created-group'; role_name := '${U}'; return next;
372
+ end if;
373
+ -- Stated whole, as a person's role is, and a member of nothing: whatever it held would reach
374
+ -- every read person.
375
+ alter role ${U} with nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls password null;
376
+ for target in
377
+ select g.rolname as of_role, pg_get_userbyid(am.grantor) as grantor
378
+ from pg_auth_members am
379
+ join pg_roles g on g.oid = am.roleid
380
+ join pg_roles m on m.oid = am.member
381
+ where m.rolname = '${U}'
382
+ loop
383
+ execute format('revoke %I from ${U} granted by %I', target.of_role, target.grantor);
384
+ end loop;
385
+ for target in
386
+ select n.nspname, pg_temp.snoutpod_customer_schema(n.oid) as customer
387
+ from pg_namespace n
388
+ where n.nspname !~ '^pg_' and n.nspname <> 'information_schema'
389
+ order by 1
390
+ loop
391
+ if target.customer then
392
+ execute format('grant usage on schema %I to ${U}', target.nspname);
393
+ execute format('grant select on all tables in schema %I to ${U}', target.nspname);
394
+ execute format('grant select on all sequences in schema %I to ${U}', target.nspname);
395
+ execute format('alter default privileges for role %I in schema %I grant select on tables to ${U}', owner_role, target.nspname);
396
+ execute format('alter default privileges for role %I in schema %I grant select on sequences to ${U}', owner_role, target.nspname);
397
+ else
398
+ -- Ours. Nothing was ever granted here unless the list or an owner changed since.
399
+ execute format('revoke all on schema %I from ${U}', target.nspname);
400
+ execute format('revoke all on all tables in schema %I from ${U}', target.nspname);
401
+ execute format('revoke all on all sequences in schema %I from ${U}', target.nspname);
402
+ end if;
403
+ end loop;
404
+ for target in
405
+ select pg_get_userbyid(d.defaclrole) as for_role, n.nspname,
406
+ case d.defaclobjtype when 'r' then 'tables' when 'S' then 'sequences' when 'f' then 'functions' when 'T' then 'types' end as kind
407
+ from pg_default_acl d
408
+ join pg_namespace n on n.oid = d.defaclnamespace
409
+ where not pg_temp.snoutpod_customer_schema(n.oid)
410
+ and exists (select from aclexplode(d.defaclacl) a where a.grantee = '${U}'::regrole)
411
+ loop
412
+ continue when target.kind is null;
413
+ execute format('alter default privileges for role %I in schema %I revoke all on %s from ${U}', target.for_role, target.nspname, target.kind);
414
+ end loop;
415
+ end if;
416
+
417
+ -- Given, or changed.
418
+ for entry in select value from jsonb_array_elements(wanted) loop
419
+ wanted_role := entry->>'role';
420
+ wanted_level := entry->>'level';
421
+ if exists (select from pg_roles where rolname = wanted_role) then
422
+ if not pg_temp.snoutpod_member(wanted_role, '${qe}') then
423
+ -- The name is right and the role is not ours: made by somebody else, who holds
424
+ -- ADMIN on it. Never adopted (see dbAccess.ts).
425
+ change := 'refused'; role_name := wanted_role; return next;
426
+ continue;
427
+ end if;
428
+ change := 'applied';
429
+ else
430
+ execute format('create role %I login in role ${qe}', wanted_role);
431
+ change := 'created';
432
+ end if;
433
+ -- No password, ever: the pg_hba line for the group is oauth, and a password could not be
434
+ -- used if it were set. Attributes stated whole, so a role somebody altered by hand is put back.
435
+ execute format('alter role %I with login nosuperuser nocreatedb nocreaterole noreplication password null', wanted_role);
436
+ execute format('grant connect on database %I to %I', db, wanted_role);
437
+ -- Whatever the level: before 2026-10-04 \`read\` held this, which reads every table in the
438
+ -- database, ours included. Checked at each statement, so open sessions lose it at once.
439
+ if pg_temp.snoutpod_member(wanted_role, 'pg_read_all_data') then
440
+ execute format('revoke pg_read_all_data from %I', wanted_role);
441
+ end if;
442
+ if wanted_level = 'full' then
443
+ if pg_temp.snoutpod_member(wanted_role, '${U}') then
444
+ execute format('revoke ${U} from %I', wanted_role);
445
+ end if;
446
+ execute format('alter role %I nobypassrls', wanted_role);
447
+ if not pg_temp.snoutpod_member(wanted_role, owner_role) then
448
+ execute format('grant %I to %I with inherit true, set true', owner_role, wanted_role);
449
+ end if;
450
+ execute format('alter role %I in database %I set role = %I', wanted_role, db, owner_role);
451
+ execute format('alter role %I in database %I reset default_transaction_read_only', wanted_role, db);
452
+ else
453
+ if pg_temp.snoutpod_member(wanted_role, owner_role) then
454
+ -- Full to read: a session already open is running as the owner, and would go on
455
+ -- doing so. Ended, as taking access away ends them.
456
+ execute format('revoke %I from %I', owner_role, wanted_role);
457
+ perform pg_terminate_backend(pid) from pg_stat_activity where usename = wanted_role;
458
+ end if;
459
+ if not pg_temp.snoutpod_member(wanted_role, '${U}') then
460
+ execute format('grant ${U} to %I with inherit true, set false', wanted_role);
461
+ end if;
462
+ execute format('alter role %I bypassrls', wanted_role);
463
+ execute format('alter role %I in database %I reset role', wanted_role, db);
464
+ execute format('alter role %I in database %I set default_transaction_read_only = on', wanted_role, db);
465
+ end if;
466
+ role_name := wanted_role; return next;
467
+ end loop;
468
+
469
+ -- Nobody reads any more: the group goes, and with it every grant and default it held, so a
470
+ -- project that stops using the level carries nothing of it.
471
+ if not any_read and exists (select from pg_roles where rolname = '${U}') then
472
+ begin
473
+ for target in
474
+ select m.rolname as member_role, pg_get_userbyid(am.grantor) as grantor
475
+ from pg_auth_members am
476
+ join pg_roles g on g.oid = am.roleid
477
+ join pg_roles m on m.oid = am.member
478
+ where g.rolname = '${U}'
479
+ loop
480
+ execute format('revoke ${U} from %I granted by %I', target.member_role, target.grantor);
481
+ end loop;
482
+ drop owned by ${U};
483
+ drop role ${U};
484
+ change := 'dropped-group'; role_name := '${U}'; return next;
485
+ exception when others then
486
+ -- Holding something in another database, which this never grants. Kept, and said.
487
+ raise warning 'snoutpod: could not drop ${U}: %', sqlerrm;
488
+ end;
489
+ end if;
490
+ end
491
+ $snoutpod$;
492
+ select change || ' ' || role_name from pg_temp.snoutpod_db_access(:'db_access'::jsonb);
493
+ `});var Ka,hy,yy,wy,by,Sy,_y,Wa=D(()=>{"use strict";ha();fr();gr();ba();Sa();xa();ka();Ea();Ia();wr();ja();La();an();br();Ka=120,hy=2*6e4,yy=15*60*1e3,wy=60*60*1e3,by=60*60*1e3,Sy=5*60*1e3,_y=10*60*1e3});import{execFile as xu}from"node:child_process";function Ja(){return(e,t,n={})=>new Promise((r,o)=>{let s=xu(e,[...t],{timeout:n.timeoutMs??0,maxBuffer:8388608,env:n.env?{...process.env,...n.env}:process.env},(a,l,i)=>{if(a&&typeof a.code=="string"){o(a);return}let u=a&&typeof a.code=="number"?a.code:0;r({code:u,stdout:String(l),stderr:String(i)})});n.stdin!==void 0&&s.stdin?.end(n.stdin)})}async function L(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
494
+ `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var pt=D(()=>{"use strict"});import{readdir as ku,readFile as $y}from"node:fs/promises";async function za(e,t,n={}){let r=n.binary??"podman",o=j(t).pod,s=await e(r,["volume","inspect",o,"--format","json"]);if(s.code!==0)return!1;let a=JSON.parse(s.stdout||"[]"),i=(Array.isArray(a)?a[0]:a)?.Mountpoint;return i?await(n.look??vu)(i)!=="empty":!1}async function vu(e){try{return(await ku(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var Va=D(()=>{"use strict";he();pt()});function Ya(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can pass to a pod: ${JSON.stringify(t)}`);return t}async function Qa(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function ei(e){let t=!!(e?.key&&e?.secret),n=["--env",`SNOUTPOD_S3_KEY_TYPE=${t?"shared":"auto"}`];return e&&(t&&(n.push("--env",`SNOUTPOD_S3_KEY=${Ya("the access key",e.key,$u)}`),n.push("--env",Xa),e.token&&n.push("--env",Za)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${Ya("the endpoint",e.endpoint,Eu)}`),e.uriStyle&&n.push("--env",`SNOUTPOD_S3_URI_STYLE=${e.uriStyle==="path"?"path":"host"}`),e.verifyTls===!1&&n.push("--env","SNOUTPOD_S3_VERIFY_TLS=n")),n}function ti(e){if(!e?.key||!e?.secret)return{};if(/[\r\n]/.test(e.secret))throw new Error("the repository secret contains a newline, which would write a second setting");if(e.token&&/[\r\n]/.test(e.token))throw new Error("the session token contains a newline, which would write a second setting");return{[Xa]:e.secret,...e.token?{[Za]:e.token}:{}}}var Xa,Za,Eu,$u,ni=D(()=>{"use strict";Xa="SNOUTPOD_S3_SECRET",Za="SNOUTPOD_S3_TOKEN",Eu=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,$u=/^[A-Za-z0-9/+=_.-]{1,256}$/});function dn(e){return Au[e]??e}var ri,oi,ye,Au,si,ai=D(()=>{"use strict";ri="authenticator",oi=`do $snoutpod$
239
495
  declare
240
496
  wanted text[];
241
497
  begin
@@ -253,31 +509,33 @@ begin
253
509
  end loop;
254
510
  end
255
511
  $snoutpod$;
256
- `,me="snout_auth_admin",yu={supabase_auth_admin:me};ka=`do $snoutpod$
512
+ `,ye="snout_auth_admin",Au={supabase_auth_admin:ye};si=`do $snoutpod$
257
513
  begin
258
514
  if exists (select from pg_roles where rolname = 'supabase_auth_admin') then
259
- if exists (select from pg_roles where rolname = '${me}') then
260
- perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${me}';
261
- reassign owned by ${me} to supabase_auth_admin;
262
- drop owned by ${me};
263
- drop role ${me};
515
+ if exists (select from pg_roles where rolname = '${ye}') then
516
+ perform pg_terminate_backend(pid) from pg_stat_activity where usename = '${ye}';
517
+ reassign owned by ${ye} to supabase_auth_admin;
518
+ drop owned by ${ye};
519
+ drop role ${ye};
264
520
  end if;
265
- alter role supabase_auth_admin rename to ${me};
521
+ alter role supabase_auth_admin rename to ${ye};
266
522
  end if;
267
523
  end
268
524
  $snoutpod$;
269
- `});function Pa(e){let t=[];for(let n of e.split(`
270
- `)){let r=n.split(er);if(r.length!==4||!Qn.includes(r[0]))continue;let[o,s,a,l]=r;t.push(l?{name:o,from:s,error:l}:{name:o,from:s,to:a})}return t}var Qn,er,va,$a=M(()=>{"use strict";Qn=["snouttime"];for(let e of Qn)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);er="",va=`
525
+ `});function ci(e){let t=[];for(let n of e.split(`
526
+ `)){let r=n.split(_r);if(r.length!==4||!Sr.includes(r[0]))continue;let[o,s,a,l]=r;t.push(l?{name:o,from:s,error:l}:{name:o,from:s,to:a})}return t}var Sr,_r,ii,li=D(()=>{"use strict";Sr=["snouttime"];for(let e of Sr)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);_r="",ii=`
271
527
  CREATE TEMP TABLE snoutpod_extension_updates (name text, from_version text, to_version text, error text);
272
528
  DO $snoutpod$
273
529
  DECLARE
274
530
  r record;
275
531
  BEGIN
532
+ -- One caller at a time (the agent and the image's own start both run this); held to commit.
533
+ PERFORM pg_advisory_xact_lock(hashtext('snoutpod.extension_updates'));
276
534
  FOR r IN
277
535
  SELECT e.extname, e.extversion
278
536
  FROM pg_extension e
279
537
  JOIN pg_available_extensions a ON a.name = e.extname
280
- WHERE e.extname = ANY (ARRAY[${Qn.map(e=>`'${e}'`).join(", ")}])
538
+ WHERE e.extname = ANY (ARRAY[${Sr.map(e=>`'${e}'`).join(", ")}])
281
539
  AND a.default_version IS DISTINCT FROM e.extversion
282
540
  LOOP
283
541
  BEGIN
@@ -292,86 +550,95 @@ BEGIN
292
550
  END
293
551
  $snoutpod$;
294
552
  SELECT name, from_version, coalesce(to_version, ''), coalesce(error, '') FROM snoutpod_extension_updates ORDER BY name;
295
- `});import{createHash as rr,randomBytes as wu}from"node:crypto";import{access as bu,constants as Su}from"node:fs/promises";import{delimiter as xu,join as _u}from"node:path";function ku(e,t){return rr("sha256").update(or(e)).update(e.smtpSecretEnv&&t?rr("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function or(e){if(Array.isArray(e))return`[${e.map(or).join(",")}]`;if(e!==null&&typeof e=="object"){let t=e;return`{${Object.keys(t).sort().filter(r=>t[r]!==void 0).map(r=>`${JSON.stringify(r)}:${or(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function ir(e){if(!vu.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function it(e){if(!Pu.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function $u(e,t,n){return`postgres://${it(t)}:${n}@127.0.0.1:5432/${X(e)}`}function Ra(e,t,n,r){let o={...t.env};if(t.dbUriEnv&&t.dbUriRole){let s=zt(t.dbUriRole),a=n.get(s);if(a===void 0)throw new Error(`no password was applied for role ${JSON.stringify(t.dbUriRole)}, which ${JSON.stringify(t.name)} needs to reach its database`);o[t.dbUriEnv]=$u(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),Tu(t.image,o)}function Tu(e,t){if(!/(^|\/)snout-auth([:@]|$)/.test(e))return{...t};let n={};for(let[r,o]of Object.entries(t)){let s=r.startsWith("GOTRUE_")?`AUTH_${r.slice(7)}`:r==="API_EXTERNAL_URL"?"AUTH_API_EXTERNAL_URL":r;s!==r&&s in t||(n[s]=o)}return n}function Oa(e,t){return`${j(e).pod}-${ir(t)}`}function Vt(e){return`snoutpod-host-${ir(e)}`}function Au(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return rr("sha256").update(JSON.stringify([e.image,e.port,e.hostPort,e.healthPath??null,e.memoryMb,t,e.adminPort??null,e.adminHostPort??null,e.command??null,e.volumes??null,e.network??null])).digest("hex").slice(0,32)}function Ru(e,t){let n=[],r=sr(t);for(let o of e??[])if(ir(o.name),ar(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){ar(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`)}return n}function sr(e){let t=e===void 0?[]:typeof e=="string"?[e]:[...e];return["127.0.0.1",...new Set(t.filter(n=>n!=="127.0.0.1"))]}function ar(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Da(e){let t=[];for(let n of Object.keys(e)){if(!Ou.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function Nu(e){let t=e.healthPath;if(t==null||t==="")return[];if(!ja.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(ar(e.hostPort,`${e.name}: hostPort`),["--label",`${cr}=${e.hostPort}:${t}`])}function Du(e){if(!e)return null;let t=e.indexOf(":");if(t<=0)return null;let n=Number(e.slice(0,t)),r=e.slice(t+1);return!Number.isInteger(n)||n<1||n>65535||!ja.test(r)?null:{port:n,path:r}}async function Iu(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>bu(o,Su.X_OK));for(let o of n.split(xu))if(o)try{let s=_u(o,e);return await r(s),s}catch{}return null}function ju(){return["\\set ON_ERROR_STOP 0","select 'create extension if not exists pg_prewarm' where exists (select 1 from pg_available_extensions where name = 'pg_prewarm') \\gexec","with candidate as ("," select c.oid, pg_relation_size(c.oid) as bytes"," from pg_class c join pg_namespace n on n.oid = c.relnamespace"," where c.relkind in ('r', 'i', 'm')"," and c.relpersistence = 'p'"," and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')","), budgeted as ("," select oid, bytes,"," sum(bytes) over (order by bytes, oid rows between unbounded preceding and current row) as running"," from candidate",")","select coalesce(sum(pg_prewarm(oid)), 0) from budgeted","where bytes > 0 and running <= pg_size_bytes(current_setting('shared_buffers')) * 3 / 4;",""].join(`
296
- `)}function Lu(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function Uu(e,t){for(let r of e??[]){let o=/^PG_MAJOR=(\d+)$/.exec(r.trim());if(o)return o[1]??null}let n=t.split("@")[0]??"";return/:(\d+)(?:[.-][^/:]*)?$/.exec(n)?.[1]??null}function lr(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function Fu(e,t){return t.image===null||t.data===null||t.image===t.data?null:`${e} was not started: its data directory was made by Postgres ${t.data} and the image runs Postgres ${t.image}. A data directory starts only on the major that made it, so run it on a ${t.data} image or move it by dump and restore.`}async function Bu(e,t,n,r){return{image:await Hu(e,t,r),data:await qu(e,t,n)}}async function Hu(e,t,n){let r=null;try{let o=await e(t,["image","inspect","--format","{{json .Config.Env}}",n]);if(o.code===0&&o.stdout.trim()){let s=JSON.parse(o.stdout.trim());r=Array.isArray(s)?s.filter(a=>typeof a=="string"):null}}catch{r=null}return Uu(r,n)}async function qu(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",j(n).pod]),o=r.code===0?r.stdout.trim().replace(/\/+$/,""):"";if(!o)return null;let s=await e(t,["unshare","cat",`${o}/PG_VERSION`]);return s.code===0?lr(s.stdout):null}catch{return null}}function Gu(e){if(!e)return[];let t=[],n=/^[1-9][0-9]{0,6}(kB|MB|GB)$/;typeof e.sharedBuffers=="string"&&n.test(e.sharedBuffers)&&t.push(`SNOUTPOD_SHARED_BUFFERS=${e.sharedBuffers}`),typeof e.workMem=="string"&&n.test(e.workMem)&&t.push(`SNOUTPOD_WORK_MEM=${e.workMem}`);let r=typeof e.maxConnections=="number"&&Number.isInteger(e.maxConnections)&&e.maxConnections>=1&&e.maxConnections<=1e4?e.maxConnections:Ma;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,Ma)}`),typeof e.privilegedExtensions=="string"&&/^[a-z][a-z0-9_-]*(,[a-z][a-z0-9_-]*)*$/.test(e.privilegedExtensions)&&t.push(`SNOUTPOD_PRIVILEGED_EXTENSIONS=${e.privilegedExtensions}`),t}function Ku(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??Mu;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Iu(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function La(e){let t=e.binary??"podman",n=e.exec,r=Ku(e),o=e.probe??Ca;async function s(m){try{await n(t,["exec","--interactive",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"--quiet","--file","-"],{timeoutMs:Cu,stdin:ju()})}catch{}}async function a(){let m=await C(n,t,["ps","--all","--filter",`label=${Fe}`,"--format","json"]),p=m.trim()?JSON.parse(m):[],b=new Map,w=new Map;for(let v of p){let A=v.Labels?.[Fe],k=v.Labels?.[st];if(!A||!k)continue;let I=b.get(A)??[];I.push(k),b.set(A,I);let D=Number(v.Labels?.[nr]??0),J=Number.isInteger(D)?D:0;w.set(A,Math.max(w.get(A)??J,J))}let _=[];for(let v of p){let A=v.Labels?.[Fe];if(!A||v.Labels?.[st])continue;let k=Number(v.Labels?.[tr]);_.push({ref:A,status:Ju(v.State),generation:Number.isInteger(k)?k:null,hasData:await e.hasData(A),services:b.get(A)??[],...w.has(A)?{servicesGeneration:w.get(A)}:{}})}return _}async function l(m){let p=await(e.majors??((b,w)=>Bu(n,t,b,w)))(m.ref,m.image);return Fu(m.ref,p)}async function i(m){let p=j(m.ref),b=await l(m);if(b)throw new Error(b);let w=e.credentials,_=await ya(e.repository,w?()=>w.forProject(m.ref,m.bucket):null),v={SNOUTPOD_OWNER_PASSWORD:m.ownerPassword,POSTGRES_PASSWORD:m.adminPassword,...ba(_)},A=[...await r(),"run","--detach","--name",p.pod,"--label",`${Fe}=${m.ref}`,"--label",`${tr}=${m.generation}`,"--userns=auto","--memory",`${m.memoryMb}m`,...zu(m.cpus),"--restart","no",...e.network?["--network",e.network]:[],...m.port?sr(e.podBind).flatMap(k=>["--publish",`${k}:${m.port}:5432`]):[],...Ru(m.services,e.podBind),"--volume",`${p.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${m.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${m.bucket}`,"--env",`SNOUTPOD_S3_REGION=${m.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...Gu(m.tuning).flatMap(k=>["--env",k]),"--env",`SNOUTPOD_RETENTION_FULL=${Hs(m.backup?.retentionDays,m.backup?.fullIntervalHours)}`,...wa(_),m.image,"snoutpod-postgres"];await C(n,t,A,{env:v})}async function d(m){let p=await C(n,t,["ps","--all","--filter",`label=${Fe}=${X(m)}`,"--filter",`label=${st}`,"--format","json"]),b=p.trim()?JSON.parse(p):[],w=[];for(let _ of b){let v=_.Labels?.[st];if(!v)continue;let A=Number(_.Labels?.[nr]??0);w.push({name:v,container:Oa(m,v),spec:_.Labels?.[Ta]??"",pod:_.Labels?.[Aa]??"",running:_.State==="running",generation:Number.isInteger(A)?A:0})}return w}async function u(m){let p=await n(t,["container","inspect","--format","{{.Id}}",j(m).pod]);return p.code===0?p.stdout.trim():""}async function f(m,p){let b=[];for(let k of p){let I=k.dbUriRole?zt(k.dbUriRole):null;k.dbUriEnv&&I&&!b.includes(I)&&b.push(it(I))}let w=new Map;if(b.length===0)return w;let _={},v=[],A=b.includes(me)?[ka]:[];return b.includes(xa)&&A.push(_a),b.forEach((k,I)=>{let D=wu(24).toString("base64url"),J=`SNOUTPOD_SERVICE_PASSWORD_${I}`;w.set(k,D),_[J]=D,v.push("--env",J),A.push(`\\getenv service_password_${I} ${J}`),A.push(`\\set service_role_${I} ${k}`),A.push(`alter role :"service_role_${I}" with password :'service_password_${I}';`)}),A.push(""),await C(n,t,["exec","--interactive",...v,j(m.ref).pod,"psql","--username","snoutpod_admin","--dbname",j(m.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:_,stdin:A.join(`
297
- `)}),w}async function h(m){let p=m.services??[],b=await d(m.ref),w;p.some(P=>P.smtpSecretEnv)&&(w=await e.smtpSecret?.().catch(()=>{}));let _=p.length>0?await u(m.ref):"",v=new Map(b.map(P=>[P.name,P])),A=new Map(p.map(P=>[P.name,ku(P,w)])),k=new Set(p.filter(P=>{let ne=v.get(P.name);return!ne||!ne.running||ne.spec!==A.get(P.name)||_===""||ne.pod!==_}).map(P=>P.name)),I=m.servicesGeneration??0;if(k.size===0&&p.length>0&&!b.some(P=>P.generation>=I))for(let P of p)k.add(P.name);let D=P=>P.dbUriEnv&&P.dbUriRole?zt(P.dbUriRole):null;for(let P=!0;P;){P=!1;let ne=new Set(p.filter(te=>k.has(te.name)).map(D));for(let te of p){let br=D(te);!k.has(te.name)&&br!==null&&ne.has(br)&&(k.add(te.name),P=!0)}}for(let P of b)(k.has(P.name)||!A.has(P.name))&&await C(n,t,["rm","--force","--ignore",P.container]);let J=p.filter(P=>k.has(P.name)),He=await f(m,J);for(let P of J){let ne=Oa(m.ref,P.name),te=[...await r(),"run","--detach","--name",ne,"--label",`${Fe}=${m.ref}`,"--label",`${tr}=${m.generation}`,"--label",`${st}=${P.name}`,"--label",`${nr}=${m.servicesGeneration??0}`,"--label",`${Ta}=${A.get(P.name)??""}`,"--label",`${Aa}=${_}`,"--network",`container:${j(m.ref).pod}`,"--userns",`container:${j(m.ref).pod}`,"--memory",`${P.memoryMb}m`,"--restart","no",...Nu(P),...Da(Ra(m,P,He,w)),P.image];await C(n,t,te,{env:Ra(m,P,He,w)})}}return{list:a,preflight:l,create:i,createServices:h,async start(m){await C(n,t,["start",j(m).pod])},async startServices(m){for(let p of await d(m))await C(n,t,["start",p.container])},async applyHostServices(m){let p=[],b=await C(n,t,["ps","--all","--filter",`label=${at}`,"--format","json"]),w=b.trim()?JSON.parse(b):[],_=new Map;for(let k of w){let I=k.Labels?.[at];I&&_.set(I,{spec:k.Labels?.[Na]??"",state:k.State??""})}for(let k of m)try{await A(k)}catch(I){p.push({name:k.name,action:"failed",reason:I instanceof Error?I.message:String(I)})}async function v(k){let I=await r();if((await n(t,[...I,"image","exists",k])).code===0)return;let D=await n(t,[...I,"pull","--quiet",k]);if(D.code===0)return;let J=Lu(k);if(!J||!e.registryLogin)throw new Error(`${k} could not be pulled: ${(D.stderr||D.stdout).trim()}`);await e.registryLogin(J),await C(n,t,[...I,"pull","--quiet",k])}async function A(k){let I=e.hostServiceLocal?.(k,m),D=I?{...k,...I.env&&Object.keys(I.env).length>0?{env:{...k.env,...I.env}}:{},...I.volumes?{volumes:I.volumes}:{},...I.network?{network:I.network}:{}}:k,J=Vt(D.name),He=Au(D),P=_.get(D.name);if(_.delete(D.name),P&&P.spec===He){P.state!=="running"&&(await C(n,t,["start",J]),p.push({name:D.name,action:"started",reason:P.state}));return}let ne=P?"the spec changed":"it was not here";await v(D.image),P&&await C(n,t,["rm","--force","--ignore",J]),await C(n,t,[...await r(),"run","--detach","--name",J,"--label",`${at}=${D.name}`,"--label",`${Na}=${He}`,...D.healthPath?["--label",`${cr}=${D.hostPort}:${D.healthPath}`]:[],"--memory",`${D.memoryMb}m`,"--restart","no",...D.network?["--network",D.network]:e.network?["--network",e.network]:[],...(D.volumes??[]).flatMap(te=>["--volume",te]),...sr(e.podBind).flatMap(te=>["--publish",`${te}:${D.hostPort}:${D.port}`]),...D.adminPort&&D.adminHostPort?["--publish",`127.0.0.1:${D.adminHostPort}:${D.adminPort}`]:[],...Da(D.env),D.image,...D.command??[]],{env:{...D.env}}),p.push({name:D.name,action:P?"recreated":"created",reason:ne})}for(let k of _.keys())await C(n,t,["rm","--force","--ignore",Vt(k)]),p.push({name:k,action:"removed",reason:"it is no longer on the list"});return p},async hostServiceHealth(m){let p=await C(n,t,["ps","--all","--filter",`label=${at}`,"--format","json"]),b=(p.trim()?JSON.parse(p):[]).map(_=>_.Labels?.[at]??"").filter(_=>_!=="");if(b.length===0)return[];let w=Date.now()+m;for(;;){let _=[];for(let v of b)_.push(await Ia(n,t,v,Vt(v),o));if(_.every(v=>v.ok)||Date.now()>=w)return _;await new Promise(v=>setTimeout(v,500))}},async serviceHealth(m,p){let b=await d(m);if(b.length===0)return[];let w=Date.now()+p;for(;;){let _=[];for(let v of b)_.push(await Ia(n,t,v.name,v.container,o));if(_.every(v=>v.ok)||Date.now()>=w)return _;await new Promise(v=>setTimeout(v,500))}},async stop(m,p){for(let b of await d(m))await C(n,t,["stop","--time",String(Eu),b.container]);await C(n,t,["stop","--time",String(p),j(m).pod])},async remove(m){for(let p of await d(m))await C(n,t,["rm","--force","--ignore",p.container]);await C(n,t,["rm","--force","--ignore","--volumes",j(m).pod])},async removeData(m){await C(n,t,["volume","rm","--force",j(m).pod])},async applyOwnerPassword(m){let p=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${m.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
298
- `);await C(n,t,["exec","--interactive","--env","SNOUTPOD_OWNER_PASSWORD",j(m.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:m.ownerPassword},stdin:p})},async applyRolePassword(m,p,b){let w=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${it(p)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
299
- `);await C(n,t,["exec","--interactive","--env","SNOUTPOD_ROLE_PASSWORD",j(m).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:b},stdin:w})},async updateExtensions(m){let p=j(m),b=await C(n,t,["exec","--interactive",p.pod,"psql","--username","snoutpod_admin","--dbname",p.database,"--quiet","-tA","-F",er,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:va});return Pa(b)},async runProjectSql(m,p){let b=j(m);await C(n,t,["exec","--interactive",b.pod,"psql","--username","snoutpod_admin","--dbname",b.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:p})},async ensureHostServiceDatabase(m,p,b){let w=Vt(m),_=it(p),v=it(b),A=I=>["exec",w,"psql","--username",_,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",I];(await C(n,t,A(`select 1 from pg_database where datname = '${v}'`))).trim()!=="1"&&await C(n,t,A(`create database "${v}"`))},async replicationSlots(m){let p=await C(n,t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tA","-F","|","-c",sa]);return aa(p)},async dropReplicationSlot(m,p){if(!/^[a-z0-9_]{1,63}$/.test(p))throw new Error(`not a replication slot name: ${JSON.stringify(p)}`);await C(n,t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-v","ON_ERROR_STOP=1","-c",`select pg_drop_replication_slot('${p}')`])},async renameProject(m,p){p!==m.ref&&await C(n,t,["exec","--interactive",j(m.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:Wu(p,m.ref)})},async waitReady(m,p){let b=j(m).pod,w=Date.now()+p;for(;;){let _=await n(t,["exec",b,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(_.code===0&&_.stdout.trim()==="t")return s(m),!0;if(Date.now()>=w)return!1;await new Promise(v=>setTimeout(v,500))}},async capacity(){let m=await a(),p=await e.capacity();return{pods:m.length,...p}},async databaseBytes(m){let p=await n(t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tAc","select pg_database_size(current_database())"]);return p.code===0?zn(p.stdout):null},async storageBytes(m){let p=await n(t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tAc","select coalesce(sum((metadata->>'size')::bigint), 0) from storage.objects"]);return p.code===0?zn(p.stdout):null},async setReadOnly(m,p){let b=j(m);await C(n,t,["exec",b.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${b.database} set default_transaction_read_only = ${p?"on":"off"}`])},async archiveState(m){let p=await n(t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tA","-F","|","-c",`select coalesce(to_char(last_archived_time, 'YYYY-MM-DD"T"HH24:MI:SS.MSOF'), ''), case when last_failed_time > coalesce(last_archived_time, '-infinity') then failed_count else 0 end from pg_stat_archiver`]);if(p.code!==0)return null;let[b="",w=""]=p.stdout.trim().split(`
300
- `)[0]?.split("|")??[],_=Number(w);return{lastArchivedAt:b.trim()||null,failures:Number.isInteger(_)&&_>=0?_:0}}}}async function Ia(e,t,n,r,o=Ca){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,l;try{let f=JSON.parse(s.stdout);a=f[0]?.State,l=f[0]?.Config?.Labels}catch{return{name:n,ok:!1,detail:"podman inspect could not be read"}}let i=a?.Status??"unknown";if(i!=="running")return{name:n,ok:!1,detail:i};let d=Du(l?.[cr]);if(!d)return{name:n,ok:!0,detail:"running"};let u=await o(d.port,d.path);return u===null?{name:n,ok:!1,detail:"not answering yet"}:{name:n,ok:!0,detail:`answered ${u}`}}function Ju(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function Wu(e,t){return[`\\set from_db ${e}`,`\\set to_db ${t}`,`\\set from_role ${e}_owner`,`\\set to_role ${t}_owner`,'alter database :"from_db" rename to :"to_db";','alter role :"from_role" rename to :"to_role";',"alter system reset restore_command;","alter system reset recovery_target_time;","alter system reset recovery_target_action;",""].join(`
301
- `)}function zu(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var Fe,tr,st,nr,Ta,Aa,Eu,vu,Pu,at,Na,Ou,ja,cr,Ca,Mu,Cu,Ma,Ua=M(()=>{"use strict";Se();Wn();Zn();Wt();Sa();Vn();Ea();$a();Fe="snoutpod.ref",tr="snoutpod.generation",st="snoutpod.service",nr="snoutpod.services-generation",Ta="snoutpod.service-spec",Aa="snoutpod.service-pod";Eu=10,vu=/^[a-z][a-z0-9-]{0,30}$/;Pu=/^[a-z][a-z0-9_]{0,62}$/;at="snoutpod.host-service",Na="snoutpod.host-service-spec";Ou=/^[A-Za-z_][A-Za-z0-9_]*$/;ja=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,cr="snoutpod.health";Ca=async(e,t)=>{let{request:n}=await import("node:http");return new Promise(r=>{let o=n({host:"127.0.0.1",port:e,path:t,method:"GET",timeout:2e3},s=>{s.resume(),r(s.statusCode??null)});o.on("error",()=>r(null)),o.on("timeout",()=>{o.destroy(),r(null)}),o.end()})},Mu="crun";Cu=3e4;Ma=200});var Xa={};Sr(Xa,{DEFAULT_LOCAL_MEMORY_MB:()=>qa,DEFAULT_LOCAL_PORT:()=>Qu,ENGINE:()=>B,ENGINE_NAME:()=>dr,IMAGE:()=>fr,NETWORK:()=>_e,NEW_PROJECT_MAJOR:()=>mr,NO_BLACKHOLE_ROUTES:()=>Ja,PUBLIC_IMAGE:()=>rd,PUBLISHED_MAJORS:()=>Be,capacity:()=>Ka,chooseLocalImage:()=>lt,ensureImage:()=>od,ensureNetwork:()=>ld,exec:()=>W,hasData:()=>Qt,localImageFor:()=>Ga,localPsql:()=>md,localRuntime:()=>cd,localSpec:()=>Va,localStatus:()=>pr,localUri:()=>za,networkPolicyInPlace:()=>Wa,newLocalPassword:()=>td,newLocalRef:()=>ed,podLog:()=>Ya,podName:()=>ut,podmanReady:()=>ad,removeLocal:()=>gd,startLocal:()=>dd,stopLocal:()=>pd,volumePgVersion:()=>Yt});import{randomBytes as Ba}from"node:crypto";import{existsSync as Fa}from"node:fs";import{platform as Zt}from"node:os";import{delimiter as Vu,join as Yu}from"node:path";function Xu(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=Zt()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(Vu))if(t&&e.some(n=>Fa(Yu(t,n))))return"podman";return Fa("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function lt(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return ct(mr);let n=Number(lr(e));if(!Be.includes(n)){let r=e.trim()||"(empty)",o=Be.map(s=>ct(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${Be.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return ct(n)}function ct(e){return{name:`${Zu}:${e}`,published:`${Ha}:${e}`,major:e}}function ed(){return Us(e=>Ba(e))}function td(){return Ba(18).toString("base64url")}async function od(e,t=W,n=lt(null)){let r=n.published;if(!r||(await t(B,["image","exists",n.name])).code===0)return null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(B,["pull",r],{timeoutMs:sd}).catch(a=>({code:1,stdout:"",stderr:a instanceof Error?a.message:String(a)}));if(o.code!==0)return`could not fetch the database image ${r}: ${o.stderr.trim()||o.stdout.trim()||"the pull failed"}`;let s=await t(B,["tag",r,n.name]);return s.code!==0?`fetched ${r} but could not tag it as ${n.name}: ${s.stderr.trim()}`:(e?.("Got it."),null)}async function ad(e=nd,t=fr){if(dr!=="podman")return`SNOUTPOD_ENGINE=${dr}: a local project is Podman-only, because a database that works under Docker and not under Podman is a database that works here and not in the cloud`;let n;try{n=await W(B,["--version"])}catch{return`${B} is not installed. macOS: brew install podman, then podman machine init && podman machine set --rootful=false && podman machine start. Windows: winget install RedHat.Podman, then the same three machine commands. Linux: your distribution's podman package.`}if(n.code!==0)return`${B} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await W(B,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await W(B,["machine","inspect","--format","{{.Rootful}}"]);return o.code===0&&o.stdout.trim().startsWith("true")?"the podman machine is ROOTFUL, and the hosts are not: podman machine stop && podman machine set --rootful=false && podman machine start":null}function id(e){let t=`if out=$(ls -A ${JSON.stringify(e)} 2>/dev/null); then if [ -n "$out" ]; then echo DATA; else echo EMPTY; fi; else echo HIDDEN; fi`;return W(B,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
302
- `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function Qt(e){let t=Zt()==="darwin"||Zt()==="win32";return ma(W,e,{binary:B,look:t?id:void 0})}async function Yt(e,t=W,n=Zt()){let r=j(e).pod;if((await t(B,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of Be){let l=ct(a).name;if((await t(B,["image","exists",l])).code===0){s=l;break}}if(!s)throw new Error(`${r} holds a database and there is no pod image on this machine to read its Postgres version with. Set SNOUTPOD_IMAGE to the image it was made with (${Be.map(a=>ct(a).published).join(" or ")}).`);o=await t(B,["run","--rm","--pull=never","--network=none","--userns=host","--user=0","--entrypoint=cat","--volume",`${r}:/data:ro`,s,"/data/PG_VERSION"])}else{let s=await t(B,["volume","inspect","--format","{{.Mountpoint}}",r]),a=s.code===0?s.stdout.trim().replace(/\/+$/,""):"";if(!a)throw new Error(`could not find where ${r} is: ${s.stderr.trim()||"no mountpoint"}`);o=await t(B,["unshare","cat",`${a}/PG_VERSION`])}if(o.code===0)return o.stdout;if(/no such file/i.test(o.stderr))return null;throw new Error(`could not read the Postgres version in ${r}: ${o.stderr.trim()||o.stdout.trim()||`exit ${o.code}`}`)}function Ga(e,t=Yt){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(lt(null));X(e);let n=t===Yt?Xt.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return lt(o)}catch(s){throw new Error(`${ut(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===Yt&&(Xt.set(e,r),r.catch(()=>Xt.delete(e))),r}async function Ka(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function cd(e){return La({exec:W,binary:B,network:_e,hasData:Qt,capacity:Ka,repository:e})}function ut(e){return j(e).pod}async function ld(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await ud()||await W(B,Kt({name:_e}));return}let t=Kt({name:_e,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await Wa())return;await e?.(),await W(B,["network","rm","--force",_e]).catch(()=>{});let o=await W(B,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${Ja}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${_e} with egress blocked${n?`: ${n}`:""}`)}async function ud(){return(await W(B,["network","inspect",_e,"--format","json"])).code===0}async function Wa(){let e=await W(B,["network","inspect",_e,"--format","json"]);if(e.code!==0)return!1;try{return ra(JSON.parse(e.stdout)?.[0])}catch{return!1}}function za(e){let t=j(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function Va(e,t={}){return X(e.ref),{ref:e.ref,generation:1,image:t.image??fr,memoryMb:t.memoryMb??qa,cpus:null,bucket:"",region:"us-east-1",ownerPassword:e.ownerPassword,adminPassword:e.adminPassword,port:e.port,tuning:t.tuning??null,backup:null,services:t.services??null}}function ur(e,t){e.onProgress?.(t)}async function dd(e,t,n={}){let r=n.image??(await Ga(t.ref,n.pgVersion)).name,o=Va(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??Qt,l=(await e.list()).find(i=>i.ref===t.ref);if(l?l.status==="stopped"?(ur(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):ur(n,"The database is already running."):(ur(n,await a(t.ref)?"Starting the database over its existing data.":"Creating the database."),await e.create(o)),!await e.waitReady(t.ref,s))throw new Error(await fd(t.ref,s,n.podLog??Ya));return await e.applyOwnerPassword(o),await e.createServices(o),pr(e,t,n)}async function md(e,t){let n=j(e.ref);return W(B,["exec","--interactive","-e","PGPASSWORD",ut(e.ref),"psql","--host","127.0.0.1","--port","5432","--username",n.ownerRole,"--dbname",n.database,"--no-psqlrc","-v","ON_ERROR_STOP=1","-tA","--file","-"],{stdin:t,env:{PGPASSWORD:e.ownerPassword}})}async function Ya(e){let t=await W(B,["logs","--tail","20",ut(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function fd(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${ut(e)} never became ready in ${t}ms
553
+ `});function ui(e){if(typeof e!="string"||!/^https?:\/\//.test(e)||e.length>255)throw new Error(`not an OAuth issuer a pod can be given: ${JSON.stringify(e)}`);if(/[\s"\\\u0000-\u001f\u007f]/.test(e))throw new Error(`an OAuth issuer may not hold spaces, quotes, backslashes or control characters: ${JSON.stringify(e)}`);return e}var di,My,Cy,mi=D(()=>{"use strict";pt();xr();di="SNOUTPOD_OAUTH_ISSUER",My=256*1024,Cy=5*6e4});import{createHash as Er,randomBytes as Pu}from"node:crypto";import{access as Ru,constants as Tu}from"node:fs/promises";import{delimiter as Ou,join as Nu}from"node:path";function Du(e,t){return Er("sha256").update($r(e)).update(e.smtpSecretEnv&&t?Er("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function $r(e){if(Array.isArray(e))return`[${e.map($r).join(",")}]`;if(e!==null&&typeof e=="object"){let t=e;return`{${Object.keys(t).sort().filter(r=>t[r]!==void 0).map(r=>`${JSON.stringify(r)}:${$r(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function Rr(e){if(!Mu.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function ht(e){if(!Cu.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function ju(e,t,n){return`postgres://${ht(t)}:${n}@127.0.0.1:5432/${Z(e)}`}function gi(e,t,n,r){let o={...t.env};if(t.dbUriEnv&&t.dbUriRole){let s=dn(t.dbUriRole),a=n.get(s);if(a===void 0)throw new Error(`no password was applied for role ${JSON.stringify(t.dbUriRole)}, which ${JSON.stringify(t.name)} needs to reach its database`);o[t.dbUriEnv]=ju(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),Lu(t.image,o)}function Lu(e,t){if(!/(^|\/)snout-auth([:@]|$)/.test(e))return{...t};let n={};for(let[r,o]of Object.entries(t)){let s=r.startsWith("GOTRUE_")?`AUTH_${r.slice(7)}`:r==="API_EXTERNAL_URL"?"AUTH_API_EXTERNAL_URL":r;s!==r&&s in t||(n[s]=o)}return n}function hi(e,t){return`${j(e).pod}-${Rr(t)}`}function un(e){return`snoutpod-host-${Rr(e)}`}function Uu(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return Er("sha256").update(JSON.stringify([e.image,e.port,e.hostPort,e.healthPath??null,e.memoryMb,t,e.adminPort??null,e.adminHostPort??null,e.command??null,e.volumes??null,e.network??null])).digest("hex").slice(0,32)}function Fu(e,t){let n=[],r=Ar(t);for(let o of e??[])if(Rr(o.name),Pr(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){Pr(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`)}return n}function Ar(e){let t=e===void 0?[]:typeof e=="string"?[e]:[...e];return["127.0.0.1",...new Set(t.filter(n=>n!=="127.0.0.1"))]}function Pr(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function wi(e){let t=[];for(let n of Object.keys(e)){if(!Bu.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function Hu(e){let t=e.healthPath;if(t==null||t==="")return[];if(!_i.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(Pr(e.hostPort,`${e.name}: hostPort`),["--label",`${Tr}=${e.hostPort}:${t}`])}function Gu(e){if(!e)return null;let t=e.indexOf(":");if(t<=0)return null;let n=Number(e.slice(0,t)),r=e.slice(t+1);return!Number.isInteger(n)||n<1||n>65535||!_i.test(r)?null:{port:n,path:r}}async function Ku(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>Ru(o,Tu.X_OK));for(let o of n.split(Ou))if(o)try{let s=Nu(o,e);return await r(s),s}catch{}return null}function Wu(){return["\\set ON_ERROR_STOP 0","select 'create extension if not exists pg_prewarm' where exists (select 1 from pg_available_extensions where name = 'pg_prewarm') \\gexec","with candidate as ("," select c.oid, pg_relation_size(c.oid) as bytes"," from pg_class c join pg_namespace n on n.oid = c.relnamespace"," where c.relkind in ('r', 'i', 'm')"," and c.relpersistence = 'p'"," and n.nspname not in ('pg_catalog', 'information_schema', 'pg_toast')","), budgeted as ("," select oid, bytes,"," sum(bytes) over (order by bytes, oid rows between unbounded preceding and current row) as running"," from candidate",")","select coalesce(sum(pg_prewarm(oid)), 0) from budgeted","where bytes > 0 and running <= pg_size_bytes(current_setting('shared_buffers')) * 3 / 4;",""].join(`
554
+ `)}function zu(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function Vu(e,t){for(let r of e??[]){let o=/^PG_MAJOR=(\d+)$/.exec(r.trim());if(o)return o[1]??null}let n=t.split("@")[0]??"";return/:(\d+)(?:[.-][^/:]*)?$/.exec(n)?.[1]??null}function Or(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function Yu(e,t){return t.image===null||t.data===null||t.image===t.data?null:`${e} was not started: its data directory was made by Postgres ${t.data} and it was handed a Postgres ${t.image} image. A data directory starts only on the major that made it. Its data is untouched, and it starts again on a ${t.data} image.`}async function Xu(e,t,n,r){return{image:await Zu(e,t,r),data:await Qu(e,t,n)}}async function Zu(e,t,n){let r=null;try{let o=await e(t,["image","inspect","--format","{{json .Config.Env}}",n]);if(o.code===0&&o.stdout.trim()){let s=JSON.parse(o.stdout.trim());r=Array.isArray(s)?s.filter(a=>typeof a=="string"):null}}catch{r=null}return Vu(r,n)}async function Qu(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",j(n).pod]),o=r.code===0?r.stdout.trim().replace(/\/+$/,""):"";if(!o)return null;let s=await e(t,["unshare","cat",`${o}/PG_VERSION`]);return s.code===0?Or(s.stdout):null}catch{return null}}function em(e){if(!e)return[];let t=[],n=/^[1-9][0-9]{0,6}(kB|MB|GB)$/;typeof e.sharedBuffers=="string"&&n.test(e.sharedBuffers)&&t.push(`SNOUTPOD_SHARED_BUFFERS=${e.sharedBuffers}`),typeof e.workMem=="string"&&n.test(e.workMem)&&t.push(`SNOUTPOD_WORK_MEM=${e.workMem}`);let r=typeof e.maxConnections=="number"&&Number.isInteger(e.maxConnections)&&e.maxConnections>=1&&e.maxConnections<=1e4?e.maxConnections:bi;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,bi)}`),typeof e.privilegedExtensions=="string"&&/^[a-z][a-z0-9_-]*(,[a-z][a-z0-9_-]*)*$/.test(e.privilegedExtensions)&&t.push(`SNOUTPOD_PRIVILEGED_EXTENSIONS=${e.privilegedExtensions}`),t}function tm(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??qu;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await Ku(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function ki(e){let t=e.binary??"podman",n=e.exec,r=tm(e),o=e.probe??xi;async function s(m){try{await n(t,["exec","--interactive",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"--quiet","--file","-"],{timeoutMs:Ju,stdin:Wu()})}catch{}}async function a(){let m=await L(n,t,["ps","--all","--filter",`label=${Ne}`,"--format","json"]),p=m.trim()?JSON.parse(m):[],S=new Map,w=new Map;for(let $ of p){let T=$.Labels?.[Ne],k=$.Labels?.[Ke];if(!T||!k)continue;let C=S.get(T)??[];C.push(k),S.set(T,C);let M=Number($.Labels?.[vr]??0),Y=Number.isInteger(M)?M:0;w.set(T,Math.max(w.get(T)??Y,Y))}let x=[];for(let $ of p){let T=$.Labels?.[Ne];if(!T||$.Labels?.[Ke])continue;let k=Number($.Labels?.[kr]);x.push({ref:T,status:nm($.State),generation:Number.isInteger(k)?k:null,hasData:await e.hasData(T),services:S.get(T)??[],...w.has(T)?{servicesGeneration:w.get(T)}:{}})}return x}async function l(m){let p=await(e.majors??((S,w)=>Xu(n,t,S,w)))(m.ref,m.image);return Yu(m.ref,p)}async function i(m){let p=j(m.ref),S=await l(m);if(S)throw new Error(S);let w=e.credentials,x=await Qa(e.repository,w?()=>w.forProject(m.ref,m.bucket):null),$={SNOUTPOD_OWNER_PASSWORD:m.ownerPassword,POSTGRES_PASSWORD:m.adminPassword,...ti(x)},T=[...await r(),"run","--detach","--name",p.pod,"--label",`${Ne}=${m.ref}`,"--label",`${kr}=${m.generation}`,"--userns=auto","--memory",`${m.memoryMb}m`,...sm(m.cpus),"--restart","no",...e.network?["--network",e.network]:[],...m.port?Ar(e.podBind).flatMap(k=>["--publish",`${k}:${m.port}:5432`]):[],...Fu(m.services,e.podBind),"--volume",`${p.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${m.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${m.bucket}`,"--env",`SNOUTPOD_S3_REGION=${m.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...om(m.oauth),...em(m.tuning).flatMap(k=>["--env",k]),"--env",`SNOUTPOD_RETENTION_FULL=${wa(m.backup?.retentionDays,m.backup?.fullIntervalHours)}`,...ei(x),m.image,"snoutpod-postgres"];await L(n,t,T,{env:$})}async function u(m){let p=await L(n,t,["ps","--all","--filter",`label=${Ne}=${Z(m)}`,"--filter",`label=${Ke}`,"--format","json"]),S=p.trim()?JSON.parse(p):[],w=[];for(let x of S){let $=x.Labels?.[Ke];if(!$)continue;let T=Number(x.Labels?.[vr]??0);w.push({name:$,container:hi(m,$),spec:x.Labels?.[fi]??"",pod:x.Labels?.[pi]??"",running:x.State==="running",generation:Number.isInteger(T)?T:0})}return w}async function d(m){let p=await n(t,["container","inspect","--format","{{.Id}}",j(m).pod]);return p.code===0?p.stdout.trim():""}async function f(m,p){let S=[];for(let k of p){let C=k.dbUriRole?dn(k.dbUriRole):null;k.dbUriEnv&&C&&!S.includes(C)&&S.push(ht(C))}let w=new Map;if(S.length===0)return w;let x={},$=[],T=S.includes(ye)?[si]:[];return S.includes(ri)&&T.push(oi),S.forEach((k,C)=>{let M=Pu(24).toString("base64url"),Y=`SNOUTPOD_SERVICE_PASSWORD_${C}`;w.set(k,M),x[Y]=M,$.push("--env",Y),T.push(`\\getenv service_password_${C} ${Y}`),T.push(`\\set service_role_${C} ${k}`),T.push(`alter role :"service_role_${C}" with password :'service_password_${C}';`)}),T.push(""),await L(n,t,["exec","--interactive",...$,j(m.ref).pod,"psql","--username","snoutpod_admin","--dbname",j(m.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:x,stdin:T.join(`
555
+ `)}),w}async function y(m){let p=m.services??[],S=await u(m.ref),w;p.some(P=>P.smtpSecretEnv)&&(w=await e.smtpSecret?.().catch(()=>{}));let x=p.length>0?await d(m.ref):"",$=new Map(S.map(P=>[P.name,P])),T=new Map(p.map(P=>[P.name,Du(P,w)])),k=new Set(p.filter(P=>{let se=$.get(P.name);return!se||!se.running||se.spec!==T.get(P.name)||x===""||se.pod!==x}).map(P=>P.name)),C=m.servicesGeneration??0;if(k.size===0&&p.length>0&&!S.some(P=>P.generation>=C))for(let P of p)k.add(P.name);let M=P=>P.dbUriEnv&&P.dbUriRole?dn(P.dbUriRole):null;for(let P=!0;P;){P=!1;let se=new Set(p.filter(oe=>k.has(oe.name)).map(M));for(let oe of p){let Br=M(oe);!k.has(oe.name)&&Br!==null&&se.has(Br)&&(k.add(oe.name),P=!0)}}for(let P of S)(k.has(P.name)||!T.has(P.name))&&await L(n,t,["rm","--force","--ignore",P.container]);let Y=p.filter(P=>k.has(P.name)),ze=await f(m,Y);for(let P of Y){let se=hi(m.ref,P.name),oe=[...await r(),"run","--detach","--name",se,"--label",`${Ne}=${m.ref}`,"--label",`${kr}=${m.generation}`,"--label",`${Ke}=${P.name}`,"--label",`${vr}=${m.servicesGeneration??0}`,"--label",`${fi}=${T.get(P.name)??""}`,"--label",`${pi}=${x}`,"--network",`container:${j(m.ref).pod}`,"--userns",`container:${j(m.ref).pod}`,"--memory",`${P.memoryMb}m`,"--restart","no",...Hu(P),...wi(gi(m,P,ze,w)),P.image];await L(n,t,oe,{env:gi(m,P,ze,w)})}}return{list:a,preflight:l,create:i,createServices:y,async start(m){await L(n,t,["start",j(m).pod])},async startServices(m){for(let p of await u(m))await L(n,t,["start",p.container])},async applyHostServices(m){let p=[],S=await L(n,t,["ps","--all","--filter",`label=${gt}`,"--format","json"]),w=S.trim()?JSON.parse(S):[],x=new Map;for(let k of w){let C=k.Labels?.[gt];C&&x.set(C,{spec:k.Labels?.[yi]??"",state:k.State??""})}for(let k of m)try{await T(k)}catch(C){p.push({name:k.name,action:"failed",reason:C instanceof Error?C.message:String(C)})}async function $(k){let C=await r();if((await n(t,[...C,"image","exists",k])).code===0)return;let M=await n(t,[...C,"pull","--quiet",k]);if(M.code===0)return;let Y=zu(k);if(!Y||!e.registryLogin)throw new Error(`${k} could not be pulled: ${(M.stderr||M.stdout).trim()}`);await e.registryLogin(Y),await L(n,t,[...C,"pull","--quiet",k])}async function T(k){let C=e.hostServiceLocal?.(k,m),M=C?{...k,...C.env&&Object.keys(C.env).length>0?{env:{...k.env,...C.env}}:{},...C.volumes?{volumes:C.volumes}:{},...C.network?{network:C.network}:{}}:k,Y=un(M.name),ze=Uu(M),P=x.get(M.name);if(x.delete(M.name),P&&P.spec===ze){P.state!=="running"&&(await L(n,t,["start",Y]),p.push({name:M.name,action:"started",reason:P.state}));return}let se=P?"the spec changed":"it was not here";await $(M.image),P&&await L(n,t,["rm","--force","--ignore",Y]),await L(n,t,[...await r(),"run","--detach","--name",Y,"--label",`${gt}=${M.name}`,"--label",`${yi}=${ze}`,...M.healthPath?["--label",`${Tr}=${M.hostPort}:${M.healthPath}`]:[],"--memory",`${M.memoryMb}m`,"--restart","no",...M.network?["--network",M.network]:e.network?["--network",e.network]:[],...(M.volumes??[]).flatMap(oe=>["--volume",oe]),...Ar(e.podBind).flatMap(oe=>["--publish",`${oe}:${M.hostPort}:${M.port}`]),...M.adminPort&&M.adminHostPort?["--publish",`127.0.0.1:${M.adminHostPort}:${M.adminPort}`]:[],...wi(M.env),M.image,...M.command??[]],{env:{...M.env}}),p.push({name:M.name,action:P?"recreated":"created",reason:se})}for(let k of x.keys())await L(n,t,["rm","--force","--ignore",un(k)]),p.push({name:k,action:"removed",reason:"it is no longer on the list"});return p},async hostServiceHealth(m){let p=await L(n,t,["ps","--all","--filter",`label=${gt}`,"--format","json"]),S=(p.trim()?JSON.parse(p):[]).map(x=>x.Labels?.[gt]??"").filter(x=>x!=="");if(S.length===0)return[];let w=Date.now()+m;for(;;){let x=[];for(let $ of S)x.push(await Si(n,t,$,un($),o));if(x.every($=>$.ok)||Date.now()>=w)return x;await new Promise($=>setTimeout($,500))}},async serviceHealth(m,p){let S=await u(m);if(S.length===0)return[];let w=Date.now()+p;for(;;){let x=[];for(let $ of S)x.push(await Si(n,t,$.name,$.container,o));if(x.every($=>$.ok)||Date.now()>=w)return x;await new Promise($=>setTimeout($,500))}},async stop(m,p){for(let S of await u(m))await L(n,t,["stop","--time",String(Iu),S.container]);await L(n,t,["stop","--time",String(p),j(m).pod])},async remove(m){for(let p of await u(m))await L(n,t,["rm","--force","--ignore",p.container]);await L(n,t,["rm","--force","--ignore","--volumes",j(m).pod])},async removeData(m){await L(n,t,["volume","rm","--force",j(m).pod])},async applyOwnerPassword(m){let p=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${m.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
556
+ `);await L(n,t,["exec","--interactive","--env","SNOUTPOD_OWNER_PASSWORD",j(m.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:m.ownerPassword},stdin:p})},async applyRolePassword(m,p,S){let w=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${ht(p)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
557
+ `);await L(n,t,["exec","--interactive","--env","SNOUTPOD_ROLE_PASSWORD",j(m).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:S},stdin:w})},async updateExtensions(m){let p=j(m),S=await L(n,t,["exec","--interactive",p.pod,"psql","--username","snoutpod_admin","--dbname",p.database,"--quiet","-tA","-F",_r,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:ii});return ci(S)},async applyDbAccess(m,p){let S=j(m),w=await L(n,t,["exec","--interactive","--env",ln,S.pod,"psql","--username","snoutpod_admin","--dbname",S.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[ln]:qa(m,p)},stdin:Ha,timeoutMs:6e4});return Ga(w)},async runProjectSql(m,p){let S=j(m);await L(n,t,["exec","--interactive",S.pod,"psql","--username","snoutpod_admin","--dbname",S.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:p})},async ensureHostServiceDatabase(m,p,S){let w=un(m),x=ht(p),$=ht(S),T=C=>["exec",w,"psql","--username",x,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",C];(await L(n,t,T(`select 1 from pg_database where datname = '${$}'`))).trim()!=="1"&&await L(n,t,T(`create database "${$}"`))},async replicationSlots(m){let p=await L(n,t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tA","-F","|","-c",Ma]);return Ca(p)},async dropReplicationSlot(m,p){if(!/^[a-z0-9_]{1,63}$/.test(p))throw new Error(`not a replication slot name: ${JSON.stringify(p)}`);await L(n,t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-v","ON_ERROR_STOP=1","-c",`select pg_drop_replication_slot('${p}')`])},async renameProject(m,p){p!==m.ref&&await L(n,t,["exec","--interactive",j(m.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:rm(p,m.ref)})},async waitReady(m,p){let S=j(m).pod,w=Date.now()+p;for(;;){let x=await n(t,["exec",S,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(x.code===0&&x.stdout.trim()==="t")return s(m),!0;if(Date.now()>=w)return!1;await new Promise($=>setTimeout($,500))}},async capacity(){let m=await a(),p=await e.capacity();return{pods:m.length,...p}},async databaseBytes(m){let p=await n(t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tAc","select pg_database_size(current_database())"]);return p.code===0?pr(p.stdout):null},async storageBytes(m){let p=await n(t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tAc","select coalesce(sum((metadata->>'size')::bigint), 0) from storage.objects"]);return p.code===0?pr(p.stdout):null},async setReadOnly(m,p){let S=j(m);await L(n,t,["exec",S.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${S.database} set default_transaction_read_only = ${p?"on":"off"}`])},async archiveState(m){let p=await n(t,["exec",j(m).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",m,"-tA","-F","|","-c",`select coalesce(to_char(last_archived_time, 'YYYY-MM-DD"T"HH24:MI:SS.MSOF'), ''), case when last_failed_time > coalesce(last_archived_time, '-infinity') then failed_count else 0 end from pg_stat_archiver`]);if(p.code!==0)return null;let[S="",w=""]=p.stdout.trim().split(`
558
+ `)[0]?.split("|")??[],x=Number(w);return{lastArchivedAt:S.trim()||null,failures:Number.isInteger(x)&&x>=0?x:0}}}}async function Si(e,t,n,r,o=xi){let s=await e(t,["inspect","--format","json",r]);if(s.code!==0)return{name:n,ok:!1,detail:"no such container"};let a,l;try{let f=JSON.parse(s.stdout);a=f[0]?.State,l=f[0]?.Config?.Labels}catch{return{name:n,ok:!1,detail:"podman inspect could not be read"}}let i=a?.Status??"unknown";if(i!=="running")return{name:n,ok:!1,detail:i};let u=Gu(l?.[Tr]);if(!u)return{name:n,ok:!0,detail:"running"};let d=await o(u.port,u.path);return d===null?{name:n,ok:!1,detail:"not answering yet"}:{name:n,ok:!0,detail:`answered ${d}`}}function nm(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function rm(e,t){return[`\\set from_db ${e}`,`\\set to_db ${t}`,`\\set from_role ${e}_owner`,`\\set to_role ${t}_owner`,'alter database :"from_db" rename to :"to_db";','alter role :"from_role" rename to :"to_role";',"alter system reset restore_command;","alter system reset recovery_target_time;","alter system reset recovery_target_action;",""].join(`
559
+ `)}function om(e){if(!e?.issuer)return[];try{return["--env",`${di}=${ui(e.issuer)}`]}catch{return[]}}function sm(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var Ne,kr,Ke,vr,fi,pi,Iu,Mu,Cu,gt,yi,Bu,_i,Tr,xi,qu,Ju,bi,xr=D(()=>{"use strict";he();fr();wr();pt();ni();gr();ai();li();br();mi();Ne="snoutpod.ref",kr="snoutpod.generation",Ke="snoutpod.service",vr="snoutpod.services-generation",fi="snoutpod.service-spec",pi="snoutpod.service-pod";Iu=10,Mu=/^[a-z][a-z0-9-]{0,30}$/;Cu=/^[a-z][a-z0-9_]{0,62}$/;gt="snoutpod.host-service",yi="snoutpod.host-service-spec";Bu=/^[A-Za-z_][A-Za-z0-9_]*$/;_i=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,Tr="snoutpod.health";xi=async(e,t)=>{let{request:n}=await import("node:http");return new Promise(r=>{let o=n({host:"127.0.0.1",port:e,path:t,method:"GET",timeout:2e3},s=>{s.resume(),r(s.statusCode??null)});o.on("error",()=>r(null)),o.on("timeout",()=>{o.destroy(),r(null)}),o.end()})},qu="crun";Ju=3e4;bi=200});var Li={};Hr(Li,{DEFAULT_LOCAL_MEMORY_MB:()=>Pi,DEFAULT_LOCAL_PORT:()=>dm,ENGINE:()=>B,ENGINE_NAME:()=>Nr,IMAGE:()=>Mr,NETWORK:()=>$e,NEW_PROJECT_MAJOR:()=>Ir,NO_BLACKHOLE_ROUTES:()=>Ni,PUBLIC_IMAGE:()=>pm,PUBLISHED_MAJORS:()=>We,capacity:()=>Oi,chooseLocalImage:()=>bt,dropIfUnused:()=>Ci,ensureImage:()=>gm,ensureNetwork:()=>Sm,exec:()=>z,hasData:()=>gn,localImageFor:()=>Ti,localPsql:()=>km,localRuntime:()=>bm,localSpec:()=>Mi,localStatus:()=>Cr,localUri:()=>Ii,networkPolicyInPlace:()=>Di,newLocalPassword:()=>mm,newLocalRef:()=>um,podLog:()=>ji,podName:()=>Je,podmanReady:()=>ym,removeLocal:()=>$m,staleImage:()=>Dr,startLocal:()=>xm,stopLocal:()=>Em,volumePgVersion:()=>mn});import{randomBytes as $i}from"node:crypto";import{existsSync as vi}from"node:fs";import{platform as pn}from"node:os";import{delimiter as am,join as im}from"node:path";function cm(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=pn()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(am))if(t&&e.some(n=>vi(im(t,n))))return"podman";return vi("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function bt(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return wt(Ir);let n=Number(Or(e));if(!We.includes(n)){let r=e.trim()||"(empty)",o=We.map(s=>wt(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${We.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return wt(n)}function wt(e){return{name:`${lm}:${e}`,published:`${Ai}:${e}`,major:e}}function um(){return ga(e=>$i(e))}function mm(){return $i(18).toString("base64url")}async function gm(e,t=z,n=bt(null)){let r=n.published;if(!r)return null;if((await t(B,["image","exists",n.name])).code===0)return await hm(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(B,["pull",r],{timeoutMs:Ri}).catch(a=>({code:1,stdout:"",stderr:a instanceof Error?a.message:String(a)}));if(o.code!==0)return`could not fetch the database image ${r}: ${o.stderr.trim()||o.stdout.trim()||"the pull failed"}`;let s=await t(B,["tag",r,n.name]);return s.code!==0?`fetched ${r} but could not tag it as ${n.name}: ${s.stderr.trim()}`:(e?.("Got it."),null)}async function hm(e,t,n,r){let o=await t(B,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
560
+ `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(B,["pull",r],{timeoutMs:Ri}).catch(y=>({code:1,stdout:"",stderr:y instanceof Error?y.message:String(y)})).finally(()=>clearTimeout(l));if(i.code!==0){let y=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
561
+ `).at(-1);e?.(`Could not check for a newer database image (${y}); using the one on this machine.`);return}let u=await t(B,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),d=u?.code===0?u.stdout.trim():"";if(!d||d===s)return;let f=await t(B,["tag",r,n]);if(f.code!==0){e?.(`Fetched a newer ${r} but could not tag it as ${n}: ${f.stderr.trim()}`);return}e?.(`Fetched a newer database image (${r}).`)}async function ym(e=fm,t=Mr){if(Nr!=="podman")return`SNOUTPOD_ENGINE=${Nr}: a local project is Podman-only, because a database that works under Docker and not under Podman is a database that works here and not in the cloud`;let n;try{n=await z(B,["--version"])}catch{return`${B} is not installed. macOS: brew install podman, then podman machine init && podman machine set --rootful=false && podman machine start. Windows: winget install RedHat.Podman, then the same three machine commands. Linux: your distribution's podman package.`}if(n.code!==0)return`${B} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await z(B,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await z(B,["machine","inspect","--format","{{.Rootful}}"]);return o.code===0&&o.stdout.trim().startsWith("true")?"the podman machine is ROOTFUL, and the hosts are not: podman machine stop && podman machine set --rootful=false && podman machine start":null}function wm(e){let t=`if out=$(ls -A ${JSON.stringify(e)} 2>/dev/null); then if [ -n "$out" ]; then echo DATA; else echo EMPTY; fi; else echo HIDDEN; fi`;return z(B,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
562
+ `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function gn(e){let t=pn()==="darwin"||pn()==="win32";return za(z,e,{binary:B,look:t?wm:void 0})}async function mn(e,t=z,n=pn()){let r=j(e).pod;if((await t(B,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of We){let l=wt(a).name;if((await t(B,["image","exists",l])).code===0){s=l;break}}if(!s)throw new Error(`${r} holds a database and there is no pod image on this machine to read its Postgres version with. Set SNOUTPOD_IMAGE to the image it was made with (${We.map(a=>wt(a).published).join(" or ")}).`);o=await t(B,["run","--rm","--pull=never","--network=none","--userns=host","--user=0","--entrypoint=cat","--volume",`${r}:/data:ro`,s,"/data/PG_VERSION"])}else{let s=await t(B,["volume","inspect","--format","{{.Mountpoint}}",r]),a=s.code===0?s.stdout.trim().replace(/\/+$/,""):"";if(!a)throw new Error(`could not find where ${r} is: ${s.stderr.trim()||"no mountpoint"}`);o=await t(B,["unshare","cat",`${a}/PG_VERSION`])}if(o.code===0)return o.stdout;if(/no such file/i.test(o.stderr))return null;throw new Error(`could not read the Postgres version in ${r}: ${o.stderr.trim()||o.stdout.trim()||`exit ${o.code}`}`)}function Ti(e,t=mn){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(bt(null));Z(e);let n=t===mn?fn.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return bt(o)}catch(s){throw new Error(`${Je(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===mn&&(fn.set(e,r),r.catch(()=>fn.delete(e))),r}async function Oi(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function bm(e){return ki({exec:z,binary:B,network:$e,hasData:gn,capacity:Oi,repository:e})}function Je(e){return j(e).pod}async function Sm(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await _m()||await z(B,sn({name:$e}));return}let t=sn({name:$e,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await Di())return;await e?.(),await z(B,["network","rm","--force",$e]).catch(()=>{});let o=await z(B,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${Ni}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${$e} with egress blocked${n?`: ${n}`:""}`)}async function _m(){return(await z(B,["network","inspect",$e,"--format","json"])).code===0}async function Di(){let e=await z(B,["network","inspect",$e,"--format","json"]);if(e.code!==0)return!1;try{return Da(JSON.parse(e.stdout)?.[0])}catch{return!1}}function Ii(e){let t=j(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function Mi(e,t={}){return Z(e.ref),{ref:e.ref,generation:1,image:t.image??Mr,memoryMb:t.memoryMb??Pi,cpus:null,bucket:"",region:"us-east-1",ownerPassword:e.ownerPassword,adminPassword:e.adminPassword,port:e.port,tuning:t.tuning??null,backup:null,services:t.services??null}}function yt(e,t){e.onProgress?.(t)}async function xm(e,t,n={}){let r=n.image??(await Ti(t.ref,n.pgVersion)).name,o=Mi(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??gn,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??Dr)(t.ref,r))?(yt(n,"Starting the database on the newer image. Its data stays as it is."),await e.remove(t.ref),await e.create(o),await(n.dropImage??Ci)(i)):l.status==="stopped"?(yt(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(yt(n,"The database is already running."),l.status==="running"&&await(n.staleImage??Dr)(t.ref,r)&&yt(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(yt(n,await a(t.ref)?"Starting the database over its existing data.":"Creating the database."),await e.create(o)),!await e.waitReady(t.ref,s))throw new Error(await vm(t.ref,s,n.podLog??ji));return await e.applyOwnerPassword(o),await e.createServices(o),Cr(e,t,n)}function Ei(e){return e.trim().replace(/^sha256:/,"")}async function Dr(e,t){let[n,r]=await Promise.all([z(B,["container","inspect","--format","{{.Image}}",Je(e)]).catch(()=>null),z(B,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=Ei(n.stdout),s=Ei(r.stdout);return o&&s&&o!==s?o:null}async function Ci(e){let t=await z(B,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await z(B,["rmi",e]).catch(()=>{})}async function km(e,t){let n=j(e.ref);return z(B,["exec","--interactive","-e","PGPASSWORD",Je(e.ref),"psql","--host","127.0.0.1","--port","5432","--username",n.ownerRole,"--dbname",n.database,"--no-psqlrc","-v","ON_ERROR_STOP=1","-tA","--file","-"],{stdin:t,env:{PGPASSWORD:e.ownerPassword}})}async function ji(e){let t=await z(B,["logs","--tail","20",Je(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function vm(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${Je(e)} never became ready in ${t}ms
303
563
  --- pod log ---
304
- ${r}`}async function pd(e,t,n={}){X(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,la),pr(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function gd(e,t,n){X(t),await e.remove(t),n&&(await e.removeData(t),Xt.delete(t))}async function pr(e,t,n={}){X(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??Qt;return{ref:t.ref,exists:!!r,status:r?.status??"absent",ready:o?await e.waitReady(t.ref,1):!1,hasData:r?.hasData??await s(t.ref),port:t.port,databaseBytes:o?await e.databaseBytes(t.ref):null,services:r?.services??[],uri:t.port>0&&t.ownerPassword?za(t):null}}var dr,B,mr,Be,Zu,Ha,fr,_e,Qu,qa,W,nd,rd,sd,Xt,Ja,Za=M(()=>{"use strict";ua();Wt();fa();Xn();Ua();Se();dr=process.env.SNOUTPOD_ENGINE??"podman";B=Xu(),mr=18,Be=[17,18],Zu="snoutpod/postgres",Ha="ghcr.io/snoutdata/snoutpod-postgres";fr=lt(null).name,_e=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",Qu=54322,qa=512,W=da();nd="cd packages/snoutpod && npm run image:build",rd=`${Ha}:${mr}`;sd=20*60*1e3;Xt=new Map;Ja="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});re();var tn=[{name:"init",summary:"Create or link a project and write a DATABASE_URL",flags:["--name","--region","--env","--ref"]},{name:"login",summary:"Sign in through a browser, with --sso through your company's identity provider, or with --device by typing a code into one anywhere",flags:["--provider","--sso","--domain","--no-browser","--device","--timeout"]},{name:"logout",summary:"Forget the stored session",flags:[]},{name:"whoami",summary:"Who this credential belongs to",flags:[]},{name:"tokens list",summary:"Long-lived access tokens on this account",flags:[]},{name:"tokens create",summary:"Mint an sdt_ token for CI or an agent",flags:["--name","--expires","--project"]},{name:"tokens revoke",summary:"Revoke one by id or prefix",flags:[]},{name:"projects list",summary:"Every project on this account",flags:[]},{name:"projects create",summary:"Make a hosted database",flags:["--name","--region","--team","--no-wait","--timeout"]},{name:"projects pause",summary:"Stop a project, and wait until it is paused",flags:["--ref","--no-wait","--timeout"]},{name:"projects resume",summary:"Start a paused project, and wait until it is ready",flags:["--ref","--no-wait","--timeout"]},{name:"projects delete",summary:"Delete a project, and wait until it is gone",flags:["--ref","--no-wait","--timeout"]},{name:"projects show",summary:"One project: state, products, functions, secrets, domains",flags:["--ref"]},{name:"products",summary:"Auth, storage, the data API and push: on or off",flags:["--ref"]},{name:"products enable",summary:"Turn auth, storage, data-api or push on",flags:["--ref"]},{name:"products disable",summary:"Turn auth, storage, data-api or push off",flags:["--ref"]},{name:"push credentials",summary:"Push keys: what is set for APNs, FCM and Web Push (never the keys)",flags:["--ref"]},{name:"push credentials set",summary:"Set the APNs key (a .p8) or the FCM service account, checked before it is stored",flags:["--ref","--p8","--key-id","--team-id","--topic","--environment","--file"]},{name:"push credentials remove",summary:"Remove the APNs or FCM credentials",flags:["--ref"]},{name:"auth",summary:"Auth settings: Google sign-in and redirect addresses",flags:["--ref"]},{name:"auth google",summary:"Sign in with Google, with your own client (secret on stdin)",flags:["--ref","--client-id","--stdin"]},{name:"auth redirects",summary:"The site URL and the addresses a sign-in may return to",flags:["--ref","--site-url","--allow"]},{name:"auth templates",summary:"The five auth emails, ours or your own",flags:["--ref"]},{name:"auth template",summary:"Use your own subject and HTML for one, or reset it",flags:["--ref","--file","--subject"]},{name:"domains",summary:"Your own domains in front of the project API",flags:["--ref"]},{name:"domains add",summary:"Add one, and print the DNS records to publish",flags:["--ref"]},{name:"domains verify",summary:"Check its records and verify it",flags:["--ref"]},{name:"domains remove",summary:"Stop serving it",flags:["--ref"]},{name:"functions deploy",summary:"Put a folder of TypeScript on the edge",flags:["--ref","--dir","--entrypoint","--no-verify-jwt"]},{name:"functions list",summary:"What this project has deployed",flags:["--ref"]},{name:"functions size",summary:"Its memory and workers, within the plan",flags:["--ref","--memory","--concurrency","--reset"]},{name:"functions delete",summary:"Remove one",flags:["--ref"]},{name:"secrets set",summary:"Set the environment functions run with",flags:["--ref","--stdin"]},{name:"secrets list",summary:"The names, never the values",flags:["--ref"]},{name:"secrets unset",summary:"Remove one",flags:["--ref"]},{name:"keys",summary:"The anon and service_role API keys for a project",flags:["--ref"]},{name:"keys rotate",summary:"New keys, breaking every one already issued",flags:["--ref","--force"]},{name:"teams",summary:"Teams this account belongs to",flags:[]},{name:"link",summary:"Point this folder at a project",flags:["--ref"]},{name:"usage",summary:"Size and compute against the plan limit",flags:["--ref","--days","--history"]},{name:"start",summary:"A Postgres on this machine, with your migrations applied",flags:["--port","--dir","--no-migrations","--out-of-order"]},{name:"stop",summary:"Stop the local database. The data stays",flags:[]},{name:"status",summary:"What the local database is doing",flags:[]},{name:"gen types typescript",summary:"The schema as a TypeScript Database type, on stdout",flags:["--ref","--db-url","--local","--schema","--out"]},{name:"db url",summary:"A connection string",flags:["--ref"]},{name:"db psql",summary:"Open psql, or run statements after --",flags:["--ref"]},{name:"db reset-password",summary:"Rotate the project password",flags:["--ref"]},{name:"db export",summary:"Take or download a dump",flags:["--ref","--out","--status","--timeout"]},{name:"db push",summary:"Apply migrations from a folder",flags:["--dir","--dry-run","--out-of-order"]},{name:"db restore",summary:"Put a dump into a project, or rewind it to a moment into a new project",flags:["--file","--force","--at","--name","--window","--ref"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}];G();Z();K();import{spawn as mc}from"node:child_process";function fc(e){return`snout-${e}`}function Ie(e,t,n){return new Promise((r,o)=>{let s=mc("docker",["compose","-p",fc(e.ref),"--project-directory",e.folder,...t],{cwd:e.folder,stdio:["ignore","pipe","pipe"]}),a="",l="";s.stdout.on("data",i=>{a+=String(i)}),s.stderr.on("data",i=>{if(l+=String(i),n)for(let d of String(i).split(/\r?\n/).filter(Boolean))c(S(` ${d.trim()}`))}),s.on("error",i=>{if(i.code==="ENOENT"){o(new U("tool-missing","docker is not installed, or not on PATH. A local project runs in Docker (Docker Desktop on Windows and macOS)."));return}o(new U("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function pc(e){let t=await Ie(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new U("failed",t.err.trim()||"docker compose ps failed. Is Docker running?");let n=t.out.trim();return(n.startsWith("[")?JSON.parse(n):n.split(/\r?\n/).filter(Boolean).map(o=>JSON.parse(o))).map(o=>({service:o.Service??"?",state:o.State==="exited"&&o.ExitCode===0?"done":o.State??"?",health:o.Health??""})).sort((o,s)=>o.service.localeCompare(s.service))}async function oo(e){let t=await pc(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return g({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(c(`${$(e.name??e.ref)} (local, ${e.folder})`),t.length===0){c("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${F([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
305
- `),c(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function so(e){c(`Starting ${e.name??e.ref} (local)\u2026`);let t=await Ie(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new U("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,api:e.apiUrl,dbPort:e.dbPort,started:!0},()=>{c(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function ao(e){let t=await Ie(e,["stop"],!0);if(t.code!==0)throw new U("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,stopped:!0},()=>c(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function io(e){g({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${F([["name",e.name??"(not set up by Studio)"],["ref",e.ref],["where",`local, ${e.folder}`],["api",e.apiUrl],["database",`127.0.0.1:${e.dbPort}`]])}
306
- `)})}We();import{cpSync as uo,existsSync as Qe,readdirSync as _c,readFileSync as mo,rmSync as fo,statSync as kc,writeFileSync as po}from"node:fs";import{join as ee,resolve as At}from"node:path";var gc=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function hc(e){return gc.test(e)}function ve(e){if(!hc(e))throw new Error(`not a function name: ${JSON.stringify(e)}. Letters, digits, hyphens and underscores, up to 48 characters, starting with a letter or a digit.`);return e}var yc=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function wc(e){if(e.length===0||e.length>255||e.includes("\\")||e.startsWith("/"))return!1;let t=e.split("/");return t.length>8?!1:t.every(n=>n!=="."&&n!==".."&&yc.test(n))}var gn=2*1024*1024,co=200;function lo(e){let{entrypoint:t}=e;if(e.files.length===0)throw new Error("there is nothing in this function to deploy");if(e.files.length>co)throw new Error(`a function may hold ${co} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!wc(a.path))throw new Error(`${JSON.stringify(a.path)} is not a path a function may contain`);if(n.has(a.path))throw new Error(`${a.path} appears twice`);n.add(a.path)}if(!n.has(t))throw new Error(`the entrypoint ${JSON.stringify(t)} is not one of the files`);let r=[...e.files].sort((a,l)=>a.path<l.path?-1:a.path>l.path?1:0),o={snoutfn:1,entrypoint:t,files:r},s=bc(o);if(s.length>gn)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${gn/1024} KB. A function is source code; put large files in Storage.`);return o}function bc(e){let t=e.files.map(r=>`{"path":${JSON.stringify(r.path)},"text":${JSON.stringify(r.text)}}`).join(","),n=`{"snoutfn":${e.snoutfn},"entrypoint":${JSON.stringify(e.entrypoint)},"files":[${t}]}`;return new TextEncoder().encode(n)}var Sc=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var xc=["SNOUTDATA_","SNOUT_"];function Ze(e){if(!Sc.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=xc.find(n=>e.startsWith(n));return t?`names beginning with ${t} are set by the platform, so ${e} cannot be one of yours.`:null}var Im=64*1024;Z();re();K();function go(e){let t=ee(e.folder,"functions");return Qe(t)?_c(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&kc(ee(t,n)).isDirectory()).sort():[]}async function Rt(e){c(S(" docker compose run --rm functions-deploy"));let t=await Ie(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new U("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function ho(e,t,n){let r=ee(e.folder,".env"),o=mo(r,"utf8"),s=(yt(o).get("FUNCTIONS_NO_VERIFY_JWT")??"").split(",").map(d=>d.trim()).filter(Boolean),a=n?[...new Set([...s,t])]:s.filter(d=>d!==t);if(a.join(",")===s.join(","))return!1;let l=`FUNCTIONS_NO_VERIFY_JWT=${a.join(",")}`,i=/^FUNCTIONS_NO_VERIFY_JWT=.*$/m;return po(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
564
+ ${r}`}async function Em(e,t,n={}){Z(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,Ka),Cr(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function $m(e,t,n){Z(t),await e.remove(t),n&&(await e.removeData(t),fn.delete(t))}async function Cr(e,t,n={}){Z(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??gn;return{ref:t.ref,exists:!!r,status:r?.status??"absent",ready:o?await e.waitReady(t.ref,1):!1,hasData:r?.hasData??await s(t.ref),port:t.port,databaseBytes:o?await e.databaseBytes(t.ref):null,services:r?.services??[],uri:t.port>0&&t.ownerPassword?Ii(t):null}}var Nr,B,Ir,We,lm,Ai,Mr,$e,dm,Pi,z,fm,pm,Ri,fn,Ni,Ui=D(()=>{"use strict";Wa();pt();Va();yr();xr();he();Nr=process.env.SNOUTPOD_ENGINE??"podman";B=cm(),Ir=18,We=[17,18],lm="snoutpod/postgres",Ai="ghcr.io/snoutdata/snoutpod-postgres";Mr=bt(null).name,$e=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",dm=54322,Pi=512,z=Ja();fm="cd packages/snoutpod && npm run image:build",pm=`${Ai}:${Ir}`;Ri=20*60*1e3;fn=new Map;Ni="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});te();var bn=[{name:"init",summary:"Create or link a project for this folder, and print its DATABASE_URL or write it to .env",flags:["--name","--region","--env","--ref"]},{name:"login",summary:"Sign in through a browser, with --sso through your company's identity provider, or with --device by typing a code into one anywhere",flags:["--email","--provider","--sso","--domain","--no-browser","--device","--timeout"]},{name:"logout",summary:"Forget the stored session",flags:[]},{name:"whoami",summary:"Who this credential belongs to, and whether it is the account login --email asked for",flags:[]},{name:"upgrade",summary:"Install the newest CLI the way this one was installed (binary or npm), checksum verified",flags:["--check"]},{name:"tokens list",summary:"Long-lived access tokens on this account",flags:[]},{name:"tokens create",summary:"Mint an sdt_ token for CI or an agent",flags:["--name","--expires","--project"]},{name:"tokens revoke",summary:"Revoke one by id or prefix",flags:[]},{name:"projects list",summary:"Every project on this account",flags:[]},{name:"projects create",summary:"Make a hosted database, and print its ref",flags:["--name","--region","--team","--no-wait","--timeout","--show-url"]},{name:"projects pause",summary:"Stop a project, and wait until it is paused",flags:["--ref","--no-wait","--timeout"]},{name:"projects resume",summary:"Start a paused project, and wait until it is ready",flags:["--ref","--no-wait","--timeout"]},{name:"projects delete",summary:"Delete a project, and wait until it is gone",flags:["--ref","--no-wait","--timeout"]},{name:"projects show",summary:"One project: state, products (Realtime included), functions, secrets, domains",flags:["--ref"]},{name:"products",summary:"Auth, storage, the data API and push: on or off. Realtime is always on, and listed",flags:["--ref"]},{name:"products enable",summary:"Turn auth, storage, data-api or push on",flags:["--ref"]},{name:"products disable",summary:"Turn auth, storage, data-api or push off",flags:["--ref"]},{name:"push credentials",summary:"Push keys: what is set for APNs, FCM and Web Push (never the keys)",flags:["--ref"]},{name:"push credentials set",summary:"Set the APNs key (a .p8) or the FCM service account, checked before it is stored",flags:["--ref","--p8","--key-id","--team-id","--topic","--environment","--file"]},{name:"push credentials remove",summary:"Remove the APNs or FCM credentials",flags:["--ref"]},{name:"auth",summary:"Auth settings: Google sign-in and redirect addresses",flags:["--ref"]},{name:"auth google",summary:"Sign in with Google, with your own client (secret on stdin)",flags:["--ref","--client-id","--stdin"]},{name:"auth anonymous",summary:"Guest sign-in on or off: signInAnonymously() gives a browser a session with no email or password",flags:["--ref"]},{name:"auth redirects",summary:"The site URL and the addresses a sign-in may return to",flags:["--ref","--site-url","--allow"]},{name:"auth templates",summary:"The five auth emails, ours or your own",flags:["--ref"]},{name:"auth template",summary:"Use your own subject and HTML for one, or reset it",flags:["--ref","--file","--subject"]},{name:"realtime inspect",summary:"The channels open now: each client, its presence, when it was last heard, and the last minute of messages",flags:["--channel","--watch","--ref"]},{name:"realtime logs",summary:"The connection log: connects, joins, leaves and disconnects, with the reason each ended",flags:["--since","--channel","--ref"]},{name:"domains",summary:"Your own domains in front of the project API",flags:["--ref"]},{name:"domains add",summary:"Add one, and print the DNS records to publish",flags:["--ref"]},{name:"domains verify",summary:"Check its records and verify it",flags:["--ref"]},{name:"domains remove",summary:"Stop serving it",flags:["--ref"]},{name:"functions deploy",summary:"Put a folder of TypeScript on the edge",flags:["--ref","--dir","--entrypoint","--no-verify-jwt"]},{name:"functions list",summary:"What this project has deployed",flags:["--ref"]},{name:"functions size",summary:"Its memory and workers, within the plan",flags:["--ref","--memory","--concurrency","--reset"]},{name:"functions delete",summary:"Remove one",flags:["--ref"]},{name:"secrets set",summary:"Set the environment functions run with",flags:["--ref","--stdin"]},{name:"secrets list",summary:"The names, never the values",flags:["--ref"]},{name:"secrets unset",summary:"Remove one",flags:["--ref"]},{name:"keys",summary:"The anon and service_role API keys for a project",flags:["--ref"]},{name:"keys rotate",summary:"New keys, breaking every one already issued",flags:["--ref","--force"]},{name:"teams",summary:"Teams this account belongs to",flags:[]},{name:"link",summary:"Point this folder at a project",flags:["--ref"]},{name:"usage",summary:"Size and compute against the plan limit",flags:["--ref","--days","--history"]},{name:"start",summary:"A Postgres on this machine, with your migrations applied",flags:["--port","--dir","--no-migrations","--out-of-order"]},{name:"stop",summary:"Stop the local database. The data stays",flags:[]},{name:"status",summary:"What the local database is doing",flags:[]},{name:"gen types typescript",summary:"The schema as a TypeScript Database type, on stdout",flags:["--ref","--db-url","--local","--schema","--out"]},{name:"db url",summary:"A connection string",flags:["--ref"]},{name:"db psql",summary:"Open psql, or run statements after --",flags:["--ref"]},{name:"db reset-password",summary:"Rotate the project password",flags:["--ref"]},{name:"db export",summary:"Take or download a dump",flags:["--ref","--out","--status","--timeout"]},{name:"db push",summary:"Apply migrations from a folder",flags:["--dir","--dry-run","--out-of-order"]},{name:"db restore",summary:"Put a dump into a project, or rewind it to a moment into a new project",flags:["--file","--force","--at","--name","--window","--ref"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}];var qr={"--ref":"The project. Defaults to SNOUTDATA_PROJECT, then .snoutdata/project.json here or in a parent","--name":"The project's name","tokens create --name":"What the token is for and where it lives, shown in tokens list","db restore --name":"The name of the new project the restore makes","--region":"Where the project runs. Defaults to the nearest","--env":"Write DATABASE_URL into .env in this folder, and add .env to .gitignore since it holds the password. Without it the URL is printed","--email":"The account to sign in as. The browser offers it, and a different account is reported at once and by whoami","--provider":"google (the default) or github","--sso":"Sign in with your company's identity provider","--domain":"With --sso: your work email or company domain. Only the domain is sent","--no-browser":"Print the sign-in URL instead of opening a browser","--device":"Sign in by typing a code into a browser on any machine: SSH, containers, agents","--timeout":"Seconds to wait before giving up","--check":"Say whether a newer version exists, and install nothing","--expires":"Days until the token stops working. Without it, it works until revoked","--project":"Limit the token to one project ref","--team":"Create it in a team (name or id), shared with its members","--no-wait":"Return as soon as it is asked for, without waiting for it to be true","--show-url":"Also print the connection string, which holds the password. Otherwise db url prints it when wanted","--p8":"The APNs .p8 key file (a path, never the key itself)","--key-id":"The APNs key id","--team-id":"Your Apple developer team id","--topic":"The app's bundle id","--environment":"production or sandbox","--file":"A file to read","push credentials set --file":"The FCM service-account JSON","auth template --file":"The email's HTML body","db restore --file":"The dump to restore (from db export or pg_dump -Fc)","--client-id":"Your Google OAuth client id; the secret is read from stdin with --stdin","--stdin":"Read the secret value from a pipe, so it is not in shell history","--site-url":"Where sign-in emails send people by default","--allow":"Comma-separated addresses a sign-in may return to","--subject":"The email's subject line","--dir":"The folder to read from","db push --dir":"The migrations folder (default ./migrations)","start --dir":"The migrations folder (default ./migrations)","functions deploy --dir":"The function's folder (default ./functions/<name>)","--entrypoint":"The file the runtime starts, when it is not index.ts","--no-verify-jwt":"Let anybody with the URL call it (webhooks). By default a caller needs a project key or a user session","--memory":"Memory per worker, in MB, within the plan","--concurrency":"How many workers it may run at once, within the plan","--reset":"Back to the plan's default size","--force":"Do it although it cannot be undone","--days":"How many days of history","--history":"Print the day-by-day table too","--port":"The port the local database listens on (default 54322)","--no-migrations":"Start the database and apply nothing","--out-of-order":"Apply a new migration that sorts before one already applied","--db-url":"Read a postgres:// database instead of a project","--local":"Read the database snoutdata start is running","--schema":"Comma-separated schemas (default public)","--out":"Write to this file instead of stdout","db export --out":"Where to save the dump","--status":"Show the last export without taking another","--dry-run":"Show what would run, and change nothing","--at":"The moment to restore to (an ISO time), into a NEW project","--window":"How far back a point-in-time restore can go","--allow-delete":"Let the agent's delete_project actually delete","--channel":"Only this channel","--since":"How far back, like 10m, 2h or 1d (default 1h)","--watch":"Keep printing what changes until interrupted","--level":"full (everything the project password can do) or read (reads the project's own tables, never the auth or storage schemas, and writes nothing). Default read"};function Sn(e,t){return qr[`${e} ${t}`]??qr[t]??""}W();Q();q();import{spawn as ol}from"node:child_process";function sl(e){return`snout-${e}`}function Ue(e,t,n){return new Promise((r,o)=>{let s=ol("docker",["compose","-p",sl(e.ref),"--project-directory",e.folder,...t],{cwd:e.folder,stdio:["ignore","pipe","pipe"]}),a="",l="";s.stdout.on("data",i=>{a+=String(i)}),s.stderr.on("data",i=>{if(l+=String(i),n)for(let u of String(i).split(/\r?\n/).filter(Boolean))c(b(` ${u.trim()}`))}),s.on("error",i=>{if(i.code==="ENOENT"){o(new N("tool-missing","docker is not installed, or not on PATH. A local project runs in Docker (Docker Desktop on Windows and macOS)."));return}o(new N("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function al(e){let t=await Ue(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new N("failed",t.err.trim()||"docker compose ps failed. Is Docker running?");let n=t.out.trim();return(n.startsWith("[")?JSON.parse(n):n.split(/\r?\n/).filter(Boolean).map(o=>JSON.parse(o))).map(o=>({service:o.Service??"?",state:o.State==="exited"&&o.ExitCode===0?"done":o.State??"?",health:o.Health??""})).sort((o,s)=>o.service.localeCompare(s.service))}async function To(e){let t=await al(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return g({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(c(`${v(e.name??e.ref)} (local, ${e.folder})`),t.length===0){c("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${F([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
565
+ `),c(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Oo(e){c(`Starting ${e.name??e.ref} (local)\u2026`);let t=await Ue(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new N("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,api:e.apiUrl,dbPort:e.dbPort,started:!0},()=>{c(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function No(e){let t=await Ue(e,["stop"],!0);if(t.code!==0)throw new N("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,stopped:!0},()=>c(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function Do(e){g({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${F([["name",e.name??"(not set up by Studio)"],["ref",e.ref],["where",`local, ${e.folder}`],["api",e.apiUrl],["database",`127.0.0.1:${e.dbPort}`]])}
566
+ `)})}Qe();import{cpSync as Co,existsSync as ct,readdirSync as pl,readFileSync as jo,rmSync as Lo,statSync as gl,writeFileSync as Uo}from"node:fs";import{join as re,resolve as Ht}from"node:path";var il=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function cl(e){return il.test(e)}function Re(e){if(!cl(e))throw new Error(`not a function name: ${JSON.stringify(e)}. Letters, digits, hyphens and underscores, up to 48 characters, starting with a letter or a digit.`);return e}var ll=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function dl(e){if(e.length===0||e.length>255||e.includes("\\")||e.startsWith("/"))return!1;let t=e.split("/");return t.length>8?!1:t.every(n=>n!=="."&&n!==".."&&ll.test(n))}var Mn=2*1024*1024,Io=200;function Mo(e){let{entrypoint:t}=e;if(e.files.length===0)throw new Error("there is nothing in this function to deploy");if(e.files.length>Io)throw new Error(`a function may hold ${Io} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!dl(a.path))throw new Error(`${JSON.stringify(a.path)} is not a path a function may contain`);if(n.has(a.path))throw new Error(`${a.path} appears twice`);n.add(a.path)}if(!n.has(t))throw new Error(`the entrypoint ${JSON.stringify(t)} is not one of the files`);let r=[...e.files].sort((a,l)=>a.path<l.path?-1:a.path>l.path?1:0),o={snoutfn:1,entrypoint:t,files:r},s=ul(o);if(s.length>Mn)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Mn/1024} KB. A function is source code; put large files in Storage.`);return o}function ul(e){let t=e.files.map(r=>`{"path":${JSON.stringify(r.path)},"text":${JSON.stringify(r.text)}}`).join(","),n=`{"snoutfn":${e.snoutfn},"entrypoint":${JSON.stringify(e.entrypoint)},"files":[${t}]}`;return new TextEncoder().encode(n)}var ml=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var fl=["SNOUTDATA_","SNOUT_"];function it(e){if(!ml.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=fl.find(n=>e.startsWith(n));return t?`names beginning with ${t} are set by the platform, so ${e} cannot be one of yours.`:null}var dp=64*1024;Q();te();q();function Fo(e){let t=re(e.folder,"functions");return ct(t)?pl(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&gl(re(t,n)).isDirectory()).sort():[]}async function Gt(e){c(b(" docker compose run --rm functions-deploy"));let t=await Ue(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new N("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function Bo(e,t,n){let r=re(e.folder,".env"),o=jo(r,"utf8"),s=($t(o).get("FUNCTIONS_NO_VERIFY_JWT")??"").split(",").map(u=>u.trim()).filter(Boolean),a=n?[...new Set([...s,t])]:s.filter(u=>u!==t);if(a.join(",")===s.join(","))return!1;let l=`FUNCTIONS_NO_VERIFY_JWT=${a.join(",")}`,i=/^FUNCTIONS_NO_VERIFY_JWT=.*$/m;return Uo(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
307
567
  `)}${l}
308
- `),!0}async function hn(e,t,n){ve(t);let r=At(n.dir??ee(process.cwd(),"functions",t));if(!Qe(ee(r,"index.ts")))throw new y(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=ee(e.folder,"functions",t);if(At(o)!==r){fo(o,{recursive:!0,force:!0}),uo(r,o,{recursive:!0});let a=ee(r,"..","_shared");Qe(a)&&At(a)!==At(ee(e.folder,"functions","_shared"))&&uo(a,ee(e.folder,"functions","_shared"),{recursive:!0})}let s=ho(e,t,!n.verifyJwt);if(await Rt(e),s){let a=await Ie(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new U("failed",`docker compose up failed after changing who may call ${t}: ${a.err.trim().split(/\r?\n/).slice(-2).join(" ")}`)}return{ref:e.ref,name:t,url:`${e.apiUrl}/functions/v1/${t}`,verifyJwt:n.verifyJwt,local:e.folder}}async function yo(e,t,n){let r=await hn(e,t,n);g(r,()=>{c(`${$(t)} deployed locally: ${r.url}`),n.verifyJwt||c(S(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function Ot(e){return{ref:e.ref,local:e.folder,functions:go(e).map(t=>({name:t,url:`${e.apiUrl}/functions/v1/${t}`}))}}function wo(e){let t=go(e);g(Ot(e),()=>{if(t.length===0){c(`No functions in ${ee(e.folder,"functions")}.`);return}process.stdout.write(`${F([["NAME","URL"],...t.map(n=>[n,`${e.apiUrl}/functions/v1/${n}`])])}
309
- `)})}async function yn(e,t){ve(t);let n=ee(e.folder,"functions",t);if(!Qe(n))throw new U("not-found",`this local project has no function called ${t}`);return fo(n,{recursive:!0,force:!0}),ho(e,t,!1),await Rt(e),{ref:e.ref,name:t,removed:!0}}async function bo(e,t){g(await yn(e,t),()=>c(`${t} is gone.`))}function Nt(e){return ee(e.folder,"functions",".env")}function wn(e){let t=Nt(e);return Qe(t)?yt(mo(t,"utf8")):new Map}function So(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;po(Nt(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
568
+ `),!0}async function Cn(e,t,n){Re(t);let r=Ht(n.dir??re(process.cwd(),"functions",t));if(!ct(re(r,"index.ts")))throw new h(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=re(e.folder,"functions",t);if(Ht(o)!==r){Lo(o,{recursive:!0,force:!0}),Co(r,o,{recursive:!0});let a=re(r,"..","_shared");ct(a)&&Ht(a)!==Ht(re(e.folder,"functions","_shared"))&&Co(a,re(e.folder,"functions","_shared"),{recursive:!0})}let s=Bo(e,t,!n.verifyJwt);if(await Gt(e),s){let a=await Ue(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new N("failed",`docker compose up failed after changing who may call ${t}: ${a.err.trim().split(/\r?\n/).slice(-2).join(" ")}`)}return{ref:e.ref,name:t,url:`${e.apiUrl}/functions/v1/${t}`,verifyJwt:n.verifyJwt,local:e.folder}}async function Ho(e,t,n){let r=await Cn(e,t,n);g(r,()=>{c(`${v(t)} deployed locally: ${r.url}`),n.verifyJwt||c(b(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function qt(e){return{ref:e.ref,local:e.folder,functions:Fo(e).map(t=>({name:t,url:`${e.apiUrl}/functions/v1/${t}`}))}}function Go(e){let t=Fo(e);g(qt(e),()=>{if(t.length===0){c(`No functions in ${re(e.folder,"functions")}.`);return}process.stdout.write(`${F([["NAME","URL"],...t.map(n=>[n,`${e.apiUrl}/functions/v1/${n}`])])}
569
+ `)})}async function jn(e,t){Re(t);let n=re(e.folder,"functions",t);if(!ct(n))throw new N("not-found",`this local project has no function called ${t}`);return Lo(n,{recursive:!0,force:!0}),Bo(e,t,!1),await Gt(e),{ref:e.ref,name:t,removed:!0}}async function qo(e,t){g(await jn(e,t),()=>c(`${t} is gone.`))}function Kt(e){return re(e.folder,"functions",".env")}function Ln(e){let t=Kt(e);return ct(t)?$t(jo(t,"utf8")):new Map}function Ko(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;Uo(Kt(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
310
570
  `)+(t.size?`
311
- `:""))}async function xo(e,t){for(let r of t){let o=Ze(r.name);if(o)throw new y(o)}let n=wn(e);for(let r of t)n.set(r.name,r.value);So(e,n),await Rt(e),g({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)c(`${$(r.name)} set in ${Nt(e)}`)})}function Dt(e){return{ref:e.ref,local:e.folder,secrets:[...wn(e).keys()].sort().map(t=>({name:t}))}}function _o(e){let t=Dt(e),n=t.secrets.map(r=>r.name);g(t,()=>{c(n.length?n.join(`
312
- `):`No function secrets in ${Nt(e)}.`)})}async function ko(e,t){let n=wn(e);if(!n.delete(t))throw new U("not-found",`${t} is not set for this local project`);So(e,n),await Rt(e),g({ref:e.ref,unset:t},()=>c(`${t} unset.`))}Z();Oe();Ee();ln();_t();K();fn();G();G();Z();Ee();K();async function Eo(){let{projects:e,allowance:t}=await Q();g({projects:e,allowance:t},()=>{if(e.length===0){c('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${F([["REF","NAME","STATE","REGION","LAST CONNECTION"],...e.map(n=>[n.ref,n.name,n.readOnly?$("read-only"):n.state==="ready"?n.state:$(n.state),n.region,ue(n.lastConnectionAt)])])}
313
- `)})}async function bn(e){let t=await x("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;c(`Created ${n}.`);let r=t.project;if(e.wait){c("Waiting for it to start\u2026");let o="";r=await Xe(n,{timeoutMs:e.timeoutMs,onTick:s=>{s!==o&&(o=s,c(S(` ${s}`)))}})}e.link&&Ge(e.link,{ref:n,name:r.name}),g({project:r,password:t.password,uri:`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`},()=>{process.stdout.write(`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require
314
- `),c(""),c(`Ready. ${$(n)} in ${r.region}.`),c("The password is stored for you: `snoutdata db url` prints this again.")})}async function vo(e,t,n={}){let r=`cloud-project-${e}`,o=await x(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){g({...o,settled:!1},()=>c(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await Ye(t,e,{timeoutMs:n.timeoutMs,onTick:d=>{d!==a&&(a=d,c(S(` ${d}`)))}}),i=l?.state??"deleted";if(i==="error")throw new U("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});g({...o,...l?{project:l}:{},settled:!0},()=>{c(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function Po(e){let t=await x("cloud-project-reset-password",{ref:e});g(t,()=>{process.stdout.write(`${t.password}
315
- `),c(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function $o(){let{teams:e}=await Tt();g({teams:e},()=>{if(e.length===0){c("You are not on any teams.");return}process.stdout.write(`${F([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
316
- `)})}async function To(e){let{teams:t}=await Tt(),n=t.find(s=>s.id===e),r=n?[n]:t.filter(s=>s.name.toLowerCase()===e.toLowerCase());if(r.length===0)throw new Error(t.length===0?`You are not on any teams, so there is no "${e}" to share with.`:`No team called "${e}". You are on: ${t.map(s=>s.name).join(", ")}.`);if(r.length>1)throw new Error(`More than one team is called "${e}". Use the id: ${r.map(s=>s.id).join(", ")}.`);let o=r[0];if(!o.mayShare)throw new Error(`${o.name}: ${o.why??"you cannot share a project into this team"}.`);return o.id}G();K();async function Ao(e){let t=await x("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});g(t,()=>{process.stdout.write(`${t.token}
317
- `),c(""),c(`${$(t.name)} \u2014 ${t.expiresAt?`expires ${t.expiresAt.slice(0,10)}`:"does not expire"}.`),c(t.project?`Reaches project ${t.project} only.`:"Reaches every project on this account."),c("This is the only time it is shown: only its hash is stored."),c(S(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function Ro(){let{tokens:e}=await x("cloud-token-list");g({tokens:e},()=>{if(e.length===0){c("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${F([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?$(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",ue(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
318
- `)})}async function Oo(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await x("cloud-token-revoke",t);g(n,()=>{c(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}G();re();K();async function No(e){let t=await Y(e);g({ref:t.ref,anonKey:t.anonKey,serviceRoleKey:t.serviceRoleKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.anonKey||!t.serviceRoleKey){c(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${Mo(t.anonKey,t.serviceRoleKey)}
319
- `),Pc(t.keysIssuedAt)})}async function Do(e,t){if(!t.force)throw c(`Rotating the keys for ${e} stops every anon and service_role key already pasted anywhere.`),c("Anything holding one (a deployed app, a CI secret, a teammate) breaks until it is updated."),new y("keys rotate needs --force, because this cannot be undone");let n=await x("cloud-project-rotate-jwt",{ref:e});g(n,()=>{process.stdout.write(`${Mo(n.anonKey,n.serviceRoleKey)}
320
- `),c(""),c(n.breaks),c(S(" snoutdata keys --json to read them again later"))})}function Mo(e,t){return F([["KEY","VALUE"],["anon",e],["service_role",t]])}function Pc(e){c(""),c(`${$("anon")} is the public one: it goes in a browser, and row-level security decides what it reads.`),c(`${$("service_role")} bypasses row-level security. Server side only, never in a client bundle.`),e&&c(S(` issued ${e.slice(0,10)}`))}G();re();K();import{readdir as Tc,readFile as Ac,stat as Rc}from"node:fs/promises";import{join as xn,relative as Oc,sep as Nc}from"node:path";var Io=["functions"];async function Sn(e){try{return(await Rc(e)).isDirectory()}catch{return!1}}async function Dc(e,t){if(t){if(!await Sn(t))throw new y(`${t} is not a directory`);return t}for(let n of Io){let r=xn(n,e);if(await Sn(r))return r}throw new y(`no folder found for ${e} at ${Io[0]}/${e}; use --dir to say where it is.`)}async function jo(e){let t=[];async function n(r){for(let o of await Tc(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=xn(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await Ac(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new y(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:Oc(e,s).split(Nc).join("/"),text:l})}}return await n(e),t}async function Mc(e,t,n){let r=await jo(t);if(r.length===0)throw new y(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new y(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=xn(t,"..","_shared");if(!await Sn(s))return{entrypoint:o,files:r};let a=(await jo(s)).map(l=>({path:`_shared/${l.path}`,text:l.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(l=>({path:`${e}/${l.path}`,text:l.text})),...a]}}async function _n(e,t,n){ve(t);let r=await Dc(t,n.dir),{entrypoint:o,files:s}=await Mc(t,r,n.entrypoint),a=lo({entrypoint:o,files:s});return{answer:await x("cloud-project-functions",{ref:e,action:"deploy",name:t,verifyJwt:n.verifyJwt,bundle:a}),directory:r,files:s.length,bytes:JSON.stringify(a).length}}async function Co(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await _n(e,t,n),l=r.functions.find(i=>i.name===t);g(r,()=>{let i=Math.max(1,Math.round(a/1024));c(`${$(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),l&&c(` ${l.url}`),n.verifyJwt||(c(""),c($(" Anybody who knows this URL can run it. No API key is required.")),c(S(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&c(S(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function kn(e){return x("cloud-project-functions",{ref:e})}function En(e,t){return ve(t),x("cloud-project-functions",{ref:e,action:"delete",name:t})}function vn(e,t,n){if(ve(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new y("functions size needs --memory MB, --concurrency N, or --reset");return x("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function Ic(e,t){if(e.memoryMb==null||e.concurrency==null)return null;let n=e.memoryMb*e.concurrency,r=t.podMemoryMb?` of ${t.podMemoryMb} MB`:"";return`${e.memoryMb} MB x ${e.concurrency} worker${e.concurrency===1?"":"s"} = ${n} MB${r}`}function Lo(e){return e.functionMemoryMb==null||e.concurrencyMax==null||e.podMemoryMb==null?null:`${e.tier??"this"} plan: up to ${e.functionMemoryMb} MB a worker and ${e.concurrencyMax} workers a function, memory x workers up to ${e.podMemoryMb} MB`}async function Uo(e,t,n){let r=await vn(e,t,n),o=r.functions.find(s=>s.name===t);g(r,()=>{let s=o?Ic(o,r.limit):null;c(`${$(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),c(S(" The next request runs at it; workers already running finish what they hold."));let a=Lo(r.limit);a&&c(S(` ${a}`))})}async function Fo(e){let t=await kn(e);g(t,()=>{if(t.functions.length===0){c(`${e} has no functions.`),c(S(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${F([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":$("open"),r.bytes===null?"-":`${Math.max(1,Math.round(r.bytes/1024))} KB`,r.memoryMb==null?"-":`${r.memoryMb} MB`,r.concurrency==null?"-":String(r.concurrency),r.url])])}
321
- `),t.limit.maxFunctions!==null&&c(S(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=Lo(t.limit);n&&c(S(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}async function Bo(e,t){let n=await En(e,t);g(n,()=>{c(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}G();re();K();function Ho(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function qo(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function Go(e,t){if(e.length===0)throw new y("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new y("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await qo()}]}return e.map(n=>{let r=Ho(n);if(!r)throw new y(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function Ko(e,t,n){if(t.length===0)throw new y("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new y("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await qo()})}else for(let s of t){let a=Ho(s);if(!a)throw new y(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=Ze(s.name);if(a)throw new y(a)}let o=null;for(let s of r)o=await x("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});g(o,()=>{for(let s of r)c(`${$(s.name)} set`);c(S(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||c(S(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function Jo(e,t){let n=Ze(t);if(n)throw new y(n);let r=await x("cloud-project-secrets",{ref:e,action:"unset",name:t});g(r,()=>{c(`${t} is gone.`)})}async function Wo(e){let t=await x("cloud-project-secrets",{ref:e});g(t,()=>{if(t.secrets.length===0){c(`${e} has no function secrets.`),c(S(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${F([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,ue(n.updatedAt)])])}
322
- `),c(S("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as tt}from"node:child_process";import{appendFileSync as us,existsSync as Ut,readFileSync as ds,unlinkSync as el,writeFileSync as Ce}from"node:fs";import{dirname as tl,join as Ft}from"node:path";import{tmpdir as nl}from"node:os";import{open as ms}from"node:fs/promises";function zo(e){let t=(r,o)=>String.fromCharCode(...Array.from(e.slice(r,r+o)));return t(0,5)==="PGDMP"?"custom":e.length>=262&&t(257,5)==="ustar"?"tar":Array.from(e.slice(0,256)).every(r=>r===9||r===10||r===13||r>=32&&r<127||r>=128)&&e.length>0?"plain":"unknown"}function Vo(e,t,n={}){return t.readOnly?{ok:!1,code:"read-only",reason:"this project is over its plan's storage limit, so it refuses writes. A restore would fail part way. Delete some rows or change plan first."}:e==="unknown"?{ok:!1,code:"unknown-format",reason:"that file is not a pg_dump archive and does not look like SQL. `snoutdata db export` writes the custom format; psql reads plain .sql."}:!n.force&&t.existingTables>0?{ok:!1,code:"not-empty",reason:`this database already has ${t.existingTables} table${t.existingTables===1?"":"s"}. Restoring into it would leave some objects created and some not. Check --ref is the project you meant, or pass --force.`}:{ok:!0,tool:e==="plain"?"psql":"pg_restore",format:e}}function Yo(e){let t=e.split(`
323
- `).filter(r=>/^pg_restore: error:/.test(r.trim())),n=t.filter(r=>!/must be owner of extension/.test(r));return{total:t.length,harmless:t.length-n.length,fatal:n}}var Pn={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},Lc=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Mt=new Set(["auth","storage","push","realtime","snout_realtime","_realtime","graphql","graphql_public","extensions","net","cron","pgbouncer","vault","pgsodium"]),Uc=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,Fc={auth:"auth",storage:"storage",push:"push"},Bc=new Set(["push.settings"]),Hc=["TABLE DATA","SEQUENCE SET","SEQUENCE OWNED BY","FK CONSTRAINT","CHECK CONSTRAINT","DEFAULT ACL","EVENT TRIGGER","ROW SECURITY","INDEX ATTACH","TABLE ATTACH","MATERIALIZED VIEW DATA","MATERIALIZED VIEW","PUBLICATION TABLES IN SCHEMA","PUBLICATION TABLE","FOREIGN TABLE","FOREIGN DATA WRAPPER","FOREIGN SERVER","USER MAPPING","TEXT SEARCH CONFIGURATION","TEXT SEARCH DICTIONARY","TEXT SEARCH PARSER","TEXT SEARCH TEMPLATE","LARGE OBJECT","PROCEDURAL LANGUAGE","OPERATOR CLASS","OPERATOR FAMILY","ACCESS METHOD","EXTENDED STATISTICS","DATABASE PROPERTIES"];function qc(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=Hc.find(i=>n.startsWith(`${i} `))??n.split(" ")[0],o=n.slice(r.length).trim().split(" "),s=o[0]??"-",a=o.length>2?o[o.length-1]:"",l=o.slice(1,o.length>2?-1:void 0).join(" ");return{line:e.trim(),type:r,schema:s,name:l,owner:a}}function Gc(e){let t=new Map,n=o=>o.replace(/"/g,""),r=/ALTER TABLE (?:ONLY )?([\w."$]+)\s+ADD CONSTRAINT [^;]*?FOREIGN KEY \([^)]*\) REFERENCES ([\w."$]+)\(/g;for(let o of e.matchAll(r)){let s=n(o[1]),a=n(o[2]);if(s!==a){let l=t.get(s)??new Set;l.add(a),t.set(s,l)}}return t}function Xo(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
324
- `)){let d=qc(i);if(!d)continue;let{type:u,schema:f,name:h,owner:m}=d;if(u==="TABLE DATA"||u==="SEQUENCE SET"){let b=/^(schema_migrations|migrations)$/.test(h)&&Mt.has(f);if(/realtime/.test(f)||b||f==="public"&&h==="spatial_ref_sys"){s++;continue}if(f==="cron"){s++,o.add("Scheduled jobs (pg_cron) are not restored: each names the project it came from. Schedule them again with cron.schedule.");continue}if(f==="push"&&h==="credentials"){s++,o.add("Push credentials are not restored: they are sealed for the project they came from. Set them again with `snoutdata push credentials set`.");continue}let w=Fc[f];if(w){w==="storage"&&h==="objects"&&o.add("Storage objects are restored as rows; the files themselves are not part of an export. Upload them again into this project."),n.set(w,[...n.get(w)??[],d]);continue}if(Mt.has(f)){s++;continue}r.push(d.line);continue}if(u==="COMMENT"&&f==="-"&&/^EXTENSION( |$)/.test(h)||(u==="SCHEMA"?Mt.has(h):Mt.has(f))||Uc.test(h)||Lc.test(m)&&u!=="SCHEMA"){s++;continue}r.push(d.line)}let a=Gc(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let d=n.get(i),u=d.filter(h=>h.type==="TABLE DATA"&&Bc.has(`${h.schema}.${h.name}`)).map(h=>`${h.schema}.${h.name}`),f=d.filter(h=>h.type==="TABLE DATA").map(h=>`${h.schema}.${h.name}`);return{product:i,clear:u,tables:f,lines:Kc(d,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function Kc(e,t){let n=l=>`${l.schema}.${l.name}`,r=new Set(e.map(n)),o=new Set,s=[],a=[...e];for(;a.length>0;){let i=a.find(d=>[...t.get(n(d))??[]].every(u=>!r.has(u)||o.has(u)||u===n(d)))??a[0];o.add(n(i)),s.push(i.line),a=a.filter(d=>d!==i)}return s}G();re();K();var Jc="2024-11-05";function L(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var N={type:"string"},It=[{name:"whoami",description:"Who this CLI is signed in as, and how (a session or a long-lived access token). Use it to check a credential before doing anything else.",inputSchema:L({})},{name:"list_projects",description:"Every hosted Postgres database on this account, with its ref, name, region, state (ready, paused, error), size, and whether it is read-only because it is over its plan's storage limit. Under `local`, the self-hosted projects on this machine (SnoutData Studio's, or linked with `snoutdata link --local`), whose refs work with get_connection_url, push_migrations, get_project and the function tools.",inputSchema:L({})},{name:"create_project",description:"Make a new hosted Postgres database and wait for it to be ready. Costs a project against the plan allowance; a free plan includes two. Returns its ref, which every other tool takes.",inputSchema:L({name:N,region:N,teamId:N},["name"])},{name:"get_connection_url",description:"The postgres:// URL for a project, ready to put in DATABASE_URL. IT CONTAINS A LIVE PASSWORD, so treat it as a credential and do not print it into a log or a file that is committed. Connecting to a paused project wakes it, which takes a few seconds.",inputSchema:L({ref:N},["ref"])},{name:"pause_project",description:"Stop a project, and wait until it is paused. Its data is kept and the next connection wakes it. A production project refuses this.",inputSchema:L({ref:N},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:L({ref:N},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs psql on this machine.",inputSchema:L({ref:N,dir:N,dryRun:{type:"boolean"}},["ref"])},{name:"usage",description:"How much storage and compute a project has used against the limit on this plan, and whether it is near it. READ THIS BEFORE a migration or a bulk insert: a project over its storage limit is made read-only, and `list_projects` will say readOnly afterwards without ever saying how much headroom was left. Takes an optional number of days of history (default 30, maximum 365).",inputSchema:L({ref:N,days:{type:"number"}},["ref"])},{name:"export_status",description:"Whether a copy of this database is being taken, when the last one finished, and a download link if there is a live one. Costs nothing and starts nothing.",inputSchema:L({ref:N},["ref"])},{name:"start_export",description:"Take a copy of the whole database. Runs a pg_dump on the server and can take minutes, so this STARTS it and returns; poll export_status for the link. Worth doing before a migration you are unsure of. It does not block the database.",inputSchema:L({ref:N},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING within a few seconds, including any DATABASE_URL already written into a file or a running process; connections already open keep working. Returns the new connection URL, which works once the change applies (wait about five seconds before connecting). Use it when a credential has leaked, not routinely.",inputSchema:L({ref:N},["ref"])},{name:"list_teams",description:"Teams this account belongs to, with the id create_project takes as teamId. Without this there is no way to discover that id.",inputSchema:L({})},{name:"list_tokens",description:"The long-lived sdt_ access tokens on this account: id, name, prefix, when each was made and last used. Never the token itself, which is shown once when it is made.",inputSchema:L({})},{name:"create_token",description:"Mint a long-lived sdt_ access token, for a CI job or another machine. RETURNS THE TOKEN ONCE AND NEVER AGAIN. Without `project` it is as capable as this account, so hand it over deliberately; with a project ref it reaches that one project and nothing else, which is what a CI job for one project should get. Optionally expires, as an ISO date.",inputSchema:L({name:N,expires:N,project:N},["name"])},{name:"revoke_token",description:"Revoke an access token by its id. Anything using it stops working at once. Safe to do to a token you think has leaked.",inputSchema:L({id:N},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/functions/v1/<name>. Reads functions/<name> unless dir says otherwise. Deno, so no build step and no node_modules. Set openToAnyone ONLY for a webhook receiver whose sender cannot send an API key: it makes the URL callable by anybody who knows it, and the function must then check the sender's signature itself.",inputSchema:L({ref:N,name:N,dir:N,entrypoint:N,openToAnyone:{type:"boolean"}},["ref","name"])},{name:"list_functions",description:"The functions deployed to a project, their URLs, bundle sizes, whether each one needs an API key, and each one's memory and concurrency with the plan's limits for both. Costs nothing.",inputSchema:L({ref:N},["ref"])},{name:"size_function",description:"Set one function's memory (MB one worker may use) and concurrency (how many workers it may run at once), within the project's plan. memoryMb x concurrency may not exceed the project's memory; list_functions shows each function's size and the plan's limits (limit.functionMemoryMb, limit.concurrencyMax, limit.podMemoryMb). A size that does not fit is refused with a sentence saying why. Omit one to keep it as it runs now; reset: true goes back to the plan's default. A second worker only helps CPU-bound work: requests that wait on the network share one.",inputSchema:L({ref:N,name:N,memoryMb:{type:"integer"},concurrency:{type:"integer"},reset:{type:"boolean"}},["ref","name"])},{name:"delete_function",description:"Remove one function from a project. It stops answering within a few seconds. It does not touch the database.",inputSchema:L({ref:N,name:N},["ref","name"])},{name:"list_function_secrets",description:"The NAMES of the environment variables a project's functions run with, their sizes and when each was last set. Never the values, which are not readable by anything but the runtime. THERE IS NO TOOL THAT SETS ONE: a secret in a tool call is a secret in an audit log, so a person sets it with `snoutdata secrets set`. Use this to check whether one a function needs is there.",inputSchema:L({ref:N},["ref"])},{name:"get_project",description:"Everything about one project in one call: its state, region and size, which products are on (auth, storage, data-api), the names of its deployed functions and function secrets, and its custom domains. Never a password, a key or a download link (export_status has the link to the last export).",inputSchema:L({ref:N},["ref"])},{name:"list_products",description:"Whether a project's auth (user sign-up and sign-in), storage (files) and data API (REST and GraphQL over its tables) are on, and whether the plan allows the data API.",inputSchema:L({ref:N},["ref"])},{name:"set_product",description:"Turn auth, storage or data-api on or off for a project. It starts within about a minute. The data API is on paid plans only, and a free project is refused in words.",inputSchema:L({ref:N,product:{type:"string",enum:["auth","storage","data-api"]},enabled:{type:"boolean"}},["ref","product","enabled"])},{name:"list_domains",description:"The custom domains in front of a project's API, whether each is verified, and the DNS records each needs.",inputSchema:L({ref:N},["ref"])},{name:"add_domain",description:"Serve a project's API at the user's own hostname (paid plans). Returns the DNS records the user must publish before verify_domain can succeed.",inputSchema:L({ref:N,hostname:N},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:L({ref:N,hostname:N},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:L({ref:N,hostname:N},["ref","hostname"])},{name:"restore_window",description:"How far back a point-in-time restore of a project can go, or why it cannot (the plan, or no backup yet). Starts nothing.",inputSchema:L({ref:N},["ref"])},{name:"restore_to_point",description:"Rewind a project to a moment (ISO 8601, inside restore_window) into a NEW project beside it, never over it. Costs a project slot on the plan. The original is untouched.",inputSchema:L({ref:N,at:N,name:N},["ref","at"])},{name:"delete_project",description:"Delete a project. THIS DESTROYS A DATABASE. Off unless the person who started this server passed --allow-delete, in which case it still only marks the project deleted and its data is erasable for a grace period.",inputSchema:L({ref:N},["ref"])}],Wc=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","get_project","list_products","list_domains","restore_window"]),zc=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain"]),Zo=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function $n(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map($n)};let n=t.export;if(n&&typeof n=="object"){let{url:r,rolesSql:o,...s}=n;return{...t,export:{...s,hasLink:typeof r=="string"&&r.length>0}}}return e}function An(e,t){return t.available?null:t.pitrEnabled?`${e} has no backup to restore from yet.`:`Point-in-time restore is part of the Pro and Business plans, and this project is on ${t.tier??"another plan"}.`}function Vc(e){return{...e,annotations:Wc.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:zc.has(e.name)}}}function Yc(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function et(e,t){return{jsonrpc:"2.0",id:e,result:t}}function Tn(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function T(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return et(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function Xc(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function q(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Qo(e,t,n){let r=e.id??null,o=e.method??"",s=e.params??{};if(e.id===void 0||e.id===null)return null;switch(o){case"initialize":return et(r,{protocolVersion:Jc,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return et(r,{});case"tools/list":return et(r,{tools:Yc([...It.map(Vc),...n.borrowed??[]])});case"tools/call":return Zc(r,s,t,n);default:return Tn(r,-32601,`unknown method: ${o}`)}}async function Zc(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(It.some(u=>u.name===o)||(r.borrowed?.some(u=>u.name===o)??!1)))return Tn(e,-32602,`unknown tool: ${o}`);let l=["get_connection_url","pause_project","resume_project","delete_project","push_migrations","usage","export_status","start_export","reset_password","get_project","list_products","set_product","list_domains","add_domain","verify_domain","remove_domain","restore_window","restore_to_point"],i=q(s,"ref");if(l.includes(o)&&!i)return T(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let d=i&&n.localProject?n.localProject(i):null;if(d&&!Zo.includes(o))return T(e,`${i} is the self-hosted project in ${d.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${Zo.join(", ")}.`,!0);try{switch(o){case"whoami":return T(e,await n.whoami());case"list_projects":return T(e,$n(await n.listProjects()));case"create_project":{let u=q(s,"name");return u?T(e,await n.createProject({name:u,region:q(s,"region")??void 0,teamId:q(s,"teamId")??void 0})):T(e,"create_project needs a name.",!0)}case"get_connection_url":{let u=await n.connection(i);return T(e,{url:u.uri,state:u.state,wakesInstantly:u.wakesInstantly,warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"pause_project":return T(e,await n.setProjectState("pause",i));case"resume_project":return T(e,await n.setProjectState("resume",i));case"usage":{let u=typeof s.days=="number"?s.days:void 0;return T(e,await n.usage(i,u))}case"export_status":return T(e,await n.exportStatus(i));case"start_export":return T(e,await n.requestExport(i));case"reset_password":{let u=await n.resetPassword(i),f=await n.connection(i);return T(e,{url:f.uri,appliesIn:u.appliesIn??"within a few seconds",next:"Wait about five seconds before connecting with this URL: until the change applies the new password is refused and the old one still works. Then update DATABASE_URL wherever it is set.",warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"list_teams":return T(e,await n.listTeams());case"get_project":return T(e,$n(await n.getProject(i)));case"list_products":return T(e,await n.getProducts(i));case"set_product":{let u=q(s,"product");return u!=="auth"&&u!=="storage"&&u!=="data-api"?T(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?T(e,"set_product needs enabled: true or false.",!0):T(e,await n.setProduct(i,u,s.enabled))}case"list_domains":return T(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let u=q(s,"hostname");return u?T(e,await n.domainAction(i,o.split("_")[0],u)):T(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let u=await n.restoreWindow(i),f=u&&u.restore?An(i,u.restore):null;return T(e,f?{...u,reason:f}:u)}case"restore_to_point":{let u=q(s,"at");return u?T(e,await n.restoreTo(i,u,q(s,"name")??void 0)):T(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let u=q(s,"ref"),f=q(s,"name");return!u||!f?T(e,"deploy_function needs a project ref and a function name.",!0):T(e,await n.deployFunction(u,f,{dir:q(s,"dir")??void 0,entrypoint:q(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let u=q(s,"ref");return u?T(e,await n.listFunctions(u)):T(e,"list_functions needs a project ref.",!0)}case"size_function":{let u=q(s,"ref"),f=q(s,"name");if(!u||!f)return T(e,"size_function needs a project ref and a function name.",!0);let h=typeof s.memoryMb=="number"?s.memoryMb:void 0,m=typeof s.concurrency=="number"?s.concurrency:void 0,p=s.reset===!0;return!p&&h===void 0&&m===void 0?T(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):T(e,await n.sizeFunction(u,f,{memoryMb:h,concurrency:m,reset:p}))}case"delete_function":{let u=q(s,"ref"),f=q(s,"name");return!u||!f?T(e,"delete_function needs a project ref and a function name.",!0):T(e,await n.deleteFunction(u,f))}case"list_function_secrets":{let u=q(s,"ref");return u?T(e,await n.listFunctionSecrets(u)):T(e,"list_function_secrets needs a project ref.",!0)}case"list_tokens":return T(e,await n.listTokens());case"create_token":{let u=q(s,"name");if(!u)return T(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let f=q(s,"expires"),h=f?Xc(f):void 0;return h===null?T(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${f}".`,!0):T(e,await n.createToken(u,h,q(s,"project")??void 0))}case"revoke_token":{let u=q(s,"id");return u?T(e,await n.revokeToken(u)):T(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return T(e,await n.pushMigrations(i,{dir:q(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?T(e,await n.setProjectState("delete",i)):T(e,"delete_project is off. The person running this server started it without --allow-delete, so deleting a database is theirs to do. Tell them that is what you wanted.",!0);default:return r.borrowed?.some(u=>u.name===o)?n.callBorrowed?et(e,await n.callBorrowed(o,s)):T(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):Tn(e,-32602,`unknown tool: ${o}`)}}catch(u){return T(e,u instanceof Error?u.message:String(u),!0)}}function es(e){let t=e.split(`
325
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var jt=["auth","storage","data-api","push"];function Pe(e){return{error:e instanceof Error?e.message:String(e)}}async function je(e){let[t,n,r,o]=await Promise.all([x("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Pe),x("cloud-project-storage",{ref:e}).then(s=>({enabled:s.storage?.enabled===!0,pending:s.storage?.pending===!0,bytes:s.storage?.bytes??null,files:s.storage?.files??null}),Pe),x("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Pe),x("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Pe)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o}}function ts(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!jt.includes(t))throw new y(`say which product: ${jt.join(", ")}`);return t}async function Rn(e,t,n){if(t==="auth"){let r=await x("cloud-project-auth",{ref:e,action:n?"enable":"disable"});return{ref:e,product:t,enabled:n,note:r.note??(n?"Auth starts within a minute.":"Auth stops. Your users stay in the auth schema.")}}return t==="push"?(await x("cloud-project-push",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?"Push starts within a minute. It runs beside the database, so the database restarts once. Set keys with: snoutdata push credentials":"Push is off. Devices, the log and the keys stay in the push schema."}):(await x(t==="storage"?"cloud-project-storage":"cloud-project-data-api",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?`${t} starts within a minute, when the host picks up the change.`:`${t} is off.`})}async function ns(e){let t=await je(e);g(t,()=>{let n=(l,i,d="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?$("on"):"off",d],r=t.storage,o=t.dataApi;process.stdout.write(`${F([["PRODUCT","STATE",""],n("auth",t.auth),n("storage",r,"pending"in r&&r.pending?"waiting for the host":""),n("data-api",o,"allowedOnPlan"in o&&!o.allowedOnPlan?"paid plans only":""),n("push",t.push,"scheduling"in t.push&&t.push.enabled&&!t.push.scheduling?"scheduled sends on paid plans":"")])}
326
- `);let s=l=>!!(l&&typeof l=="object"&&"enabled"in l&&!l.enabled),a=["auth","storage","data-api","push"].find(l=>s(l==="data-api"?o:t[l]));a&&c(S(` snoutdata products enable ${a} --ref ${e}`))})}async function rs(e,t,n){let r=await Rn(e,t,n);g(r,()=>c(r.note))}async function Ct(e){let t=await x("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function On(e,t,n){if(!n)throw new y(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await x("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Qc(e,t){let n=[["TYPE","NAME","VALUE"]];e.record&&n.push([e.record.type,e.record.name,e.record.value]),e.certificateRecord&&n.push([e.certificateRecord.type,e.certificateRecord.name,e.certificateRecord.value]),n.push(["CNAME",e.hostname,`${t}.api.snoutdata.com`]),process.stdout.write(`${F(n)}
327
- `)}async function os(e,t,n){if(t===void 0||t==="list"){let o=await Ct(e);g(o,()=>{if(o.domains.length===0){c(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${F([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":$("no"),s.certificate?.state??""])])}
328
- `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new y(`unknown command: domains ${t}`);let r=await On(e,t,n??"");g(r,()=>{if(t==="remove"){c(`${n} removed.`);return}r.domain&&(c(r.domain.verified?`${n} is verified.`:r.domain.verificationError??`${n} is not verified yet. Publish these records, then \`snoutdata domains verify ${n}\`:`),r.domain.verified||Qc(r.domain,e)),r.next&&c(r.next)})}async function Nn(e){return x("cloud-project-restore",{ref:e},"GET")}async function Dn(e,t,n){if(Number.isNaN(Date.parse(t)))throw new y(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return x("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function ss(e){let t=await Nn(e);g(t,()=>{let n=t.restore,r=An(e,n);if(r){c(r);return}c(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),c(S(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function as(e,t,n){let r=await Dn(e,t,n);g(r,()=>{c(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${$(r.project.name)} (${r.project.ref}).`),c(S(" snoutdata projects list to see when it is ready"))})}async function Mn(e){let{projects:t}=await Q(),n=t.find(l=>l.ref===e);if(!n)throw new y(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([je(e),x("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),Pe),x("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),Pe),Ct(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Pe)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function is(e){let t=await Mn(e);g(t,()=>{let n=t,r=s=>s&&typeof s=="object"&&"enabled"in s?s.enabled?"on":"off":"?",o=s=>Array.isArray(s)?s.length?s.map(a=>typeof a=="string"?a:a.hostname).join(", "):"none":"could not read";process.stdout.write(`${F([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],["auth / storage / data-api / push",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)} / ${r(n.products.push)}`],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
329
- `)})}G();Z();Oe();G();K();async function fs(e){try{let{projects:t}=await Q(),n=t.find(r=>r.ref===e);n?.readOnly?V(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&V(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function jn(e){await fs(e);let t=await Y(e);g(t,()=>{process.stdout.write(`${t.uri}
330
- `)})}async function ps(e,t){if(t.length===0&&!ge()){let o=Re();R("usage",`db psql with no statements opens an interactive session, and ${o?Ae(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await fs(e);let n=await Y(e);n.wakesInstantly||c("This project is paused; the connection will wake it, which takes a few seconds.");let r=tt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,PGSSLMODE:n.ssl==="disable"?"disable":"require"}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new U("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new U("failed",String(a)))}),r.on("close",a=>o(a??0))})}function gs(e,t,n="DATABASE_URL"){let r=Ft(e,".env"),o=`${n}=${t}`,s=rl(e);if(!Ut(r))return Ce(r,`${o}
331
- `),{path:r,replaced:!1,ignored:s};let a=ds(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(Ce(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(us(r,a.endsWith(`
571
+ `:""))}async function Wo(e,t){for(let r of t){let o=it(r.name);if(o)throw new h(o)}let n=Ln(e);for(let r of t)n.set(r.name,r.value);Ko(e,n),await Gt(e),g({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)c(`${v(r.name)} set in ${Kt(e)}`)})}function Wt(e){return{ref:e.ref,local:e.folder,secrets:[...Ln(e).keys()].sort().map(t=>({name:t}))}}function Jo(e){let t=Wt(e),n=t.secrets.map(r=>r.name);g(t,()=>{c(n.length?n.join(`
572
+ `):`No function secrets in ${Kt(e)}.`)})}async function zo(e,t){let n=Ln(e);if(!n.delete(t))throw new N("not-found",`${t} is not set for this local project`);Ko(e,n),await Gt(e),g({ref:e.ref,unset:t},()=>c(`${t} unset.`))}Q();Ce();we();Pn();Ot();q();Ft();W();W();Q();we();q();async function Vo(){let{projects:e,allowance:t}=await ne();g({projects:e,allowance:t},()=>{if(e.length===0){c('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${F([["REF","NAME","STATE","REGION","LAST CONNECTION"],...e.map(n=>[n.ref,n.name,n.readOnly?v("read-only"):n.state==="ready"?n.state:v(n.state),n.region,fe(n.lastConnectionAt)])])}
573
+ `)})}async function Un(e){let t=await _("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;c(`Created ${n}.`);let r=t.project;if(e.wait){c("Waiting for it to start\u2026");let s="";r=await at(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,c(b(` ${a}`)))}})}e.link&&Ye(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;g({project:r,ref:n,host:r.host,database:r.database,user:r.user,...e.showUrl?{password:t.password,uri:o}:{connectionString:"snoutdata db url"}},()=>{process.stdout.write(`${e.showUrl?o:n}
574
+ `),c(""),c(`Ready. ${v(n)} in ${r.region}, at ${r.host}.`),c(e.showUrl?"The password is stored for you: `snoutdata db url` prints this again.":"`snoutdata db url` prints the connection string (it holds the password, so it is not shown here).")})}async function Yo(e,t,n={}){let r=`cloud-project-${e}`,o=await _(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){g({...o,settled:!1},()=>c(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await st(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,c(b(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new N("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});g({...o,...l?{project:l}:{},settled:!0},()=>{c(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function Xo(e){let t=await _("cloud-project-reset-password",{ref:e});g(t,()=>{process.stdout.write(`${t.password}
575
+ `),c(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function Zo(){let{teams:e}=await Bt();g({teams:e},()=>{if(e.length===0){c("You are not on any teams.");return}process.stdout.write(`${F([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
576
+ `)})}async function Qo(e){let{teams:t}=await Bt(),n=t.find(s=>s.id===e),r=n?[n]:t.filter(s=>s.name.toLowerCase()===e.toLowerCase());if(r.length===0)throw new Error(t.length===0?`You are not on any teams, so there is no "${e}" to share with.`:`No team called "${e}". You are on: ${t.map(s=>s.name).join(", ")}.`);if(r.length>1)throw new Error(`More than one team is called "${e}". Use the id: ${r.map(s=>s.id).join(", ")}.`);let o=r[0];if(!o.mayShare)throw new Error(`${o.name}: ${o.why??"you cannot share a project into this team"}.`);return o.id}W();q();async function es(e){let t=await _("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});g(t,()=>{process.stdout.write(`${t.token}
577
+ `),c(""),c(`${v(t.name)} \u2014 ${t.expiresAt?`expires ${t.expiresAt.slice(0,10)}`:"does not expire"}.`),c(t.project?`Reaches project ${t.project} only.`:"Reaches every project on this account."),c("This is the only time it is shown: only its hash is stored."),c(b(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function ts(){let{tokens:e}=await _("cloud-token-list");g({tokens:e},()=>{if(e.length===0){c("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${F([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?v(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",fe(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
578
+ `)})}async function ns(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await _("cloud-token-revoke",t);g(n,()=>{c(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}W();te();q();async function rs(e){let t=await ee(e);g({ref:t.ref,anonKey:t.anonKey,serviceRoleKey:t.serviceRoleKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.anonKey||!t.serviceRoleKey){c(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${ss(t.anonKey,t.serviceRoleKey)}
579
+ `),wl(t.keysIssuedAt)})}async function os(e,t){if(!t.force)throw c(`Rotating the keys for ${e} stops every anon and service_role key already pasted anywhere.`),c("Anything holding one (a deployed app, a CI secret, a teammate) breaks until it is updated."),new h("keys rotate needs --force, because this cannot be undone");let n=await _("cloud-project-rotate-jwt",{ref:e});g(n,()=>{process.stdout.write(`${ss(n.anonKey,n.serviceRoleKey)}
580
+ `),c(""),c(n.breaks),c(b(" snoutdata keys --json to read them again later"))})}function ss(e,t){return F([["KEY","VALUE"],["anon",e],["service_role",t]])}function wl(e){c(""),c(`${v("anon")} is the public one: it goes in a browser, and row-level security decides what it reads.`),c(`${v("service_role")} bypasses row-level security. Server side only, never in a client bundle.`),e&&c(b(` issued ${e.slice(0,10)}`))}W();te();q();import{readdir as Sl,readFile as _l,stat as xl}from"node:fs/promises";import{join as Bn,relative as kl,sep as vl}from"node:path";var as=["functions"];async function Fn(e){try{return(await xl(e)).isDirectory()}catch{return!1}}async function El(e,t){if(t){if(!await Fn(t))throw new h(`${t} is not a directory`);return t}for(let n of as){let r=Bn(n,e);if(await Fn(r))return r}throw new h(`no folder found for ${e} at ${as[0]}/${e}; use --dir to say where it is.`)}async function is(e){let t=[];async function n(r){for(let o of await Sl(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Bn(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await _l(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new h(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:kl(e,s).split(vl).join("/"),text:l})}}return await n(e),t}async function $l(e,t,n){let r=await is(t);if(r.length===0)throw new h(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new h(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Bn(t,"..","_shared");if(!await Fn(s))return{entrypoint:o,files:r};let a=(await is(s)).map(l=>({path:`_shared/${l.path}`,text:l.text}));return{entrypoint:`${e}/${o}`,files:[...r.map(l=>({path:`${e}/${l.path}`,text:l.text})),...a]}}async function Hn(e,t,n){Re(t);let r=await El(t,n.dir),{entrypoint:o,files:s}=await $l(t,r,n.entrypoint),a=Mo({entrypoint:o,files:s});return{answer:await _("cloud-project-functions",{ref:e,action:"deploy",name:t,verifyJwt:n.verifyJwt,bundle:a}),directory:r,files:s.length,bytes:JSON.stringify(a).length}}async function cs(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await Hn(e,t,n),l=r.functions.find(i=>i.name===t);g(r,()=>{let i=Math.max(1,Math.round(a/1024));c(`${v(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),l&&c(` ${l.url}`),n.verifyJwt||(c(""),c(v(" Anybody who knows this URL can run it. No API key is required.")),c(b(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&c(b(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function Gn(e){return _("cloud-project-functions",{ref:e})}function qn(e,t){return Re(t),_("cloud-project-functions",{ref:e,action:"delete",name:t})}function Kn(e,t,n){if(Re(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new h("functions size needs --memory MB, --concurrency N, or --reset");return _("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function Al(e,t){if(e.memoryMb==null||e.concurrency==null)return null;let n=e.memoryMb*e.concurrency,r=t.podMemoryMb?` of ${t.podMemoryMb} MB`:"";return`${e.memoryMb} MB x ${e.concurrency} worker${e.concurrency===1?"":"s"} = ${n} MB${r}`}function ls(e){return e.functionMemoryMb==null||e.concurrencyMax==null||e.podMemoryMb==null?null:`${e.tier??"this"} plan: up to ${e.functionMemoryMb} MB a worker and ${e.concurrencyMax} workers a function, memory x workers up to ${e.podMemoryMb} MB`}async function ds(e,t,n){let r=await Kn(e,t,n),o=r.functions.find(s=>s.name===t);g(r,()=>{let s=o?Al(o,r.limit):null;c(`${v(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),c(b(" The next request runs at it; workers already running finish what they hold."));let a=ls(r.limit);a&&c(b(` ${a}`))})}async function us(e){let t=await Gn(e);g(t,()=>{if(t.functions.length===0){c(`${e} has no functions.`),c(b(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${F([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":v("open"),r.bytes===null?"-":`${Math.max(1,Math.round(r.bytes/1024))} KB`,r.memoryMb==null?"-":`${r.memoryMb} MB`,r.concurrency==null?"-":String(r.concurrency),r.url])])}
581
+ `),t.limit.maxFunctions!==null&&c(b(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=ls(t.limit);n&&c(b(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}async function ms(e,t){let n=await qn(e,t);g(n,()=>{c(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}W();te();q();function fs(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function ps(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function gs(e,t){if(e.length===0)throw new h("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new h("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await ps()}]}return e.map(n=>{let r=fs(n);if(!r)throw new h(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function hs(e,t,n){if(t.length===0)throw new h("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new h("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await ps()})}else for(let s of t){let a=fs(s);if(!a)throw new h(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=it(s.name);if(a)throw new h(a)}let o=null;for(let s of r)o=await _("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});g(o,()=>{for(let s of r)c(`${v(s.name)} set`);c(b(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||c(b(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function ys(e,t){let n=it(t);if(n)throw new h(n);let r=await _("cloud-project-secrets",{ref:e,action:"unset",name:t});g(r,()=>{c(`${t} is gone.`)})}async function ws(e){let t=await _("cloud-project-secrets",{ref:e});g(t,()=>{if(t.secrets.length===0){c(`${e} has no function secrets.`),c(b(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${F([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,fe(n.updatedAt)])])}
582
+ `),c(b("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as dt}from"node:child_process";import{appendFileSync as Ms,existsSync as Zt,readFileSync as Cs,unlinkSync as Jl,writeFileSync as Be}from"node:fs";import{dirname as zl,join as Qt}from"node:path";import{tmpdir as Vl}from"node:os";import{open as js}from"node:fs/promises";function bs(e){let t=(r,o)=>String.fromCharCode(...Array.from(e.slice(r,r+o)));return t(0,5)==="PGDMP"?"custom":e.length>=262&&t(257,5)==="ustar"?"tar":Array.from(e.slice(0,256)).every(r=>r===9||r===10||r===13||r>=32&&r<127||r>=128)&&e.length>0?"plain":"unknown"}function Ss(e,t,n={}){return t.readOnly?{ok:!1,code:"read-only",reason:"this project is over its plan's storage limit, so it refuses writes. A restore would fail part way. Delete some rows or change plan first."}:e==="unknown"?{ok:!1,code:"unknown-format",reason:"that file is not a pg_dump archive and does not look like SQL. `snoutdata db export` writes the custom format; psql reads plain .sql."}:!n.force&&t.existingTables>0?{ok:!1,code:"not-empty",reason:`this database already has ${t.existingTables} table${t.existingTables===1?"":"s"}. Restoring into it would leave some objects created and some not. Check --ref is the project you meant, or pass --force.`}:{ok:!0,tool:e==="plain"?"psql":"pg_restore",format:e}}function _s(e){let t=e.split(`
583
+ `).filter(r=>/^pg_restore: error:/.test(r.trim())),n=t.filter(r=>!/must be owner of extension/.test(r));return{total:t.length,harmless:t.length-n.length,fatal:n}}var Wn={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},Tl=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Jt=new Set(["auth","storage","push","realtime","snout_realtime","_realtime","graphql","graphql_public","extensions","net","cron","pgbouncer","vault","pgsodium"]),Ol=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,Nl={auth:"auth",storage:"storage",push:"push"},Dl=new Set(["push.settings"]),Il=["TABLE DATA","SEQUENCE SET","SEQUENCE OWNED BY","FK CONSTRAINT","CHECK CONSTRAINT","DEFAULT ACL","EVENT TRIGGER","ROW SECURITY","INDEX ATTACH","TABLE ATTACH","MATERIALIZED VIEW DATA","MATERIALIZED VIEW","PUBLICATION TABLES IN SCHEMA","PUBLICATION TABLE","FOREIGN TABLE","FOREIGN DATA WRAPPER","FOREIGN SERVER","USER MAPPING","TEXT SEARCH CONFIGURATION","TEXT SEARCH DICTIONARY","TEXT SEARCH PARSER","TEXT SEARCH TEMPLATE","LARGE OBJECT","PROCEDURAL LANGUAGE","OPERATOR CLASS","OPERATOR FAMILY","ACCESS METHOD","EXTENDED STATISTICS","DATABASE PROPERTIES"];function Ml(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=Il.find(i=>n.startsWith(`${i} `))??n.split(" ")[0],o=n.slice(r.length).trim().split(" "),s=o[0]??"-",a=o.length>2?o[o.length-1]:"",l=o.slice(1,o.length>2?-1:void 0).join(" ");return{line:e.trim(),type:r,schema:s,name:l,owner:a}}function Cl(e){let t=new Map,n=o=>o.replace(/"/g,""),r=/ALTER TABLE (?:ONLY )?([\w."$]+)\s+ADD CONSTRAINT [^;]*?FOREIGN KEY \([^)]*\) REFERENCES ([\w."$]+)\(/g;for(let o of e.matchAll(r)){let s=n(o[1]),a=n(o[2]);if(s!==a){let l=t.get(s)??new Set;l.add(a),t.set(s,l)}}return t}function xs(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
584
+ `)){let u=Ml(i);if(!u)continue;let{type:d,schema:f,name:y,owner:m}=u;if(d==="TABLE DATA"||d==="SEQUENCE SET"){let S=/^(schema_migrations|migrations)$/.test(y)&&Jt.has(f);if(/realtime/.test(f)||S||f==="public"&&y==="spatial_ref_sys"){s++;continue}if(f==="cron"){s++,o.add("Scheduled jobs (pg_cron) are not restored: each names the project it came from. Schedule them again with cron.schedule.");continue}if(f==="push"&&y==="credentials"){s++,o.add("Push credentials are not restored: they are sealed for the project they came from. Set them again with `snoutdata push credentials set`.");continue}let w=Nl[f];if(w){w==="storage"&&y==="objects"&&o.add("Storage objects are restored as rows; the files themselves are not part of an export. Upload them again into this project."),n.set(w,[...n.get(w)??[],u]);continue}if(Jt.has(f)){s++;continue}r.push(u.line);continue}if(d==="COMMENT"&&f==="-"&&/^EXTENSION( |$)/.test(y)||(d==="SCHEMA"?Jt.has(y):Jt.has(f))||Ol.test(y)||Tl.test(m)&&d!=="SCHEMA"){s++;continue}r.push(u.line)}let a=Cl(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let u=n.get(i),d=u.filter(y=>y.type==="TABLE DATA"&&Dl.has(`${y.schema}.${y.name}`)).map(y=>`${y.schema}.${y.name}`),f=u.filter(y=>y.type==="TABLE DATA").map(y=>`${y.schema}.${y.name}`);return{product:i,clear:d,tables:f,lines:jl(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function jl(e,t){let n=l=>`${l.schema}.${l.name}`,r=new Set(e.map(n)),o=new Set,s=[],a=[...e];for(;a.length>0;){let i=a.find(u=>[...t.get(n(u))??[]].every(d=>!r.has(d)||o.has(d)||d===n(u)))??a[0];o.add(n(i)),s.push(i.line),a=a.filter(u=>u!==i)}return s}W();te();q();var Ll="2024-11-05";function H(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var I={type:"string"},zt=[{name:"whoami",description:"Who this CLI is signed in as, and how (a session or a long-lived access token). Use it to check a credential before doing anything else.",inputSchema:H({})},{name:"list_projects",description:"Every hosted Postgres database on this account, with its ref, name, region, state (ready, paused, error), size, and whether it is read-only because it is over its plan's storage limit. Under `local`, the self-hosted projects on this machine (SnoutData Studio's, or linked with `snoutdata link --local`), whose refs work with get_connection_url, push_migrations, get_project and the function tools.",inputSchema:H({})},{name:"create_project",description:"Make a new hosted Postgres database and wait for it to be ready. Costs a project against the plan allowance; a free plan includes two. Returns its ref, which every other tool takes.",inputSchema:H({name:I,region:I,teamId:I},["name"])},{name:"get_connection_url",description:"The postgres:// URL for a project, ready to put in DATABASE_URL. IT CONTAINS A LIVE PASSWORD, so treat it as a credential and do not print it into a log or a file that is committed. Connecting to a paused project wakes it, which takes a few seconds.",inputSchema:H({ref:I},["ref"])},{name:"pause_project",description:"Stop a project, and wait until it is paused. Its data is kept and the next connection wakes it. A production project refuses this.",inputSchema:H({ref:I},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:H({ref:I},["ref"])},{name:"push_migrations",description:"Run the .sql files in a folder against a project, in name order, once each, recording what ran in a _snoutdata_migrations table. Refuses a file that changed after it ran, a file that has gone, and a new file that sorts before one already applied. Use dryRun first to see what would happen. Needs psql on this machine.",inputSchema:H({ref:I,dir:I,dryRun:{type:"boolean"}},["ref"])},{name:"usage",description:"How much storage and compute a project has used against the limit on this plan, and whether it is near it. READ THIS BEFORE a migration or a bulk insert: a project over its storage limit is made read-only, and `list_projects` will say readOnly afterwards without ever saying how much headroom was left. Takes an optional number of days of history (default 30, maximum 365).",inputSchema:H({ref:I,days:{type:"number"}},["ref"])},{name:"export_status",description:"Whether a copy of this database is being taken, when the last one finished, and a download link if there is a live one. Costs nothing and starts nothing.",inputSchema:H({ref:I},["ref"])},{name:"start_export",description:"Take a copy of the whole database. Runs a pg_dump on the server and can take minutes, so this STARTS it and returns; poll export_status for the link. Worth doing before a migration you are unsure of. It does not block the database.",inputSchema:H({ref:I},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING within a few seconds, including any DATABASE_URL already written into a file or a running process; connections already open keep working. Returns the new connection URL, which works once the change applies (wait about five seconds before connecting). Use it when a credential has leaked, not routinely.",inputSchema:H({ref:I},["ref"])},{name:"list_teams",description:"Teams this account belongs to, with the id create_project takes as teamId. Without this there is no way to discover that id.",inputSchema:H({})},{name:"list_tokens",description:"The long-lived sdt_ access tokens on this account: id, name, prefix, when each was made and last used. Never the token itself, which is shown once when it is made.",inputSchema:H({})},{name:"create_token",description:"Mint a long-lived sdt_ access token, for a CI job or another machine. RETURNS THE TOKEN ONCE AND NEVER AGAIN. Without `project` it is as capable as this account, so hand it over deliberately; with a project ref it reaches that one project and nothing else, which is what a CI job for one project should get. Optionally expires, as an ISO date.",inputSchema:H({name:I,expires:I,project:I},["name"])},{name:"revoke_token",description:"Revoke an access token by its id. Anything using it stops working at once. Safe to do to a token you think has leaked.",inputSchema:H({id:I},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/functions/v1/<name>. Reads functions/<name> unless dir says otherwise. Deno, so no build step and no node_modules. Set openToAnyone ONLY for a webhook receiver whose sender cannot send an API key: it makes the URL callable by anybody who knows it, and the function must then check the sender's signature itself.",inputSchema:H({ref:I,name:I,dir:I,entrypoint:I,openToAnyone:{type:"boolean"}},["ref","name"])},{name:"list_functions",description:"The functions deployed to a project, their URLs, bundle sizes, whether each one needs an API key, and each one's memory and concurrency with the plan's limits for both. Costs nothing.",inputSchema:H({ref:I},["ref"])},{name:"size_function",description:"Set one function's memory (MB one worker may use) and concurrency (how many workers it may run at once), within the project's plan. memoryMb x concurrency may not exceed the project's memory; list_functions shows each function's size and the plan's limits (limit.functionMemoryMb, limit.concurrencyMax, limit.podMemoryMb). A size that does not fit is refused with a sentence saying why. Omit one to keep it as it runs now; reset: true goes back to the plan's default. A second worker only helps CPU-bound work: requests that wait on the network share one.",inputSchema:H({ref:I,name:I,memoryMb:{type:"integer"},concurrency:{type:"integer"},reset:{type:"boolean"}},["ref","name"])},{name:"delete_function",description:"Remove one function from a project. It stops answering within a few seconds. It does not touch the database.",inputSchema:H({ref:I,name:I},["ref","name"])},{name:"list_function_secrets",description:"The NAMES of the environment variables a project's functions run with, their sizes and when each was last set. Never the values, which are not readable by anything but the runtime. THERE IS NO TOOL THAT SETS ONE: a secret in a tool call is a secret in an audit log, so a person sets it with `snoutdata secrets set`. Use this to check whether one a function needs is there.",inputSchema:H({ref:I},["ref"])},{name:"get_project",description:"Everything about one project in one call: its state, region and size, which products are on (auth, storage, data-api), the names of its deployed functions and function secrets, and its custom domains. Never a password, a key or a download link (export_status has the link to the last export).",inputSchema:H({ref:I},["ref"])},{name:"list_products",description:"Whether a project's auth (user sign-up and sign-in), storage (files) and data API (REST and GraphQL over its tables) are on, and whether the plan allows the data API.",inputSchema:H({ref:I},["ref"])},{name:"set_product",description:"Turn auth, storage or data-api on or off for a project. It starts within about a minute. All three are on every plan, including free.",inputSchema:H({ref:I,product:{type:"string",enum:["auth","storage","data-api"]},enabled:{type:"boolean"}},["ref","product","enabled"])},{name:"list_domains",description:"The custom domains in front of a project's API, whether each is verified, and the DNS records each needs.",inputSchema:H({ref:I},["ref"])},{name:"add_domain",description:"Serve a project's API at the user's own hostname (paid plans). Returns the DNS records the user must publish before verify_domain can succeed.",inputSchema:H({ref:I,hostname:I},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:H({ref:I,hostname:I},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:H({ref:I,hostname:I},["ref","hostname"])},{name:"restore_window",description:"How far back a point-in-time restore of a project can go, or why it cannot (the plan, or no backup yet). Starts nothing.",inputSchema:H({ref:I},["ref"])},{name:"restore_to_point",description:"Rewind a project to a moment (ISO 8601, inside restore_window) into a NEW project beside it, never over it. Costs a project slot on the plan. The original is untouched.",inputSchema:H({ref:I,at:I,name:I},["ref","at"])},{name:"delete_project",description:"Delete a project. THIS DESTROYS A DATABASE. Off unless the person who started this server passed --allow-delete, in which case it still only marks the project deleted and its data is erasable for a grace period.",inputSchema:H({ref:I},["ref"])}],Ul=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","get_project","list_products","list_domains","restore_window"]),Fl=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain"]),ks=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function Jn(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(Jn)};let n=t.export;if(n&&typeof n=="object"){let{url:r,rolesSql:o,...s}=n;return{...t,export:{...s,hasLink:typeof r=="string"&&r.length>0}}}return e}function Vn(e,t){return t.available?null:t.pitrEnabled?`${e} has no backup to restore from yet.`:`Point-in-time restore is part of the Pro and Business plans, and this project is on ${t.tier??"another plan"}.`}function Bl(e){return{...e,annotations:Ul.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:Fl.has(e.name)}}}function Hl(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function lt(e,t){return{jsonrpc:"2.0",id:e,result:t}}function zn(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function R(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return lt(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function Gl(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function K(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function vs(e,t,n){let r=e.id??null,o=e.method??"",s=e.params??{};if(e.id===void 0||e.id===null)return null;switch(o){case"initialize":return lt(r,{protocolVersion:Ll,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return lt(r,{});case"tools/list":return lt(r,{tools:Hl([...zt.map(Bl),...n.borrowed??[]])});case"tools/call":return ql(r,s,t,n);default:return zn(r,-32601,`unknown method: ${o}`)}}async function ql(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(zt.some(d=>d.name===o)||(r.borrowed?.some(d=>d.name===o)??!1)))return zn(e,-32602,`unknown tool: ${o}`);let l=["get_connection_url","pause_project","resume_project","delete_project","push_migrations","usage","export_status","start_export","reset_password","get_project","list_products","set_product","list_domains","add_domain","verify_domain","remove_domain","restore_window","restore_to_point"],i=K(s,"ref");if(l.includes(o)&&!i)return R(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!ks.includes(o))return R(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${ks.join(", ")}.`,!0);try{switch(o){case"whoami":return R(e,await n.whoami());case"list_projects":return R(e,Jn(await n.listProjects()));case"create_project":{let d=K(s,"name");return d?R(e,await n.createProject({name:d,region:K(s,"region")??void 0,teamId:K(s,"teamId")??void 0})):R(e,"create_project needs a name.",!0)}case"get_connection_url":{let d=await n.connection(i);return R(e,{url:d.uri,state:d.state,wakesInstantly:d.wakesInstantly,warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"pause_project":return R(e,await n.setProjectState("pause",i));case"resume_project":return R(e,await n.setProjectState("resume",i));case"usage":{let d=typeof s.days=="number"?s.days:void 0;return R(e,await n.usage(i,d))}case"export_status":return R(e,await n.exportStatus(i));case"start_export":return R(e,await n.requestExport(i));case"reset_password":{let d=await n.resetPassword(i),f=await n.connection(i);return R(e,{url:f.uri,appliesIn:d.appliesIn??"within a few seconds",next:"Wait about five seconds before connecting with this URL: until the change applies the new password is refused and the old one still works. Then update DATABASE_URL wherever it is set.",warning:"This URL contains a live database password. Put it in an environment variable, not in a file that is committed."})}case"list_teams":return R(e,await n.listTeams());case"get_project":return R(e,Jn(await n.getProject(i)));case"list_products":return R(e,await n.getProducts(i));case"set_product":{let d=K(s,"product");return d!=="auth"&&d!=="storage"&&d!=="data-api"?R(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?R(e,"set_product needs enabled: true or false.",!0):R(e,await n.setProduct(i,d,s.enabled))}case"list_domains":return R(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let d=K(s,"hostname");return d?R(e,await n.domainAction(i,o.split("_")[0],d)):R(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let d=await n.restoreWindow(i),f=d&&d.restore?Vn(i,d.restore):null;return R(e,f?{...d,reason:f}:d)}case"restore_to_point":{let d=K(s,"at");return d?R(e,await n.restoreTo(i,d,K(s,"name")??void 0)):R(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let d=K(s,"ref"),f=K(s,"name");return!d||!f?R(e,"deploy_function needs a project ref and a function name.",!0):R(e,await n.deployFunction(d,f,{dir:K(s,"dir")??void 0,entrypoint:K(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let d=K(s,"ref");return d?R(e,await n.listFunctions(d)):R(e,"list_functions needs a project ref.",!0)}case"size_function":{let d=K(s,"ref"),f=K(s,"name");if(!d||!f)return R(e,"size_function needs a project ref and a function name.",!0);let y=typeof s.memoryMb=="number"?s.memoryMb:void 0,m=typeof s.concurrency=="number"?s.concurrency:void 0,p=s.reset===!0;return!p&&y===void 0&&m===void 0?R(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):R(e,await n.sizeFunction(d,f,{memoryMb:y,concurrency:m,reset:p}))}case"delete_function":{let d=K(s,"ref"),f=K(s,"name");return!d||!f?R(e,"delete_function needs a project ref and a function name.",!0):R(e,await n.deleteFunction(d,f))}case"list_function_secrets":{let d=K(s,"ref");return d?R(e,await n.listFunctionSecrets(d)):R(e,"list_function_secrets needs a project ref.",!0)}case"list_tokens":return R(e,await n.listTokens());case"create_token":{let d=K(s,"name");if(!d)return R(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let f=K(s,"expires"),y=f?Gl(f):void 0;return y===null?R(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${f}".`,!0):R(e,await n.createToken(d,y,K(s,"project")??void 0))}case"revoke_token":{let d=K(s,"id");return d?R(e,await n.revokeToken(d)):R(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return R(e,await n.pushMigrations(i,{dir:K(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?R(e,await n.setProjectState("delete",i)):R(e,"delete_project is off. The person running this server started it without --allow-delete, so deleting a database is theirs to do. Tell them that is what you wanted.",!0);default:return r.borrowed?.some(d=>d.name===o)?n.callBorrowed?lt(e,await n.callBorrowed(o,s)):R(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):zn(e,-32602,`unknown tool: ${o}`)}}catch(d){return R(e,d instanceof Error?d.message:String(d),!0)}}function Es(e){let t=e.split(`
585
+ `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Vt=["auth","storage","data-api","push"];function Te(e){return{error:e instanceof Error?e.message:String(e)}}async function Fe(e){let[t,n,r,o]=await Promise.all([_("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Te),_("cloud-project-storage",{ref:e}).then(s=>({enabled:s.storage?.enabled===!0,pending:s.storage?.pending===!0,bytes:s.storage?.bytes??null,files:s.storage?.files??null}),Te),_("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Te),_("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Te)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:Kl}}var Kl={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function $s(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Vt.includes(t))throw new h(`say which product: ${Vt.join(", ")}`);return t}async function Yn(e,t,n){if(t==="auth"){let r=await _("cloud-project-auth",{ref:e,action:n?"enable":"disable"});return{ref:e,product:t,enabled:n,note:r.note??(n?"Auth starts within a minute.":"Auth stops. Your users stay in the auth schema.")}}return t==="push"?(await _("cloud-project-push",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?"Push starts within a minute. It runs beside the database, so the database restarts once. Set keys with: snoutdata push credentials":"Push is off. Devices, the log and the keys stay in the push schema."}):(await _(t==="storage"?"cloud-project-storage":"cloud-project-data-api",{ref:e,enable:n}),{ref:e,product:t,enabled:n,note:n?`${t} starts within a minute, when the host picks up the change.`:`${t} is off.`})}async function As(e){let t=await Fe(e);g(t,()=>{let n=(l,i,u="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?v("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${F([["PRODUCT","STATE",""],n("auth",t.auth),n("storage",r,"pending"in r&&r.pending?"waiting for the host":""),n("data-api",o,"allowedOnPlan"in o&&!o.allowedOnPlan?"not on this plan":""),n("push",t.push,"scheduling"in t.push&&t.push.enabled&&!t.push.scheduling?"scheduled sends on paid plans":""),n("realtime",t.realtime,"always on: broadcast, presence; table changes on Plus and Pro")])}
586
+ `);let s=l=>!!(l&&typeof l=="object"&&"enabled"in l&&!l.enabled),a=["auth","storage","data-api","push"].find(l=>s(l==="data-api"?o:t[l]));a&&c(b(` snoutdata products enable ${a} --ref ${e}`))})}async function Ps(e,t,n){let r=await Yn(e,t,n);g(r,()=>c(r.note))}async function Yt(e){let t=await _("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function Xn(e,t,n){if(!n)throw new h(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await _("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Wl(e,t){let n=[["TYPE","NAME","VALUE"]];e.record&&n.push([e.record.type,e.record.name,e.record.value]),e.certificateRecord&&n.push([e.certificateRecord.type,e.certificateRecord.name,e.certificateRecord.value]),n.push(["CNAME",e.hostname,`${t}.api.snoutdata.com`]),process.stdout.write(`${F(n)}
587
+ `)}async function Rs(e,t,n){if(t===void 0||t==="list"){let o=await Yt(e);g(o,()=>{if(o.domains.length===0){c(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${F([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":v("no"),s.certificate?.state??""])])}
588
+ `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new h(`unknown command: domains ${t}`);let r=await Xn(e,t,n??"");g(r,()=>{if(t==="remove"){c(`${n} removed.`);return}r.domain&&(c(r.domain.verified?`${n} is verified.`:r.domain.verificationError??`${n} is not verified yet. Publish these records, then \`snoutdata domains verify ${n}\`:`),r.domain.verified||Wl(r.domain,e)),r.next&&c(r.next)})}async function Zn(e){return _("cloud-project-restore",{ref:e},"GET")}async function Qn(e,t,n){if(Number.isNaN(Date.parse(t)))throw new h(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return _("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function Ts(e){let t=await Zn(e);g(t,()=>{let n=t.restore,r=Vn(e,n);if(r){c(r);return}c(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),c(b(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function Os(e,t,n){let r=await Qn(e,t,n);g(r,()=>{c(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${v(r.project.name)} (${r.project.ref}).`),c(b(" snoutdata projects list to see when it is ready"))})}async function er(e){let{projects:t}=await ne(),n=t.find(l=>l.ref===e);if(!n)throw new h(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([Fe(e),_("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),Te),_("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),Te),Yt(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Te)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function Ns(e){let t=await er(e);g(t,()=>{let n=t,r=s=>s&&typeof s=="object"&&"enabled"in s?s.enabled?"on":"off":"?",o=s=>Array.isArray(s)?s.length?s.map(a=>typeof a=="string"?a:a.hostname).join(", "):"none":"could not read";process.stdout.write(`${F([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],["auth / storage / data-api / push",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)} / ${r(n.products.push)}`],["realtime","on (broadcast and presence; table changes on Plus and Pro)"],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
589
+ `)})}W();Q();Ce();W();q();async function Ls(e){try{let{projects:t}=await ne(),n=t.find(r=>r.ref===e);n?.readOnly?J(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&J(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function nr(e){await Ls(e);let t=await ee(e);g(t,()=>{process.stdout.write(`${t.uri}
590
+ `)})}async function Us(e,t){if(t.length===0&&!_e()){let o=Me();O("usage",`db psql with no statements opens an interactive session, and ${o?Ie(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await Ls(e);let n=await ee(e);n.wakesInstantly||c("This project is paused; the connection will wake it, which takes a few seconds.");let r=dt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,PGSSLMODE:n.ssl==="disable"?"disable":"require"}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new N("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new N("failed",String(a)))}),r.on("close",a=>o(a??0))})}function Fs(e,t,n="DATABASE_URL"){let r=Qt(e,".env"),o=`${n}=${t}`,s=Yl(e);if(!Zt(r))return Be(r,`${o}
591
+ `),{path:r,replaced:!1,ignored:s};let a=Cs(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(Be(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(Ms(r,a.endsWith(`
332
592
  `)?`${o}
333
593
  `:`
334
594
  ${o}
335
- `),{path:r,replaced:!1,ignored:s})}function rl(e){let t=e;for(;!Ut(Ft(t,".git"));){let s=tl(t);if(s===t)return!1;t=s}let n=Ft(e,".gitignore"),r=Ut(n)?ds(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(us(n,r===""||r.endsWith(`
595
+ `),{path:r,replaced:!1,ignored:s})}function Yl(e){let t=e;for(;!Zt(Qt(t,".git"));){let s=zl(t);if(s===t)return!1;t=s}let n=Qt(e,".gitignore"),r=Zt(n)?Cs(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(Ms(n,r===""||r.endsWith(`
336
596
  `)?`.env
337
597
  `:`
338
598
  .env
339
- `),!0)}async function hs(e,t={}){if(t.statusOnly){let{export:a}=await Me(e);return g({ref:e,export:a},()=>In(a)),0}let n=await Ve(e);if(!n.export.pending&&n.export.url&&!t.out)return g({ref:e,export:n.export},()=>In(n.export)),0;c("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await sl(e,t.timeoutMs);if(r.error&&R("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return g({ref:e,export:r},()=>In(r)),0;r.url||R("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await al(r.url,t.out),s=r.rolesSql?ol(t.out):null;return s&&r.rolesSql&&Ce(s,r.rolesSql.endsWith(`
599
+ `),!0)}async function Bs(e,t={}){if(t.statusOnly){let{export:a}=await Le(e);return g({ref:e,export:a},()=>tr(a)),0}let n=await ot(e);if(!n.export.pending&&n.export.url&&!t.out)return g({ref:e,export:n.export},()=>tr(n.export)),0;c("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Zl(e,t.timeoutMs);if(r.error&&O("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return g({ref:e,export:r},()=>tr(r)),0;r.url||O("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await Ql(r.url,t.out),s=r.rolesSql?Xl(t.out):null;return s&&r.rolesSql&&Be(s,r.rolesSql.endsWith(`
340
600
  `)?r.rolesSql:`${r.rolesSql}
341
- `,"utf8"),g({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{c(`Saved ${de(o)} to ${t.out}.`),c(S(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(c(`Saved the roles it needs to ${s}.`),c(S("Into any other Postgres, in this order:")),c(S(` psql --dbname <your database> -f ${s}`)),c(S(` pg_restore --dbname <your database> ${t.out}`)),c(S("The roles are created without passwords. Set them yourself if anything signs in as one."))):(c(S("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),c(S("Owners and grants are skipped: this export carries no roles file.")))}),0}function ol(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function sl(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await Me(e);if(!r.pending)return r;if(Date.now()>=n)throw new Error(`the export of ${e} was still running after waiting`);await new Promise(o=>setTimeout(o,3e3))}}async function al(e,t){let n=await fetch(e);if(!n.ok||!n.body)throw new Error(`the download failed: ${n.status} ${n.statusText}`);let r=await ms(t,"w"),o=0;try{for await(let s of n.body)await r.write(s),o+=s.byteLength}finally{await r.close()}return o}function In(e){if(e.error){V(`The last export did not finish: ${e.error}`);return}if(e.pending){c("An export is running. Ask again in a minute, or run `snoutdata db export`, which waits for it.");return}if(!e.completedAt){c("This project has never been exported.");return}c(`A copy taken ${ue(e.completedAt)}, ${de(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
342
- `),e.urlExpiresAt&&c(S(`That link stops working ${ue(e.urlExpiresAt)}.`))):c(S("The link has expired. Run the export again to sign a new one; the copy is kept."))}function de(e){if(e<1024)return`${e} B`;let t=["KB","MB","GB","TB"],n=e/1024,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n.toFixed(n>=10?0:1)} ${t[r]}`}async function ys(e,t){let n=t.file;n||R("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),Ut(n)||R("usage",`no file at ${n}`,{file:n});let r=await ms(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=zo(o.slice(0,s.bytesRead)),l=await Y(e);l.wakesInstantly||c("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await Q(),d=i.find(b=>b.ref===e),u=await il(l);u===null&&R("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let f=Vo(a,{existingTables:u,readOnly:d?.readOnly===!0},{force:t.force});f.ok||R("conflict",f.reason,{ref:e,file:n,refusal:f.code});let h=null;if(f.tool==="pg_restore"){let b=await ls("pg_restore",["--list",n]),w=await ls("pg_restore",["--section=post-data","--file=-",n]);if((b.code!==0||w.code!==0)&&R("failed",`pg_restore could not read ${n}: ${(b.stderr||w.stderr).trim().slice(0,300)}`,{ref:e,file:n}),h=Xo(b.stdout,w.stdout),h.products.length>0){let _=await je(e),v=k=>!!(k&&typeof k=="object"&&"enabled"in k&&k.enabled),A=h.products.filter(k=>!v(_[k]));A.length>0&&R("conflict",`this dump holds ${A.length>1?`${A.slice(0,-1).join(", ")} and ${A[A.length-1]}`:A[0]} data, and ${e} has ${A.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${A.length===1?"it":"them"} on, wait a minute, and run this again:
343
- ${A.map(k=>` snoutdata products enable ${k} --ref ${e}`).join(`
344
- `)}`,{ref:e,file:n,refusal:"products-off",products:A})}}if(h&&h.platformData.length>0){let b=h.platformData.flatMap(({tables:v})=>v),w=await cl(l,b);w===null&&R("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let _=h.platformData.filter(({tables:v})=>v.some(A=>w.includes(A))).map(({product:v})=>v);_.length>0&&R("conflict",`${_.join(", ")} on ${e} ${_.length===1?"has":"have"} not finished setting up ${_.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:_})}c(`Restoring ${n} into ${e} with ${f.tool} (${f.format} format).`);let m=h?await ll(l,n,h):await Lt(l,f.tool,n),p=Yo(m.stderr);for(let b of h?.notes??[])c(S(b));return m.code!==0&&p.fatal.length===0&&p.total>0?(g({ref:e,file:n,ok:!0,tool:f.tool,format:f.format,ignoredErrors:p.harmless},()=>{c(`Restored into ${e}.`),c(`${p.harmless} error${p.harmless===1?"":"s"} above ${p.harmless===1?"is":"are"} expected and nothing is missing: a dump carries comments on the extensions this image installs, and your role does not own them.`)}),0):(m.code!==0&&R("failed",`${f.tool} exited ${m.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:p.fatal}),g({ref:e,file:n,ok:!0,tool:f.tool,format:f.format},()=>c(`Restored into ${e}.`)),0)}async function il(e){return new Promise(t=>{let n=tt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc","select count(*) from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind = 'r' and n.nspname not in ('pg_catalog', 'information_schema') and n.nspname not like 'pg\\_toast%' and not exists (select 1 from pg_depend d where d.objid = c.oid and d.deptype = 'e') and pg_get_userbyid(c.relowner) !~ '^(snoutpod_admin|snout_[a-z]+_admin)$'"],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.ssl==="disable"?"disable":"require"}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{V(o.code==="ENOENT"?"psql is not installed, and db restore needs it (and pg_restore for an archive).":String(o)),t(null)}),n.on("close",o=>t(o===0?Number(r.trim())||0:null))})}function cl(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=tt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc",`select t from unnest(array[${n}]) t where to_regclass(t) is null`],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.ssl==="disable"?"disable":"require"}}),s="";o.stdout.on("data",a=>s+=String(a)),o.on("error",()=>r(null)),o.on("close",a=>r(a===0?s.split(`
345
- `).map(l=>l.trim()).filter(Boolean):null))})}function ls(e,t){return new Promise(n=>{let r=tt(e,t,{stdio:["ignore","pipe","pipe"]}),o="",s="";r.stdout.on("data",a=>o+=String(a)),r.stderr.on("data",a=>s+=String(a)),r.on("error",a=>{V(a.code==="ENOENT"?`${e} is not installed, and this dump needs it.`:String(a)),n({code:127,stdout:o,stderr:s})}),r.on("close",a=>n({code:a??0,stdout:o,stderr:s}))})}async function ll(e,t,n){let r=Ft(nl(),`snoutdata-restore-${process.pid}-${Date.now()}`),o=0,s="";try{for(let{product:l,clear:i,lines:d}of n.platformData){if(i.length>0){Ce(`${r}.sql`,`set role ${Pn[l]};
346
- ${i.map(h=>`delete from ${h};`).join(`
601
+ `,"utf8"),g({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{c(`Saved ${ge(o)} to ${t.out}.`),c(b(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(c(`Saved the roles it needs to ${s}.`),c(b("Into any other Postgres, in this order:")),c(b(` psql --dbname <your database> -f ${s}`)),c(b(` pg_restore --dbname <your database> ${t.out}`)),c(b("The roles are created without passwords. Set them yourself if anything signs in as one."))):(c(b("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),c(b("Owners and grants are skipped: this export carries no roles file.")))}),0}function Xl(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function Zl(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await Le(e);if(!r.pending)return r;if(Date.now()>=n)throw new Error(`the export of ${e} was still running after waiting`);await new Promise(o=>setTimeout(o,3e3))}}async function Ql(e,t){let n=await fetch(e);if(!n.ok||!n.body)throw new Error(`the download failed: ${n.status} ${n.statusText}`);let r=await js(t,"w"),o=0;try{for await(let s of n.body)await r.write(s),o+=s.byteLength}finally{await r.close()}return o}function tr(e){if(e.error){J(`The last export did not finish: ${e.error}`);return}if(e.pending){c("An export is running. Ask again in a minute, or run `snoutdata db export`, which waits for it.");return}if(!e.completedAt){c("This project has never been exported.");return}c(`A copy taken ${fe(e.completedAt)}, ${ge(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
602
+ `),e.urlExpiresAt&&c(b(`That link stops working ${fe(e.urlExpiresAt)}.`))):c(b("The link has expired. Run the export again to sign a new one; the copy is kept."))}function ge(e){if(e<1024)return`${e} B`;let t=["KB","MB","GB","TB"],n=e/1024,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n.toFixed(n>=10?0:1)} ${t[r]}`}async function Hs(e,t){let n=t.file;n||O("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),Zt(n)||O("usage",`no file at ${n}`,{file:n});let r=await js(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=bs(o.slice(0,s.bytesRead)),l=await ee(e);l.wakesInstantly||c("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await ne(),u=i.find(S=>S.ref===e),d=await ed(l);d===null&&O("tool-missing","psql is not installed, so the tables in this project could not be counted. Install the Postgres client tools, or restore with your own client using `snoutdata db url`.");let f=Ss(a,{existingTables:d,readOnly:u?.readOnly===!0},{force:t.force});f.ok||O("conflict",f.reason,{ref:e,file:n,refusal:f.code});let y=null;if(f.tool==="pg_restore"){let S=await Is("pg_restore",["--list",n]),w=await Is("pg_restore",["--section=post-data","--file=-",n]);if((S.code!==0||w.code!==0)&&O("failed",`pg_restore could not read ${n}: ${(S.stderr||w.stderr).trim().slice(0,300)}`,{ref:e,file:n}),y=xs(S.stdout,w.stdout),y.products.length>0){let x=await Fe(e),$=k=>!!(k&&typeof k=="object"&&"enabled"in k&&k.enabled),T=y.products.filter(k=>!$(x[k]));T.length>0&&O("conflict",`this dump holds ${T.length>1?`${T.slice(0,-1).join(", ")} and ${T[T.length-1]}`:T[0]} data, and ${e} has ${T.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${T.length===1?"it":"them"} on, wait a minute, and run this again:
603
+ ${T.map(k=>` snoutdata products enable ${k} --ref ${e}`).join(`
604
+ `)}`,{ref:e,file:n,refusal:"products-off",products:T})}}if(y&&y.platformData.length>0){let S=y.platformData.flatMap(({tables:$})=>$),w=await td(l,S);w===null&&O("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let x=y.platformData.filter(({tables:$})=>$.some(T=>w.includes(T))).map(({product:$})=>$);x.length>0&&O("conflict",`${x.join(", ")} on ${e} ${x.length===1?"has":"have"} not finished setting up ${x.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:x})}c(`Restoring ${n} into ${e} with ${f.tool} (${f.format} format).`);let m=y?await nd(l,n,y):await Xt(l,f.tool,n),p=_s(m.stderr);for(let S of y?.notes??[])c(b(S));return m.code!==0&&p.fatal.length===0&&p.total>0?(g({ref:e,file:n,ok:!0,tool:f.tool,format:f.format,ignoredErrors:p.harmless},()=>{c(`Restored into ${e}.`),c(`${p.harmless} error${p.harmless===1?"":"s"} above ${p.harmless===1?"is":"are"} expected and nothing is missing: a dump carries comments on the extensions this image installs, and your role does not own them.`)}),0):(m.code!==0&&O("failed",`${f.tool} exited ${m.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:p.fatal}),g({ref:e,file:n,ok:!0,tool:f.tool,format:f.format},()=>c(`Restored into ${e}.`)),0)}async function ed(e){return new Promise(t=>{let n=dt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc","select count(*) from pg_class c join pg_namespace n on n.oid = c.relnamespace where c.relkind = 'r' and n.nspname not in ('pg_catalog', 'information_schema') and n.nspname not like 'pg\\_toast%' and not exists (select 1 from pg_depend d where d.objid = c.oid and d.deptype = 'e') and pg_get_userbyid(c.relowner) !~ '^(snoutpod_admin|snout_[a-z]+_admin)$'"],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.ssl==="disable"?"disable":"require"}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{J(o.code==="ENOENT"?"psql is not installed, and db restore needs it (and pg_restore for an archive).":String(o)),t(null)}),n.on("close",o=>t(o===0?Number(r.trim())||0:null))})}function td(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=dt("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-tAc",`select t from unnest(array[${n}]) t where to_regclass(t) is null`],{stdio:["ignore","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.ssl==="disable"?"disable":"require"}}),s="";o.stdout.on("data",a=>s+=String(a)),o.on("error",()=>r(null)),o.on("close",a=>r(a===0?s.split(`
605
+ `).map(l=>l.trim()).filter(Boolean):null))})}function Is(e,t){return new Promise(n=>{let r=dt(e,t,{stdio:["ignore","pipe","pipe"]}),o="",s="";r.stdout.on("data",a=>o+=String(a)),r.stderr.on("data",a=>s+=String(a)),r.on("error",a=>{J(a.code==="ENOENT"?`${e} is not installed, and this dump needs it.`:String(a)),n({code:127,stdout:o,stderr:s})}),r.on("close",a=>n({code:a??0,stdout:o,stderr:s}))})}async function nd(e,t,n){let r=Qt(Vl(),`snoutdata-restore-${process.pid}-${Date.now()}`),o=0,s="";try{for(let{product:l,clear:i,lines:u}of n.platformData){if(i.length>0){Be(`${r}.sql`,`set role ${Wn[l]};
606
+ ${i.map(y=>`delete from ${y};`).join(`
347
607
  `)}
348
- `,"utf8");let f=await Lt(e,"psql",`${r}.sql`,["--quiet"]);o=o||f.code,s+=f.stderr}Ce(`${r}.platform`,`${d.join(`
608
+ `,"utf8");let f=await Xt(e,"psql",`${r}.sql`,["--quiet"]);o=o||f.code,s+=f.stderr}Be(`${r}.platform`,`${u.join(`
349
609
  `)}
350
- `,"utf8");let u=await Lt(e,"pg_restore",t,["--data-only",`--role=${Pn[l]}`,`--use-list=${r}.platform`]);o=o||u.code,s+=u.stderr}Ce(`${r}.rest`,`${n.rest.join(`
610
+ `,"utf8");let d=await Xt(e,"pg_restore",t,["--data-only",`--role=${Wn[l]}`,`--use-list=${r}.platform`]);o=o||d.code,s+=d.stderr}Be(`${r}.rest`,`${n.rest.join(`
351
611
  `)}
352
- `,"utf8");let a=await Lt(e,"pg_restore",t,[`--use-list=${r}.rest`]);return{code:o||a.code,stderr:s+a.stderr}}finally{for(let a of[".platform",".rest",".sql"])try{el(`${r}${a}`)}catch{}}}function Lt(e,t,n,r=[]){let o=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],s=t==="psql"?[...o,"--no-psqlrc","-v","ON_ERROR_STOP=1",...r,"-f",n]:[...o,"--no-owner","--no-privileges",...r,n];return new Promise(a=>{let l=tt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.ssl==="disable"?"disable":"require"}}),i="";l.stderr?.on("data",d=>{let u=String(d);i+=u,process.stderr.write(u)}),l.on("error",d=>{V(d.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(d)),a({code:127,stderr:i})}),l.on("close",d=>a({code:d??0,stderr:i}))})}G();re();K();import{readFile as dl}from"node:fs/promises";var ml="api.snoutdata.com";function Ss(e,t){return{ref:e,enabled:t.enabled===!0,siteUrl:t.settings?.site_url??null,redirects:t.settings?.uri_allow_list??[],google:{enabled:t.google?.enabled===!0,clientId:t.google?.clientId??null,clientSecretSet:t.google?.clientSecretSet===!0,ready:t.google?.ready===!0},googleCallback:`https://${e}.${ml}/auth/v1/callback`}}async function fl(e){return Ss(e,await x("cloud-project-auth",{ref:e,action:"status"}))}async function Cn(e,t){return Ss(e,await x("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function Ln(e){let t=e.google,n="off";t.ready?n=$("on"):t.enabled&&(n=`switched on, not offered yet: needs ${[!t.clientId&&"a client id",!t.clientSecretSet&&"a client secret"].filter(Boolean).join(" and ")}`),process.stdout.write(`${F([["auth",e.enabled?$("on"):"off"],["site url",e.siteUrl??S("not set")],["redirects",e.redirects.length?e.redirects.join(", "):S("none")],["google",n],["google client id",t.clientId??S("not set")],["google callback",e.googleCallback]])}
353
- `),e.enabled||c(S(` snoutdata products enable auth --ref ${e.ref}`))}async function pl(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\r?\n$/,"")}async function xs(e,t,n,r){if(t===void 0||t==="show"){let o=await fl(e);g(o,()=>Ln(o));return}if(t==="google"){if(n[0]==="off"){let a=await Cn(e,{googleEnabled:!1});g(a,()=>c("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new y('auth google takes --client-id and --stdin, or "off"');let o={googleEnabled:!0};if(r.clientId&&(o.googleClientId=r.clientId),r.stdin){let a=await pl();if(!a)throw new y("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await Cn(e,o);g(s,()=>{s.google.ready?c("Google sign-in is on. Your auth service restarts within about a minute."):Ln(s),c(S(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="redirects"){let o={};if(r.siteUrl!==void 0&&(o.siteUrl=r.siteUrl||null),r.allow!==void 0&&(o.uriAllowList=r.allow.split(",").map(a=>a.trim()).filter(Boolean)),Object.keys(o).length===0)throw new y("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await Cn(e,o);g(s,()=>Ln(s));return}if(t==="templates"){let o=await x("cloud-project-auth",{ref:e,action:"templates"});g(o,()=>bs(o.templates));return}if(t==="template"){let o=n[0];if(!o||!ws.includes(o))throw new y(`auth template needs a kind: ${ws.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new y(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await dl(r.file,"utf8"),i=await x("cloud-project-auth",{ref:e,action:"templates"}),d=r.subject??i.templates.find(u=>u.kind===o)?.subject;s={kind:o,subject:d,body:l}}let a=await x("cloud-project-auth",{ref:e,action:"template",template:s});g(a,()=>{bs(a.templates),c(S(" Your auth service restarts with it within about a minute."))});return}throw new y(`unknown command: auth ${t}. auth [google [off] | redirects | templates | template KIND]`)}var ws=["confirmation","recovery","magic_link","invite","email_change"];function bs(e){process.stdout.write(`${F([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?$("yours"):"ours",t.subject])])}
354
- `)}G();K();Z();import{spawn as hl}from"node:child_process";import{createHash as yl}from"node:crypto";import{existsSync as wl,readdirSync as bl,readFileSync as Sl,statSync as xl}from"node:fs";import{isAbsolute as _l,join as Un,relative as kl,resolve as El}from"node:path";function gl(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Bt(e,t,n={}){let r=gl(e),o=new Map(r.map(d=>[d.name,d])),s=[];for(let d of t){let u=o.get(d.name);if(!u){s.push({code:"missing",name:d.name,reason:`${d.name} has already run against this database and is no longer in the folder. If it was renamed, the old name is still recorded and the new one looks new, so its statements would run a second time.`});continue}u.checksum!==d.checksum&&s.push({code:"changed",name:d.name,reason:`${d.name} has changed since it ran. The database and this folder no longer agree about what happened to the schema, and running anything cannot fix that. Put the change in a new migration instead.`})}let a=t.map(d=>d.name).sort((d,u)=>d<u?-1:d>u?1:0)[t.length-1],l=new Set(t.map(d=>d.name)),i=[];for(let d of r)if(!l.has(d.name)){if(a!==void 0&&d.name<a&&!n.outOfOrder){s.push({code:"out-of-order",name:d.name,reason:`${d.name} sorts before ${a}, which has already run. Applying it now gives this database a history no fresh database will have. Pass --out-of-order if that is what you mean.`});continue}i.push(d)}return s.length>0?{ok:!1,problems:s}:{ok:!0,pending:i}}function nt(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function ks(e){let t=kl(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var rt="_snoutdata_migrations",vl=`create table if not exists ${rt} (
612
+ `,"utf8");let a=await Xt(e,"pg_restore",t,[`--use-list=${r}.rest`]);return{code:o||a.code,stderr:s+a.stderr}}finally{for(let a of[".platform",".rest",".sql"])try{Jl(`${r}${a}`)}catch{}}}function Xt(e,t,n,r=[]){let o=["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database],s=t==="psql"?[...o,"--no-psqlrc","-v","ON_ERROR_STOP=1",...r,"-f",n]:[...o,"--no-owner","--no-privileges",...r,n];return new Promise(a=>{let l=dt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.ssl==="disable"?"disable":"require"}}),i="";l.stderr?.on("data",u=>{let d=String(u);i+=d,process.stderr.write(d)}),l.on("error",u=>{J(u.code==="ENOENT"?`${t} is not installed, and this dump needs it.`:String(u)),a({code:127,stderr:i})}),l.on("close",u=>a({code:u??0,stderr:i}))})}W();te();q();function od(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new h(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new h("db access list takes no argument");if(t!==void 0)throw new h("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new h("db access grant needs an email address: db access grant you@example.com [--level full|read]");let s=t??"read";if(s!=="full"&&s!=="read")throw new h(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new h("db access revoke needs an email address or a role name");if(t!==void 0)throw new h("--level is for db access grant");return{action:"revoke",who:r};default:throw new h(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function Gs(e,t,n){let r=od(t,n.level);if(r.action==="list"){let s=await _("cloud-project-db-access",{ref:e,action:"list"});g(s,()=>sd(s));return}if(r.action==="grant"){let s=await _("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});g(s,()=>ad(s));return}let o=await _("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});g(o,()=>cd(o))}function sd(e){if(e.note&&c(e.note),e.access.length===0){c(`Nobody signs in to ${e.ref}'s database as themselves yet. \`snoutdata db access grant you@example.com --level full\`.`);return}process.stdout.write(`${F([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?v(n.email):n.email,n.level,n.role])])}
613
+ `);let t=e.access.find(n=>n.you);t?.connection&&(c(""),c("You sign in with (psql 18; a browser approves it):"),c(b(` psql "${t.connection.keywords}"`)))}function ad(e){e.connection&&process.stdout.write(`${e.connection.keywords}
614
+ `),c("");let t=e.outcome==="unchanged"?"already has":"now has";c(`${e.email} ${t} ${v(e.level)} access to ${e.ref}'s database, as ${e.role}${e.means?`: ${e.means}`:""}.`),e.outcome!=="unchanged"&&c(e.appliesIn),c("They sign in with psql 18 (libpq 18 with OAuth support) and approve the code it prints in a browser:"),e.connection&&c(b(` psql "${e.connection.keywords}"`))}function id(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function cd(e){let t=id(e);for(let n of t.slice(0,-1))c(n);J(t[t.length-1])}W();te();q();var ld=6e4;async function rr(e,t={}){return _("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function dd(e,t=Date.now()){let n=/^(\d+)\s*(s|m|h|d)$/i.exec(e.trim());if(n){let o={s:1e3,m:6e4,h:36e5,d:864e5}[n[2].toLowerCase()];return t-Number(n[1])*o}let r=Date.parse(e);if(Number.isFinite(r))return r;throw new h(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function ud(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function qs(e){if(e===null)return"-";let t=Math.round(e/1e3);if(t<60)return`${t}s ago`;let n=Math.round(t/60);return n<60?`${n}m ago`:`${Math.round(n/60)}h ago`}var md={connect:"connected",connect_refused:"refused at connect",join:"joined",join_refused:"join refused",leave:"left",channel_closed:"channel closed by the server",disconnect:"disconnected"};function Ks(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[ud(e.at),md[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function fd(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function pd(e){let t=e.inspect;if(c(v(`Realtime for ${e.ref}`)),c(`${t.connections} connection${t.connections===1?"":"s"}, ${t.connected_users} on a channel. The project's limit is ${t.limits.max_events_per_second} messages a second, counted once per message sent, however many receive it.`),t.channels.length===0){c("No channels are open.");return}for(let n of t.channels){let r=n.messages;if(c(""),c(v(`${n.name}${n.private?" (private)":""}`)+` ${n.clients.length} client${n.clients.length===1?"":"s"}`),c(b(` last ${r.window_seconds}s: ${r.broadcasts_received} broadcast${r.broadcasts_received===1?"":"s"} sent, ${r.broadcasts_delivered} delivered, busiest second ${r.peak_broadcasts_per_second}; ${r.presence_diffs_delivered} presence update${r.presence_diffs_delivered===1?"":"s"} delivered`)),n.clients.length>0){let s=n.clients.map(a=>[String(a.socket),a.presence_key,qs(Date.now()-a.joined_at),qs(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>ld?" (silent: likely gone)":"")]);c(F([["SOCKET","PRESENCE KEY","JOINED","LAST HEARD"],...s]).split(`
615
+ `).map(a=>` ${a}`).join(`
616
+ `))}let o=Object.keys(n.presence);if(o.length>0){c(" presence:");for(let s of o)for(let a of n.presence[s].metas)c(` ${s} ${fd(a)}`)}}}async function Ws(e,t={}){if(t.watch&&yo())throw new h("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await rr(e,{channel:t.channel});if(g(n.inspect,()=>pd(n)),!t.watch)return 0;c(""),c(b("Watching. New events appear below as they happen; Ctrl+C to stop."));let r=n.at+1,o=t.intervalMs??2e3;for(;;){await new Promise(a=>setTimeout(a,o));let s=await rr(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Ks(a)}
617
+ `);if(s.events.length>0){r=Math.max(...s.events.map(l=>l.at))+1;let a=s.inspect.channels.map(l=>`${l.name}: ${Object.keys(l.presence).join(", ")||"nobody tracked"}`).join("; ");process.stdout.write(b(` now ${a||"no channels open"}
618
+ `))}}}async function Js(e,t={}){let n=t.since?dd(t.since):0,r=await rr(e,{channel:t.channel,since:n});return g({ref:r.ref,events:r.events,capacity:r.logCapacity},()=>{if(r.events.length===0){c(t.since||t.channel?"Nothing in the log for that.":"Nothing in the log yet. It fills as clients connect, and starts again when the project's host restarts.");return}for(let o of r.events)process.stdout.write(`${Ks(o)}
619
+ `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&c(b(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}W();te();q();import{readFile as hd}from"node:fs/promises";var yd="api.snoutdata.com";function Ys(e,t){return{ref:e,enabled:t.enabled===!0,siteUrl:t.settings?.site_url??null,redirects:t.settings?.uri_allow_list??[],google:{enabled:t.google?.enabled===!0,clientId:t.google?.clientId??null,clientSecretSet:t.google?.clientSecretSet===!0,ready:t.google?.ready===!0},googleCallback:`https://${e}.${yd}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function wd(e){return Ys(e,await _("cloud-project-auth",{ref:e,action:"status"}))}async function en(e,t){return Ys(e,await _("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function or(e){let t=e.google,n="off";t.ready?n=v("on"):t.enabled&&(n=`switched on, not offered yet: needs ${[!t.clientId&&"a client id",!t.clientSecretSet&&"a client secret"].filter(Boolean).join(" and ")}`),process.stdout.write(`${F([["auth",e.enabled?v("on"):"off"],["site url",e.siteUrl??b("not set")],["redirects",e.redirects.length?e.redirects.join(", "):b("none")],["google",n],["google client id",t.clientId??b("not set")],["google callback",e.googleCallback],["guests",e.anonymous.ready?v("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
620
+ `),e.enabled||c(b(` snoutdata products enable auth --ref ${e.ref}`))}async function bd(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\r?\n$/,"")}async function Xs(e,t,n,r){if(t===void 0||t==="show"){let o=await wd(e);g(o,()=>or(o));return}if(t==="google"){if(n[0]==="off"){let a=await en(e,{googleEnabled:!1});g(a,()=>c("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new h('auth google takes --client-id and --stdin, or "off"');let o={googleEnabled:!0};if(r.clientId&&(o.googleClientId=r.clientId),r.stdin){let a=await bd();if(!a)throw new h("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await en(e,o);g(s,()=>{s.google.ready?c("Google sign-in is on. Your auth service restarts within about a minute."):or(s),c(b(` Add ${s.googleCallback} to your Google client's Authorized redirect URIs.`))});return}if(t==="anonymous"||t==="guests"){let o=n[0];if(o!=="on"&&o!=="off")throw new h('auth anonymous takes "on" or "off"');let s=await en(e,{anonymousEnabled:o==="on"});g(s,()=>{o==="off"?c("Guest sign-in is off. Guests who already have a session keep it until it ends. Your auth service restarts within about a minute."):s.enabled?c("Guest sign-in is on: signInAnonymously() gives a browser a session with no email or password. Your auth service restarts within about a minute."):(c("Guest sign-in is switched on, and takes effect when auth is."),c(b(` snoutdata products enable auth --ref ${s.ref}`)))});return}if(t==="redirects"){let o={};if(r.siteUrl!==void 0&&(o.siteUrl=r.siteUrl||null),r.allow!==void 0&&(o.uriAllowList=r.allow.split(",").map(a=>a.trim()).filter(Boolean)),Object.keys(o).length===0)throw new h("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await en(e,o);g(s,()=>or(s));return}if(t==="templates"){let o=await _("cloud-project-auth",{ref:e,action:"templates"});g(o,()=>Vs(o.templates));return}if(t==="template"){let o=n[0];if(!o||!zs.includes(o))throw new h(`auth template needs a kind: ${zs.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new h(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await hd(r.file,"utf8"),i=await _("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(d=>d.kind===o)?.subject;s={kind:o,subject:u,body:l}}let a=await _("cloud-project-auth",{ref:e,action:"template",template:s});g(a,()=>{Vs(a.templates),c(b(" Your auth service restarts with it within about a minute."))});return}throw new h(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var zs=["confirmation","recovery","magic_link","invite","email_change"];function Vs(e){process.stdout.write(`${F([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?v("yours"):"ours",t.subject])])}
621
+ `)}W();q();Q();import{spawn as _d}from"node:child_process";import{createHash as xd}from"node:crypto";import{existsSync as kd,readdirSync as vd,readFileSync as Ed,statSync as $d}from"node:fs";import{isAbsolute as Ad,join as sr,relative as Pd,resolve as Rd}from"node:path";function Sd(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function tn(e,t,n={}){let r=Sd(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let d=o.get(u.name);if(!d){s.push({code:"missing",name:u.name,reason:`${u.name} has already run against this database and is no longer in the folder. If it was renamed, the old name is still recorded and the new one looks new, so its statements would run a second time.`});continue}d.checksum!==u.checksum&&s.push({code:"changed",name:u.name,reason:`${u.name} has changed since it ran. The database and this folder no longer agree about what happened to the schema, and running anything cannot fix that. Put the change in a new migration instead.`})}let a=t.map(u=>u.name).sort((u,d)=>u<d?-1:u>d?1:0)[t.length-1],l=new Set(t.map(u=>u.name)),i=[];for(let u of r)if(!l.has(u.name)){if(a!==void 0&&u.name<a&&!n.outOfOrder){s.push({code:"out-of-order",name:u.name,reason:`${u.name} sorts before ${a}, which has already run. Applying it now gives this database a history no fresh database will have. Pass --out-of-order if that is what you mean.`});continue}i.push(u)}return s.length>0?{ok:!1,problems:s}:{ok:!0,pending:i}}function ut(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}function Qs(e){let t=Pd(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var mt="_snoutdata_migrations",Td=`create table if not exists ${mt} (
355
622
  name text primary key,
356
623
  checksum text not null,
357
624
  applied_at timestamptz not null default now()
358
- );`;function Pl(e){if(!wl(e)||!xl(e).isDirectory())throw new Error(`no migrations folder at ${ks(e)}. Put your .sql files there, or pass --dir.`);return bl(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=Un(e,t),r=Sl(n,"utf8");return{name:t,path:n,sql:r,checksum:yl("sha256").update(r,"utf8").digest("hex")}})}function Fn(e,t,n=[]){return new Promise(r=>{let o=hl("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1",...n],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.ssl==="disable"?"disable":"require"}}),s="",a="";o.stdout.on("data",l=>s+=String(l)),o.stderr.on("data",l=>a+=String(l)),o.on("error",l=>{r({code:127,out:"",err:l.code==="ENOENT"?"psql is not installed, and db push needs it to run your migrations. `snoutdata db url` prints a connection string for any client.":String(l)})}),o.on("close",l=>r({code:l??0,out:s,err:a})),o.stdin.end(t)})}async function $l(e){let t=await Fn(e,vl);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${rt}`);let n=await Fn(e,`select name || E'\\t' || checksum from ${rt};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${rt}`);return n.out.split(`
359
- `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let o=r.indexOf(" ");return{name:r.slice(0,o),checksum:r.slice(o+1)}})}function _s(e){return`'${e.replace(/'/g,"''")}'`}async function Es(e,t={}){let n=await Bn(e,t,r=>c(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
360
- `);R("conflict",`Refused. This folder and ${e} do not agree:
361
- ${r}`,{ref:e,problems:n.problems})}R("failed",`${n.failed} failed, and nothing after it was tried.
362
- ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return g(n,()=>{if(n.wouldApply){c(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)c(` ${r}`);return}if(n.upToDate){c(`${e} is up to date.`);return}c(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function Bn(e,t={},n=()=>{}){let r=El(t.dir===void 0?Un(process.cwd(),"migrations"):_l(t.dir)?t.dir:Un(process.cwd(),t.dir)),o=[];function s(m){o.push(m),n(m)}let a=Pl(r);if(a.length===0)return s(`No .sql files in ${ks(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let l=await Y(e);l.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let i=await $l(l),d=Bt(a,i,{outOfOrder:t.outOfOrder});if(!d.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...d.problems],notes:o};let u=new Map(a.map(m=>[m.name,m])),f=d.pending.map(m=>u.get(m.name));if(f.length===0)return s(`${e} is up to date: ${i.length} migration${i.length===1?"":"s"} already applied.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};if(t.dryRun){for(let m of f)nt(m.sql)||s(`${m.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:f.map(m=>m.name),notes:o}}let h=[];for(let m of f){let p=`insert into ${rt} (name, checksum) values (${_s(m.name)}, ${_s(m.checksum)});`,b=nt(m.sql);b||s(`${m.name} runs outside a transaction, so an interruption can leave it half applied.`);let w=b?`begin;
625
+ );`;function Od(e){if(!kd(e)||!$d(e).isDirectory())throw new Error(`no migrations folder at ${Qs(e)}. Put your .sql files there, or pass --dir.`);return vd(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=sr(e,t),r=Ed(n,"utf8");return{name:t,path:n,sql:r,checksum:xd("sha256").update(r,"utf8").digest("hex")}})}function ar(e,t,n=[]){return new Promise(r=>{let o=_d("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1",...n],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.ssl==="disable"?"disable":"require"}}),s="",a="";o.stdout.on("data",l=>s+=String(l)),o.stderr.on("data",l=>a+=String(l)),o.on("error",l=>{r({code:127,out:"",err:l.code==="ENOENT"?"psql is not installed, and db push needs it to run your migrations. `snoutdata db url` prints a connection string for any client.":String(l)})}),o.on("close",l=>r({code:l??0,out:s,err:a})),o.stdin.end(t)})}async function Nd(e){let t=await ar(e,Td);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${mt}`);let n=await ar(e,`select name || E'\\t' || checksum from ${mt};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${mt}`);return n.out.split(`
626
+ `).map(r=>r.trim()).filter(r=>r.length>0).map(r=>{let o=r.indexOf(" ");return{name:r.slice(0,o),checksum:r.slice(o+1)}})}function Zs(e){return`'${e.replace(/'/g,"''")}'`}async function ea(e,t={}){let n=await ir(e,t,r=>c(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
627
+ `);O("conflict",`Refused. This folder and ${e} do not agree:
628
+ ${r}`,{ref:e,problems:n.problems})}O("failed",`${n.failed} failed, and nothing after it was tried.
629
+ ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return g(n,()=>{if(n.wouldApply){c(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)c(` ${r}`);return}if(n.upToDate){c(`${e} is up to date.`);return}c(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function ir(e,t={},n=()=>{}){let r=Rd(t.dir===void 0?sr(process.cwd(),"migrations"):Ad(t.dir)?t.dir:sr(process.cwd(),t.dir)),o=[];function s(m){o.push(m),n(m)}let a=Od(r);if(a.length===0)return s(`No .sql files in ${Qs(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let l=await ee(e);l.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let i=await Nd(l),u=tn(a,i,{outOfOrder:t.outOfOrder});if(!u.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...u.problems],notes:o};let d=new Map(a.map(m=>[m.name,m])),f=u.pending.map(m=>d.get(m.name));if(f.length===0)return s(`${e} is up to date: ${i.length} migration${i.length===1?"":"s"} already applied.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};if(t.dryRun){for(let m of f)ut(m.sql)||s(`${m.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:f.map(m=>m.name),notes:o}}let y=[];for(let m of f){let p=`insert into ${mt} (name, checksum) values (${Zs(m.name)}, ${Zs(m.checksum)});`,S=ut(m.sql);S||s(`${m.name} runs outside a transaction, so an interruption can leave it half applied.`);let w=S?`begin;
363
630
  ${m.sql}
364
631
  ${p}
365
632
  commit;
366
633
  `:`${m.sql}
367
634
  ${p}
368
- `,_=await Fn(l,w);if(_.code!==0)return b&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:h,failed:m.name,error:_.err.trim(),notes:o};h.push(m.name),s(` applied ${m.name}`)}return{ref:e,directory:r,ok:!0,applied:h,notes:o}}G();re();K();import{readFile as vs}from"node:fs/promises";function Hn(e){if(e==="apns"||e==="fcm")return e;throw new y("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function Tl(e){let t=await x("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function Ps(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new y("push credentials set apns needs --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app");if(e.environment!==void 0&&e.environment!=="production"&&e.environment!=="sandbox")throw new y("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await vs(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function $s(e){if(!e)throw new y("push credentials set fcm needs --file service-account.json");return{service_account:await vs(e,"utf8")}}async function Al(e,t,n){let r=await x("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function Rl(e,t){return await x("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function Ts(e){let t=await Tl(e);g(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${F([["TRANSPORT","SET",""],["web push",o?"yes":"starting",o?.public_key?`public key ${o.public_key.slice(0,16)}\u2026`:""],["apns",n?"yes":"no",n?`${n.topic}, key ${n.keys?.map(s=>s.key_id).join(", ")}`:""],["fcm",r?"yes":"no",r?`${r.project_id} as ${r.client_email}`:""]])}
369
- `),(!n||!r)&&c(S(` snoutdata push credentials set ${n?"fcm --file service-account.json":"apns --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app"} --ref ${e}`))})}async function As(e,t,n){let r=await Al(e,t,n);g(r,()=>c(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function Rs(e,t){let n=await Rl(e,t);g(n,()=>c(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}G();Z();K();import{spawn as Gl}from"node:child_process";import{writeFileSync as Kl}from"node:fs";import{isAbsolute as Jl,resolve as Wl}from"node:path";var Nl=" ";function E(e){return Nl.repeat(e)}function z(e){return JSON.stringify(e)}function ae(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:z(e)}function $e(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Dl(e){let t=(e??"").trim(),n=!1;for(;t.endsWith("[]");)n=!0,t=t.slice(0,-2).trim();t.startsWith("_")&&t.length>1&&(n=!0,t=t.slice(1));let r=t.indexOf("(");return r>0&&(t=t.slice(0,r)),{base:t.trim().toLowerCase(),array:n}}var Ml={smallint:"number",int2:"number",integer:"number",int:"number",int4:"number",bigint:"number",int8:"number",decimal:"number",numeric:"number",number:"number",real:"number",float:"number",float4:"number",float8:"number","double precision":"number",double:"number",binary_float:"number",binary_double:"number",smallserial:"number",serial:"number",bigserial:"number",oid:"number",smallmoney:"number",money:"number",bool:"boolean",boolean:"boolean",json:"json",jsonb:"json",variant:"json",object:"json",char:"string",character:"string","character varying":"string",varchar:"string",varchar2:"string",nvarchar:"string",nvarchar2:"string",nchar:"string",text:"string",ntext:"string",citext:"string",clob:"string",nclob:"string",longtext:"string",mediumtext:"string",tinytext:"string",uuid:"string",uniqueidentifier:"string",date:"string",time:"string","time without time zone":"string","time with time zone":"string",timetz:"string",timestamp:"string","timestamp without time zone":"string","timestamp with time zone":"string",timestamptz:"string",datetime:"string",datetime2:"string",datetimeoffset:"string",smalldatetime:"string",timestamp_ntz:"string",timestamp_ltz:"string",timestamp_tz:"string",interval:"string",year:"string",bytea:"string",blob:"string",longblob:"string",mediumblob:"string",tinyblob:"string",binary:"string",varbinary:"string",raw:"string",xml:"string",inet:"string",cidr:"string",macaddr:"string",macaddr8:"string",tsvector:"string",tsquery:"string",name:"string",bit:"string",varbit:"string",tinyint:"number",enum:"string",set:"string"},Il={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function jl(e,t){let n=Il[e]?.[t];return n||(Ml[t]??"unknown")}function Ht(e,t){let{base:n,array:r}=Dl(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=jl(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function Cl(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function qn(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${E(n)}${ae(t.name)}?: never`;let s=Ht(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${E(n)}${ae(t.name)}${r?"?":""}: ${a}`}function Ll(e,t){let n=[...t.columns].sort(),r=o=>{let s=[...o].sort();return s.length===n.length&&s.every((a,l)=>a===n[l])};return e.primaryKey.length>0&&r(e.primaryKey)?!0:(e.indexes??[]).some(o=>o.unique&&r(o.columns))}function Ul(e,t){let n=[],r=[...e.foreignKeys].sort((o,s)=>o.constraintName<s.constraintName?-1:o.constraintName>s.constraintName?1:0);if(r.length===0)return n.push(`${E(t)}Relationships: []`),n;n.push(`${E(t)}Relationships: [`);for(let o of r)n.push(`${E(t+1)}{`),n.push(`${E(t+2)}foreignKeyName: ${z(o.constraintName)}`),n.push(`${E(t+2)}columns: [${o.columns.map(z).join(", ")}]`),n.push(`${E(t+2)}isOneToOne: ${Ll(e,o)?"true":"false"}`),n.push(`${E(t+2)}referencedRelation: ${z(o.referencedTable)}`),n.push(`${E(t+2)}referencedColumns: [${o.referencedColumns.map(z).join(", ")}]`),n.push(`${E(t+1)}},`);return n.push(`${E(t)}]`),n}function Os(e,t,n){let r=$e(t.columns),o=[];o.push(`${E(n)}${ae(t.name)}: {`),o.push(`${E(n+1)}Row: {`);for(let s of r)o.push(qn(e,s,n+2,!1));if(o.push(`${E(n+1)}}`),t.kind!=="view"){o.push(`${E(n+1)}Insert: {`);for(let s of r)o.push(qn(e,s,n+2,Cl(s),!0));o.push(`${E(n+1)}}`),o.push(`${E(n+1)}Update: {`);for(let s of r)o.push(qn(e,s,n+2,!0,!0));o.push(`${E(n+1)}}`)}return o.push(...Ul(t,n+1)),o.push(`${E(n)}}`),o}function ot(e,t){return[`${E(t)}${e}: {`,`${E(t+1)}[_ in never]: never`,`${E(t)}}`]}function Fl(e,t){if(t.kind==="procedure"||!t.returnType)return"undefined";let n=t.returnType.trim(),r=n.toLowerCase();if(r==="void"||r==="")return"undefined";if(r.startsWith("table("))return"Record<string, unknown>[]";if(r.startsWith("setof ")){let s=n.slice(6).trim();if(s.toLowerCase()==="record")return"Record<string, unknown>[]";let a=e.rowRefs.get(s.toLowerCase());return a?`${a}[]`:`${Ht(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??Ht(e,n)}function Bl(e,t,n){let r=[],o=$e((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${E(n)}${ae(t.name)}: {`),o.length===0)r.push(`${E(n+1)}Args: never`);else{r.push(`${E(n+1)}Args: {`);for(let s of o)r.push(`${E(n+2)}${ae(s.name)}: ${Ht(e,s.dataType)}`);r.push(`${E(n+1)}}`)}return r.push(`${E(n+1)}Returns: ${Fl(e,t)}`),r.push(`${E(n)}}`),r}function Le(e,t){return e.schema&&e.schema.length>0?e.schema:t}function Ns(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let f of o){let h=f.schema&&f.schema.length>0?f.schema:n;s.set(f.name.toLowerCase(),`Database[${z(h)}][${z("Enums")}][${z(f.name)}]`)}let a=new Map;for(let f of e.tables){let h=Le(f,n),m=f.kind==="view"?"Views":"Tables";a.set(f.name.toLowerCase(),`Database[${z(h)}][${z(m)}][${z(f.name)}][${z("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let f of e.tables)i.add(Le(f,n));for(let f of e.routines??[])i.add(Le(f,n));for(let f of o)i.add(f.schema&&f.schema.length>0?f.schema:n);let d=[...i].sort(),u=[];u.push("export type Json ="),u.push(" | string"),u.push(" | number"),u.push(" | boolean"),u.push(" | null"),u.push(" | { [key: string]: Json | undefined }"),u.push(" | Json[]"),u.push(""),u.push("export type Database = {");for(let f of d){let h=$e(e.tables.filter(w=>Le(w,n)===f&&w.kind!=="view")),m=$e(e.tables.filter(w=>Le(w,n)===f&&w.kind==="view")),p=$e((e.routines??[]).filter(w=>Le(w,n)===f)),b=$e(o.filter(w=>(w.schema&&w.schema.length>0?w.schema:n)===f));if(u.push(`${E(1)}${ae(f)}: {`),h.length===0)u.push(...ot("Tables",2));else{u.push(`${E(2)}Tables: {`);for(let w of h)u.push(...Os(l,w,3));u.push(`${E(2)}}`)}if(m.length===0)u.push(...ot("Views",2));else{u.push(`${E(2)}Views: {`);for(let w of m)u.push(...Os(l,w,3));u.push(`${E(2)}}`)}if(p.length===0)u.push(...ot("Functions",2));else{u.push(`${E(2)}Functions: {`);for(let w of p)u.push(...Bl(l,w,3));u.push(`${E(2)}}`)}if(b.length===0)u.push(...ot("Enums",2));else{u.push(`${E(2)}Enums: {`);for(let w of b)u.push(`${E(3)}${ae(w.name)}: ${w.values.map(z).join(" | ")}`);u.push(`${E(2)}}`)}u.push(...ot("CompositeTypes",2)),u.push(`${E(1)}}`)}return u.push("}"),t.helpers!==!1&&(u.push(""),u.push(ql(n))),t.constants!==!1&&(u.push(""),u.push(Hl(d,o,n))),`${u.join(`
635
+ `,x=await ar(l,w);if(x.code!==0)return S&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:y,failed:m.name,error:x.err.trim(),notes:o};y.push(m.name),s(` applied ${m.name}`)}return{ref:e,directory:r,ok:!0,applied:y,notes:o}}W();te();q();import{readFile as ta}from"node:fs/promises";function cr(e){if(e==="apns"||e==="fcm")return e;throw new h("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function Dd(e){let t=await _("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function na(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new h("push credentials set apns needs --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app");if(e.environment!==void 0&&e.environment!=="production"&&e.environment!=="sandbox")throw new h("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await ta(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function ra(e){if(!e)throw new h("push credentials set fcm needs --file service-account.json");return{service_account:await ta(e,"utf8")}}async function Id(e,t,n){let r=await _("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function Md(e,t){return await _("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function oa(e){let t=await Dd(e);g(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${F([["TRANSPORT","SET",""],["web push",o?"yes":"starting",o?.public_key?`public key ${o.public_key.slice(0,16)}\u2026`:""],["apns",n?"yes":"no",n?`${n.topic}, key ${n.keys?.map(s=>s.key_id).join(", ")}`:""],["fcm",r?"yes":"no",r?`${r.project_id} as ${r.client_email}`:""]])}
636
+ `),(!n||!r)&&c(b(` snoutdata push credentials set ${n?"fcm --file service-account.json":"apns --p8 AuthKey.p8 --key-id ID --team-id ID --topic com.example.app"} --ref ${e}`))})}async function sa(e,t,n){let r=await Id(e,t,n);g(r,()=>c(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function aa(e,t){let n=await Md(e,t);g(n,()=>c(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}W();Q();q();import{spawn as Vd}from"node:child_process";import{writeFileSync as Yd}from"node:fs";import{isAbsolute as Xd,resolve as Zd}from"node:path";var jd=" ";function E(e){return jd.repeat(e)}function X(e){return JSON.stringify(e)}function le(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:X(e)}function Oe(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Ld(e){let t=(e??"").trim(),n=!1;for(;t.endsWith("[]");)n=!0,t=t.slice(0,-2).trim();t.startsWith("_")&&t.length>1&&(n=!0,t=t.slice(1));let r=t.indexOf("(");return r>0&&(t=t.slice(0,r)),{base:t.trim().toLowerCase(),array:n}}var Ud={smallint:"number",int2:"number",integer:"number",int:"number",int4:"number",bigint:"number",int8:"number",decimal:"number",numeric:"number",number:"number",real:"number",float:"number",float4:"number",float8:"number","double precision":"number",double:"number",binary_float:"number",binary_double:"number",smallserial:"number",serial:"number",bigserial:"number",oid:"number",smallmoney:"number",money:"number",bool:"boolean",boolean:"boolean",json:"json",jsonb:"json",variant:"json",object:"json",char:"string",character:"string","character varying":"string",varchar:"string",varchar2:"string",nvarchar:"string",nvarchar2:"string",nchar:"string",text:"string",ntext:"string",citext:"string",clob:"string",nclob:"string",longtext:"string",mediumtext:"string",tinytext:"string",uuid:"string",uniqueidentifier:"string",date:"string",time:"string","time without time zone":"string","time with time zone":"string",timetz:"string",timestamp:"string","timestamp without time zone":"string","timestamp with time zone":"string",timestamptz:"string",datetime:"string",datetime2:"string",datetimeoffset:"string",smalldatetime:"string",timestamp_ntz:"string",timestamp_ltz:"string",timestamp_tz:"string",interval:"string",year:"string",bytea:"string",blob:"string",longblob:"string",mediumblob:"string",tinyblob:"string",binary:"string",varbinary:"string",raw:"string",xml:"string",inet:"string",cidr:"string",macaddr:"string",macaddr8:"string",tsvector:"string",tsquery:"string",name:"string",bit:"string",varbit:"string",tinyint:"number",enum:"string",set:"string"},Fd={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Bd(e,t){let n=Fd[e]?.[t];return n||(Ud[t]??"unknown")}function nn(e,t){let{base:n,array:r}=Ld(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Bd(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function Hd(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function lr(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${E(n)}${le(t.name)}?: never`;let s=nn(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${E(n)}${le(t.name)}${r?"?":""}: ${a}`}function Gd(e,t){let n=[...t.columns].sort(),r=o=>{let s=[...o].sort();return s.length===n.length&&s.every((a,l)=>a===n[l])};return e.primaryKey.length>0&&r(e.primaryKey)?!0:(e.indexes??[]).some(o=>o.unique&&r(o.columns))}function qd(e,t){let n=[],r=[...e.foreignKeys].sort((o,s)=>o.constraintName<s.constraintName?-1:o.constraintName>s.constraintName?1:0);if(r.length===0)return n.push(`${E(t)}Relationships: []`),n;n.push(`${E(t)}Relationships: [`);for(let o of r)n.push(`${E(t+1)}{`),n.push(`${E(t+2)}foreignKeyName: ${X(o.constraintName)}`),n.push(`${E(t+2)}columns: [${o.columns.map(X).join(", ")}]`),n.push(`${E(t+2)}isOneToOne: ${Gd(e,o)?"true":"false"}`),n.push(`${E(t+2)}referencedRelation: ${X(o.referencedTable)}`),n.push(`${E(t+2)}referencedColumns: [${o.referencedColumns.map(X).join(", ")}]`),n.push(`${E(t+1)}},`);return n.push(`${E(t)}]`),n}function ia(e,t,n){let r=Oe(t.columns),o=[];o.push(`${E(n)}${le(t.name)}: {`),o.push(`${E(n+1)}Row: {`);for(let s of r)o.push(lr(e,s,n+2,!1));if(o.push(`${E(n+1)}}`),t.kind!=="view"){o.push(`${E(n+1)}Insert: {`);for(let s of r)o.push(lr(e,s,n+2,Hd(s),!0));o.push(`${E(n+1)}}`),o.push(`${E(n+1)}Update: {`);for(let s of r)o.push(lr(e,s,n+2,!0,!0));o.push(`${E(n+1)}}`)}return o.push(...qd(t,n+1)),o.push(`${E(n)}}`),o}function ft(e,t){return[`${E(t)}${e}: {`,`${E(t+1)}[_ in never]: never`,`${E(t)}}`]}function Kd(e,t){if(t.kind==="procedure"||!t.returnType)return"undefined";let n=t.returnType.trim(),r=n.toLowerCase();if(r==="void"||r==="")return"undefined";if(r.startsWith("table("))return"Record<string, unknown>[]";if(r.startsWith("setof ")){let s=n.slice(6).trim();if(s.toLowerCase()==="record")return"Record<string, unknown>[]";let a=e.rowRefs.get(s.toLowerCase());return a?`${a}[]`:`${nn(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??nn(e,n)}function Wd(e,t,n){let r=[],o=Oe((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${E(n)}${le(t.name)}: {`),o.length===0)r.push(`${E(n+1)}Args: never`);else{r.push(`${E(n+1)}Args: {`);for(let s of o)r.push(`${E(n+2)}${le(s.name)}: ${nn(e,s.dataType)}`);r.push(`${E(n+1)}}`)}return r.push(`${E(n+1)}Returns: ${Kd(e,t)}`),r.push(`${E(n)}}`),r}function He(e,t){return e.schema&&e.schema.length>0?e.schema:t}function ca(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let f of o){let y=f.schema&&f.schema.length>0?f.schema:n;s.set(f.name.toLowerCase(),`Database[${X(y)}][${X("Enums")}][${X(f.name)}]`)}let a=new Map;for(let f of e.tables){let y=He(f,n),m=f.kind==="view"?"Views":"Tables";a.set(f.name.toLowerCase(),`Database[${X(y)}][${X(m)}][${X(f.name)}][${X("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let f of e.tables)i.add(He(f,n));for(let f of e.routines??[])i.add(He(f,n));for(let f of o)i.add(f.schema&&f.schema.length>0?f.schema:n);let u=[...i].sort(),d=[];d.push("export type Json ="),d.push(" | string"),d.push(" | number"),d.push(" | boolean"),d.push(" | null"),d.push(" | { [key: string]: Json | undefined }"),d.push(" | Json[]"),d.push(""),d.push("export type Database = {");for(let f of u){let y=Oe(e.tables.filter(w=>He(w,n)===f&&w.kind!=="view")),m=Oe(e.tables.filter(w=>He(w,n)===f&&w.kind==="view")),p=Oe((e.routines??[]).filter(w=>He(w,n)===f)),S=Oe(o.filter(w=>(w.schema&&w.schema.length>0?w.schema:n)===f));if(d.push(`${E(1)}${le(f)}: {`),y.length===0)d.push(...ft("Tables",2));else{d.push(`${E(2)}Tables: {`);for(let w of y)d.push(...ia(l,w,3));d.push(`${E(2)}}`)}if(m.length===0)d.push(...ft("Views",2));else{d.push(`${E(2)}Views: {`);for(let w of m)d.push(...ia(l,w,3));d.push(`${E(2)}}`)}if(p.length===0)d.push(...ft("Functions",2));else{d.push(`${E(2)}Functions: {`);for(let w of p)d.push(...Wd(l,w,3));d.push(`${E(2)}}`)}if(S.length===0)d.push(...ft("Enums",2));else{d.push(`${E(2)}Enums: {`);for(let w of S)d.push(`${E(3)}${le(w.name)}: ${w.values.map(X).join(" | ")}`);d.push(`${E(2)}}`)}d.push(...ft("CompositeTypes",2)),d.push(`${E(1)}}`)}return d.push("}"),t.helpers!==!1&&(d.push(""),d.push(zd(n))),t.constants!==!1&&(d.push(""),d.push(Jd(u,o,n))),`${d.join(`
370
637
  `)}
371
- `}function Hl(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=$e(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${E(1)}${ae(o)}: {`),r.push(`${E(2)}Enums: {},`),r.push(`${E(1)}},`);continue}r.push(`${E(1)}${ae(o)}: {`),r.push(`${E(2)}Enums: {`);for(let a of s)r.push(`${E(3)}${ae(a.name)}: [${a.values.map(z).join(", ")}],`);r.push(`${E(2)}},`),r.push(`${E(1)}},`)}return r.push("} as const"),r.join(`
372
- `)}function ql(e){return`type DatabaseWithoutInternals = Database
638
+ `}function Jd(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=Oe(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${E(1)}${le(o)}: {`),r.push(`${E(2)}Enums: {},`),r.push(`${E(1)}},`);continue}r.push(`${E(1)}${le(o)}: {`),r.push(`${E(2)}Enums: {`);for(let a of s)r.push(`${E(3)}${le(a.name)}: [${a.values.map(X).join(", ")}],`);r.push(`${E(2)}},`),r.push(`${E(1)}},`)}return r.push("} as const"),r.join(`
639
+ `)}function zd(e){return`type DatabaseWithoutInternals = Database
373
640
 
374
- type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${z(e)}>]
641
+ type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${X(e)}>]
375
642
 
376
643
  export type Tables<
377
644
  DefaultSchemaTableNameOrOptions extends
@@ -484,7 +751,7 @@ export type CompositeTypes<
484
751
  ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
485
752
  : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"]
486
753
  ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
487
- : never`}function zl(e){return{host:e.host,port:e.port,database:e.database,user:e.user,password:e.password,sslMode:e.ssl==="disable"?"disable":"require",label:e.ref}}function qt(e){let t;try{t=new URL(e)}catch{throw new Error(`--db-url is not a URL: ${e}`)}if(t.protocol!=="postgres:"&&t.protocol!=="postgresql:")throw new Error(`--db-url must be a postgres:// URL, not ${t.protocol}//`);let n=decodeURIComponent(t.pathname.replace(/^\//,""));if(!n)throw new Error("--db-url has no database name in its path");let r=t.searchParams.get("sslmode")??"prefer";return{host:t.hostname||"127.0.0.1",port:t.port?Number(t.port):5432,database:n,user:decodeURIComponent(t.username)||"postgres",password:decodeURIComponent(t.password),sslMode:r,label:`${t.hostname||"127.0.0.1"}/${n}`}}function Gn(e,t){return new Promise(n=>{let r=Gl("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1","-tA"],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.sslMode}}),o="",s="";r.stdout.on("data",a=>o+=String(a)),r.stderr.on("data",a=>s+=String(a)),r.on("error",a=>{n({code:127,out:"",err:a.code==="ENOENT"?"psql is not installed, and gen types needs it to read the schema. `snoutdata db url` prints a connection string for any client.":String(a)})}),r.on("close",a=>n({code:a??0,out:o,err:s})),r.stdin.end(t)})}var Vl=`
754
+ : never`}function Qd(e){return{host:e.host,port:e.port,database:e.database,user:e.user,password:e.password,sslMode:e.ssl==="disable"?"disable":"require",label:e.ref}}function rn(e){let t;try{t=new URL(e)}catch{throw new Error(`--db-url is not a URL: ${e}`)}if(t.protocol!=="postgres:"&&t.protocol!=="postgresql:")throw new Error(`--db-url must be a postgres:// URL, not ${t.protocol}//`);let n=decodeURIComponent(t.pathname.replace(/^\//,""));if(!n)throw new Error("--db-url has no database name in its path");let r=t.searchParams.get("sslmode")??"prefer";return{host:t.hostname||"127.0.0.1",port:t.port?Number(t.port):5432,database:n,user:decodeURIComponent(t.username)||"postgres",password:decodeURIComponent(t.password),sslMode:r,label:`${t.hostname||"127.0.0.1"}/${n}`}}function dr(e,t){return new Promise(n=>{let r=Vd("psql",["--host",e.host,"--port",String(e.port),"--username",e.user,"--dbname",e.database,"--no-psqlrc","-v","ON_ERROR_STOP=1","-tA"],{stdio:["pipe","pipe","pipe"],env:{...process.env,PGPASSWORD:e.password,PGSSLMODE:e.sslMode}}),o="",s="";r.stdout.on("data",a=>o+=String(a)),r.stderr.on("data",a=>s+=String(a)),r.on("error",a=>{n({code:127,out:"",err:a.code==="ENOENT"?"psql is not installed, and gen types needs it to read the schema. `snoutdata db url` prints a connection string for any client.":String(a)})}),r.on("close",a=>n({code:a??0,out:o,err:s})),r.stdin.end(t)})}var eu=`
488
755
  with wanted as (select unnest($SCHEMAS$::text[]) as nspname)
489
756
  select json_build_object(
490
757
  'tables', coalesce((
@@ -588,30 +855,36 @@ select json_build_object(
588
855
  where t.typtype = 'e' and n.nspname in (select nspname from wanted)
589
856
  ), '[]'::json)
590
857
  )::text
591
- `;function Yl(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function Xl(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),d=a.columns.map(u=>({name:u.name,dataType:u.dataType,nullable:u.nullable,isPrimaryKey:i.has(u.name),default:u.default,autoIncrement:u.autoIncrement,...u.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:d,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(u=>({constraintName:u.constraintName,columns:u.columns??[],referencedTable:u.referencedTable,referencedColumns:u.referencedColumns??[]})),indexes:(a.indexes??[]).filter(u=>Array.isArray(u.columns)&&u.columns.every(f=>typeof f=="string")).map(u=>({name:u.name,unique:u.unique,columns:u.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(l=>({name:l.name,dataType:l.dataType,mode:l.mode}))})),s=e.enums.map(a=>({name:a.name,schema:a.schema,values:a.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}async function Zl(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?qt(e.dbUrl):zl(await Y(eu(e))),r=Vl.replace("$SCHEMAS$",Yl(t)),o=await(e.sql?e.sql(r):Gn(n,r));o.code===127&&!e.sql&&!e.dbUrl&&e.ref&&(o=await Ql(e.ref,r)),o.code===127&&R("tool-missing",o.err.trim()),o.code!==0&&R("failed",o.err.trim()||`could not read the schema of ${n.label}`);let s=o.out.trim();s||R("failed",`${n.label} returned nothing for the schema query`);let a;try{a=JSON.parse(s)}catch{R("failed",`could not read the schema of ${n.label}: the answer was not JSON`)}let{schema:l,enums:i}=Xl(a,n.database,Date.now()),d=Ns(l,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:i}),u=null;return e.out&&(u=Jl(e.out)?e.out:Wl(process.cwd(),e.out),Kl(u,d,"utf8")),{target:n.label,schemas:t,tables:l.tables.filter(f=>f.kind!=="view").length,views:l.tables.filter(f=>f.kind==="view").length,functions:(l.routines??[]).length,enums:i.length,path:u,types:d}}async function Ql(e,t,n=x){try{let r=await n("cloud-project-sql",{ref:e,sql:t});if(r.error)return{code:1,out:"",err:r.error};let o=Object.values(r.rows?.[0]??{})[0];return{code:0,out:typeof o=="string"?o:o==null?"":JSON.stringify(o),err:""}}catch(r){return{code:1,out:"",err:r instanceof Error?r.message:String(r)}}}function eu(e){if(!e.ref)throw new Error("no project: pass --ref, --db-url, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return e.ref}async function Ds(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&R("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await Zl(t);return r.path&&c(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),g(r,()=>{r.path||process.stdout.write(r.types)}),0}Z();import{createHash as hd}from"node:crypto";import{existsSync as gr,mkdirSync as yd,readFileSync as hr,readdirSync as wd,statSync as bd,writeFileSync as Sd}from"node:fs";import{dirname as xd,isAbsolute as ni,join as mt,resolve as ri}from"node:path";import{pathToFileURL as _d}from"node:url";K();function kd(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:ni(e)||e.startsWith("./")||e.startsWith("../")?_d(ri(e)).href:e}function Ed(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(kd(e)):Promise.resolve().then(()=>(Za(),Xa))}function vd(e){let t=e instanceof Error?e.message:String(e),n=process.env.SNOUTDATA_LOCAL_RUNTIME;return n?`SNOUTDATA_LOCAL_RUNTIME points at ${n}, and it could not be loaded: ${t}`:`this build of the CLI has no local pod runtime in it, so \`snoutdata start\` cannot run. That is a packaging fault rather than anything you did: please report it. (${t})`}var Qa="It is fetched from ghcr.io/snoutdata/snoutpod-postgres on the first `snoutdata start`, so this usually means that registry could not be reached from here.";async function ei(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function ft(){let e;try{e=await Ed()}catch(t){R("tool-missing",vd(t))}return{ready:async t=>{let n=await ei(e,t);return typeof n=="string"?await e.podmanReady(Qa)??n:e.podmanReady(Qa,n.name)},ensureImage:async(t,n)=>{let r=await ei(e,t);return typeof r=="string"?r:e.ensureImage(n,void 0,r)},prepare:()=>e.ensureNetwork(),start:(t,n)=>e.startLocal(e.localRuntime(),t,{onProgress:n}),stop:t=>e.stopLocal(e.localRuntime(),t),status:t=>e.localStatus(e.localRuntime(),t),remove:(t,n)=>e.removeLocal(e.localRuntime(),t,n),uri:t=>e.localUri(t),psql:async(t,n)=>{let r=await e.localPsql(t,n);return{code:r.code,out:r.stdout,err:r.stderr}},mint:t=>({ref:e.newLocalRef(),port:t,ownerPassword:e.newLocalPassword(),adminPassword:e.newLocalPassword()})}}function oi(e){return mt(e,".snoutdata","local.json")}function en(e){let t=oi(e);if(!gr(t))return null;try{let n=JSON.parse(hr(t,"utf8"));return!n.ref||!n.port||!n.ownerPassword||!n.adminPassword?null:{ref:n.ref,port:n.port,ownerPassword:n.ownerPassword,adminPassword:n.adminPassword}}catch{return null}}function Pd(e,t){let n=oi(e);return yd(xd(n),{recursive:!0}),Sd(n,`${JSON.stringify(t,null,2)}
592
- `,{encoding:"utf8",mode:384}),n}var $d=54322;async function si(e,t){let n=await e.ensureImage(t,o=>c(o));n&&R("tool-missing",n);let r=await e.ready(t);r&&R("tool-missing",r),await e.prepare()}function ai(e,t,n,r=Gn){let o=!1;return async s=>{if(o)return e.psql(t,s);let a=await r(n,s);return a.code!==127?a:(o=!0,e.psql(t,s))}}async function Td(e,t={}){let n=t.cwd??process.cwd(),r=en(n);await si(e,r?.ref??null);let o=r??e.mint(t.port??$d),s=r?null:Pd(n,o),a=await e.start(o,f=>c(f)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=ai(e,o,qt(l)),d=await Ad(i,n,t),u=await Rd(i,n,d.length>0||!!s);return{status:a,uri:l,applied:d,seeded:u,created:s}}async function Ad(e,t,n){let r=ri(n.dir===void 0?mt(t,"migrations"):ni(n.dir)?n.dir:mt(t,n.dir));if(!gr(r)||!bd(r).isDirectory())return[];let o=wd(r).filter(f=>f.endsWith(".sql")).map(f=>{let h=hr(mt(r,f),"utf8");return{name:f,sql:h,checksum:hd("sha256").update(h,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(Od);s.code!==0&&R(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${dt}`);let a=await e(`select name || E'\\t' || checksum from ${dt};`);a.code!==0&&R("failed",a.err.trim()||`could not read ${dt}`);let l=a.out.split(`
593
- `).map(f=>f.trim()).filter(f=>f.length>0).map(f=>{let h=f.indexOf(" ");return{name:f.slice(0,h),checksum:f.slice(h+1)}}),i=Bt(o,l,{outOfOrder:n.outOfOrder});i.ok||R("conflict",`Refused. This folder and the local database do not agree:
858
+ `;function tu(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function nu(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),u=a.columns.map(d=>({name:d.name,dataType:d.dataType,nullable:d.nullable,isPrimaryKey:i.has(d.name),default:d.default,autoIncrement:d.autoIncrement,...d.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(d=>({constraintName:d.constraintName,columns:d.columns??[],referencedTable:d.referencedTable,referencedColumns:d.referencedColumns??[]})),indexes:(a.indexes??[]).filter(d=>Array.isArray(d.columns)&&d.columns.every(f=>typeof f=="string")).map(d=>({name:d.name,unique:d.unique,columns:d.columns??[]}))}}),o=e.routines.map(a=>({name:a.name,schema:a.schema,kind:a.kind,returnType:a.returnType??void 0,parameters:(a.parameters??[]).map(l=>({name:l.name,dataType:l.dataType,mode:l.mode}))})),s=e.enums.map(a=>({name:a.name,schema:a.schema,values:a.values??[]}));return{schema:{database:t,tables:r,routines:o,fetchedAt:n},enums:s}}async function ru(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?rn(e.dbUrl):Qd(await ee(su(e))),r=eu.replace("$SCHEMAS$",tu(t)),o=await(e.sql?e.sql(r):dr(n,r));o.code===127&&!e.sql&&!e.dbUrl&&e.ref&&(o=await ou(e.ref,r)),o.code===127&&O("tool-missing",o.err.trim()),o.code!==0&&O("failed",o.err.trim()||`could not read the schema of ${n.label}`);let s=o.out.trim();s||O("failed",`${n.label} returned nothing for the schema query`);let a;try{a=JSON.parse(s)}catch{O("failed",`could not read the schema of ${n.label}: the answer was not JSON`)}let{schema:l,enums:i}=nu(a,n.database,Date.now()),u=ca(l,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:i}),d=null;return e.out&&(d=Xd(e.out)?e.out:Zd(process.cwd(),e.out),Yd(d,u,"utf8")),{target:n.label,schemas:t,tables:l.tables.filter(f=>f.kind!=="view").length,views:l.tables.filter(f=>f.kind==="view").length,functions:(l.routines??[]).length,enums:i.length,path:d,types:u}}async function ou(e,t,n=_){try{let r=await n("cloud-project-sql",{ref:e,sql:t});if(r.error)return{code:1,out:"",err:r.error};let o=Object.values(r.rows?.[0]??{})[0];return{code:0,out:typeof o=="string"?o:o==null?"":JSON.stringify(o),err:""}}catch(r){return{code:1,out:"",err:r instanceof Error?r.message:String(r)}}}function su(e){if(!e.ref)throw new Error("no project: pass --ref, --db-url, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return e.ref}async function la(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&O("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await ru(t);return r.path&&c(`Wrote ${r.tables} table${r.tables===1?"":"s"}, ${r.views} view${r.views===1?"":"s"}, ${r.functions} function${r.functions===1?"":"s"} and ${r.enums} enum${r.enums===1?"":"s"} to ${r.path}.`),g(r,()=>{r.path||process.stdout.write(r.types)}),0}Q();import{createHash as Am}from"node:crypto";import{existsSync as jr,mkdirSync as Pm,readFileSync as Lr,readdirSync as Rm,statSync as Tm,writeFileSync as Om}from"node:fs";import{dirname as Nm,isAbsolute as Gi,join as _t,resolve as qi}from"node:path";import{pathToFileURL as Dm}from"node:url";q();function Im(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:Gi(e)||e.startsWith("./")||e.startsWith("../")?Dm(qi(e)).href:e}function Mm(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(Im(e)):Promise.resolve().then(()=>(Ui(),Li))}function Cm(e){let t=e instanceof Error?e.message:String(e),n=process.env.SNOUTDATA_LOCAL_RUNTIME;return n?`SNOUTDATA_LOCAL_RUNTIME points at ${n}, and it could not be loaded: ${t}`:`this build of the CLI has no local pod runtime in it, so \`snoutdata start\` cannot run. That is a packaging fault rather than anything you did: please report it. (${t})`}var Fi="It is fetched from ghcr.io/snoutdata/snoutpod-postgres on the first `snoutdata start`, so this usually means that registry could not be reached from here.";async function Bi(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function xt(){let e;try{e=await Mm()}catch(t){O("tool-missing",Cm(t))}return{ready:async t=>{let n=await Bi(e,t);return typeof n=="string"?await e.podmanReady(Fi)??n:e.podmanReady(Fi,n.name)},ensureImage:async(t,n)=>{let r=await Bi(e,t);return typeof r=="string"?r:e.ensureImage(n,void 0,r)},prepare:()=>e.ensureNetwork(),start:(t,n)=>e.startLocal(e.localRuntime(),t,{onProgress:n}),stop:t=>e.stopLocal(e.localRuntime(),t),status:t=>e.localStatus(e.localRuntime(),t),remove:(t,n)=>e.removeLocal(e.localRuntime(),t,n),uri:t=>e.localUri(t),psql:async(t,n)=>{let r=await e.localPsql(t,n);return{code:r.code,out:r.stdout,err:r.stderr}},mint:t=>({ref:e.newLocalRef(),port:t,ownerPassword:e.newLocalPassword(),adminPassword:e.newLocalPassword()})}}function Ki(e){return _t(e,".snoutdata","local.json")}function hn(e){let t=Ki(e);if(!jr(t))return null;try{let n=JSON.parse(Lr(t,"utf8"));return!n.ref||!n.port||!n.ownerPassword||!n.adminPassword?null:{ref:n.ref,port:n.port,ownerPassword:n.ownerPassword,adminPassword:n.adminPassword}}catch{return null}}function jm(e,t){let n=Ki(e);return Pm(Nm(n),{recursive:!0}),Om(n,`${JSON.stringify(t,null,2)}
859
+ `,{encoding:"utf8",mode:384}),n}var Lm=54322;async function Wi(e,t){let n=await e.ensureImage(t,o=>c(o));n&&O("tool-missing",n);let r=await e.ready(t);r&&O("tool-missing",r),await e.prepare()}function Ji(e,t,n,r=dr){let o=!1;return async s=>{if(o)return e.psql(t,s);let a=await r(n,s);return a.code!==127?a:(o=!0,e.psql(t,s))}}async function Um(e,t={}){let n=t.cwd??process.cwd(),r=hn(n);await Wi(e,r?.ref??null);let o=r??e.mint(t.port??Lm),s=r?null:jm(n,o),a=await e.start(o,f=>c(f)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=Ji(e,o,rn(l)),u=await Fm(i,n,t),d=await Bm(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:d,created:s}}async function Fm(e,t,n){let r=qi(n.dir===void 0?_t(t,"migrations"):Gi(n.dir)?n.dir:_t(t,n.dir));if(!jr(r)||!Tm(r).isDirectory())return[];let o=Rm(r).filter(f=>f.endsWith(".sql")).map(f=>{let y=Lr(_t(r,f),"utf8");return{name:f,sql:y,checksum:Am("sha256").update(y,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(Hm);s.code!==0&&O(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${St}`);let a=await e(`select name || E'\\t' || checksum from ${St};`);a.code!==0&&O("failed",a.err.trim()||`could not read ${St}`);let l=a.out.split(`
860
+ `).map(f=>f.trim()).filter(f=>f.length>0).map(f=>{let y=f.indexOf(" ");return{name:f.slice(0,y),checksum:f.slice(y+1)}}),i=tn(o,l,{outOfOrder:n.outOfOrder});i.ok||O("conflict",`Refused. This folder and the local database do not agree:
594
861
  ${i.problems.map(f=>` ${f.reason}`).join(`
595
- `)}`,{problems:i.problems});let d=new Map(o.map(f=>[f.name,f])),u=[];for(let f of i.pending){let h=d.get(f.name),m=`insert into ${dt} (name, checksum) values (${ti(h.name)}, ${ti(h.checksum)});`,p=nt(h.sql);p||c(`${h.name} runs outside a transaction, so an interruption can leave it half applied.`);let b=p?`begin;
596
- ${h.sql}
862
+ `)}`,{problems:i.problems});let u=new Map(o.map(f=>[f.name,f])),d=[];for(let f of i.pending){let y=u.get(f.name),m=`insert into ${St} (name, checksum) values (${Hi(y.name)}, ${Hi(y.checksum)});`,p=ut(y.sql);p||c(`${y.name} runs outside a transaction, so an interruption can leave it half applied.`);let S=p?`begin;
863
+ ${y.sql}
597
864
  ${m}
598
865
  commit;
599
- `:`${h.sql}
866
+ `:`${y.sql}
600
867
  ${m}
601
- `,w=await e(b);w.code!==0&&R("failed",`${h.name} failed, and nothing after it was tried.
602
- ${w.err.trim()}`,{failed:h.name,applied:u}),u.push(h.name),c(` applied ${h.name}`)}return u}async function Rd(e,t,n){let r=mt(t,"seed.sql");if(!n||!gr(r))return!1;let o=await e(hr(r,"utf8"));return o.code!==0&&R("failed",`seed.sql failed.
603
- ${o.err.trim()}`),c(" ran seed.sql"),!0}var dt="_snoutdata_migrations",Od=`create table if not exists ${dt} (
868
+ `,w=await e(S);w.code!==0&&O("failed",`${y.name} failed, and nothing after it was tried.
869
+ ${w.err.trim()}`,{failed:y.name,applied:d}),d.push(y.name),c(` applied ${y.name}`)}return d}async function Bm(e,t,n){let r=_t(t,"seed.sql");if(!n||!jr(r))return!1;let o=await e(Lr(r,"utf8"));return o.code!==0&&O("failed",`seed.sql failed.
870
+ ${o.err.trim()}`),c(" ran seed.sql"),!0}var St="_snoutdata_migrations",Hm=`create table if not exists ${St} (
604
871
  name text primary key,
605
872
  checksum text not null,
606
873
  applied_at timestamptz not null default now()
607
- );`;function ti(e){return`'${e.replace(/'/g,"''")}'`}function Nd(e){if(e===null)return"unknown";let t=["B","kB","MB","GB"],n=e,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n<10&&r>0?n.toFixed(1):Math.round(n)} ${t[r]}`}async function ii(e,t={}){let n=await Td(e,t);return g(n,()=>{n.created&&(c(""),c(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),c(""),c(` ${$("Database")} running on port ${n.status.port}`),n.applied.length>0&&c(` ${$("Migrations")} ${n.applied.length} applied`),n.seeded&&c(` ${$("Seed")} seed.sql ran`),c(""),process.stdout.write(`${n.uri}
608
- `),c(S(" snoutdata gen types typescript --local > database.types.ts")),c(S(" snoutdata stop"))}),0}async function ci(e,t={}){let n=t.cwd??process.cwd(),r=en(n);r||R("not-found","there is no local database for this folder. `snoutdata start` makes one."),await si(e,r.ref);let o=await e.stop(r.ref);return g(o,()=>c("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function li(e,t={}){let n=t.cwd??process.cwd(),r=en(n);if(!r)return g({exists:!1,ref:null,status:"absent"},()=>c("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready(r.ref);if(o)return g({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>c(o)),0;let s=await e.status(r);return g({...s,uri:s.uri},()=>{c(` ${$("Project")} ${s.ref}`),c(` ${$("State")} ${s.ready?"running":s.status}`),c(` ${$("Port")} ${s.port}`),c(` ${$("Size")} ${Nd(s.databaseBytes)}`),s.services.length>0&&c(` ${$("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
609
- `)}),0}async function ui(e,t=process.cwd()){let n=en(t);n||R("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:ai(e,n,qt(r))}}G();K();_t();We();Ee();import{resolve as di}from"node:path";function Dd(){let e=t=>we(t);return{whoami:()=>De(),listProjects:async()=>({...await Q(),local:Md()}),localProject:t=>e(t),createProject:async t=>{let n=await x("cloud-project-create",{name:t.name,...t.region?{region:t.region}:{},...t.teamId?{teamId:t.teamId}:{}});try{return await Xe(n.project.ref)}catch{return{...n.project,ready:!1,note:`${n.project.ref} was created and is still starting. It was not ready within the wait. Call list_projects in a few seconds; do not connect to it yet.`}}},usage:(t,n)=>x("cloud-project-usage",{ref:t,...n?{days:n}:{}}),exportStatus:t=>Me(t),requestExport:t=>Ve(t),resetPassword:t=>x("cloud-project-reset-password",{ref:t}),listTeams:()=>x("cloud-team-list",{}),deployFunction:(t,n,r)=>{let o=e(t);return o?hn(o,n,r):_n(t,n,r).then(s=>s.answer)},listFunctions:async t=>{let n=e(t);return n?Ot(n):kn(t)},deleteFunction:(t,n)=>{let r=e(t);return r?yn(r,n):En(t,n)},sizeFunction:(t,n,r)=>vn(t,n,r),listFunctionSecrets:async t=>{let n=e(t);return n?Dt(n):x("cloud-project-secrets",{ref:t})},listTokens:()=>x("cloud-token-list",{}),createToken:(t,n,r)=>x("cloud-token-create",{name:t,...n?{expiresInDays:n}:{},...r?{project:r}:{}}),revokeToken:t=>x("cloud-token-revoke",{id:t}),getProject:async t=>{let n=e(t);return n?{ref:n.ref,name:n.name,local:n.folder,api:n.apiUrl,database:`127.0.0.1:${n.dbPort}`,functions:Ot(n).functions.map(r=>r.name),secretNames:Dt(n).secrets.map(r=>r.name)}:Mn(t)},getProducts:t=>je(t),setProduct:(t,n,r)=>Rn(t,n,r),listDomains:t=>Ct(t),domainAction:(t,n,r)=>On(t,n,r),restoreWindow:t=>Nn(t),restoreTo:(t,n,r)=>Dn(t,n,r),setProjectState:async(t,n)=>{let r=await x(`cloud-project-${t}`,{ref:n});try{let o=await Ye(n,t,{timeoutMs:12e4});return{changed:r.changed,state:o?.state??"deleted",...o?{project:o}:{}}}catch{return{...r,settled:!1,note:`${n} has not finished yet. Call list_projects in a few seconds.`}}},connection:t=>Y(t),pushMigrations:(t,n)=>Bn(t,{dir:n.dir,dryRun:n.dryRun})}}function Md(){let e=ht().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=qe(process.cwd())?.local;if(t&&!e.some(n=>di(n.folder)===di(t)))try{let n=pe(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var mi="app_";async function Id(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await St();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await Br(t)).map(o=>({name:`${mi}${o.name}`,description:`[SnoutData Studio, on this machine] ${o.description??o.name}`,inputSchema:o.inputSchema??{type:"object",properties:{}}})),config:t}}catch{return{tools:[],config:null}}}async function fi(e){ze(!0);let{tools:t,config:n}=await Id(),r=Dd();n&&(r.callBorrowed=(i,d)=>Hr(n,i.slice(mi.length),d));let o={...e,borrowed:t};V(t.length>0?`snoutdata mcp: ${It.length} cloud tools, and ${t.length} borrowed from SnoutData Studio on this machine.`:"snoutdata mcp: cloud tools only. SnoutData Studio is not answering here, so its database tools are not on this endpoint.");let s="";process.stdin.setEncoding("utf8");try{let i=await De();V(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){return V(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),V("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3}V(`snoutdata mcp: ready${e.allowDelete?" (delete_project is ON)":""}. Speaking MCP on stdin/stdout; stop it with Ctrl-C.`);let a=new Set;function l(i){a.add(i),i.finally(()=>a.delete(i))}return new Promise(i=>{process.stdin.on("data",d=>{let u=es(s+d);s=u.rest;for(let f of u.messages)l(jd(f,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;a.size>0;)await Promise.allSettled([...a]);i(0)})()}),process.stdin.on("error",()=>i(1))})}async function jd(e,t,n){let r;try{r=JSON.parse(e)}catch{V(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Qo(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
874
+ );`;function Hi(e){return`'${e.replace(/'/g,"''")}'`}function Gm(e){if(e===null)return"unknown";let t=["B","kB","MB","GB"],n=e,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n<10&&r>0?n.toFixed(1):Math.round(n)} ${t[r]}`}async function zi(e,t={}){let n=await Um(e,t);return g(n,()=>{n.created&&(c(""),c(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),c(""),c(` ${v("Database")} running on port ${n.status.port}`),n.applied.length>0&&c(` ${v("Migrations")} ${n.applied.length} applied`),n.seeded&&c(` ${v("Seed")} seed.sql ran`),c(""),process.stdout.write(`${n.uri}
875
+ `),c(b(" snoutdata gen types typescript --local > database.types.ts")),c(b(" snoutdata stop"))}),0}async function Vi(e,t={}){let n=t.cwd??process.cwd(),r=hn(n);r||O("not-found","there is no local database for this folder. `snoutdata start` makes one."),await Wi(e,r.ref);let o=await e.stop(r.ref);return g(o,()=>c("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function Yi(e,t={}){let n=t.cwd??process.cwd(),r=hn(n);if(!r)return g({exists:!1,ref:null,status:"absent"},()=>c("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready(r.ref);if(o)return g({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>c(o)),0;let s=await e.status(r);return g({...s,uri:s.uri},()=>{c(` ${v("Project")} ${s.ref}`),c(` ${v("State")} ${s.ready?"running":s.status}`),c(` ${v("Port")} ${s.port}`),c(` ${v("Size")} ${Gm(s.databaseBytes)}`),s.services.length>0&&c(` ${v("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
876
+ `)}),0}async function Xi(e,t=process.cwd()){let n=hn(t);n||O("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:Ji(e,n,rn(r))}}W();q();Ot();Qe();we();import{resolve as Zi}from"node:path";function qm(){let e=t=>ve(t);return{whoami:()=>Pe(),listProjects:async()=>({...await ne(),local:Km()}),localProject:t=>e(t),createProject:async t=>{let n=await _("cloud-project-create",{name:t.name,...t.region?{region:t.region}:{},...t.teamId?{teamId:t.teamId}:{}});try{return await at(n.project.ref)}catch{return{...n.project,ready:!1,note:`${n.project.ref} was created and is still starting. It was not ready within the wait. Call list_projects in a few seconds; do not connect to it yet.`}}},usage:(t,n)=>_("cloud-project-usage",{ref:t,...n?{days:n}:{}}),exportStatus:t=>Le(t),requestExport:t=>ot(t),resetPassword:t=>_("cloud-project-reset-password",{ref:t}),listTeams:()=>_("cloud-team-list",{}),deployFunction:(t,n,r)=>{let o=e(t);return o?Cn(o,n,r):Hn(t,n,r).then(s=>s.answer)},listFunctions:async t=>{let n=e(t);return n?qt(n):Gn(t)},deleteFunction:(t,n)=>{let r=e(t);return r?jn(r,n):qn(t,n)},sizeFunction:(t,n,r)=>Kn(t,n,r),listFunctionSecrets:async t=>{let n=e(t);return n?Wt(n):_("cloud-project-secrets",{ref:t})},listTokens:()=>_("cloud-token-list",{}),createToken:(t,n,r)=>_("cloud-token-create",{name:t,...n?{expiresInDays:n}:{},...r?{project:r}:{}}),revokeToken:t=>_("cloud-token-revoke",{id:t}),getProject:async t=>{let n=e(t);return n?{ref:n.ref,name:n.name,local:n.folder,api:n.apiUrl,database:`127.0.0.1:${n.dbPort}`,functions:qt(n).functions.map(r=>r.name),secretNames:Wt(n).secrets.map(r=>r.name)}:er(t)},getProducts:t=>Fe(t),setProduct:(t,n,r)=>Yn(t,n,r),listDomains:t=>Yt(t),domainAction:(t,n,r)=>Xn(t,n,r),restoreWindow:t=>Zn(t),restoreTo:(t,n,r)=>Qn(t,n,r),setProjectState:async(t,n)=>{let r=await _(`cloud-project-${t}`,{ref:n});try{let o=await st(n,t,{timeoutMs:12e4});return{changed:r.changed,state:o?.state??"deleted",...o?{project:o}:{}}}catch{return{...r,settled:!1,note:`${n} has not finished yet. Call list_projects in a few seconds.`}}},connection:t=>ee(t),pushMigrations:(t,n)=>ir(t,{dir:n.dir,dryRun:n.dryRun})}}function Km(){let e=Et().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=Ve(process.cwd())?.local;if(t&&!e.some(n=>Zi(n.folder)===Zi(t)))try{let n=Se(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var Qi="app_";async function Wm(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await Rt();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await lo(t)).map(o=>({name:`${Qi}${o.name}`,description:`[SnoutData Studio, on this machine] ${o.description??o.name}`,inputSchema:o.inputSchema??{type:"object",properties:{}}})),config:t}}catch{return{tools:[],config:null}}}async function ec(e){rt(!0);let{tools:t,config:n}=await Wm(),r=qm();n&&(r.callBorrowed=(i,u)=>uo(n,i.slice(Qi.length),u));let o={...e,borrowed:t};J(t.length>0?`snoutdata mcp: ${zt.length} cloud tools, and ${t.length} borrowed from SnoutData Studio on this machine.`:"snoutdata mcp: cloud tools only. SnoutData Studio is not answering here, so its database tools are not on this endpoint.");let s="";process.stdin.setEncoding("utf8");try{let i=await Pe();J(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){return J(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),J("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3}J(`snoutdata mcp: ready${e.allowDelete?" (delete_project is ON)":""}. Speaking MCP on stdin/stdout; stop it with Ctrl-C.`);let a=new Set;function l(i){a.add(i),i.finally(()=>a.delete(i))}return new Promise(i=>{process.stdin.on("data",u=>{let d=Es(s+u);s=d.rest;for(let f of d.messages)l(Jm(f,r,o))}),process.stdin.on("end",()=>{(async()=>{for(;a.size>0;)await Promise.allSettled([...a]);i(0)})()}),process.stdin.on("error",()=>i(1))})}async function Jm(e,t,n){let r;try{r=JSON.parse(e)}catch{J(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await vs(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
610
877
  `)}catch(o){let s=r.id??null;process.stdout.write(`${JSON.stringify({jsonrpc:"2.0",id:s,error:{code:-32603,message:o instanceof Error?o.message:String(o)}})}
611
- `)}}We();G();K();function Cd(e){return e>=1?"over":e>=.8?"near":"ok"}function pi(e,t){let n=null,r=null,o=0,s=0;for(let l of e)o+=Number.isFinite(l.computeSeconds)?l.computeSeconds:0,s+=Number.isFinite(l.connections)?l.connections:0,!(l.dbBytes===null||!Number.isFinite(l.dbBytes))&&(r=r===null?l.dbBytes:Math.max(r,l.dbBytes),(!n||l.day>=n.day)&&(n={day:l.day,dbBytes:l.dbBytes,repoBytes:l.repoBytes}));let a=n&&t&&t.maxStorageBytes>0?{limitBytes:t.maxStorageBytes,usedBytes:n.dbBytes,fraction:n.dbBytes/t.maxStorageBytes,state:Cd(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function yr(e){if(!Number.isFinite(e)||e<=0)return"none";let t=Math.floor(e/86400),n=Math.floor(e%86400/3600),r=Math.floor(e%3600/60);return t>0?n>0?`${t}d ${n}h`:`${t}d`:n>0?r>0?`${n}h ${r}m`:`${n}h`:r>0?`${r}m`:`${Math.round(e)}s`}async function gi(e,t={}){let n=await x("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=pi(n.days,n.limit);return g({...n,summary:r},()=>{if(c($(`${n.name} (${n.ref})`)),!r.latest)c("Nothing measured yet. The host samples every fifteen minutes.");else if(r.storage){let o=Math.round(r.storage.fraction*100),s=`${de(r.storage.usedBytes)} of ${de(r.storage.limitBytes)} on ${n.limit?.tier}`;c(r.storage.state==="over"?`${s} (${o}%). Over the limit, so writes are refused until it shrinks or the plan changes.`:r.storage.state==="near"?`${s} (${o}%). Close to the limit: at 100% the database goes read-only.`:`${s} (${o}%).`)}else c(`${de(r.latest.dbBytes)} as of ${r.latest.day}.`);n.readOnly&&r.storage?.state!=="over"&&c("This project is currently read-only."),c(`Over ${r.days} day${r.days===1?"":"s"}: ${yr(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&c(S(`Peak in that window was ${de(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(c(""),c(F([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":de(o.dbBytes),o.repoBytes===null?"-":de(o.repoBytes),yr(o.computeSeconds),String(o.connections)])])),c(S("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}var pt="0.10.1",hi=`snoutdata ${pt} \u2014 hosted Postgres, from a terminal or an agent
878
+ `)}}Qe();W();q();function zm(e){return e>=1?"over":e>=.8?"near":"ok"}function tc(e,t){let n=null,r=null,o=0,s=0;for(let l of e)o+=Number.isFinite(l.computeSeconds)?l.computeSeconds:0,s+=Number.isFinite(l.connections)?l.connections:0,!(l.dbBytes===null||!Number.isFinite(l.dbBytes))&&(r=r===null?l.dbBytes:Math.max(r,l.dbBytes),(!n||l.day>=n.day)&&(n={day:l.day,dbBytes:l.dbBytes,repoBytes:l.repoBytes}));let a=n&&t&&t.maxStorageBytes>0?{limitBytes:t.maxStorageBytes,usedBytes:n.dbBytes,fraction:n.dbBytes/t.maxStorageBytes,state:zm(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function Ur(e){if(!Number.isFinite(e)||e<=0)return"none";let t=Math.floor(e/86400),n=Math.floor(e%86400/3600),r=Math.floor(e%3600/60);return t>0?n>0?`${t}d ${n}h`:`${t}d`:n>0?r>0?`${n}h ${r}m`:`${n}h`:r>0?`${r}m`:`${Math.round(e)}s`}async function nc(e,t={}){let n=await _("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=tc(n.days,n.limit);return g({...n,summary:r},()=>{if(c(v(`${n.name} (${n.ref})`)),!r.latest)c("Nothing measured yet. The host samples every fifteen minutes.");else if(r.storage){let o=Math.round(r.storage.fraction*100),s=`${ge(r.storage.usedBytes)} of ${ge(r.storage.limitBytes)} on ${n.limit?.tier}`;c(r.storage.state==="over"?`${s} (${o}%). Over the limit, so writes are refused until it shrinks or the plan changes.`:r.storage.state==="near"?`${s} (${o}%). Close to the limit: at 100% the database goes read-only.`:`${s} (${o}%).`)}else c(`${ge(r.latest.dbBytes)} as of ${r.latest.day}.`);n.readOnly&&r.storage?.state!=="over"&&c("This project is currently read-only."),c(`Over ${r.days} day${r.days===1?"":"s"}: ${Ur(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&c(b(`Peak in that window was ${ge(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(c(""),c(F([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":ge(o.dbBytes),o.repoBytes===null?"-":ge(o.repoBytes),Ur(o.computeSeconds),String(o.connections)])])),c(b("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}Q();q();tt();import{spawn as Vm}from"node:child_process";import{createHash as Ym}from"node:crypto";import{chmodSync as Xm,renameSync as yn,rmSync as rc,writeFileSync as Zm}from"node:fs";import{dirname as Qm,join as ef}from"node:path";var wn=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",tf="https://github.com/snoutdata/app/releases/download";function nf(e,t){let n=t==="x64"||t==="amd64"?"x64":t==="arm64"?"arm64":null;return n?e==="linux"||e==="darwin"?`snoutdata-${e}-${n}`:e==="win32"&&n==="x64"?"snoutdata-windows-x64.exe":null:null}function rf(e,t){for(let n of e.split(/\r?\n/)){let r=/^([0-9a-f]{64})\s+\*?(.+)$/i.exec(n.trim());if(r&&r[2]===t)return r[1].toLowerCase()}return null}async function of(){let e;try{e=await fetch(`${wn}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new N("network",`could not reach ${wn} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new N("server",`${wn} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new N("server",`${wn} did not say which version is newest`);return t.version}async function oc(e){let t;try{t=await fetch(e)}catch(n){throw new N("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new N("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function sf(e,t){let n=ef(Qm(e),`.snoutdata-upgrade-${process.pid}`);Zm(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;rc(r,{force:!0}),yn(e,r);try{yn(n,e)}catch(o){throw yn(r,e),o}}else Xm(n,493),yn(n,e)}catch(r){rc(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new N("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?ue:et}`):r}}function af(){return new Promise((e,t)=>{let n=Vm(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new N("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${ue}`))),n.on("exit",r=>e(r??1))})}async function sc(e={}){let t=e.kind??Dt(),n=await of(),r=fo(V,n),o={installed:V,latest:n,upToDate:!r,install:t};if(!r||e.check)return g({...o,upgraded:!1},()=>{c(r?`snoutdata ${V} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${V} is the newest version.`)}),0;switch(t){case"npx":return g({...o,upgraded:!1},()=>{c(`This is a copy npx fetched (${V}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new N("usage",`this snoutdata (${V}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?ue:et}`);case"npm":{c(`Upgrading snoutdata ${V} to ${n} with npm\u2026`);let s=await af();if(s!==0)throw new N("failed",`npm exited ${s}. Run it yourself: ${ue}`);return g({...o,upgraded:!0},()=>c(`Upgraded to ${n}.`)),0}case"binary":{let s=nf(process.platform,process.arch);if(!s)throw new N("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${ue}`);c(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${tf}/cli-v${n}`,[l,i]=await Promise.all([oc(`${a}/${s}`),oc(`${a}/SHA256SUMS`)]),u=rf(i.toString("utf8"),s);if(!u)throw new N("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let d=Ym("sha256").update(l).digest("hex");if(d!==u)throw new N("failed",`checksum mismatch for ${s}. Refusing to install.
879
+ expected ${u}
880
+ actual ${d}`);return sf(process.execPath,l),g({...o,upgraded:!0,path:process.execPath},()=>c(`Upgraded ${process.execPath} to ${n}.`)),0}}}Ft();we();tt();var ac=`snoutdata ${V} \u2014 hosted Postgres, from a terminal or an agent
612
881
 
613
- snoutdata init [--name X] [--env] a database for this folder, linked, ready to use
882
+ snoutdata init [--name X] [--env] a database for this folder, linked, ready to use;
883
+ --env writes DATABASE_URL into ./.env (and adds
884
+ .env to .gitignore) instead of printing it
614
885
  snoutdata login [--provider github] sign in through a browser
886
+ snoutdata login --email you@x.com sign in as that account: the browser offers it,
887
+ and a different account is reported at once
615
888
  snoutdata login --sso [--domain D] sign in with your company's identity provider;
616
889
  D is a work email or a domain, and it is asked
617
890
  for when not given. Only the domain is sent
@@ -619,20 +892,24 @@ ${o.err.trim()}`),c(" ran seed.sql"),!0}var dt="_snoutdata_migrations",Od=`crea
619
892
  snoutdata login --no-browser print the URL instead of opening one
620
893
  snoutdata logout
621
894
  snoutdata whoami
895
+ snoutdata upgrade [--check] install the newest CLI, the way this one was
622
896
 
623
897
  snoutdata tokens create --name ci [--expires DAYS] [--project REF] a credential for CI, shown once
624
898
  snoutdata tokens list
625
899
  snoutdata tokens revoke <id|sdt_prefix>
626
900
 
627
901
  snoutdata projects list
628
- snoutdata projects create --name X [--region R] [--no-wait]
902
+ snoutdata projects create --name X [--region R] [--no-wait] [--show-url]
903
+ prints the ref; the connection string (it holds
904
+ the password) only with --show-url, or from db url
629
905
  snoutdata projects pause|resume|delete [--ref R] [--no-wait]
630
906
  snoutdata link --ref R write .snoutdata/project.json here
631
907
  snoutdata link --local [NAME|FOLDER] use a local project (Studio's, or a snout-stack folder) here
632
908
 
633
909
  snoutdata projects show [--ref R] one project: state, products, functions, domains
634
910
 
635
- snoutdata products [--ref R] auth, storage, the data API and push: on or off
911
+ snoutdata products [--ref R] auth, storage, the data API and push: on or off;
912
+ Realtime is always on (shown, not switched)
636
913
  snoutdata products enable|disable auth|storage|data-api|push [--ref R]
637
914
  snoutdata push credentials [--ref R] push keys: what is set (never the keys)
638
915
  snoutdata push credentials set apns --p8 FILE --key-id ID --team-id ID --topic BUNDLE [--environment E]
@@ -641,10 +918,16 @@ ${o.err.trim()}`),c(" ran seed.sql"),!0}var dt="_snoutdata_migrations",Od=`crea
641
918
  snoutdata auth [--ref R] Google sign-in and redirect addresses
642
919
  \u2026 | snoutdata auth google --client-id ID --stdin your own Google client; secret on stdin
643
920
  snoutdata auth google off
921
+ snoutdata auth anonymous on|off guest sign-in: signInAnonymously(), no email or password
644
922
  snoutdata auth redirects --site-url URL --allow URL,URL
645
923
  snoutdata auth templates the five auth emails, ours or yours
646
924
  snoutdata auth template KIND --file body.html [--subject S] | reset
647
925
 
926
+ snoutdata realtime inspect [--channel C] [--watch] [--ref R]
927
+ channels open now: who, their presence, the last minute
928
+ snoutdata realtime logs [--since 10m] [--channel C] [--ref R]
929
+ connects, joins, leaves and disconnects, with why
930
+
648
931
  snoutdata domains [--ref R] your own domains in front of the project's API
649
932
  snoutdata domains add|verify|remove <hostname> [--ref R]
650
933
 
@@ -690,7 +973,9 @@ ${o.err.trim()}`),c(" ran seed.sql"),!0}var dt="_snoutdata_migrations",Od=`crea
690
973
  Every command takes --json, --quiet and --help, and anything that waits takes --timeout
691
974
  SECONDS. A failure in --json mode is {"ok":false,"code","error"} on stdout, and the exit
692
975
  code says the same thing more coarsely: 2 the command was wrong, 3 the credential, 4 not
693
- ready yet, 5 forbidden, 6 not found, 7 conflict, 8 quota, 9 network, 10 timed out.
976
+ ready yet, 5 forbidden, 6 not found, 7 conflict, 8 quota, 9 network, 10 timed out, 11 this
977
+ CLI is out of date ("code":"outdated", run "snoutdata upgrade"). "<command> --help"
978
+ explains each of a command's flags.
694
979
 
695
980
  With no credential and a person present, sign-in is offered: SnoutData Studio if it is
696
981
  running here, then a pairing code. With no person (not a terminal, --json, CI, or
@@ -698,16 +983,17 @@ SNOUTDATA_NO_INTERACTIVE) nothing is asked and it exits 3 at once.
698
983
 
699
984
  A project comes from --ref, then SNOUTDATA_PROJECT, then
700
985
  .snoutdata/project.json in this folder or a parent. A token comes from
701
- SNOUTDATA_ACCESS_TOKEN, then ~/.snoutdata/auth.json. The session that login writes lasts
702
- an hour, so CI wants "tokens create", which does not expire.
986
+ SNOUTDATA_ACCESS_TOKEN, then ~/.snoutdata/auth.json. The session that login writes renews
987
+ itself as it is used; CI, which has no browser to sign in with, wants "tokens create".
703
988
 
704
- Docs: https://docs.snoutdata.com/cloud/cli
989
+ Docs: https://docs.snoutdata.com/developers/cli
705
990
  Examples: https://github.com/snoutdata/snoutdata (a star helps other people find it)
706
991
  Source: https://github.com/snoutdata/snout-cli
707
- `;async function Ld(e){let[t,n,...r]=e.command;if(H(e,"version")||t==="version")return g({version:pt},()=>process.stdout.write(`${pt}
708
- `)),0;if(H(e,"help")&&t&&t!=="help"){let i=tn.filter(d=>d.name===t||d.name.startsWith(`${t} `));if(i.length>0)return g({commands:i},()=>{for(let d of i){let u=d.flags.length>0?` ${d.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${d.name}${u}
709
- ${d.summary}
992
+ `;async function cf(e){let[t,n,...r]=e.command;if(G(e,"version")||t==="version")return g({version:V},()=>process.stdout.write(`${V}
993
+ `)),0;if(G(e,"help")&&t&&t!=="help"){let i=bn.filter(u=>u.name===t||u.name.startsWith(`${t} `));if(i.length>0)return g({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(d=>[d,Sn(u.name,d)]))}))},()=>{for(let u of i){let d=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${d}
994
+ ${u.summary}
995
+ `);let f=Math.max(0,...u.flags.map(y=>y.length));for(let y of u.flags)process.stdout.write(` ${y.padEnd(f)} ${Sn(u.name,y)}
710
996
  `)}process.stdout.write(`
711
997
  Every command also takes --json, --quiet and --help.
712
- `)}),0}let o=H(e,"help")||t==="help";if(o||!t)return g({version:pt,usage:hi.trim(),commands:tn},()=>process.stdout.write(hi)),o?0:ce.usage;let s=()=>{let i=ke(e,"timeout");if(i!==void 0){if(!(i>0))throw new y("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=Ke({flag:O(e,"ref")});if(!i)throw new y("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=Ke({flag:O(e,"ref")});return i?we(i):null};switch(t){case"login":{if(H(e,"device")){let f=await kt({name:xt(),platform:process.platform});g({verifyUrl:f.verifyUrl,code:f.code,expiresAt:f.expiresAt},()=>{c(""),c(` Open ${f.verifyUrl}`),c(` Type ${f.code}`),c(""),c(S("Waiting for that to be approved\u2026"))});let h=await Et(f.secret,{intervalMs:Math.max(1,f.interval)*1e3,expiresAt:f.expiresAt});return ie({accessToken:h.token}),g({ok:!0,name:h.name??null},()=>c("Signed in.")),0}let i=O(e,"domain"),d=H(e,"sso")||i!==void 0;if(d&&O(e,"provider"))throw new y("--sso and --provider are two different doors: pass one or the other");let u=await $t({provider:O(e,"provider"),sso:d,domain:i,noBrowser:H(e,"no-browser")||!ge(),timeoutMs:s()});return g({email:u.email??null},()=>c(`Signed in${u.email?` as ${u.email}`:""}.`)),0}case"logout":{Tr();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return g({ok:!0,environmentTokenStillSet:i},()=>{c("Signed out."),i&&(c("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),c(S(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await De();return g(i,()=>{process.stdout.write(`${i.email??i.id}
713
- `),i.token&&c(`via access token ${i.token.prefix} (${i.token.name})${i.token.project?`, limited to project ${i.token.project}`:""}.`)}),0}case"tokens":switch(n){case void 0:case"list":return await Ro(),0;case"create":{let i=O(e,"name")??r[0];if(!i)throw new y("tokens create needs --name (what is it for, and where does it live)");return await Ao({name:i,expiresInDays:ke(e,"expires"),project:O(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new y("tokens revoke needs the token's id or its sdt_ prefix");return await Oo(i),0}default:throw new y(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await Eo(),0;case"create":{let i=O(e,"name")??r[0];if(!i)throw new y("projects create needs --name");let d=O(e,"team");return await bn({teamId:d?await To(d):void 0,name:i,region:O(e,"region"),wait:!H(e,"no-wait"),timeoutMs:s(),link:void 0}),0}case"pause":case"resume":case"delete":return await vo(n,O(e,"ref")??r[0]??a(),{wait:!H(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=O(e,"ref")??r[0]??a(),d=we(i);d?io(d):await is(i)}return 0;default:throw new y(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new y("functions deploy needs a name: snoutdata functions deploy <name>");return await yo(i,r[0],{dir:O(e,"dir"),verifyJwt:!H(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return wo(i),0;if((n==="delete"||n==="remove")&&r[0])return await bo(i,r[0]),0}switch(n){case"deploy":{let d=r[0];if(!d)throw new y("functions deploy needs a name: snoutdata functions deploy <name>");return await Co(a(),d,{dir:O(e,"dir"),entrypoint:O(e,"entrypoint"),verifyJwt:!H(e,"no-verify-jwt")}),0}case void 0:case"list":return await Fo(a()),0;case"size":{let d=r[0];if(!d)throw new y("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await Uo(a(),d,{memoryMb:ke(e,"memory"),concurrency:ke(e,"concurrency"),reset:H(e,"reset")}),0}case"delete":case"remove":{let d=r[0];if(!d)throw new y("functions delete needs a name");return await Bo(a(),d),0}default:throw new y(`unknown command: functions ${n}`)}}case"secrets":{let i=l();if(i){if(n==="set")return await xo(i,await Go(r,{stdin:H(e,"stdin")})),0;if(n===void 0||n==="list")return _o(i),0;if((n==="unset"||n==="remove")&&r[0])return await ko(i,r[0]),0}switch(n){case"set":return await Ko(a(),r,{stdin:H(e,"stdin")}),0;case void 0:case"list":return await Wo(a()),0;case"unset":case"remove":{let d=r[0];if(!d)throw new y("secrets unset needs a name");return await Jo(a(),d),0}default:throw new y(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await No(a()),0;case"rotate":return await Do(a(),{force:H(e,"force")}),0;default:throw new y(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await ns(a()),0;if(n==="enable"||n==="disable")return await rs(a(),ts(r[0]),n==="enable"),0;throw new y(`unknown command: products ${n}. products [enable|disable ${jt.join("|")}]`)}case"push":{if(n!=="credentials")throw new y("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await Ts(a()),0;if(i==="set"){let d=Hn(r[1]),u=d==="apns"?await Ps({p8:O(e,"p8"),keyId:O(e,"key-id"),teamId:O(e,"team-id"),topic:O(e,"topic"),environment:O(e,"environment")}):await $s(O(e,"file"));return await As(a(),d,u),0}if(i==="remove"||i==="unset")return await Rs(a(),Hn(r[1])),0;throw new y(`unknown command: push credentials ${i}`)}case"auth":return await xs(a(),n,r,{clientId:O(e,"client-id"),stdin:H(e,"stdin"),siteUrl:O(e,"site-url"),allow:O(e,"allow"),file:O(e,"file"),subject:O(e,"subject")}),0;case"domains":return await os(a(),n,r[0]),0;case"teams":return await $o(),0;case"link":{if(H(e,"local")){let u;try{u=Nr(n)}catch(h){throw new U("not-found",h instanceof Error?h.message:String(h))}let f=Ge(process.cwd(),{ref:u.ref,...u.name?{name:u.name}:{},local:u.folder});return g({ref:u.ref,local:u.folder,api:u.apiUrl,path:f},()=>{c(`Linked ${u.name??u.ref}, the local project in ${u.folder} (${f}).`),c(`Its API is ${u.apiUrl}; its database is on 127.0.0.1:${u.dbPort}.`)}),0}let i=O(e,"ref")??n;if(!i)throw new y("link needs --ref");let d=Ge(process.cwd(),{ref:i});return g({ref:i,path:d},()=>c(`Linked ${i} (${d}).`)),0}case"db":switch(n){case"url":return await jn(a()),0;case"psql":return ps(a(),r);case"reset-password":return await Po(a()),0;case"push":return Es(a(),{dir:O(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return H(e,"window")?(await ss(a()),0):O(e,"at")?(await as(a(),O(e,"at"),O(e,"name")),0):ys(a(),{file:O(e,"file"),force:!!e.flags.force});case"export":return hs(a(),{timeoutMs:s(),out:O(e,"out"),statusOnly:!!e.flags.status});default:throw new y(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, export, push, restore")}case"gen":{if(n!=="types")throw new y(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=H(e,"local")?await ui(await ft()):null,d=i?i.dbUrl:O(e,"db-url");return Ds(r[0],{ref:d?void 0:a(),dbUrl:d,schemas:(O(e,"schema")??"public").split(",").map(u=>u.trim()).filter(Boolean),out:O(e,"out"),sql:i?.sql})}case"start":{let i=l();if(i)return so(i)}return ii(await ft(),{port:ke(e,"port"),dir:O(e,"dir"),noMigrations:H(e,"no-migrations"),outOfOrder:H(e,"out-of-order")});case"stop":{let i=l();return i?ao(i):ci(await ft())}case"status":{let i=l();return i?oo(i):li(await ft())}case"usage":return gi(a(),{days:ke(e,"days"),history:!!e.flags.history});case"mcp":return fi({version:pt,allowDelete:!!e.flags["allow-delete"]});case"init":return Ud(e);default:throw new y(`unknown command: ${t}`)}}async function Ud(e){let t=Ke({flag:O(e,"ref")});if(t)return c(`This folder is already linked to ${t}.`),H(e,"env")?(await yi(t),0):(await jn(t),0);let n=O(e,"name")??Fd(process.cwd());c(`Creating a project called "${n}"\u2026`),await bn({name:n,region:O(e,"region"),wait:!0,link:process.cwd()});let r=Ke({cwd:process.cwd()});return r&&H(e,"env")&&await yi(r),0}async function yi(e){let{connection:t}=await Promise.resolve().then(()=>(G(),be)),n=await t(e),r=gs(process.cwd(),n.uri);c(`${r.replaced?"Updated":"Wrote"} DATABASE_URL in ${r.path}.`),r.ignored&&c("Added .env to .gitignore, since it holds the password.")}function Fd(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function Bd(e){if(e instanceof U)return he(e.code,e.message,e.details),e.exitCode;if(e instanceof y)return he("usage",e.message),ce.usage;if(e instanceof ye)return he("not-signed-in",e.message),ce["not-signed-in"];if(e instanceof se){let r=wt(e.status);return he(r,e.message,{status:e.status}),ce[r]}let t=Ir(e);if(t)return he("usage",t),ce.usage;let n=jr(e);return he(n,e instanceof Error?e.message:String(e)),ce[n]}function wr(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function Hd(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function qd(){ze(Hd(process.argv.slice(2)));let e;try{e=xr(process.argv.slice(2))}catch(t){he("usage",String(t instanceof Error?t.message:t)),wr(ce.usage);return}ze(e.json),Jr(e.flags.quiet===!0),Cr(bt({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{wr(await Ld(e))}catch(t){wr(Bd(t))}}qd();
998
+ `)}),0}let o=G(e,"help")||t==="help";if(o||!t)return g({version:V,usage:ac.trim(),commands:bn},()=>process.stdout.write(ac)),o?0:de.usage;let s=()=>{let i=Ae(e,"timeout");if(i!==void 0){if(!(i>0))throw new h("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=Xe({flag:A(e,"ref")});if(!i)throw new h("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=Xe({flag:A(e,"ref")});return i?ve(i):null};switch(t){case"login":{if(G(e,"device")){let f=await It({name:Tt(),platform:process.platform});g({verifyUrl:f.verifyUrl,code:f.code,expiresAt:f.expiresAt},()=>{c(""),c(` Open ${f.verifyUrl}`),c(` Type ${f.code}`),c(""),c(b("Waiting for that to be approved\u2026"))});let y=await Mt(f.secret,{intervalMs:Math.max(1,f.interval)*1e3,expiresAt:f.expiresAt});ae({accessToken:y.token});let m=A(e,"email"),p=m?(await Pe()).email??void 0:void 0;return m&&Nn(m,{accessToken:y.token,email:p}),g({ok:!0,name:y.name??null,email:p??null},()=>c(`Signed in${p?` as ${p}`:""}.`)),0}let i=A(e,"domain"),u=G(e,"sso")||i!==void 0;if(u&&A(e,"provider"))throw new h("--sso and --provider are two different doors: pass one or the other");let d=await Ut({provider:A(e,"provider"),sso:u,domain:i,noBrowser:G(e,"no-browser")||!_e(),timeoutMs:s(),email:A(e,"email")});return g({email:d.email??null},()=>c(`Signed in${d.email?` as ${d.email}`:""}.`)),0}case"logout":{Yr();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return g({ok:!0,environmentTokenStillSet:i},()=>{c("Signed out."),i&&(c("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),c(b(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await Pe(),u=vt()?.expectedEmail,d=!!(u&&i.email&&!Dn(u,i.email));return d&&J(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),g({...i,...u?{expectedEmail:u,matchesExpected:!d}:{}},()=>{process.stdout.write(`${i.email??i.id}
999
+ `),i.token&&c(`via access token ${i.token.prefix} (${i.token.name})${i.token.project?`, limited to project ${i.token.project}`:""}.`)}),0}case"tokens":switch(n){case void 0:case"list":return await ts(),0;case"create":{let i=A(e,"name")??r[0];if(!i)throw new h("tokens create needs --name (what is it for, and where does it live)");return await es({name:i,expiresInDays:Ae(e,"expires"),project:A(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new h("tokens revoke needs the token's id or its sdt_ prefix");return await ns(i),0}default:throw new h(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await Vo(),0;case"create":{let i=A(e,"name")??r[0];if(!i)throw new h("projects create needs --name");let u=A(e,"team");return await Un({teamId:u?await Qo(u):void 0,name:i,region:A(e,"region"),wait:!G(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:G(e,"show-url")}),0}case"pause":case"resume":case"delete":return await Yo(n,A(e,"ref")??r[0]??a(),{wait:!G(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=A(e,"ref")??r[0]??a(),u=ve(i);u?Do(u):await Ns(i)}return 0;default:throw new h(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new h("functions deploy needs a name: snoutdata functions deploy <name>");return await Ho(i,r[0],{dir:A(e,"dir"),verifyJwt:!G(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return Go(i),0;if((n==="delete"||n==="remove")&&r[0])return await qo(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new h("functions deploy needs a name: snoutdata functions deploy <name>");return await cs(a(),u,{dir:A(e,"dir"),entrypoint:A(e,"entrypoint"),verifyJwt:!G(e,"no-verify-jwt")}),0}case void 0:case"list":return await us(a()),0;case"size":{let u=r[0];if(!u)throw new h("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await ds(a(),u,{memoryMb:Ae(e,"memory"),concurrency:Ae(e,"concurrency"),reset:G(e,"reset")}),0}case"delete":case"remove":{let u=r[0];if(!u)throw new h("functions delete needs a name");return await ms(a(),u),0}default:throw new h(`unknown command: functions ${n}`)}}case"secrets":{let i=l();if(i){if(n==="set")return await Wo(i,await gs(r,{stdin:G(e,"stdin")})),0;if(n===void 0||n==="list")return Jo(i),0;if((n==="unset"||n==="remove")&&r[0])return await zo(i,r[0]),0}switch(n){case"set":return await hs(a(),r,{stdin:G(e,"stdin")}),0;case void 0:case"list":return await ws(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new h("secrets unset needs a name");return await ys(a(),u),0}default:throw new h(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await rs(a()),0;case"rotate":return await os(a(),{force:G(e,"force")}),0;default:throw new h(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await As(a()),0;if(n==="enable"||n==="disable")return await Ps(a(),$s(r[0]),n==="enable"),0;throw new h(`unknown command: products ${n}. products [enable|disable ${Vt.join("|")}]`)}case"push":{if(n!=="credentials")throw new h("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await oa(a()),0;if(i==="set"){let u=cr(r[1]),d=u==="apns"?await na({p8:A(e,"p8"),keyId:A(e,"key-id"),teamId:A(e,"team-id"),topic:A(e,"topic"),environment:A(e,"environment")}):await ra(A(e,"file"));return await sa(a(),u,d),0}if(i==="remove"||i==="unset")return await aa(a(),cr(r[1])),0;throw new h(`unknown command: push credentials ${i}`)}case"auth":return await Xs(a(),n,r,{clientId:A(e,"client-id"),stdin:G(e,"stdin"),siteUrl:A(e,"site-url"),allow:A(e,"allow"),file:A(e,"file"),subject:A(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return Ws(a(),{channel:A(e,"channel"),watch:G(e,"watch")});if(n==="logs")return Js(a(),{channel:A(e,"channel"),since:A(e,"since")});throw new h(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await Rs(a(),n,r[0]),0;case"teams":return await Zo(),0;case"link":{if(G(e,"local")){let d;try{d=eo(n)}catch(y){throw new N("not-found",y instanceof Error?y.message:String(y))}let f=Ye(process.cwd(),{ref:d.ref,...d.name?{name:d.name}:{},local:d.folder});return g({ref:d.ref,local:d.folder,api:d.apiUrl,path:f},()=>{c(`Linked ${d.name??d.ref}, the local project in ${d.folder} (${f}).`),c(`Its API is ${d.apiUrl}; its database is on 127.0.0.1:${d.dbPort}.`)}),0}let i=A(e,"ref")??n;if(!i)throw new h("link needs --ref");let u=Ye(process.cwd(),{ref:i});return g({ref:i,path:u},()=>c(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await nr(a()),0;case"psql":return Us(a(),r);case"reset-password":return await Xo(a()),0;case"access":return await Gs(a(),r,{level:A(e,"level")}),0;case"push":return ea(a(),{dir:A(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return G(e,"window")?(await Ts(a()),0):A(e,"at")?(await Os(a(),A(e,"at"),A(e,"name")),0):Hs(a(),{file:A(e,"file"),force:!!e.flags.force});case"export":return Bs(a(),{timeoutMs:s(),out:A(e,"out"),statusOnly:!!e.flags.status});default:throw new h(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new h(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=G(e,"local")?await Xi(await xt()):null,u=i?i.dbUrl:A(e,"db-url");return la(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:(A(e,"schema")??"public").split(",").map(d=>d.trim()).filter(Boolean),out:A(e,"out"),sql:i?.sql})}case"start":{let i=l();if(i)return Oo(i)}return zi(await xt(),{port:Ae(e,"port"),dir:A(e,"dir"),noMigrations:G(e,"no-migrations"),outOfOrder:G(e,"out-of-order")});case"stop":{let i=l();return i?No(i):Vi(await xt())}case"status":{let i=l();return i?To(i):Yi(await xt())}case"usage":return nc(a(),{days:Ae(e,"days"),history:!!e.flags.history});case"mcp":return ec({version:V,allowDelete:!!e.flags["allow-delete"]});case"init":return lf(e);case"upgrade":return sc({check:G(e,"check")});default:throw new h(`unknown command: ${t}`)}}async function lf(e){let t=Xe({flag:A(e,"ref")});if(t)return c(`This folder is already linked to ${t}.`),G(e,"env")?(await ic(t),0):(await nr(t),0);let n=A(e,"name")??df(process.cwd());c(`Creating a project called "${n}"\u2026`),await Un({name:n,region:A(e,"region"),wait:!0,link:process.cwd(),showUrl:!G(e,"env")});let r=Xe({cwd:process.cwd()});return r&&G(e,"env")&&await ic(r),0}async function ic(e){let{connection:t}=await Promise.resolve().then(()=>(W(),pe)),n=await t(e),r=Fs(process.cwd(),n.uri);c(`${r.replaced?"Updated":"Wrote"} DATABASE_URL in ${r.path}.`),r.ignored&&c("Added .env to .gitignore, since it holds the password.")}function df(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function uf(e){if(e instanceof N)return xe(e.code,e.message,e.details),e.exitCode;if(e instanceof h)return xe("usage",e.message),de.usage;if(e instanceof ke)return xe("not-signed-in",e.message),de["not-signed-in"];if(e instanceof ce){let r=At(e.status);return xe(r,e.message,{status:e.status}),de[r]}let t=ro(e);if(t)return xe("usage",t),de.usage;let n=oo(e);return xe(n,e instanceof Error?e.message:String(e)),de[n]}function Fr(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function mf(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function ff(){rt(mf(process.argv.slice(2)));let e;try{e=Gr(process.argv.slice(2))}catch(t){xe("usage",String(t instanceof Error?t.message:t)),Fr(de.usage);return}rt(e.json),ho(e.flags.quiet===!0),so(Pt({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{Fr(await cf(e))}catch(t){Fr(uf(t))}}ff();