snoutdata 0.17.3 → 0.17.4

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,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- var wd=Object.defineProperty;var j=(e,t)=>()=>(e&&(t=e(e=0)),t);var ds=(e,t)=>{for(var n in t)wd(e,n,{get:t[n],enumerable:!0})};function us(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]=Sd(o);if(bd.has(s)){let l=a??e[++r];if(l===void 0)throw new p(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new p(`--${s} does not take a value`);if(!_d.has(s))throw new p(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function Sd(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function $(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function P(e,t){return e.flags[t]===!0}function be(e,t){let n=$(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new p(`--${t} must be a whole number, not "${n}"`);return r}var p,bd,_d,ue=j(()=>{"use strict";p=class extends Error{},bd=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","tail","registry-user","dockerfile","client-id","site-url","allow","subject","p8","key-id","team-id","topic","environment","email","channel","since","level","admin","token","host","dbname","sslmode","peer-host","to","key-type","ranges","seed","nodes","column","keyspace","range","node","value","add","remove","max-write-pause-ms","drain-timeout-ms","ack-timeout-ms","copy-mb-per-s","set","sample-ms"]);_d=new Set(["json","window","help","version","force","no-verify-jwt","reset","errors","follow","stdin","clear-registry","quiet","yes","plan","status","no-wait","no-join","queue","no-browser","device","sso","env","dry-run","out-of-order","allow-delete","local","no-migrations","history","show-url","check","watch"])});import{chmodSync as kd,existsSync as fs,mkdirSync as gs,readFileSync as hs,rmSync as vd,writeFileSync as ys}from"node:fs";import{homedir as Zt}from"node:os";import{dirname as pr,join as ws,parse as xd,resolve as $d}from"node:path";function fr(e=Zt()){return ws(e,".snoutdata","auth.json")}function Qt(e=Zt()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=fr(e);if(!fs(n))return null;try{let r=JSON.parse(hs(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function xe(e,t=Zt()){let n=fr(t);gs(pr(n),{recursive:!0}),ys(n,`${JSON.stringify(e,null,2)}
3
- `,{mode:384}),kd(n,384)}function bs(e=Zt()){vd(fr(e),{force:!0})}function _s(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function Ss(e){return ws(e,".snoutdata","project.json")}function ot(e){let t=$d(e),n=xd(t).root;for(;;){let r=Ss(t);if(fs(r))try{let o=JSON.parse(hs(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=pr(t)}}function st(e,t){let n=Ss(e);return gs(pr(n),{recursive:!0}),ys(n,`${JSON.stringify(t,null,2)}
4
- `),n}function en(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:ot(e.cwd??process.cwd())?.ref??null}function ks(e){let t=e.environment??process.env,n=ot(e.cwd??process.cwd())?.ref??null;if(e.flag)return n===e.flag?{kind:"linked",ref:n}:{kind:"link",ref:e.flag,replaces:n};let r=t.SNOUTDATA_PROJECT;return r?n===r?{kind:"linked",ref:n}:{kind:"environment",ref:r}:n?{kind:"linked",ref:n}:{kind:"create"}}var Me=j(()=>{"use strict"});import{existsSync as gr,readFileSync as vs}from"node:fs";import{homedir as Ed}from"node:os";import{isAbsolute as Pd,join as Le,resolve as at}from"node:path";function Ad(e=process.env,t=process.platform,n=Ed()){let r;return t==="win32"?r=e.APPDATA??Le(n,"AppData","Roaming"):t==="darwin"?r=Le(n,"Library","Application Support"):r=e.XDG_CONFIG_HOME??Le(n,".config"),["snoutdata","snoutdata-dev"].map(o=>Le(r,o,"localProjects.json"))}function tn(e=Ad()){let t=[];for(let n of e)if(gr(n))try{let r=JSON.parse(vs(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 nn(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 _t(e){return gr(Le(e,"compose.yaml"))&&gr(Le(e,".env"))}function Ue(e,t=null){let n=at(e);if(!_t(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=nn(vs(Le(n,".env"),"utf8")),o=r.get("SNOUT_REF"),s=r.get("POSTGRES_OWNER_PASSWORD");if(!o||!s)throw new Error(`${Le(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,appKey:r.get("APP_KEY")||r.get("ANON_KEY")||null,serverKey:r.get("SERVER_KEY")||r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function xs(e,t=process.cwd(),n=tn()){if(!e){if(_t(t))return Ue(t);let[i]=n;if(n.length===1&&i)return Ue(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=Pd(e)?e:at(t,e);if(_t(r)){let i=n.find(u=>at(u.folder)===at(r));return Ue(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 Ue(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 $s(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}`,appKey:e.appKey,serverKey:e.serverKey,keysIssuedAt:null,keysExpireAt:null,wakesInstantly:!0,state:"local"}}function Es(e,t,n=tn()){if(t&&_t(t))try{let r=n.find(s=>at(s.folder)===at(t)),o=Ue(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&_t(r.folder))return Ue(r.folder,r.name);return null}var St=j(()=>{"use strict"});function I(e,t,n){throw new v(e,t,n)}function Ve(e,t){return t==="no-capacity"?"no-capacity":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 Ps(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 rn(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(Rd.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 Ne,v,Rd,se=j(()=>{"use strict";Ne={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,"no-capacity":12,"tool-missing":127,server:1},v=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return Ne[this.code]}};Rd=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function on(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 Td){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 it(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 As(e){hr=e}function ae(){return(hr??on({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function ct(){return(hr??on({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var Td,hr,Ye=j(()=>{"use strict";Td=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];hr=null});import{createHmac as Od,randomBytes as Nd,timingSafeEqual as jd}from"node:crypto";import{existsSync as Cd,readFileSync as Id}from"node:fs";import{homedir as yr,hostname as Dd}from"node:os";import{join as lt}from"node:path";function Md(e,t,n){if(t==="win32"){let r=n.APPDATA??lt(yr(),"AppData","Roaming");return lt(r,e)}return t==="darwin"?lt(yr(),"Library","Application Support",e):lt(n.XDG_CONFIG_HOME??lt(yr(),".config"),e)}function Ts(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>lt(Md(n,e,t),"mcp.json"))}function Ld(e=process.platform,t=process.env){return Ts(e,t)[0]}function Ud(e=Ld()){if(!Cd(e))return null;try{let t=JSON.parse(Id(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function Fd(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function Hd(e,t,n){return Od("sha256",e).update(`${Bd}
2
+ var Hd=Object.defineProperty;var C=(e,t)=>()=>(e&&(t=e(e=0)),t);var ks=(e,t)=>{for(var n in t)Hd(e,n,{get:t[n],enumerable:!0})};function vs(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]=Kd(o);if(qd.has(s)){let l=a??e[++r];if(l===void 0)throw new g(`--${s} needs a value`);n[s]=l;continue}if(a!==void 0)throw new g(`--${s} does not take a value`);if(!Gd.has(s))throw new g(`unknown option --${s}`);n[s]=!0}return{command:t,flags:n,json:n.json===!0}}function Kd(e){let t=e.replace(/^--?/,""),n=t.indexOf("=");return n===-1?[t,void 0]:[t.slice(0,n),t.slice(n+1)]}function x(e,t){let n=e.flags[t];return typeof n=="string"?n:void 0}function T(e,t){return e.flags[t]===!0}function $e(e,t){let n=x(e,t);if(n===void 0)return;let r=Number(n);if(!Number.isInteger(r))throw new g(`--${t} must be a whole number, not "${n}"`);return r}var g,qd,Gd,he=C(()=>{"use strict";g=class extends Error{},qd=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","tail","registry-user","dockerfile","client-id","site-url","allow","subject","p8","key-id","team-id","topic","environment","email","channel","since","level","admin","token","host","dbname","sslmode","peer-host","to","key-type","ranges","seed","nodes","column","keyspace","range","node","value","add","remove","max-write-pause-ms","drain-timeout-ms","ack-timeout-ms","copy-mb-per-s","set","sample-ms"]);Gd=new Set(["json","window","help","version","force","no-verify-jwt","reset","errors","follow","stdin","clear-registry","quiet","yes","plan","status","no-wait","no-join","queue","no-browser","device","sso","env","dry-run","out-of-order","allow-delete","local","no-migrations","history","show-url","check","watch"])});import{chmodSync as Jd,existsSync as Es,mkdirSync as Ps,readFileSync as As,rmSync as zd,writeFileSync as Rs}from"node:fs";import{homedir as rn}from"node:os";import{dirname as kr,join as Ts,parse as Wd,resolve as Vd}from"node:path";function vr(e=rn()){return Ts(e,".snoutdata","auth.json")}function on(e=rn()){let t=process.env.SNOUTDATA_ACCESS_TOKEN;if(t)return{accessToken:t};let n=vr(e);if(!Es(n))return null;try{let r=JSON.parse(As(n,"utf8"));return typeof r.accessToken=="string"?r:null}catch{return null}}function Ae(e,t=rn()){let n=vr(t);Ps(kr(n),{recursive:!0}),Rs(n,`${JSON.stringify(e,null,2)}
3
+ `,{mode:384}),Jd(n,384)}function Os(e=rn()){zd(vr(e),{force:!0})}function Ns(e,t=Date.now()){return e.expiresAt?e.expiresAt*1e3-6e4<=t:!1}function Cs(e){return Ts(e,".snoutdata","project.json")}function ct(e){let t=Vd(e),n=Wd(t).root;for(;;){let r=Cs(t);if(Es(r))try{let o=JSON.parse(As(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=kr(t)}}function lt(e,t){let n=Cs(e);return Ps(kr(n),{recursive:!0}),Rs(n,`${JSON.stringify(t,null,2)}
4
+ `),n}function sn(e){let t=e.environment??process.env;return e.flag?e.flag:t.SNOUTDATA_PROJECT?t.SNOUTDATA_PROJECT:ct(e.cwd??process.cwd())?.ref??null}function js(e){let t=e.environment??process.env,n=ct(e.cwd??process.cwd())?.ref??null;if(e.flag)return n===e.flag?{kind:"linked",ref:n}:{kind:"link",ref:e.flag,replaces:n};let r=t.SNOUTDATA_PROJECT;return r?n===r?{kind:"linked",ref:n}:{kind:"environment",ref:r}:n?{kind:"linked",ref:n}:{kind:"create"}}var Fe=C(()=>{"use strict"});import{existsSync as $r,readFileSync as Is}from"node:fs";import{homedir as Yd}from"node:os";import{isAbsolute as Xd,join as Be,resolve as dt}from"node:path";function Zd(e=process.env,t=process.platform,n=Yd()){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 an(e=Zd()){let t=[];for(let n of e)if($r(n))try{let r=JSON.parse(Is(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 cn(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 xt(e){return $r(Be(e,"compose.yaml"))&&$r(Be(e,".env"))}function He(e,t=null){let n=dt(e);if(!xt(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=cn(Is(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,appKey:r.get("APP_KEY")||r.get("ANON_KEY")||null,serverKey:r.get("SERVER_KEY")||r.get("SERVICE_ROLE_KEY")||null,ownerPassword:s}}function Ds(e,t=process.cwd(),n=an()){if(!e){if(xt(t))return He(t);let[i]=n;if(n.length===1&&i)return He(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=Xd(e)?e:dt(t,e);if(xt(r)){let i=n.find(u=>dt(u.folder)===dt(r));return He(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 He(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 Ms(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}`,appKey:e.appKey,serverKey:e.serverKey,keysIssuedAt:null,keysExpireAt:null,wakesInstantly:!0,state:"local"}}function Ls(e,t,n=an()){if(t&&xt(t))try{let r=n.find(s=>dt(s.folder)===dt(t)),o=He(t,r?.name??null);if(o.ref===e)return o}catch{}for(let r of n)if(r.ref===e&&xt(r.folder))return He(r.folder,r.name);return null}var Et=C(()=>{"use strict"});function L(e,t,n){throw new v(e,t,n)}function et(e,t){return t==="no-capacity"?"no-capacity":e===400&&t==="region"?"usage":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 Us(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 ln(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(Qd.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 Ie,v,Qd,pe=C(()=>{"use strict";Ie={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,"no-capacity":12,"tool-missing":127,server:1},v=class extends Error{code;details;constructor(t,n,r){super(n),this.name="CliFailure",this.code=t,this.details=r}get exitCode(){return Ie[this.code]}};Qd=new Set(["ECONNREFUSED","ECONNRESET","ENOTFOUND","EAI_AGAIN","EHOSTUNREACH","ENETUNREACH","EPIPE","CERT_HAS_EXPIRED","UNABLE_TO_VERIFY_LEAF_SIGNATURE","DEPTH_ZERO_SELF_SIGNED_CERT"])});function dn(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 eu){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 ut(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 Fs(e){xr=e}function me(){return(xr??dn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).canAsk}function pt(){return(xr??dn({isTty:!!process.stdin.isTTY,json:!1,env:process.env})).reason}var eu,xr,tt=C(()=>{"use strict";eu=["CI","GITHUB_ACTIONS","GITLAB_CI","BUILDKITE","CIRCLECI","TF_BUILD"];xr=null});import{createHmac as tu,randomBytes as nu,timingSafeEqual as ru}from"node:crypto";import{existsSync as ou,readFileSync as su}from"node:fs";import{homedir as Er,hostname as au}from"node:os";import{join as mt}from"node:path";function iu(e,t,n){if(t==="win32"){let r=n.APPDATA??mt(Er(),"AppData","Roaming");return mt(r,e)}return t==="darwin"?mt(Er(),"Library","Application Support",e):mt(n.XDG_CONFIG_HOME??mt(Er(),".config"),e)}function Hs(e=process.platform,t=process.env){return t.SNOUTDATA_DESKTOP_CONFIG?[t.SNOUTDATA_DESKTOP_CONFIG]:["snoutdata","snoutdata-dev"].map(n=>mt(iu(n,e,t),"mcp.json"))}function cu(e=process.platform,t=process.env){return Hs(e,t)[0]}function lu(e=cu()){if(!ou(e))return null;try{let t=JSON.parse(su(e,"utf8"));return typeof t.port!="number"||typeof t.token!="string"||!t.token?null:t}catch{return null}}function du(e){return e?e.enabled===!1?"server-disabled":e.signInHandoff===!1?"handoff-disabled":null:"no-config"}function pu(e,t,n){return tu("sha256",e).update(`${uu}
5
5
  ${t}
6
- ${n}`).digest("base64url")}function Gd(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&jd(n,r)}async function Ns(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=Nd(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout(Os)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&Gd(o.proof,Hd(e.token,t,n))}catch{return!1}}async function br(e,t,n,r=Ns){if(!await r(e).catch(()=>!1))throw new sn(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function an(e,t=Ns){if(e!==void 0)return Rs(e,t);let n="no-config";for(let r of Ts()){let o=Ud(r);if(!o)continue;let s=await Rs(o,t);if(s.available)return s;(s.skip!=="not-running"&&s.skip!=="not-verified"||n==="no-config")&&(n=s.skip)}return{available:!1,skip:n}}async function Rs(e,t){let n=Fd(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await br(r,"/cli/whoami",{signal:AbortSignal.timeout(Os)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof sn?{available:!1,skip:"not-verified"}:{available:!1,skip:"not-running"}}return o.signedIn?o.canMintToken?{available:!0,who:o,config:r}:{available:!1,skip:"cannot-mint"}:{available:!1,skip:"signed-out"}}async function js(e,t){let n=await br(e,"/cli/token",{method:"POST",headers:{"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 cn(){return Dd()}async function wr(e,t,n,r){let o=await br(e,"/mcp",{method:"POST",headers:{"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 Cs(e){return await wr(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await wr(e,"tools/list",{},3e3)).tools??[]}async function Is(e,t,n){return wr(e,"tools/call",{name:t,arguments:n},12e4)}var Os,Bd,sn,ln=j(()=>{"use strict";Os=1500,Bd="snoutdata-studio-proof-v1";sn=class extends Error{constructor(t){super(`the process on 127.0.0.1:${t} did not prove it is SnoutData Studio, so Studio's token was not sent to it`),this.name="PeerNotVerified"}}});function _r(e){try{return new URL(e).host}catch{return e}}function un(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function Fe(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function Jd(e){return dn.has(_r(e))}function zd(e,t,n){return t===void 0?e:{...e,[n?Kd:qd]:t}}async function $e(e,t,n={},r=fetch){let o=_r(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:zd(s,n.key,u),body:n.body})}if(dn.has(o))return a(!0);let l=await a(!1);if(l.status!==404)return l;let i=await a(!0);return i.status===404?l:(dn.add(o),i)}async function Ms(e,t,n,r=fetch){if(!dn.has(_r(e)))try{await $e(e,Fe("/health","/health"),{key:n},r)}catch{}return`${e}${Jd(e)?t.oldPath:t.path}`}var qd,Kd,dn,mn=j(()=>{"use strict";qd="Snout-Key",Kd="apikey",dn=new Set});import{basename as Wd}from"node:path";function fn(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=Wd(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 Vd(e=fn(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?je:kt}}function Sr(e={}){let t=e.kind??fn(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?je:`${kt} (or ${je})`;return`snoutdata ${ne} is installed and ${r}. Run \`${Vd(t,n)}\`, or install again: ${o}`}function Ls(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 ne,pn,kt,je,vt=j(()=>{"use strict";ne="0.17.3",pn=`snoutdata-cli/${ne}`,kt="curl -fsSL https://snoutdata.com/install.sh | sh",je="npm install -g snoutdata@latest"});async function Us(e,t){let n=await $e(Ee,un(e),{method:"POST",key:Pe,headers:{"Content-Type":"application/json","x-client-info":pn},body:JSON.stringify(t)});yn(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw wn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function gn(e={}){let{status:t,body:n}=await Us("cloud-device-start",{client:e});return t===429&&I("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&I("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)&&I("server",n.error??`could not start a pairing (${t})`),n}async function hn(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&I("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 Us("cloud-device-poll",{secret:e});switch(o===404&&I("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&I("usage",s.error??"that is not a pairing secret"),o!==200&&I("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":I("forbidden",s.error??"that pairing was refused in the browser");case"used":I("conflict",s.error??"that pairing has already been completed");case"expired":I("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 kr=j(()=>{"use strict";Y();mn();se();vt()});function $t(e){xt=e}function Bs(e){Fs=e}function bn(){return xt}function w(e){return Hs?`\x1B[2m${e}\x1B[0m`:e}function k(e){return Hs?`\x1B[1m${e}\x1B[0m`:e}function g(e,t){if(xt){process.stdout.write(`${JSON.stringify(e,null,2)}
7
- `);return}t()}function d(e){!xt&&!Fs&&process.stderr.write(`${e}
8
- `)}function Be(e,t,n){process.stderr.write(`${t}
9
- `),xt&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
10
- `)}function X(e){process.stderr.write(`${e}
11
- `)}function M(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(`
12
- `)}function Ce(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 xt,Fs,Hs,W=j(()=>{"use strict";xt=!1,Fs=!1;Hs=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as Ks,randomBytes as vr}from"node:crypto";import{createServer as Xd}from"node:http";import{createInterface as Zd}from"node:readline/promises";import{spawn as Qd}from"node:child_process";function _n(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function Gs(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.":hu(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
6
+ ${n}`).digest("base64url")}function mu(e,t){let n=Buffer.from(e),r=Buffer.from(t);return n.length===r.length&&ru(n,r)}async function Gs(e){let t=e.port;if(typeof t!="number"||!Number.isInteger(t)||t<=0||t>65535||!e.token)return!1;let n=nu(32).toString("base64url");try{let r=await fetch(`http://127.0.0.1:${t}/cli/prove?nonce=${n}`,{redirect:"error",signal:AbortSignal.timeout(qs)});if(!r.ok)return!1;let o=await r.json();return typeof o.proof=="string"&&mu(o.proof,pu(e.token,t,n))}catch{return!1}}async function Ar(e,t,n,r=Gs){if(!await r(e).catch(()=>!1))throw new un(e.port);return fetch(`http://127.0.0.1:${e.port}${t}`,{...n,redirect:"error",headers:{...n.headers,Authorization:`Bearer ${e.token}`}})}async function pn(e,t=Gs){if(e!==void 0)return Bs(e,t);let n="no-config";for(let r of Hs()){let o=lu(r);if(!o)continue;let s=await Bs(o,t);if(s.available)return s;(s.skip!=="not-running"&&s.skip!=="not-verified"||n==="no-config")&&(n=s.skip)}return{available:!1,skip:n}}async function Bs(e,t){let n=du(e);if(n)return{available:!1,skip:n};let r=e,o;try{let s=await Ar(r,"/cli/whoami",{signal:AbortSignal.timeout(qs)},t);if(!s.ok)return{available:!1,skip:"not-running"};o=await s.json()}catch(s){return s instanceof un?{available:!1,skip:"not-verified"}:{available:!1,skip:"not-running"}}return o.signedIn?o.canMintToken?{available:!0,who:o,config:r}:{available:!1,skip:"cannot-mint"}:{available:!1,skip:"signed-out"}}async function Ks(e,t){let n=await Ar(e,"/cli/token",{method:"POST",headers:{"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 mn(){return au()}async function Pr(e,t,n,r){let o=await Ar(e,"/mcp",{method:"POST",headers:{"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 Js(e){return await Pr(e,"initialize",{protocolVersion:"2025-06-18",capabilities:{},clientInfo:{name:"snoutdata-cli",version:"0"}},3e3),(await Pr(e,"tools/list",{},3e3)).tools??[]}async function zs(e,t,n){return Pr(e,"tools/call",{name:t,arguments:n},12e4)}var qs,uu,un,fn=C(()=>{"use strict";qs=1500,uu="snoutdata-studio-proof-v1";un=class extends Error{constructor(t){super(`the process on 127.0.0.1:${t} did not prove it is SnoutData Studio, so Studio's token was not sent to it`),this.name="PeerNotVerified"}}});function Rr(e){try{return new URL(e).host}catch{return e}}function hn(e,t=""){return{path:`/v1/fn/${e}${t}`,oldPath:`/functions/v1/${e}${t}`}}function qe(e,t){return{path:`/v1/identity${e}`,oldPath:`/auth/v1${t}`}}function hu(e){return gn.has(Rr(e))}function yu(e,t,n){return t===void 0?e:{...e,[n?gu:fu]:t}}async function Re(e,t,n={},r=fetch){let o=Rr(e),s=n.headers??{};function a(u){return r(`${e}${u?t.oldPath:t.path}`,{method:n.method??"GET",headers:yu(s,n.key,u),body:n.body})}if(gn.has(o))return a(!0);let l=await a(!1);if(l.status!==404)return l;let i=await a(!0);return i.status===404?l:(gn.add(o),i)}async function Vs(e,t,n,r=fetch){if(!gn.has(Rr(e)))try{await Re(e,qe("/health","/health"),{key:n},r)}catch{}return`${e}${hu(e)?t.oldPath:t.path}`}var fu,gu,gn,yn=C(()=>{"use strict";fu="Snout-Key",gu="apikey",gn=new Set});import{basename as wu}from"node:path";function bn(e={bun:typeof process.versions.bun=="string",execPath:process.execPath,script:process.argv[1]??""}){let t=wu(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 bu(e=bn(),t=process.platform){switch(e){case"binary":case"npm":return"snoutdata upgrade";case"npx":return"npx snoutdata@latest";case"source":return t==="win32"?De:Pt}}function Tr(e={}){let t=e.kind??bn(),n=e.platform??process.platform,r=e.minimum?`${e.minimum} or later is required`:"a newer version is required",o=n==="win32"?De:`${Pt} (or ${De})`;return`snoutdata ${le} is installed and ${r}. Run \`${bu(t,n)}\`, or install again: ${o}`}function Ys(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 le,wn,Pt,De,At=C(()=>{"use strict";le="0.17.4",wn=`snoutdata-cli/${le}`,Pt="curl -fsSL https://snoutdata.com/install.sh | sh",De="npm install -g snoutdata@latest"});async function Xs(e,t){let n=await Re(Te,hn(e),{method:"POST",key:Oe,headers:{"Content-Type":"application/json","x-client-info":wn},body:JSON.stringify(t)});kn(n);let r=await n.json().catch(()=>({}));if(n.status===410){let o=r.error;throw vn(n,typeof o=="string"?o:"This version of the snoutdata CLI is no longer supported.")}return{status:n.status,body:r}}async function Sn(e={}){let{status:t,body:n}=await Xs("cloud-device-start",{client:e});return t===429&&L("quota",n.error??"too many pairing attempts from here: wait a few minutes"),t===404&&L("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)&&L("server",n.error??`could not start a pairing (${t})`),n}async function _n(e,t){let n=t.intervalMs,r=Date.parse(t.expiresAt);for(;;){Date.now()>r&&L("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 Xs("cloud-device-poll",{secret:e});switch(o===404&&L("not-found","that pairing is not known to the server. Run the command again for a new code."),o===400&&L("usage",s.error??"that is not a pairing secret"),o!==200&&L("server",s.error??`the pairing could not be checked (${o})`),s.status){case"approved":return{token:s.token,name:s.name};case"denied":L("forbidden",s.error??"that pairing was refused in the browser");case"used":L("conflict",s.error??"that pairing has already been completed");case"expired":L("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 Or=C(()=>{"use strict";Q();yn();pe();At()});function Tt(e){Rt=e}function Qs(e){Zs=e}function $n(){return Rt}function w(e){return ea?`\x1B[2m${e}\x1B[0m`:e}function $(e){return ea?`\x1B[1m${e}\x1B[0m`:e}function h(e,t){if(Rt){process.stdout.write(`${JSON.stringify(e,null,2)}
7
+ `);return}t()}function d(e){!Rt&&!Zs&&process.stderr.write(`${e}
8
+ `)}function Ge(e,t,n){process.stderr.write(`${t}
9
+ `),Rt&&process.stdout.write(`${JSON.stringify({ok:!1,code:e,error:t,...n},null,2)}
10
+ `)}function ee(e){process.stderr.write(`${e}
11
+ `)}function B(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(`
12
+ `)}function Me(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 Rt,Zs,ea,Z=C(()=>{"use strict";Rt=!1,Zs=!1;ea=process.stdout.isTTY&&!process.env.NO_COLOR});import{createHash as ra,randomBytes as Nr}from"node:crypto";import{createServer as _u}from"node:http";import{createInterface as ku}from"node:readline/promises";import{spawn as vu}from"node:child_process";function xn(e){return e.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function ta(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.":Fu(t??"Something went wrong.");return`<!doctype html><html><head><meta charset="utf-8"><title>SnoutData</title>
13
13
  <meta name="viewport" content="width=device-width,initial-scale=1"></head>
14
14
  <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">
15
15
  <div style="text-align:center;max-width:460px;padding:40px 24px">
@@ -21,24 +21,66 @@ ${n}`).digest("base64url")}function Gd(e,t){let n=Buffer.from(e),r=Buffer.from(t
21
21
  <p style="color:#8f887e;margin:0;font-size:15px;line-height:1.5">${o}</p>
22
22
  ${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>'}
23
23
  </div>
24
- </body></html>`}function xr(){let e=_n(vr(32)),t=_n(Ks("sha256").update(e).digest());return{verifier:e,challenge:t}}function eu(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 tu(e){let{command:t,args:n,verbatim:r}=eu(process.platform,e),o=Qd(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function $r(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function ou(){return"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"}function su(e){let t=e??_n(vr(32));return{raw:t,hashed:Ks("sha256").update(t).digest("hex")}}function au(e){return`${nu}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:$r(),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 iu(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 Er(e){return new Promise((t,n)=>{let r="",o=Xd((a,l)=>{let i=iu(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"?Gs("ok"):Gs("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}
25
- `),e.noBrowser?d("Open that in a browser, on any machine, and sign in."):(d("Opening that in your browser. If nothing opens, paste it into one."),tu(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 Js(e,t){let n=await $e(Ee,Fe("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Pe,headers:{"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 zs(await n.json())}async function cu(e,t,n){let r=await fetch(ru,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:$r(),client_secret:ou(),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 lu(e){if(!$r())throw new Error("this build of snoutdata has no Google sign-in (it was built for testing). Sign in with a released snoutdata, then run this one.");let{verifier:t,challenge:n}=xr(),{raw:r,hashed:o}=su(),s=_n(vr(16)),{code:a,redirectUri:l}=await Er({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>au({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await cu(a,t,l),u=await $e(Ee,Fe("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Pe,headers:{"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 zs(await u.json())}function zs(e){let t=e,n={accessToken:t.access_token,refreshToken:t.refresh_token,expiresAt:t.expires_at,email:t.user?.email};return xe(n),n}function qs(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 du(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 uu(e,t,n){let r=du(t);return e===404||/no sso provider/i.test(r)?new v("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 v("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new v(Ve(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function mu(e){let t=await $e(Ee,Fe("/sso/start","/sso"),{method:"POST",key:Pe,headers:{"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 uu(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new v("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 pu(e){if(e!==void 0){let r=qs(e);if(!r)throw new p(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!ae()){let r=ct();throw new p(`login --sso needs --domain <your work email or company domain>${r?`, because ${it(r)}`:""}`)}let t=Zd({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new p("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=qs(o);if(s)return s;d("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new p("no domain to sign in with: pass --domain <your work email or company domain>")}async function fu(e){let t=await pu(e.domain),{verifier:n,challenge:r}=xr();d(`Signing in with the identity provider for ${k(t)}.`);let{code:o}=await Er({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>mu({domain:t,redirectTo:s,challenge:r})});return Js(o,n)}async function Sn(e={}){return Pr(e.email,await gu(e))}function Pr(e,t){if(!e)return t;let n={...t,expectedEmail:e};return xe(n),t.email&&!Ar(t.email,e)&&X(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function Ar(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function gu(e){if(e.sso)return fu({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return lu(e);let{verifier:n,challenge:r}=xr(),{code:o}=await Er({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:async s=>{let a=new URL(await Ms(Ee,Fe("/provider/start","/authorize"),Pe));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 Js(o,n)}function hu(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var nu,ru,kn=j(()=>{"use strict";Y();mn();ue();Me();se();Ye();W();nu="https://accounts.google.com/o/oauth2/v2/auth",ru="https://oauth2.googleapis.com/token"});import{createInterface as yu}from"node:readline/promises";async function Rr(e){let t=yu({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 wu(){let e=await an();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&d(w("SnoutData Studio is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";if(d(""),d(`SnoutData Studio is open here, signed in as ${k(t)}.`),d(w("It can create an access token for this terminal. Your sign-in itself is not handed over,")),d(w("and you can revoke the token later with `snoutdata tokens revoke`.")),!await Rr("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await js(e.config,process.argv.slice(2).join(" ").slice(0,60)||"snoutdata");if("declined"in n)return d(`The app did not create a token: ${n.reason}`),null;let r={accessToken:n.token};return xe(r),d(`Signed in with "${n.name}".`),r}async function bu(){if(!await Rr("Sign in with a browser?"))return null;try{let e=await Sn({});return d(`Signed in${e.email?` as ${e.email}`:""}.`),{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt}}catch(e){return d(`That did not finish: ${e instanceof Error?e.message:String(e)}`),null}}async function _u(){if(!await Rr("Sign in by typing a code into a browser on another machine?"))return null;let e=await gn({name:cn(),platform:process.platform});d(""),d(` Open ${k(e.verifyUrl)}`),d(` Type ${k(e.code)}`),d(""),d(w("The code is typed rather than in the link on purpose: it is what proves the person")),d(w("at the browser is the person at this terminal. Waiting\u2026"));let t=await hn(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return xe(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function Ws(){if(!ae())return null;let e=await wu();if(e)return e;let t=await bu();return t||_u()}var Vs=j(()=>{"use strict";Me();ln();kr();Ye();kn();W()});var Ae={};ds(Ae,{ACCOUNTS_URL:()=>Ee,APP_KEY:()=>Pe,ApiError:()=>ge,NotSignedIn:()=>He,call:()=>b,connection:()=>pe,exportStatus:()=>dt,isSettled:()=>Xs,listProjects:()=>he,listTeams:()=>xn,localStack:()=>Ie,noticeFrom:()=>yn,outdated:()=>wn,requestExport:()=>Et,waitForReady:()=>At,waitForSettled:()=>Pt,whoami:()=>Xe,withKeyNames:()=>vn});function Su(){let e=ct();return e?`not signed in, and ${it(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 ku(){let e=Qt();if(e||(e=await Ws()),!e)throw new He;if(!_s(e)||!e.refreshToken)return e.accessToken;let t=await $e(Ee,Fe("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Pe,headers:{"Content-Type":"application/json"},body:JSON.stringify({refresh_token:e.refreshToken})});if(!t.ok)throw new He;let n=await t.json(),r={...e,accessToken:n.access_token,refreshToken:n.refresh_token,expiresAt:n.expires_at};return xe(r),r.accessToken}async function b(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=Ie(r);if(u)throw new v("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 ku(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await $e(Ee,un(e,s),{method:n,key:Pe,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":pn},body:n==="GET"?void 0:JSON.stringify(t)});yn(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new ge(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}`;if(a.status===401)throw new He(u);if(a.status===410)throw wn(a,u);let c=typeof i=="object"&&i&&"code"in i&&typeof i.code=="string"?i.code:null;throw new ge(a.status,u,c,i)}return i}function yn(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||Ys||(Ys=!0,X(`${t} ${Sr({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function wn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new v("outdated",`${t.replace(/\s+$/,"")} ${Sr({minimum:n})}`,{status:410,installed:ne,minimum:n??null})}async function Xe(){return b("cloud-whoami")}async function he(){return b("cloud-project-list")}async function pe(e){let t=Ie(e);return t?$s(t):vn(await b("cloud-project-connection",{ref:e}))}function vn(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function Ie(e){return Es(e,ot(process.cwd())?.local??null)}async function xn(){return b("cloud-team-list")}async function Et(e){return b("cloud-project-export",{ref:e})}async function dt(e){return b("cloud-project-export",{ref:e},"GET")}async function Pt(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await he(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),Xs(t,a))return s;if(Date.now()>=r)throw new ge(504,`${e} is still ${a} after waiting`);await new Promise(l=>setTimeout(l,2e3))}}function Xs(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 he(),o=r.find(s=>s.ref===e);if(!o)throw new ge(404,`no project ${e}`);if(t.onTick?.(o.state),o.state==="ready")return o;if(Date.now()>=n)throw new ge(504,`${e} is still ${o.state} after waiting`);await new Promise(s=>setTimeout(s,2e3))}}var Ee,Pe,ge,He,Ys,Y=j(()=>{"use strict";Me();St();se();Ye();Vs();W();vt();mn();Ee=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Pe=process.env.SNOUTDATA_APP_KEY??process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyb2xlIjoic25vdXRfYXBwIiwiaWF0IjoxNzkyMDc2NTQyLCJleHAiOjIxMDQ4NzU1MjR9.hWSbcDfbGuyDHHSFLoZzD8KVSg9bWVZzzQ_eftW6TPM",ge=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},He=class extends Error{constructor(t){super(t??Su())}};Ys=!1});function $o(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function sc(e,t,n){let r="";for(;r.length<n;)r+=$o(e,t);return r}var xo,oc,ac=j(()=>{"use strict";xo="0123456789abcdefghjkmnpqrstvwxyz",oc="abcdefghjkmnpqrstvwxyz"});function lc(e){return $o(e,ic)+sc(e,xo,cc-1)}function Cf(e){return jf.test(e)}function re(e){if(!Cf(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function U(e,t=If){return re(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var ic,cc,jf,If,_e=j(()=>{"use strict";ac();ic=oc,cc=13,jf=new RegExp(`^[${ic}][${xo}]{${cc-1}}$`);If="db.snoutdata.com"});var dc=j(()=>{"use strict";_e()});function mc(e,t){let n=uc(e,7),r=uc(t,24);return Math.max(2,Math.ceil(n*24/r))}function uc(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var Eo=j(()=>{"use strict"});function Po(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var Ao=j(()=>{"use strict"});var pc=j(()=>{"use strict"});function gc(e){let n=(e.trim().split(`
26
- `)[0]??"").split("|");if(n.length!==4||n[0]!=="t"&&n[0]!=="f")return null;let r=Number(n[1]);return!Number.isFinite(r)||r<0?null:{inRecovery:n[0]==="t",checksumFailures:r,syncNames:n[2],streaming:n[3]===""?[]:n[3].split(",")}}function hc(e){if(e!==""&&!/^"[a-z0-9-]+"$/.test(e))throw new Error(`not a synchronous_standby_names value: ${e}`);return[e===""?"alter system reset synchronous_standby_names":`alter system set synchronous_standby_names = '${e}'`,"select pg_reload_conf()"]}var Df,fc,Ro=j(()=>{"use strict";_e();Df=20,fc=`select pg_is_in_recovery(), coalesce((select sum(checksum_failures) from pg_stat_database), 0), current_setting('synchronous_standby_names'), coalesce((select string_agg(application_name, ',') from pg_stat_replication where state = 'streaming' and (reply_time is null or reply_time > now() - interval '${Df} seconds')), '')`});var v_,yc=j(()=>{"use strict";_e();v_=7*24});var $_,wc=j(()=>{"use strict";$_=7*24*60*60*1e3});var Mf,bc=j(()=>{"use strict";wc();Mf=60*60*1e3});var _c=j(()=>{"use strict"});var To,N_,Sc=j(()=>{"use strict";To="snoutdata_realtime",N_=`do $snoutpod$
24
+ </body></html>`}function Cr(){let e=xn(Nr(32)),t=xn(ra("sha256").update(e).digest());return{verifier:e,challenge:t}}function $u(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 xu(e){let{command:t,args:n,verbatim:r}=$u(process.platform,e),o=vu(t,n,{stdio:"ignore",detached:!0,windowsVerbatimArguments:r});o.on("error",()=>{}),o.unref()}function jr(){return"1064562458607-9fqmghjoqjr7d56b98agkvjt9vt26pdc.apps.googleusercontent.com"}function Au(){return"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"}function Ru(e){let t=e??xn(Nr(32));return{raw:t,hashed:ra("sha256").update(t).digest("hex")}}function Tu(e){return`${Eu}?${new URLSearchParams({...e.loginHint?{login_hint:e.loginHint}:{},client_id:jr(),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 Ou(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 Ir(e){return new Promise((t,n)=>{let r="",o=_u((a,l)=>{let i=Ou(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"?ta("ok"):ta("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}
25
+ `),e.noBrowser?d("Open that in a browser, on any machine, and sign in."):(d("Opening that in your browser. If nothing opens, paste it into one."),xu(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 oa(e,t){let n=await Re(Te,qe("/session?via=pkce","/token?grant_type=pkce"),{method:"POST",key:Oe,headers:{"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 sa(await n.json())}async function Nu(e,t,n){let r=await fetch(Pu,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:jr(),client_secret:Au(),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 Cu(e){if(!jr())throw new Error("this build of snoutdata has no Google sign-in (it was built for testing). Sign in with a released snoutdata, then run this one.");let{verifier:t,challenge:n}=Cr(),{raw:r,hashed:o}=Ru(),s=xn(Nr(16)),{code:a,redirectUri:l}=await Ir({state:s,timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:c=>Tu({redirectUri:c,challenge:n,hashedNonce:o,state:s,loginHint:e.email})}),i=await Nu(a,t,l),u=await Re(Te,qe("/session?via=id_token","/token?grant_type=id_token"),{method:"POST",key:Oe,headers:{"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 sa(await u.json())}function sa(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 na(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 ju(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 Iu(e,t,n){let r=ju(t);return e===404||/no sso provider/i.test(r)?new v("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 v("failed",`Single sign-on is not available for ${n} yet. Ask whoever administers your team to connect your identity provider.`,{domain:n}):new v(et(e),r||`Could not start single sign-on for ${n}.`,{domain:n})}async function Du(e){let t=await Re(Te,qe("/sso/start","/sso"),{method:"POST",key:Oe,headers:{"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 Iu(t.status,n,e.domain);let r;try{r=JSON.parse(n).url}catch{r=void 0}if(typeof r!="string"||!r)throw new v("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 Mu(e){if(e!==void 0){let r=na(e);if(!r)throw new g(`--domain wants a work email or a company domain, like you@yourcompany.com, not "${e}"`);return r}if(!me()){let r=pt();throw new g(`login --sso needs --domain <your work email or company domain>${r?`, because ${ut(r)}`:""}`)}let t=ku({input:process.stdin,output:process.stderr}),n=new Promise((r,o)=>{t.once("close",()=>o(new g("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=na(o);if(s)return s;d("That is not a work email or a company domain. Try something like you@yourcompany.com.")}}finally{t.close()}throw new g("no domain to sign in with: pass --domain <your work email or company domain>")}async function Lu(e){let t=await Mu(e.domain),{verifier:n,challenge:r}=Cr();d(`Signing in with the identity provider for ${$(t)}.`);let{code:o}=await Ir({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:s=>Du({domain:t,redirectTo:s,challenge:r})});return oa(o,n)}async function En(e={}){return Dr(e.email,await Uu(e))}function Dr(e,t){if(!e)return t;let n={...t,expectedEmail:e};return Ae(n),t.email&&!Mr(t.email,e)&&ee(`Signed in as ${t.email}, not ${e}. Run \`snoutdata login --email ${e}\` and pick that account.`),n}function Mr(e,t){return e.trim().toLowerCase()===t.trim().toLowerCase()}async function Uu(e){if(e.sso)return Lu({...e,domain:e.domain??e.email});let t=e.provider??"google";if(t==="google")return Cu(e);let{verifier:n,challenge:r}=Cr(),{code:o}=await Ir({callbackPath:"/callback",timeoutMs:e.timeoutMs,noBrowser:e.noBrowser,authorizeUrl:async s=>{let a=new URL(await Vs(Te,qe("/provider/start","/authorize"),Oe));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 oa(o,n)}function Fu(e){return e.replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t])}var Eu,Pu,Pn=C(()=>{"use strict";Q();yn();he();Fe();pe();tt();Z();Eu="https://accounts.google.com/o/oauth2/v2/auth",Pu="https://oauth2.googleapis.com/token"});import{createInterface as Bu}from"node:readline/promises";async function Lr(e){let t=Bu({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 Hu(){let e=await pn();if(!e.available)return(e.skip==="handoff-disabled"||e.skip==="server-disabled")&&d(w("SnoutData Studio is running but is not set to hand out sign-ins.")),null;let t=e.who.email??"the signed-in account";if(d(""),d(`SnoutData Studio is open here, signed in as ${$(t)}.`),d(w("It can create an access token for this terminal. Your sign-in itself is not handed over,")),d(w("and you can revoke the token later with `snoutdata tokens revoke`.")),!await Lr("Use it?"))return null;d("Asking SnoutData Studio. Approve it there.");let n=await Ks(e.config,process.argv.slice(2).join(" ").slice(0,60)||"snoutdata");if("declined"in n)return d(`The app did not create a token: ${n.reason}`),null;let r={accessToken:n.token};return Ae(r),d(`Signed in with "${n.name}".`),r}async function qu(){if(!await Lr("Sign in with a browser?"))return null;try{let e=await En({});return d(`Signed in${e.email?` as ${e.email}`:""}.`),{accessToken:e.accessToken,refreshToken:e.refreshToken,expiresAt:e.expiresAt}}catch(e){return d(`That did not finish: ${e instanceof Error?e.message:String(e)}`),null}}async function Gu(){if(!await Lr("Sign in by typing a code into a browser on another machine?"))return null;let e=await Sn({name:mn(),platform:process.platform});d(""),d(` Open ${$(e.verifyUrl)}`),d(` Type ${$(e.code)}`),d(""),d(w("The code is typed rather than in the link on purpose: it is what proves the person")),d(w("at the browser is the person at this terminal. Waiting\u2026"));let t=await _n(e.secret,{intervalMs:Math.max(1,e.interval)*1e3,expiresAt:e.expiresAt}),n={accessToken:t.token};return Ae(n),d(`Signed in${t.name?` with "${t.name}"`:""}.`),n}async function aa(){if(!me())return null;let e=await Hu();if(e)return e;let t=await qu();return t||Gu()}var ia=C(()=>{"use strict";Fe();fn();Or();tt();Pn();Z()});var Ne={};ks(Ne,{ACCOUNTS_URL:()=>Te,APP_KEY:()=>Oe,ApiError:()=>Se,NotSignedIn:()=>Ke,call:()=>S,connection:()=>we,errorSentence:()=>da,exportStatus:()=>ft,isSettled:()=>la,listProjects:()=>_e,listTeams:()=>Rn,localStack:()=>Le,noticeFrom:()=>kn,outdated:()=>vn,requestExport:()=>Ot,unreadableAnswer:()=>ua,waitForReady:()=>Ct,waitForSettled:()=>Nt,whoami:()=>nt,withKeyNames:()=>An});function Ku(){let e=pt();return e?`not signed in, and ${ut(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 Ju(){let e=on();if(e||(e=await aa()),!e)throw new Ke;if(!Ns(e)||!e.refreshToken)return e.accessToken;let t=await Re(Te,qe("/session?via=renew","/token?grant_type=refresh_token"),{method:"POST",key:Oe,headers:{"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 S(e,t={},n="POST"){let r=typeof t=="object"&&t?t.ref:void 0;if(typeof r=="string"){let u=Le(r);if(u)throw new v("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 Ju(),s=n==="GET"&&typeof t=="object"&&t?`?${new URLSearchParams(t).toString()}`:"",a=await Re(Te,hn(e,s),{method:n,key:Oe,headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json","x-client-info":wn},body:n==="GET"?void 0:JSON.stringify(t)});kn(a);let l=await a.text(),i;try{i=l?JSON.parse(l):{}}catch{throw new Se(a.status,ua(a.status,a.statusText,l))}if(!a.ok){let u=da(i)??`${a.status} ${a.statusText}`;if(a.status===401)throw new Ke(u);if(a.status===410)throw vn(a,u);let c=typeof i=="object"&&i&&"code"in i&&typeof i.code=="string"?i.code:null;throw new Se(a.status,u,c,i)}return i}function kn(e){let t=e.headers.get("x-snoutdata-cli-notice");!t||ca||(ca=!0,ee(`${t} ${Tr({minimum:e.headers.get("x-snoutdata-cli-minimum")})}`))}function vn(e,t){let n=e.headers.get("x-snoutdata-cli-minimum");return new v("outdated",`${t.replace(/\s+$/,"")} ${Tr({minimum:n})}`,{status:410,installed:le,minimum:n??null})}async function nt(){return S("cloud-whoami")}async function _e(){return S("cloud-project-list")}async function we(e){let t=Le(e);return t?Ms(t):An(await S("cloud-project-connection",{ref:e}))}function An(e){let{anonKey:t,serviceRoleKey:n,...r}=e;return{...r,appKey:e.appKey??t??null,serverKey:e.serverKey??n??null}}function Le(e){return Ls(e,ct(process.cwd())?.local??null)}async function Rn(){return S("cloud-team-list")}async function Ot(e){return S("cloud-project-export",{ref:e})}async function ft(e){return S("cloud-project-export",{ref:e},"GET")}async function Nt(e,t,n={}){let r=Date.now()+(n.timeoutMs??3e5);for(;;){let{projects:o}=await _e(),s=o.find(l=>l.ref===e)??null,a=s?.state??"deleted";if(n.onTick?.(a),la(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 la(e,t){let n={pause:"paused",resume:"ready",delete:"deleted"}[e];return t===n||t==="error"}async function Ct(e,t={}){let n=Date.now()+(t.timeoutMs??3e5);for(;;){let{projects:r}=await _e(),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))}}function da(e){if(typeof e!="object"||!e)return null;let t=e;return typeof t.error=="string"&&t.error?t.error:typeof t.message=="string"&&t.message?typeof t.hint=="string"&&t.hint?`${t.message} ${t.hint}`:t.message:null}function ua(e,t,n){return/^\s*</.test(n)||!n.trim()?`SnoutData Cloud answered ${e}${t?` ${t}`:""} with no message. Try again in a minute.`:n.slice(0,300)}var Te,Oe,Se,Ke,ca,Q=C(()=>{"use strict";Fe();Et();pe();tt();ia();Z();At();yn();Te=process.env.SNOUTDATA_ACCOUNTS_URL??"https://accounts.snoutdata.com",Oe=process.env.SNOUTDATA_APP_KEY??process.env.SNOUTDATA_ANON_KEY??"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzbm91dGRhdGEiLCJyb2xlIjoic25vdXRfYXBwIiwiaWF0IjoxNzkyMDc2NTQyLCJleHAiOjIxMDQ4NzU1MjR9.hWSbcDfbGuyDHHSFLoZzD8KVSg9bWVZzzQ_eftW6TPM",Se=class extends Error{status;serverCode;body;constructor(t,n,r=null,o){super(n),this.status=t,this.serverCode=r,this.body=o}},Ke=class extends Error{constructor(t){super(t??Ku())}};ca=!1});function Io(e,t){let n=256-256%t.length;for(;;){let r=e(1)[0];if(r<n)return t[r%t.length]}}function xc(e,t,n){let r="";for(;r.length<n;)r+=Io(e,t);return r}var jo,$c,Ec=C(()=>{"use strict";jo="0123456789abcdefghjkmnpqrstvwxyz",$c="abcdefghjkmnpqrstvwxyz"});function Rc(e){return Io(e,Pc)+xc(e,jo,Ac-1)}function vg(e){return kg.test(e)}function de(e){if(!vg(e))throw new Error(`not a project ref: ${JSON.stringify(e)}`);return e}function F(e,t=$g){return de(e),{ref:e,database:e,ownerRole:`${e}_owner`,pod:`snoutpod-${e}`,host:`${e}.${t}`}}var Pc,Ac,kg,$g,xe=C(()=>{"use strict";Ec();Pc=$c,Ac=13,kg=new RegExp(`^[${Pc}][${jo}]{${Ac-1}}$`);$g="db.snoutdata.com"});var Tc=C(()=>{"use strict";xe()});function Nc(e,t){let n=Oc(e,7),r=Oc(t,24);return Math.max(2,Math.ceil(n*24/r))}function Oc(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?e:t}var Do=C(()=>{"use strict"});function Mo(e){let t=e.trim();if(!/^\d+$/.test(t))return null;let n=Number(t);return Number.isSafeInteger(n)?n:null}var Lo=C(()=>{"use strict"});var Cc=C(()=>{"use strict"});function Ic(e){let n=(e.trim().split(`
26
+ `)[0]??"").split("|");if(n.length!==4||n[0]!=="t"&&n[0]!=="f")return null;let r=Number(n[1]);return!Number.isFinite(r)||r<0?null:{inRecovery:n[0]==="t",checksumFailures:r,syncNames:n[2],streaming:n[3]===""?[]:n[3].split(",")}}function Dc(e){if(e!==""&&!/^"[a-z0-9-]+"$/.test(e))throw new Error(`not a synchronous_standby_names value: ${e}`);return[e===""?"alter system reset synchronous_standby_names":`alter system set synchronous_standby_names = '${e}'`,"select pg_reload_conf()"]}var xg,jc,Uo=C(()=>{"use strict";xe();xg=20,jc=`select pg_is_in_recovery(), coalesce((select sum(checksum_failures) from pg_stat_database), 0), current_setting('synchronous_standby_names'), coalesce((select string_agg(application_name, ',') from pg_stat_replication where state = 'streaming' and (reply_time is null or reply_time > now() - interval '${xg} seconds')), '')`});function Lc(e,t){let n=new Map;for(let[o,s]of t)n.set(s,o);let r=new Map;for(let o of e.split(`
27
+ `)){let[s,a]=o.trim().split(/\s+/),l=s?n.get(s):void 0;l&&(a==="0"||a==="1"?r.set(l,!0):a==="2"&&r.set(l,!1))}return r}var Mc,Fo=C(()=>{"use strict";Mc='for h in "$@"; do ( pg_isready -q -h "$h" -p 5432 -U snoutpod_admin -d postgres -t 3; echo "$h $?" ) & done; wait'});function Pg(e){return/^[0-9A-F]{1,8}\/[0-9A-F]{1,8}$/i.test(e)}function Fc(e){let[t,n]=(e.trim().split(`
28
+ `)[0]??"").split("|");return t==="t"&&n!==void 0&&Pg(n)?n:null}var Eg,v_,Uc,Bo=C(()=>{"use strict";Eg="/var/lib/postgresql/data",v_=`set -u
29
+ D=${Eg}
30
+ W="$D/pg_wal"
31
+ fail() { echo "salvage-error $*"; exit 3; }
32
+ [ -d "$W" ] || fail "the volume has no pg_wal"
33
+ chown postgres:postgres "$D" && chown -R postgres:postgres "$W" || fail "pg_wal cannot be read"
34
+ cd "$W" || fail "pg_wal cannot be read"
35
+ all=$(ls | grep -E '^[0-9A-F]{24}$' | sort)
36
+ [ -n "$all" ] || fail "pg_wal holds no WAL"
37
+ tli=$(printf '%s\\n' $all | cut -c1-8 | sort | tail -1)
38
+ segs=$(printf '%s\\n' $all | grep "^$tli")
39
+ lastdone=""; first=""
40
+ for s in $segs; do
41
+ if [ -f "archive_status/$s.done" ]; then lastdone=$s; elif [ -z "$first" ]; then first=$s; fi
42
+ done
43
+ start=\${lastdone:-$first}
44
+ [ -n "$start" ] || fail "no WAL to read on timeline $tli"
45
+ # From the start of that file to the end of what is whole: given a file NAME, pg_waldump stops at
46
+ # the end of that file, so it is given a position on the timeline instead.
47
+ from=$(printf '%X/%X' "0x$(printf '%s' "$start" | cut -c9-16)" "$(( 0x$(printf '%s' "$start" | cut -c17-24) << 24 ))")
48
+ dump=$(gosu postgres pg_waldump -p "$W" -t "$(( 0x$tli ))" -s "$from" 2>&1 | tail -n 4)
49
+ last=$(printf '%s\\n' "$dump" | grep -o 'lsn: [0-9A-F]*/[0-9A-F]*' | tail -1 | cut -d' ' -f2)
50
+ [ -n "$last" ] || fail "no whole WAL record from $start: $(printf '%s' "$dump" | tail -n 1 | tr -d '\\n' | cut -c1-200)"
51
+ stop=$(printf '%s\\n' "$dump" | grep 'error' | grep -o 'at [0-9A-F]*/[0-9A-F]*' | tail -1 | cut -d' ' -f2)
52
+ [ -n "$stop" ] || stop=$last
53
+ hi=\${stop%/*}; lo=\${stop#*/}
54
+ endseg=$(printf '%s%08X%08X' "$tli" "0x$hi" "$(( 0x$lo >> 24 ))")
55
+ echo "salvage-last $last"
56
+ echo "salvage-stop $stop"
57
+ for h in $(ls archive_status 2>/dev/null | grep -E '^[0-9A-F]{8}\\.history\\.ready$' | sed 's/\\.ready$//'); do
58
+ snoutpod-pgbackrest --reset-pg1-path archive-push "$W/$h" >/dev/null 2>/tmp/push.err || fail "push $h: $(tail -n 1 /tmp/push.err | cut -c1-200)"
59
+ echo "salvage-pushed $h"
60
+ done
61
+ for s in $segs; do
62
+ [ -f "archive_status/$s.done" ] && continue
63
+ [ "$s" \\> "$endseg" ] && break
64
+ snoutpod-pgbackrest --reset-pg1-path archive-push "$W/$s" >/dev/null 2>/tmp/push.err || fail "push $s: $(tail -n 1 /tmp/push.err | cut -c1-200)"
65
+ echo "salvage-pushed $s"
66
+ done
67
+ echo "salvage-done"
68
+ `;Uc="select pg_is_in_recovery(), coalesce(pg_last_wal_replay_lsn()::text, '')"});var E_,Bc=C(()=>{"use strict";xe();E_=7*24});var A_,Hc=C(()=>{"use strict";A_=7*24*60*60*1e3});var Ag,qc=C(()=>{"use strict";Hc();Ag=60*60*1e3});var Gc=C(()=>{"use strict"});var Ho,I_,Kc=C(()=>{"use strict";Ho="snoutdata_realtime",I_=`do $snoutpod$
27
69
  begin
28
- if exists (select 1 from pg_publication where pubname = '${To}') then
70
+ if exists (select 1 from pg_publication where pubname = '${Ho}') then
29
71
  return;
30
72
  end if;
31
- create publication ${To};
32
- execute format('alter publication ${To} owner to %I',
73
+ create publication ${Ho};
74
+ execute format('alter publication ${Ho} owner to %I',
33
75
  (select pg_get_userbyid(datdba) from pg_database where datname = current_database()));
34
76
  end
35
77
  $snoutpod$;
36
- `});var C_,I_,kc=j(()=>{"use strict";C_=2*1024*1024,I_=64*1024});var vc=j(()=>{"use strict"});function Oo(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function zn(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 Uf(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 Uf(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(Oo("the subnet",n,xc),Oo("the sink",r,Lf),!Ec(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Ec(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",`${Oo("a denied prefix",s,xc)},${r}`);return o.push(e.name),o}function $c(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Ec(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return($c(e)&s)>>>0===($c(n??"")&s)>>>0}function Ac(e){return e?.internal===!0}var Pc,xc,Lf,No=j(()=>{"use strict";Pc=["169.254.0.0/16","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"],xc=/^((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])$/,Lf=/^((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 Ff,Wn=j(()=>{"use strict";Ff=7*24*60*60});function qf(e,t){let[n,r]=e.split("/");if(r!=="24"||!n?.endsWith(".0")||!t.startsWith(n.slice(0,-1)))throw new Error(`${t} is not an address of the /24 ${e}`);let o=n.slice(0,-1),s=Number(t.slice(o.length)),a=[];for(let l=32;l>=25;l--){let i=2**(32-l);a.push(`${o}${(s^i)&~(i-1)&255}/${l}`)}return a}var Hf,Gf,Z_,Rc=j(()=>{"use strict";_e();kc();vc();No();Wn();Hf="10.93.0.0/24",Gf="10.93.0.1",Z_=[...Pc,"100.64.0.0/10",...qf(Hf,Gf)]});var Tc=j(()=>{"use strict"});import{createHash as Kf}from"node:crypto";function Vn(e){return`${U(e).pod}-lepis-tls`}function zf(e){return Kf("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function Cc(e,t){let n={...e},r=n[jo],o=n[Co];delete n[jo],delete n[Co];for(let[s,a,l]of[[jo,r,t],[Co,o,zf(t)]])if(a!==void 0){if(!Jf.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var Ht,jo,Co,Oc,Nc,Io,jc,Jf,Do=j(()=>{"use strict";_e();Tc();Ht="lepis",jo="SNOUTPOD_ADMIN_PASSWORD_ENV",Co="SNOUTPOD_ADMIN_TOKEN_ENV",Oc=7433,Nc=1e3;Io="/tls",jc=["set -e","if [ ! -s /tls/cert.pem ] || [ ! -s /tls/key.pem ]; then"," openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 3650 -subj /CN=lepis -addext subjectAltName=DNS:lepis,IP:127.0.0.1 -keyout /tls/key.pem -out /tls/cert.pem 2>/dev/null","fi","chmod 755 /tls","chmod 644 /tls/cert.pem /tls/key.pem"].join(`
37
- `),Jf=/^[A-Z][A-Z0-9_]{0,63}$/});var dS,uS,Ic=j(()=>{"use strict";_e();Wn();dS=6*60*60,uS=5*60});var Dc=j(()=>{"use strict"});var Mc=j(()=>{"use strict"});var gS,Lc=j(()=>{"use strict";gS=2*1024**3});var Uc=j(()=>{"use strict";Mc();Lc()});function Bc(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"||!Vf.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!Wf.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 Hc(e,t){return`${e}.nspname !~ '^pg_'
38
- and ${e}.nspname <> all (array[${Fc.map(n=>`'${n}'`).join(", ")}]::text[])
78
+ `});var M_,L_,Jc=C(()=>{"use strict";M_=2*1024*1024,L_=64*1024});var zc=C(()=>{"use strict"});function qo(e,t,n){if(!n.test(t))throw new Error(`${e} is not a value this can route: ${JSON.stringify(t)}`);return t}function nr(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 Tg(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 Tg(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(qo("the subnet",n,Wc),qo("the sink",r,Rg),!Yc(r,n))throw new Error(`the sink ${r} is not inside ${n}, so denied traffic would be routed at the host`);if(e.gateway&&!Yc(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",`${qo("a denied prefix",s,Wc)},${r}`);return o.push(e.name),o}function Vc(e){return e.split(".").reduce((t,n)=>t*256+Number(n),0)}function Yc(e,t){let[n,r]=t.split("/"),o=Number(r),s=o===0?0:4294967295<<32-o>>>0;return(Vc(e)&s)>>>0===(Vc(n??"")&s)>>>0}function Zc(e){return e?.internal===!0}var Xc,Wc,Rg,Go=C(()=>{"use strict";Xc=["169.254.0.0/16","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"],Wc=/^((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])$/,Rg=/^((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 Og,rr=C(()=>{"use strict";Og=7*24*60*60});function Ig(e,t){let[n,r]=e.split("/");if(r!=="24"||!n?.endsWith(".0")||!t.startsWith(n.slice(0,-1)))throw new Error(`${t} is not an address of the /24 ${e}`);let o=n.slice(0,-1),s=Number(t.slice(o.length)),a=[];for(let l=32;l>=25;l--){let i=2**(32-l);a.push(`${o}${(s^i)&~(i-1)&255}/${l}`)}return a}var Cg,jg,tk,Qc=C(()=>{"use strict";xe();Jc();zc();Go();rr();Cg="10.93.0.0/24",jg="10.93.0.1",tk=[...Xc,"100.64.0.0/10",...Ig(Cg,jg)]});var el=C(()=>{"use strict"});import{createHash as Dg}from"node:crypto";function or(e){return`${F(e).pod}-lepis-tls`}function Lg(e){return Dg("sha256").update(`snout-lepis-admin:${e}`).digest("hex")}function ol(e,t){let n={...e},r=n[Ko],o=n[Jo];delete n[Ko],delete n[Jo];for(let[s,a,l]of[[Ko,r,t],[Jo,o,Lg(t)]])if(a!==void 0){if(!Mg.test(a))throw new Error(`${s} names ${JSON.stringify(a)}, which is not a variable name`);n[a]=l}return n}var zt,Ko,Jo,tl,nl,zo,rl,Mg,Wo=C(()=>{"use strict";xe();el();zt="lepis",Ko="SNOUTPOD_ADMIN_PASSWORD_ENV",Jo="SNOUTPOD_ADMIN_TOKEN_ENV",tl=7433,nl=1e3;zo="/tls",rl=["set -e","if [ ! -s /tls/cert.pem ] || [ ! -s /tls/key.pem ]; then"," openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 3650 -subj /CN=lepis -addext subjectAltName=DNS:lepis,IP:127.0.0.1 -keyout /tls/key.pem -out /tls/cert.pem 2>/dev/null","fi","chmod 755 /tls","chmod 644 /tls/cert.pem /tls/key.pem"].join(`
79
+ `),Mg=/^[A-Z][A-Z0-9_]{0,63}$/});var mk,fk,sl=C(()=>{"use strict";xe();rr();mk=6*60*60,fk=5*60});var al=C(()=>{"use strict"});var il=C(()=>{"use strict"});var wk,cl=C(()=>{"use strict";wk=2*1024**3});var ll=C(()=>{"use strict";il();cl()});function ul(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"||!Fg.test(o))throw new Error(`not a database access role: ${JSON.stringify(o)}`);if(typeof s!="string"||!Ug.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 pl(e,t){return`${e}.nspname !~ '^pg_'
80
+ and ${e}.nspname <> all (array[${dl.map(n=>`'${n}'`).join(", ")}]::text[])
39
81
  and not ${t}.rolsuper
40
- and ${t}.rolname !~ '${Yf.source}'`}function qc(e){let t=[];for(let n of e.split(`
41
- `)){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 Kc(e,t){return re(e),JSON.stringify(Bc(t).map(n=>({role:n.role,level:n.level})))}var Wf,gt,Vf,J,Fc,Yf,Xn,Xf,Yn,Zf,Gc,Mo=j(()=>{"use strict";_e();Wf=["full","read"],gt="snout_oauth",Vf=/^oauth_[0-9a-f]{32}$/,J="snout_oauth_read",Fc=["information_schema","auth","storage","realtime","snout_realtime","_realtime","push","graphql","graphql_public","extensions","snout_identity","snout_files","snout_live","snout_graph","snout_ext","cron","net","vault","pgsodium","pgbouncer","snouttime","snouttime_internal"],Yf=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;Xn="SNOUTPOD_DB_ACCESS",Xf=/^[a-z_][a-z0-9_]*$/;for(let e of Fc)if(!Xf.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);Yn="snoutpod_oauth_read_new_schema",Zf=`do $home$
82
+ and ${t}.rolname !~ '${Bg.source}'`}function fl(e){let t=[];for(let n of e.split(`
83
+ `)){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 gl(e,t){return de(e),JSON.stringify(ul(t).map(n=>({role:n.role,level:n.level})))}var Ug,St,Fg,V,dl,Bg,ar,Hg,sr,qg,ml,Vo=C(()=>{"use strict";xe();Ug=["full","read"],St="snout_oauth",Fg=/^oauth_[0-9a-f]{32}$/,V="snout_oauth_read",dl=["information_schema","auth","storage","realtime","snout_realtime","_realtime","push","graphql","graphql_public","extensions","snout_identity","snout_files","snout_live","snout_graph","snout_ext","cron","net","vault","pgsodium","pgbouncer","snouttime","snouttime_internal"],Bg=/^(snout_[a-z]+_admin|snout_gate|authenticator|pgbouncer)$/;ar="SNOUTPOD_DB_ACCESS",Hg=/^[a-z_][a-z0-9_]*$/;for(let e of dl)if(!Hg.test(e))throw new Error(`not a schema name: ${JSON.stringify(e)}`);sr="snoutpod_oauth_read_new_schema",qg=`do $home$
42
84
  declare
43
85
  -- snout_ext in the own layout, extensions in a database not yet renamed (docs/cloud/OWN-LAYOUT.md).
44
86
  home text := case
@@ -47,7 +89,7 @@ declare
47
89
  end;
48
90
  begin
49
91
  execute pg_catalog.format('create schema if not exists %I', home);
50
- execute pg_catalog.replace($fn$create or replace function @home@.${Yn}() returns event_trigger
92
+ execute pg_catalog.replace($fn$create or replace function @home@.${sr}() returns event_trigger
51
93
  language plpgsql
52
94
  security invoker
53
95
  set search_path = pg_catalog, pg_temp
@@ -56,7 +98,7 @@ declare
56
98
  created record;
57
99
  owner_role text := pg_catalog.current_database() || '_owner';
58
100
  begin
59
- if not exists (select from pg_catalog.pg_roles where rolname = '${J}') then
101
+ if not exists (select from pg_catalog.pg_roles where rolname = '${V}') then
60
102
  return;
61
103
  end if;
62
104
  for created in
@@ -67,29 +109,29 @@ begin
67
109
  where c.command_tag = 'CREATE SCHEMA'
68
110
  and c.classid = 'pg_catalog.pg_namespace'::pg_catalog.regclass
69
111
  and not c.in_extension
70
- and ${Hc("n","r")}
112
+ and ${pl("n","r")}
71
113
  loop
72
114
  begin
73
- execute pg_catalog.format('grant usage on schema %I to ${J}', created.nspname);
74
- execute pg_catalog.format('grant select on all tables in schema %I to ${J}', created.nspname);
75
- execute pg_catalog.format('grant select on all sequences in schema %I to ${J}', created.nspname);
76
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${J}', owner_role, created.nspname);
77
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${J}', owner_role, created.nspname);
115
+ execute pg_catalog.format('grant usage on schema %I to ${V}', created.nspname);
116
+ execute pg_catalog.format('grant select on all tables in schema %I to ${V}', created.nspname);
117
+ execute pg_catalog.format('grant select on all sequences in schema %I to ${V}', created.nspname);
118
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${V}', owner_role, created.nspname);
119
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${V}', owner_role, created.nspname);
78
120
  exception when others then
79
121
  raise warning 'snoutpod: read access reaches schema % at the next apply, not now: %', created.nspname, sqlerrm;
80
122
  end;
81
123
  end loop;
82
124
  end
83
125
  $snoutpod$$fn$, '@home@', pg_catalog.quote_ident(home));
84
- if not exists (select from pg_catalog.pg_event_trigger where evtname = '${Yn}') then
85
- execute pg_catalog.format('create event trigger ${Yn} on ddl_command_end
126
+ if not exists (select from pg_catalog.pg_event_trigger where evtname = '${sr}') then
127
+ execute pg_catalog.format('create event trigger ${sr} on ddl_command_end
86
128
  when tag in (''CREATE SCHEMA'')
87
- execute function %I.${Yn}()', home);
129
+ execute function %I.${sr}()', home);
88
130
  end if;
89
131
  end
90
132
  $home$;
91
- `,Gc=`\\getenv db_access ${Xn}
92
- ${Zf}create function pg_temp.snoutpod_member(member_name text, group_name text)
133
+ `,ml=`\\getenv db_access ${ar}
134
+ ${qg}create function pg_temp.snoutpod_member(member_name text, group_name text)
93
135
  returns boolean
94
136
  language sql
95
137
  stable
@@ -110,7 +152,7 @@ create function pg_temp.snoutpod_customer_schema(schema_oid oid)
110
152
  set search_path = pg_catalog, pg_temp
111
153
  as $snoutpod$
112
154
  -- isCustomerSchema in dbAccess.ts, from the same SYSTEM_SCHEMAS and PLATFORM_OWNER.
113
- select ${Hc("n","r")}
155
+ select ${pl("n","r")}
114
156
  from pg_namespace n
115
157
  join pg_roles r on r.oid = n.nspowner
116
158
  where n.oid = schema_oid
@@ -145,9 +187,9 @@ begin
145
187
  end if;
146
188
  any_read := exists (select from jsonb_array_elements(wanted) w where w->>'level' = 'read');
147
189
 
148
- if not exists (select from pg_roles where rolname = '${gt}') then
149
- create role ${gt} nologin;
150
- change := 'created-group'; role_name := '${gt}'; return next;
190
+ if not exists (select from pg_roles where rolname = '${St}') then
191
+ create role ${St} nologin;
192
+ change := 'created-group'; role_name := '${St}'; return next;
151
193
  end if;
152
194
 
153
195
  -- Taken away: every member of the group the list does not name.
@@ -156,7 +198,7 @@ begin
156
198
  from pg_auth_members am
157
199
  join pg_roles g on g.oid = am.roleid
158
200
  join pg_roles m on m.oid = am.member
159
- where g.rolname = '${gt}'
201
+ where g.rolname = '${St}'
160
202
  order by 1
161
203
  loop
162
204
  continue when exists (select from jsonb_array_elements(wanted) w where w->>'role' = found_role);
@@ -175,21 +217,21 @@ begin
175
217
 
176
218
  -- What a read person may read: the customer's schemas, and nothing of ours (dbAccess.ts).
177
219
  if any_read then
178
- if not exists (select from pg_roles where rolname = '${J}') then
179
- create role ${J} nologin;
180
- change := 'created-group'; role_name := '${J}'; return next;
220
+ if not exists (select from pg_roles where rolname = '${V}') then
221
+ create role ${V} nologin;
222
+ change := 'created-group'; role_name := '${V}'; return next;
181
223
  end if;
182
224
  -- Stated whole, as a person's role is, and a member of nothing: whatever it held would reach
183
225
  -- every read person.
184
- alter role ${J} with nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls password null;
226
+ alter role ${V} with nologin nosuperuser nocreatedb nocreaterole noreplication nobypassrls password null;
185
227
  for target in
186
228
  select g.rolname as of_role, pg_get_userbyid(am.grantor) as grantor
187
229
  from pg_auth_members am
188
230
  join pg_roles g on g.oid = am.roleid
189
231
  join pg_roles m on m.oid = am.member
190
- where m.rolname = '${J}'
232
+ where m.rolname = '${V}'
191
233
  loop
192
- execute pg_catalog.format('revoke %I from ${J} granted by %I', target.of_role, target.grantor);
234
+ execute pg_catalog.format('revoke %I from ${V} granted by %I', target.of_role, target.grantor);
193
235
  end loop;
194
236
  for target in
195
237
  select n.nspname, pg_temp.snoutpod_customer_schema(n.oid) as customer
@@ -198,16 +240,16 @@ begin
198
240
  order by 1
199
241
  loop
200
242
  if target.customer then
201
- execute pg_catalog.format('grant usage on schema %I to ${J}', target.nspname);
202
- execute pg_catalog.format('grant select on all tables in schema %I to ${J}', target.nspname);
203
- execute pg_catalog.format('grant select on all sequences in schema %I to ${J}', target.nspname);
204
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${J}', owner_role, target.nspname);
205
- execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${J}', owner_role, target.nspname);
243
+ execute pg_catalog.format('grant usage on schema %I to ${V}', target.nspname);
244
+ execute pg_catalog.format('grant select on all tables in schema %I to ${V}', target.nspname);
245
+ execute pg_catalog.format('grant select on all sequences in schema %I to ${V}', target.nspname);
246
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on tables to ${V}', owner_role, target.nspname);
247
+ execute pg_catalog.format('alter default privileges for role %I in schema %I grant select on sequences to ${V}', owner_role, target.nspname);
206
248
  else
207
249
  -- Ours. Nothing was ever granted here unless the list or an owner changed since.
208
- execute pg_catalog.format('revoke all on schema %I from ${J}', target.nspname);
209
- execute pg_catalog.format('revoke all on all tables in schema %I from ${J}', target.nspname);
210
- execute pg_catalog.format('revoke all on all sequences in schema %I from ${J}', target.nspname);
250
+ execute pg_catalog.format('revoke all on schema %I from ${V}', target.nspname);
251
+ execute pg_catalog.format('revoke all on all tables in schema %I from ${V}', target.nspname);
252
+ execute pg_catalog.format('revoke all on all sequences in schema %I from ${V}', target.nspname);
211
253
  end if;
212
254
  end loop;
213
255
  for target in
@@ -216,10 +258,10 @@ begin
216
258
  from pg_default_acl d
217
259
  join pg_namespace n on n.oid = d.defaclnamespace
218
260
  where not pg_temp.snoutpod_customer_schema(n.oid)
219
- and exists (select from aclexplode(d.defaclacl) a where a.grantee = '${J}'::regrole)
261
+ and exists (select from aclexplode(d.defaclacl) a where a.grantee = '${V}'::regrole)
220
262
  loop
221
263
  continue when target.kind is null;
222
- execute pg_catalog.format('alter default privileges for role %I in schema %I revoke all on %s from ${J}', target.for_role, target.nspname, target.kind);
264
+ execute pg_catalog.format('alter default privileges for role %I in schema %I revoke all on %s from ${V}', target.for_role, target.nspname, target.kind);
223
265
  end loop;
224
266
  end if;
225
267
 
@@ -228,7 +270,7 @@ begin
228
270
  wanted_role := entry->>'role';
229
271
  wanted_level := entry->>'level';
230
272
  if exists (select from pg_roles where rolname = wanted_role) then
231
- if not pg_temp.snoutpod_member(wanted_role, '${gt}') then
273
+ if not pg_temp.snoutpod_member(wanted_role, '${St}') then
232
274
  -- The name is right and the role is not ours: made by somebody else, who holds
233
275
  -- ADMIN on it. Never adopted (see dbAccess.ts).
234
276
  change := 'refused'; role_name := wanted_role; return next;
@@ -236,7 +278,7 @@ begin
236
278
  end if;
237
279
  change := 'applied';
238
280
  else
239
- execute pg_catalog.format('create role %I login in role ${gt}', wanted_role);
281
+ execute pg_catalog.format('create role %I login in role ${St}', wanted_role);
240
282
  change := 'created';
241
283
  end if;
242
284
  -- No password, ever: the pg_hba line for the group is oauth, and a password could not be
@@ -249,8 +291,8 @@ begin
249
291
  execute pg_catalog.format('revoke pg_read_all_data from %I', wanted_role);
250
292
  end if;
251
293
  if wanted_level = 'full' then
252
- if pg_temp.snoutpod_member(wanted_role, '${J}') then
253
- execute pg_catalog.format('revoke ${J} from %I', wanted_role);
294
+ if pg_temp.snoutpod_member(wanted_role, '${V}') then
295
+ execute pg_catalog.format('revoke ${V} from %I', wanted_role);
254
296
  end if;
255
297
  execute pg_catalog.format('alter role %I nobypassrls', wanted_role);
256
298
  if not pg_temp.snoutpod_member(wanted_role, owner_role) then
@@ -265,8 +307,8 @@ begin
265
307
  execute pg_catalog.format('revoke %I from %I', owner_role, wanted_role);
266
308
  perform pg_terminate_backend(pid) from pg_stat_activity where usename = wanted_role;
267
309
  end if;
268
- if not pg_temp.snoutpod_member(wanted_role, '${J}') then
269
- execute pg_catalog.format('grant ${J} to %I with inherit true, set false', wanted_role);
310
+ if not pg_temp.snoutpod_member(wanted_role, '${V}') then
311
+ execute pg_catalog.format('grant ${V} to %I with inherit true, set false', wanted_role);
270
312
  end if;
271
313
  execute pg_catalog.format('alter role %I bypassrls', wanted_role);
272
314
  execute pg_catalog.format('alter role %I in database %I reset role', wanted_role, db);
@@ -277,30 +319,30 @@ begin
277
319
 
278
320
  -- Nobody reads any more: the group goes, and with it every grant and default it held, so a
279
321
  -- project that stops using the level carries nothing of it.
280
- if not any_read and exists (select from pg_roles where rolname = '${J}') then
322
+ if not any_read and exists (select from pg_roles where rolname = '${V}') then
281
323
  begin
282
324
  for target in
283
325
  select m.rolname as member_role, pg_get_userbyid(am.grantor) as grantor
284
326
  from pg_auth_members am
285
327
  join pg_roles g on g.oid = am.roleid
286
328
  join pg_roles m on m.oid = am.member
287
- where g.rolname = '${J}'
329
+ where g.rolname = '${V}'
288
330
  loop
289
- execute pg_catalog.format('revoke ${J} from %I granted by %I', target.member_role, target.grantor);
331
+ execute pg_catalog.format('revoke ${V} from %I granted by %I', target.member_role, target.grantor);
290
332
  end loop;
291
- drop owned by ${J};
292
- drop role ${J};
293
- change := 'dropped-group'; role_name := '${J}'; return next;
333
+ drop owned by ${V};
334
+ drop role ${V};
335
+ change := 'dropped-group'; role_name := '${V}'; return next;
294
336
  exception when others then
295
337
  -- Holding something in another database, which this never grants. Kept, and said.
296
- raise warning 'snoutpod: could not drop ${J}: %', sqlerrm;
338
+ raise warning 'snoutpod: could not drop ${V}: %', sqlerrm;
297
339
  end;
298
340
  end if;
299
341
  end
300
342
  $snoutpod$;
301
343
  select change || ' ' || role_name from pg_temp.snoutpod_db_access(:'db_access'::jsonb);
302
- `});var Jc,dk,uk,mk,pk,fk,zc=j(()=>{"use strict";dc();Eo();Ao();pc();Ro();yc();bc();_c();Sc();Rc();Do();Ic();Dc();Uc();Wn();Mo();Jc=120,dk=2*6e4,uk=15*60*1e3,mk=60*60*1e3,pk=5*60*1e3,fk=10*60*1e3});import{execFile as Qf}from"node:child_process";function Wc(){return(e,t,n={})=>new Promise((r,o)=>{let s=Qf(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 q(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
303
- `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var Gt=j(()=>{"use strict"});import{readdir as eg,readFile as bk}from"node:fs/promises";async function Vc(e,t,n={}){let r=n.binary??"podman",o=U(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??tg)(i)!=="empty":!1}async function tg(e){try{return(await eg(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var Yc=j(()=>{"use strict";_e();Gt()});import{createHash as ng,createHmac as Xc,pbkdf2Sync as rg}from"node:crypto";function Zc(e,t){let n=og.exec(e.trim());if(!n)return!1;let[,r,o,s,a]=n,l=Number(r);if(!Number.isInteger(l)||l<1||l>1e6)return!1;let i=rg(t.normalize("NFKC"),Buffer.from(o,"base64"),l,32,"sha256"),u=Xc("sha256",i).update("Client Key").digest(),c=ng("sha256").update(u).digest(),m=Xc("sha256",i).update("Server Key").digest();return c.equals(Buffer.from(s,"base64"))&&m.equals(Buffer.from(a,"base64"))}var og,Qc=j(()=>{"use strict";og=/^SCRAM-SHA-256\$(\d+):([^$]+)\$([^:]+):(.+)$/});function el(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 rl(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function ol(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=${el("the access key",e.key,ag)}`),n.push("--env",tl),e.token&&n.push("--env",nl)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${el("the endpoint",e.endpoint,sg)}`),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 sl(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{[tl]:e.secret,...e.token?{[nl]:e.token}:{}}}var tl,nl,sg,ag,al=j(()=>{"use strict";tl="SNOUTPOD_S3_SECRET",nl="SNOUTPOD_S3_TOKEN",sg=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,ag=/^[A-Za-z0-9/+=_.-]{1,256}$/});var il,cl,ll=j(()=>{"use strict";il=["snout_gate","authenticator"],cl=`do $snoutpod$
344
+ `});var hl,bv,Sv,_v,kv,vv,yl=C(()=>{"use strict";Tc();Do();Lo();Cc();Uo();Fo();Bo();Bc();qc();Gc();Kc();Qc();Wo();sl();al();ll();rr();Vo();hl=120,bv=2*6e4,Sv=15*60*1e3,_v=60*60*1e3,kv=5*60*1e3,vv=10*60*1e3});import{execFile as Gg}from"node:child_process";function wl(){return(e,t,n={})=>new Promise((r,o)=>{let s=Gg(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 z(e,t,n,r){let o=await e(t,n,r);if(o.code!==0){let s=(o.stderr||o.stdout).trim().split(`
345
+ `).slice(-3).join("; ");throw new Error(`${t} ${n[0]??""} failed (${o.code}): ${s}`)}return o.stdout}var Wt=C(()=>{"use strict"});import{readdir as Kg,readFile as Av}from"node:fs/promises";async function bl(e,t,n={}){let r=n.binary??"podman",o=F(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??Jg)(i)!=="empty":!1}async function Jg(e){try{return(await Kg(e)).length>0?"data":"empty"}catch(t){return t.code==="ENOENT"?"empty":"hidden"}}var Sl=C(()=>{"use strict";xe();Wt()});import{createHash as zg,createHmac as _l,pbkdf2Sync as Wg}from"node:crypto";function kl(e,t){let n=Vg.exec(e.trim());if(!n)return!1;let[,r,o,s,a]=n,l=Number(r);if(!Number.isInteger(l)||l<1||l>1e6)return!1;let i=Wg(t.normalize("NFKC"),Buffer.from(o,"base64"),l,32,"sha256"),u=_l("sha256",i).update("Client Key").digest(),c=zg("sha256").update(u).digest(),p=_l("sha256",i).update("Server Key").digest();return c.equals(Buffer.from(s,"base64"))&&p.equals(Buffer.from(a,"base64"))}var Vg,vl=C(()=>{"use strict";Vg=/^SCRAM-SHA-256\$(\d+):([^$]+)\$([^:]+):(.+)$/});function $l(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 Pl(e,t){if(!t)return e??null;let n=await t();return{...e??{},key:n.accessKeyId,secret:n.secretAccessKey,token:n.sessionToken}}function Al(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=${$l("the access key",e.key,Xg)}`),n.push("--env",xl),e.token&&n.push("--env",El)),e.endpoint&&n.push("--env",`SNOUTPOD_S3_ENDPOINT=${$l("the endpoint",e.endpoint,Yg)}`),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 Rl(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{[xl]:e.secret,...e.token?{[El]:e.token}:{}}}var xl,El,Yg,Xg,Tl=C(()=>{"use strict";xl="SNOUTPOD_S3_SECRET",El="SNOUTPOD_S3_TOKEN",Yg=/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?(:[0-9]{1,5})?$/i,Xg=/^[A-Za-z0-9/+=_.-]{1,256}$/});var Ol,Nl,Cl=C(()=>{"use strict";Ol=["snout_gate","authenticator"],Nl=`do $snoutpod$
304
346
  declare
305
347
  wanted text[];
306
348
  begin
@@ -318,8 +360,8 @@ begin
318
360
  end loop;
319
361
  end
320
362
  $snoutpod$;
321
- `});function ul(e){let t=[];for(let n of e.split(`
322
- `)){let r=n.split(Uo);if(r.length!==4||!Lo.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 Lo,Uo,dl,ml=j(()=>{"use strict";Lo=["snouttime"];for(let e of Lo)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);Uo="",dl=`
363
+ `});function Il(e){let t=[];for(let n of e.split(`
364
+ `)){let r=n.split(Xo);if(r.length!==4||!Yo.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 Yo,Xo,jl,Dl=C(()=>{"use strict";Yo=["snouttime"];for(let e of Yo)if(!/^[a-z][a-z0-9_]{0,62}$/.test(e))throw new Error(`not an extension name: ${JSON.stringify(e)}`);Xo="",jl=`
323
365
  SET search_path = pg_catalog, pg_temp;
324
366
  CREATE TEMP TABLE snoutpod_extension_updates (name text, from_version text, to_version text, error text);
325
367
  DO $snoutpod$
@@ -333,7 +375,7 @@ BEGIN
333
375
  SELECT e.extname, e.extversion
334
376
  FROM pg_extension e
335
377
  JOIN pg_available_extensions a ON a.name = e.extname
336
- WHERE e.extname = ANY (ARRAY[${Lo.map(e=>`'${e}'`).join(", ")}])
378
+ WHERE e.extname = ANY (ARRAY[${Yo.map(e=>`'${e}'`).join(", ")}])
337
379
  AND a.default_version IS DISTINCT FROM e.extversion
338
380
  LOOP
339
381
  BEGIN
@@ -364,102 +406,106 @@ BEGIN
364
406
  END
365
407
  $snoutpod$;
366
408
  SELECT name, from_version, coalesce(to_version, ''), coalesce(error, '') FROM snoutpod_extension_updates ORDER BY name;
367
- `});function fl(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 pl,Tk,Ok,gl=j(()=>{"use strict";Gt();Fo();pl="SNOUTPOD_OAUTH_ISSUER",Tk=256*1024,Ok=5*6e4});import{createHash as Go,randomBytes as ig}from"node:crypto";import{access as cg,constants as lg}from"node:fs/promises";import{delimiter as dg,join as ug}from"node:path";function mg(e,t){return Go("sha256").update(qo(e)).update(e.smtpSecretEnv&&t?Go("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function qo(e){if(Array.isArray(e))return`[${e.map(qo).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)}:${qo(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function Jo(e){if(!fg.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Kt(e){if(!gg.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function hg(e,t,n){return`postgres://${Kt(t)}:${n}@127.0.0.1:5432/${re(e)}`}function wl(e,t,n,r){let o=Cc(t.env,e.adminPassword);if(t.dbUriEnv&&t.dbUriRole){let s=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]=hg(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),o}function bl(e,t){return`${U(e).pod}-${Jo(t)}`}function Zn(e){return`snoutpod-host-${Jo(e)}`}function yg(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return Go("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 wg(e,t){let n=[],r=Ko(t);for(let o of e??[])if(Jo(o.name),Qn(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){Qn(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===Ht){let s=o.hostPort+Nc;Qn(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${Oc}`)}}return n}function Ko(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 Qn(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Sl(e){let t=[];for(let n of Object.keys(e)){if(!bg.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function _g(e){let t=e.healthPath;if(t==null||t==="")return[];if(!$l.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(Qn(e.hostPort,`${e.name}: hostPort`),["--label",`${zo}=${e.hostPort}:${t}`])}function Sg(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||!$l.test(r)?null:{port:n,path:r}}async function vg(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>cg(o,lg.X_OK));for(let o of n.split(dg))if(o)try{let s=ug(o,e);return await r(s),s}catch{}return null}function xg(){return["\\set ON_ERROR_STOP 0","set search_path = pg_catalog, pg_temp;","select 'create extension if not exists pg_prewarm schema public' where exists (select 1 from pg_available_extensions where name = 'pg_prewarm') \\gexec","select pg_catalog.format($snoutpod$with candidate as ("," select c.oid, pg_catalog.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(%I.pg_prewarm(oid::pg_catalog.regclass, 'buffer'::pg_catalog.text, 'main'::pg_catalog.text, null::pg_catalog.int8, null::pg_catalog.int8)), 0) from budgeted","where bytes > 0 and running <= pg_size_bytes(current_setting('shared_buffers')) * 3 / 4$snoutpod$, n.nspname)","from pg_extension e join pg_namespace n on n.oid = e.extnamespace where e.extname = 'pg_prewarm' \\gexec",""].join(`
368
- `)}function Eg(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function Pg(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 Wo(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function Ag(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 Rg(e,t,n,r){return{image:await Tg(e,t,r),data:await Og(e,t,n)}}async function Tg(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 Pg(r,n)}async function Og(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",U(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?Wo(s.stdout):null}catch{return null}}function Ng(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:kl;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,kl)}`),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 jg(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??kg;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await vg(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Pl(e){let t=e.binary??"podman",n=e.exec,r=jg(e),o=e.probe??El;async function s(f){try{await n(t,["exec",...ce,"--interactive",U(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",f,"--quiet","--file","-"],{timeoutMs:$g,stdin:xg()})}catch{}}async function a(){let f=await q(n,t,["ps","--all","--filter",`label=${Se}`,"--format","json"]),y=f.trim()?JSON.parse(f):[],_=new Map,C=new Map,R=new Map;for(let E of y){let Z=E.Labels?.[Se],Q=E.Labels?.[nt];if(!Z||!Q)continue;let oe=_.get(Z)??[];oe.push(Q),_.set(Z,oe),xl(E.State)==="stopped"&&C.set(Z,[...C.get(Z)??[],Q]);let O=Number(E.Labels?.[Ho]??0),te=Number.isInteger(O)?O:0;R.set(Z,Math.max(R.get(Z)??te,te))}let H=y.filter(E=>E.Labels?.[Se]&&!E.Labels?.[nt]),B=H.map(E=>xl(E.State)),G=await Ig(H,Cg,(E,Z)=>B[Z]==="running"?Promise.resolve(!0):e.hasData(E.Labels?.[Se]??""));return H.map((E,Z)=>{let Q=E.Labels?.[Se]??"",oe=Number(E.Labels?.[Bo]);return{ref:Q,status:B[Z]??"unknown",generation:Number.isInteger(oe)?oe:null,hasData:G[Z]??!1,services:_.get(Q)??[],servicesStopped:C.get(Q)??[],...R.has(Q)?{servicesGeneration:R.get(Q)}:{}}})}async function l(){let f=await q(n,t,["ps","--all","--filter",`label=${Se}`,"--format","json"]);return(f.trim()?JSON.parse(f):[]).filter(_=>_.Labels?.[Se]&&!_.Labels?.[nt]).length}async function i(f){let y=await(e.majors??((_,C)=>Rg(n,t,_,C)))(f.ref,f.image);return Ag(f.ref,y)}async function u(f){let y=U(f.ref),_=await i(f);if(_)throw new Error(_);let C=e.credentials,R=await rl(e.repository,C?()=>C.forProject(f.ref,f.bucket):null),H={SNOUTPOD_OWNER_PASSWORD:f.ownerPassword,POSTGRES_PASSWORD:f.adminPassword,...sl(R)},B=[...await r(),"run","--detach","--name",y.pod,"--label",`${Se}=${f.ref}`,"--label",`${Bo}=${f.generation}`,"--userns=auto","--memory",`${f.memoryMb}m`,...Lg(f.cpus),"--restart","no",...e.network?["--network",e.network]:[],...f.port?Ko(e.podBind).flatMap(G=>["--publish",`${G}:${f.port}:5432`]):[],...wg(f.services,e.podBind),"--volume",`${y.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${f.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${f.bucket}`,"--env",`SNOUTPOD_S3_REGION=${f.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...Mg(f.oauth),...Ng(f.tuning).flatMap(G=>["--env",G]),"--env",`SNOUTPOD_RETENTION_FULL=${mc(f.backup?.retentionDays,f.backup?.fullIntervalHours)}`,...ol(R),f.image,"snoutpod-postgres"];await q(n,t,B,{env:H})}async function c(f){let y=await q(n,t,["ps","--all","--filter",`label=${Se}=${re(f)}`,"--filter",`label=${nt}`,"--format","json"]),_=y.trim()?JSON.parse(y):[],C=[];for(let R of _){let H=R.Labels?.[nt];if(!H)continue;let B=Number(R.Labels?.[Ho]??0);C.push({name:H,container:bl(f,H),spec:R.Labels?.[hl]??"",pod:R.Labels?.[yl]??"",running:R.State==="running",generation:Number.isInteger(B)?B:0})}return C}async function m(f){let y=await n(t,["container","inspect","--format","{{.Id}}",U(f).pod]);return y.code===0?y.stdout.trim():""}async function h(f,y){let _=[];for(let G of y){let E=G.dbUriRole??null;G.dbUriEnv&&E&&!_.includes(E)&&_.push(Kt(E))}let C=new Map;if(_.length===0)return C;let R={},H=[],B=[];return _.some(G=>il.includes(G))&&B.push(cl),_.forEach((G,E)=>{let Z=ig(24).toString("base64url"),Q=`SNOUTPOD_SERVICE_PASSWORD_${E}`;C.set(G,Z),R[Q]=Z,H.push("--env",Q),B.push(`\\getenv service_password_${E} ${Q}`),B.push(`\\set service_role_${E} ${G}`),B.push(`alter role :"service_role_${E}" with password :'service_password_${E}';`)}),B.push(""),await q(n,t,["exec",...ce,"--interactive",...H,U(f.ref).pod,"psql","--username","snoutpod_admin","--dbname",U(f.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:R,stdin:B.join(`
369
- `)}),C}async function x(f){let y=Vn(f.ref);await q(n,t,["volume","create","--ignore",y]),await q(n,t,[...await r(),"run","--rm","--network","none","--volume",`${y}:${Io}`,"--entrypoint","bash",f.image,"-c",jc])}async function L(f){let y=await r();if((await n(t,[...y,"image","exists",f])).code===0)return;let _=await n(t,[...y,"pull","--quiet",f]);if(_.code===0)return;let C=Eg(f);if(!C||!e.registryLogin)throw new Error(`${f} could not be pulled: ${(_.stderr||_.stdout).trim()}`);await e.registryLogin(C),await q(n,t,[...y,"pull","--quiet",f])}async function V(f){let y=f.services??[],_=await c(f.ref),C;y.some(O=>O.smtpSecretEnv)&&(C=await e.smtpSecret?.().catch(()=>{}));let R=y.length>0?await m(f.ref):"",H=new Map(_.map(O=>[O.name,O])),B=new Map(y.map(O=>[O.name,mg(O,C)])),G=new Set(y.filter(O=>{let te=H.get(O.name);return!te||!te.running||te.spec!==B.get(O.name)||R===""||te.pod!==R}).map(O=>O.name)),E=f.servicesGeneration??0;if(G.size===0&&y.length>0&&!_.some(O=>O.generation>=E))for(let O of y)G.add(O.name);let Z=O=>O.dbUriEnv&&O.dbUriRole?O.dbUriRole:null;for(let O=!0;O;){O=!1;let te=new Set(y.filter(We=>G.has(We.name)).map(Z));for(let We of y){let ls=Z(We);!G.has(We.name)&&ls!==null&&te.has(ls)&&(G.add(We.name),O=!0)}}for(let O of new Set(y.filter(te=>G.has(te.name)).map(te=>te.image)))await L(O);for(let O of _)(G.has(O.name)||!B.has(O.name))&&await q(n,t,["rm","--force","--ignore",O.container]);let Q=y.filter(O=>G.has(O.name)),oe=await h(f,Q);Q.some(O=>O.name===Ht)&&await x(f);for(let O of Q){let te=bl(f.ref,O.name),We=[...await r(),"run","--detach","--name",te,"--label",`${Se}=${f.ref}`,"--label",`${Bo}=${f.generation}`,"--label",`${nt}=${O.name}`,"--label",`${Ho}=${f.servicesGeneration??0}`,"--label",`${hl}=${B.get(O.name)??""}`,"--label",`${yl}=${R}`,"--network",`container:${U(f.ref).pod}`,"--userns",`container:${U(f.ref).pod}`,"--memory",`${O.memoryMb}m`,"--restart","no",..._g(O),...O.name===Ht?["--volume",`${Vn(f.ref)}:${Io}:ro`]:[],...Sl(wl(f,O,oe,C)),O.image];await q(n,t,We,{env:wl(f,O,oe,C)})}}async function F(f,y){if(y%6!==0)return!1;let _=await n(t,["container","inspect","--format","{{.State.Status}}",U(f).pod]);return _.code===0&&/^(exited|stopped|dead)$/.test(_.stdout.trim())}async function de(f){let y=await n(t,["exec",U(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select pg_is_in_recovery()"]);if(y.code!==0)return null;let _=y.stdout.trim();return _==="t"?!0:_==="f"?!1:null}async function Oe(f,y){let _=R=>n(t,["exec",U(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",R]),C=Date.now()+y;do{if((await _("checkpoint")).code!==0)return!1;let H=await _("select (select timeline_id from pg_control_checkpoint()) = ('x' || substr(pg_walfile_name(pg_current_wal_lsn()), 1, 8))::bit(32)::int");if(H.code===0&&H.stdout.trim()==="t")return!0;await new Promise(B=>setTimeout(B,1e3))}while(Date.now()<C);return!1}async function le(f){let y=await n(t,["exec",U(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",fc]);return y.code===0?gc(y.stdout):null}async function ve(f,y){return(await n(t,["exec",U(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-v","ON_ERROR_STOP=1",...hc(y).flatMap(C=>["-c",C])])).code===0}return{list:a,inRecovery:de,settleTimeline:Oe,podHealth:le,setSyncNames:ve,preflight:i,create:u,createServices:V,async start(f){let y=(await c(f).catch(()=>[])).filter(_=>_.running).map(_=>_.container);y.length>0&&await q(n,t,["stop","--time","5",...y]),await q(n,t,["start",U(f).pod])},async startServices(f){let y=(await c(f)).map(_=>_.container);y.length>0&&await q(n,t,["start",...y])},async applyHostServices(f){let y=[],_=await q(n,t,["ps","--all","--filter",`label=${qt}`,"--format","json"]),C=_.trim()?JSON.parse(_):[],R=new Map;for(let B of C){let G=B.Labels?.[qt];G&&R.set(G,{spec:B.Labels?.[_l]??"",state:B.State??""})}for(let B of f)try{await H(B)}catch(G){y.push({name:B.name,action:"failed",reason:G instanceof Error?G.message:String(G)})}async function H(B){let G=e.hostServiceLocal?.(B,f),E=G?{...B,...G.env&&Object.keys(G.env).length>0?{env:{...B.env,...G.env}}:{},...G.volumes?{volumes:G.volumes}:{},...G.network?{network:G.network}:{}}:B,Z=Zn(E.name),Q=yg(E),oe=R.get(E.name);if(R.delete(E.name),oe&&oe.spec===Q){oe.state!=="running"&&(await q(n,t,["start",Z]),y.push({name:E.name,action:"started",reason:oe.state}));return}let O=oe?"the spec changed":"it was not here";await L(E.image),oe&&await q(n,t,["rm","--force","--ignore",Z]),await q(n,t,[...await r(),"run","--detach","--name",Z,"--label",`${qt}=${E.name}`,"--label",`${_l}=${Q}`,...E.healthPath?["--label",`${zo}=${E.hostPort}:${E.healthPath}`]:[],"--memory",`${E.memoryMb}m`,"--restart","no",...E.network?["--network",E.network]:e.network?["--network",e.network]:[],...(E.volumes??[]).flatMap(te=>["--volume",te]),...Ko(e.podBind).flatMap(te=>["--publish",`${te}:${E.hostPort}:${E.port}`]),...E.adminPort&&E.adminHostPort?["--publish",`127.0.0.1:${E.adminHostPort}:${E.adminPort}`]:[],...Sl(E.env),E.image,...E.command??[]],{env:{...E.env}}),y.push({name:E.name,action:oe?"recreated":"created",reason:O})}for(let B of R.keys())await q(n,t,["rm","--force","--ignore",Zn(B)]),y.push({name:B,action:"removed",reason:"it is no longer on the list"});return y},async hostServiceHealth(f){let y=await q(n,t,["ps","--all","--filter",`label=${qt}`,"--format","json"]),_=(y.trim()?JSON.parse(y):[]).map(R=>R.Labels?.[qt]??"").filter(R=>R!=="");if(_.length===0)return[];let C=Date.now()+f;for(;;){let R=[];for(let H of _)R.push(await vl(n,t,H,Zn(H),o));if(R.every(H=>H.ok)||Date.now()>=C)return R;await new Promise(H=>setTimeout(H,500))}},async serviceHealth(f,y){let _=await c(f);if(_.length===0)return[];let C=Date.now()+y;for(;;){let R=await Promise.all(_.map(H=>vl(n,t,H.name,H.container,o)));if(R.every(H=>H.ok)||Date.now()>=C)return R;await new Promise(H=>setTimeout(H,500))}},async stop(f,y){for(let _ of await c(f))await q(n,t,["stop","--time",String(pg),_.container]);await q(n,t,["stop","--time",String(y),U(f).pod])},async remove(f){for(let y of await c(f))await q(n,t,["rm","--force","--ignore",y.container]);await q(n,t,["rm","--force","--ignore","--volumes",U(f).pod])},async removeData(f){await q(n,t,["volume","rm","--force",U(f).pod]);let y=Vn(f);(await n(t,["volume","exists",y])).code===0&&await q(n,t,["volume","rm","--force",y])},async applyOwnerPassword(f){let y=await n(t,["exec",...ce,U(f.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select rolpassword from pg_authid where rolname = '${re(f.ref)}_owner'`]);if(y.code===0&&Zc(y.stdout,f.ownerPassword))return;let _=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${f.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
370
- `);await q(n,t,["exec",...ce,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",U(f.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:f.ownerPassword},stdin:_})},async applyRolePassword(f,y,_){let C=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${Kt(y)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
371
- `);await q(n,t,["exec",...ce,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",U(f).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:_},stdin:C})},async updateExtensions(f){let y=U(f),_=await n(t,["exec",...ce,y.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select datname from pg_database where datname = '${y.database}' or (not datistemplate and datname <> 'postgres') order by datname = '${y.database}' desc, datname limit 1`]),C=_.code===0&&/^[a-z][a-z0-9_]{0,62}$/.test(_.stdout.trim())?_.stdout.trim():y.database,R=await q(n,t,["exec",...ce,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",C,"--quiet","-tA","-F",Uo,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:dl});return ul(R)},async applyDbAccess(f,y){let _=U(f),C=await q(n,t,["exec",...ce,"--interactive","--env",Xn,_.pod,"psql","--username","snoutpod_admin","--dbname",_.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[Xn]:Kc(f,y)},stdin:Gc,timeoutMs:6e4});return qc(C)},async runProjectSql(f,y){let _=U(f);await q(n,t,["exec",...ce,"--interactive",_.pod,"psql","--username","snoutpod_admin","--dbname",_.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:y})},async ensureHostServiceDatabase(f,y,_){let C=Zn(f),R=Kt(y),H=Kt(_),B=E=>["exec",C,"psql","--username",R,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",E];(await q(n,t,B(`select 1 from pg_database where datname = '${H}'`))).trim()!=="1"&&await q(n,t,B(`create database "${H}"`))},async renameProject(f,y){y!==f.ref&&await q(n,t,["exec",...ce,"--interactive",U(f.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:Dg(y,f.ref)})},async waitReady(f,y){let _=U(f).pod,C=Date.now()+y,R=0;for(;;){let H=await n(t,["exec",...ce,_,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(H.code===0&&H.stdout.trim()==="t")return s(f),!0;if(Date.now()>=C||await F(f,++R))return!1;await new Promise(B=>setTimeout(B,500))}},async waitStandby(f,y){let _=Date.now()+y,C=0;for(;;){if(await de(f)===!0)return!0;if(Date.now()>=_||await F(f,++C))return!1;await new Promise(R=>setTimeout(R,500))}},async capacity(){let[f,y]=await Promise.all([l(),e.capacity()]);return{pods:f,...y}},async databaseBytes(f){let y=await n(t,["exec",...ce,U(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",f,"-tAc","select pg_database_size(current_database())"]);return y.code===0?Po(y.stdout):null},async storageBytes(f){let y=await n(t,["exec",...ce,U(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",f,"-tAc","select (xpath('/row/n/text()', query_to_xml(format('select coalesce(sum((metadata->>''size'')::bigint), 0) as n from %s', coalesce(to_regclass('snout_files.files'), to_regclass('storage.objects'))), false, true, '')))[1]::text"]);return y.code===0?Po(y.stdout):null},async setReadOnly(f,y){let _=U(f);await q(n,t,["exec",...ce,_.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${_.database} set default_transaction_read_only = ${y?"on":"off"}`])},async archiveState(f){let y=await n(t,["exec",...ce,U(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",f,"-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(y.code!==0)return null;let[_="",C=""]=y.stdout.trim().split(`
372
- `)[0]?.split("|")??[],R=Number(C);return{lastArchivedAt:_.trim()||null,failures:Number.isInteger(R)&&R>=0?R:0}}}}async function vl(e,t,n,r,o=El){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 m=JSON.parse(s.stdout);a=m[0]?.State,l=m[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=Sg(l?.[zo]);if(!u)return{name:n,ok:!0,detail:"running"};let c=await o(u.port,u.path);return c===null?{name:n,ok:!1,detail:"not answering yet"}:{name:n,ok:!0,detail:`answered ${c}`}}async function Ig(e,t,n){let r=new Array(e.length),o=0;async function s(){for(;o<e.length;){let a=o++;r[a]=await n(e[a],a)}}return await Promise.all(Array.from({length:Math.min(Math.max(1,t),e.length)},s)),r}function xl(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function Dg(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(`
373
- `)}function Mg(e){if(!e?.issuer)return[];try{return["--env",`${pl}=${fl(e.issuer)}`]}catch{return[]}}function Lg(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var Se,Bo,nt,Ho,hl,yl,pg,fg,gg,qt,_l,bg,$l,zo,El,kg,ce,$g,kl,Cg,Fo=j(()=>{"use strict";_e();Eo();Ro();Qc();Gt();al();Ao();ll();ml();Mo();gl();Do();Se="snoutpod.ref",Bo="snoutpod.generation",nt="snoutpod.service",Ho="snoutpod.services-generation",hl="snoutpod.service-spec",yl="snoutpod.service-pod";pg=10,fg=/^[a-z][a-z0-9-]{0,30}$/;gg=/^[a-z][a-z0-9_]{0,62}$/;qt="snoutpod.host-service",_l="snoutpod.host-service-spec";bg=/^[A-Za-z_][A-Za-z0-9_]*$/;$l=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,zo="snoutpod.health";El=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()})},kg="crun";ce=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp"],$g=3e4;kl=200;Cg=8});var Hl={};ds(Hl,{DEFAULT_LOCAL_MEMORY_MB:()=>Nl,DEFAULT_LOCAL_PORT:()=>Gg,ENGINE:()=>z,ENGINE_NAME:()=>Vo,IMAGE:()=>Zo,NETWORK:()=>Ke,NEW_PROJECT_MAJOR:()=>Xo,NO_BLACKHOLE_ROUTES:()=>Dl,PUBLIC_IMAGE:()=>zg,PUBLISHED_MAJORS:()=>ht,capacity:()=>Il,chooseLocalImage:()=>Wt,dropIfUnused:()=>Fl,ensureImage:()=>Wg,ensureNetwork:()=>Qg,exec:()=>ee,hasData:()=>rr,localImageFor:()=>Cl,localPsql:()=>nh,localRuntime:()=>Zg,localSpec:()=>Ul,localStatus:()=>Qo,localUri:()=>Ll,networkPolicyInPlace:()=>Ml,newLocalPassword:()=>Kg,newLocalRef:()=>qg,podLog:()=>Bl,podName:()=>yt,podmanReady:()=>Yg,removeLocal:()=>sh,staleImage:()=>Yo,startLocal:()=>th,stopLocal:()=>oh,volumePgVersion:()=>er});import{randomBytes as Tl}from"node:crypto";import{existsSync as Al}from"node:fs";import{platform as nr}from"node:os";import{delimiter as Ug,join as Fg}from"node:path";function Bg(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=nr()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(Ug))if(t&&e.some(n=>Al(Fg(t,n))))return"podman";return Al("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Wt(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return zt(Xo);let n=Number(Wo(e));if(!ht.includes(n)){let r=e.trim()||"(empty)",o=ht.map(s=>zt(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${ht.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return zt(n)}function zt(e){return{name:`${Hg}:${e}`,published:`${Ol}:${e}`,major:e}}function qg(){return lc(e=>Tl(e))}function Kg(){return Tl(18).toString("base64url")}async function Wg(e,t=ee,n=Wt(null)){let r=n.published;if(!r)return null;if((await t(z,["image","exists",n.name])).code===0)return await Vg(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(z,["pull",r],{timeoutMs:jl}).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(z,["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 Vg(e,t,n,r){let o=await t(z,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
374
- `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(z,["pull",r],{timeoutMs:jl}).catch(h=>({code:1,stdout:"",stderr:h instanceof Error?h.message:String(h)})).finally(()=>clearTimeout(l));if(i.code!==0){let h=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
375
- `).at(-1);e?.(`Could not check for a newer database image (${h}); using the one on this machine.`);return}let u=await t(z,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),c=u?.code===0?u.stdout.trim():"";if(!c||c===s)return;let m=await t(z,["tag",r,n]);if(m.code!==0){e?.(`Fetched a newer ${r} but could not tag it as ${n}: ${m.stderr.trim()}`);return}e?.(`Fetched a newer database image (${r}).`)}async function Yg(e=Jg,t=Zo){if(Vo!=="podman")return`SNOUTPOD_ENGINE=${Vo}: 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 ee(z,["--version"])}catch{return`${z} 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`${z} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await ee(z,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await ee(z,["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 Xg(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 ee(z,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
376
- `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function rr(e){let t=nr()==="darwin"||nr()==="win32";return Vc(ee,e,{binary:z,look:t?Xg:void 0})}async function er(e,t=ee,n=nr()){let r=U(e).pod;if((await t(z,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of ht){let l=zt(a).name;if((await t(z,["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 (${ht.map(a=>zt(a).published).join(" or ")}).`);o=await t(z,["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(z,["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(z,["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 Cl(e,t=er){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(Wt(null));re(e);let n=t===er?tr.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return Wt(o)}catch(s){throw new Error(`${yt(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===er&&(tr.set(e,r),r.catch(()=>tr.delete(e))),r}async function Il(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function Zg(e){return Pl({exec:ee,binary:z,network:Ke,hasData:rr,capacity:Il,repository:e})}function yt(e){return U(e).pod}async function Qg(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await eh()||await ee(z,zn({name:Ke}));return}let t=zn({name:Ke,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await Ml())return;await e?.(),await ee(z,["network","rm","--force",Ke]).catch(()=>{});let o=await ee(z,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${Dl}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${Ke} with egress blocked${n?`: ${n}`:""}`)}async function eh(){return(await ee(z,["network","inspect",Ke,"--format","json"])).code===0}async function Ml(){let e=await ee(z,["network","inspect",Ke,"--format","json"]);if(e.code!==0)return!1;try{return Ac(JSON.parse(e.stdout)?.[0])}catch{return!1}}function Ll(e){let t=U(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function Ul(e,t={}){return re(e.ref),{ref:e.ref,generation:1,image:t.image??Zo,memoryMb:t.memoryMb??Nl,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 Jt(e,t){e.onProgress?.(t)}async function th(e,t,n={}){let r=n.image??(await Cl(t.ref,n.pgVersion)).name,o=Ul(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??rr,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??Yo)(t.ref,r))?(Jt(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??Fl)(i)):l.status==="stopped"?(Jt(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(Jt(n,"The database is already running."),l.status==="running"&&await(n.staleImage??Yo)(t.ref,r)&&Jt(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(Jt(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 rh(t.ref,s,n.podLog??Bl));return await e.applyOwnerPassword(o),await e.createServices(o),Qo(e,t,n)}function Rl(e){return e.trim().replace(/^sha256:/,"")}async function Yo(e,t){let[n,r]=await Promise.all([ee(z,["container","inspect","--format","{{.Image}}",yt(e)]).catch(()=>null),ee(z,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=Rl(n.stdout),s=Rl(r.stdout);return o&&s&&o!==s?o:null}async function Fl(e){let t=await ee(z,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await ee(z,["rmi",e]).catch(()=>{})}async function nh(e,t){let n=U(e.ref);return ee(z,["exec","--interactive","-e","PGPASSWORD",yt(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 Bl(e){let t=await ee(z,["logs","--tail","20",yt(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function rh(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${yt(e)} never became ready in ${t}ms
409
+ `});function Ll(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 Ml,Uv,Fv,Ul=C(()=>{"use strict";Wt();Zo();Ml="SNOUTPOD_OAUTH_ISSUER",Uv=256*1024,Fv=5*6e4});import{createHash as ts,randomBytes as Qg}from"node:crypto";import{access as eh,constants as th}from"node:fs/promises";import{delimiter as nh,join as rh}from"node:path";function oh(e,t){return ts("sha256").update(ns(e)).update(e.smtpSecretEnv&&t?ts("sha256").update(t).digest("hex"):"").digest("hex").slice(0,32)}function ns(e){if(Array.isArray(e))return`[${e.map(ns).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)}:${ns(t[r])}`).join(",")}}`}return JSON.stringify(e??null)}function os(e){if(!ah.test(e))throw new Error(`not a service name: ${JSON.stringify(e)}`);return e}function Yt(e){if(!ih.test(e))throw new Error(`not a database role: ${JSON.stringify(e)}`);return e}function ch(e,t,n){return`postgres://${Yt(t)}:${n}@127.0.0.1:5432/${de(e)}`}function Hl(e,t,n,r){let o=ol(t.env,e.adminPassword);if(t.dbUriEnv&&t.dbUriRole){let s=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]=ch(e.ref,s,a)}return t.smtpSecretEnv&&r&&(o[t.smtpSecretEnv]=r),o}function ql(e,t){return`${F(e).pod}-${os(t)}`}function ir(e){return`snoutpod-host-${os(e)}`}function lh(e){let t=Object.keys(e.env).sort().map(n=>[n,e.env[n]]);return ts("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 dh(e,t){let n=[],r=rs(t);for(let o of e??[])if(os(o.name),cr(o.port,`${o.name}: port`),!(o.hostPort===null||o.hostPort===void 0)){cr(o.hostPort,`${o.name}: hostPort`);for(let s of r)n.push("--publish",`${s}:${o.hostPort}:${o.port}`);if(o.name===zt){let s=o.hostPort+nl;cr(s,`${o.name}: admin hostPort`);for(let a of r)n.push("--publish",`${a}:${s}:${tl}`)}}return n}function rs(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 cr(e,t){if(!Number.isInteger(e)||e<1||e>65535)throw new Error(`${t} is not a port: ${JSON.stringify(e)}`);return e}function Kl(e){let t=[];for(let n of Object.keys(e)){if(!uh.test(n))throw new Error(`not an environment variable name: ${JSON.stringify(n)}`);t.push("--env",n)}return t}function ph(e){let t=e.healthPath;if(t==null||t==="")return[];if(!Vl.test(t))throw new Error(`${e.name}: not a health path: ${JSON.stringify(t)}`);return e.hostPort===null||e.hostPort===void 0?[]:(cr(e.hostPort,`${e.name}: hostPort`),["--label",`${ss}=${e.hostPort}:${t}`])}function mh(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||!Vl.test(r)?null:{port:n,path:r}}async function gh(e,t={}){let n=t.path??process.env.PATH??"",r=t.look??(o=>eh(o,th.X_OK));for(let o of n.split(nh))if(o)try{let s=rh(o,e);return await r(s),s}catch{}return null}function hh(){return["\\set ON_ERROR_STOP 0","set search_path = pg_catalog, pg_temp;","select 'create extension if not exists pg_prewarm schema public' where exists (select 1 from pg_available_extensions where name = 'pg_prewarm') \\gexec","select pg_catalog.format($snoutpod$with candidate as ("," select c.oid, pg_catalog.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(%I.pg_prewarm(oid::pg_catalog.regclass, 'buffer'::pg_catalog.text, 'main'::pg_catalog.text, null::pg_catalog.int8, null::pg_catalog.int8)), 0) from budgeted","where bytes > 0 and running <= pg_size_bytes(current_setting('shared_buffers')) * 3 / 4$snoutpod$, n.nspname)","from pg_extension e join pg_namespace n on n.oid = e.extnamespace where e.extname = 'pg_prewarm' \\gexec",""].join(`
410
+ `)}function wh(e){return/^(\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com)\//.exec(e)?.[1]??null}function bh(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 as(e){let t=(e??"").trim();return/^\d+(?:\.\d+)?$/.test(t)?t:null}function Sh(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 _h(e,t,n,r){return{image:await kh(e,t,r),data:await vh(e,t,n)}}async function kh(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 bh(r,n)}async function vh(e,t,n){try{let r=await e(t,["volume","inspect","--format","{{.Mountpoint}}",F(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?as(s.stdout):null}catch{return null}}function $h(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:Jl;return t.push(`SNOUTPOD_MAX_CONNECTIONS=${Math.max(r,Jl)}`),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 xh(e){let t=null;return()=>(t===null&&(t=n()),t);async function n(){if(e.runtime===null)return[];let r=e.runtime??fh;return e.runtime!==void 0&&e.runtime!==""?["--runtime",r]:await gh(r,{look:e.lookExecutable})===null?[]:["--runtime",r]}}function Xl(e){let t=e.binary??"podman",n=e.exec,r=xh(e),o=e.probe??Yl;async function s(f){try{await n(t,["exec",...ge,"--interactive",F(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",f,"--quiet","--file","-"],{timeoutMs:yh,stdin:hh()})}catch{}}async function a(){let f=await z(n,t,["ps","--all","--filter",`label=${Ee}`,"--format","json"]),y=f.trim()?JSON.parse(f):[],b=new Map,O=new Map,A=new Map;for(let R of y){let te=R.Labels?.[Ee],se=R.Labels?.[at];if(!te||!se)continue;let ue=b.get(te)??[];ue.push(se),b.set(te,ue),Wl(R.State)==="stopped"&&O.set(te,[...O.get(te)??[],se]);let D=Number(R.Labels?.[es]??0),ie=Number.isInteger(D)?D:0;A.set(te,Math.max(A.get(te)??ie,ie))}let K=y.filter(R=>R.Labels?.[Ee]&&!R.Labels?.[at]),q=K.map(R=>Wl(R.State)),J=await Ph(K,Eh,(R,te)=>q[te]==="running"?Promise.resolve(!0):e.hasData(R.Labels?.[Ee]??""));return K.map((R,te)=>{let se=R.Labels?.[Ee]??"",ue=Number(R.Labels?.[Qo]);return{ref:se,status:q[te]??"unknown",generation:Number.isInteger(ue)?ue:null,hasData:J[te]??!1,services:b.get(se)??[],servicesStopped:O.get(se)??[],...A.has(se)?{servicesGeneration:A.get(se)}:{}}})}async function l(){let f=await z(n,t,["ps","--all","--filter",`label=${Ee}`,"--format","json"]);return(f.trim()?JSON.parse(f):[]).filter(b=>b.Labels?.[Ee]&&!b.Labels?.[at]).length}async function i(f){let y=await(e.majors??((b,O)=>_h(n,t,b,O)))(f.ref,f.image);return Sh(f.ref,y)}async function u(f){let y=F(f.ref),b=await i(f);if(b)throw new Error(b);let O=e.credentials,A=await Pl(e.repository,O?()=>O.forProject(f.ref,f.bucket):null),K={SNOUTPOD_OWNER_PASSWORD:f.ownerPassword,POSTGRES_PASSWORD:f.adminPassword,...Rl(A)},q=[...await r(),"run","--detach","--name",y.pod,"--label",`${Ee}=${f.ref}`,"--label",`${Qo}=${f.generation}`,"--userns=auto","--memory",`${f.memoryMb}m`,...Th(f.cpus),"--restart","no",...e.network?["--network",e.network]:[],...f.port?rs(e.podBind).flatMap(J=>["--publish",`${J}:${f.port}:5432`]):[],...dh(f.services,e.podBind),"--volume",`${y.pod}:/var/lib/postgresql/data:U`,"--env","SNOUTPOD_OWNER_PASSWORD","--env","POSTGRES_PASSWORD","--env",`SNOUTPOD_REF=${f.ref}`,"--env","POSTGRES_USER=snoutpod_admin","--env","POSTGRES_DB=postgres","--env",`SNOUTPOD_S3_BUCKET=${f.bucket}`,"--env",`SNOUTPOD_S3_REGION=${f.region}`,...e.egressDenied?["--env","SNOUTPOD_EGRESS=denied"]:[],...Rh(f.oauth),...$h(f.tuning).flatMap(J=>["--env",J]),"--env",`SNOUTPOD_RETENTION_FULL=${Nc(f.backup?.retentionDays,f.backup?.fullIntervalHours)}`,...Al(A),f.image,"snoutpod-postgres"];await z(n,t,q,{env:K})}async function c(f){let y=await z(n,t,["ps","--all","--filter",`label=${Ee}=${de(f)}`,"--filter",`label=${at}`,"--format","json"]),b=y.trim()?JSON.parse(y):[],O=[];for(let A of b){let K=A.Labels?.[at];if(!K)continue;let q=Number(A.Labels?.[es]??0);O.push({name:K,container:ql(f,K),spec:A.Labels?.[Fl]??"",pod:A.Labels?.[Bl]??"",running:A.State==="running",generation:Number.isInteger(q)?q:0})}return O}async function p(f){let y=await n(t,["container","inspect","--format","{{.Id}}",F(f).pod]);return y.code===0?y.stdout.trim():""}async function m(f,y){let b=[];for(let J of y){let R=J.dbUriRole??null;J.dbUriEnv&&R&&!b.includes(R)&&b.push(Yt(R))}let O=new Map;if(b.length===0)return O;let A={},K=[],q=[];return b.some(J=>Ol.includes(J))&&q.push(Nl),b.forEach((J,R)=>{let te=Qg(24).toString("base64url"),se=`SNOUTPOD_SERVICE_PASSWORD_${R}`;O.set(J,te),A[se]=te,K.push("--env",se),q.push(`\\getenv service_password_${R} ${se}`),q.push(`\\set service_role_${R} ${J}`),q.push(`alter role :"service_role_${R}" with password :'service_password_${R}';`)}),q.push(""),await z(n,t,["exec",...ge,"--interactive",...K,F(f.ref).pod,"psql","--username","snoutpod_admin","--dbname",F(f.ref).database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:A,stdin:q.join(`
411
+ `)}),O}async function E(f){let y=or(f.ref);await z(n,t,["volume","create","--ignore",y]),await z(n,t,[...await r(),"run","--rm","--network","none","--volume",`${y}:${zo}`,"--entrypoint","bash",f.image,"-c",rl])}async function k(f){let y=await r();if((await n(t,[...y,"image","exists",f])).code===0)return;let b=await n(t,[...y,"pull","--quiet",f]);if(b.code===0)return;let O=wh(f);if(!O||!e.registryLogin)throw new Error(`${f} could not be pulled: ${(b.stderr||b.stdout).trim()}`);await e.registryLogin(O),await z(n,t,[...y,"pull","--quiet",f])}async function X(f){let y=f.services??[],b=await c(f.ref),O;y.some(D=>D.smtpSecretEnv)&&(O=await e.smtpSecret?.().catch(()=>{}));let A=y.length>0?await p(f.ref):"",K=new Map(b.map(D=>[D.name,D])),q=new Map(y.map(D=>[D.name,oh(D,O)])),J=new Set(y.filter(D=>{let ie=K.get(D.name);return!ie||!ie.running||ie.spec!==q.get(D.name)||A===""||ie.pod!==A}).map(D=>D.name)),R=f.servicesGeneration??0;if(J.size===0&&y.length>0&&!b.some(D=>D.generation>=R))for(let D of y)J.add(D.name);let te=D=>D.dbUriEnv&&D.dbUriRole?D.dbUriRole:null;for(let D=!0;D;){D=!1;let ie=new Set(y.filter(Qe=>J.has(Qe.name)).map(te));for(let Qe of y){let _s=te(Qe);!J.has(Qe.name)&&_s!==null&&ie.has(_s)&&(J.add(Qe.name),D=!0)}}for(let D of new Set(y.filter(ie=>J.has(ie.name)).map(ie=>ie.image)))await k(D);for(let D of b)(J.has(D.name)||!q.has(D.name))&&await z(n,t,["rm","--force","--ignore",D.container]);let se=y.filter(D=>J.has(D.name)),ue=await m(f,se);se.some(D=>D.name===zt)&&await E(f);for(let D of se){let ie=ql(f.ref,D.name),Qe=[...await r(),"run","--detach","--name",ie,"--label",`${Ee}=${f.ref}`,"--label",`${Qo}=${f.generation}`,"--label",`${at}=${D.name}`,"--label",`${es}=${f.servicesGeneration??0}`,"--label",`${Fl}=${q.get(D.name)??""}`,"--label",`${Bl}=${A}`,"--network",`container:${F(f.ref).pod}`,"--userns",`container:${F(f.ref).pod}`,"--memory",`${D.memoryMb}m`,"--restart","no",...ph(D),...D.name===zt?["--volume",`${or(f.ref)}:${zo}:ro`]:[],...Kl(Hl(f,D,ue,O)),D.image];await z(n,t,Qe,{env:Hl(f,D,ue,O)})}}async function j(f,y){if(y%6!==0)return!1;let b=await n(t,["container","inspect","--format","{{.State.Status}}",F(f).pod]);return b.code===0&&/^(exited|stopped|dead)$/.test(b.stdout.trim())}async function H(f){let y=await n(t,["exec",F(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",Uc]);return y.code===0?Fc(y.stdout):null}async function oe(f){let y=await n(t,["exec",F(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select pg_is_in_recovery()"]);if(y.code!==0)return null;let b=y.stdout.trim();return b==="t"?!0:b==="f"?!1:null}async function ne(f,y){let b=A=>n(t,["exec",F(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",A]),O=Date.now()+y;do{if((await b("checkpoint")).code!==0)return!1;let K=await b("select (select timeline_id from pg_control_checkpoint()) = ('x' || substr(pg_walfile_name(pg_current_wal_lsn()), 1, 8))::bit(32)::int");if(K.code===0&&K.stdout.trim()==="t")return!0;await new Promise(q=>setTimeout(q,1e3))}while(Date.now()<O);return!1}async function I(f){let y=await n(t,["exec",F(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc",jc]);return y.code===0?Ic(y.stdout):null}async function ce(f,y){let b=new Map;for(let A of y)de(A),b.set(A,F(A).pod);if(b.size===0)return new Map;let O=await n(t,["exec",F(f).pod,"timeout",String(10),"sh","-c",Mc,"sh",...b.values()]);return O.code===0?Lc(O.stdout,b):null}async function G(f,y){return(await n(t,["exec",F(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-v","ON_ERROR_STOP=1",...Dc(y).flatMap(O=>["-c",O])])).code===0}return{list:a,inRecovery:oe,replayLsn:H,settleTimeline:ne,podHealth:I,setSyncNames:G,nodesAnswer:ce,preflight:i,create:u,createServices:X,async start(f){let y=(await c(f).catch(()=>[])).filter(b=>b.running).map(b=>b.container);y.length>0&&await z(n,t,["stop","--time","5",...y]),await z(n,t,["start",F(f).pod])},async startServices(f){let y=(await c(f)).map(b=>b.container);y.length>0&&await z(n,t,["start",...y])},async applyHostServices(f){let y=[],b=await z(n,t,["ps","--all","--filter",`label=${Vt}`,"--format","json"]),O=b.trim()?JSON.parse(b):[],A=new Map;for(let q of O){let J=q.Labels?.[Vt];J&&A.set(J,{spec:q.Labels?.[Gl]??"",state:q.State??""})}for(let q of f)try{await K(q)}catch(J){y.push({name:q.name,action:"failed",reason:J instanceof Error?J.message:String(J)})}async function K(q){let J=e.hostServiceLocal?.(q,f),R=J?{...q,...J.env&&Object.keys(J.env).length>0?{env:{...q.env,...J.env}}:{},...J.volumes?{volumes:J.volumes}:{},...J.network?{network:J.network}:{}}:q,te=ir(R.name),se=lh(R),ue=A.get(R.name);if(A.delete(R.name),ue&&ue.spec===se){ue.state!=="running"&&(await z(n,t,["start",te]),y.push({name:R.name,action:"started",reason:ue.state}));return}let D=ue?"the spec changed":"it was not here";await k(R.image),ue&&await z(n,t,["rm","--force","--ignore",te]),await z(n,t,[...await r(),"run","--detach","--name",te,"--label",`${Vt}=${R.name}`,"--label",`${Gl}=${se}`,...R.healthPath?["--label",`${ss}=${R.hostPort}:${R.healthPath}`]:[],"--memory",`${R.memoryMb}m`,"--restart","no",...R.network?["--network",R.network]:e.network?["--network",e.network]:[],...(R.volumes??[]).flatMap(ie=>["--volume",ie]),...rs(e.podBind).flatMap(ie=>["--publish",`${ie}:${R.hostPort}:${R.port}`]),...R.adminPort&&R.adminHostPort?["--publish",`127.0.0.1:${R.adminHostPort}:${R.adminPort}`]:[],...Kl(R.env),R.image,...R.command??[]],{env:{...R.env}}),y.push({name:R.name,action:ue?"recreated":"created",reason:D})}for(let q of A.keys())await z(n,t,["rm","--force","--ignore",ir(q)]),y.push({name:q,action:"removed",reason:"it is no longer on the list"});return y},async hostServiceHealth(f){let y=await z(n,t,["ps","--all","--filter",`label=${Vt}`,"--format","json"]),b=(y.trim()?JSON.parse(y):[]).map(A=>A.Labels?.[Vt]??"").filter(A=>A!=="");if(b.length===0)return[];let O=Date.now()+f;for(;;){let A=[];for(let K of b)A.push(await zl(n,t,K,ir(K),o));if(A.every(K=>K.ok)||Date.now()>=O)return A;await new Promise(K=>setTimeout(K,500))}},async serviceHealth(f,y){let b=await c(f);if(b.length===0)return[];let O=Date.now()+y;for(;;){let A=await Promise.all(b.map(K=>zl(n,t,K.name,K.container,o)));if(A.every(K=>K.ok)||Date.now()>=O)return A;await new Promise(K=>setTimeout(K,500))}},async stop(f,y){for(let b of await c(f))await z(n,t,["stop","--time",String(sh),b.container]);await z(n,t,["stop","--time",String(y),F(f).pod])},async remove(f){for(let y of await c(f))await z(n,t,["rm","--force","--ignore",y.container]);await z(n,t,["rm","--force","--ignore","--volumes",F(f).pod])},async removeData(f){await z(n,t,["volume","rm","--force",F(f).pod]);let y=or(f);(await n(t,["volume","exists",y])).code===0&&await z(n,t,["volume","rm","--force",y])},async applyOwnerPassword(f){let y=await n(t,["exec",...ge,F(f.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select rolpassword from pg_authid where rolname = '${de(f.ref)}_owner'`]);if(y.code===0&&kl(y.stdout,f.ownerPassword))return;let b=["\\getenv owner_password SNOUTPOD_OWNER_PASSWORD",`\\set owner_role ${f.ref}_owner`,`alter role :"owner_role" with password :'owner_password';`,""].join(`
412
+ `);await z(n,t,["exec",...ge,"--interactive","--env","SNOUTPOD_OWNER_PASSWORD",F(f.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_OWNER_PASSWORD:f.ownerPassword},stdin:b})},async applyRolePassword(f,y,b){let O=["\\getenv role_password SNOUTPOD_ROLE_PASSWORD",`\\set target_role ${Yt(y)}`,`alter role :"target_role" with password :'role_password';`,""].join(`
413
+ `);await z(n,t,["exec",...ge,"--interactive","--env","SNOUTPOD_ROLE_PASSWORD",F(f).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{env:{SNOUTPOD_ROLE_PASSWORD:b},stdin:O})},async updateExtensions(f){let y=F(f),b=await n(t,["exec",...ge,y.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`select datname from pg_database where datname = '${y.database}' or (not datistemplate and datname <> 'postgres') order by datname = '${y.database}' desc, datname limit 1`]),O=b.code===0&&/^[a-z][a-z0-9_]{0,62}$/.test(b.stdout.trim())?b.stdout.trim():y.database,A=await z(n,t,["exec",...ge,"--interactive",y.pod,"psql","--username","snoutpod_admin","--dbname",O,"--quiet","-tA","-F",Xo,"-v","ON_ERROR_STOP=1","--file","-"],{stdin:jl});return Il(A)},async applyDbAccess(f,y){let b=F(f),O=await z(n,t,["exec",...ge,"--interactive","--env",ar,b.pod,"psql","--username","snoutpod_admin","--dbname",b.database,"--quiet","-tA","-v","ON_ERROR_STOP=1","--file","-"],{env:{[ar]:gl(f,y)},stdin:ml,timeoutMs:6e4});return fl(O)},async runProjectSql(f,y){let b=F(f);await z(n,t,["exec",...ge,"--interactive",b.pod,"psql","--username","snoutpod_admin","--dbname",b.database,"--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:y})},async ensureHostServiceDatabase(f,y,b){let O=ir(f),A=Yt(y),K=Yt(b),q=R=>["exec",O,"psql","--username",A,"--dbname","postgres","--quiet","-tA","-v","ON_ERROR_STOP=1","-c",R];(await z(n,t,q(`select 1 from pg_database where datname = '${K}'`))).trim()!=="1"&&await z(n,t,q(`create database "${K}"`))},async renameProject(f,y){y!==f.ref&&await z(n,t,["exec",...ge,"--interactive",F(f.ref).pod,"psql","--username","snoutpod_admin","--dbname","postgres","--quiet","-v","ON_ERROR_STOP=1","--file","-"],{stdin:Ah(y,f.ref)})},async waitReady(f,y){let b=F(f).pod,O=Date.now()+y,A=0;for(;;){let K=await n(t,["exec",...ge,b,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname","postgres","-tAc","select not pg_is_in_recovery()"]);if(K.code===0&&K.stdout.trim()==="t")return s(f),!0;if(Date.now()>=O||await j(f,++A))return!1;await new Promise(q=>setTimeout(q,500))}},async waitStandby(f,y){let b=Date.now()+y,O=0;for(;;){if(await oe(f)===!0)return!0;if(Date.now()>=b||await j(f,++O))return!1;await new Promise(A=>setTimeout(A,500))}},async capacity(){let[f,y]=await Promise.all([l(),e.capacity()]);return{pods:f,...y}},async databaseBytes(f){let y=await n(t,["exec",...ge,F(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",f,"-tAc","select pg_database_size(current_database())"]);return y.code===0?Mo(y.stdout):null},async storageBytes(f){let y=await n(t,["exec",...ge,F(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",f,"-tAc","select (xpath('/row/n/text()', query_to_xml(format('select coalesce(sum((metadata->>''size'')::bigint), 0) as n from %s', coalesce(to_regclass('snout_files.files'), to_regclass('storage.objects'))), false, true, '')))[1]::text"]);return y.code===0?Mo(y.stdout):null},async setReadOnly(f,y){let b=F(f);await z(n,t,["exec",...ge,b.pod,"psql","--username","snoutpod_admin","--dbname","postgres","-tAc",`alter database ${b.database} set default_transaction_read_only = ${y?"on":"off"}`])},async archiveState(f){let y=await n(t,["exec",...ge,F(f).pod,"psql","--host","127.0.0.1","--username","snoutpod_admin","--dbname",f,"-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(y.code!==0)return null;let[b="",O=""]=y.stdout.trim().split(`
414
+ `)[0]?.split("|")??[],A=Number(O);return{lastArchivedAt:b.trim()||null,failures:Number.isInteger(A)&&A>=0?A:0}}}}async function zl(e,t,n,r,o=Yl){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 p=JSON.parse(s.stdout);a=p[0]?.State,l=p[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=mh(l?.[ss]);if(!u)return{name:n,ok:!0,detail:"running"};let c=await o(u.port,u.path);return c===null?{name:n,ok:!1,detail:"not answering yet"}:{name:n,ok:!0,detail:`answered ${c}`}}async function Ph(e,t,n){let r=new Array(e.length),o=0;async function s(){for(;o<e.length;){let a=o++;r[a]=await n(e[a],a)}}return await Promise.all(Array.from({length:Math.min(Math.max(1,t),e.length)},s)),r}function Wl(e){switch((e??"").toLowerCase()){case"running":return"running";case"created":case"configured":return"starting";case"exited":case"stopped":return"stopped";default:return"unknown"}}function Ah(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(`
415
+ `)}function Rh(e){if(!e?.issuer)return[];try{return["--env",`${Ml}=${Ll(e.issuer)}`]}catch{return[]}}function Th(e){return typeof e!="number"||!Number.isFinite(e)||e<=0?[]:["--cpus",e.toFixed(2)]}var Ee,Qo,at,es,Fl,Bl,sh,ah,ih,Vt,Gl,uh,Vl,ss,Yl,fh,ge,yh,Jl,Eh,Zo=C(()=>{"use strict";xe();Do();Uo();Fo();vl();Wt();Tl();Lo();Cl();Dl();Vo();Ul();Bo();Wo();Ee="snoutpod.ref",Qo="snoutpod.generation",at="snoutpod.service",es="snoutpod.services-generation",Fl="snoutpod.service-spec",Bl="snoutpod.service-pod";sh=10,ah=/^[a-z][a-z0-9-]{0,30}$/;ih=/^[a-z][a-z0-9_]{0,62}$/;Vt="snoutpod.host-service",Gl="snoutpod.host-service-spec";uh=/^[A-Za-z_][A-Za-z0-9_]*$/;Vl=/^\/[A-Za-z0-9\/_\-.~%?=&]*$/,ss="snoutpod.health";Yl=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()})},fh="crun";ge=["--env","PGOPTIONS=-c search_path=pg_catalog,pg_temp"],yh=3e4;Jl=200;Eh=8});var pd={};ks(pd,{DEFAULT_LOCAL_MEMORY_MB:()=>nd,DEFAULT_LOCAL_PORT:()=>Ih,ENGINE:()=>Y,ENGINE_NAME:()=>is,IMAGE:()=>ds,NETWORK:()=>Ye,NEW_PROJECT_MAJOR:()=>ls,NO_BLACKHOLE_ROUTES:()=>ad,PUBLIC_IMAGE:()=>Uh,PUBLISHED_MAJORS:()=>_t,capacity:()=>sd,chooseLocalImage:()=>Qt,dropIfUnused:()=>dd,ensureImage:()=>Fh,ensureNetwork:()=>Kh,exec:()=>ae,hasData:()=>pr,localImageFor:()=>od,localPsql:()=>Wh,localRuntime:()=>Gh,localSpec:()=>ld,localStatus:()=>us,localUri:()=>cd,networkPolicyInPlace:()=>id,newLocalPassword:()=>Mh,newLocalRef:()=>Dh,podLog:()=>ud,podName:()=>kt,podmanReady:()=>Hh,removeLocal:()=>Xh,staleImage:()=>cs,startLocal:()=>zh,stopLocal:()=>Yh,volumePgVersion:()=>lr});import{randomBytes as ed}from"node:crypto";import{existsSync as Zl}from"node:fs";import{platform as ur}from"node:os";import{delimiter as Oh,join as Nh}from"node:path";function Ch(){if(process.env.SNOUTPOD_ENGINE)return process.env.SNOUTPOD_ENGINE;let e=ur()==="win32"?["podman.exe","podman"]:["podman"];for(let t of(process.env.PATH??"").split(Oh))if(t&&e.some(n=>Zl(Nh(t,n))))return"podman";return Zl("/opt/podman/bin/podman")?"/opt/podman/bin/podman":"podman"}function Qt(e,t=process.env.SNOUTPOD_IMAGE){if(t)return{name:t,published:null,major:null};if(e===null)return Zt(ls);let n=Number(as(e));if(!_t.includes(n)){let r=e.trim()||"(empty)",o=_t.map(s=>Zt(s).published).join(" or ");throw new Error(`its data directory's PG_VERSION says ${r}, and there is an image only for Postgres ${_t.join(" and ")}. Set SNOUTPOD_IMAGE to an image of that major (${o} for those).`)}return Zt(n)}function Zt(e){return{name:`${jh}:${e}`,published:`${td}:${e}`,major:e}}function Dh(){return Rc(e=>ed(e))}function Mh(){return ed(18).toString("base64url")}async function Fh(e,t=ae,n=Qt(null)){let r=n.published;if(!r)return null;if((await t(Y,["image","exists",n.name])).code===0)return await Bh(e,t,n.name,r),null;e?.(`Fetching the database image (${r}). This happens once.`);let o=await t(Y,["pull",r],{timeoutMs:rd}).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(Y,["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 Bh(e,t,n,r){let o=await t(Y,["image","inspect","--format","{{.Id}}",n,r]).catch(()=>null),[s,a]=o?.code===0?o.stdout.trim().split(`
416
+ `):[];if(!s||s!==a)return;let l=setTimeout(()=>e?.(`Fetching a newer database image (${r}).`),3e3),i=await t(Y,["pull",r],{timeoutMs:rd}).catch(m=>({code:1,stdout:"",stderr:m instanceof Error?m.message:String(m)})).finally(()=>clearTimeout(l));if(i.code!==0){let m=(i.stderr.trim()||i.stdout.trim()||"the pull failed").split(`
417
+ `).at(-1);e?.(`Could not check for a newer database image (${m}); using the one on this machine.`);return}let u=await t(Y,["image","inspect","--format","{{.Id}}",r]).catch(()=>null),c=u?.code===0?u.stdout.trim():"";if(!c||c===s)return;let p=await t(Y,["tag",r,n]);if(p.code!==0){e?.(`Fetched a newer ${r} but could not tag it as ${n}: ${p.stderr.trim()}`);return}e?.(`Fetched a newer database image (${r}).`)}async function Hh(e=Lh,t=ds){if(is!=="podman")return`SNOUTPOD_ENGINE=${is}: 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 ae(Y,["--version"])}catch{return`${Y} 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`${Y} --version failed: ${n.stderr.trim()||n.stdout.trim()}`;if((await ae(Y,["image","exists",t])).code!==0)return`the pod image ${t} is not on this machine. ${e}`;let o=await ae(Y,["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 qh(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 ae(Y,["machine","ssh",t]).then(n=>{let r=n.stdout.trim().split(`
418
+ `).at(-1);return r==="DATA"?"data":r==="EMPTY"?"empty":"hidden"},()=>"hidden")}function pr(e){let t=ur()==="darwin"||ur()==="win32";return bl(ae,e,{binary:Y,look:t?qh:void 0})}async function lr(e,t=ae,n=ur()){let r=F(e).pod;if((await t(Y,["volume","exists",r])).code!==0)return null;let o;if(n==="darwin"||n==="win32"){let s=null;for(let a of _t){let l=Zt(a).name;if((await t(Y,["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 (${_t.map(a=>Zt(a).published).join(" or ")}).`);o=await t(Y,["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(Y,["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(Y,["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 od(e,t=lr){if(!e||process.env.SNOUTPOD_IMAGE)return Promise.resolve(Qt(null));de(e);let n=t===lr?dr.get(e):void 0;if(n)return n;let r=t(e).then(o=>{try{return Qt(o)}catch(s){throw new Error(`${kt(e)}: ${s instanceof Error?s.message:String(s)}`)}});return t===lr&&(dr.set(e,r),r.catch(()=>dr.delete(e))),r}async function sd(){return{ramFreeMb:4096,ramTotalMb:8192,diskFreeMb:32768,diskTotalMb:65536}}function Gh(e){return Xl({exec:ae,binary:Y,network:Ye,hasData:pr,capacity:sd,repository:e})}function kt(e){return F(e).pod}async function Kh(e){if(process.env.SNOUTPOD_LOCAL_ALLOW_EGRESS==="1"){await Jh()||await ae(Y,nr({name:Ye}));return}let t=nr({name:Ye,allow:[]}),n="";for(let r=0;r<10;r+=1){if(await id())return;await e?.(),await ae(Y,["network","rm","--force",Ye]).catch(()=>{});let o=await ae(Y,t);if(o.code===0)return;if(n=`${o.stderr}${o.stdout}`.trim(),/invalid route gateway/i.test(n))throw new Error(`${ad}: ${n}`);await new Promise(s=>setTimeout(s,500))}throw new Error(`could not create ${Ye} with egress blocked${n?`: ${n}`:""}`)}async function Jh(){return(await ae(Y,["network","inspect",Ye,"--format","json"])).code===0}async function id(){let e=await ae(Y,["network","inspect",Ye,"--format","json"]);if(e.code!==0)return!1;try{return Zc(JSON.parse(e.stdout)?.[0])}catch{return!1}}function cd(e){let t=F(e.ref);return`postgres://${t.ownerRole}:${encodeURIComponent(e.ownerPassword)}@127.0.0.1:${e.port}/${t.database}?sslmode=disable`}function ld(e,t={}){return de(e.ref),{ref:e.ref,generation:1,image:t.image??ds,memoryMb:t.memoryMb??nd,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 Xt(e,t){e.onProgress?.(t)}async function zh(e,t,n={}){let r=n.image??(await od(t.ref,n.pgVersion)).name,o=ld(t,{...n,image:r}),s=n.timeoutMs??12e4,a=n.hasData??pr,l=(await e.list()).find(u=>u.ref===t.ref),i=null;if(l?l.status==="stopped"&&(i=await(n.staleImage??cs)(t.ref,r))?(Xt(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??dd)(i)):l.status==="stopped"?(Xt(n,"Starting the database."),await e.start(t.ref),await e.startServices(t.ref)):(Xt(n,"The database is already running."),l.status==="running"&&await(n.staleImage??cs)(t.ref,r)&&Xt(n,"A newer database image is on this machine. To move onto it: snoutdata stop, then snoutdata start.")):(Xt(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 Vh(t.ref,s,n.podLog??ud));return await e.applyOwnerPassword(o),await e.createServices(o),us(e,t,n)}function Ql(e){return e.trim().replace(/^sha256:/,"")}async function cs(e,t){let[n,r]=await Promise.all([ae(Y,["container","inspect","--format","{{.Image}}",kt(e)]).catch(()=>null),ae(Y,["image","inspect","--format","{{.Id}}",t]).catch(()=>null)]);if(n?.code!==0||r?.code!==0)return null;let o=Ql(n.stdout),s=Ql(r.stdout);return o&&s&&o!==s?o:null}async function dd(e){let t=await ae(Y,["image","inspect","--format","{{len .RepoTags}}",e]).catch(()=>null);t?.code===0&&t.stdout.trim()==="0"&&await ae(Y,["rmi",e]).catch(()=>{})}async function Wh(e,t){let n=F(e.ref);return ae(Y,["exec","--interactive","-e","PGPASSWORD",kt(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 ud(e){let t=await ae(Y,["logs","--tail","20",kt(e)]).catch(()=>null);return t?`${t.stdout}${t.stderr}`:"(no log: the container is not there)"}async function Vh(e,t,n){let r=await n(e).catch(()=>"(no log)");return`${kt(e)} never became ready in ${t}ms
377
419
  --- pod log ---
378
- ${r}`}async function oh(e,t,n={}){re(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,Jc),Qo(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function sh(e,t,n){re(t),await e.remove(t),n&&(await e.removeData(t),tr.delete(t))}async function Qo(e,t,n={}){re(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??rr;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?Ll(t):null}}var Vo,z,Xo,ht,Hg,Ol,Zo,Ke,Gg,Nl,ee,Jg,zg,jl,tr,Dl,Gl=j(()=>{"use strict";zc();Gt();Yc();No();Fo();_e();Vo=process.env.SNOUTPOD_ENGINE??"podman";z=Bg(),Xo=18,ht=[17,18],Hg="snoutpod/postgres",Ol="ghcr.io/snoutdata/snoutpod-postgres";Zo=Wt(null).name,Ke=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",Gg=54322,Nl=512,ee=Wc();Jg="cd packages/snoutpod && npm run image:build",zg=`${Ol}:${Xo}`;jl=20*60*1e3;tr=new Map;Dl="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});ue();var fe=["--admin","--token","--project"],dr=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],rt=[...fe,"--plan","--no-wait","--timeout",...dr],me=[...rt,"--yes"],ur=[{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",args:"<id|sdt_prefix>",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",args:"auth|storage|data-api|push",summary:"Turn auth, storage, data-api or push on",flags:["--ref"]},{name:"products disable",args:"auth|storage|data-api|push",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",args:"apns|fcm",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",args:"apns|fcm",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",args:"on|off",summary:"Guest sign-in on or off: guestSignIn() 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",args:"KIND",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 roster entry, 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",args:"<hostname>",summary:"Add one, and print the DNS records to publish",flags:["--ref"]},{name:"domains verify",args:"<hostname>",summary:"Check its records and verify it",flags:["--ref"]},{name:"domains remove",args:"<hostname>",summary:"Stop serving it",flags:["--ref"]},{name:"functions deploy",args:"<name>",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",args:"<name>",summary:"Its memory and workers, within the plan",flags:["--ref","--memory","--concurrency","--reset"]},{name:"functions logs",args:"[name]",summary:"What your functions printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"functions delete",args:"<name>",summary:"Remove one",flags:["--ref"]},{name:"containers deploy",args:"<name> <image> | <name> --dockerfile <dir>",summary:"Run a Docker image of yours all the time, pinned to the digest its tag points at now, or built from a Dockerfile with your Docker",flags:["--ref","--port","--dockerfile","--registry-user","--stdin","--clear-registry","--no-wait"]},{name:"containers list",summary:"Each container: status, image and URL",flags:["--ref"]},{name:"containers logs",args:"[name]",summary:"What your containers printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"containers restart",args:"<name>",summary:"Start one again from its image",flags:["--ref"]},{name:"containers delete",args:"<name>",summary:"Stop one and remove it",flags:["--ref"]},{name:"secrets set",summary:"Set the environment functions and containers 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 app key and server key 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 access",summary:"Who signs in to the database as themselves with OAuth (Postgres 18), at which level, as which role",flags:["--ref"]},{name:"db access grant",summary:"Let a person sign in to the database as themselves, and print their connection string",flags:["--ref","--level"]},{name:"db access revoke",summary:"Take it away, by email or role; a token already issued can work until it expires (up to 1 hour)",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:"cluster status",summary:"A Lepis cluster: its nodes, keyspaces and their ranges (keyspace:lo), tables, routers and unfinished jobs",flags:[...fe]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...fe]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...fe]},{name:"cluster enable",summary:"SnoutData Cloud: make a project a cluster. The router joins its pod, which restarts the database once. Each node then uses one of the plan project slots",flags:["--project","--plan","--yes"]},{name:"cluster disable",summary:"SnoutData Cloud: turn a cluster of one node back into a plain project. The data is checked, the cluster is taken apart and the router leaves its pod, which restarts the database once",flags:["--project","--plan","--yes"]},{name:"cluster nodes attach",summary:"SnoutData Cloud: hand a ready node pod made with nodes add --no-join to the cluster, as a standby until a range moves onto it",flags:[...rt]},{name:"cluster nodes add",summary:"Join a Postgres node: checks its version and settings, syncs roles and reference tables. In SnoutData Cloud it makes a node that starts as a copy of the home node, then joins and takes its share of the data on its own (--no-join to attach it by hand); it uses one of the plan project slots",flags:[...rt,"--host","--port","--dbname","--sslmode","--peer-host","--no-join"]},{name:"cluster spread",summary:"SnoutData Cloud: spread a table over the cluster in one go: a keyspace for its key (shared with tables keyed the same way), the table distributed by it, then a rebalance",flags:["--project","--column","--key-type","--plan","--yes"]},{name:"cluster retry",summary:"SnoutData Cloud: take up a stopped step again: a node whose join stopped, or --queue for a stopped spread",flags:["--project","--queue"]},{name:"cluster replica add",args:"<node-ref>",summary:"SnoutData Cloud: give a data node a replica, a copy that follows it and takes its place on its own if the node is lost or corrupt. It takes one of the plan project slots, and the node needs its first backup",flags:["--project","--node","--plan","--yes"]},{name:"cluster replica remove",args:"<node-ref>",summary:"SnoutData Cloud: remove a node's replica and free its project slot. If the node is then lost or corrupt, its share of the data is unavailable until it is restored from backups",flags:["--project","--node","--plan","--yes"]},{name:"cluster alerts",summary:"SnoutData Cloud: what happened to the cluster that you must know about (a node lost or corrupt, a failover, a node without its replica), open ones first, with those resolved in the last day",flags:["--project"]},{name:"cluster alerts seen",args:"<id>",summary:"SnoutData Cloud: mark an alert as seen. It stays open until what it is about is over",flags:["--project"]},{name:"cluster nodes drain",summary:"Move every range off a node",flags:[...me,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...me]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...rt,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...rt]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...me,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...me]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...me]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...me,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...me]},{name:"cluster range move",summary:"Give a range to another node",flags:[...me,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...me,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...me,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...me,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...rt,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...me,"--node"]},{name:"cluster dissolve",summary:"A standalone cluster of the home node alone: check the data, take the fences off and forget every keyspace and table, then stop the router yourself",flags:[...me]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...fe,...dr]},{name:"cluster jobs",summary:"The latest jobs",flags:[...fe]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...fe]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...fe,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...fe,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...fe,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...fe,...dr,"--set"]},{name:"cluster advise",summary:"What to split, move or add, from the router's advisor: each recommendation with its reason, its plan and the command that runs it. Runs nothing",flags:[...fe,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],ms={"--ref":"The project. Defaults to SNOUTDATA_PROJECT, then .snoutdata/project.json here or in a parent","functions logs --errors":"Only what was printed with console.error or console.warn, and the errors that stopped a request","functions logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","functions logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","containers deploy --port":"The port the container listens on, also given to it as PORT (default 8080)","containers deploy --dockerfile":"A folder with a Dockerfile (or the Dockerfile): built for linux/arm64 with your Docker and uploaded to the project","containers deploy --registry-user":"The user name for a private registry. The token comes on stdin, with --stdin","containers deploy --stdin":"Read the registry token from a pipe, so it is not in shell history or ps","containers deploy --clear-registry":"Forget the stored registry login, for an image that is public now","containers deploy --no-wait":"Answer once it is recorded, without waiting for the host to start it","containers logs --errors":"Only what was printed to stderr","containers logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","containers logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","--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":"A SnoutData Cloud project ref: its cluster, through the control plane, as you (instead of --admin)","tokens create --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","--admin":"The Lepis router's admin API, like http://127.0.0.1:7432 (LEPIS_ADMIN_ADDR). https for any other machine; NODE_EXTRA_CA_CERTS for a private CA. Defaults to LEPIS_ADMIN_URL","--token":"The router's admin token. Prefer LEPIS_ADMIN_TOKEN, which keeps it out of shell history and ps","--plan":"Show the plan (steps, what moves, rows, size, copy time, expected pause) and change nothing","--yes":"Run it without asking. Without a terminal to ask at, an operation that moves or deletes data needs it","cluster jobs cancel --yes":"Cancel without asking","cluster nodes add --port":"The node's port (default 5432)","--host":"The address the router reaches the node at","--dbname":"The database on the node (default postgres)","--sslmode":"disable, require or verify-full (the default)","--peer-host":"The address the other nodes reach it at, when it differs from the router's","--to":"The node (id or name) that receives it","cluster nodes drain --to":"Comma-separated nodes to move its ranges to. Default: the least loaded","--key-type":"The distribution key's Postgres type: bigint, int, text, uuid, ...","--ranges":"How many equal ranges to start with","--seed":"The hash seed","--nodes":"Comma-separated nodes (ids or names) to place the ranges on","--column":"The distribution key column","--no-join":"SnoutData Cloud: leave the new node for you to attach with cluster nodes attach, instead of joining it on its own","--queue":"The stopped request (a table being spread) rather than a node","--keyspace":"The keyspace","--range":"The range's lower bound, with --keyspace: the spelling for a bound that starts with a dash","cluster range split --at":"Where to split (a hash value). Default: the middle","--node":"A node, by id or name","cluster cleanup --node":"Only this node. Default: every node","cluster replica add --node":"The node pod's ref, as cluster status lists it (or as the first word)","cluster replica remove --node":"The node pod's ref, as cluster status lists it (or as the first word)","--value":"The key value to pin","--add":"Comma-separated nodes to add, each name=host[:port]","--remove":"How many of the emptiest nodes to drain and remove","cluster scale --nodes":"SnoutData Cloud: how many nodes the project should have, the home included","--max-write-pause-ms":"The longest a cutover may pause writes; one that would take longer is postponed","--drain-timeout-ms":"How long a cutover waits for open transactions to finish","--set":"Settings by their API names, name=value,... (the advisor's advice_* thresholds among them)","--sample-ms":"How long to measure write rates over, in ms (default: the advice_sample_ms setting; 0 skips them)","--ack-timeout-ms":"How long a cutover waits for every router to acknowledge it","--copy-mb-per-s":"The copy rate a plan estimates with (chosen, not measured)","--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 mr(e,t){return ms[`${e} ${t}`]??ms[t]??""}function ps(e,t){let n=ur.filter(s=>s.name===e||s.name.startsWith(`${e} `));if(!t)return n;let r=`${e} ${t}`,o=n.filter(s=>s.name===r||s.name.startsWith(`${r} `));return o.length>0?o:n}Y();se();W();import{spawn as vu}from"node:child_process";function xu(e){return`snout-${e}`}function ut(e,t,n){return new Promise((r,o)=>{let s=vu("docker",["compose","-p",xu(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))d(w(` ${u.trim()}`))}),s.on("error",i=>{if(i.code==="ENOENT"){o(new v("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 v("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function $u(e){let t=await ut(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new v("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 Zs(e){let t=await $u(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(d(`${k(e.name??e.ref)} (local, ${e.folder})`),t.length===0){d("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${M([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
379
- `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function Qs(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await ut(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new v("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},()=>{d(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function ea(e){let t=await ut(e,["stop"],!0);if(t.code!==0)throw new v("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return g({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function ta(e){g({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${M([["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}`]])}
380
- `)})}St();import{cpSync as oa,existsSync as Tt,readdirSync as ju,readFileSync as sa,rmSync as aa,statSync as Cu,writeFileSync as ia}from"node:fs";import{join as ye,resolve as $n}from"node:path";var Eu=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Pu(e){return Eu.test(e)}function Ze(e){if(!Pu(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 Au=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function Ru(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!==".."&&Au.test(n))}var Tr=2*1024*1024,na=200;function ra(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>na)throw new Error(`a function may hold ${na} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!Ru(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=Tu(o);if(s.length>Tr)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Tr/1024} KB. A function is source code; put large files in Storage.`);return o}function Tu(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 Ou=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var Nu=["SNOUTDATA_","SNOUT_"];function Rt(e){if(!Ou.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=Nu.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 fw=64*1024;se();ue();W();function ca(e){let t=ye(e.folder,"functions");return Tt(t)?ju(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&Cu(ye(t,n)).isDirectory()).sort():[]}async function En(e){d(w(" docker compose run --rm functions-deploy"));let t=await ut(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new v("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function la(e,t,n){let r=ye(e.folder,".env"),o=sa(r,"utf8"),s=(nn(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 ia(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
420
+ ${r}`}async function Yh(e,t,n={}){de(t);let r=(await e.list()).find(o=>o.ref===t);return r&&r.status!=="stopped"&&await e.stop(t,hl),us(e,{ref:t,port:0,ownerPassword:"",adminPassword:""},n)}async function Xh(e,t,n){de(t),await e.remove(t),n&&(await e.removeData(t),dr.delete(t))}async function us(e,t,n={}){de(t.ref);let r=(await e.list()).find(a=>a.ref===t.ref),o=r?.status==="running",s=n.hasData??pr;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?cd(t):null}}var is,Y,ls,_t,jh,td,ds,Ye,Ih,nd,ae,Lh,Uh,rd,dr,ad,md=C(()=>{"use strict";yl();Wt();Sl();Go();Zo();xe();is=process.env.SNOUTPOD_ENGINE??"podman";Y=Ch(),ls=18,_t=[17,18],jh="snoutpod/postgres",td="ghcr.io/snoutdata/snoutpod-postgres";ds=Qt(null).name,Ye=process.env.SNOUTPOD_LOCAL_NETWORK??"snoutpod-local",Ih=54322,nd=512,ae=wl();Lh="cd packages/snoutpod && npm run image:build",Uh=`${td}:${ls}`;rd=20*60*1e3;dr=new Map;ad="this podman cannot blackhole a route, so the local tier cannot isolate its containers"});he();var be=["--admin","--token","--project"],br=["--max-write-pause-ms","--drain-timeout-ms","--ack-timeout-ms","--copy-mb-per-s"],it=[...be,"--plan","--no-wait","--timeout",...br],ye=[...it,"--yes"],Sr=[{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",args:"<id|sdt_prefix>",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",args:"auth|storage|data-api|push",summary:"Turn auth, storage, data-api or push on",flags:["--ref"]},{name:"products disable",args:"auth|storage|data-api|push",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",args:"apns|fcm",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",args:"apns|fcm",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",args:"on|off",summary:"Guest sign-in on or off: guestSignIn() 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",args:"KIND",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 roster entry, 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",args:"<hostname>",summary:"Add one, and print the DNS records to publish",flags:["--ref"]},{name:"domains verify",args:"<hostname>",summary:"Check its records and verify it",flags:["--ref"]},{name:"domains remove",args:"<hostname>",summary:"Stop serving it",flags:["--ref"]},{name:"functions deploy",args:"<name>",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",args:"<name>",summary:"Its memory and workers, within the plan",flags:["--ref","--memory","--concurrency","--reset"]},{name:"functions logs",args:"[name]",summary:"What your functions printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"functions delete",args:"<name>",summary:"Remove one",flags:["--ref"]},{name:"containers deploy",args:"<name> <image> | <name> --dockerfile <dir>",summary:"Run a Docker image of yours all the time, pinned to the digest its tag points at now, or built from a Dockerfile with your Docker",flags:["--ref","--port","--dockerfile","--registry-user","--stdin","--clear-registry","--no-wait"]},{name:"containers list",summary:"Each container: status, image and URL",flags:["--ref"]},{name:"containers logs",args:"[name]",summary:"What your containers printed, the last seven days",flags:["--ref","--errors","--tail","--follow"]},{name:"containers restart",args:"<name>",summary:"Start one again from its image",flags:["--ref"]},{name:"containers delete",args:"<name>",summary:"Stop one and remove it",flags:["--ref"]},{name:"secrets set",summary:"Set the environment functions and containers 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 app key and server key 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 access",summary:"Who signs in to the database as themselves with OAuth (Postgres 18), at which level, as which role",flags:["--ref"]},{name:"db access grant",summary:"Let a person sign in to the database as themselves, and print their connection string",flags:["--ref","--level"]},{name:"db access revoke",summary:"Take it away, by email or role; a token already issued can work until it expires (up to 1 hour)",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:"cluster status",summary:"A Lepis cluster: its nodes, keyspaces and their ranges (keyspace:lo), tables, routers and unfinished jobs",flags:[...be]},{name:"cluster ops",summary:"The operations the router offers, and its default settings",flags:[...be]},{name:"cluster nodes",summary:"The nodes, their state and how many ranges each owns",flags:[...be]},{name:"cluster enable",summary:"SnoutData Cloud: make a project a cluster. The router joins its pod, which restarts the database once. Each node then uses one of the plan project slots",flags:["--project","--plan","--yes"]},{name:"cluster disable",summary:"SnoutData Cloud: turn a cluster of one node back into a plain project. The data is checked, the cluster is taken apart and the router leaves its pod, which restarts the database once",flags:["--project","--plan","--yes"]},{name:"cluster nodes attach",summary:"SnoutData Cloud: hand a ready node pod made with nodes add --no-join to the cluster, as a standby until a range moves onto it",flags:[...it]},{name:"cluster nodes add",summary:"Join a Postgres node: checks its version and settings, syncs roles and reference tables. In SnoutData Cloud it makes a node that starts as a copy of the home node, then joins and takes its share of the data on its own (--no-join to attach it by hand); it uses one of the plan project slots",flags:[...it,"--host","--port","--dbname","--sslmode","--peer-host","--no-join"]},{name:"cluster spread",summary:"SnoutData Cloud: spread a table over the cluster in one go: a keyspace for its key (shared with tables keyed the same way), the table distributed by it, then a rebalance",flags:["--project","--column","--key-type","--plan","--yes"]},{name:"cluster retry",summary:"SnoutData Cloud: take up a stopped step again: a node whose join stopped, or --queue for a stopped spread",flags:["--project","--queue"]},{name:"cluster replica add",args:"<node-ref>",summary:"SnoutData Cloud: give a data node a replica, a copy that follows it and takes its place on its own if the node is lost or corrupt. It takes one of the plan project slots, and the node needs its first backup",flags:["--project","--node","--plan","--yes"]},{name:"cluster replica remove",args:"<node-ref>",summary:"SnoutData Cloud: remove a node's replica and free its project slot. If the node is then lost or corrupt, its share of the data is unavailable until it is restored from backups",flags:["--project","--node","--plan","--yes"]},{name:"cluster alerts",summary:"SnoutData Cloud: what happened to the cluster that you must know about (a node lost or corrupt, a failover, a node without its replica), open ones first, with those resolved in the last day",flags:["--project"]},{name:"cluster alerts seen",args:"<id>",summary:"SnoutData Cloud: mark an alert as seen. It stays open until what it is about is over",flags:["--project"]},{name:"cluster nodes drain",summary:"Move every range off a node",flags:[...ye,"--to"]},{name:"cluster nodes remove",summary:"Detach a drained node",flags:[...ye]},{name:"cluster keyspace create",summary:"A keyspace: the shard key type and its initial ranges",flags:[...it,"--key-type","--ranges","--seed","--nodes"]},{name:"cluster keyspace drop",summary:"Forget a keyspace no table is distributed by",flags:[...it]},{name:"cluster table distribute",summary:"Shard a table by a column, moving its rows out from the home node",flags:[...ye,"--column","--keyspace"]},{name:"cluster table reference",summary:"Copy a table to every node",flags:[...ye]},{name:"cluster table global",summary:"Bring a table back to the home node alone",flags:[...ye]},{name:"cluster range split",summary:"One range becomes two, and one half moves",flags:[...ye,"--keyspace","--range","--at","--to"]},{name:"cluster range merge",summary:"Two adjacent ranges become one",flags:[...ye]},{name:"cluster range move",summary:"Give a range to another node",flags:[...ye,"--keyspace","--range","--to"]},{name:"cluster tenant pin",summary:"One key value gets its own range, and optionally its own node",flags:[...ye,"--keyspace","--value","--node"]},{name:"cluster rebalance",summary:"Even out the ranges across the nodes",flags:[...ye,"--keyspace"]},{name:"cluster scale",summary:"Add nodes and rebalance, or drain and remove the emptiest",flags:[...ye,"--add","--remove","--nodes","--dbname","--sslmode"]},{name:"cluster verify",summary:"Row counts and a checksum per range, source against target",flags:[...it,"--keyspace"]},{name:"cluster cleanup",summary:"Delete the rows a node no longer owns, then VACUUM",flags:[...ye,"--node"]},{name:"cluster dissolve",summary:"A standalone cluster of the home node alone: check the data, take the fences off and forget every keyspace and table, then stop the router yourself",flags:[...ye]},{name:"cluster plan",summary:"The dry run of any operation above: steps, what moves, rows, size, copy time, expected pause",flags:[...be,...br]},{name:"cluster jobs",summary:"The latest jobs",flags:[...be]},{name:"cluster jobs show",summary:"One job, its plan and its steps",flags:[...be]},{name:"cluster jobs watch",summary:"Follow a job until it finishes: exit 0 done, 1 failed or cancelled, 10 timed out",flags:[...be,"--timeout"]},{name:"cluster jobs cancel",summary:"Stop a job: a move before its cutover rolls back, after it finishes its cleanup",flags:[...be,"--yes"]},{name:"cluster jobs resume",summary:"Run a failed job again from the step that failed, and follow it",flags:[...be,"--no-wait","--timeout"]},{name:"cluster settings",summary:"The cluster's cutover and advisor settings, or set them",flags:[...be,...br,"--set"]},{name:"cluster advise",summary:"What to split, move or add, from the router's advisor: each recommendation with its reason, its plan and the command that runs it. Runs nothing",flags:[...be,"--sample-ms"]},{name:"mcp",summary:"Serve these operations as tools over stdio",flags:["--allow-delete"]}],$s={"--ref":"The project. Defaults to SNOUTDATA_PROJECT, then .snoutdata/project.json here or in a parent","functions logs --errors":"Only what was printed with console.error or console.warn, and the errors that stopped a request","functions logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","functions logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","containers deploy --port":"The port the container listens on, also given to it as PORT (default 8080)","containers deploy --dockerfile":"A folder with a Dockerfile (or the Dockerfile): built for linux/arm64 with your Docker and uploaded to the project","containers deploy --registry-user":"The user name for a private registry. The token comes on stdin, with --stdin","containers deploy --stdin":"Read the registry token from a pipe, so it is not in shell history or ps","containers deploy --clear-registry":"Forget the stored registry login, for an image that is public now","containers deploy --no-wait":"Answer once it is recorded, without waiting for the host to start it","containers logs --errors":"Only what was printed to stderr","containers logs --tail":"How many of the newest lines to show. Defaults to 100, at most 500","containers logs --follow":"Keep printing new lines as they arrive, about a minute after they are printed","--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":"A SnoutData Cloud project ref: its cluster, through the control plane, as you (instead of --admin)","tokens create --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","--admin":"The Lepis router's admin API, like http://127.0.0.1:7432 (LEPIS_ADMIN_ADDR). https for any other machine; NODE_EXTRA_CA_CERTS for a private CA. Defaults to LEPIS_ADMIN_URL","--token":"The router's admin token. Prefer LEPIS_ADMIN_TOKEN, which keeps it out of shell history and ps","--plan":"Show the plan (steps, what moves, rows, size, copy time, expected pause) and change nothing","--yes":"Run it without asking. Without a terminal to ask at, an operation that moves or deletes data needs it","cluster jobs cancel --yes":"Cancel without asking","cluster nodes add --port":"The node's port (default 5432)","--host":"The address the router reaches the node at","--dbname":"The database on the node (default postgres)","--sslmode":"disable, require or verify-full (the default)","--peer-host":"The address the other nodes reach it at, when it differs from the router's","--to":"The node (id or name) that receives it","cluster nodes drain --to":"Comma-separated nodes to move its ranges to. Default: the least loaded","--key-type":"The distribution key's Postgres type: bigint, int, text, uuid, ...","--ranges":"How many equal ranges to start with","--seed":"The hash seed","--nodes":"Comma-separated nodes (ids or names) to place the ranges on","--column":"The distribution key column","--no-join":"SnoutData Cloud: leave the new node for you to attach with cluster nodes attach, instead of joining it on its own","--queue":"The stopped request (a table being spread) rather than a node","--keyspace":"The keyspace","--range":"The range's lower bound, with --keyspace: the spelling for a bound that starts with a dash","cluster range split --at":"Where to split (a hash value). Default: the middle","--node":"A node, by id or name","cluster cleanup --node":"Only this node. Default: every node","cluster replica add --node":"The node pod's ref, as cluster status lists it (or as the first word)","cluster replica remove --node":"The node pod's ref, as cluster status lists it (or as the first word)","--value":"The key value to pin","--add":"Comma-separated nodes to add, each name=host[:port]","--remove":"How many of the emptiest nodes to drain and remove","cluster scale --nodes":"SnoutData Cloud: how many nodes the project should have, the home included","--max-write-pause-ms":"The longest a cutover may pause writes; one that would take longer is postponed","--drain-timeout-ms":"How long a cutover waits for open transactions to finish","--set":"Settings by their API names, name=value,... (the advisor's advice_* thresholds among them)","--sample-ms":"How long to measure write rates over, in ms (default: the advice_sample_ms setting; 0 skips them)","--ack-timeout-ms":"How long a cutover waits for every router to acknowledge it","--copy-mb-per-s":"The copy rate a plan estimates with (chosen, not measured)","--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 _r(e,t){return $s[`${e} ${t}`]??$s[t]??""}function xs(e,t){let n=Sr.filter(s=>s.name===e||s.name.startsWith(`${e} `));if(!t)return n;let r=`${e} ${t}`,o=n.filter(s=>s.name===r||s.name.startsWith(`${r} `));return o.length>0?o:n}Q();pe();Z();import{spawn as zu}from"node:child_process";function Wu(e){return`snout-${e}`}function gt(e,t,n){return new Promise((r,o)=>{let s=zu("docker",["compose","-p",Wu(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))d(w(` ${u.trim()}`))}),s.on("error",i=>{if(i.code==="ENOENT"){o(new v("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 v("failed",String(i)))}),s.on("close",i=>r({code:i??0,out:a,err:l}))})}async function Vu(e){let t=await gt(e,["ps","-a","--format","json"],!1);if(t.code!==0)throw new v("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 pa(e){let t=await Vu(e),n=t.filter(o=>o.state==="running").length,r=t.filter(o=>o.state!=="done").length;return h({ref:e.ref,local:e.folder,api:e.apiUrl,dbPort:e.dbPort,running:n,services:t},()=>{if(d(`${$(e.name??e.ref)} (local, ${e.folder})`),t.length===0){d("Not started. `snoutdata start` brings it up.");return}process.stdout.write(`${B([["SERVICE","STATE","HEALTH"],...t.map(o=>[o.service,o.state,o.health])])}
421
+ `),d(`${n} of ${r} services running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}.`)}),0}async function ma(e){d(`Starting ${e.name??e.ref} (local)\u2026`);let t=await gt(e,["up","-d","--wait","--wait-timeout","600"],!0);if(t.code!==0)throw new v("failed",`docker compose up failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return h({ref:e.ref,api:e.apiUrl,dbPort:e.dbPort,started:!0},()=>{d(`Running. API ${e.apiUrl}, database 127.0.0.1:${e.dbPort}. \`snoutdata db url\` prints the connection string.`)}),0}async function fa(e){let t=await gt(e,["stop"],!0);if(t.code!==0)throw new v("failed",`docker compose stop failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`);return h({ref:e.ref,stopped:!0},()=>d(`Stopped ${e.name??e.ref}. Its data is kept; \`snoutdata start\` brings it back.`)),0}function ga(e){h({ref:e.ref,name:e.name,local:e.folder,api:e.apiUrl,dbPort:e.dbPort},()=>{process.stdout.write(`${B([["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}`]])}
422
+ `)})}Et();import{cpSync as wa,existsSync as It,readdirSync as rp,readFileSync as ba,rmSync as Sa,statSync as op,writeFileSync as _a}from"node:fs";import{join as ke,resolve as Tn}from"node:path";var Yu=/^[A-Za-z0-9][A-Za-z0-9_-]{0,47}$/;function Xu(e){return Yu.test(e)}function rt(e){if(!Xu(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 Zu=/^[A-Za-z0-9_][A-Za-z0-9._-]*$/;function Qu(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!==".."&&Zu.test(n))}var Ur=2*1024*1024,ha=200;function ya(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>ha)throw new Error(`a function may hold ${ha} files and this one has ${e.files.length}`);let n=new Set;for(let a of e.files){if(!Qu(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=ep(o);if(s.length>Ur)throw new Error(`this function is ${Math.round(s.length/1024)} KB and the limit is ${Ur/1024} KB. A function is source code; put large files in Storage.`);return o}function ep(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 tp=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var np=["SNOUTDATA_","SNOUT_"];function jt(e){if(!tp.test(e))return`${JSON.stringify(e)} is not an environment variable name: letters, digits and underscores, not starting with a digit.`;let t=np.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 ab=64*1024;pe();he();Z();function ka(e){let t=ke(e.folder,"functions");return It(t)?rp(t).filter(n=>!n.startsWith(".")&&!n.startsWith("_")&&op(ke(t,n)).isDirectory()).sort():[]}async function On(e){d(w(" docker compose run --rm functions-deploy"));let t=await gt(e,["run","--rm","functions-deploy"],!0);if(t.code!==0)throw new v("failed",`functions-deploy failed: ${t.err.trim().split(/\r?\n/).slice(-3).join(" ")}`)}function va(e,t,n){let r=ke(e.folder,".env"),o=ba(r,"utf8"),s=(cn(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 _a(r,i.test(o)?o.replace(i,l):`${o.replace(/\n?$/,`
381
423
  `)}${l}
382
- `),!0}async function Or(e,t,n){Ze(t);let r=$n(n.dir??ye(process.cwd(),"functions",t));if(!Tt(ye(r,"index.ts")))throw new p(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=ye(e.folder,"functions",t);if($n(o)!==r){aa(o,{recursive:!0,force:!0}),oa(r,o,{recursive:!0});let a=ye(r,"..","_shared");Tt(a)&&$n(a)!==$n(ye(e.folder,"functions","_shared"))&&oa(a,ye(e.folder,"functions","_shared"),{recursive:!0})}let s=la(e,t,!n.verifyJwt);if(await En(e),s){let a=await ut(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new v("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:Nr(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function da(e,t,n){let r=await Or(e,t,n);g(r,()=>{d(`${k(t)} deployed locally: ${r.url}`),n.verifyJwt||d(w(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function Nr(e,t){return`${e.apiUrl}/functions/v1/${t}`}function Pn(e){return{ref:e.ref,local:e.folder,functions:ca(e).map(t=>({name:t,url:Nr(e,t)}))}}function ua(e){let t=ca(e);g(Pn(e),()=>{if(t.length===0){d(`No functions in ${ye(e.folder,"functions")}.`);return}process.stdout.write(`${M([["NAME","URL"],...t.map(n=>[n,Nr(e,n)])])}
383
- `)})}async function jr(e,t){Ze(t);let n=ye(e.folder,"functions",t);if(!Tt(n))throw new v("not-found",`this local project has no function called ${t}`);return aa(n,{recursive:!0,force:!0}),la(e,t,!1),await En(e),{ref:e.ref,name:t,removed:!0}}async function ma(e,t){g(await jr(e,t),()=>d(`${t} is gone.`))}function An(e){return ye(e.folder,"functions",".env")}function Cr(e){let t=An(e);return Tt(t)?nn(sa(t,"utf8")):new Map}function pa(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;ia(An(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
424
+ `),!0}async function Fr(e,t,n){rt(t);let r=Tn(n.dir??ke(process.cwd(),"functions",t));if(!It(ke(r,"index.ts")))throw new g(`${r} has no index.ts. A local function is a folder with an index.ts in it.`);let o=ke(e.folder,"functions",t);if(Tn(o)!==r){Sa(o,{recursive:!0,force:!0}),wa(r,o,{recursive:!0});let a=ke(r,"..","_shared");It(a)&&Tn(a)!==Tn(ke(e.folder,"functions","_shared"))&&wa(a,ke(e.folder,"functions","_shared"),{recursive:!0})}let s=va(e,t,!n.verifyJwt);if(await On(e),s){let a=await gt(e,["up","-d","--wait","--wait-timeout","600"],!1);if(a.code!==0)throw new v("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:Br(e,t),verifyJwt:n.verifyJwt,local:e.folder}}async function $a(e,t,n){let r=await Fr(e,t,n);h(r,()=>{d(`${$(t)} deployed locally: ${r.url}`),n.verifyJwt||d(w(" Callable with no key (FUNCTIONS_NO_VERIFY_JWT), so it must check what calls it itself."))})}function Br(e,t){return`${e.apiUrl}/functions/v1/${t}`}function Nn(e){return{ref:e.ref,local:e.folder,functions:ka(e).map(t=>({name:t,url:Br(e,t)}))}}function xa(e){let t=ka(e);h(Nn(e),()=>{if(t.length===0){d(`No functions in ${ke(e.folder,"functions")}.`);return}process.stdout.write(`${B([["NAME","URL"],...t.map(n=>[n,Br(e,n)])])}
425
+ `)})}async function Hr(e,t){rt(t);let n=ke(e.folder,"functions",t);if(!It(n))throw new v("not-found",`this local project has no function called ${t}`);return Sa(n,{recursive:!0,force:!0}),va(e,t,!1),await On(e),{ref:e.ref,name:t,removed:!0}}async function Ea(e,t){h(await Hr(e,t),()=>d(`${t} is gone.`))}function Cn(e){return ke(e.folder,"functions",".env")}function qr(e){let t=Cn(e);return It(t)?cn(ba(t,"utf8")):new Map}function Pa(e,t){let n=r=>r.includes("'")?JSON.stringify(r):`'${r}'`;_a(Cn(e),[...t].map(([r,o])=>`${r}=${n(o)}`).join(`
384
426
  `)+(t.size?`
385
- `:""))}async function fa(e,t){for(let r of t){let o=Rt(r.name);if(o)throw new p(o)}let n=Cr(e);for(let r of t)n.set(r.name,r.value);pa(e,n),await En(e),g({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${k(r.name)} set in ${An(e)}`)})}function Rn(e){return{ref:e.ref,local:e.folder,secrets:[...Cr(e).keys()].sort().map(t=>({name:t}))}}function ga(e){let t=Rn(e),n=t.secrets.map(r=>r.name);g(t,()=>{d(n.length?n.join(`
386
- `):`No function secrets in ${An(e)}.`)})}async function ha(e,t){let n=Cr(e);if(!n.delete(t))throw new v("not-found",`${t} is not set for this local project`);pa(e,n),await En(e),g({ref:e.ref,unset:t},()=>d(`${t} unset.`))}se();Ye();Me();kr();ln();W();kn();Y();Y();se();Me();W();async function ya(){let{projects:e,allowance:t}=await he();g({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${M(Iu(e))}
387
- `)})}function Iu(e){let t=e.some(n=>typeof n.postgresVersion=="number");return[["REF","NAME","STATE","REGION",...t?["POSTGRES"]:[],"LAST CONNECTION"],...e.map(n=>[n.ref,n.name,n.readOnly?k("read-only"):n.state==="ready"?n.state:k(n.state),n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],Ce(n.lastConnectionAt)])]}async function Ir(e){let t=await b("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;d(`Created ${n}.`);let r=t.project;if(e.wait){d("Waiting for it to start\u2026");let s="";r=await At(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(w(` ${a}`)))}})}e.link&&st(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}
388
- `),d(""),d(`Ready. ${k(n)} in ${r.region}, at ${r.host}.`),d(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 wa(e,t,n={}){let r=`cloud-project-${e}`,o=await b(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){g({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await Pt(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(w(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new v("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});g({...o,...l?{project:l}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function ba(e){let t=await b("cloud-project-reset-password",{ref:e});g(t,()=>{process.stdout.write(`${t.password}
389
- `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function _a(){let{teams:e}=await xn();g({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${M([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
390
- `)})}async function Sa(e){let{teams:t}=await xn(),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}Y();W();async function ka(e){let t=await b("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});g(t,()=>{process.stdout.write(`${t.token}
391
- `),d(""),d(`${k(t.name)} \u2014 ${t.expiresAt?`expires ${t.expiresAt.slice(0,10)}`:"does not expire"}.`),d(t.project?`Reaches project ${t.project} only.`:"Reaches every project on this account."),d("This is the only time it is shown: only its hash is stored."),d(w(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function va(){let{tokens:e}=await b("cloud-token-list");g({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${M([["PREFIX","NAME","PROJECT","STATE","LAST USED","EXPIRES"],...e.map(t=>[t.current?k(t.prefix):t.prefix,t.name,t.project??"all",t.revokedAt?"revoked":t.live?"live":"expired",Ce(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
392
- `)})}async function xa(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await b("cloud-token-revoke",t);g(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}Y();ue();W();async function $a(e){let t=await pe(e);g({ref:t.ref,appKey:t.appKey,serverKey:t.serverKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.appKey||!t.serverKey){d(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${Pa(t.appKey,t.serverKey)}
393
- `),Lu(t.keysIssuedAt)})}async function Ea(e,t){if(!t.force)throw d(`Rotating the keys for ${e} stops every app key and server key already pasted anywhere.`),d("Anything holding one (a deployed app, a CI secret, a teammate) breaks until it is updated."),new p("keys rotate needs --force, because this cannot be undone");let n=vn(await b("cloud-project-rotate-jwt",{ref:e}));g(n,()=>{process.stdout.write(`${Pa(n.appKey,n.serverKey)}
394
- `),d(""),d(n.breaks),d(w(" snoutdata keys --json to read them again later"))})}function Pa(e,t){return M([["KEY","VALUE"],["app key",e],["server key",t]])}function Lu(e){d(""),d(`The ${k("app key")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`The ${k("server key")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(w(` issued ${e.slice(0,10)}`))}Y();ue();W();import{readdir as Fu,readFile as Bu,stat as Hu}from"node:fs/promises";import{join as Mr,relative as Gu,sep as qu}from"node:path";var Aa=["functions"];async function Dr(e){try{return(await Hu(e)).isDirectory()}catch{return!1}}async function Ku(e,t){if(t){if(!await Dr(t))throw new p(`${t} is not a directory`);return t}for(let n of Aa){let r=Mr(n,e);if(await Dr(r))return r}throw new p(`no folder found for ${e} at ${Aa[0]}/${e}; use --dir to say where it is.`)}async function Ra(e){let t=[];async function n(r){for(let o of await Fu(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Mr(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await Bu(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new p(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:Gu(e,s).split(qu).join("/"),text:l})}}return await n(e),t}async function Ju(e,t,n){let r=await Ra(t);if(r.length===0)throw new p(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new p(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Mr(t,"..","_shared");if(!await Dr(s))return{entrypoint:o,files:r};let a=(await Ra(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 Lr(e,t,n){Ze(t);let r=await Ku(t,n.dir),{entrypoint:o,files:s}=await Ju(t,r,n.entrypoint),a=ra({entrypoint:o,files:s});return{answer:await b("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 Oa(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await Lr(e,t,n),l=r.functions.find(i=>i.name===t);g(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${k(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),l&&d(` ${l.url}`),n.verifyJwt||(d(""),d(k(" Anybody who knows this URL can run it. No API key is required.")),d(w(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&d(w(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function Ur(e){return b("cloud-project-functions",{ref:e})}function Fr(e,t){return Ze(t),b("cloud-project-functions",{ref:e,action:"delete",name:t})}function Br(e,t,n){if(Ze(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new p("functions size needs --memory MB, --concurrency N, or --reset");return b("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function zu(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 Na(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 ja(e,t,n){let r=await Br(e,t,n),o=r.functions.find(s=>s.name===t);g(r,()=>{let s=o?zu(o,r.limit):null;d(`${k(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),d(w(" The next request runs at it; workers already running finish what they hold."));let a=Na(r.limit);a&&d(w(` ${a}`))})}async function Ca(e){let t=await Ur(e);g(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(w(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${M([["NAME","KEY","SIZE","MEMORY","WORKERS","URL"],...t.functions.map(r=>[r.name,r.verifyJwt?"required":k("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])])}
395
- `),t.limit.maxFunctions!==null&&d(w(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=Na(t.limit);n&&d(w(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function Ta(e){let t=e.dropped_before>0?w(`(${e.dropped_before} earlier line(s) were printed too fast to keep) `):"",n=e.cut_bytes?w(` (cut from ${e.cut_bytes} bytes)`):"",r=e.level==="error"?k("error "):"";return`${w(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function Ia(e,t,n){let r=a=>b("cloud-project-functions",{ref:e,action:"logs",...t?{name:t}:{},...n.errorsOnly?{errorsOnly:!0}:{},...a?{since:a}:{},limit:n.limit??100}),o=((await r()).lines??[]).slice().reverse();if(g({ref:e,lines:o},()=>{if(o.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${Ta(a)}
396
- `)}),!n.follow)return;let s=o.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(l=>setTimeout(l,5e3));let a=((await r(s)).lines??[]).slice().reverse();for(let l of a)process.stdout.write(`${Ta(l)}
397
- `),s=l.at}}async function Da(e,t){let n=await Fr(e,t);g(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}Y();W();import{spawn as lm}from"node:child_process";import{createReadStream as dm}from"node:fs";import{mkdtemp as um,rm as mm,stat as Tn}from"node:fs/promises";import{request as pm}from"node:http";import{request as fm}from"node:https";import{tmpdir as gm}from"node:os";import{basename as hm,dirname as ym,join as Kr}from"node:path";var Vu=/^[a-z0-9][a-z0-9_-]{0,31}$/;function Yu(e){return Vu.test(e)}function La(e){return Yu(e)?null:`${JSON.stringify(e)} is not a container name: lower-case letters, digits, hyphens and underscores, up to 32 characters, starting with a letter or a digit.`}var Xu=/^sha256:[0-9a-f]{64}$/,Zu=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,Qu=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,em=/^(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*(?::[0-9]{1,5})?$/;function tm(e){return Xu.test(e)}function Ua(e){let t=e.trim();if(t.length===0||t.length>512||/\s/.test(t))return{error:`${JSON.stringify(e)} is not an image reference.`};let n=t,r=null,o=n.indexOf("@");if(o!==-1&&(r=n.slice(o+1),n=n.slice(0,o),!tm(r)))return{error:`${JSON.stringify(r)} is not a sha256 digest.`};let s=null,a=n.lastIndexOf("/"),l=n.lastIndexOf(":");if(l>a&&(s=n.slice(l+1),n=n.slice(0,l),!Zu.test(s)))return{error:`${JSON.stringify(s)} is not an image tag.`};let i=n.split("/"),u="docker.io",c=i[0]??"";return i.length>1&&(c.includes(".")||c.includes(":")||c==="localhost")&&(u=c.toLowerCase(),i.shift()),em.test(u)?((u==="index.docker.io"||u==="registry-1.docker.io")&&(u="docker.io"),u==="docker.io"&&i.length===1&&i.unshift("library"),i.length===0||i.some(m=>!Qu.test(m))?{error:`${JSON.stringify(e)} is not an image name: a repository is lower-case letters, digits and separators.`}:(s===null&&r===null&&(s="latest"),{registry:u,repository:i.join("/"),tag:s,digest:r})):{error:`${JSON.stringify(c)} is not a registry address.`}}var Fw=["application/vnd.oci.image.index.v1+json","application/vnd.docker.distribution.manifest.list.v2+json","application/vnd.oci.image.manifest.v1+json","application/vnd.docker.distribution.manifest.v2+json"].join(", ");function Hr(e){let t=e.split(".");if(t.length!==4)return null;let n=[];for(let r of t){if(!/^\d{1,3}$/.test(r))return null;let o=Number(r);if(o>255)return null;n.push(o)}return n}function nm(e){let t=e.replace(/^\[|\]$/g,""),n=t.indexOf("%");if(n!==-1&&(t=t.slice(0,n)),!/^[0-9a-fA-F:.]+$/.test(t)||!t.includes(":"))return null;let r=t.lastIndexOf(":");if(t.slice(r+1).includes(".")){let c=Hr(t.slice(r+1));if(!c)return null;t=`${t.slice(0,r+1)}${(c[0]<<8|c[1]).toString(16)}:${(c[2]<<8|c[3]).toString(16)}`}let o=t.split("::");if(o.length>2)return null;let s=c=>c===""?[]:c.split(":"),a=s(o[0]??""),l=o.length===2?s(o[1]??""):[];if([...a,...l].some(c=>!/^[0-9a-fA-F]{1,4}$/.test(c)))return null;let i=a.length+l.length;if(o.length===1?i!==8:i>7)return null;let u=new Array(8-i).fill(0);return[...a.map(c=>parseInt(c,16)),...u,...l.map(c=>parseInt(c,16))]}function rm(e,t,n){let r=Hr(t),o=a=>(a[0]<<24|a[1]<<16|a[2]<<8|a[3])>>>0,s=n===0?0:4294967295<<32-n>>>0;return(o(e)&s)===(o(r)&s)}var om=[["0.0.0.0",8],["10.0.0.0",8],["100.64.0.0",10],["127.0.0.0",8],["169.254.0.0",16],["172.16.0.0",12],["192.0.0.0",24],["192.0.2.0",24],["192.88.99.0",24],["192.168.0.0",16],["198.18.0.0",15],["198.51.100.0",24],["203.0.113.0",24],["224.0.0.0",4],["240.0.0.0",4]];var Bw=1024**3;var Ma=16,sm=/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,62}$/,am=/^(\d{1,3}(?:\.\d{1,3}){3})(?:\/(\d{1,2}))?$/;function im(e,t){let n=e??[],r={error:`${t}: ports are up to ${Ma} whole numbers, 1 to 65535.`};if(!Array.isArray(n)||n.length>Ma)return r;let o=n.map(s=>typeof s=="string"&&/^\d{1,5}$/.test(s)?Number(s):s);return o.some(s=>typeof s!="number"||!Number.isInteger(s)||s<1||s>65535)?r:[...new Set(o)].sort((s,a)=>s-a)}function cm(e){let t=e.trim().toLowerCase(),n=am.exec(t);if(n){let r=Hr(n[1]??""),o=n[2]===void 0?32:Number(n[2]);if(!r||o>32)return{error:`${e} is not an IPv4 address or range.`};let s=(r[0]<<24|r[1]<<16|r[2]<<8|r[3])>>>0,a=o===0?0:4294967295<<32-o>>>0,l=(s&a)>>>0,i=[24,16,8,0].map(u=>l>>>u&255);return om.some(([u,c])=>o>=c&&rm(i,u,c))?{error:`${e} is not on the public internet, and a container reaches nothing else.`}:o===32?i.join("."):`${i.join(".")}/${o}`}return nm(t)?{error:`${e} is an IPv6 address. Containers send over IPv4 only; list its IPv4 address or its host name.`}:t.startsWith("*.")?{error:`${e}: a wildcard cannot be looked up. List each host name, or the address range.`}:sm.test(t)?t:{error:`${e} is not a host name, an IPv4 address or an IPv4 range.`}}function Fa(e){let t=e.trim(),n=t.lastIndexOf(":"),r=n!==-1&&t.indexOf(":")===n,o=cm(r?t.slice(0,n):t);if(typeof o!="string")return o;let s=im(r?t.slice(n+1).split(",").map(a=>a.trim()):[],o);return"error"in s?s:{destination:o,ports:s}}function Gr(e){return e.ports.length===0?e.destination:`${e.destination}:${e.ports.join(",")}`}var wm=new Set(["pending","pulling","starting","restarting"]),bm=5*6e4;function Nn(e){let t=La(e);if(t)throw new Error(t)}function Jr(e,t,n){Nn(t);let r=Ua(n.image);if("error"in r)throw new Error(r.error);return b("cloud-project-containers",{ref:e,action:"deploy",name:t,image:n.image,...n.port===void 0?{}:{port:n.port},...n.registryUser?{registryUser:n.registryUser,registryPassword:n.registryPassword??""}:{},...n.clearCredential?{clearCredential:!0}:{}})}function Ot(e){return b("cloud-project-containers",{ref:e})}function zr(e,t){return Nn(t),b("cloud-project-containers",{ref:e,action:"delete",name:t})}function Wr(e,t){return Nn(t),b("cloud-project-containers",{ref:e,action:"restart",name:t})}function On(e,t){return b("cloud-project-containers",{ref:e,action:"logs",...t.name?{name:t.name}:{},...t.errorsOnly?{errorsOnly:!0}:{},...t.since?{since:t.since}:{},limit:t.limit??100})}async function Ka(e,t){let n=Date.now()+bm,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await Ot(e)).containers.find(o=>o.name===t)??null,!r||!wm.has(r.status))return r;return r}function Vr(e){if(!e.allowed)return"stopped: not covered by the plan";let t=e.restarts>0?`, ${e.restarts} restart${e.restarts===1?"":"s"}`:"";return`${e.status}${t}`}async function Ja(e,t,n){await za(e,t,await Jr(e,t,n),n.wait!==!1)}async function za(e,t,n,r){let o=n.containers.find(s=>s.name===t)??null;r&&o&&(d(w(`${t}: ${o.image} pinned to ${o.digest.slice(0,19)}\u2026, waiting for the host`)),o=await Ka(e,t)??o),g({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${k(t)} deployed.`);return}d(`${k(t)} ${Vr(o)}`),d(` ${o.url}`),o.statusDetail&&d(` ${o.statusDetail}`),d(""),d(w(" Listens on PORT ("+o.port+"). DATABASE_URL and your secrets are in its environment.")),d(k(" Anything written inside the container is lost when it restarts or is redeployed.")),d(w(" Keep what matters in your database.")),n.allowance&&d(w(` ${n.allowance.deployed} of ${n.allowance.maxContainers} containers on this plan, ${n.allowance.memoryMb} MB and ${n.allowance.cpuPercent}% of a core each`))})}var _m=2*1024**3,Sm=3*6e4;async function km(e){let t;try{t=await Tn(e)}catch{throw new Error(`${e} does not exist: --dockerfile takes a folder with a Dockerfile in it, or the Dockerfile.`)}if(t.isDirectory()){try{await Tn(Kr(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:ym(e),file:e}}function vm(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function Yr(e){return e.replace(/\b\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com(?:\/[^\s"',;)]*)?/g,"SnoutData's registry").replace(/\barn:aws[a-z-]*:[^\s"',;)]*/g,"a SnoutData credential").replace(/\b[a-z0-9.-]*\.amazonaws\.com(?:\/[^\s"',;)]*)?/g,"SnoutData Cloud").replace(/\b\d{12}\b/g,"(an account)")}function xm(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?Yr(n):e===413?"the image is too large to upload: a container's image may be at most 1 GB compressed; a slimmer base (alpine, distroless, -slim) usually fixes it.":`the upload failed (${e}); build again in a moment.`}function $m(e){return new Promise((t,n)=>{let r=lm("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function Em(e,t,n){return new Promise((r,o)=>{let s=new URL(e),a=(s.protocol==="http:"?pm:fm)(s,{method:"PUT",headers:{"content-type":"application/octet-stream","content-length":String(n)}});a.on("response",i=>{let u=[];i.on("data",c=>u.push(c)),i.on("end",()=>r({status:i.statusCode??0,body:Buffer.concat(u).toString("utf8")})),i.on("error",o)}),a.on("error",o);let l=dm(t);l.on("error",i=>{a.destroy(),o(i)}),l.pipe(a)})}var Wa={docker:$m,scratch:()=>um(Kr(gm(),"snoutdata-build-")),remove:e=>mm(e,{recursive:!0,force:!0}),size:async e=>(await Tn(e)).size,upload:async(e,t)=>Em(e,t,(await Tn(t)).size),ask:e=>b("cloud-project-containers",e),say:e=>d(w(e)),now:()=>Date.now()};function Ba(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function Ha(e,t,n){let r=await e.ask({ref:t,action:"upload-ticket",name:n});if(typeof r.url!="string"||!r.url.startsWith("https://"))throw new Error("SnoutData Cloud did not hand over an upload link; try again in a moment.");return{url:r.url,expiresAt:String(r.expiresAt??"")}}async function Pm(e,t,n,r=Wa){try{if(await r.docker(["version","--format","{{.Server.Version}}"])!==0)throw new Error("docker answered with an error")}catch{throw new Error("deploy --dockerfile builds with your own Docker, and none is running here. Start Docker, or deploy an image from a registry.")}try{let o=await Ha(r,e,t),s=await r.scratch();try{let a=Kr(s,"image.tar");if(r.say(`${t}: building ${n.file?hm(n.file):n.context} for linux/arm64`),await r.docker(vm({...n,archive:a}))!==0)throw new Error("the build failed; its output is above. If it says the docker driver cannot export OCI, turn on the containerd image store, or run: docker buildx create --use");let l=await r.size(a).catch(()=>0);if(l<=0)throw new Error("docker finished but wrote no image archive, so there is nothing to upload.");if(l>_m)throw new Error(`the image is ${Ba(l)}, and a container's image may be at most 1 GB compressed. A slimmer base (alpine, distroless, -slim) usually fixes it.`);Date.parse(o.expiresAt)-r.now()>Sm||(o=await Ha(r,e,t)),r.say(`${t}: uploading the image (${Ba(l)})`);let i=await r.upload(o.url,a);if(i.status!==200)throw new Error(xm(i.status,i.body));let u="";try{u=String(JSON.parse(i.body).digest??"")}catch{}if(!/^sha256:[0-9a-f]{64}$/.test(u))throw new Error("the upload finished without saying what it stored, so there is nothing to deploy; build again.");return u}finally{await r.remove(s).catch(()=>{})}}catch(o){throw new Error(Yr(o instanceof Error?o.message:String(o)))}}async function Va(e,t,n,r=Wa){Nn(t);let o=await km(n.path),s=await Pm(e,t,o,r),a;try{a=await r.ask({ref:e,action:"deploy",name:t,pushed:s,...n.port===void 0?{}:{port:n.port}})}catch(l){throw new Error(Yr(l instanceof Error?l.message:String(l)))}await za(e,t,a,n.wait!==!1)}async function Ya(e){let t=await Ot(e);g(t,()=>{if(t.containers.length===0){d(`No containers on ${e}.`),t.allowance&&t.allowance.maxContainers===0?d(w(" Containers run on the Plus and Pro plans.")):d(w(" snoutdata containers deploy <name> <image>"));return}d(M([["name","status","image","url"],...t.containers.map(r=>[r.name,Vr(r),r.image,r.url])]));for(let r of t.containers)r.statusDetail&&r.status!=="running"&&d(w(` ${r.name}: ${r.statusDetail}`));let n=t.allowance?.egress;n&&n.allowanceBytes>0&&d(w(` Sent this month: ${qr(n.usedBytes)} of ${qr(n.allowanceBytes)}. At ${qr(n.allowanceBytes)} the project's containers stop until the 1st.`))})}async function Ga(e,t){if(t===null)return b("cloud-project-containers",{ref:e,action:"egress",clear:!0});let n=[];for(let r of t){let o=Fa(r);if("error"in o)throw new Error(o.error);n.push(o)}return b("cloud-project-containers",{ref:e,action:"egress",allow:n})}function Am(e){if(e.allowlist===void 0)return["This control plane does not know about allowlists yet."];if(e.allowlist===null)return[`Containers on ${e.ref} may reach the public internet (within the limits on how much and how fast).`,w(" snoutdata containers egress set <host[:port]> ... to allow only some destinations")];let t=[e.allowlist.allow.length===0?`Containers on ${e.ref} may reach nothing outside the project.`:`Containers on ${e.ref} may reach only:`];for(let n of e.allowlist.allow)t.push(` ${Gr(n)}${n.ports.length===0?w(" (any port)"):""}`);return t.push(w(` and always: ${e.allowlist.alwaysAllowed.map(Gr).join(", ")}, and each other`)),t.push(w(" snoutdata containers egress clear to allow the public internet again")),t}async function jn(e,t){let n=t.clear?await Ga(e,null):t.set?await Ga(e,t.set):await Ot(e);g({ref:n.ref,allowlist:n.allowlist??null},()=>{for(let r of Am(n))d(r);(t.set||t.clear)&&d(w(" Running containers follow it within a minute; nothing restarts."))})}function qr(e){return`${Math.round(e/1024**3*10)/10} GB`}async function Xa(e,t){await Wr(e,t);let n=await Ka(e,t);g({ref:e,containers:n?[n]:[]},()=>{d(n?`${k(t)} ${Vr(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function Za(e,t){let n=await zr(e,t);g(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function qa(e){return`${w(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?k(e.message):e.message}`}async function Qa(e,t,n){let r=((await On(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit})).lines??[]).slice().reverse();if(g({ref:e,lines:r},()=>{if(r.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a container prints them."));return}for(let s of r)process.stdout.write(`${qa(s)}
398
- `)}),!n.follow)return;let o=r.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(a=>setTimeout(a,5e3));let s=((await On(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${qa(a)}
399
- `),o=a.at}}Y();ue();W();function ti(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function Cn(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function ni(e,t){if(e.length===0)throw new p("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new p("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await Cn()}]}return e.map(n=>{let r=ti(n);if(!r)throw new p(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function ri(e,t,n){if(t.length===0)throw new p("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new p("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await Cn()})}else for(let s of t){let a=ti(s);if(!a)throw new p(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=Rt(s.name);if(a)throw new p(a)}let o=null;for(let s of r)o=await b("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});g(o,()=>{for(let s of r)d(`${k(s.name)} set`);d(w(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||d(w(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function oi(e,t){let n=Rt(t);if(n)throw new p(n);let r=await b("cloud-project-secrets",{ref:e,action:"unset",name:t});g(r,()=>{d(`${t} is gone.`)})}async function si(e){let t=await b("cloud-project-secrets",{ref:e});g(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(w(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${M([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,Ce(n.updatedAt)])])}
400
- `),d(w("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Lt}from"node:child_process";import{appendFileSync as Ai,existsSync as Bn,readFileSync as Ri,statSync as fp,unlinkSync as gp,writeFileSync as pt}from"node:fs";import{dirname as hp,join as Hn}from"node:path";import{tmpdir as yp}from"node:os";import{open as Ti}from"node:fs/promises";function ai(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 ii(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 ci(e){let t=e.split(`
401
- `).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 Xr={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},Tm=/^(snoutpod_admin|snout_[a-z]+_admin)$/,In=new Set(["snout_identity","snout_files","push","snout_live","snout_realtime","_realtime","graphql","snout_graph","snout_ext","net","cron","pgbouncer","vault","pgsodium","auth","storage","realtime","graphql_public","extensions"]),Om=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,Nm={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},jm=new Set(["push.settings"]),Cm=["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 Im(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=Cm.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 Dm(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 li(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
402
- `)){let u=Im(i);if(!u)continue;let{type:c,schema:m,name:h,owner:x}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let V=/^(schema_migrations|migrations)$/.test(h)&&In.has(m);if(/realtime/.test(m)||m==="snout_live"||V||m==="public"&&h==="spatial_ref_sys"){s++;continue}if(m==="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(m==="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 F=Nm[m];if(F){F==="storage"&&(h==="objects"||h==="files")&&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(F,[...n.get(F)??[],u]);continue}if(In.has(m)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&m==="-"&&/^EXTENSION( |$)/.test(h)||(c==="SCHEMA"?In.has(h):In.has(m))||Om.test(h)||Tm.test(x)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=Dm(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let u=n.get(i),c=u.filter(h=>h.type==="TABLE DATA"&&jm.has(`${h.schema}.${h.name}`)).map(h=>`${h.schema}.${h.name}`),m=u.filter(h=>h.type==="TABLE DATA").map(h=>`${h.schema}.${h.name}`);return{product:i,clear:c,tables:m,lines:Mm(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function Mm(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(c=>!r.has(c)||o.has(c)||c===n(u)))??a[0];o.add(n(i)),s.push(i.line),a=a.filter(u=>u!==i)}return s}Y();ue();se();W();Y();se();var Qr=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],ui=["advice_sample_ms","advice_min_bytes","advice_split_bytes","advice_skew_pct","advice_hot_pct","advice_min_writes_per_s","advice_pin_pct","advice_node_disk_bytes","advice_disk_pct","advice_conn_pct"];function Nt(e){return e==="done"||e==="failed"||e==="cancelled"}function jt(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new v("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new Re(Hm(e.project))}let n=e.admin??di(t.LEPIS_ADMIN_URL);if(!n)throw new v("usage","cluster needs a project: --project <ref> for a SnoutData Cloud project, or --admin <url> (or LEPIS_ADMIN_URL) for the admin API of a standalone Lepis router (LEPIS_ADMIN_ADDR)");let r=e.token??di(t.LEPIS_ADMIN_TOKEN);if(!r)throw new v("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new Zr(Lm(n),r)}function di(e){return e===void 0||e===""?void 0:e}function Lm(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new v("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new v("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new v("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!Um(n.hostname))throw new v("usage",`--admin: ${n.host} is not this machine, and plain http would send the token in the clear. Use https (the router serves it when it has LEPIS_TLS_CERT), or a tunnel to 127.0.0.1`);return n.pathname=n.pathname.replace(/\/+$/,""),n.search="",n.hash="",n}function Um(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var Fm=6e4,Zr=class{target;#t;#n;constructor(t,n){this.#t=t,this.#n=n,this.target=t.toString().replace(/\/$/,"")}status(){return this.#e("GET","/v1/status")}ops(){return this.#e("GET","/v1/ops")}plan(t){return this.#e("POST","/v1/plan",t)}submit(t){return this.#e("POST","/v1/jobs",t)}async jobs(){return(await this.#e("GET","/v1/jobs")).jobs}job(t){return this.#e("GET",`/v1/jobs/${t}`)}cancel(t){return this.#e("POST",`/v1/jobs/${t}/cancel`)}resume(t){return this.#e("POST",`/v1/jobs/${t}/resume`)}settings(t){return this.#e("POST","/v1/settings",t)}advice(t){return this.#e("GET",t===void 0?"/v1/advice":`/v1/advice?sample_ms=${Math.max(0,Math.floor(t))}`)}async#e(t,n,r){let o=`${this.target}${n}`,s;try{s=await fetch(o,{method:t,headers:{authorization:`Bearer ${this.#n}`,accept:"application/json",...t==="POST"?{"content-type":"application/json"}:{}},...t==="POST"?{body:JSON.stringify(r??{})}:{},signal:AbortSignal.timeout(Fm)})}catch(i){let u=rn(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new v(u==="failed"?"network":u,`could not reach the Lepis admin API at ${this.target}: ${c}`)}let a=await s.text(),l;try{l=a===""?{}:JSON.parse(a)}catch{throw new v("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw mi(s.status,l);return l}},Bm={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function mi(e,t){let n=t?.error??{},r=n.message??`the admin API answered ${e}`,o={status:e};n.code&&(o.apiCode=n.code),n.kind&&(o.kind=n.kind),n.sqlstate&&(o.sqlstate=n.sqlstate);let s=(n.code?Bm[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":Ve(e));return s==="not-signed-in"?new v(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new v(s,r,o)}function Hm(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new v("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var Gm={"node.add":"add-node","node.attach":"attach","node.drain":"drain","node.remove":"remove","keyspace.create":"keyspace","table.distribute":"distribute","table.reference":"reference","table.global":"global","range.split":"split","range.merge":"merge","range.move":"move","tenant.pin":"pin",rebalance:"rebalance",scale:"scale",verify:"verify",cleanup:"cleanup","keyspace.drop":"drop-keyspace",dissolve:"disable"},qm="cloud-project-shards",Re=class{target;ref;#t;constructor(t,n=b){this.ref=t,this.target=`project ${t}`,this.#t=n}async status(){let t=await this.#e({action:"status"}),{cluster:n,...r}=t;return{...n??{catalog:!1},cloud:r}}ops(){return this.#e({action:"ops"})}async plan(t){return this.#n(t),this.#e({action:"plan",request:t})}async submit(t){let n=this.#n(t),r={...t};delete r.op;let o=await this.#e({action:n,request:r});return typeof o.job=="number"?{job:o.job,plan:o.plan}:{done:Ge(o),answer:o}}async jobs(){return(await this.#e({action:"jobs"})).jobs}job(t){return this.#e({action:"jobs",id:t})}cancel(t){return this.#e({action:"cancel",id:t})}resume(t){return this.#e({action:"resume",id:t})}settings(t){return this.#e({action:"settings",settings:t})}advice(t){return this.#e({action:"advice",...t===void 0?{}:{sampleMs:Math.max(0,Math.floor(t))}})}enable(t){return this.#e({action:"enable",...t?{plan:!0}:{}})}spread(t,n){return this.#e({action:"spread",request:t,...n?{plan:!0}:{}})}retry(t){return this.#e({action:"retry",...t})}disable(t){return this.#e({action:"disable",...t?{plan:!0}:{}})}replica(t,n,r){return this.#e({action:n?"remove-replica":"add-replica",node:t,...r?{plan:!0}:{}})}async alerts(){return(await this.status()).cloud?.alerts??[]}acknowledge(t){return this.#e({action:"acknowledge",alert:t})}#n(t){let n=Gm[t.op];if(!n)throw new v("usage",`${t.op} is not an operation a SnoutData Cloud project takes`);let r=t;if(t.op==="node.add"&&(r.host!==void 0||r.name!==void 0))throw new v("usage","in SnoutData Cloud a node is a pod the project makes: nodes add takes no name or --host. It joins the cluster on its own once it is ready.");if(t.op==="scale"&&(r.add!==void 0||r.remove!==void 0))throw new v("usage","in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");return n}async#e(t){try{return await this.#t(qm,{ref:this.ref,...t})}catch(n){throw Km(n)}}};function Ge(e){if(Array.isArray(e.created)){let t=e.created.map(String),n=typeof e.stopped=="string"?` Stopped there: ${e.stopped}`:"",r=t.length===1;return e.joining===!1?`New node${r?"":"s"} ${t.join(", ")}, starting as a copy of the home node. Attach ${r?"it":"each"} once it is ready: snoutdata cluster nodes attach <ref>.${n}`:`New node${r?"":"s"} ${t.join(", ")}, starting as a copy of the home node. ${r?"It joins":"They join"} the cluster and take${r?"s":""} ${r?"its":"their"} share of the data on ${r?"its":"their"} own; follow it with snoutdata cluster status.${n}`}return e.deleted===!0?`The pod of ${String(e.pod)} is being deleted. It owned nothing in the cluster.`:Array.isArray(e.queued)?`${String(e.next??"The table is being spread over the cluster.")} Steps: ${e.queued.map(String).join(", ")}. Follow it with snoutdata cluster status.`:typeof e.joining=="boolean"&&typeof e.node=="string"?e.joining?`${e.node} is joining the cluster again.`:`${e.node} is left as it is.`:e.queue==="retrying"?"The cluster is trying that step again.":typeof e.replica=="string"&&typeof e.node=="string"?`A replica of node ${e.node} is being made (${e.replica}). ${String(e.note??"It protects the node once it is following it.")}`:typeof e.changed=="boolean"&&typeof e.node=="string"&&e.sharded===void 0?e.changed?`The replica of node ${e.node} is being removed, and its project slot freed.`:`node ${e.node} has no replica. Nothing changed.`:e.acknowledged===!0?"Marked as seen. The alert stays open until what it is about is over.":e.unchanged===!0?`The project already has ${String(e.nodes)} nodes. Nothing changed.`:e.sharded===!0?e.changed===!1?"This project is already a cluster.":String(e.note??"This project is a cluster now."):e.sharded===!1?e.changed===!1?"This project is not a cluster.":String(e.note??"This project is not a cluster any more."):typeof e.next=="string"?e.next:"Done."}function Km(e){if(!(e instanceof ge))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return mi(e.status,{error:{code:typeof t.apiCode=="string"?t.apiCode:void 0,kind:typeof t.kind=="string"?t.kind:void 0,message:e.message,sqlstate:typeof t.sqlstate=="string"?t.sqlstate:void 0}});let n=t.code==="not-sharded"?"conflict":Ve(e.status);return new v(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function It(e){if(Array.isArray(e))return`[${e.map(It).join(",")}]`;if(e&&typeof e=="object"){let t=e;return`{${Object.keys(t).filter(n=>t[n]!==void 0).sort().map(n=>`${JSON.stringify(n)}:${It(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var no=class{#t=new Map;#n;#e;constructor(t=15*6e4,n=Date.now){this.#n=t,this.#e=n}remember(t,n=""){this.#t.set(`${n}
403
- ${It(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
404
- ${It(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
405
- ${It(t)}`)}},Jm=new no,ro=["node.add","node.drain","node.remove","keyspace.create","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","verify","cleanup","node.attach","keyspace.drop","dissolve"],zm="2024-11-05";function D(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var A={type:"string"},pi={type:"object",description:'The operation as the router takes it, e.g. {"op": "range.split", "keyspace": "k", "range": "-3074457345618258603", "at": "18454348402311335", "to": 3}. Range bounds and seeds are 64-bit, so pass them as strings. cluster_advice returns these ready-made, as `operation`. For a SnoutData Cloud project a node is a pod the project makes: {"op": "node.add"} makes one (no host), {"op": "node.attach", "name": "<its ref>"} hands a ready one to the cluster, and {"op": "scale", "nodes": N} is a node count.',properties:{op:{type:"string",enum:[...ro]}},required:["op"],additionalProperties:!0},Dn={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},qe={type:"string",description:"A SnoutData Cloud project's ref (from list_projects): its cluster, as the signed-in user. Omit for the standalone router this server was started with (LEPIS_ADMIN_URL)."},Mt=[{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:D({})},{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:D({})},{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:D({name:A,region:A,teamId:A},["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:D({ref:A},["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:D({ref:A},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:D({ref:A},["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:D({ref:A,dir:A,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:D({ref:A,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:D({ref:A},["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:D({ref:A},["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:D({ref:A},["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:D({})},{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:D({})},{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:D({name:A,expires:A,project:A},["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:D({id:A},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/v1/fn/<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:D({ref:A,name:A,dir:A,entrypoint:A,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:D({ref:A},["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:D({ref:A,name:A,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:D({ref:A,name:A},["ref","name"])},{name:"list_containers",description:"The project's Snout Containers: each one's status (pending, pulling, starting, running, restarting, failed, stopped), why when it is not running, its image and pinned digest, restarts, and its public URL. allowed=false means the plan no longer covers it and it is stopped. Also the plan's allowance. Costs nothing.",inputSchema:D({ref:A},["ref"])},{name:"deploy_container",description:"Run a Docker image all the time beside the project, at https://<ref>.api.snoutdata.com/containers/v1/<name>/ (no API key needed there: the container authenticates its own callers). The image tag is pinned to the digest it points at now and must include linux/arm64. It listens on PORT (port, default 8080) and gets DATABASE_URL and the project's secrets in its environment. STATELESS: anything written inside it is lost on every restart or redeploy, so state belongs in the database. Plus and Pro plans only. A private image needs a person's first deploy with the CLI (no tool takes a registry token); a redeploy reuses the stored login. Returns at once: poll list_containers until the status is running or failed.",inputSchema:D({ref:A,name:A,image:A,port:{type:"integer"}},["ref","name","image"])},{name:"restart_container",description:"Start one container again from its image. Everything written inside it is lost, which is also how to clear a container stuck in a bad state. Poll list_containers to see it come back.",inputSchema:D({ref:A,name:A},["ref","name"])},{name:"delete_container",description:"Stop one container and remove it from the project. It does not touch the database.",inputSchema:D({ref:A,name:A},["ref","name"])},{name:"container_logs",description:"What a project's containers printed to stdout and stderr in the last seven days, newest first: each line's time, container, stream and text. Lines arrive about a minute after they are printed. Use it when list_containers says failed or restarting. name narrows to one container, errorsOnly to stderr, limit is at most 500 (default 100). Costs nothing.",inputSchema:D({ref:A,name:A,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["ref"])},{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:D({ref:A},["ref"])},{name:"function_logs",description:"What a project's functions printed with console.log and console.error in the last seven days, newest first: each line's time, function, level ('error' for console.error and console.warn) and text. Lines arrive about a minute after they are printed. Use it after deploying or calling a function to see why it failed. name narrows to one function, errorsOnly to errors, limit is at most 500 (default 100). Costs nothing.",inputSchema:D({ref:A,name:A,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["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:D({ref:A},["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:D({ref:A},["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:D({ref:A,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:D({ref:A},["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:D({ref:A,hostname:A},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:D({ref:A,hostname:A},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:D({ref:A,hostname:A},["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:D({ref:A},["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:D({ref:A,at:A,name:A},["ref","at"])},{name:"cluster_status",description:"A Lepis cluster (one Postgres database spread over several nodes): a Cloud project's with project, else the router this server was started against with LEPIS_ADMIN_URL. Its nodes, keyspaces and their ranges (a range is keyspace:lo; bounds are strings), tables, routers, unfinished jobs and settings; for a Cloud project also whether it is a cluster, its plan's node limit, and each node pod with the step that follows (attach, delete-pod, wait). Costs nothing.",inputSchema:D({project:qe})},{name:"cluster_advice",description:"Ask the cluster's advisor what to split, move or add, from each node's size, connections and write rate and each range's measured share. Each recommendation has a reason, its plan, and an `operation` that cluster_run takes as it is. RUNS NOTHING. sampleMs: how long to measure write rates over (the cluster's setting by default; 0 skips them; at most 30000).",inputSchema:D({project:qe,sampleMs:{type:"integer"}})},{name:"cluster_plan",description:"The dry run of one cluster operation: its steps, what moves where, rows, bytes, the estimated copy time and the expected write pause. Changes nothing. Planning an operation is what lets cluster_run run it.",inputSchema:D({project:qe,operation:pi},["operation"])},{name:"cluster_run",description:"Run one cluster operation as a durable job. IT MOVES DATA, and each cutover pauses writes to what moves for up to the cluster's max_write_pause_ms. Without confirm: true it only returns the plan and runs nothing: show that plan to the user, and when they agree call again with the same operation and confirm: true. confirm runs only an operation planned here (cluster_plan, cluster_advice, or cluster_run without confirm) in the last 15 minutes. Returns the job id; follow it with cluster_watch_job.",inputSchema:D({project:qe,operation:pi,confirm:{type:"boolean"}},["operation"])},{name:"cluster_jobs",description:"The cluster's latest jobs, or with id one job with each step's state and phase. Costs nothing.",inputSchema:D({project:qe,id:Dn})},{name:"cluster_watch_job",description:"Wait for a job to finish, up to timeoutSeconds (default 30, at most 120), and return its state and steps: done, failed (with the error; cluster_resume_job runs it again from the failed step) or cancelled. Running out of time stops the waiting, never the job; call again to keep waiting.",inputSchema:D({project:qe,id:Dn,timeoutSeconds:{type:"integer"}},["id"])},{name:"cluster_cancel_job",description:"Stop a running job. A move cancelled before its cutover rolls back; one cancelled after it finishes its cleanup.",inputSchema:D({project:qe,id:Dn},["id"])},{name:"cluster_resume_job",description:"Run a failed job again from the step that failed. It carries on the operation that was already approved and planned; follow it with cluster_watch_job.",inputSchema:D({project:qe,id:Dn},["id"])},{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:D({ref:A},["ref"])}],Wm=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","function_logs","get_project","list_products","list_domains","restore_window","cluster_status","cluster_advice","cluster_plan","cluster_jobs","cluster_watch_job"]),Vm=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),fi=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function oo(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(oo)};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 so(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 Ym(e){return{...e,annotations:Wm.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:Vm.has(e.name)}}}function Xm(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function Dt(e,t){return{jsonrpc:"2.0",id:e,result:t}}function Mn(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function S(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return Dt(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function Zm(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Ct(e){return{op:e.op,cutovers:e.cutovers,moves:(e.moves??[]).map(t=>({from:t.source,to:t.targets,tables:t.tables,estimated_rows:t.estimated_rows,estimated_bytes:t.estimated_bytes,estimated_copy_seconds:t.estimated_copy_seconds,expected_pause_ms:t.expected_pause_ms})),steps:(e.steps??[]).map(t=>t.kind),estimated_rows:e.estimated_rows,estimated_bytes:e.estimated_bytes,estimated_copy_seconds:e.estimated_copy_seconds,expected_pause_ms:e.expected_pause_ms,max_write_pause_ms:e.max_write_pause_ms,warnings:e.warnings??[],...e.assumptions?{assumptions:e.assumptions}:{}}}function eo(e){return{id:e.id,op:e.op,state:e.state,finished:Nt(e.state),error:e.error,steps:e.steps.map(t=>({n:t.n+1,kind:t.kind,state:t.state,...typeof t.detail?.phase=="string"?{phase:t.detail.phase}:{}})),...e.state==="failed"?{next:`cluster_resume_job {"id": ${e.id}} runs it again from the failed step.`}:{}}}function gi(e,t){let n=e.operation;if(!n||typeof n!="object"||Array.isArray(n))return`${t} needs operation: an object like {"op": "range.split", "keyspace": "k", "range": "<lo>"}. cluster_advice returns them ready-made.`;let r=n;if(typeof r.op!="string"||!ro.includes(r.op))return`${t}: operation.op must be one of ${ro.join(", ")}.`;for(let o of["range","at","a","b","seed"]){let s=r[o];if(typeof s=="number"&&!Number.isSafeInteger(s))return`${t}: operation.${o} is a 64-bit number and lost its last digits as a JSON number. Pass it as a string, exactly as cluster_status or cluster_advice wrote it.`}return r}function to(e){let t=typeof e.id=="string"&&/^\d+$/.test(e.id)?Number(e.id):e.id;return typeof t=="number"&&Number.isSafeInteger(t)&&t>0?t:null}function Qm(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var ep="The cluster tools need a cluster: pass project (a SnoutData Cloud project's ref), or start this server with LEPIS_ADMIN_URL (a standalone router's admin API, e.g. http://127.0.0.1:7432) and LEPIS_ADMIN_TOKEN set. Tell the user that is what is missing.";async function tp(e,t,n,r,o){let s,a=typeof n.project=="string"&&n.project.trim()!==""?n.project.trim():void 0;try{if(!r.shards)return S(e,ep,!0);s=r.shards(a)}catch(u){return S(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??Jm,i=a??"";try{switch(t){case"cluster_status":return S(e,await s.status());case"cluster_advice":{let u=typeof n.sampleMs=="number"&&n.sampleMs>=0?Math.min(Math.floor(n.sampleMs),3e4):void 0,c=await s.advice(u);for(let m of c.advice)m.plan&&l.remember(m.request,i);return S(e,{summary:c.summary,advice:c.advice.map(m=>({op:m.op,reason:m.reason,metric:m.metric,operation:m.request,...m.plan?{plan:Ct(m.plan)}:{},...m.needs.length>0?{needs:m.needs,note:`Ask the user for ${m.needs.join(", ")}, then plan it with cluster_plan.`}:{},...m.plan_error?{plan_error:m.plan_error}:{}})),facts:c.facts,assumptions:c.assumptions,...c.advice.length>0?{next:"Nothing has run. Show the user a recommendation and its plan; if they agree, call cluster_run with its operation and confirm: true. Ask for advice again after each one runs."}:{}})}case"cluster_plan":{let u=gi(n,t);if(typeof u=="string")return S(e,u,!0);let c=await s.plan(u);return l.remember(u,i),S(e,{plan:Ct(c),next:"Nothing has run. To run it, show the user this plan, then call cluster_run with the same operation and confirm: true."})}case"cluster_run":{let u=gi(n,t);if(typeof u=="string")return S(e,u,!0);if(n.confirm!==!0||!l.shown(u,i)){let m=await s.plan(u);l.remember(u,i);let h=n.confirm===!0;return S(e,{ran:!1,plan:Ct(m),next:h?"NOT RUN: confirm applies to an operation planned here in the last 15 minutes, and this one was not, so it has been planned now instead. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true.":"Nothing has run. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true."},h)}let c=await s.submit(u);return l.forget(u,i),c.job===void 0?S(e,{ran:!0,done:c.done,answer:c.answer}):S(e,{ran:!0,job:c.job,plan:Ct(c.plan),next:`Follow it with cluster_watch_job {"id": ${c.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return S(e,{jobs:await s.jobs()});let u=to(n);if(u===null)return S(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let c=await s.job(u);return S(e,{...eo(c),plan:c.plan&&Array.isArray(c.plan.steps)?Ct(c.plan):null})}case"cluster_watch_job":{let u=to(n);if(u===null)return S(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let c=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,m=Date.now()+c*1e3;for(;;){let h=await s.job(u);if(Nt(h.state))return S(e,eo(h),h.state!=="done");if(Date.now()>=m)return S(e,{...eo(h),note:`Still ${h.state} after ${c}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(x=>setTimeout(x,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=to(n);if(u===null)return S(e,`${t} needs id, a job number.`,!0);let c=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return S(e,{...c,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return Mn(e,-32602,`unknown tool: ${t}`)}}catch(u){return S(e,Qm(u),!0)}}function K(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function hi(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 Dt(r,{protocolVersion:zm,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return Dt(r,{});case"tools/list":return Dt(r,{tools:Xm([...Mt.map(Ym),...n.borrowed??[]])});case"tools/call":return np(r,s,t,n);default:return Mn(r,-32601,`unknown method: ${o}`)}}async function np(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Mt.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return Mn(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"];if(o.startsWith("cluster_")&&Mt.some(c=>c.name===o))return tp(e,o,s,n,r);let i=K(s,"ref");if(l.includes(o)&&!i)return S(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!fi.includes(o))return S(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${fi.join(", ")}.`,!0);try{switch(o){case"whoami":return S(e,await n.whoami());case"list_projects":return S(e,oo(await n.listProjects()));case"create_project":{let c=K(s,"name");return c?S(e,await n.createProject({name:c,region:K(s,"region")??void 0,teamId:K(s,"teamId")??void 0})):S(e,"create_project needs a name.",!0)}case"get_connection_url":{let c=await n.connection(i);return S(e,{url:c.uri,state:c.state,wakesInstantly:c.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 S(e,await n.setProjectState("pause",i));case"resume_project":return S(e,await n.setProjectState("resume",i));case"usage":{let c=typeof s.days=="number"?s.days:void 0;return S(e,await n.usage(i,c))}case"export_status":return S(e,await n.exportStatus(i));case"start_export":return S(e,await n.requestExport(i));case"reset_password":{let c=await n.resetPassword(i),m=await n.connection(i);return S(e,{url:m.uri,appliesIn:c.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 S(e,await n.listTeams());case"get_project":return S(e,oo(await n.getProject(i)));case"list_products":return S(e,await n.getProducts(i));case"set_product":{let c=K(s,"product");return c!=="auth"&&c!=="storage"&&c!=="data-api"?S(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?S(e,"set_product needs enabled: true or false.",!0):S(e,await n.setProduct(i,c,s.enabled))}case"list_domains":return S(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let c=K(s,"hostname");return c?S(e,await n.domainAction(i,o.split("_")[0],c)):S(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let c=await n.restoreWindow(i),m=c&&c.restore?so(i,c.restore):null;return S(e,m?{...c,reason:m}:c)}case"restore_to_point":{let c=K(s,"at");return c?S(e,await n.restoreTo(i,c,K(s,"name")??void 0)):S(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=K(s,"ref"),m=K(s,"name");return!c||!m?S(e,"deploy_function needs a project ref and a function name.",!0):S(e,await n.deployFunction(c,m,{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 c=K(s,"ref");return c?S(e,await n.listFunctions(c)):S(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=K(s,"ref"),m=K(s,"name");if(!c||!m)return S(e,"size_function needs a project ref and a function name.",!0);let h=typeof s.memoryMb=="number"?s.memoryMb:void 0,x=typeof s.concurrency=="number"?s.concurrency:void 0,L=s.reset===!0;return!L&&h===void 0&&x===void 0?S(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):S(e,await n.sizeFunction(c,m,{memoryMb:h,concurrency:x,reset:L}))}case"delete_function":{let c=K(s,"ref"),m=K(s,"name");return!c||!m?S(e,"delete_function needs a project ref and a function name.",!0):S(e,await n.deleteFunction(c,m))}case"list_containers":{let c=K(s,"ref");return c?S(e,await n.listContainers(c)):S(e,"list_containers needs a project ref.",!0)}case"deploy_container":{let c=K(s,"ref"),m=K(s,"name"),h=K(s,"image");if(!c||!m||!h)return S(e,"deploy_container needs a project ref, a container name and an image.",!0);let x=typeof s.port=="number"?s.port:void 0;return S(e,await n.deployContainer(c,m,{image:h,...x===void 0?{}:{port:x}}))}case"restart_container":case"delete_container":{let c=K(s,"ref"),m=K(s,"name");return!c||!m?S(e,`${o} needs a project ref and a container name.`,!0):S(e,o==="restart_container"?await n.restartContainer(c,m):await n.deleteContainer(c,m))}case"container_logs":{let c=K(s,"ref");if(!c)return S(e,"container_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return S(e,await n.containerLogs(c,{name:K(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_function_secrets":{let c=K(s,"ref");return c?S(e,await n.listFunctionSecrets(c)):S(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let c=K(s,"ref");if(!c)return S(e,"function_logs needs a project ref.",!0);let m=typeof s.limit=="number"?s.limit:void 0;return S(e,await n.functionLogs(c,{name:K(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:m}))}case"list_tokens":return S(e,await n.listTokens());case"create_token":{let c=K(s,"name");if(!c)return S(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let m=K(s,"expires"),h=m?Zm(m):void 0;return h===null?S(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${m}".`,!0):S(e,await n.createToken(c,h,K(s,"project")??void 0))}case"revoke_token":{let c=K(s,"id");return c?S(e,await n.revokeToken(c)):S(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return S(e,await n.pushMigrations(i,{dir:K(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?S(e,await n.setProjectState("delete",i)):S(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(c=>c.name===o)?n.callBorrowed?Dt(e,await n.callBorrowed(o,s)):S(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):Mn(e,-32602,`unknown tool: ${o}`)}}catch(c){return S(e,c instanceof Error?c.message:String(c),!0)}}function yi(e){let t=e.split(`
406
- `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Ln=["auth","storage","data-api","push"];function Qe(e){return{error:e instanceof Error?e.message:String(e)}}async function mt(e){let[t,n,r,o]=await Promise.all([b("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),Qe),b("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}),Qe),b("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),Qe),b("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),Qe)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:rp}}var rp={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function bi(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Ln.includes(t))throw new p(`say which product: ${Ln.join(", ")}`);return t}async function ao(e,t,n){if(t==="auth"){let r=await b("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 b("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 b(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 _i(e){let t=await mt(e);g(t,()=>{let n=(l,i,u="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?k("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${M([["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: signals, roster; table changes on Plus and Pro")])}
407
- `);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&&d(w(` snoutdata products enable ${a} --ref ${e}`))})}async function Si(e,t,n){let r=await ao(e,t,n);g(r,()=>d(r.note))}async function Un(e){let t=await b("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function io(e,t,n){if(!n)throw new p(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await b("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function wi(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(`${M(n)}
408
- `)}async function ki(e,t,n){if(t===void 0||t==="list"){let o=await Un(e);g(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${M([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":k("no"),s.certificate?.state??""])])}
409
- `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new p(`unknown command: domains ${t}`);let r=await io(e,t,n??"");t==="verify"&&r.domain&&!r.domain.verified&&(bn()||wi(r.domain,e),I("not-ready",r.domain.verificationError??`${n} is not verified yet. Publish the records above, then run this again.`,{ref:e,domain:r.domain})),g(r,()=>{if(t==="remove"){d(`${n} removed.`);return}r.domain&&(d(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||wi(r.domain,e)),r.next&&d(r.next)})}async function co(e){return b("cloud-project-restore",{ref:e},"GET")}async function lo(e,t,n){if(Number.isNaN(Date.parse(t)))throw new p(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return b("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function vi(e){let t=await co(e);g(t,()=>{let n=t.restore,r=so(e,n);if(r){d(r);return}d(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),d(w(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function xi(e,t,n){let r=await lo(e,t,n);g(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${k(r.project.name)} (${r.project.ref}).`),d(w(" snoutdata projects list to see when it is ready"))})}async function uo(e){let{projects:t}=await he(),n=t.find(l=>l.ref===e);if(!n)throw new p(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([mt(e),b("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),Qe),b("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),Qe),Un(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),Qe)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function $i(e){let t=await uo(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(`${M([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],...typeof n.postgresVersion=="number"?[["postgres",String(n.postgresVersion)]]:[],["auth / storage / data-api / push",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)} / ${r(n.products.push)}`],["realtime","on (signals and the roster; table changes on Plus and Pro)"],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
410
- `)})}Y();se();Ye();Y();W();import{existsSync as op,mkdirSync as sp,readFileSync as ap,renameSync as ip,writeFileSync as cp}from"node:fs";import{homedir as mo}from"node:os";import{dirname as lp,join as dp}from"node:path";import{rootCertificates as up}from"node:tls";var mp="SNOUTDATA_DB_SSLMODE";function pp(e=mo()){let t=dp(e,".snoutdata","ca-roots.pem"),n=`${up.join(`
427
+ `:""))}async function Aa(e,t){for(let r of t){let o=jt(r.name);if(o)throw new g(o)}let n=qr(e);for(let r of t)n.set(r.name,r.value);Pa(e,n),await On(e),h({ref:e.ref,set:t.map(r=>r.name)},()=>{for(let r of t)d(`${$(r.name)} set in ${Cn(e)}`)})}function jn(e){return{ref:e.ref,local:e.folder,secrets:[...qr(e).keys()].sort().map(t=>({name:t}))}}function Ra(e){let t=jn(e),n=t.secrets.map(r=>r.name);h(t,()=>{d(n.length?n.join(`
428
+ `):`No function secrets in ${Cn(e)}.`)})}async function Ta(e,t){let n=qr(e);if(!n.delete(t))throw new v("not-found",`${t} is not set for this local project`);Pa(e,n),await On(e),h({ref:e.ref,unset:t},()=>d(`${t} unset.`))}pe();tt();Fe();Or();fn();Z();Pn();Q();Q();pe();Fe();Z();async function Oa(){let{projects:e,allowance:t}=await _e();h({projects:e,allowance:t},()=>{if(e.length===0){d('No projects yet. `snoutdata projects create --name "my project"`.');return}process.stdout.write(`${B(sp(e))}
429
+ `)})}function sp(e){let t=e.some(n=>typeof n.postgresVersion=="number");return[["REF","NAME","STATE","REGION",...t?["POSTGRES"]:[],"LAST CONNECTION"],...e.map(n=>[n.ref,n.name,n.readOnly?$("read-only"):n.state==="ready"?n.state:$(n.state),n.region,...t?[typeof n.postgresVersion=="number"?String(n.postgresVersion):"-"]:[],Me(n.lastConnectionAt)])]}async function Gr(e){let t=await S("cloud-project-create",{name:e.name,region:e.region,teamId:e.teamId}),n=t.project.ref;d(`Created ${n}.`);let r=t.project;if(e.wait){d("Waiting for it to start\u2026");let s="";r=await Ct(n,{timeoutMs:e.timeoutMs,onTick:a=>{a!==s&&(s=a,d(w(` ${a}`)))}})}e.link&&lt(e.link,{ref:n,name:r.name});let o=`postgres://${r.user}:${encodeURIComponent(t.password)}@${r.host}:5432/${r.database}?sslmode=require`;h({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}
430
+ `),d(""),d(`Ready. ${$(n)} in ${r.region}, at ${r.host}.`),d(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 Na(e,t,n={}){let r=`cloud-project-${e}`,o=await S(r,{ref:t}),s=e==="delete"?"deleted":`${e}d`;if(n.wait===!1){h({...o,settled:!1},()=>d(`${t}: asked to be ${s}. \`snoutdata projects list\` shows when it is.`));return}let a=o.project?.state??"",l=await Nt(t,e,{timeoutMs:n.timeoutMs,onTick:u=>{u!==a&&(a=u,d(w(` ${u}`)))}}),i=l?.state??"deleted";if(i==="error")throw new v("failed",`${t} went to error instead of ${s}${l?.stateDetail?`: ${l.stateDetail}`:""}.`,{ref:t,state:i});h({...o,...l?{project:l}:{},settled:!0},()=>{d(o.changed?`${t}: ${s}.`:`${t} was already ${s}.`)})}async function Ca(e){let t=await S("cloud-project-reset-password",{ref:e});h(t,()=>{process.stdout.write(`${t.password}
431
+ `),d(`New password for ${t.user}. It applies ${t.appliesIn}.`)})}async function ja(){let{teams:e}=await Rn();h({teams:e},()=>{if(e.length===0){d("You are not on any teams.");return}process.stdout.write(`${B([["NAME","ID","SHARE"],...e.map(t=>[t.name,t.id,t.mayShare?"yes":t.why??"no"])])}
432
+ `)})}async function Ia(e){let{teams:t}=await Rn(),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}Q();Z();async function Da(e){let t=await S("cloud-token-create",{name:e.name,expiresInDays:e.expiresInDays,...e.project?{project:e.project}:{}});h(t,()=>{process.stdout.write(`${t.token}
433
+ `),d(""),d(`${$(t.name)} \u2014 ${t.expiresAt?`expires ${t.expiresAt.slice(0,10)}`:"does not expire"}.`),d(t.project?`Reaches project ${t.project} only.`:"Reaches every project on this account."),d("This is the only time it is shown: only its hash is stored."),d(w(" export SNOUTDATA_ACCESS_TOKEN=<that token>"))})}async function Ma(){let{tokens:e}=await S("cloud-token-list");h({tokens:e},()=>{if(e.length===0){d("No access tokens. `snoutdata tokens create --name ci`.");return}process.stdout.write(`${B([["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",Me(t.lastUsedAt),t.expiresAt?t.expiresAt.slice(0,10):"never"])])}
434
+ `)})}async function La(e){let t=e.startsWith("sdt_")?{prefix:e}:{id:e},n=await S("cloud-token-revoke",t);h(n,()=>{d(n.changed?`Revoked ${n.token.prefix} (${n.token.name}).`:`${n.token.prefix} was already revoked.`)})}Q();he();Z();async function Ua(e){let t=await we(e);h({ref:t.ref,appKey:t.appKey,serverKey:t.serverKey,issuedAt:t.keysIssuedAt,expiresAt:t.keysExpireAt},()=>{if(!t.appKey||!t.serverKey){d(`${e} has no API keys yet. Ask again in a moment.`);return}process.stdout.write(`${Ba(t.appKey,t.serverKey)}
435
+ `),cp(t.keysIssuedAt)})}async function Fa(e,t){if(!t.force)throw d(`Rotating the keys for ${e} stops every app key and server key already pasted anywhere.`),d("Anything holding one (a deployed app, a CI secret, a teammate) breaks until it is updated."),new g("keys rotate needs --force, because this cannot be undone");let n=An(await S("cloud-project-rotate-jwt",{ref:e}));h(n,()=>{process.stdout.write(`${Ba(n.appKey,n.serverKey)}
436
+ `),d(""),d(n.breaks),d(w(" snoutdata keys --json to read them again later"))})}function Ba(e,t){return B([["KEY","VALUE"],["app key",e],["server key",t]])}function cp(e){d(""),d(`The ${$("app key")} is the public one: it goes in a browser, and row-level security decides what it reads.`),d(`The ${$("server key")} bypasses row-level security. Server side only, never in a client bundle.`),e&&d(w(` issued ${e.slice(0,10)}`))}Q();he();Z();import{readdir as dp,readFile as up,stat as pp}from"node:fs/promises";import{join as Jr,relative as mp,sep as fp}from"node:path";var Ha=["functions"];async function Kr(e){try{return(await pp(e)).isDirectory()}catch{return!1}}async function gp(e,t){if(t){if(!await Kr(t))throw new g(`${t} is not a directory`);return t}for(let n of Ha){let r=Jr(n,e);if(await Kr(r))return r}throw new g(`no folder found for ${e} at ${Ha[0]}/${e}; use --dir to say where it is.`)}async function qa(e){let t=[];async function n(r){for(let o of await dp(r,{withFileTypes:!0})){if(o.name.startsWith(".")||o.name==="node_modules")continue;let s=Jr(r,o.name);if(o.isDirectory()){await n(s);continue}if(!o.isFile())continue;let a=await up(s),l=a.toString("utf8");if(Buffer.compare(Buffer.from(l,"utf8"),a)!==0)throw new g(`${s} is not text, and a function bundle is source code. Put binary assets in Storage.`);t.push({path:mp(e,s).split(fp).join("/"),text:l})}}return await n(e),t}async function hp(e,t,n){let r=await qa(t);if(r.length===0)throw new g(`${t} has no files in it`);let o=n??(r.some(l=>l.path==="index.ts")?"index.ts":"");if(!o)throw new g(`${t} has no index.ts, so there is nothing to start. Name the file with --entrypoint.`);let s=Jr(t,"..","_shared");if(!await Kr(s))return{entrypoint:o,files:r};let a=(await qa(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 zr(e,t,n){rt(t);let r=await gp(t,n.dir),{entrypoint:o,files:s}=await hp(t,r,n.entrypoint),a=ya({entrypoint:o,files:s});return{answer:await S("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 Ka(e,t,n){let{answer:r,directory:o,files:s,bytes:a}=await zr(e,t,n),l=r.functions.find(i=>i.name===t);h(r,()=>{let i=Math.max(1,Math.round(a/1024));d(`${$(t)} deployed from ${o} (${s} file${s===1?"":"s"}, ${i} KB)`),l&&d(` ${l.url}`),n.verifyJwt||(d(""),d($(" Anybody who knows this URL can run it. No API key is required.")),d(w(" That is what a webhook receiver needs. Check the sender signature inside the function."))),r.limit.maxFunctions!==null&&d(w(` ${r.limit.deployed} of ${r.limit.maxFunctions} functions on this plan`))})}function Wr(e){return S("cloud-project-functions",{ref:e})}function Vr(e,t){return rt(t),S("cloud-project-functions",{ref:e,action:"delete",name:t})}function Yr(e,t,n){if(rt(t),!n.reset&&n.memoryMb===void 0&&n.concurrency===void 0)throw new g("functions size needs --memory MB, --concurrency N, or --reset");return S("cloud-project-functions",{ref:e,action:"size",name:t,...n.reset?{reset:!0}:{memoryMb:n.memoryMb??null,concurrency:n.concurrency??null}})}function yp(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 Ja(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 za(e,t,n){let r=await Yr(e,t,n),o=r.functions.find(s=>s.name===t);h(r,()=>{let s=o?yp(o,r.limit):null;d(`${$(t)} ${n.reset?"is back to the plan default":"is resized"}${s?`: ${s}`:""}`),d(w(" The next request runs at it; workers already running finish what they hold."));let a=Ja(r.limit);a&&d(w(` ${a}`))})}async function Wa(e){let t=await Wr(e);h(t,()=>{if(t.functions.length===0){d(`${e} has no functions.`),d(w(" snoutdata functions deploy <name> from functions/<name>"));return}process.stdout.write(`${B([["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])])}
437
+ `),t.limit.maxFunctions!==null&&d(w(`${t.limit.deployed} of ${t.limit.maxFunctions} on this plan`));let n=Ja(t.limit);n&&d(w(`${n}. Change one with: snoutdata functions size <name> --memory MB --concurrency N`))})}function Ga(e){let t=e.dropped_before>0?w(`(${e.dropped_before} earlier line(s) were printed too fast to keep) `):"",n=e.cut_bytes?w(` (cut from ${e.cut_bytes} bytes)`):"",r=e.level==="error"?$("error "):"";return`${w(e.at)} ${e.name} ${r}${t}${e.message}${n}`}async function Va(e,t,n){let r=a=>S("cloud-project-functions",{ref:e,action:"logs",...t?{name:t}:{},...n.errorsOnly?{errorsOnly:!0}:{},...a?{since:a}:{},limit:n.limit??100}),o=((await r()).lines??[]).slice().reverse();if(h({ref:e,lines:o},()=>{if(o.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a function prints them."));return}for(let a of o)process.stdout.write(`${Ga(a)}
438
+ `)}),!n.follow)return;let s=o.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(l=>setTimeout(l,5e3));let a=((await r(s)).lines??[]).slice().reverse();for(let l of a)process.stdout.write(`${Ga(l)}
439
+ `),s=l.at}}async function Ya(e,t){let n=await Vr(e,t);h(n,()=>{d(n.live?`${t} is gone.`:`${t} is gone. It stops answering within a few seconds.`)})}Q();Z();import{spawn as Cp}from"node:child_process";import{createReadStream as jp}from"node:fs";import{mkdtemp as Ip,rm as Dp,stat as In}from"node:fs/promises";import{request as Mp}from"node:http";import{request as Lp}from"node:https";import{tmpdir as Up}from"node:os";import{basename as Fp,dirname as Bp,join as eo}from"node:path";var bp=/^[a-z0-9][a-z0-9_-]{0,31}$/;function Sp(e){return bp.test(e)}function Za(e){return Sp(e)?null:`${JSON.stringify(e)} is not a container name: lower-case letters, digits, hyphens and underscores, up to 32 characters, starting with a letter or a digit.`}var _p=/^sha256:[0-9a-f]{64}$/,kp=/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/,vp=/^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*$/,$p=/^(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*(?::[0-9]{1,5})?$/;function xp(e){return _p.test(e)}function Qa(e){let t=e.trim();if(t.length===0||t.length>512||/\s/.test(t))return{error:`${JSON.stringify(e)} is not an image reference.`};let n=t,r=null,o=n.indexOf("@");if(o!==-1&&(r=n.slice(o+1),n=n.slice(0,o),!xp(r)))return{error:`${JSON.stringify(r)} is not a sha256 digest.`};let s=null,a=n.lastIndexOf("/"),l=n.lastIndexOf(":");if(l>a&&(s=n.slice(l+1),n=n.slice(0,l),!kp.test(s)))return{error:`${JSON.stringify(s)} is not an image tag.`};let i=n.split("/"),u="docker.io",c=i[0]??"";return i.length>1&&(c.includes(".")||c.includes(":")||c==="localhost")&&(u=c.toLowerCase(),i.shift()),$p.test(u)?((u==="index.docker.io"||u==="registry-1.docker.io")&&(u="docker.io"),u==="docker.io"&&i.length===1&&i.unshift("library"),i.length===0||i.some(p=>!vp.test(p))?{error:`${JSON.stringify(e)} is not an image name: a repository is lower-case letters, digits and separators.`}:(s===null&&r===null&&(s="latest"),{registry:u,repository:i.join("/"),tag:s,digest:r})):{error:`${JSON.stringify(c)} is not a registry address.`}}var Nb=["application/vnd.oci.image.index.v1+json","application/vnd.docker.distribution.manifest.list.v2+json","application/vnd.oci.image.manifest.v1+json","application/vnd.docker.distribution.manifest.v2+json"].join(", ");function Xr(e){let t=e.split(".");if(t.length!==4)return null;let n=[];for(let r of t){if(!/^\d{1,3}$/.test(r))return null;let o=Number(r);if(o>255)return null;n.push(o)}return n}function Ep(e){let t=e.replace(/^\[|\]$/g,""),n=t.indexOf("%");if(n!==-1&&(t=t.slice(0,n)),!/^[0-9a-fA-F:.]+$/.test(t)||!t.includes(":"))return null;let r=t.lastIndexOf(":");if(t.slice(r+1).includes(".")){let c=Xr(t.slice(r+1));if(!c)return null;t=`${t.slice(0,r+1)}${(c[0]<<8|c[1]).toString(16)}:${(c[2]<<8|c[3]).toString(16)}`}let o=t.split("::");if(o.length>2)return null;let s=c=>c===""?[]:c.split(":"),a=s(o[0]??""),l=o.length===2?s(o[1]??""):[];if([...a,...l].some(c=>!/^[0-9a-fA-F]{1,4}$/.test(c)))return null;let i=a.length+l.length;if(o.length===1?i!==8:i>7)return null;let u=new Array(8-i).fill(0);return[...a.map(c=>parseInt(c,16)),...u,...l.map(c=>parseInt(c,16))]}function Pp(e,t,n){let r=Xr(t),o=a=>(a[0]<<24|a[1]<<16|a[2]<<8|a[3])>>>0,s=n===0?0:4294967295<<32-n>>>0;return(o(e)&s)===(o(r)&s)}var Ap=[["0.0.0.0",8],["10.0.0.0",8],["100.64.0.0",10],["127.0.0.0",8],["169.254.0.0",16],["172.16.0.0",12],["192.0.0.0",24],["192.0.2.0",24],["192.88.99.0",24],["192.168.0.0",16],["198.18.0.0",15],["198.51.100.0",24],["203.0.113.0",24],["224.0.0.0",4],["240.0.0.0",4]];var Cb=1024**3;var Xa=16,Rp=/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,62}$/,Tp=/^(\d{1,3}(?:\.\d{1,3}){3})(?:\/(\d{1,2}))?$/;function Op(e,t){let n=e??[],r={error:`${t}: ports are up to ${Xa} whole numbers, 1 to 65535.`};if(!Array.isArray(n)||n.length>Xa)return r;let o=n.map(s=>typeof s=="string"&&/^\d{1,5}$/.test(s)?Number(s):s);return o.some(s=>typeof s!="number"||!Number.isInteger(s)||s<1||s>65535)?r:[...new Set(o)].sort((s,a)=>s-a)}function Np(e){let t=e.trim().toLowerCase(),n=Tp.exec(t);if(n){let r=Xr(n[1]??""),o=n[2]===void 0?32:Number(n[2]);if(!r||o>32)return{error:`${e} is not an IPv4 address or range.`};let s=(r[0]<<24|r[1]<<16|r[2]<<8|r[3])>>>0,a=o===0?0:4294967295<<32-o>>>0,l=(s&a)>>>0,i=[24,16,8,0].map(u=>l>>>u&255);return Ap.some(([u,c])=>o>=c&&Pp(i,u,c))?{error:`${e} is not on the public internet, and a container reaches nothing else.`}:o===32?i.join("."):`${i.join(".")}/${o}`}return Ep(t)?{error:`${e} is an IPv6 address. Containers send over IPv4 only; list its IPv4 address or its host name.`}:t.startsWith("*.")?{error:`${e}: a wildcard cannot be looked up. List each host name, or the address range.`}:Rp.test(t)?t:{error:`${e} is not a host name, an IPv4 address or an IPv4 range.`}}function ei(e){let t=e.trim(),n=t.lastIndexOf(":"),r=n!==-1&&t.indexOf(":")===n,o=Np(r?t.slice(0,n):t);if(typeof o!="string")return o;let s=Op(r?t.slice(n+1).split(",").map(a=>a.trim()):[],o);return"error"in s?s:{destination:o,ports:s}}function Zr(e){return e.ports.length===0?e.destination:`${e.destination}:${e.ports.join(",")}`}var Hp=new Set(["pending","pulling","starting","restarting"]),qp=5*6e4;function Ln(e){let t=Za(e);if(t)throw new Error(t)}function to(e,t,n){Ln(t);let r=Qa(n.image);if("error"in r)throw new Error(r.error);return S("cloud-project-containers",{ref:e,action:"deploy",name:t,image:n.image,...n.port===void 0?{}:{port:n.port},...n.registryUser?{registryUser:n.registryUser,registryPassword:n.registryPassword??""}:{},...n.clearCredential?{clearCredential:!0}:{}})}function ht(e){return S("cloud-project-containers",{ref:e})}function no(e,t){return Ln(t),S("cloud-project-containers",{ref:e,action:"delete",name:t})}function ro(e,t){return Ln(t),S("cloud-project-containers",{ref:e,action:"restart",name:t})}function Dn(e,t){return S("cloud-project-containers",{ref:e,action:"logs",...t.name?{name:t.name}:{},...t.errorsOnly?{errorsOnly:!0}:{},...t.since?{since:t.since}:{},limit:t.limit??100})}async function oi(e,t){let n=Date.now()+qp,r=null;for(;Date.now()<n;)if(await new Promise(o=>setTimeout(o,3e3)),r=(await ht(e)).containers.find(o=>o.name===t)??null,!r||!Hp.has(r.status))return r;return r}function oo(e){if(!e.allowed)return"stopped: not covered by the plan";let t=e.restarts>0?`, ${e.restarts} restart${e.restarts===1?"":"s"}`:"";return`${e.status}${t}`}async function si(e,t,n){await ai(e,t,await to(e,t,n),n.wait!==!1)}async function ai(e,t,n,r){let o=n.containers.find(s=>s.name===t)??null;r&&o&&(d(w(`${t}: ${o.image} pinned to ${o.digest.slice(0,19)}\u2026, waiting for the host`)),o=await oi(e,t)??o),h({...n,containers:o?[o]:n.containers},()=>{if(!o){d(`${$(t)} deployed.`);return}d(`${$(t)} ${oo(o)}`),d(` ${o.url}`),o.statusDetail&&d(` ${o.statusDetail}`),d(""),d(w(" Listens on PORT ("+o.port+"). DATABASE_URL and your secrets are in its environment.")),d($(" Anything written inside the container is lost when it restarts or is redeployed.")),d(w(" Keep what matters in your database.")),n.allowance&&d(w(` ${n.allowance.deployed} of ${n.allowance.maxContainers} containers on this plan, ${n.allowance.memoryMb} MB and ${n.allowance.cpuPercent}% of a core each`))})}var Gp=2*1024**3,Kp=3*6e4;async function Jp(e){let t;try{t=await In(e)}catch{throw new Error(`${e} does not exist: --dockerfile takes a folder with a Dockerfile in it, or the Dockerfile.`)}if(t.isDirectory()){try{await In(eo(e,"Dockerfile"))}catch{throw new Error(`${e} has no Dockerfile in it.`)}return{context:e,file:null}}return{context:Bp(e),file:e}}function zp(e){return["buildx","build","--platform","linux/arm64","--provenance=false","--output",`type=oci,dest=${e.archive}`,...e.file?["--file",e.file]:[],e.context]}function so(e){return e.replace(/\b\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com(?:\/[^\s"',;)]*)?/g,"SnoutData's registry").replace(/\barn:aws[a-z-]*:[^\s"',;)]*/g,"a SnoutData credential").replace(/\b[a-z0-9.-]*\.amazonaws\.com(?:\/[^\s"',;)]*)?/g,"SnoutData Cloud").replace(/\b\d{12}\b/g,"(an account)")}function Wp(e,t){let n="";try{let r=JSON.parse(t);n=String(r.error??r.message??"")}catch{}return n?so(n):e===413?"the image is too large to upload: a container's image may be at most 1 GB compressed; a slimmer base (alpine, distroless, -slim) usually fixes it.":`the upload failed (${e}); build again in a moment.`}function Vp(e){return new Promise((t,n)=>{let r=Cp("docker",e,{stdio:["ignore",2,2]});r.on("error",o=>n(o)),r.on("close",o=>t(o??1))})}function Yp(e,t,n){return new Promise((r,o)=>{let s=new URL(e),a=(s.protocol==="http:"?Mp:Lp)(s,{method:"PUT",headers:{"content-type":"application/octet-stream","content-length":String(n)}});a.on("response",i=>{let u=[];i.on("data",c=>u.push(c)),i.on("end",()=>r({status:i.statusCode??0,body:Buffer.concat(u).toString("utf8")})),i.on("error",o)}),a.on("error",o);let l=jp(t);l.on("error",i=>{a.destroy(),o(i)}),l.pipe(a)})}var ii={docker:Vp,scratch:()=>Ip(eo(Up(),"snoutdata-build-")),remove:e=>Dp(e,{recursive:!0,force:!0}),size:async e=>(await In(e)).size,upload:async(e,t)=>Yp(e,t,(await In(t)).size),ask:e=>S("cloud-project-containers",e),say:e=>d(w(e)),now:()=>Date.now()};function ti(e){return`${Math.max(1,Math.round(e/1024**2))} MB`}async function ni(e,t,n){let r=await e.ask({ref:t,action:"upload-ticket",name:n});if(typeof r.url!="string"||!r.url.startsWith("https://"))throw new Error("SnoutData Cloud did not hand over an upload link; try again in a moment.");return{url:r.url,expiresAt:String(r.expiresAt??"")}}async function Xp(e,t,n,r=ii){try{if(await r.docker(["version","--format","{{.Server.Version}}"])!==0)throw new Error("docker answered with an error")}catch{throw new Error("deploy --dockerfile builds with your own Docker, and none is running here. Start Docker, or deploy an image from a registry.")}try{let o=await ni(r,e,t),s=await r.scratch();try{let a=eo(s,"image.tar");if(r.say(`${t}: building ${n.file?Fp(n.file):n.context} for linux/arm64`),await r.docker(zp({...n,archive:a}))!==0)throw new Error("the build failed; its output is above. If it says the docker driver cannot export OCI, turn on the containerd image store, or run: docker buildx create --use");let l=await r.size(a).catch(()=>0);if(l<=0)throw new Error("docker finished but wrote no image archive, so there is nothing to upload.");if(l>Gp)throw new Error(`the image is ${ti(l)}, and a container's image may be at most 1 GB compressed. A slimmer base (alpine, distroless, -slim) usually fixes it.`);Date.parse(o.expiresAt)-r.now()>Kp||(o=await ni(r,e,t)),r.say(`${t}: uploading the image (${ti(l)})`);let i=await r.upload(o.url,a);if(i.status!==200)throw new Error(Wp(i.status,i.body));let u="";try{u=String(JSON.parse(i.body).digest??"")}catch{}if(!/^sha256:[0-9a-f]{64}$/.test(u))throw new Error("the upload finished without saying what it stored, so there is nothing to deploy; build again.");return u}finally{await r.remove(s).catch(()=>{})}}catch(o){throw new Error(so(o instanceof Error?o.message:String(o)))}}async function ci(e,t,n,r=ii){Ln(t);let o=await Jp(n.path),s=await Xp(e,t,o,r),a;try{a=await r.ask({ref:e,action:"deploy",name:t,pushed:s,...n.port===void 0?{}:{port:n.port}})}catch(l){throw new Error(so(l instanceof Error?l.message:String(l)))}await ai(e,t,a,n.wait!==!1)}async function li(e){let t=await ht(e);h(t,()=>{if(t.containers.length===0){d(`No containers on ${e}.`),t.allowance&&t.allowance.maxContainers===0?d(w(" Containers run on the Plus and Pro plans.")):d(w(" snoutdata containers deploy <name> <image>"));return}d(B([["name","status","image","url"],...t.containers.map(r=>[r.name,oo(r),r.image,r.url])]));for(let r of t.containers)r.statusDetail&&r.status!=="running"&&d(w(` ${r.name}: ${r.statusDetail}`));let n=t.allowance?.egress;n&&n.allowanceBytes>0&&d(w(` Sent this month: ${Qr(n.usedBytes)} of ${Qr(n.allowanceBytes)}. At ${Qr(n.allowanceBytes)} the project's containers stop until the 1st.`))})}async function Mn(e,t){if(t===null)return S("cloud-project-containers",{ref:e,action:"egress",clear:!0});let n=[];for(let r of t){let o=ei(r);if("error"in o)throw new Error(o.error);n.push(o)}return S("cloud-project-containers",{ref:e,action:"egress",allow:n})}function Zp(e){if(e.allowlist===void 0)return["This control plane does not know about allowlists yet."];if(e.allowlist===null)return[`Containers on ${e.ref} may reach the public internet (within the limits on how much and how fast).`,w(" snoutdata containers egress set <host[:port]> ... to allow only some destinations")];let t=[e.allowlist.allow.length===0?`Containers on ${e.ref} may reach nothing outside the project.`:`Containers on ${e.ref} may reach only:`];for(let n of e.allowlist.allow)t.push(` ${Zr(n)}${n.ports.length===0?w(" (any port)"):""}`);return t.push(w(` and always: ${e.allowlist.alwaysAllowed.map(Zr).join(", ")}, and each other`)),t.push(w(" snoutdata containers egress clear to allow the public internet again")),t}async function Un(e,t){let n=t.clear?await Mn(e,null):t.set?await Mn(e,t.set):await ht(e);h({ref:n.ref,allowlist:n.allowlist??null},()=>{for(let r of Zp(n))d(r);(t.set||t.clear)&&d(w(" Running containers follow it within a minute; nothing restarts."))})}function Qr(e){return`${Math.round(e/1024**3*10)/10} GB`}async function di(e,t){await ro(e,t);let n=await oi(e,t);h({ref:e,containers:n?[n]:[]},()=>{d(n?`${$(t)} ${oo(n)}`:`${t} is restarting.`),n?.statusDetail&&d(` ${n.statusDetail}`)})}async function ui(e,t){let n=await no(e,t);h(n,()=>d(`${t} is gone. It stops within a few seconds.`))}function ri(e){return`${w(e.at.replace("T"," ").slice(0,19))} ${e.name} ${e.stream==="stderr"?$(e.message):e.message}`}async function pi(e,t,n){let r=((await Dn(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit})).lines??[]).slice().reverse();if(h({ref:e,lines:r},()=>{if(r.length===0&&!n.follow){d(`Nothing printed${t?` by ${t}`:""} on ${e} in the last seven days.`),d(w(" Lines show up about a minute after a container prints them."));return}for(let s of r)process.stdout.write(`${ri(s)}
440
+ `)}),!n.follow)return;let o=r.at(-1)?.at??new Date().toISOString();for(;;){await new Promise(a=>setTimeout(a,5e3));let s=((await Dn(e,{name:t,errorsOnly:n.errorsOnly,limit:n.limit,since:o})).lines??[]).slice().reverse();for(let a of s)process.stdout.write(`${ri(a)}
441
+ `),o=a.at}}Q();he();Z();function fi(e){let t=e.indexOf("=");return t<=0?null:{name:e.slice(0,t),value:e.slice(t+1)}}async function Fn(){let e=[];for await(let t of process.stdin)e.push(Buffer.from(t));return Buffer.concat(e).toString("utf8").replace(/\n$/,"")}async function gi(e,t){if(e.length===0)throw new g("secrets set needs NAME=value, or NAME with --stdin");if(t.stdin){if(e.length!==1)throw new g("--stdin sets one secret, so it takes one NAME");return[{name:e[0],value:await Fn()}]}return e.map(n=>{let r=fi(n);if(!r)throw new g(`${n} is not NAME=value. Use --stdin to set a value from a pipe.`);return r})}async function hi(e,t,n){if(t.length===0)throw new g("secrets set needs NAME=value, or NAME with --stdin");let r=[];if(n.stdin){if(t.length!==1)throw new g("--stdin sets one secret, so it takes one NAME");r.push({name:t[0],value:await Fn()})}else for(let s of t){let a=fi(s);if(!a)throw new g(`${s} is not NAME=value. Use --stdin to set a value from a pipe.`);r.push(a)}for(let s of r){let a=jt(s.name);if(a)throw new g(a)}let o=null;for(let s of r)o=await S("cloud-project-secrets",{ref:e,action:"set",name:s.name,value:s.value});h(o,()=>{for(let s of r)d(`${$(s.name)} set`);d(w(" Functions pick it up within a few seconds. Redeploying is not needed.")),n.stdin||d(w(" A value typed here is in your shell history. In CI, use: \u2026 | snoutdata secrets set NAME --stdin"))})}async function yi(e,t){let n=jt(t);if(n)throw new g(n);let r=await S("cloud-project-secrets",{ref:e,action:"unset",name:t});h(r,()=>{d(`${t} is gone.`)})}async function wi(e){let t=await S("cloud-project-secrets",{ref:e});h(t,()=>{if(t.secrets.length===0){d(`${e} has no function secrets.`),d(w(" snoutdata secrets set STRIPE_KEY=sk_..."));return}process.stdout.write(`${B([["NAME","SIZE","SET"],...t.secrets.map(n=>[n.name,n.bytes===null?"-":`${n.bytes} B`,Me(n.updatedAt)])])}
442
+ `),d(w("Values are not readable, here or anywhere. Set one again to change it."))})}import{spawn as Gt}from"node:child_process";import{appendFileSync as Bi,existsSync as zn,readFileSync as Hi,statSync as Lm,unlinkSync as Um,writeFileSync as wt}from"node:fs";import{dirname as Fm,join as Wn}from"node:path";import{tmpdir as Bm}from"node:os";import{open as qi}from"node:fs/promises";function bi(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 Si(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 _i(e){let t=e.split(`
443
+ `).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 ao={auth:"snout_auth_admin",storage:"snout_storage_admin",push:"snout_push_admin"},em=/^(snoutpod_admin|snout_[a-z]+_admin)$/,Bn=new Set(["snout_identity","snout_files","push","snout_live","snout_realtime","_realtime","graphql","snout_graph","snout_ext","net","cron","pgbouncer","vault","pgsodium","auth","storage","realtime","graphql_public","extensions"]),tm=/^(pgrst_ddl_watch|pgrst_drop_watch|snoutpod_[a-z_]+|snoutdata_realtime|snout_realtime_messages)(\(|$)/,nm={snout_identity:"auth",snout_files:"storage",auth:"auth",storage:"storage",push:"push"},rm=new Set(["push.settings"]),om=["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 sm(e){let t=/^\d+; \d+ \d+ (.+)$/.exec(e.trim());if(!t)return null;let n=t[1],r=om.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 am(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 ki(e,t){let n=new Map,r=[],o=new Set,s=0;for(let i of e.split(`
444
+ `)){let u=sm(i);if(!u)continue;let{type:c,schema:p,name:m,owner:E}=u;if(c==="TABLE DATA"||c==="SEQUENCE SET"){let X=/^(schema_migrations|migrations)$/.test(m)&&Bn.has(p);if(/realtime/.test(p)||p==="snout_live"||X||p==="public"&&m==="spatial_ref_sys"){s++;continue}if(p==="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(p==="push"&&m==="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 j=nm[p];if(j){j==="storage"&&(m==="objects"||m==="files")&&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(j,[...n.get(j)??[],u]);continue}if(Bn.has(p)){s++;continue}r.push(u.line);continue}if(c==="COMMENT"&&p==="-"&&/^EXTENSION( |$)/.test(m)||(c==="SCHEMA"?Bn.has(m):Bn.has(p))||tm.test(m)||em.test(E)&&c!=="SCHEMA"){s++;continue}r.push(u.line)}let a=am(t),l=["auth","storage","push"];return{platformData:l.filter(i=>n.has(i)).map(i=>{let u=n.get(i),c=u.filter(m=>m.type==="TABLE DATA"&&rm.has(`${m.schema}.${m.name}`)).map(m=>`${m.schema}.${m.name}`),p=u.filter(m=>m.type==="TABLE DATA").map(m=>`${m.schema}.${m.name}`);return{product:i,clear:c,tables:p,lines:im(u,a)}}),rest:r,products:l.filter(i=>n.has(i)),skipped:s,notes:[...o]}}function im(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(c=>!r.has(c)||o.has(c)||c===n(u)))??a[0];o.add(n(i)),s.push(i.line),a=a.filter(u=>u!==i)}return s}Q();he();pe();Z();Q();pe();var co=["max_write_pause_ms","drain_timeout_ms","ack_timeout_ms","copy_mb_per_s"],$i=["advice_sample_ms","advice_min_bytes","advice_split_bytes","advice_skew_pct","advice_hot_pct","advice_min_writes_per_s","advice_pin_pct","advice_node_disk_bytes","advice_disk_pct","advice_conn_pct"];function Dt(e){return e==="done"||e==="failed"||e==="cancelled"}function Mt(e,t=process.env){if(e.project!==void 0){if(e.admin!==void 0)throw new v("usage","cluster takes --project (a SnoutData Cloud project) or --admin (a standalone router), not both");return new Ce(pm(e.project))}let n=e.admin??vi(t.LEPIS_ADMIN_URL);if(!n)throw new v("usage","cluster needs a project: --project <ref> for a SnoutData Cloud project, or --admin <url> (or LEPIS_ADMIN_URL) for the admin API of a standalone Lepis router (LEPIS_ADMIN_ADDR)");let r=e.token??vi(t.LEPIS_ADMIN_TOKEN);if(!r)throw new v("not-signed-in","no admin token: set LEPIS_ADMIN_TOKEN to the router's token, or pass --token");return new io(cm(n),r)}function vi(e){return e===void 0||e===""?void 0:e}function cm(e){let t=/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`,n;try{n=new URL(t)}catch{throw new v("usage",`--admin: not a URL: ${e}`)}if(n.protocol!=="http:"&&n.protocol!=="https:")throw new v("usage",`--admin: http or https, not ${n.protocol.replace(":","")}`);if(n.username||n.password)throw new v("usage","--admin: the URL carries no credentials; the token comes from LEPIS_ADMIN_TOKEN or --token");if(n.protocol==="http:"&&!lm(n.hostname))throw new v("usage",`--admin: ${n.host} is not this machine, and plain http would send the token in the clear. Use https (the router serves it when it has LEPIS_TLS_CERT), or a tunnel to 127.0.0.1`);return n.pathname=n.pathname.replace(/\/+$/,""),n.search="",n.hash="",n}function lm(e){let t=e.replace(/^\[|\]$/g,"").toLowerCase();return t==="localhost"||t==="::1"||/^127\.\d+\.\d+\.\d+$/.test(t)}var dm=6e4,io=class{target;#t;#n;constructor(t,n){this.#t=t,this.#n=n,this.target=t.toString().replace(/\/$/,"")}status(){return this.#e("GET","/v1/status")}ops(){return this.#e("GET","/v1/ops")}plan(t){return this.#e("POST","/v1/plan",t)}submit(t){return this.#e("POST","/v1/jobs",t)}async jobs(){return(await this.#e("GET","/v1/jobs")).jobs}job(t){return this.#e("GET",`/v1/jobs/${t}`)}cancel(t){return this.#e("POST",`/v1/jobs/${t}/cancel`)}resume(t){return this.#e("POST",`/v1/jobs/${t}/resume`)}settings(t){return this.#e("POST","/v1/settings",t)}advice(t){return this.#e("GET",t===void 0?"/v1/advice":`/v1/advice?sample_ms=${Math.max(0,Math.floor(t))}`)}async#e(t,n,r){let o=`${this.target}${n}`,s;try{s=await fetch(o,{method:t,headers:{authorization:`Bearer ${this.#n}`,accept:"application/json",...t==="POST"?{"content-type":"application/json"}:{}},...t==="POST"?{body:JSON.stringify(r??{})}:{},signal:AbortSignal.timeout(dm)})}catch(i){let u=ln(i),c=i instanceof Error?i.cause instanceof Error?i.cause.message:i.message:String(i);throw new v(u==="failed"?"network":u,`could not reach the Lepis admin API at ${this.target}: ${c}`)}let a=await s.text(),l;try{l=a===""?{}:JSON.parse(a)}catch{throw new v("server",`the admin API at ${this.target} answered ${s.status} with something that is not JSON`,{status:s.status})}if(!s.ok)throw xi(s.status,l);return l}},um={bad_request:"usage",unauthorized:"not-signed-in",not_found:"not-found",conflict:"conflict",failed:"failed",timeout:"timeout"};function xi(e,t){let n=t?.error??{},r=n.message??`the admin API answered ${e}`,o={status:e};n.code&&(o.apiCode=n.code),n.kind&&(o.kind=n.kind),n.sqlstate&&(o.sqlstate=n.sqlstate);let s=(n.code?um[n.code]:void 0)??(e===401?"not-signed-in":e===400?"usage":et(e));return s==="not-signed-in"?new v(s,"the router refused the admin token (LEPIS_ADMIN_TOKEN or --token)",o):new v(s,r,o)}function pm(e){let t=e.trim();if(!/^[a-z][0-9a-z]{12}$/.test(t))throw new v("usage",`--project: not a project ref: ${e} (13 characters, as projects list prints it)`);return t}var mm={"node.add":"add-node","node.attach":"attach","node.drain":"drain","node.remove":"remove","keyspace.create":"keyspace","table.distribute":"distribute","table.reference":"reference","table.global":"global","range.split":"split","range.merge":"merge","range.move":"move","tenant.pin":"pin",rebalance:"rebalance",scale:"scale",verify:"verify",cleanup:"cleanup","keyspace.drop":"drop-keyspace",dissolve:"disable"},fm="cloud-project-shards",Ce=class{target;ref;#t;constructor(t,n=S){this.ref=t,this.target=`project ${t}`,this.#t=n}async status(){let t=await this.#e({action:"status"}),{cluster:n,...r}=t;return{...n??{catalog:!1},cloud:r}}ops(){return this.#e({action:"ops"})}async plan(t){return this.#n(t),this.#e({action:"plan",request:t})}async submit(t){let n=this.#n(t),r={...t};delete r.op;let o=await this.#e({action:n,request:r});return typeof o.job=="number"?{job:o.job,plan:o.plan}:{done:Je(o),answer:o}}async jobs(){return(await this.#e({action:"jobs"})).jobs}job(t){return this.#e({action:"jobs",id:t})}cancel(t){return this.#e({action:"cancel",id:t})}resume(t){return this.#e({action:"resume",id:t})}settings(t){return this.#e({action:"settings",settings:t})}advice(t){return this.#e({action:"advice",...t===void 0?{}:{sampleMs:Math.max(0,Math.floor(t))}})}enable(t){return this.#e({action:"enable",...t?{plan:!0}:{}})}spread(t,n){return this.#e({action:"spread",request:t,...n?{plan:!0}:{}})}retry(t){return this.#e({action:"retry",...t})}disable(t){return this.#e({action:"disable",...t?{plan:!0}:{}})}replica(t,n,r){return this.#e({action:n?"remove-replica":"add-replica",node:t,...r?{plan:!0}:{}})}async alerts(){return(await this.status()).cloud?.alerts??[]}acknowledge(t){return this.#e({action:"acknowledge",alert:t})}#n(t){let n=mm[t.op];if(!n)throw new v("usage",`${t.op} is not an operation a SnoutData Cloud project takes`);let r=t;if(t.op==="node.add"&&(r.host!==void 0||r.name!==void 0))throw new v("usage","in SnoutData Cloud a node is a pod the project makes: nodes add takes no name or --host. It joins the cluster on its own once it is ready.");if(t.op==="scale"&&(r.add!==void 0||r.remove!==void 0))throw new v("usage","in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");return n}async#e(t){try{return await this.#t(fm,{ref:this.ref,...t})}catch(n){throw gm(n)}}};function Je(e){if(Array.isArray(e.created)){let t=e.created.map(String),n=typeof e.stopped=="string"?` Stopped there: ${e.stopped}`:"",r=t.length===1;return e.joining===!1?`New node${r?"":"s"} ${t.join(", ")}, starting as a copy of the home node. Attach ${r?"it":"each"} once it is ready: snoutdata cluster nodes attach <ref>.${n}`:`New node${r?"":"s"} ${t.join(", ")}, starting as a copy of the home node. ${r?"It joins":"They join"} the cluster and take${r?"s":""} ${r?"its":"their"} share of the data on ${r?"its":"their"} own; follow it with snoutdata cluster status.${n}`}return e.deleted===!0?`The pod of ${String(e.pod)} is being deleted. It owned nothing in the cluster.`:Array.isArray(e.queued)?`${String(e.next??"The table is being spread over the cluster.")} Steps: ${e.queued.map(String).join(", ")}. Follow it with snoutdata cluster status.`:typeof e.joining=="boolean"&&typeof e.node=="string"?e.joining?`${e.node} is joining the cluster again.`:`${e.node} is left as it is.`:e.queue==="retrying"?"The cluster is trying that step again.":typeof e.replica=="string"&&typeof e.node=="string"?`A replica of node ${e.node} is being made (${e.replica}). ${String(e.note??"It protects the node once it is following it.")}`:typeof e.changed=="boolean"&&typeof e.node=="string"&&e.sharded===void 0?e.changed?`The replica of node ${e.node} is being removed, and its project slot freed.`:`node ${e.node} has no replica. Nothing changed.`:e.acknowledged===!0?"Marked as seen. The alert stays open until what it is about is over.":e.unchanged===!0?`The project already has ${String(e.nodes)} nodes. Nothing changed.`:e.sharded===!0?e.changed===!1?"This project is already a cluster.":String(e.note??"This project is a cluster now."):e.sharded===!1?e.changed===!1?"This project is not a cluster.":String(e.note??"This project is not a cluster any more."):typeof e.next=="string"?e.next:"Done."}function gm(e){if(!(e instanceof Se))return e;let t=e.body??{};if(typeof t.apiCode=="string"||typeof t.kind=="string")return xi(e.status,{error:{code:typeof t.apiCode=="string"?t.apiCode:void 0,kind:typeof t.kind=="string"?t.kind:void 0,message:e.message,sqlstate:typeof t.sqlstate=="string"?t.sqlstate:void 0}});let n=t.code==="not-sharded"?"conflict":et(e.status);return new v(n,e.message,{status:e.status,...typeof t.code=="string"?{kind:t.code}:{}})}function Ut(e){if(Array.isArray(e))return`[${e.map(Ut).join(",")}]`;if(e&&typeof e=="object"){let t=e;return`{${Object.keys(t).filter(n=>t[n]!==void 0).sort().map(n=>`${JSON.stringify(n)}:${Ut(t[n])}`).join(",")}}`}return JSON.stringify(e)??"null"}var po=class{#t=new Map;#n;#e;constructor(t=15*6e4,n=Date.now){this.#n=t,this.#e=n}remember(t,n=""){this.#t.set(`${n}
445
+ ${Ut(t)}`,this.#e())}shown(t,n=""){let r=this.#t.get(`${n}
446
+ ${Ut(t)}`);return r!==void 0&&this.#e()-r<=this.#n}forget(t,n=""){this.#t.delete(`${n}
447
+ ${Ut(t)}`)}},hm=new po,mo=["node.add","node.drain","node.remove","keyspace.create","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","verify","cleanup","node.attach","keyspace.drop","dissolve"],ym="2024-11-05";function U(e,t=[]){return{type:"object",properties:e,additionalProperties:!1,...t.length?{required:t}:{}}}var P={type:"string"},Ei={type:"object",description:'The operation as the router takes it, e.g. {"op": "range.split", "keyspace": "k", "range": "-3074457345618258603", "at": "18454348402311335", "to": 3}. Range bounds and seeds are 64-bit, so pass them as strings. cluster_advice returns these ready-made, as `operation`. For a SnoutData Cloud project a node is a pod the project makes: {"op": "node.add"} makes one (no host), {"op": "node.attach", "name": "<its ref>"} hands a ready one to the cluster, and {"op": "scale", "nodes": N} is a node count.',properties:{op:{type:"string",enum:[...mo]}},required:["op"],additionalProperties:!0},Hn={type:"integer",description:"The job id cluster_run returned, or one from cluster_jobs."},ze={type:"string",description:"A SnoutData Cloud project's ref (from list_projects): its cluster, as the signed-in user. Omit for the standalone router this server was started with (LEPIS_ADMIN_URL)."},Bt=[{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:U({})},{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:U({})},{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:U({name:P,region:P,teamId:P},["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:U({ref:P},["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:U({ref:P},["ref"])},{name:"resume_project",description:"Start a paused project without waiting for a connection to wake it, and wait until it is ready.",inputSchema:U({ref:P},["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 nothing installed: the CLI connects to the database itself. A file holding a psql command (\\i, \\set) is refused before anything runs.",inputSchema:U({ref:P,dir:P,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:U({ref:P,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:U({ref:P},["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:U({ref:P},["ref"])},{name:"reset_password",description:"Rotate the project's database password. THE OLD ONE STOPS WORKING usually within fifteen seconds (longer while the host is busy starting other databases), 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 fifteen seconds before connecting, and retry a refusal for up to two minutes). Use it when a credential has leaked, not routinely.",inputSchema:U({ref:P},["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:U({})},{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:U({})},{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:U({name:P,expires:P,project:P},["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:U({id:P},["id"])},{name:"deploy_function",description:"Put a folder of TypeScript on the edge as an HTTP endpoint at https://<ref>.api.snoutdata.com/v1/fn/<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:U({ref:P,name:P,dir:P,entrypoint:P,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:U({ref:P},["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:U({ref:P,name:P,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:U({ref:P,name:P},["ref","name"])},{name:"list_containers",description:"The project's Snout Containers: each one's status (pending, pulling, starting, running, restarting, failed, stopped), why when it is not running, its image and pinned digest, restarts, and its public URL. allowed=false means the plan no longer covers it and it is stopped. Also the plan's allowance. Costs nothing.",inputSchema:U({ref:P},["ref"])},{name:"deploy_container",description:"Run a Docker image all the time beside the project, at https://<ref>.api.snoutdata.com/containers/v1/<name>/ (no API key needed there: the container authenticates its own callers). The image tag is pinned to the digest it points at now and must include linux/arm64. It listens on PORT (port, default 8080) and gets DATABASE_URL and the project's secrets in its environment. STATELESS: anything written inside it is lost on every restart or redeploy, so state belongs in the database. Plus and Pro plans only. A private image needs a person's first deploy with the CLI (no tool takes a registry token); a redeploy reuses the stored login. Returns at once: poll list_containers until the status is running or failed.",inputSchema:U({ref:P,name:P,image:P,port:{type:"integer"}},["ref","name","image"])},{name:"restart_container",description:"Start one container again from its image. Everything written inside it is lost, which is also how to clear a container stuck in a bad state. Poll list_containers to see it come back.",inputSchema:U({ref:P,name:P},["ref","name"])},{name:"delete_container",description:"Stop one container and remove it from the project. It does not touch the database.",inputSchema:U({ref:P,name:P},["ref","name"])},{name:"container_logs",description:"What a project's containers printed to stdout and stderr in the last seven days, newest first: each line's time, container, stream and text. Lines arrive about a minute after they are printed. Use it when list_containers says failed or restarting. name narrows to one container, errorsOnly to stderr, limit is at most 500 (default 100). Costs nothing.",inputSchema:U({ref:P,name:P,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["ref"])},{name:"container_egress",description:"Where a project's containers may send. allowlist null means the public internet (within the limits on how much and how fast); otherwise allowlist.allow is the only destinations they reach, and alwaysAllowed what they reach whatever the list says (the project's own database and API). The same as `snoutdata containers egress`. Costs nothing.",inputSchema:U({ref:P},["ref"])},{name:"set_container_egress",description:'Hold a project\'s containers to an allowlist, or let them reach the public internet again. Give EITHER allow, a list of destinations ("api.example.com:443", "api.example.com" for any port, "203.0.113.0/24:443,8443"; an empty list means nothing outside the project), OR clear: true. The list replaces the one there. Running containers follow it within a minute and nothing restarts, so a container that needs a destination left off stops reaching it. The same as `snoutdata containers egress set|clear`.',inputSchema:U({ref:P,allow:{type:"array",items:P},clear:{type:"boolean"}},["ref"])},{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:U({ref:P},["ref"])},{name:"function_logs",description:"What a project's functions printed with console.log and console.error in the last seven days, newest first: each line's time, function, level ('error' for console.error and console.warn) and text. Lines arrive about a minute after they are printed. Use it after deploying or calling a function to see why it failed. name narrows to one function, errorsOnly to errors, limit is at most 500 (default 100). Costs nothing.",inputSchema:U({ref:P,name:P,errorsOnly:{type:"boolean"},limit:{type:"integer"}},["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:U({ref:P},["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:U({ref:P},["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:U({ref:P,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:U({ref:P},["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:U({ref:P,hostname:P},["ref","hostname"])},{name:"verify_domain",description:"Check a custom domain's DNS records and verify it once they are published.",inputSchema:U({ref:P,hostname:P},["ref","hostname"])},{name:"remove_domain",description:"Stop serving a project at a custom domain.",inputSchema:U({ref:P,hostname:P},["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:U({ref:P},["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:U({ref:P,at:P,name:P},["ref","at"])},{name:"cluster_status",description:"A Lepis cluster (one Postgres database spread over several nodes): a Cloud project's with project, else the router this server was started against with LEPIS_ADMIN_URL. Its nodes, keyspaces and their ranges (a range is keyspace:lo; bounds are strings), tables, routers, unfinished jobs and settings; for a Cloud project also whether it is a cluster, its plan's node limit, and each node pod with the step that follows (attach, delete-pod, wait). Costs nothing.",inputSchema:U({project:ze})},{name:"cluster_advice",description:"Ask the cluster's advisor what to split, move or add, from each node's size, connections and write rate and each range's measured share. Each recommendation has a reason, its plan, and an `operation` that cluster_run takes as it is. RUNS NOTHING. sampleMs: how long to measure write rates over (the cluster's setting by default; 0 skips them; at most 30000).",inputSchema:U({project:ze,sampleMs:{type:"integer"}})},{name:"cluster_plan",description:"The dry run of one cluster operation: its steps, what moves where, rows, bytes, the estimated copy time and the expected write pause. Changes nothing. Planning an operation is what lets cluster_run run it.",inputSchema:U({project:ze,operation:Ei},["operation"])},{name:"cluster_run",description:"Run one cluster operation as a durable job. IT MOVES DATA, and each cutover pauses writes to what moves for up to the cluster's max_write_pause_ms. Without confirm: true it only returns the plan and runs nothing: show that plan to the user, and when they agree call again with the same operation and confirm: true. confirm runs only an operation planned here (cluster_plan, cluster_advice, or cluster_run without confirm) in the last 15 minutes. Returns the job id; follow it with cluster_watch_job.",inputSchema:U({project:ze,operation:Ei,confirm:{type:"boolean"}},["operation"])},{name:"cluster_jobs",description:"The cluster's latest jobs, or with id one job with each step's state and phase. Costs nothing.",inputSchema:U({project:ze,id:Hn})},{name:"cluster_watch_job",description:"Wait for a job to finish, up to timeoutSeconds (default 30, at most 120), and return its state and steps: done, failed (with the error; cluster_resume_job runs it again from the failed step) or cancelled. Running out of time stops the waiting, never the job; call again to keep waiting.",inputSchema:U({project:ze,id:Hn,timeoutSeconds:{type:"integer"}},["id"])},{name:"cluster_cancel_job",description:"Stop a running job. A move cancelled before its cutover rolls back; one cancelled after it finishes its cleanup.",inputSchema:U({project:ze,id:Hn},["id"])},{name:"cluster_resume_job",description:"Run a failed job again from the step that failed. It carries on the operation that was already approved and planned; follow it with cluster_watch_job.",inputSchema:U({project:ze,id:Hn},["id"])},{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:U({ref:P},["ref"])}],wm=new Set(["whoami","list_projects","get_connection_url","usage","export_status","list_teams","list_tokens","list_functions","list_function_secrets","function_logs","container_egress","get_project","list_products","list_domains","restore_window","cluster_status","cluster_advice","cluster_plan","cluster_jobs","cluster_watch_job"]),bm=new Set(["delete_project","delete_function","reset_password","revoke_token","remove_domain","cluster_run"]),Pi=["get_connection_url","push_migrations","get_project","list_functions","deploy_function","delete_function","list_function_secrets"];function fo(e){if(!e||typeof e!="object")return e;let t=e;if(Array.isArray(t.projects))return{...t,projects:t.projects.map(fo)};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 go(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 Sm(e){return{...e,annotations:wm.has(e.name)?{readOnlyHint:!0}:{readOnlyHint:!1,destructiveHint:bm.has(e.name)}}}function _m(e){let t=new Set;return e.filter(n=>!t.has(n.name)&&t.add(n.name))}function Ft(e,t){return{jsonrpc:"2.0",id:e,result:t}}function qn(e,t,n){return{jsonrpc:"2.0",id:e,error:{code:t,message:n}}}function _(e,t,n=!1){let r=typeof t=="string"?t:JSON.stringify(t,null,2);return Ft(e,{content:[{type:"text",text:r}],...n?{isError:!0}:{}})}function km(e,t=new Date){let n=Date.parse(e);return!Number.isFinite(n)||n<=t.getTime()?null:Math.ceil((n-t.getTime())/864e5)}function Lt(e){return{op:e.op,cutovers:e.cutovers,moves:(e.moves??[]).map(t=>({from:t.source,to:t.targets,tables:t.tables,estimated_rows:t.estimated_rows,estimated_bytes:t.estimated_bytes,estimated_copy_seconds:t.estimated_copy_seconds,expected_pause_ms:t.expected_pause_ms})),steps:(e.steps??[]).map(t=>t.kind),estimated_rows:e.estimated_rows,estimated_bytes:e.estimated_bytes,estimated_copy_seconds:e.estimated_copy_seconds,expected_pause_ms:e.expected_pause_ms,max_write_pause_ms:e.max_write_pause_ms,warnings:e.warnings??[],...e.assumptions?{assumptions:e.assumptions}:{}}}function lo(e){return{id:e.id,op:e.op,state:e.state,finished:Dt(e.state),error:e.error,steps:e.steps.map(t=>({n:t.n+1,kind:t.kind,state:t.state,...typeof t.detail?.phase=="string"?{phase:t.detail.phase}:{}})),...e.state==="failed"?{next:`cluster_resume_job {"id": ${e.id}} runs it again from the failed step.`}:{}}}function Ai(e,t){let n=e.operation;if(!n||typeof n!="object"||Array.isArray(n))return`${t} needs operation: an object like {"op": "range.split", "keyspace": "k", "range": "<lo>"}. cluster_advice returns them ready-made.`;let r=n;if(typeof r.op!="string"||!mo.includes(r.op))return`${t}: operation.op must be one of ${mo.join(", ")}.`;for(let o of["range","at","a","b","seed"]){let s=r[o];if(typeof s=="number"&&!Number.isSafeInteger(s))return`${t}: operation.${o} is a 64-bit number and lost its last digits as a JSON number. Pass it as a string, exactly as cluster_status or cluster_advice wrote it.`}return r}function uo(e){let t=typeof e.id=="string"&&/^\d+$/.test(e.id)?Number(e.id):e.id;return typeof t=="number"&&Number.isSafeInteger(t)&&t>0?t:null}function vm(e){let t=e instanceof Error?e.message:String(e),n=e?.details?.kind;return typeof n=="string"?{error:t,kind:n}:t}var $m="The cluster tools need a cluster: pass project (a SnoutData Cloud project's ref), or start this server with LEPIS_ADMIN_URL (a standalone router's admin API, e.g. http://127.0.0.1:7432) and LEPIS_ADMIN_TOKEN set. Tell the user that is what is missing.";async function xm(e,t,n,r,o){let s,a=typeof n.project=="string"&&n.project.trim()!==""?n.project.trim():void 0;try{if(!r.shards)return _(e,$m,!0);s=r.shards(a)}catch(u){return _(e,u instanceof Error?u.message:String(u),!0)}let l=o.shardsPlans??hm,i=a??"";try{switch(t){case"cluster_status":return _(e,await s.status());case"cluster_advice":{let u=typeof n.sampleMs=="number"&&n.sampleMs>=0?Math.min(Math.floor(n.sampleMs),3e4):void 0,c=await s.advice(u);for(let p of c.advice)p.plan&&l.remember(p.request,i);return _(e,{summary:c.summary,advice:c.advice.map(p=>({op:p.op,reason:p.reason,metric:p.metric,operation:p.request,...p.plan?{plan:Lt(p.plan)}:{},...p.needs.length>0?{needs:p.needs,note:`Ask the user for ${p.needs.join(", ")}, then plan it with cluster_plan.`}:{},...p.plan_error?{plan_error:p.plan_error}:{}})),facts:c.facts,assumptions:c.assumptions,...c.advice.length>0?{next:"Nothing has run. Show the user a recommendation and its plan; if they agree, call cluster_run with its operation and confirm: true. Ask for advice again after each one runs."}:{}})}case"cluster_plan":{let u=Ai(n,t);if(typeof u=="string")return _(e,u,!0);let c=await s.plan(u);return l.remember(u,i),_(e,{plan:Lt(c),next:"Nothing has run. To run it, show the user this plan, then call cluster_run with the same operation and confirm: true."})}case"cluster_run":{let u=Ai(n,t);if(typeof u=="string")return _(e,u,!0);if(n.confirm!==!0||!l.shown(u,i)){let p=await s.plan(u);l.remember(u,i);let m=n.confirm===!0;return _(e,{ran:!1,plan:Lt(p),next:m?"NOT RUN: confirm applies to an operation planned here in the last 15 minutes, and this one was not, so it has been planned now instead. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true.":"Nothing has run. Show the user this plan; if they agree, call cluster_run again with the same operation and confirm: true."},m)}let c=await s.submit(u);return l.forget(u,i),c.job===void 0?_(e,{ran:!0,done:c.done,answer:c.answer}):_(e,{ran:!0,job:c.job,plan:Lt(c.plan),next:`Follow it with cluster_watch_job {"id": ${c.job}}.`})}case"cluster_jobs":{if(n.id===void 0)return _(e,{jobs:await s.jobs()});let u=uo(n);if(u===null)return _(e,"cluster_jobs: id is a job number. Call it without id to list them.",!0);let c=await s.job(u);return _(e,{...lo(c),plan:c.plan&&Array.isArray(c.plan.steps)?Lt(c.plan):null})}case"cluster_watch_job":{let u=uo(n);if(u===null)return _(e,"cluster_watch_job needs id, the job number cluster_run returned.",!0);let c=typeof n.timeoutSeconds=="number"&&n.timeoutSeconds>0?Math.min(n.timeoutSeconds,120):30,p=Date.now()+c*1e3;for(;;){let m=await s.job(u);if(Dt(m.state))return _(e,lo(m),m.state!=="done");if(Date.now()>=p)return _(e,{...lo(m),note:`Still ${m.state} after ${c}s. It carries on; call cluster_watch_job again to keep waiting.`});await new Promise(E=>setTimeout(E,o.shardsPollMs??1e3))}}case"cluster_cancel_job":case"cluster_resume_job":{let u=uo(n);if(u===null)return _(e,`${t} needs id, a job number.`,!0);let c=t==="cluster_cancel_job"?await s.cancel(u):await s.resume(u);return _(e,{...c,next:`Follow it with cluster_watch_job {"id": ${u}}.`})}default:return qn(e,-32602,`unknown tool: ${t}`)}}catch(u){return _(e,vm(u),!0)}}function W(e,t){let n=e[t];return typeof n=="string"&&n.trim().length>0?n.trim():null}async function Ri(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 Ft(r,{protocolVersion:ym,capabilities:{tools:{listChanged:!1}},serverInfo:{name:"snoutdata",version:n.version}});case"ping":return Ft(r,{});case"tools/list":return Ft(r,{tools:_m([...Bt.map(Sm),...n.borrowed??[]])});case"tools/call":return Em(r,s,t,n);default:return qn(r,-32601,`unknown method: ${o}`)}}async function Em(e,t,n,r){let o=typeof t.name=="string"?t.name:"",s=t.arguments??{};if(!(Bt.some(c=>c.name===o)||(r.borrowed?.some(c=>c.name===o)??!1)))return qn(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"];if(o.startsWith("cluster_")&&Bt.some(c=>c.name===o))return xm(e,o,s,n,r);let i=W(s,"ref");if(l.includes(o)&&!i)return _(e,`${o} needs a project ref. Call list_projects to see them.`,!0);let u=i&&n.localProject?n.localProject(i):null;if(u&&!Pi.includes(o))return _(e,`${i} is the self-hosted project in ${u.folder}, and ${o} is about SnoutData Cloud. On a local project these tools work: ${Pi.join(", ")}.`,!0);try{switch(o){case"whoami":return _(e,await n.whoami());case"list_projects":return _(e,fo(await n.listProjects()));case"create_project":{let c=W(s,"name");return c?_(e,await n.createProject({name:c,region:W(s,"region")??void 0,teamId:W(s,"teamId")??void 0})):_(e,"create_project needs a name.",!0)}case"get_connection_url":{let c=await n.connection(i);return _(e,{url:c.uri,state:c.state,wakesInstantly:c.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 _(e,await n.setProjectState("pause",i));case"resume_project":return _(e,await n.setProjectState("resume",i));case"usage":{let c=typeof s.days=="number"?s.days:void 0;return _(e,await n.usage(i,c))}case"export_status":return _(e,await n.exportStatus(i));case"start_export":return _(e,await n.requestExport(i));case"reset_password":{let c=await n.resetPassword(i),p=await n.connection(i);return _(e,{url:p.uri,appliesIn:c.appliesIn??"within about fifteen seconds",next:"Wait about fifteen seconds before connecting with this URL, and retry a refusal for up to two minutes: until the change applies (usually 5 to 15 seconds, longer while the host is busy) the new password is refused and the old one still works, which does not mean the new one is wrong. 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 _(e,await n.listTeams());case"get_project":return _(e,fo(await n.getProject(i)));case"list_products":return _(e,await n.getProducts(i));case"set_product":{let c=W(s,"product");return c!=="auth"&&c!=="storage"&&c!=="data-api"?_(e,"set_product needs product: auth, storage or data-api.",!0):typeof s.enabled!="boolean"?_(e,"set_product needs enabled: true or false.",!0):_(e,await n.setProduct(i,c,s.enabled))}case"list_domains":return _(e,await n.listDomains(i));case"add_domain":case"verify_domain":case"remove_domain":{let c=W(s,"hostname");return c?_(e,await n.domainAction(i,o.split("_")[0],c)):_(e,`${o} needs a hostname, e.g. api.example.com.`,!0)}case"restore_window":{let c=await n.restoreWindow(i),p=c&&c.restore?go(i,c.restore):null;return _(e,p?{...c,reason:p}:c)}case"restore_to_point":{let c=W(s,"at");return c?_(e,await n.restoreTo(i,c,W(s,"name")??void 0)):_(e,"restore_to_point needs at: an ISO 8601 moment inside restore_window.",!0)}case"deploy_function":{let c=W(s,"ref"),p=W(s,"name");return!c||!p?_(e,"deploy_function needs a project ref and a function name.",!0):_(e,await n.deployFunction(c,p,{dir:W(s,"dir")??void 0,entrypoint:W(s,"entrypoint")??void 0,verifyJwt:s&&typeof s=="object"&&"openToAnyone"in s?s.openToAnyone!==!0:!0}))}case"list_functions":{let c=W(s,"ref");return c?_(e,await n.listFunctions(c)):_(e,"list_functions needs a project ref.",!0)}case"size_function":{let c=W(s,"ref"),p=W(s,"name");if(!c||!p)return _(e,"size_function needs a project ref and a function name.",!0);let m=typeof s.memoryMb=="number"?s.memoryMb:void 0,E=typeof s.concurrency=="number"?s.concurrency:void 0,k=s.reset===!0;return!k&&m===void 0&&E===void 0?_(e,"size_function needs memoryMb, concurrency, or reset: true.",!0):_(e,await n.sizeFunction(c,p,{memoryMb:m,concurrency:E,reset:k}))}case"delete_function":{let c=W(s,"ref"),p=W(s,"name");return!c||!p?_(e,"delete_function needs a project ref and a function name.",!0):_(e,await n.deleteFunction(c,p))}case"list_containers":{let c=W(s,"ref");return c?_(e,await n.listContainers(c)):_(e,"list_containers needs a project ref.",!0)}case"deploy_container":{let c=W(s,"ref"),p=W(s,"name"),m=W(s,"image");if(!c||!p||!m)return _(e,"deploy_container needs a project ref, a container name and an image.",!0);let E=typeof s.port=="number"?s.port:void 0;return _(e,await n.deployContainer(c,p,{image:m,...E===void 0?{}:{port:E}}))}case"restart_container":case"delete_container":{let c=W(s,"ref"),p=W(s,"name");return!c||!p?_(e,`${o} needs a project ref and a container name.`,!0):_(e,o==="restart_container"?await n.restartContainer(c,p):await n.deleteContainer(c,p))}case"container_logs":{let c=W(s,"ref");if(!c)return _(e,"container_logs needs a project ref.",!0);let p=typeof s.limit=="number"?s.limit:void 0;return _(e,await n.containerLogs(c,{name:W(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:p}))}case"container_egress":{let c=W(s,"ref");return c?_(e,await n.containerEgress(c)):_(e,"container_egress needs a project ref.",!0)}case"set_container_egress":{let c=W(s,"ref");if(!c)return _(e,"set_container_egress needs a project ref.",!0);let p=s.clear===!0,m=s.allow;return m!==void 0&&(!Array.isArray(m)||!m.every(E=>typeof E=="string"))?_(e,'set_container_egress takes allow as a list of destinations, such as ["api.example.com:443"].',!0):p===(m!==void 0)?_(e,"set_container_egress takes either allow (a list of destinations; [] for nothing outside the project) or clear: true (the public internet again), and exactly one of them.",!0):_(e,await n.setContainerEgress(c,p?null:m))}case"list_function_secrets":{let c=W(s,"ref");return c?_(e,await n.listFunctionSecrets(c)):_(e,"list_function_secrets needs a project ref.",!0)}case"function_logs":{let c=W(s,"ref");if(!c)return _(e,"function_logs needs a project ref.",!0);let p=typeof s.limit=="number"?s.limit:void 0;return _(e,await n.functionLogs(c,{name:W(s,"name")??void 0,errorsOnly:s.errorsOnly===!0,limit:p}))}case"list_tokens":return _(e,await n.listTokens());case"create_token":{let c=W(s,"name");if(!c)return _(e,"create_token needs a name, so somebody can tell later what it was for.",!0);let p=W(s,"expires"),m=p?km(p):void 0;return m===null?_(e,`create_token: \`expires\` must be a date in the future, like 2026-12-31. Got "${p}".`,!0):_(e,await n.createToken(c,m,W(s,"project")??void 0))}case"revoke_token":{let c=W(s,"id");return c?_(e,await n.revokeToken(c)):_(e,"revoke_token needs the id of a token. list_tokens has them.",!0)}case"push_migrations":return _(e,await n.pushMigrations(i,{dir:W(s,"dir")??void 0,dryRun:s.dryRun===!0}));case"delete_project":return r.allowDelete?_(e,await n.setProjectState("delete",i)):_(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(c=>c.name===o)?n.callBorrowed?Ft(e,await n.callBorrowed(o,s)):_(e,`${o} needs SnoutData Studio, which is no longer answering.`,!0):qn(e,-32602,`unknown tool: ${o}`)}}catch(c){return _(e,c instanceof Error?c.message:String(c),!0)}}function Ti(e){let t=e.split(`
448
+ `),n=t.pop()??"";return{messages:t.map(r=>r.trim()).filter(r=>r.length>0),rest:n}}var Gn=["auth","storage","data-api","push"];function ot(e){return{error:e instanceof Error?e.message:String(e)}}async function yt(e){let[t,n,r,o]=await Promise.all([S("cloud-project-auth",{ref:e,action:"status"}).then(s=>({enabled:s.enabled===!0,google:s.google?.enabled===!0,saml:s.saml?.enabled===!0}),ot),S("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}),ot),S("cloud-project-data-api",{ref:e}).then(s=>({enabled:s.dataApi?.enabled===!0,allowedOnPlan:s.dataApi?.allowed===!0}),ot),S("cloud-project-push",{ref:e}).then(s=>({enabled:s.push?.enabled===!0,scheduling:s.push?.scheduling===!0}),ot)]);return{ref:e,auth:t,storage:n,dataApi:r,push:o,realtime:Pm}}var Pm={enabled:!0,broadcast:!0,presence:!0,tableChanges:"Plus and Pro plans"};function Ni(e){let t=(e??"").toLowerCase().replace("_","-").replace(/^dataapi$/,"data-api");if(!Gn.includes(t))throw new g(`say which product: ${Gn.join(", ")}`);return t}async function ho(e,t,n){if(t==="auth"){let r=await S("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 S("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 S(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 Ci(e){let t=await yt(e);h(t,()=>{let n=(l,i,u="")=>[l,i&&typeof i=="object"&&"error"in i?`could not read: ${i.error}`:i.enabled?$("on"):"off",u],r=t.storage,o=t.dataApi;process.stdout.write(`${B([["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: signals, roster; table changes on Plus and Pro")])}
449
+ `);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&&d(w(` snoutdata products enable ${a} --ref ${e}`))})}async function ji(e,t,n){let r=await ho(e,t,n);h(r,()=>d(r.note))}async function Kn(e){let t=await S("cloud-project-domain",{ref:e,action:"list"});return{ref:e,domains:t.domains??[]}}async function yo(e,t,n){if(!n)throw new g(`domains ${t} needs a hostname, e.g. api.example.com`);let r=await S("cloud-project-domain",{ref:e,action:t,hostname:n});return{ref:e,domain:r.domain??null,next:r.next??null}}function Oi(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(`${B(n)}
450
+ `)}async function Ii(e,t,n){if(t===void 0||t==="list"){let o=await Kn(e);h(o,()=>{if(o.domains.length===0){d(`No domains on ${e}. \`snoutdata domains add api.example.com\`.`);return}process.stdout.write(`${B([["HOSTNAME","VERIFIED","CERTIFICATE"],...o.domains.map(s=>[s.hostname,s.verified?"yes":$("no"),s.certificate?.state??""])])}
451
+ `)});return}if(t!=="add"&&t!=="verify"&&t!=="remove")throw new g(`unknown command: domains ${t}`);let r=await yo(e,t,n??"");t==="verify"&&r.domain&&!r.domain.verified&&($n()||Oi(r.domain,e),L("not-ready",r.domain.verificationError??`${n} is not verified yet. Publish the records above, then run this again.`,{ref:e,domain:r.domain})),h(r,()=>{if(t==="remove"){d(`${n} removed.`);return}r.domain&&(d(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||Oi(r.domain,e)),r.next&&d(r.next)})}async function wo(e){return S("cloud-project-restore",{ref:e},"GET")}async function bo(e,t,n){if(Number.isNaN(Date.parse(t)))throw new g(`--at needs a moment, e.g. 2026-09-20T14:30:00Z (got "${t}")`);return S("cloud-project-restore",{ref:e,at:new Date(t).toISOString(),...n?{name:n}:{}})}async function Di(e){let t=await wo(e);h(t,()=>{let n=t.restore,r=go(e,n);if(r){d(r);return}d(`${e} can be restored to any moment from ${n.earliest} to ${n.latest}.`),d(w(` snoutdata db restore --at ${n.latest} --ref ${e}`))})}async function Mi(e,t,n){let r=await bo(e,t,n);h(r,()=>{d(`Restoring ${e} as it was at ${r.targetAt} into a new project, ${$(r.project.name)} (${r.project.ref}).`),d(w(" snoutdata projects list to see when it is ready"))})}async function So(e){let{projects:t}=await _e(),n=t.find(l=>l.ref===e);if(!n)throw new g(`no project ${e} on this account. \`snoutdata projects list\`.`);let[r,o,s,a]=await Promise.all([yt(e),S("cloud-project-functions",{ref:e}).then(l=>(l.functions??[]).map(i=>i.name),ot),S("cloud-project-secrets",{ref:e}).then(l=>(l.secrets??[]).map(i=>i.name),ot),Kn(e).then(l=>l.domains.map(i=>({hostname:i.hostname,verified:i.verified})),ot)]);return{...n,products:r,functions:o,secretNames:s,domains:a}}async function Li(e){let t=await So(e);h(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(`${B([["name",n.name],["ref",n.ref],["state",n.state],["region",n.region],...typeof n.postgresVersion=="number"?[["postgres",String(n.postgresVersion)]]:[],["auth / storage / data-api / push",`${r(n.products.auth)} / ${r(n.products.storage)} / ${r(n.products.dataApi)} / ${r(n.products.push)}`],["realtime","on (signals and the roster; table changes on Plus and Pro)"],["functions",o(n.functions)],["secrets",o(n.secretNames)],["domains",o(n.domains)]])}
452
+ `)})}Q();pe();tt();Q();Z();import{existsSync as Am,mkdirSync as Rm,readFileSync as Tm,renameSync as Om,writeFileSync as Nm}from"node:fs";import{homedir as _o}from"node:os";import{dirname as Cm,join as jm}from"node:path";import{rootCertificates as Im}from"node:tls";var Dm="SNOUTDATA_DB_SSLMODE";function Mm(e=_o()){let t=jm(e,".snoutdata","ca-roots.pem"),n=`${Im.join(`
411
453
  `)}
412
- `,r=null;try{r=op(t)?ap(t,"utf8"):null}catch{r=null}if(r!==n){sp(lp(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;cp(o,n,{mode:420}),ip(o,t)}return t}function po(e,t=process.env,n=mo()){if(e==="disable")return{mode:"disable"};let r=t[mp]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||pp(n)}}function et(e,t=process.env,n=mo()){let{mode:r,rootCert:o}=po(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function Oi(e){try{let{projects:t}=await he(),n=t.find(r=>r.ref===e);n?.readOnly?X(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&X(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function go(e){await Oi(e);let t=await pe(e);g(t,()=>{process.stdout.write(`${t.uri}
413
- `)})}async function Ni(e,t){if(t.length===0&&!ae()){let o=ct();I("usage",`db psql with no statements opens an interactive session, and ${o?it(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await Oi(e);let n=await pe(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=Lt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...et(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new v("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new v("failed",String(a)))}),r.on("close",a=>o(a??0))})}function ji(e,t,n="DATABASE_URL"){let r=Hn(e,".env"),o=`${n}=${t}`,s=wp(e);if(!Bn(r))return pt(r,`${o}
414
- `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&fp(r).mode&63&&X(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=Ri(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(pt(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(Ai(r,a.endsWith(`
454
+ `,r=null;try{r=Am(t)?Tm(t,"utf8"):null}catch{r=null}if(r!==n){Rm(Cm(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.tmp`;Nm(o,n,{mode:420}),Om(o,t)}return t}function Ht(e,t=process.env,n=_o()){if(e==="disable")return{mode:"disable"};let r=t[Dm]?.trim();return r?{mode:r}:{mode:"verify-full",rootCert:t.PGSSLROOTCERT||Mm(n)}}function qt(e,t=process.env,n=_o()){let{mode:r,rootCert:o}=Ht(e,t,n);return o?{PGSSLMODE:r,PGSSLROOTCERT:o}:{PGSSLMODE:r}}async function Gi(e){try{let{projects:t}=await _e(),n=t.find(r=>r.ref===e);n?.readOnly?ee(`${e} is read-only: it is over the storage limit on your plan. Reading works; writing does not.`):n?.readOnlyPending&&ee(`${e} is about to become read-only: it has grown past the storage limit on your plan.`)}catch{}}async function vo(e){await Gi(e);let t=await we(e);h(t,()=>{process.stdout.write(`${t.uri}
455
+ `)})}async function Ki(e,t){if(t.length===0&&!me()){let o=pt();L("usage",`db psql with no statements opens an interactive session, and ${o?ut(o):"there is nobody to use it"}. Pass statements after --, or use \`snoutdata db url\` and your own client.`)}await Gi(e);let n=await we(e);n.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let r=Gt("psql",["--host",n.host,"--port",String(n.port),"--username",n.user,"--dbname",n.database,...t],{stdio:"inherit",env:{...process.env,PGPASSWORD:n.password,...qt(n.ssl)}});return new Promise((o,s)=>{r.on("error",a=>{if(a.code==="ENOENT"){s(new v("tool-missing","psql is not installed. `snoutdata db url` prints a connection string for any client."));return}s(new v("failed",String(a)))}),r.on("close",a=>o(a??0))})}function Ji(e,t,n="DATABASE_URL"){let r=Wn(e,".env"),o=`${n}=${t}`,s=Hm(e);if(!zn(r))return wt(r,`${o}
456
+ `,{mode:384}),{path:r,replaced:!1,ignored:s};process.platform!=="win32"&&Lm(r).mode&63&&ee(`${r} can be read by other users on this machine, and it holds a database password. \`chmod 600 ${r}\` closes it.`);let a=Hi(r,"utf8"),l=new RegExp(`^${n}=.*$`,"m");return l.test(a)?(wt(r,a.replace(l,o)),{path:r,replaced:!0,ignored:s}):(Bi(r,a.endsWith(`
415
457
  `)?`${o}
416
458
  `:`
417
459
  ${o}
418
- `),{path:r,replaced:!1,ignored:s})}function wp(e){let t=e;for(;!Bn(Hn(t,".git"));){let s=hp(t);if(s===t)return!1;t=s}let n=Hn(e,".gitignore"),r=Bn(n)?Ri(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(Ai(n,r===""||r.endsWith(`
460
+ `),{path:r,replaced:!1,ignored:s})}function Hm(e){let t=e;for(;!zn(Wn(t,".git"));){let s=Fm(t);if(s===t)return!1;t=s}let n=Wn(e,".gitignore"),r=zn(n)?Hi(n,"utf8"):"";return r.split(/\r?\n/).some(s=>/^\/?(\.env|\.env\*|\*\.env|\.env\.\*)$/.test(s.trim()))?!1:(Bi(n,r===""||r.endsWith(`
419
461
  `)?`.env
420
462
  `:`
421
463
  .env
422
- `),!0)}async function Ci(e,t={}){if(t.statusOnly){let{export:a}=await dt(e);return g({ref:e,export:a},()=>fo(a)),0}let n=await Et(e);if(!n.export.pending&&n.export.url&&!t.out)return g({ref:e,export:n.export},()=>fo(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await _p(e,t.timeoutMs);if(r.error&&I("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return g({ref:e,export:r},()=>fo(r)),0;r.url||I("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await Sp(r.url,t.out),s=r.rolesSql?bp(t.out):null;return s&&r.rolesSql&&pt(s,r.rolesSql.endsWith(`
464
+ `),!0)}async function zi(e,t={}){if(t.statusOnly){let{export:a}=await ft(e);return h({ref:e,export:a},()=>ko(a)),0}let n=await Ot(e);if(!n.export.pending&&n.export.url&&!t.out)return h({ref:e,export:n.export},()=>ko(n.export)),0;d("Taking a copy of the database. This runs on the server and can take a few minutes.");let r=await Gm(e,t.timeoutMs);if(r.error&&L("failed",`The export did not finish: ${r.error}`,{ref:e}),!t.out)return h({ref:e,export:r},()=>ko(r)),0;r.url||L("failed","The copy was taken, but no download link could be signed. Run this again to get one.",{ref:e});let o=await Km(r.url,t.out),s=r.rolesSql?qm(t.out):null;return s&&r.rolesSql&&wt(s,r.rolesSql.endsWith(`
423
465
  `)?r.rolesSql:`${r.rolesSql}
424
- `,"utf8"),g({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${De(o)} to ${t.out}.`),d(w(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(d(`Saved the roles it needs to ${s}.`),d(w("Into any other Postgres, in this order:")),d(w(` psql --dbname <your database> -f ${s}`)),d(w(` pg_restore --dbname <your database> ${t.out}`)),d(w("The roles are created without passwords. Set them yourself if anything signs in as one."))):(d(w("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),d(w("Owners and grants are skipped: this export carries no roles file.")))}),0}function bp(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function _p(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await dt(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 Sp(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 Ti(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 fo(e){if(e.error){X(`The last export did not finish: ${e.error}`);return}if(e.pending){d("An export is running. Ask again in a minute, or run `snoutdata db export`, which waits for it.");return}if(!e.completedAt){d("This project has never been exported.");return}d(`A copy taken ${Ce(e.completedAt)}, ${De(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
425
- `),e.urlExpiresAt&&d(w(`That link stops working ${Ce(e.urlExpiresAt)}.`))):d(w("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 Ii(e,t){let n=t.file;n||I("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),Bn(n)||I("usage",`no file at ${n}`,{file:n});let r=await Ti(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=ai(o.slice(0,s.bytesRead)),l=await pe(e);l.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await he(),u=i.find(V=>V.ref===e),c=await kp(l);c===null&&I("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 m=ii(a,{existingTables:c,readOnly:u?.readOnly===!0},{force:t.force});m.ok||I("conflict",m.reason,{ref:e,file:n,refusal:m.code});let h=null;if(m.tool==="pg_restore"){let V=await Pi("pg_restore",["--list",n]),F=await Pi("pg_restore",["--section=post-data","--file=-",n]);if((V.code!==0||F.code!==0)&&I("failed",`pg_restore could not read ${n}: ${(V.stderr||F.stderr).trim().slice(0,300)}`,{ref:e,file:n}),h=li(V.stdout,F.stdout),h.products.length>0){let de=await mt(e),Oe=ve=>!!(ve&&typeof ve=="object"&&"enabled"in ve&&ve.enabled),le=h.products.filter(ve=>!Oe(de[ve]));le.length>0&&I("conflict",`this dump holds ${le.length>1?`${le.slice(0,-1).join(", ")} and ${le[le.length-1]}`:le[0]} data, and ${e} has ${le.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${le.length===1?"it":"them"} on, wait a minute, and run this again:
426
- ${le.map(ve=>` snoutdata products enable ${ve} --ref ${e}`).join(`
427
- `)}`,{ref:e,file:n,refusal:"products-off",products:le})}}if(h&&h.platformData.length>0){let V=h.platformData.flatMap(({tables:Oe})=>Oe),F=await vp(l,V);F===null&&I("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let de=h.platformData.filter(({tables:Oe})=>Oe.some(le=>F.includes(le))).map(({product:Oe})=>Oe);de.length>0&&I("conflict",`${de.join(", ")} on ${e} ${de.length===1?"has":"have"} not finished setting up ${de.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:de})}d(`Restoring ${n} into ${e} with ${m.tool} (${m.format} format).`);let x=h?await xp(l,n,h):await Fn(l,m.tool,n),L=ci(x.stderr);for(let V of h?.notes??[])d(w(V));return x.code!==0&&L.fatal.length===0&&L.total>0?(g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format,ignoredErrors:L.harmless},()=>{d(`Restored into ${e}.`),d(`${L.harmless} error${L.harmless===1?"":"s"} above ${L.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):(x.code!==0&&I("failed",`${m.tool} exited ${x.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:L.fatal}),g({ref:e,file:n,ok:!0,tool:m.tool,format:m.format},()=>d(`Restored into ${e}.`)),0)}async function kp(e){return new Promise(t=>{let n=Lt("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,...et(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{X(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 vp(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=Lt("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,...et(e.ssl)}}),s="";o.stdout.on("data",a=>s+=String(a)),o.on("error",()=>r(null)),o.on("close",a=>r(a===0?s.split(`
428
- `).map(l=>l.trim()).filter(Boolean):null))})}function Pi(e,t){return new Promise(n=>{let r=Lt(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=>{X(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 xp(e,t,n){let r=Hn(yp(),`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){pt(`${r}.sql`,`set role ${Xr[l]};
429
- ${i.map(h=>`delete from ${h};`).join(`
466
+ `,"utf8"),h({ref:e,path:t.out,bytes:o,rolesPath:s},()=>{d(`Saved ${Ue(o)} to ${t.out}.`),d(w(`Into a SnoutData project: snoutdata db restore --file ${t.out} --ref <ref>`)),s?(d(`Saved the roles it needs to ${s}.`),d(w("Into any other Postgres, in this order:")),d(w(` psql --dbname <your database> -f ${s}`)),d(w(` pg_restore --dbname <your database> ${t.out}`)),d(w("The roles are created without passwords. Set them yourself if anything signs in as one."))):(d(w("Restore it with: pg_restore --no-owner --no-privileges --dbname <your database> "+t.out)),d(w("Owners and grants are skipped: this export carries no roles file.")))}),0}function qm(e){return e.replace(/\.[^./\\]*$/,"")+".roles.sql"}async function Gm(e,t=30*6e4){let n=Date.now()+t;for(;;){let{export:r}=await ft(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 Km(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 qi(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 ko(e){if(e.error){ee(`The last export did not finish: ${e.error}`);return}if(e.pending){d("An export is running. Ask again in a minute, or run `snoutdata db export`, which waits for it.");return}if(!e.completedAt){d("This project has never been exported.");return}d(`A copy taken ${Me(e.completedAt)}, ${Ue(e.bytes??0)}.`),e.url?(process.stdout.write(`${e.url}
467
+ `),e.urlExpiresAt&&d(w(`That link stops working ${Me(e.urlExpiresAt)}.`))):d(w("The link has expired. Run the export again to sign a new one; the copy is kept."))}function Ue(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 Wi(e,t){let n=t.file;n||L("usage","db restore needs --file, pointing at a dump. `snoutdata db export` writes one."),zn(n)||L("usage",`no file at ${n}`,{file:n});let r=await qi(n,"r"),o=new Uint8Array(512),s=await r.read(o,0,512,0);await r.close();let a=bi(o.slice(0,s.bytesRead)),l=await we(e);l.wakesInstantly||d("This project is paused; the connection will wake it, which takes a few seconds.");let{projects:i}=await _e(),u=i.find(X=>X.ref===e),c=await Jm(l);c===null&&L("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 p=Si(a,{existingTables:c,readOnly:u?.readOnly===!0},{force:t.force});p.ok||L("conflict",p.reason,{ref:e,file:n,refusal:p.code});let m=null;if(p.tool==="pg_restore"){let X=await Fi("pg_restore",["--list",n]),j=await Fi("pg_restore",["--section=post-data","--file=-",n]);if((X.code!==0||j.code!==0)&&L("failed",`pg_restore could not read ${n}: ${(X.stderr||j.stderr).trim().slice(0,300)}`,{ref:e,file:n}),m=ki(X.stdout,j.stdout),m.products.length>0){let H=await yt(e),oe=I=>!!(I&&typeof I=="object"&&"enabled"in I&&I.enabled),ne=m.products.filter(I=>!oe(H[I]));ne.length>0&&L("conflict",`this dump holds ${ne.length>1?`${ne.slice(0,-1).join(", ")} and ${ne[ne.length-1]}`:ne[0]} data, and ${e} has ${ne.length===1?"it":"them"} off. Their tables are the platform's, so the product has to be on to take the rows. Switch ${ne.length===1?"it":"them"} on, wait a minute, and run this again:
468
+ ${ne.map(I=>` snoutdata products enable ${I} --ref ${e}`).join(`
469
+ `)}`,{ref:e,file:n,refusal:"products-off",products:ne})}}if(m&&m.platformData.length>0){let X=m.platformData.flatMap(({tables:oe})=>oe),j=await zm(l,X);j===null&&L("failed",`could not check ${e} for the tables this dump loads into.`,{ref:e});let H=m.platformData.filter(({tables:oe})=>oe.some(ne=>j.includes(ne))).map(({product:oe})=>oe);H.length>0&&L("conflict",`${H.join(", ")} on ${e} ${H.length===1?"has":"have"} not finished setting up ${H.length===1?"its":"their"} tables yet. Run this again in a minute.`,{ref:e,file:n,refusal:"products-starting",products:H})}d(`Restoring ${n} into ${e} with ${p.tool} (${p.format} format).`);let E=m?await Wm(l,n,m):await Jn(l,p.tool,n),k=_i(E.stderr);for(let X of m?.notes??[])d(w(X));return E.code!==0&&k.fatal.length===0&&k.total>0?(h({ref:e,file:n,ok:!0,tool:p.tool,format:p.format,ignoredErrors:k.harmless},()=>{d(`Restored into ${e}.`),d(`${k.harmless} error${k.harmless===1?"":"s"} above ${k.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):(E.code!==0&&L("failed",`${p.tool} exited ${E.code}. Nothing here retried it: a half-finished restore is undone by deleting the project and making another.`,{ref:e,file:n,errors:k.fatal}),h({ref:e,file:n,ok:!0,tool:p.tool,format:p.format},()=>d(`Restored into ${e}.`)),0)}async function Jm(e){return new Promise(t=>{let n=Gt("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,...qt(e.ssl)}}),r="";n.stdout.on("data",o=>r+=String(o)),n.on("error",o=>{ee(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 zm(e,t){if(t.length===0)return Promise.resolve([]);let n=t.map(r=>`'${r.replace(/'/g,"''")}'`).join(", ");return new Promise(r=>{let o=Gt("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,...qt(e.ssl)}}),s="";o.stdout.on("data",a=>s+=String(a)),o.on("error",()=>r(null)),o.on("close",a=>r(a===0?s.split(`
470
+ `).map(l=>l.trim()).filter(Boolean):null))})}function Fi(e,t){return new Promise(n=>{let r=Gt(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=>{ee(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 Wm(e,t,n){let r=Wn(Bm(),`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){wt(`${r}.sql`,`set role ${ao[l]};
471
+ ${i.map(m=>`delete from ${m};`).join(`
430
472
  `)}
431
- `,"utf8");let m=await Fn(e,"psql",`${r}.sql`,["--quiet"]);o=o||m.code,s+=m.stderr}pt(`${r}.platform`,`${u.join(`
473
+ `,"utf8");let p=await Jn(e,"psql",`${r}.sql`,["--quiet"]);o=o||p.code,s+=p.stderr}wt(`${r}.platform`,`${u.join(`
432
474
  `)}
433
- `,"utf8");let c=await Fn(e,"pg_restore",t,["--data-only",`--role=${Xr[l]}`,`--use-list=${r}.platform`]);o=o||c.code,s+=c.stderr}pt(`${r}.rest`,`${n.rest.join(`
475
+ `,"utf8");let c=await Jn(e,"pg_restore",t,["--data-only",`--role=${ao[l]}`,`--use-list=${r}.platform`]);o=o||c.code,s+=c.stderr}wt(`${r}.rest`,`${n.rest.join(`
434
476
  `)}
435
- `,"utf8");let a=await Fn(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{gp(`${r}${a}`)}catch{}}}function Fn(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=Lt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...et(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{X(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}))})}Y();ue();W();function Ep(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new p(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new p("db access list takes no argument");if(t!==void 0)throw new p("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new p("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 p(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new p("db access revoke needs an email address or a role name");if(t!==void 0)throw new p("--level is for db access grant");return{action:"revoke",who:r};default:throw new p(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function Di(e,t,n){let r=Ep(t,n.level);if(r.action==="list"){let s=await b("cloud-project-db-access",{ref:e,action:"list"});g(s,()=>Pp(s));return}if(r.action==="grant"){let s=await b("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});g(s,()=>Ap(s));return}let o=await b("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});g(o,()=>Tp(o))}function Pp(e){if(e.note&&d(e.note),e.access.length===0){d(`Nobody signs in to ${e.ref}'s database as themselves yet. \`snoutdata db access grant you@example.com --level full\`.`);return}process.stdout.write(`${M([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?k(n.email):n.email,n.level,n.role])])}
436
- `);let t=e.access.find(n=>n.you);t?.connection&&(d(""),d("You sign in with (psql 18; a browser approves it):"),d(w(` psql "${t.connection.keywords}"`)))}function Ap(e){e.connection&&process.stdout.write(`${e.connection.keywords}
437
- `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${k(e.level)} access to ${e.ref}'s database, as ${e.role}${e.means?`: ${e.means}`:""}.`),e.outcome!=="unchanged"&&d(e.appliesIn),d("They sign in with psql 18 (libpq 18 with OAuth support) and approve the code it prints in a browser:"),e.connection&&d(w(` psql "${e.connection.keywords}"`))}function Rp(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function Tp(e){let t=Rp(e);for(let n of t.slice(0,-1))d(n);X(t[t.length-1])}Y();ue();W();var Op=6e4;async function ho(e,t={}){return b("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function Np(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 p(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function jp(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Mi(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 Cp={connect:"connected",connect_refused:"refused at connect",join:"joined",join_refused:"join refused",leave:"left",channel_closed:"channel closed by the server",disconnect:"disconnected",handler_failed:"handler failed"};function Li(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[jp(e.at),Cp[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function Ip(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function Dp(e){let t=e.inspect;if(d(k(`Realtime for ${e.ref}`)),d(`${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){d("No channels are open.");return}for(let n of t.channels){let r=n.messages;if(d(""),d(k(`${n.name}${n.private?" (private)":""}`)+` ${n.clients.length} client${n.clients.length===1?"":"s"}`),d(w(` last ${r.window_seconds}s: ${r.broadcasts_received} signal${r.broadcasts_received===1?"":"s"} sent, ${r.broadcasts_delivered} delivered, busiest second ${r.peak_broadcasts_per_second}; ${r.presence_diffs_delivered} roster update${r.presence_diffs_delivered===1?"":"s"} delivered`)),n.clients.length>0){let s=n.clients.map(a=>[String(a.socket),a.presence_key,Mi(Date.now()-a.joined_at),Mi(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>Op?" (silent: likely gone)":"")]);d(M([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
477
+ `,"utf8");let a=await Jn(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{Um(`${r}${a}`)}catch{}}}function Jn(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=Gt(t,s,{stdio:["ignore","inherit","pipe"],env:{...process.env,PGPASSWORD:e.password,...qt(e.ssl)}}),i="";l.stderr?.on("data",u=>{let c=String(u);i+=c,process.stderr.write(c)}),l.on("error",u=>{ee(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}))})}Q();he();Z();function Ym(e,t){let[n="list",r,...o]=e;if(o.length>0)throw new g(`db access ${n} takes one argument, not ${1+o.length}`);switch(n){case"list":if(r!==void 0)throw new g("db access list takes no argument");if(t!==void 0)throw new g("--level is for db access grant");return{action:"list"};case"grant":{if(!r||!r.includes("@"))throw new g("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 g(`--level is full or read, not ${JSON.stringify(s)}`);return{action:"grant",email:r,level:s}}case"revoke":if(!r)throw new g("db access revoke needs an email address or a role name");if(t!==void 0)throw new g("--level is for db access grant");return{action:"revoke",who:r};default:throw new g(`unknown command: db access ${n}. db access list|grant|revoke`)}}async function Vi(e,t,n){let r=Ym(t,n.level);if(r.action==="list"){let s=await S("cloud-project-db-access",{ref:e,action:"list"});h(s,()=>Xm(s));return}if(r.action==="grant"){let s=await S("cloud-project-db-access",{ref:e,action:"grant",email:r.email,level:r.level});h(s,()=>Zm(s));return}let o=await S("cloud-project-db-access",{ref:e,action:"revoke",who:r.who});h(o,()=>ef(o))}function Xm(e){if(e.note&&d(e.note),e.access.length===0){d(`Nobody signs in to ${e.ref}'s database as themselves yet. \`snoutdata db access grant you@example.com --level full\`.`);return}process.stdout.write(`${B([["EMAIL","LEVEL","ROLE"],...e.access.map(n=>[n.you?$(n.email):n.email,n.level,n.role])])}
478
+ `);let t=e.access.find(n=>n.you);t?.connection&&(d(""),d("You sign in with (psql 18; a browser approves it):"),d(w(` psql "${t.connection.keywords}"`)))}function Zm(e){e.connection&&process.stdout.write(`${e.connection.keywords}
479
+ `),d("");let t=e.outcome==="unchanged"?"already has":"now has";d(`${e.email} ${t} ${$(e.level)} access to ${e.ref}'s database, as ${e.role}${e.means?`: ${e.means}`:""}.`),e.outcome!=="unchanged"&&d(e.appliesIn),d("They sign in with psql 18 (libpq 18 with OAuth support) and approve the code it prints in a browser:"),e.connection&&d(w(` psql "${e.connection.keywords}"`))}function Qm(e){return[...e.revoked.map(t=>`Took away ${t.email}'s ${t.level} access to ${e.ref}'s database (${t.role}).`),e.note]}function ef(e){let t=Qm(e);for(let n of t.slice(0,-1))d(n);ee(t[t.length-1])}Q();he();Z();var tf=6e4;async function $o(e,t={}){return S("cloud-project-realtime",{ref:e,...t.channel?{channel:t.channel}:{},...t.since!==void 0?{since:t.since}:{}})}function nf(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 g(`--since wants a duration like 10m, 2h or 1d, or a time like 2026-10-03T14:00, not "${e}"`)}function rf(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return`${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function Yi(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 of={connect:"connected",connect_refused:"refused at connect",join:"joined",join_refused:"join refused",leave:"left",channel_closed:"channel closed by the server",disconnect:"disconnected",handler_failed:"handler failed"};function Xi(e){let t=[e.presence_key?e.presence_key:null,e.socket!==void 0?`socket ${e.socket}`:null].filter(Boolean).join(", "),n=[rf(e.at),of[e.kind]??e.kind];return e.channel&&n.push(e.channel),t&&n.push(`(${t})`),e.reason?`${n.join(" ")}: ${e.reason}`:n.join(" ")}function sf(e){let t=Object.fromEntries(Object.entries(e).filter(([n])=>!n.startsWith("phx_ref")));return JSON.stringify(t)}function af(e){let t=e.inspect;if(d($(`Realtime for ${e.ref}`)),d(`${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){d("No channels are open.");return}for(let n of t.channels){let r=n.messages;if(d(""),d($(`${n.name}${n.private?" (private)":""}`)+` ${n.clients.length} client${n.clients.length===1?"":"s"}`),d(w(` last ${r.window_seconds}s: ${r.broadcasts_received} signal${r.broadcasts_received===1?"":"s"} sent, ${r.broadcasts_delivered} delivered, busiest second ${r.peak_broadcasts_per_second}; ${r.presence_diffs_delivered} roster update${r.presence_diffs_delivered===1?"":"s"} delivered`)),n.clients.length>0){let s=n.clients.map(a=>[String(a.socket),a.presence_key,Yi(Date.now()-a.joined_at),Yi(a.last_seen_ms_ago)+(a.last_seen_ms_ago!==null&&a.last_seen_ms_ago>tf?" (silent: likely gone)":"")]);d(B([["SOCKET","ROSTER KEY","JOINED","LAST HEARD"],...s]).split(`
438
480
  `).map(a=>` ${a}`).join(`
439
- `))}let o=Object.keys(n.presence);if(o.length>0){d(" roster:");for(let s of o)for(let a of n.presence[s].metas)d(` ${s} ${Ip(a)}`)}}}async function Ui(e,t={}){if(t.watch&&bn())throw new p("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await ho(e,{channel:t.channel});if(g(n.inspect,()=>Dp(n)),!t.watch)return 0;d(""),d(w("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 ho(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Li(a)}
481
+ `))}let o=Object.keys(n.presence);if(o.length>0){d(" roster:");for(let s of o)for(let a of n.presence[s].metas)d(` ${s} ${sf(a)}`)}}}async function Zi(e,t={}){if(t.watch&&$n())throw new g("--watch is for a person; with --json, poll `realtime inspect --json` or `realtime logs --since 1m --json`");let n=await $o(e,{channel:t.channel});if(h(n.inspect,()=>af(n)),!t.watch)return 0;d(""),d(w("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 $o(e,{channel:t.channel,since:r});for(let a of s.events)process.stdout.write(`${Xi(a)}
440
482
  `);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(w(` now ${a||"no channels open"}
441
- `))}}}async function Fi(e,t={}){let n=t.since?Np(t.since):0,r=await ho(e,{channel:t.channel,since:n});return g({ref:r.ref,events:r.events,capacity:r.logCapacity},()=>{if(r.events.length===0){d(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(`${Li(o)}
442
- `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(w(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}Y();ue();W();import{readFile as Lp}from"node:fs/promises";var Up="api.snoutdata.com";function Gi(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}.${Up}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function Fp(e){return Gi(e,await b("cloud-project-auth",{ref:e,action:"status"}))}async function Gn(e,t){return Gi(e,await b("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function yo(e){let t=e.google,n="off";t.ready?n=k("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(`${M([["auth",e.enabled?k("on"):"off"],["site url",e.siteUrl??w("not set")],["redirects",e.redirects.length?e.redirects.join(", "):w("none")],["google",n],["google client id",t.clientId??w("not set")],["google callback",e.googleCallback],["guests",e.anonymous.ready?k("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
443
- `),e.enabled||d(w(` snoutdata products enable auth --ref ${e.ref}`))}async function Bp(){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 qi(e,t,n,r){if(t===void 0||t==="show"){let o=await Fp(e);g(o,()=>yo(o));return}if(t==="google"){if(n[0]==="off"){let a=await Gn(e,{googleEnabled:!1});g(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new p('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 Bp();if(!a)throw new p("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await Gn(e,o);g(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):yo(s),d(w(` 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 p('auth anonymous takes "on" or "off"');let s=await Gn(e,{anonymousEnabled:o==="on"});g(s,()=>{o==="off"?d("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?d("Guest sign-in is on: guestSignIn() gives a browser a session with no email or password. Your auth service restarts within about a minute."):(d("Guest sign-in is switched on, and takes effect when auth is."),d(w(` 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 p("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await Gn(e,o);g(s,()=>yo(s));return}if(t==="templates"){let o=await b("cloud-project-auth",{ref:e,action:"templates"});g(o,()=>Hi(o.templates));return}if(t==="template"){let o=n[0];if(!o||!Bi.includes(o))throw new p(`auth template needs a kind: ${Bi.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new p(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await Lp(r.file,"utf8"),i=await b("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(c=>c.kind===o)?.subject;s={kind:o,subject:u,body:l}}let a=await b("cloud-project-auth",{ref:e,action:"template",template:s});g(a,()=>{Hi(a.templates),d(w(" Your auth service restarts with it within about a minute."))});return}throw new p(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var Bi=["confirmation","recovery","magic_link","invite","email_change"];function Hi(e){process.stdout.write(`${M([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?k("yours"):"ours",t.subject])])}
444
- `)}Y();W();se();import{spawn as Gp}from"node:child_process";import{createHash as qp}from"node:crypto";import{existsSync as Kp,readdirSync as Jp,readFileSync as zp,statSync as Wp}from"node:fs";import{isAbsolute as Vp,join as wo,relative as Yp,resolve as Xp}from"node:path";function Hp(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function qn(e,t,n={}){let r=Hp(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let c=o.get(u.name);if(!c){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}c.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,c)=>u<c?-1:u>c?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 Ji(e){let t=Yp(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Ft="_snoutdata_migrations",Zp=`create table if not exists ${Ft} (
483
+ `))}}}async function Qi(e,t={}){let n=t.since?nf(t.since):0,r=await $o(e,{channel:t.channel,since:n});return h({ref:r.ref,events:r.events,capacity:r.logCapacity},()=>{if(r.events.length===0){d(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(`${Xi(o)}
484
+ `);r.logCapacity!==null&&r.events.length>=r.logCapacity&&d(w(`The log keeps the newest ${r.logCapacity} events; older ones have gone.`))}),0}Q();he();Z();import{readFile as lf}from"node:fs/promises";var df="api.snoutdata.com";function nc(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}.${df}/auth/v1/callback`,anonymous:{enabled:t.anonymous?.enabled===!0,ready:t.anonymous?.ready===!0}}}async function uf(e){return nc(e,await S("cloud-project-auth",{ref:e,action:"status"}))}async function Vn(e,t){return nc(e,await S("cloud-project-auth",{ref:e,action:"settings",settings:t}))}function xo(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(`${B([["auth",e.enabled?$("on"):"off"],["site url",e.siteUrl??w("not set")],["redirects",e.redirects.length?e.redirects.join(", "):w("none")],["google",n],["google client id",t.clientId??w("not set")],["google callback",e.googleCallback],["guests",e.anonymous.ready?$("on"):e.anonymous.enabled?"switched on, not applied yet":"off"]])}
485
+ `),e.enabled||d(w(` snoutdata products enable auth --ref ${e.ref}`))}async function pf(){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 rc(e,t,n,r){if(t===void 0||t==="show"){let o=await uf(e);h(o,()=>xo(o));return}if(t==="google"){if(n[0]==="off"){let a=await Vn(e,{googleEnabled:!1});h(a,()=>d("Google sign-in is off. Your auth service restarts within about a minute."));return}if(n.length>0)throw new g('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 pf();if(!a)throw new g("--stdin read nothing. Pipe the Google client secret in.");o.googleClientSecret=a}let s=await Vn(e,o);h(s,()=>{s.google.ready?d("Google sign-in is on. Your auth service restarts within about a minute."):xo(s),d(w(` 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 g('auth anonymous takes "on" or "off"');let s=await Vn(e,{anonymousEnabled:o==="on"});h(s,()=>{o==="off"?d("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?d("Guest sign-in is on: guestSignIn() gives a browser a session with no email or password. Your auth service restarts within about a minute."):(d("Guest sign-in is switched on, and takes effect when auth is."),d(w(` 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 g("auth redirects needs --site-url and/or --allow URL,URL (an empty --allow= clears the list)");let s=await Vn(e,o);h(s,()=>xo(s));return}if(t==="templates"){let o=await S("cloud-project-auth",{ref:e,action:"templates"});h(o,()=>tc(o.templates));return}if(t==="template"){let o=n[0];if(!o||!ec.includes(o))throw new g(`auth template needs a kind: ${ec.join(", ")}`);let s;if(n[1]==="reset")s={kind:o,reset:!0};else{if(!r.file)throw new g(`auth template ${o} needs --file body.html (and --subject), or "reset"`);let l=await lf(r.file,"utf8"),i=await S("cloud-project-auth",{ref:e,action:"templates"}),u=r.subject??i.templates.find(c=>c.kind===o)?.subject;s={kind:o,subject:u,body:l}}let a=await S("cloud-project-auth",{ref:e,action:"template",template:s});h(a,()=>{tc(a.templates),d(w(" Your auth service restarts with it within about a minute."))});return}throw new g(`unknown command: auth ${t}. auth [google [off] | anonymous on|off | redirects | templates | template KIND]`)}var ec=["confirmation","recovery","magic_link","invite","email_change"];function tc(e){process.stdout.write(`${B([["KIND","SOURCE","SUBJECT"],...e.map(t=>[t.kind,t.custom?$("yours"):"ours",t.subject])])}
486
+ `)}Q();Z();pe();import{createHash as Nf}from"node:crypto";import{existsSync as Cf,readdirSync as jf,readFileSync as If,statSync as Df}from"node:fs";import{isAbsolute as Mf,join as Ro,relative as Lf,resolve as Uf}from"node:path";function mf(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Yn(e,t,n={}){let r=mf(e),o=new Map(r.map(u=>[u.name,u])),s=[];for(let u of t){let c=o.get(u.name);if(!c){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}c.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,c)=>u<c?-1:u>c?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 Kt(e){return!/^[\t ]*--[\t ]*snoutdata:no-transaction[\t ]*$/im.test(e)}import{createHash as Eo,createHmac as Xn,pbkdf2Sync as ff,randomBytes as gf,timingSafeEqual as hf}from"node:crypto";import{readFileSync as yf}from"node:fs";import{homedir as wf}from"node:os";import{connect as bf,isIP as Sf}from"node:net";import{connect as _f}from"node:tls";var Ve=class extends Error{severity;code;detail;hint;position;where;constructor(t){super(t.get("M")??"the server reported an error"),this.name="PgServerError",this.severity=t.get("V")??t.get("S")??"ERROR",this.code=t.get("C")??"";let n=t.get("D"),r=t.get("H"),o=Number(t.get("P")),s=t.get("W");n&&(this.detail=n),r&&(this.hint=r),Number.isInteger(o)&&o>0&&(this.position=o),s&&(this.where=s)}},re=class extends Error{constructor(t){super(t),this.name="PgConnectError"}},kf=196608,vf=80877103;function oc(e,t){let n=Buffer.alloc(5);return n.write(e,0,"latin1"),n.writeInt32BE(t.length+4,1),Buffer.concat([n,t])}function We(e){return Buffer.concat([Buffer.from(e,"utf8"),Buffer.from([0])])}function $f(e,t=0){let n=[],r=t;for(;r<e.length;){let o=e.indexOf(0,r);if(o<0||o===r)break;n.push(e.toString("utf8",r,o)),r=o+1}return n}function sc(e){let t=new Map,n=0;for(;n<e.length&&e[n]!==0;){let r=String.fromCharCode(e[n]),o=e.indexOf(0,n+1);if(o<0)break;t.set(r,e.toString("utf8",n+1,o)),n=o+1}return t}var Po=class{constructor(t,n=gf(18).toString("base64")){this.password=t;this.nonce=n,this.firstBare=`n=,r=${this.nonce}`}nonce;firstBare;serverSignature=null;first(){return`n,,${this.firstBare}`}final(t){let n=new Map(t.split(",").map(k=>[k.slice(0,1),k.slice(2)])),r=n.get("r"),o=n.get("s"),s=Number(n.get("i"));if(!r||!r.startsWith(this.nonce)||r.length<=this.nonce.length)throw new re("the server answered the password exchange with a nonce that is not ours");if(!o||!Number.isInteger(s)||s<1)throw new re("the server answered the password exchange with no salt or iteration count");let a=ff(this.password.normalize("NFKC"),Buffer.from(o,"base64"),s,32,"sha256"),l=Xn("sha256",a).update("Client Key").digest(),i=Eo("sha256").update(l).digest(),u=`c=biws,r=${r}`,c=`${this.firstBare},${t},${u}`,p=Xn("sha256",i).update(c).digest(),m=Buffer.alloc(l.length);for(let k=0;k<m.length;k++)m[k]=l[k]^p[k];let E=Xn("sha256",a).update("Server Key").digest();return this.serverSignature=Xn("sha256",E).update(c).digest(),`${u},p=${m.toString("base64")}`}verify(t){let n=/(?:^|,)e=([^,]*)/.exec(t);if(n)throw new re(`the server refused the password exchange: ${n[1]}`);let r=/(?:^|,)v=([^,]*)/.exec(t),o=r?Buffer.from(r[1],"base64"):Buffer.alloc(0);if(!this.serverSignature||o.length!==this.serverSignature.length||!hf(o,this.serverSignature))throw new re("the server could not prove it knows the password, so this is not the database it claims to be")}},Ao=class{buffer=Buffer.alloc(0);queue=[];waiting=null;failure=null;constructor(t){t.on("data",n=>this.take(n)),t.on("error",n=>this.fail(new re(n.message))),t.on("close",()=>this.fail(new re("the server closed the connection")))}take(t){for(this.buffer=this.buffer.length===0?t:Buffer.concat([this.buffer,t]);this.buffer.length>=5;){let n=this.buffer.readInt32BE(1);if(n<4){this.fail(new re("the server sent a message this client cannot read"));return}if(this.buffer.length<n+1)break;let r={type:String.fromCharCode(this.buffer[0]),body:this.buffer.subarray(5,n+1)};if(this.buffer=this.buffer.subarray(n+1),this.waiting){let{resolve:o}=this.waiting;this.waiting=null,o(r)}else this.queue.push(r)}}fail(t){if(!this.failure&&(this.failure=t,this.waiting)){let{reject:n}=this.waiting;this.waiting=null,n(t)}}next(){let t=this.queue.shift();return t?Promise.resolve(t):this.failure?Promise.reject(this.failure):new Promise((n,r)=>{this.waiting={resolve:n,reject:r}})}};function xf(e){return new Promise((t,n)=>{let r=l=>{if(a(),l.length!==1){n(new re("the server sent more than its answer to the TLS request, so the connection was closed"));return}t(String.fromCharCode(l[0]))},o=l=>{a(),n(new re(l.message))},s=()=>{a(),n(new re("the server closed the connection before TLS"))},a=()=>{e.off("data",r),e.off("error",o),e.off("close",s)};e.on("data",r),e.on("error",o),e.on("close",s)})}function Ef(e,t){return new Promise((n,r)=>{let o=bf({host:e,port:t}),s=a=>r(new re(`could not connect to ${e}:${t}: ${a.message}`));o.once("connect",()=>{o.off("error",s),n(o)}),o.once("error",s)})}async function Pf(e,t,n,r){let o=await Ef(e.host,e.port);if(r(o),t==="disable")return o;let s=Buffer.alloc(8);if(s.writeInt32BE(8,0),s.writeInt32BE(vf,4),o.write(s),await xf(o)!=="S"){if(t==="allow"||t==="prefer")return o;throw new re(`${e.host} does not accept TLS, and this connection requires it (sslmode ${t})`)}let l=t==="verify-ca"||t==="verify-full";return new Promise((i,u)=>{let c=_f({socket:o,host:e.host,...Sf(e.host)===0?{servername:e.host}:{},rejectUnauthorized:l,...l&&n?{ca:yf(n,"utf8")}:{},...t==="verify-ca"?{checkServerIdentity:()=>{}}:{}});r(c),c.once("secureConnect",()=>{c.off("error",p),i(c)});let p=m=>u(new re(`TLS to ${e.host} failed: ${m.message}`));c.once("error",p)})}var Zn=class e{constructor(t,n){this.socket=t;this.reader=n}static async open(t,n={}){let r=n.env??process.env,o=Ht(t.ssl,r,n.home??wf()),s=o.rootCert??(r.PGSSLROOTCERT||void 0),a=o.mode==="require"&&s?"verify-ca":o.mode,l=n.connectTimeoutMs??12e4,i=[],u=()=>{for(let m of i)m.destroy()},c,p=new Promise((m,E)=>{c=setTimeout(()=>{u(),E(new re(`${t.host}:${t.port} did not answer within ${Math.round(l/1e3)} seconds`))},l)});try{return await Promise.race([(async()=>{let m=await Pf(t,a,s,k=>i.push(k)),E=new e(m,new Ao(m));return await E.startup(t),E})(),p])}catch(m){throw u(),m}finally{clearTimeout(c)}}send(t,n){this.socket.write(oc(t,n))}async startup(t){let n=Buffer.concat([...[["user",t.user],["database",t.database],["application_name","snoutdata"],["client_encoding","UTF8"]].flatMap(([s,a])=>[We(s),We(a)]),Buffer.from([0])]),r=Buffer.alloc(8);r.writeInt32BE(n.length+8,0),r.writeInt32BE(kf,4),this.socket.write(Buffer.concat([r,n]));let o=null;for(;;){let s=await this.reader.next();switch(s.type){case"R":{let a=s.body.readInt32BE(0);if(a===0)break;if(a===3)this.send("p",We(t.password));else if(a===5){let l=Eo("md5").update(t.password+t.user).digest("hex"),i=Eo("md5").update(Buffer.concat([Buffer.from(l),s.body.subarray(4,8)])).digest("hex");this.send("p",We(`md5${i}`))}else if(a===10){let l=$f(s.body,4);if(!l.includes("SCRAM-SHA-256"))throw new re(`the server asked for ${l.join(", ")||"a password exchange"}, which this client does not speak`);o=new Po(t.password);let i=Buffer.from(o.first(),"utf8"),u=Buffer.alloc(4);u.writeInt32BE(i.length,0),this.send("p",Buffer.concat([We("SCRAM-SHA-256"),u,i]))}else if(a===11&&o)this.send("p",Buffer.from(o.final(s.body.toString("utf8",4)),"utf8"));else if(a===12&&o)o.verify(s.body.toString("utf8",4));else throw new re(`the server asked for an authentication method (${a}) this client does not speak`);break}case"E":throw new Ve(sc(s.body));case"Z":return;default:break}}}async query(t,n){this.send("Q",We(t));let r=[],o=null,s=null;for(;;){let a=await this.reader.next();switch(a.type){case"T":r=[];break;case"D":{let l=a.body.readInt16BE(0),i=[],u=2;for(let c=0;c<l;c++){let p=a.body.readInt32BE(u);u+=4,p<0?i.push(null):(i.push(a.body.toString("utf8",u,u+p)),u+=p)}r.push(i);break}case"C":o=a.body.toString("utf8",0,Math.max(0,a.body.indexOf(0)));break;case"E":s??=new Ve(sc(a.body));break;case"G":n===void 0?this.send("f",We("db push has no rows for this COPY: the lines after it, ending in \\., are what it reads")):(n.length>0&&this.send("d",Buffer.from(n,"utf8")),this.send("c",Buffer.alloc(0)));break;case"W":this.send("f",We("COPY BOTH is not something a migration can do"));break;case"Z":if(s)throw s;return{rows:r,command:o};default:break}}}close(){this.socket.destroyed||this.socket.end(oc("X",Buffer.alloc(0)))}};function ac(e,t,n=1){let r=[`${e.severity}: ${e.message}`];if(e.position!==void 0){let o=[...t].slice(0,e.position-1),s=o.filter(u=>u===`
487
+ `).length,a=t.split(`
488
+ `)[s]??"",l=o.length-(o.lastIndexOf(`
489
+ `)+1),i=`LINE ${n+s}: `;r.push(`${i}${a.replace(/\r$/,"")}`,`${" ".repeat(i.length+l)}^`)}return e.detail&&r.push(`DETAIL: ${e.detail}`),e.hint&&r.push(`HINT: ${e.hint}`),e.where&&r.push(`CONTEXT: ${e.where}`),r.join(`
490
+ `)}var Af=/[A-Za-z0-9_$\u0080-￿]/,Rf=/[A-Za-z_\u0080-￿]/,Tf=/^\$(?:[A-Za-z_\u0080-￿][A-Za-z0-9_\u0080-￿]*)?\$/;function Of(e){let t=ic(e);return/^\s*copy\b/i.test(t)&&/\bfrom\s+stdin\b/i.test(t)}function ic(e){return e.replace(/\/\*[\s\S]*?\*\//g," ").replace(/--[^\n]*/g," ")}function cc(e){let t=[],n=[],r=e.length,o=0,s=1,a=-1,l=1,i=0,u=0,c=[],p=0,m=()=>{a<0&&(a=o,l=s)},E=()=>{a=-1,i=0,u=0,c=[],p=0},k=I=>{let ce=Math.min(r,o+I);for(let G=o;G<ce;G++)e.charCodeAt(G)===10&&s++;o=ce},X=I=>k(I<0?r-o:I-o),j=null,H=()=>{let I=a>=0?e.slice(a,o).trim():"";if(ic(I).replace(/;/g,"").trim()!=="")if(!Of(I))t.push({sql:I,line:l});else{let ce=e.indexOf(`
491
+ `,o),G=[];if(ce>=0){let f=ce+1,y=s+1;for(;f<r;){let b=e.indexOf(`
492
+ `,f),O=e.slice(f,b<0?r:b).replace(/\r$/,"");if(f=b<0?r:b+1,y++,O==="\\.")break;G.push(`${O}
493
+ `)}j={from:ce+1,to:f,line:y}}t.push({sql:I,line:l,copyData:G.join("")})}E()};for(;o<r;){if(j&&o>=j.from){o=j.to,s=j.line,j=null;continue}let I=e[o],ce=e[o+1];if(I===`
494
+ `||I===" "||I===" "||I==="\r"||I==="\f"||I==="\v"){k(1);continue}if(I==="-"&&ce==="-"){X(e.indexOf(`
495
+ `,o));continue}if(I==="/"&&ce==="*"){m();let G=0;for(;o<r;)if(e[o]==="/"&&e[o+1]==="*")G++,k(2);else if(e[o]==="*"&&e[o+1]==="/"){if(G--,k(2),G===0)break}else k(1);continue}if(I==="'"){m(),oe(!1);continue}if(I==='"'){for(m(),k(1);;){let G=e.indexOf('"',o);if(G<0){X(-1);break}if(X(G+1),e[o]==='"'){k(1);continue}break}continue}if(I==="$"){m();let G=Tf.exec(e.slice(o,o+64))?.[0];if(G){k(G.length);let f=e.indexOf(G,o);X(f<0?-1:f+G.length)}else k(1);continue}if(I==="\\"){let G=e.indexOf(`
496
+ `,o);n.push({line:s,text:e.slice(o,G<0?r:G).replace(/\r$/,"").trimEnd()}),X(G);continue}if(I==="("){m(),i++,k(1);continue}if(I===")"){m(),i>0&&i--,k(1);continue}if(I===";"){m(),k(1),i===0&&u===0&&H();continue}if(Rf.test(I)){m();let G=o+1;for(;G<r&&Af.test(e[G]);)G++;let f=e.slice(o,G);if(k(G-o),e[o]==="'"){oe(f==="e"||f==="E");continue}ne(f.toLowerCase());continue}if(I>="0"&&I<="9"){m();let G=o+1;for(;G<r&&/[0-9A-Za-z_.]/.test(e[G]);)G++;k(G-o);continue}m(),k(1)}return H(),{statements:t,metaCommands:n};function oe(I){for(k(1);o<r;){let ce=e[o];if(I&&ce==="\\"){k(2);continue}if(ce==="'"){if(k(1),e[o]==="'"){k(1);continue}return}k(1)}}function ne(I){p<4&&["create","function","procedure","or","replace"].includes(I)&&(c[p]=I[0]),p++,!(!(c[0]==="c"&&(c[1]==="f"||c[1]==="p"||c[1]==="o"&&c[2]==="r"&&(c[3]==="f"||c[3]==="p")))||i!==0)&&(I==="begin"?u++:I==="case"?u>=1&&u++:I==="end"&&u>0&&u--)}}function dc(e){let t=Lf(process.cwd(),e);return t&&!t.startsWith("..")?t:e}var Qn="_snoutdata_migrations",Ff=`create table if not exists ${Qn} (
445
497
  name text primary key,
446
498
  checksum text not null,
447
499
  applied_at timestamptz not null default now()
448
- );`;function Qp(e){if(!Kp(e)||!Wp(e).isDirectory())throw new Error(`no migrations folder at ${Ji(e)}. Put your .sql files there, or pass --dir.`);return Jp(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=wo(e,t),r=zp(n,"utf8");return{name:t,path:n,sql:r,checksum:qp("sha256").update(r,"utf8").digest("hex")}})}function bo(e,t,n=[]){return new Promise(r=>{let o=Gp("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,...et(e.ssl)}}),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 ef(e){let t=await bo(e,Zp);if(t.code!==0)throw new Error(t.err.trim()||`could not create ${Ft}`);let n=await bo(e,`select name || E'\\t' || checksum from ${Ft};`,["-tA"]);if(n.code!==0)throw new Error(n.err.trim()||`could not read ${Ft}`);return n.out.split(`
449
- `).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 Ki(e){return`'${e.replace(/'/g,"''")}'`}async function zi(e,t={}){let n=await _o(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
450
- `);I("conflict",`Refused. This folder and ${e} do not agree:
451
- ${r}`,{ref:e,problems:n.problems})}I("failed",`${n.failed} failed, and nothing after it was tried.
452
- ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return g(n,()=>{if(n.wouldApply){d(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)d(` ${r}`);return}if(n.upToDate){d(`${e} is up to date.`);return}d(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function _o(e,t={},n=()=>{}){let r=Xp(t.dir===void 0?wo(process.cwd(),"migrations"):Vp(t.dir)?t.dir:wo(process.cwd(),t.dir)),o=[];function s(x){o.push(x),n(x)}let a=Qp(r);if(a.length===0)return s(`No .sql files in ${Ji(r)}.`),{ref:e,directory:r,ok:!0,applied:[],upToDate:!0,notes:o};let l=await pe(e);l.wakesInstantly||s("This project is paused; the connection will wake it, which takes a few seconds.");let i=await ef(l),u=qn(a,i,{outOfOrder:t.outOfOrder});if(!u.ok)return{ref:e,directory:r,ok:!1,applied:[],problems:[...u.problems],notes:o};let c=new Map(a.map(x=>[x.name,x])),m=u.pending.map(x=>c.get(x.name));if(m.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 x of m)Ut(x.sql)||s(`${x.name} would run outside a transaction.`);return{ref:e,directory:r,ok:!0,applied:[],wouldApply:m.map(x=>x.name),notes:o}}let h=[];for(let x of m){let L=`insert into ${Ft} (name, checksum) values (${Ki(x.name)}, ${Ki(x.checksum)});`,V=Ut(x.sql);V||s(`${x.name} runs outside a transaction, so an interruption can leave it half applied.`);let F=V?`begin;
453
- ${x.sql}
454
- ${L}
455
- commit;
456
- `:`${x.sql}
457
- ${L}
458
- `,de=await bo(l,F);if(de.code!==0)return V&&s("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:r,ok:!1,applied:h,failed:x.name,error:de.err.trim(),notes:o};h.push(x.name),s(` applied ${x.name}`)}return{ref:e,directory:r,ok:!0,applied:h,notes:o}}Y();ue();W();import{readFile as Wi}from"node:fs/promises";function So(e){if(e==="apns"||e==="fcm")return e;throw new p("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function tf(e){let t=await b("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function Vi(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new p("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 p("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await Wi(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function Yi(e){if(!e)throw new p("push credentials set fcm needs --file service-account.json");return{service_account:await Wi(e,"utf8")}}async function nf(e,t,n){let r=await b("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function rf(e,t){return await b("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function Xi(e){let t=await tf(e);g(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${M([["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}`:""]])}
459
- `),(!n||!r)&&d(w(` 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 Zi(e,t,n){let r=await nf(e,t,n);g(r,()=>d(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function Qi(e,t){let n=await rf(e,t);g(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}Y();se();W();import{spawn as wf}from"node:child_process";import{writeFileSync as bf}from"node:fs";import{isAbsolute as _f,resolve as Sf}from"node:path";var sf=" ";function N(e){return sf.repeat(e)}function ie(e){return JSON.stringify(e)}function Te(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:ie(e)}function tt(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function af(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 cf={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"},lf={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function df(e,t){let n=lf[e]?.[t];return n||(cf[t]??"unknown")}function Kn(e,t){let{base:n,array:r}=af(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=df(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function uf(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function ko(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${N(n)}${Te(t.name)}?: never`;let s=Kn(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${N(n)}${Te(t.name)}${r?"?":""}: ${a}`}function mf(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 pf(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(`${N(t)}Relationships: []`),n;n.push(`${N(t)}Relationships: [`);for(let o of r)n.push(`${N(t+1)}{`),n.push(`${N(t+2)}foreignKeyName: ${ie(o.constraintName)}`),n.push(`${N(t+2)}columns: [${o.columns.map(ie).join(", ")}]`),n.push(`${N(t+2)}isOneToOne: ${mf(e,o)?"true":"false"}`),n.push(`${N(t+2)}referencedRelation: ${ie(o.referencedTable)}`),n.push(`${N(t+2)}referencedColumns: [${o.referencedColumns.map(ie).join(", ")}]`),n.push(`${N(t+1)}},`);return n.push(`${N(t)}]`),n}function ec(e,t,n){let r=tt(t.columns),o=[];o.push(`${N(n)}${Te(t.name)}: {`),o.push(`${N(n+1)}Row: {`);for(let s of r)o.push(ko(e,s,n+2,!1));if(o.push(`${N(n+1)}}`),t.kind!=="view"){o.push(`${N(n+1)}Insert: {`);for(let s of r)o.push(ko(e,s,n+2,uf(s),!0));o.push(`${N(n+1)}}`),o.push(`${N(n+1)}Update: {`);for(let s of r)o.push(ko(e,s,n+2,!0,!0));o.push(`${N(n+1)}}`)}return o.push(...pf(t,n+1)),o.push(`${N(n)}}`),o}function Bt(e,t){return[`${N(t)}${e}: {`,`${N(t+1)}[_ in never]: never`,`${N(t)}}`]}function ff(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}[]`:`${Kn(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??Kn(e,n)}function gf(e,t,n){let r=[],o=tt((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${N(n)}${Te(t.name)}: {`),o.length===0)r.push(`${N(n+1)}Args: never`);else{r.push(`${N(n+1)}Args: {`);for(let s of o)r.push(`${N(n+2)}${Te(s.name)}: ${Kn(e,s.dataType)}`);r.push(`${N(n+1)}}`)}return r.push(`${N(n+1)}Returns: ${ff(e,t)}`),r.push(`${N(n)}}`),r}function ft(e,t){return e.schema&&e.schema.length>0?e.schema:t}function tc(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let m of o){let h=m.schema&&m.schema.length>0?m.schema:n;s.set(m.name.toLowerCase(),`Database[${ie(h)}][${ie("Enums")}][${ie(m.name)}]`)}let a=new Map;for(let m of e.tables){let h=ft(m,n),x=m.kind==="view"?"Views":"Tables";a.set(m.name.toLowerCase(),`Database[${ie(h)}][${ie(x)}][${ie(m.name)}][${ie("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let m of e.tables)i.add(ft(m,n));for(let m of e.routines??[])i.add(ft(m,n));for(let m of o)i.add(m.schema&&m.schema.length>0?m.schema:n);let u=[...i].sort(),c=[];c.push("export type Json ="),c.push(" | string"),c.push(" | number"),c.push(" | boolean"),c.push(" | null"),c.push(" | { [key: string]: Json | undefined }"),c.push(" | Json[]"),c.push(""),c.push("export type Database = {");for(let m of u){let h=tt(e.tables.filter(F=>ft(F,n)===m&&F.kind!=="view")),x=tt(e.tables.filter(F=>ft(F,n)===m&&F.kind==="view")),L=tt((e.routines??[]).filter(F=>ft(F,n)===m)),V=tt(o.filter(F=>(F.schema&&F.schema.length>0?F.schema:n)===m));if(c.push(`${N(1)}${Te(m)}: {`),h.length===0)c.push(...Bt("Tables",2));else{c.push(`${N(2)}Tables: {`);for(let F of h)c.push(...ec(l,F,3));c.push(`${N(2)}}`)}if(x.length===0)c.push(...Bt("Views",2));else{c.push(`${N(2)}Views: {`);for(let F of x)c.push(...ec(l,F,3));c.push(`${N(2)}}`)}if(L.length===0)c.push(...Bt("Functions",2));else{c.push(`${N(2)}Functions: {`);for(let F of L)c.push(...gf(l,F,3));c.push(`${N(2)}}`)}if(V.length===0)c.push(...Bt("Enums",2));else{c.push(`${N(2)}Enums: {`);for(let F of V)c.push(`${N(3)}${Te(F.name)}: ${F.values.map(ie).join(" | ")}`);c.push(`${N(2)}}`)}c.push(...Bt("CompositeTypes",2)),c.push(`${N(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(yf(n))),t.constants!==!1&&(c.push(""),c.push(hf(u,o,n))),`${c.join(`
500
+ );`;function Bf(e){if(!Cf(e)||!Df(e).isDirectory())throw new Error(`no migrations folder at ${dc(e)}. Put your .sql files there, or pass --dir.`);return jf(e).filter(t=>t.endsWith(".sql")).map(t=>{let n=Ro(e,t),r=If(n,"utf8");return{name:t,path:n,sql:r,checksum:Nf("sha256").update(r,"utf8").digest("hex")}})}var Hf={connection:e=>we(e),open:e=>Zn.open(e)};function uc(e,t){throw e instanceof re?new v("network",`Could not reach ${t}'s database: ${e.message}.`):e instanceof Ve?new v("failed",`${t}'s database refused the connection: ${e.message}`):e}async function pc(e,t,n){try{return await e.open(t)}catch(r){return uc(r,n)}}async function qf(e,t,n){let r=await pc(e,t,n);try{await r.query(Ff);let{rows:o}=await r.query(`select name, checksum from ${Qn};`);return o.map(([s,a])=>({name:s??"",checksum:a??""}))}catch(o){if(o instanceof Ve)throw new v("failed",`Could not read ${Qn} on ${n}: ${o.message}`);return uc(o,n)}finally{r.close()}}async function Gf(e,t,n){for(let r of t)try{await e.query(r.sql,r.copyData)}catch(o){if(o instanceof Ve)return`${r.line>0?`${n}:${r.line}: `:""}${ac(o,r.sql,r.line>0?r.line:1)}`;if(o instanceof re)return`the connection was lost: ${o.message}`;throw o}return null}function lc(e){return`'${e.replace(/'/g,"''")}'`}async function mc(e,t={}){let n=await To(e,t,r=>d(r));if(!n.ok){if(n.problems){let r=n.problems.map(o=>` ${o.reason}`).join(`
501
+ `);n.problems.every(o=>o.code==="psql-command")&&L("failed",`Refused, and nothing ran:
502
+ ${r}`,{ref:e,problems:n.problems}),L("conflict",`Refused. This folder and ${e} do not agree:
503
+ ${r}`,{ref:e,problems:n.problems})}L("failed",`${n.failed} failed, and nothing after it was tried.
504
+ ${n.error??""}`.trimEnd(),{ref:e,failed:n.failed})}return h(n,()=>{if(n.wouldApply){d(`Would apply ${n.wouldApply.length} migration${n.wouldApply.length===1?"":"s"} to ${e}:`);for(let r of n.wouldApply)d(` ${r}`);return}if(n.upToDate){d(`${e} is up to date.`);return}d(`Applied ${n.applied.length} migration${n.applied.length===1?"":"s"} to ${e}.`)}),0}async function To(e,t={},n=()=>{},r=Hf){let o=Uf(t.dir===void 0?Ro(process.cwd(),"migrations"):Mf(t.dir)?t.dir:Ro(process.cwd(),t.dir)),s=[];function a(H){s.push(H),n(H)}let l=Bf(o);if(l.length===0)return a(`No .sql files in ${dc(o)}.`),{ref:e,directory:o,ok:!0,applied:[],upToDate:!0,notes:s};let i=await r.connection(e),u={host:i.host,port:i.port,database:i.database,user:i.user,password:i.password,ssl:i.ssl};i.wakesInstantly||a("This project is paused; the connection will wake it, which takes a few seconds.");let c=await qf(r,u,e),p=Yn(l,c,{outOfOrder:t.outOfOrder});if(!p.ok)return{ref:e,directory:o,ok:!1,applied:[],problems:[...p.problems],notes:s};let m=new Map(l.map(H=>[H.name,H])),E=p.pending.map(H=>m.get(H.name)),k=new Map(E.map(H=>[H.name,cc(H.sql)])),X=E.flatMap(H=>k.get(H.name).metaCommands.map(oe=>({code:"psql-command",name:H.name,reason:`${H.name} line ${oe.line} is a psql command (${oe.text.length>60?`${oe.text.slice(0,57)}...`:oe.text}), which only psql runs. db push sends SQL to the database itself, so put the SQL it stands for in the file instead.`})));if(X.length>0)return{ref:e,directory:o,ok:!1,applied:[],problems:X,notes:s};if(E.length===0)return a(`${e} is up to date: ${c.length} migration${c.length===1?"":"s"} already applied.`),{ref:e,directory:o,ok:!0,applied:[],upToDate:!0,notes:s};if(t.dryRun){for(let H of E)Kt(H.sql)||a(`${H.name} would run outside a transaction.`);return{ref:e,directory:o,ok:!0,applied:[],wouldApply:E.map(H=>H.name),notes:s}}let j=[];for(let H of E){let oe=`insert into ${Qn} (name, checksum) values (${lc(H.name)}, ${lc(H.checksum)});`,ne=Kt(H.sql);ne||a(`${H.name} runs outside a transaction, so an interruption can leave it half applied.`);let I=[...ne?[{sql:"begin;",line:0}]:[],...k.get(H.name).statements,{sql:oe,line:0},...ne?[{sql:"commit;",line:0}]:[]],ce=await pc(r,u,e),G;try{G=await Gf(ce,I,H.name)}finally{ce.close()}if(G!==null)return ne&&a("It ran inside a transaction, so nothing from it is in the database."),{ref:e,directory:o,ok:!1,applied:j,failed:H.name,error:G,notes:s};j.push(H.name),a(` applied ${H.name}`)}return{ref:e,directory:o,ok:!0,applied:j,notes:s}}Q();he();Z();import{readFile as fc}from"node:fs/promises";function Oo(e){if(e==="apns"||e==="fcm")return e;throw new g("say which: apns (an Apple .p8) or fcm (a Firebase service account)")}async function Kf(e){let t=await S("cloud-project-push",{ref:e,credentials:{list:!0}});return{ref:e,credentials:t??{}}}async function gc(e){if(!e.p8||!e.keyId||!e.teamId||!e.topic)throw new g("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 g("--environment is production or sandbox; leave it out for a key that serves both");return{topic:e.topic,keys:[{p8:await fc(e.p8,"utf8"),key_id:e.keyId,team_id:e.teamId,environment:e.environment??null}]}}async function hc(e){if(!e)throw new g("push credentials set fcm needs --file service-account.json");return{service_account:await fc(e,"utf8")}}async function Jf(e,t,n){let r=await S("cloud-project-push",{ref:e,credentials:{set:{kind:t,body:n}}});return{ref:e,kind:t,summary:r}}async function zf(e,t){return await S("cloud-project-push",{ref:e,credentials:{remove:t}}),{ref:e,kind:t,removed:!0}}async function yc(e){let t=await Kf(e);h(t,()=>{let{apns:n,fcm:r,vapid:o}=t.credentials;process.stdout.write(`${B([["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}`:""]])}
505
+ `),(!n||!r)&&d(w(` 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 wc(e,t,n){let r=await Jf(e,t,n);h(r,()=>d(`${t==="apns"?"The Apple key signed":"Google accepted the service account"}, and it is stored in the project's own database.`))}async function bc(e,t){let n=await zf(e,t);h(n,()=>d(`${t} credentials removed. Devices on it stop receiving until new ones are set.`))}Q();pe();Z();import{spawn as ig}from"node:child_process";import{writeFileSync as cg}from"node:fs";import{isAbsolute as lg,resolve as dg}from"node:path";var Vf=" ";function M(e){return Vf.repeat(e)}function fe(e){return JSON.stringify(e)}function je(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)?e:fe(e)}function st(e){return[...e].sort((t,n)=>t.name<n.name?-1:t.name>n.name?1:0)}function Yf(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 Xf={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"},Zf={sqlserver:{bit:"boolean"},"mysql-aurora":{bit:"number"},mariadb:{bit:"number"},sqlite:{bit:"number"},snowflake:{array:"json"}};function Qf(e,t){let n=Zf[e]?.[t];return n||(Xf[t]??"unknown")}function er(e,t){let{base:n,array:r}=Yf(t),o=e.enumRefs.get(n);if(o)return r?`${o}[]`:o;if(n==="array")return"unknown[]";let s=Qf(e.driverId,n),a=s==="json"?"Json":s;return r?`${a}[]`:a}function eg(e){return e.nullable||e.autoIncrement===!0||e.default!==null&&e.default!==void 0}function No(e,t,n,r,o=!1){if(o&&t.generatedAlways===!0)return`${M(n)}${je(t.name)}?: never`;let s=er(e,t.dataType),a=t.nullable?`${s} | null`:s;return`${M(n)}${je(t.name)}${r?"?":""}: ${a}`}function tg(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 ng(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(`${M(t)}Relationships: []`),n;n.push(`${M(t)}Relationships: [`);for(let o of r)n.push(`${M(t+1)}{`),n.push(`${M(t+2)}foreignKeyName: ${fe(o.constraintName)}`),n.push(`${M(t+2)}columns: [${o.columns.map(fe).join(", ")}]`),n.push(`${M(t+2)}isOneToOne: ${tg(e,o)?"true":"false"}`),n.push(`${M(t+2)}referencedRelation: ${fe(o.referencedTable)}`),n.push(`${M(t+2)}referencedColumns: [${o.referencedColumns.map(fe).join(", ")}]`),n.push(`${M(t+1)}},`);return n.push(`${M(t)}]`),n}function Sc(e,t,n){let r=st(t.columns),o=[];o.push(`${M(n)}${je(t.name)}: {`),o.push(`${M(n+1)}Row: {`);for(let s of r)o.push(No(e,s,n+2,!1));if(o.push(`${M(n+1)}}`),t.kind!=="view"){o.push(`${M(n+1)}Insert: {`);for(let s of r)o.push(No(e,s,n+2,eg(s),!0));o.push(`${M(n+1)}}`),o.push(`${M(n+1)}Update: {`);for(let s of r)o.push(No(e,s,n+2,!0,!0));o.push(`${M(n+1)}}`)}return o.push(...ng(t,n+1)),o.push(`${M(n)}}`),o}function Jt(e,t){return[`${M(t)}${e}: {`,`${M(t+1)}[_ in never]: never`,`${M(t)}}`]}function rg(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}[]`:`${er(e,s)}[]`}return r==="record"||r==="trigger"||r==="event_trigger"?"unknown":e.rowRefs.get(r)??er(e,n)}function og(e,t,n){let r=[],o=st((t.parameters??[]).filter(s=>s.name.length>0&&(s.mode??"IN").toUpperCase()!=="OUT").map(s=>({name:s.name,dataType:s.dataType})));if(r.push(`${M(n)}${je(t.name)}: {`),o.length===0)r.push(`${M(n+1)}Args: never`);else{r.push(`${M(n+1)}Args: {`);for(let s of o)r.push(`${M(n+2)}${je(s.name)}: ${er(e,s.dataType)}`);r.push(`${M(n+1)}}`)}return r.push(`${M(n+1)}Returns: ${rg(e,t)}`),r.push(`${M(n)}}`),r}function bt(e,t){return e.schema&&e.schema.length>0?e.schema:t}function _c(e,t={}){let n=t.defaultSchema??"public",r=t.driverId??"postgres",o=t.enums??[],s=new Map;for(let p of o){let m=p.schema&&p.schema.length>0?p.schema:n;s.set(p.name.toLowerCase(),`Database[${fe(m)}][${fe("Enums")}][${fe(p.name)}]`)}let a=new Map;for(let p of e.tables){let m=bt(p,n),E=p.kind==="view"?"Views":"Tables";a.set(p.name.toLowerCase(),`Database[${fe(m)}][${fe(E)}][${fe(p.name)}][${fe("Row")}]`)}let l={driverId:r,defaultSchema:n,enumRefs:s,rowRefs:a},i=new Set([n]);for(let p of e.tables)i.add(bt(p,n));for(let p of e.routines??[])i.add(bt(p,n));for(let p of o)i.add(p.schema&&p.schema.length>0?p.schema:n);let u=[...i].sort(),c=[];c.push("export type Json ="),c.push(" | string"),c.push(" | number"),c.push(" | boolean"),c.push(" | null"),c.push(" | { [key: string]: Json | undefined }"),c.push(" | Json[]"),c.push(""),c.push("export type Database = {");for(let p of u){let m=st(e.tables.filter(j=>bt(j,n)===p&&j.kind!=="view")),E=st(e.tables.filter(j=>bt(j,n)===p&&j.kind==="view")),k=st((e.routines??[]).filter(j=>bt(j,n)===p)),X=st(o.filter(j=>(j.schema&&j.schema.length>0?j.schema:n)===p));if(c.push(`${M(1)}${je(p)}: {`),m.length===0)c.push(...Jt("Tables",2));else{c.push(`${M(2)}Tables: {`);for(let j of m)c.push(...Sc(l,j,3));c.push(`${M(2)}}`)}if(E.length===0)c.push(...Jt("Views",2));else{c.push(`${M(2)}Views: {`);for(let j of E)c.push(...Sc(l,j,3));c.push(`${M(2)}}`)}if(k.length===0)c.push(...Jt("Functions",2));else{c.push(`${M(2)}Functions: {`);for(let j of k)c.push(...og(l,j,3));c.push(`${M(2)}}`)}if(X.length===0)c.push(...Jt("Enums",2));else{c.push(`${M(2)}Enums: {`);for(let j of X)c.push(`${M(3)}${je(j.name)}: ${j.values.map(fe).join(" | ")}`);c.push(`${M(2)}}`)}c.push(...Jt("CompositeTypes",2)),c.push(`${M(1)}}`)}return c.push("}"),t.helpers!==!1&&(c.push(""),c.push(ag(n))),t.constants!==!1&&(c.push(""),c.push(sg(u,o,n))),`${c.join(`
460
506
  `)}
461
- `}function hf(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=tt(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${N(1)}${Te(o)}: {`),r.push(`${N(2)}Enums: {},`),r.push(`${N(1)}},`);continue}r.push(`${N(1)}${Te(o)}: {`),r.push(`${N(2)}Enums: {`);for(let a of s)r.push(`${N(3)}${Te(a.name)}: [${a.values.map(ie).join(", ")}],`);r.push(`${N(2)}},`),r.push(`${N(1)}},`)}return r.push("} as const"),r.join(`
462
- `)}function yf(e){return`type DefaultSchemaName = Extract<keyof Database, ${ie(e)}>
507
+ `}function sg(e,t,n){let r=["export const Constants = {"];for(let o of e){let s=st(t.filter(a=>(a.schema&&a.schema.length>0?a.schema:n)===o));if(s.length===0){r.push(`${M(1)}${je(o)}: {`),r.push(`${M(2)}Enums: {},`),r.push(`${M(1)}},`);continue}r.push(`${M(1)}${je(o)}: {`),r.push(`${M(2)}Enums: {`);for(let a of s)r.push(`${M(3)}${je(a.name)}: [${a.values.map(fe).join(", ")}],`);r.push(`${M(2)}},`),r.push(`${M(1)}},`)}return r.push("} as const"),r.join(`
508
+ `)}function ag(e){return`type DefaultSchemaName = Extract<keyof Database, ${fe(e)}>
463
509
 
464
510
  type InSchema = { schema: keyof Database }
465
511
 
@@ -496,7 +542,7 @@ export type Enums<
496
542
  export type CompositeTypes<
497
543
  Choice extends keyof Database[DefaultSchemaName]["CompositeTypes"] | InSchema,
498
544
  Name extends Choice extends InSchema ? keyof Database[Choice["schema"]]["CompositeTypes"] : never = never,
499
- > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function kf(e){let t=po(e.ssl);return{host:e.host,port:e.port,database:e.database,user:e.user,password:e.password,sslMode:t.mode,...t.rootCert?{sslRootCert:t.rootCert}:{},label:e.ref}}function Jn(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 vo(e,t){return new Promise(n=>{let r=wf("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,...e.sslRootCert?{PGSSLROOTCERT:e.sslRootCert}:{}}}),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 vf=`
545
+ > = Lookup<Database[SchemaOf<Choice>]["CompositeTypes"], NameOf<Choice, Name>>`}function ug(e){let t=Ht(e.ssl);return{host:e.host,port:e.port,database:e.database,user:e.user,password:e.password,sslMode:t.mode,...t.rootCert?{sslRootCert:t.rootCert}:{},label:e.ref}}function tr(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 Co(e,t){return new Promise(n=>{let r=ig("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,...e.sslRootCert?{PGSSLROOTCERT:e.sslRootCert}:{}}}),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 pg=`
500
546
  with wanted as (select unnest($SCHEMAS$::text[]) as nspname)
501
547
  select json_build_object(
502
548
  'tables', coalesce((
@@ -600,40 +646,40 @@ select json_build_object(
600
646
  where t.typtype = 'e' and n.nspname in (select nspname from wanted)
601
647
  ), '[]'::json)
602
648
  )::text
603
- `;function xf(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function $f(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),u=a.columns.map(c=>({name:c.name,dataType:c.dataType,nullable:c.nullable,isPrimaryKey:i.has(c.name),default:c.default,autoIncrement:c.autoIncrement,...c.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(c=>({constraintName:c.constraintName,columns:c.columns??[],referencedTable:c.referencedTable,referencedColumns:c.referencedColumns??[]})),indexes:(a.indexes??[]).filter(c=>Array.isArray(c.columns)&&c.columns.every(m=>typeof m=="string")).map(c=>({name:c.name,unique:c.unique,columns:c.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}}var Ef=`select exists (
649
+ `;function mg(e){return`'{${e.map(n=>`"${n.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(",").replace(/'/g,"''")}}'`}function fg(e,t,n=0){let r=e.tables.map(a=>{let l=a.primaryKey??[],i=new Set(l),u=a.columns.map(c=>({name:c.name,dataType:c.dataType,nullable:c.nullable,isPrimaryKey:i.has(c.name),default:c.default,autoIncrement:c.autoIncrement,...c.generatedAlways===!0?{generatedAlways:!0}:{}}));return{name:a.name,schema:a.schema,kind:a.kind,columns:u,primaryKey:l,foreignKeys:(a.foreignKeys??[]).map(c=>({constraintName:c.constraintName,columns:c.columns??[],referencedTable:c.referencedTable,referencedColumns:c.referencedColumns??[]})),indexes:(a.indexes??[]).filter(c=>Array.isArray(c.columns)&&c.columns.every(p=>typeof p=="string")).map(c=>({name:c.name,unique:c.unique,columns:c.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}}var gg=`select exists (
604
650
  select from pg_class c join pg_namespace n on n.oid = c.relnamespace
605
651
  where n.nspname = 'lepis' and c.relname = 'relation'
606
- )::text`,Pf=`select coalesce(json_agg(json_build_object(
652
+ )::text`,hg=`select coalesce(json_agg(json_build_object(
607
653
  'schema', schema_name, 'table', table_name, 'kind', kind, 'keyColumn', key_column
608
- ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function Af(e){let t=[];for(let n of e.split(/\r?\n/)){let r=n.trim();if(r)try{t.push(JSON.parse(r))}catch{return null}}return t.length===0?null:Rf(t)}function Rf(e){let t=(n,r)=>{let o=new Set,s=[];for(let a of n)for(let l of a??[]){let i=r(l);o.has(i)||(o.add(i),s.push(l))}return s};return{tables:t(e.map(n=>n.tables),n=>`${n.schema}.${n.name}`),routines:t(e.map(n=>n.routines),n=>`${n.schema}.${n.name}(${(n.parameters??[]).map(r=>`${r.mode} ${r.dataType}`).join(",")})`),enums:t(e.map(n=>n.enums),n=>`${n.schema}.${n.name}`)}}async function Tf(e,t){let n=await e(Ef);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(Pf);if(r.code!==0)return[];try{let o=JSON.parse(r.out.trim()),s=new Set(t);return o.filter(a=>s.has(a.schema)).map(a=>({schema:a.schema,table:a.table,kind:a.kind,keyColumn:a.keyColumn??null}))}catch{return[]}}async function Of(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?Jn(e.dbUrl):kf(await pe(Nf(e))),r=!1,o=async L=>{if(e.sql)return e.sql(L);if(r&&e.ref)return nc(e.ref,L);let V=await vo(n,L);return V.code===127&&!e.dbUrl&&e.ref?(r=!0,nc(e.ref,L)):V},s=vf.replace("$SCHEMAS$",xf(t)),a=await o(s);a.code===127&&I("tool-missing",a.err.trim()),a.code!==0&&I("failed",a.err.trim()||`could not read the schema of ${n.label}`);let l=a.out.trim();l||I("failed",`${n.label} returned nothing for the schema query`);let i=Af(l);i||I("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await Tf(o,t),{schema:c,enums:m}=$f(i,n.database,Date.now()),h=tc(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:m}),x=null;return e.out&&(x=_f(e.out)?e.out:Sf(process.cwd(),e.out),bf(x,h,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(L=>L.kind!=="view").length,views:c.tables.filter(L=>L.kind==="view").length,functions:(c.routines??[]).length,enums:m.length,path:x,distribution:u,types:h}}async function nc(e,t,n=b){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 Nf(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 rc(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&I("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await Of(t);return r.path&&d(`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}se();import{createHash as ah}from"node:crypto";import{existsSync as es,mkdirSync as ih,readFileSync as ts,readdirSync as ch,statSync as lh,writeFileSync as dh}from"node:fs";import{dirname as uh,isAbsolute as zl,join as Yt,resolve as Wl}from"node:path";import{pathToFileURL as mh}from"node:url";W();function ph(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:zl(e)||e.startsWith("./")||e.startsWith("../")?mh(Wl(e)).href:e}function fh(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(ph(e)):Promise.resolve().then(()=>(Gl(),Hl))}function gh(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 ql="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 Kl(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 fh()}catch(t){I("tool-missing",gh(t))}return{ready:async t=>{let n=await Kl(e,t);return typeof n=="string"?await e.podmanReady(ql)??n:e.podmanReady(ql,n.name)},ensureImage:async(t,n)=>{let r=await Kl(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 Vl(e){return Yt(e,".snoutdata","local.json")}function or(e){let t=Vl(e);if(!es(t))return null;try{let n=JSON.parse(ts(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 hh(e,t){let n=Vl(e);return ih(uh(n),{recursive:!0}),dh(n,`${JSON.stringify(t,null,2)}
609
- `,{encoding:"utf8",mode:384}),n}var yh=54322;async function Yl(e,t){let n=await e.ensureImage(t,o=>d(o));n&&I("tool-missing",n);let r=await e.ready(t);r&&I("tool-missing",r),await e.prepare()}function Xl(e,t,n,r=vo){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 wh(e,t={}){let n=t.cwd??process.cwd(),r=or(n);await Yl(e,r?.ref??null);let o=r??e.mint(t.port??yh),s=r?null:hh(n,o),a=await e.start(o,m=>d(m)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=Xl(e,o,Jn(l)),u=await bh(i,n,t),c=await _h(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:c,created:s}}async function bh(e,t,n){let r=Wl(n.dir===void 0?Yt(t,"migrations"):zl(n.dir)?n.dir:Yt(t,n.dir));if(!es(r)||!lh(r).isDirectory())return[];let o=ch(r).filter(m=>m.endsWith(".sql")).map(m=>{let h=ts(Yt(r,m),"utf8");return{name:m,sql:h,checksum:ah("sha256").update(h,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(Sh);s.code!==0&&I(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${Vt}`);let a=await e(`select name || E'\\t' || checksum from ${Vt};`);a.code!==0&&I("failed",a.err.trim()||`could not read ${Vt}`);let l=a.out.split(`
610
- `).map(m=>m.trim()).filter(m=>m.length>0).map(m=>{let h=m.indexOf(" ");return{name:m.slice(0,h),checksum:m.slice(h+1)}}),i=qn(o,l,{outOfOrder:n.outOfOrder});i.ok||I("conflict",`Refused. This folder and the local database do not agree:
611
- ${i.problems.map(m=>` ${m.reason}`).join(`
612
- `)}`,{problems:i.problems});let u=new Map(o.map(m=>[m.name,m])),c=[];for(let m of i.pending){let h=u.get(m.name),x=`insert into ${Vt} (name, checksum) values (${Jl(h.name)}, ${Jl(h.checksum)});`,L=Ut(h.sql);L||d(`${h.name} runs outside a transaction, so an interruption can leave it half applied.`);let V=L?`begin;
613
- ${h.sql}
614
- ${x}
654
+ ) order by schema_name, table_name), '[]'::json)::text from lepis.relation`;function yg(e){let t=[];for(let n of e.split(/\r?\n/)){let r=n.trim();if(r)try{t.push(JSON.parse(r))}catch{return null}}return t.length===0?null:wg(t)}function wg(e){let t=(n,r)=>{let o=new Set,s=[];for(let a of n)for(let l of a??[]){let i=r(l);o.has(i)||(o.add(i),s.push(l))}return s};return{tables:t(e.map(n=>n.tables),n=>`${n.schema}.${n.name}`),routines:t(e.map(n=>n.routines),n=>`${n.schema}.${n.name}(${(n.parameters??[]).map(r=>`${r.mode} ${r.dataType}`).join(",")})`),enums:t(e.map(n=>n.enums),n=>`${n.schema}.${n.name}`)}}async function bg(e,t){let n=await e(gg);if(n.code!==0||n.out.trim()!=="true")return[];let r=await e(hg);if(r.code!==0)return[];try{let o=JSON.parse(r.out.trim()),s=new Set(t);return o.filter(a=>s.has(a.schema)).map(a=>({schema:a.schema,table:a.table,kind:a.kind,keyColumn:a.keyColumn??null}))}catch{return[]}}async function Sg(e){let t=e.schemas&&e.schemas.length>0?[...e.schemas]:["public"],n=e.dbUrl?tr(e.dbUrl):ug(await we(_g(e))),r=!1,o=async k=>{if(e.sql)return e.sql(k);if(r&&e.ref)return kc(e.ref,k);let X=await Co(n,k);return X.code===127&&!e.dbUrl&&e.ref?(r=!0,kc(e.ref,k)):X},s=pg.replace("$SCHEMAS$",mg(t)),a=await o(s);a.code===127&&L("tool-missing",a.err.trim()),a.code!==0&&L("failed",a.err.trim()||`could not read the schema of ${n.label}`);let l=a.out.trim();l||L("failed",`${n.label} returned nothing for the schema query`);let i=yg(l);i||L("failed",`could not read the schema of ${n.label}: the answer was not JSON`);let u=await bg(o,t),{schema:c,enums:p}=fg(i,n.database,Date.now()),m=_c(c,{driverId:"postgres",defaultSchema:e.defaultSchema??t[0]??"public",enums:p}),E=null;return e.out&&(E=lg(e.out)?e.out:dg(process.cwd(),e.out),cg(E,m,"utf8")),{target:n.label,schemas:t,tables:c.tables.filter(k=>k.kind!=="view").length,views:c.tables.filter(k=>k.kind==="view").length,functions:(c.routines??[]).length,enums:p.length,path:E,distribution:u,types:m}}async function kc(e,t,n=S){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 _g(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 vc(e,t){let n=(e??"typescript").toLowerCase();n!=="typescript"&&n!=="ts"&&L("usage",`gen types does not know how to write ${e}. It writes typescript, which is the only language it claims.`);let r=await Sg(t);return r.path&&d(`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}.`),h(r,()=>{r.path||process.stdout.write(r.types)}),0}pe();import{createHash as Zh}from"node:crypto";import{existsSync as ps,mkdirSync as Qh,readFileSync as ms,readdirSync as ey,statSync as ty,writeFileSync as ny}from"node:fs";import{dirname as ry,isAbsolute as yd,join as tn,resolve as wd}from"node:path";import{pathToFileURL as oy}from"node:url";Z();function sy(e){return/^[a-z][a-z0-9+.-]+:/i.test(e)?e:yd(e)||e.startsWith("./")||e.startsWith("../")?oy(wd(e)).href:e}function ay(){let e=process.env.SNOUTDATA_LOCAL_RUNTIME;return e?import(sy(e)):Promise.resolve().then(()=>(md(),pd))}function iy(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 fd="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 gd(e,t){try{return await e.localImageFor(t)}catch(n){return n instanceof Error?n.message:String(n)}}async function nn(){let e;try{e=await ay()}catch(t){L("tool-missing",iy(t))}return{ready:async t=>{let n=await gd(e,t);return typeof n=="string"?await e.podmanReady(fd)??n:e.podmanReady(fd,n.name)},ensureImage:async(t,n)=>{let r=await gd(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 bd(e){return tn(e,".snoutdata","local.json")}function mr(e){let t=bd(e);if(!ps(t))return null;try{let n=JSON.parse(ms(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 cy(e,t){let n=bd(e);return Qh(ry(n),{recursive:!0}),ny(n,`${JSON.stringify(t,null,2)}
655
+ `,{encoding:"utf8",mode:384}),n}var ly=54322;async function Sd(e,t){let n=await e.ensureImage(t,o=>d(o));n&&L("tool-missing",n);let r=await e.ready(t);r&&L("tool-missing",r),await e.prepare()}function _d(e,t,n,r=Co){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 dy(e,t={}){let n=t.cwd??process.cwd(),r=mr(n);await Sd(e,r?.ref??null);let o=r??e.mint(t.port??ly),s=r?null:cy(n,o),a=await e.start(o,p=>d(p)),l=e.uri(o);if(t.noMigrations)return{status:a,uri:l,applied:[],seeded:!1,created:s};let i=_d(e,o,tr(l)),u=await uy(i,n,t),c=await py(i,n,u.length>0||!!s);return{status:a,uri:l,applied:u,seeded:c,created:s}}async function uy(e,t,n){let r=wd(n.dir===void 0?tn(t,"migrations"):yd(n.dir)?n.dir:tn(t,n.dir));if(!ps(r)||!ty(r).isDirectory())return[];let o=ey(r).filter(p=>p.endsWith(".sql")).map(p=>{let m=ms(tn(r,p),"utf8");return{name:p,sql:m,checksum:Zh("sha256").update(m,"utf8").digest("hex")}});if(o.length===0)return[];let s=await e(my);s.code!==0&&L(s.code===127?"tool-missing":"failed",s.err.trim()||`could not create ${en}`);let a=await e(`select name || E'\\t' || checksum from ${en};`);a.code!==0&&L("failed",a.err.trim()||`could not read ${en}`);let l=a.out.split(`
656
+ `).map(p=>p.trim()).filter(p=>p.length>0).map(p=>{let m=p.indexOf(" ");return{name:p.slice(0,m),checksum:p.slice(m+1)}}),i=Yn(o,l,{outOfOrder:n.outOfOrder});i.ok||L("conflict",`Refused. This folder and the local database do not agree:
657
+ ${i.problems.map(p=>` ${p.reason}`).join(`
658
+ `)}`,{problems:i.problems});let u=new Map(o.map(p=>[p.name,p])),c=[];for(let p of i.pending){let m=u.get(p.name),E=`insert into ${en} (name, checksum) values (${hd(m.name)}, ${hd(m.checksum)});`,k=Kt(m.sql);k||d(`${m.name} runs outside a transaction, so an interruption can leave it half applied.`);let X=k?`begin;
659
+ ${m.sql}
660
+ ${E}
615
661
  commit;
616
- `:`${h.sql}
617
- ${x}
618
- `,F=await e(V);F.code!==0&&I("failed",`${h.name} failed, and nothing after it was tried.
619
- ${F.err.trim()}`,{failed:h.name,applied:c}),c.push(h.name),d(` applied ${h.name}`)}return c}async function _h(e,t,n){let r=Yt(t,"seed.sql");if(!n||!es(r))return!1;let o=await e(ts(r,"utf8"));return o.code!==0&&I("failed",`seed.sql failed.
620
- ${o.err.trim()}`),d(" ran seed.sql"),!0}var Vt="_snoutdata_migrations",Sh=`create table if not exists ${Vt} (
662
+ `:`${m.sql}
663
+ ${E}
664
+ `,j=await e(X);j.code!==0&&L("failed",`${m.name} failed, and nothing after it was tried.
665
+ ${j.err.trim()}`,{failed:m.name,applied:c}),c.push(m.name),d(` applied ${m.name}`)}return c}async function py(e,t,n){let r=tn(t,"seed.sql");if(!n||!ps(r))return!1;let o=await e(ms(r,"utf8"));return o.code!==0&&L("failed",`seed.sql failed.
666
+ ${o.err.trim()}`),d(" ran seed.sql"),!0}var en="_snoutdata_migrations",my=`create table if not exists ${en} (
621
667
  name text primary key,
622
668
  checksum text not null,
623
669
  applied_at timestamptz not null default now()
624
- );`;function Jl(e){return`'${e.replace(/'/g,"''")}'`}function kh(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 Zl(e,t={}){let n=await wh(e,t);return g(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${k("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${k("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${k("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
625
- `),d(w(" snoutdata gen types typescript --local > database.types.ts")),d(w(" snoutdata stop"))}),0}async function Ql(e,t={}){let n=t.cwd??process.cwd(),r=or(n);r||I("not-found","there is no local database for this folder. `snoutdata start` makes one."),await Yl(e,r.ref);let o=await e.stop(r.ref);return g(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function ed(e,t={}){let n=t.cwd??process.cwd(),r=or(n);if(!r)return g({exists:!1,ref:null,status:"absent"},()=>d("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},()=>d(o)),0;let s=await e.status(r);return g({...s,uri:s.uri},()=>{d(` ${k("Project")} ${s.ref}`),d(` ${k("State")} ${s.ready?"running":s.status}`),d(` ${k("Port")} ${s.port}`),d(` ${k("Size")} ${kh(s.databaseBytes)}`),s.services.length>0&&d(` ${k("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
626
- `)}),0}async function td(e,t=process.cwd()){let n=or(t);n||I("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:Xl(e,n,Jn(r))}}Y();W();ln();St();Me();import{resolve as nd}from"node:path";function vh(){let e=n=>Ie(n),t;return{shards:n=>{if(n!==void 0)return jt({project:n});if(!process.env.LEPIS_ADMIN_URL)throw new Error("The cluster tools need a cluster: pass project (a SnoutData Cloud project's ref, from list_projects), or start snoutdata mcp with LEPIS_ADMIN_URL (a standalone router's admin API, e.g. http://127.0.0.1:7432) and LEPIS_ADMIN_TOKEN set.");return t??=jt({}),t},whoami:()=>Xe(),listProjects:async()=>({...await he(),local:xh()}),localProject:n=>e(n),createProject:async n=>{let r=await b("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await At(r.project.ref)}catch{return{...r.project,ready:!1,note:`${r.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:(n,r)=>b("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>dt(n),requestExport:n=>Et(n),resetPassword:n=>b("cloud-project-reset-password",{ref:n}),listTeams:()=>b("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?Or(s,r,o):Lr(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?Pn(r):Ur(n)},deleteFunction:(n,r)=>{let o=e(n);return o?jr(o,r):Fr(n,r)},sizeFunction:(n,r,o)=>Br(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?Rn(r):b("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>b("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>Ot(n),deployContainer:(n,r,o)=>Jr(n,r,o),restartContainer:(n,r)=>Wr(n,r),deleteContainer:(n,r)=>zr(n,r),containerLogs:(n,r)=>On(n,r),listTokens:()=>b("cloud-token-list",{}),createToken:(n,r,o)=>b("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>b("cloud-token-revoke",{id:n}),getProject:async n=>{let r=e(n);return r?{ref:r.ref,name:r.name,local:r.folder,api:r.apiUrl,database:`127.0.0.1:${r.dbPort}`,functions:Pn(r).functions.map(o=>o.name),secretNames:Rn(r).secrets.map(o=>o.name)}:uo(n)},getProducts:n=>mt(n),setProduct:(n,r,o)=>ao(n,r,o),listDomains:n=>Un(n),domainAction:(n,r,o)=>io(n,r,o),restoreWindow:n=>co(n),restoreTo:(n,r,o)=>lo(n,r,o),setProjectState:async(n,r)=>{let o=await b(`cloud-project-${n}`,{ref:r});try{let s=await Pt(r,n,{timeoutMs:12e4});return{changed:o.changed,state:s?.state??"deleted",...s?{project:s}:{}}}catch{return{...o,settled:!1,note:`${r} has not finished yet. Call list_projects in a few seconds.`}}},connection:n=>pe(n),pushMigrations:(n,r)=>_o(n,{dir:r.dir,dryRun:r.dryRun})}}function xh(){let e=tn().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=ot(process.cwd())?.local;if(t&&!e.some(n=>nd(n.folder)===nd(t)))try{let n=Ue(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var rd="app_";async function $h(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await an();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await Cs(t)).map(o=>({name:`${rd}${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 od(e){$t(!0);let{tools:t,config:n}=await $h(),r=vh();n&&(r.callBorrowed=(i,u)=>Is(n,i.slice(rd.length),u));let o={...e,borrowed:t};X(t.length>0?`snoutdata mcp: ${Mt.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 Xe();X(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(X(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return X("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;X(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}X(`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 c=yi(s+u);s=c.rest;for(let m of c.messages)l(Eh(m,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 Eh(e,t,n){let r;try{r=JSON.parse(e)}catch{X(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await hi(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
670
+ );`;function hd(e){return`'${e.replace(/'/g,"''")}'`}function fy(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 kd(e,t={}){let n=await dy(e,t);return h(n,()=>{n.created&&(d(""),d(`Wrote ${n.created}. It holds a password, so put .snoutdata/ in your .gitignore.`)),d(""),d(` ${$("Database")} running on port ${n.status.port}`),n.applied.length>0&&d(` ${$("Migrations")} ${n.applied.length} applied`),n.seeded&&d(` ${$("Seed")} seed.sql ran`),d(""),process.stdout.write(`${n.uri}
671
+ `),d(w(" snoutdata gen types typescript --local > database.types.ts")),d(w(" snoutdata stop"))}),0}async function vd(e,t={}){let n=t.cwd??process.cwd(),r=mr(n);r||L("not-found","there is no local database for this folder. `snoutdata start` makes one."),await Sd(e,r.ref);let o=await e.stop(r.ref);return h(o,()=>d("Stopped. The data is still there; `snoutdata start` brings it back.")),0}async function $d(e,t={}){let n=t.cwd??process.cwd(),r=mr(n);if(!r)return h({exists:!1,ref:null,status:"absent"},()=>d("No local database for this folder. `snoutdata start` makes one.")),0;let o=await e.ready(r.ref);if(o)return h({exists:!1,ref:r.ref,status:"unknown",reason:o},()=>d(o)),0;let s=await e.status(r);return h({...s,uri:s.uri},()=>{d(` ${$("Project")} ${s.ref}`),d(` ${$("State")} ${s.ready?"running":s.status}`),d(` ${$("Port")} ${s.port}`),d(` ${$("Size")} ${fy(s.databaseBytes)}`),s.services.length>0&&d(` ${$("Services")} ${s.services.join(", ")}`),s.uri&&process.stdout.write(`${s.uri}
672
+ `)}),0}async function xd(e,t=process.cwd()){let n=mr(t);n||L("not-found","there is no local database for this folder. `snoutdata start` makes one.");let r=e.uri(n);return{dbUrl:r,sql:_d(e,n,tr(r))}}Q();Z();fn();Et();Fe();import{resolve as Ed}from"node:path";function gy(){let e=n=>Le(n),t;return{shards:n=>{if(n!==void 0)return Mt({project:n});if(!process.env.LEPIS_ADMIN_URL)throw new Error("The cluster tools need a cluster: pass project (a SnoutData Cloud project's ref, from list_projects), or start snoutdata mcp with LEPIS_ADMIN_URL (a standalone router's admin API, e.g. http://127.0.0.1:7432) and LEPIS_ADMIN_TOKEN set.");return t??=Mt({}),t},whoami:()=>nt(),listProjects:async()=>({...await _e(),local:hy()}),localProject:n=>e(n),createProject:async n=>{let r=await S("cloud-project-create",{name:n.name,...n.region?{region:n.region}:{},...n.teamId?{teamId:n.teamId}:{}});try{return await Ct(r.project.ref)}catch{return{...r.project,ready:!1,note:`${r.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:(n,r)=>S("cloud-project-usage",{ref:n,...r?{days:r}:{}}),exportStatus:n=>ft(n),requestExport:n=>Ot(n),resetPassword:n=>S("cloud-project-reset-password",{ref:n}),listTeams:()=>S("cloud-team-list",{}),deployFunction:(n,r,o)=>{let s=e(n);return s?Fr(s,r,o):zr(n,r,o).then(a=>a.answer)},listFunctions:async n=>{let r=e(n);return r?Nn(r):Wr(n)},deleteFunction:(n,r)=>{let o=e(n);return o?Hr(o,r):Vr(n,r)},sizeFunction:(n,r,o)=>Yr(n,r,o),listFunctionSecrets:async n=>{let r=e(n);return r?jn(r):S("cloud-project-secrets",{ref:n})},functionLogs:(n,r)=>S("cloud-project-functions",{ref:n,action:"logs",...r.name?{name:r.name}:{},...r.errorsOnly?{errorsOnly:!0}:{},...r.limit?{limit:r.limit}:{}}),listContainers:n=>ht(n),deployContainer:(n,r,o)=>to(n,r,o),restartContainer:(n,r)=>ro(n,r),deleteContainer:(n,r)=>no(n,r),containerLogs:(n,r)=>Dn(n,r),containerEgress:async n=>{let r=await ht(n);return{ref:r.ref,allowlist:r.allowlist??null}},setContainerEgress:async(n,r)=>{let o=await Mn(n,r);return{ref:o.ref,allowlist:o.allowlist??null,note:"Running containers follow it within a minute; nothing restarts."}},listTokens:()=>S("cloud-token-list",{}),createToken:(n,r,o)=>S("cloud-token-create",{name:n,...r?{expiresInDays:r}:{},...o?{project:o}:{}}),revokeToken:n=>S("cloud-token-revoke",{id:n}),getProject:async n=>{let r=e(n);return r?{ref:r.ref,name:r.name,local:r.folder,api:r.apiUrl,database:`127.0.0.1:${r.dbPort}`,functions:Nn(r).functions.map(o=>o.name),secretNames:jn(r).secrets.map(o=>o.name)}:So(n)},getProducts:n=>yt(n),setProduct:(n,r,o)=>ho(n,r,o),listDomains:n=>Kn(n),domainAction:(n,r,o)=>yo(n,r,o),restoreWindow:n=>wo(n),restoreTo:(n,r,o)=>bo(n,r,o),setProjectState:async(n,r)=>{let o=await S(`cloud-project-${n}`,{ref:r});try{let s=await Nt(r,n,{timeoutMs:12e4});return{changed:o.changed,state:s?.state??"deleted",...s?{project:s}:{}}}catch{return{...o,settled:!1,note:`${r} has not finished yet. Call list_projects in a few seconds.`}}},connection:n=>we(n),pushMigrations:(n,r)=>To(n,{dir:r.dir,dryRun:r.dryRun})}}function hy(){let e=an().filter(n=>n.ref!==null).map(n=>({ref:n.ref,name:n.name,folder:n.folder})),t=ct(process.cwd())?.local;if(t&&!e.some(n=>Ed(n.folder)===Ed(t)))try{let n=He(t);e.push({ref:n.ref,name:n.name,folder:n.folder})}catch{}return e}var Pd="app_";async function yy(){if(process.env.SNOUTDATA_NO_DESKTOP)return{tools:[],config:null};let e=await pn();if(!e.available)return{tools:[],config:null};let t=e.config;try{return{tools:(await Js(t)).map(o=>({name:`${Pd}${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 Ad(e){Tt(!0);let{tools:t,config:n}=await yy(),r=gy();n&&(r.callBorrowed=(i,u)=>zs(n,i.slice(Pd.length),u));let o={...e,borrowed:t};ee(t.length>0?`snoutdata mcp: ${Bt.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 nt();ee(`snoutdata mcp: signed in as ${i.email??"this account"}.`)}catch(i){if(ee(`snoutdata mcp: ${i instanceof Error?i.message:String(i)}`),!process.env.LEPIS_ADMIN_URL)return ee("Sign in with `snoutdata login`, or set SNOUTDATA_ACCESS_TOKEN."),3;ee(`snoutdata mcp: not signed in, so only the cluster tools will work (router ${process.env.LEPIS_ADMIN_URL}).`)}ee(`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 c=Ti(s+u);s=c.rest;for(let p of c.messages)l(wy(p,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 wy(e,t,n){let r;try{r=JSON.parse(e)}catch{ee(`snoutdata mcp: ignoring a line that is not JSON: ${e.slice(0,120)}`);return}try{let o=await Ri(r,t,n);o&&process.stdout.write(`${JSON.stringify(o)}
627
673
  `)}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)}})}
628
- `)}}St();Y();W();function Ph(e){return e>=1?"over":e>=.8?"near":"ok"}function sd(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:Ph(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function ns(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 ad(e,t={}){let n=await b("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=sd(n.days,n.limit);return g({...n,summary:r},()=>{if(d(k(`${n.name} (${n.ref})`)),!r.latest)d("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}`;d(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 d(`${De(r.latest.dbBytes)} as of ${r.latest.day}.`);n.readOnly&&r.storage?.state!=="over"&&d("This project is currently read-only."),d(`Over ${r.days} day${r.days===1?"":"s"}: ${ns(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&d(w(`Peak in that window was ${De(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(M([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":De(o.dbBytes),o.repoBytes===null?"-":De(o.repoBytes),ns(o.computeSeconds),String(o.connections)])])),d(w("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}ue();se();Ye();W();import{createInterface as Ah}from"node:readline/promises";var Rh=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),rs={max_write_pause_ms:"max-write-pause-ms",drain_timeout_ms:"drain-timeout-ms",ack_timeout_ms:"ack-timeout-ms",copy_mb_per_s:"copy-mb-per-s"},Th="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function id(e,t,n=!1){let[r,o,...s]=e,a=c=>$(t,c),l=(c,m)=>{if(c===void 0||c==="")throw new p(m);return c},i,u=`${r??""}${o!==void 0&&Oh(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new p("in SnoutData Cloud a node is a pod the project makes: cluster nodes add --project <ref>, with no name or --host. It joins the cluster on its own once it is ready.");return{body:P(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new p("nodes attach is for a SnoutData Cloud project (--project): a standalone node joins with cluster nodes add <name> --host H");return{body:{op:"node.attach",name:l(s[0]??a("node"),"nodes attach needs the node pod's ref, as cluster status lists it")},destructive:!1}}if(n&&u==="scale"){if(a("add")!==void 0||a("remove")!==void 0)throw new p("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=ze(t,"nodes","nodes");if(!c.nodes)throw new p("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[m,h]of Object.entries(rs))Object.assign(i,ze(t,h,m));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:l(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:l(a("host"),"nodes add needs --host, the address the router reaches the node at"),...ze(t,"port","port"),...Je(t,"dbname","dbname"),...Je(t,"sslmode","sslmode"),...Je(t,"peer-host","peer_host")};break;case"nodes drain":case"node drain":i={op:"node.drain",node:l(s[0]??a("node"),"nodes drain needs a node: cluster nodes drain <id|name> [--to a,b]"),...a("to")?{to:ir(a("to"))}:{}};break;case"nodes remove":case"node remove":i={op:"node.remove",node:l(s[0]??a("node"),"nodes remove needs a node: cluster nodes remove <id|name>")};break;case"keyspace create":i={op:"keyspace.create",name:l(s[0]??a("name"),"keyspace create needs a name: cluster keyspace create <name> --key-type bigint [--ranges N] [--nodes a,b]"),key_type:l(a("key-type"),"keyspace create needs --key-type, the distribution key's type (bigint, int, text, uuid, ...)"),...ze(t,"ranges","ranges"),...a("seed")!==void 0?{seed:Nh(a("seed"))}:{},...a("nodes")?{nodes:ir(a("nodes"))}:{}};break;case"keyspace drop":i={op:"keyspace.drop",name:l(s[0]??a("name"),"keyspace drop needs a name: cluster keyspace drop <name> (a keyspace no table is distributed by)")};break;case"dissolve":if(n)throw new p("in SnoutData Cloud, turn the cluster off with cluster disable --project <ref>: it dissolves the cluster and then stops the router");i={op:"dissolve"};break;case"table distribute":i={op:"table.distribute",table:l(s[0],"table distribute needs a table: cluster table distribute <schema.table> --column C --keyspace K"),column:l(a("column"),"table distribute needs --column, the distribution key column"),keyspace:l(a("keyspace"),"table distribute needs --keyspace")};break;case"table reference":case"table global":i={op:`table.${o}`,table:l(s[0],`table ${o} needs a table: cluster table ${o} <schema.table>`)};break;case"range split":{let c=sr(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:os(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=sr(s[0],t);i={op:"range.move",keyspace:c.keyspace,range:c.lo,to:l(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new p("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let c=sr(s[0],t),m=sr(s[1],t);if(c.keyspace!==m.keyspace)throw new p(`range merge: ${c.keyspace} and ${m.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:c.keyspace,a:c.lo,b:m.lo};break}case"tenant pin":{let c=a("keyspace")??s[0],m=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:l(c,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:l(m,"tenant pin needs the key value to pin (--value V for one that starts with a dash)"),...a("node")?{node:a("node")}:{}};break}case"rebalance":i={op:"rebalance",...Je(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?ir(a("add")).map(h=>jh(h,t)):[],m=ze(t,"remove","remove");if(c.length===0&&!m.remove)throw new p("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...c.length>0?{add:c}:{},...m};break}case"verify":i={op:"verify",...Je(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Je(t,"node","node")};break;default:throw new p(`unknown operation: cluster ${e.join(" ")}. One of ${Th}`)}for(let[c,m]of Object.entries(rs)){let h=ze(t,m,c);Object.assign(i,h)}return{body:i,destructive:Rh.has(i.op)}}function Oh(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function ir(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Je(e,t,n){let r=$(e,t);return r===void 0?{}:{[n]:r}}function ze(e,t,n){let r=$(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new p(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function os(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new p(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function Nh(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new p(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function sr(e,t){let n=$(t,"keyspace"),r=$(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new p(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:os(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new p("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:os(o,"--range")}}function jh(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new p(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Je(t,"dbname","dbname"),...Je(t,"sslmode","sslmode")}}function ar(e,t){if(e===void 0||!/^\d+$/.test(e))throw new p(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function dd(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=$(e,"project")??$(e,"ref"),l=a!==void 0,i=()=>t.client??jt({project:a,admin:$(e,"admin"),token:$(e,"token")});switch(r){case"enable":{if(!l)throw new p("cluster enable is for a SnoutData Cloud project: cluster enable --project <ref>. A standalone router is a cluster from the moment it runs");let c=i();if(!(c instanceof Re))throw new p("cluster enable needs --project <ref>");if(P(e,"plan")){let h=await c.enable(!0);return g(h,()=>we(h,process.stdout)),0}if(!P(e,"yes")){if(!t.confirm&&!ae())throw new p("making it a cluster restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.enable(!0),process.stderr),!await(t.confirm??bt)(`Make a cluster of ${c.target}?`))throw new v("failed","Not turned on. Nothing was changed.")}let m=await c.enable(!1);return g(m,()=>d(Ge(m))),0}case"disable":{if(!l)throw new p("cluster disable is for a SnoutData Cloud project: cluster disable --project <ref>. A standalone cluster is taken apart with cluster dissolve, and then by stopping the router");let c=i();if(!(c instanceof Re))throw new p("cluster disable needs --project <ref>");if(P(e,"plan")){let h=await c.disable(!0);return g(h,()=>we(h,process.stdout)),0}if(!P(e,"yes")){if(!t.confirm&&!ae())throw new p("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.disable(!0),process.stderr),!await(t.confirm??bt)(`Turn the cluster of ${c.target} off?`))throw new v("failed","Not turned off. Nothing was changed.")}let m=await c.disable(!1);return g(m,()=>d(Ge(m))),0}case"spread":{if(!l)throw new p("cluster spread is for a SnoutData Cloud project (--project). On a standalone router: cluster keyspace create, then cluster table distribute and cluster rebalance");let c=i();if(!(c instanceof Re))throw new p("cluster spread needs --project <ref>");let m={table:o??wt($(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --column C --key-type T"),column:wt($(e,"column"),"cluster spread needs --column, the column to spread the rows by"),key_type:wt($(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(P(e,"plan")){let x=await c.spread(m,!0);return g(x,()=>we(x,process.stdout)),0}if(!P(e,"yes")){if(!t.confirm&&!ae())throw new p("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.spread(m,!0),process.stderr),!await(t.confirm??bt)(`Spread ${m.table} over ${c.target}?`))throw new v("failed","Not spread. Nothing was changed.")}let h=await c.spread(m,!1);return g(h,()=>d(Ge(h))),0}case"retry":{if(!l)throw new p("cluster retry is for a SnoutData Cloud project (--project): a stopped job on a standalone router is resumed with cluster jobs resume <id>");let c=i();if(!(c instanceof Re))throw new p("cluster retry needs --project <ref>");let m=P(e,"queue")?await c.retry({queue:!0}):await c.retry({node:wt(o??$(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return g(m,()=>d(Ge(m))),0}case"replica":{if(!l)throw new p("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new p("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let c=i();if(!(c instanceof Re))throw new p("cluster replica needs --project <ref>");let m=o==="remove",h=wt(s??$(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(P(e,"plan")){let L=await c.replica(h,m,!0);return g(L,()=>we(L,process.stdout)),0}if(!P(e,"yes")){if(!t.confirm&&!ae())throw new p(m?"removing a replica leaves the node without one, and nothing was asked: pass --yes, or --plan to see what it does":"a replica takes one project slot, and nothing was asked: pass --yes, or --plan to see what it does");if(we(await c.replica(h,m,!0),process.stderr),!await(t.confirm??bt)(m?`Remove the replica of node ${h}?`:`Add a replica to node ${h}?`))throw new v("failed","Nothing was changed.")}let x=await c.replica(h,m,!1);return g(x,()=>d(Ge(x))),0}case"alerts":{if(!l)throw new p("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let c=i();if(!(c instanceof Re))throw new p("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let m=await c.alerts();return g({alerts:m},()=>md(m,process.stdout,!0,c.ref)),0}if(o==="seen"||o==="acknowledge"){let m=wt(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(m))throw new p(`not an alert id: ${m} (as cluster alerts lists it)`);let h=await c.acknowledge(m.toLowerCase());return g(h,()=>d(Ge(h))),0}throw new p(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let c=await i().status();return g(c,()=>Fh(c)),0}case"ops":{let c=await i().ops();return g(c,()=>{process.stdout.write(`${c.ops.join(`
674
+ `)}}Et();Q();Z();function by(e){return e>=1?"over":e>=.8?"near":"ok"}function Rd(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:by(n.dbBytes/t.maxStorageBytes)}:null;return{days:e.length,latest:n,peakDbBytes:r,computeSeconds:o,connections:s,storage:a}}function fs(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 Td(e,t={}){let n=await S("cloud-project-usage",{ref:e,...t.days?{days:t.days}:{}}),r=Rd(n.days,n.limit);return h({...n,summary:r},()=>{if(d($(`${n.name} (${n.ref})`)),!r.latest)d("Nothing measured yet. The host samples every fifteen minutes.");else if(r.storage){let o=Math.round(r.storage.fraction*100),s=`${Ue(r.storage.usedBytes)} of ${Ue(r.storage.limitBytes)} on ${n.limit?.tier}`;d(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 d(`${Ue(r.latest.dbBytes)} as of ${r.latest.day}.`);n.readOnly&&r.storage?.state!=="over"&&d("This project is currently read-only."),d(`Over ${r.days} day${r.days===1?"":"s"}: ${fs(r.computeSeconds)} of compute, ${r.connections} connection${r.connections===1?"":"s"}.`),r.peakDbBytes!==null&&r.latest&&r.peakDbBytes>r.latest.dbBytes&&d(w(`Peak in that window was ${Ue(r.peakDbBytes)}.`)),t.history&&n.days.length>0&&(d(""),d(B([["DAY","SIZE","BACKUPS","COMPUTE","CONNS"],...n.days.map(o=>[o.day,o.dbBytes===null?"-":Ue(o.dbBytes),o.repoBytes===null?"-":Ue(o.repoBytes),fs(o.computeSeconds),String(o.connections)])])),d(w("A dash is a day nothing measured it, which is what a paused project looks like.")))}),0}he();pe();tt();Z();import{createInterface as Sy}from"node:readline/promises";var _y=new Set(["node.drain","node.remove","table.distribute","table.reference","table.global","range.split","range.merge","range.move","tenant.pin","rebalance","scale","cleanup","dissolve"]),gs={max_write_pause_ms:"max-write-pause-ms",drain_timeout_ms:"drain-timeout-ms",ack_timeout_ms:"ack-timeout-ms",copy_mb_per_s:"copy-mb-per-s"},ky="nodes add|drain|remove, keyspace create|drop, table distribute|reference|global, range split|merge|move, tenant pin, rebalance, scale, verify, cleanup, dissolve";function Od(e,t,n=!1){let[r,o,...s]=e,a=c=>x(t,c),l=(c,p)=>{if(c===void 0||c==="")throw new g(p);return c},i,u=`${r??""}${o!==void 0&&vy(r)?` ${o}`:""}`;if(n&&(u==="nodes add"||u==="node add")){if(s.length>0||a("host")!==void 0)throw new g("in SnoutData Cloud a node is a pod the project makes: cluster nodes add --project <ref>, with no name or --host. It joins the cluster on its own once it is ready.");return{body:T(t,"no-join")?{op:"node.add",join:!1}:{op:"node.add"},destructive:!1}}if(u==="nodes attach"||u==="node attach"){if(!n)throw new g("nodes attach is for a SnoutData Cloud project (--project): a standalone node joins with cluster nodes add <name> --host H");return{body:{op:"node.attach",name:l(s[0]??a("node"),"nodes attach needs the node pod's ref, as cluster status lists it")},destructive:!1}}if(n&&u==="scale"){if(a("add")!==void 0||a("remove")!==void 0)throw new g("in SnoutData Cloud, scale takes --nodes N: how many nodes the project should have, the home included");let c=Ze(t,"nodes","nodes");if(!c.nodes)throw new g("scale needs --nodes N: how many nodes the project should have, the home included");i={op:"scale",...c};for(let[p,m]of Object.entries(gs))Object.assign(i,Ze(t,m,p));return{body:i,destructive:!0}}switch(u){case"nodes add":case"node add":i={op:"node.add",name:l(s[0]??a("name"),"nodes add needs a name: cluster nodes add <name> --host H [--port 5432] [--dbname D] [--sslmode M]"),host:l(a("host"),"nodes add needs --host, the address the router reaches the node at"),...Ze(t,"port","port"),...Xe(t,"dbname","dbname"),...Xe(t,"sslmode","sslmode"),...Xe(t,"peer-host","peer_host")};break;case"nodes drain":case"node drain":i={op:"node.drain",node:l(s[0]??a("node"),"nodes drain needs a node: cluster nodes drain <id|name> [--to a,b]"),...a("to")?{to:hr(a("to"))}:{}};break;case"nodes remove":case"node remove":i={op:"node.remove",node:l(s[0]??a("node"),"nodes remove needs a node: cluster nodes remove <id|name>")};break;case"keyspace create":i={op:"keyspace.create",name:l(s[0]??a("name"),"keyspace create needs a name: cluster keyspace create <name> --key-type bigint [--ranges N] [--nodes a,b]"),key_type:l(a("key-type"),"keyspace create needs --key-type, the distribution key's type (bigint, int, text, uuid, ...)"),...Ze(t,"ranges","ranges"),...a("seed")!==void 0?{seed:$y(a("seed"))}:{},...a("nodes")?{nodes:hr(a("nodes"))}:{}};break;case"keyspace drop":i={op:"keyspace.drop",name:l(s[0]??a("name"),"keyspace drop needs a name: cluster keyspace drop <name> (a keyspace no table is distributed by)")};break;case"dissolve":if(n)throw new g("in SnoutData Cloud, turn the cluster off with cluster disable --project <ref>: it dissolves the cluster and then stops the router");i={op:"dissolve"};break;case"table distribute":i={op:"table.distribute",table:l(s[0],"table distribute needs a table: cluster table distribute <schema.table> --column C --keyspace K"),column:l(a("column"),"table distribute needs --column, the distribution key column"),keyspace:l(a("keyspace"),"table distribute needs --keyspace")};break;case"table reference":case"table global":i={op:`table.${o}`,table:l(s[0],`table ${o} needs a table: cluster table ${o} <schema.table>`)};break;case"range split":{let c=fr(s[0],t);i={op:"range.split",keyspace:c.keyspace,range:c.lo,...a("at")!==void 0?{at:hs(a("at"),"--at")}:{},...a("to")?{to:a("to")}:{}};break}case"range move":{let c=fr(s[0],t);i={op:"range.move",keyspace:c.keyspace,range:c.lo,to:l(a("to"),"range move needs --to, the node that will own it")};break}case"range merge":{if(s.length!==2)throw new g("range merge needs two adjacent ranges: cluster range merge <keyspace:lo> <keyspace:lo>");let c=fr(s[0],t),p=fr(s[1],t);if(c.keyspace!==p.keyspace)throw new g(`range merge: ${c.keyspace} and ${p.keyspace} are different keyspaces`);i={op:"range.merge",keyspace:c.keyspace,a:c.lo,b:p.lo};break}case"tenant pin":{let c=a("keyspace")??s[0],p=a("value")??(a("keyspace")?s[0]:s[1]);i={op:"tenant.pin",keyspace:l(c,"tenant pin needs a keyspace: cluster tenant pin <keyspace> <value> [--node N]"),value:l(p,"tenant pin needs the key value to pin (--value V for one that starts with a dash)"),...a("node")?{node:a("node")}:{}};break}case"rebalance":i={op:"rebalance",...Xe(t,"keyspace","keyspace")};break;case"scale":{let c=a("add")?hr(a("add")).map(m=>xy(m,t)):[],p=Ze(t,"remove","remove");if(c.length===0&&!p.remove)throw new g("scale needs --add name=host[:port],... or --remove N (or both)");i={op:"scale",...c.length>0?{add:c}:{},...p};break}case"verify":i={op:"verify",...Xe(t,"keyspace","keyspace")};break;case"cleanup":i={op:"cleanup",...Xe(t,"node","node")};break;default:throw new g(`unknown operation: cluster ${e.join(" ")}. One of ${ky}`)}for(let[c,p]of Object.entries(gs)){let m=Ze(t,p,c);Object.assign(i,m)}return{body:i,destructive:_y.has(i.op)}}function vy(e){return e==="nodes"||e==="node"||e==="keyspace"||e==="table"||e==="range"||e==="tenant"}function hr(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function Xe(e,t,n){let r=x(e,t);return r===void 0?{}:{[n]:r}}function Ze(e,t,n){let r=x(e,t);if(r===void 0)return{};let o=Number(r);if(!/^\d+$/.test(r)||!Number.isSafeInteger(o))throw new g(`--${t} must be a whole number, not "${r}"`);return{[n]:o}}function hs(e,t){let n=e.trim();if(!/^-?\d+$/.test(n)||BigInt(n)<-(2n**63n)||BigInt(n)>=2n**63n)throw new g(`${t} must be a 64-bit whole number, not "${e}"`);return BigInt(n).toString()}function $y(e){let t=e.trim();if(!/^\d+$/.test(t)||BigInt(t)>=2n**64n)throw new g(`--seed must be an unsigned 64-bit whole number, not "${e}"`);return BigInt(t).toString()}function fr(e,t){let n=x(t,"keyspace"),r=x(t,"range");if(e!==void 0&&e.includes(":")){let s=e.lastIndexOf(":"),a=e.slice(0,s);if(!a)throw new g(`a range is keyspace:lo, as cluster status prints it, not "${e}"`);return{keyspace:a,lo:hs(e.slice(s+1),`the range's lower bound in "${e}"`)}}let o=r??e;if(!n||o===void 0)throw new g("name the range as keyspace:lo (as cluster status prints it), or with --keyspace K --range LO");return{keyspace:n,lo:hs(o,"--range")}}function xy(e,t){let n=/^([^=]+)=([^:]+)(?::(\d+))?$/.exec(e);if(!n)throw new g(`scale --add takes name=host[:port], not "${e}"`);return{name:n[1],host:n[2],...n[3]?{port:Number(n[3])}:{},...Xe(t,"dbname","dbname"),...Xe(t,"sslmode","sslmode")}}function gr(e,t){if(e===void 0||!/^\d+$/.test(e))throw new g(`jobs ${t} needs a job id: cluster jobs ${t} <id>`);return Number(e)}async function jd(e,t={}){let n=e.command.slice(1),[r,o,s]=n,a=x(e,"project")??x(e,"ref"),l=a!==void 0,i=()=>t.client??Mt({project:a,admin:x(e,"admin"),token:x(e,"token")});switch(r){case"enable":{if(!l)throw new g("cluster enable is for a SnoutData Cloud project: cluster enable --project <ref>. A standalone router is a cluster from the moment it runs");let c=i();if(!(c instanceof Ce))throw new g("cluster enable needs --project <ref>");if(T(e,"plan")){let m=await c.enable(!0);return h(m,()=>ve(m,process.stdout)),0}if(!T(e,"yes")){if(!t.confirm&&!me())throw new g("making it a cluster restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(ve(await c.enable(!0),process.stderr),!await(t.confirm??$t)(`Make a cluster of ${c.target}?`))throw new v("failed","Not turned on. Nothing was changed.")}let p=await c.enable(!1);return h(p,()=>d(Je(p))),0}case"disable":{if(!l)throw new g("cluster disable is for a SnoutData Cloud project: cluster disable --project <ref>. A standalone cluster is taken apart with cluster dissolve, and then by stopping the router");let c=i();if(!(c instanceof Ce))throw new g("cluster disable needs --project <ref>");if(T(e,"plan")){let m=await c.disable(!0);return h(m,()=>ve(m,process.stdout)),0}if(!T(e,"yes")){if(!t.confirm&&!me())throw new g("turning the cluster off restarts the database once, and nothing was asked: pass --yes, or --plan to see what it does");if(ve(await c.disable(!0),process.stderr),!await(t.confirm??$t)(`Turn the cluster of ${c.target} off?`))throw new v("failed","Not turned off. Nothing was changed.")}let p=await c.disable(!1);return h(p,()=>d(Je(p))),0}case"spread":{if(!l)throw new g("cluster spread is for a SnoutData Cloud project (--project). On a standalone router: cluster keyspace create, then cluster table distribute and cluster rebalance");let c=i();if(!(c instanceof Ce))throw new g("cluster spread needs --project <ref>");let p={table:o??vt(x(e,"table"),"cluster spread needs the table: cluster spread <schema.table> --column C --key-type T"),column:vt(x(e,"column"),"cluster spread needs --column, the column to spread the rows by"),key_type:vt(x(e,"key-type"),"cluster spread needs --key-type, the column's type (bigint, uuid, text, ...)")};if(T(e,"plan")){let E=await c.spread(p,!0);return h(E,()=>ve(E,process.stdout)),0}if(!T(e,"yes")){if(!t.confirm&&!me())throw new g("spreading a table moves its rows, and nothing was asked: pass --yes, or --plan to see what it does");if(ve(await c.spread(p,!0),process.stderr),!await(t.confirm??$t)(`Spread ${p.table} over ${c.target}?`))throw new v("failed","Not spread. Nothing was changed.")}let m=await c.spread(p,!1);return h(m,()=>d(Je(m))),0}case"retry":{if(!l)throw new g("cluster retry is for a SnoutData Cloud project (--project): a stopped job on a standalone router is resumed with cluster jobs resume <id>");let c=i();if(!(c instanceof Ce))throw new g("cluster retry needs --project <ref>");let p=T(e,"queue")?await c.retry({queue:!0}):await c.retry({node:vt(o??x(e,"node"),"cluster retry needs the node pod whose join stopped, or --queue")});return h(p,()=>d(Je(p))),0}case"replica":{if(!l)throw new g("cluster replica is for a SnoutData Cloud project (--project): cluster replica add|remove <node-ref> --project <ref>");if(o!=="add"&&o!=="remove")throw new g("cluster replica add|remove <node-ref> --project <ref>: the node pod, as cluster status lists it");let c=i();if(!(c instanceof Ce))throw new g("cluster replica needs --project <ref>");let p=o==="remove",m=vt(s??x(e,"node"),`cluster replica ${o} needs the node pod's ref, as cluster status lists it`);if(T(e,"plan")){let k=await c.replica(m,p,!0);return h(k,()=>ve(k,process.stdout)),0}if(!T(e,"yes")){if(!t.confirm&&!me())throw new g(p?"removing a replica leaves the node without one, and nothing was asked: pass --yes, or --plan to see what it does":"a replica takes one project slot, and nothing was asked: pass --yes, or --plan to see what it does");if(ve(await c.replica(m,p,!0),process.stderr),!await(t.confirm??$t)(p?`Remove the replica of node ${m}?`:`Add a replica to node ${m}?`))throw new v("failed","Nothing was changed.")}let E=await c.replica(m,p,!1);return h(E,()=>d(Je(E))),0}case"alerts":{if(!l)throw new g("cluster alerts is for a SnoutData Cloud project (--project <ref>)");let c=i();if(!(c instanceof Ce))throw new g("cluster alerts needs --project <ref>");if(o===void 0||o==="list"){let p=await c.alerts();return h({alerts:p},()=>Dd(p,process.stdout,!0,c.ref)),0}if(o==="seen"||o==="acknowledge"){let p=vt(s,`cluster alerts ${o} needs the alert's id, as cluster alerts lists it`);if(!/^[0-9a-f-]{36}$/i.test(p))throw new g(`not an alert id: ${p} (as cluster alerts lists it)`);let m=await c.acknowledge(p.toLowerCase());return h(m,()=>d(Je(m))),0}throw new g(`unknown command: cluster alerts ${o}. alerts [list], or alerts seen <id>`)}case void 0:case"status":{let c=await i().status();return h(c,()=>Ny(c)),0}case"ops":{let c=await i().ops();return h(c,()=>{process.stdout.write(`${c.ops.join(`
629
675
  `)}
630
- `),d(w(`Default settings: ${Object.entries(c.settings).map(([m,h])=>`${m}=${h}`).join(", ")}`)),c.refusals&&c.refusals.length>0&&d(w(`Refusal kinds: ${c.refusals.map(m=>`${m.kind} (${m.status})`).join(", ")}`))}),0}case"settings":return Dh(e,i);case"advise":case"advice":{let c=ze(e,"sample-ms","sampleMs").sampleMs,m=await i().advice(c);return g(m,()=>Uh(m,process.stdout)),0}case"jobs":return Mh(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let c=await i().status();return g({nodes:c.nodes??[]},()=>ud(c)),0}break;case"plan":{let c=id(n.slice(1),e,l),m=await i().plan(c.body);return g(m,()=>we(m,process.stdout)),0}}let u=id(n,e,l);if(P(e,"plan")){let c=await i().plan(u.body);return g(c,()=>we(c,process.stdout)),0}return Ch(u,e,i,t)}async function Ch(e,t,n,r){let o=P(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!ae())throw new p(`${s} moves or deletes data, and nothing was asked: pass --yes to run it, or --plan to see what it would do`);let a=n();if(e.destructive&&!o){let u=await a.plan(e.body);if(we(u,process.stderr),!await(r.confirm??bt)(`Run ${s} on ${a.target}?`))throw new v("failed","Not run. Nothing was changed.")}let l=await a.submit(e.body);if(l.job===void 0)return g(l.answer,()=>d(l.done)),0;if(P(t,"no-wait"))return g({job:l.job,state:"pending",plan:l.plan},()=>{process.stdout.write(`${l.job}
631
- `),d(`Job ${l.job} (${s}) is queued. Follow it: snoutdata cluster jobs watch ${l.job}`)}),0;d(`Job ${l.job} (${s}) is queued; following it. Ctrl+C stops following, not the job.`);let i=await ss(a,l.job,r);return as(i)}function wt(e,t){if(e===void 0||e==="")throw new p(t);return e}async function bt(e){let t=Ah({input:process.stdin,output:process.stderr});try{let n=(await t.question(`${e} [y/N] `)).trim().toLowerCase();return n==="y"||n==="yes"}finally{t.close()}}function Ih(e){let t=[...Qr,...ui],n={};for(let r of ir(e)){let o=r.indexOf("="),s=o<0?r:r.slice(0,o).trim(),a=o<0?"":r.slice(o+1).trim();if(!t.includes(s))throw new p(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new p(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function Dh(e,t){let n={};for(let s of Qr)Object.assign(n,ze(e,rs[s],s));let r=$(e,"set");if(r!==void 0&&Object.assign(n,Ih(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return g({settings:a},()=>process.stdout.write(`${M(Object.entries(a).map(([l,i])=>[l,String(i)]))}
632
- `)),0}let o=await t().settings(n);return g(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function Mh(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return g({jobs:s},()=>Jh(s)),0}case"show":{let s=await r.job(ar(t,"show"));return g(s,()=>zh(s)),0}case"watch":{let s=await ss(r,ar(t,"watch"),o);return as(s)}case"cancel":{let s=ar(t,"cancel");if(!P(n,"yes")){if(!o.confirm&&!ae())throw new p(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??bt)(`Cancel job ${s} on ${r.target}?`))throw new v("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return g(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=ar(t,"resume"),a=await r.resume(s);return P(n,"no-wait")?(g(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):as(await ss(r,s,o))}default:throw new p(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function ss(e,t,n={}){let r=n.pollMs??1e3,o=n.timeoutMs===void 0?1/0:Date.now()+n.timeoutMs,s=new Map;for(;;){let a=await e.job(t);for(let l of a.steps){let i=typeof l.detail?.phase=="string"?l.detail.phase:"",u=`${l.state}${i?` ${i}`:""}`;s.get(l.n)!==u&&(s.set(l.n,u),l.state!=="pending"&&d(w(` step ${l.n+1}/${a.steps.length} ${l.kind}: ${u}`)))}if(Nt(a.state))return a;if(Date.now()>=o)throw new v("timeout",`job ${t} is still ${a.state}; it carries on without this command. Follow it again: snoutdata cluster jobs watch ${t}`,{job:a});await new Promise(l=>setTimeout(l,r))}}function as(e){if(e.state==="done")return g(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
633
- `)),0;let t=e.state==="failed"?`failed: ${e.error??"no reason recorded"}. Resume it from the failed step: snoutdata cluster jobs resume ${e.id}`:"was cancelled";throw new v("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function T(e,t=""){e.write(`${t}
634
- `)}function is(e){let t=["B","kB","MB","GB","TB"],n=e,r=0;for(;n>=1e3&&r<t.length-1;)n/=1e3,r+=1;return`${r===0?n:n.toFixed(1)} ${t[r]}`}function we(e,t){T(t,`${k(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&T(t,M([[" FROM","TO","TABLES","ROWS","SIZE","COPY","PAUSE"],...e.moves.map(n=>[` node ${n.source}`,n.targets.map(r=>`node ${r}`).join(", ")||"-",n.tables.join(", ")||"-",`~${n.estimated_rows.toLocaleString("en-US")}`,is(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),T(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${is(e.estimated_bytes)}, ~${e.estimated_copy_seconds}s to copy, expected write pause ~${e.expected_pause_ms}ms (ceiling ${e.max_write_pause_ms}ms)`),T(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)T(t,` warning: ${n}`);e.assumptions&&T(t,w(` ${e.assumptions}`))}function ke(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function Lh(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${ke(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${ke(t.at)}`:""}${t.to!==void 0?` --to ${ke(t.to)}`:""}`;case"range.move":return`${n} range move ${ke(`${t.keyspace}:${t.range}`)} --to ${ke(t.to)}`;case"tenant.pin":return`${n} tenant pin ${ke(t.keyspace)} --value ${ke(t.value)}${t.node!==void 0?` --node ${ke(t.node)}`:""}`;case"node.add":return`${n} nodes add ${ke(t.name)} --host ${t.host?ke(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${ke(t.keyspace)}`:""}`;default:return null}}function Uh(e,t){T(t,e.summary),e.advice.forEach((n,r)=>{T(t),T(t,`${k(`${r+1}. ${n.op}`)} (${n.metric})`),T(t,` ${n.reason}`);let o=Lh(n.request);if(T(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;T(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${is(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])T(t,` warning: ${a}`)}else n.needs.length>0?T(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&T(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(T(t),T(t,w(e.assumptions)))}function cd(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function ud(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}T(process.stdout,M([["ID","NAME","ADDRESS","STATE","HOME","RANGES","POSTGRES"],...t.map(n=>[String(n.id),n.name,`${n.host}:${n.port}/${n.dbname}`,n.state,n.home?"yes":"",String(n.ranges.length),n.server_version_num?String(Math.floor(n.server_version_num/1e4)):"?"])]))}function Fh(e){let t=process.stdout;if(e.cloud&&(Kh(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){T(t,"The home node has no Lepis catalog yet (schema lepis).");return}T(t,`${k("Cluster")} at epoch ${e.epoch}`),T(t),ud(e);for(let s of e.keyspaces??[]){T(t),T(t,`${k(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),T(t,M([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,cd(e,a.node)])]));for(let a of s.pins)T(t,` pinned: ${a.value} on ${cd(e,a.node)}`)}let n=e.tables??[];n.length>0&&(T(t),T(t,M([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(T(t),T(t,`Routers: ${r.map(s=>`${s.id} (epoch ${s.epoch}${s.live?"":", not heard from"}${s.current?"":", behind"})`).join("; ")}`));let o=e.jobs??[];o.length>0&&T(t,`Unfinished jobs: ${o.map(s=>`${s.id} ${s.op} ${s.state}`).join("; ")}`),e.settings&&d(w(`Settings: ${Object.entries(e.settings).map(([s,a])=>`${s}=${a}`).join(", ")}`))}function Bh(e,t){if(t.joining==="failed"&&t.next!=="delete-pod")return`stopped joining: ${t.joinError??"see the audit log"}. Retry: snoutdata cluster retry ${t.ref} --project ${e.ref}`;if(t.replicaOf)return`replica of node ${t.replicaOf} (${t.state})`;switch(t.next){case"attach":return`ready: snoutdata cluster nodes attach ${t.ref} --project ${e.ref}`;case"delete-pod":return`let go by the cluster: snoutdata cluster nodes remove ${t.ref} --project ${e.ref} deletes its pod`;case"wait":return`starting as a copy of the home node (${t.state})`;case"joining":return t.joining==="spreading"?"joining: taking its share of the data":t.joining==="joining"?"joining the cluster":`joining: starting as a copy of the home node (${t.state})`;default:return t.node?`node ${t.node.id}, ${t.node.state}`:t.state}}function Hh(e){if(e.replicaOf)return`replica of node ${e.replicaOf}`;switch(e.failover){case"fence":return"failing over: stopping the node so its replica can take its place";case"promote":return"failing over: its replica is taking its place";case"failed":return`failover stopped: ${e.failoverError??"see cluster alerts"}`}return e.desiredState==="deleted"||e.replicaOf===void 0?"":e.replica?`protected by replica ${e.replica}`:e.wantsReplica?"no replica yet: one is being made":"no replica"}function ld(e){return e.severity.toUpperCase()}function md(e,t,n,r="<ref>"){let o=e.filter(a=>a.resolvedAt===null),s=e.filter(a=>a.resolvedAt!==null);if(!n){for(let a of o)T(t,`${k(ld(a))} ${a.message}${a.acknowledged?w(" (seen)"):""}`);o.length>0&&T(t,w(` See them all, and mark one as seen: snoutdata cluster alerts --project ${r}`));return}if(e.length===0){d("No alerts. Nothing has happened to this cluster that needs you.");return}for(let a of o)T(t,`${k(ld(a))} ${a.message}`),T(t,w(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&T(t),T(t,"Resolved in the last day:");for(let a of s)T(t,` ${a.message}${a.resolution?` ${w(a.resolution)}`:""}`)}}function Gh(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function qh(e){return e.projectsUsed===null||e.maxProjects===null?"the plan could not be read":`${e.projectsUsed} of ${e.maxProjects} project slots used on the ${e.tier??"current"} plan; each node uses one`}function Kh(e,t){let n=qh(e);if(!e.sharded){T(t,`${k(e.ref)} is not a cluster (${n}).`),Gh(e)>0&&T(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database restarts once).`);return}md(e.alerts??[],t,!1,e.ref);let r=e.pods.filter(l=>l.desiredState!=="deleted"),o=1+r.filter(l=>!l.replicaOf).length,s=r.length-(o-1);if(T(t,`${k(e.ref)}: ${o} node${o===1?"":"s"}${s>0?` and ${s} replica${s===1?"":"s"}`:""}, ${n}`),e.pods.length>0){let l=e.pods.some(i=>i.replicaOf!==void 0);T(t,M([[" POD","STATE","NEXT",...l?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,Bh(e,i),...l?[Hh(i)]:[]])]))}e.router==="starting"?T(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&T(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&T(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?T(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&T(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),T(t)}function Jh(e){if(e.length===0){d("No jobs yet.");return}T(process.stdout,M([["ID","OPERATION","STATE","STEPS","CREATED","ERROR"],...e.map(t=>[String(t.id),t.op,t.state,`${t.steps_done}/${t.steps}`,t.created_at.slice(0,19).replace("T"," "),t.error??""])]))}function zh(e){let t=process.stdout;T(t,`${k(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),T(t,M([[" STEP","KIND","STATE","PHASE"],...e.steps.map(n=>[` ${n.n+1}`,n.kind,n.state,typeof n.detail?.phase=="string"?n.detail.phase:""])])),e.plan&&Array.isArray(e.plan.steps)&&(T(t),we(e.plan,t))}se();W();vt();import{spawn as Wh}from"node:child_process";import{createHash as Vh}from"node:crypto";import{chmodSync as Yh,renameSync as cr,rmSync as pd,writeFileSync as Xh}from"node:fs";import{dirname as Zh,join as Qh}from"node:path";var lr=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",ey="https://github.com/snoutdata/studio/releases/download";function ty(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 ny(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 ry(){let e;try{e=await fetch(`${lr}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new v("network",`could not reach ${lr} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new v("server",`${lr} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new v("server",`${lr} did not say which version is newest`);return t.version}async function fd(e){let t;try{t=await fetch(e)}catch(n){throw new v("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new v("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function oy(e,t){let n=Qh(Zh(e),`.snoutdata-upgrade-${process.pid}`);Xh(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;pd(r,{force:!0}),cr(e,r);try{cr(n,e)}catch(o){throw cr(r,e),o}}else Yh(n,493),cr(n,e)}catch(r){pd(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new v("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?je:kt}`):r}}function sy(){return new Promise((e,t)=>{let n=Wh(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new v("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${je}`))),n.on("exit",r=>e(r??1))})}async function gd(e={}){let t=e.kind??fn(),n=await ry(),r=Ls(ne,n),o={installed:ne,latest:n,upToDate:!r,install:t};if(!r||e.check)return g({...o,upgraded:!1},()=>{d(r?`snoutdata ${ne} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${ne} is the newest version.`)}),0;switch(t){case"npx":return g({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${ne}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new v("usage",`this snoutdata (${ne}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?je:kt}`);case"npm":{d(`Upgrading snoutdata ${ne} to ${n} with npm\u2026`);let s=await sy();if(s!==0)throw new v("failed",`npm exited ${s}. Run it yourself: ${je}`);return g({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=ty(process.platform,process.arch);if(!s)throw new v("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${je}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${ey}/cli-v${n}`,[l,i]=await Promise.all([fd(`${a}/${s}`),fd(`${a}/SHA256SUMS`)]),u=ny(i.toString("utf8"),s);if(!u)throw new v("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=Vh("sha256").update(l).digest("hex");if(c!==u)throw new v("failed",`checksum mismatch for ${s}. Refusing to install.
676
+ `),d(w(`Default settings: ${Object.entries(c.settings).map(([p,m])=>`${p}=${m}`).join(", ")}`)),c.refusals&&c.refusals.length>0&&d(w(`Refusal kinds: ${c.refusals.map(p=>`${p.kind} (${p.status})`).join(", ")}`))}),0}case"settings":return Ay(e,i);case"advise":case"advice":{let c=Ze(e,"sample-ms","sampleMs").sampleMs,p=await i().advice(c);return h(p,()=>Oy(p,process.stdout)),0}case"jobs":return Ry(o,s,e,i(),t);case"nodes":case"node":if(o===void 0||o==="list"){let c=await i().status();return h({nodes:c.nodes??[]},()=>Id(c)),0}break;case"plan":{let c=Od(n.slice(1),e,l),p=await i().plan(c.body);return h(p,()=>ve(p,process.stdout)),0}}let u=Od(n,e,l);if(T(e,"plan")){let c=await i().plan(u.body);return h(c,()=>ve(c,process.stdout)),0}return Ey(u,e,i,t)}async function Ey(e,t,n,r){let o=T(t,"yes"),s=e.body.op;if(e.destructive&&!o&&!r.confirm&&!me())throw new g(`${s} moves or deletes data, and nothing was asked: pass --yes to run it, or --plan to see what it would do`);let a=n();if(e.destructive&&!o){let u=await a.plan(e.body);if(ve(u,process.stderr),!await(r.confirm??$t)(`Run ${s} on ${a.target}?`))throw new v("failed","Not run. Nothing was changed.")}let l=await a.submit(e.body);if(l.job===void 0)return h(l.answer,()=>d(l.done)),0;if(T(t,"no-wait"))return h({job:l.job,state:"pending",plan:l.plan},()=>{process.stdout.write(`${l.job}
677
+ `),d(`Job ${l.job} (${s}) is queued. Follow it: snoutdata cluster jobs watch ${l.job}`)}),0;d(`Job ${l.job} (${s}) is queued; following it. Ctrl+C stops following, not the job.`);let i=await ys(a,l.job,r);return ws(i)}function vt(e,t){if(e===void 0||e==="")throw new g(t);return e}async function $t(e){let t=Sy({input:process.stdin,output:process.stderr});try{let n=(await t.question(`${e} [y/N] `)).trim().toLowerCase();return n==="y"||n==="yes"}finally{t.close()}}function Py(e){let t=[...co,...$i],n={};for(let r of hr(e)){let o=r.indexOf("="),s=o<0?r:r.slice(0,o).trim(),a=o<0?"":r.slice(o+1).trim();if(!t.includes(s))throw new g(`--set: unknown setting "${s}". One of ${t.join(", ")}`);let l=Number(a);if(!/^\d+$/.test(a)||!Number.isSafeInteger(l))throw new g(`--set ${s} must be a whole number, not "${a}"`);n[s]=l}return n}async function Ay(e,t){let n={};for(let s of co)Object.assign(n,Ze(e,gs[s],s));let r=x(e,"set");if(r!==void 0&&Object.assign(n,Py(r)),Object.keys(n).length===0){let s=await t().status(),a={...s.settings??{},...s.advice_settings??{}};return h({settings:a},()=>process.stdout.write(`${B(Object.entries(a).map(([l,i])=>[l,String(i)]))}
678
+ `)),0}let o=await t().settings(n);return h(o,()=>d(`Set ${Object.entries(o.settings).map(([s,a])=>`${s}=${a}`).join(", ")}.`)),0}async function Ry(e,t,n,r,o){switch(e){case void 0:case"list":{let s=await r.jobs();return h({jobs:s},()=>Ly(s)),0}case"show":{let s=await r.job(gr(t,"show"));return h(s,()=>Uy(s)),0}case"watch":{let s=await ys(r,gr(t,"watch"),o);return ws(s)}case"cancel":{let s=gr(t,"cancel");if(!T(n,"yes")){if(!o.confirm&&!me())throw new g(`cancelling job ${s} stops a running operation, and nothing was asked: pass --yes`);if(!await(o.confirm??$t)(`Cancel job ${s} on ${r.target}?`))throw new v("failed","Not cancelled. Nothing was changed.")}let a=await r.cancel(s);return h(a,()=>d(`Job ${s}: ${a.state}.`)),0}case"resume":{let s=gr(t,"resume"),a=await r.resume(s);return T(n,"no-wait")?(h(a,()=>d(`Job ${s}: ${a.state}. Follow it: snoutdata cluster jobs watch ${s}`)),0):ws(await ys(r,s,o))}default:throw new g(`unknown command: cluster jobs ${e}. jobs [list|show|watch|cancel|resume] <id>`)}}async function ys(e,t,n={}){let r=n.pollMs??1e3,o=n.timeoutMs===void 0?1/0:Date.now()+n.timeoutMs,s=new Map;for(;;){let a=await e.job(t);for(let l of a.steps){let i=typeof l.detail?.phase=="string"?l.detail.phase:"",u=`${l.state}${i?` ${i}`:""}`;s.get(l.n)!==u&&(s.set(l.n,u),l.state!=="pending"&&d(w(` step ${l.n+1}/${a.steps.length} ${l.kind}: ${u}`)))}if(Dt(a.state))return a;if(Date.now()>=o)throw new v("timeout",`job ${t} is still ${a.state}; it carries on without this command. Follow it again: snoutdata cluster jobs watch ${t}`,{job:a});await new Promise(l=>setTimeout(l,r))}}function ws(e){if(e.state==="done")return h(e,()=>process.stdout.write(`Job ${e.id} (${e.op}): done, ${e.steps.length} step${e.steps.length===1?"":"s"}.
679
+ `)),0;let t=e.state==="failed"?`failed: ${e.error??"no reason recorded"}. Resume it from the failed step: snoutdata cluster jobs resume ${e.id}`:"was cancelled";throw new v("failed",`job ${e.id} (${e.op}) ${t}`,{job:e})}function N(e,t=""){e.write(`${t}
680
+ `)}function bs(e){let t=["B","kB","MB","GB","TB"],n=e,r=0;for(;n>=1e3&&r<t.length-1;)n/=1e3,r+=1;return`${r===0?n:n.toFixed(1)} ${t[r]}`}function ve(e,t){N(t,`${$(e.op)}: ${e.steps.length} step${e.steps.length===1?"":"s"}, ${e.cutovers} cutover${e.cutovers===1?"":"s"}`),e.moves.length>0&&N(t,B([[" FROM","TO","TABLES","ROWS","SIZE","COPY","PAUSE"],...e.moves.map(n=>[` node ${n.source}`,n.targets.map(r=>`node ${r}`).join(", ")||"-",n.tables.join(", ")||"-",`~${n.estimated_rows.toLocaleString("en-US")}`,bs(n.estimated_bytes),`~${n.estimated_copy_seconds}s`,`~${n.expected_pause_ms}ms`])])),N(t,` total: ~${e.estimated_rows.toLocaleString("en-US")} rows, ${bs(e.estimated_bytes)}, ~${e.estimated_copy_seconds}s to copy, expected write pause ~${e.expected_pause_ms}ms (ceiling ${e.max_write_pause_ms}ms)`),N(t,` steps: ${e.steps.map(n=>n.kind).join(", ")||"none"}`);for(let n of e.warnings)N(t,` warning: ${n}`);e.assumptions&&N(t,w(` ${e.assumptions}`))}function Pe(e){let t=String(e);return/^[\w.:@+-]+$/.test(t)?t:`'${t.replace(/'/g,"'\\''")}'`}function Ty(e){let t=e,n="snoutdata cluster";switch(e.op){case"range.split":return`${n} range split ${Pe(`${t.keyspace}:${t.range}`)}${t.at!==void 0?` --at ${Pe(t.at)}`:""}${t.to!==void 0?` --to ${Pe(t.to)}`:""}`;case"range.move":return`${n} range move ${Pe(`${t.keyspace}:${t.range}`)} --to ${Pe(t.to)}`;case"tenant.pin":return`${n} tenant pin ${Pe(t.keyspace)} --value ${Pe(t.value)}${t.node!==void 0?` --node ${Pe(t.node)}`:""}`;case"node.add":return`${n} nodes add ${Pe(t.name)} --host ${t.host?Pe(t.host):"<its address>"}, then ${n} rebalance`;case"rebalance":return`${n} rebalance${t.keyspace?` --keyspace ${Pe(t.keyspace)}`:""}`;default:return null}}function Oy(e,t){N(t,e.summary),e.advice.forEach((n,r)=>{N(t),N(t,`${$(`${r+1}. ${n.op}`)} (${n.metric})`),N(t,` ${n.reason}`);let o=Ty(n.request);if(N(t,` run: ${o??JSON.stringify(n.request)}`),n.plan){let s=n.plan;N(t,` plan: ${s.cutovers} cutover${s.cutovers===1?"":"s"}, ~${s.estimated_rows.toLocaleString("en-US")} rows, ${bs(s.estimated_bytes)}, ~${s.estimated_copy_seconds}s to copy, expected write pause ~${s.expected_pause_ms}ms`);for(let a of s.warnings??[])N(t,` warning: ${a}`)}else n.needs.length>0?N(t,` needs: ${n.needs.join(", ")}, so it has no plan yet`):n.plan_error&&N(t,` no plan: ${n.plan_error.message} (${n.plan_error.kind})`)}),e.assumptions&&(N(t),N(t,w(e.assumptions)))}function Nd(e,t){return e.nodes?.find(n=>n.id===t)?.name??`node ${t}`}function Id(e){let t=e.nodes??[];if(t.length===0){d("No nodes in the catalog.");return}N(process.stdout,B([["ID","NAME","ADDRESS","STATE","HOME","RANGES","POSTGRES"],...t.map(n=>[String(n.id),n.name,`${n.host}:${n.port}/${n.dbname}`,n.state,n.home?"yes":"",String(n.ranges.length),n.server_version_num?String(Math.floor(n.server_version_num/1e4)):"?"])]))}function Ny(e){let t=process.stdout;if(e.cloud&&(My(e.cloud,t),!e.cloud.sharded))return;if(!e.catalog){N(t,"The home node has no Lepis catalog yet (schema lepis).");return}N(t,`${$("Cluster")} at epoch ${e.epoch}`),N(t),Id(e);for(let s of e.keyspaces??[]){N(t),N(t,`${$(`Keyspace ${s.name}`)} (${s.key_type}, ${s.ranges.length} range${s.ranges.length===1?"":"s"})`),N(t,B([[" RANGE","HI","NODE"],...s.ranges.map(a=>[` ${s.name}:${a.lo}`,a.hi,Nd(e,a.node)])]));for(let a of s.pins)N(t,` pinned: ${a.value} on ${Nd(e,a.node)}`)}let n=e.tables??[];n.length>0&&(N(t),N(t,B([["TABLE","KIND","KEYSPACE","COLUMN"],...n.map(s=>[s.table,s.kind,s.keyspace??"",s.column??""])])));let r=e.routers??[];r.length>0&&(N(t),N(t,`Routers: ${r.map(s=>`${s.id} (epoch ${s.epoch}${s.live?"":", not heard from"}${s.current?"":", behind"})`).join("; ")}`));let o=e.jobs??[];o.length>0&&N(t,`Unfinished jobs: ${o.map(s=>`${s.id} ${s.op} ${s.state}`).join("; ")}`),e.settings&&d(w(`Settings: ${Object.entries(e.settings).map(([s,a])=>`${s}=${a}`).join(", ")}`))}function Cy(e,t){if(t.joining==="failed"&&t.next!=="delete-pod")return`stopped joining: ${t.joinError??"see the audit log"}. Retry: snoutdata cluster retry ${t.ref} --project ${e.ref}`;if(t.replicaOf)return`replica of node ${t.replicaOf} (${t.state})`;switch(t.next){case"attach":return`ready: snoutdata cluster nodes attach ${t.ref} --project ${e.ref}`;case"delete-pod":return`let go by the cluster: snoutdata cluster nodes remove ${t.ref} --project ${e.ref} deletes its pod`;case"wait":return`starting as a copy of the home node (${t.state})`;case"joining":return t.joining==="spreading"?"joining: taking its share of the data":t.joining==="joining"?"joining the cluster":`joining: starting as a copy of the home node (${t.state})`;default:return t.node?`node ${t.node.id}, ${t.node.state}`:t.state}}function jy(e){if(e.replicaOf)return`replica of node ${e.replicaOf}`;switch(e.failover){case"fence":return"failing over: stopping the node so its replica can take its place";case"promote":return"failing over: its replica is taking its place";case"failed":return`failover stopped: ${e.failoverError??"see cluster alerts"}`;case"held":return"failover held: its replica was behind and its last writes could not be copied to it, so it was not promoted (see cluster alerts)"}return e.desiredState==="deleted"||e.replicaOf===void 0?"":e.replica?`protected by replica ${e.replica}`:e.wantsReplica?"no replica yet: one is being made":"no replica"}function Cd(e){return e.severity.toUpperCase()}function Dd(e,t,n,r="<ref>"){let o=e.filter(a=>a.resolvedAt===null),s=e.filter(a=>a.resolvedAt!==null);if(!n){for(let a of o)N(t,`${$(Cd(a))} ${a.message}${a.acknowledged?w(" (seen)"):""}`);o.length>0&&N(t,w(` See them all, and mark one as seen: snoutdata cluster alerts --project ${r}`));return}if(e.length===0){d("No alerts. Nothing has happened to this cluster that needs you.");return}for(let a of o)N(t,`${$(Cd(a))} ${a.message}`),N(t,w(` ${a.id}, opened ${a.openedAt.slice(0,16).replace("T"," ")}${a.acknowledged?", seen":""}`));if(s.length>0){o.length>0&&N(t),N(t,"Resolved in the last day:");for(let a of s)N(t,` ${a.message}${a.resolution?` ${w(a.resolution)}`:""}`)}}function Iy(e){return e.projectsUsed===null||e.maxProjects===null?0:Math.max(0,e.maxProjects-e.projectsUsed)}function Dy(e){return e.projectsUsed===null||e.maxProjects===null?"the plan could not be read":`${e.projectsUsed} of ${e.maxProjects} project slots used on the ${e.tier??"current"} plan; each node uses one`}function My(e,t){let n=Dy(e);if(!e.sharded){N(t,`${$(e.ref)} is not a cluster (${n}).`),Iy(e)>0&&N(t,`Make it a cluster: snoutdata cluster enable --project ${e.ref} (the database restarts once).`);return}Dd(e.alerts??[],t,!1,e.ref);let r=e.pods.filter(l=>l.desiredState!=="deleted"),o=1+r.filter(l=>!l.replicaOf).length,s=r.length-(o-1);if(N(t,`${$(e.ref)}: ${o} node${o===1?"":"s"}${s>0?` and ${s} replica${s===1?"":"s"}`:""}, ${n}`),e.pods.length>0){let l=e.pods.some(i=>i.replicaOf!==void 0);N(t,B([[" POD","STATE","NEXT",...l?["REPLICA"]:[]],...e.pods.map(i=>[` ${i.ref}`,i.state,Cy(e,i),...l?[jy(i)]:[]])]))}e.router==="starting"?N(t,"The router is starting beside the database, which takes about twenty seconds."):e.router==="paused"&&N(t,"The project is paused; the router wakes with it on the next connection."),e.routerError&&N(t,`The router is not answering: ${e.routerError}`);let a=e.automation;a&&a.queueError?N(t,`Stopped: ${a.queueError}. Retry: snoutdata cluster retry --queue --project ${e.ref}`):a&&(a.queue.length>0||a.queueJob!==null)&&N(t,`Working through a request${a.queueJob!==null?` (job ${a.queueJob})`:""}${a.queue.length>0?`; then ${a.queue.join(", ")}`:""}.`),N(t)}function Ly(e){if(e.length===0){d("No jobs yet.");return}N(process.stdout,B([["ID","OPERATION","STATE","STEPS","CREATED","ERROR"],...e.map(t=>[String(t.id),t.op,t.state,`${t.steps_done}/${t.steps}`,t.created_at.slice(0,19).replace("T"," "),t.error??""])]))}function Uy(e){let t=process.stdout;N(t,`${$(`Job ${e.id}`)} ${e.op}: ${e.state}${e.error?` (${e.error})`:""}`),N(t,B([[" STEP","KIND","STATE","PHASE"],...e.steps.map(n=>[` ${n.n+1}`,n.kind,n.state,typeof n.detail?.phase=="string"?n.detail.phase:""])])),e.plan&&Array.isArray(e.plan.steps)&&(N(t),ve(e.plan,t))}pe();Z();At();import{spawn as Fy}from"node:child_process";import{createHash as By}from"node:crypto";import{chmodSync as Hy,renameSync as yr,rmSync as Md,writeFileSync as qy}from"node:fs";import{dirname as Gy,join as Ky}from"node:path";var wr=process.env.SNOUTDATA_NPM_REGISTRY??"https://registry.npmjs.org",Jy="https://github.com/snoutdata/studio/releases/download";function zy(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 Wy(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 Vy(){let e;try{e=await fetch(`${wr}/snoutdata/latest`,{headers:{accept:"application/json"}})}catch(n){throw new v("network",`could not reach ${wr} to find the newest version: ${n instanceof Error?n.message:String(n)}`)}if(!e.ok)throw new v("server",`${wr} answered ${e.status} when asked for the newest version`);let t=await e.json();if(typeof t.version!="string")throw new v("server",`${wr} did not say which version is newest`);return t.version}async function Ld(e){let t;try{t=await fetch(e)}catch(n){throw new v("network",`could not download ${e}: ${n instanceof Error?n.message:String(n)}`)}if(!t.ok)throw new v("failed",`download failed (${t.status}): ${e}`);return Buffer.from(await t.arrayBuffer())}function Yy(e,t){let n=Ky(Gy(e),`.snoutdata-upgrade-${process.pid}`);qy(n,t,{mode:493});try{if(process.platform==="win32"){let r=`${e}.old`;Md(r,{force:!0}),yr(e,r);try{yr(n,e)}catch(o){throw yr(r,e),o}}else Hy(n,493),yr(n,e)}catch(r){Md(n,{force:!0});let o=r.code;throw o==="EACCES"||o==="EPERM"?new v("forbidden",`${e} is not writable by this user. Run the installer instead: ${process.platform==="win32"?De:Pt}`):r}}function Xy(){return new Promise((e,t)=>{let n=Fy(process.platform==="win32"?"npm.cmd":"npm",["install","-g","snoutdata@latest"],{stdio:["ignore","inherit","inherit"],shell:process.platform==="win32"});n.on("error",r=>t(new v("tool-missing",`npm could not be run (${r.message}). Run it yourself: ${De}`))),n.on("exit",r=>e(r??1))})}async function Ud(e={}){let t=e.kind??bn(),n=await Vy(),r=Ys(le,n),o={installed:le,latest:n,upToDate:!r,install:t};if(!r||e.check)return h({...o,upgraded:!1},()=>{d(r?`snoutdata ${le} is installed; ${n} is the newest. \`snoutdata upgrade\` installs it.`:`snoutdata ${le} is the newest version.`)}),0;switch(t){case"npx":return h({...o,upgraded:!1},()=>{d(`This is a copy npx fetched (${le}). Run \`npx snoutdata@latest\` to use ${n}; there is nothing installed to upgrade.`)}),0;case"source":throw new v("usage",`this snoutdata (${le}) is running from source, so there is nothing to replace. Install the published one: ${process.platform==="win32"?De:Pt}`);case"npm":{d(`Upgrading snoutdata ${le} to ${n} with npm\u2026`);let s=await Xy();if(s!==0)throw new v("failed",`npm exited ${s}. Run it yourself: ${De}`);return h({...o,upgraded:!0},()=>d(`Upgraded to ${n}.`)),0}case"binary":{let s=zy(process.platform,process.arch);if(!s)throw new v("failed",`no snoutdata binary is built for ${process.platform}-${process.arch}. Use npm: ${De}`);d(`Downloading snoutdata ${n} for ${process.platform}-${process.arch}\u2026`);let a=`${Jy}/cli-v${n}`,[l,i]=await Promise.all([Ld(`${a}/${s}`),Ld(`${a}/SHA256SUMS`)]),u=Wy(i.toString("utf8"),s);if(!u)throw new v("failed",`${s} is not listed in the release's SHA256SUMS. Refusing to install.`);let c=By("sha256").update(l).digest("hex");if(c!==u)throw new v("failed",`checksum mismatch for ${s}. Refusing to install.
635
681
  expected ${u}
636
- actual ${c}`);return oy(process.execPath,l),g({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}kn();Me();vt();var hd=`snoutdata ${ne} \u2014 hosted Postgres, from a terminal or an agent
682
+ actual ${c}`);return Yy(process.execPath,l),h({...o,upgraded:!0,path:process.execPath},()=>d(`Upgraded ${process.execPath} to ${n}.`)),0}}}Pn();Fe();At();var Fd=`snoutdata ${le} \u2014 hosted Postgres, from a terminal or an agent
637
683
 
638
684
  snoutdata init [--name X] [--env] a database for this folder, linked, ready to use;
639
685
  --env writes DATABASE_URL into ./.env (and adds
@@ -791,11 +837,11 @@ that re-signs Postgres traffic.
791
837
  Docs: https://docs.snoutdata.com/developers/cli
792
838
  Examples: https://github.com/snoutdata/snoutdata (a star helps other people find it)
793
839
  Source: https://github.com/snoutdata/snout-cli
794
- `,ay=["yes","token"];async function iy(e){let[t,n,...r]=e.command;if(P(e,"version")||t==="version")return g({version:ne},()=>process.stdout.write(`${ne}
795
- `)),0;if(P(e,"help")&&t&&t!=="help"){let i=ps(t,n);if(i.length>0)return g({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(c=>[c,mr(u.name,c)]))}))},()=>{for(let u of i){let c="args"in u&&u.args?` ${u.args}`:"",m=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${c}${m}
840
+ `,Zy=["yes","token"];async function Qy(e){let[t,n,...r]=e.command;if(T(e,"version")||t==="version")return h({version:le},()=>process.stdout.write(`${le}
841
+ `)),0;if(T(e,"help")&&t&&t!=="help"){let i=xs(t,n);if(i.length>0)return h({commands:i.map(u=>({...u,flagHelp:Object.fromEntries(u.flags.map(c=>[c,_r(u.name,c)]))}))},()=>{for(let u of i){let c="args"in u&&u.args?` ${u.args}`:"",p=u.flags.length>0?` ${u.flags.join(" ")}`:"";process.stdout.write(` snoutdata ${u.name}${c}${p}
796
842
  ${u.summary}
797
- `);let h=Math.max(0,...u.flags.map(x=>x.length));for(let x of u.flags)process.stdout.write(` ${x.padEnd(h)} ${mr(u.name,x)}
843
+ `);let m=Math.max(0,...u.flags.map(E=>E.length));for(let E of u.flags)process.stdout.write(` ${E.padEnd(m)} ${_r(u.name,E)}
798
844
  `)}process.stdout.write(`
799
845
  Every command also takes --json, --quiet and --help.
800
- `)}),0}let o=P(e,"help")||t==="help";if(o||!t)return g({version:ne,usage:hd.trim(),commands:ur},()=>process.stdout.write(hd)),o?0:Ne.usage;let s=()=>{let i=be(e,"timeout");if(i!==void 0){if(!(i>0))throw new p("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=en({flag:$(e,"ref")});if(!i)throw new p("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=en({flag:$(e,"ref")});return i?Ie(i):null};for(let i of ay)if(e.flags[i]!==void 0&&t!=="cluster")throw new p(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return dd(e,{timeoutMs:s()});case"login":{if(P(e,"device")){let m=await gn({name:cn(),platform:process.platform});g({verifyUrl:m.verifyUrl,code:m.code,expiresAt:m.expiresAt},()=>{d(""),d(` Open ${m.verifyUrl}`),d(` Type ${m.code}`),d(""),d(w("Waiting for that to be approved\u2026"))});let h=await hn(m.secret,{intervalMs:Math.max(1,m.interval)*1e3,expiresAt:m.expiresAt});xe({accessToken:h.token});let x=$(e,"email"),L=x?(await Xe()).email??void 0:void 0;return x&&Pr(x,{accessToken:h.token,email:L}),g({ok:!0,name:h.name??null,email:L??null},()=>d(`Signed in${L?` as ${L}`:""}.`)),0}let i=$(e,"domain"),u=P(e,"sso")||i!==void 0;if(u&&$(e,"provider"))throw new p("--sso and --provider are two different doors: pass one or the other");let c=await Sn({provider:$(e,"provider"),sso:u,domain:i,noBrowser:P(e,"no-browser")||!ae(),timeoutMs:s(),email:$(e,"email")});return g({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{bs();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return g({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(w(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await Xe(),u=Qt()?.expectedEmail,c=!!(u&&i.email&&!Ar(u,i.email));return c&&X(`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:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
801
- `),i.token&&d(`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 va(),0;case"create":{let i=$(e,"name")??r[0];if(!i)throw new p("tokens create needs --name (what is it for, and where does it live)");return await ka({name:i,expiresInDays:be(e,"expires"),project:$(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new p("tokens revoke needs the token's id or its sdt_ prefix");return await xa(i),0}default:throw new p(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await ya(),0;case"create":{let i=$(e,"name")??r[0];if(!i)throw new p("projects create needs --name");let u=$(e,"team");return await Ir({teamId:u?await Sa(u):void 0,name:i,region:$(e,"region"),wait:!P(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:P(e,"show-url")}),0}case"pause":case"resume":case"delete":return await wa(n,$(e,"ref")??r[0]??a(),{wait:!P(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=$(e,"ref")??r[0]??a(),u=Ie(i);u?ta(u):await $i(i)}return 0;default:throw new p(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new p("functions deploy needs a name: snoutdata functions deploy <name>");return await da(i,r[0],{dir:$(e,"dir"),verifyJwt:!P(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return ua(i),0;if((n==="delete"||n==="remove")&&r[0])return await ma(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new p("functions deploy needs a name: snoutdata functions deploy <name>");return await Oa(a(),u,{dir:$(e,"dir"),entrypoint:$(e,"entrypoint"),verifyJwt:!P(e,"no-verify-jwt")}),0}case void 0:case"list":return await Ca(a()),0;case"size":{let u=r[0];if(!u)throw new p("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await ja(a(),u,{memoryMb:be(e,"memory"),concurrency:be(e,"concurrency"),reset:P(e,"reset")}),0}case"logs":return await Ia(a(),r[0],{errorsOnly:P(e,"errors"),limit:be(e,"tail"),follow:P(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new p("functions delete needs a name");return await Da(a(),u),0}default:throw new p(`unknown command: functions ${n}`)}}case"containers":switch(n){case"deploy":{let i=r[0],u=r[1],c=$(e,"dockerfile");if(i&&c){if(u)throw new p("containers deploy takes an image or --dockerfile, not both");if($(e,"registry-user")||P(e,"clear-registry"))throw new p("an image built with --dockerfile is uploaded to the project and needs no registry login");return await Va(a(),i,{path:c,port:be(e,"port"),wait:!P(e,"no-wait")}),0}if(!i||!u)throw new p("containers deploy needs a name and an image: snoutdata containers deploy <name> <image>, or <name> --dockerfile <dir>");let m=$(e,"registry-user");if(m&&!P(e,"stdin"))throw new p('a registry login takes its token on stdin: echo "$TOKEN" | snoutdata containers deploy <name> <image> --registry-user U --stdin');return await Ja(a(),i,{image:u,port:be(e,"port"),registryUser:m,registryPassword:m?await Cn():void 0,clearCredential:P(e,"clear-registry"),wait:!P(e,"no-wait")}),0}case void 0:case"list":return await Ya(a()),0;case"logs":return await Qa(a(),r[0],{errorsOnly:P(e,"errors"),limit:be(e,"tail"),follow:P(e,"follow")}),0;case"restart":{if(!r[0])throw new p("containers restart needs a name");return await Xa(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new p("containers delete needs a name");return await Za(a(),r[0]),0}case"egress":{let i=r[0];if(i===void 0||i==="list"||i==="show")return await jn(a(),{}),0;if(i==="clear")return await jn(a(),{clear:!0}),0;if(i==="set"){let u=r.slice(1);if(u.length===0&&!P(e,"none"))throw new p("containers egress set needs at least one destination (api.example.com:443), or --none for nothing outside the project");if(u.length>0&&P(e,"none"))throw new p("containers egress set takes destinations or --none, not both");return await jn(a(),{set:u}),0}throw new p(`unknown command: containers egress ${i}; it is egress, egress set or egress clear`)}default:throw new p(`unknown command: containers ${n}`)}case"secrets":{let i=l();if(i){if(n==="set")return await fa(i,await ni(r,{stdin:P(e,"stdin")})),0;if(n===void 0||n==="list")return ga(i),0;if((n==="unset"||n==="remove")&&r[0])return await ha(i,r[0]),0}switch(n){case"set":return await ri(a(),r,{stdin:P(e,"stdin")}),0;case void 0:case"list":return await si(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new p("secrets unset needs a name");return await oi(a(),u),0}default:throw new p(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await $a(a()),0;case"rotate":return await Ea(a(),{force:P(e,"force")}),0;default:throw new p(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await _i(a()),0;if(n==="enable"||n==="disable")return await Si(a(),bi(r[0]),n==="enable"),0;throw new p(`unknown command: products ${n}. products [enable|disable ${Ln.join("|")}]`)}case"push":{if(n!=="credentials")throw new p("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await Xi(a()),0;if(i==="set"){let u=So(r[1]),c=u==="apns"?await Vi({p8:$(e,"p8"),keyId:$(e,"key-id"),teamId:$(e,"team-id"),topic:$(e,"topic"),environment:$(e,"environment")}):await Yi($(e,"file"));return await Zi(a(),u,c),0}if(i==="remove"||i==="unset")return await Qi(a(),So(r[1])),0;throw new p(`unknown command: push credentials ${i}`)}case"auth":return await qi(a(),n,r,{clientId:$(e,"client-id"),stdin:P(e,"stdin"),siteUrl:$(e,"site-url"),allow:$(e,"allow"),file:$(e,"file"),subject:$(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return Ui(a(),{channel:$(e,"channel"),watch:P(e,"watch")});if(n==="logs")return Fi(a(),{channel:$(e,"channel"),since:$(e,"since")});throw new p(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await ki(a(),n,r[0]),0;case"teams":return await _a(),0;case"link":{if(P(e,"local")){let c;try{c=xs(n)}catch(h){throw new v("not-found",h instanceof Error?h.message:String(h))}let m=st(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return g({ref:c.ref,local:c.folder,api:c.apiUrl,path:m},()=>{d(`Linked ${c.name??c.ref}, the local project in ${c.folder} (${m}).`),d(`Its API is ${c.apiUrl}; its database is on 127.0.0.1:${c.dbPort}.`)}),0}let i=$(e,"ref")??n;if(!i)throw new p("link needs --ref");let u=st(process.cwd(),{ref:i});return g({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await go(a()),0;case"psql":return Ni(a(),r);case"reset-password":return await ba(a()),0;case"access":return await Di(a(),r,{level:$(e,"level")}),0;case"push":return zi(a(),{dir:$(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return P(e,"window")?(await vi(a()),0):$(e,"at")?(await xi(a(),$(e,"at"),$(e,"name")),0):Ii(a(),{file:$(e,"file"),force:!!e.flags.force});case"export":return Ci(a(),{timeoutMs:s(),out:$(e,"out"),statusOnly:!!e.flags.status});default:throw new p(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new p(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=P(e,"local")?await td(await Xt()):null,u=i?i.dbUrl:$(e,"db-url");return rc(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:($(e,"schema")??"public").split(",").map(c=>c.trim()).filter(Boolean),out:$(e,"out"),sql:i?.sql})}case"start":{let i=l();if(i)return Qs(i)}return Zl(await Xt(),{port:be(e,"port"),dir:$(e,"dir"),noMigrations:P(e,"no-migrations"),outOfOrder:P(e,"out-of-order")});case"stop":{let i=l();return i?ea(i):Ql(await Xt())}case"status":{let i=l();return i?Zs(i):ed(await Xt())}case"usage":return ad(a(),{days:be(e,"days"),history:!!e.flags.history});case"mcp":return od({version:ne,allowDelete:!!e.flags["allow-delete"]});case"init":return cy(e);case"upgrade":return gd({check:P(e,"check")});default:throw new p(`unknown command: ${t}`)}}async function cy(e){let t=ks({flag:$(e,"ref")});if(t.kind!=="create"){let o=t.ref;if(t.kind==="linked"?d(`This folder is already linked to ${o}.`):t.kind==="environment"&&d(`Using ${o}, from SNOUTDATA_PROJECT. This folder is not linked to it.`),P(e,"env")?await yd(o):await go(o),t.kind==="link"){let s=Ie(o),a=st(process.cwd(),s?{ref:o,...s.name?{name:s.name}:{},local:s.folder}:{ref:o});d(`Linked this folder to ${o}${t.replaces?`, in place of ${t.replaces}`:""} (${a}).`)}return 0}let n=$(e,"name")??ly(process.cwd());d(`Creating a project called "${n}"\u2026`),await Ir({name:n,region:$(e,"region"),wait:!0,link:process.cwd(),showUrl:!P(e,"env")});let r=en({cwd:process.cwd()});return r&&P(e,"env")&&await yd(r),0}async function yd(e){let{connection:t}=await Promise.resolve().then(()=>(Y(),Ae)),n=await t(e),r=ji(process.cwd(),n.uri);d(`${r.replaced?"Updated":"Wrote"} DATABASE_URL in ${r.path}.`),r.ignored&&d("Added .env to .gitignore, since it holds the password.")}function ly(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function dy(e){if(e instanceof v)return Be(e.code,e.message,e.details),e.exitCode;if(e instanceof p)return Be("usage",e.message),Ne.usage;if(e instanceof He)return Be("not-signed-in",e.message),Ne["not-signed-in"];if(e instanceof ge){let r=Ve(e.status,e.serverCode);return Be(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),Ne[r]}let t=Ps(e);if(t)return Be("usage",t),Ne.usage;let n=rn(e);return Be(n,e instanceof Error?e.message:String(e)),Ne[n]}function cs(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function uy(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function my(){$t(uy(process.argv.slice(2)));let e;try{e=us(process.argv.slice(2))}catch(t){Be("usage",String(t instanceof Error?t.message:t)),cs(Ne.usage);return}$t(e.json),Bs(e.flags.quiet===!0),As(on({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{cs(await iy(e))}catch(t){cs(dy(t))}}my();
846
+ `)}),0}let o=T(e,"help")||t==="help";if(o||!t)return h({version:le,usage:Fd.trim(),commands:Sr},()=>process.stdout.write(Fd)),o?0:Ie.usage;let s=()=>{let i=$e(e,"timeout");if(i!==void 0){if(!(i>0))throw new g("--timeout is a number of seconds greater than zero");return i*1e3}},a=()=>{let i=sn({flag:x(e,"ref")});if(!i)throw new g("no project: pass --ref, set SNOUTDATA_PROJECT, or run `snoutdata link --ref <ref>`");return i},l=()=>{let i=sn({flag:x(e,"ref")});return i?Le(i):null};for(let i of Zy)if(e.flags[i]!==void 0&&t!=="cluster")throw new g(`--${i} is only for snoutdata cluster`);switch(t){case"cluster":return jd(e,{timeoutMs:s()});case"login":{if(T(e,"device")){let p=await Sn({name:mn(),platform:process.platform});h({verifyUrl:p.verifyUrl,code:p.code,expiresAt:p.expiresAt},()=>{d(""),d(` Open ${p.verifyUrl}`),d(` Type ${p.code}`),d(""),d(w("Waiting for that to be approved\u2026"))});let m=await _n(p.secret,{intervalMs:Math.max(1,p.interval)*1e3,expiresAt:p.expiresAt});Ae({accessToken:m.token});let E=x(e,"email"),k=E?(await nt()).email??void 0:void 0;return E&&Dr(E,{accessToken:m.token,email:k}),h({ok:!0,name:m.name??null,email:k??null},()=>d(`Signed in${k?` as ${k}`:""}.`)),0}let i=x(e,"domain"),u=T(e,"sso")||i!==void 0;if(u&&x(e,"provider"))throw new g("--sso and --provider are two different doors: pass one or the other");let c=await En({provider:x(e,"provider"),sso:u,domain:i,noBrowser:T(e,"no-browser")||!me(),timeoutMs:s(),email:x(e,"email")});return h({email:c.email??null},()=>d(`Signed in${c.email?` as ${c.email}`:""}.`)),0}case"logout":{Os();let i=!!process.env.SNOUTDATA_ACCESS_TOKEN;return h({ok:!0,environmentTokenStillSet:i},()=>{d("Signed out."),i&&(d("SNOUTDATA_ACCESS_TOKEN is still set in this shell, so commands will keep working."),d(w(" unset SNOUTDATA_ACCESS_TOKEN")))}),0}case"whoami":{let i=await nt(),u=on()?.expectedEmail,c=!!(u&&i.email&&!Mr(u,i.email));return c&&ee(`This session is ${i.email}, and login asked for ${u}. Run "snoutdata login --email ${u}" to sign in as that account.`),h({...i,...u?{expectedEmail:u,matchesExpected:!c}:{}},()=>{process.stdout.write(`${i.email??i.id}
847
+ `),i.token&&d(`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 Ma(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new g("tokens create needs --name (what is it for, and where does it live)");return await Da({name:i,expiresInDays:$e(e,"expires"),project:x(e,"project")}),0}case"revoke":{let i=r[0];if(!i)throw new g("tokens revoke needs the token's id or its sdt_ prefix");return await La(i),0}default:throw new g(`unknown command: tokens ${n}`)}case"projects":switch(n){case void 0:case"list":return await Oa(),0;case"create":{let i=x(e,"name")??r[0];if(!i)throw new g("projects create needs --name");let u=x(e,"team");return await Gr({teamId:u?await Ia(u):void 0,name:i,region:x(e,"region"),wait:!T(e,"no-wait"),timeoutMs:s(),link:void 0,showUrl:T(e,"show-url")}),0}case"pause":case"resume":case"delete":return await Na(n,x(e,"ref")??r[0]??a(),{wait:!T(e,"no-wait"),timeoutMs:s()}),0;case"show":{let i=x(e,"ref")??r[0]??a(),u=Le(i);u?ga(u):await Li(i)}return 0;default:throw new g(`unknown command: projects ${n}`)}case"functions":{let i=l();if(i){if(n==="deploy"){if(!r[0])throw new g("functions deploy needs a name: snoutdata functions deploy <name>");return await $a(i,r[0],{dir:x(e,"dir"),verifyJwt:!T(e,"no-verify-jwt")}),0}if(n===void 0||n==="list")return xa(i),0;if((n==="delete"||n==="remove")&&r[0])return await Ea(i,r[0]),0}switch(n){case"deploy":{let u=r[0];if(!u)throw new g("functions deploy needs a name: snoutdata functions deploy <name>");return await Ka(a(),u,{dir:x(e,"dir"),entrypoint:x(e,"entrypoint"),verifyJwt:!T(e,"no-verify-jwt")}),0}case void 0:case"list":return await Wa(a()),0;case"size":{let u=r[0];if(!u)throw new g("functions size needs a name: snoutdata functions size <name> --memory MB --concurrency N");return await za(a(),u,{memoryMb:$e(e,"memory"),concurrency:$e(e,"concurrency"),reset:T(e,"reset")}),0}case"logs":return await Va(a(),r[0],{errorsOnly:T(e,"errors"),limit:$e(e,"tail"),follow:T(e,"follow")}),0;case"delete":case"remove":{let u=r[0];if(!u)throw new g("functions delete needs a name");return await Ya(a(),u),0}default:throw new g(`unknown command: functions ${n}`)}}case"containers":switch(n){case"deploy":{let i=r[0],u=r[1],c=x(e,"dockerfile");if(i&&c){if(u)throw new g("containers deploy takes an image or --dockerfile, not both");if(x(e,"registry-user")||T(e,"clear-registry"))throw new g("an image built with --dockerfile is uploaded to the project and needs no registry login");return await ci(a(),i,{path:c,port:$e(e,"port"),wait:!T(e,"no-wait")}),0}if(!i||!u)throw new g("containers deploy needs a name and an image: snoutdata containers deploy <name> <image>, or <name> --dockerfile <dir>");let p=x(e,"registry-user");if(p&&!T(e,"stdin"))throw new g('a registry login takes its token on stdin: echo "$TOKEN" | snoutdata containers deploy <name> <image> --registry-user U --stdin');return await si(a(),i,{image:u,port:$e(e,"port"),registryUser:p,registryPassword:p?await Fn():void 0,clearCredential:T(e,"clear-registry"),wait:!T(e,"no-wait")}),0}case void 0:case"list":return await li(a()),0;case"logs":return await pi(a(),r[0],{errorsOnly:T(e,"errors"),limit:$e(e,"tail"),follow:T(e,"follow")}),0;case"restart":{if(!r[0])throw new g("containers restart needs a name");return await di(a(),r[0]),0}case"delete":case"remove":{if(!r[0])throw new g("containers delete needs a name");return await ui(a(),r[0]),0}case"egress":{let i=r[0];if(i===void 0||i==="list"||i==="show")return await Un(a(),{}),0;if(i==="clear")return await Un(a(),{clear:!0}),0;if(i==="set"){let u=r.slice(1);if(u.length===0&&!T(e,"none"))throw new g("containers egress set needs at least one destination (api.example.com:443), or --none for nothing outside the project");if(u.length>0&&T(e,"none"))throw new g("containers egress set takes destinations or --none, not both");return await Un(a(),{set:u}),0}throw new g(`unknown command: containers egress ${i}; it is egress, egress set or egress clear`)}default:throw new g(`unknown command: containers ${n}`)}case"secrets":{let i=l();if(i){if(n==="set")return await Aa(i,await gi(r,{stdin:T(e,"stdin")})),0;if(n===void 0||n==="list")return Ra(i),0;if((n==="unset"||n==="remove")&&r[0])return await Ta(i,r[0]),0}switch(n){case"set":return await hi(a(),r,{stdin:T(e,"stdin")}),0;case void 0:case"list":return await wi(a()),0;case"unset":case"remove":{let u=r[0];if(!u)throw new g("secrets unset needs a name");return await yi(a(),u),0}default:throw new g(`unknown command: secrets ${n}`)}}case"keys":switch(n){case void 0:case"show":case"list":return await Ua(a()),0;case"rotate":return await Fa(a(),{force:T(e,"force")}),0;default:throw new g(`unknown command: keys ${n}`)}case"products":{if(n===void 0||n==="list")return await Ci(a()),0;if(n==="enable"||n==="disable")return await ji(a(),Ni(r[0]),n==="enable"),0;throw new g(`unknown command: products ${n}. products [enable|disable ${Gn.join("|")}]`)}case"push":{if(n!=="credentials")throw new g("push credentials [set apns|fcm | remove apns|fcm]");let i=r[0];if(i===void 0||i==="list")return await yc(a()),0;if(i==="set"){let u=Oo(r[1]),c=u==="apns"?await gc({p8:x(e,"p8"),keyId:x(e,"key-id"),teamId:x(e,"team-id"),topic:x(e,"topic"),environment:x(e,"environment")}):await hc(x(e,"file"));return await wc(a(),u,c),0}if(i==="remove"||i==="unset")return await bc(a(),Oo(r[1])),0;throw new g(`unknown command: push credentials ${i}`)}case"auth":return await rc(a(),n,r,{clientId:x(e,"client-id"),stdin:T(e,"stdin"),siteUrl:x(e,"site-url"),allow:x(e,"allow"),file:x(e,"file"),subject:x(e,"subject")}),0;case"realtime":{if(n===void 0||n==="inspect")return Zi(a(),{channel:x(e,"channel"),watch:T(e,"watch")});if(n==="logs")return Qi(a(),{channel:x(e,"channel"),since:x(e,"since")});throw new g(`unknown command: realtime ${n}. realtime inspect|logs`)}case"domains":return await Ii(a(),n,r[0]),0;case"teams":return await ja(),0;case"link":{if(T(e,"local")){let c;try{c=Ds(n)}catch(m){throw new v("not-found",m instanceof Error?m.message:String(m))}let p=lt(process.cwd(),{ref:c.ref,...c.name?{name:c.name}:{},local:c.folder});return h({ref:c.ref,local:c.folder,api:c.apiUrl,path:p},()=>{d(`Linked ${c.name??c.ref}, the local project in ${c.folder} (${p}).`),d(`Its API is ${c.apiUrl}; its database is on 127.0.0.1:${c.dbPort}.`)}),0}let i=x(e,"ref")??n;if(!i)throw new g("link needs --ref");let u=lt(process.cwd(),{ref:i});return h({ref:i,path:u},()=>d(`Linked ${i} (${u}).`)),0}case"db":switch(n){case"url":return await vo(a()),0;case"psql":return Ki(a(),r);case"reset-password":return await Ca(a()),0;case"access":return await Vi(a(),r,{level:x(e,"level")}),0;case"push":return mc(a(),{dir:x(e,"dir"),dryRun:!!e.flags["dry-run"],outOfOrder:!!e.flags["out-of-order"]});case"restore":return T(e,"window")?(await Di(a()),0):x(e,"at")?(await Mi(a(),x(e,"at"),x(e,"name")),0):Wi(a(),{file:x(e,"file"),force:!!e.flags.force});case"export":return zi(a(),{timeoutMs:s(),out:x(e,"out"),statusOnly:!!e.flags.status});default:throw new g(n?`unknown command: db ${n}`:"db needs an action: url, psql, reset-password, access, export, push, restore")}case"gen":{if(n!=="types")throw new g(n?`unknown command: gen ${n}`:"gen needs an action: types");let i=T(e,"local")?await xd(await nn()):null,u=i?i.dbUrl:x(e,"db-url");return vc(r[0],{ref:u?void 0:a(),dbUrl:u,schemas:(x(e,"schema")??"public").split(",").map(c=>c.trim()).filter(Boolean),out:x(e,"out"),sql:i?.sql})}case"start":{let i=l();if(i)return ma(i)}return kd(await nn(),{port:$e(e,"port"),dir:x(e,"dir"),noMigrations:T(e,"no-migrations"),outOfOrder:T(e,"out-of-order")});case"stop":{let i=l();return i?fa(i):vd(await nn())}case"status":{let i=l();return i?pa(i):$d(await nn())}case"usage":return Td(a(),{days:$e(e,"days"),history:!!e.flags.history});case"mcp":return Ad({version:le,allowDelete:!!e.flags["allow-delete"]});case"init":return ew(e);case"upgrade":return Ud({check:T(e,"check")});default:throw new g(`unknown command: ${t}`)}}async function ew(e){let t=js({flag:x(e,"ref")});if(t.kind!=="create"){let o=t.ref;if(t.kind==="linked"?d(`This folder is already linked to ${o}.`):t.kind==="environment"&&d(`Using ${o}, from SNOUTDATA_PROJECT. This folder is not linked to it.`),T(e,"env")?await Bd(o):await vo(o),t.kind==="link"){let s=Le(o),a=lt(process.cwd(),s?{ref:o,...s.name?{name:s.name}:{},local:s.folder}:{ref:o});d(`Linked this folder to ${o}${t.replaces?`, in place of ${t.replaces}`:""} (${a}).`)}return 0}let n=x(e,"name")??tw(process.cwd());d(`Creating a project called "${n}"\u2026`),await Gr({name:n,region:x(e,"region"),wait:!0,link:process.cwd(),showUrl:!T(e,"env")});let r=sn({cwd:process.cwd()});return r&&T(e,"env")&&await Bd(r),0}async function Bd(e){let{connection:t}=await Promise.resolve().then(()=>(Q(),Ne)),n=await t(e),r=Ji(process.cwd(),n.uri);d(`${r.replaced?"Updated":"Wrote"} DATABASE_URL in ${r.path}.`),r.ignored&&d("Added .env to .gitignore, since it holds the password.")}function tw(e){let t=e.split(/[\\/]/).filter(Boolean);return t[t.length-1]??"project"}function nw(e){if(e instanceof v)return Ge(e.code,e.message,e.details),e.exitCode;if(e instanceof g)return Ge("usage",e.message),Ie.usage;if(e instanceof Ke)return Ge("not-signed-in",e.message),Ie["not-signed-in"];if(e instanceof Se){let r=et(e.status,e.serverCode);return Ge(r,e.message,{status:e.status,...e.serverCode?{serverCode:e.serverCode}:{}}),Ie[r]}let t=Us(e);if(t)return Ge("usage",t),Ie.usage;let n=ln(e);return Ge(n,e instanceof Error?e.message:String(e)),Ie[n]}function Ss(e){process.exitCode=e,setTimeout(()=>process.exit(e),2e3).unref()}function rw(e){let t=e.indexOf("--");return(t===-1?e:e.slice(0,t)).includes("--json")}async function ow(){Tt(rw(process.argv.slice(2)));let e;try{e=vs(process.argv.slice(2))}catch(t){Ge("usage",String(t instanceof Error?t.message:t)),Ss(Ie.usage);return}Tt(e.json),Qs(e.flags.quiet===!0),Fs(dn({isTty:!!process.stdin.isTTY,json:e.json,env:process.env}));try{Ss(await Qy(e))}catch(t){Ss(nw(t))}}ow();